ai-pipeline-core 0.2.6__py3-none-any.whl → 0.4.1__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.
Files changed (94) hide show
  1. ai_pipeline_core/__init__.py +78 -125
  2. ai_pipeline_core/deployment/__init__.py +34 -0
  3. ai_pipeline_core/deployment/base.py +861 -0
  4. ai_pipeline_core/deployment/contract.py +80 -0
  5. ai_pipeline_core/deployment/deploy.py +561 -0
  6. ai_pipeline_core/deployment/helpers.py +97 -0
  7. ai_pipeline_core/deployment/progress.py +126 -0
  8. ai_pipeline_core/deployment/remote.py +116 -0
  9. ai_pipeline_core/docs_generator/__init__.py +54 -0
  10. ai_pipeline_core/docs_generator/__main__.py +5 -0
  11. ai_pipeline_core/docs_generator/cli.py +196 -0
  12. ai_pipeline_core/docs_generator/extractor.py +324 -0
  13. ai_pipeline_core/docs_generator/guide_builder.py +644 -0
  14. ai_pipeline_core/docs_generator/trimmer.py +35 -0
  15. ai_pipeline_core/docs_generator/validator.py +114 -0
  16. ai_pipeline_core/document_store/__init__.py +13 -0
  17. ai_pipeline_core/document_store/_summary.py +9 -0
  18. ai_pipeline_core/document_store/_summary_worker.py +170 -0
  19. ai_pipeline_core/document_store/clickhouse.py +492 -0
  20. ai_pipeline_core/document_store/factory.py +38 -0
  21. ai_pipeline_core/document_store/local.py +312 -0
  22. ai_pipeline_core/document_store/memory.py +85 -0
  23. ai_pipeline_core/document_store/protocol.py +68 -0
  24. ai_pipeline_core/documents/__init__.py +12 -14
  25. ai_pipeline_core/documents/_context_vars.py +85 -0
  26. ai_pipeline_core/documents/_hashing.py +52 -0
  27. ai_pipeline_core/documents/attachment.py +85 -0
  28. ai_pipeline_core/documents/context.py +128 -0
  29. ai_pipeline_core/documents/document.py +318 -1434
  30. ai_pipeline_core/documents/mime_type.py +37 -82
  31. ai_pipeline_core/documents/utils.py +4 -12
  32. ai_pipeline_core/exceptions.py +10 -62
  33. ai_pipeline_core/images/__init__.py +309 -0
  34. ai_pipeline_core/images/_processing.py +151 -0
  35. ai_pipeline_core/llm/__init__.py +6 -4
  36. ai_pipeline_core/llm/ai_messages.py +130 -81
  37. ai_pipeline_core/llm/client.py +327 -193
  38. ai_pipeline_core/llm/model_options.py +14 -86
  39. ai_pipeline_core/llm/model_response.py +60 -103
  40. ai_pipeline_core/llm/model_types.py +16 -34
  41. ai_pipeline_core/logging/__init__.py +2 -7
  42. ai_pipeline_core/logging/logging.yml +1 -1
  43. ai_pipeline_core/logging/logging_config.py +27 -37
  44. ai_pipeline_core/logging/logging_mixin.py +15 -41
  45. ai_pipeline_core/observability/__init__.py +32 -0
  46. ai_pipeline_core/observability/_debug/__init__.py +30 -0
  47. ai_pipeline_core/observability/_debug/_auto_summary.py +94 -0
  48. ai_pipeline_core/observability/_debug/_config.py +95 -0
  49. ai_pipeline_core/observability/_debug/_content.py +764 -0
  50. ai_pipeline_core/observability/_debug/_processor.py +98 -0
  51. ai_pipeline_core/observability/_debug/_summary.py +312 -0
  52. ai_pipeline_core/observability/_debug/_types.py +75 -0
  53. ai_pipeline_core/observability/_debug/_writer.py +843 -0
  54. ai_pipeline_core/observability/_document_tracking.py +146 -0
  55. ai_pipeline_core/observability/_initialization.py +194 -0
  56. ai_pipeline_core/observability/_logging_bridge.py +57 -0
  57. ai_pipeline_core/observability/_summary.py +81 -0
  58. ai_pipeline_core/observability/_tracking/__init__.py +6 -0
  59. ai_pipeline_core/observability/_tracking/_client.py +178 -0
  60. ai_pipeline_core/observability/_tracking/_internal.py +28 -0
  61. ai_pipeline_core/observability/_tracking/_models.py +138 -0
  62. ai_pipeline_core/observability/_tracking/_processor.py +158 -0
  63. ai_pipeline_core/observability/_tracking/_service.py +311 -0
  64. ai_pipeline_core/observability/_tracking/_writer.py +229 -0
  65. ai_pipeline_core/{tracing.py → observability/tracing.py} +139 -283
  66. ai_pipeline_core/pipeline/__init__.py +10 -0
  67. ai_pipeline_core/pipeline/decorators.py +915 -0
  68. ai_pipeline_core/pipeline/options.py +16 -0
  69. ai_pipeline_core/prompt_manager.py +16 -102
  70. ai_pipeline_core/settings.py +26 -31
  71. ai_pipeline_core/testing.py +9 -0
  72. ai_pipeline_core-0.4.1.dist-info/METADATA +807 -0
  73. ai_pipeline_core-0.4.1.dist-info/RECORD +76 -0
  74. {ai_pipeline_core-0.2.6.dist-info → ai_pipeline_core-0.4.1.dist-info}/WHEEL +1 -1
  75. ai_pipeline_core/documents/document_list.py +0 -420
  76. ai_pipeline_core/documents/flow_document.py +0 -112
  77. ai_pipeline_core/documents/task_document.py +0 -117
  78. ai_pipeline_core/documents/temporary_document.py +0 -74
  79. ai_pipeline_core/flow/__init__.py +0 -9
  80. ai_pipeline_core/flow/config.py +0 -483
  81. ai_pipeline_core/flow/options.py +0 -75
  82. ai_pipeline_core/pipeline.py +0 -718
  83. ai_pipeline_core/prefect.py +0 -63
  84. ai_pipeline_core/simple_runner/__init__.py +0 -14
  85. ai_pipeline_core/simple_runner/cli.py +0 -254
  86. ai_pipeline_core/simple_runner/simple_runner.py +0 -247
  87. ai_pipeline_core/storage/__init__.py +0 -8
  88. ai_pipeline_core/storage/storage.py +0 -628
  89. ai_pipeline_core/utils/__init__.py +0 -8
  90. ai_pipeline_core/utils/deploy.py +0 -373
  91. ai_pipeline_core/utils/remote_deployment.py +0 -269
  92. ai_pipeline_core-0.2.6.dist-info/METADATA +0 -500
  93. ai_pipeline_core-0.2.6.dist-info/RECORD +0 -41
  94. {ai_pipeline_core-0.2.6.dist-info → ai_pipeline_core-0.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -6,7 +6,7 @@ Provides logging configuration management that integrates with Prefect's logging
6
6
  import logging.config
7
7
  import os
8
8
  from pathlib import Path
9
- from typing import Any, Dict, Optional
9
+ from typing import Any
10
10
 
11
11
  import yaml
12
12
  from prefect.logging import get_logger
@@ -16,7 +16,7 @@ DEFAULT_LOG_LEVELS = {
16
16
  "ai_pipeline_core": "INFO",
17
17
  "ai_pipeline_core.documents": "INFO",
18
18
  "ai_pipeline_core.llm": "INFO",
19
- "ai_pipeline_core.flow": "INFO",
19
+ "ai_pipeline_core.pipeline": "INFO",
20
20
  "ai_pipeline_core.testing": "DEBUG",
21
21
  }
22
22
 
@@ -32,22 +32,19 @@ class LoggingConfig:
32
32
  3. PREFECT_LOGGING_SETTINGS_PATH environment variable
33
33
  4. Default configuration
34
34
 
35
- Example:
36
- >>> config = LoggingConfig()
37
- >>> config.apply()
38
35
  """
39
36
 
40
- def __init__(self, config_path: Optional[Path] = None):
37
+ def __init__(self, config_path: Path | None = None):
41
38
  """Initialize logging configuration.
42
39
 
43
40
  Args:
44
41
  config_path: Optional path to YAML configuration file.
45
42
  """
46
43
  self.config_path = config_path or self._get_default_config_path()
47
- self._config: Optional[Dict[str, Any]] = None
44
+ self._config: dict[str, Any] | None = None
48
45
 
49
46
  @staticmethod
50
- def _get_default_config_path() -> Optional[Path]:
47
+ def _get_default_config_path() -> Path | None:
51
48
  """Get default config path from environment variables.
52
49
 
53
50
  Returns:
@@ -63,7 +60,7 @@ class LoggingConfig:
63
60
 
64
61
  return None
65
62
 
66
- def load_config(self) -> Dict[str, Any]:
63
+ def load_config(self) -> dict[str, Any]:
67
64
  """Load logging configuration from file or defaults.
68
65
 
69
66
  Returns:
@@ -71,7 +68,7 @@ class LoggingConfig:
71
68
  """
72
69
  if self._config is None:
73
70
  if self.config_path and self.config_path.exists():
74
- with open(self.config_path, "r") as f:
71
+ with open(self.config_path, encoding="utf-8") as f:
75
72
  self._config = yaml.safe_load(f)
76
73
  else:
77
74
  self._config = self._get_default_config()
@@ -80,7 +77,7 @@ class LoggingConfig:
80
77
  return self._config
81
78
 
82
79
  @staticmethod
83
- def _get_default_config() -> Dict[str, Any]:
80
+ def _get_default_config() -> dict[str, Any]:
84
81
  """Get default logging configuration.
85
82
 
86
83
  Returns:
@@ -95,10 +92,7 @@ class LoggingConfig:
95
92
  "datefmt": "%H:%M:%S",
96
93
  },
97
94
  "detailed": {
98
- "format": (
99
- "%(asctime)s | %(levelname)-7s | %(name)s | "
100
- "%(funcName)s:%(lineno)d - %(message)s"
101
- ),
95
+ "format": ("%(asctime)s | %(levelname)-7s | %(name)s | %(funcName)s:%(lineno)d - %(message)s"),
102
96
  "datefmt": "%Y-%m-%d %H:%M:%S",
103
97
  },
104
98
  },
@@ -134,10 +128,10 @@ class LoggingConfig:
134
128
 
135
129
 
136
130
  # Global configuration instance
137
- _logging_config: Optional[LoggingConfig] = None
131
+ _logging_config: LoggingConfig | None = None
138
132
 
139
133
 
140
- def setup_logging(config_path: Optional[Path] = None, level: Optional[str] = None):
134
+ def setup_logging(config_path: Path | None = None, level: str | None = None):
141
135
  """Setup logging for the AI Pipeline Core library.
142
136
 
143
137
  Initializes logging configuration for the pipeline system.
@@ -149,18 +143,8 @@ def setup_logging(config_path: Optional[Path] = None, level: Optional[str] = Non
149
143
  config_path: Optional path to YAML logging configuration file.
150
144
  level: Optional log level override (INFO, DEBUG, WARNING, etc.).
151
145
 
152
- Example:
153
- >>> # In your main.py or application entry point:
154
- >>> def main():
155
- ... setup_logging() # Call once at startup
156
- ... # Your application code here
157
- ...
158
- >>> # Or with custom level:
159
- >>> if __name__ == "__main__":
160
- ... setup_logging(level="DEBUG")
161
- ... run_application()
162
146
  """
163
- global _logging_config
147
+ global _logging_config # noqa: PLW0603
164
148
 
165
149
  _logging_config = LoggingConfig(config_path)
166
150
  _logging_config.apply()
@@ -179,22 +163,28 @@ def setup_logging(config_path: Optional[Path] = None, level: Optional[str] = Non
179
163
  def get_pipeline_logger(name: str):
180
164
  """Get a logger for pipeline components.
181
165
 
182
- @public
183
-
184
- Returns a Prefect-integrated logger with proper configuration.
166
+ Returns a Prefect-integrated logger with the OTel span-event bridge
167
+ attached. Any log record at INFO+ emitted while an OTel span is
168
+ recording will be captured as a span event in the trace.
185
169
 
186
170
  Args:
187
171
  name: Logger name, typically __name__.
188
172
 
189
173
  Returns:
190
- Prefect logger instance.
174
+ Prefect logger instance with bridge handler.
191
175
 
192
- Example:
193
- >>> logger = get_pipeline_logger(__name__)
194
- >>> logger.info("Module initialized")
195
176
  """
196
- # Ensure logging is setup
197
177
  if _logging_config is None:
198
178
  setup_logging()
199
179
 
200
- return get_logger(name)
180
+ logger = get_logger(name)
181
+
182
+ # Attach the singleton bridge handler so log records become OTel span events.
183
+ # The handler is a no-op when no span is recording, so early attachment is safe.
184
+ from ai_pipeline_core.observability._logging_bridge import get_bridge_handler # noqa: PLC0415
185
+
186
+ handler = get_bridge_handler()
187
+ if handler not in logger.handlers:
188
+ logger.addHandler(handler)
189
+
190
+ return logger
@@ -2,9 +2,10 @@
2
2
 
3
3
  import contextlib
4
4
  import time
5
+ from collections.abc import Generator
5
6
  from contextlib import contextmanager
6
7
  from functools import cached_property
7
- from typing import Any, Dict, Generator, Optional
8
+ from typing import Any
8
9
 
9
10
  from prefect import get_run_logger
10
11
  from prefect.context import FlowRunContext, TaskRunContext
@@ -23,7 +24,7 @@ class LoggerMixin:
23
24
  - Internal routing when outside flow/task context
24
25
  """
25
26
 
26
- _logger_name: Optional[str] = None
27
+ _logger_name: str | None = None
27
28
 
28
29
  @cached_property
29
30
  def logger(self):
@@ -32,7 +33,8 @@ class LoggerMixin:
32
33
  return logger
33
34
  return get_logger(self._logger_name or self.__class__.__module__)
34
35
 
35
- def _get_run_logger(self):
36
+ @staticmethod
37
+ def _get_run_logger():
36
38
  """Attempt to get Prefect run logger.
37
39
 
38
40
  Returns:
@@ -56,15 +58,15 @@ class LoggerMixin:
56
58
  """Log warning message with optional context."""
57
59
  self.logger.warning(message, extra=kwargs)
58
60
 
59
- def log_error(self, message: str, exc_info: bool = False, **kwargs: Any) -> None:
61
+ def log_error(self, message: str, *, exc_info: bool = False, **kwargs: Any) -> None:
60
62
  """Log error message with optional exception info."""
61
63
  self.logger.error(message, exc_info=exc_info, extra=kwargs)
62
64
 
63
- def log_critical(self, message: str, exc_info: bool = False, **kwargs: Any) -> None:
65
+ def log_critical(self, message: str, *, exc_info: bool = False, **kwargs: Any) -> None:
64
66
  """Log critical message with optional exception info."""
65
67
  self.logger.critical(message, exc_info=exc_info, extra=kwargs)
66
68
 
67
- def log_with_context(self, level: str, message: str, context: Dict[str, Any]) -> None:
69
+ def log_with_context(self, level: str, message: str, context: dict[str, Any]) -> None:
68
70
  """Log message with structured context.
69
71
 
70
72
  Args:
@@ -72,12 +74,6 @@ class LoggerMixin:
72
74
  message: Log message
73
75
  context: Additional context as dictionary
74
76
 
75
- Example:
76
- self.log_with_context("info", "Processing document", {
77
- "document_id": doc.id,
78
- "document_size": doc.size,
79
- "document_type": doc.type
80
- })
81
77
  """
82
78
  log_method = getattr(self.logger, level.lower(), self.logger.info)
83
79
 
@@ -98,11 +94,6 @@ class StructuredLoggerMixin(LoggerMixin):
98
94
  event: Event name
99
95
  **kwargs: Event attributes
100
96
 
101
- Example:
102
- self.log_event("document_processed",
103
- document_id=doc.id,
104
- duration_ms=processing_time,
105
- status="success")
106
97
  """
107
98
  self.logger.info(event, extra={"event": event, "structured": True, **kwargs})
108
99
 
@@ -115,9 +106,6 @@ class StructuredLoggerMixin(LoggerMixin):
115
106
  unit: Unit of measurement
116
107
  **tags: Additional tags
117
108
 
118
- Example:
119
- self.log_metric("processing_time", 1.23, "seconds",
120
- document_type="pdf", model="gpt-4")
121
109
  """
122
110
  self.logger.info(
123
111
  f"Metric: {metric_name}",
@@ -138,9 +126,6 @@ class StructuredLoggerMixin(LoggerMixin):
138
126
  duration_ms: Duration in milliseconds
139
127
  **attributes: Additional attributes
140
128
 
141
- Example:
142
- self.log_span("llm_generation", 1234.5,
143
- model="gpt-4", tokens=500)
144
129
  """
145
130
  self.logger.info(
146
131
  f"Span: {operation}",
@@ -160,9 +145,6 @@ class StructuredLoggerMixin(LoggerMixin):
160
145
  operation: Operation name
161
146
  **context: Additional context
162
147
 
163
- Example:
164
- with self.log_operation("document_processing", doc_id=doc.id):
165
- process_document(doc)
166
148
  """
167
149
  start_time = time.perf_counter()
168
150
 
@@ -171,14 +153,12 @@ class StructuredLoggerMixin(LoggerMixin):
171
153
  try:
172
154
  yield
173
155
  duration_ms = (time.perf_counter() - start_time) * 1000
174
- self.log_info(
175
- f"Completed {operation}", duration_ms=duration_ms, status="success", **context
176
- )
156
+ self.log_info(f"Completed {operation}", duration_ms=duration_ms, status="success", **context)
177
157
  except Exception as e:
178
158
  # Intentionally broad: Context manager must catch all exceptions to log them
179
159
  duration_ms = (time.perf_counter() - start_time) * 1000
180
160
  self.log_error(
181
- f"Failed {operation}: {str(e)}",
161
+ f"Failed {operation}: {e!s}",
182
162
  exc_info=True,
183
163
  duration_ms=duration_ms,
184
164
  status="failure",
@@ -190,31 +170,25 @@ class StructuredLoggerMixin(LoggerMixin):
190
170
  class PrefectLoggerMixin(StructuredLoggerMixin):
191
171
  """Enhanced mixin specifically for Prefect flows and tasks."""
192
172
 
193
- def log_flow_start(self, flow_name: str, parameters: Dict[str, Any]) -> None:
173
+ def log_flow_start(self, flow_name: str, parameters: dict[str, Any]) -> None:
194
174
  """Log flow start with parameters."""
195
175
  self.log_event("flow_started", flow_name=flow_name, parameters=parameters)
196
176
 
197
177
  def log_flow_end(self, flow_name: str, status: str, duration_ms: float) -> None:
198
178
  """Log flow completion."""
199
- self.log_event(
200
- "flow_completed", flow_name=flow_name, status=status, duration_ms=duration_ms
201
- )
179
+ self.log_event("flow_completed", flow_name=flow_name, status=status, duration_ms=duration_ms)
202
180
 
203
- def log_task_start(self, task_name: str, inputs: Dict[str, Any]) -> None:
181
+ def log_task_start(self, task_name: str, inputs: dict[str, Any]) -> None:
204
182
  """Log task start with inputs."""
205
183
  self.log_event("task_started", task_name=task_name, inputs=inputs)
206
184
 
207
185
  def log_task_end(self, task_name: str, status: str, duration_ms: float) -> None:
208
186
  """Log task completion."""
209
- self.log_event(
210
- "task_completed", task_name=task_name, status=status, duration_ms=duration_ms
211
- )
187
+ self.log_event("task_completed", task_name=task_name, status=status, duration_ms=duration_ms)
212
188
 
213
189
  def log_retry(self, operation: str, attempt: int, max_attempts: int, error: str) -> None:
214
190
  """Log retry attempt."""
215
- self.log_warning(
216
- f"Retrying {operation}", attempt=attempt, max_attempts=max_attempts, error=error
217
- )
191
+ self.log_warning(f"Retrying {operation}", attempt=attempt, max_attempts=max_attempts, error=error)
218
192
 
219
193
  def log_checkpoint(self, checkpoint_name: str, **data: Any) -> None:
220
194
  """Log a checkpoint in processing."""
@@ -0,0 +1,32 @@
1
+ """Observability system for AI pipelines.
2
+
3
+ Contains debug tracing, ClickHouse-based tracking, and initialization utilities.
4
+ """
5
+
6
+ from ai_pipeline_core.observability._debug import (
7
+ ArtifactStore,
8
+ ContentRef,
9
+ ContentWriter,
10
+ LocalDebugSpanProcessor,
11
+ LocalTraceWriter,
12
+ SpanInfo,
13
+ TraceDebugConfig,
14
+ TraceState,
15
+ WriteJob,
16
+ generate_summary,
17
+ )
18
+ from ai_pipeline_core.observability._debug._content import reconstruct_span_content
19
+
20
+ __all__ = [
21
+ "ArtifactStore",
22
+ "ContentRef",
23
+ "ContentWriter",
24
+ "LocalDebugSpanProcessor",
25
+ "LocalTraceWriter",
26
+ "SpanInfo",
27
+ "TraceDebugConfig",
28
+ "TraceState",
29
+ "WriteJob",
30
+ "generate_summary",
31
+ "reconstruct_span_content",
32
+ ]
@@ -0,0 +1,30 @@
1
+ """Local trace debugging system for AI pipelines.
2
+
3
+ This module provides filesystem-based trace debugging that saves all spans
4
+ with their inputs/outputs for LLM-assisted debugging. Includes static
5
+ summary generation and LLM-powered auto-summary capabilities.
6
+
7
+ Enabled automatically in CLI mode (``run_cli``), writing to ``<working_dir>/.trace``.
8
+ Disable with ``--no-trace``.
9
+ """
10
+
11
+ from ._config import TraceDebugConfig
12
+ from ._content import ArtifactStore, ContentRef, ContentWriter, reconstruct_span_content
13
+ from ._processor import LocalDebugSpanProcessor
14
+ from ._summary import generate_summary
15
+ from ._types import SpanInfo, TraceState, WriteJob
16
+ from ._writer import LocalTraceWriter
17
+
18
+ __all__ = [
19
+ "ArtifactStore",
20
+ "ContentRef",
21
+ "ContentWriter",
22
+ "LocalDebugSpanProcessor",
23
+ "LocalTraceWriter",
24
+ "SpanInfo",
25
+ "TraceDebugConfig",
26
+ "TraceState",
27
+ "WriteJob",
28
+ "generate_summary",
29
+ "reconstruct_span_content",
30
+ ]
@@ -0,0 +1,94 @@
1
+ """LLM-powered auto-summary generation for trace debugging.
2
+
3
+ Separated from _summary.py to avoid circular imports: this module depends on
4
+ ai_pipeline_core.llm, which cannot be imported during the initial package load
5
+ chain that includes _debug/__init__.py.
6
+ """
7
+
8
+ from pydantic import BaseModel, ConfigDict
9
+
10
+ from ai_pipeline_core.llm import generate_structured
11
+ from ai_pipeline_core.llm.ai_messages import AIMessages
12
+ from ai_pipeline_core.llm.model_options import ModelOptions
13
+
14
+ from ._types import TraceState
15
+
16
+
17
+ class AutoTraceSummary(BaseModel):
18
+ """LLM-generated trace analysis."""
19
+
20
+ model_config = ConfigDict(frozen=True)
21
+
22
+ overview: str
23
+ outcome: str
24
+ error_analysis: str
25
+ bottlenecks: tuple[str, ...] = ()
26
+ cost_assessment: str
27
+ recommendations: tuple[str, ...] = ()
28
+
29
+
30
+ async def generate_auto_summary(
31
+ trace: TraceState, # noqa: ARG001
32
+ static_summary: str,
33
+ model: str,
34
+ ) -> str | None:
35
+ """Generate LLM-powered auto-summary of the trace.
36
+
37
+ Args:
38
+ trace: Completed trace state with all span data.
39
+ static_summary: Pre-generated static summary text used as LLM input context.
40
+ model: LLM model name for summary generation.
41
+
42
+ Returns:
43
+ Formatted markdown auto-summary string, or None if generation fails.
44
+ """
45
+ messages = AIMessages()
46
+ messages.append(static_summary)
47
+
48
+ options = ModelOptions(
49
+ system_prompt=(
50
+ "You are analyzing an AI pipeline execution trace. "
51
+ "Provide concise, actionable analysis based on the execution data. "
52
+ "Focus on cost efficiency, performance bottlenecks, and errors."
53
+ ),
54
+ )
55
+
56
+ result = await generate_structured(
57
+ model=model,
58
+ response_format=AutoTraceSummary,
59
+ messages=messages,
60
+ options=options,
61
+ purpose="trace_auto_summary",
62
+ )
63
+
64
+ if not result or not result.parsed:
65
+ return None
66
+
67
+ summary = result.parsed
68
+ lines = [
69
+ "# Auto-Summary (LLM-Generated)",
70
+ "",
71
+ f"**Overview:** {summary.overview}",
72
+ "",
73
+ f"**Outcome:** {summary.outcome}",
74
+ "",
75
+ ]
76
+
77
+ if summary.error_analysis:
78
+ lines.append(f"**Error Analysis:** {summary.error_analysis}")
79
+ lines.append("")
80
+
81
+ if summary.bottlenecks:
82
+ lines.append("**Bottlenecks:**")
83
+ lines.extend(f"- {b}" for b in summary.bottlenecks)
84
+ lines.append("")
85
+
86
+ lines.append(f"**Cost Assessment:** {summary.cost_assessment}")
87
+ lines.append("")
88
+
89
+ if summary.recommendations:
90
+ lines.append("**Recommendations:**")
91
+ lines.extend(f"- {r}" for r in summary.recommendations)
92
+ lines.append("")
93
+
94
+ return "\n".join(lines)
@@ -0,0 +1,95 @@
1
+ """Configuration for local trace debugging."""
2
+
3
+ from pathlib import Path
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+
8
+ class TraceDebugConfig(BaseModel):
9
+ """Configuration for local trace debugging.
10
+
11
+ Controls how traces are written to the local filesystem for debugging.
12
+ Enabled automatically in CLI mode, writing to ``<working_dir>/.trace``.
13
+ """
14
+
15
+ model_config = ConfigDict(frozen=True)
16
+
17
+ path: Path = Field(description="Directory for debug traces")
18
+ enabled: bool = Field(default=True, description="Whether debug tracing is enabled")
19
+
20
+ # Content size limits (Issue #2)
21
+ max_file_bytes: int = Field(
22
+ default=50_000,
23
+ description="Max bytes for input.yaml or output.yaml. Elements externalized to stay under.",
24
+ )
25
+ max_element_bytes: int = Field(
26
+ default=10_000,
27
+ description="Max bytes for single element. Above this, partial + artifact ref.",
28
+ )
29
+ element_excerpt_bytes: int = Field(
30
+ default=2_000,
31
+ description="Bytes of content to keep inline when element exceeds max_element_bytes.",
32
+ )
33
+ max_content_bytes: int = Field(
34
+ default=10_000_000,
35
+ description="Max bytes for any single artifact. Above this, truncate.",
36
+ )
37
+
38
+ # Image handling (Issue #7 - no changes per user)
39
+ extract_base64_images: bool = Field(
40
+ default=True,
41
+ description="Extract base64 images to artifact files",
42
+ )
43
+
44
+ # Span optimization (Issue #4)
45
+ merge_wrapper_spans: bool = Field(
46
+ default=True,
47
+ description="Merge Prefect wrapper spans with inner traced function spans",
48
+ )
49
+
50
+ # Indexes (Issue #1)
51
+ include_llm_index: bool = Field(
52
+ default=True,
53
+ description="Generate _llm_calls.yaml with LLM-specific details",
54
+ )
55
+ include_error_index: bool = Field(
56
+ default=True,
57
+ description="Generate _errors.yaml with failed span details",
58
+ )
59
+
60
+ # Cleanup
61
+ max_traces: int | None = Field(
62
+ default=None,
63
+ description="Max number of traces to keep. None for unlimited.",
64
+ )
65
+
66
+ # Security - default redaction patterns for common secrets
67
+ redact_patterns: tuple[str, ...] = Field(
68
+ default=(
69
+ r"sk-[a-zA-Z0-9]{20,}", # OpenAI API keys
70
+ r"sk-proj-[a-zA-Z0-9\-_]{20,}", # OpenAI project keys
71
+ r"AKIA[0-9A-Z]{16}", # AWS access keys
72
+ r"ghp_[a-zA-Z0-9]{36}", # GitHub personal tokens
73
+ r"gho_[a-zA-Z0-9]{36}", # GitHub OAuth tokens
74
+ r"xoxb-[a-zA-Z0-9\-]+", # Slack bot tokens
75
+ r"xoxp-[a-zA-Z0-9\-]+", # Slack user tokens
76
+ r"(?i)password\s*[:=]\s*['\"]?[^\s'\"]+", # Passwords
77
+ r"(?i)secret\s*[:=]\s*['\"]?[^\s'\"]+", # Secrets
78
+ r"(?i)api[_\-]?key\s*[:=]\s*['\"]?[^\s'\"]+", # API keys
79
+ r"(?i)bearer\s+[a-zA-Z0-9\-_\.]+", # Bearer tokens
80
+ ),
81
+ description="Regex patterns for secrets to redact",
82
+ )
83
+
84
+ # Summary
85
+ generate_summary: bool = Field(default=True, description="Generate _summary.md")
86
+
87
+ # Auto-summary (LLM-powered)
88
+ auto_summary_enabled: bool = Field(
89
+ default=False,
90
+ description="Generate LLM-powered auto-summary after trace completion",
91
+ )
92
+ auto_summary_model: str = Field(
93
+ default="gemini-3-flash",
94
+ description="Model to use for auto-summary generation",
95
+ )