Metadata-Version: 2.4
Name: catalyst-tracing
Version: 0.1.3
Summary: First-party OpenInference-shaped tracing for Python LLM and agent applications on Catalyst by Inference.net.
Project-URL: Homepage, https://inference.net
Keywords: agents,ai,catalyst,inference,llm,observability,openinference,opentelemetry,tracing
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Monitoring
Requires-Python: >=3.10
Requires-Dist: opentelemetry-api>=1.18.0
Requires-Dist: opentelemetry-exporter-otlp-proto-common>=1.18.0
Requires-Dist: opentelemetry-sdk>=1.18.0
Requires-Dist: protobuf>=3.20.2
Requires-Dist: requests>=2.28.2
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.16.0; extra == 'anthropic'
Provides-Extra: claude-agent-sdk
Requires-Dist: claude-agent-sdk>=0.0.23; extra == 'claude-agent-sdk'
Provides-Extra: elevenlabs
Requires-Dist: elevenlabs>=2.11.0; extra == 'elevenlabs'
Provides-Extra: langchain
Requires-Dist: langchain>=1.2.15; extra == 'langchain'
Provides-Extra: langgraph
Requires-Dist: langgraph>=0.6.0; extra == 'langgraph'
Provides-Extra: langsmith
Requires-Dist: langsmith>=0.6.0; extra == 'langsmith'
Provides-Extra: livekit-agents
Requires-Dist: livekit-agents>=1.2.0; extra == 'livekit-agents'
Provides-Extra: openai
Requires-Dist: openai>=1.66.0; extra == 'openai'
Provides-Extra: openai-agents
Requires-Dist: openai-agents>=0.0.2; extra == 'openai-agents'
Requires-Dist: openai>=1.66.0; extra == 'openai-agents'
Provides-Extra: pydantic-ai
Requires-Dist: pydantic-ai>=0.0.34; extra == 'pydantic-ai'
Description-Content-Type: text/markdown

# catalyst-tracing

First-party OpenInference-shaped tracing for Python LLM and agent applications
running on Catalyst by Inference.net.

`catalyst-tracing` gives you one Python package for instrumenting common model
SDKs, agent frameworks, and custom agent work. It emits OpenTelemetry spans with
OpenInference-compatible attributes over OTLP/HTTP so Catalyst can display model
calls, tool calls, prompts, responses, token usage, and parent-child agent flows.

This package is currently in beta. APIs may change before `1.0`, but the package
name and import path are intended to remain stable.

Full documentation lives at
[docs.inference.net/integrations/traces/overview](https://docs.inference.net/integrations/traces/overview).

## Install

Install the base tracing runtime:

```bash
pip install catalyst-tracing
```

Install only the integrations your application uses:

```bash
pip install 'catalyst-tracing[openai]'
pip install 'catalyst-tracing[anthropic]'
pip install 'catalyst-tracing[langchain]'
pip install 'catalyst-tracing[langgraph]'
pip install 'catalyst-tracing[langsmith]'
pip install 'catalyst-tracing[openai-agents]'
pip install 'catalyst-tracing[claude-agent-sdk]'
pip install 'catalyst-tracing[pydantic-ai]'
pip install 'catalyst-tracing[elevenlabs]'
pip install 'catalyst-tracing[livekit-agents]'
```

You can combine extras. For LangChain agents that are already wrapped in
LangSmith decorators, install both integrations plus the provider SDKs you use:

```bash
pip install 'catalyst-tracing[openai,anthropic,langchain]'
pip install 'catalyst-tracing[openai,langchain,langsmith]'
```

## Quick Start

Set your Catalyst endpoint and token:

```bash
export CATALYST_OTLP_ENDPOINT="https://your-catalyst-otlp-endpoint"
export CATALYST_OTLP_TOKEN="your-token"
export CATALYST_SERVICE_NAME="checkout-agent"
```

Initialize tracing before creating SDK clients:

```python
from catalyst_tracing import setup
from openai import OpenAI

tracing = setup()

client = OpenAI()
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Summarize this order."}],
)

tracing.shutdown()
```

The OpenAI call is captured as an OpenInference-shaped LLM span and exported to
Catalyst through OTLP/HTTP.

## What It Instruments

| Integration | Install extra | What is captured |
|---|---|---|
| OpenAI | `openai` | Chat Completions, Responses, sync clients, and async clients |
| Anthropic | `anthropic` | Messages API calls, sync clients, and async clients |
| LangChain | `langchain` | Callback-manager driven chain, model, tool, and retriever spans |
| LangGraph | `langgraph` | Graph and node spans through the LangChain callback path |
| LangSmith | `langsmith` | LangSmith OpenTelemetry spans bridged into the Catalyst provider |
| OpenAI Agents | `openai-agents` | Agent runs plus nested OpenAI model spans |
| Claude Agent SDK | `claude-agent-sdk` | `query()` calls and yielded agent messages |
| Pydantic AI | `pydantic-ai` | Pydantic AI's native OpenTelemetry instrumentation |
| ElevenLabs Agents | `elevenlabs` | Conversation sessions, user/agent messages, and client tool calls |
| LiveKit Agents | `livekit-agents` | Native LiveKit OTel spans bridged into Catalyst and enriched as AGENT, LLM, TOOL, and CHAIN spans |

The base package includes the tracing runtime. Extras install the upstream SDKs
themselves so you can keep production environments narrow.

## LangSmith Decorator Integration

Applications that already use LangSmith decorators do not need Catalyst-specific
wrappers around those functions. Keep using LangSmith's `@traceable` decorator
and initialize Catalyst once at process startup. The SDK bridges LangSmith's
OpenTelemetry context into the Catalyst tracer provider, so provider spans
created below a decorated function are parented under that LangSmith run.

```python
import os

from catalyst_tracing import setup
from langsmith import Client, traceable

os.environ["LANGSMITH_TRACING"] = "true"

tracing = setup(service_name="support-agent")
client = Client()

@traceable(name="lookup_order", run_type="tool", client=client, enabled=True)
def lookup_order(order_id: str) -> str:
    return f"order {order_id} is shipped"

lookup_order("ABC-123")
client.flush()
tracing.shutdown()
```

When `LANGSMITH_TRACING=true` is set and no LangSmith OTel mode is configured,
Catalyst defaults LangSmith to `hybrid` mode. LangSmith still receives its own
traces, and Catalyst receives the OpenTelemetry spans from the same decorators.
Set `LANGSMITH_TRACING_MODE=otel` when you want LangSmith to emit only OTel
spans for this process. The SDK also enables the LangSmith OTel context flags
needed by older LangSmith clients, including `LANGSMITH_OTEL_ENABLED`, so the
active decorator span becomes the parent for nested LangChain and provider
spans.

The SDK enriches LangSmith spans before export so Catalyst can render them like
the rest of your agent trace:

| LangSmith signal | Catalyst/OpenInference attribute |
|---|---|
| `run_type` / `langsmith.span.kind` | `openinference.span.kind` |
| `gen_ai.prompt` | `input.value` with `input.mime_type=application/json` |
| `gen_ai.completion` | `output.value` with `output.mime_type=application/json` |
| tool trace name | `tool.name` |
| agent trace name | `agent.name` |
| LangSmith model metadata | `llm.model_name`, `llm.provider` |

This also works with LangChain or LangGraph spans emitted under a LangSmith
decorated root span. Catalyst captures LangChain chain/tool/retriever spans
through its LangChain callback integration, captures provider spans through the
provider integrations, and preserves the LangSmith decorator spans in the same
trace tree.

For a LangChain agent with LangSmith tracing, the customer application should
look like normal LangSmith and LangChain code:

```python
import os

from catalyst_tracing import setup
from langchain.agents import create_agent
from langsmith import Client, traceable

os.environ["LANGSMITH_TRACING"] = "true"

tracing = setup(service_name="question-gen-agent")
langsmith_client = Client()

agent = create_agent(...)

@traceable(name="question_generation_agent", run_type="chain", client=langsmith_client)
def run_agent(payload: dict) -> dict:
    return agent.invoke(payload)

run_agent({"messages": [{"role": "user", "content": "Generate one question."}]})
langsmith_client.flush()
tracing.shutdown()
```

Do not add a separate customer-owned Catalyst wrapper around the decorated
entry point just to make grouping work. If every model span appears as a root
span, check that the process installed the `langsmith` extra, initialized
Catalyst before invoking the agent, and did not override LangSmith's OTel mode
after setup.

## Public API

Most applications only need `setup()`:

```python
from catalyst_tracing import setup

tracing = setup(
    service_name="support-agent",
    service_version="0.4.0",
)
```

`setup()` returns a `CatalystTracing` handle with:

| Attribute | Purpose |
|---|---|
| `provider` | OpenTelemetry `TracerProvider` configured for Catalyst export |
| `tracer` | Tracer for manual spans |
| `install_results` | Per-integration install results |
| `shutdown()` | Flush and close tracing before process exit |

You can also import integration installers directly:

```python
from catalyst_tracing import setup
from catalyst_tracing.openai import install_openai

tracing = setup()
install_openai(tracing.provider)
```

Available entry-point modules:

| Import | Export |
|---|---|
| `catalyst_tracing.openai` | `install_openai` |
| `catalyst_tracing.anthropic` | `install_anthropic` |
| `catalyst_tracing.langchain` | `install_langchain` |
| `catalyst_tracing.langgraph` | `install_langgraph` |
| `catalyst_tracing.langsmith` | `install_langsmith` |
| `catalyst_tracing.openai_agents` | `install_openai_agents` |
| `catalyst_tracing.claude_agent_sdk` | `install_claude_agent_sdk` |
| `catalyst_tracing.pydantic_ai` | `install_pydantic_ai` |
| `catalyst_tracing.elevenlabs` | `install_elevenlabs` |
| `catalyst_tracing.livekit_agents` | `install_livekit_agents` |

## Manual Agent Spans

Use `manual_span()` when work does not go through a supported SDK, such as a
custom router, planner, evaluator, provider-failover step, or tool executor.
It accepts OpenInference span kinds, provider-shaped usage payloads, structured
inputs/outputs, and OTel-safe custom attributes.

```python
from catalyst_tracing import SpanKindValues, manual_span, setup

tracing = setup()

with manual_span(
    tracing.tracer,
    name="question_generation/bloom",
    span_kind=SpanKindValues.CHAIN,
    system="fireworks",
    input={"template_id": "bloom", "total_questions": 10},
    model="accounts/fireworks/models/gpt-oss-120b",
    user_id="user-7723",
    metadata={"deck_id": "deck_123"},
    tags=["question-gen-agent", "template:bloom"],
) as span:
    result = run_question_generation()
    span.set_output({"question_count": len(result.questions)})
    span.record_usage(result.usage)

tracing.shutdown()
```

`agent_span()` remains available as a focused convenience wrapper for AGENT
spans:

```python
from catalyst_tracing import agent_span, setup

tracing = setup()

with agent_span(
    tracing.tracer,
    agent_id="refund-review-agent",
    agent_name="Refund Review Agent",
    span_name="refund-review.run",
    session_id="conversation-1842",
    user_id="user-7723",
    agent_role="refunds",
    system="internal",
    metadata={"queue": "priority"},
    tags=["refunds", "beta"],
) as span:
    span.set_input("Review refund request #1842")
    decision = run_refund_review()
    span.set_output(decision.summary)
    span.record_tokens(prompt=820, completion=160)

tracing.shutdown()
```

`agent_id` becomes the `agent.id` span attribute. Use a stable value that
survives display-name changes so Catalyst can group executions correctly in the
agent dashboard. `agent_name` is optional and becomes `agent.name` when you want
a human-readable display label. `session_id` is optional and should identify one
conversation, not the whole process. `user_id` is optional and becomes `user.id`
so Catalyst can filter and group traces by end user. `metadata` and `tags` are
also optional: `metadata` attaches a JSON bag and `tags` attaches a string array
to the span. Any child spans created inside the context automatically parent
under the agent span and supported SDK wrappers copy the active `agent.id` and
optional `agent.name` / `agent.role` onto their child spans.

When you already have an active span, reusable helpers are exported for common
manual instrumentation tasks:

```python
from catalyst_tracing import record_span_usage, set_span_attributes

record_span_usage(span, {"prompt_tokens": "820", "completion_tokens": 160})
set_span_attributes(span, {"metadata": {"deck_id": "deck_123"}})
```

## Configuration

You can configure tracing with keyword arguments or environment variables.

| Option | Environment variable | Default |
|---|---|---|
| `endpoint` | `CATALYST_OTLP_ENDPOINT` | `http://localhost:8799` |
| `token` | `CATALYST_OTLP_TOKEN` | unset |
| `service_name` | `CATALYST_SERVICE_NAME` | generated `catalyst-app-*` name |
| `service_version` | `CATALYST_SERVICE_VERSION` | `0.0.6` |
| `debug` | `CATALYST_DEBUG` | `false` |
| `batching` | none | `"batch"` |

Legacy `OTLP_ENDPOINT`, `OTLP_INGEST_TOKEN`, and `SERVICE_NAME` variables are
also accepted for compatibility.

## Span Shape

Spans use OpenInference-style semantic attributes so LLM-aware viewers can
understand them without custom adapters:

| Attribute family | Examples |
|---|---|
| Span kind | `openinference.span.kind` |
| Inputs and outputs | `input.value`, `output.value` |
| Messages | `llm.input_messages.*`, `llm.output_messages.*` |
| Model metadata | `llm.model_name`, `llm.invocation_parameters` |
| Token counts | `llm.token_count.prompt`, `llm.token_count.completion`, `llm.token_count.total` |
| Provider/system | `gen_ai.system` |
| Agent and session | `agent.id`, `agent.name`, `agent.role`, `session.id`, `user.id`, `metadata`, `tags` |

Constants are exported for custom spans:

```python
from catalyst_tracing import Attr, SpanKindValues

span.set_attribute(Attr.SPAN_KIND, SpanKindValues.LLM.value)
span.set_attribute(Attr.MODEL_NAME, "gpt-4o-mini")
```

## Error Handling

The package raises typed errors for misuse and returns structured install
results for optional integrations:

```python
from catalyst_tracing import CatalystTracingError, InvalidTracerProviderError
from catalyst_tracing.openai import install_openai

try:
    result = install_openai(provider)
except InvalidTracerProviderError as exc:
    print(exc.code)
except CatalystTracingError:
    raise
```

Each installer returns an `InstrumentResult` with:

| Field | Meaning |
|---|---|
| `name` | Integration name |
| `installed` | Whether instrumentation was installed |
| `code` | Stable status code such as `INSTALLED` or `SDK_NOT_INSTALLED` |
| `reason` | Human-readable detail when installation is skipped |

## Package Names

The primary package is `catalyst-tracing` and the primary import path is
`catalyst_tracing`.

Inference also publishes `inference-catalyst-tracing` as a company-qualified
install name. It depends on this package and re-exports the same public API from
the `inference_catalyst_tracing` import path.
