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
qtype/` ADDED
File without changes
@@ -3,10 +3,8 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from . import commons, converters
6
- from .facade import QTypeFacade
7
6
 
8
7
  __all__ = [
9
- "QTypeFacade",
10
8
  "converters",
11
9
  "commons",
12
10
  ]
@@ -26,7 +26,7 @@ from qtype.dsl.model import (
26
26
  BearerTokenAuthProvider,
27
27
  CustomType,
28
28
  OAuth2AuthProvider,
29
- ToolParameter,
29
+ Variable,
30
30
  VariableType,
31
31
  )
32
32
 
@@ -86,47 +86,53 @@ def _create_custom_type_from_schema(
86
86
  schema_name_map: dict[int, str],
87
87
  ) -> CustomType:
88
88
  """Create a CustomType from an Object schema."""
89
- # Generate a unique ID for this schema-based type
90
- type_id = None
89
+ # Use object id instead of hash(str()) to avoid recursion with circular refs
90
+ schema_id = id(schema)
91
+
92
+ # Check if we already have this type (prevents circular reference issues)
93
+ if schema_id in schema_name_map:
94
+ type_id = schema_name_map[schema_id]
95
+ if type_id in existing_custom_types:
96
+ return existing_custom_types[type_id]
91
97
 
92
- schema_hash = hash(str(schema))
93
- if schema_hash in schema_name_map:
94
- type_id = schema_name_map[schema_hash]
98
+ # Generate a unique ID for this schema-based type
99
+ if schema.title:
100
+ # Use title if available, make it lowercase, alphanumeric, snake_case
101
+ base_id = schema.title.lower().replace(" ", "_").replace("-", "_")
102
+ # Remove non-alphanumeric characters except underscores
103
+ type_id = "schema_" + "".join(
104
+ c for c in base_id if c.isalnum() or c == "_"
105
+ )
95
106
  else:
96
- # make a type id manually
97
- if schema.title:
98
- # Use title if available, make it lowercase, alphanumeric, snake_case
99
- base_id = schema.title.lower().replace(" ", "_").replace("-", "_")
100
- # Remove non-alphanumeric characters except underscores
101
- type_id = "schema_" + "".join(
102
- c for c in base_id if c.isalnum() or c == "_"
103
- )
104
- else:
105
- # Fallback to hash if no title
106
- type_id = f"schema_{hash(str(schema))}"
107
+ # Fallback to object id if no title
108
+ type_id = f"schema_{schema_id}"
107
109
 
108
- # Check if we already have this type
110
+ # Check again with the generated type_id
109
111
  if type_id in existing_custom_types:
110
112
  return existing_custom_types[type_id]
111
113
 
112
- # Create properties from the schema
113
- properties = _schema_to_qtype_properties(
114
- schema, existing_custom_types, schema_name_map
115
- )
116
-
117
- # Create the custom type
118
- custom_type = CustomType(
114
+ # Create a placeholder to prevent infinite recursion
115
+ # This will be updated with properties below
116
+ placeholder = CustomType(
119
117
  id=type_id,
120
118
  description=schema.description
121
119
  or schema.title
122
120
  or "Generated from OpenAPI schema",
123
- properties=properties,
121
+ properties={}, # Empty initially
124
122
  )
125
123
 
126
- # Store it in the registry to prevent infinite recursion
127
- existing_custom_types[type_id] = custom_type
124
+ # Store it BEFORE processing properties to break circular references
125
+ existing_custom_types[type_id] = placeholder
128
126
 
129
- return custom_type
127
+ # Now process properties (which may reference back to this type)
128
+ properties = _schema_to_qtype_properties(
129
+ schema, existing_custom_types, schema_name_map
130
+ )
131
+
132
+ # Update the placeholder with actual properties
133
+ placeholder.properties = properties
134
+
135
+ return placeholder
130
136
 
131
137
 
132
138
  def _schema_to_qtype_type(
@@ -196,9 +202,9 @@ def create_tool_parameters_from_body(
196
202
  existing_custom_types: dict[str, CustomType],
197
203
  schema_name_map: dict[int, str],
198
204
  default_param_name: str,
199
- ) -> dict[str, ToolParameter]:
205
+ ) -> list[Variable]:
200
206
  """
201
- Convert an OpenAPI Response or RequestBody to a dictionary of ToolParameters.
207
+ Convert an OpenAPI Response or RequestBody to a list of Variables.
202
208
 
203
209
  If the body has only one content type with an Object schema, flatten its properties
204
210
  to individual parameters. Otherwise, create a single parameter with the body type.
@@ -210,18 +216,18 @@ def create_tool_parameters_from_body(
210
216
  default_param_name: Name to use for non-flattened parameter
211
217
 
212
218
  Returns:
213
- Dictionary of parameter name to ToolParameter objects
219
+ List of Variable objects
214
220
  """
215
221
  # Check if we have content to analyze
216
222
  if not hasattr(oas, "content") or not oas.content:
217
- return {}
223
+ return []
218
224
 
219
225
  content = oas.content[0]
220
226
  input_type = to_variable_type(
221
227
  content, existing_custom_types, schema_name_map
222
228
  )
223
229
 
224
- # Convert CustomType to string ID for ToolParameter
230
+ # Convert CustomType to string ID for Variable
225
231
  input_type_value = (
226
232
  input_type.id if isinstance(input_type, CustomType) else input_type
227
233
  )
@@ -234,7 +240,7 @@ def create_tool_parameters_from_body(
234
240
  custom_type = existing_custom_types[input_type.id]
235
241
 
236
242
  # Flatten the custom type properties to individual parameters
237
- flattened_parameters = {}
243
+ flattened_parameters = []
238
244
  for prop_name, prop_type_str in custom_type.properties.items():
239
245
  # Check if the property is optional (has '?' suffix)
240
246
  is_optional = prop_type_str.endswith("?")
@@ -242,8 +248,10 @@ def create_tool_parameters_from_body(
242
248
  prop_type_str.rstrip("?") if is_optional else prop_type_str
243
249
  )
244
250
 
245
- flattened_parameters[prop_name] = ToolParameter(
246
- type=clean_type, optional=is_optional
251
+ flattened_parameters.append(
252
+ Variable.model_construct(
253
+ id=prop_name, type=clean_type, optional=is_optional
254
+ )
247
255
  )
248
256
 
249
257
  # remove the type from existing_custom_types to avoid confusion
@@ -252,11 +260,11 @@ def create_tool_parameters_from_body(
252
260
  return flattened_parameters
253
261
 
254
262
  # If not flattening, create a single parameter (e.g., for simple types or arrays)
255
- return {
256
- default_param_name: ToolParameter(
257
- type=input_type_value, optional=False
263
+ return [
264
+ Variable.model_construct(
265
+ id=default_param_name, type=input_type_value, optional=False
258
266
  )
259
- }
267
+ ]
260
268
 
261
269
 
262
270
  def to_api_tool(
@@ -291,7 +299,7 @@ def to_api_tool(
291
299
  ).replace("\n", " ")
292
300
 
293
301
  # Process inputs from request body and parameters
294
- inputs = {}
302
+ inputs = []
295
303
  if operation.request_body and operation.request_body.content:
296
304
  # Create input parameters from request body using the new function
297
305
  input_params = create_tool_parameters_from_body(
@@ -300,27 +308,31 @@ def to_api_tool(
300
308
  schema_name_map,
301
309
  default_param_name="request",
302
310
  )
303
- inputs.update(input_params)
311
+ inputs.extend(input_params)
304
312
 
305
313
  # Add path and query parameters as inputs
306
- parameters = {}
314
+ parameters = []
307
315
  for param in operation.parameters:
308
316
  if param.schema:
309
317
  param_type = _schema_to_qtype_type(
310
318
  param.schema, existing_custom_types, schema_name_map
311
319
  )
312
- # Convert to appropriate type for ToolParameter
320
+ # Convert to appropriate type for Variable
313
321
  param_type_value = (
314
322
  param_type.id
315
323
  if isinstance(param_type, CustomType)
316
324
  else param_type
317
325
  )
318
- parameters[param.name] = ToolParameter(
319
- type=param_type_value, optional=not param.required
326
+ parameters.append(
327
+ Variable.model_construct(
328
+ id=param.name,
329
+ type=param_type_value,
330
+ optional=not param.required,
331
+ )
320
332
  )
321
333
 
322
334
  # Process outputs from responses
323
- outputs = {}
335
+ outputs = []
324
336
  # Find the success response (200-299 status codes) or default response
325
337
  success_response = next(
326
338
  (r for r in operation.responses if r.code and 200 <= r.code < 300),
@@ -333,9 +345,9 @@ def to_api_tool(
333
345
  success_response,
334
346
  existing_custom_types,
335
347
  schema_name_map,
336
- default_param_name="response",
348
+ default_param_name=f"{tool_id}_response",
337
349
  )
338
- outputs.update(output_params)
350
+ outputs.extend(output_params)
339
351
 
340
352
  return APITool(
341
353
  id=tool_id,
@@ -353,9 +365,6 @@ def to_api_tool(
353
365
  def to_authorization_provider(
354
366
  api_name: str, scheme_name: str, security: Security
355
367
  ) -> AuthProviderType:
356
- if security.scheme is None:
357
- raise ValueError("Security scheme is missing")
358
-
359
368
  match security.type:
360
369
  case SecurityType.API_KEY:
361
370
  return APIKeyAuthProvider(
@@ -364,6 +373,8 @@ def to_authorization_provider(
364
373
  host=None, # Will be set from base URL if available
365
374
  )
366
375
  case SecurityType.HTTP:
376
+ if security.scheme is None:
377
+ raise ValueError("HTTP security scheme is missing")
367
378
  if security.scheme == AuthenticationScheme.BEARER:
368
379
  return BearerTokenAuthProvider(
369
380
  id=f"{api_name}_{scheme_name}_{security.bearer_format or 'token'}",
@@ -452,11 +463,10 @@ def tools_from_api(
452
463
  existing_custom_types: dict[str, CustomType] = {}
453
464
  tools = []
454
465
 
455
- # Create a mapping from schema hash to their names in the OpenAPI spec
456
- # Note: We can't monkey-patch here since the openapi_parser duplicates instances in memory
457
- # if they are $ref'd in the content
466
+ # Create a mapping from schema id to their names in the OpenAPI spec
467
+ # Use id() instead of hash(str()) to avoid infinite recursion with circular refs
458
468
  schema_name_map: dict[int, str] = {
459
- hash(str(schema)): name.replace(" ", "-").replace("_", "-")
469
+ id(schema): name.replace(" ", "-").replace("_", "-")
460
470
  for name, schema in specification.schemas.items()
461
471
  }
462
472
 
@@ -10,7 +10,7 @@ from qtype.dsl.model import (
10
10
  CustomType,
11
11
  ListType,
12
12
  PythonFunctionTool,
13
- ToolParameter,
13
+ Variable,
14
14
  VariableType,
15
15
  )
16
16
 
@@ -43,14 +43,19 @@ def tools_from_module(
43
43
  f"No public functions found in module '{module_path}'"
44
44
  )
45
45
 
46
- custom_types: dict[str, CustomType] = {}
46
+ # Registry of actual Pydantic classes for validation
47
+ custom_type_registry: dict[str, Type[BaseModel]] = {}
48
+ # CustomType instances for YAML output
49
+ custom_type_models: dict[str, CustomType] = {}
47
50
 
48
51
  # Create Tool instances from functions
49
52
  tools = [
50
- _create_tool_from_function(func_name, func_info, custom_types)
53
+ _create_tool_from_function(
54
+ func_name, func_info, custom_type_registry, custom_type_models
55
+ )
51
56
  for func_name, func_info in functions.items()
52
57
  ]
53
- return (tools, list(custom_types.values()))
58
+ return (tools, list(custom_type_models.values()))
54
59
  except ImportError as e:
55
60
  raise ImportError(f"Cannot import module '{module_path}': {e}") from e
56
61
 
@@ -116,7 +121,8 @@ def _get_module_functions(
116
121
  def _create_tool_from_function(
117
122
  func_name: str,
118
123
  func_info: dict[str, Any],
119
- custom_types: dict[str, CustomType],
124
+ custom_type_registry: dict[str, Type[BaseModel]],
125
+ custom_type_models: dict[str, CustomType],
120
126
  ) -> PythonFunctionTool:
121
127
  """
122
128
  Convert function metadata into a Tool instance.
@@ -135,29 +141,38 @@ def _create_tool_from_function(
135
141
  else f"Function {func_name}"
136
142
  )
137
143
 
138
- # Create input parameters from function parameters
139
- inputs = {
140
- p["name"]: ToolParameter(
141
- type=_map_python_type_to_variable_type(p["type"], custom_types),
142
- optional=p["default"] != inspect.Parameter.empty,
144
+ # Create input parameters as list of Variables
145
+ inputs = [
146
+ Variable.model_validate(
147
+ {
148
+ "id": p["name"],
149
+ "type": _map_python_type_to_variable_type(
150
+ p["type"], custom_type_registry, custom_type_models
151
+ ),
152
+ "optional": p["default"] != inspect.Parameter.empty,
153
+ },
154
+ context={"custom_types": custom_type_registry},
143
155
  )
144
156
  for p in func_info["parameters"]
145
- }
146
-
147
- # # quick hack
148
- # for k, v in inputs.items():
149
- # if inspect.isclass(v.type) and issubclass(v.type, BaseModel):
150
- # v.type = str(v.type.__name__)
157
+ ]
151
158
 
152
159
  # Create output parameter based on return type
153
160
  tool_id = func_info["module"] + "." + func_name
154
161
 
155
162
  output_type = _map_python_type_to_variable_type(
156
- func_info["return_type"], custom_types
163
+ func_info["return_type"], custom_type_registry, custom_type_models
157
164
  )
158
165
 
159
- outputs = {"result": ToolParameter(type=output_type, optional=False)}
160
- # outputs['result'].type =
166
+ outputs = [
167
+ Variable.model_validate(
168
+ {
169
+ "id": f"{func_name}_result",
170
+ "type": output_type,
171
+ "optional": False,
172
+ },
173
+ context={"custom_types": custom_type_registry},
174
+ )
175
+ ]
161
176
 
162
177
  return PythonFunctionTool(
163
178
  id=tool_id,
@@ -172,7 +187,8 @@ def _create_tool_from_function(
172
187
 
173
188
  def _pydantic_to_custom_types(
174
189
  model_cls: Type[BaseModel],
175
- custom_types: dict[str, CustomType],
190
+ custom_type_registry: dict[str, Type[BaseModel]],
191
+ custom_type_models: dict[str, CustomType],
176
192
  ) -> str:
177
193
  """
178
194
  Converts a Pydantic BaseModel class into a QType CustomType.
@@ -184,15 +200,20 @@ def _pydantic_to_custom_types(
184
200
 
185
201
  Args:
186
202
  model_cls: The Pydantic model class to convert.
203
+ custom_type_registry: Registry of actual Pydantic classes for validation
204
+ custom_type_models: Dictionary of CustomType models for YAML output
187
205
 
188
206
  Returns:
189
- A dictionary mapping field names to their corresponding CustomType definitions.
207
+ The model name as a string type reference
190
208
  """
191
209
  properties = {}
192
210
  model_name = model_cls.__name__
193
- if model_name in custom_types:
211
+ if model_name in custom_type_registry:
194
212
  return model_name # Already processed
195
213
 
214
+ # Register the actual class for validation
215
+ custom_type_registry[model_name] = model_cls
216
+
196
217
  for field_name, field_info in model_cls.model_fields.items():
197
218
  # Use the annotation (the type hint) for the field
198
219
  field_type = field_info.annotation
@@ -202,22 +223,27 @@ def _pydantic_to_custom_types(
202
223
  )
203
224
  elif get_origin(field_type) is Union:
204
225
  # Assume the union means it's optional
226
+ # TODO: support proper unions
205
227
  field_type = [
206
228
  t for t in get_args(field_type) if t is not type(None)
207
229
  ][0]
208
- rv = _map_python_type_to_type_str(field_type, custom_types)
230
+ rv = _map_python_type_to_type_str(
231
+ field_type, custom_type_registry, custom_type_models
232
+ )
209
233
  properties[field_name] = f"{rv}?"
210
234
  elif get_origin(field_type) is list:
211
235
  inner_type = get_args(field_type)[0]
212
- rv = _map_python_type_to_type_str(inner_type, custom_types)
236
+ rv = _map_python_type_to_type_str(
237
+ inner_type, custom_type_registry, custom_type_models
238
+ )
213
239
  properties[field_name] = f"list[{rv}]"
214
240
  else:
215
241
  properties[field_name] = _map_python_type_to_type_str(
216
- field_type, custom_types
242
+ field_type, custom_type_registry, custom_type_models
217
243
  )
218
244
 
219
- # Add the custom type to the list
220
- custom_types[model_name] = CustomType(
245
+ # Add the CustomType model for YAML output
246
+ custom_type_models[model_name] = CustomType(
221
247
  id=model_name,
222
248
  properties=properties,
223
249
  description=model_cls.__doc__ or f"Custom type for {model_name}",
@@ -227,7 +253,8 @@ def _pydantic_to_custom_types(
227
253
 
228
254
  def _map_python_type_to_variable_type(
229
255
  python_type: Any,
230
- custom_types: dict[str, CustomType],
256
+ custom_type_registry: dict[str, Type[BaseModel]],
257
+ custom_type_models: dict[str, CustomType],
231
258
  ) -> str | VariableType:
232
259
  """
233
260
  Map Python type annotations to QType VariableType.
@@ -248,7 +275,9 @@ def _map_python_type_to_variable_type(
248
275
  element_type_annotation = args[0]
249
276
  # Recursively map the element type
250
277
  element_type = _map_python_type_to_variable_type(
251
- element_type_annotation, custom_types
278
+ element_type_annotation,
279
+ custom_type_registry,
280
+ custom_type_models,
252
281
  )
253
282
  # Support lists of both primitive types and custom types
254
283
  if isinstance(element_type, PrimitiveTypeEnum):
@@ -281,7 +310,9 @@ def _map_python_type_to_variable_type(
281
310
  return python_type.__name__
282
311
  elif inspect.isclass(python_type) and issubclass(python_type, BaseModel):
283
312
  # If it's a Pydantic model, create or retrieve its CustomType definition
284
- return _pydantic_to_custom_types(python_type, custom_types)
313
+ return _pydantic_to_custom_types(
314
+ python_type, custom_type_registry, custom_type_models
315
+ )
285
316
  raise ValueError(
286
317
  f"Unsupported Python type '{python_type}' for VariableType mapping"
287
318
  )
@@ -289,9 +320,12 @@ def _map_python_type_to_variable_type(
289
320
 
290
321
  def _map_python_type_to_type_str(
291
322
  python_type: Any,
292
- custom_types: dict[str, CustomType],
323
+ custom_type_registry: dict[str, Type[BaseModel]],
324
+ custom_type_models: dict[str, CustomType],
293
325
  ) -> str:
294
- var_type = _map_python_type_to_variable_type(python_type, custom_types)
326
+ var_type = _map_python_type_to_variable_type(
327
+ python_type, custom_type_registry, custom_type_models
328
+ )
295
329
  if isinstance(var_type, PrimitiveTypeEnum):
296
330
  return var_type.value
297
331
  elif inspect.isclass(python_type):
qtype/base/types.py CHANGED
@@ -20,7 +20,7 @@ from typing import (
20
20
 
21
21
  from pydantic import BaseModel
22
22
  from pydantic import ConfigDict as PydanticConfigDict
23
- from pydantic import Field, model_validator
23
+ from pydantic import Field, model_serializer, model_validator
24
24
 
25
25
  # JSON-serializable value types
26
26
  JSONValue = Union[
@@ -73,6 +73,11 @@ class Reference(BaseModel, Generic[ReferenceT]):
73
73
 
74
74
  ref: str = Field(..., alias="$ref")
75
75
 
76
+ @model_serializer(mode="plain")
77
+ def serialize_as_string(self) -> str:
78
+ """Serialize Reference as a plain string (just the ID)."""
79
+ return self.ref
80
+
76
81
 
77
82
  def _contains_reference_and_str(type_hint: Any) -> bool:
78
83
  """Check if type contains both Reference and str in a union."""
qtype/commands/convert.py CHANGED
@@ -13,7 +13,7 @@ from qtype.dsl.model import Application, Document, ToolList
13
13
  logger = logging.getLogger(__name__)
14
14
 
15
15
 
16
- def _convert_to_yaml(doc: Application | ToolList) -> str:
16
+ def convert_to_yaml(doc: Application | ToolList) -> str:
17
17
  """Convert a document to YAML format."""
18
18
  from pydantic_yaml import to_yaml_str
19
19
 
@@ -23,9 +23,6 @@ def _convert_to_yaml(doc: Application | ToolList) -> str:
23
23
  else:
24
24
  wrapped = doc
25
25
 
26
- import pprint
27
-
28
- pprint.pprint(wrapped)
29
26
  # NOTE: We use exclude_none but NOT exclude_unset because discriminator
30
27
  # fields like 'type' have default values and must be included in output
31
28
  return to_yaml_str(wrapped, exclude_none=True)
@@ -54,7 +51,7 @@ def convert_api(args: argparse.Namespace) -> None:
54
51
  auths=auths,
55
52
  )
56
53
  # Convert to YAML format
57
- content = _convert_to_yaml(doc)
54
+ content = convert_to_yaml(doc)
58
55
 
59
56
  # Write to file or stdout
60
57
  if args.output:
@@ -96,7 +93,7 @@ def convert_module(args: argparse.Namespace) -> None:
96
93
  )
97
94
 
98
95
  # Convert to YAML format
99
- content = _convert_to_yaml(doc)
96
+ content = convert_to_yaml(doc)
100
97
 
101
98
  # Write to file or stdout
102
99
  if args.output: