ai-pipeline-core 0.3.4__py3-none-any.whl → 0.4.0__py3-none-any.whl
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.
- ai_pipeline_core/__init__.py +64 -158
- ai_pipeline_core/deployment/__init__.py +6 -18
- ai_pipeline_core/deployment/base.py +392 -212
- ai_pipeline_core/deployment/contract.py +6 -10
- ai_pipeline_core/{utils → deployment}/deploy.py +50 -69
- ai_pipeline_core/deployment/helpers.py +16 -17
- ai_pipeline_core/{progress.py → deployment/progress.py} +23 -24
- ai_pipeline_core/{utils/remote_deployment.py → deployment/remote.py} +11 -14
- ai_pipeline_core/docs_generator/__init__.py +54 -0
- ai_pipeline_core/docs_generator/__main__.py +5 -0
- ai_pipeline_core/docs_generator/cli.py +196 -0
- ai_pipeline_core/docs_generator/extractor.py +324 -0
- ai_pipeline_core/docs_generator/guide_builder.py +644 -0
- ai_pipeline_core/docs_generator/trimmer.py +35 -0
- ai_pipeline_core/docs_generator/validator.py +114 -0
- ai_pipeline_core/document_store/__init__.py +13 -0
- ai_pipeline_core/document_store/_summary.py +9 -0
- ai_pipeline_core/document_store/_summary_worker.py +170 -0
- ai_pipeline_core/document_store/clickhouse.py +492 -0
- ai_pipeline_core/document_store/factory.py +38 -0
- ai_pipeline_core/document_store/local.py +312 -0
- ai_pipeline_core/document_store/memory.py +85 -0
- ai_pipeline_core/document_store/protocol.py +68 -0
- ai_pipeline_core/documents/__init__.py +12 -14
- ai_pipeline_core/documents/_context_vars.py +85 -0
- ai_pipeline_core/documents/_hashing.py +52 -0
- ai_pipeline_core/documents/attachment.py +85 -0
- ai_pipeline_core/documents/context.py +128 -0
- ai_pipeline_core/documents/document.py +318 -1434
- ai_pipeline_core/documents/mime_type.py +11 -84
- ai_pipeline_core/documents/utils.py +4 -12
- ai_pipeline_core/exceptions.py +10 -62
- ai_pipeline_core/images/__init__.py +32 -85
- ai_pipeline_core/images/_processing.py +5 -11
- ai_pipeline_core/llm/__init__.py +6 -4
- ai_pipeline_core/llm/ai_messages.py +102 -90
- ai_pipeline_core/llm/client.py +229 -183
- ai_pipeline_core/llm/model_options.py +12 -84
- ai_pipeline_core/llm/model_response.py +53 -99
- ai_pipeline_core/llm/model_types.py +8 -23
- ai_pipeline_core/logging/__init__.py +2 -7
- ai_pipeline_core/logging/logging.yml +1 -1
- ai_pipeline_core/logging/logging_config.py +27 -37
- ai_pipeline_core/logging/logging_mixin.py +15 -41
- ai_pipeline_core/observability/__init__.py +32 -0
- ai_pipeline_core/observability/_debug/__init__.py +30 -0
- ai_pipeline_core/observability/_debug/_auto_summary.py +94 -0
- ai_pipeline_core/{debug/config.py → observability/_debug/_config.py} +11 -7
- ai_pipeline_core/{debug/content.py → observability/_debug/_content.py} +133 -75
- ai_pipeline_core/{debug/processor.py → observability/_debug/_processor.py} +16 -17
- ai_pipeline_core/{debug/summary.py → observability/_debug/_summary.py} +113 -37
- ai_pipeline_core/observability/_debug/_types.py +75 -0
- ai_pipeline_core/{debug/writer.py → observability/_debug/_writer.py} +126 -196
- ai_pipeline_core/observability/_document_tracking.py +146 -0
- ai_pipeline_core/observability/_initialization.py +194 -0
- ai_pipeline_core/observability/_logging_bridge.py +57 -0
- ai_pipeline_core/observability/_summary.py +81 -0
- ai_pipeline_core/observability/_tracking/__init__.py +6 -0
- ai_pipeline_core/observability/_tracking/_client.py +178 -0
- ai_pipeline_core/observability/_tracking/_internal.py +28 -0
- ai_pipeline_core/observability/_tracking/_models.py +138 -0
- ai_pipeline_core/observability/_tracking/_processor.py +158 -0
- ai_pipeline_core/observability/_tracking/_service.py +311 -0
- ai_pipeline_core/observability/_tracking/_writer.py +229 -0
- ai_pipeline_core/{tracing.py → observability/tracing.py} +139 -335
- ai_pipeline_core/pipeline/__init__.py +10 -0
- ai_pipeline_core/pipeline/decorators.py +915 -0
- ai_pipeline_core/pipeline/options.py +16 -0
- ai_pipeline_core/prompt_manager.py +16 -102
- ai_pipeline_core/settings.py +26 -31
- ai_pipeline_core/testing.py +9 -0
- ai_pipeline_core-0.4.0.dist-info/METADATA +807 -0
- ai_pipeline_core-0.4.0.dist-info/RECORD +76 -0
- ai_pipeline_core/debug/__init__.py +0 -26
- ai_pipeline_core/documents/document_list.py +0 -420
- ai_pipeline_core/documents/flow_document.py +0 -112
- ai_pipeline_core/documents/task_document.py +0 -117
- ai_pipeline_core/documents/temporary_document.py +0 -74
- ai_pipeline_core/flow/__init__.py +0 -9
- ai_pipeline_core/flow/config.py +0 -494
- ai_pipeline_core/flow/options.py +0 -75
- ai_pipeline_core/pipeline.py +0 -718
- ai_pipeline_core/prefect.py +0 -63
- ai_pipeline_core/prompt_builder/__init__.py +0 -5
- ai_pipeline_core/prompt_builder/documents_prompt.jinja2 +0 -23
- ai_pipeline_core/prompt_builder/global_cache.py +0 -78
- ai_pipeline_core/prompt_builder/new_core_documents_prompt.jinja2 +0 -6
- ai_pipeline_core/prompt_builder/prompt_builder.py +0 -253
- ai_pipeline_core/prompt_builder/system_prompt.jinja2 +0 -41
- ai_pipeline_core/storage/__init__.py +0 -8
- ai_pipeline_core/storage/storage.py +0 -628
- ai_pipeline_core/utils/__init__.py +0 -8
- ai_pipeline_core-0.3.4.dist-info/METADATA +0 -569
- ai_pipeline_core-0.3.4.dist-info/RECORD +0 -57
- {ai_pipeline_core-0.3.4.dist-info → ai_pipeline_core-0.4.0.dist-info}/WHEEL +0 -0
- {ai_pipeline_core-0.3.4.dist-info → ai_pipeline_core-0.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Static summary generation for trace debugging.
|
|
2
2
|
|
|
3
|
-
Generates
|
|
4
|
-
|
|
3
|
+
Generates _summary.md files with execution tree, LLM calls, cost breakdown,
|
|
4
|
+
and navigation guide. No LLM dependencies — pure text formatting.
|
|
5
|
+
|
|
6
|
+
For LLM-powered auto-summary, see _auto_summary.py.
|
|
5
7
|
"""
|
|
6
8
|
|
|
7
|
-
from
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from ._types import SpanInfo, TraceState
|
|
8
12
|
|
|
9
13
|
|
|
10
|
-
def generate_summary(trace: TraceState) -> str:
|
|
14
|
+
def generate_summary(trace: TraceState) -> str: # noqa: PLR0912, PLR0914, PLR0915
|
|
11
15
|
"""Generate unified _summary.md file.
|
|
12
16
|
|
|
13
17
|
Single file optimized for both human inspection and LLM debugger context.
|
|
14
|
-
Structure: Overview
|
|
18
|
+
Structure: Overview -> Tree -> Root Span -> LLM Calls -> Cost by Task -> Errors -> Navigation.
|
|
19
|
+
Cost by Task table includes expected cost comparison with OVER/OK status indicators.
|
|
15
20
|
"""
|
|
16
21
|
lines = [
|
|
17
22
|
f"# Trace Summary: {trace.name}",
|
|
@@ -20,17 +25,21 @@ def generate_summary(trace: TraceState) -> str:
|
|
|
20
25
|
|
|
21
26
|
# Status and stats
|
|
22
27
|
failed_spans = [s for s in trace.spans.values() if s.status == "failed"]
|
|
23
|
-
status_emoji = "
|
|
28
|
+
status_emoji = "\u274c" if failed_spans else "\u2705"
|
|
24
29
|
status_text = f"Failed ({len(failed_spans)} errors)" if failed_spans else "Completed"
|
|
25
30
|
duration_str = _format_duration(trace)
|
|
26
31
|
|
|
32
|
+
cost_str = f"**Total Cost**: ${trace.total_cost:.4f}"
|
|
33
|
+
if trace.total_expected_cost > 0:
|
|
34
|
+
cost_str += f" (expected: ${trace.total_expected_cost:.4f})"
|
|
35
|
+
|
|
27
36
|
lines.extend([
|
|
28
37
|
f"**Status**: {status_emoji} {status_text} | "
|
|
29
38
|
f"**Duration**: {duration_str} | "
|
|
30
39
|
f"**Spans**: {len(trace.spans)} | "
|
|
31
40
|
f"**LLM Calls**: {trace.llm_call_count} | "
|
|
32
41
|
f"**Total Tokens**: {trace.total_tokens:,} | "
|
|
33
|
-
f"
|
|
42
|
+
f"{cost_str}",
|
|
34
43
|
"",
|
|
35
44
|
])
|
|
36
45
|
|
|
@@ -46,8 +55,7 @@ def generate_summary(trace: TraceState) -> str:
|
|
|
46
55
|
lines.extend(tree_lines)
|
|
47
56
|
else:
|
|
48
57
|
# Fallback: list all spans
|
|
49
|
-
for span in sorted(trace.spans.values(), key=lambda s: s.start_time)
|
|
50
|
-
lines.append(_format_span_line(span))
|
|
58
|
+
lines.extend(_format_span_line(span) for span in sorted(trace.spans.values(), key=lambda s: s.start_time))
|
|
51
59
|
|
|
52
60
|
lines.extend([
|
|
53
61
|
"```",
|
|
@@ -77,27 +85,48 @@ def generate_summary(trace: TraceState) -> str:
|
|
|
77
85
|
lines.extend([
|
|
78
86
|
"## LLM Calls (by cost)",
|
|
79
87
|
"",
|
|
80
|
-
"| # | Span | Model | Input
|
|
81
|
-
"
|
|
88
|
+
"| # | Span | Purpose | Model | Input\u2192Output | Total | Cost | Expected | Path |",
|
|
89
|
+
"|---|------|---------|-------|--------------|-------|------|----------|------|",
|
|
82
90
|
])
|
|
83
91
|
|
|
84
92
|
for i, span in enumerate(llm_spans, 1):
|
|
85
93
|
info = span.llm_info
|
|
86
94
|
if info:
|
|
87
95
|
model = info.get("model", "unknown")
|
|
96
|
+
purpose = info.get("purpose", "")
|
|
88
97
|
in_tokens = info.get("input_tokens", 0)
|
|
89
98
|
out_tokens = info.get("output_tokens", 0)
|
|
90
99
|
total_tokens = info.get("total_tokens", 0)
|
|
91
100
|
cost = info.get("cost", 0)
|
|
101
|
+
expected = info.get("expected_cost")
|
|
102
|
+
expected_str = f"${expected:.4f}" if expected else ""
|
|
92
103
|
span_path = span.path.relative_to(trace.path).as_posix()
|
|
93
104
|
lines.append(
|
|
94
|
-
f"| {i} | {span.name} | {model} | "
|
|
95
|
-
f"{in_tokens:,}
|
|
96
|
-
f"`{span_path}/` |"
|
|
105
|
+
f"| {i} | {span.name} | {purpose} | {model} | "
|
|
106
|
+
f"{in_tokens:,}\u2192{out_tokens:,} | {total_tokens:,} | ${cost:.4f} | "
|
|
107
|
+
f"{expected_str} | `{span_path}/` |"
|
|
97
108
|
)
|
|
98
109
|
|
|
99
110
|
lines.append("")
|
|
100
111
|
|
|
112
|
+
# Cost aggregation by parent task/flow
|
|
113
|
+
cost_by_parent = _aggregate_costs_by_parent(trace)
|
|
114
|
+
if cost_by_parent:
|
|
115
|
+
lines.extend([
|
|
116
|
+
"## Cost by Task",
|
|
117
|
+
"",
|
|
118
|
+
"| Name | Type | LLM Calls | Cost | Expected | Status |",
|
|
119
|
+
"|------|------|-----------|------|----------|--------|",
|
|
120
|
+
])
|
|
121
|
+
for entry in cost_by_parent:
|
|
122
|
+
expected_str = f"${entry['expected_cost']:.4f}" if entry["expected_cost"] else ""
|
|
123
|
+
status = ""
|
|
124
|
+
if entry["expected_cost"] and entry["actual_cost"] > 0:
|
|
125
|
+
ratio = entry["actual_cost"] / entry["expected_cost"]
|
|
126
|
+
status = "OVER" if ratio > 1.1 else "OK"
|
|
127
|
+
lines.append(f"| {entry['name']} | {entry['type']} | {entry['llm_calls']} | ${entry['actual_cost']:.4f} | {expected_str} | {status} |")
|
|
128
|
+
lines.append("")
|
|
129
|
+
|
|
101
130
|
# Errors
|
|
102
131
|
if failed_spans:
|
|
103
132
|
lines.extend([
|
|
@@ -122,13 +151,48 @@ def generate_summary(trace: TraceState) -> str:
|
|
|
122
151
|
"",
|
|
123
152
|
"- Each span directory contains `_span.yaml` (metadata), `input.yaml`, `output.yaml`",
|
|
124
153
|
"- LLM span inputs contain the full message list",
|
|
125
|
-
"- `_tree.yaml` has span_id
|
|
154
|
+
"- `_tree.yaml` has span_id \u2192 path mapping and full hierarchy",
|
|
126
155
|
"",
|
|
127
156
|
])
|
|
128
157
|
|
|
129
158
|
return "\n".join(lines)
|
|
130
159
|
|
|
131
160
|
|
|
161
|
+
def _aggregate_costs_by_parent(trace: TraceState) -> list[dict[str, Any]]:
|
|
162
|
+
"""Aggregate LLM costs by parent task/flow span."""
|
|
163
|
+
parent_costs: dict[str, dict[str, Any]] = {}
|
|
164
|
+
|
|
165
|
+
for span in trace.spans.values():
|
|
166
|
+
if not span.llm_info:
|
|
167
|
+
continue
|
|
168
|
+
cost = span.llm_info.get("cost", 0.0)
|
|
169
|
+
if not cost:
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
# Find parent (task or flow span)
|
|
173
|
+
parent_id = span.parent_id
|
|
174
|
+
if not parent_id or parent_id not in trace.spans:
|
|
175
|
+
continue
|
|
176
|
+
parent = trace.spans[parent_id]
|
|
177
|
+
|
|
178
|
+
if parent_id not in parent_costs:
|
|
179
|
+
run_type = "unknown"
|
|
180
|
+
if parent.prefect_info:
|
|
181
|
+
run_type = parent.prefect_info.get("run_type", "unknown")
|
|
182
|
+
parent_costs[parent_id] = {
|
|
183
|
+
"name": parent.name,
|
|
184
|
+
"type": run_type,
|
|
185
|
+
"actual_cost": 0.0,
|
|
186
|
+
"expected_cost": parent.expected_cost,
|
|
187
|
+
"llm_calls": 0,
|
|
188
|
+
}
|
|
189
|
+
parent_costs[parent_id]["actual_cost"] += cost
|
|
190
|
+
parent_costs[parent_id]["llm_calls"] += 1
|
|
191
|
+
|
|
192
|
+
# Sort by cost descending
|
|
193
|
+
return sorted(parent_costs.values(), key=lambda x: x["actual_cost"], reverse=True)
|
|
194
|
+
|
|
195
|
+
|
|
132
196
|
def _format_duration(trace: TraceState) -> str:
|
|
133
197
|
"""Format trace duration as human-readable string."""
|
|
134
198
|
# Calculate from spans if we have them
|
|
@@ -147,32 +211,45 @@ def _format_duration(trace: TraceState) -> str:
|
|
|
147
211
|
|
|
148
212
|
if duration < 1:
|
|
149
213
|
return f"{int(duration * 1000)}ms"
|
|
150
|
-
|
|
214
|
+
if duration < 60:
|
|
151
215
|
return f"{duration:.1f}s"
|
|
152
|
-
|
|
216
|
+
if duration < 3600:
|
|
153
217
|
minutes = int(duration // 60)
|
|
154
218
|
seconds = int(duration % 60)
|
|
155
219
|
return f"{minutes}m {seconds}s"
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return f"{hours}h {minutes}m"
|
|
220
|
+
hours = int(duration // 3600)
|
|
221
|
+
minutes = int((duration % 3600) // 60)
|
|
222
|
+
return f"{hours}h {minutes}m"
|
|
160
223
|
|
|
161
224
|
|
|
162
225
|
def _format_span_line(span: SpanInfo) -> str:
|
|
163
226
|
"""Format a single span as a tree line (without prefix)."""
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
227
|
+
if span.status == "completed":
|
|
228
|
+
status_icon = "\u2705"
|
|
229
|
+
elif span.status == "failed":
|
|
230
|
+
status_icon = "\u274c"
|
|
231
|
+
else:
|
|
232
|
+
status_icon = "\u23f3"
|
|
233
|
+
duration = f"{span.duration_ms}ms" if span.duration_ms < 1000 else f"{span.duration_ms / 1000:.1f}s"
|
|
234
|
+
|
|
235
|
+
# Description suffix for task/flow spans
|
|
236
|
+
desc_suffix = ""
|
|
237
|
+
if span.description and span.span_type != "llm":
|
|
238
|
+
desc_suffix = f" -- {span.description}"
|
|
168
239
|
|
|
240
|
+
# LLM suffix: show purpose (if available) alongside model, plus cost
|
|
169
241
|
llm_suffix = ""
|
|
170
242
|
if span.llm_info:
|
|
171
243
|
model = span.llm_info.get("model", "?")
|
|
172
244
|
tokens = span.llm_info.get("total_tokens", 0)
|
|
173
|
-
|
|
245
|
+
cost = span.llm_info.get("cost", 0)
|
|
246
|
+
purpose = span.llm_info.get("purpose")
|
|
247
|
+
|
|
248
|
+
purpose_part = f"{purpose} | " if purpose else ""
|
|
249
|
+
cost_part = f", ${cost:.4f}" if cost else ""
|
|
250
|
+
llm_suffix = f" [LLM: {purpose_part}{model}, {tokens:,} tokens{cost_part}]"
|
|
174
251
|
|
|
175
|
-
return f"{span.name} ({duration}) {status_icon}{llm_suffix}"
|
|
252
|
+
return f"{span.name} ({duration}) {status_icon}{desc_suffix}{llm_suffix}"
|
|
176
253
|
|
|
177
254
|
|
|
178
255
|
def _build_tree(trace: TraceState, span_id: str, prefix: str = "") -> list[str]:
|
|
@@ -189,8 +266,8 @@ def _build_tree(trace: TraceState, span_id: str, prefix: str = "") -> list[str]:
|
|
|
189
266
|
children = span.children
|
|
190
267
|
for i, child_id in enumerate(children):
|
|
191
268
|
is_last = i == len(children) - 1
|
|
192
|
-
child_prefix = prefix + ("
|
|
193
|
-
continuation_prefix = prefix + (" " if is_last else "
|
|
269
|
+
child_prefix = prefix + ("\u2514\u2500\u2500 " if is_last else "\u251c\u2500\u2500 ")
|
|
270
|
+
continuation_prefix = prefix + (" " if is_last else "\u2502 ")
|
|
194
271
|
|
|
195
272
|
child_span = trace.spans.get(child_id)
|
|
196
273
|
if child_span:
|
|
@@ -200,8 +277,9 @@ def _build_tree(trace: TraceState, span_id: str, prefix: str = "") -> list[str]:
|
|
|
200
277
|
# Recursively add all descendants
|
|
201
278
|
for j, grandchild_id in enumerate(child_span.children):
|
|
202
279
|
gc_is_last = j == len(child_span.children) - 1
|
|
203
|
-
|
|
204
|
-
|
|
280
|
+
gc_connector = "\u2514\u2500\u2500 " if gc_is_last else "\u251c\u2500\u2500 "
|
|
281
|
+
gc_prefix = continuation_prefix + gc_connector
|
|
282
|
+
gc_continuation = continuation_prefix + (" " if gc_is_last else "\u2502 ")
|
|
205
283
|
|
|
206
284
|
# Recursively build subtree for grandchild and all its descendants
|
|
207
285
|
subtree = _build_tree_recursive(trace, grandchild_id, gc_prefix, gc_continuation)
|
|
@@ -210,9 +288,7 @@ def _build_tree(trace: TraceState, span_id: str, prefix: str = "") -> list[str]:
|
|
|
210
288
|
return lines
|
|
211
289
|
|
|
212
290
|
|
|
213
|
-
def _build_tree_recursive(
|
|
214
|
-
trace: TraceState, span_id: str, prefix: str, continuation: str
|
|
215
|
-
) -> list[str]:
|
|
291
|
+
def _build_tree_recursive(trace: TraceState, span_id: str, prefix: str, continuation: str) -> list[str]:
|
|
216
292
|
"""Recursively build tree for a span and all descendants."""
|
|
217
293
|
lines: list[str] = []
|
|
218
294
|
span = trace.spans.get(span_id)
|
|
@@ -226,8 +302,8 @@ def _build_tree_recursive(
|
|
|
226
302
|
children = span.children
|
|
227
303
|
for i, child_id in enumerate(children):
|
|
228
304
|
is_last = i == len(children) - 1
|
|
229
|
-
child_prefix = continuation + ("
|
|
230
|
-
child_continuation = continuation + (" " if is_last else "
|
|
305
|
+
child_prefix = continuation + ("\u2514\u2500\u2500 " if is_last else "\u251c\u2500\u2500 ")
|
|
306
|
+
child_continuation = continuation + (" " if is_last else "\u2502 ")
|
|
231
307
|
|
|
232
308
|
# Recurse for all children
|
|
233
309
|
subtree = _build_tree_recursive(trace, child_id, child_prefix, child_continuation)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Shared data types for the debug tracing system.
|
|
2
|
+
|
|
3
|
+
Extracted to break the circular dependency between _writer.py and _summary.py:
|
|
4
|
+
_writer needs summary generation functions, _summary needs SpanInfo/TraceState.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class WriteJob:
|
|
15
|
+
"""Job for background writer thread."""
|
|
16
|
+
|
|
17
|
+
trace_id: str
|
|
18
|
+
span_id: str
|
|
19
|
+
name: str
|
|
20
|
+
parent_id: str | None
|
|
21
|
+
attributes: dict[str, Any]
|
|
22
|
+
events: list[Any]
|
|
23
|
+
status_code: str # "OK" | "ERROR" | "UNSET"
|
|
24
|
+
status_description: str | None
|
|
25
|
+
start_time_ns: int
|
|
26
|
+
end_time_ns: int
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class SpanInfo:
|
|
31
|
+
"""Information about a span for index building.
|
|
32
|
+
|
|
33
|
+
Tracks execution details including timing, LLM metrics (tokens, cost, expected_cost, purpose),
|
|
34
|
+
and Prefect context for observability and cost tracking across the trace hierarchy.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
span_id: str
|
|
38
|
+
parent_id: str | None
|
|
39
|
+
name: str
|
|
40
|
+
span_type: str
|
|
41
|
+
status: str
|
|
42
|
+
start_time: datetime
|
|
43
|
+
path: Path # Actual directory path for this span
|
|
44
|
+
depth: int = 0 # Nesting depth (0 for root)
|
|
45
|
+
order: int = 0 # Global execution order within trace
|
|
46
|
+
end_time: datetime | None = None
|
|
47
|
+
duration_ms: int = 0
|
|
48
|
+
children: list[str] = field(default_factory=list)
|
|
49
|
+
llm_info: dict[str, Any] | None = None
|
|
50
|
+
prefect_info: dict[str, Any] | None = None
|
|
51
|
+
description: str | None = None
|
|
52
|
+
expected_cost: float | None = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class TraceState:
|
|
57
|
+
"""State for an active trace.
|
|
58
|
+
|
|
59
|
+
Maintains trace metadata and span hierarchy with accumulated cost
|
|
60
|
+
metrics (total_cost, total_expected_cost) for monitoring resource
|
|
61
|
+
usage and budget tracking during trace execution.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
trace_id: str
|
|
65
|
+
name: str
|
|
66
|
+
path: Path
|
|
67
|
+
start_time: datetime
|
|
68
|
+
spans: dict[str, SpanInfo] = field(default_factory=dict)
|
|
69
|
+
root_span_id: str | None = None
|
|
70
|
+
total_tokens: int = 0
|
|
71
|
+
total_cost: float = 0.0
|
|
72
|
+
total_expected_cost: float = 0.0
|
|
73
|
+
llm_call_count: int = 0
|
|
74
|
+
span_counter: int = 0 # Global counter for ordering span directories
|
|
75
|
+
merged_wrapper_ids: set[str] = field(default_factory=set) # IDs of merged wrappers
|