tracecast 0.1.2__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. tracecast-0.2.2/PKG-INFO +342 -0
  2. tracecast-0.2.2/README.md +308 -0
  3. {tracecast-0.1.2 → tracecast-0.2.2}/pyproject.toml +9 -2
  4. tracecast-0.2.2/tests/test_context_propagation.py +68 -0
  5. tracecast-0.2.2/tests/test_dashboard_mount.py +25 -0
  6. tracecast-0.2.2/tests/test_dashboard_pushdown.py +106 -0
  7. tracecast-0.2.2/tests/test_dashboard_sessions.py +126 -0
  8. tracecast-0.2.2/tests/test_decorators.py +85 -0
  9. tracecast-0.2.2/tests/test_e2e_langgraph.py +129 -0
  10. {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_exporters_db.py +34 -7
  11. {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_integration_frameworks.py +2 -2
  12. {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_langchain_callback.py +37 -4
  13. tracecast-0.2.2/tests/test_llm_wrappers.py +46 -0
  14. tracecast-0.2.2/tests/test_mongo_exporter.py +36 -0
  15. tracecast-0.2.2/tests/test_prompt_dashboard.py +56 -0
  16. tracecast-0.2.2/tests/test_prompts.py +84 -0
  17. tracecast-0.2.2/tests/test_roundtrip.py +82 -0
  18. tracecast-0.2.2/tests/test_schema_v2.py +91 -0
  19. tracecast-0.2.2/tests/test_score.py +56 -0
  20. tracecast-0.2.2/tests/test_score_dashboard.py +49 -0
  21. tracecast-0.2.2/tests/test_scoring_api.py +49 -0
  22. tracecast-0.2.2/tests/test_serve.py +53 -0
  23. tracecast-0.2.2/tests/test_streaming.py +121 -0
  24. tracecast-0.2.2/tests/test_trace_span.py +102 -0
  25. {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_tracer.py +24 -6
  26. tracecast-0.2.2/tracecast/__init__.py +22 -0
  27. tracecast-0.2.2/tracecast/core/scoring.py +53 -0
  28. tracecast-0.2.2/tracecast/core/token_counter.py +114 -0
  29. tracecast-0.2.2/tracecast/core/tracer.py +223 -0
  30. tracecast-0.2.2/tracecast/dashboard/aggregator.py +341 -0
  31. tracecast-0.2.2/tracecast/dashboard/asgi_middleware.py +28 -0
  32. tracecast-0.2.2/tracecast/dashboard/blueprint.py +140 -0
  33. tracecast-0.2.2/tracecast/dashboard/eval_reader.py +28 -0
  34. tracecast-0.2.2/tracecast/dashboard/prompt_reader.py +40 -0
  35. tracecast-0.2.2/tracecast/dashboard/reader.py +240 -0
  36. tracecast-0.2.2/tracecast/dashboard/router.py +292 -0
  37. tracecast-0.2.2/tracecast/dashboard/score_reader.py +28 -0
  38. tracecast-0.2.2/tracecast/dashboard/standalone.py +73 -0
  39. tracecast-0.2.2/tracecast/dashboard/static/assets/index-CBra-omk.js +151 -0
  40. tracecast-0.2.2/tracecast/dashboard/static/assets/index-DllJJzmE.css +1 -0
  41. tracecast-0.2.2/tracecast/dashboard/static/index.html +13 -0
  42. tracecast-0.2.2/tracecast/decorators.py +169 -0
  43. tracecast-0.2.2/tracecast/eval/__init__.py +25 -0
  44. tracecast-0.2.2/tracecast/eval/cli.py +59 -0
  45. tracecast-0.2.2/tracecast/eval/compare.py +42 -0
  46. tracecast-0.2.2/tracecast/eval/dataset.py +136 -0
  47. tracecast-0.2.2/tracecast/eval/decorator.py +72 -0
  48. tracecast-0.2.2/tracecast/eval/judge.py +124 -0
  49. tracecast-0.2.2/tracecast/eval/metrics.py +128 -0
  50. tracecast-0.2.2/tracecast/eval/models.py +221 -0
  51. tracecast-0.2.2/tracecast/eval/online.py +71 -0
  52. tracecast-0.2.2/tracecast/eval/runner.py +160 -0
  53. tracecast-0.2.2/tracecast/eval/scorers.py +42 -0
  54. tracecast-0.2.2/tracecast/exporters/_eval_store.py +59 -0
  55. tracecast-0.2.2/tracecast/exporters/base.py +44 -0
  56. tracecast-0.2.2/tracecast/exporters/dict_exporter.py +79 -0
  57. tracecast-0.2.2/tracecast/exporters/json_file.py +117 -0
  58. tracecast-0.2.2/tracecast/exporters/mongo.py +182 -0
  59. tracecast-0.2.2/tracecast/exporters/postgres.py +446 -0
  60. tracecast-0.2.2/tracecast/exporters/query.py +23 -0
  61. tracecast-0.2.2/tracecast/instrument.py +47 -0
  62. tracecast-0.2.2/tracecast/instrumentors/__init__.py +41 -0
  63. tracecast-0.2.2/tracecast/instrumentors/_llamaindex_handler.py +249 -0
  64. tracecast-0.2.2/tracecast/instrumentors/_streaming.py +94 -0
  65. tracecast-0.2.2/tracecast/instrumentors/anthropic_inst.py +154 -0
  66. tracecast-0.2.2/tracecast/instrumentors/base.py +21 -0
  67. tracecast-0.2.2/tracecast/instrumentors/crewai_inst.py +97 -0
  68. tracecast-0.2.2/tracecast/instrumentors/gemini_inst.py +86 -0
  69. tracecast-0.2.2/tracecast/instrumentors/langchain_inst.py +132 -0
  70. tracecast-0.2.2/tracecast/instrumentors/llamaindex_inst.py +52 -0
  71. tracecast-0.2.2/tracecast/instrumentors/openai_inst.py +174 -0
  72. {tracecast-0.1.2 → tracecast-0.2.2}/tracecast/integrations/langchain.py +97 -1
  73. tracecast-0.2.2/tracecast/integrations/llm.py +143 -0
  74. tracecast-0.2.2/tracecast/middleware.py +31 -0
  75. tracecast-0.2.2/tracecast/models/__init__.py +0 -0
  76. tracecast-0.2.2/tracecast/models/score.py +84 -0
  77. {tracecast-0.1.2 → tracecast-0.2.2}/tracecast/models/span.py +19 -0
  78. {tracecast-0.1.2 → tracecast-0.2.2}/tracecast/models/trace.py +29 -1
  79. tracecast-0.2.2/tracecast/prompts/__init__.py +4 -0
  80. tracecast-0.2.2/tracecast/prompts/client.py +134 -0
  81. tracecast-0.2.2/tracecast/prompts/models.py +49 -0
  82. tracecast-0.2.2/tracecast/serve.py +75 -0
  83. tracecast-0.2.2/tracecast.egg-info/PKG-INFO +342 -0
  84. tracecast-0.2.2/tracecast.egg-info/SOURCES.txt +101 -0
  85. tracecast-0.2.2/tracecast.egg-info/entry_points.txt +2 -0
  86. tracecast-0.2.2/tracecast.egg-info/requires.txt +16 -0
  87. tracecast-0.1.2/PKG-INFO +0 -696
  88. tracecast-0.1.2/README.md +0 -665
  89. tracecast-0.1.2/tests/test_mongo_exporter.py +0 -23
  90. tracecast-0.1.2/tracecast/__init__.py +0 -6
  91. tracecast-0.1.2/tracecast/core/token_counter.py +0 -46
  92. tracecast-0.1.2/tracecast/core/tracer.py +0 -106
  93. tracecast-0.1.2/tracecast/exporters/base.py +0 -15
  94. tracecast-0.1.2/tracecast/exporters/dict_exporter.py +0 -35
  95. tracecast-0.1.2/tracecast/exporters/json_file.py +0 -37
  96. tracecast-0.1.2/tracecast/exporters/mongo.py +0 -34
  97. tracecast-0.1.2/tracecast/exporters/postgres.py +0 -164
  98. tracecast-0.1.2/tracecast.egg-info/PKG-INFO +0 -696
  99. tracecast-0.1.2/tracecast.egg-info/SOURCES.txt +0 -37
  100. tracecast-0.1.2/tracecast.egg-info/requires.txt +0 -12
  101. {tracecast-0.1.2 → tracecast-0.2.2}/LICENSE +0 -0
  102. {tracecast-0.1.2 → tracecast-0.2.2}/setup.cfg +0 -0
  103. {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_cost_calculator.py +0 -0
  104. {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_integration_langchain.py +0 -0
  105. {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_json_exporter.py +0 -0
  106. {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_logging.py +0 -0
  107. {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_span.py +0 -0
  108. {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_token_counter.py +0 -0
  109. {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_trace.py +0 -0
  110. {tracecast-0.1.2 → tracecast-0.2.2}/tracecast/core/__init__.py +0 -0
  111. {tracecast-0.1.2 → tracecast-0.2.2}/tracecast/core/cost_calculator.py +0 -0
  112. {tracecast-0.1.2 → tracecast-0.2.2}/tracecast/core/logger.py +0 -0
  113. {tracecast-0.1.2/tracecast/integrations → tracecast-0.2.2/tracecast/dashboard}/__init__.py +0 -0
  114. {tracecast-0.1.2 → tracecast-0.2.2}/tracecast/exporters/__init__.py +0 -0
  115. {tracecast-0.1.2/tracecast/models → tracecast-0.2.2/tracecast/integrations}/__init__.py +0 -0
  116. {tracecast-0.1.2 → tracecast-0.2.2}/tracecast.egg-info/dependency_links.txt +0 -0
  117. {tracecast-0.1.2 → tracecast-0.2.2}/tracecast.egg-info/top_level.txt +0 -0
@@ -0,0 +1,342 @@
1
+ Metadata-Version: 2.4
2
+ Name: tracecast
3
+ Version: 0.2.2
4
+ Summary: LLM observability SDK — framework-agnostic tracing for AI applications
5
+ Author: Pedro Castanheira Costa
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/pedrocastanha/tracecast
8
+ Project-URL: Repository, https://github.com/pedrocastanha/tracecast
9
+ Project-URL: Issues, https://github.com/pedrocastanha/tracecast/issues
10
+ Keywords: llm,observability,tracing,langchain,openai,anthropic
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Provides-Extra: mongo
23
+ Requires-Dist: pymongo>=4.6; extra == "mongo"
24
+ Provides-Extra: postgres
25
+ Requires-Dist: psycopg2>=2.9; extra == "postgres"
26
+ Provides-Extra: langchain
27
+ Requires-Dist: langchain-core>=0.3; extra == "langchain"
28
+ Provides-Extra: dashboard
29
+ Requires-Dist: fastapi>=0.100.0; extra == "dashboard"
30
+ Requires-Dist: uvicorn>=0.20; extra == "dashboard"
31
+ Provides-Extra: all
32
+ Requires-Dist: tracecast[dashboard,langchain,mongo,postgres]; extra == "all"
33
+ Dynamic: license-file
34
+
35
+ # TraceCast
36
+
37
+ > **SDK de observabilidade para LLMs** — rastreie tokens, custo, latência, tool calls e o **grafo completo** de agentes em qualquer framework de IA. Self-hosted, framework-agnostic, Python.
38
+
39
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://pypi.org/project/tracecast/)
40
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
41
+ [![Tests](https://img.shields.io/badge/tests-242%20passing-brightgreen)](#testes)
42
+
43
+ ---
44
+
45
+ ## O que é
46
+
47
+ TraceCast captura **automaticamente** cada interação dentro de uma request de IA — nós, arestas,
48
+ guardrails, chamadas LLM e tools — e exporta para onde você quiser: MongoDB, PostgreSQL, arquivo JSONL,
49
+ ou qualquer destino customizado. Um dashboard self-hosted permite visualizar o **grafo percorrido**,
50
+ filtrar traces e inspecionar cada etapa.
51
+
52
+ - **Captura o fluxo inteiro** — hierarquia pai→filho de spans e arestas (`from → to`) reconstroem o grafo.
53
+ - **Zero configuração** para LangChain/LangGraph — um callback (ou `auto_instrument()`) resolve tudo.
54
+ - **Dados confiáveis** — tokens (incl. streaming e cache), custo, latência e status por etapa.
55
+ - **Save resiliente** — falhas de export são logadas e expostas por hook, nunca silenciosas.
56
+ - **Dashboard separável** — rode embutido no app ou standalone numa VM lendo o storage remoto.
57
+ - **Zero dependências core** — instale só o que for usar.
58
+
59
+ ---
60
+
61
+ ## Instalação
62
+
63
+ ```bash
64
+ # Core (sem dependências)
65
+ pip install tracecast
66
+
67
+ # Extras
68
+ pip install "tracecast[mongo]" # MongoExporter
69
+ pip install "tracecast[postgres]" # PostgresExporter
70
+ pip install "tracecast[langchain]" # LangChain / LangGraph
71
+ pip install "tracecast[dashboard]" # FastAPI + uvicorn (dashboard/servidor)
72
+ pip install "tracecast[all]" # tudo
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Início rápido
78
+
79
+ ### LangGraph / LangChain (captura automática)
80
+
81
+ ```python
82
+ from tracecast import Tracer, auto_instrument
83
+ from tracecast.exporters.mongo import MongoExporter
84
+
85
+ tracer = Tracer(exporters=[MongoExporter("mongodb://localhost:27017", db="myapp")], logging=True)
86
+ auto_instrument(tracer) # registra o callback global do LangChain
87
+
88
+ # decore a função/rota que inicia a request
89
+ from tracecast import trace_cast
90
+
91
+ @trace_cast(project_id="suporte", user_id="u1")
92
+ def handle(message: str):
93
+ return app.invoke({"messages": [message]}) # seu StateGraph compilado
94
+ ```
95
+
96
+ Cada nó do grafo vira um span com pai correto; arestas percorridas são derivadas automaticamente.
97
+ Tokens, custo, latência e status são preenchidos por chamada e agregados no trace.
98
+
99
+ ### Span manual
100
+
101
+ ```python
102
+ from tracecast import Tracer, Span, SpanType, calculate_cost
103
+ from tracecast.exporters import JsonFileExporter
104
+ from datetime import datetime, timezone
105
+ import uuid
106
+
107
+ tracer = Tracer(exporters=[JsonFileExporter("./traces.jsonl")])
108
+
109
+ with tracer.trace("minha_run", user_id="usr_1") as trace:
110
+ span = Span(
111
+ span_id=str(uuid.uuid4()),
112
+ type=SpanType.LLM,
113
+ name="llm:gpt-4o",
114
+ model="gpt-4o",
115
+ started_at=datetime.now(timezone.utc),
116
+ finished_at=datetime.now(timezone.utc),
117
+ tokens_in=120,
118
+ tokens_out=80,
119
+ )
120
+ span.cost_usd = calculate_cost("gpt-4o", span.tokens_in, span.tokens_out)
121
+ trace.spans.append(span)
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Decorators
127
+
128
+ | Decorator | Uso |
129
+ |-----------|-----|
130
+ | `@trace_cast(...)` | Abre um **trace** (request inteira). Coloque na rota/handler de entrada. Sync e async. |
131
+ | `@trace_span(name=..., type=...)` | Cria um **span filho** para qualquer função (guardrails, validações, etapas). Captura input/output/latência/status. Aninha sob o span ativo. |
132
+
133
+ ```python
134
+ from tracecast import trace_cast, trace_span
135
+ from tracecast.models.span import SpanType
136
+
137
+ @trace_span(name="guardrail_pii", type=SpanType.TOOL)
138
+ def check_pii(text: str) -> str:
139
+ ...
140
+ return cleaned
141
+
142
+ @trace_cast(project_id="suporte")
143
+ async def handle(req):
144
+ safe = check_pii(req.text) # vira span filho com input/output
145
+ return await agent.ainvoke(safe)
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Frameworks
151
+
152
+ ### OpenAI / Anthropic (auto-instrument, inclui streaming)
153
+
154
+ ```python
155
+ import openai
156
+ from tracecast import Tracer, auto_instrument
157
+
158
+ tracer = Tracer(exporters=[...])
159
+ auto_instrument(tracer) # faz patch em openai/anthropic/gemini
160
+
161
+ client = openai.OpenAI()
162
+ with tracer.trace("openai-run"):
163
+ # não-streaming e streaming são capturados (tokens via include_usage)
164
+ stream = client.chat.completions.create(
165
+ model="gpt-4o", messages=[{"role": "user", "content": "Olá!"}], stream=True,
166
+ )
167
+ for chunk in stream:
168
+ ...
169
+ ```
170
+
171
+ Alternativa explícita sem patch global: `wrap_openai(client)` / `wrap_anthropic(client)`.
172
+
173
+ ### CrewAI / LlamaIndex
174
+
175
+ `auto_instrument()` registra os instrumentors disponíveis. Veja `real_examples/` para setups completos.
176
+
177
+ ---
178
+
179
+ ## Propagação de contexto em threads
180
+
181
+ ContextVars propagam para tasks asyncio filhas, mas **não** para threads (`run_in_executor`/ThreadPool).
182
+ Use `bind_context` para capturar o trace ativo e rodar a função no worker:
183
+
184
+ ```python
185
+ from tracecast import bind_context
186
+
187
+ # ThreadPoolExecutor
188
+ pool.submit(bind_context(node_fn, state))
189
+
190
+ # asyncio executor
191
+ await loop.run_in_executor(None, bind_context(node_fn, state))
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Exporters
197
+
198
+ Todos aceitam `include_fields` / `exclude_fields`. Mongo e Postgres expõem `query/get/count` usados pelo
199
+ dashboard para **filtrar no banco** (data, projeto, usuário, sessão) — sem carregar tudo em memória.
200
+
201
+ ```python
202
+ from tracecast.exporters import JsonFileExporter, DictExporter
203
+ from tracecast.exporters.mongo import MongoExporter
204
+ from tracecast.exporters.postgres import PostgresExporter
205
+
206
+ JsonFileExporter("./traces.jsonl", exclude_fields={"spans"})
207
+ MongoExporter("mongodb://localhost:27017", db="myapp", collection="traces") # upsert por trace_id
208
+ PostgresExporter("postgresql://user:pass@host:5432/db", table="traces") # ON CONFLICT upsert
209
+ DictExporter(on_trace=lambda d: fila.put(d))
210
+ ```
211
+
212
+ Custom:
213
+
214
+ ```python
215
+ from tracecast.exporters.base import BaseExporter
216
+
217
+ class WebhookExporter(BaseExporter):
218
+ def __init__(self, url): self.url = url
219
+ def export(self, trace) -> None:
220
+ import httpx; httpx.post(self.url, json=trace.to_dict())
221
+ ```
222
+
223
+ ---
224
+
225
+ ## Dashboard
226
+
227
+ O dashboard (SPA React) é servido pelo backend Python. Duas formas de uso:
228
+
229
+ ### 1. Embutido no app
230
+
231
+ ```python
232
+ from fastapi import FastAPI
233
+ app = FastAPI()
234
+ tracer.mount(app, prefix="/tracecast") # dashboard em /tracecast, API em /tracecast/api
235
+ ```
236
+
237
+ ### 2. Standalone numa VM (lê o storage remoto)
238
+
239
+ O servidor não importa o app de produção — só aponta para o mesmo banco.
240
+
241
+ ```bash
242
+ pip install "tracecast[dashboard,mongo]"
243
+
244
+ export TRACECAST_STORE="mongodb://prod-host:27017" # postgresql:// ou file:// também
245
+ export TRACECAST_DB="myapp"
246
+ export TRACECAST_PORT=7777
247
+ export TRACECAST_AUTH="admin:senha" # basic auth opcional
248
+ export TRACECAST_CORS="https://meu-front.com" # opcional
249
+
250
+ tracecast-server
251
+ ```
252
+
253
+ Variáveis: `TRACECAST_STORE` (obrigatória), `TRACECAST_HOST`, `TRACECAST_PORT`, `TRACECAST_PREFIX`,
254
+ `TRACECAST_DB`, `TRACECAST_COLLECTION`, `TRACECAST_TABLE`, `TRACECAST_AUTH`, `TRACECAST_CORS`,
255
+ `TRACECAST_MAX_TRACES`.
256
+
257
+ **Recursos:** filtros por data/projeto/usuário/sessão (server-side), lista paginada, e visualização do
258
+ **grafo DAG** percorrido (nós + arestas) com detalhe de input/output/tokens/custo/latência/status ao
259
+ clicar em cada etapa.
260
+
261
+ **API REST** (`{prefix}/api`): `/traces`, `/traces/{id}`, `/traces/{id}/graph`, `/metrics`,
262
+ `/sessions`, `/projects`, `/health`.
263
+
264
+ ---
265
+
266
+ ## Logging integrado
267
+
268
+ ```python
269
+ tracer = Tracer(exporters=[...], logging=True, log_prefix="meu_agente")
270
+ ```
271
+
272
+ ```
273
+ [meu_agente] Trace started
274
+ [meu_agente] LLM end → gpt-4o | tokens: 310 in / 95 out | $0.0019 | 2.10s
275
+ [meu_agente] Trace finished → total: 405 tokens | $0.0019 | 3.40s | tools: search_web×1
276
+ ```
277
+
278
+ Usa `logging.getLogger("tracecast")`. Configure com `logging.basicConfig` ou qualquer handler.
279
+
280
+ ---
281
+
282
+ ## Modelo de dados
283
+
284
+ `to_dict()` inclui `schema_version: 2`. Leitura é retrocompatível com v1.
285
+
286
+ ### Trace
287
+ | Campo | Tipo | Descrição |
288
+ |-------|------|-----------|
289
+ | `trace_id` | `str` | UUID da execução. |
290
+ | `name`, `user_id`, `session_id`, `project_id` | `str?` | Identificadores. |
291
+ | `model` | `str?` | Modelo que mais consumiu tokens. |
292
+ | `total_tokens_in/out/_in_cached/total_tokens` | `int` | Agregados dos spans. |
293
+ | `cost_usd` | `float` | Custo total. |
294
+ | `latency_ms` | `int?` | Wall-clock da request. |
295
+ | `tools_used` | `dict` | `{tool: count}`. |
296
+ | `spans` | `list[Span]` | Spans da request. |
297
+ | `edges` | `list` | Arestas percorridas: `{from, to, parent_span_id, conditional}`. |
298
+ | `metadata`, `started_at`, `finished_at` | | |
299
+
300
+ ### Span
301
+ | Campo | Tipo | Descrição |
302
+ |-------|------|-----------|
303
+ | `span_id` | `str` | UUID. |
304
+ | `parent_span_id` | `str?` | Pai na árvore (None = raiz). |
305
+ | `type` | `Enum` | `LLM` / `TOOL` / `AGENT`. |
306
+ | `name` | `str` | Nome descritivo. |
307
+ | `status` | `Enum` | `ok` / `error`. |
308
+ | `error` | `str?` | Mensagem quando `status == error`. |
309
+ | `model`, `tokens_in/out/_in_cached`, `cost_usd`, `latency_ms` | | Métricas. |
310
+ | `input`, `output`, `metadata` | | Conteúdo da etapa. |
311
+ | `started_at`, `finished_at` | `ISO8601` | |
312
+
313
+ ---
314
+
315
+ ## Resiliência
316
+
317
+ 1. **Exceção no código do usuário** → trace sempre finalizado e exportado via `finally`.
318
+ 2. **Exceção no exporter** → logada em nível `ERROR` e exposta via hook `on_export_error`; **nunca**
319
+ silenciosa e nunca interrompe o app. Outros exporters continuam.
320
+ ```python
321
+ Tracer(exporters=[...], on_export_error=lambda exc, trace, exp: alertar(exc))
322
+ ```
323
+ 3. **Export assíncrono não-bloqueante** — `aexport` roda fora do event loop (offload em thread).
324
+ 4. **Spans com erro** marcados com `status="error"` + `error` (first-class, não enterrado em metadata).
325
+ 5. **Multi-tenant / concorrência** — cada coroutine/thread isola seu próprio trace via `ContextVar`.
326
+
327
+ ---
328
+
329
+ ## Testes
330
+
331
+ ```bash
332
+ cd packages/tracecast-py
333
+ pip install -e ".[all]" pytest
334
+ python -m pytest tests/ -q
335
+ # → 242 passed
336
+ ```
337
+
338
+ ---
339
+
340
+ ## Licença
341
+
342
+ MIT © Pedro Castanheira Costa
@@ -0,0 +1,308 @@
1
+ # TraceCast
2
+
3
+ > **SDK de observabilidade para LLMs** — rastreie tokens, custo, latência, tool calls e o **grafo completo** de agentes em qualquer framework de IA. Self-hosted, framework-agnostic, Python.
4
+
5
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://pypi.org/project/tracecast/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
7
+ [![Tests](https://img.shields.io/badge/tests-242%20passing-brightgreen)](#testes)
8
+
9
+ ---
10
+
11
+ ## O que é
12
+
13
+ TraceCast captura **automaticamente** cada interação dentro de uma request de IA — nós, arestas,
14
+ guardrails, chamadas LLM e tools — e exporta para onde você quiser: MongoDB, PostgreSQL, arquivo JSONL,
15
+ ou qualquer destino customizado. Um dashboard self-hosted permite visualizar o **grafo percorrido**,
16
+ filtrar traces e inspecionar cada etapa.
17
+
18
+ - **Captura o fluxo inteiro** — hierarquia pai→filho de spans e arestas (`from → to`) reconstroem o grafo.
19
+ - **Zero configuração** para LangChain/LangGraph — um callback (ou `auto_instrument()`) resolve tudo.
20
+ - **Dados confiáveis** — tokens (incl. streaming e cache), custo, latência e status por etapa.
21
+ - **Save resiliente** — falhas de export são logadas e expostas por hook, nunca silenciosas.
22
+ - **Dashboard separável** — rode embutido no app ou standalone numa VM lendo o storage remoto.
23
+ - **Zero dependências core** — instale só o que for usar.
24
+
25
+ ---
26
+
27
+ ## Instalação
28
+
29
+ ```bash
30
+ # Core (sem dependências)
31
+ pip install tracecast
32
+
33
+ # Extras
34
+ pip install "tracecast[mongo]" # MongoExporter
35
+ pip install "tracecast[postgres]" # PostgresExporter
36
+ pip install "tracecast[langchain]" # LangChain / LangGraph
37
+ pip install "tracecast[dashboard]" # FastAPI + uvicorn (dashboard/servidor)
38
+ pip install "tracecast[all]" # tudo
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Início rápido
44
+
45
+ ### LangGraph / LangChain (captura automática)
46
+
47
+ ```python
48
+ from tracecast import Tracer, auto_instrument
49
+ from tracecast.exporters.mongo import MongoExporter
50
+
51
+ tracer = Tracer(exporters=[MongoExporter("mongodb://localhost:27017", db="myapp")], logging=True)
52
+ auto_instrument(tracer) # registra o callback global do LangChain
53
+
54
+ # decore a função/rota que inicia a request
55
+ from tracecast import trace_cast
56
+
57
+ @trace_cast(project_id="suporte", user_id="u1")
58
+ def handle(message: str):
59
+ return app.invoke({"messages": [message]}) # seu StateGraph compilado
60
+ ```
61
+
62
+ Cada nó do grafo vira um span com pai correto; arestas percorridas são derivadas automaticamente.
63
+ Tokens, custo, latência e status são preenchidos por chamada e agregados no trace.
64
+
65
+ ### Span manual
66
+
67
+ ```python
68
+ from tracecast import Tracer, Span, SpanType, calculate_cost
69
+ from tracecast.exporters import JsonFileExporter
70
+ from datetime import datetime, timezone
71
+ import uuid
72
+
73
+ tracer = Tracer(exporters=[JsonFileExporter("./traces.jsonl")])
74
+
75
+ with tracer.trace("minha_run", user_id="usr_1") as trace:
76
+ span = Span(
77
+ span_id=str(uuid.uuid4()),
78
+ type=SpanType.LLM,
79
+ name="llm:gpt-4o",
80
+ model="gpt-4o",
81
+ started_at=datetime.now(timezone.utc),
82
+ finished_at=datetime.now(timezone.utc),
83
+ tokens_in=120,
84
+ tokens_out=80,
85
+ )
86
+ span.cost_usd = calculate_cost("gpt-4o", span.tokens_in, span.tokens_out)
87
+ trace.spans.append(span)
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Decorators
93
+
94
+ | Decorator | Uso |
95
+ |-----------|-----|
96
+ | `@trace_cast(...)` | Abre um **trace** (request inteira). Coloque na rota/handler de entrada. Sync e async. |
97
+ | `@trace_span(name=..., type=...)` | Cria um **span filho** para qualquer função (guardrails, validações, etapas). Captura input/output/latência/status. Aninha sob o span ativo. |
98
+
99
+ ```python
100
+ from tracecast import trace_cast, trace_span
101
+ from tracecast.models.span import SpanType
102
+
103
+ @trace_span(name="guardrail_pii", type=SpanType.TOOL)
104
+ def check_pii(text: str) -> str:
105
+ ...
106
+ return cleaned
107
+
108
+ @trace_cast(project_id="suporte")
109
+ async def handle(req):
110
+ safe = check_pii(req.text) # vira span filho com input/output
111
+ return await agent.ainvoke(safe)
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Frameworks
117
+
118
+ ### OpenAI / Anthropic (auto-instrument, inclui streaming)
119
+
120
+ ```python
121
+ import openai
122
+ from tracecast import Tracer, auto_instrument
123
+
124
+ tracer = Tracer(exporters=[...])
125
+ auto_instrument(tracer) # faz patch em openai/anthropic/gemini
126
+
127
+ client = openai.OpenAI()
128
+ with tracer.trace("openai-run"):
129
+ # não-streaming e streaming são capturados (tokens via include_usage)
130
+ stream = client.chat.completions.create(
131
+ model="gpt-4o", messages=[{"role": "user", "content": "Olá!"}], stream=True,
132
+ )
133
+ for chunk in stream:
134
+ ...
135
+ ```
136
+
137
+ Alternativa explícita sem patch global: `wrap_openai(client)` / `wrap_anthropic(client)`.
138
+
139
+ ### CrewAI / LlamaIndex
140
+
141
+ `auto_instrument()` registra os instrumentors disponíveis. Veja `real_examples/` para setups completos.
142
+
143
+ ---
144
+
145
+ ## Propagação de contexto em threads
146
+
147
+ ContextVars propagam para tasks asyncio filhas, mas **não** para threads (`run_in_executor`/ThreadPool).
148
+ Use `bind_context` para capturar o trace ativo e rodar a função no worker:
149
+
150
+ ```python
151
+ from tracecast import bind_context
152
+
153
+ # ThreadPoolExecutor
154
+ pool.submit(bind_context(node_fn, state))
155
+
156
+ # asyncio executor
157
+ await loop.run_in_executor(None, bind_context(node_fn, state))
158
+ ```
159
+
160
+ ---
161
+
162
+ ## Exporters
163
+
164
+ Todos aceitam `include_fields` / `exclude_fields`. Mongo e Postgres expõem `query/get/count` usados pelo
165
+ dashboard para **filtrar no banco** (data, projeto, usuário, sessão) — sem carregar tudo em memória.
166
+
167
+ ```python
168
+ from tracecast.exporters import JsonFileExporter, DictExporter
169
+ from tracecast.exporters.mongo import MongoExporter
170
+ from tracecast.exporters.postgres import PostgresExporter
171
+
172
+ JsonFileExporter("./traces.jsonl", exclude_fields={"spans"})
173
+ MongoExporter("mongodb://localhost:27017", db="myapp", collection="traces") # upsert por trace_id
174
+ PostgresExporter("postgresql://user:pass@host:5432/db", table="traces") # ON CONFLICT upsert
175
+ DictExporter(on_trace=lambda d: fila.put(d))
176
+ ```
177
+
178
+ Custom:
179
+
180
+ ```python
181
+ from tracecast.exporters.base import BaseExporter
182
+
183
+ class WebhookExporter(BaseExporter):
184
+ def __init__(self, url): self.url = url
185
+ def export(self, trace) -> None:
186
+ import httpx; httpx.post(self.url, json=trace.to_dict())
187
+ ```
188
+
189
+ ---
190
+
191
+ ## Dashboard
192
+
193
+ O dashboard (SPA React) é servido pelo backend Python. Duas formas de uso:
194
+
195
+ ### 1. Embutido no app
196
+
197
+ ```python
198
+ from fastapi import FastAPI
199
+ app = FastAPI()
200
+ tracer.mount(app, prefix="/tracecast") # dashboard em /tracecast, API em /tracecast/api
201
+ ```
202
+
203
+ ### 2. Standalone numa VM (lê o storage remoto)
204
+
205
+ O servidor não importa o app de produção — só aponta para o mesmo banco.
206
+
207
+ ```bash
208
+ pip install "tracecast[dashboard,mongo]"
209
+
210
+ export TRACECAST_STORE="mongodb://prod-host:27017" # postgresql:// ou file:// também
211
+ export TRACECAST_DB="myapp"
212
+ export TRACECAST_PORT=7777
213
+ export TRACECAST_AUTH="admin:senha" # basic auth opcional
214
+ export TRACECAST_CORS="https://meu-front.com" # opcional
215
+
216
+ tracecast-server
217
+ ```
218
+
219
+ Variáveis: `TRACECAST_STORE` (obrigatória), `TRACECAST_HOST`, `TRACECAST_PORT`, `TRACECAST_PREFIX`,
220
+ `TRACECAST_DB`, `TRACECAST_COLLECTION`, `TRACECAST_TABLE`, `TRACECAST_AUTH`, `TRACECAST_CORS`,
221
+ `TRACECAST_MAX_TRACES`.
222
+
223
+ **Recursos:** filtros por data/projeto/usuário/sessão (server-side), lista paginada, e visualização do
224
+ **grafo DAG** percorrido (nós + arestas) com detalhe de input/output/tokens/custo/latência/status ao
225
+ clicar em cada etapa.
226
+
227
+ **API REST** (`{prefix}/api`): `/traces`, `/traces/{id}`, `/traces/{id}/graph`, `/metrics`,
228
+ `/sessions`, `/projects`, `/health`.
229
+
230
+ ---
231
+
232
+ ## Logging integrado
233
+
234
+ ```python
235
+ tracer = Tracer(exporters=[...], logging=True, log_prefix="meu_agente")
236
+ ```
237
+
238
+ ```
239
+ [meu_agente] Trace started
240
+ [meu_agente] LLM end → gpt-4o | tokens: 310 in / 95 out | $0.0019 | 2.10s
241
+ [meu_agente] Trace finished → total: 405 tokens | $0.0019 | 3.40s | tools: search_web×1
242
+ ```
243
+
244
+ Usa `logging.getLogger("tracecast")`. Configure com `logging.basicConfig` ou qualquer handler.
245
+
246
+ ---
247
+
248
+ ## Modelo de dados
249
+
250
+ `to_dict()` inclui `schema_version: 2`. Leitura é retrocompatível com v1.
251
+
252
+ ### Trace
253
+ | Campo | Tipo | Descrição |
254
+ |-------|------|-----------|
255
+ | `trace_id` | `str` | UUID da execução. |
256
+ | `name`, `user_id`, `session_id`, `project_id` | `str?` | Identificadores. |
257
+ | `model` | `str?` | Modelo que mais consumiu tokens. |
258
+ | `total_tokens_in/out/_in_cached/total_tokens` | `int` | Agregados dos spans. |
259
+ | `cost_usd` | `float` | Custo total. |
260
+ | `latency_ms` | `int?` | Wall-clock da request. |
261
+ | `tools_used` | `dict` | `{tool: count}`. |
262
+ | `spans` | `list[Span]` | Spans da request. |
263
+ | `edges` | `list` | Arestas percorridas: `{from, to, parent_span_id, conditional}`. |
264
+ | `metadata`, `started_at`, `finished_at` | | |
265
+
266
+ ### Span
267
+ | Campo | Tipo | Descrição |
268
+ |-------|------|-----------|
269
+ | `span_id` | `str` | UUID. |
270
+ | `parent_span_id` | `str?` | Pai na árvore (None = raiz). |
271
+ | `type` | `Enum` | `LLM` / `TOOL` / `AGENT`. |
272
+ | `name` | `str` | Nome descritivo. |
273
+ | `status` | `Enum` | `ok` / `error`. |
274
+ | `error` | `str?` | Mensagem quando `status == error`. |
275
+ | `model`, `tokens_in/out/_in_cached`, `cost_usd`, `latency_ms` | | Métricas. |
276
+ | `input`, `output`, `metadata` | | Conteúdo da etapa. |
277
+ | `started_at`, `finished_at` | `ISO8601` | |
278
+
279
+ ---
280
+
281
+ ## Resiliência
282
+
283
+ 1. **Exceção no código do usuário** → trace sempre finalizado e exportado via `finally`.
284
+ 2. **Exceção no exporter** → logada em nível `ERROR` e exposta via hook `on_export_error`; **nunca**
285
+ silenciosa e nunca interrompe o app. Outros exporters continuam.
286
+ ```python
287
+ Tracer(exporters=[...], on_export_error=lambda exc, trace, exp: alertar(exc))
288
+ ```
289
+ 3. **Export assíncrono não-bloqueante** — `aexport` roda fora do event loop (offload em thread).
290
+ 4. **Spans com erro** marcados com `status="error"` + `error` (first-class, não enterrado em metadata).
291
+ 5. **Multi-tenant / concorrência** — cada coroutine/thread isola seu próprio trace via `ContextVar`.
292
+
293
+ ---
294
+
295
+ ## Testes
296
+
297
+ ```bash
298
+ cd packages/tracecast-py
299
+ pip install -e ".[all]" pytest
300
+ python -m pytest tests/ -q
301
+ # → 242 passed
302
+ ```
303
+
304
+ ---
305
+
306
+ ## Licença
307
+
308
+ MIT © Pedro Castanheira Costa
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tracecast"
7
- version = "0.1.2"
7
+ version = "0.2.2"
8
8
  description = "LLM observability SDK — framework-agnostic tracing for AI applications"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -23,6 +23,9 @@ classifiers = [
23
23
  ]
24
24
  dependencies = []
25
25
 
26
+ [project.scripts]
27
+ tracecast-server = "tracecast.serve:main"
28
+
26
29
  [project.urls]
27
30
  Homepage = "https://github.com/pedrocastanha/tracecast"
28
31
  Repository = "https://github.com/pedrocastanha/tracecast"
@@ -32,12 +35,16 @@ Issues = "https://github.com/pedrocastanha/tracecast/issues"
32
35
  mongo = ["pymongo>=4.6"]
33
36
  postgres = ["psycopg2>=2.9"]
34
37
  langchain = ["langchain-core>=0.3"]
35
- all = ["tracecast[mongo,postgres,langchain]"]
38
+ dashboard = ["fastapi>=0.100.0", "uvicorn>=0.20"]
39
+ all = ["tracecast[mongo,postgres,langchain,dashboard]"]
36
40
 
37
41
  [tool.setuptools.packages.find]
38
42
  where = ["."]
39
43
  include = ["tracecast*"]
40
44
  exclude = ["tests*"]
41
45
 
46
+ [tool.setuptools.package-data]
47
+ tracecast = ["dashboard/static/**/*", "dashboard/static/*"]
48
+
42
49
  [tool.pytest.ini_options]
43
50
  testpaths = ["tests"]