Metadata-Version: 2.4
Name: traceport
Version: 0.1.0
Summary: A unified read, query, and export layer for LLM and AI-agent traces.
Project-URL: Homepage, https://github.com/TheValmarAI/traceport
Project-URL: Documentation, https://github.com/TheValmarAI/traceport#readme
Project-URL: Issues, https://github.com/TheValmarAI/traceport/issues
Project-URL: Source, https://github.com/TheValmarAI/traceport
Author: Traceport contributors
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: agents,ai,llm,mcp,observability,openinference,opentelemetry,tracing
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
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: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.0
Requires-Dist: typing-extensions>=4.8
Provides-Extra: all
Requires-Dist: arize-phoenix-client>=2.9; extra == 'all'
Requires-Dist: braintrust-api; extra == 'all'
Requires-Dist: datadog-api-client; extra == 'all'
Requires-Dist: langfuse>=4; extra == 'all'
Requires-Dist: langsmith; extra == 'all'
Requires-Dist: logfire; extra == 'all'
Requires-Dist: mcp<2,>=1.27; extra == 'all'
Requires-Dist: wandb; extra == 'all'
Requires-Dist: weave>=0.52.43; extra == 'all'
Provides-Extra: braintrust
Requires-Dist: braintrust-api; extra == 'braintrust'
Provides-Extra: datadog
Requires-Dist: datadog-api-client; extra == 'datadog'
Provides-Extra: langfuse
Requires-Dist: langfuse>=4; extra == 'langfuse'
Provides-Extra: langsmith
Requires-Dist: langsmith; extra == 'langsmith'
Provides-Extra: logfire
Requires-Dist: logfire; extra == 'logfire'
Provides-Extra: mcp
Requires-Dist: mcp<2,>=1.27; extra == 'mcp'
Provides-Extra: phoenix
Requires-Dist: arize-phoenix-client>=2.9; extra == 'phoenix'
Provides-Extra: weave
Requires-Dist: wandb; extra == 'weave'
Requires-Dist: weave>=0.52.43; extra == 'weave'
Description-Content-Type: text/markdown

# traceport

Read agent conversations from any supported observability provider through one consistent API.

Trace providers store the same conversation in very different shapes: runs, records, observations,
spans, and cumulative message histories. `traceport` normalizes those records into sessions with one
readable, chronological transcript:

```python
from traceport import TraceClient

client = TraceClient.from_env("langsmith")

sessions = client.list_sessions(project="Branna", since="7d")
session = client.get_session(sessions[0].id, project="Branna")

for event in session.agent_traces:
    print(event.time, event.name, event.type, event.content)
```

`session.agent_traces` removes repeated cumulative histories while preserving system prompts, human
messages, paired tool calls, AI messages, and the timestamp where each event first appeared.

## Install

Use `uv` and install only the provider extras you need:

```bash
uv add 'traceport[langsmith]'
uv add 'traceport[logfire]'
uv add 'traceport[langfuse]'
uv add 'traceport[phoenix]'
```

Install MCP support alongside a provider:

```bash
uv add 'traceport[mcp,langsmith]'
```

Helicone uses the core `httpx` dependency and does not require an extra.

When developing Traceport itself:

```bash
cd packages/traceport
uv sync --extra langsmith
```

## Core Model

Traceport's primary hierarchy is:

```text
Session
├── agent_traces     readable, deduplicated conversation
└── full_traces      complete provider executions
    └── spans        full normalized telemetry
```

### `session.agent_traces`

Despite the name, `agent_traces` is a flat `list[AgentSpan]`. Each event has four fields:

```python
class AgentSpan:
    name: str
    type: Literal[
        "system_prompt",
        "human_message",
        "tool_call",
        "ai_message",
    ]
    content: str | AgentToolCall
    time: datetime | None
```

For example:

```text
2026-06-21 23:12:05+00:00  System      system_prompt  You are a concise assistant.
2026-06-21 23:12:05+00:00  Human       human_message  Count to three in Spanish.
2026-06-21 23:12:05+00:00  ChatOpenAI  ai_message     uno, dos, tres
2026-06-21 23:12:11+00:00  Human       human_message  And in German?
2026-06-21 23:12:11+00:00  ChatOpenAI  ai_message     eins, zwei, drei
```

Providers such as LangSmith often include the complete conversation history in every later trace.
Traceport runs transcript extraction across the whole session, deduplicates repeated messages, and
keeps the timestamp from the first trace where each event appeared.

Tool invocations and results are paired into one event:

```python
for event in session.agent_traces:
    if event.type == "tool_call":
        call = event.content
        print(call.name)
        print(call.input)
        print(call.output)
```

`AgentToolCall` contains:

```python
class AgentToolCall:
    name: str
    input: Any | None
    output: Any | None
```

### `session.full_traces`

Use full traces only when you need observability details:

```python
for trace in session.full_traces:
    print(trace.id, trace.name, trace.status, trace.latency_ms)

    for span in trace.spans:
        print(span.id, span.name, span.span_kind, span.model)
```

Full traces preserve supporting HTTP, database, chain, and infrastructure spans. Untouched provider
records remain available through each model's `raw` escape hatch, but `raw` is excluded from normal
model serialization.

`session.model_dump()` returns the lightweight session summary. It intentionally excludes both the
computed transcript and the potentially large `full_traces` payload.

## Sessions

`list_sessions()` returns lightweight summaries and defaults to sessions containing agent activity:

```python
sessions = client.list_sessions(
    project="Branna",
    since="7d",
    agent_only=True,
    limit=100,
    scan_limit=500,
)

for ref in sessions:
    print(ref.id, ref.trace_count, ref.agent_span_count, ref.status)
```

`get_session()` hydrates the selected session:

```python
session = client.get_session(sessions[0].id, project="Branna")
```

Traceport uses provider-native session, thread, or conversation identifiers when available. If a
provider does not expose one, the trace becomes a synthetic session with ID `trace:<trace-id>`.

Some providers can identify agent sessions natively. Others use a bounded candidate scan. Set
`scan_limit` to control the maximum work. Traceport emits `ScanLimitWarning` if the bound may have
truncated the result set.

## Individual Traces

Use trace-level APIs when you need one specific provider execution rather than the entire
conversation:

```python
refs = client.list_traces(
    project="Branna",
    since="24h",
    agent_only=True,
    error=False,
    limit=50,
)

trace = client.get_trace(refs[0].id, project="Branna")

for event in trace.agent_spans:
    print(event.time, event.type, event.content)
```

`trace.agent_spans` applies the same readable extraction to one trace. `trace.spans` contains its
complete telemetry.

Provider-independent filtering is available through `F`:

```python
from traceport import F

traces = client.query_traces(
    F.project("Branna")
    & F.since("24h")
    & F.has_agent_spans()
    & F.model_contains("gpt"),
    limit=100,
)
```

Supported filters include project, time range, status, model, latency, cost, metadata, name, and
agent-span presence. Unsupported native predicates are post-filtered after normalization rather
than silently ignored.

## Provider Configuration

| Provider | Required environment variables | Optional | Extra |
| --- | --- | --- | --- |
| LangSmith | `LANGSMITH_API_KEY` | `LANGSMITH_ENDPOINT`, `LANGSMITH_WORKSPACE_ID` | `langsmith` |
| Logfire | `LOGFIRE_READ_TOKEN` | `LOGFIRE_BASE_URL`, `LOGFIRE_PROJECT`, `LOGFIRE_ENVIRONMENT` | `logfire` |
| Langfuse | `LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY` | `LANGFUSE_HOST`, `LANGFUSE_PROJECT` | `langfuse` |
| Phoenix | `PHOENIX_COLLECTOR_ENDPOINT`, `PHOENIX_PROJECT_NAME` | `PHOENIX_API_KEY` | `phoenix` |
| Braintrust | `BRAINTRUST_API_KEY` | `BRAINTRUST_API_URL`, `BRAINTRUST_PROJECT_ID` | `braintrust` |
| Weave | `WANDB_API_KEY`, `WEAVE_PROJECT` | — | `weave` |
| Helicone | `HELICONE_API_KEY` | `HELICONE_BASE_URL` | core |
| Datadog | `DD_API_KEY`, `DD_APP_KEY` | `DD_SITE` | `datadog` |

Example:

```bash
export LANGSMITH_API_KEY=...
export LANGSMITH_PROJECT=Branna
```

```python
client = TraceClient.from_env("langsmith")
sessions = client.list_sessions(project="Branna", since="7d")
```

## Provider Capabilities

| Provider | Read method | Session identity | Agent discovery |
| --- | --- | --- | --- |
| `memory` | In-memory | Native or synthetic | Complete reference behavior |
| `langsmith` | Runs SDK | Thread metadata | Bounded hydration |
| `logfire` | SQL records | OTel/GenAI attributes | Native SQL child-record query |
| `langfuse` | SDK and observations API | Native session ID | Bounded hydration |
| `phoenix` | Spans API | `session.id` | OpenInference kinds |
| `braintrust` | BTQL | Session/thread metadata | Bounded hydration |
| `weave` | Calls SDK | Session/thread metadata or synthetic | Bounded hydration |
| `helicone` | REST requests | Native session ID | Native; every request is an LLM call |
| `datadog` | Planned | Planned | Planned |

All active adapters preserve complete telemetry after an agent session is selected. Filtering a
session for agent activity never removes its supporting spans from `full_traces`.

## MCP Server

The MCP server is intentionally small and transcript-focused. Each server process reads one
provider and one project and exposes exactly two read-only tools:

- `list_sessions(since="7d", until=None, error=None, limit=20)`
- `get_session(session_id)`

`get_session` returns the session summary and one flat `agent_traces` transcript. MCP never exposes
`full_traces`, normalized spans, raw provider records, trace filters, or exporters.

Install MCP with the provider extra:

```bash
uv add 'traceport[mcp,langsmith]'
```

The generic stdio command is:

```bash
export TRACEPORT_PROVIDER=langsmith
export TRACEPORT_PROJECT=Branna
export LANGSMITH_API_KEY=...
uv run traceport-mcp
```

FastMCP uses stdio by default. An MCP stdio process must write only MCP protocol messages to
`stdout`; diagnostic logging belongs on `stderr`.

### Codex with a local checkout

Add a server with the Codex CLI:

```bash
codex mcp add traceport-langsmith \
  --env TRACEPORT_PROVIDER=langsmith \
  --env TRACEPORT_PROJECT=Branna \
  --env LANGSMITH_API_KEY=... \
  -- uv run \
    --directory /absolute/path/to/packages/traceport \
    --extra mcp \
    --extra langsmith \
    traceport-mcp
```

Or add the equivalent TOML from
[examples/mcp.codex.toml](examples/mcp.codex.toml) to `~/.codex/config.toml` or a trusted
project's `.codex/config.toml`.

### Codex with the published package

```bash
codex mcp add traceport-langsmith \
  --env TRACEPORT_PROVIDER=langsmith \
  --env TRACEPORT_PROJECT=Branna \
  --env LANGSMITH_API_KEY=... \
  -- uvx --from 'traceport[mcp,langsmith]' traceport-mcp
```

Run one named MCP server per provider/project combination:

```text
traceport-branna-langsmith
traceport-production-logfire
```

The server instructs clients to list sessions before hydration, starts with a seven-day window,
defaults to 20 results, caps requests at 100, and warns clients not to repeat identical calls because
observability providers may rate-limit reads.

## Export Full Traces

Export is available through the Python API for full observability workflows:

```python
trace = session.full_traces[0]

client.export_trace(trace, format="json")
client.export_trace(trace, format="otel")
client.export_trace(trace, format="openinference")
```

OpenInference is the default format and emits OTLP JSON with normalized OpenInference attributes.

## Local Testing

The memory adapter provides deterministic offline behavior:

```python
from traceport import Span, SpanKind, Trace, TraceClient
from traceport.adapters.memory import MemoryAdapter

trace = Trace(
    id="trace-1",
    session_id="session-1",
    provider="memory",
    spans=[
        Span(
            id="span-1",
            trace_id="trace-1",
            name="chat.completion",
            span_kind=SpanKind.LLM,
            inputs=[{"role": "user", "content": "hello"}],
            outputs=[{"role": "assistant", "content": "hi"}],
        )
    ],
)

client = TraceClient(adapter=MemoryAdapter([trace]))
session = client.get_session("session-1")
print(session.agent_traces)
```

## Development

```bash
cd packages/traceport
UV_CACHE_DIR=/tmp/uv-cache uv sync
UV_CACHE_DIR=/tmp/uv-cache uv run ruff format src tests
UV_CACHE_DIR=/tmp/uv-cache uv run ruff check src tests
UV_CACHE_DIR=/tmp/uv-cache uv run mypy src
UV_CACHE_DIR=/tmp/uv-cache uv run pytest
UV_CACHE_DIR=/tmp/uv-cache uv build
```

No live provider calls run in CI. Provider behavior is covered with scrubbed fixtures.

Third-party adapters can register through the `traceport.adapters` entry point:

```toml
[project.entry-points."traceport.adapters"]
acme = "acme_traceport:AcmeAdapter"
```

Adapters must subclass `traceport.adapters.BaseAdapter`. Unsupported filters must be translated,
post-filtered against normalized traces, or rejected with `UnsupportedFilterError`.
