tracecast 0.2.2__tar.gz → 0.2.3__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 (104) hide show
  1. {tracecast-0.2.2/tracecast.egg-info → tracecast-0.2.3}/PKG-INFO +1 -1
  2. {tracecast-0.2.2 → tracecast-0.2.3}/pyproject.toml +1 -1
  3. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/dashboard/aggregator.py +82 -16
  4. tracecast-0.2.3/tracecast/dashboard/static/assets/index-BbNHE9WD.js +151 -0
  5. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/dashboard/static/index.html +1 -1
  6. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/integrations/langchain.py +19 -2
  7. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/integrations/llm.py +75 -1
  8. {tracecast-0.2.2 → tracecast-0.2.3/tracecast.egg-info}/PKG-INFO +1 -1
  9. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast.egg-info/SOURCES.txt +1 -1
  10. tracecast-0.2.2/tracecast/dashboard/static/assets/index-CBra-omk.js +0 -151
  11. {tracecast-0.2.2 → tracecast-0.2.3}/LICENSE +0 -0
  12. {tracecast-0.2.2 → tracecast-0.2.3}/README.md +0 -0
  13. {tracecast-0.2.2 → tracecast-0.2.3}/setup.cfg +0 -0
  14. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_context_propagation.py +0 -0
  15. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_cost_calculator.py +0 -0
  16. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_dashboard_mount.py +0 -0
  17. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_dashboard_pushdown.py +0 -0
  18. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_dashboard_sessions.py +0 -0
  19. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_decorators.py +0 -0
  20. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_e2e_langgraph.py +0 -0
  21. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_exporters_db.py +0 -0
  22. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_integration_frameworks.py +0 -0
  23. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_integration_langchain.py +0 -0
  24. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_json_exporter.py +0 -0
  25. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_langchain_callback.py +0 -0
  26. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_llm_wrappers.py +0 -0
  27. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_logging.py +0 -0
  28. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_mongo_exporter.py +0 -0
  29. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_prompt_dashboard.py +0 -0
  30. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_prompts.py +0 -0
  31. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_roundtrip.py +0 -0
  32. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_schema_v2.py +0 -0
  33. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_score.py +0 -0
  34. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_score_dashboard.py +0 -0
  35. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_scoring_api.py +0 -0
  36. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_serve.py +0 -0
  37. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_span.py +0 -0
  38. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_streaming.py +0 -0
  39. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_token_counter.py +0 -0
  40. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_trace.py +0 -0
  41. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_trace_span.py +0 -0
  42. {tracecast-0.2.2 → tracecast-0.2.3}/tests/test_tracer.py +0 -0
  43. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/__init__.py +0 -0
  44. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/core/__init__.py +0 -0
  45. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/core/cost_calculator.py +0 -0
  46. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/core/logger.py +0 -0
  47. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/core/scoring.py +0 -0
  48. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/core/token_counter.py +0 -0
  49. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/core/tracer.py +0 -0
  50. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/dashboard/__init__.py +0 -0
  51. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/dashboard/asgi_middleware.py +0 -0
  52. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/dashboard/blueprint.py +0 -0
  53. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/dashboard/eval_reader.py +0 -0
  54. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/dashboard/prompt_reader.py +0 -0
  55. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/dashboard/reader.py +0 -0
  56. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/dashboard/router.py +0 -0
  57. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/dashboard/score_reader.py +0 -0
  58. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/dashboard/standalone.py +0 -0
  59. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/dashboard/static/assets/index-DllJJzmE.css +0 -0
  60. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/decorators.py +0 -0
  61. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/eval/__init__.py +0 -0
  62. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/eval/cli.py +0 -0
  63. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/eval/compare.py +0 -0
  64. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/eval/dataset.py +0 -0
  65. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/eval/decorator.py +0 -0
  66. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/eval/judge.py +0 -0
  67. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/eval/metrics.py +0 -0
  68. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/eval/models.py +0 -0
  69. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/eval/online.py +0 -0
  70. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/eval/runner.py +0 -0
  71. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/eval/scorers.py +0 -0
  72. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/exporters/__init__.py +0 -0
  73. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/exporters/_eval_store.py +0 -0
  74. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/exporters/base.py +0 -0
  75. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/exporters/dict_exporter.py +0 -0
  76. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/exporters/json_file.py +0 -0
  77. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/exporters/mongo.py +0 -0
  78. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/exporters/postgres.py +0 -0
  79. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/exporters/query.py +0 -0
  80. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/instrument.py +0 -0
  81. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/instrumentors/__init__.py +0 -0
  82. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/instrumentors/_llamaindex_handler.py +0 -0
  83. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/instrumentors/_streaming.py +0 -0
  84. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/instrumentors/anthropic_inst.py +0 -0
  85. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/instrumentors/base.py +0 -0
  86. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/instrumentors/crewai_inst.py +0 -0
  87. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/instrumentors/gemini_inst.py +0 -0
  88. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/instrumentors/langchain_inst.py +0 -0
  89. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/instrumentors/llamaindex_inst.py +0 -0
  90. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/instrumentors/openai_inst.py +0 -0
  91. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/integrations/__init__.py +0 -0
  92. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/middleware.py +0 -0
  93. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/models/__init__.py +0 -0
  94. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/models/score.py +0 -0
  95. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/models/span.py +0 -0
  96. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/models/trace.py +0 -0
  97. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/prompts/__init__.py +0 -0
  98. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/prompts/client.py +0 -0
  99. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/prompts/models.py +0 -0
  100. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast/serve.py +0 -0
  101. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast.egg-info/dependency_links.txt +0 -0
  102. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast.egg-info/entry_points.txt +0 -0
  103. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast.egg-info/requires.txt +0 -0
  104. {tracecast-0.2.2 → tracecast-0.2.3}/tracecast.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tracecast
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: LLM observability SDK — framework-agnostic tracing for AI applications
5
5
  Author: Pedro Castanheira Costa
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tracecast"
7
- version = "0.2.2"
7
+ version = "0.2.3"
8
8
  description = "LLM observability SDK — framework-agnostic tracing for AI applications"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -43,11 +43,20 @@ def compute_metrics(
43
43
 
44
44
  cost_by_model: dict[str, float] = defaultdict(float)
45
45
  cost_by_project: dict[str, float] = defaultdict(float)
46
+ models_breakdown: dict[str, dict] = defaultdict(lambda: {"calls": 0, "total_tokens": 0, "total_tokens_in": 0, "total_tokens_out": 0, "cost_usd": 0.0})
46
47
  for t in filtered:
47
- if t.model:
48
- cost_by_model[t.model] += t.cost_usd
49
48
  if t.project_id:
50
49
  cost_by_project[t.project_id] += t.cost_usd
50
+ for s in t.spans:
51
+ if s.type.value == "llm" and s.model:
52
+ cost_by_model[s.model] += s.cost_usd
53
+ models_breakdown[s.model]["calls"] += 1
54
+ models_breakdown[s.model]["total_tokens"] += s.total_tokens
55
+ models_breakdown[s.model]["total_tokens_in"] += s.tokens_in
56
+ models_breakdown[s.model]["total_tokens_out"] += s.tokens_out
57
+ models_breakdown[s.model]["cost_usd"] = round(
58
+ models_breakdown[s.model]["cost_usd"] + s.cost_usd, 6
59
+ )
51
60
 
52
61
  traces_over_time = _group_by_day(filtered)
53
62
  tokens_by_model_over_time = _group_by_day_and_model(filtered)
@@ -63,6 +72,7 @@ def compute_metrics(
63
72
  "avg_latency_ms": round(avg_latency, 1),
64
73
  "cost_by_model": dict(cost_by_model),
65
74
  "cost_by_project": dict(cost_by_project),
75
+ "models_breakdown": {k: v for k, v in sorted(models_breakdown.items())},
66
76
  "traces_over_time": traces_over_time,
67
77
  "tokens_by_model_over_time": tokens_by_model_over_time,
68
78
  }
@@ -136,8 +146,53 @@ def _trace_summary(trace: Trace) -> dict:
136
146
 
137
147
 
138
148
  def build_graph(trace: Trace) -> dict:
149
+ # T5: exclude broken orphan LLM spans (pid=None + 0 tokens = wrap_openai async bug).
150
+ # Temporary until wrap_openai async fix lands in tracecast lib.
151
+ valid_spans = [
152
+ s for s in trace.spans
153
+ if not (
154
+ s.parent_span_id is None
155
+ and s.type.value == "llm"
156
+ and s.tokens_in == 0
157
+ and s.tokens_out == 0
158
+ )
159
+ ]
160
+
161
+ span_by_id = {s.span_id: s for s in valid_spans}
162
+ valid_ids = set(span_by_id.keys())
163
+
164
+ # Build parent→children map
165
+ children: dict[str, list] = defaultdict(list)
166
+ for s in valid_spans:
167
+ if s.parent_span_id and s.parent_span_id in span_by_id:
168
+ children[s.parent_span_id].append(s.span_id)
169
+
170
+ # Aggregate tokens + primary_model bottom-up (DFS, memoized).
171
+ # primary_model = model of the LLM descendant with most tokens (for parent card display).
172
+ agg_cache: dict[str, tuple] = {}
173
+
174
+ def _agg(sid: str) -> tuple:
175
+ if sid in agg_cache:
176
+ return agg_cache[sid]
177
+ s = span_by_id[sid]
178
+ ti, to_, cost = s.tokens_in, s.tokens_out, s.cost_usd
179
+ primary_model: str | None = s.model if s.type.value == "llm" else None
180
+ best_child_tok = 0
181
+ for child_id in children.get(sid, []):
182
+ ci, co, cc, cm = _agg(child_id)
183
+ ti += ci
184
+ to_ += co
185
+ cost += cc
186
+ child_tok = ci + co
187
+ if cm is not None and child_tok > best_child_tok:
188
+ primary_model = cm
189
+ best_child_tok = child_tok
190
+ agg_cache[sid] = (ti, to_, cost, primary_model)
191
+ return ti, to_, cost, primary_model
192
+
139
193
  nodes = []
140
- for s in trace.spans:
194
+ for s in valid_spans:
195
+ ti, to_, cost, pm = _agg(s.span_id)
141
196
  nodes.append({
142
197
  "id": s.span_id,
143
198
  "parent_span_id": s.parent_span_id,
@@ -145,18 +200,26 @@ def build_graph(trace: Trace) -> dict:
145
200
  "type": s.type.value,
146
201
  "status": s.status.value if hasattr(s.status, "value") else s.status,
147
202
  "model": s.model,
148
- "tokens_in": s.tokens_in,
149
- "tokens_out": s.tokens_out,
150
- "total_tokens": s.total_tokens,
151
- "cost_usd": s.cost_usd,
203
+ "primary_model": pm,
204
+ "tokens_in": ti,
205
+ "tokens_out": to_,
206
+ "total_tokens": ti + to_,
207
+ "cost_usd": round(cost, 6),
152
208
  "latency_ms": s.latency_ms,
153
209
  "error": s.error,
154
210
  })
211
+
212
+ # Filter edges to only reference valid span ids (removes orphan edges)
213
+ valid_edges = [
214
+ e for e in trace.edges
215
+ if e.get("from") in valid_ids and e.get("to") in valid_ids
216
+ ]
217
+
155
218
  return {
156
219
  "trace_id": trace.trace_id,
157
220
  "name": trace.name,
158
221
  "nodes": nodes,
159
- "edges": trace.edges,
222
+ "edges": valid_edges,
160
223
  }
161
224
 
162
225
 
@@ -327,15 +390,18 @@ def _group_by_day(traces: List[Trace]) -> list:
327
390
 
328
391
 
329
392
  def _group_by_day_and_model(traces: List[Trace]) -> list:
330
- """Group by (date, model) for multi-series token/cost chart."""
393
+ """Group by (date, model) at span level for multi-series token/cost chart."""
331
394
  groups: dict[tuple, dict] = {}
332
395
  for t in traces:
333
396
  day = t.started_at.strftime("%Y-%m-%d")
334
- model = t.model or "unknown"
335
- key = (day, model)
336
- if key not in groups:
337
- groups[key] = {"date": day, "model": model, "tokens_in": 0, "tokens_out": 0, "cost_usd": 0.0}
338
- groups[key]["tokens_in"] += t.total_tokens_in
339
- groups[key]["tokens_out"] += t.total_tokens_out
340
- groups[key]["cost_usd"] = round(groups[key]["cost_usd"] + t.cost_usd, 6)
397
+ for s in t.spans:
398
+ if s.type.value != "llm" or not s.model:
399
+ continue
400
+ model = s.model
401
+ key = (day, model)
402
+ if key not in groups:
403
+ groups[key] = {"date": day, "model": model, "tokens_in": 0, "tokens_out": 0, "cost_usd": 0.0}
404
+ groups[key]["tokens_in"] += s.tokens_in
405
+ groups[key]["tokens_out"] += s.tokens_out
406
+ groups[key]["cost_usd"] = round(groups[key]["cost_usd"] + s.cost_usd, 6)
341
407
  return sorted(groups.values(), key=lambda x: (x["date"], x["model"]))