Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
8cc131b
feat: update analyze CLI to use typed analyzer framework
ryan-arman Apr 14, 2026
2377011
test: add tests for quality analyzer, turn stats analyzer, and test e…
ryan-arman Apr 14, 2026
a01156c
docs: update analyze docs and config for typed analyzer framework
ryan-arman Apr 14, 2026
cabb863
refactor: remove deduplication, llm_analyzer, and llm_criteria
ryan-arman Apr 14, 2026
d6759aa
revert: restore length.py from main, fix tests to match
ryan-arman Apr 14, 2026
9a5876b
revert: restore quality.py from main, rewrite tests to match
ryan-arman Apr 14, 2026
6f11088
revert: restore turn_stats.py from main, rewrite tests to match
ryan-arman Apr 14, 2026
f27a237
fix: properly restore quality.py from main, update tests and docs
ryan-arman Apr 14, 2026
bae5037
fix: remove stale params from example analyze config
ryan-arman Apr 14, 2026
6db1c4a
fix: restore analyzers __init__.py exports to match main
ryan-arman Apr 14, 2026
8b867c8
feat: restore CLI flags and backward compat for analyze v2
ryan-arman Apr 14, 2026
a254dce
fix: update docs key capabilities to match actual quality checks
ryan-arman Apr 15, 2026
b71dcb7
docs: use --config in CLI examples, mention -c shorthand
ryan-arman Apr 15, 2026
5894848
feat: remove custom metrics feature from analyze framework
ryan-arman Apr 15, 2026
e357230
fix: reject duplicate analyzer names in AnalysisPipeline constructor
ryan-arman Apr 15, 2026
2087823
fix: preserve original indices in test engine when None values filtered
ryan-arman Apr 15, 2026
8195671
fix: populate failure_reasons for max_percentage failures in percenta…
ryan-arman Apr 15, 2026
5f2774d
fix: walk MRO in _get_result_type for analyzer subclass support
ryan-arman Apr 15, 2026
e5bb1b3
fix: restore BatchTestEngine and core TestType in testing exports
ryan-arman Apr 15, 2026
acac138
fix: restore all_affected_indices field on TestResult
ryan-arman Apr 15, 2026
caad7d5
fix: convert TypeError to ValueError for unknown YAML config fields
ryan-arman Apr 15, 2026
7a5660b
fix: handle raw dicts from cache in dataframe conversion
ryan-arman Apr 15, 2026
c43d02e
fix: use core registry in discovery instead of CLI module
ryan-arman Apr 15, 2026
885b7ce
fix: resolve pyright errors and empty-except code quality findings
ryan-arman Apr 15, 2026
998d9fb
Merge branch 'main' into ryan-arman/analyzer-cli-update
ryan-arman Apr 15, 2026
6322d9d
refactor: consolidate analyze/cli.py into cli/analyze.py
ryan-arman Apr 15, 2026
31d8e5d
fix: unify duplicate logging imports in cli/analyze.py
ryan-arman Apr 15, 2026
46443a8
fix: exclude v2 analyze config from old config parse test
ryan-arman Apr 15, 2026
08bdf8a
revert: restore analyzer test files to match main
ryan-arman Apr 15, 2026
ba33410
fix: clean up TestEngine — remove percentage/range, restore helpers, …
ryan-arman Apr 15, 2026
8e11f55
fix: remove percentage/range references from config, yaml, and docs
ryan-arman Apr 15, 2026
e4e55ea
style: use --config instead of -c in analyze.yaml usage comment
ryan-arman Apr 15, 2026
401c816
update
ryan-arman Apr 15, 2026
1a052d2
docs: clarify --output CLI flag vs output_path config field
ryan-arman Apr 15, 2026
a3a8c42
style: remove unnecessary comments in testing __init__.py
ryan-arman Apr 15, 2026
076019a
refactor: merge TestConfigYAML into TestConfig
ryan-arman Apr 15, 2026
ef56549
refactor: replace TestConfig with TestParams from core for API consis…
ryan-arman Apr 15, 2026
357bdb4
fix: raise TypeError on unexpected types in TestEngine._get_nested_value
ryan-arman Apr 15, 2026
4486a91
style: remove AI slop and fix analyze.md docs
ryan-arman Apr 16, 2026
e5ca56c
refactor: drop heuristic field-mapping in analyze CLI dataset loader
ryan-arman Apr 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion configs/examples/analyze/analyze.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ dataset_path: data/dataset_examples/oumi_format.jsonl
output_path: ./analysis_output

analyzers:
- id: length
- type: length
display_name: Length
params:
# Tokenizer name - automatically detects tiktoken vs HuggingFace
tokenizer_name: cl100k_base # tiktoken encoding (GPT-4)
Expand Down
213 changes: 124 additions & 89 deletions src/oumi/analyze/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,125 +12,160 @@
# See the License for the specific language governing permissions and
# limitations under the License.

"""Analyzer framework for dataset analysis."""
"""Typed analyzer framework for dataset analysis.

from oumi.analyze.analyzers import (
DataQualityAnalyzer,
DataQualityMetrics,
LengthAnalyzer,
LengthAnalyzerConfig,
LengthMetrics,
TurnStatsAnalyzer,
TurnStatsMetrics,
This module provides a typed, Pydantic-based approach to analyzing datasets,
replacing the DataFrame-centric approach with typed Conversation objects
and strongly-typed result models.

Example usage:

from oumi.analyze import LengthAnalyzer, AnalysisPipeline

# Single conversation analysis
analyzer = LengthAnalyzer()
result = analyzer.analyze(conversation)
print(f"Total words: {result.total_words}")

# Batch processing with pipeline
pipeline = AnalysisPipeline(analyzers=[LengthAnalyzer()])
results = pipeline.run(conversations)

# Convert to DataFrame when needed
df = pipeline.to_dataframe()
"""

# Import analyzers and result models (co-located in analyzer files)
from oumi.analyze.analyzers.deduplication import (
DeduplicationAnalyzer,
DeduplicationResult,
DuplicateGroup,
)
from oumi.analyze.analyzers.length import LengthAnalyzer, LengthMetrics
from oumi.analyze.analyzers.llm_analyzer import (
CoherenceAnalyzer,
FactualityAnalyzer,
InstructionFollowingAnalyzer,
JudgmentType,
LLMAnalyzer,
LLMJudgmentMetrics,
SafetyAnalyzer,
TargetScope,
UsefulnessAnalyzer,
get_available_criteria,
get_criteria_info,
)
from oumi.analyze.analyzers.quality import DataQualityAnalyzer, DataQualityMetrics
from oumi.analyze.analyzers.turn_stats import TurnStatsAnalyzer, TurnStatsMetrics
from oumi.analyze.base import (
BaseAnalyzer,
ConversationAnalyzer,
DatasetAnalyzer,
MessageAnalyzer,
PreferenceAnalyzer,
)

# Import CLI utilities
from oumi.analyze.cli import (
create_analyzer_from_config,
generate_tests,
get_analyzer_class,
list_metrics,
print_summary,
run_from_config_file,
run_typed_analysis,
save_results,
)

# Import config
from oumi.analyze.config import (
AnalyzerConfig,
CustomMetricConfig,
TypedAnalyzeConfig,
)

# Import custom metrics
from oumi.analyze.custom_metrics import (
CustomConversationMetric,
CustomMessageMetric,
CustomMetricResult,
create_custom_metric,
)

# Import discovery utilities
from oumi.analyze.discovery import (
describe_analyzer,
generate_test_template,
get_analyzer_info,
get_instance_metrics,
list_available_metrics,
print_analyzer_metrics,
)
from oumi.analyze.pipeline import AnalysisPipeline

# Import testing
from oumi.analyze.testing import TestEngine, TestResult, TestSummary
from oumi.analyze.utils.dataframe import to_analysis_dataframe
from oumi.core.registry import (
REGISTRY,
)
from oumi.core.registry import (
register_sample_analyzer as register_analyzer,
)


def get_analyzer_class(name: str) -> type | None:
"""Get an analyzer class by name.

Args:
name: Name of the analyzer.

Returns:
The analyzer class or None if not found.
"""
from typing import cast

result = REGISTRY.get_sample_analyzer(name)
return cast(type | None, result)


def create_analyzer_from_config(
analyzer_id: str,
params: dict,
) -> "MessageAnalyzer | ConversationAnalyzer | DatasetAnalyzer | None":
"""Create an analyzer instance from configuration.

Prefers using the analyzer's from_config() classmethod if available,
otherwise falls back to direct instantiation with **params.

Args:
analyzer_id: Analyzer type identifier.
params: Analyzer-specific parameters.

Returns:
Analyzer instance or None if not found.
"""
import logging

logger = logging.getLogger(__name__)

analyzer_class = REGISTRY.get_sample_analyzer(analyzer_id)
if analyzer_class is None:
logger.warning(f"Unknown analyzer: {analyzer_id}")
return None

try:
# Prefer from_config() if available for better config handling
if hasattr(analyzer_class, "from_config") and callable(
getattr(analyzer_class, "from_config")
):
return analyzer_class.from_config(params) # type: ignore[union-attr]
else:
return analyzer_class(**params)
except Exception as e:
logger.error(f"Failed to create analyzer {analyzer_id}: {e}")
return None


__all__ = [
"AnalysisPipeline",
"AnalyzerConfig",
"BaseAnalyzer",
# Base classes
"MessageAnalyzer",
"ConversationAnalyzer",
"DataQualityAnalyzer",
"DataQualityMetrics",
"DatasetAnalyzer",
"PreferenceAnalyzer",
# Pipeline
"AnalysisPipeline",
# Utilities
"to_analysis_dataframe",
# Analyzers
"LengthAnalyzer",
"LengthAnalyzerConfig",
"TurnStatsAnalyzer",
"DataQualityAnalyzer",
"DeduplicationAnalyzer",
"LLMAnalyzer",
"UsefulnessAnalyzer",
"SafetyAnalyzer",
"FactualityAnalyzer",
"CoherenceAnalyzer",
"InstructionFollowingAnalyzer",
# Enums
"TargetScope",
"JudgmentType",
# Utilities
"get_available_criteria",
"get_criteria_info",
# Result models
"LengthMetrics",
"MessageAnalyzer",
"PreferenceAnalyzer",
"TurnStatsMetrics",
"DataQualityMetrics",
"DeduplicationResult",
"DuplicateGroup",
"LLMJudgmentMetrics",
# Config
"TypedAnalyzeConfig",
"AnalyzerConfig",
"CustomMetricConfig",
# Testing
"TestEngine",
"TestResult",
"TestSummary",
"TurnStatsAnalyzer",
"TurnStatsMetrics",
"TypedAnalyzeConfig",
"create_analyzer_from_config",
"describe_analyzer",
"get_analyzer_class",
"get_analyzer_info",
"get_instance_metrics",
# CLI utilities
"run_typed_analysis",
"run_from_config_file",
"save_results",
"print_summary",
"list_metrics",
"generate_tests",
# Custom metrics
"CustomConversationMetric",
"CustomMessageMetric",
"CustomMetricResult",
"create_custom_metric",
# Discovery utilities
"list_available_metrics",
"print_analyzer_metrics",
"register_analyzer",
"to_analysis_dataframe",
"get_analyzer_info",
"generate_test_template",
"describe_analyzer",
# Backward-compatible exports (used by API worker)
"create_analyzer_from_config",
"get_analyzer_class",
]
44 changes: 37 additions & 7 deletions src/oumi/analyze/analyzers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,52 @@
file contains both the analyzer class and its result model for better cohesion.
"""

from oumi.analyze.analyzers.length import (
LengthAnalyzer,
LengthAnalyzerConfig,
LengthMetrics,
Tokenizer,
from oumi.analyze.analyzers.deduplication import (
DeduplicationAnalyzer,
DeduplicationResult,
DuplicateGroup,
)
from oumi.analyze.analyzers.length import LengthAnalyzer, LengthMetrics
from oumi.analyze.analyzers.llm_analyzer import (
CoherenceAnalyzer,
FactualityAnalyzer,
InstructionFollowingAnalyzer,
JudgmentType,
LLMAnalyzer,
LLMJudgmentMetrics,
SafetyAnalyzer,
TargetScope,
UsefulnessAnalyzer,
get_available_criteria,
get_criteria_info,
)
from oumi.analyze.analyzers.quality import DataQualityAnalyzer, DataQualityMetrics
from oumi.analyze.analyzers.turn_stats import TurnStatsAnalyzer, TurnStatsMetrics

__all__ = [
# Non-LLM analyzers
"LengthAnalyzer",
"LengthAnalyzerConfig",
"LengthMetrics",
"Tokenizer",
"TurnStatsAnalyzer",
"TurnStatsMetrics",
"DataQualityAnalyzer",
"DataQualityMetrics",
# Dataset-level analyzers
"DeduplicationAnalyzer",
"DeduplicationResult",
"DuplicateGroup",
# LLM-based analyzers
"LLMAnalyzer",
"LLMJudgmentMetrics",
"UsefulnessAnalyzer",
"SafetyAnalyzer",
"FactualityAnalyzer",
"CoherenceAnalyzer",
"InstructionFollowingAnalyzer",
# Enums
"TargetScope",
"JudgmentType",
# Utilities
"get_available_criteria",
"get_criteria_info",
]
Loading
Loading