qtype 0.0.16__py3-none-any.whl → 0.1.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 (128) hide show
  1. qtype/application/commons/tools.py +1 -1
  2. qtype/application/converters/tools_from_api.py +5 -5
  3. qtype/application/converters/tools_from_module.py +2 -2
  4. qtype/application/converters/types.py +14 -43
  5. qtype/application/documentation.py +1 -1
  6. qtype/application/facade.py +94 -73
  7. qtype/base/types.py +227 -7
  8. qtype/cli.py +4 -0
  9. qtype/commands/convert.py +20 -8
  10. qtype/commands/generate.py +19 -27
  11. qtype/commands/run.py +73 -36
  12. qtype/commands/serve.py +74 -54
  13. qtype/commands/validate.py +34 -8
  14. qtype/commands/visualize.py +46 -22
  15. qtype/dsl/__init__.py +6 -5
  16. qtype/dsl/custom_types.py +1 -1
  17. qtype/dsl/domain_types.py +65 -5
  18. qtype/dsl/linker.py +384 -0
  19. qtype/dsl/loader.py +315 -0
  20. qtype/dsl/model.py +612 -363
  21. qtype/dsl/parser.py +200 -0
  22. qtype/dsl/types.py +50 -0
  23. qtype/interpreter/api.py +57 -136
  24. qtype/interpreter/auth/aws.py +19 -9
  25. qtype/interpreter/auth/generic.py +93 -16
  26. qtype/interpreter/base/base_step_executor.py +436 -0
  27. qtype/interpreter/base/batch_step_executor.py +171 -0
  28. qtype/interpreter/base/exceptions.py +50 -0
  29. qtype/interpreter/base/executor_context.py +74 -0
  30. qtype/interpreter/base/factory.py +117 -0
  31. qtype/interpreter/base/progress_tracker.py +110 -0
  32. qtype/interpreter/base/secrets.py +339 -0
  33. qtype/interpreter/base/step_cache.py +74 -0
  34. qtype/interpreter/base/stream_emitter.py +469 -0
  35. qtype/interpreter/conversions.py +462 -22
  36. qtype/interpreter/converters.py +77 -0
  37. qtype/interpreter/endpoints.py +355 -0
  38. qtype/interpreter/executors/agent_executor.py +242 -0
  39. qtype/interpreter/executors/aggregate_executor.py +93 -0
  40. qtype/interpreter/executors/decoder_executor.py +163 -0
  41. qtype/interpreter/executors/doc_to_text_executor.py +112 -0
  42. qtype/interpreter/executors/document_embedder_executor.py +107 -0
  43. qtype/interpreter/executors/document_search_executor.py +122 -0
  44. qtype/interpreter/executors/document_source_executor.py +118 -0
  45. qtype/interpreter/executors/document_splitter_executor.py +105 -0
  46. qtype/interpreter/executors/echo_executor.py +63 -0
  47. qtype/interpreter/executors/field_extractor_executor.py +160 -0
  48. qtype/interpreter/executors/file_source_executor.py +101 -0
  49. qtype/interpreter/executors/file_writer_executor.py +110 -0
  50. qtype/interpreter/executors/index_upsert_executor.py +228 -0
  51. qtype/interpreter/executors/invoke_embedding_executor.py +92 -0
  52. qtype/interpreter/executors/invoke_flow_executor.py +51 -0
  53. qtype/interpreter/executors/invoke_tool_executor.py +358 -0
  54. qtype/interpreter/executors/llm_inference_executor.py +272 -0
  55. qtype/interpreter/executors/prompt_template_executor.py +78 -0
  56. qtype/interpreter/executors/sql_source_executor.py +106 -0
  57. qtype/interpreter/executors/vector_search_executor.py +91 -0
  58. qtype/interpreter/flow.py +159 -22
  59. qtype/interpreter/metadata_api.py +115 -0
  60. qtype/interpreter/resource_cache.py +5 -4
  61. qtype/interpreter/rich_progress.py +225 -0
  62. qtype/interpreter/stream/chat/__init__.py +15 -0
  63. qtype/interpreter/stream/chat/converter.py +391 -0
  64. qtype/interpreter/{chat → stream/chat}/file_conversions.py +2 -2
  65. qtype/interpreter/stream/chat/ui_request_to_domain_type.py +140 -0
  66. qtype/interpreter/stream/chat/vercel.py +609 -0
  67. qtype/interpreter/stream/utils/__init__.py +15 -0
  68. qtype/interpreter/stream/utils/build_vercel_ai_formatter.py +74 -0
  69. qtype/interpreter/stream/utils/callback_to_stream.py +66 -0
  70. qtype/interpreter/stream/utils/create_streaming_response.py +18 -0
  71. qtype/interpreter/stream/utils/default_chat_extract_text.py +20 -0
  72. qtype/interpreter/stream/utils/error_streaming_response.py +20 -0
  73. qtype/interpreter/telemetry.py +135 -8
  74. qtype/interpreter/tools/__init__.py +5 -0
  75. qtype/interpreter/tools/function_tool_helper.py +265 -0
  76. qtype/interpreter/types.py +330 -0
  77. qtype/interpreter/typing.py +83 -89
  78. qtype/interpreter/ui/404/index.html +1 -1
  79. qtype/interpreter/ui/404.html +1 -1
  80. qtype/interpreter/ui/_next/static/{nUaw6_IwRwPqkzwe5s725 → 20HoJN6otZ_LyHLHpCPE6}/_buildManifest.js +1 -1
  81. qtype/interpreter/ui/_next/static/chunks/{393-8fd474427f8e19ce.js → 434-b2112d19f25c44ff.js} +3 -3
  82. qtype/interpreter/ui/_next/static/chunks/app/page-8c67d16ac90d23cb.js +1 -0
  83. qtype/interpreter/ui/_next/static/chunks/ba12c10f-546f2714ff8abc66.js +1 -0
  84. qtype/interpreter/ui/_next/static/css/8a8d1269e362fef7.css +3 -0
  85. qtype/interpreter/ui/icon.png +0 -0
  86. qtype/interpreter/ui/index.html +1 -1
  87. qtype/interpreter/ui/index.txt +4 -4
  88. qtype/semantic/checker.py +583 -0
  89. qtype/semantic/generate.py +262 -83
  90. qtype/semantic/loader.py +95 -0
  91. qtype/semantic/model.py +436 -159
  92. qtype/semantic/resolver.py +63 -19
  93. qtype/semantic/visualize.py +28 -31
  94. {qtype-0.0.16.dist-info → qtype-0.1.1.dist-info}/METADATA +16 -3
  95. qtype-0.1.1.dist-info/RECORD +135 -0
  96. qtype/dsl/base_types.py +0 -38
  97. qtype/dsl/validator.py +0 -465
  98. qtype/interpreter/batch/__init__.py +0 -0
  99. qtype/interpreter/batch/file_sink_source.py +0 -162
  100. qtype/interpreter/batch/flow.py +0 -95
  101. qtype/interpreter/batch/sql_source.py +0 -92
  102. qtype/interpreter/batch/step.py +0 -74
  103. qtype/interpreter/batch/types.py +0 -41
  104. qtype/interpreter/batch/utils.py +0 -178
  105. qtype/interpreter/chat/chat_api.py +0 -237
  106. qtype/interpreter/chat/vercel.py +0 -314
  107. qtype/interpreter/exceptions.py +0 -10
  108. qtype/interpreter/step.py +0 -67
  109. qtype/interpreter/steps/__init__.py +0 -0
  110. qtype/interpreter/steps/agent.py +0 -114
  111. qtype/interpreter/steps/condition.py +0 -36
  112. qtype/interpreter/steps/decoder.py +0 -88
  113. qtype/interpreter/steps/llm_inference.py +0 -171
  114. qtype/interpreter/steps/prompt_template.py +0 -54
  115. qtype/interpreter/steps/search.py +0 -24
  116. qtype/interpreter/steps/tool.py +0 -219
  117. qtype/interpreter/streaming_helpers.py +0 -123
  118. qtype/interpreter/ui/_next/static/chunks/app/page-7e26b6156cfb55d3.js +0 -1
  119. qtype/interpreter/ui/_next/static/chunks/ba12c10f-22556063851a6df2.js +0 -1
  120. qtype/interpreter/ui/_next/static/css/b40532b0db09cce3.css +0 -3
  121. qtype/interpreter/ui/favicon.ico +0 -0
  122. qtype/loader.py +0 -390
  123. qtype-0.0.16.dist-info/RECORD +0 -106
  124. /qtype/interpreter/ui/_next/static/{nUaw6_IwRwPqkzwe5s725 → 20HoJN6otZ_LyHLHpCPE6}/_ssgManifest.js +0 -0
  125. {qtype-0.0.16.dist-info → qtype-0.1.1.dist-info}/WHEEL +0 -0
  126. {qtype-0.0.16.dist-info → qtype-0.1.1.dist-info}/entry_points.txt +0 -0
  127. {qtype-0.0.16.dist-info → qtype-0.1.1.dist-info}/licenses/LICENSE +0 -0
  128. {qtype-0.0.16.dist-info → qtype-0.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,171 @@
1
+ from abc import abstractmethod
2
+ from typing import Any, AsyncIterator
3
+
4
+ from aiostream import stream
5
+ from opentelemetry.trace import Status, StatusCode
6
+
7
+ from qtype.interpreter.base.base_step_executor import StepExecutor
8
+ from qtype.interpreter.base.executor_context import ExecutorContext
9
+ from qtype.interpreter.types import FlowMessage
10
+
11
+
12
+ class BatchedStepExecutor(StepExecutor):
13
+ """
14
+ Executor for steps that benefit from API-level batching.
15
+
16
+ This executor groups messages into batches and processes them together,
17
+ which is useful for operations that can leverage batch APIs for better
18
+ performance (e.g., GPU operations, bulk database operations, batch inference).
19
+
20
+ Like StepExecutor, this supports concurrent processing, but the unit of
21
+ concurrency is the batch rather than individual messages.
22
+
23
+ **Subclass Requirements:**
24
+ - Must implement `process_batch()` to handle batch processing
25
+ - Must NOT implement `process_message()` (it's handled automatically)
26
+ - Can optionally implement `finalize()` for cleanup/terminal operations
27
+
28
+ Args:
29
+ step: The semantic step model defining behavior and configuration
30
+ on_stream_event: Optional callback for real-time streaming events
31
+ on_progress: Optional callback for progress updates
32
+ **dependencies: Executor-specific dependencies
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ step: Any,
38
+ context: ExecutorContext,
39
+ **dependencies: Any,
40
+ ):
41
+ super().__init__(step, context, **dependencies)
42
+ # Override the processor to use batch processing with telemetry
43
+ # instead of message processing
44
+ self._processor = self._process_batch_with_telemetry
45
+
46
+ def _prepare_message_stream(
47
+ self, valid_messages: AsyncIterator[FlowMessage]
48
+ ) -> Any:
49
+ """
50
+ Prepare messages by chunking them into batches.
51
+
52
+ Overrides the base implementation to group messages into batches
53
+ based on the step's batch_config.
54
+
55
+ Args:
56
+ valid_messages: Stream of valid (non-failed) messages
57
+
58
+ Returns:
59
+ Stream of message batches (AsyncIterable[list[FlowMessage]])
60
+ """
61
+ # Determine batch size from step configuration
62
+ batch_size = 1
63
+ if (
64
+ hasattr(self.step, "batch_config")
65
+ and self.step.batch_config is not None # type: ignore[attr-defined]
66
+ ):
67
+ batch_size = self.step.batch_config.batch_size # type: ignore[attr-defined]
68
+
69
+ # Group messages into batches
70
+ return stream.chunks(valid_messages, batch_size)
71
+
72
+ async def process_message(
73
+ self, message: FlowMessage
74
+ ) -> AsyncIterator[FlowMessage]:
75
+ """
76
+ Process a single message by wrapping it in a batch of one.
77
+
78
+ This method is implemented automatically to satisfy the base class
79
+ contract. Subclasses should NOT override this method.
80
+
81
+ Args:
82
+ message: The input message to process
83
+
84
+ Yields:
85
+ Processed messages from the batch
86
+ """
87
+ raise NotImplementedError(
88
+ "Batch executors should call process_batch, not process_message."
89
+ )
90
+ yield # type: ignore[misc]
91
+
92
+ @abstractmethod
93
+ async def process_batch(
94
+ self, batch: list[FlowMessage]
95
+ ) -> AsyncIterator[FlowMessage]:
96
+ """
97
+ Process a batch of messages as a single API call.
98
+
99
+ Subclasses MUST implement this method to define how batches are
100
+ processed together for improved performance.
101
+
102
+ This is a many-to-many operation: a batch of input messages yields
103
+ a corresponding set of output messages. Messages should be yielded
104
+ as they become available to maintain memory efficiency (don't collect
105
+ all results before yielding).
106
+
107
+ This method is automatically wrapped with telemetry tracing when
108
+ called through the executor's execution pipeline.
109
+
110
+ Args:
111
+ batch: List of input messages to process as a batch
112
+
113
+ Yields:
114
+ Processed messages corresponding to the input batch
115
+ """
116
+ yield # type: ignore[misc]
117
+
118
+ async def _process_batch_with_telemetry(
119
+ self, batch: list[FlowMessage]
120
+ ) -> AsyncIterator[FlowMessage]:
121
+ """
122
+ Internal wrapper that adds telemetry tracing to process_batch.
123
+
124
+ This method creates a span for batch processing operations,
125
+ automatically recording batch size, errors, and success metrics.
126
+ """
127
+ span = self._tracer.start_span(
128
+ f"step.{self.step.id}.process_batch",
129
+ attributes={
130
+ "batch.size": len(batch),
131
+ },
132
+ )
133
+
134
+ try:
135
+ output_count = 0
136
+ error_count = 0
137
+
138
+ async for output_msg in self.process_batch(batch):
139
+ output_count += 1
140
+ if output_msg.is_failed():
141
+ error_count += 1
142
+ span.add_event(
143
+ "message_failed",
144
+ {
145
+ "error": str(output_msg.error),
146
+ },
147
+ )
148
+ yield output_msg
149
+
150
+ # Record processing metrics
151
+ span.set_attribute("batch.outputs", output_count)
152
+ span.set_attribute("batch.errors", error_count)
153
+
154
+ if error_count > 0:
155
+ span.set_status(
156
+ Status(
157
+ StatusCode.ERROR,
158
+ f"{error_count} of {output_count} messages failed",
159
+ )
160
+ )
161
+ else:
162
+ span.set_status(Status(StatusCode.OK))
163
+
164
+ except Exception as e:
165
+ span.record_exception(e)
166
+ span.set_status(
167
+ Status(StatusCode.ERROR, f"Batch processing failed: {e}")
168
+ )
169
+ raise
170
+ finally:
171
+ span.end()
@@ -0,0 +1,50 @@
1
+ """
2
+ Custom exception types for the QType interpreter.
3
+
4
+ This module provides specialized exception classes for better error handling
5
+ and reporting throughout the interpreter layer.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+
11
+ class SecretResolutionError(Exception):
12
+ """
13
+ Raised when a secret cannot be resolved.
14
+
15
+ This exception is raised when attempting to resolve a SecretReference
16
+ but the operation fails due to:
17
+ - No secret manager configured
18
+ - Secret not found in the secret store
19
+ - Invalid secret format or structure
20
+ - Authentication/authorization failures
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ secret_name: str,
26
+ context: str = "",
27
+ cause: Exception | None = None,
28
+ ):
29
+ """
30
+ Initialize SecretResolutionError with structured attributes.
31
+
32
+ Args:
33
+ secret_name: Name/ID/ARN of the secret that failed to resolve
34
+ context: Optional context describing where resolution failed
35
+ (e.g., "auth provider 'my_auth'", "step 'my_step'")
36
+ cause: Optional underlying exception that caused the failure
37
+ """
38
+ self.secret_name = secret_name
39
+ self.context = context
40
+ self.cause = cause
41
+ super().__init__(self._format_message())
42
+
43
+ def _format_message(self) -> str:
44
+ """Format the error message from structured attributes."""
45
+ msg = f"Failed to resolve secret '{self.secret_name}'"
46
+ if self.context:
47
+ msg += f" in {self.context}"
48
+ if self.cause:
49
+ msg += f": {self.cause}"
50
+ return msg
@@ -0,0 +1,74 @@
1
+ """
2
+ Execution context for flow and step executors.
3
+
4
+ This module provides the ExecutorContext dataclass that bundles cross-cutting
5
+ concerns threaded through the execution pipeline.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+
12
+ from opentelemetry.trace import Tracer
13
+
14
+ from qtype.interpreter.base.secrets import SecretManagerBase
15
+ from qtype.interpreter.types import ProgressCallback, StreamingCallback
16
+
17
+
18
+ @dataclass
19
+ class ExecutorContext:
20
+ """
21
+ Runtime context for flow execution shared across all executors.
22
+
23
+ This bundles cross-cutting concerns that need to be threaded through
24
+ the execution pipeline but aren't specific to individual step types.
25
+ Using a context object reduces parameter threading boilerplate while
26
+ keeping dependencies explicit and testable.
27
+
28
+ Secret Resolution Lifecycle:
29
+ Secrets are resolved EARLY in the execution pipeline, following a
30
+ fail-fast principle:
31
+
32
+ 1. At executor construction time, any SecretReferences in step
33
+ configuration are resolved
34
+ 2. At auth context creation time, SecretReferences in auth providers
35
+ are resolved (via auth() context manager)
36
+ 3. Resolution failures raise SecretResolutionError immediately,
37
+ preventing execution from starting with invalid configuration
38
+
39
+ This ensures:
40
+ - Errors are caught before expensive operations begin
41
+ - All secrets are validated at initialization
42
+ - No partial execution with missing secrets
43
+ - Clear, actionable error messages at startup
44
+
45
+ Attributes:
46
+ secret_manager: Secret manager for resolving SecretReferences at
47
+ runtime. Always present (uses NoOpSecretManager if no secrets
48
+ are configured), eliminating the need for None checks.
49
+ on_stream_event: Optional callback for streaming real-time execution
50
+ events (chunks, steps, errors) to clients.
51
+ on_progress: Optional callback for progress updates during execution.
52
+ tracer: OpenTelemetry tracer for distributed tracing and observability.
53
+ Defaults to a no-op tracer if telemetry is not configured.
54
+
55
+ Example:
56
+ ```python
57
+ from qtype.interpreter.base.executor_context import ExecutorContext
58
+ from qtype.interpreter.base.secrets import create_secret_manager
59
+ from opentelemetry import trace
60
+
61
+ context = ExecutorContext(
62
+ secret_manager=create_secret_manager(config),
63
+ on_stream_event=my_stream_callback,
64
+ tracer=trace.get_tracer(__name__)
65
+ )
66
+
67
+ executor = create_executor(step, context=context)
68
+ ```
69
+ """
70
+
71
+ secret_manager: SecretManagerBase
72
+ on_stream_event: StreamingCallback | None = None
73
+ on_progress: ProgressCallback | None = None
74
+ tracer: Tracer | None = None
@@ -0,0 +1,117 @@
1
+ from qtype.interpreter.executors.agent_executor import AgentExecutor
2
+ from qtype.interpreter.executors.aggregate_executor import AggregateExecutor
3
+ from qtype.interpreter.executors.decoder_executor import DecoderExecutor
4
+ from qtype.interpreter.executors.doc_to_text_executor import (
5
+ DocToTextConverterExecutor,
6
+ )
7
+ from qtype.interpreter.executors.document_embedder_executor import (
8
+ DocumentEmbedderExecutor,
9
+ )
10
+ from qtype.interpreter.executors.document_search_executor import (
11
+ DocumentSearchExecutor,
12
+ )
13
+ from qtype.interpreter.executors.document_source_executor import (
14
+ DocumentSourceExecutor,
15
+ )
16
+ from qtype.interpreter.executors.document_splitter_executor import (
17
+ DocumentSplitterExecutor,
18
+ )
19
+ from qtype.interpreter.executors.echo_executor import EchoExecutor
20
+ from qtype.interpreter.executors.field_extractor_executor import (
21
+ FieldExtractorExecutor,
22
+ )
23
+ from qtype.interpreter.executors.file_source_executor import FileSourceExecutor
24
+ from qtype.interpreter.executors.file_writer_executor import FileWriterExecutor
25
+ from qtype.interpreter.executors.index_upsert_executor import (
26
+ IndexUpsertExecutor,
27
+ )
28
+ from qtype.interpreter.executors.invoke_embedding_executor import (
29
+ InvokeEmbeddingExecutor,
30
+ )
31
+ from qtype.interpreter.executors.invoke_flow_executor import InvokeFlowExecutor
32
+ from qtype.interpreter.executors.invoke_tool_executor import InvokeToolExecutor
33
+ from qtype.interpreter.executors.llm_inference_executor import (
34
+ LLMInferenceExecutor,
35
+ )
36
+ from qtype.interpreter.executors.prompt_template_executor import (
37
+ PromptTemplateExecutor,
38
+ )
39
+ from qtype.interpreter.executors.sql_source_executor import SQLSourceExecutor
40
+ from qtype.interpreter.executors.vector_search_executor import (
41
+ VectorSearchExecutor,
42
+ )
43
+ from qtype.semantic.model import (
44
+ Agent,
45
+ Aggregate,
46
+ Decoder,
47
+ DocToTextConverter,
48
+ DocumentEmbedder,
49
+ DocumentSearch,
50
+ DocumentSource,
51
+ DocumentSplitter,
52
+ Echo,
53
+ FieldExtractor,
54
+ FileSource,
55
+ FileWriter,
56
+ IndexUpsert,
57
+ InvokeEmbedding,
58
+ InvokeFlow,
59
+ InvokeTool,
60
+ LLMInference,
61
+ PromptTemplate,
62
+ SQLSource,
63
+ Step,
64
+ VectorSearch,
65
+ )
66
+
67
+ from .batch_step_executor import StepExecutor
68
+ from .executor_context import ExecutorContext
69
+
70
+ # ... import other executors
71
+
72
+ EXECUTOR_REGISTRY = {
73
+ Agent: AgentExecutor,
74
+ Aggregate: AggregateExecutor,
75
+ Decoder: DecoderExecutor,
76
+ DocToTextConverter: DocToTextConverterExecutor,
77
+ DocumentEmbedder: DocumentEmbedderExecutor,
78
+ DocumentSearch: DocumentSearchExecutor,
79
+ DocumentSource: DocumentSourceExecutor,
80
+ DocumentSplitter: DocumentSplitterExecutor,
81
+ Echo: EchoExecutor,
82
+ FieldExtractor: FieldExtractorExecutor,
83
+ FileSource: FileSourceExecutor,
84
+ FileWriter: FileWriterExecutor,
85
+ IndexUpsert: IndexUpsertExecutor,
86
+ InvokeEmbedding: InvokeEmbeddingExecutor,
87
+ InvokeFlow: InvokeFlowExecutor,
88
+ InvokeTool: InvokeToolExecutor,
89
+ LLMInference: LLMInferenceExecutor,
90
+ PromptTemplate: PromptTemplateExecutor,
91
+ SQLSource: SQLSourceExecutor,
92
+ VectorSearch: VectorSearchExecutor,
93
+ }
94
+
95
+
96
+ def create_executor(
97
+ step: Step, context: ExecutorContext, **dependencies
98
+ ) -> StepExecutor:
99
+ """
100
+ Factory to create the appropriate executor for a given step.
101
+
102
+ Args:
103
+ step: The step to create an executor for
104
+ context: ExecutorContext containing cross-cutting concerns
105
+ **dependencies: Executor-specific dependencies
106
+
107
+ Returns:
108
+ StepExecutor: Configured executor instance
109
+ """
110
+ executor_class = EXECUTOR_REGISTRY.get(type(step))
111
+ if not executor_class:
112
+ raise ValueError(
113
+ f"No executor found for step type: {type(step).__name__}"
114
+ )
115
+
116
+ # This assumes the constructor takes the step, context, then dependencies
117
+ return executor_class(step, context, **dependencies)
@@ -0,0 +1,110 @@
1
+ from qtype.interpreter.types import FlowMessage, ProgressCallback
2
+
3
+
4
+ class ProgressTracker:
5
+ """
6
+ Tracks progress for step execution.
7
+
8
+ This class encapsulates all progress tracking logic, separating it from
9
+ the execution logic in StepExecutor.
10
+
11
+ Attributes:
12
+ step_id: ID of the step being tracked
13
+ items_processed: Total number of items processed
14
+ items_in_error: Number of items that encountered errors
15
+ total_items: Total expected items (None if unknown)
16
+ """
17
+
18
+ def __init__(self, step_id: str, total_items: int | None = None):
19
+ self.step_id = step_id
20
+ self.items_processed = 0
21
+ self.items_in_error = 0
22
+ self.total_items = total_items
23
+ self.cache_hits = None
24
+ self.cache_misses = None
25
+
26
+ @property
27
+ def items_succeeded(self) -> int:
28
+ """
29
+ Number of items successfully processed.
30
+
31
+ This is derived from items_processed and items_in_error to avoid
32
+ state inconsistency.
33
+ """
34
+ return self.items_processed - self.items_in_error
35
+
36
+ def update(
37
+ self,
38
+ on_progress: ProgressCallback | None,
39
+ processed_delta: int,
40
+ error_delta: int,
41
+ hit_delta: int | None = None,
42
+ miss_delta: int | None = None,
43
+ ) -> None:
44
+ """
45
+ Update progress counters and invoke the progress callback.
46
+
47
+ Internal state is always updated regardless of whether a callback
48
+ is provided. This ensures the aggregator can access accurate counts.
49
+
50
+ Args:
51
+ on_progress: Optional callback to notify of progress updates
52
+ processed_delta: Number of items processed in this update
53
+ error_delta: Number of items that failed in this update
54
+ """
55
+ self.items_processed += processed_delta
56
+ self.items_in_error += error_delta
57
+
58
+ if hit_delta is not None:
59
+ self.cache_hits = (
60
+ self.cache_hits + hit_delta
61
+ if self.cache_hits is not None
62
+ else hit_delta
63
+ )
64
+ if miss_delta is not None:
65
+ self.cache_misses = (
66
+ self.cache_misses + miss_delta
67
+ if self.cache_misses is not None
68
+ else miss_delta
69
+ )
70
+
71
+ if on_progress:
72
+ on_progress(
73
+ self.step_id,
74
+ self.items_processed,
75
+ self.items_in_error,
76
+ self.items_succeeded,
77
+ self.total_items,
78
+ self.cache_hits,
79
+ self.cache_misses,
80
+ )
81
+
82
+ def update_for_message(
83
+ self,
84
+ message: FlowMessage,
85
+ on_progress: ProgressCallback | None,
86
+ ) -> None:
87
+ """
88
+ Update progress based on a single message result.
89
+
90
+ Args:
91
+ message: The message to check for success/failure
92
+ on_progress: Optional callback to notify of progress updates
93
+ """
94
+ self.update(on_progress, 1, 1 if message.is_failed() else 0)
95
+
96
+ def increment_cache(
97
+ self,
98
+ on_progress: ProgressCallback | None,
99
+ hit_delta: int = 0,
100
+ miss_delta: int = 0,
101
+ ) -> None:
102
+ """
103
+ Increment cache hit/miss counters.
104
+
105
+ Args:
106
+ on_progress: Optional callback to notify of progress updates
107
+ hit_delta: Number of cache hits to add
108
+ miss_delta: Number of cache misses to add
109
+ """
110
+ self.update(on_progress, 0, 0, hit_delta, miss_delta)