Metadata-Version: 2.3
Name: spect-track
Version: 0.0.1a3
Summary: Python observability/tracing client for Spect.
Author: Steven Sinatra
Author-email: Steven Sinatra <steven@spect.tools>
Requires-Dist: uuid7>=0.1.0
Requires-Python: >=3.12
Description-Content-Type: text/markdown

# spect-track

Python tracing client for Spect.

## Status

`spect-track` 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, record spans manually around the SDK call you want to trace.

## Install

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

## Usage

```python
import asyncio
import time

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


async def main() -> None:
    started_at = time.time_ns() // 1_000_000
    observer = create_observer(
        ObserverOptions(
            organization_id="your-org-id",
            api_key="your-spect-api-key",
            provider="openai",
        )
    )
    session = observer.start_session(
        SessionOptions(
            model=ModelSpec(model_id="gpt-4.1", provider="openai"),
            name="openai.responses.create",
            start_time=started_at,
        )
    )

    session.start_span(
        name="system",
        kind="system",
        status="ok",
        start_time=started_at,
        messages=[{"role": "system", "content": "You are a helpful assistant."}],
    )
    session.start_span(
        name="user",
        kind="user",
        status="ok",
        start_time=started_at,
        messages=[{"role": "user", "content": [{"type": "text", "text": "Explain tracing."}]}],
    )

    response_started_at = time.time_ns() // 1_000_000
    text = "Tracing records the model call, transcript, tools, timing, and metadata."
    response_ended_at = time.time_ns() // 1_000_000

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

    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())
```

## Message Shape

Record transcript items on spans with `messages`. Timing belongs on spans, not messages.

- System messages use `content: str`.
- User and assistant messages can use text/content parts.
- Tool calls/results should include both the span-level `tool` overlay and canonical message parts.

```python
{"role": "system", "content": "You are a helpful assistant."}
{"role": "user", "content": [{"type": "text", "text": "Hello"}]}
{"role": "assistant", "content": [{"type": "reasoning", "text": "I should search first."}]}
{"role": "assistant", "content": [{"type": "tool-call", "toolCallId": "call_1", "toolName": "search", "input": {"query": "asthma"}}]}
{"role": "tool", "content": [{"type": "tool-result", "toolCallId": "call_1", "toolName": "search", "output": {"type": "json", "value": {"matches": 3}}}]}
```

## Local Mode

```python
payloads = []

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

## Development

From the repo root:

```bash
cd python
uv sync
uv run --package spect-track ruff check packages/spect-track packages/playground
uv run --package spect-track ty check packages/spect-track
uv run --package spect-track pytest packages/spect-track/tests
```

## Build

```bash
cd python
uv build --package spect-track
```
