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,228 @@
1
+ """Index upsert executor for inserting documents/chunks into indexes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import AsyncIterator
7
+
8
+ from llama_index.core.schema import TextNode
9
+
10
+ from qtype.dsl.domain_types import RAGChunk, RAGDocument
11
+ from qtype.interpreter.base.batch_step_executor import BatchedStepExecutor
12
+ from qtype.interpreter.base.executor_context import ExecutorContext
13
+ from qtype.interpreter.conversions import (
14
+ to_llama_vector_store_and_retriever,
15
+ to_opensearch_client,
16
+ )
17
+ from qtype.interpreter.types import FlowMessage
18
+ from qtype.semantic.model import DocumentIndex, IndexUpsert, VectorIndex
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class IndexUpsertExecutor(BatchedStepExecutor):
24
+ """Executor for IndexUpsert steps supporting both vector and document indexes."""
25
+
26
+ def __init__(
27
+ self, step: IndexUpsert, context: ExecutorContext, **dependencies
28
+ ):
29
+ super().__init__(step, context, **dependencies)
30
+ if not isinstance(step, IndexUpsert):
31
+ raise ValueError(
32
+ "IndexUpsertExecutor can only execute IndexUpsert steps."
33
+ )
34
+ self.step: IndexUpsert = step
35
+
36
+ # Determine index type and initialize appropriate client
37
+ if isinstance(self.step.index, VectorIndex):
38
+ # Vector index for RAGChunk embeddings
39
+ self._vector_store, _ = to_llama_vector_store_and_retriever(
40
+ self.step.index, self.context.secret_manager
41
+ )
42
+ self._opensearch_client = None
43
+ self.index_type = "vector"
44
+ elif isinstance(self.step.index, DocumentIndex):
45
+ # Document index for text-based search
46
+ self._opensearch_client = to_opensearch_client(
47
+ self.step.index, self.context.secret_manager
48
+ )
49
+ self._vector_store = None
50
+ self.index_type = "document"
51
+ self.index_name = self.step.index.name
52
+ else:
53
+ raise ValueError(
54
+ f"Unsupported index type: {type(self.step.index)}"
55
+ )
56
+
57
+ async def process_batch(
58
+ self, batch: list[FlowMessage]
59
+ ) -> AsyncIterator[FlowMessage]:
60
+ """Process a batch of FlowMessages for the IndexUpsert step.
61
+
62
+ Args:
63
+ batch: A list of FlowMessages to process.
64
+
65
+ Yields:
66
+ FlowMessages: Success messages after upserting to the index
67
+ """
68
+ logger.debug(
69
+ f"Executing IndexUpsert step: {self.step.id} with batch size: {len(batch)}"
70
+ )
71
+
72
+ try:
73
+ # Get the input variable (exactly one as validated by checker)
74
+ if not self.step.inputs:
75
+ raise ValueError("IndexUpsert step requires exactly one input")
76
+
77
+ input_var = self.step.inputs[0]
78
+
79
+ # Collect all RAGChunks or RAGDocuments from the batch
80
+ items_to_upsert = []
81
+ for message in batch:
82
+ input_data = message.variables.get(input_var.id)
83
+
84
+ if input_data is None:
85
+ logger.warning(
86
+ f"No data found for input: {input_var.id} in message"
87
+ )
88
+ continue
89
+
90
+ if not isinstance(input_data, (RAGChunk, RAGDocument)):
91
+ raise ValueError(
92
+ f"IndexUpsert only supports RAGChunk or RAGDocument "
93
+ f"inputs. Got: {type(input_data)}"
94
+ )
95
+
96
+ items_to_upsert.append(input_data)
97
+
98
+ # Upsert to appropriate index type
99
+ if items_to_upsert:
100
+ if self.index_type == "vector":
101
+ await self._upsert_to_vector_store(items_to_upsert)
102
+ else: # document index
103
+ await self._upsert_to_document_index(items_to_upsert)
104
+
105
+ logger.debug(
106
+ f"Successfully upserted {len(items_to_upsert)} items "
107
+ f"to {self.index_type} index in batch"
108
+ )
109
+
110
+ # Emit status update
111
+ index_type_display = (
112
+ "vector index"
113
+ if self.index_type == "vector"
114
+ else "document index"
115
+ )
116
+ await self.stream_emitter.status(
117
+ f"Upserted {len(items_to_upsert)} items to "
118
+ f"{index_type_display}"
119
+ )
120
+
121
+ # Yield all input messages back (IndexUpsert typically doesn't have outputs)
122
+ for message in batch:
123
+ yield message
124
+
125
+ except Exception as e:
126
+ logger.error(f"Error in IndexUpsert step {self.step.id}: {e}")
127
+ # Emit error event to stream so frontend can display it
128
+ await self.stream_emitter.error(str(e))
129
+
130
+ # Mark all messages with the error and yield them
131
+ for message in batch:
132
+ message.set_error(self.step.id, e)
133
+ yield message
134
+
135
+ async def _upsert_to_vector_store(
136
+ self, items: list[RAGChunk | RAGDocument]
137
+ ) -> None:
138
+ """Upsert items to vector store.
139
+
140
+ Args:
141
+ items: List of RAGChunk or RAGDocument objects
142
+ """
143
+ # Convert to LlamaIndex TextNode objects
144
+ nodes = []
145
+ for item in items:
146
+ if isinstance(item, RAGChunk):
147
+ node = TextNode(
148
+ id_=item.chunk_id,
149
+ text=str(item.content),
150
+ metadata=item.metadata,
151
+ embedding=item.vector,
152
+ )
153
+ else: # RAGDocument
154
+ # For documents, use file_id and convert content to string
155
+ node = TextNode(
156
+ id_=item.file_id,
157
+ text=str(item.content),
158
+ metadata=item.metadata,
159
+ embedding=None, # Documents don't have embeddings
160
+ )
161
+ nodes.append(node)
162
+
163
+ # Batch upsert all nodes to the vector store
164
+ await self._vector_store.async_add(nodes)
165
+
166
+ async def _upsert_to_document_index(
167
+ self, items: list[RAGChunk | RAGDocument]
168
+ ) -> None:
169
+ """Upsert items to document index using bulk API.
170
+
171
+ Args:
172
+ items: List of RAGChunk or RAGDocument objects
173
+ """
174
+ # Build bulk request body
175
+ bulk_body = []
176
+ for item in items:
177
+ if isinstance(item, RAGChunk):
178
+ # Add index action
179
+ bulk_body.append(
180
+ {
181
+ "index": {
182
+ "_index": self.index_name,
183
+ "_id": item.chunk_id,
184
+ }
185
+ }
186
+ )
187
+ # Add document content
188
+ doc = {
189
+ "text": str(item.content),
190
+ "metadata": item.metadata,
191
+ }
192
+ # Include embedding if available
193
+ if item.vector:
194
+ doc["embedding"] = item.vector
195
+ bulk_body.append(doc)
196
+ else: # RAGDocument
197
+ # Add index action
198
+ bulk_body.append(
199
+ {
200
+ "index": {
201
+ "_index": self.index_name,
202
+ "_id": item.file_id,
203
+ }
204
+ }
205
+ )
206
+ # Add document content
207
+ doc = {
208
+ "text": str(item.content),
209
+ "metadata": item.metadata,
210
+ "file_name": item.file_name,
211
+ }
212
+ if item.uri:
213
+ doc["uri"] = item.uri
214
+ bulk_body.append(doc)
215
+
216
+ # Execute bulk request
217
+ response = self._opensearch_client.bulk(body=bulk_body)
218
+
219
+ # Check for errors
220
+ if response.get("errors"):
221
+ error_items = [
222
+ item
223
+ for item in response["items"]
224
+ if "error" in item.get("index", {})
225
+ ]
226
+ logger.warning(
227
+ f"Bulk upsert had {len(error_items)} errors: {error_items}"
228
+ )
@@ -0,0 +1,92 @@
1
+ from typing import AsyncIterator
2
+
3
+ from openinference.semconv.trace import OpenInferenceSpanKindValues
4
+
5
+ from qtype.base.types import PrimitiveTypeEnum
6
+ from qtype.dsl.domain_types import Embedding
7
+ from qtype.interpreter.base.base_step_executor import StepExecutor
8
+ from qtype.interpreter.base.executor_context import ExecutorContext
9
+ from qtype.interpreter.conversions import to_embedding_model
10
+ from qtype.interpreter.types import FlowMessage
11
+ from qtype.semantic.model import InvokeEmbedding
12
+
13
+
14
+ class InvokeEmbeddingExecutor(StepExecutor):
15
+ """Executor for InvokeEmbedding steps."""
16
+
17
+ # Embedding operations should be marked as EMBEDDING type
18
+ span_kind = OpenInferenceSpanKindValues.EMBEDDING
19
+
20
+ def __init__(
21
+ self, step: InvokeEmbedding, context: ExecutorContext, **dependencies
22
+ ):
23
+ super().__init__(step, context, **dependencies)
24
+ if not isinstance(step, InvokeEmbedding):
25
+ raise ValueError(
26
+ (
27
+ "InvokeEmbeddingExecutor can only execute "
28
+ "InvokeEmbedding steps."
29
+ )
30
+ )
31
+ self.step: InvokeEmbedding = step
32
+ # Initialize the embedding model once for the executor
33
+ self.embedding_model = to_embedding_model(self.step.model)
34
+
35
+ async def process_message(
36
+ self,
37
+ message: FlowMessage,
38
+ ) -> AsyncIterator[FlowMessage]:
39
+ """Process a single FlowMessage for the InvokeEmbedding step.
40
+
41
+ Args:
42
+ message: The FlowMessage to process.
43
+ Yields:
44
+ FlowMessage with embedding.
45
+ """
46
+ input_id = self.step.inputs[0].id
47
+ input_type = self.step.inputs[0].type
48
+ output_id = self.step.outputs[0].id
49
+
50
+ try:
51
+ # Get the input value
52
+ input_value = message.variables.get(input_id)
53
+
54
+ if input_value is None:
55
+ raise ValueError(f"Input variable '{input_id}' is missing")
56
+
57
+ # Generate embedding based on input type
58
+ if input_type == PrimitiveTypeEnum.text:
59
+ if not isinstance(input_value, str):
60
+ input_value = str(input_value)
61
+ vector = await self.embedding_model.aget_text_embedding(
62
+ text=input_value
63
+ )
64
+ content = input_value
65
+ elif input_type == PrimitiveTypeEnum.image:
66
+ # For image embeddings
67
+ vector = await self.embedding_model.aget_image_embedding(
68
+ image_path=input_value
69
+ )
70
+ content = input_value
71
+ else:
72
+ raise ValueError(
73
+ (
74
+ f"Unsupported input type for embedding: "
75
+ f"{input_type}. Must be 'text' or 'image'."
76
+ )
77
+ )
78
+
79
+ # Create the Embedding object
80
+ embedding = Embedding(
81
+ vector=vector,
82
+ content=content,
83
+ )
84
+
85
+ # Yield the result
86
+ yield message.copy_with_variables({output_id: embedding})
87
+
88
+ except Exception as e:
89
+ # Emit error event to stream so frontend can display it
90
+ await self.stream_emitter.error(str(e))
91
+ message.set_error(self.step.id, e)
92
+ yield message
@@ -0,0 +1,51 @@
1
+ from typing import AsyncIterator
2
+
3
+ from qtype.interpreter.base.base_step_executor import StepExecutor
4
+ from qtype.interpreter.base.executor_context import ExecutorContext
5
+ from qtype.interpreter.types import FlowMessage
6
+ from qtype.semantic.model import InvokeFlow
7
+
8
+
9
+ class InvokeFlowExecutor(StepExecutor):
10
+ """Executor for InvokeFlow steps."""
11
+
12
+ def __init__(
13
+ self, step: InvokeFlow, context: ExecutorContext, **dependencies
14
+ ):
15
+ super().__init__(step, context, **dependencies)
16
+ if not isinstance(step, InvokeFlow):
17
+ raise ValueError(
18
+ ("InvokeFlowExecutor can only execute InvokeFlow steps.")
19
+ )
20
+ self.step: InvokeFlow = step
21
+
22
+ async def process_message(
23
+ self, message: FlowMessage
24
+ ) -> AsyncIterator[FlowMessage]:
25
+ """Process a single FlowMessage for the InvokeFlow step.
26
+
27
+ Args:
28
+ message: The FlowMessage to process.
29
+ Yields:
30
+ FlowMessage with results from the invoked flow.
31
+ """
32
+ from qtype.interpreter.flow import run_flow
33
+
34
+ initial = message.copy_with_variables(
35
+ {
36
+ id: message.variables.get(var.id)
37
+ for var, id in self.step.input_bindings.items()
38
+ }
39
+ )
40
+ # Pass through context (already available as self.context)
41
+ result = await run_flow(
42
+ self.step.flow, [initial], context=self.context
43
+ )
44
+
45
+ for msg in result:
46
+ yield msg.copy_with_variables(
47
+ {
48
+ var.id: msg.variables.get(id)
49
+ for var, id in self.step.output_bindings.items()
50
+ }
51
+ )