agenttic-openai-agents 1.0.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.
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ ascore.db
5
+ report.md
6
+ # build artifacts (scripts/release/pypi.sh writes here)
7
+ dist/
8
+ build/
9
+ *.egg-info/
10
+ # secrets & host-generated deploy artifacts — never commit
11
+ .env
12
+ docker-compose.override.yml
13
+ backups/
@@ -0,0 +1,35 @@
1
+ Metadata-Version: 2.4
2
+ Name: agenttic-openai-agents
3
+ Version: 1.0.0
4
+ Summary: Trace an OpenAI Agents SDK agent onto the Agenttic OTel-GenAI bus (public RunHooks only).
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: agenttic
7
+ Requires-Dist: openai-agents>=0.0.1
8
+ Provides-Extra: dev
9
+ Requires-Dist: pytest>=8.0; extra == 'dev'
10
+ Description-Content-Type: text/markdown
11
+
12
+ # agenttic-openai-agents
13
+
14
+ Trace an [OpenAI Agents SDK](https://github.com/openai/openai-agents-python)
15
+ agent onto the Agenttic OTel-GenAI bus using the SDK's **public `RunHooks`
16
+ lifecycle only** — no monkey-patching, no private internals.
17
+
18
+ ```python
19
+ from agenttic_openai_agents import trace
20
+
21
+ agent = trace(my_agent, agent_id="triage", endpoint="https://agenttic.internal")
22
+ result = await agent.run("hello") # behaviour-identical; spans emitted
23
+ ```
24
+
25
+ Or let Agenttic auto-detect the framework for you:
26
+
27
+ ```python
28
+ from agenttic import trace # pip install agenttic[openai]
29
+ agent = trace(my_agent) # dispatches here automatically
30
+ ```
31
+
32
+ `trace` returns a transparent wrapper: it only observes (Hard Rule 38 —
33
+ behaviour-identical), and the optional `enforce=` argument routes tool calls
34
+ through the Agenttic gateway at the ramp's non-blocking default posture. The span
35
+ builder lives in the `agenttic` distribution; this package is a thin adapter.
@@ -0,0 +1,24 @@
1
+ # agenttic-openai-agents
2
+
3
+ Trace an [OpenAI Agents SDK](https://github.com/openai/openai-agents-python)
4
+ agent onto the Agenttic OTel-GenAI bus using the SDK's **public `RunHooks`
5
+ lifecycle only** — no monkey-patching, no private internals.
6
+
7
+ ```python
8
+ from agenttic_openai_agents import trace
9
+
10
+ agent = trace(my_agent, agent_id="triage", endpoint="https://agenttic.internal")
11
+ result = await agent.run("hello") # behaviour-identical; spans emitted
12
+ ```
13
+
14
+ Or let Agenttic auto-detect the framework for you:
15
+
16
+ ```python
17
+ from agenttic import trace # pip install agenttic[openai]
18
+ agent = trace(my_agent) # dispatches here automatically
19
+ ```
20
+
21
+ `trace` returns a transparent wrapper: it only observes (Hard Rule 38 —
22
+ behaviour-identical), and the optional `enforce=` argument routes tool calls
23
+ through the Agenttic gateway at the ramp's non-blocking default posture. The span
24
+ builder lives in the `agenttic` distribution; this package is a thin adapter.
@@ -0,0 +1,158 @@
1
+ """agenttic-openai-agents — trace an OpenAI Agents SDK agent onto the OTel bus.
2
+
3
+ Two lines for the user::
4
+
5
+ from agenttic_openai_agents import trace
6
+ agent = trace(my_agent, agent_id="triage", endpoint="https://agenttic.internal")
7
+ result = await agent.run("hello")
8
+
9
+ ``trace`` returns a transparent wrapper around the agent whose ``run``/``run_sync``
10
+ inject an Agenttic ``RunHooks`` instance into ``Runner.run(...)`` — the SDK's
11
+ **public** lifecycle-hook extension point. The hooks observe LLM and tool events
12
+ and emit OTel-GenAI spans via :class:`agenttic.ingest.emit.SpanEmitter`. Users who
13
+ drive ``Runner`` themselves can pass :class:`AgentticRunHooks` directly.
14
+
15
+ Guarantees (SPEC-7 Step 36, Hard Rules 31–32): behavior-identical (hooks only
16
+ observe; the wrapper delegates all else to the agent), public-API-only (no
17
+ private-module imports, no monkey-patching), observe-by-default with an optional
18
+ non-blocking ``enforce=`` guard that fails loud without a compiled policy.
19
+ """
20
+ from __future__ import annotations
21
+
22
+ from typing import Any
23
+
24
+ from agenttic.ingest.emit import SpanEmitter
25
+
26
+ try: # public RunHooks lifecycle base; optional at import time
27
+ from agents import RunHooks as _HooksBase
28
+ HAVE_OPENAI_AGENTS = True
29
+ except Exception: # pragma: no cover - exercised only where the SDK is absent
30
+ _HooksBase = object
31
+ HAVE_OPENAI_AGENTS = False
32
+
33
+ __all__ = ["trace", "AgentticRunHooks", "HAVE_OPENAI_AGENTS"]
34
+
35
+
36
+ class AgentticRunHooks(_HooksBase):
37
+ """OpenAI Agents SDK ``RunHooks`` that emit OTel-GenAI spans.
38
+
39
+ The lifecycle methods are async (as the SDK expects) and purely
40
+ observational — they record a span and return None; they never alter the run
41
+ result. Signatures accept ``*args, **kwargs`` defensively so the adapter is
42
+ resilient to minor SDK version differences without reaching into internals."""
43
+
44
+ def __init__(self, emitter: SpanEmitter):
45
+ super().__init__()
46
+ self.emitter = emitter
47
+ self._pending: dict[int, Any] = {}
48
+
49
+ async def on_llm_end(self, context, agent, response, *args, **kwargs):
50
+ usage = _usage(response)
51
+ self.emitter.emit_llm_call(
52
+ system="openai_agents",
53
+ model=_model(agent, response),
54
+ completion=_output_text(response),
55
+ input_tokens=usage.get("input"),
56
+ output_tokens=usage.get("output"))
57
+
58
+ async def on_tool_start(self, context, agent, tool, *args, **kwargs):
59
+ self._pending[id(tool)] = _tool_name(tool)
60
+
61
+ async def on_tool_end(self, context, agent, tool, result=None, *args, **kwargs):
62
+ name = self._pending.pop(id(tool), None) or _tool_name(tool)
63
+ self.emitter.emit_tool_call(tool_name=name, result=result)
64
+
65
+ def flush(self):
66
+ return self.emitter.flush()
67
+
68
+
69
+ # --- defensive extraction --------------------------------------------------
70
+
71
+ def _tool_name(tool) -> str:
72
+ return getattr(tool, "name", None) or getattr(tool, "__name__", None) or "tool"
73
+
74
+
75
+ def _model(agent, response) -> str:
76
+ return (getattr(response, "model", None)
77
+ or getattr(agent, "model", None) and str(getattr(agent, "model"))
78
+ or "")
79
+
80
+
81
+ def _output_text(response) -> str | None:
82
+ for attr in ("output_text", "final_output", "content", "text"):
83
+ v = getattr(response, attr, None)
84
+ if v:
85
+ return str(v)
86
+ return None
87
+
88
+
89
+ def _usage(response) -> dict:
90
+ u = getattr(response, "usage", None)
91
+ if u is None:
92
+ return {}
93
+ return {
94
+ "input": getattr(u, "input_tokens", None) or getattr(u, "prompt_tokens", None),
95
+ "output": getattr(u, "output_tokens", None) or getattr(u, "completion_tokens", None),
96
+ }
97
+
98
+
99
+ class _TracedAgent:
100
+ """Transparent wrapper delegating to the agent; injects tracing hooks on run."""
101
+
102
+ def __init__(self, agent, *, agent_id: str, agent_config_hash: str = "",
103
+ endpoint: str | None = None, auth_header: str | None = None,
104
+ sink: list | None = None, enforce_guard=None):
105
+ self._agent = agent
106
+ self._agent_id = agent_id
107
+ self._agent_config_hash = agent_config_hash
108
+ self._endpoint = endpoint
109
+ self._auth_header = auth_header
110
+ self._sink = sink
111
+ self._enforce_guard = enforce_guard
112
+
113
+ def make_hooks(self) -> AgentticRunHooks:
114
+ emitter = SpanEmitter(
115
+ self._agent_id, agent_config_hash=self._agent_config_hash,
116
+ endpoint=self._endpoint, auth_header=self._auth_header,
117
+ sink=self._sink, scope_name="agenttic_openai_agents")
118
+ return AgentticRunHooks(emitter)
119
+
120
+ async def run(self, input, **kwargs):
121
+ from agents import Runner # public entrypoint
122
+ hooks = self.make_hooks()
123
+ if self._enforce_guard is not None:
124
+ self._enforce_guard.begin()
125
+ try:
126
+ return await Runner.run(self._agent, input, hooks=hooks, **kwargs)
127
+ finally:
128
+ hooks.flush()
129
+ if self._enforce_guard is not None:
130
+ self._enforce_guard.end()
131
+
132
+ def run_sync(self, input, **kwargs):
133
+ from agents import Runner
134
+ hooks = self.make_hooks()
135
+ try:
136
+ return Runner.run_sync(self._agent, input, hooks=hooks, **kwargs)
137
+ finally:
138
+ hooks.flush()
139
+
140
+ def __getattr__(self, item):
141
+ return getattr(self._agent, item)
142
+
143
+
144
+ def trace(agent, *, agent_id: str = "openai-agent", agent_config_hash: str = "",
145
+ endpoint: str | None = None, auth_header: str | None = None,
146
+ sink: list | None = None, enforce: Any = None, reg=None, cfg=None):
147
+ """Wrap an OpenAI Agents SDK agent so its runs emit OTel-GenAI spans.
148
+
149
+ ``enforce`` is off by default. When set, tool calls route through the SPEC-4
150
+ gateway at the ramp's non-blocking default posture; a missing compiled policy
151
+ fails loudly (T36.3)."""
152
+ guard = None
153
+ if enforce:
154
+ from agenttic.enforce.adapter_guard import build_enforce_guard
155
+ guard = build_enforce_guard(agent_id, enforce, reg=reg, cfg=cfg)
156
+ return _TracedAgent(agent, agent_id=agent_id,
157
+ agent_config_hash=agent_config_hash, endpoint=endpoint,
158
+ auth_header=auth_header, sink=sink, enforce_guard=guard)
@@ -0,0 +1,21 @@
1
+ [project]
2
+ name = "agenttic-openai-agents"
3
+ version = "1.0.0"
4
+ description = "Trace an OpenAI Agents SDK agent onto the Agenttic OTel-GenAI bus (public RunHooks only)."
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ # Thin adapter: the span builder + optional enforcement guard live in the
8
+ # `agenttic` umbrella distribution (which ships the internal ascore package);
9
+ # the OpenAI Agents SDK provides the public RunHooks lifecycle. The SDK is a peer
10
+ # dependency of the user's app.
11
+ dependencies = ["agenttic", "openai-agents>=0.0.1"]
12
+
13
+ [project.optional-dependencies]
14
+ dev = ["pytest>=8.0"]
15
+
16
+ [build-system]
17
+ requires = ["hatchling"]
18
+ build-backend = "hatchling.build"
19
+
20
+ [tool.hatch.build.targets.wheel]
21
+ packages = ["agenttic_openai_agents"]