Spect

Python

Track Python AI calls with the spect-track client.

Status

spect-track is the Python tracing client for Spect. It currently provides the core observer/session lifecycle, trace payload building, HTTP transport, local mode, and typed payload contracts.

Provider-specific wrappers are still in progress. For now, use sessions manually around the SDK call you want to trace.

Installation

uv add spect-track
# or
pip install spect-track

Basic usage

Create an observer, start a session, record messages on spans, then send the result.

import asyncio
import time

from spect_track import ModelSpec, ObserverOptions, SendResult, SessionOptions, create_observer


async def main() -> None:
    observer = create_observer(
        ObserverOptions(
            organization_id="your-org-id",
            api_key="your-spect-api-key",
            provider="openai",
        )
    )

    started_at = time.time_ns() // 1_000_000
    session = observer.start_session(
        SessionOptions(
            model=ModelSpec(model_id="gpt-4.1", provider="openai"),
            name="openai.responses.create",
            start_time=started_at,
            metadata={"spect": {"adapter": "python-openai-responses"}},
        )
    )

    session.start_span(
        name="user",
        kind="user",
        status="ok",
        start_time=started_at,
        end_time=started_at,
        messages=[
            {
                "role": "user",
                "content": [{"type": "text", "text": "Explain tracing in one sentence."}],
            }
        ],
    )

    response_started_at = time.time_ns() // 1_000_000
    # Call your provider SDK here.
    text = "Tracing records what happened around an AI call."
    response_ended_at = time.time_ns() // 1_000_000

    await session.send(
        SendResult(
            text=text,
            response={"model": "gpt-4.1", "finishReasons": ["stop"]},
            start_time=response_started_at,
            duration=max(0, response_ended_at - response_started_at),
            end_time=response_ended_at,
        )
    )


asyncio.run(main())

Recording spans

Use spans for work inside the session, such as a model call, tool call, or subagent step.

response_started_at = time.time_ns() // 1_000_000
# Call your provider SDK here.
text = "Tracing records what happened around an AI call."
response_ended_at = time.time_ns() // 1_000_000

session.start_span(
    name="openai.responses.create",
    kind="generate",
    start_time=response_started_at,
    end_time=response_ended_at,
    metadata={"scenario": "openai:responses"},
    model={"modelId": "gpt-4.1", "provider": "openai"},
    messages=[
        {
            "role": "assistant",
            "content": [{"type": "text", "text": text}],
        }
    ],
)

Span records are attached to the trace payload under metadata.result.spans.

Tool spans

Tool calls and results use both a span-level tool overlay and optional transcript messages.

session.start_span(
    name="evidence_search",
    kind="tool-call",
    status="ok",
    start_time=tool_started_at,
    end_time=tool_ended_at,
    tool={
        "toolCallId": "call_1",
        "toolName": "evidence_search",
        "parameters": {"query": "acute asthma protocols"},
    },
    messages=[
        {
            "role": "assistant",
            "content": [
                {
                    "type": "tool-call",
                    "toolCallId": "call_1",
                    "toolName": "evidence_search",
                    "input": {"query": "acute asthma protocols"},
                }
            ],
        }
    ],
)

session.start_span(
    name="evidence_search.result",
    kind="tool-result",
    status="ok",
    start_time=result_started_at,
    end_time=result_ended_at,
    tool={
        "toolCallId": "call_1",
        "toolName": "evidence_search",
        "result": {"matches": 3},
    },
    messages=[
        {
            "role": "tool",
            "content": [
                {
                    "type": "tool-result",
                    "toolCallId": "call_1",
                    "toolName": "evidence_search",
                    "output": {"type": "json", "value": {"matches": 3}},
                }
            ],
        }
    ],
)

Message content parts

Messages should use Spect's canonical part names, which match the web viewer contract:

{"type": "text", "text": "Hello"}
{"type": "reasoning", "text": "I should search first."}
{"type": "tool-call", "toolCallId": "call_1", "toolName": "search", "input": {"query": "asthma protocol"}}
{"type": "tool-result", "toolCallId": "call_1", "toolName": "search", "output": {"type": "json", "value": {"matches": 3}}}

Use role: "tool" for messages that contain tool-result parts:

{
    "role": "tool",
    "content": [
        {
            "type": "tool-result",
            "toolCallId": "call_1",
            "toolName": "search",
            "output": {"type": "text", "value": "..."},
        }
    ],
}

Normalize provider-native shapes before passing messages to spect-track. For example, Anthropic thinking should be recorded as reasoning, tool_use as tool-call, and tool_result as tool-result.

Local mode

Use local=True to build payloads without sending them to the collector. This is useful for tests and adapter development.

payloads = []

observer = create_observer(
    ObserverOptions(
        organization_id="org_123",
        local=True,
        provider="openai",
        on_trace=payloads.append,
    )
)

In local mode, api_key is optional. Outside local mode, api_key is required.

Error traces

Use send_error() when the provider call fails.

try:
    # Call your provider SDK here.
    ...
except Exception as error:
    await session.send_error(
        error,
        SendResult(
            start_time=response_started_at,
            duration=max(0, (time.time_ns() // 1_000_000) - response_started_at),
        ),
    )
    raise

Spect serializes the exception into the trace metadata and sends the payload using the same session context.

Typed contracts

The Python package exports mirrored versions of the shared JavaScript trace contracts:

  • CollectorTracePayload
  • ConversationMessage
  • SpanRecord
  • SpanStatus
  • SpanKind
  • PlainSpan, ToolCallSpan, ToolResultSpan, SubagentSpan

These types currently live in Python as a mirror of the shared TypeScript contracts. They are intended to stay aligned with @spect-tools/shared.

Playground

The repo includes a Python playground package for emitting realistic traces:

cd python
uv run --package spect-playground spect-playground --scenario openai:responses

Set these environment variables before running it:

SPECT_ORGANIZATION_ID=
SPECT_API_KEY=
SPECT_COLLECTOR_URL=http://localhost:8787
OPENAI_API_KEY=

On this page