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.
- ciagent-0.6.0/.gitignore +43 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/CHANGELOG.md +36 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/LICENSE +2 -0
- ciagent-0.6.0/PKG-INFO +156 -0
- ciagent-0.6.0/README.md +113 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/pyproject.toml +5 -5
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/__init__.py +2 -0
- ciagent-0.6.0/src/agentci/_version.py +3 -0
- ciagent-0.6.0/src/agentci/adapters/__init__.py +5 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/adapters/base.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/adapters/generic.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/adapters/langgraph.py +12 -2
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/adapters/openai_agents.py +17 -1
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/assertions.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/baselines.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/capture.py +61 -3
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/cli.py +433 -74
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/config.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/cost.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/diff_engine.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/__init__.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/correctness.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/cost.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/cost_estimator.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/diff.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/judge.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/metrics.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/mock_runner.py +12 -4
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/parallel.py +24 -1
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/path.py +32 -2
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/reporter.py +154 -28
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/results.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/runner.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/engine/span_assertions.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/exceptions.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/loader.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/mocks.py +3 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/models.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/pytest_plugin.py +3 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/report.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/runner.py +3 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/schema/__init__.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/schema/agentci_spec.schema.json +33 -1
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/schema/generate_schema.py +2 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/schema/spec_models.py +10 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/templates/github_action.yml.j2 +2 -0
- ciagent-0.6.0/src/agentci/templates/report.html.j2 +194 -0
- ciagent-0.5.0/.gitignore +0 -24
- ciagent-0.5.0/PKG-INFO +0 -323
- ciagent-0.5.0/README.md +0 -283
- ciagent-0.5.0/src/agentci/_version.py +0 -1
- ciagent-0.5.0/src/agentci/adapters/__init__.py +0 -3
- ciagent-0.5.0/src/agentci/templates/agentci.yaml +0 -16
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/py.typed +0 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/templates/__init__.py +0 -0
- {ciagent-0.5.0 → ciagent-0.6.0}/src/agentci/templates/pre_push_hook.sh.j2 +0 -0
ciagent-0.6.0/.gitignore
ADDED
|
@@ -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
|
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
|
+
[](https://pypi.org/project/ciagent/)
|
|
49
|
+
[](LICENSE)
|
|
50
|
+
[](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
|
+

|
|
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
|
+
---
|
ciagent-0.6.0/README.md
ADDED
|
@@ -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
|
+
[](https://pypi.org/project/ciagent/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](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
|
+

|
|
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.
|
|
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/
|
|
32
|
-
Repository = "https://github.com/
|
|
33
|
-
Issues = "https://github.com/
|
|
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 = ["
|
|
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
|
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
|
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."""
|