qtype 0.1.11__py3-none-any.whl → 0.1.12__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 (215) hide show
  1. docs/Concepts/mental-model-and-philosophy.md +363 -0
  2. docs/Contributing/index.md +276 -0
  3. docs/Contributing/roadmap.md +81 -0
  4. docs/Decisions/ADR-001-Chat-vs-Completion-Endpoint-Features.md +56 -0
  5. docs/Gallery/dataflow_pipelines.md +80 -0
  6. docs/Gallery/dataflow_pipelines.mermaid +45 -0
  7. docs/Gallery/research_assistant.md +98 -0
  8. docs/Gallery/research_assistant.mermaid +42 -0
  9. docs/Gallery/simple_chatbot.md +36 -0
  10. docs/Gallery/simple_chatbot.mermaid +35 -0
  11. docs/How To/Authentication/configure_aws_authentication.md +60 -0
  12. docs/How To/Authentication/use_api_key_authentication.md +40 -0
  13. docs/How To/Command Line Usage/load_multiple_inputs_from_files.md +62 -0
  14. docs/How To/Command Line Usage/pass_inputs_on_the_cli.md +52 -0
  15. docs/How To/Command Line Usage/serve_with_auto_reload.md +26 -0
  16. docs/How To/Data Processing/adjust_concurrency.md +41 -0
  17. docs/How To/Data Processing/cache_step_results.md +71 -0
  18. docs/How To/Data Processing/decode_json_xml.md +24 -0
  19. docs/How To/Data Processing/explode_collections.md +40 -0
  20. docs/How To/Data Processing/gather_results.md +68 -0
  21. docs/How To/Data Processing/read_data_from_files.md +35 -0
  22. docs/How To/Data Processing/read_sql_databases.md +47 -0
  23. docs/How To/Data Processing/write_data_to_file.md +40 -0
  24. docs/How To/Invoke Models/call_large_language_models.md +51 -0
  25. docs/How To/Invoke Models/create_embeddings.md +49 -0
  26. docs/How To/Invoke Models/reuse_prompts_with_templates.md +39 -0
  27. docs/How To/Language Features/include_qtype_yaml.md +45 -0
  28. docs/How To/Language Features/include_raw_text_from_other_files.md +47 -0
  29. docs/How To/Language Features/reference_entities_by_id.md +51 -0
  30. docs/How To/Language Features/use_environment_variables.md +47 -0
  31. docs/How To/Language Features/use_qtype_mcp.md +59 -0
  32. docs/How To/Observability & Debugging/trace_calls_with_open_telemetry.md +49 -0
  33. docs/How To/Observability & Debugging/validate_qtype_yaml.md +35 -0
  34. docs/How To/Observability & Debugging/visualize_application_architecture.md +61 -0
  35. docs/How To/Observability & Debugging/visualize_example.mermaid +35 -0
  36. docs/How To/Qtype Server/flow_as_ui.png +0 -0
  37. docs/How To/Qtype Server/serve_flows_as_apis.md +40 -0
  38. docs/How To/Qtype Server/serve_flows_as_ui.md +42 -0
  39. docs/How To/Qtype Server/use_conversational_interfaces.md +59 -0
  40. docs/How To/Qtype Server/use_variables_with_ui_hints.md +47 -0
  41. docs/How To/Tools & Integration/bind_tool_inputs_and_outputs.md +48 -0
  42. docs/How To/Tools & Integration/create_tools_from_openapi_specifications.md +89 -0
  43. docs/How To/Tools & Integration/create_tools_from_python_modules.md +90 -0
  44. docs/Reference/cli.md +338 -0
  45. docs/Reference/plugins.md +95 -0
  46. docs/Reference/semantic-validation-rules.md +179 -0
  47. docs/Tutorials/01-first-qtype-application.md +248 -0
  48. docs/Tutorials/02-conversational-chatbot.md +327 -0
  49. docs/Tutorials/03-structured-data.md +481 -0
  50. docs/Tutorials/04-tools-and-function-calling.md +483 -0
  51. docs/Tutorials/example_chat.png +0 -0
  52. docs/Tutorials/index.md +92 -0
  53. docs/components/APIKeyAuthProvider.md +7 -0
  54. docs/components/APITool.md +10 -0
  55. docs/components/AWSAuthProvider.md +13 -0
  56. docs/components/AWSSecretManager.md +5 -0
  57. docs/components/Agent.md +6 -0
  58. docs/components/Aggregate.md +8 -0
  59. docs/components/AggregateStats.md +7 -0
  60. docs/components/Application.md +22 -0
  61. docs/components/AuthorizationProvider.md +6 -0
  62. docs/components/AuthorizationProviderList.md +5 -0
  63. docs/components/BearerTokenAuthProvider.md +6 -0
  64. docs/components/BedrockReranker.md +8 -0
  65. docs/components/ChatContent.md +7 -0
  66. docs/components/ChatMessage.md +6 -0
  67. docs/components/ConstantPath.md +5 -0
  68. docs/components/CustomType.md +7 -0
  69. docs/components/Decoder.md +8 -0
  70. docs/components/DecoderFormat.md +8 -0
  71. docs/components/DocToTextConverter.md +7 -0
  72. docs/components/Document.md +7 -0
  73. docs/components/DocumentEmbedder.md +7 -0
  74. docs/components/DocumentIndex.md +7 -0
  75. docs/components/DocumentSearch.md +7 -0
  76. docs/components/DocumentSource.md +12 -0
  77. docs/components/DocumentSplitter.md +10 -0
  78. docs/components/Echo.md +8 -0
  79. docs/components/Embedding.md +7 -0
  80. docs/components/EmbeddingModel.md +6 -0
  81. docs/components/FieldExtractor.md +20 -0
  82. docs/components/FileSource.md +6 -0
  83. docs/components/FileWriter.md +7 -0
  84. docs/components/Flow.md +14 -0
  85. docs/components/FlowInterface.md +7 -0
  86. docs/components/Index.md +8 -0
  87. docs/components/IndexUpsert.md +6 -0
  88. docs/components/InvokeEmbedding.md +7 -0
  89. docs/components/InvokeFlow.md +8 -0
  90. docs/components/InvokeTool.md +8 -0
  91. docs/components/LLMInference.md +9 -0
  92. docs/components/ListType.md +5 -0
  93. docs/components/Memory.md +8 -0
  94. docs/components/MessageRole.md +14 -0
  95. docs/components/Model.md +10 -0
  96. docs/components/ModelList.md +5 -0
  97. docs/components/OAuth2AuthProvider.md +9 -0
  98. docs/components/PrimitiveTypeEnum.md +21 -0
  99. docs/components/PromptTemplate.md +7 -0
  100. docs/components/PythonFunctionTool.md +7 -0
  101. docs/components/RAGChunk.md +7 -0
  102. docs/components/RAGDocument.md +10 -0
  103. docs/components/RAGSearchResult.md +8 -0
  104. docs/components/Reranker.md +5 -0
  105. docs/components/SQLSource.md +8 -0
  106. docs/components/Search.md +7 -0
  107. docs/components/SearchResult.md +7 -0
  108. docs/components/SecretManager.md +7 -0
  109. docs/components/SecretReference.md +7 -0
  110. docs/components/Source.md +6 -0
  111. docs/components/Step.md +9 -0
  112. docs/components/TelemetrySink.md +9 -0
  113. docs/components/Tool.md +9 -0
  114. docs/components/ToolList.md +5 -0
  115. docs/components/ToolParameter.md +6 -0
  116. docs/components/TypeList.md +5 -0
  117. docs/components/Variable.md +6 -0
  118. docs/components/VariableList.md +5 -0
  119. docs/components/VectorIndex.md +7 -0
  120. docs/components/VectorSearch.md +6 -0
  121. docs/components/VertexAuthProvider.md +9 -0
  122. docs/components/Writer.md +5 -0
  123. docs/example_ui.png +0 -0
  124. docs/index.md +81 -0
  125. docs/legacy_how_tos/Configuration/modular-yaml.md +366 -0
  126. docs/legacy_how_tos/Configuration/phoenix_projects.png +0 -0
  127. docs/legacy_how_tos/Configuration/phoenix_traces.png +0 -0
  128. docs/legacy_how_tos/Configuration/reference-by-id.md +251 -0
  129. docs/legacy_how_tos/Configuration/telemetry-setup.md +259 -0
  130. docs/legacy_how_tos/Data Types/custom-types.md +52 -0
  131. docs/legacy_how_tos/Data Types/domain-types.md +113 -0
  132. docs/legacy_how_tos/Debugging/visualize-apps.md +147 -0
  133. docs/legacy_how_tos/Tools/api-tools.md +29 -0
  134. docs/legacy_how_tos/Tools/python-tools.md +299 -0
  135. examples/authentication/aws_authentication.qtype.yaml +63 -0
  136. examples/conversational_ai/hello_world_chat.qtype.yaml +43 -0
  137. examples/conversational_ai/simple_chatbot.qtype.yaml +40 -0
  138. examples/data_processing/batch_processing.qtype.yaml +54 -0
  139. examples/data_processing/cache_step_results.qtype.yaml +78 -0
  140. examples/data_processing/collect_results.qtype.yaml +55 -0
  141. examples/data_processing/dataflow_pipelines.qtype.yaml +108 -0
  142. examples/data_processing/decode_json.qtype.yaml +23 -0
  143. examples/data_processing/explode_items.qtype.yaml +25 -0
  144. examples/data_processing/read_file.qtype.yaml +60 -0
  145. examples/invoke_models/create_embeddings.qtype.yaml +28 -0
  146. examples/invoke_models/simple_llm_call.qtype.yaml +32 -0
  147. examples/language_features/include_raw.qtype.yaml +27 -0
  148. examples/language_features/ui_hints.qtype.yaml +52 -0
  149. examples/legacy/bedrock/data_analysis_with_telemetry.qtype.yaml +169 -0
  150. examples/legacy/bedrock/hello_world.qtype.yaml +39 -0
  151. examples/legacy/bedrock/hello_world_chat.qtype.yaml +37 -0
  152. examples/legacy/bedrock/hello_world_chat_with_telemetry.qtype.yaml +40 -0
  153. examples/legacy/bedrock/hello_world_chat_with_thinking.qtype.yaml +40 -0
  154. examples/legacy/bedrock/hello_world_completion.qtype.yaml +41 -0
  155. examples/legacy/bedrock/hello_world_completion_with_auth.qtype.yaml +44 -0
  156. examples/legacy/bedrock/simple_agent_chat.qtype.yaml +46 -0
  157. examples/legacy/chat_with_langfuse.qtype.yaml +50 -0
  158. examples/legacy/data_processor.qtype.yaml +48 -0
  159. examples/legacy/echo/debug_example.qtype.yaml +59 -0
  160. examples/legacy/echo/prompt.qtype.yaml +22 -0
  161. examples/legacy/echo/test.qtype.yaml +26 -0
  162. examples/legacy/echo/video.qtype.yaml +20 -0
  163. examples/legacy/field_extractor_example.qtype.yaml +137 -0
  164. examples/legacy/multi_flow_example.qtype.yaml +125 -0
  165. examples/legacy/openai/hello_world_chat.qtype.yaml +43 -0
  166. examples/legacy/openai/hello_world_chat_with_telemetry.qtype.yaml +46 -0
  167. examples/legacy/rag.qtype.yaml +207 -0
  168. examples/legacy/time_utilities.qtype.yaml +64 -0
  169. examples/legacy/vertex/hello_world_chat.qtype.yaml +36 -0
  170. examples/legacy/vertex/hello_world_completion.qtype.yaml +40 -0
  171. examples/legacy/vertex/hello_world_completion_with_auth.qtype.yaml +45 -0
  172. examples/observability_debugging/trace_with_opentelemetry.qtype.yaml +40 -0
  173. examples/research_assistant/research_assistant.qtype.yaml +94 -0
  174. examples/research_assistant/tavily.oas.yaml +722 -0
  175. examples/research_assistant/tavily.qtype.yaml +289 -0
  176. examples/tutorials/01_hello_world.qtype.yaml +48 -0
  177. examples/tutorials/02_conversational_chat.qtype.yaml +37 -0
  178. examples/tutorials/03_structured_data.qtype.yaml +130 -0
  179. examples/tutorials/04_tools_and_function_calling.qtype.yaml +89 -0
  180. qtype/application/converters/tools_from_api.py +39 -35
  181. qtype/base/types.py +6 -1
  182. qtype/commands/convert.py +3 -6
  183. qtype/commands/generate.py +7 -3
  184. qtype/commands/mcp.py +68 -0
  185. qtype/commands/validate.py +4 -4
  186. qtype/dsl/custom_types.py +2 -1
  187. qtype/dsl/linker.py +15 -7
  188. qtype/dsl/loader.py +3 -3
  189. qtype/dsl/model.py +24 -3
  190. qtype/interpreter/api.py +4 -1
  191. qtype/interpreter/base/base_step_executor.py +3 -1
  192. qtype/interpreter/conversions.py +7 -3
  193. qtype/interpreter/executors/construct_executor.py +1 -1
  194. qtype/interpreter/executors/file_source_executor.py +3 -3
  195. qtype/interpreter/executors/file_writer_executor.py +4 -4
  196. qtype/interpreter/executors/index_upsert_executor.py +1 -1
  197. qtype/interpreter/executors/sql_source_executor.py +1 -1
  198. qtype/interpreter/resource_cache.py +3 -1
  199. qtype/interpreter/rich_progress.py +6 -3
  200. qtype/interpreter/stream/chat/converter.py +25 -17
  201. qtype/interpreter/stream/chat/ui_request_to_domain_type.py +2 -2
  202. qtype/interpreter/typing.py +5 -7
  203. qtype/mcp/__init__.py +0 -0
  204. qtype/mcp/server.py +467 -0
  205. qtype/semantic/checker.py +1 -1
  206. qtype/semantic/generate.py +3 -3
  207. qtype/semantic/visualize.py +38 -51
  208. {qtype-0.1.11.dist-info → qtype-0.1.12.dist-info}/METADATA +21 -1
  209. qtype-0.1.12.dist-info/RECORD +325 -0
  210. {qtype-0.1.11.dist-info → qtype-0.1.12.dist-info}/WHEEL +1 -1
  211. schema/qtype.schema.json +4018 -0
  212. qtype-0.1.11.dist-info/RECORD +0 -142
  213. {qtype-0.1.11.dist-info → qtype-0.1.12.dist-info}/entry_points.txt +0 -0
  214. {qtype-0.1.11.dist-info → qtype-0.1.12.dist-info}/licenses/LICENSE +0 -0
  215. {qtype-0.1.11.dist-info → qtype-0.1.12.dist-info}/top_level.txt +0 -0
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:
@@ -51,7 +51,11 @@ def run_dump_commons_library(args: argparse.Namespace) -> None:
51
51
  )
52
52
 
53
53
  # Convert to YAML and save
54
- content = facade.convert_document(model_list)
54
+ from pydantic_yaml import to_yaml_str
55
+
56
+ content = to_yaml_str(
57
+ model_list, exclude_none=True, exclude_unset=True
58
+ )
55
59
  output_path = Path(f"{args.prefix}/aws.bedrock.models.qtype.yaml")
56
60
  output_path.write_text(content, encoding="utf-8")
57
61
  logger.info(f"AWS Bedrock models exported to {output_path}")
@@ -118,8 +122,8 @@ def generate_schema(args: argparse.Namespace) -> None:
118
122
 
119
123
  schema["$defs"]["qtype_env_var"] = {
120
124
  "type": "string",
121
- "pattern": "^.*\\$\\{[^}:]+(?::[^}]*)?\\}.*$",
122
- "description": "String with environment variable substitution using ${VAR_NAME} or ${VAR_NAME:default} syntax",
125
+ "pattern": "^.*\\$\\{[^}:]+(?::-[^}]*)?\\}.*$",
126
+ "description": "String with environment variable substitution using ${VAR_NAME} or ${VAR_NAME:-default} syntax",
123
127
  }
124
128
 
125
129
  output = json.dumps(schema, indent=2)
qtype/commands/mcp.py ADDED
@@ -0,0 +1,68 @@
1
+ """
2
+ Command-line interface for starting the QType MCP server.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import argparse
8
+ import logging
9
+ from typing import Any
10
+
11
+ from qtype.mcp.server import mcp
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def main(args: Any) -> None:
17
+ """
18
+ Start the QType MCP server with the specified transport.
19
+
20
+ Args:
21
+ args: Arguments passed from the command line or calling context.
22
+ """
23
+
24
+ # Update server settings with CLI arguments
25
+ mcp.settings.host = args.host
26
+ mcp.settings.port = args.port
27
+
28
+ logger.info(f"Starting QType MCP server with {args.transport} transport")
29
+
30
+ if args.transport in ("sse", "streamable-http"):
31
+ logger.info(f"Server will be available at {args.host}:{args.port}")
32
+
33
+ # Run the server with the specified transport
34
+ mcp.run(transport=args.transport)
35
+
36
+
37
+ def parser(subparsers: argparse._SubParsersAction) -> None:
38
+ """Set up the mcp subcommand parser.
39
+
40
+ Args:
41
+ subparsers: The subparsers object to add the command to.
42
+ """
43
+ cmd_parser = subparsers.add_parser(
44
+ "mcp",
45
+ help="Start the QType MCP server for AI agent integration.",
46
+ )
47
+ cmd_parser.add_argument(
48
+ "-t",
49
+ "--transport",
50
+ type=str,
51
+ choices=["stdio", "sse", "streamable-http"],
52
+ default="stdio",
53
+ help="Transport protocol to use (default: stdio)",
54
+ )
55
+ cmd_parser.add_argument(
56
+ "--host",
57
+ type=str,
58
+ default="0.0.0.0",
59
+ help="Host to bind to for HTTP/SSE transports (default: 0.0.0.0)",
60
+ )
61
+ cmd_parser.add_argument(
62
+ "-p",
63
+ "--port",
64
+ type=int,
65
+ default=8000,
66
+ help="Port to bind to for HTTP/SSE transports (default: 8000)",
67
+ )
68
+ cmd_parser.set_defaults(func=main)
@@ -10,7 +10,7 @@ import sys
10
10
  from pathlib import Path
11
11
  from typing import Any
12
12
 
13
- from qtype.base.exceptions import LoadError, SemanticError, ValidationError
13
+ from qtype.base.exceptions import LoadError, ValidationError
14
14
  from qtype.dsl.linker import DuplicateComponentError, ReferenceNotFoundError
15
15
  from qtype.dsl.loader import YAMLLoadError
16
16
  from qtype.dsl.model import Application as DSLApplication
@@ -60,9 +60,9 @@ def main(args: Any) -> None:
60
60
  except ValidationError as e:
61
61
  logger.error(f"❌ Validation failed: {e}")
62
62
  sys.exit(1)
63
- except SemanticError as e:
64
- logger.error(f"❌ Semantic validation failed: {e}")
65
- sys.exit(1)
63
+ # except SemanticError as e:
64
+ # logger.error(f"❌ Semantic validation failed: {e}")
65
+ # sys.exit(1)
66
66
 
67
67
  # If printing is requested, load and print the document
68
68
  if args.print:
qtype/dsl/custom_types.py CHANGED
@@ -43,7 +43,8 @@ def build_dynamic_types(
43
43
  resolved_type = ForwardRef(type_str)
44
44
 
45
45
  if is_optional:
46
- return Union[resolved_type, None], True
46
+ # Type checker: resolved_type is runtime-constructed type
47
+ return Union[resolved_type, None], True # type: ignore[valid-type]
47
48
 
48
49
  return resolved_type, False
49
50
 
qtype/dsl/linker.py CHANGED
@@ -253,18 +253,26 @@ def _resolve_rootmodel_references(
253
253
  model: RootModel instance to resolve
254
254
  lookup_map: Map of component IDs to objects
255
255
  """
256
- root_list = model.root # type: ignore
257
- if not isinstance(root_list, list):
258
- return
256
+ root_value = model.root # type: ignore[attr-defined]
259
257
 
260
- for i, item in enumerate(root_list):
258
+ def _resolve_root_item(item: Any) -> Any:
261
259
  match item:
262
260
  case base_types.Reference():
263
- root_list[i] = _resolve_reference(
264
- item.ref, type(item), lookup_map
265
- )
261
+ return _resolve_reference(item.ref, type(item), lookup_map)
266
262
  case BaseModel():
267
263
  _resolve_all_references(item, lookup_map)
264
+ return item
265
+ case _:
266
+ return item
267
+
268
+ # RootModel can wrap either a list (e.g., ToolList) or a single model
269
+ # (e.g., Document -> Application). We need to traverse both.
270
+ if isinstance(root_value, list):
271
+ for i, item in enumerate(root_value):
272
+ root_value[i] = _resolve_root_item(item)
273
+ return
274
+ else:
275
+ model.root = _resolve_root_item(root_value) # type: ignore[attr-defined]
268
276
 
269
277
 
270
278
  def _resolve_list_references(
qtype/dsl/loader.py CHANGED
@@ -6,7 +6,7 @@ This module provides two explicit functions for loading YAML:
6
6
  - load_yaml_string(content, base_path): Load YAML from a string
7
7
 
8
8
  Both support:
9
- - Environment variable substitution (${VAR} or ${VAR:default})
9
+ - Environment variable substitution (${VAR} or ${VAR:-default})
10
10
  - File inclusion (!include and !include_raw)
11
11
  - Multiple URI schemes via fsspec (local, http, s3, etc.)
12
12
 
@@ -81,7 +81,7 @@ def _substitute_env_vars(value: str) -> str:
81
81
  """
82
82
  Substitute environment variables in a string.
83
83
 
84
- Supports ${VAR_NAME} or ${VAR_NAME:default} syntax.
84
+ Supports ${VAR_NAME} or ${VAR_NAME:-default} syntax.
85
85
 
86
86
  Args:
87
87
  value: String containing environment variable references
@@ -92,7 +92,7 @@ def _substitute_env_vars(value: str) -> str:
92
92
  Raises:
93
93
  ValueError: If required environment variable is not found
94
94
  """
95
- pattern = r"\$\{([^}:]+)(?::([^}]*))?\}"
95
+ pattern = r"\$\{([^}:]+)(?::-([^}]*))?\}"
96
96
 
97
97
  def replace_env_var(match: re.Match[str]) -> str:
98
98
  var_name = match.group(1)
qtype/dsl/model.py CHANGED
@@ -285,9 +285,10 @@ class ToolParameter(BaseModel):
285
285
  @model_serializer
286
286
  def _model_serializer(self):
287
287
  # Use the default serialization, but ensure 'type' is a string
288
- data = self.model_dump()
289
- data["type"] = self._serialize_type(data.get("type"))
290
- return data
288
+ return {
289
+ "type": self._serialize_type(self.type),
290
+ "optional": self.optional,
291
+ }
291
292
 
292
293
 
293
294
  class ListType(BaseModel):
@@ -656,6 +657,26 @@ class InvokeTool(Step, ConcurrentStepMixin):
656
657
  description="Mapping from variable references to tool output parameter names.",
657
658
  )
658
659
 
660
+ @model_validator(mode="after")
661
+ 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)
678
+ return self
679
+
659
680
 
660
681
  class InvokeFlow(Step):
661
682
  """Invokes a flow with input and output bindings."""
qtype/interpreter/api.py CHANGED
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import asyncio
4
4
  from contextlib import asynccontextmanager
5
5
  from pathlib import Path
6
+ from typing import Any
6
7
 
7
8
  from fastapi import FastAPI
8
9
  from fastapi.middleware.cors import CORSMiddleware
@@ -86,8 +87,10 @@ class APIExecutor:
86
87
  if ui_enabled:
87
88
  # Add CORS middleware only for localhost development
88
89
  if self.host in ("localhost", "127.0.0.1", "0.0.0.0"):
90
+ from typing import cast
91
+
89
92
  app.add_middleware(
90
- CORSMiddleware,
93
+ cast(Any, CORSMiddleware),
91
94
  allow_origins=["*"],
92
95
  allow_credentials=True,
93
96
  allow_methods=["*"],
@@ -355,7 +355,9 @@ class StepExecutor(ABC):
355
355
  ):
356
356
  serialized = [to_cache_value(m, self.step) for m in buf]
357
357
  self.cache.set(
358
- key, serialized, expire=self.step.cache_config.ttl
358
+ key,
359
+ serialized,
360
+ expire=self.step.cache_config.ttl, # type: ignore[union-attr]
359
361
  ) # type: ignore
360
362
 
361
363
  async def _process_message_with_telemetry(
@@ -331,7 +331,7 @@ def to_embedding_model(
331
331
  OpenAIEmbedding,
332
332
  )
333
333
 
334
- api_key = None
334
+ api_key: str | None = None
335
335
  if model.auth:
336
336
  with auth(model.auth, secret_manager) as provider:
337
337
  if not isinstance(provider, APIKeyAuthProvider):
@@ -343,7 +343,7 @@ def to_embedding_model(
343
343
  api_key = provider.api_key # type: ignore[assignment]
344
344
 
345
345
  openai_embedding: BaseEmbedding = OpenAIEmbedding(
346
- api_key=api_key,
346
+ api_key=api_key, # type: ignore[arg-type]
347
347
  model_name=model.model_id if model.model_id else model.id,
348
348
  )
349
349
  return openai_embedding
@@ -523,7 +523,11 @@ def from_chat_message(message: LlamaChatMessage) -> ChatMessage:
523
523
  f"Unsupported content block type: {type(block)}"
524
524
  )
525
525
 
526
- return ChatMessage(role=message.role, blocks=blocks)
526
+ # Convert llama_index MessageRole to our MessageRole
527
+ from qtype.dsl.domain_types import MessageRole as QTypeMessageRole
528
+
529
+ role = QTypeMessageRole(message.role.value)
530
+ return ChatMessage(role=role, blocks=blocks)
527
531
 
528
532
 
529
533
  def to_text_splitter(splitter: DocumentSplitter) -> Any:
@@ -53,7 +53,7 @@ class ConstructExecutor(StepExecutor):
53
53
  }
54
54
  # use the mapping to convert variable names to
55
55
  inputs = {
56
- self.step.field_mapping.get(var_name, var_name): value
56
+ self.step.field_mapping.get(var_name, var_name): value # type: ignore[attr-defined]
57
57
  for var_name, value in input_values.items()
58
58
  }
59
59
  else:
@@ -40,10 +40,10 @@ class FileSourceExecutor(StepExecutor):
40
40
  output_columns = {output.id for output in self.step.outputs}
41
41
 
42
42
  # get the path
43
- if isinstance(self.step.path, ConstantPath):
44
- file_path = self.step.path
43
+ if isinstance(self.step.path, ConstantPath): # type: ignore[attr-defined]
44
+ file_path = self.step.path # type: ignore[attr-defined]
45
45
  else:
46
- file_path = message.variables.get(self.step.path.id)
46
+ file_path = message.variables.get(self.step.path.id) # type: ignore[attr-defined]
47
47
  if not file_path:
48
48
  raise ValueError(
49
49
  (
@@ -48,8 +48,8 @@ class FileWriterExecutor(BatchedStepExecutor):
48
48
  if len(self.step.outputs):
49
49
  output_name = self.step.outputs[0].id
50
50
 
51
- if isinstance(self.step.path, ConstantPath):
52
- file_path = self.step.path.uri
51
+ if isinstance(self.step.path, ConstantPath): # type: ignore[attr-defined]
52
+ file_path = self.step.path.uri # type: ignore[attr-defined]
53
53
  df = self.to_pandas(batch)
54
54
  # A fixed path is provided -- just write all of the data
55
55
  await self.stream_emitter.status(
@@ -67,12 +67,12 @@ class FileWriterExecutor(BatchedStepExecutor):
67
67
 
68
68
  else:
69
69
  # Group messages by file path (path is a Variable in this branch)
70
- if not isinstance(self.step.path, Variable):
70
+ if not isinstance(self.step.path, Variable): # type: ignore[attr-defined]
71
71
  raise ValueError(
72
72
  "Expected path to be a Variable in dynamic path case."
73
73
  )
74
74
 
75
- path_var_id = self.step.path.id
75
+ path_var_id = self.step.path.id # type: ignore[attr-defined]
76
76
 
77
77
  # Sort messages by file path for groupby
78
78
  sorted_batch = sorted(
@@ -145,7 +145,7 @@ class IndexUpsertExecutor(BatchedStepExecutor):
145
145
  nodes.append(node)
146
146
 
147
147
  # Batch upsert all nodes to the vector store
148
- await self._vector_store.async_add(nodes)
148
+ await self._vector_store.async_add(nodes) # type: ignore[union-attr]
149
149
  num_inserted = len(items)
150
150
 
151
151
  # Emit status update
@@ -41,7 +41,7 @@ class SQLSourceExecutor(StepExecutor):
41
41
  connection_string = self._resolve_secret(self.step.connection)
42
42
  connect_args = {}
43
43
  if self.step.auth:
44
- with auth(self.step.auth) as creds:
44
+ with auth(self.step.auth, self._secret_manager) as creds:
45
45
  if isinstance(creds, boto3.Session):
46
46
  connect_args["session"] = creds
47
47
  engine = create_engine(connection_string, connect_args=connect_args)
@@ -25,9 +25,11 @@ def cached_resource(func: Callable[..., Any]) -> Callable[..., Any]:
25
25
 
26
26
  @functools.wraps(func)
27
27
  def wrapper(*args: Any, **kwargs: Any) -> Any:
28
+ # Use getattr with fallback for callables that aren't functions
29
+ qualname = getattr(func, "__qualname__", repr(func))
28
30
  cache_key = (
29
31
  func.__module__,
30
- func.__qualname__,
32
+ qualname,
31
33
  args,
32
34
  tuple(sorted(kwargs.items())),
33
35
  )
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import logging
4
4
  import threading
5
5
  from collections import deque
6
- from typing import Deque, Dict
6
+ from typing import Any, Deque, Dict
7
7
 
8
8
  from rich.console import Console
9
9
  from rich.live import Live
@@ -11,6 +11,7 @@ from rich.panel import Panel
11
11
  from rich.progress import (
12
12
  Progress,
13
13
  ProgressColumn,
14
+ TaskID,
14
15
  TaskProgressColumn,
15
16
  TextColumn,
16
17
  TimeElapsedColumn,
@@ -140,7 +141,7 @@ class RichProgressCallback(ProgressCallback):
140
141
  )
141
142
 
142
143
  # Map step_id -> Rich task id
143
- self.tasks: Dict[str, int] = {}
144
+ self.tasks: dict[str, TaskID] = {}
144
145
  self._started = False
145
146
 
146
147
  # Pre-create tasks in the desired order if provided
@@ -202,7 +203,9 @@ class RichProgressCallback(ProgressCallback):
202
203
  if total_items is not None:
203
204
  update_kwargs["total"] = total_items
204
205
 
205
- self.progress.update(task_id, **update_kwargs)
206
+ from typing import cast
207
+
208
+ self.progress.update(task_id, **cast(Any, update_kwargs))
206
209
 
207
210
  def compute_color(self, items_processed: int, items_in_error: int) -> str:
208
211
  # Avoid divide-by-zero
@@ -258,10 +258,12 @@ class StreamEventConverter:
258
258
  3. ToolInputAvailableChunk - Complete input ready, tool can execute
259
259
  """
260
260
  # 1. Start tool input streaming
261
- yield ToolInputStartChunk(
262
- toolCallId=event.tool_call_id,
263
- toolName=event.tool_name,
264
- providerExecuted=True, # Tools are executed on the server
261
+ yield ToolInputStartChunk.model_validate(
262
+ {
263
+ "toolCallId": event.tool_call_id,
264
+ "toolName": event.tool_name,
265
+ "providerExecuted": True,
266
+ }
265
267
  )
266
268
 
267
269
  # 2. Stream the input as JSON text delta
@@ -274,11 +276,13 @@ class StreamEventConverter:
274
276
  )
275
277
 
276
278
  # 3. Signal input is complete and ready for execution
277
- yield ToolInputAvailableChunk(
278
- toolCallId=event.tool_call_id,
279
- toolName=event.tool_name,
280
- input=event.tool_input,
281
- providerExecuted=True, # Tools are executed on the server
279
+ yield ToolInputAvailableChunk.model_validate(
280
+ {
281
+ "toolCallId": event.tool_call_id,
282
+ "toolName": event.tool_name,
283
+ "input": event.tool_input,
284
+ "providerExecuted": True,
285
+ }
282
286
  )
283
287
 
284
288
  def _convert_tool_execution_end(
@@ -289,10 +293,12 @@ class StreamEventConverter:
289
293
 
290
294
  Signals successful tool completion with output.
291
295
  """
292
- yield ToolOutputAvailableChunk(
293
- toolCallId=event.tool_call_id,
294
- output=event.tool_output,
295
- providerExecuted=True, # Tools are executed on the server
296
+ yield ToolOutputAvailableChunk.model_validate(
297
+ {
298
+ "toolCallId": event.tool_call_id,
299
+ "output": event.tool_output,
300
+ "providerExecuted": True,
301
+ }
296
302
  )
297
303
 
298
304
  def _convert_tool_execution_error(
@@ -303,10 +309,12 @@ class StreamEventConverter:
303
309
 
304
310
  Signals tool execution failure with error message.
305
311
  """
306
- yield ToolOutputErrorChunk(
307
- toolCallId=event.tool_call_id,
308
- errorText=event.error_message,
309
- providerExecuted=True, # Tools are executed on the server
312
+ yield ToolOutputErrorChunk.model_validate(
313
+ {
314
+ "toolCallId": event.tool_call_id,
315
+ "errorText": event.error_message,
316
+ "providerExecuted": True,
317
+ }
310
318
  )
311
319
 
312
320
  def _convert_error(self, event: ErrorEvent) -> Iterator[UIMessageChunk]:
@@ -39,11 +39,11 @@ def _ui_message_to_domain_type(message: UIMessage) -> ChatMessage:
39
39
  for part in message.parts:
40
40
  if part.type == "text":
41
41
  blocks.append(
42
- ChatContent(type=PrimitiveTypeEnum.text, content=part.text)
42
+ ChatContent(type=PrimitiveTypeEnum.text, content=part.text) # type: ignore[attr-defined]
43
43
  )
44
44
  elif part.type == "reasoning":
45
45
  blocks.append(
46
- ChatContent(type=PrimitiveTypeEnum.text, content=part.text)
46
+ ChatContent(type=PrimitiveTypeEnum.text, content=part.text) # type: ignore[attr-defined]
47
47
  )
48
48
  elif part.type == "file":
49
49
  blocks.append(
@@ -25,11 +25,9 @@ def _get_variable_type(var: DSLVariable) -> tuple[Type, dict[str, Any]]:
25
25
  python_type = PRIMITIVE_TO_PYTHON_TYPE.get(var.type, str)
26
26
  field_metadata["qtype_type"] = var.type.value
27
27
  elif isinstance(var.type, ListType):
28
- python_type = list[
29
- _get_variable_type(DSLVariable(id="", type=var.type.element_type))[
30
- 0
31
- ]
32
- ]
28
+ element_var = DSLVariable(id="", type=var.type.element_type)
29
+ element_type, _ = _get_variable_type(element_var)
30
+ python_type = list[element_type] # type: ignore[valid-type]
33
31
  field_metadata["qtype_type"] = f"list[{var.type.element_type}]"
34
32
  elif (
35
33
  isinstance(var.type, type)
@@ -82,7 +80,7 @@ def create_output_container_type(flow: Flow) -> Type[BaseModel]:
82
80
  Field(description="List of errored execution outputs"),
83
81
  )
84
82
  fields["outputs"] = (
85
- list[output_shape],
83
+ list[output_shape], # type: ignore[valid-type]
86
84
  Field(description="List of successful execution outputs"),
87
85
  )
88
86
  return create_model(f"{flow.id}Response", __base__=BaseModel, **fields) # type: ignore
@@ -162,7 +160,7 @@ def instantiate_variable(variable: DSLVariable, value: Any) -> Any:
162
160
 
163
161
  # 2. Handle Pydantic Models (Custom/Domain Types)
164
162
  if hasattr(target_type, "model_validate"):
165
- return target_type.model_validate(value)
163
+ return target_type.model_validate(value) # type: ignore[misc]
166
164
 
167
165
  # 3. Handle Primitives & Complex Python Types (List, Optional, Union)
168
166
  try:
qtype/mcp/__init__.py ADDED
File without changes