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.
- tracecast-0.2.2/PKG-INFO +342 -0
- tracecast-0.2.2/README.md +308 -0
- {tracecast-0.1.2 → tracecast-0.2.2}/pyproject.toml +9 -2
- tracecast-0.2.2/tests/test_context_propagation.py +68 -0
- tracecast-0.2.2/tests/test_dashboard_mount.py +25 -0
- tracecast-0.2.2/tests/test_dashboard_pushdown.py +106 -0
- tracecast-0.2.2/tests/test_dashboard_sessions.py +126 -0
- tracecast-0.2.2/tests/test_decorators.py +85 -0
- tracecast-0.2.2/tests/test_e2e_langgraph.py +129 -0
- {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_exporters_db.py +34 -7
- {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_integration_frameworks.py +2 -2
- {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_langchain_callback.py +37 -4
- tracecast-0.2.2/tests/test_llm_wrappers.py +46 -0
- tracecast-0.2.2/tests/test_mongo_exporter.py +36 -0
- tracecast-0.2.2/tests/test_prompt_dashboard.py +56 -0
- tracecast-0.2.2/tests/test_prompts.py +84 -0
- tracecast-0.2.2/tests/test_roundtrip.py +82 -0
- tracecast-0.2.2/tests/test_schema_v2.py +91 -0
- tracecast-0.2.2/tests/test_score.py +56 -0
- tracecast-0.2.2/tests/test_score_dashboard.py +49 -0
- tracecast-0.2.2/tests/test_scoring_api.py +49 -0
- tracecast-0.2.2/tests/test_serve.py +53 -0
- tracecast-0.2.2/tests/test_streaming.py +121 -0
- tracecast-0.2.2/tests/test_trace_span.py +102 -0
- {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_tracer.py +24 -6
- tracecast-0.2.2/tracecast/__init__.py +22 -0
- tracecast-0.2.2/tracecast/core/scoring.py +53 -0
- tracecast-0.2.2/tracecast/core/token_counter.py +114 -0
- tracecast-0.2.2/tracecast/core/tracer.py +223 -0
- tracecast-0.2.2/tracecast/dashboard/aggregator.py +341 -0
- tracecast-0.2.2/tracecast/dashboard/asgi_middleware.py +28 -0
- tracecast-0.2.2/tracecast/dashboard/blueprint.py +140 -0
- tracecast-0.2.2/tracecast/dashboard/eval_reader.py +28 -0
- tracecast-0.2.2/tracecast/dashboard/prompt_reader.py +40 -0
- tracecast-0.2.2/tracecast/dashboard/reader.py +240 -0
- tracecast-0.2.2/tracecast/dashboard/router.py +292 -0
- tracecast-0.2.2/tracecast/dashboard/score_reader.py +28 -0
- tracecast-0.2.2/tracecast/dashboard/standalone.py +73 -0
- tracecast-0.2.2/tracecast/dashboard/static/assets/index-CBra-omk.js +151 -0
- tracecast-0.2.2/tracecast/dashboard/static/assets/index-DllJJzmE.css +1 -0
- tracecast-0.2.2/tracecast/dashboard/static/index.html +13 -0
- tracecast-0.2.2/tracecast/decorators.py +169 -0
- tracecast-0.2.2/tracecast/eval/__init__.py +25 -0
- tracecast-0.2.2/tracecast/eval/cli.py +59 -0
- tracecast-0.2.2/tracecast/eval/compare.py +42 -0
- tracecast-0.2.2/tracecast/eval/dataset.py +136 -0
- tracecast-0.2.2/tracecast/eval/decorator.py +72 -0
- tracecast-0.2.2/tracecast/eval/judge.py +124 -0
- tracecast-0.2.2/tracecast/eval/metrics.py +128 -0
- tracecast-0.2.2/tracecast/eval/models.py +221 -0
- tracecast-0.2.2/tracecast/eval/online.py +71 -0
- tracecast-0.2.2/tracecast/eval/runner.py +160 -0
- tracecast-0.2.2/tracecast/eval/scorers.py +42 -0
- tracecast-0.2.2/tracecast/exporters/_eval_store.py +59 -0
- tracecast-0.2.2/tracecast/exporters/base.py +44 -0
- tracecast-0.2.2/tracecast/exporters/dict_exporter.py +79 -0
- tracecast-0.2.2/tracecast/exporters/json_file.py +117 -0
- tracecast-0.2.2/tracecast/exporters/mongo.py +182 -0
- tracecast-0.2.2/tracecast/exporters/postgres.py +446 -0
- tracecast-0.2.2/tracecast/exporters/query.py +23 -0
- tracecast-0.2.2/tracecast/instrument.py +47 -0
- tracecast-0.2.2/tracecast/instrumentors/__init__.py +41 -0
- tracecast-0.2.2/tracecast/instrumentors/_llamaindex_handler.py +249 -0
- tracecast-0.2.2/tracecast/instrumentors/_streaming.py +94 -0
- tracecast-0.2.2/tracecast/instrumentors/anthropic_inst.py +154 -0
- tracecast-0.2.2/tracecast/instrumentors/base.py +21 -0
- tracecast-0.2.2/tracecast/instrumentors/crewai_inst.py +97 -0
- tracecast-0.2.2/tracecast/instrumentors/gemini_inst.py +86 -0
- tracecast-0.2.2/tracecast/instrumentors/langchain_inst.py +132 -0
- tracecast-0.2.2/tracecast/instrumentors/llamaindex_inst.py +52 -0
- tracecast-0.2.2/tracecast/instrumentors/openai_inst.py +174 -0
- {tracecast-0.1.2 → tracecast-0.2.2}/tracecast/integrations/langchain.py +97 -1
- tracecast-0.2.2/tracecast/integrations/llm.py +143 -0
- tracecast-0.2.2/tracecast/middleware.py +31 -0
- tracecast-0.2.2/tracecast/models/__init__.py +0 -0
- tracecast-0.2.2/tracecast/models/score.py +84 -0
- {tracecast-0.1.2 → tracecast-0.2.2}/tracecast/models/span.py +19 -0
- {tracecast-0.1.2 → tracecast-0.2.2}/tracecast/models/trace.py +29 -1
- tracecast-0.2.2/tracecast/prompts/__init__.py +4 -0
- tracecast-0.2.2/tracecast/prompts/client.py +134 -0
- tracecast-0.2.2/tracecast/prompts/models.py +49 -0
- tracecast-0.2.2/tracecast/serve.py +75 -0
- tracecast-0.2.2/tracecast.egg-info/PKG-INFO +342 -0
- tracecast-0.2.2/tracecast.egg-info/SOURCES.txt +101 -0
- tracecast-0.2.2/tracecast.egg-info/entry_points.txt +2 -0
- tracecast-0.2.2/tracecast.egg-info/requires.txt +16 -0
- tracecast-0.1.2/PKG-INFO +0 -696
- tracecast-0.1.2/README.md +0 -665
- tracecast-0.1.2/tests/test_mongo_exporter.py +0 -23
- tracecast-0.1.2/tracecast/__init__.py +0 -6
- tracecast-0.1.2/tracecast/core/token_counter.py +0 -46
- tracecast-0.1.2/tracecast/core/tracer.py +0 -106
- tracecast-0.1.2/tracecast/exporters/base.py +0 -15
- tracecast-0.1.2/tracecast/exporters/dict_exporter.py +0 -35
- tracecast-0.1.2/tracecast/exporters/json_file.py +0 -37
- tracecast-0.1.2/tracecast/exporters/mongo.py +0 -34
- tracecast-0.1.2/tracecast/exporters/postgres.py +0 -164
- tracecast-0.1.2/tracecast.egg-info/PKG-INFO +0 -696
- tracecast-0.1.2/tracecast.egg-info/SOURCES.txt +0 -37
- tracecast-0.1.2/tracecast.egg-info/requires.txt +0 -12
- {tracecast-0.1.2 → tracecast-0.2.2}/LICENSE +0 -0
- {tracecast-0.1.2 → tracecast-0.2.2}/setup.cfg +0 -0
- {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_cost_calculator.py +0 -0
- {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_integration_langchain.py +0 -0
- {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_json_exporter.py +0 -0
- {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_logging.py +0 -0
- {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_span.py +0 -0
- {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_token_counter.py +0 -0
- {tracecast-0.1.2 → tracecast-0.2.2}/tests/test_trace.py +0 -0
- {tracecast-0.1.2 → tracecast-0.2.2}/tracecast/core/__init__.py +0 -0
- {tracecast-0.1.2 → tracecast-0.2.2}/tracecast/core/cost_calculator.py +0 -0
- {tracecast-0.1.2 → tracecast-0.2.2}/tracecast/core/logger.py +0 -0
- {tracecast-0.1.2/tracecast/integrations → tracecast-0.2.2/tracecast/dashboard}/__init__.py +0 -0
- {tracecast-0.1.2 → tracecast-0.2.2}/tracecast/exporters/__init__.py +0 -0
- {tracecast-0.1.2/tracecast/models → tracecast-0.2.2/tracecast/integrations}/__init__.py +0 -0
- {tracecast-0.1.2 → tracecast-0.2.2}/tracecast.egg-info/dependency_links.txt +0 -0
- {tracecast-0.1.2 → tracecast-0.2.2}/tracecast.egg-info/top_level.txt +0 -0
tracecast-0.2.2/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://pypi.org/project/tracecast/)
|
|
40
|
+
[](LICENSE)
|
|
41
|
+
[](#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
|
+
[](https://pypi.org/project/tracecast/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](#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.
|
|
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
|
-
|
|
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"]
|