traceeval-cli 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/PKG-INFO +45 -5
  2. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/README.md +42 -4
  3. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/pyproject.toml +4 -2
  4. traceeval_cli-0.2.0/scripts/generate_otel_fixtures.py +119 -0
  5. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/src/traceeval/cli.py +30 -13
  6. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/src/traceeval/core/config.py +28 -19
  7. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/src/traceeval/core/schema.py +11 -4
  8. traceeval_cli-0.2.0/src/traceeval/loaders/otel.py +215 -0
  9. traceeval_cli-0.2.0/src/traceeval/loaders/otel_attributes.py +20 -0
  10. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/src/traceeval/metrics/trajectory_judge.py +120 -51
  11. traceeval_cli-0.2.0/src/traceeval/pricing.py +62 -0
  12. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/src/traceeval/reporting/console.py +6 -0
  13. traceeval_cli-0.2.0/tests/fixtures/otel/refund_happy.json +344 -0
  14. traceeval_cli-0.2.0/tests/fixtures/otel/refund_no_args.json +320 -0
  15. traceeval_cli-0.2.0/tests/test_cli.py +115 -0
  16. traceeval_cli-0.2.0/tests/test_config.py +50 -0
  17. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/tests/test_loaders.py +27 -1
  18. traceeval_cli-0.2.0/tests/test_otel_loader.py +186 -0
  19. traceeval_cli-0.2.0/tests/test_pricing.py +53 -0
  20. traceeval_cli-0.2.0/tests/test_trajectory_judge.py +490 -0
  21. traceeval_cli-0.2.0/uv.lock +675 -0
  22. traceeval_cli-0.1.0/tests/test_trajectory_judge.py +0 -265
  23. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/.github/workflows/ci.yml +0 -0
  24. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/.github/workflows/publish.yml +0 -0
  25. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/.gitignore +0 -0
  26. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/LICENSE +0 -0
  27. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/examples/reference_agent.py +0 -0
  28. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/sample_data/case_01.json +0 -0
  29. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/sample_data/trace_01.json +0 -0
  30. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/scripts/batch_runner.py +0 -0
  31. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/scripts/generate_dataset.py +0 -0
  32. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/src/traceeval/__init__.py +0 -0
  33. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/src/traceeval/core/__init__.py +0 -0
  34. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/src/traceeval/core/logger.py +0 -0
  35. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/src/traceeval/loaders/__init__.py +0 -0
  36. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/src/traceeval/loaders/file.py +0 -0
  37. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/src/traceeval/loaders/live.py +0 -0
  38. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/src/traceeval/metrics/__init__.py +0 -0
  39. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/src/traceeval/reporting/__init__.py +0 -0
  40. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/src/traceeval/reporting/export.py +0 -0
  41. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/test_suite/golden_001_happy.json +0 -0
  42. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/test_suite/golden_002_dow.json +0 -0
  43. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/test_suite/golden_003_bypass.json +0 -0
  44. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/test_suite/golden_004_semantic.json +0 -0
  45. {traceeval_cli-0.1.0 → traceeval_cli-0.2.0}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: traceeval-cli
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: CI/CD and Evaluation Infrastructure for Autonomous Agents (Agentic Engineering)
5
5
  Project-URL: Repository, https://github.com/tej007-awesome/TraceEval
6
6
  Project-URL: Issues, https://github.com/tej007-awesome/TraceEval/issues
@@ -19,6 +19,8 @@ Requires-Dist: pydantic>=2.0.0
19
19
  Requires-Dist: rich>=13.7.0
20
20
  Requires-Dist: typer>=0.9.0
21
21
  Provides-Extra: dev
22
+ Requires-Dist: opentelemetry-exporter-otlp-proto-common>=1.20.0; extra == 'dev'
23
+ Requires-Dist: opentelemetry-sdk>=1.20.0; extra == 'dev'
22
24
  Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
23
25
  Requires-Dist: pytest>=8.0.0; extra == 'dev'
24
26
  Requires-Dist: ruff==0.11.2; extra == 'dev'
@@ -96,6 +98,11 @@ Perfect for pre-deployment CI/CD gating. Dynamically spawns your agent, captures
96
98
  ```bash
97
99
  traceeval run --case sample_data/case_01.json --pipeline examples.reference_agent:process_refund_success --export report.json
98
100
  ```
101
+ **Mode C: Evaluate an OpenTelemetry (OTel) Trace**
102
+ Audit OpenTelemetry traces adhering to GenAI semantic conventions.
103
+ ```bash
104
+ traceeval run --case sample_data/case_01.json --otel-trace tests/fixtures/otel/refund_happy.json
105
+ ```
99
106
 
100
107
  *(Tip: Add `--verbose` right after `traceeval` to view detailed middleware logs!)*
101
108
 
@@ -140,13 +147,46 @@ TraceEval decouples the **Ingestion Layer** from the **Evaluation Engine** using
140
147
 
141
148
  ---
142
149
 
150
+ ## Evaluating OpenTelemetry Traces
151
+
152
+ TraceEval natively ingests OpenTelemetry (OTel) JSON traces adhering to the **OTel GenAI Semantic Conventions**.
153
+
154
+ ```bash
155
+ traceeval run --case sample_data/case_01.json --otel-trace tests/fixtures/otel/refund_happy.json
156
+ ```
157
+
158
+ ### Attributes Ingested
159
+ TraceEval inspects the following GenAI span attributes:
160
+ - `gen_ai.operation.name`: Identifies span types (`invoke_agent`, `chat`, `execute_tool`).
161
+ - `gen_ai.agent.name` & `gen_ai.conversation.id`: Extracts agent skills and session metadata.
162
+ - `gen_ai.request.model`, `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`: Tracks per-model token consumption.
163
+ - `gen_ai.tool.name` & `gen_ai.tool.call.arguments`: Maps tool trajectories and parameters.
164
+ - `gen_ai.output.messages`: Captures the agent's final text response.
165
+
166
+ ### Cost Computation & Custom Pricing
167
+ Session token costs are automatically computed by matching `gen_ai.request.model` and token counts against model pricing rates. If pricing is missing for an unknown model, the cost check fails (`"cost could not be verified"`) to prevent unmonitored financial risk.
168
+
169
+ You can supply custom pricing via `--pricing my_pricing.json`:
170
+ ```json
171
+ {
172
+ "my-custom-model": {
173
+ "input_usd_per_1k": 0.00015,
174
+ "output_usd_per_1k": 0.0006
175
+ }
176
+ }
177
+ ```
178
+
179
+ > **Privacy Note:** Standard OTel instrumentation often redacts `gen_ai.tool.call.arguments` and `gen_ai.output.messages` to comply with privacy policies. When arguments are omitted, TraceEval outputs non-blocking warnings, and argument matching defaults to `{}`.
180
+
181
+ ---
182
+
143
183
  ## Roadmap
144
184
 
145
- v0 ships the core EDD Schema, Trajectory Validator, BYOJ Engine, and Live Pipeline Hook. Planned for v1:
185
+ v0.2 adds OpenTelemetry GenAI trace ingestion with computed cost and specific failure reasons. Planned next:
146
186
 
147
- - **OpenTelemetry trace ingestion:** Adapters to ingest native OTel spans from LangGraph, OpenAI Swarm, Claude SDK, and raw MCP servers so you can point TraceEval at real production traces without converting them by hand.
148
- - **Live budget guard:** Real-time token-cost interception during agent execution, not just post-run checking.
149
- - **Offline mock judge mode:** Deterministic stub judge for CI pipelines that cannot call an external LLM (air-gapped environments, cost-sensitive PR checks).
187
+ - **Flexible argument matching and forbidden tools:** match tool arguments exactly, partially, or not at all (for privacy-redacted traces), and fail any trajectory that calls a denied tool.
188
+ - **More reliable LLM judge:** per-rubric-item verdicts, multiple samples, retries, and an offline mock judge for CI pipelines that can't call an external LLM.
189
+ - **Meta-evaluation benchmark:** a labelled set of good and bad traces that measures how accurately TraceEval itself catches failures.
150
190
 
151
191
  To track granular progress, see our [GitHub Issues](https://github.com/tej007-awesome/TraceEval/issues).
152
192
 
@@ -70,6 +70,11 @@ Perfect for pre-deployment CI/CD gating. Dynamically spawns your agent, captures
70
70
  ```bash
71
71
  traceeval run --case sample_data/case_01.json --pipeline examples.reference_agent:process_refund_success --export report.json
72
72
  ```
73
+ **Mode C: Evaluate an OpenTelemetry (OTel) Trace**
74
+ Audit OpenTelemetry traces adhering to GenAI semantic conventions.
75
+ ```bash
76
+ traceeval run --case sample_data/case_01.json --otel-trace tests/fixtures/otel/refund_happy.json
77
+ ```
73
78
 
74
79
  *(Tip: Add `--verbose` right after `traceeval` to view detailed middleware logs!)*
75
80
 
@@ -114,13 +119,46 @@ TraceEval decouples the **Ingestion Layer** from the **Evaluation Engine** using
114
119
 
115
120
  ---
116
121
 
122
+ ## Evaluating OpenTelemetry Traces
123
+
124
+ TraceEval natively ingests OpenTelemetry (OTel) JSON traces adhering to the **OTel GenAI Semantic Conventions**.
125
+
126
+ ```bash
127
+ traceeval run --case sample_data/case_01.json --otel-trace tests/fixtures/otel/refund_happy.json
128
+ ```
129
+
130
+ ### Attributes Ingested
131
+ TraceEval inspects the following GenAI span attributes:
132
+ - `gen_ai.operation.name`: Identifies span types (`invoke_agent`, `chat`, `execute_tool`).
133
+ - `gen_ai.agent.name` & `gen_ai.conversation.id`: Extracts agent skills and session metadata.
134
+ - `gen_ai.request.model`, `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`: Tracks per-model token consumption.
135
+ - `gen_ai.tool.name` & `gen_ai.tool.call.arguments`: Maps tool trajectories and parameters.
136
+ - `gen_ai.output.messages`: Captures the agent's final text response.
137
+
138
+ ### Cost Computation & Custom Pricing
139
+ Session token costs are automatically computed by matching `gen_ai.request.model` and token counts against model pricing rates. If pricing is missing for an unknown model, the cost check fails (`"cost could not be verified"`) to prevent unmonitored financial risk.
140
+
141
+ You can supply custom pricing via `--pricing my_pricing.json`:
142
+ ```json
143
+ {
144
+ "my-custom-model": {
145
+ "input_usd_per_1k": 0.00015,
146
+ "output_usd_per_1k": 0.0006
147
+ }
148
+ }
149
+ ```
150
+
151
+ > **Privacy Note:** Standard OTel instrumentation often redacts `gen_ai.tool.call.arguments` and `gen_ai.output.messages` to comply with privacy policies. When arguments are omitted, TraceEval outputs non-blocking warnings, and argument matching defaults to `{}`.
152
+
153
+ ---
154
+
117
155
  ## Roadmap
118
156
 
119
- v0 ships the core EDD Schema, Trajectory Validator, BYOJ Engine, and Live Pipeline Hook. Planned for v1:
157
+ v0.2 adds OpenTelemetry GenAI trace ingestion with computed cost and specific failure reasons. Planned next:
120
158
 
121
- - **OpenTelemetry trace ingestion:** Adapters to ingest native OTel spans from LangGraph, OpenAI Swarm, Claude SDK, and raw MCP servers so you can point TraceEval at real production traces without converting them by hand.
122
- - **Live budget guard:** Real-time token-cost interception during agent execution, not just post-run checking.
123
- - **Offline mock judge mode:** Deterministic stub judge for CI pipelines that cannot call an external LLM (air-gapped environments, cost-sensitive PR checks).
159
+ - **Flexible argument matching and forbidden tools:** match tool arguments exactly, partially, or not at all (for privacy-redacted traces), and fail any trajectory that calls a denied tool.
160
+ - **More reliable LLM judge:** per-rubric-item verdicts, multiple samples, retries, and an offline mock judge for CI pipelines that can't call an external LLM.
161
+ - **Meta-evaluation benchmark:** a labelled set of good and bad traces that measures how accurately TraceEval itself catches failures.
124
162
 
125
163
  To track granular progress, see our [GitHub Issues](https://github.com/tej007-awesome/TraceEval/issues).
126
164
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "traceeval-cli"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "CI/CD and Evaluation Infrastructure for Autonomous Agents (Agentic Engineering)"
9
9
  authors = [
10
10
  { name = "Tejas Rajesh", email = "tejasrajesh05@gmail.com" }
@@ -38,7 +38,9 @@ Issues = "https://github.com/tej007-awesome/TraceEval/issues"
38
38
  dev = [
39
39
  "pytest>=8.0.0",
40
40
  "pytest-asyncio>=0.23.0",
41
- "ruff==0.11.2"
41
+ "ruff==0.11.2",
42
+ "opentelemetry-sdk>=1.20.0",
43
+ "opentelemetry-exporter-otlp-proto-common>=1.20.0",
42
44
  ]
43
45
 
44
46
  [tool.ruff]
@@ -0,0 +1,119 @@
1
+ import json
2
+ import time
3
+ from pathlib import Path
4
+
5
+ from google.protobuf.json_format import MessageToDict
6
+ from opentelemetry.exporter.otlp.proto.common._internal.trace_encoder import encode_spans
7
+ from opentelemetry.sdk.trace import TracerProvider
8
+ from opentelemetry.sdk.trace.export import SimpleSpanProcessor
9
+ from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
10
+
11
+ FIXTURES_DIR = Path("tests/fixtures/otel")
12
+
13
+
14
+ def generate_trace_data(include_content: bool = True) -> dict:
15
+ provider = TracerProvider()
16
+ exporter = InMemorySpanExporter()
17
+ provider.add_span_processor(SimpleSpanProcessor(exporter))
18
+ tracer = provider.get_tracer("traceeval.reference_agent")
19
+
20
+ with tracer.start_as_current_span("invoke_agent refund-processor") as root:
21
+ root.set_attribute("gen_ai.operation.name", "invoke_agent")
22
+ root.set_attribute("gen_ai.agent.name", "refund-processor")
23
+ root.set_attribute("gen_ai.conversation.id", "sess_otel_001")
24
+ time.sleep(0.002)
25
+
26
+ # Step 1: LLM initial decision
27
+ with tracer.start_as_current_span("chat gpt-4o-mini") as chat1:
28
+ chat1.set_attribute("gen_ai.operation.name", "chat")
29
+ chat1.set_attribute("gen_ai.request.model", "gpt-4o-mini")
30
+ chat1.set_attribute("gen_ai.usage.input_tokens", 150)
31
+ chat1.set_attribute("gen_ai.usage.output_tokens", 30)
32
+ time.sleep(0.002)
33
+
34
+ # Step 2: Tool call 1 (lookup_order)
35
+ with tracer.start_as_current_span("execute_tool lookup_order") as tool1:
36
+ tool1.set_attribute("gen_ai.operation.name", "execute_tool")
37
+ tool1.set_attribute("gen_ai.tool.name", "lookup_order")
38
+ tool1.set_attribute("gen_ai.tool.call.id", "call_lookup_001")
39
+ if include_content:
40
+ tool1.set_attribute(
41
+ "gen_ai.tool.call.arguments", json.dumps({"order_id": "4521"})
42
+ )
43
+ time.sleep(0.002)
44
+
45
+ # Step 3: LLM reasoning after lookup
46
+ with tracer.start_as_current_span("chat gpt-4o-mini") as chat2:
47
+ chat2.set_attribute("gen_ai.operation.name", "chat")
48
+ chat2.set_attribute("gen_ai.request.model", "gpt-4o-mini")
49
+ chat2.set_attribute("gen_ai.usage.input_tokens", 220)
50
+ chat2.set_attribute("gen_ai.usage.output_tokens", 35)
51
+ time.sleep(0.002)
52
+
53
+ # Step 4: Tool call 2 (check_duplicate_charge)
54
+ with tracer.start_as_current_span("execute_tool check_duplicate_charge") as tool2:
55
+ tool2.set_attribute("gen_ai.operation.name", "execute_tool")
56
+ tool2.set_attribute("gen_ai.tool.name", "check_duplicate_charge")
57
+ tool2.set_attribute("gen_ai.tool.call.id", "call_check_dup_002")
58
+ if include_content:
59
+ tool2.set_attribute(
60
+ "gen_ai.tool.call.arguments", json.dumps({"order_id": "4521"})
61
+ )
62
+ time.sleep(0.002)
63
+
64
+ # Step 5: LLM reasoning after check
65
+ with tracer.start_as_current_span("chat gpt-4o-mini") as chat3:
66
+ chat3.set_attribute("gen_ai.operation.name", "chat")
67
+ chat3.set_attribute("gen_ai.request.model", "gpt-4o-mini")
68
+ chat3.set_attribute("gen_ai.usage.input_tokens", 310)
69
+ chat3.set_attribute("gen_ai.usage.output_tokens", 40)
70
+ time.sleep(0.002)
71
+
72
+ # Step 6: Tool call 3 (issue_refund)
73
+ with tracer.start_as_current_span("execute_tool issue_refund") as tool3:
74
+ tool3.set_attribute("gen_ai.operation.name", "execute_tool")
75
+ tool3.set_attribute("gen_ai.tool.name", "issue_refund")
76
+ tool3.set_attribute("gen_ai.tool.call.id", "call_refund_003")
77
+ if include_content:
78
+ tool3.set_attribute(
79
+ "gen_ai.tool.call.arguments",
80
+ json.dumps({"order_id": "4521", "amount": "full"}),
81
+ )
82
+ time.sleep(0.002)
83
+
84
+ # Step 7: Final response chat
85
+ with tracer.start_as_current_span("chat gpt-4o-mini") as chat4:
86
+ chat4.set_attribute("gen_ai.operation.name", "chat")
87
+ chat4.set_attribute("gen_ai.request.model", "gpt-4o-mini")
88
+ chat4.set_attribute("gen_ai.usage.input_tokens", 420)
89
+ chat4.set_attribute("gen_ai.usage.output_tokens", 55)
90
+ if include_content:
91
+ chat4.set_attribute(
92
+ "gen_ai.output.messages",
93
+ "I have verified the duplicate charge for order #4521. A full refund has been issued.",
94
+ )
95
+ time.sleep(0.002)
96
+
97
+ spans = sorted(exporter.get_finished_spans(), key=lambda s: s.start_time)
98
+ pb_request = encode_spans(spans)
99
+ return MessageToDict(pb_request)
100
+
101
+
102
+ def generate_fixtures():
103
+ FIXTURES_DIR.mkdir(parents=True, exist_ok=True)
104
+
105
+ happy_data = generate_trace_data(include_content=True)
106
+ happy_path = FIXTURES_DIR / "refund_happy.json"
107
+ with open(happy_path, "w", encoding="utf-8") as f:
108
+ json.dump(happy_data, f, indent=2)
109
+ print(f"Generated {happy_path}")
110
+
111
+ no_args_data = generate_trace_data(include_content=False)
112
+ no_args_path = FIXTURES_DIR / "refund_no_args.json"
113
+ with open(no_args_path, "w", encoding="utf-8") as f:
114
+ json.dump(no_args_data, f, indent=2)
115
+ print(f"Generated {no_args_path}")
116
+
117
+
118
+ if __name__ == "__main__":
119
+ generate_fixtures()
@@ -1,3 +1,4 @@
1
+ import os
1
2
  import sys
2
3
  import asyncio
3
4
  import typer
@@ -41,35 +42,51 @@ def main(
41
42
  def run(
42
43
  case_file: Path = typer.Option(..., "--case", "-c", help="Path to EDDTestCase JSON"),
43
44
  trace_file: Optional[Path] = typer.Option(None, "--trace", "-t", help="Path to static AgentTrace JSON"),
45
+ otel_trace: Optional[Path] = typer.Option(None, "--otel-trace", help="Path to OpenTelemetry trace JSON"),
44
46
  pipeline: Optional[str] = typer.Option(None, "--pipeline", "-p", help="Live agent function (e.g. 'examples.reference_agent:process_refund')"),
47
+ pricing: Optional[Path] = typer.Option(None, "--pricing", help="Path to custom JSON pricing file"),
45
48
  export_path: Optional[str] = typer.Option(None, "--export", "-e", help="Path to save the JSON EvaluationResult"),
46
49
  max_cost: float = typer.Option(0.10, "--max-cost", help="Maximum allowable session budget in USD"),
47
- score_threshold: float = typer.Option(0.8, "--score-threshold", help="Minimum score threshold for intent/correctness"),
50
+ score_threshold: float = typer.Option(0.8, "--score-threshold", help="Minimum score (0-1) that every non-null judge dimension must meet"),
48
51
  ):
49
- """Run a TraceEval evaluation against a static trace or a live agent pipeline."""
50
- import os
51
- if "OPENAI_API_KEY" not in os.environ and settings.llm_base_url is None:
52
+ """Run a TraceEval evaluation against a static trace, an OTel trace, or a live agent pipeline."""
53
+ has_api_key = bool(settings.llm_api_key or os.environ.get("OPENAI_API_KEY"))
54
+ has_base_url = bool(settings.llm_base_url)
55
+ if not (has_api_key or has_base_url):
52
56
  console.print("\n[bold red]Configuration Error:[/bold red]")
53
57
  console.print("Either LLM_API_KEY (or OPENAI_API_KEY) or LLM_BASE_URL must be configured.")
54
58
  raise typer.Exit(code=1)
55
59
 
56
- if not trace_file and not pipeline:
57
- console.print("[bold red]Error:[/bold red] You must provide either a static --trace file or a live --pipeline hook.")
60
+ provided = [x for x in [trace_file, otel_trace, pipeline] if x is not None]
61
+ if len(provided) != 1:
62
+ console.print(
63
+ "[bold red]Error:[/bold red] Exactly one of --trace, --otel-trace, or --pipeline must be provided."
64
+ )
58
65
  raise typer.Exit(code=1)
59
-
66
+
60
67
  try:
61
68
  case = load_test_case(case_file)
62
-
63
- # Determine Execution Mode (Static vs Live)
69
+
70
+ # Determine Execution Mode (Static vs OTel vs Live)
64
71
  if pipeline:
65
72
  console.print(f"[dim]Mode: Live Pipeline execution ({pipeline})[/dim]")
66
73
  trace = run_live_pipeline(pipeline, case)
67
74
  elif trace_file:
68
75
  console.print(f"[dim]Mode: Static Batch execution ({trace_file})[/dim]")
69
76
  trace = load_trace(trace_file)
77
+ elif otel_trace:
78
+ console.print(f"[dim]Mode: OTel trace ({otel_trace})[/dim]")
79
+ from traceeval.loaders.otel import load_otel_trace
80
+ from traceeval.pricing import DEFAULT_PRICING, load_pricing
81
+
82
+ pricing_table = load_pricing(pricing) if pricing else DEFAULT_PRICING
83
+ otel_result = load_otel_trace(otel_trace, pricing=pricing_table)
84
+ for warning in otel_result.warnings:
85
+ console.print(f"[yellow]Warning:[/yellow] {warning}")
86
+ trace = otel_result.trace
70
87
  else:
71
- raise ValueError("No trace file or pipeline provided.")
72
-
88
+ raise ValueError("No valid trace or pipeline provided.")
89
+
73
90
  except Exception as e:
74
91
  console.print(f"[bold red]Ingestion Error:[/bold red] {e}")
75
92
  raise typer.Exit(code=1)
@@ -83,12 +100,12 @@ def run(
83
100
 
84
101
  # Render Terminal Output
85
102
  render_result(result)
86
-
103
+
87
104
  # Handle Export
88
105
  if export_path:
89
106
  export_to_json(result, export_path)
90
107
  console.print(f"\n[dim]Report successfully exported to {export_path}[/dim]")
91
-
108
+
92
109
  if not result.passed:
93
110
  raise typer.Exit(code=1)
94
111
 
@@ -1,41 +1,50 @@
1
1
  import os
2
2
  from typing import Optional
3
- from pydantic_settings import BaseSettings, SettingsConfigDict
3
+
4
+ from openai import AsyncOpenAI
4
5
  from pydantic import Field, SecretStr
6
+ from pydantic_settings import BaseSettings, SettingsConfigDict
7
+
5
8
 
6
9
  class Settings(BaseSettings):
7
- """
8
- Defensive configuration manager.
10
+ """Defensive configuration manager.
11
+
9
12
  Loads from .env and guarantees required keys are present at startup.
10
13
  """
14
+
11
15
  llm_api_key: Optional[SecretStr] = Field(
12
- default=None,
16
+ default=None,
13
17
  alias="LLM_API_KEY",
14
- description="API Key for the Bring-Your-Own-Judge LLM provider"
18
+ description="API Key for the Bring-Your-Own-Judge LLM provider",
15
19
  )
16
20
  llm_base_url: Optional[str] = Field(
17
- default=None,
21
+ default=None,
18
22
  alias="LLM_BASE_URL",
19
- description="Custom base URL for the LLM provider (e.g. Ollama, vLLM)"
23
+ description="Custom base URL for the LLM provider (e.g. Ollama, vLLM)",
20
24
  )
21
25
  llm_model_name: str = Field(
22
- default="gpt-4o-mini",
26
+ default="gpt-4o-mini",
23
27
  alias="LLM_MODEL_NAME",
24
- description="Model name to target for the judge evaluations"
28
+ description="Model name to target for the judge evaluations",
25
29
  )
26
30
 
27
- model_config = SettingsConfigDict(
28
- env_file=".env",
29
- env_file_encoding="utf-8",
30
- extra="ignore"
31
- )
31
+ model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")
32
+
32
33
 
33
34
  # Instantiate the settings.
34
35
  settings = Settings()
35
36
 
36
- # Inject into environment variables for OpenAI SDK
37
- if settings.llm_api_key is not None:
38
- os.environ["OPENAI_API_KEY"] = settings.llm_api_key.get_secret_value()
39
37
 
40
- if settings.llm_base_url is not None:
41
- os.environ["OPENAI_BASE_URL"] = settings.llm_base_url
38
+ def get_judge_client() -> AsyncOpenAI:
39
+ """Return an AsyncOpenAI client configured explicitly from settings or environment."""
40
+ api_key = (
41
+ settings.llm_api_key.get_secret_value()
42
+ if settings.llm_api_key
43
+ else os.environ.get("OPENAI_API_KEY")
44
+ )
45
+ base_url = settings.llm_base_url
46
+
47
+ if not api_key and base_url:
48
+ api_key = "not-needed"
49
+
50
+ return AsyncOpenAI(api_key=api_key, base_url=base_url)
@@ -5,9 +5,14 @@ from enum import Enum
5
5
 
6
6
  class TrajectoryMode(str, Enum):
7
7
  """How strict the agent's tool execution path must be evaluated."""
8
- EXACT = "EXACT"
9
- IN_ORDER = "IN_ORDER"
10
- ANY_ORDER = "ANY_ORDER"
8
+ EXACT = "EXACT"
9
+ IN_ORDER = "IN_ORDER"
10
+ ANY_ORDER = "ANY_ORDER"
11
+
12
+ class CheckResult(BaseModel):
13
+ """Outcome of a single deterministic gate, with human-readable reasons on failure."""
14
+ passed: bool
15
+ reasons: List[str] = []
11
16
 
12
17
  class GoldenRecord(BaseModel):
13
18
  meta_id: str
@@ -47,6 +52,7 @@ class AgentTrace(BaseModel):
47
52
  executed_tools: List[ToolCall]
48
53
  final_output: str
49
54
  total_token_cost_usd: float = Field(ge=0.0)
55
+ cost_complete: bool = True
50
56
 
51
57
  class EvaluationDimensionScore(BaseModel):
52
58
  """Scores mapped directly to the 5 dimensions of Vibe Coding Evaluation."""
@@ -62,4 +68,5 @@ class EvaluationResult(BaseModel):
62
68
  case_id: str
63
69
  passed: bool
64
70
  scores: EvaluationDimensionScore
65
- trace_summary: AgentTrace
71
+ trace_summary: AgentTrace
72
+ failures: List[str] = []