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"]
|