qtype 0.1.12__py3-none-any.whl → 0.1.14__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 (272) 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/base/__init__.py +8 -2
  6. qtype/base/logging.py +0 -17
  7. qtype/base/resources.py +193 -0
  8. qtype/cli.py +5 -9
  9. qtype/commands/generate.py +95 -7
  10. qtype/commands/run.py +153 -54
  11. qtype/docs/.pages +8 -0
  12. {docs → qtype/docs}/Concepts/mental-model-and-philosophy.md +1 -1
  13. qtype/docs/Contributing/.pages +4 -0
  14. {docs → qtype/docs}/Contributing/index.md +8 -1
  15. {docs → qtype/docs}/Gallery/dataflow_pipelines.md +18 -4
  16. qtype/docs/Gallery/recipe_chatbot.md +103 -0
  17. qtype/docs/Gallery/recipe_chatbot.mermaid +62 -0
  18. qtype/docs/Gallery/recipe_chatbot.png +0 -0
  19. {docs → qtype/docs}/Gallery/research_assistant.md +4 -5
  20. {docs → qtype/docs}/Gallery/simple_chatbot.md +3 -1
  21. {docs → qtype/docs}/How To/Authentication/configure_aws_authentication.md +2 -2
  22. {docs → qtype/docs}/How To/Authentication/use_api_key_authentication.md +2 -2
  23. {docs → qtype/docs}/How To/Command Line Usage/load_multiple_inputs_from_files.md +24 -9
  24. {docs → qtype/docs}/How To/Command Line Usage/pass_inputs_on_the_cli.md +7 -4
  25. {docs → qtype/docs}/How To/Command Line Usage/serve_with_auto_reload.md +3 -2
  26. {docs → qtype/docs}/How To/Data Processing/adjust_concurrency.md +3 -4
  27. {docs → qtype/docs}/How To/Data Processing/cache_step_results.md +2 -2
  28. {docs → qtype/docs}/How To/Data Processing/decode_json_xml.md +1 -1
  29. {docs → qtype/docs}/How To/Data Processing/explode_collections.md +2 -2
  30. {docs → qtype/docs}/How To/Data Processing/gather_results.md +4 -4
  31. qtype/docs/How To/Data Processing/invoke_other_flows.md +71 -0
  32. qtype/docs/How To/Data Processing/load_data_from_athena.md +49 -0
  33. qtype/docs/How To/Data Processing/load_documents.md +74 -0
  34. qtype/docs/How To/Data Processing/read_data_from_files.md +61 -0
  35. {docs → qtype/docs}/How To/Data Processing/read_sql_databases.md +4 -3
  36. {docs → qtype/docs}/How To/Data Processing/write_data_to_file.md +1 -2
  37. {docs → qtype/docs}/How To/Invoke Models/call_large_language_models.md +1 -1
  38. {docs → qtype/docs}/How To/Invoke Models/create_embeddings.md +1 -1
  39. {docs → qtype/docs}/How To/Invoke Models/reuse_prompts_with_templates.md +2 -3
  40. {docs → qtype/docs}/How To/Language Features/include_raw_text_from_other_files.md +2 -1
  41. {docs → qtype/docs}/How To/Language Features/reference_entities_by_id.md +2 -2
  42. qtype/docs/How To/Language Features/use_agent_skills.md +29 -0
  43. {docs → qtype/docs}/How To/Language Features/use_environment_variables.md +2 -1
  44. qtype/docs/How To/Language Features/use_optional_variables.md +42 -0
  45. {docs → qtype/docs}/How To/Language Features/use_qtype_mcp.md +4 -4
  46. {docs → qtype/docs}/How To/Observability & Debugging/trace_calls_with_open_telemetry.md +1 -1
  47. {docs → qtype/docs}/How To/Observability & Debugging/validate_qtype_yaml.md +3 -2
  48. {docs → qtype/docs}/How To/Observability & Debugging/visualize_application_architecture.md +1 -1
  49. {docs → qtype/docs}/How To/Qtype Server/serve_flows_as_apis.md +3 -3
  50. {docs → qtype/docs}/How To/Qtype Server/serve_flows_as_ui.md +2 -3
  51. {docs → qtype/docs}/How To/Qtype Server/use_conversational_interfaces.md +1 -4
  52. {docs → qtype/docs}/How To/Qtype Server/use_variables_with_ui_hints.md +3 -2
  53. {docs → qtype/docs}/How To/Tools & Integration/bind_tool_inputs_and_outputs.md +1 -2
  54. {docs → qtype/docs}/How To/Tools & Integration/create_tools_from_openapi_specifications.md +10 -14
  55. {docs → qtype/docs}/How To/Tools & Integration/create_tools_from_python_modules.md +5 -8
  56. {docs → qtype/docs}/Reference/cli.md +16 -17
  57. qtype/docs/Tutorials/.pages +1 -0
  58. {docs → qtype/docs}/Tutorials/01-first-qtype-application.md +4 -3
  59. {docs → qtype/docs}/Tutorials/02-conversational-chatbot.md +3 -3
  60. {docs → qtype/docs}/Tutorials/03-structured-data.md +10 -11
  61. {docs → qtype/docs}/Tutorials/04-tools-and-function-calling.md +13 -20
  62. {docs → qtype/docs}/components/APITool.md +1 -1
  63. qtype/docs/components/Aggregate.md +7 -0
  64. qtype/docs/components/Collect.md +6 -0
  65. qtype/docs/components/Construct.md +6 -0
  66. {docs → qtype/docs}/components/DocumentEmbedder.md +0 -1
  67. {docs → qtype/docs}/components/DocumentSplitter.md +0 -1
  68. qtype/docs/components/Explode.md +5 -0
  69. {docs → qtype/docs}/components/FieldExtractor.md +2 -1
  70. qtype/docs/components/InvokeFlow.md +8 -0
  71. qtype/docs/components/InvokeTool.md +8 -0
  72. {docs → qtype/docs}/components/PrimitiveTypeEnum.md +0 -1
  73. {docs → qtype/docs}/components/Source.md +0 -1
  74. {docs → qtype/docs}/components/Step.md +0 -1
  75. {docs → qtype/docs}/components/Tool.md +2 -2
  76. {docs → qtype/docs}/components/Variable.md +2 -0
  77. qtype/docs/legacy_how_tos/.pages +6 -0
  78. qtype/docs/skills/architect/SKILL.md +188 -0
  79. qtype/docs/skills/architect/references/cheatsheet.md +198 -0
  80. qtype/docs/skills/architect/references/patterns.md +29 -0
  81. qtype/docs/stylesheets/extra.css +27 -0
  82. qtype/dsl/linker.py +8 -0
  83. qtype/dsl/model.py +177 -84
  84. qtype/examples/conversational_ai/simple_chatbot_with_auth.qtype.yaml +48 -0
  85. qtype/examples/data_processing/athena_query.qtype.yaml +56 -0
  86. qtype/examples/data_processing/batch_inputs.csv +5 -0
  87. qtype/examples/data_processing/create_sample_db.py +129 -0
  88. qtype/examples/data_processing/invoke_other_flows.qtype.yaml +98 -0
  89. qtype/examples/data_processing/load_documents.qtype.yaml +31 -0
  90. qtype/examples/data_processing/reviews.db +0 -0
  91. qtype/examples/data_processing/sample_article.txt +1 -0
  92. qtype/examples/data_processing/sample_documents.jsonl +5 -0
  93. qtype/examples/invoke_models/invoke_embedding_aws.qtype.yaml +45 -0
  94. qtype/examples/language_features/optional_variables.qtype.yaml +32 -0
  95. qtype/examples/language_features/story_prompt.txt +6 -0
  96. qtype/examples/legacy/data/customers.csv +6 -0
  97. qtype/examples/legacy/echo/readme.md +29 -0
  98. qtype/examples/legacy/qtype_plugin_example.py +51 -0
  99. qtype/examples/legacy/sample_data.txt +43 -0
  100. qtype/examples/legacy/vertex/README.md +11 -0
  101. qtype/examples/rag/recipe_chatbot.qtype.yaml +216 -0
  102. qtype/examples/research_assistant/tavily.qtype.yaml +216 -0
  103. {examples → qtype/examples}/tutorials/03_structured_data.qtype.yaml +2 -2
  104. {examples → qtype/examples}/tutorials/04_tools_and_function_calling.qtype.yaml +5 -5
  105. qtype/interpreter/auth/aws.py +94 -17
  106. qtype/interpreter/auth/generic.py +11 -12
  107. qtype/interpreter/base/secrets.py +4 -2
  108. qtype/interpreter/base/stream_emitter.py +19 -13
  109. qtype/interpreter/conversions.py +15 -14
  110. qtype/interpreter/converters.py +142 -26
  111. qtype/interpreter/executors/agent_executor.py +2 -3
  112. qtype/interpreter/executors/aggregate_executor.py +3 -4
  113. qtype/interpreter/executors/bedrock_reranker_executor.py +17 -28
  114. qtype/interpreter/executors/construct_executor.py +15 -15
  115. qtype/interpreter/executors/doc_to_text_executor.py +1 -3
  116. qtype/interpreter/executors/document_embedder_executor.py +1 -12
  117. qtype/interpreter/executors/field_extractor_executor.py +13 -12
  118. qtype/interpreter/executors/file_source_executor.py +18 -31
  119. qtype/interpreter/executors/invoke_embedding_executor.py +24 -37
  120. qtype/interpreter/executors/invoke_flow_executor.py +2 -2
  121. qtype/interpreter/executors/invoke_tool_executor.py +19 -18
  122. qtype/interpreter/executors/llm_inference_executor.py +18 -18
  123. qtype/interpreter/executors/prompt_template_executor.py +1 -3
  124. qtype/interpreter/executors/sql_source_executor.py +6 -2
  125. qtype/interpreter/flow.py +11 -1
  126. qtype/interpreter/tools/function_tool_helper.py +11 -10
  127. qtype/interpreter/types.py +89 -4
  128. qtype/interpreter/typing.py +31 -32
  129. qtype/mcp/server.py +194 -86
  130. {schema → qtype/schema}/qtype.schema.json +77 -79
  131. qtype/semantic/checker.py +19 -0
  132. qtype/semantic/generate.py +3 -6
  133. qtype/semantic/model.py +26 -33
  134. qtype/semantic/resolver.py +7 -0
  135. qtype/semantic/visualize.py +18 -6
  136. {qtype-0.1.12.dist-info → qtype-0.1.14.dist-info}/METADATA +47 -46
  137. qtype-0.1.14.dist-info/RECORD +361 -0
  138. {qtype-0.1.12.dist-info → qtype-0.1.14.dist-info}/WHEEL +1 -2
  139. docs/How To/Data Processing/read_data_from_files.md +0 -35
  140. docs/components/Aggregate.md +0 -8
  141. docs/components/InvokeFlow.md +0 -8
  142. docs/components/InvokeTool.md +0 -8
  143. docs/components/ToolParameter.md +0 -6
  144. examples/research_assistant/tavily.qtype.yaml +0 -289
  145. qtype/application/facade.py +0 -177
  146. qtype-0.1.12.dist-info/RECORD +0 -325
  147. qtype-0.1.12.dist-info/top_level.txt +0 -1
  148. {docs → qtype/docs}/Contributing/roadmap.md +0 -0
  149. {docs → qtype/docs}/Decisions/ADR-001-Chat-vs-Completion-Endpoint-Features.md +0 -0
  150. {docs → qtype/docs}/Gallery/dataflow_pipelines.mermaid +0 -0
  151. {docs → qtype/docs}/Gallery/research_assistant.mermaid +0 -0
  152. {docs → qtype/docs}/Gallery/simple_chatbot.mermaid +0 -0
  153. {docs → qtype/docs}/How To/Language Features/include_qtype_yaml.md +0 -0
  154. {docs → qtype/docs}/How To/Observability & Debugging/visualize_example.mermaid +0 -0
  155. {docs → qtype/docs}/How To/Qtype Server/flow_as_ui.png +0 -0
  156. {docs → qtype/docs}/Reference/plugins.md +0 -0
  157. {docs → qtype/docs}/Reference/semantic-validation-rules.md +0 -0
  158. {docs → qtype/docs}/Tutorials/example_chat.png +0 -0
  159. {docs → qtype/docs}/Tutorials/index.md +0 -0
  160. {docs → qtype/docs}/components/APIKeyAuthProvider.md +0 -0
  161. {docs → qtype/docs}/components/AWSAuthProvider.md +0 -0
  162. {docs → qtype/docs}/components/AWSSecretManager.md +0 -0
  163. {docs → qtype/docs}/components/Agent.md +0 -0
  164. {docs → qtype/docs}/components/AggregateStats.md +0 -0
  165. {docs → qtype/docs}/components/Application.md +0 -0
  166. {docs → qtype/docs}/components/AuthorizationProvider.md +0 -0
  167. {docs → qtype/docs}/components/AuthorizationProviderList.md +0 -0
  168. {docs → qtype/docs}/components/BearerTokenAuthProvider.md +0 -0
  169. {docs → qtype/docs}/components/BedrockReranker.md +0 -0
  170. {docs → qtype/docs}/components/ChatContent.md +0 -0
  171. {docs → qtype/docs}/components/ChatMessage.md +0 -0
  172. {docs → qtype/docs}/components/ConstantPath.md +0 -0
  173. {docs → qtype/docs}/components/CustomType.md +0 -0
  174. {docs → qtype/docs}/components/Decoder.md +0 -0
  175. {docs → qtype/docs}/components/DecoderFormat.md +0 -0
  176. {docs → qtype/docs}/components/DocToTextConverter.md +0 -0
  177. {docs → qtype/docs}/components/Document.md +0 -0
  178. {docs → qtype/docs}/components/DocumentIndex.md +0 -0
  179. {docs → qtype/docs}/components/DocumentSearch.md +0 -0
  180. {docs → qtype/docs}/components/DocumentSource.md +0 -0
  181. {docs → qtype/docs}/components/Echo.md +0 -0
  182. {docs → qtype/docs}/components/Embedding.md +0 -0
  183. {docs → qtype/docs}/components/EmbeddingModel.md +0 -0
  184. {docs → qtype/docs}/components/FileSource.md +0 -0
  185. {docs → qtype/docs}/components/FileWriter.md +0 -0
  186. {docs → qtype/docs}/components/Flow.md +0 -0
  187. {docs → qtype/docs}/components/FlowInterface.md +0 -0
  188. {docs → qtype/docs}/components/Index.md +0 -0
  189. {docs → qtype/docs}/components/IndexUpsert.md +0 -0
  190. {docs → qtype/docs}/components/InvokeEmbedding.md +0 -0
  191. {docs → qtype/docs}/components/LLMInference.md +0 -0
  192. {docs → qtype/docs}/components/ListType.md +0 -0
  193. {docs → qtype/docs}/components/Memory.md +0 -0
  194. {docs → qtype/docs}/components/MessageRole.md +0 -0
  195. {docs → qtype/docs}/components/Model.md +0 -0
  196. {docs → qtype/docs}/components/ModelList.md +0 -0
  197. {docs → qtype/docs}/components/OAuth2AuthProvider.md +0 -0
  198. {docs → qtype/docs}/components/PromptTemplate.md +0 -0
  199. {docs → qtype/docs}/components/PythonFunctionTool.md +0 -0
  200. {docs → qtype/docs}/components/RAGChunk.md +0 -0
  201. {docs → qtype/docs}/components/RAGDocument.md +0 -0
  202. {docs → qtype/docs}/components/RAGSearchResult.md +0 -0
  203. {docs → qtype/docs}/components/Reranker.md +0 -0
  204. {docs → qtype/docs}/components/SQLSource.md +0 -0
  205. {docs → qtype/docs}/components/Search.md +0 -0
  206. {docs → qtype/docs}/components/SearchResult.md +0 -0
  207. {docs → qtype/docs}/components/SecretManager.md +0 -0
  208. {docs → qtype/docs}/components/SecretReference.md +0 -0
  209. {docs → qtype/docs}/components/TelemetrySink.md +0 -0
  210. {docs → qtype/docs}/components/ToolList.md +0 -0
  211. {docs → qtype/docs}/components/TypeList.md +0 -0
  212. {docs → qtype/docs}/components/VariableList.md +0 -0
  213. {docs → qtype/docs}/components/VectorIndex.md +0 -0
  214. {docs → qtype/docs}/components/VectorSearch.md +0 -0
  215. {docs → qtype/docs}/components/VertexAuthProvider.md +0 -0
  216. {docs → qtype/docs}/components/Writer.md +0 -0
  217. {docs → qtype/docs}/example_ui.png +0 -0
  218. {docs → qtype/docs}/index.md +0 -0
  219. {docs → qtype/docs}/legacy_how_tos/Configuration/modular-yaml.md +0 -0
  220. {docs → qtype/docs}/legacy_how_tos/Configuration/phoenix_projects.png +0 -0
  221. {docs → qtype/docs}/legacy_how_tos/Configuration/phoenix_traces.png +0 -0
  222. {docs → qtype/docs}/legacy_how_tos/Configuration/reference-by-id.md +0 -0
  223. {docs → qtype/docs}/legacy_how_tos/Configuration/telemetry-setup.md +0 -0
  224. {docs → qtype/docs}/legacy_how_tos/Data Types/custom-types.md +0 -0
  225. {docs → qtype/docs}/legacy_how_tos/Data Types/domain-types.md +0 -0
  226. {docs → qtype/docs}/legacy_how_tos/Debugging/visualize-apps.md +0 -0
  227. {docs → qtype/docs}/legacy_how_tos/Tools/api-tools.md +0 -0
  228. {docs → qtype/docs}/legacy_how_tos/Tools/python-tools.md +0 -0
  229. {examples → qtype/examples}/authentication/aws_authentication.qtype.yaml +0 -0
  230. {examples → qtype/examples}/conversational_ai/hello_world_chat.qtype.yaml +0 -0
  231. {examples → qtype/examples}/conversational_ai/simple_chatbot.qtype.yaml +0 -0
  232. {examples → qtype/examples}/data_processing/batch_processing.qtype.yaml +0 -0
  233. {examples → qtype/examples}/data_processing/cache_step_results.qtype.yaml +0 -0
  234. {examples → qtype/examples}/data_processing/collect_results.qtype.yaml +0 -0
  235. {examples → qtype/examples}/data_processing/dataflow_pipelines.qtype.yaml +0 -0
  236. {examples → qtype/examples}/data_processing/decode_json.qtype.yaml +0 -0
  237. {examples → qtype/examples}/data_processing/explode_items.qtype.yaml +0 -0
  238. {examples → qtype/examples}/data_processing/read_file.qtype.yaml +0 -0
  239. {examples → qtype/examples}/invoke_models/create_embeddings.qtype.yaml +0 -0
  240. {examples → qtype/examples}/invoke_models/simple_llm_call.qtype.yaml +0 -0
  241. {examples → qtype/examples}/language_features/include_raw.qtype.yaml +0 -0
  242. {examples → qtype/examples}/language_features/ui_hints.qtype.yaml +0 -0
  243. {examples → qtype/examples}/legacy/bedrock/data_analysis_with_telemetry.qtype.yaml +0 -0
  244. {examples → qtype/examples}/legacy/bedrock/hello_world.qtype.yaml +0 -0
  245. {examples → qtype/examples}/legacy/bedrock/hello_world_chat.qtype.yaml +0 -0
  246. {examples → qtype/examples}/legacy/bedrock/hello_world_chat_with_telemetry.qtype.yaml +0 -0
  247. {examples → qtype/examples}/legacy/bedrock/hello_world_chat_with_thinking.qtype.yaml +0 -0
  248. {examples → qtype/examples}/legacy/bedrock/hello_world_completion.qtype.yaml +0 -0
  249. {examples → qtype/examples}/legacy/bedrock/hello_world_completion_with_auth.qtype.yaml +0 -0
  250. {examples → qtype/examples}/legacy/bedrock/simple_agent_chat.qtype.yaml +0 -0
  251. {examples → qtype/examples}/legacy/chat_with_langfuse.qtype.yaml +0 -0
  252. {examples → qtype/examples}/legacy/data_processor.qtype.yaml +0 -0
  253. {examples → qtype/examples}/legacy/echo/debug_example.qtype.yaml +0 -0
  254. {examples → qtype/examples}/legacy/echo/prompt.qtype.yaml +0 -0
  255. {examples → qtype/examples}/legacy/echo/test.qtype.yaml +0 -0
  256. {examples → qtype/examples}/legacy/echo/video.qtype.yaml +0 -0
  257. {examples → qtype/examples}/legacy/field_extractor_example.qtype.yaml +0 -0
  258. {examples → qtype/examples}/legacy/multi_flow_example.qtype.yaml +0 -0
  259. {examples → qtype/examples}/legacy/openai/hello_world_chat.qtype.yaml +0 -0
  260. {examples → qtype/examples}/legacy/openai/hello_world_chat_with_telemetry.qtype.yaml +0 -0
  261. {examples → qtype/examples}/legacy/rag.qtype.yaml +0 -0
  262. {examples → qtype/examples}/legacy/time_utilities.qtype.yaml +0 -0
  263. {examples → qtype/examples}/legacy/vertex/hello_world_chat.qtype.yaml +0 -0
  264. {examples → qtype/examples}/legacy/vertex/hello_world_completion.qtype.yaml +0 -0
  265. {examples → qtype/examples}/legacy/vertex/hello_world_completion_with_auth.qtype.yaml +0 -0
  266. {examples → qtype/examples}/observability_debugging/trace_with_opentelemetry.qtype.yaml +0 -0
  267. {examples → qtype/examples}/research_assistant/research_assistant.qtype.yaml +0 -0
  268. {examples → qtype/examples}/research_assistant/tavily.oas.yaml +0 -0
  269. {examples → qtype/examples}/tutorials/01_hello_world.qtype.yaml +0 -0
  270. {examples → qtype/examples}/tutorials/02_conversational_chat.qtype.yaml +0 -0
  271. {qtype-0.1.12.dist-info → qtype-0.1.14.dist-info}/entry_points.txt +0 -0
  272. {qtype-0.1.12.dist-info → qtype-0.1.14.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
 
@@ -202,9 +202,9 @@ def create_tool_parameters_from_body(
202
202
  existing_custom_types: dict[str, CustomType],
203
203
  schema_name_map: dict[int, str],
204
204
  default_param_name: str,
205
- ) -> dict[str, ToolParameter]:
205
+ ) -> list[Variable]:
206
206
  """
207
- Convert an OpenAPI Response or RequestBody to a dictionary of ToolParameters.
207
+ Convert an OpenAPI Response or RequestBody to a list of Variables.
208
208
 
209
209
  If the body has only one content type with an Object schema, flatten its properties
210
210
  to individual parameters. Otherwise, create a single parameter with the body type.
@@ -216,18 +216,18 @@ def create_tool_parameters_from_body(
216
216
  default_param_name: Name to use for non-flattened parameter
217
217
 
218
218
  Returns:
219
- Dictionary of parameter name to ToolParameter objects
219
+ List of Variable objects
220
220
  """
221
221
  # Check if we have content to analyze
222
222
  if not hasattr(oas, "content") or not oas.content:
223
- return {}
223
+ return []
224
224
 
225
225
  content = oas.content[0]
226
226
  input_type = to_variable_type(
227
227
  content, existing_custom_types, schema_name_map
228
228
  )
229
229
 
230
- # Convert CustomType to string ID for ToolParameter
230
+ # Convert CustomType to string ID for Variable
231
231
  input_type_value = (
232
232
  input_type.id if isinstance(input_type, CustomType) else input_type
233
233
  )
@@ -240,7 +240,7 @@ def create_tool_parameters_from_body(
240
240
  custom_type = existing_custom_types[input_type.id]
241
241
 
242
242
  # Flatten the custom type properties to individual parameters
243
- flattened_parameters = {}
243
+ flattened_parameters = []
244
244
  for prop_name, prop_type_str in custom_type.properties.items():
245
245
  # Check if the property is optional (has '?' suffix)
246
246
  is_optional = prop_type_str.endswith("?")
@@ -248,8 +248,10 @@ def create_tool_parameters_from_body(
248
248
  prop_type_str.rstrip("?") if is_optional else prop_type_str
249
249
  )
250
250
 
251
- flattened_parameters[prop_name] = ToolParameter(
252
- 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
+ )
253
255
  )
254
256
 
255
257
  # remove the type from existing_custom_types to avoid confusion
@@ -258,11 +260,11 @@ def create_tool_parameters_from_body(
258
260
  return flattened_parameters
259
261
 
260
262
  # If not flattening, create a single parameter (e.g., for simple types or arrays)
261
- return {
262
- default_param_name: ToolParameter(
263
- type=input_type_value, optional=False
263
+ return [
264
+ Variable.model_construct(
265
+ id=default_param_name, type=input_type_value, optional=False
264
266
  )
265
- }
267
+ ]
266
268
 
267
269
 
268
270
  def to_api_tool(
@@ -297,7 +299,7 @@ def to_api_tool(
297
299
  ).replace("\n", " ")
298
300
 
299
301
  # Process inputs from request body and parameters
300
- inputs = {}
302
+ inputs = []
301
303
  if operation.request_body and operation.request_body.content:
302
304
  # Create input parameters from request body using the new function
303
305
  input_params = create_tool_parameters_from_body(
@@ -306,27 +308,31 @@ def to_api_tool(
306
308
  schema_name_map,
307
309
  default_param_name="request",
308
310
  )
309
- inputs.update(input_params)
311
+ inputs.extend(input_params)
310
312
 
311
313
  # Add path and query parameters as inputs
312
- parameters = {}
314
+ parameters = []
313
315
  for param in operation.parameters:
314
316
  if param.schema:
315
317
  param_type = _schema_to_qtype_type(
316
318
  param.schema, existing_custom_types, schema_name_map
317
319
  )
318
- # Convert to appropriate type for ToolParameter
320
+ # Convert to appropriate type for Variable
319
321
  param_type_value = (
320
322
  param_type.id
321
323
  if isinstance(param_type, CustomType)
322
324
  else param_type
323
325
  )
324
- parameters[param.name] = ToolParameter(
325
- 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
+ )
326
332
  )
327
333
 
328
334
  # Process outputs from responses
329
- outputs = {}
335
+ outputs = []
330
336
  # Find the success response (200-299 status codes) or default response
331
337
  success_response = next(
332
338
  (r for r in operation.responses if r.code and 200 <= r.code < 300),
@@ -339,9 +345,9 @@ def to_api_tool(
339
345
  success_response,
340
346
  existing_custom_types,
341
347
  schema_name_map,
342
- default_param_name="response",
348
+ default_param_name=f"{tool_id}_response",
343
349
  )
344
- outputs.update(output_params)
350
+ outputs.extend(output_params)
345
351
 
346
352
  return APITool(
347
353
  id=tool_id,
@@ -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/__init__.py CHANGED
@@ -3,12 +3,18 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from .exceptions import QTypeError, ValidationError
6
- from .logging import get_logger
6
+ from .resources import (
7
+ ResourceDirectory,
8
+ get_docs_resource,
9
+ get_examples_resource,
10
+ )
7
11
  from .types import JSONValue
8
12
 
9
13
  __all__ = [
10
14
  "QTypeError",
11
15
  "ValidationError",
12
- "get_logger",
13
16
  "JSONValue",
17
+ "ResourceDirectory",
18
+ "get_docs_resource",
19
+ "get_examples_resource",
14
20
  ]
qtype/base/logging.py CHANGED
@@ -5,23 +5,6 @@ from __future__ import annotations
5
5
  import logging
6
6
 
7
7
 
8
- def get_logger(name: str) -> logging.Logger:
9
- """Get a logger with the given name and consistent formatting."""
10
- logger = logging.getLogger(f"qtype.{name}")
11
-
12
- # Only configure if not already configured
13
- if not logger.handlers:
14
- handler = logging.StreamHandler()
15
- formatter = logging.Formatter(
16
- "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
17
- )
18
- handler.setFormatter(formatter)
19
- logger.addHandler(handler)
20
- logger.setLevel(logging.INFO)
21
-
22
- return logger
23
-
24
-
25
8
  def configure_logging(
26
9
  level: str = "INFO", format_string: str | None = None
27
10
  ) -> None:
@@ -0,0 +1,193 @@
1
+ """Resource directory access utilities for QType package resources."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from functools import lru_cache
7
+ from importlib.resources import files
8
+ from pathlib import Path
9
+
10
+ # Regex for pymdownx snippets: --8<-- "path/to/file"
11
+ SNIPPET_REGEX = re.compile(r'--8<--\s+"([^"]+)"')
12
+
13
+
14
+ class ResourceDirectory:
15
+ """Abstraction for accessing resource directories (docs, examples, etc.)."""
16
+
17
+ def __init__(
18
+ self, name: str, file_extension: str, resolve_snippets: bool = False
19
+ ):
20
+ """Initialize a resource directory.
21
+
22
+ Args:
23
+ name: Directory name (e.g., "docs", "examples")
24
+ file_extension: File extension to search for (e.g., ".md", ".yaml")
25
+ resolve_snippets: Whether to resolve MkDocs snippets in file content
26
+ """
27
+ self.name = name
28
+ self.file_extension = file_extension
29
+ self.resolve_snippets = resolve_snippets
30
+ self._path_cache: Path | None = None
31
+
32
+ def get_path(self) -> Path:
33
+ """Get the path to this resource directory.
34
+
35
+ Returns:
36
+ Path to the resource directory, trying installed package first,
37
+ then falling back to development path.
38
+ """
39
+ if self._path_cache is not None:
40
+ return self._path_cache
41
+
42
+ try:
43
+ # Try to get from installed package
44
+ resource_root = files("qtype") / self.name
45
+ # Check if it exists by trying to iterate
46
+ list(resource_root.iterdir())
47
+ self._path_cache = Path(str(resource_root))
48
+ except (FileNotFoundError, AttributeError, TypeError):
49
+ # Fall back to development path
50
+ self._path_cache = Path(__file__).parent.parent.parent / self.name
51
+
52
+ return self._path_cache
53
+
54
+ def get_file(self, file_path: str) -> str:
55
+ """Get the content of a specific file.
56
+
57
+ Args:
58
+ file_path: Relative path to the file from the resource root.
59
+
60
+ Returns:
61
+ The full content of the file.
62
+
63
+ Raises:
64
+ FileNotFoundError: If the specified file doesn't exist.
65
+ ValueError: If the path tries to access files outside the directory.
66
+ """
67
+ resource_path = self.get_path()
68
+
69
+ # Resolve the requested file path
70
+ requested_file = (resource_path / file_path).resolve()
71
+
72
+ # Security check: ensure the resolved path is within resource directory
73
+ try:
74
+ requested_file.relative_to(resource_path.resolve())
75
+ except ValueError as e:
76
+ raise ValueError(
77
+ f"Invalid path: '{file_path}' is outside {self.name} directory"
78
+ ) from e
79
+
80
+ if not requested_file.exists():
81
+ raise FileNotFoundError(
82
+ (
83
+ f"{self.name.capitalize()} file not found: '{file_path}'. "
84
+ f"Use list_{self.name} to see available files."
85
+ )
86
+ )
87
+
88
+ if not requested_file.is_file():
89
+ raise ValueError(f"Path is not a file: '{file_path}'")
90
+
91
+ content = requested_file.read_text(encoding="utf-8")
92
+
93
+ # Apply snippet resolution if enabled
94
+ if self.resolve_snippets:
95
+ content = _resolve_snippets(content, requested_file, self)
96
+
97
+ return content
98
+
99
+ def list_files(self) -> list[str]:
100
+ """List all files in this resource directory.
101
+
102
+ Returns:
103
+ Sorted list of relative paths to all files with the configured extension.
104
+
105
+ Raises:
106
+ FileNotFoundError: If the resource directory doesn't exist.
107
+ """
108
+ resource_path = self.get_path()
109
+
110
+ if not resource_path.exists():
111
+ raise FileNotFoundError(
112
+ (
113
+ f"{self.name.capitalize()} directory not found: "
114
+ f"{resource_path}"
115
+ )
116
+ )
117
+
118
+ # Find all files with the configured extension
119
+ pattern = f"*{self.file_extension}"
120
+ files_list = []
121
+ for file in resource_path.rglob(pattern):
122
+ # Get relative path from resource root
123
+ rel_path = file.relative_to(resource_path)
124
+ files_list.append(str(rel_path))
125
+
126
+ return sorted(files_list)
127
+
128
+
129
+ def _resolve_snippets(
130
+ content: str, base_path: Path, docs_resource: ResourceDirectory
131
+ ) -> str:
132
+ """Recursively resolve MkDocs snippets in markdown content.
133
+
134
+ Mimics the behavior of pymdownx.snippets.
135
+
136
+ Args:
137
+ content: The markdown content to process
138
+ base_path: Path to the file being processed (for resolving relative paths)
139
+ docs_resource: The docs ResourceDirectory for resolving snippet paths
140
+
141
+ Returns:
142
+ Content with all snippets resolved
143
+ """
144
+ docs_root = docs_resource.get_path()
145
+ project_root = docs_root.parent
146
+
147
+ def replace_match(match: re.Match) -> str:
148
+ snippet_path = match.group(1)
149
+
150
+ # pymdownx logic: try relative to current file, then docs, then project
151
+ candidates = [
152
+ base_path.parent / snippet_path, # Relative to the doc file
153
+ docs_root / snippet_path, # Relative to docs root
154
+ project_root / snippet_path, # Relative to project root
155
+ ]
156
+
157
+ for candidate in candidates:
158
+ if candidate.exists() and candidate.is_file():
159
+ # Recursively resolve snippets inside the included file
160
+ return _resolve_snippets(
161
+ candidate.read_text(encoding="utf-8"),
162
+ candidate,
163
+ docs_resource,
164
+ )
165
+
166
+ return f"> [!WARNING] Could not resolve snippet: {snippet_path}"
167
+
168
+ return SNIPPET_REGEX.sub(replace_match, content)
169
+
170
+
171
+ # Initialize singleton resource directories
172
+ _docs_resource = ResourceDirectory("docs", ".md", resolve_snippets=True)
173
+ _examples_resource = ResourceDirectory("examples", ".yaml")
174
+
175
+
176
+ @lru_cache(maxsize=1)
177
+ def get_docs_resource() -> ResourceDirectory:
178
+ """Get the singleton docs resource directory.
179
+
180
+ Returns:
181
+ ResourceDirectory instance for documentation files.
182
+ """
183
+ return _docs_resource
184
+
185
+
186
+ @lru_cache(maxsize=1)
187
+ def get_examples_resource() -> ResourceDirectory:
188
+ """Get the singleton examples resource directory.
189
+
190
+ Returns:
191
+ ResourceDirectory instance for example files.
192
+ """
193
+ return _examples_resource
qtype/cli.py CHANGED
@@ -7,9 +7,9 @@ import importlib
7
7
  import logging
8
8
  from pathlib import Path
9
9
 
10
- from qtype.base.logging import get_logger
10
+ from qtype.base.logging import configure_logging
11
11
 
12
- logger = get_logger("application.facade")
12
+ logger = logging.getLogger(__name__)
13
13
 
14
14
  try:
15
15
  from importlib.metadata import entry_points
@@ -59,9 +59,8 @@ def _discover_local_commands(subparsers: argparse._SubParsersAction) -> None:
59
59
  f"Built-in command module {module_name} does not have a 'parser' function"
60
60
  )
61
61
  except Exception as e:
62
- logging.error(
63
- f"Failed to load built-in command module {module_name}: {e}",
64
- exc_info=True,
62
+ logging.debug(
63
+ f"Failed to load built-in command module {module_name}: {e} -- you may need the mcp or interpreter extras."
65
64
  )
66
65
 
67
66
 
@@ -133,10 +132,7 @@ def main() -> None:
133
132
  args = parser.parse_args()
134
133
 
135
134
  # Set logging level based on user input
136
- logging.basicConfig(
137
- level=getattr(logging, args.log_level),
138
- format="%(asctime)s - %(levelname)s: %(message)s",
139
- )
135
+ configure_logging(level=args.log_level)
140
136
 
141
137
  # Dispatch to the selected subcommand
142
138
  args.func(args)