Metadata-Version: 2.4
Name: uselemma-tracing
Version: 4.2.0
Summary: HTTP tracing SDK for Lemma
Project-URL: Homepage, https://github.com/uselemma/tracing
Project-URL: Repository, https://github.com/uselemma/tracing
Project-URL: Issues, https://github.com/uselemma/tracing/issues
License-Expression: MIT
License-File: LICENSE
Keywords: llm,monitoring,observability,sdk,tracing
Requires-Python: >=3.9
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Provides-Extra: langchain
Requires-Dist: langchain>=0.3.0; extra == 'langchain'
Provides-Extra: langgraph
Requires-Dist: langchain>=0.3.0; extra == 'langgraph'
Requires-Dist: langgraph>=0.2.0; extra == 'langgraph'
Provides-Extra: openai-agents
Requires-Dist: openai-agents>=0.17.0; (python_version >= '3.10') and extra == 'openai-agents'
Description-Content-Type: text/markdown

# uselemma-tracing

HTTP tracing SDK for AI agents. The primary API sends trace payloads directly to Lemma over HTTP.

## Installation

```bash
pip install uselemma-tracing
```

## Quick Start

```python
from uselemma_tracing import Lemma

lemma = Lemma()

def run(trace):
    docs = search_docs(user_message)
    trace.record_tool(
        name="search_docs",
        input={"query": user_message},
        output=docs,
        tool_parameters={"query": "string"},
    )

    response = call_model(user_message, docs)
    trace.record_generation(
        name="draft-reply",
        input=response.messages,
        output=response.text,
        model="gpt-4o",
        usage={
            "input_tokens": response.usage.input_tokens,
            "output_tokens": response.usage.output_tokens,
        },
        llm_input_messages=[{"role": "user", "content": user_message}],
        llm_invocation_parameters={"temperature": 0.2},
    )

    return response.text

answer = lemma.trace(
    "support-agent",
    run,
    input=user_message,
    thread_id=conversation_id,
    user_id=user.id,
)
```

`lemma.trace()` measures the trace from callback start to completion. Use
`async_trace()` for async callbacks.

## Live Spans

```python
def run(trace):
    span = trace.start_span(name="retrieve-context", input=query)
    try:
        docs = retrieve(query)
        span.end(
            output=docs,
            retrieval_documents=[
                {"id": doc.id, "content": doc.text, "score": doc.score}
                for doc in docs
            ],
        )
        return docs
    except Exception as error:
        span.end(status="ERROR", error=error)
        raise
```

Live handles know their start time when created and their end time when
`.end()` is called, so you usually do not pass `duration_ms`. Pass
`duration_ms` only when replaying historical work or overriding the measured
duration with a value from another timer.

For one-off records where you already measured the work, pass `duration_ms` on
the record call:

```python
trace.record_generation(
    name="answer",
    output=text,
    model="gpt-4o",
    duration_ms=measured_model_ms,
)
```

The same handle pattern is available for tool calls and generations:

```python
tool = trace.start_tool(name="search_docs", input={"query": query})
docs = search_docs(query)
tool.end(output=docs)

generation = trace.start_generation(name="answer", input=messages)
response = call_model(messages)
generation.end(output=response.text)
```

## OpenAI Agents SDK

Install the OpenAI Agents extra and register the Lemma processor:

```bash
pip install "uselemma-tracing[openai-agents]" openai-agents
```

```python
from agents import Agent, Runner
from uselemma_tracing import instrument_openai_agents

instrument_openai_agents()

agent = Agent(
    name="support-agent",
    instructions="Answer customer questions clearly and concisely.",
)

async def call_agent(user_message: str):
    result = await Runner.run(agent, user_message)
    return result.final_output
```

The processor creates one Lemma trace for each OpenAI Agents trace. Generation
spans become Lemma generations, function spans become Lemma tool spans, and
parent IDs are preserved so tools stay nested under the generation or agent
span that called them.

Enable debug mode to validate live span shape while developing:

```python
from uselemma_tracing import enable_debug_mode

enable_debug_mode()
```

Use `openai_agents(record_inputs=False, record_outputs=False)` when you need a
processor that avoids sending prompts, tool inputs, tool outputs, and generated
text.

## LangChain and LangGraph

Install the optional integration dependency and pass `langchain()` as a callback
handler:

```bash
pip install "uselemma-tracing[langchain]" langchain-openai
```

```python
from langchain_openai import ChatOpenAI
from uselemma_tracing import langchain

model = ChatOpenAI(
    model="gpt-4o",
    callbacks=[langchain(agent_name="support-agent")],
)

response = model.invoke(user_message)
```

LangGraph uses LangChain callbacks too:

```bash
pip install "uselemma-tracing[langgraph]"
```

```python
from uselemma_tracing import langgraph

result = graph.invoke(
    {"input": user_message},
    {"callbacks": [langgraph(agent_name="support-graph")]},
)
```

The handler creates one Lemma trace for the root chain/graph run, records LLM
calls as generations, tools as tool spans, retrievers as spans with retrieval
documents, and nested chains or graph nodes as child spans.

Use `langchain(record_inputs=False, record_outputs=False)` or
`langgraph(record_inputs=False, record_outputs=False)` to avoid sending prompts,
tool inputs, tool outputs, retrieved documents, or generated text.

## Supported Contract Fields

Use native SDK keyword arguments for OpenInference-style fields:

- LLM: `llm_model_name`, `llm_provider`, `llm_system`,
  `llm_invocation_parameters`, `llm_input_messages`, `llm_output_messages`,
  `llm_tools`, token counts, and prompt template fields
- tools: `tool_description`, `tool_parameters`
- retrieval: `retrieval_documents`
- embeddings and rerankers: `embedding_model_name`,
  `embedding_invocation_parameters`, `embedding_embeddings`,
  `reranker_model_name`, `reranker_input_documents`,
  `reranker_output_documents`

Use `attributes` for raw attributes that do not yet have a native SDK keyword.

## Configuration

| Option       | Environment variable | Default                   |
| ------------ | -------------------- | ------------------------- |
| `api_key`    | `LEMMA_API_KEY`      | Required                  |
| `project_id` | `LEMMA_PROJECT_ID`   | Required                  |
| `base_url`   | none                 | `https://api.uselemma.ai` |

The SDK sends to `{base_url}/traces/ingest`.

You can pass configuration directly to the constructor instead of using
environment variables:

```python
lemma = Lemma(
    api_key="sk_...",
    project_id="proj_...",
    base_url="https://api.uselemma.ai",
)
```

## Debug Mode

Debug mode logs trace starts, span starts, span completions, send attempts, and
send results as they happen:

```python
from uselemma_tracing import enable_debug_mode

enable_debug_mode()
```

You can also set `LEMMA_DEBUG=true`. Use this when validating that spans are
created in the expected order and the SDK is sending to the intended URL.

## License

MIT
