catalyst-tracing 0.0.1__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.
- catalyst_tracing-0.0.1/.gitignore +7 -0
- catalyst_tracing-0.0.1/PKG-INFO +26 -0
- catalyst_tracing-0.0.1/README.md +201 -0
- catalyst_tracing-0.0.1/pyproject.toml +59 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/__init__.py +94 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/agent_span.py +199 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/anthropic.py +17 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/claude_agent_sdk.py +21 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/constants.py +122 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/env.py +67 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/errors.py +134 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/exporter.py +60 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/installers/__init__.py +0 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/installers/anthropic.py +49 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/installers/base.py +48 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/installers/claude_agent_sdk.py +51 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/installers/langchain.py +49 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/installers/langgraph.py +42 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/installers/langsmith.py +125 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/installers/openai.py +60 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/installers/openai_agents.py +62 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/installers/pydantic_ai.py +43 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/instrumentation/__init__.py +0 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/instrumentation/anthropic.py +400 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/instrumentation/claude_agent_sdk.py +274 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/instrumentation/langchain.py +474 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/instrumentation/openai.py +573 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/instrumentation/stream_accumulator.py +387 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/langchain.py +17 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/langgraph.py +7 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/langsmith.py +7 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/openai.py +21 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/openai_agents.py +21 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/pydantic_ai.py +21 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/semconv.py +181 -0
- catalyst_tracing-0.0.1/src/catalyst_tracing/setup.py +288 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: catalyst-tracing
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Catalyst tracing SDK for Python. OpenInference-shaped instrumentation for openai, anthropic, langchain, langgraph, langsmith, openai-agents, claude-agent-sdk, and pydantic-ai.
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: opentelemetry-api>=1.28.0
|
|
7
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-common>=1.28.0
|
|
8
|
+
Requires-Dist: opentelemetry-sdk>=1.28.0
|
|
9
|
+
Requires-Dist: protobuf>=5.0.0
|
|
10
|
+
Requires-Dist: requests>=2.32.0
|
|
11
|
+
Provides-Extra: anthropic
|
|
12
|
+
Requires-Dist: anthropic>=0.30.0; extra == 'anthropic'
|
|
13
|
+
Provides-Extra: claude-agent-sdk
|
|
14
|
+
Requires-Dist: claude-agent-sdk>=0.0.1; extra == 'claude-agent-sdk'
|
|
15
|
+
Provides-Extra: langchain
|
|
16
|
+
Requires-Dist: langchain>=0.3.0; extra == 'langchain'
|
|
17
|
+
Provides-Extra: langgraph
|
|
18
|
+
Requires-Dist: langgraph>=0.6.0; extra == 'langgraph'
|
|
19
|
+
Provides-Extra: langsmith
|
|
20
|
+
Requires-Dist: langsmith>=0.3.15; extra == 'langsmith'
|
|
21
|
+
Provides-Extra: openai
|
|
22
|
+
Requires-Dist: openai>=1.54.0; extra == 'openai'
|
|
23
|
+
Provides-Extra: openai-agents
|
|
24
|
+
Requires-Dist: openai-agents>=0.0.5; extra == 'openai-agents'
|
|
25
|
+
Provides-Extra: pydantic-ai
|
|
26
|
+
Requires-Dist: pydantic-ai>=0.0.50; extra == 'pydantic-ai'
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# `catalyst-tracing` (Python)
|
|
2
|
+
|
|
3
|
+
First-party tracing for LLM apps on CPython 3.11+.
|
|
4
|
+
OpenInference-shaped spans, OTLP/JSON export to a catalyst ingest, no
|
|
5
|
+
`openinference-instrumentation-*` runtime dependencies.
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from catalyst_tracing import setup
|
|
11
|
+
from openai import OpenAI
|
|
12
|
+
|
|
13
|
+
tracing = setup()
|
|
14
|
+
|
|
15
|
+
client = OpenAI(api_key=...)
|
|
16
|
+
client.chat.completions.create(
|
|
17
|
+
model="gpt-4o-mini",
|
|
18
|
+
messages=[{"role": "user", "content": "hello"}],
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
tracing.shutdown()
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
A single `OpenAI Chat Completions` span lands at your catalyst ingest
|
|
25
|
+
with the full OpenInference attribute set.
|
|
26
|
+
|
|
27
|
+
## Install with extras
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install 'catalyst-tracing[openai]'
|
|
31
|
+
pip install 'catalyst-tracing[openai,anthropic]'
|
|
32
|
+
pip install 'catalyst-tracing[langchain]'
|
|
33
|
+
pip install 'catalyst-tracing[langgraph]'
|
|
34
|
+
pip install 'catalyst-tracing[langsmith]'
|
|
35
|
+
pip install 'catalyst-tracing[claude-agent-sdk]'
|
|
36
|
+
pip install 'catalyst-tracing[pydantic-ai]'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The extras pull in the upstream LLM SDKs themselves. The
|
|
40
|
+
instrumentation runtime ships with the base package — no additional
|
|
41
|
+
deps required for tracing.
|
|
42
|
+
|
|
43
|
+
## Supported SDKs
|
|
44
|
+
|
|
45
|
+
| SDK | Patches |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `openai` | `Completions.create` / `AsyncCompletions.create` and `Responses.create` / `AsyncResponses.create` |
|
|
48
|
+
| `anthropic` | `Messages.create` / `AsyncMessages.create` |
|
|
49
|
+
| `langchain` | Hooks `BaseCallbackManager.__init__` so every callback manager picks up our handler |
|
|
50
|
+
| `langgraph` | Uses the LangChain callback manager and preserves graph/node parent-child spans |
|
|
51
|
+
| `langsmith` | Bridges LangSmith OTel tracing into the Catalyst provider for `traceable` and LangSmith framework integrations |
|
|
52
|
+
| `openai-agents` | The agents SDK calls `openai` under the hood — install both extras and you're covered |
|
|
53
|
+
| `claude-agent-sdk` | Patches `claude_agent_sdk.query.query` AND the package-level `claude_agent_sdk.query` snapshot, so top-level `from claude_agent_sdk import query` works |
|
|
54
|
+
| `pydantic-ai` | Native first-party OTel — we just call `Agent.instrument_all()` for you |
|
|
55
|
+
|
|
56
|
+
## Per-SDK entry-point imports
|
|
57
|
+
|
|
58
|
+
If you only need one or two SDKs, import per-SDK to keep the import
|
|
59
|
+
graph small and skip the multi-module orchestration in `setup()`:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from catalyst_tracing import setup
|
|
63
|
+
from catalyst_tracing.openai import install_openai
|
|
64
|
+
|
|
65
|
+
tracing = setup()
|
|
66
|
+
install_openai(tracing.provider)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Available entry points:
|
|
70
|
+
|
|
71
|
+
| Import | Exports | Use when |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `catalyst_tracing` | `setup`, `agent_span`, `Attr`, `SpanKindValues`, error types | You want everything from one import. |
|
|
74
|
+
| `catalyst_tracing.openai` | `install_openai` | Only OpenAI. |
|
|
75
|
+
| `catalyst_tracing.anthropic` | `install_anthropic` | Only Anthropic. |
|
|
76
|
+
| `catalyst_tracing.langchain` | `install_langchain` | Only LangChain. |
|
|
77
|
+
| `catalyst_tracing.langgraph` | `install_langgraph` | Only LangGraph. |
|
|
78
|
+
| `catalyst_tracing.langsmith` | `install_langsmith` | Bridge existing LangSmith OTel tracing into Catalyst. |
|
|
79
|
+
| `catalyst_tracing.openai_agents` | `install_openai_agents` | Only `agents` (OpenAI's). |
|
|
80
|
+
| `catalyst_tracing.claude_agent_sdk` | `install_claude_agent_sdk` | Only the Claude Agent SDK. |
|
|
81
|
+
| `catalyst_tracing.pydantic_ai` | `install_pydantic_ai` | Only Pydantic-AI. |
|
|
82
|
+
|
|
83
|
+
Every `install_*` helper takes a `TracerProvider` (the standard OTel
|
|
84
|
+
type, what `tracing.provider` is). It returns an `InstrumentResult`
|
|
85
|
+
on success, raises `InvalidTracerProviderError` on misuse.
|
|
86
|
+
|
|
87
|
+
## Internal source layout
|
|
88
|
+
|
|
89
|
+
The package separates public entry points from installation glue and
|
|
90
|
+
low-level patchers:
|
|
91
|
+
|
|
92
|
+
| Path | Purpose |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `src/catalyst_tracing/*.py` | Public package and per-SDK entry-point modules. Keep these thin and stable. |
|
|
95
|
+
| `src/catalyst_tracing/installers/` | `setup()` integration adapters. These validate optional SDK modules, call the patchers, and report install results. |
|
|
96
|
+
| `src/catalyst_tracing/instrumentation/` | Low-level SDK patchers and shared extraction helpers. This is where OpenInference attribute extraction belongs. |
|
|
97
|
+
|
|
98
|
+
## Errors
|
|
99
|
+
|
|
100
|
+
The package raises typed errors for misuse so you can catch and
|
|
101
|
+
branch programmatically:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from catalyst_tracing import (
|
|
105
|
+
CatalystTracingError, InvalidTracerProviderError,
|
|
106
|
+
)
|
|
107
|
+
from catalyst_tracing.openai import install_openai
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
install_openai(provider)
|
|
111
|
+
except InvalidTracerProviderError as err:
|
|
112
|
+
# err.code == "ERR_INVALID_TRACER_PROVIDER"
|
|
113
|
+
# err.integration == "openai"
|
|
114
|
+
# str(err) carries a "Hint:" line
|
|
115
|
+
raise
|
|
116
|
+
except CatalystTracingError:
|
|
117
|
+
raise # base class — catch this for "did the package raise?"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Each `install_*` returns a frozen `InstrumentResult` dataclass:
|
|
121
|
+
|
|
122
|
+
| Field | Type | Meaning |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| `name` | `str` | Integration name. |
|
|
125
|
+
| `installed` | `bool` | Whether the patch took. |
|
|
126
|
+
| `code` | `Literal["INSTALLED", "SDK_NOT_INSTALLED", "SDK_VERSION_INCOMPATIBLE"]` | Stable code for branching. |
|
|
127
|
+
| `reason` | `str | None` | Human-readable reason; `None` on success. |
|
|
128
|
+
|
|
129
|
+
## Manual spans
|
|
130
|
+
|
|
131
|
+
For CLI subprocess wrappers and custom logic, `agent_span` is a
|
|
132
|
+
context manager:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from catalyst_tracing import agent_span, setup
|
|
136
|
+
|
|
137
|
+
tracing = setup()
|
|
138
|
+
|
|
139
|
+
with agent_span(tracing.tracer, name="ClaudeCode", system="anthropic") as span:
|
|
140
|
+
span.set_input(prompt)
|
|
141
|
+
answer = run_cli(prompt)
|
|
142
|
+
span.set_output(answer)
|
|
143
|
+
span.record_tokens(prompt=120, completion=40)
|
|
144
|
+
|
|
145
|
+
tracing.shutdown()
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The body runs inside the span's active OTel context, so any child
|
|
149
|
+
spans created by instrumented LLM SDKs auto-parent under it.
|
|
150
|
+
|
|
151
|
+
## Configuration
|
|
152
|
+
|
|
153
|
+
| Option (programmatic) | Env var | Default |
|
|
154
|
+
|---|---|---|
|
|
155
|
+
| `endpoint` | `CATALYST_OTLP_ENDPOINT` (legacy `OTLP_ENDPOINT`) | `http://localhost:8799` |
|
|
156
|
+
| `token` | `CATALYST_OTLP_TOKEN` (legacy `OTLP_INGEST_TOKEN`) | — |
|
|
157
|
+
| `service_name` | `CATALYST_SERVICE_NAME` (legacy `SERVICE_NAME`) | `catalyst-app-${random8}` |
|
|
158
|
+
| `service_version` | `CATALYST_SERVICE_VERSION` | `0.0.1` |
|
|
159
|
+
| `debug` | `CATALYST_DEBUG` (`1`/`true`/`yes`) | `false` |
|
|
160
|
+
| `batching` | — | `"batch"` |
|
|
161
|
+
|
|
162
|
+
## Wire format
|
|
163
|
+
|
|
164
|
+
Every span carries the OpenInference attribute set —
|
|
165
|
+
`openinference.span.kind`, `input.value`, `output.value`,
|
|
166
|
+
`llm.input_messages.{i}.message.{role,content}`,
|
|
167
|
+
`llm.output_messages.{i}.message.{role,content}`,
|
|
168
|
+
`llm.invocation_parameters`, `llm.model_name`, `llm.token_count.*`,
|
|
169
|
+
`llm.finish_reason`, `gen_ai.system`. OI-aware viewers can render our
|
|
170
|
+
spans without configuration.
|
|
171
|
+
|
|
172
|
+
The constants live in `Attr` and `SpanKindValues`:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from catalyst_tracing import Attr, SpanKindValues
|
|
176
|
+
|
|
177
|
+
span.set_attribute(Attr.SPAN_KIND, SpanKindValues.LLM.value)
|
|
178
|
+
span.set_attribute(Attr.MODEL_NAME, "gpt-4o-mini")
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Testing
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# From the repository root, run the full cross-language gate.
|
|
185
|
+
task check
|
|
186
|
+
|
|
187
|
+
# From this package, run the Python package tests directly.
|
|
188
|
+
uv run pytest
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Integration tests exercise the real openai/anthropic/langchain/langgraph/langsmith SDKs
|
|
192
|
+
against `respx`-mocked httpx (no API keys required). The
|
|
193
|
+
`claude-agent-sdk` integration test imports the real message types
|
|
194
|
+
and asserts our wrapper handles them correctly.
|
|
195
|
+
|
|
196
|
+
## See also
|
|
197
|
+
|
|
198
|
+
- `docs/internal/architecture/catalyst-tracing-package-plan.md` — design doc
|
|
199
|
+
- `docs/internal/architecture/claude-agent-sdk-frozen-namespace.md` — TS-specific note
|
|
200
|
+
on why we ship `wrapClaudeAgentSdkQuery` instead of mutating the
|
|
201
|
+
ESM namespace
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "catalyst-tracing"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "Catalyst tracing SDK for Python. OpenInference-shaped instrumentation for openai, anthropic, langchain, langgraph, langsmith, openai-agents, claude-agent-sdk, and pydantic-ai."
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"opentelemetry-api>=1.28.0",
|
|
8
|
+
"opentelemetry-sdk>=1.28.0",
|
|
9
|
+
"opentelemetry-exporter-otlp-proto-common>=1.28.0",
|
|
10
|
+
"protobuf>=5.0.0",
|
|
11
|
+
"requests>=2.32.0",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[project.optional-dependencies]
|
|
15
|
+
openai = ["openai>=1.54.0"]
|
|
16
|
+
anthropic = ["anthropic>=0.30.0"]
|
|
17
|
+
langchain = ["langchain>=0.3.0"]
|
|
18
|
+
langgraph = ["langgraph>=0.6.0"]
|
|
19
|
+
langsmith = ["langsmith>=0.3.15"]
|
|
20
|
+
openai-agents = ["openai-agents>=0.0.5"]
|
|
21
|
+
claude-agent-sdk = ["claude-agent-sdk>=0.0.1"]
|
|
22
|
+
pydantic-ai = ["pydantic-ai>=0.0.50"]
|
|
23
|
+
|
|
24
|
+
[dependency-groups]
|
|
25
|
+
dev = [
|
|
26
|
+
"pytest>=8.0.0",
|
|
27
|
+
"pytest-asyncio>=0.23.0",
|
|
28
|
+
"respx>=0.21.0",
|
|
29
|
+
# Real SDKs imported by the integration tests. Optional in
|
|
30
|
+
# production (declared via [project.optional-dependencies]); we
|
|
31
|
+
# add them here so the test suite can exercise the real APIs
|
|
32
|
+
# against mocked HTTP backends.
|
|
33
|
+
"openai>=1.54.0",
|
|
34
|
+
"anthropic>=0.30.0",
|
|
35
|
+
"langchain>=0.3.0",
|
|
36
|
+
"langchain-core>=0.3.0",
|
|
37
|
+
"langgraph>=0.6.0",
|
|
38
|
+
"langsmith>=0.3.15",
|
|
39
|
+
"claude-agent-sdk>=0.1.0",
|
|
40
|
+
"pydantic-ai>=0.0.50",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[build-system]
|
|
44
|
+
requires = ["hatchling"]
|
|
45
|
+
build-backend = "hatchling.build"
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build.targets.wheel]
|
|
48
|
+
packages = ["src/catalyst_tracing"]
|
|
49
|
+
|
|
50
|
+
[tool.hatch.build.targets.sdist]
|
|
51
|
+
exclude = [
|
|
52
|
+
"/.venv",
|
|
53
|
+
"/tests",
|
|
54
|
+
"/uv.lock",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[tool.pytest.ini_options]
|
|
58
|
+
testpaths = ["tests"]
|
|
59
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""``catalyst_tracing`` — first-party tracing for LLM apps.
|
|
2
|
+
|
|
3
|
+
Drop-in OpenInference-shaped instrumentation for the SDKs your app
|
|
4
|
+
uses (openai, anthropic, langchain, langgraph, langsmith,
|
|
5
|
+
openai-agents, claude-agent-sdk, pydantic-ai), plus a small ergonomic helper layer for cases where
|
|
6
|
+
you need to author spans by hand.
|
|
7
|
+
|
|
8
|
+
Quick start::
|
|
9
|
+
|
|
10
|
+
from catalyst_tracing import setup
|
|
11
|
+
from openai import OpenAI
|
|
12
|
+
|
|
13
|
+
tracing = setup()
|
|
14
|
+
client = OpenAI(api_key=...)
|
|
15
|
+
client.chat.completions.create(model=..., messages=...)
|
|
16
|
+
tracing.shutdown()
|
|
17
|
+
|
|
18
|
+
What this package owns
|
|
19
|
+
----------------------
|
|
20
|
+
|
|
21
|
+
- **OTLP plumbing** — endpoint, token, service-name resource, batch
|
|
22
|
+
vs. simple processor selection, env-var resolution with sensible
|
|
23
|
+
defaults including legacy ``OTLP_*`` fallback.
|
|
24
|
+
- **First-party OpenInference instrumentation** for openai, anthropic,
|
|
25
|
+
langchain, langgraph, openai-agents, claude-agent-sdk, plus bridges
|
|
26
|
+
for langsmith and pydantic-ai's native OTel. No
|
|
27
|
+
``openinference-instrumentation-*`` runtime dependency.
|
|
28
|
+
- **Helpers**: :func:`agent_span` for manual AGENT spans;
|
|
29
|
+
:class:`Attr` and :class:`SpanKindValues` for the OI semantic-
|
|
30
|
+
convention constants you'd want when authoring custom spans.
|
|
31
|
+
|
|
32
|
+
What this package doesn't own
|
|
33
|
+
-----------------------------
|
|
34
|
+
|
|
35
|
+
- The SDKs themselves. They're optional extras — pull in only the
|
|
36
|
+
ones you use::
|
|
37
|
+
|
|
38
|
+
pip install 'catalyst-tracing[openai,anthropic]'
|
|
39
|
+
|
|
40
|
+
- Shipping spans somewhere other than OTLP/HTTP/JSON. If you need a
|
|
41
|
+
different transport, build your own exporter and add it to
|
|
42
|
+
``tracing.provider``.
|
|
43
|
+
|
|
44
|
+
Public API
|
|
45
|
+
----------
|
|
46
|
+
|
|
47
|
+
* :func:`setup` — bootstrap tracing, returns a
|
|
48
|
+
:class:`CatalystTracing` handle.
|
|
49
|
+
* :func:`agent_span` — context manager that wraps work in an
|
|
50
|
+
OI-shaped AGENT span.
|
|
51
|
+
* :class:`Attr`, :class:`SpanKindValues` — wire-format constants
|
|
52
|
+
shared across instrumentation and consumer-authored spans.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
from .agent_span import AgentSpanHandle, agent_span
|
|
56
|
+
from .errors import (
|
|
57
|
+
CatalystTracingError,
|
|
58
|
+
CatalystTracingErrorCode,
|
|
59
|
+
CatalystTracingErrorCodes,
|
|
60
|
+
InvalidSdkInputError,
|
|
61
|
+
InvalidTracerProviderError,
|
|
62
|
+
)
|
|
63
|
+
from .semconv import Attr, MessageRole, OpenInferenceAttribute, SpanKindValues
|
|
64
|
+
from .setup import CatalystTracing, InstrumentResult, setup
|
|
65
|
+
|
|
66
|
+
__version__ = "0.0.1"
|
|
67
|
+
PACKAGE_NAME = "catalyst-tracing"
|
|
68
|
+
IMPORT_NAME = "catalyst_tracing"
|
|
69
|
+
COMPANY_NAME = "Inference"
|
|
70
|
+
PLATFORM_NAME = "Catalyst"
|
|
71
|
+
PRODUCT_NAME = "Catalyst by Inference.net"
|
|
72
|
+
|
|
73
|
+
__all__ = [
|
|
74
|
+
"AgentSpanHandle",
|
|
75
|
+
"Attr",
|
|
76
|
+
"CatalystTracing",
|
|
77
|
+
"CatalystTracingError",
|
|
78
|
+
"CatalystTracingErrorCode",
|
|
79
|
+
"CatalystTracingErrorCodes",
|
|
80
|
+
"COMPANY_NAME",
|
|
81
|
+
"IMPORT_NAME",
|
|
82
|
+
"InstrumentResult",
|
|
83
|
+
"InvalidSdkInputError",
|
|
84
|
+
"InvalidTracerProviderError",
|
|
85
|
+
"MessageRole",
|
|
86
|
+
"OpenInferenceAttribute",
|
|
87
|
+
"PACKAGE_NAME",
|
|
88
|
+
"PLATFORM_NAME",
|
|
89
|
+
"PRODUCT_NAME",
|
|
90
|
+
"SpanKindValues",
|
|
91
|
+
"__version__",
|
|
92
|
+
"agent_span",
|
|
93
|
+
"setup",
|
|
94
|
+
]
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
""":func:`agent_span` — context manager that wraps work in an
|
|
2
|
+
OpenInference-flavored AGENT span.
|
|
3
|
+
|
|
4
|
+
Use this when you have a chunk of "agent work" — a CLI subprocess
|
|
5
|
+
call, a multi-step Pydantic AI run, the outer wrapper around an
|
|
6
|
+
``agents-sdk`` ``Runner.run()`` — and you want it to show up in
|
|
7
|
+
the trace viewer as a single AGENT-kind row with input/output and
|
|
8
|
+
optional token counts.
|
|
9
|
+
|
|
10
|
+
The context manager:
|
|
11
|
+
|
|
12
|
+
* Sets ``openinference.span.kind=AGENT``, ``agent.name``,
|
|
13
|
+
``gen_ai.system`` automatically.
|
|
14
|
+
* Runs the body inside the span's active OTel context, so child
|
|
15
|
+
spans created by instrumented LLM SDKs auto-parent to this
|
|
16
|
+
AGENT span. (That's how a full trace tree forms.)
|
|
17
|
+
* Ends with ``OK`` on success, records the exception and ends with
|
|
18
|
+
``ERROR`` on failure (re-raising so the caller's error handling
|
|
19
|
+
is unaffected).
|
|
20
|
+
|
|
21
|
+
Example
|
|
22
|
+
-------
|
|
23
|
+
Wrapping a CLI subprocess::
|
|
24
|
+
|
|
25
|
+
from catalyst_tracing import agent_span, setup
|
|
26
|
+
|
|
27
|
+
tracing = setup()
|
|
28
|
+
with agent_span(tracing.tracer, name="ClaudeCode", system="anthropic") as span:
|
|
29
|
+
span.set_input(prompt)
|
|
30
|
+
answer = run_cli(prompt)
|
|
31
|
+
span.set_output(answer)
|
|
32
|
+
span.record_tokens(prompt=120, completion=40)
|
|
33
|
+
|
|
34
|
+
Wrapping an `openai-agents` runner so its LLM-call spans nest
|
|
35
|
+
under the AGENT span::
|
|
36
|
+
|
|
37
|
+
from catalyst_tracing import agent_span, setup
|
|
38
|
+
from agents import Agent, Runner
|
|
39
|
+
|
|
40
|
+
tracing = setup()
|
|
41
|
+
agent = Agent(name="SupportAgent", instructions=..., tools=[...])
|
|
42
|
+
|
|
43
|
+
with agent_span(tracing.tracer, name="SupportAgent", system="openai") as span:
|
|
44
|
+
span.set_input(user_message)
|
|
45
|
+
result = await Runner.run(agent, input=user_message)
|
|
46
|
+
span.set_output(str(result.final_output))
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
from __future__ import annotations
|
|
50
|
+
|
|
51
|
+
import json
|
|
52
|
+
from collections.abc import Iterator
|
|
53
|
+
from contextlib import contextmanager
|
|
54
|
+
from dataclasses import dataclass
|
|
55
|
+
from typing import Any
|
|
56
|
+
|
|
57
|
+
from opentelemetry.trace import Span, SpanKind, Status, StatusCode, Tracer
|
|
58
|
+
|
|
59
|
+
from .semconv import Attr, SpanKindValues
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class AgentSpanHandle:
|
|
64
|
+
"""The handle yielded by :func:`agent_span`.
|
|
65
|
+
|
|
66
|
+
Provides type-safe helpers for the OI attribute set the agent
|
|
67
|
+
span should carry, plus a ``span`` escape hatch for callers
|
|
68
|
+
that need the underlying OTel span (e.g. to add custom
|
|
69
|
+
attributes outside the OI vocabulary).
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
#: The underlying OTel span — escape hatch for advanced use.
|
|
73
|
+
span: Span
|
|
74
|
+
|
|
75
|
+
def set_input(self, value: Any) -> None:
|
|
76
|
+
"""Record the agent's input.
|
|
77
|
+
|
|
78
|
+
Strings are stored as-is on ``input.value``. Non-strings
|
|
79
|
+
are JSON-stringified.
|
|
80
|
+
"""
|
|
81
|
+
self.span.set_attribute(
|
|
82
|
+
Attr.INPUT_VALUE,
|
|
83
|
+
value if isinstance(value, str) else json.dumps(value),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def set_output(self, value: Any) -> None:
|
|
87
|
+
"""Record the agent's final output.
|
|
88
|
+
|
|
89
|
+
Same string-vs-JSON rule as :meth:`set_input`.
|
|
90
|
+
"""
|
|
91
|
+
self.span.set_attribute(
|
|
92
|
+
Attr.OUTPUT_VALUE,
|
|
93
|
+
value if isinstance(value, str) else json.dumps(value),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def record_tokens(
|
|
97
|
+
self,
|
|
98
|
+
*,
|
|
99
|
+
prompt: int | None = None,
|
|
100
|
+
completion: int | None = None,
|
|
101
|
+
total: int | None = None,
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Record token usage on the agent span.
|
|
104
|
+
|
|
105
|
+
Any of ``prompt``, ``completion``, ``total`` may be omitted;
|
|
106
|
+
only the provided fields are written. Use this when you have
|
|
107
|
+
aggregated counts across multiple LLM calls inside the agent
|
|
108
|
+
loop — per-call counts get captured by the LLM-level
|
|
109
|
+
instrumentation separately.
|
|
110
|
+
"""
|
|
111
|
+
if prompt is not None:
|
|
112
|
+
self.span.set_attribute(Attr.TOKEN_COUNT_PROMPT, prompt)
|
|
113
|
+
if completion is not None:
|
|
114
|
+
self.span.set_attribute(Attr.TOKEN_COUNT_COMPLETION, completion)
|
|
115
|
+
if total is not None:
|
|
116
|
+
self.span.set_attribute(Attr.TOKEN_COUNT_TOTAL, total)
|
|
117
|
+
|
|
118
|
+
def set_model(self, model: str) -> None:
|
|
119
|
+
"""Record the model name (becomes ``llm.model_name``)."""
|
|
120
|
+
self.span.set_attribute(Attr.MODEL_NAME, model)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@contextmanager
|
|
124
|
+
def agent_span(
|
|
125
|
+
tracer: Tracer,
|
|
126
|
+
*,
|
|
127
|
+
name: str,
|
|
128
|
+
system: str | None = None,
|
|
129
|
+
span_kind: SpanKindValues | str = SpanKindValues.AGENT,
|
|
130
|
+
otel_kind: SpanKind = SpanKind.INTERNAL,
|
|
131
|
+
span_name: str | None = None,
|
|
132
|
+
) -> Iterator[AgentSpanHandle]:
|
|
133
|
+
"""Wrap a chunk of agent work in an OI-shaped AGENT span.
|
|
134
|
+
|
|
135
|
+
Parameters
|
|
136
|
+
----------
|
|
137
|
+
tracer : Tracer
|
|
138
|
+
OpenTelemetry tracer to author the span from. Use the one
|
|
139
|
+
on :class:`CatalystTracing.tracer`.
|
|
140
|
+
name : str
|
|
141
|
+
Logical agent name — e.g. ``"ClaudeCode"``,
|
|
142
|
+
``"WeatherAgent"``. Becomes the ``agent.name`` attribute
|
|
143
|
+
and the prefix of the default span name.
|
|
144
|
+
system : str, optional
|
|
145
|
+
Identifier of the LLM provider this agent runs on, e.g.
|
|
146
|
+
``"openai"`` or ``"anthropic"``. Becomes the
|
|
147
|
+
``gen_ai.system`` attribute. Optional — omit if the agent
|
|
148
|
+
is provider-agnostic.
|
|
149
|
+
span_kind : SpanKindValues or str, default AGENT
|
|
150
|
+
OpenInference span kind. Pass another
|
|
151
|
+
:class:`SpanKindValues` (e.g. ``CHAIN``) when this helper
|
|
152
|
+
is used outside a strictly agent context.
|
|
153
|
+
otel_kind : SpanKind, default INTERNAL
|
|
154
|
+
OTel-level span kind. ``INTERNAL`` because the work itself
|
|
155
|
+
is internal to this process; the LLM-call children get
|
|
156
|
+
their own ``CLIENT`` kind from the per-SDK patchers.
|
|
157
|
+
span_name : str, optional
|
|
158
|
+
Override the auto-generated span name (``f"{name}.run"``).
|
|
159
|
+
Useful when "run" doesn't fit — e.g. our examples use
|
|
160
|
+
``"claude-code.invocation"`` for one-shot CLI calls.
|
|
161
|
+
|
|
162
|
+
Yields
|
|
163
|
+
------
|
|
164
|
+
AgentSpanHandle
|
|
165
|
+
Helpers to set input/output/tokens/model on the span.
|
|
166
|
+
|
|
167
|
+
Notes
|
|
168
|
+
-----
|
|
169
|
+
The span is closed automatically when the ``with`` block
|
|
170
|
+
exits. On unhandled exceptions the span ends with ``ERROR``
|
|
171
|
+
status and an ``exception`` event recording the traceback,
|
|
172
|
+
then the exception re-raises so caller error handling is
|
|
173
|
+
unaffected.
|
|
174
|
+
"""
|
|
175
|
+
resolved_span_name = span_name or f"{name}.run"
|
|
176
|
+
resolved_kind_value = (
|
|
177
|
+
span_kind.value if isinstance(span_kind, SpanKindValues) else span_kind
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
attributes: dict[str, str] = {
|
|
181
|
+
Attr.SPAN_KIND: resolved_kind_value,
|
|
182
|
+
Attr.AGENT_NAME: name,
|
|
183
|
+
}
|
|
184
|
+
if system is not None:
|
|
185
|
+
attributes[Attr.SYSTEM] = system
|
|
186
|
+
|
|
187
|
+
with tracer.start_as_current_span(
|
|
188
|
+
resolved_span_name,
|
|
189
|
+
kind=otel_kind,
|
|
190
|
+
attributes=attributes,
|
|
191
|
+
) as span:
|
|
192
|
+
handle = AgentSpanHandle(span=span)
|
|
193
|
+
try:
|
|
194
|
+
yield handle
|
|
195
|
+
span.set_status(Status(StatusCode.OK))
|
|
196
|
+
except Exception as err:
|
|
197
|
+
span.record_exception(err)
|
|
198
|
+
span.set_status(Status(StatusCode.ERROR, str(err)))
|
|
199
|
+
raise
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""``catalyst_tracing.anthropic`` — granular entry-point import for the
|
|
2
|
+
Anthropic integration.
|
|
3
|
+
|
|
4
|
+
Example::
|
|
5
|
+
|
|
6
|
+
from catalyst_tracing import setup
|
|
7
|
+
from catalyst_tracing.anthropic import install_anthropic
|
|
8
|
+
|
|
9
|
+
tracing = setup()
|
|
10
|
+
install_anthropic(tracing.provider)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from .installers.anthropic import install_anthropic
|
|
16
|
+
|
|
17
|
+
__all__ = ["install_anthropic"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""``catalyst_tracing.claude_agent_sdk`` — granular entry-point import
|
|
2
|
+
for the Claude Agent SDK integration.
|
|
3
|
+
|
|
4
|
+
Example::
|
|
5
|
+
|
|
6
|
+
from catalyst_tracing import setup
|
|
7
|
+
from catalyst_tracing.claude_agent_sdk import install_claude_agent_sdk
|
|
8
|
+
|
|
9
|
+
tracing = setup()
|
|
10
|
+
install_claude_agent_sdk(tracing.provider)
|
|
11
|
+
|
|
12
|
+
# `from claude_agent_sdk import query` continues to read the
|
|
13
|
+
# wrapped function. The patch covers both the package-level and
|
|
14
|
+
# submodule bindings.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from .installers.claude_agent_sdk import install_claude_agent_sdk
|
|
20
|
+
|
|
21
|
+
__all__ = ["install_claude_agent_sdk"]
|