qtype 0.1.12__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 (252) hide show
  1. qtype/` +0 -0
  2. qtype/application/__init__.py +0 -2
  3. qtype/application/converters/tools_from_api.py +28 -22
  4. qtype/application/converters/tools_from_module.py +66 -32
  5. qtype/commands/generate.py +90 -7
  6. qtype/commands/run.py +116 -44
  7. qtype/docs/.pages +8 -0
  8. {docs → qtype/docs}/Concepts/mental-model-and-philosophy.md +1 -1
  9. qtype/docs/Contributing/.pages +4 -0
  10. {docs → qtype/docs}/Contributing/index.md +8 -1
  11. {docs → qtype/docs}/Gallery/dataflow_pipelines.md +3 -2
  12. {docs → qtype/docs}/Gallery/research_assistant.md +3 -4
  13. {docs → qtype/docs}/Gallery/simple_chatbot.md +3 -1
  14. {docs → qtype/docs}/How To/Authentication/configure_aws_authentication.md +2 -2
  15. {docs → qtype/docs}/How To/Authentication/use_api_key_authentication.md +2 -2
  16. {docs → qtype/docs}/How To/Command Line Usage/load_multiple_inputs_from_files.md +24 -9
  17. {docs → qtype/docs}/How To/Command Line Usage/pass_inputs_on_the_cli.md +3 -3
  18. {docs → qtype/docs}/How To/Command Line Usage/serve_with_auto_reload.md +3 -2
  19. {docs → qtype/docs}/How To/Data Processing/adjust_concurrency.md +3 -4
  20. {docs → qtype/docs}/How To/Data Processing/cache_step_results.md +2 -2
  21. {docs → qtype/docs}/How To/Data Processing/decode_json_xml.md +1 -1
  22. {docs → qtype/docs}/How To/Data Processing/explode_collections.md +2 -2
  23. {docs → qtype/docs}/How To/Data Processing/gather_results.md +4 -4
  24. qtype/docs/How To/Data Processing/invoke_other_flows.md +71 -0
  25. qtype/docs/How To/Data Processing/load_data_from_athena.md +49 -0
  26. qtype/docs/How To/Data Processing/read_data_from_files.md +61 -0
  27. {docs → qtype/docs}/How To/Data Processing/read_sql_databases.md +2 -3
  28. {docs → qtype/docs}/How To/Data Processing/write_data_to_file.md +1 -2
  29. {docs → qtype/docs}/How To/Invoke Models/call_large_language_models.md +1 -1
  30. {docs → qtype/docs}/How To/Invoke Models/create_embeddings.md +1 -1
  31. {docs → qtype/docs}/How To/Invoke Models/reuse_prompts_with_templates.md +2 -3
  32. {docs → qtype/docs}/How To/Language Features/include_raw_text_from_other_files.md +2 -1
  33. {docs → qtype/docs}/How To/Language Features/reference_entities_by_id.md +2 -2
  34. qtype/docs/How To/Language Features/use_agent_skills.md +29 -0
  35. {docs → qtype/docs}/How To/Language Features/use_environment_variables.md +2 -1
  36. qtype/docs/How To/Language Features/use_optional_variables.md +42 -0
  37. {docs → qtype/docs}/How To/Language Features/use_qtype_mcp.md +4 -4
  38. {docs → qtype/docs}/How To/Observability & Debugging/trace_calls_with_open_telemetry.md +1 -1
  39. {docs → qtype/docs}/How To/Observability & Debugging/validate_qtype_yaml.md +3 -2
  40. {docs → qtype/docs}/How To/Observability & Debugging/visualize_application_architecture.md +1 -1
  41. {docs → qtype/docs}/How To/Qtype Server/serve_flows_as_apis.md +3 -3
  42. {docs → qtype/docs}/How To/Qtype Server/serve_flows_as_ui.md +2 -3
  43. {docs → qtype/docs}/How To/Qtype Server/use_conversational_interfaces.md +1 -4
  44. {docs → qtype/docs}/How To/Qtype Server/use_variables_with_ui_hints.md +3 -2
  45. {docs → qtype/docs}/How To/Tools & Integration/bind_tool_inputs_and_outputs.md +1 -2
  46. {docs → qtype/docs}/How To/Tools & Integration/create_tools_from_openapi_specifications.md +10 -14
  47. {docs → qtype/docs}/How To/Tools & Integration/create_tools_from_python_modules.md +5 -8
  48. {docs → qtype/docs}/Reference/cli.md +13 -15
  49. {docs → qtype/docs}/Reference/plugins.md +4 -0
  50. {docs → qtype/docs}/Reference/semantic-validation-rules.md +6 -1
  51. qtype/docs/Tutorials/.pages +1 -0
  52. {docs → qtype/docs}/Tutorials/01-first-qtype-application.md +3 -2
  53. {docs → qtype/docs}/Tutorials/02-conversational-chatbot.md +3 -3
  54. {docs → qtype/docs}/Tutorials/03-structured-data.md +9 -10
  55. {docs → qtype/docs}/Tutorials/04-tools-and-function-calling.md +12 -19
  56. {docs → qtype/docs}/components/APITool.md +1 -1
  57. qtype/docs/components/Aggregate.md +7 -0
  58. qtype/docs/components/Collect.md +6 -0
  59. qtype/docs/components/Construct.md +6 -0
  60. {docs → qtype/docs}/components/DocumentEmbedder.md +0 -1
  61. {docs → qtype/docs}/components/DocumentSplitter.md +0 -1
  62. qtype/docs/components/Explode.md +5 -0
  63. {docs → qtype/docs}/components/FieldExtractor.md +2 -1
  64. qtype/docs/components/InvokeFlow.md +8 -0
  65. qtype/docs/components/InvokeTool.md +8 -0
  66. {docs → qtype/docs}/components/PrimitiveTypeEnum.md +0 -1
  67. {docs → qtype/docs}/components/Source.md +0 -1
  68. {docs → qtype/docs}/components/Step.md +0 -1
  69. {docs → qtype/docs}/components/Tool.md +2 -2
  70. {docs → qtype/docs}/components/Variable.md +2 -0
  71. qtype/docs/legacy_how_tos/.pages +6 -0
  72. qtype/docs/skills/architect/SKILL.md +188 -0
  73. qtype/docs/skills/architect/references/cheatsheet.md +198 -0
  74. qtype/docs/skills/architect/references/patterns.md +29 -0
  75. qtype/docs/stylesheets/extra.css +27 -0
  76. qtype/dsl/linker.py +8 -0
  77. qtype/dsl/model.py +177 -84
  78. qtype/examples/data_processing/athena_query.qtype.yaml +56 -0
  79. qtype/examples/data_processing/batch_inputs.csv +5 -0
  80. qtype/examples/data_processing/create_sample_db.py +129 -0
  81. qtype/examples/data_processing/invoke_other_flows.qtype.yaml +98 -0
  82. qtype/examples/data_processing/reviews.db +0 -0
  83. qtype/examples/data_processing/sample_article.txt +1 -0
  84. qtype/examples/data_processing/sample_documents.jsonl +5 -0
  85. qtype/examples/language_features/optional_variables.qtype.yaml +32 -0
  86. qtype/examples/language_features/story_prompt.txt +6 -0
  87. qtype/examples/legacy/data/customers.csv +6 -0
  88. qtype/examples/legacy/echo/readme.md +29 -0
  89. qtype/examples/legacy/qtype_plugin_example.py +51 -0
  90. qtype/examples/legacy/sample_data.txt +43 -0
  91. qtype/examples/legacy/vertex/README.md +11 -0
  92. qtype/examples/research_assistant/tavily.qtype.yaml +216 -0
  93. {examples → qtype/examples}/tutorials/03_structured_data.qtype.yaml +2 -2
  94. {examples → qtype/examples}/tutorials/04_tools_and_function_calling.qtype.yaml +5 -5
  95. qtype/interpreter/base/stream_emitter.py +19 -13
  96. qtype/interpreter/converters.py +142 -26
  97. qtype/interpreter/executors/agent_executor.py +2 -3
  98. qtype/interpreter/executors/aggregate_executor.py +3 -4
  99. qtype/interpreter/executors/construct_executor.py +15 -15
  100. qtype/interpreter/executors/doc_to_text_executor.py +1 -3
  101. qtype/interpreter/executors/field_extractor_executor.py +13 -12
  102. qtype/interpreter/executors/file_source_executor.py +18 -31
  103. qtype/interpreter/executors/invoke_embedding_executor.py +1 -4
  104. qtype/interpreter/executors/invoke_flow_executor.py +2 -2
  105. qtype/interpreter/executors/invoke_tool_executor.py +19 -18
  106. qtype/interpreter/executors/llm_inference_executor.py +16 -18
  107. qtype/interpreter/executors/prompt_template_executor.py +1 -3
  108. qtype/interpreter/tools/function_tool_helper.py +11 -10
  109. qtype/interpreter/types.py +89 -4
  110. qtype/interpreter/typing.py +31 -32
  111. qtype/mcp/server.py +312 -57
  112. {schema → qtype/schema}/qtype.schema.json +77 -79
  113. qtype/semantic/checker.py +19 -0
  114. qtype/semantic/generate.py +3 -6
  115. qtype/semantic/model.py +26 -33
  116. qtype/semantic/resolver.py +7 -0
  117. qtype/semantic/visualize.py +8 -3
  118. {qtype-0.1.12.dist-info → qtype-0.1.13.dist-info}/METADATA +47 -46
  119. qtype-0.1.13.dist-info/RECORD +352 -0
  120. {qtype-0.1.12.dist-info → qtype-0.1.13.dist-info}/WHEEL +1 -2
  121. docs/How To/Data Processing/read_data_from_files.md +0 -35
  122. docs/components/Aggregate.md +0 -8
  123. docs/components/InvokeFlow.md +0 -8
  124. docs/components/InvokeTool.md +0 -8
  125. docs/components/ToolParameter.md +0 -6
  126. examples/research_assistant/tavily.qtype.yaml +0 -289
  127. qtype/application/facade.py +0 -177
  128. qtype-0.1.12.dist-info/RECORD +0 -325
  129. qtype-0.1.12.dist-info/top_level.txt +0 -1
  130. {docs → qtype/docs}/Contributing/roadmap.md +0 -0
  131. {docs → qtype/docs}/Decisions/ADR-001-Chat-vs-Completion-Endpoint-Features.md +0 -0
  132. {docs → qtype/docs}/Gallery/dataflow_pipelines.mermaid +0 -0
  133. {docs → qtype/docs}/Gallery/research_assistant.mermaid +0 -0
  134. {docs → qtype/docs}/Gallery/simple_chatbot.mermaid +0 -0
  135. {docs → qtype/docs}/How To/Language Features/include_qtype_yaml.md +0 -0
  136. {docs → qtype/docs}/How To/Observability & Debugging/visualize_example.mermaid +0 -0
  137. {docs → qtype/docs}/How To/Qtype Server/flow_as_ui.png +0 -0
  138. {docs → qtype/docs}/Tutorials/example_chat.png +0 -0
  139. {docs → qtype/docs}/Tutorials/index.md +0 -0
  140. {docs → qtype/docs}/components/APIKeyAuthProvider.md +0 -0
  141. {docs → qtype/docs}/components/AWSAuthProvider.md +0 -0
  142. {docs → qtype/docs}/components/AWSSecretManager.md +0 -0
  143. {docs → qtype/docs}/components/Agent.md +0 -0
  144. {docs → qtype/docs}/components/AggregateStats.md +0 -0
  145. {docs → qtype/docs}/components/Application.md +0 -0
  146. {docs → qtype/docs}/components/AuthorizationProvider.md +0 -0
  147. {docs → qtype/docs}/components/AuthorizationProviderList.md +0 -0
  148. {docs → qtype/docs}/components/BearerTokenAuthProvider.md +0 -0
  149. {docs → qtype/docs}/components/BedrockReranker.md +0 -0
  150. {docs → qtype/docs}/components/ChatContent.md +0 -0
  151. {docs → qtype/docs}/components/ChatMessage.md +0 -0
  152. {docs → qtype/docs}/components/ConstantPath.md +0 -0
  153. {docs → qtype/docs}/components/CustomType.md +0 -0
  154. {docs → qtype/docs}/components/Decoder.md +0 -0
  155. {docs → qtype/docs}/components/DecoderFormat.md +0 -0
  156. {docs → qtype/docs}/components/DocToTextConverter.md +0 -0
  157. {docs → qtype/docs}/components/Document.md +0 -0
  158. {docs → qtype/docs}/components/DocumentIndex.md +0 -0
  159. {docs → qtype/docs}/components/DocumentSearch.md +0 -0
  160. {docs → qtype/docs}/components/DocumentSource.md +0 -0
  161. {docs → qtype/docs}/components/Echo.md +0 -0
  162. {docs → qtype/docs}/components/Embedding.md +0 -0
  163. {docs → qtype/docs}/components/EmbeddingModel.md +0 -0
  164. {docs → qtype/docs}/components/FileSource.md +0 -0
  165. {docs → qtype/docs}/components/FileWriter.md +0 -0
  166. {docs → qtype/docs}/components/Flow.md +0 -0
  167. {docs → qtype/docs}/components/FlowInterface.md +0 -0
  168. {docs → qtype/docs}/components/Index.md +0 -0
  169. {docs → qtype/docs}/components/IndexUpsert.md +0 -0
  170. {docs → qtype/docs}/components/InvokeEmbedding.md +0 -0
  171. {docs → qtype/docs}/components/LLMInference.md +0 -0
  172. {docs → qtype/docs}/components/ListType.md +0 -0
  173. {docs → qtype/docs}/components/Memory.md +0 -0
  174. {docs → qtype/docs}/components/MessageRole.md +0 -0
  175. {docs → qtype/docs}/components/Model.md +0 -0
  176. {docs → qtype/docs}/components/ModelList.md +0 -0
  177. {docs → qtype/docs}/components/OAuth2AuthProvider.md +0 -0
  178. {docs → qtype/docs}/components/PromptTemplate.md +0 -0
  179. {docs → qtype/docs}/components/PythonFunctionTool.md +0 -0
  180. {docs → qtype/docs}/components/RAGChunk.md +0 -0
  181. {docs → qtype/docs}/components/RAGDocument.md +0 -0
  182. {docs → qtype/docs}/components/RAGSearchResult.md +0 -0
  183. {docs → qtype/docs}/components/Reranker.md +0 -0
  184. {docs → qtype/docs}/components/SQLSource.md +0 -0
  185. {docs → qtype/docs}/components/Search.md +0 -0
  186. {docs → qtype/docs}/components/SearchResult.md +0 -0
  187. {docs → qtype/docs}/components/SecretManager.md +0 -0
  188. {docs → qtype/docs}/components/SecretReference.md +0 -0
  189. {docs → qtype/docs}/components/TelemetrySink.md +0 -0
  190. {docs → qtype/docs}/components/ToolList.md +0 -0
  191. {docs → qtype/docs}/components/TypeList.md +0 -0
  192. {docs → qtype/docs}/components/VariableList.md +0 -0
  193. {docs → qtype/docs}/components/VectorIndex.md +0 -0
  194. {docs → qtype/docs}/components/VectorSearch.md +0 -0
  195. {docs → qtype/docs}/components/VertexAuthProvider.md +0 -0
  196. {docs → qtype/docs}/components/Writer.md +0 -0
  197. {docs → qtype/docs}/example_ui.png +0 -0
  198. {docs → qtype/docs}/index.md +0 -0
  199. {docs → qtype/docs}/legacy_how_tos/Configuration/modular-yaml.md +0 -0
  200. {docs → qtype/docs}/legacy_how_tos/Configuration/phoenix_projects.png +0 -0
  201. {docs → qtype/docs}/legacy_how_tos/Configuration/phoenix_traces.png +0 -0
  202. {docs → qtype/docs}/legacy_how_tos/Configuration/reference-by-id.md +0 -0
  203. {docs → qtype/docs}/legacy_how_tos/Configuration/telemetry-setup.md +0 -0
  204. {docs → qtype/docs}/legacy_how_tos/Data Types/custom-types.md +0 -0
  205. {docs → qtype/docs}/legacy_how_tos/Data Types/domain-types.md +0 -0
  206. {docs → qtype/docs}/legacy_how_tos/Debugging/visualize-apps.md +0 -0
  207. {docs → qtype/docs}/legacy_how_tos/Tools/api-tools.md +0 -0
  208. {docs → qtype/docs}/legacy_how_tos/Tools/python-tools.md +0 -0
  209. {examples → qtype/examples}/authentication/aws_authentication.qtype.yaml +0 -0
  210. {examples → qtype/examples}/conversational_ai/hello_world_chat.qtype.yaml +0 -0
  211. {examples → qtype/examples}/conversational_ai/simple_chatbot.qtype.yaml +0 -0
  212. {examples → qtype/examples}/data_processing/batch_processing.qtype.yaml +0 -0
  213. {examples → qtype/examples}/data_processing/cache_step_results.qtype.yaml +0 -0
  214. {examples → qtype/examples}/data_processing/collect_results.qtype.yaml +0 -0
  215. {examples → qtype/examples}/data_processing/dataflow_pipelines.qtype.yaml +0 -0
  216. {examples → qtype/examples}/data_processing/decode_json.qtype.yaml +0 -0
  217. {examples → qtype/examples}/data_processing/explode_items.qtype.yaml +0 -0
  218. {examples → qtype/examples}/data_processing/read_file.qtype.yaml +0 -0
  219. {examples → qtype/examples}/invoke_models/create_embeddings.qtype.yaml +0 -0
  220. {examples → qtype/examples}/invoke_models/simple_llm_call.qtype.yaml +0 -0
  221. {examples → qtype/examples}/language_features/include_raw.qtype.yaml +0 -0
  222. {examples → qtype/examples}/language_features/ui_hints.qtype.yaml +0 -0
  223. {examples → qtype/examples}/legacy/bedrock/data_analysis_with_telemetry.qtype.yaml +0 -0
  224. {examples → qtype/examples}/legacy/bedrock/hello_world.qtype.yaml +0 -0
  225. {examples → qtype/examples}/legacy/bedrock/hello_world_chat.qtype.yaml +0 -0
  226. {examples → qtype/examples}/legacy/bedrock/hello_world_chat_with_telemetry.qtype.yaml +0 -0
  227. {examples → qtype/examples}/legacy/bedrock/hello_world_chat_with_thinking.qtype.yaml +0 -0
  228. {examples → qtype/examples}/legacy/bedrock/hello_world_completion.qtype.yaml +0 -0
  229. {examples → qtype/examples}/legacy/bedrock/hello_world_completion_with_auth.qtype.yaml +0 -0
  230. {examples → qtype/examples}/legacy/bedrock/simple_agent_chat.qtype.yaml +0 -0
  231. {examples → qtype/examples}/legacy/chat_with_langfuse.qtype.yaml +0 -0
  232. {examples → qtype/examples}/legacy/data_processor.qtype.yaml +0 -0
  233. {examples → qtype/examples}/legacy/echo/debug_example.qtype.yaml +0 -0
  234. {examples → qtype/examples}/legacy/echo/prompt.qtype.yaml +0 -0
  235. {examples → qtype/examples}/legacy/echo/test.qtype.yaml +0 -0
  236. {examples → qtype/examples}/legacy/echo/video.qtype.yaml +0 -0
  237. {examples → qtype/examples}/legacy/field_extractor_example.qtype.yaml +0 -0
  238. {examples → qtype/examples}/legacy/multi_flow_example.qtype.yaml +0 -0
  239. {examples → qtype/examples}/legacy/openai/hello_world_chat.qtype.yaml +0 -0
  240. {examples → qtype/examples}/legacy/openai/hello_world_chat_with_telemetry.qtype.yaml +0 -0
  241. {examples → qtype/examples}/legacy/rag.qtype.yaml +0 -0
  242. {examples → qtype/examples}/legacy/time_utilities.qtype.yaml +0 -0
  243. {examples → qtype/examples}/legacy/vertex/hello_world_chat.qtype.yaml +0 -0
  244. {examples → qtype/examples}/legacy/vertex/hello_world_completion.qtype.yaml +0 -0
  245. {examples → qtype/examples}/legacy/vertex/hello_world_completion_with_auth.qtype.yaml +0 -0
  246. {examples → qtype/examples}/observability_debugging/trace_with_opentelemetry.qtype.yaml +0 -0
  247. {examples → qtype/examples}/research_assistant/research_assistant.qtype.yaml +0 -0
  248. {examples → qtype/examples}/research_assistant/tavily.oas.yaml +0 -0
  249. {examples → qtype/examples}/tutorials/01_hello_world.qtype.yaml +0 -0
  250. {examples → qtype/examples}/tutorials/02_conversational_chat.qtype.yaml +0 -0
  251. {qtype-0.1.12.dist-info → qtype-0.1.13.dist-info}/entry_points.txt +0 -0
  252. {qtype-0.1.12.dist-info → qtype-0.1.13.dist-info}/licenses/LICENSE +0 -0
qtype/dsl/model.py CHANGED
@@ -79,6 +79,11 @@ def _resolve_list_type(
79
79
  if cls == element_type:
80
80
  return ListType(element_type=name)
81
81
  return ListType(element_type=str(element_type))
82
+ elif isinstance(element_type, type) and issubclass(
83
+ element_type, BaseModel
84
+ ):
85
+ # Custom type class - store its name as string reference
86
+ return ListType(element_type=element_type.__name__)
82
87
  else:
83
88
  raise ValueError(
84
89
  (
@@ -140,6 +145,8 @@ def _resolve_variable_type(
140
145
  Resolve a type to its corresponding representation.
141
146
 
142
147
  Handles primitive types, list types, domain types, and custom types.
148
+ Unknown types are returned as strings (forward references) and will be
149
+ validated later during the linking phase.
143
150
 
144
151
  Args:
145
152
  parsed_type: The type to resolve (can be string or already resolved)
@@ -172,17 +179,26 @@ def _resolve_variable_type(
172
179
  if custom is not None:
173
180
  return custom
174
181
 
175
- # If it's not any known type, return it as a string.
176
- # This assumes it might be a forward reference to a custom type.
177
- return parsed_type
182
+ # If it's not any known type, raise an error
183
+ available_types = (
184
+ f"primitive types ({', '.join([t.value for t in PrimitiveTypeEnum])}), "
185
+ f"domain types ({', '.join(DOMAIN_CLASSES.keys())})"
186
+ )
187
+ if custom_type_registry:
188
+ available_types += (
189
+ f", or custom types ({', '.join(custom_type_registry.keys())})"
190
+ )
191
+ raise ValueError(
192
+ f"Unknown type '{parsed_type}'. Must be one of: {available_types}"
193
+ )
178
194
 
179
195
 
180
196
  def _resolve_type_field_validator(data: Any, info: ValidationInfo) -> Any:
181
197
  """
182
198
  Shared validator for resolving 'type' fields in models.
183
199
 
184
- This validator resolves string-based type references using the custom
185
- type registry from the validation context.
200
+ This validator handles optional '?' syntax and resolves string-based
201
+ type references using the custom type registry from the validation context.
186
202
 
187
203
  Args:
188
204
  data: The data dict being validated
@@ -196,6 +212,13 @@ def _resolve_type_field_validator(data: Any, info: ValidationInfo) -> Any:
196
212
  and "type" in data
197
213
  and isinstance(data["type"], str)
198
214
  ):
215
+ # Handle '?' suffix for optional types BEFORE type resolution
216
+ type_value = data["type"]
217
+ if type_value.endswith("?"):
218
+ # Strip '?' and mark as optional
219
+ data["type"] = type_value[:-1]
220
+ data["optional"] = True
221
+
199
222
  # Get the registry of custom types from the validation context.
200
223
  custom_types = (info.context or {}).get("custom_types", {})
201
224
  resolved = _resolve_variable_type(data["type"], custom_types)
@@ -203,6 +226,54 @@ def _resolve_type_field_validator(data: Any, info: ValidationInfo) -> Any:
203
226
  return data
204
227
 
205
228
 
229
+ def _merge_vars_from_bindings(
230
+ existing: list[Reference[Variable] | str],
231
+ bindings: dict[str, Reference[Variable] | str],
232
+ ) -> list[Reference[Variable] | str]:
233
+ """Merge existing variables with bindings and deduplicate by variable ID.
234
+
235
+ Args:
236
+ existing: Existing list of variable references or IDs
237
+ bindings: Dict mapping parameter names to variable references or IDs
238
+
239
+ Returns:
240
+ Merged list with duplicates removed, preserving original form
241
+ """
242
+
243
+ def get_id(item):
244
+ return item.ref if isinstance(item, Reference) else item
245
+
246
+ seen = set()
247
+ result = []
248
+ for item in list(existing) + list(bindings.values()):
249
+ var_id = get_id(item)
250
+ if var_id not in seen:
251
+ seen.add(var_id)
252
+ result.append(item)
253
+ return result
254
+
255
+
256
+ def _type_to_string(type_value: Any) -> str:
257
+ """Convert a type value to its string representation.
258
+
259
+ Args:
260
+ type_value: The type value to convert
261
+
262
+ Returns:
263
+ String representation of the type
264
+ """
265
+ if isinstance(type_value, str):
266
+ return type_value
267
+ elif isinstance(type_value, PrimitiveTypeEnum):
268
+ return type_value.value
269
+ elif isinstance(type_value, ListType):
270
+ return str(type_value)
271
+ elif isinstance(type_value, type):
272
+ return type_value.__name__
273
+ else:
274
+ return str(type_value)
275
+
276
+
206
277
  class Variable(StrictBaseModel):
207
278
  """Schema for a variable that can serve as input, output, or parameter within the DSL."""
208
279
 
@@ -216,15 +287,37 @@ class Variable(StrictBaseModel):
216
287
  "Type of data expected or produced. Either a CustomType or domain specific type."
217
288
  ),
218
289
  )
290
+ optional: bool = Field(
291
+ default=False,
292
+ description=(
293
+ "Whether this variable can be unset or None. "
294
+ "Use '?' suffix in type string as shorthand (e.g., 'text?')."
295
+ ),
296
+ )
219
297
 
220
- ui: UIType | None = Field(None, description="Hints for the UI if needed.")
298
+ ui: UIType | None = Field(
299
+ default=None, description="Hints for the UI if needed."
300
+ )
221
301
 
222
302
  @model_validator(mode="before")
223
303
  @classmethod
224
304
  def resolve_type(cls, data: Any, info: ValidationInfo) -> Any:
225
- """Resolve string-based type references using the shared validator."""
305
+ """Resolve string-based type references and handle optional '?' syntax."""
226
306
  return _resolve_type_field_validator(data, info)
227
307
 
308
+ @model_serializer
309
+ def serialize_model(self):
310
+ """Serialize with '?' suffix for optional types."""
311
+ result: dict[str, Any] = {"id": self.id}
312
+
313
+ type_str = _type_to_string(self.type)
314
+ result["type"] = f"{type_str}?" if self.optional else type_str
315
+
316
+ if self.ui is not None:
317
+ result["ui"] = self.ui.model_dump()
318
+
319
+ return result
320
+
228
321
  @model_validator(mode="after")
229
322
  def validate_ui_type(self) -> Variable:
230
323
  """Ensure at least one credential source is provided."""
@@ -260,37 +353,6 @@ class CustomType(StrictBaseModel):
260
353
  properties: dict[str, str]
261
354
 
262
355
 
263
- class ToolParameter(BaseModel):
264
- """Defines a tool input or output parameter with type and optional flag."""
265
-
266
- type: VariableType | str
267
- optional: bool = Field(
268
- default=False, description="Whether this parameter is optional"
269
- )
270
-
271
- @model_validator(mode="before")
272
- @classmethod
273
- def resolve_type(cls, data: Any, info: ValidationInfo) -> Any:
274
- """Resolve string-based type references using the shared validator."""
275
- return _resolve_type_field_validator(data, info)
276
-
277
- @staticmethod
278
- def _serialize_type(value):
279
- if isinstance(value, type):
280
- return value.__name__
281
- elif hasattr(value, "__name__"):
282
- return value.__name__
283
- return value
284
-
285
- @model_serializer
286
- def _model_serializer(self):
287
- # Use the default serialization, but ensure 'type' is a string
288
- return {
289
- "type": self._serialize_type(self.type),
290
- "optional": self.optional,
291
- }
292
-
293
-
294
356
  class ListType(BaseModel):
295
357
  """Represents a list type with a specific element type."""
296
358
 
@@ -418,11 +480,19 @@ class Construct(Step):
418
480
  """A step that converts variables into an instance of a Custom or Domain Type"""
419
481
 
420
482
  type: Literal["Construct"] = "Construct"
421
- field_mapping: dict[str, str] = Field(
483
+ field_bindings: dict[str, Reference[Variable] | str] = Field(
422
484
  ...,
423
- description="Mapping of type inputs to variable names, if needed.",
485
+ description="Mapping from type field names to flow variable names.",
424
486
  )
425
487
 
488
+ @model_validator(mode="after")
489
+ def infer_inputs_from_bindings(self) -> "Construct":
490
+ """Infer inputs from field bindings."""
491
+ self.inputs = _merge_vars_from_bindings(
492
+ self.inputs, self.field_bindings
493
+ )
494
+ return self
495
+
426
496
 
427
497
  class PromptTemplate(Step):
428
498
  """Defines a prompt template with a string format and variable bindings.
@@ -445,12 +515,12 @@ class Tool(StrictBaseModel, ABC):
445
515
  description: str = Field(
446
516
  ..., description="Description of what the tool does."
447
517
  )
448
- inputs: dict[str, ToolParameter] = Field(
449
- default_factory=dict,
518
+ inputs: list[Variable] = Field(
519
+ default_factory=list,
450
520
  description="Input parameters required by this tool.",
451
521
  )
452
- outputs: dict[str, ToolParameter] = Field(
453
- default_factory=dict,
522
+ outputs: list[Variable] = Field(
523
+ default_factory=list,
454
524
  description="Output parameters produced by this tool.",
455
525
  )
456
526
 
@@ -485,9 +555,9 @@ class APITool(Tool):
485
555
  default_factory=dict,
486
556
  description="Optional HTTP headers to include in the request.",
487
557
  )
488
- parameters: dict[str, ToolParameter] = Field(
489
- default_factory=dict,
490
- description="Output parameters produced by this tool.",
558
+ parameters: list[Variable] = Field(
559
+ default_factory=list,
560
+ description="Path and query parameters for the API call.",
491
561
  )
492
562
 
493
563
 
@@ -622,6 +692,9 @@ class FieldExtractor(Step):
622
692
  The extracted data is used to construct the output variable by passing it
623
693
  as keyword arguments to the output type's constructor.
624
694
 
695
+ If there is no match and the output variable is optional, it is set to None.
696
+ If there is no match and the output variable is required, an error is raised.
697
+
625
698
  Example JSONPath expressions:
626
699
  - `$.field_name` - Extract a single field
627
700
  - `$.items[*]` - Extract all items from a list
@@ -633,10 +706,6 @@ class FieldExtractor(Step):
633
706
  ...,
634
707
  description="JSONPath expression to extract data from the input. Uses jsonpath-ng syntax.",
635
708
  )
636
- fail_on_missing: bool = Field(
637
- default=True,
638
- description="Whether to raise an error if the JSONPath matches no data. If False, returns None.",
639
- )
640
709
 
641
710
 
642
711
  class InvokeTool(Step, ConcurrentStepMixin):
@@ -648,33 +717,23 @@ class InvokeTool(Step, ConcurrentStepMixin):
648
717
  ...,
649
718
  description="Tool to invoke.",
650
719
  )
651
- input_bindings: dict[str, str] = Field(
720
+ input_bindings: dict[str, Reference[Variable] | str] = Field(
652
721
  ...,
653
- description="Mapping from variable references to tool input parameter names.",
722
+ description="Mapping from tool parameter names to flow variable names.",
654
723
  )
655
- output_bindings: dict[str, str] = Field(
724
+ output_bindings: dict[str, Reference[Variable] | str] = Field(
656
725
  ...,
657
- description="Mapping from variable references to tool output parameter names.",
726
+ description="Mapping from tool output names to flow variable names.",
658
727
  )
659
728
 
660
729
  @model_validator(mode="after")
661
730
  def infer_inputs_outputs_from_bindings(self) -> "InvokeTool":
662
- def _merge_vars(
663
- existing: list[Reference[Variable] | str],
664
- bindings: dict[str, str],
665
- ) -> list[Reference[Variable] | str]:
666
- """Merge existing variables with bindings and deduplicate."""
667
- # NOTE: doesn't handle references. You may duplicate inputs here..
668
- existing_ids = [item for item in existing if isinstance(item, str)]
669
- inferred_ids = list(bindings.values())
670
- merged_ids: list[Reference[Variable] | str] = [
671
- Reference[Variable].model_validate({"$ref": var_id})
672
- for var_id in dict.fromkeys(existing_ids + inferred_ids)
673
- ]
674
- return merged_ids
675
-
676
- self.inputs = _merge_vars(self.inputs, self.input_bindings)
677
- self.outputs = _merge_vars(self.outputs, self.output_bindings)
731
+ self.inputs = _merge_vars_from_bindings(
732
+ self.inputs, self.input_bindings
733
+ )
734
+ self.outputs = _merge_vars_from_bindings(
735
+ self.outputs, self.output_bindings
736
+ )
678
737
  return self
679
738
 
680
739
 
@@ -687,15 +746,25 @@ class InvokeFlow(Step):
687
746
  ...,
688
747
  description="Flow to invoke.",
689
748
  )
690
- input_bindings: dict[Reference[Variable], str] = Field(
749
+ input_bindings: dict[str, Reference[Variable] | str] = Field(
691
750
  ...,
692
- description="Mapping from variable references to flow input variable IDs.",
751
+ description="Mapping from flow input variable IDs to step variable names.",
693
752
  )
694
- output_bindings: dict[Reference[Variable], str] = Field(
753
+ output_bindings: dict[str, Reference[Variable] | str] = Field(
695
754
  ...,
696
- description="Mapping from variable references to flow output variable IDs.",
755
+ description="Mapping from flow output variable IDs to step variable names.",
697
756
  )
698
757
 
758
+ @model_validator(mode="after")
759
+ def infer_inputs_outputs_from_bindings(self) -> "InvokeFlow":
760
+ self.inputs = _merge_vars_from_bindings(
761
+ self.inputs, self.input_bindings
762
+ )
763
+ self.outputs = _merge_vars_from_bindings(
764
+ self.outputs, self.output_bindings
765
+ )
766
+ return self
767
+
699
768
 
700
769
  #
701
770
  # ---------------- Secret Manager Component ----------------
@@ -1000,6 +1069,21 @@ class FileSource(Source):
1000
1069
  description="Reference to a variable with an fsspec-compatible URI to read from, or the uri itself.",
1001
1070
  )
1002
1071
 
1072
+ @model_validator(mode="after")
1073
+ def infer_inputs_from_path(self) -> "FileSource":
1074
+ """Add path variable to inputs if it's a variable reference."""
1075
+ if isinstance(self.path, str):
1076
+ # Path is a variable ID, add it to inputs
1077
+ path_ref = Reference[Variable].model_validate({"$ref": self.path})
1078
+ if path_ref not in self.inputs and self.path not in self.inputs:
1079
+ self.inputs = list(self.inputs) + [path_ref]
1080
+ elif isinstance(self.path, Reference):
1081
+ # Path is already a Reference, add it to inputs
1082
+ if self.path not in self.inputs:
1083
+ self.inputs = list(self.inputs) + [self.path]
1084
+ # If path is ConstantPath, don't add to inputs
1085
+ return self
1086
+
1003
1087
 
1004
1088
  class Writer(Step, BatchableStepMixin):
1005
1089
  """Base class for things that write data in batches."""
@@ -1020,22 +1104,31 @@ class FileWriter(Writer, BatchableStepMixin):
1020
1104
  description="Configuration for processing the input stream in batches. If omitted, the step processes items one by one.",
1021
1105
  )
1022
1106
 
1107
+ @model_validator(mode="after")
1108
+ def infer_inputs_from_path(self) -> "FileWriter":
1109
+ """Add path variable to inputs if it's a variable reference."""
1110
+ if isinstance(self.path, str):
1111
+ # Path is a variable ID, add it to inputs
1112
+ path_ref = Reference[Variable].model_validate({"$ref": self.path})
1113
+ if path_ref not in self.inputs and self.path not in self.inputs:
1114
+ self.inputs = list(self.inputs) + [path_ref]
1115
+ elif isinstance(self.path, Reference):
1116
+ # Path is already a Reference, add it to inputs
1117
+ if self.path not in self.inputs:
1118
+ self.inputs = list(self.inputs) + [self.path]
1119
+ # If path is ConstantPath, don't add to inputs
1120
+ return self
1121
+
1023
1122
 
1024
1123
  class Aggregate(Step):
1025
1124
  """
1026
- A terminal step that consumes an entire input stream and produces a single
1027
- summary message with success/error counts.
1125
+ A step that, after all messages have been processed,
1126
+ returns a single message containing the counts of successful and failed
1127
+ messages. Other messages are passed through unchanged.
1028
1128
  """
1029
1129
 
1030
1130
  type: Literal["Aggregate"] = "Aggregate"
1031
1131
 
1032
- # Outputs are now optional. The user can provide 0, 1, 2, or 3 names.
1033
- # The order will be: success_count, error_count, total_count
1034
- outputs: list[Reference[Variable] | str] = Field(
1035
- default_factory=list,
1036
- description="References to the variables for the output. There should be one and only one output with type AggregateStats",
1037
- )
1038
-
1039
1132
 
1040
1133
  #
1041
1134
  # ---------------- Retrieval Augmented Generation Components ----------------
@@ -0,0 +1,56 @@
1
+ id: athena-query-example
2
+ description: Query AWS Athena database and process results
3
+
4
+ auths:
5
+ - type: aws
6
+ id: aws_auth
7
+ region: us-east-1
8
+ profile_name: default
9
+
10
+ flows:
11
+ - type: Flow
12
+ id: query-athena
13
+
14
+ variables:
15
+ - id: min_sales
16
+ type: int
17
+ - id: product_id
18
+ type: text
19
+ - id: product_name
20
+ type: text
21
+ - id: total_sales
22
+ type: int
23
+ - id: region
24
+ type: text
25
+
26
+ inputs:
27
+ - min_sales
28
+
29
+ outputs:
30
+ - product_id
31
+ - product_name
32
+ - total_sales
33
+ - region
34
+
35
+ steps:
36
+ - type: SQLSource
37
+ id: load_sales
38
+ connection: "awsathena+rest://:@athena.us-east-1.amazonaws.com:443/sales_db?s3_staging_dir=s3://my-results-bucket/athena-results/&work_group=primary&catalog_name=some_catalog""
39
+ auth: aws_auth
40
+ query: |
41
+ SELECT
42
+ product_id,
43
+ product_name,
44
+ total_sales,
45
+ region
46
+ FROM product_sales
47
+ WHERE total_sales >= :min_sales
48
+ ORDER BY total_sales DESC
49
+ LIMIT 100
50
+ inputs:
51
+ - min_sales
52
+ outputs:
53
+ - product_id
54
+ - product_name
55
+ - total_sales
56
+ - region
@@ -0,0 +1,5 @@
1
+ query,topic
2
+ What is machine learning?,Technology
3
+ Best pasta recipes,Cooking
4
+ How to train for a marathon,Fitness
5
+ Climate change solutions,Environment
@@ -0,0 +1,129 @@
1
+ """Create a sample SQLite database with product reviews for the example."""
2
+
3
+ import sqlite3
4
+ from pathlib import Path
5
+
6
+ # Sample product reviews data
7
+ SAMPLE_REVIEWS = [
8
+ (
9
+ 1,
10
+ "Wireless Headphones",
11
+ 5,
12
+ "Amazing sound quality! The noise cancellation is superb and "
13
+ "battery lasts all day. Highly recommend for music lovers.",
14
+ ),
15
+ (
16
+ 2,
17
+ "Wireless Headphones",
18
+ 2,
19
+ "Disappointed with the build quality. They broke after just 2 "
20
+ "weeks of normal use. Sound is okay but not worth the price.",
21
+ ),
22
+ (
23
+ 3,
24
+ "Smart Watch",
25
+ 4,
26
+ "Great fitness tracker with accurate heart rate monitoring. "
27
+ "Battery life could be better, but overall very satisfied.",
28
+ ),
29
+ (
30
+ 4,
31
+ "Smart Watch",
32
+ 5,
33
+ "Best smartwatch I've owned! Seamless integration with my phone, "
34
+ "tons of useful features, and looks professional.",
35
+ ),
36
+ (
37
+ 5,
38
+ "Laptop Stand",
39
+ 3,
40
+ "Does the job but feels flimsy. The adjustability is limited and "
41
+ "it wobbles a bit. Expected better quality for the price.",
42
+ ),
43
+ (
44
+ 6,
45
+ "Laptop Stand",
46
+ 5,
47
+ "Perfect for my home office setup! Sturdy construction, multiple "
48
+ "height options, and really helps with posture.",
49
+ ),
50
+ (
51
+ 7,
52
+ "USB-C Hub",
53
+ 4,
54
+ "Works well with all my devices. All ports function properly and "
55
+ "data transfer is fast. Gets a bit warm during heavy use.",
56
+ ),
57
+ (
58
+ 8,
59
+ "USB-C Hub",
60
+ 1,
61
+ "Stopped working after a week. One port was DOA and then the whole "
62
+ "hub died. Total waste of money.",
63
+ ),
64
+ (
65
+ 9,
66
+ "Mechanical Keyboard",
67
+ 5,
68
+ "Typing feels incredible! The switches are responsive and the build "
69
+ "quality is excellent. Worth every penny.",
70
+ ),
71
+ (
72
+ 10,
73
+ "Mechanical Keyboard",
74
+ 4,
75
+ "Great keyboard for coding. Switches are a bit loud for an office "
76
+ "environment but the tactile feedback is amazing.",
77
+ ),
78
+ ]
79
+
80
+
81
+ def create_database(db_path: Path | str) -> None:
82
+ """Create SQLite database with sample product reviews.
83
+
84
+ Args:
85
+ db_path: Path where the database file should be created
86
+ """
87
+ db_path = Path(db_path)
88
+
89
+ # Remove existing database if it exists
90
+ if db_path.exists():
91
+ db_path.unlink()
92
+
93
+ # Create database and table
94
+ conn = sqlite3.connect(db_path)
95
+ cursor = conn.cursor()
96
+
97
+ # Create reviews table
98
+ cursor.execute(
99
+ """
100
+ CREATE TABLE product_reviews (
101
+ review_id INTEGER PRIMARY KEY,
102
+ product_name TEXT NOT NULL,
103
+ rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 5),
104
+ review_text TEXT NOT NULL,
105
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
106
+ )
107
+ """
108
+ )
109
+
110
+ # Insert sample data
111
+ cursor.executemany(
112
+ """
113
+ INSERT INTO product_reviews
114
+ (review_id, product_name, rating, review_text)
115
+ VALUES (?, ?, ?, ?)
116
+ """,
117
+ SAMPLE_REVIEWS,
118
+ )
119
+
120
+ conn.commit()
121
+ conn.close()
122
+
123
+ print(f"Created database at {db_path} with {len(SAMPLE_REVIEWS)} reviews")
124
+
125
+
126
+ if __name__ == "__main__":
127
+ # Create database in the data_processing examples directory
128
+ db_path = Path(__file__).parent / "reviews.db"
129
+ create_database(db_path)
@@ -0,0 +1,98 @@
1
+ id: text_analysis_pipeline
2
+ description: Demonstrates invoking reusable flows for text analysis
3
+
4
+ models:
5
+ - type: Model
6
+ id: nova_lite
7
+ provider: aws-bedrock
8
+ model_id: amazon.nova-lite-v1:0
9
+
10
+ flows:
11
+ # Reusable flow for text summarization
12
+ - type: Flow
13
+ id: summarize_text
14
+ description: Summarizes input text
15
+ variables:
16
+ - id: input_text
17
+ type: text
18
+ - id: output_summary
19
+ type: text
20
+ inputs:
21
+ - input_text
22
+ outputs:
23
+ - output_summary
24
+ steps:
25
+ - type: LLMInference
26
+ id: summarize_step
27
+ model: nova_lite
28
+ system_message: "You provide concise summaries."
29
+ inputs: [input_text]
30
+ outputs: [output_summary]
31
+
32
+ # Reusable flow for sentiment analysis
33
+ - type: Flow
34
+ id: analyze_sentiment
35
+ description: Analyzes sentiment of text
36
+ variables:
37
+ - id: input_for_sentiment
38
+ type: text
39
+ - id: output_sentiment
40
+ type: text
41
+ inputs:
42
+ - input_for_sentiment
43
+ outputs:
44
+ - output_sentiment
45
+ steps:
46
+ - type: LLMInference
47
+ id: sentiment_step
48
+ model: nova_lite
49
+ system_message: "Analyze sentiment. Respond with only: positive, negative, or neutral."
50
+ inputs: [input_for_sentiment]
51
+ outputs: [output_sentiment]
52
+
53
+ # Main processing flow
54
+ - type: Flow
55
+ id: main
56
+ description: Orchestrates text analysis using multiple flows
57
+ variables:
58
+ - id: article_text
59
+ type: text
60
+ - id: summary
61
+ type: text
62
+ - id: sentiment
63
+ type: text
64
+ - id: report
65
+ type: text
66
+ inputs:
67
+ - article_text
68
+ outputs:
69
+ - report
70
+ steps:
71
+ # Invoke summarization flow
72
+ - type: InvokeFlow
73
+ id: get_summary
74
+ flow: summarize_text
75
+ input_bindings:
76
+ input_text: article_text
77
+ output_bindings:
78
+ output_summary: summary
79
+
80
+ # Invoke sentiment analysis flow
81
+ - type: InvokeFlow
82
+ id: get_sentiment
83
+ flow: analyze_sentiment
84
+ input_bindings:
85
+ input_for_sentiment: article_text
86
+ output_bindings:
87
+ output_sentiment: sentiment
88
+
89
+ # Combine results into a report
90
+ - type: PromptTemplate
91
+ id: create_report
92
+ template: |
93
+ Analysis Report
94
+ ---------------
95
+ Sentiment: {sentiment}
96
+ Summary: {summary}
97
+ inputs: [summary, sentiment]
98
+ outputs: [report]