qtype 0.1.11__py3-none-any.whl → 0.1.13__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 (263) hide show
  1. qtype/` +0 -0
  2. qtype/application/__init__.py +0 -2
  3. qtype/application/converters/tools_from_api.py +67 -57
  4. qtype/application/converters/tools_from_module.py +66 -32
  5. qtype/base/types.py +6 -1
  6. qtype/commands/convert.py +3 -6
  7. qtype/commands/generate.py +97 -10
  8. qtype/commands/mcp.py +68 -0
  9. qtype/commands/run.py +116 -44
  10. qtype/commands/validate.py +4 -4
  11. qtype/docs/.pages +8 -0
  12. qtype/docs/Concepts/mental-model-and-philosophy.md +363 -0
  13. qtype/docs/Contributing/.pages +4 -0
  14. qtype/docs/Contributing/index.md +283 -0
  15. qtype/docs/Contributing/roadmap.md +81 -0
  16. qtype/docs/Decisions/ADR-001-Chat-vs-Completion-Endpoint-Features.md +56 -0
  17. qtype/docs/Gallery/dataflow_pipelines.md +81 -0
  18. qtype/docs/Gallery/dataflow_pipelines.mermaid +45 -0
  19. qtype/docs/Gallery/research_assistant.md +97 -0
  20. qtype/docs/Gallery/research_assistant.mermaid +42 -0
  21. qtype/docs/Gallery/simple_chatbot.md +38 -0
  22. qtype/docs/Gallery/simple_chatbot.mermaid +35 -0
  23. qtype/docs/How To/Authentication/configure_aws_authentication.md +60 -0
  24. qtype/docs/How To/Authentication/use_api_key_authentication.md +40 -0
  25. qtype/docs/How To/Command Line Usage/load_multiple_inputs_from_files.md +77 -0
  26. qtype/docs/How To/Command Line Usage/pass_inputs_on_the_cli.md +52 -0
  27. qtype/docs/How To/Command Line Usage/serve_with_auto_reload.md +27 -0
  28. qtype/docs/How To/Data Processing/adjust_concurrency.md +40 -0
  29. qtype/docs/How To/Data Processing/cache_step_results.md +71 -0
  30. qtype/docs/How To/Data Processing/decode_json_xml.md +24 -0
  31. qtype/docs/How To/Data Processing/explode_collections.md +40 -0
  32. qtype/docs/How To/Data Processing/gather_results.md +68 -0
  33. qtype/docs/How To/Data Processing/invoke_other_flows.md +71 -0
  34. qtype/docs/How To/Data Processing/load_data_from_athena.md +49 -0
  35. qtype/docs/How To/Data Processing/read_data_from_files.md +61 -0
  36. qtype/docs/How To/Data Processing/read_sql_databases.md +46 -0
  37. qtype/docs/How To/Data Processing/write_data_to_file.md +39 -0
  38. qtype/docs/How To/Invoke Models/call_large_language_models.md +51 -0
  39. qtype/docs/How To/Invoke Models/create_embeddings.md +49 -0
  40. qtype/docs/How To/Invoke Models/reuse_prompts_with_templates.md +38 -0
  41. qtype/docs/How To/Language Features/include_qtype_yaml.md +45 -0
  42. qtype/docs/How To/Language Features/include_raw_text_from_other_files.md +48 -0
  43. qtype/docs/How To/Language Features/reference_entities_by_id.md +51 -0
  44. qtype/docs/How To/Language Features/use_agent_skills.md +29 -0
  45. qtype/docs/How To/Language Features/use_environment_variables.md +48 -0
  46. qtype/docs/How To/Language Features/use_optional_variables.md +42 -0
  47. qtype/docs/How To/Language Features/use_qtype_mcp.md +59 -0
  48. qtype/docs/How To/Observability & Debugging/trace_calls_with_open_telemetry.md +49 -0
  49. qtype/docs/How To/Observability & Debugging/validate_qtype_yaml.md +36 -0
  50. qtype/docs/How To/Observability & Debugging/visualize_application_architecture.md +61 -0
  51. qtype/docs/How To/Observability & Debugging/visualize_example.mermaid +35 -0
  52. qtype/docs/How To/Qtype Server/flow_as_ui.png +0 -0
  53. qtype/docs/How To/Qtype Server/serve_flows_as_apis.md +40 -0
  54. qtype/docs/How To/Qtype Server/serve_flows_as_ui.md +41 -0
  55. qtype/docs/How To/Qtype Server/use_conversational_interfaces.md +56 -0
  56. qtype/docs/How To/Qtype Server/use_variables_with_ui_hints.md +48 -0
  57. qtype/docs/How To/Tools & Integration/bind_tool_inputs_and_outputs.md +47 -0
  58. qtype/docs/How To/Tools & Integration/create_tools_from_openapi_specifications.md +85 -0
  59. qtype/docs/How To/Tools & Integration/create_tools_from_python_modules.md +87 -0
  60. qtype/docs/Reference/cli.md +336 -0
  61. qtype/docs/Reference/plugins.md +99 -0
  62. qtype/docs/Reference/semantic-validation-rules.md +184 -0
  63. qtype/docs/Tutorials/.pages +1 -0
  64. qtype/docs/Tutorials/01-first-qtype-application.md +249 -0
  65. qtype/docs/Tutorials/02-conversational-chatbot.md +327 -0
  66. qtype/docs/Tutorials/03-structured-data.md +480 -0
  67. qtype/docs/Tutorials/04-tools-and-function-calling.md +476 -0
  68. qtype/docs/Tutorials/example_chat.png +0 -0
  69. qtype/docs/Tutorials/index.md +92 -0
  70. qtype/docs/components/APIKeyAuthProvider.md +7 -0
  71. qtype/docs/components/APITool.md +10 -0
  72. qtype/docs/components/AWSAuthProvider.md +13 -0
  73. qtype/docs/components/AWSSecretManager.md +5 -0
  74. qtype/docs/components/Agent.md +6 -0
  75. qtype/docs/components/Aggregate.md +7 -0
  76. qtype/docs/components/AggregateStats.md +7 -0
  77. qtype/docs/components/Application.md +22 -0
  78. qtype/docs/components/AuthorizationProvider.md +6 -0
  79. qtype/docs/components/AuthorizationProviderList.md +5 -0
  80. qtype/docs/components/BearerTokenAuthProvider.md +6 -0
  81. qtype/docs/components/BedrockReranker.md +8 -0
  82. qtype/docs/components/ChatContent.md +7 -0
  83. qtype/docs/components/ChatMessage.md +6 -0
  84. qtype/docs/components/Collect.md +6 -0
  85. qtype/docs/components/ConstantPath.md +5 -0
  86. qtype/docs/components/Construct.md +6 -0
  87. qtype/docs/components/CustomType.md +7 -0
  88. qtype/docs/components/Decoder.md +8 -0
  89. qtype/docs/components/DecoderFormat.md +8 -0
  90. qtype/docs/components/DocToTextConverter.md +7 -0
  91. qtype/docs/components/Document.md +7 -0
  92. qtype/docs/components/DocumentEmbedder.md +6 -0
  93. qtype/docs/components/DocumentIndex.md +7 -0
  94. qtype/docs/components/DocumentSearch.md +7 -0
  95. qtype/docs/components/DocumentSource.md +12 -0
  96. qtype/docs/components/DocumentSplitter.md +9 -0
  97. qtype/docs/components/Echo.md +8 -0
  98. qtype/docs/components/Embedding.md +7 -0
  99. qtype/docs/components/EmbeddingModel.md +6 -0
  100. qtype/docs/components/Explode.md +5 -0
  101. qtype/docs/components/FieldExtractor.md +21 -0
  102. qtype/docs/components/FileSource.md +6 -0
  103. qtype/docs/components/FileWriter.md +7 -0
  104. qtype/docs/components/Flow.md +14 -0
  105. qtype/docs/components/FlowInterface.md +7 -0
  106. qtype/docs/components/Index.md +8 -0
  107. qtype/docs/components/IndexUpsert.md +6 -0
  108. qtype/docs/components/InvokeEmbedding.md +7 -0
  109. qtype/docs/components/InvokeFlow.md +8 -0
  110. qtype/docs/components/InvokeTool.md +8 -0
  111. qtype/docs/components/LLMInference.md +9 -0
  112. qtype/docs/components/ListType.md +5 -0
  113. qtype/docs/components/Memory.md +8 -0
  114. qtype/docs/components/MessageRole.md +14 -0
  115. qtype/docs/components/Model.md +10 -0
  116. qtype/docs/components/ModelList.md +5 -0
  117. qtype/docs/components/OAuth2AuthProvider.md +9 -0
  118. qtype/docs/components/PrimitiveTypeEnum.md +20 -0
  119. qtype/docs/components/PromptTemplate.md +7 -0
  120. qtype/docs/components/PythonFunctionTool.md +7 -0
  121. qtype/docs/components/RAGChunk.md +7 -0
  122. qtype/docs/components/RAGDocument.md +10 -0
  123. qtype/docs/components/RAGSearchResult.md +8 -0
  124. qtype/docs/components/Reranker.md +5 -0
  125. qtype/docs/components/SQLSource.md +8 -0
  126. qtype/docs/components/Search.md +7 -0
  127. qtype/docs/components/SearchResult.md +7 -0
  128. qtype/docs/components/SecretManager.md +7 -0
  129. qtype/docs/components/SecretReference.md +7 -0
  130. qtype/docs/components/Source.md +5 -0
  131. qtype/docs/components/Step.md +8 -0
  132. qtype/docs/components/TelemetrySink.md +9 -0
  133. qtype/docs/components/Tool.md +9 -0
  134. qtype/docs/components/ToolList.md +5 -0
  135. qtype/docs/components/TypeList.md +5 -0
  136. qtype/docs/components/Variable.md +8 -0
  137. qtype/docs/components/VariableList.md +5 -0
  138. qtype/docs/components/VectorIndex.md +7 -0
  139. qtype/docs/components/VectorSearch.md +6 -0
  140. qtype/docs/components/VertexAuthProvider.md +9 -0
  141. qtype/docs/components/Writer.md +5 -0
  142. qtype/docs/example_ui.png +0 -0
  143. qtype/docs/index.md +81 -0
  144. qtype/docs/legacy_how_tos/.pages +6 -0
  145. qtype/docs/legacy_how_tos/Configuration/modular-yaml.md +366 -0
  146. qtype/docs/legacy_how_tos/Configuration/phoenix_projects.png +0 -0
  147. qtype/docs/legacy_how_tos/Configuration/phoenix_traces.png +0 -0
  148. qtype/docs/legacy_how_tos/Configuration/reference-by-id.md +251 -0
  149. qtype/docs/legacy_how_tos/Configuration/telemetry-setup.md +259 -0
  150. qtype/docs/legacy_how_tos/Data Types/custom-types.md +52 -0
  151. qtype/docs/legacy_how_tos/Data Types/domain-types.md +113 -0
  152. qtype/docs/legacy_how_tos/Debugging/visualize-apps.md +147 -0
  153. qtype/docs/legacy_how_tos/Tools/api-tools.md +29 -0
  154. qtype/docs/legacy_how_tos/Tools/python-tools.md +299 -0
  155. qtype/docs/skills/architect/SKILL.md +188 -0
  156. qtype/docs/skills/architect/references/cheatsheet.md +198 -0
  157. qtype/docs/skills/architect/references/patterns.md +29 -0
  158. qtype/docs/stylesheets/extra.css +27 -0
  159. qtype/dsl/custom_types.py +2 -1
  160. qtype/dsl/linker.py +23 -7
  161. qtype/dsl/loader.py +3 -3
  162. qtype/dsl/model.py +181 -67
  163. qtype/examples/authentication/aws_authentication.qtype.yaml +63 -0
  164. qtype/examples/conversational_ai/hello_world_chat.qtype.yaml +43 -0
  165. qtype/examples/conversational_ai/simple_chatbot.qtype.yaml +40 -0
  166. qtype/examples/data_processing/athena_query.qtype.yaml +56 -0
  167. qtype/examples/data_processing/batch_inputs.csv +5 -0
  168. qtype/examples/data_processing/batch_processing.qtype.yaml +54 -0
  169. qtype/examples/data_processing/cache_step_results.qtype.yaml +78 -0
  170. qtype/examples/data_processing/collect_results.qtype.yaml +55 -0
  171. qtype/examples/data_processing/create_sample_db.py +129 -0
  172. qtype/examples/data_processing/dataflow_pipelines.qtype.yaml +108 -0
  173. qtype/examples/data_processing/decode_json.qtype.yaml +23 -0
  174. qtype/examples/data_processing/explode_items.qtype.yaml +25 -0
  175. qtype/examples/data_processing/invoke_other_flows.qtype.yaml +98 -0
  176. qtype/examples/data_processing/read_file.qtype.yaml +60 -0
  177. qtype/examples/data_processing/reviews.db +0 -0
  178. qtype/examples/data_processing/sample_article.txt +1 -0
  179. qtype/examples/data_processing/sample_documents.jsonl +5 -0
  180. qtype/examples/invoke_models/create_embeddings.qtype.yaml +28 -0
  181. qtype/examples/invoke_models/simple_llm_call.qtype.yaml +32 -0
  182. qtype/examples/language_features/include_raw.qtype.yaml +27 -0
  183. qtype/examples/language_features/optional_variables.qtype.yaml +32 -0
  184. qtype/examples/language_features/story_prompt.txt +6 -0
  185. qtype/examples/language_features/ui_hints.qtype.yaml +52 -0
  186. qtype/examples/legacy/bedrock/data_analysis_with_telemetry.qtype.yaml +169 -0
  187. qtype/examples/legacy/bedrock/hello_world.qtype.yaml +39 -0
  188. qtype/examples/legacy/bedrock/hello_world_chat.qtype.yaml +37 -0
  189. qtype/examples/legacy/bedrock/hello_world_chat_with_telemetry.qtype.yaml +40 -0
  190. qtype/examples/legacy/bedrock/hello_world_chat_with_thinking.qtype.yaml +40 -0
  191. qtype/examples/legacy/bedrock/hello_world_completion.qtype.yaml +41 -0
  192. qtype/examples/legacy/bedrock/hello_world_completion_with_auth.qtype.yaml +44 -0
  193. qtype/examples/legacy/bedrock/simple_agent_chat.qtype.yaml +46 -0
  194. qtype/examples/legacy/chat_with_langfuse.qtype.yaml +50 -0
  195. qtype/examples/legacy/data/customers.csv +6 -0
  196. qtype/examples/legacy/data_processor.qtype.yaml +48 -0
  197. qtype/examples/legacy/echo/debug_example.qtype.yaml +59 -0
  198. qtype/examples/legacy/echo/prompt.qtype.yaml +22 -0
  199. qtype/examples/legacy/echo/readme.md +29 -0
  200. qtype/examples/legacy/echo/test.qtype.yaml +26 -0
  201. qtype/examples/legacy/echo/video.qtype.yaml +20 -0
  202. qtype/examples/legacy/field_extractor_example.qtype.yaml +137 -0
  203. qtype/examples/legacy/multi_flow_example.qtype.yaml +125 -0
  204. qtype/examples/legacy/openai/hello_world_chat.qtype.yaml +43 -0
  205. qtype/examples/legacy/openai/hello_world_chat_with_telemetry.qtype.yaml +46 -0
  206. qtype/examples/legacy/qtype_plugin_example.py +51 -0
  207. qtype/examples/legacy/rag.qtype.yaml +207 -0
  208. qtype/examples/legacy/sample_data.txt +43 -0
  209. qtype/examples/legacy/time_utilities.qtype.yaml +64 -0
  210. qtype/examples/legacy/vertex/README.md +11 -0
  211. qtype/examples/legacy/vertex/hello_world_chat.qtype.yaml +36 -0
  212. qtype/examples/legacy/vertex/hello_world_completion.qtype.yaml +40 -0
  213. qtype/examples/legacy/vertex/hello_world_completion_with_auth.qtype.yaml +45 -0
  214. qtype/examples/observability_debugging/trace_with_opentelemetry.qtype.yaml +40 -0
  215. qtype/examples/research_assistant/research_assistant.qtype.yaml +94 -0
  216. qtype/examples/research_assistant/tavily.oas.yaml +722 -0
  217. qtype/examples/research_assistant/tavily.qtype.yaml +216 -0
  218. qtype/examples/tutorials/01_hello_world.qtype.yaml +48 -0
  219. qtype/examples/tutorials/02_conversational_chat.qtype.yaml +37 -0
  220. qtype/examples/tutorials/03_structured_data.qtype.yaml +130 -0
  221. qtype/examples/tutorials/04_tools_and_function_calling.qtype.yaml +89 -0
  222. qtype/interpreter/api.py +4 -1
  223. qtype/interpreter/base/base_step_executor.py +3 -1
  224. qtype/interpreter/base/stream_emitter.py +19 -13
  225. qtype/interpreter/conversions.py +7 -3
  226. qtype/interpreter/converters.py +142 -26
  227. qtype/interpreter/executors/agent_executor.py +2 -3
  228. qtype/interpreter/executors/aggregate_executor.py +3 -4
  229. qtype/interpreter/executors/construct_executor.py +15 -15
  230. qtype/interpreter/executors/doc_to_text_executor.py +1 -3
  231. qtype/interpreter/executors/field_extractor_executor.py +13 -12
  232. qtype/interpreter/executors/file_source_executor.py +21 -34
  233. qtype/interpreter/executors/file_writer_executor.py +4 -4
  234. qtype/interpreter/executors/index_upsert_executor.py +1 -1
  235. qtype/interpreter/executors/invoke_embedding_executor.py +1 -4
  236. qtype/interpreter/executors/invoke_flow_executor.py +2 -2
  237. qtype/interpreter/executors/invoke_tool_executor.py +19 -18
  238. qtype/interpreter/executors/llm_inference_executor.py +16 -18
  239. qtype/interpreter/executors/prompt_template_executor.py +1 -3
  240. qtype/interpreter/executors/sql_source_executor.py +1 -1
  241. qtype/interpreter/resource_cache.py +3 -1
  242. qtype/interpreter/rich_progress.py +6 -3
  243. qtype/interpreter/stream/chat/converter.py +25 -17
  244. qtype/interpreter/stream/chat/ui_request_to_domain_type.py +2 -2
  245. qtype/interpreter/tools/function_tool_helper.py +11 -10
  246. qtype/interpreter/types.py +89 -4
  247. qtype/interpreter/typing.py +35 -38
  248. qtype/mcp/__init__.py +0 -0
  249. qtype/mcp/server.py +722 -0
  250. qtype/schema/qtype.schema.json +4016 -0
  251. qtype/semantic/checker.py +20 -1
  252. qtype/semantic/generate.py +6 -9
  253. qtype/semantic/model.py +26 -33
  254. qtype/semantic/resolver.py +7 -0
  255. qtype/semantic/visualize.py +45 -53
  256. {qtype-0.1.11.dist-info → qtype-0.1.13.dist-info}/METADATA +65 -44
  257. qtype-0.1.13.dist-info/RECORD +352 -0
  258. {qtype-0.1.11.dist-info → qtype-0.1.13.dist-info}/WHEEL +1 -2
  259. qtype/application/facade.py +0 -177
  260. qtype-0.1.11.dist-info/RECORD +0 -142
  261. qtype-0.1.11.dist-info/top_level.txt +0 -1
  262. {qtype-0.1.11.dist-info → qtype-0.1.13.dist-info}/entry_points.txt +0 -0
  263. {qtype-0.1.11.dist-info → qtype-0.1.13.dist-info}/licenses/LICENSE +0 -0
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import logging
4
4
  import threading
5
5
  from collections import deque
6
- from typing import Deque, Dict
6
+ from typing import Any, Deque, Dict
7
7
 
8
8
  from rich.console import Console
9
9
  from rich.live import Live
@@ -11,6 +11,7 @@ from rich.panel import Panel
11
11
  from rich.progress import (
12
12
  Progress,
13
13
  ProgressColumn,
14
+ TaskID,
14
15
  TaskProgressColumn,
15
16
  TextColumn,
16
17
  TimeElapsedColumn,
@@ -140,7 +141,7 @@ class RichProgressCallback(ProgressCallback):
140
141
  )
141
142
 
142
143
  # Map step_id -> Rich task id
143
- self.tasks: Dict[str, int] = {}
144
+ self.tasks: dict[str, TaskID] = {}
144
145
  self._started = False
145
146
 
146
147
  # Pre-create tasks in the desired order if provided
@@ -202,7 +203,9 @@ class RichProgressCallback(ProgressCallback):
202
203
  if total_items is not None:
203
204
  update_kwargs["total"] = total_items
204
205
 
205
- self.progress.update(task_id, **update_kwargs)
206
+ from typing import cast
207
+
208
+ self.progress.update(task_id, **cast(Any, update_kwargs))
206
209
 
207
210
  def compute_color(self, items_processed: int, items_in_error: int) -> str:
208
211
  # Avoid divide-by-zero
@@ -258,10 +258,12 @@ class StreamEventConverter:
258
258
  3. ToolInputAvailableChunk - Complete input ready, tool can execute
259
259
  """
260
260
  # 1. Start tool input streaming
261
- yield ToolInputStartChunk(
262
- toolCallId=event.tool_call_id,
263
- toolName=event.tool_name,
264
- providerExecuted=True, # Tools are executed on the server
261
+ yield ToolInputStartChunk.model_validate(
262
+ {
263
+ "toolCallId": event.tool_call_id,
264
+ "toolName": event.tool_name,
265
+ "providerExecuted": True,
266
+ }
265
267
  )
266
268
 
267
269
  # 2. Stream the input as JSON text delta
@@ -274,11 +276,13 @@ class StreamEventConverter:
274
276
  )
275
277
 
276
278
  # 3. Signal input is complete and ready for execution
277
- yield ToolInputAvailableChunk(
278
- toolCallId=event.tool_call_id,
279
- toolName=event.tool_name,
280
- input=event.tool_input,
281
- providerExecuted=True, # Tools are executed on the server
279
+ yield ToolInputAvailableChunk.model_validate(
280
+ {
281
+ "toolCallId": event.tool_call_id,
282
+ "toolName": event.tool_name,
283
+ "input": event.tool_input,
284
+ "providerExecuted": True,
285
+ }
282
286
  )
283
287
 
284
288
  def _convert_tool_execution_end(
@@ -289,10 +293,12 @@ class StreamEventConverter:
289
293
 
290
294
  Signals successful tool completion with output.
291
295
  """
292
- yield ToolOutputAvailableChunk(
293
- toolCallId=event.tool_call_id,
294
- output=event.tool_output,
295
- providerExecuted=True, # Tools are executed on the server
296
+ yield ToolOutputAvailableChunk.model_validate(
297
+ {
298
+ "toolCallId": event.tool_call_id,
299
+ "output": event.tool_output,
300
+ "providerExecuted": True,
301
+ }
296
302
  )
297
303
 
298
304
  def _convert_tool_execution_error(
@@ -303,10 +309,12 @@ class StreamEventConverter:
303
309
 
304
310
  Signals tool execution failure with error message.
305
311
  """
306
- yield ToolOutputErrorChunk(
307
- toolCallId=event.tool_call_id,
308
- errorText=event.error_message,
309
- providerExecuted=True, # Tools are executed on the server
312
+ yield ToolOutputErrorChunk.model_validate(
313
+ {
314
+ "toolCallId": event.tool_call_id,
315
+ "errorText": event.error_message,
316
+ "providerExecuted": True,
317
+ }
310
318
  )
311
319
 
312
320
  def _convert_error(self, event: ErrorEvent) -> Iterator[UIMessageChunk]:
@@ -39,11 +39,11 @@ def _ui_message_to_domain_type(message: UIMessage) -> ChatMessage:
39
39
  for part in message.parts:
40
40
  if part.type == "text":
41
41
  blocks.append(
42
- ChatContent(type=PrimitiveTypeEnum.text, content=part.text)
42
+ ChatContent(type=PrimitiveTypeEnum.text, content=part.text) # type: ignore[attr-defined]
43
43
  )
44
44
  elif part.type == "reasoning":
45
45
  blocks.append(
46
- ChatContent(type=PrimitiveTypeEnum.text, content=part.text)
46
+ ChatContent(type=PrimitiveTypeEnum.text, content=part.text) # type: ignore[attr-defined]
47
47
  )
48
48
  elif part.type == "file":
49
49
  blocks.append(
@@ -14,7 +14,7 @@ from pydantic import create_model
14
14
  from qtype.base.types import PrimitiveTypeEnum
15
15
  from qtype.dsl.model import ListType
16
16
  from qtype.dsl.types import PRIMITIVE_TO_PYTHON_TYPE
17
- from qtype.semantic.model import APITool, PythonFunctionTool, ToolParameter
17
+ from qtype.semantic.model import APITool, PythonFunctionTool, Variable
18
18
 
19
19
  logger = logging.getLogger(__name__)
20
20
 
@@ -29,9 +29,9 @@ class FunctionToolHelper:
29
29
 
30
30
  @staticmethod
31
31
  def _qtype_type_to_python_type(
32
- param: ToolParameter,
32
+ param: Variable,
33
33
  ) -> type:
34
- """Convert QType ToolParameter type to Python type for Pydantic.
34
+ """Convert QType Variable type to Python type for Pydantic.
35
35
 
36
36
  The param.type has already been resolved during semantic model
37
37
  creation, so we just need to convert it to the appropriate Python
@@ -42,7 +42,7 @@ class FunctionToolHelper:
42
42
  - Unknown → str
43
43
 
44
44
  Args:
45
- param: The QType ToolParameter to convert.
45
+ param: The QType Variable to convert.
46
46
 
47
47
  Returns:
48
48
  Python type suitable for Pydantic field annotation.
@@ -55,7 +55,8 @@ class FunctionToolHelper:
55
55
  if isinstance(param.type, ListType):
56
56
  # Create a mock parameter with the element type to recursively
57
57
  # resolve it
58
- element_param = ToolParameter(
58
+ element_param = Variable(
59
+ id="temp",
59
60
  type=param.type.element_type,
60
61
  optional=False,
61
62
  )
@@ -74,13 +75,13 @@ class FunctionToolHelper:
74
75
  @staticmethod
75
76
  def _create_fn_schema(
76
77
  tool_name: str,
77
- inputs: dict[str, ToolParameter],
78
+ inputs: list[Variable],
78
79
  ) -> type[BaseModel] | None:
79
80
  """Create a Pydantic model from QType tool input parameters.
80
81
 
81
82
  Args:
82
83
  tool_name: Name of the tool (used for model name).
83
- inputs: Dictionary of input parameter names to ToolParameter.
84
+ inputs: List of input Variables.
84
85
 
85
86
  Returns:
86
87
  Pydantic BaseModel class representing the tool's input schema.
@@ -91,17 +92,17 @@ class FunctionToolHelper:
91
92
  # Each field is a tuple of (type_annotation, field_info)
92
93
  field_definitions: dict[str, Any] = {}
93
94
 
94
- for param_name, param in inputs.items():
95
+ for param in inputs:
95
96
  python_type = FunctionToolHelper._qtype_type_to_python_type(param)
96
97
 
97
98
  # Create field with optional annotation
98
99
  if param.optional:
99
- field_definitions[param_name] = (
100
+ field_definitions[param.id] = (
100
101
  python_type | None, # type: ignore[valid-type]
101
102
  PydanticField(default=None),
102
103
  )
103
104
  else:
104
- field_definitions[param_name] = (
105
+ field_definitions[param.id] = (
105
106
  python_type,
106
107
  PydanticField(...),
107
108
  )
@@ -1,11 +1,38 @@
1
1
  from typing import Any, Dict, Literal, Optional, Protocol, Union
2
2
 
3
- from pydantic import BaseModel, ConfigDict, Field
3
+ from pydantic import BaseModel, ConfigDict, Field, model_serializer
4
4
 
5
5
  from qtype.base.types import StrictBaseModel
6
6
  from qtype.dsl.domain_types import ChatMessage
7
7
  from qtype.semantic.model import Step
8
8
 
9
+
10
+ class _UnsetType:
11
+ """Sentinel representing an unset variable.
12
+
13
+ Distinguishes between:
14
+ - Variable never mentioned (not in dict)
15
+ - Variable explicitly unset (UNSET value in dict)
16
+ - Variable set to None (None value in dict)
17
+ """
18
+
19
+ _instance = None
20
+
21
+ def __new__(cls):
22
+ if cls._instance is None:
23
+ cls._instance = super().__new__(cls)
24
+ return cls._instance
25
+
26
+ def __repr__(self) -> str:
27
+ return "UNSET"
28
+
29
+ def __bool__(self) -> bool:
30
+ return False
31
+
32
+
33
+ UNSET = _UnsetType()
34
+
35
+
9
36
  # Stream Event Types (Discriminated Union)
10
37
  # These events are emitted by executors during flow execution
11
38
  # and can be converted to Vercel UI chunks for frontend display
@@ -293,8 +320,9 @@ class FlowMessage(BaseModel):
293
320
  """
294
321
 
295
322
  model_config = ConfigDict(
296
- frozen=True
297
- ) # Enforces immutability at the model level
323
+ frozen=True,
324
+ arbitrary_types_allowed=True, # Allow UNSET sentinel
325
+ )
298
326
 
299
327
  session: Session
300
328
  variables: Dict[str, Any] = Field(
@@ -307,6 +335,49 @@ class FlowMessage(BaseModel):
307
335
  """Checks if this state has encountered an error."""
308
336
  return self.error is not None
309
337
 
338
+ def is_set(self, var_id: str) -> bool:
339
+ """Check if a variable is set (not UNSET, may be None)."""
340
+ value = self.variables.get(var_id, UNSET)
341
+ return value is not UNSET
342
+
343
+ def get_variable(self, var_id: str, *, default: Any = UNSET) -> Any:
344
+ """Get variable value, raising if unset and no default provided.
345
+
346
+ Args:
347
+ var_id: Variable identifier
348
+ default: Value to return if variable is unset. If not provided,
349
+ raises ValueError on unset variables.
350
+
351
+ Returns:
352
+ Variable value (may be None if explicitly set to None)
353
+
354
+ Raises:
355
+ ValueError: If variable is unset and no default provided
356
+
357
+ Examples:
358
+ # Required variable - throws if unset
359
+ value = message.get_variable("user_input")
360
+
361
+ # Optional variable - returns None if unset
362
+ value = message.get_variable("optional_field", default=None)
363
+
364
+ # Optional with custom default
365
+ value = message.get_variable("count", default=0)
366
+ """
367
+ value = self.variables.get(var_id, UNSET)
368
+
369
+ if value is UNSET:
370
+ if default is UNSET:
371
+ raise ValueError(
372
+ (
373
+ f"Required variable '{var_id}' is not set. "
374
+ f"Available variables: {list(self.variables.keys())}"
375
+ )
376
+ )
377
+ return default
378
+
379
+ return value
380
+
310
381
  def copy_with_error(self, step_id: str, exc: Exception) -> "FlowMessage":
311
382
  """Returns a copy of this state marked as failed."""
312
383
  return self.model_copy(
@@ -319,15 +390,29 @@ class FlowMessage(BaseModel):
319
390
  }
320
391
  )
321
392
 
322
- # It's useful to have copy-on-write style helpers
323
393
  def copy_with_variables(
324
394
  self, new_variables: dict[str, Any]
325
395
  ) -> "FlowMessage":
396
+ """Create a new FlowMessage with updated variables.
397
+
398
+ Note: Can set variables to UNSET to explicitly mark them as unset.
399
+ """
326
400
  new_vars = self.variables.copy()
327
401
  new_vars.update(new_variables)
328
402
  new_state = self.model_copy(update={"variables": new_vars})
329
403
  return new_state
330
404
 
405
+ @model_serializer
406
+ def serialize_model(self):
407
+ """Custom serialization that excludes UNSET variables."""
408
+ return {
409
+ "session": self.session,
410
+ "variables": {
411
+ k: v for k, v in self.variables.items() if v is not UNSET
412
+ },
413
+ "error": self.error,
414
+ }
415
+
331
416
 
332
417
  class InterpreterError(Exception):
333
418
  """Base exception class for ProtoGen interpreter errors."""
@@ -1,9 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import uuid
4
- from typing import Any, Type, get_origin
4
+ from typing import Any, Type
5
5
 
6
- from pydantic import BaseModel, Field, TypeAdapter, create_model
6
+ from pydantic import BaseModel, Field, create_model
7
7
 
8
8
  from qtype.dsl.model import ListType, PrimitiveTypeEnum
9
9
  from qtype.dsl.model import Variable as DSLVariable
@@ -25,11 +25,9 @@ def _get_variable_type(var: DSLVariable) -> tuple[Type, dict[str, Any]]:
25
25
  python_type = PRIMITIVE_TO_PYTHON_TYPE.get(var.type, str)
26
26
  field_metadata["qtype_type"] = var.type.value
27
27
  elif isinstance(var.type, ListType):
28
- python_type = list[
29
- _get_variable_type(DSLVariable(id="", type=var.type.element_type))[
30
- 0
31
- ]
32
- ]
28
+ element_var = DSLVariable(id="", type=var.type.element_type)
29
+ element_type, _ = _get_variable_type(element_var)
30
+ python_type = list[element_type] # type: ignore[valid-type]
33
31
  field_metadata["qtype_type"] = f"list[{var.type.element_type}]"
34
32
  elif (
35
33
  isinstance(var.type, type)
@@ -82,7 +80,7 @@ def create_output_container_type(flow: Flow) -> Type[BaseModel]:
82
80
  Field(description="List of errored execution outputs"),
83
81
  )
84
82
  fields["outputs"] = (
85
- list[output_shape],
83
+ list[output_shape], # type: ignore[valid-type]
86
84
  Field(description="List of successful execution outputs"),
87
85
  )
88
86
  return create_model(f"{flow.id}Response", __base__=BaseModel, **fields) # type: ignore
@@ -140,37 +138,36 @@ def flow_results_to_output_container(
140
138
  return output_container(outputs=outputs, errors=errors)
141
139
 
142
140
 
143
- def instantiate_variable(variable: DSLVariable, value: Any) -> Any:
141
+ def convert_dict_to_typed_variables(
142
+ data: dict[str, Any], variables: list[Variable]
143
+ ) -> dict[str, Any]:
144
144
  """
145
- Unified contract to ensure data matches its QType definition.
146
- Handles CustomTypes, DomainTypes, and Primitives.
145
+ Convert a dictionary of raw values to properly typed variables.
146
+
147
+ Uses Pydantic model validation to convert all values at once based on
148
+ Variable type declarations. This is more efficient than converting each
149
+ field individually.
150
+
151
+ Args:
152
+ data: Dictionary with raw values (e.g., from DataFrame row)
153
+ variables: List of Variable definitions with type information
154
+
155
+ Returns:
156
+ Dictionary with values converted to their declared types
157
+
158
+ Raises:
159
+ ValidationError: If values cannot be converted to declared types
147
160
  """
148
- target_type, _ = _get_variable_type(variable)
161
+ # Create a Pydantic model from the variable definitions
162
+ model_class = create_model(
163
+ "TypedVariables",
164
+ __base__=BaseModel,
165
+ **_fields_from_variables(variables),
166
+ )
149
167
 
150
- # 1. Handle the 'Parameterized Generic' Check (The isinstance fix)
151
- # We check if target_type is a generic (like list[T]) vs a simple class.
152
- origin = get_origin(target_type)
168
+ # Validate and convert the data using Pydantic
169
+ validated = model_class.model_validate(data)
153
170
 
154
- if origin is None:
155
- # It's a simple type (int, RAGChunk, etc.)
156
- if isinstance(value, target_type):
157
- return value
158
- else:
159
- # It's a generic (list[str], etc.).
160
- # We skip the identity check and let TypeAdapter handle it.
161
- pass
162
-
163
- # 2. Handle Pydantic Models (Custom/Domain Types)
164
- if hasattr(target_type, "model_validate"):
165
- return target_type.model_validate(value)
166
-
167
- # 3. Handle Primitives & Complex Python Types (List, Optional, Union)
168
- try:
169
- # TypeAdapter is the "V2 way" to validate things that aren't
170
- # full Pydantic models (like List[int] or Optional[str])
171
- return TypeAdapter(target_type).validate_python(value)
172
- except Exception:
173
- # Fallback to your original manual cast if TypeAdapter is overkill
174
- if isinstance(target_type, type):
175
- return target_type(value)
176
- raise ValueError(f"Unsupported target type: {target_type}")
171
+ # Return as dict but preserve actual typed instances (not serialized)
172
+ # Use __dict__ to get the actual field values without serialization
173
+ return dict(validated)
qtype/mcp/__init__.py ADDED
File without changes