contract4agents 0.1.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.
- contract4agents-0.1.0/LICENSE +21 -0
- contract4agents-0.1.0/PKG-INFO +132 -0
- contract4agents-0.1.0/README.md +76 -0
- contract4agents-0.1.0/pyproject.toml +141 -0
- contract4agents-0.1.0/src/contract4agents/__init__.py +7 -0
- contract4agents-0.1.0/src/contract4agents/__main__.py +6 -0
- contract4agents-0.1.0/src/contract4agents/adapters/__init__.py +0 -0
- contract4agents-0.1.0/src/contract4agents/adapters/openai.py +169 -0
- contract4agents-0.1.0/src/contract4agents/ast.py +135 -0
- contract4agents-0.1.0/src/contract4agents/cli.py +143 -0
- contract4agents-0.1.0/src/contract4agents/compiler/__init__.py +310 -0
- contract4agents-0.1.0/src/contract4agents/diagnostics.py +39 -0
- contract4agents-0.1.0/src/contract4agents/docscheck.py +69 -0
- contract4agents-0.1.0/src/contract4agents/evaluation.py +84 -0
- contract4agents-0.1.0/src/contract4agents/expressions/__init__.py +31 -0
- contract4agents-0.1.0/src/contract4agents/expressions/_eval.py +152 -0
- contract4agents-0.1.0/src/contract4agents/expressions/_grammar.py +212 -0
- contract4agents-0.1.0/src/contract4agents/expressions/_model.py +29 -0
- contract4agents-0.1.0/src/contract4agents/expressions/_refs.py +18 -0
- contract4agents-0.1.0/src/contract4agents/expressions/_trace_ops.py +56 -0
- contract4agents-0.1.0/src/contract4agents/fixtures/__init__.py +123 -0
- contract4agents-0.1.0/src/contract4agents/fixtures/_artifacts.py +65 -0
- contract4agents-0.1.0/src/contract4agents/fixtures/_execution.py +83 -0
- contract4agents-0.1.0/src/contract4agents/fixtures/_models.py +75 -0
- contract4agents-0.1.0/src/contract4agents/fixtures/_reports.py +72 -0
- contract4agents-0.1.0/src/contract4agents/monitor.py +46 -0
- contract4agents-0.1.0/src/contract4agents/parser/__init__.py +57 -0
- contract4agents-0.1.0/src/contract4agents/parser/_grammar.py +91 -0
- contract4agents-0.1.0/src/contract4agents/parser/_transformer.py +323 -0
- contract4agents-0.1.0/src/contract4agents/parser/_values.py +61 -0
- contract4agents-0.1.0/src/contract4agents/runtime/__init__.py +53 -0
- contract4agents-0.1.0/src/contract4agents/runtime/_datasources.py +301 -0
- contract4agents-0.1.0/src/contract4agents/runtime/_errors.py +64 -0
- contract4agents-0.1.0/src/contract4agents/runtime/_tools.py +59 -0
- contract4agents-0.1.0/src/contract4agents/runtime/_trace.py +41 -0
- contract4agents-0.1.0/src/contract4agents/runtime/_trace_io.py +49 -0
- contract4agents-0.1.0/src/contract4agents/runtime/_utils.py +23 -0
- contract4agents-0.1.0/src/contract4agents/schema.py +86 -0
- contract4agents-0.1.0/src/contract4agents/semantics.py +335 -0
- contract4agents-0.1.0/src/contract4agents/visualization/__init__.py +30 -0
- contract4agents-0.1.0/src/contract4agents/visualization/_artifacts.py +23 -0
- contract4agents-0.1.0/src/contract4agents/visualization/_graph.py +185 -0
- contract4agents-0.1.0/src/contract4agents/visualization/_html.py +508 -0
- contract4agents-0.1.0/src/contract4agents/visualization/_mermaid.py +82 -0
- contract4agents-0.1.0/src/contract4agents/visualization/_types.py +60 -0
- contract4agents-0.1.0/src/contract4agents/visualization/_utils.py +39 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, B.T. Franklin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: contract4agents
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Typed declarative contracts for AI agent systems.
|
|
5
|
+
Keywords: agents,contracts,evals,llm,openai,tooling
|
|
6
|
+
Author-Email: "B.T. Franklin" <brandon.franklin@gmail.com>
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2026, B.T. Franklin
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
|
|
29
|
+
Classifier: Programming Language :: Python :: 3
|
|
30
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
31
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
35
|
+
Classifier: Typing :: Typed
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Operating System :: OS Independent
|
|
38
|
+
Classifier: Intended Audience :: Developers
|
|
39
|
+
Classifier: Environment :: Console
|
|
40
|
+
Classifier: Topic :: File Formats
|
|
41
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
42
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
43
|
+
Project-URL: Homepage, https://github.com/btfranklin/contract4agents
|
|
44
|
+
Project-URL: Issues, https://github.com/btfranklin/contract4agents/issues
|
|
45
|
+
Project-URL: Changelog, https://github.com/btfranklin/contract4agents/releases
|
|
46
|
+
Project-URL: Repository, https://github.com/btfranklin/contract4agents.git
|
|
47
|
+
Requires-Python: >=3.11
|
|
48
|
+
Requires-Dist: click>=8.4.1
|
|
49
|
+
Requires-Dist: lark>=1.3.1
|
|
50
|
+
Requires-Dist: jsonschema>=4.26.0
|
|
51
|
+
Requires-Dist: promptdown>=1.1.6
|
|
52
|
+
Provides-Extra: openai
|
|
53
|
+
Requires-Dist: openai>=2.44.0; extra == "openai"
|
|
54
|
+
Requires-Dist: openai-agents>=0.17.7; extra == "openai"
|
|
55
|
+
Description-Content-Type: text/markdown
|
|
56
|
+
|
|
57
|
+
# Contract4Agents
|
|
58
|
+
|
|
59
|
+

|
|
60
|
+
|
|
61
|
+
Contract4Agents is a typed declarative language and local toolchain for defining AI agents as inspectable contracts.
|
|
62
|
+
|
|
63
|
+
The source artifact is a `.contract` file. It describes an agent's callable interface, allowed capabilities, policies, guards, assertions, and output contract. The compiler turns that source into prompts, provider-neutral manifests, JSON Schemas, eval packs, monitor rules, and visualization artifacts.
|
|
64
|
+
|
|
65
|
+
Contract4Agents includes the compiler, CLI, local fixtures, monitor checks, runtime primitives, and provider adapters needed to use those contracts beside a host agent application.
|
|
66
|
+
|
|
67
|
+
Start here:
|
|
68
|
+
|
|
69
|
+
- `docs/tutorials/using-contract4agents-with-an-agent-app.md` explains how to
|
|
70
|
+
use Contract4Agents beside an existing agent SDK implementation.
|
|
71
|
+
- `VISION.md` explains the concept and why it exists.
|
|
72
|
+
- `examples/incident-command/README.md` is the most concrete first read: it
|
|
73
|
+
explains what users write, what the files mean, and what artifacts are
|
|
74
|
+
generated.
|
|
75
|
+
- `examples/multi-lens-research/README.md` shows a complex research team split
|
|
76
|
+
into focused expert lenses.
|
|
77
|
+
- `examples/market-research-brief/README.md` shows document-driven market
|
|
78
|
+
research against dated current-fact snapshots.
|
|
79
|
+
- `examples/README.md` explains the reusable pattern for future examples.
|
|
80
|
+
- `docs/index.md` is the documentation map.
|
|
81
|
+
- `docs/examples/incident-command-walkthrough.md` walks through the clone-only example.
|
|
82
|
+
- `docs/research/agent-sdk-pattern-survey.md` captures the cross-SDK patterns Contract4Agents should preserve.
|
|
83
|
+
- `docs/decisions/accepted-decisions.md` records choices that should not be reopened casually.
|
|
84
|
+
- `docs/implementation/roadmap.md` tracks the active backlog for VISION gaps that are not implemented yet.
|
|
85
|
+
|
|
86
|
+
## What's Included
|
|
87
|
+
|
|
88
|
+
- `contract4agents` Python package
|
|
89
|
+
- `contract4agents` Click CLI
|
|
90
|
+
- Lark-backed parser
|
|
91
|
+
- semantic analyzer
|
|
92
|
+
- JSON Schema and provider-neutral manifest compiler
|
|
93
|
+
- local fake-tool and datasource runtime primitives
|
|
94
|
+
- eval and monitor runners
|
|
95
|
+
- static project visualization
|
|
96
|
+
- first OpenAI adapter and semantic judge adapter
|
|
97
|
+
- clone-only `Incident Command` example backed by SQLite fake data
|
|
98
|
+
|
|
99
|
+
## Development Setup
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pdm install
|
|
103
|
+
pdm run contract4agents --help
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Useful Commands
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
pdm run contract4agents check examples/incident-command
|
|
110
|
+
pdm run contract4agents compile examples/incident-command --out .contract/build
|
|
111
|
+
pdm run contract4agents visualize examples/incident-command --out .contract/build/visualization
|
|
112
|
+
pdm run contract4agents eval tests/fixtures/contract_projects/ops-desk-lab
|
|
113
|
+
pdm run docs-check
|
|
114
|
+
pdm run validate
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The `examples/incident-command` project is the public walkthrough fixture for check, compile, and visualization. The eval command currently uses the richer `tests/fixtures/contract_projects/ops-desk-lab` fixture because the reusable fixture runner expects a `fixture.json` project.
|
|
118
|
+
|
|
119
|
+
## OpenAI Adapter Checks
|
|
120
|
+
|
|
121
|
+
The normal validation suite does not call external APIs. Live OpenAI checks are opt-in:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
CONTRACT4AGENTS_RUN_OPENAI_LIVE=1 pdm run test:openai-live
|
|
125
|
+
CONTRACT4AGENTS_RUN_OPENAI_AGENT_LIVE=1 pdm run test:openai-agent-live
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Those commands require `OPENAI_API_KEY` in the environment or in the ignored local `.env` file.
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Contract4Agents
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Contract4Agents is a typed declarative language and local toolchain for defining AI agents as inspectable contracts.
|
|
6
|
+
|
|
7
|
+
The source artifact is a `.contract` file. It describes an agent's callable interface, allowed capabilities, policies, guards, assertions, and output contract. The compiler turns that source into prompts, provider-neutral manifests, JSON Schemas, eval packs, monitor rules, and visualization artifacts.
|
|
8
|
+
|
|
9
|
+
Contract4Agents includes the compiler, CLI, local fixtures, monitor checks, runtime primitives, and provider adapters needed to use those contracts beside a host agent application.
|
|
10
|
+
|
|
11
|
+
Start here:
|
|
12
|
+
|
|
13
|
+
- `docs/tutorials/using-contract4agents-with-an-agent-app.md` explains how to
|
|
14
|
+
use Contract4Agents beside an existing agent SDK implementation.
|
|
15
|
+
- `VISION.md` explains the concept and why it exists.
|
|
16
|
+
- `examples/incident-command/README.md` is the most concrete first read: it
|
|
17
|
+
explains what users write, what the files mean, and what artifacts are
|
|
18
|
+
generated.
|
|
19
|
+
- `examples/multi-lens-research/README.md` shows a complex research team split
|
|
20
|
+
into focused expert lenses.
|
|
21
|
+
- `examples/market-research-brief/README.md` shows document-driven market
|
|
22
|
+
research against dated current-fact snapshots.
|
|
23
|
+
- `examples/README.md` explains the reusable pattern for future examples.
|
|
24
|
+
- `docs/index.md` is the documentation map.
|
|
25
|
+
- `docs/examples/incident-command-walkthrough.md` walks through the clone-only example.
|
|
26
|
+
- `docs/research/agent-sdk-pattern-survey.md` captures the cross-SDK patterns Contract4Agents should preserve.
|
|
27
|
+
- `docs/decisions/accepted-decisions.md` records choices that should not be reopened casually.
|
|
28
|
+
- `docs/implementation/roadmap.md` tracks the active backlog for VISION gaps that are not implemented yet.
|
|
29
|
+
|
|
30
|
+
## What's Included
|
|
31
|
+
|
|
32
|
+
- `contract4agents` Python package
|
|
33
|
+
- `contract4agents` Click CLI
|
|
34
|
+
- Lark-backed parser
|
|
35
|
+
- semantic analyzer
|
|
36
|
+
- JSON Schema and provider-neutral manifest compiler
|
|
37
|
+
- local fake-tool and datasource runtime primitives
|
|
38
|
+
- eval and monitor runners
|
|
39
|
+
- static project visualization
|
|
40
|
+
- first OpenAI adapter and semantic judge adapter
|
|
41
|
+
- clone-only `Incident Command` example backed by SQLite fake data
|
|
42
|
+
|
|
43
|
+
## Development Setup
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pdm install
|
|
47
|
+
pdm run contract4agents --help
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Useful Commands
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pdm run contract4agents check examples/incident-command
|
|
54
|
+
pdm run contract4agents compile examples/incident-command --out .contract/build
|
|
55
|
+
pdm run contract4agents visualize examples/incident-command --out .contract/build/visualization
|
|
56
|
+
pdm run contract4agents eval tests/fixtures/contract_projects/ops-desk-lab
|
|
57
|
+
pdm run docs-check
|
|
58
|
+
pdm run validate
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The `examples/incident-command` project is the public walkthrough fixture for check, compile, and visualization. The eval command currently uses the richer `tests/fixtures/contract_projects/ops-desk-lab` fixture because the reusable fixture runner expects a `fixture.json` project.
|
|
62
|
+
|
|
63
|
+
## OpenAI Adapter Checks
|
|
64
|
+
|
|
65
|
+
The normal validation suite does not call external APIs. Live OpenAI checks are opt-in:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
CONTRACT4AGENTS_RUN_OPENAI_LIVE=1 pdm run test:openai-live
|
|
69
|
+
CONTRACT4AGENTS_RUN_OPENAI_AGENT_LIVE=1 pdm run test:openai-agent-live
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Those commands require `OPENAI_API_KEY` in the environment or in the ignored local `.env` file.
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = [
|
|
3
|
+
"pdm-backend>=2.4.9",
|
|
4
|
+
]
|
|
5
|
+
build-backend = "pdm.backend"
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "contract4agents"
|
|
9
|
+
dynamic = []
|
|
10
|
+
description = "Typed declarative contracts for AI agent systems."
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "B.T. Franklin", email = "brandon.franklin@gmail.com" },
|
|
13
|
+
]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"click>=8.4.1",
|
|
16
|
+
"lark>=1.3.1",
|
|
17
|
+
"jsonschema>=4.26.0",
|
|
18
|
+
"promptdown>=1.1.6",
|
|
19
|
+
]
|
|
20
|
+
requires-python = ">=3.11"
|
|
21
|
+
readme = "README.md"
|
|
22
|
+
keywords = [
|
|
23
|
+
"agents",
|
|
24
|
+
"contracts",
|
|
25
|
+
"evals",
|
|
26
|
+
"llm",
|
|
27
|
+
"openai",
|
|
28
|
+
"tooling",
|
|
29
|
+
]
|
|
30
|
+
classifiers = [
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
33
|
+
"Programming Language :: Python :: 3.11",
|
|
34
|
+
"Programming Language :: Python :: 3.12",
|
|
35
|
+
"Programming Language :: Python :: 3.13",
|
|
36
|
+
"Programming Language :: Python :: 3.14",
|
|
37
|
+
"Typing :: Typed",
|
|
38
|
+
"License :: OSI Approved :: MIT License",
|
|
39
|
+
"Operating System :: OS Independent",
|
|
40
|
+
"Intended Audience :: Developers",
|
|
41
|
+
"Environment :: Console",
|
|
42
|
+
"Topic :: File Formats",
|
|
43
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
44
|
+
"Topic :: Software Development :: Code Generators",
|
|
45
|
+
]
|
|
46
|
+
version = "0.1.0"
|
|
47
|
+
|
|
48
|
+
[project.license]
|
|
49
|
+
file = "LICENSE"
|
|
50
|
+
|
|
51
|
+
[project.urls]
|
|
52
|
+
Homepage = "https://github.com/btfranklin/contract4agents"
|
|
53
|
+
Issues = "https://github.com/btfranklin/contract4agents/issues"
|
|
54
|
+
Changelog = "https://github.com/btfranklin/contract4agents/releases"
|
|
55
|
+
Repository = "https://github.com/btfranklin/contract4agents.git"
|
|
56
|
+
|
|
57
|
+
[project.scripts]
|
|
58
|
+
contract4agents = "contract4agents.cli:main"
|
|
59
|
+
|
|
60
|
+
[project.optional-dependencies]
|
|
61
|
+
openai = [
|
|
62
|
+
"openai>=2.44.0",
|
|
63
|
+
"openai-agents>=0.17.7",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
[tool.pdm]
|
|
67
|
+
distribution = true
|
|
68
|
+
|
|
69
|
+
[tool.pdm.version]
|
|
70
|
+
source = "scm"
|
|
71
|
+
|
|
72
|
+
[tool.pdm.build]
|
|
73
|
+
package-dir = "src"
|
|
74
|
+
excludes = [
|
|
75
|
+
"examples/**",
|
|
76
|
+
"tests/**",
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
[tool.pdm.scripts]
|
|
80
|
+
test = "pytest"
|
|
81
|
+
"test:unit" = "pytest tests/unit --cov=contract4agents --cov-report=term-missing --cov-fail-under=90"
|
|
82
|
+
"test:integration" = "pytest tests/integration"
|
|
83
|
+
"test:openai-live" = "pytest tests/integration/test_openai_live.py"
|
|
84
|
+
"test:agent-fixture" = "pytest tests/integration/test_ops_desk_fixture.py"
|
|
85
|
+
"test:openai-agent-live" = "pytest tests/integration/test_openai_agent_live.py"
|
|
86
|
+
docs-check = "python -m contract4agents docs-check"
|
|
87
|
+
lint = "ruff check src tests examples"
|
|
88
|
+
typecheck = "mypy src"
|
|
89
|
+
contract4agents = "python -m contract4agents"
|
|
90
|
+
|
|
91
|
+
[tool.pdm.scripts.validate]
|
|
92
|
+
composite = [
|
|
93
|
+
"lint",
|
|
94
|
+
"typecheck",
|
|
95
|
+
"docs-check",
|
|
96
|
+
"test",
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
[tool.pytest.ini_options]
|
|
100
|
+
testpaths = [
|
|
101
|
+
"tests",
|
|
102
|
+
]
|
|
103
|
+
markers = [
|
|
104
|
+
"integration: tests that may require optional services or credentials",
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
[tool.ruff]
|
|
108
|
+
line-length = 120
|
|
109
|
+
target-version = "py311"
|
|
110
|
+
|
|
111
|
+
[tool.ruff.lint]
|
|
112
|
+
select = [
|
|
113
|
+
"E",
|
|
114
|
+
"F",
|
|
115
|
+
"I",
|
|
116
|
+
"UP",
|
|
117
|
+
"B",
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
[tool.mypy]
|
|
121
|
+
python_version = "3.11"
|
|
122
|
+
strict = true
|
|
123
|
+
warn_unused_ignores = true
|
|
124
|
+
warn_return_any = true
|
|
125
|
+
files = [
|
|
126
|
+
"src",
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
[dependency-groups]
|
|
130
|
+
dev = [
|
|
131
|
+
"pytest>=9.1.1",
|
|
132
|
+
"pytest-cov>=7.1.0",
|
|
133
|
+
"ruff>=0.15.18",
|
|
134
|
+
"mypy>=2.1.0",
|
|
135
|
+
"pytest-asyncio>=1.4.0",
|
|
136
|
+
"types-jsonschema>=4.26.0.20260518",
|
|
137
|
+
]
|
|
138
|
+
openai = [
|
|
139
|
+
"openai>=2.44.0",
|
|
140
|
+
"openai-agents>=0.17.7",
|
|
141
|
+
]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Contract4Agents Python package."""
|
|
2
|
+
|
|
3
|
+
from contract4agents.compiler import compile_project
|
|
4
|
+
from contract4agents.parser import parse_file, parse_project
|
|
5
|
+
from contract4agents.semantics import analyze_project
|
|
6
|
+
|
|
7
|
+
__all__ = ["analyze_project", "compile_project", "parse_file", "parse_project"]
|
|
File without changes
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""OpenAI Agents SDK adapter.
|
|
2
|
+
|
|
3
|
+
The adapter is intentionally thin: Contract4Agents compiles to provider-neutral
|
|
4
|
+
manifests first, and this module projects those manifests onto OpenAI's SDK.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from contract4agents.compiler import AgentManifest
|
|
14
|
+
from contract4agents.runtime import TraceRecorder
|
|
15
|
+
|
|
16
|
+
_RunHooksBase: type[Any]
|
|
17
|
+
try:
|
|
18
|
+
from agents import RunHooks as _ImportedRunHooks
|
|
19
|
+
|
|
20
|
+
_RunHooksBase = _ImportedRunHooks
|
|
21
|
+
except Exception: # noqa: BLE001 - optional adapter import boundary.
|
|
22
|
+
_RunHooksBase = object
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class OpenAIAdapterResult:
|
|
27
|
+
final_output: Any
|
|
28
|
+
last_agent: str | None
|
|
29
|
+
raw_result: Any
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class OpenAIAdapterUnavailable(RuntimeError):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def openai_tool_name(contract_name: str) -> str:
|
|
37
|
+
"""Convert a Contract4Agents capability name into an OpenAI-safe tool name."""
|
|
38
|
+
return contract_name.replace(".", "__")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def contract_tool_name(openai_name: str) -> str:
|
|
42
|
+
"""Convert a generated OpenAI tool name back into the Contract4Agents capability name."""
|
|
43
|
+
return openai_name.replace("__", ".")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class OpenAITraceHooks(_RunHooksBase): # type: ignore[misc]
|
|
47
|
+
"""Minimal hook object that normalizes Agents SDK lifecycle events to Contract4Agents traces."""
|
|
48
|
+
|
|
49
|
+
def __init__(self, trace: TraceRecorder) -> None:
|
|
50
|
+
super().__init__()
|
|
51
|
+
self.trace = trace
|
|
52
|
+
|
|
53
|
+
async def on_agent_start(self, _context: Any, agent: Any) -> None:
|
|
54
|
+
self.trace.record("agent.started", agent=getattr(agent, "name", str(agent)))
|
|
55
|
+
|
|
56
|
+
async def on_agent_end(self, _context: Any, agent: Any, output: Any) -> None:
|
|
57
|
+
self.trace.record("agent.completed", agent=getattr(agent, "name", str(agent)), output=_serializable(output))
|
|
58
|
+
|
|
59
|
+
async def on_handoff(self, _context: Any, from_agent: Any, to_agent: Any) -> None:
|
|
60
|
+
self.trace.record(
|
|
61
|
+
"agent.handoff",
|
|
62
|
+
from_agent=getattr(from_agent, "name", str(from_agent)),
|
|
63
|
+
to_agent=getattr(to_agent, "name", str(to_agent)),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
async def on_tool_start(self, _context: Any, agent: Any, tool: Any) -> None:
|
|
67
|
+
self.trace.record(
|
|
68
|
+
"tool.started",
|
|
69
|
+
agent=getattr(agent, "name", str(agent)),
|
|
70
|
+
tool=contract_tool_name(getattr(tool, "name", str(tool))),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
async def on_tool_end(self, _context: Any, agent: Any, tool: Any, result: str) -> None:
|
|
74
|
+
self.trace.record(
|
|
75
|
+
"tool.completed",
|
|
76
|
+
agent=getattr(agent, "name", str(agent)),
|
|
77
|
+
tool=contract_tool_name(getattr(tool, "name", str(tool))),
|
|
78
|
+
result=_serializable(result),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
async def on_llm_start(self, _context: Any, agent: Any, _system_prompt: str | None, input_items: list[Any]) -> None:
|
|
82
|
+
self.trace.record("llm.started", agent=getattr(agent, "name", str(agent)), input_count=len(input_items))
|
|
83
|
+
|
|
84
|
+
async def on_llm_end(self, _context: Any, agent: Any, _response: Any) -> None:
|
|
85
|
+
self.trace.record("llm.completed", agent=getattr(agent, "name", str(agent)))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def build_openai_agent(
|
|
89
|
+
manifest: AgentManifest,
|
|
90
|
+
instructions: str,
|
|
91
|
+
tools: list[Any] | None = None,
|
|
92
|
+
handoffs: list[Any] | None = None,
|
|
93
|
+
output_type: Any | None = None,
|
|
94
|
+
hooks: Any | None = None,
|
|
95
|
+
input_guardrails: list[Any] | None = None,
|
|
96
|
+
) -> Any:
|
|
97
|
+
try:
|
|
98
|
+
from agents import Agent
|
|
99
|
+
except Exception as exc: # noqa: BLE001 - optional adapter import boundary.
|
|
100
|
+
raise OpenAIAdapterUnavailable("openai-agents is not installed") from exc
|
|
101
|
+
kwargs: dict[str, Any] = {
|
|
102
|
+
"name": manifest["agent"],
|
|
103
|
+
"instructions": instructions,
|
|
104
|
+
"model": manifest.get("model", "gpt-5.5"),
|
|
105
|
+
"tools": tools or [],
|
|
106
|
+
"handoffs": handoffs or [],
|
|
107
|
+
}
|
|
108
|
+
if output_type is not None:
|
|
109
|
+
kwargs["output_type"] = output_type
|
|
110
|
+
if hooks is not None:
|
|
111
|
+
kwargs["hooks"] = hooks
|
|
112
|
+
if input_guardrails is not None:
|
|
113
|
+
kwargs["input_guardrails"] = input_guardrails
|
|
114
|
+
return Agent(**kwargs)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
async def run_openai_agent(
|
|
118
|
+
agent: Any,
|
|
119
|
+
user_input: str,
|
|
120
|
+
*,
|
|
121
|
+
context: Any | None = None,
|
|
122
|
+
max_turns: int | None = 10,
|
|
123
|
+
hooks: Any | None = None,
|
|
124
|
+
) -> OpenAIAdapterResult:
|
|
125
|
+
try:
|
|
126
|
+
from agents import Runner
|
|
127
|
+
except Exception as exc: # noqa: BLE001 - optional adapter import boundary.
|
|
128
|
+
raise OpenAIAdapterUnavailable("openai-agents is not installed") from exc
|
|
129
|
+
result = await Runner.run(agent, user_input, context=context, max_turns=max_turns, hooks=hooks)
|
|
130
|
+
last_agent = getattr(getattr(result, "last_agent", None), "name", None)
|
|
131
|
+
return OpenAIAdapterResult(getattr(result, "final_output", None), last_agent, result)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class OpenAISemanticJudge:
|
|
135
|
+
def __init__(self, model: str = "gpt-5.5", api_key_env: str = "OPENAI_API_KEY") -> None:
|
|
136
|
+
self.model = model
|
|
137
|
+
self.api_key_env = api_key_env
|
|
138
|
+
|
|
139
|
+
async def judge(self, *, output: dict[str, Any], criterion: str) -> bool:
|
|
140
|
+
if not os.getenv(self.api_key_env):
|
|
141
|
+
raise OpenAIAdapterUnavailable(f"{self.api_key_env} is not set")
|
|
142
|
+
try:
|
|
143
|
+
from openai import AsyncOpenAI
|
|
144
|
+
except Exception as exc: # noqa: BLE001 - optional adapter import boundary.
|
|
145
|
+
raise OpenAIAdapterUnavailable("openai package is not installed") from exc
|
|
146
|
+
client = AsyncOpenAI()
|
|
147
|
+
response = await client.responses.create(
|
|
148
|
+
model=self.model,
|
|
149
|
+
input=[
|
|
150
|
+
{
|
|
151
|
+
"role": "system",
|
|
152
|
+
"content": "Return only PASS or FAIL. Evaluate whether the output satisfies the criterion.",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"role": "user",
|
|
156
|
+
"content": f"Criterion: {criterion}\nOutput: {output}",
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
)
|
|
160
|
+
text = getattr(response, "output_text", "")
|
|
161
|
+
return str(text).strip().upper() == "PASS"
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _serializable(value: Any) -> Any:
|
|
165
|
+
if hasattr(value, "model_dump"):
|
|
166
|
+
return value.model_dump()
|
|
167
|
+
if isinstance(value, dict | list | str | int | float | bool) or value is None:
|
|
168
|
+
return value
|
|
169
|
+
return str(value)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""AST nodes for Contract4Agents source files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Literal
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class SourceSpan:
|
|
12
|
+
path: Path
|
|
13
|
+
line: int
|
|
14
|
+
column: int = 1
|
|
15
|
+
|
|
16
|
+
def display(self) -> str:
|
|
17
|
+
return f"{self.path}:{self.line}:{self.column}"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class FieldDef:
|
|
22
|
+
name: str
|
|
23
|
+
type_name: str
|
|
24
|
+
nullable: bool = False
|
|
25
|
+
default: str | None = None
|
|
26
|
+
span: SourceSpan | None = None
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def normalized_type(self) -> str:
|
|
30
|
+
return self.type_name.rstrip("?").strip()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class TypeDef:
|
|
35
|
+
name: str
|
|
36
|
+
fields: list[FieldDef]
|
|
37
|
+
span: SourceSpan
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
Permission = Literal["available", "preapproved", "requires_approval", "denied", "sandboxed"]
|
|
41
|
+
UseKind = Literal["tool", "agent", "datasource"]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class UseDecl:
|
|
46
|
+
kind: UseKind
|
|
47
|
+
name: str
|
|
48
|
+
source: str
|
|
49
|
+
permission: Permission = "available"
|
|
50
|
+
span: SourceSpan | None = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True)
|
|
54
|
+
class DatasourceDef:
|
|
55
|
+
name: str
|
|
56
|
+
python: str
|
|
57
|
+
requires: list[str]
|
|
58
|
+
produces: str
|
|
59
|
+
render: str = "markdown"
|
|
60
|
+
cache: str = "run"
|
|
61
|
+
span: SourceSpan | None = None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True)
|
|
65
|
+
class AgentDef:
|
|
66
|
+
name: str
|
|
67
|
+
parameters: list[FieldDef]
|
|
68
|
+
return_type: str
|
|
69
|
+
uses: list[UseDecl]
|
|
70
|
+
attributes: dict[str, Any]
|
|
71
|
+
span: SourceSpan
|
|
72
|
+
|
|
73
|
+
def list_attr(self, key: str) -> list[str]:
|
|
74
|
+
value = self.attributes.get(key, [])
|
|
75
|
+
return value if isinstance(value, list) else []
|
|
76
|
+
|
|
77
|
+
def text_attr(self, key: str) -> str:
|
|
78
|
+
value = self.attributes.get(key, "")
|
|
79
|
+
return value if isinstance(value, str) else ""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True)
|
|
83
|
+
class EvalCase:
|
|
84
|
+
name: str
|
|
85
|
+
agent: str
|
|
86
|
+
givens: dict[str, str]
|
|
87
|
+
expects: list[str]
|
|
88
|
+
semantic_expects: list[str]
|
|
89
|
+
span: SourceSpan
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass(frozen=True)
|
|
93
|
+
class MonitorDef:
|
|
94
|
+
name: str
|
|
95
|
+
agent: str
|
|
96
|
+
severity: str
|
|
97
|
+
condition: str
|
|
98
|
+
expectation: str
|
|
99
|
+
span: SourceSpan
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class ContractModule:
|
|
104
|
+
path: Path
|
|
105
|
+
types: list[TypeDef] = field(default_factory=list)
|
|
106
|
+
datasources: list[DatasourceDef] = field(default_factory=list)
|
|
107
|
+
agents: list[AgentDef] = field(default_factory=list)
|
|
108
|
+
evals: list[EvalCase] = field(default_factory=list)
|
|
109
|
+
monitors: list[MonitorDef] = field(default_factory=list)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class ContractProject:
|
|
114
|
+
root: Path
|
|
115
|
+
modules: list[ContractModule]
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def types(self) -> dict[str, TypeDef]:
|
|
119
|
+
return {item.name: item for module in self.modules for item in module.types}
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def datasources(self) -> dict[str, DatasourceDef]:
|
|
123
|
+
return {item.name: item for module in self.modules for item in module.datasources}
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def agents(self) -> dict[str, AgentDef]:
|
|
127
|
+
return {item.name: item for module in self.modules for item in module.agents}
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def evals(self) -> list[EvalCase]:
|
|
131
|
+
return [item for module in self.modules for item in module.evals]
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def monitors(self) -> list[MonitorDef]:
|
|
135
|
+
return [item for module in self.modules for item in module.monitors]
|