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.
Files changed (36) hide show
  1. catalyst_tracing-0.0.1/.gitignore +7 -0
  2. catalyst_tracing-0.0.1/PKG-INFO +26 -0
  3. catalyst_tracing-0.0.1/README.md +201 -0
  4. catalyst_tracing-0.0.1/pyproject.toml +59 -0
  5. catalyst_tracing-0.0.1/src/catalyst_tracing/__init__.py +94 -0
  6. catalyst_tracing-0.0.1/src/catalyst_tracing/agent_span.py +199 -0
  7. catalyst_tracing-0.0.1/src/catalyst_tracing/anthropic.py +17 -0
  8. catalyst_tracing-0.0.1/src/catalyst_tracing/claude_agent_sdk.py +21 -0
  9. catalyst_tracing-0.0.1/src/catalyst_tracing/constants.py +122 -0
  10. catalyst_tracing-0.0.1/src/catalyst_tracing/env.py +67 -0
  11. catalyst_tracing-0.0.1/src/catalyst_tracing/errors.py +134 -0
  12. catalyst_tracing-0.0.1/src/catalyst_tracing/exporter.py +60 -0
  13. catalyst_tracing-0.0.1/src/catalyst_tracing/installers/__init__.py +0 -0
  14. catalyst_tracing-0.0.1/src/catalyst_tracing/installers/anthropic.py +49 -0
  15. catalyst_tracing-0.0.1/src/catalyst_tracing/installers/base.py +48 -0
  16. catalyst_tracing-0.0.1/src/catalyst_tracing/installers/claude_agent_sdk.py +51 -0
  17. catalyst_tracing-0.0.1/src/catalyst_tracing/installers/langchain.py +49 -0
  18. catalyst_tracing-0.0.1/src/catalyst_tracing/installers/langgraph.py +42 -0
  19. catalyst_tracing-0.0.1/src/catalyst_tracing/installers/langsmith.py +125 -0
  20. catalyst_tracing-0.0.1/src/catalyst_tracing/installers/openai.py +60 -0
  21. catalyst_tracing-0.0.1/src/catalyst_tracing/installers/openai_agents.py +62 -0
  22. catalyst_tracing-0.0.1/src/catalyst_tracing/installers/pydantic_ai.py +43 -0
  23. catalyst_tracing-0.0.1/src/catalyst_tracing/instrumentation/__init__.py +0 -0
  24. catalyst_tracing-0.0.1/src/catalyst_tracing/instrumentation/anthropic.py +400 -0
  25. catalyst_tracing-0.0.1/src/catalyst_tracing/instrumentation/claude_agent_sdk.py +274 -0
  26. catalyst_tracing-0.0.1/src/catalyst_tracing/instrumentation/langchain.py +474 -0
  27. catalyst_tracing-0.0.1/src/catalyst_tracing/instrumentation/openai.py +573 -0
  28. catalyst_tracing-0.0.1/src/catalyst_tracing/instrumentation/stream_accumulator.py +387 -0
  29. catalyst_tracing-0.0.1/src/catalyst_tracing/langchain.py +17 -0
  30. catalyst_tracing-0.0.1/src/catalyst_tracing/langgraph.py +7 -0
  31. catalyst_tracing-0.0.1/src/catalyst_tracing/langsmith.py +7 -0
  32. catalyst_tracing-0.0.1/src/catalyst_tracing/openai.py +21 -0
  33. catalyst_tracing-0.0.1/src/catalyst_tracing/openai_agents.py +21 -0
  34. catalyst_tracing-0.0.1/src/catalyst_tracing/pydantic_ai.py +21 -0
  35. catalyst_tracing-0.0.1/src/catalyst_tracing/semconv.py +181 -0
  36. catalyst_tracing-0.0.1/src/catalyst_tracing/setup.py +288 -0
@@ -0,0 +1,7 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ .uv/
5
+ dist/
6
+ build/
7
+ *.egg-info/
@@ -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"]