LLM observability SDK — framework-agnostic tracing for AI applications
Project description
TraceCast
SDK de observabilidade para LLMs — rastreie tokens, custo, latência e tool calls em qualquer framework de IA. Python · TypeScript · Framework-Agnostic.
O que é
TraceCast é um SDK leve que captura automaticamente cada interação com LLMs e as exporta para onde você precisar — MongoDB, PostgreSQL, arquivo JSONL, ou qualquer destino customizado.
- Zero vendor lock-in — funciona com OpenAI, Anthropic, Google, Groq, Ollama, e qualquer SDK compatível.
- Zero configuração para LangChain/LangGraph — um callback resolve tudo.
- Zero dependências core — instale só o que for usar.
- Exemplos Reais — Veja a pasta
real_examples/em cada pacote para implementações completas.
Instalação
Python
#Core (sem dependências)
pip install tracecast
# Com suporte a MongoDB
pip install "tracecast[mongo]"
# Com suporte a LangChain / LangGraph
pip install "tracecast[langchain]"
# Tudo junto
pip install "tracecast[all]"
TypeScript / Node.js
npm install tracecast
# Opcionais — instale apenas o que for usar
npm install mongodb # para MongoExporter
npm install pg # para PostgresExporter
Inicio Rápido
Python
from tracecast import Tracer, Span, SpanType, calculate_cost
from tracecast.exporters import JsonFileExporter
from datetime import datetime, timezone
import uuid
tracer = Tracer(
exporters=[JsonFileExporter("./traces.jsonl")],
logging=True,
log_prefix="meu_agente",
)
with tracer.trace("minha_run", user_id="usr_1") as trace:
# sua chamada LLM aqui
span = Span(
span_id=str(uuid.uuid4()),
type=SpanType.LLM,
name="llm:gpt-4o",
model="gpt-4o",
started_at=datetime.now(timezone.utc),
finished_at=datetime.now(timezone.utc),
tokens_in=120,
tokens_out=80,
)
span.cost_usd = calculate_cost("gpt-4o", span.tokens_in, span.tokens_out)
trace.spans.append(span)
# → trace exportado automaticamente ao sair do `with`
TypeScript
import { Tracer, JsonFileExporter, SpanType, calculateCost } from "tracecast";
import { randomUUID } from "crypto";
const tracer = new Tracer({
exporters: [new JsonFileExporter("./traces.jsonl")],
logging: true,
logPrefix: "meu_agente",
});
await tracer.trace("minha_run", async (trace) => {
const span = {
spanId: randomUUID(),
type: SpanType.LLM,
name: "llm:gpt-4o",
model: "gpt-4o",
startedAt: new Date(),
finishedAt: new Date(),
tokensIn: 120,
tokensOut: 80,
costUsd: calculateCost("gpt-4o", 120, 80),
};
trace.spans.push(span);
}, { userId: "usr_1" });
Frameworks Suportados
SDK Puro (OpenAI, Anthropic, Google, Groq...)
Crie spans manualmente com os dados de uso retornados pelo SDK.
Python
import openai
from tracecast import Tracer, Span, SpanType, calculate_cost
from tracecast.exporters import JsonFileExporter
from datetime import datetime, timezone
import uuid
client = openai.OpenAI()
tracer = Tracer(exporters=[JsonFileExporter("./traces.jsonl")], logging=True)
with tracer.trace("openai-run", user_id="u1") as trace:
started = datetime.now(timezone.utc)
resp = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Olá!"}]
)
finished = datetime.now(timezone.utc)
span = Span(
span_id=str(uuid.uuid4()),
type=SpanType.LLM,
name="llm:gpt-4o",
model="gpt-4o",
started_at=started,
finished_at=finished,
tokens_in=resp.usage.prompt_tokens,
tokens_out=resp.usage.completion_tokens,
)
span.cost_usd = calculate_cost("gpt-4o", span.tokens_in, span.tokens_out)
trace.spans.append(span)
TypeScript
import OpenAI from "openai";
import { Tracer, JsonFileExporter, SpanType, calculateCost } from "tracecast";
import { randomUUID } from "crypto";
const client = new OpenAI();
const tracer = new Tracer({ exporters: [new JsonFileExporter("./traces.jsonl")], logging: true });
await tracer.trace("openai-run", async (trace) => {
const start = new Date();
const resp = await client.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "Olá!" }],
});
trace.spans.push({
spanId: randomUUID(),
type: SpanType.LLM,
name: "llm:gpt-4o",
model: "gpt-4o",
startedAt: start,
finishedAt: new Date(),
tokensIn: resp.usage!.prompt_tokens,
tokensOut: resp.usage!.completion_tokens,
costUsd: calculateCost("gpt-4o", resp.usage!.prompt_tokens, resp.usage!.completion_tokens),
});
}, { userId: "u1" });
LangChain
O TraceCastCallback intercepta automaticamente todos os eventos LLM, Tool e Chain — nenhum código extra necessário.
Python
from tracecast import Tracer
from tracecast.exporters import JsonFileExporter
from tracecast.integrations.langchain import TraceCastCallback
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
tracer = Tracer(
exporters=[JsonFileExporter("./traces.jsonl")],
logging=True,
log_prefix="langchain_agent",
)
callback = TraceCastCallback(tracer=tracer)
# stream_usage=True garante token tracking quando usar AgentExecutor
llm = ChatOpenAI(model="gpt-4o-mini", stream_usage=True)
prompt = ChatPromptTemplate.from_messages([("user", "{input}")])
chain = prompt | llm
with tracer.trace("langchain-run", user_id="u1"):
result = chain.invoke({"input": "Olá!"}, config={"callbacks": [callback]})
TypeScript
import { Tracer, JsonFileExporter } from "tracecast";
import { TraceCastCallback } from "tracecast/integrations/langchain";
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
const tracer = new Tracer({
exporters: [new JsonFileExporter("./traces.jsonl")],
logging: true,
logPrefix: "langchain_agent",
});
const cb = new TraceCastCallback(tracer);
// streamUsage: true garante token tracking quando usar AgentExecutor
const llm = new ChatOpenAI({ model: "gpt-4o-mini", streamUsage: true });
const prompt = ChatPromptTemplate.fromMessages([["user", "{input}"]]);
const chain = prompt.pipe(llm as any);
await tracer.trace("langchain-run", async () => {
await chain.invoke({ input: "Olá!" }, { callbacks: [cb] });
}, { userId: "u1" });
O que é capturado automaticamente:
LLM started → modeloao chamar o LLMLLM end → modelo | tokens: X in / Y out | $custo | latênciaao concluirTool call → nome | inputao invocar uma toolTool end → nome | latênciaao retornarChain → nomepara o nó raiz (LangGraph/Chains)
LangGraph
LangGraph usa o mesmo TraceCastCallback — cada nó do StateGraph gera um span AGENT automaticamente.
Python
from tracecast import Tracer
from tracecast.integrations.langchain import TraceCastCallback
from langgraph.graph import StateGraph, END
from typing import TypedDict
tracer = Tracer(logging=True, log_prefix="meu_grafo")
callback = TraceCastCallback(tracer=tracer)
class State(TypedDict):
messages: list
def node_a(state): return {"messages": state["messages"] + ["A"]}
def node_b(state): return {"messages": state["messages"] + ["B"]}
graph = StateGraph(State)
graph.add_node("A", node_a)
graph.add_node("B", node_b)
graph.add_edge("A", "B")
graph.add_edge("B", END)
graph.set_entry_point("A")
app = graph.compile()
with tracer.trace("grafo-run"):
app.invoke({"messages": []}, config={"callbacks": [callback]})
Cada nó gera um span do tipo
AGENTcomname: "chain:NomeDoNó"e latência medida automaticamente.
CrewAI
CrewAI usa seu próprio layer de LLM e não propaga callbacks LangChain. Use spans manuais para capturar tokens após o kickoff().
from tracecast import Tracer, Span, SpanType, calculate_cost
from tracecast.exporters import JsonFileExporter
from crewai import Agent, Task, Crew
from datetime import datetime, timezone
import uuid
tracer = Tracer(exporters=[JsonFileExporter("traces.jsonl")], logging=True)
with tracer.trace("crew-run", project_id="proj-crew") as trace:
agent = Agent(role="Pesquisador", goal="Pesquisar IA", backstory="...", llm="gpt-4o-mini")
task = Task(description="Explique LLMs em 2 frases", expected_output="Texto", agent=agent)
crew = Crew(agents=[agent], tasks=[task])
started = datetime.now(timezone.utc)
result = crew.kickoff()
finished = datetime.now(timezone.utc)
# Capture usage do LLM internamente (CrewAI > 1.x expõe usage no result)
span = Span(
span_id=str(uuid.uuid4()),
type=SpanType.LLM,
name="llm:gpt-4o-mini",
model="gpt-4o-mini",
started_at=started,
finished_at=finished,
tokens_in=120, # extraia de result.token_usage se disponível
tokens_out=45,
)
span.cost_usd = calculate_cost("gpt-4o-mini", span.tokens_in, span.tokens_out)
trace.spans.append(span)
Nota: O log automático de eventos LLM/Tool não está disponível para CrewAI (o framework não expõe hooks). O TraceCast loga automaticamente o início e fim do trace completo.
LlamaIndex
Use o LlamaDebugHandler para capturar eventos LLM e criar spans manualmente.
from tracecast import Tracer, Span, SpanType, calculate_cost
from tracecast.exporters import JsonFileExporter
from llama_index.core.callbacks import CallbackManager, LlamaDebugHandler, CBEventType
from llama_index.core.llms import MockLLM
from datetime import datetime, timezone
import uuid
tracer = Tracer(exporters=[JsonFileExporter("traces.jsonl")], logging=True)
dbh = LlamaDebugHandler()
llm = MockLLM(callback_manager=CallbackManager([dbh]))
with tracer.trace("rag-query") as trace:
llm.complete("Minha pergunta sobre o documento")
for event in dbh.get_events():
if event.event_type == CBEventType.LLM:
span = Span(
span_id=str(uuid.uuid4()),
type=SpanType.LLM,
name=f"llm:{event.payload.get('model', 'unknown')}",
model=event.payload.get("model", "unknown"),
started_at=datetime.now(timezone.utc),
finished_at=datetime.now(timezone.utc),
tokens_in=event.payload.get("formatted_prompt_tokens_count", 0),
tokens_out=event.payload.get("completion_tokens_count", 0),
)
span.cost_usd = calculate_cost(span.model, span.tokens_in, span.tokens_out)
trace.spans.append(span)
🚀 Exemplos Completos (Módulo real_examples)
Para facilitar a sua implementação, criamos exemplos completos e comentados que simulam cenários reais (como agentes de pesquisa, suporte técnico e análise financeira) em ambas as linguagens.
Python (packages/tracecast-py/real_examples/)
- openai_sdk.py — Funcionamento manual com tools.
- anthropic_sdk.py — Claude com tool use extraído.
- langchain_agent.py — Agente financeiro automático.
- langgraph_graph.py — Orquestração de grafos.
- crewai_crew.py — Multi-agent setup.
- llamaindex_index.py — RAG com interceptação de eventos.
TypeScript (packages/tracecast-ts/real_examples/)
- openaiSdk.ts — Análise de código com MongoDB.
- langchainAgent.ts — Tradução automática.
- langgraphAgent.ts — Ciclo de decisão com LangGraph.
Exporters e Campos Disponíveis
Cada exporter permite filtrar quais campos serão salvos usando include_fields ou exclude_fields. Isso é útil para reduzir o tamanho dos logs ou ocultar dados sensíveis.
JsonFileExporter — JSONL (sem dependências)
from tracecast.exporters import JsonFileExporter
# Salva tudo
exporter = JsonFileExporter("./traces.jsonl")
# Apenas campos essenciais
exporter = JsonFileExporter("./traces.jsonl", include_fields={"trace_id", "cost_usd", "total_tokens", "model"})
# Sem spans (mais leve)
exporter = JsonFileExporter("./traces.jsonl", exclude_fields={"spans"})
import { JsonFileExporter } from "tracecast";
const exporter = new JsonFileExporter("./traces.jsonl", { excludeFields: ["spans"] });
MongoExporter
pip install "tracecast[mongo]" # Python
npm install mongodb # TypeScript
from tracecast.exporters.mongo import MongoExporter
exporter = MongoExporter(
uri="mongodb://localhost:27017",
db="myapp",
collection="traces",
include_fields={"trace_id", "cost_usd", "total_tokens", "model", "latency_ms"},
)
import { MongoExporter } from "tracecast/exporters/mongo";
const exporter = new MongoExporter("mongodb://localhost:27017", "myapp", "traces", {
excludeFields: ["spans"], // economiza espaço
});
await exporter.close(); // ao encerrar a aplicação
PostgresExporter
pip install psycopg2-binary # Python
npm install pg # TypeScript
from tracecast.exporters.postgres import PostgresExporter
# Tabela criada automaticamente; schema adapta-se a include/exclude
with PostgresExporter(
dsn="postgresql://user:pass@host:5432/db",
table="traces",
include_fields=["trace_id", "name", "model", "cost_usd", "total_tokens", "latency_ms"],
) as exporter:
tracer = Tracer(exporters=[exporter])
import { PostgresExporter } from "tracecast/exporters/postgres";
const exporter = new PostgresExporter("postgresql://user:pass@host:5432/db", "traces", {
excludeFields: ["spans", "metadata"],
});
await exporter.close();
DictExporter — sem arquivo, sem banco
Ideal para testes, pipelines customizados ou integração com filas.
from tracecast.exporters import DictExporter
# Coleta em lista (default)
exporter = DictExporter()
tracer = Tracer(exporters=[exporter])
with tracer.trace("run"):
...
print(exporter.traces) # [{"trace_id": ..., "cost_usd": ..., ...}]
# Ou via callback
exporter = DictExporter(
on_trace=lambda d: minha_fila.put(d),
include_fields={"trace_id", "cost_usd", "total_tokens"},
)
import { DictExporter } from "tracecast";
const exporter = new DictExporter({
onTrace: (d) => myQueue.push(d),
includeFields: ["traceId", "costUsd", "totalTokens"],
});
Custom Exporter
from tracecast.exporters.base import BaseExporter
class WebhookExporter(BaseExporter):
def __init__(self, url: str):
self.url = url
def export(self, trace) -> None:
import httpx
httpx.post(self.url, json=trace.to_dict())
import { BaseExporter } from "tracecast/exporters/base";
import type { Trace } from "tracecast";
class WebhookExporter implements BaseExporter {
constructor(private url: string) {}
async export(trace: Trace): Promise<void> {
await fetch(this.url, { method: "POST", body: JSON.stringify(trace) });
}
}
Logging Integrado
Habilite com um único flag — logs estruturados de todos os eventos LLM/Tool/Chain, sem código extra.
tracer = Tracer(
exporters=[...],
logging=True,
log_prefix="meu_agente", # opcional
)
const tracer = new Tracer({
exporters: [...],
logging: true,
logPrefix: "meu_agente",
// logFn: (msg) => myLogger.info(msg) // customizável
});
Saída:
[meu_agente] Trace started
[meu_agente] LLM started → gpt-4o
[meu_agente] Tool call → search_web | quem é Pedro Castanheira...
[meu_agente] Tool end → search_web | 0.82s
[meu_agente] LLM end → gpt-4o | tokens: 310 in / 95 out | $0.0019 | 2.10s
[meu_agente] Trace finished → total: 405 tokens | $0.0019 | 3.40s | tools: search_web×1
- Python: usa
logging.getLogger("tracecast")— configure comlogging.basicConfigou qualquer handler. - TypeScript: usa
console.logpor padrão — injetelogFn/warnFnpara redirecionar. - Cada linha é garantidamente uma única linha (newlines colapsados).
- Chain logging mostra apenas o nó raiz (sem spam de sub-nós internos do LangGraph).
Tabela de Preços Built-in
| Modelo | Input ($/1K tokens) | Output ($/1K tokens) |
|---|---|---|
gpt-5 |
0.00125 | 0.01000 |
gpt-5.4 |
0.00250 | 0.01500 |
gpt-4o |
0.00250 | 0.01000 |
gpt-4o-mini |
0.00015 | 0.00060 |
gpt-4.1 |
0.00200 | 0.00800 |
gpt-4.1-mini |
0.00040 | 0.00160 |
o3 |
0.00200 | 0.00800 |
o4-mini |
0.00110 | 0.00440 |
claude-opus-4-6 |
0.00500 | 0.02500 |
claude-sonnet-4-6 |
0.00300 | 0.01500 |
claude-haiku-4-5 |
0.00100 | 0.00500 |
claude-sonnet-4 |
0.00300 | 0.01500 |
claude-haiku-3-5 |
0.00080 | 0.00400 |
gemini-3.1-pro |
0.00200 | 0.01200 |
gemini-3-flash |
0.00050 | 0.00300 |
gemini-2.5-flash |
0.00030 | 0.00250 |
gemini-2.5-pro |
0.00125 | 0.01000 |
gemini-2.0-flash |
0.00010 | 0.00040 |
llama-4-scout |
0.00011 | 0.00034 |
llama-4-maverick |
0.00020 | 0.00060 |
llama-3.3-70b |
0.00059 | 0.00079 |
ollama/* |
0.00000 | 0.00000 |
Preços customizados:
tracer = Tracer(exporters=[...])
from tracecast.core.cost_calculator import calculate_cost
# Use custom_prices no calculate_cost
cost = calculate_cost("meu-modelo", tokens_in=100, tokens_out=50,
custom_prices={"meu-modelo": (0.001, 0.002)})
Modelo de Dados
Trace (Nível Superior)
| Campo | Tipo | Descrição |
|---|---|---|
trace_id |
str |
UUID único gerado para cada execução. |
name |
str |
Nome lógico da tarefa (ex: "pesquisa_web"). |
user_id |
str? |
ID do usuário final (útil para auditoria). |
session_id |
str? |
Identificador da conversa ou sessão. |
project_id |
str? |
Identificador do projeto no qual a IA está inserida. |
model |
str? |
O modelo que mais consumiu tokens neste trace. |
total_tokens_in |
int |
Soma de tokens_in de todos os spans LLM. |
total_tokens_out |
int |
Soma de tokens_out de todos os spans LLM. |
total_tokens |
int |
Soma total de tokens (in + out). |
cost_usd |
float |
Custo total estimado baseado na tabela built-in. |
latency_ms |
int? |
Tempo total decorrido (finished_at - started_at). |
tools_used |
dict |
Mapa {"tool_name": count} de todas as tools chamadas. |
spans |
list |
Lista de objetos Span. |
metadata |
dict |
Objeto livre para tags personalizadas. |
started_at |
ISO8601 |
Timestamp de início (UTC). |
finished_at |
ISO8601 |
Timestamp de conclusão (UTC). |
Span (Eventos Individuais)
| Campo | Tipo | Descrição |
|---|---|---|
span_id |
str |
UUID único do span. |
type |
Enum |
LLM (IA), TOOL (ferramenta), AGENT (passo intermediário). |
name |
str |
Nome descritivo (ex: "gpt-4o:summarize"). |
model |
str? |
Modelo específico usado neste span. |
tokens_in |
int? |
Tokens enviados ao modelo. |
tokens_out |
int? |
Tokens gerados pelo modelo. |
cost_usd |
float? |
Custo específico deste span. |
latency_ms |
int? |
Tempo de resposta deste span individual. |
started_at |
ISO8601 |
Início do evento. |
finished_at |
ISO8601 |
Fim do evento. |
metadata |
dict? |
Dados extras (ex: inputs da tool ou erros via _error). |
Resiliência
- Exceção no código do usuário → trace sempre finalizado e exportado via
finally. - Exceção no exporter → engolida com
warning— nunca interrompe o código principal. failOnExportError: true(TypeScript) → propaga erros do exporter se quiser controle explícito.- Traces aninhados (Python) → suportados via stack de
ContextVar. - Multi-tenant → seguro — cada coroutine/thread tem seu próprio contexto isolado.
Testes
# Python
cd packages/tracecast-py
pip install -e ".[all]"
python -m pytest tests/ -q
# → 128 passed
# TypeScript
cd packages/tracecast-ts
npm install
npx jest --forceExit
# → 113 passed
Licença
MIT © Pedro Castanheira Costa
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tracecast-0.1.1.tar.gz.
File metadata
- Download URL: tracecast-0.1.1.tar.gz
- Upload date:
- Size: 36.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
397bf29f19c314f56e0f59bb5de1cc828df68728a721df14db73fda0f567c5f8
|
|
| MD5 |
8b4259b5faedf018872f66f989cde422
|
|
| BLAKE2b-256 |
34b17777006c653686eb6027d9cc170d71d57f4b91ab61ee25ce2c6ff25fa879
|
File details
Details for the file tracecast-0.1.1-py3-none-any.whl.
File metadata
- Download URL: tracecast-0.1.1-py3-none-any.whl
- Upload date:
- Size: 21.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6f00ab8a9eec5af25e3c220fb83a63630587c04b79b24c0ae4c0baf113faad94
|
|
| MD5 |
31fb9f11e703044e38b566c84b7d3829
|
|
| BLAKE2b-256 |
74b78b57f5463f2ebdc154ef05b4b49506f8fb0e5d66fb1a3b1b2f66fefa2e50
|