ciagent 0.5.0__tar.gz → 0.6.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 (56) hide show
  1. ciagent-0.6.0/.gitignore +43 -0
  2. {ciagent-0.5.0 → ciagent-0.6.0}/CHANGELOG.md +36 -0
  3. {ciagent-0.5.0 → ciagent-0.6.0}/LICENSE +2 -0
  4. ciagent-0.6.0/PKG-INFO +156 -0
  5. ciagent-0.6.0/README.md +113 -0
  6. {ciagent-0.5.0 → ciagent-0.6.0}/pyproject.toml +5 -5
  7. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/__init__.py +2 -0
  8. ciagent-0.6.0/src/agentci/_version.py +3 -0
  9. ciagent-0.6.0/src/agentci/adapters/__init__.py +5 -0
  10. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/adapters/base.py +2 -0
  11. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/adapters/generic.py +2 -0
  12. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/adapters/langgraph.py +12 -2
  13. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/adapters/openai_agents.py +17 -1
  14. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/assertions.py +2 -0
  15. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/baselines.py +2 -0
  16. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/capture.py +61 -3
  17. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/cli.py +433 -74
  18. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/config.py +2 -0
  19. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/cost.py +2 -0
  20. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/diff_engine.py +2 -0
  21. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/__init__.py +2 -0
  22. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/correctness.py +2 -0
  23. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/cost.py +2 -0
  24. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/cost_estimator.py +2 -0
  25. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/diff.py +2 -0
  26. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/judge.py +2 -0
  27. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/metrics.py +2 -0
  28. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/mock_runner.py +12 -4
  29. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/parallel.py +24 -1
  30. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/path.py +32 -2
  31. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/reporter.py +154 -28
  32. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/results.py +2 -0
  33. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/runner.py +2 -0
  34. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/span_assertions.py +2 -0
  35. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/exceptions.py +2 -0
  36. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/loader.py +2 -0
  37. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/mocks.py +3 -0
  38. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/models.py +2 -0
  39. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/pytest_plugin.py +3 -0
  40. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/report.py +2 -0
  41. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/runner.py +3 -0
  42. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/schema/__init__.py +2 -0
  43. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/schema/agentci_spec.schema.json +33 -1
  44. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/schema/generate_schema.py +2 -0
  45. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/schema/spec_models.py +10 -0
  46. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/templates/github_action.yml.j2 +2 -0
  47. ciagent-0.6.0/src/agentci/templates/report.html.j2 +194 -0
  48. ciagent-0.5.0/.gitignore +0 -24
  49. ciagent-0.5.0/PKG-INFO +0 -323
  50. ciagent-0.5.0/README.md +0 -283
  51. ciagent-0.5.0/src/agentci/_version.py +0 -1
  52. ciagent-0.5.0/src/agentci/adapters/__init__.py +0 -3
  53. ciagent-0.5.0/src/agentci/templates/agentci.yaml +0 -16
  54. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/py.typed +0 -0
  55. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/templates/__init__.py +0 -0
  56. {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/templates/pre_push_hook.sh.j2 +0 -0
@@ -0,0 +1,43 @@
1
+
2
+ # Python
3
+ __pycache__/
4
+ *.py[cod]
5
+ *$py.class
6
+ venv/
7
+ .venv/
8
+ env/
9
+ .env
10
+
11
+ # Distribution
12
+ dist/
13
+ build/
14
+ *.egg-info/
15
+
16
+ # IDEs
17
+ .idea/
18
+ .vscode/
19
+ *.swp
20
+ .DS_Store
21
+
22
+ # Testing cache
23
+ .pytest_cache/
24
+
25
+ # Agent CI
26
+ golden/
27
+ test_results/
28
+
29
+ # Internal planning & development
30
+ Plan_docs/
31
+ phase1.md
32
+ Vision.md
33
+ llms.txt
34
+ demo_ux/
35
+ scripts/
36
+ README_v2.md
37
+
38
+ # Local config
39
+ agentci.yaml
40
+ agentci_spec.yaml
41
+
42
+ # Claude context
43
+ .claude/
@@ -5,6 +5,42 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.6.0] - 2026-03-05
9
+
10
+ ### Added
11
+
12
+ #### `final_output` Auto-Capture
13
+ - `TraceContext._auto_extract_final_output()` called in `__exit__()` — automatically extracts the agent's answer from traces
14
+ - Extraction priority: LangGraph state messages > span `output_data` (string) > span `output_data` (dict with `content`/`message`/`text`/`output` keys) > last LLM call `output_text`
15
+ - Manual `trace.metadata["final_output"]` still takes precedence (no overwrite)
16
+ - LangGraph adapter: auto-sets `final_output` from last AI message in `parse_state()`
17
+ - OpenAI Agents adapter: auto-sets `final_output` from last span output in `on_trace_end()`
18
+
19
+ #### `agentci calibrate` Command
20
+ - Runs N sample queries against the live agent, measures actual metrics, shows Rich comparison table
21
+ - Updates spec budgets with headroom: +50% for LLM/tool calls, +100% for tokens/cost
22
+ - Flags: `--samples N` (default 2), `--dry-run`, `--yes`, `--spec PATH`
23
+
24
+ #### Strict Tool Sequence Assertions
25
+ - `PathSpec.expected_tool_sequence: Optional[list[str]]` — strict ordered tool call check
26
+ - Mismatch = WARN (soft warning) with position-level diff via `_format_sequence_diff()`
27
+
28
+ #### HTML Trace Report
29
+ - Self-contained `report.html.j2` Jinja2 template with dark theme
30
+ - Summary dashboard (pass/fail/warn counts, total cost)
31
+ - Per-query cards with status badges, answer preview, three-layer details
32
+ - Collapsible trace tree with JS toggle
33
+ - Available via `agentci test --format html --output report.html` or `agentci report -i results.json`
34
+
35
+ ### Changed
36
+ - `max_llm_calls` default in spec generator raised from 8 to 10 (better headroom for real agents)
37
+ - `max_llm_calls` fallback in mock runner raised from 3 to 10
38
+ - Calibrate command floor raised from 8 to 10
39
+ - `--format` choices in `test` and `eval` commands now include `html`
40
+ - `--output / -o` option added to `test` and `eval` commands for HTML file path
41
+ - `agentci report` command fully implemented (was stub) — converts JSON results to HTML
42
+ - 22 new tests added (570 total, up from 548 in v0.5.1)
43
+
8
44
  ## [0.5.0] - 2026-03-01
9
45
 
10
46
  ### Added
@@ -1,3 +1,5 @@
1
+ Copyright 2025-2026 The AgentCI Authors
2
+
1
3
  Apache License
2
4
  Version 2.0, January 2004
3
5
  http://www.apache.org/licenses/
ciagent-0.6.0/PKG-INFO ADDED
@@ -0,0 +1,156 @@
1
+ Metadata-Version: 2.4
2
+ Name: ciagent
3
+ Version: 0.6.0
4
+ Summary: Continuous Integration for AI Agents. Catch cost spikes and logic regressions before production.
5
+ Project-URL: Documentation, https://github.com/suniel12/AgentCI#readme
6
+ Project-URL: Repository, https://github.com/suniel12/AgentCI.git
7
+ Project-URL: Issues, https://github.com/suniel12/AgentCI/issues
8
+ License: Apache-2.0
9
+ License-File: LICENSE
10
+ Keywords: agents,ai,ci-cd,llm,regression,testing
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Framework :: Pytest
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Topic :: Software Development :: Testing
15
+ Requires-Python: >=3.10
16
+ Requires-Dist: click>=8.0
17
+ Requires-Dist: jinja2>=3.0
18
+ Requires-Dist: opentelemetry-api>=1.20
19
+ Requires-Dist: opentelemetry-sdk>=1.20
20
+ Requires-Dist: pydantic>=2.0
21
+ Requires-Dist: pytest>=7.0
22
+ Requires-Dist: python-dotenv>=0.19
23
+ Requires-Dist: pyyaml>=6.0
24
+ Requires-Dist: rich>=13.0
25
+ Provides-Extra: all
26
+ Requires-Dist: anthropic>=0.20; extra == 'all'
27
+ Requires-Dist: jsonschema>=4.0; extra == 'all'
28
+ Requires-Dist: langgraph>=0.1; extra == 'all'
29
+ Requires-Dist: openai>=1.0; extra == 'all'
30
+ Provides-Extra: anthropic
31
+ Requires-Dist: anthropic>=0.20; extra == 'anthropic'
32
+ Provides-Extra: dev
33
+ Requires-Dist: mypy; extra == 'dev'
34
+ Requires-Dist: pytest>=8.0; extra == 'dev'
35
+ Requires-Dist: ruff; extra == 'dev'
36
+ Provides-Extra: jsonschema
37
+ Requires-Dist: jsonschema>=4.0; extra == 'jsonschema'
38
+ Provides-Extra: langgraph
39
+ Requires-Dist: langgraph>=0.1; extra == 'langgraph'
40
+ Provides-Extra: openai
41
+ Requires-Dist: openai>=1.0; extra == 'openai'
42
+ Description-Content-Type: text/markdown
43
+
44
+ # AgentCI
45
+
46
+ **Pytest-native regression testing for AI agents.** Catch routing changes, tool call drift, and cost spikes before production.
47
+
48
+ [![PyPI](https://img.shields.io/pypi/v/ciagent)](https://pypi.org/project/ciagent/)
49
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
50
+ [![AGENTS.md](https://img.shields.io/badge/AGENTS.md-supported-blue)](AGENTS.md)
51
+
52
+ You changed a prompt. Your agent broke in production. Three days later, a user complained. You had no tests, no diff, no idea what went wrong.
53
+
54
+ Works with OpenAI, Anthropic, and LangGraph. Runs inside pytest.
55
+
56
+ ## Add to Your Project
57
+
58
+ ```bash
59
+ pip install ciagent
60
+ ```
61
+
62
+ Write your golden queries — what should your agent handle, and what should it refuse?
63
+
64
+ ```yaml
65
+ # agentci_spec.yaml
66
+ # runner: any function that takes a query string and returns a response
67
+ runner: my_app.agent:run_for_agentci
68
+ queries:
69
+ - query: "How do I install AgentCI?"
70
+ correctness:
71
+ any_expected_in_answer: ["pip install", "ciagent"]
72
+ path:
73
+ expected_tools: [retrieve_docs]
74
+ cost:
75
+ max_llm_calls: 8
76
+
77
+ - query: "What's the CEO's favorite restaurant?"
78
+ correctness:
79
+ not_in_answer: ["restaurant", "favorite"]
80
+ path:
81
+ expected_tools: [] # expect no tools called for out-of-scope queries
82
+ ```
83
+
84
+ Run:
85
+
86
+ ```bash
87
+ agentci test --mock # start here: zero-cost with synthetic traces
88
+ agentci test # run live against your real agent
89
+ ```
90
+
91
+ `agentci test` evaluates each query through 3 layers — correctness, path, and cost:
92
+
93
+ ```
94
+ ============================================================
95
+
96
+ Query: How do I install AgentCI?
97
+ Answer: To install AgentCI, you can use pip with the following command:
98
+ pip install ciagent. Make sure you have Python 3.10 or later.
99
+
100
+ ✅ CORRECTNESS: PASS
101
+ ✓ Found keywords: "pip install ciagent"
102
+ ✓ LLM judge passed (score: 5 ≥ 0.6)
103
+ 📈 PATH: PASS
104
+ ✓ Tool recall: 1.000 (expected: [retrieve_docs])
105
+ ✓ Tool precision: 0.500
106
+ ✓ No loops detected
107
+ 💰 COST: PASS
108
+ ✓ LLM calls: 8 ≤ max 8
109
+
110
+ ============================================================
111
+
112
+ Query: What Python version does AgentCI require and what frameworks does it support?
113
+ Answer: AgentCI currently does not specify a required Python version
114
+ in the provided context, so I don't have that information...
115
+
116
+ ❌ CORRECTNESS: FAIL
117
+ • Expected '3.10' not found in answer
118
+ 📈 PATH: PASS
119
+ ✓ Tool recall: 1.000 (expected: [retrieve_docs])
120
+ ✓ Loops: 1 ≤ max 3
121
+ 💰 COST: PASS
122
+ ✓ LLM calls: 4 ≤ max 5
123
+
124
+ ============================================================
125
+ ```
126
+
127
+ Don't have golden queries yet? `agentci init --generate` scans your code and generates a starter spec.
128
+
129
+ ## Demo
130
+
131
+ Here's a RAG agent demo where someone "optimizes for latency" by reducing retriever docs from 8 to 1. AgentCI catches the correctness regression:
132
+
133
+ ![AgentCI Demo](demo/agentci-rag-demo.gif)
134
+
135
+ ## CLI
136
+
137
+ ```bash
138
+ agentci init --generate # Scan project, generate test spec
139
+ agentci init # Generate GitHub Actions workflow + pre-push hook
140
+ agentci test --mock --yes # Zero-cost synthetic traces, CI-friendly (no keys, no prompts)
141
+ agentci test # Run 3-layer evaluation (correctness → path → cost)
142
+ agentci test --format html -o report.html # HTML report with per-query details
143
+ agentci calibrate # Measure real agent metrics, auto-tune spec budgets
144
+ agentci doctor # Health check: spec, deps, API keys
145
+ agentci record <test> # Record golden baseline
146
+ agentci diff # Diff against baseline
147
+ agentci report -i results.json # Generate HTML report from JSON results
148
+ ```
149
+ ## Contributing
150
+
151
+ [GitHub Issues](https://github.com/suniel12/AgentCI/issues)
152
+ [DemoAgents](https://github.com/suniel12/DemoAgents) — working examples for all three frameworks
153
+
154
+ Apache 2.0. If you build an agent and test it with AgentCI, I'd love to hear about it.
155
+
156
+ ---
@@ -0,0 +1,113 @@
1
+ # AgentCI
2
+
3
+ **Pytest-native regression testing for AI agents.** Catch routing changes, tool call drift, and cost spikes before production.
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/ciagent)](https://pypi.org/project/ciagent/)
6
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
7
+ [![AGENTS.md](https://img.shields.io/badge/AGENTS.md-supported-blue)](AGENTS.md)
8
+
9
+ You changed a prompt. Your agent broke in production. Three days later, a user complained. You had no tests, no diff, no idea what went wrong.
10
+
11
+ Works with OpenAI, Anthropic, and LangGraph. Runs inside pytest.
12
+
13
+ ## Add to Your Project
14
+
15
+ ```bash
16
+ pip install ciagent
17
+ ```
18
+
19
+ Write your golden queries — what should your agent handle, and what should it refuse?
20
+
21
+ ```yaml
22
+ # agentci_spec.yaml
23
+ # runner: any function that takes a query string and returns a response
24
+ runner: my_app.agent:run_for_agentci
25
+ queries:
26
+ - query: "How do I install AgentCI?"
27
+ correctness:
28
+ any_expected_in_answer: ["pip install", "ciagent"]
29
+ path:
30
+ expected_tools: [retrieve_docs]
31
+ cost:
32
+ max_llm_calls: 8
33
+
34
+ - query: "What's the CEO's favorite restaurant?"
35
+ correctness:
36
+ not_in_answer: ["restaurant", "favorite"]
37
+ path:
38
+ expected_tools: [] # expect no tools called for out-of-scope queries
39
+ ```
40
+
41
+ Run:
42
+
43
+ ```bash
44
+ agentci test --mock # start here: zero-cost with synthetic traces
45
+ agentci test # run live against your real agent
46
+ ```
47
+
48
+ `agentci test` evaluates each query through 3 layers — correctness, path, and cost:
49
+
50
+ ```
51
+ ============================================================
52
+
53
+ Query: How do I install AgentCI?
54
+ Answer: To install AgentCI, you can use pip with the following command:
55
+ pip install ciagent. Make sure you have Python 3.10 or later.
56
+
57
+ ✅ CORRECTNESS: PASS
58
+ ✓ Found keywords: "pip install ciagent"
59
+ ✓ LLM judge passed (score: 5 ≥ 0.6)
60
+ 📈 PATH: PASS
61
+ ✓ Tool recall: 1.000 (expected: [retrieve_docs])
62
+ ✓ Tool precision: 0.500
63
+ ✓ No loops detected
64
+ 💰 COST: PASS
65
+ ✓ LLM calls: 8 ≤ max 8
66
+
67
+ ============================================================
68
+
69
+ Query: What Python version does AgentCI require and what frameworks does it support?
70
+ Answer: AgentCI currently does not specify a required Python version
71
+ in the provided context, so I don't have that information...
72
+
73
+ ❌ CORRECTNESS: FAIL
74
+ • Expected '3.10' not found in answer
75
+ 📈 PATH: PASS
76
+ ✓ Tool recall: 1.000 (expected: [retrieve_docs])
77
+ ✓ Loops: 1 ≤ max 3
78
+ 💰 COST: PASS
79
+ ✓ LLM calls: 4 ≤ max 5
80
+
81
+ ============================================================
82
+ ```
83
+
84
+ Don't have golden queries yet? `agentci init --generate` scans your code and generates a starter spec.
85
+
86
+ ## Demo
87
+
88
+ Here's a RAG agent demo where someone "optimizes for latency" by reducing retriever docs from 8 to 1. AgentCI catches the correctness regression:
89
+
90
+ ![AgentCI Demo](demo/agentci-rag-demo.gif)
91
+
92
+ ## CLI
93
+
94
+ ```bash
95
+ agentci init --generate # Scan project, generate test spec
96
+ agentci init # Generate GitHub Actions workflow + pre-push hook
97
+ agentci test --mock --yes # Zero-cost synthetic traces, CI-friendly (no keys, no prompts)
98
+ agentci test # Run 3-layer evaluation (correctness → path → cost)
99
+ agentci test --format html -o report.html # HTML report with per-query details
100
+ agentci calibrate # Measure real agent metrics, auto-tune spec budgets
101
+ agentci doctor # Health check: spec, deps, API keys
102
+ agentci record <test> # Record golden baseline
103
+ agentci diff # Diff against baseline
104
+ agentci report -i results.json # Generate HTML report from JSON results
105
+ ```
106
+ ## Contributing
107
+
108
+ [GitHub Issues](https://github.com/suniel12/AgentCI/issues)
109
+ [DemoAgents](https://github.com/suniel12/DemoAgents) — working examples for all three frameworks
110
+
111
+ Apache 2.0. If you build an agent and test it with AgentCI, I'd love to hear about it.
112
+
113
+ ---
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ciagent"
3
- version = "0.5.0"
3
+ version = "0.6.0"
4
4
  description = "Continuous Integration for AI Agents. Catch cost spikes and logic regressions before production."
5
5
  readme = "README.md"
6
6
  license = {text = "Apache-2.0"}
@@ -28,16 +28,16 @@ dependencies = [
28
28
  ]
29
29
 
30
30
  [project.urls]
31
- Documentation = "https://github.com/agentci-org/agentci#readme"
32
- Repository = "https://github.com/agentci-org/agentci.git"
33
- Issues = "https://github.com/agentci-org/agentci/issues"
31
+ Documentation = "https://github.com/suniel12/AgentCI#readme"
32
+ Repository = "https://github.com/suniel12/AgentCI.git"
33
+ Issues = "https://github.com/suniel12/AgentCI/issues"
34
34
 
35
35
  [project.optional-dependencies]
36
36
  openai = ["openai>=1.0"]
37
37
  anthropic = ["anthropic>=0.20"]
38
38
  langgraph = ["langgraph>=0.1"]
39
39
  jsonschema = ["jsonschema>=4.0"]
40
- all = ["agentci[openai,anthropic,langgraph,jsonschema]"]
40
+ all = ["ciagent[openai,anthropic,langgraph,jsonschema]"]
41
41
  dev = ["pytest>=8.0", "ruff", "mypy"]
42
42
 
43
43
  [project.scripts]
@@ -1,3 +1,5 @@
1
+ # Copyright 2025-2026 The AgentCI Authors
2
+ # SPDX-License-Identifier: Apache-2.0
1
3
  """
2
4
  Agent CI — Continuous Integration for AI Agents.
3
5
 
@@ -0,0 +1,3 @@
1
+ # Copyright 2025-2026 The AgentCI Authors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ # Copyright 2025-2026 The AgentCI Authors
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """
4
+ Adapter registry and base classes.
5
+ """
@@ -1,3 +1,5 @@
1
+ # Copyright 2025-2026 The AgentCI Authors
2
+ # SPDX-License-Identifier: Apache-2.0
1
3
  """
2
4
  Base Adapter class.
3
5
  """
@@ -1,3 +1,5 @@
1
+ # Copyright 2025-2026 The AgentCI Authors
2
+ # SPDX-License-Identifier: Apache-2.0
1
3
  """
2
4
  Generic Python Function Adapter.
3
5
  """
@@ -1,3 +1,5 @@
1
+ # Copyright 2025-2026 The AgentCI Authors
2
+ # SPDX-License-Identifier: Apache-2.0
1
3
  """
2
4
  LangGraph Adapter.
3
5
  """
@@ -61,9 +63,17 @@ class LangGraphAdapter(BaseAdapter):
61
63
  Parses a standard LangChain/LangGraph `messages` state list into a Trace.
62
64
  """
63
65
  trace = Trace(framework="langgraph", graph_state=state)
64
-
66
+
65
67
  messages = state.get("messages", [])
66
-
68
+
69
+ # Extract final output from last AI message
70
+ for msg in reversed(messages):
71
+ if getattr(msg, "type", "") == "ai":
72
+ content = getattr(msg, "content", "")
73
+ if content:
74
+ trace.metadata["final_output"] = str(content)
75
+ break
76
+
67
77
  current_span = Span(name="langgraph_execution")
68
78
 
69
79
  for msg in messages:
@@ -1,3 +1,5 @@
1
+ # Copyright 2025-2026 The AgentCI Authors
2
+ # SPDX-License-Identifier: Apache-2.0
1
3
  """
2
4
  OpenAI Agents SDK Adapter for AgentCI.
3
5
 
@@ -85,9 +87,23 @@ class AgentCITraceProcessor:
85
87
  self._span_map[span_id] = agentci_span
86
88
 
87
89
  def on_trace_end(self, trace: Any) -> None:
88
- """Called when the trace ends. Finalize metrics."""
90
+ """Called when the trace ends. Finalize metrics and extract output."""
89
91
  if self._current_trace is not None:
90
92
  self._current_trace.compute_metrics()
93
+
94
+ # Auto-extract final output from last agent or generation span
95
+ if "final_output" not in self._current_trace.metadata:
96
+ for span in reversed(self._current_trace.spans):
97
+ if span.output_data:
98
+ self._current_trace.metadata["final_output"] = str(span.output_data)
99
+ break
100
+ if span.llm_calls:
101
+ last_llm = span.llm_calls[-1]
102
+ text = getattr(last_llm, "output_text", "")
103
+ if text:
104
+ self._current_trace.metadata["final_output"] = text
105
+ break
106
+
91
107
  self._last_trace = self._current_trace
92
108
  self._current_trace = None
93
109
 
@@ -1,3 +1,5 @@
1
+ # Copyright 2025-2026 The AgentCI Authors
2
+ # SPDX-License-Identifier: Apache-2.0
1
3
  """
2
4
  Built-in assertion evaluators.
3
5
 
@@ -1,3 +1,5 @@
1
+ # Copyright 2025-2026 The AgentCI Authors
2
+ # SPDX-License-Identifier: Apache-2.0
1
3
  """
2
4
  AgentCI v2 Baseline Manager.
3
5
 
@@ -1,3 +1,5 @@
1
+ # Copyright 2025-2026 The AgentCI Authors
2
+ # SPDX-License-Identifier: Apache-2.0
1
3
  """
2
4
  Trace capture via monkey-patching.
3
5
 
@@ -8,6 +10,7 @@ object. The developer doesn't change their agent code at all.
8
10
  Phase 1: Patch openai.ChatCompletion and anthropic.Messages
9
11
  Phase 2: Add OTEL span emission for interop with Arize/Langfuse
10
12
  """
13
+ from __future__ import annotations
11
14
 
12
15
  import time
13
16
  import contextvars
@@ -75,17 +78,72 @@ class TraceContext:
75
78
  def __exit__(self, *args):
76
79
  # Compute duration
77
80
  self.trace.total_duration_ms = (time.perf_counter() - self._start_time) * 1000
78
-
81
+
79
82
  # Roll up metrics
80
83
  self.trace.compute_metrics()
81
-
84
+
85
+ # Auto-extract final output if not manually set
86
+ self._auto_extract_final_output()
87
+
82
88
  # Remove patches
83
89
  for restore_fn in self._patches:
84
90
  restore_fn()
85
-
91
+
86
92
  # Clear context
87
93
  _active_trace.set(None)
88
94
  _active_span.set(None)
95
+
96
+ def _auto_extract_final_output(self) -> None:
97
+ """Auto-extract the agent's final output from the trace.
98
+
99
+ Only runs if ``final_output`` has not been manually set in
100
+ ``trace.metadata``. Extraction priority:
101
+
102
+ 1. LangGraph state: last AI message's ``.content``
103
+ 2. Last span's ``output_data`` (string)
104
+ 3. Last span's ``output_data`` dict with common keys
105
+ 4. Last LLM call's ``output_text`` from last span
106
+ """
107
+ if "final_output" in self.trace.metadata:
108
+ return
109
+
110
+ # 1. LangGraph state
111
+ graph_state = getattr(self.trace, "graph_state", None)
112
+ if graph_state:
113
+ messages = graph_state.get("messages", [])
114
+ if messages:
115
+ last_msg = messages[-1]
116
+ content = getattr(last_msg, "content", None)
117
+ if content:
118
+ self.trace.metadata["final_output"] = str(content)
119
+ return
120
+
121
+ # 2-3. Last span output_data
122
+ if self.trace.spans:
123
+ last_span = self.trace.spans[-1]
124
+ output = last_span.output_data
125
+
126
+ if output is not None:
127
+ if isinstance(output, str):
128
+ self.trace.metadata["final_output"] = output
129
+ return
130
+ if isinstance(output, dict):
131
+ for key in ("content", "message", "text", "output"):
132
+ if key in output:
133
+ self.trace.metadata["final_output"] = str(output[key])
134
+ return
135
+
136
+ # 4. Last LLM call output
137
+ if last_span.llm_calls:
138
+ last_llm = last_span.llm_calls[-1]
139
+ # Handle both LLMCall objects and raw dicts
140
+ if isinstance(last_llm, dict):
141
+ text = last_llm.get("content") or last_llm.get("output_text", "")
142
+ else:
143
+ text = getattr(last_llm, "output_text", "")
144
+ if text:
145
+ self.trace.metadata["final_output"] = str(text)
146
+ return
89
147
 
90
148
  def _patch_openai(self):
91
149
  """Wrap openai.chat.completions.create to capture LLM calls."""