qtype 0.0.12__py3-none-any.whl → 0.1.7__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 (137) hide show
  1. qtype/application/commons/tools.py +1 -1
  2. qtype/application/converters/tools_from_api.py +476 -11
  3. qtype/application/converters/tools_from_module.py +38 -14
  4. qtype/application/converters/types.py +15 -30
  5. qtype/application/documentation.py +1 -1
  6. qtype/application/facade.py +102 -85
  7. qtype/base/types.py +227 -7
  8. qtype/cli.py +5 -1
  9. qtype/commands/convert.py +52 -6
  10. qtype/commands/generate.py +44 -4
  11. qtype/commands/run.py +78 -36
  12. qtype/commands/serve.py +74 -44
  13. qtype/commands/validate.py +37 -14
  14. qtype/commands/visualize.py +46 -25
  15. qtype/dsl/__init__.py +6 -5
  16. qtype/dsl/custom_types.py +1 -1
  17. qtype/dsl/domain_types.py +86 -5
  18. qtype/dsl/linker.py +384 -0
  19. qtype/dsl/loader.py +315 -0
  20. qtype/dsl/model.py +753 -264
  21. qtype/dsl/parser.py +200 -0
  22. qtype/dsl/types.py +50 -0
  23. qtype/interpreter/api.py +63 -136
  24. qtype/interpreter/auth/aws.py +19 -9
  25. qtype/interpreter/auth/generic.py +93 -16
  26. qtype/interpreter/base/base_step_executor.py +436 -0
  27. qtype/interpreter/base/batch_step_executor.py +171 -0
  28. qtype/interpreter/base/exceptions.py +50 -0
  29. qtype/interpreter/base/executor_context.py +91 -0
  30. qtype/interpreter/base/factory.py +84 -0
  31. qtype/interpreter/base/progress_tracker.py +110 -0
  32. qtype/interpreter/base/secrets.py +339 -0
  33. qtype/interpreter/base/step_cache.py +74 -0
  34. qtype/interpreter/base/stream_emitter.py +469 -0
  35. qtype/interpreter/conversions.py +495 -24
  36. qtype/interpreter/converters.py +79 -0
  37. qtype/interpreter/endpoints.py +355 -0
  38. qtype/interpreter/executors/agent_executor.py +242 -0
  39. qtype/interpreter/executors/aggregate_executor.py +93 -0
  40. qtype/interpreter/executors/bedrock_reranker_executor.py +195 -0
  41. qtype/interpreter/executors/decoder_executor.py +163 -0
  42. qtype/interpreter/executors/doc_to_text_executor.py +112 -0
  43. qtype/interpreter/executors/document_embedder_executor.py +123 -0
  44. qtype/interpreter/executors/document_search_executor.py +113 -0
  45. qtype/interpreter/executors/document_source_executor.py +118 -0
  46. qtype/interpreter/executors/document_splitter_executor.py +105 -0
  47. qtype/interpreter/executors/echo_executor.py +63 -0
  48. qtype/interpreter/executors/field_extractor_executor.py +165 -0
  49. qtype/interpreter/executors/file_source_executor.py +101 -0
  50. qtype/interpreter/executors/file_writer_executor.py +110 -0
  51. qtype/interpreter/executors/index_upsert_executor.py +232 -0
  52. qtype/interpreter/executors/invoke_embedding_executor.py +104 -0
  53. qtype/interpreter/executors/invoke_flow_executor.py +51 -0
  54. qtype/interpreter/executors/invoke_tool_executor.py +358 -0
  55. qtype/interpreter/executors/llm_inference_executor.py +272 -0
  56. qtype/interpreter/executors/prompt_template_executor.py +78 -0
  57. qtype/interpreter/executors/sql_source_executor.py +106 -0
  58. qtype/interpreter/executors/vector_search_executor.py +91 -0
  59. qtype/interpreter/flow.py +172 -22
  60. qtype/interpreter/logging_progress.py +61 -0
  61. qtype/interpreter/metadata_api.py +115 -0
  62. qtype/interpreter/resource_cache.py +5 -4
  63. qtype/interpreter/rich_progress.py +225 -0
  64. qtype/interpreter/stream/chat/__init__.py +15 -0
  65. qtype/interpreter/stream/chat/converter.py +391 -0
  66. qtype/interpreter/{chat → stream/chat}/file_conversions.py +2 -2
  67. qtype/interpreter/stream/chat/ui_request_to_domain_type.py +140 -0
  68. qtype/interpreter/stream/chat/vercel.py +609 -0
  69. qtype/interpreter/stream/utils/__init__.py +15 -0
  70. qtype/interpreter/stream/utils/build_vercel_ai_formatter.py +74 -0
  71. qtype/interpreter/stream/utils/callback_to_stream.py +66 -0
  72. qtype/interpreter/stream/utils/create_streaming_response.py +18 -0
  73. qtype/interpreter/stream/utils/default_chat_extract_text.py +20 -0
  74. qtype/interpreter/stream/utils/error_streaming_response.py +20 -0
  75. qtype/interpreter/telemetry.py +135 -8
  76. qtype/interpreter/tools/__init__.py +5 -0
  77. qtype/interpreter/tools/function_tool_helper.py +265 -0
  78. qtype/interpreter/types.py +330 -0
  79. qtype/interpreter/typing.py +83 -89
  80. qtype/interpreter/ui/404/index.html +1 -1
  81. qtype/interpreter/ui/404.html +1 -1
  82. qtype/interpreter/ui/_next/static/{OT8QJQW3J70VbDWWfrEMT → 20HoJN6otZ_LyHLHpCPE6}/_buildManifest.js +1 -1
  83. qtype/interpreter/ui/_next/static/chunks/434-b2112d19f25c44ff.js +36 -0
  84. qtype/interpreter/ui/_next/static/chunks/{964-ed4ab073db645007.js → 964-2b041321a01cbf56.js} +1 -1
  85. qtype/interpreter/ui/_next/static/chunks/app/{layout-5ccbc44fd528d089.js → layout-a05273ead5de2c41.js} +1 -1
  86. qtype/interpreter/ui/_next/static/chunks/app/page-8c67d16ac90d23cb.js +1 -0
  87. qtype/interpreter/ui/_next/static/chunks/ba12c10f-546f2714ff8abc66.js +1 -0
  88. qtype/interpreter/ui/_next/static/chunks/{main-6d261b6c5d6fb6c2.js → main-e26b9cb206da2cac.js} +1 -1
  89. qtype/interpreter/ui/_next/static/chunks/webpack-08642e441b39b6c2.js +1 -0
  90. qtype/interpreter/ui/_next/static/css/8a8d1269e362fef7.css +3 -0
  91. qtype/interpreter/ui/_next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
  92. qtype/interpreter/ui/icon.png +0 -0
  93. qtype/interpreter/ui/index.html +1 -1
  94. qtype/interpreter/ui/index.txt +5 -5
  95. qtype/semantic/checker.py +643 -0
  96. qtype/semantic/generate.py +268 -85
  97. qtype/semantic/loader.py +95 -0
  98. qtype/semantic/model.py +535 -163
  99. qtype/semantic/resolver.py +63 -19
  100. qtype/semantic/visualize.py +50 -35
  101. {qtype-0.0.12.dist-info → qtype-0.1.7.dist-info}/METADATA +22 -5
  102. qtype-0.1.7.dist-info/RECORD +137 -0
  103. qtype/dsl/base_types.py +0 -38
  104. qtype/dsl/validator.py +0 -464
  105. qtype/interpreter/batch/__init__.py +0 -0
  106. qtype/interpreter/batch/flow.py +0 -95
  107. qtype/interpreter/batch/sql_source.py +0 -95
  108. qtype/interpreter/batch/step.py +0 -63
  109. qtype/interpreter/batch/types.py +0 -41
  110. qtype/interpreter/batch/utils.py +0 -179
  111. qtype/interpreter/chat/chat_api.py +0 -237
  112. qtype/interpreter/chat/vercel.py +0 -314
  113. qtype/interpreter/exceptions.py +0 -10
  114. qtype/interpreter/step.py +0 -67
  115. qtype/interpreter/steps/__init__.py +0 -0
  116. qtype/interpreter/steps/agent.py +0 -114
  117. qtype/interpreter/steps/condition.py +0 -36
  118. qtype/interpreter/steps/decoder.py +0 -88
  119. qtype/interpreter/steps/llm_inference.py +0 -150
  120. qtype/interpreter/steps/prompt_template.py +0 -54
  121. qtype/interpreter/steps/search.py +0 -24
  122. qtype/interpreter/steps/tool.py +0 -53
  123. qtype/interpreter/streaming_helpers.py +0 -123
  124. qtype/interpreter/ui/_next/static/chunks/736-7fc606e244fedcb1.js +0 -36
  125. qtype/interpreter/ui/_next/static/chunks/app/page-c72e847e888e549d.js +0 -1
  126. qtype/interpreter/ui/_next/static/chunks/ba12c10f-22556063851a6df2.js +0 -1
  127. qtype/interpreter/ui/_next/static/chunks/webpack-8289c17c67827f22.js +0 -1
  128. qtype/interpreter/ui/_next/static/css/a262c53826df929b.css +0 -3
  129. qtype/interpreter/ui/_next/static/media/569ce4b8f30dc480-s.p.woff2 +0 -0
  130. qtype/interpreter/ui/favicon.ico +0 -0
  131. qtype/loader.py +0 -389
  132. qtype-0.0.12.dist-info/RECORD +0 -105
  133. /qtype/interpreter/ui/_next/static/{OT8QJQW3J70VbDWWfrEMT → 20HoJN6otZ_LyHLHpCPE6}/_ssgManifest.js +0 -0
  134. {qtype-0.0.12.dist-info → qtype-0.1.7.dist-info}/WHEEL +0 -0
  135. {qtype-0.0.12.dist-info → qtype-0.1.7.dist-info}/entry_points.txt +0 -0
  136. {qtype-0.0.12.dist-info → qtype-0.1.7.dist-info}/licenses/LICENSE +0 -0
  137. {qtype-0.0.12.dist-info → qtype-0.1.7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,339 @@
1
+ """
2
+ Base class for secret manager implementations.
3
+
4
+ This module provides an abstract base class for secret managers that
5
+ resolve SecretReference objects at runtime.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ from abc import ABC, abstractmethod
12
+ from typing import Any
13
+
14
+ from qtype.interpreter.base.exceptions import SecretResolutionError
15
+ from qtype.semantic.model import AWSAuthProvider
16
+ from qtype.semantic.model import AWSSecretManager as AWSSecretManagerConfig
17
+ from qtype.semantic.model import SecretManager as SecretManagerConfig
18
+ from qtype.semantic.model import SecretReference
19
+
20
+
21
+ class SecretManagerBase(ABC):
22
+ """
23
+ Abstract base class for secret manager implementations.
24
+
25
+ Secret managers are responsible for resolving SecretReference objects
26
+ into actual secret values at runtime. Each implementation corresponds
27
+ to a specific secret management service (e.g., AWS Secrets Manager,
28
+ Kubernetes Secrets, HashiCorp Vault).
29
+ """
30
+
31
+ @abstractmethod
32
+ def get_secret(self, secret_ref: SecretReference) -> str:
33
+ """
34
+ Retrieve a secret value from the underlying secret store.
35
+
36
+ Subclasses must implement this method to interface with their
37
+ specific secret management service.
38
+
39
+ Args:
40
+ secret_ref: SecretReference containing the secret identifier
41
+ and optional key for accessing specific fields
42
+
43
+ Returns:
44
+ str: The resolved secret value
45
+
46
+ Raises:
47
+ Exception: If the secret cannot be retrieved or resolved
48
+ """
49
+ pass
50
+
51
+ def __call__(self, value: str | SecretReference, context: str = "") -> str:
52
+ """
53
+ Resolve a value that may be a string or a SecretReference.
54
+
55
+ This is the main entry point for secret resolution. It handles
56
+ plain strings (pass-through) and SecretReferences (delegates to
57
+ get_secret()).
58
+
59
+ Args:
60
+ value: Either a plain string or a SecretReference to resolve
61
+ context: Optional context string describing where the secret
62
+ is being resolved (e.g., "step 'my_step'", "model 'gpt4'").
63
+ This is included in error messages to aid debugging.
64
+
65
+ Returns:
66
+ The resolved string value. If value is already a string, it is
67
+ returned unchanged. If value is a SecretReference, it is
68
+ resolved using get_secret().
69
+
70
+ Raises:
71
+ SecretResolutionError: If secret resolution fails
72
+
73
+ Examples:
74
+ >>> # Resolve a plain string (no-op)
75
+ >>> secret_manager("plain-text")
76
+ 'plain-text'
77
+
78
+ >>> # Resolve a secret reference
79
+ >>> ref = SecretReference(secret_name="my-app/api-key")
80
+ >>> secret_manager(ref, context="model 'gpt4'")
81
+ 'sk-abc123...'
82
+ """
83
+ if isinstance(value, str):
84
+ return value
85
+
86
+ try:
87
+ return self.get_secret(value)
88
+ except Exception as e:
89
+ raise SecretResolutionError(
90
+ secret_name=value.secret_name, context=context, cause=e
91
+ ) from e
92
+
93
+ def resolve_secrets_in_dict(
94
+ self, args: dict[str, Any], context: str = ""
95
+ ) -> dict[str, Any]:
96
+ """
97
+ Resolve any SecretReferences in a dictionary's values.
98
+
99
+ This is a convenience method that iterates over a dictionary and
100
+ resolves any values that might be SecretReferences. Non-secret
101
+ values (strings, numbers, etc.) are passed through unchanged.
102
+
103
+ Args:
104
+ args: Dictionary with potentially secret-containing values
105
+ context: Optional context string describing where secrets are
106
+ being resolved (e.g., "step 'my_step'", "index 'my_index'").
107
+ This is included in error messages to aid debugging.
108
+
109
+ Returns:
110
+ A new dictionary with all SecretReferences resolved to strings.
111
+ Other values are copied unchanged.
112
+
113
+ Raises:
114
+ SecretResolutionError: If resolution fails for any secret.
115
+
116
+ Examples:
117
+ >>> args = {
118
+ ... "api_key": SecretReference(secret_name="my-app/key"),
119
+ ... "host": "api.example.com",
120
+ ... "port": 443
121
+ ... }
122
+ >>> secret_manager.resolve_secrets_in_dict(
123
+ ... args, "tool 'my_api'"
124
+ ... )
125
+ {'api_key': 'sk-abc123...', 'host': 'api.example.com', 'port': 443}
126
+ """
127
+ resolved = {}
128
+ for key, value in args.items():
129
+ # Check if value might be a SecretReference
130
+ if isinstance(value, str) or hasattr(value, "secret_name"):
131
+ resolved[key] = self(value, context)
132
+ else:
133
+ resolved[key] = value
134
+ return resolved
135
+
136
+
137
+ class AWSSecretManagerError(Exception):
138
+ """Raised when AWS Secrets Manager operations fail."""
139
+
140
+ pass
141
+
142
+
143
+ class NoOpSecretManager(SecretManagerBase):
144
+ """
145
+ No-op secret manager that always raises an error.
146
+
147
+ This implementation is used when no secret manager is configured.
148
+ It allows code to always have a valid SecretManagerBase instance
149
+ instead of dealing with Optional types, following the Null Object
150
+ pattern.
151
+
152
+ Any attempt to resolve a secret will raise a SecretResolutionError
153
+ with a helpful message explaining that no secret manager is configured.
154
+ """
155
+
156
+ def get_secret(self, secret_ref: SecretReference) -> str:
157
+ """
158
+ Raise an error indicating no secret manager is configured.
159
+
160
+ Args:
161
+ secret_ref: The SecretReference that cannot be resolved
162
+
163
+ Raises:
164
+ SecretResolutionError: Always raised with configuration help
165
+ """
166
+ raise SecretResolutionError(
167
+ secret_name=secret_ref.secret_name,
168
+ context="no secret manager configured",
169
+ cause=ValueError(
170
+ "Please add a secret_manager to your application configuration"
171
+ ),
172
+ )
173
+
174
+
175
+ class AWSSecretManager(SecretManagerBase):
176
+ """
177
+ AWS Secrets Manager implementation.
178
+
179
+ This class uses boto3 to retrieve secrets from AWS Secrets Manager.
180
+ It supports both string secrets and JSON secrets with optional key
181
+ extraction.
182
+
183
+ The implementation uses the existing auth library to authenticate
184
+ with AWS and caches the boto3 session for efficient reuse.
185
+
186
+ Example:
187
+ ```python
188
+ from qtype.semantic.model import (
189
+ AWSSecretManager as AWSSecretManagerConfig,
190
+ AWSAuthProvider,
191
+ SecretReference
192
+ )
193
+ from qtype.interpreter.base.secrets import AWSSecretManager
194
+
195
+ # Create auth provider
196
+ auth = AWSAuthProvider(
197
+ id="my-aws-auth",
198
+ type="aws",
199
+ profile_name="default",
200
+ region="us-east-1"
201
+ )
202
+
203
+ # Create secret manager config
204
+ secret_mgr_config = AWSSecretManagerConfig(
205
+ id="my-secret-manager",
206
+ type="aws_secret_manager",
207
+ auth=auth
208
+ )
209
+
210
+ # Create implementation
211
+ secret_mgr = AWSSecretManager(secret_mgr_config)
212
+
213
+ # Resolve a secret
214
+ secret_ref = SecretReference(
215
+ secret_name="my-app/api-key",
216
+ key=None
217
+ )
218
+ api_key = secret_mgr(secret_ref)
219
+ ```
220
+ """
221
+
222
+ def __init__(self, config: AWSSecretManagerConfig):
223
+ """
224
+ Initialize AWS Secrets Manager implementation.
225
+
226
+ Args:
227
+ config: AWSSecretManager configuration from semantic model
228
+ """
229
+ if not isinstance(config.auth, AWSAuthProvider):
230
+ raise AWSSecretManagerError(
231
+ f"AWSSecretManager requires AWSAuthProvider, got "
232
+ f"{type(config.auth).__name__}"
233
+ )
234
+ self.config = config
235
+
236
+ def get_secret(self, secret_ref: SecretReference) -> str:
237
+ """
238
+ Retrieve a secret from AWS Secrets Manager.
239
+
240
+ This method retrieves the secret value from AWS Secrets Manager
241
+ using the secret name provided in the reference. If the secret
242
+ is a JSON object and a key is specified, it extracts that
243
+ specific key's value.
244
+
245
+ Args:
246
+ secret_ref: SecretReference containing the secret name and
247
+ optional key
248
+
249
+ Returns:
250
+ str: The resolved secret value
251
+
252
+ Raises:
253
+ AWSSecretManagerError: If auth provider is wrong type
254
+ ClientError: If AWS API call fails
255
+ json.JSONDecodeError: If secret is not valid JSON when key
256
+ is specified
257
+ """
258
+ from qtype.interpreter.auth.aws import aws
259
+
260
+ with aws(self.config.auth) as session: # type: ignore
261
+ client = session.client("secretsmanager")
262
+ response = client.get_secret_value(SecretId=secret_ref.secret_name)
263
+
264
+ if "SecretString" not in response:
265
+ raise AWSSecretManagerError(
266
+ f"Secret '{secret_ref.secret_name}' contains binary "
267
+ "data, which is not supported"
268
+ )
269
+
270
+ secret_value: str = response["SecretString"]
271
+
272
+ if not secret_ref.key:
273
+ return secret_value
274
+
275
+ # Parse JSON and extract key
276
+ secret_dict = json.loads(secret_value)
277
+ if not isinstance(secret_dict, dict):
278
+ raise AWSSecretManagerError(
279
+ f"Secret '{secret_ref.secret_name}' is not a JSON "
280
+ f"object, cannot extract key '{secret_ref.key}'"
281
+ )
282
+
283
+ if secret_ref.key not in secret_dict:
284
+ raise AWSSecretManagerError(
285
+ f"Key '{secret_ref.key}' not found in secret "
286
+ f"'{secret_ref.secret_name}'"
287
+ )
288
+
289
+ return str(secret_dict[secret_ref.key])
290
+
291
+
292
+ def create_secret_manager(
293
+ config: SecretManagerConfig | None,
294
+ ) -> SecretManagerBase:
295
+ """
296
+ Factory function to create the appropriate secret manager implementation.
297
+
298
+ Args:
299
+ config: SecretManager configuration from semantic model, or None
300
+
301
+ Returns:
302
+ SecretManagerBase: Appropriate implementation based on config type.
303
+ Returns NoOpSecretManager if config is None.
304
+
305
+ Raises:
306
+ ValueError: If the secret manager type is not supported
307
+
308
+ Example:
309
+ ```python
310
+ from qtype.semantic.model import (
311
+ AWSSecretManager as AWSSecretManagerConfig,
312
+ AWSAuthProvider
313
+ )
314
+ from qtype.interpreter.base.secrets import create_secret_manager
315
+
316
+ # Create config
317
+ config = AWSSecretManagerConfig(
318
+ id="my-secret-manager",
319
+ type="aws_secret_manager",
320
+ auth=auth_provider
321
+ )
322
+
323
+ # Create implementation
324
+ secret_manager = create_secret_manager(config)
325
+
326
+ # Use it directly - no None checks needed!
327
+ secret_value = secret_manager(secret_ref)
328
+ ```
329
+ """
330
+ if config is None:
331
+ return NoOpSecretManager()
332
+
333
+ if isinstance(config, AWSSecretManagerConfig):
334
+ return AWSSecretManager(config)
335
+
336
+ raise ValueError(
337
+ f"Unsupported secret manager type: {config.type}. "
338
+ f"Supported types: aws_secret_manager"
339
+ )
@@ -0,0 +1,74 @@
1
+ import hashlib
2
+ import json
3
+ import pathlib
4
+ from typing import Any
5
+
6
+ import diskcache as dc
7
+ from pydantic import BaseModel
8
+ from pydantic.json import pydantic_encoder
9
+
10
+ from qtype.base.types import CacheConfig
11
+ from qtype.interpreter.types import FlowMessage
12
+ from qtype.semantic.model import Step
13
+
14
+
15
+ def create_cache(config: CacheConfig | None, step_id: str) -> dc.Cache | None:
16
+ if config is None:
17
+ return None
18
+ cache_dir = pathlib.Path(config.directory)
19
+ if config.namespace:
20
+ cache_dir = cache_dir / config.namespace
21
+ cache_dir = cache_dir / step_id / config.version
22
+
23
+ return dc.Cache(
24
+ directory=str(cache_dir),
25
+ size_limit=0, # 0 = unlimited
26
+ eviction_policy="none", # disables auto-eviction
27
+ )
28
+
29
+
30
+ def cache_key(message: FlowMessage, step: Step) -> str:
31
+ """Generates a cache key based on the message content."""
32
+
33
+ inputs = {}
34
+ for var in step.inputs:
35
+ if var.id in message.variables:
36
+ value = message.variables[var.id]
37
+ if isinstance(value, BaseModel):
38
+ inputs[var.id] = value.model_dump()
39
+ else:
40
+ inputs[var.id] = value
41
+ else:
42
+ raise ValueError(
43
+ f"Input variable '{var.id}' not found in message -- caching can not be performed."
44
+ )
45
+ input_str = json.dumps(inputs, sort_keys=True, default=pydantic_encoder)
46
+ return hashlib.sha256(input_str.encode("utf-8")).hexdigest()
47
+
48
+
49
+ def to_cache_value(message: FlowMessage, step: Step) -> dict[str, Any]:
50
+ """Converts a FlowMessage to a serializable cache value."""
51
+ if message.is_failed():
52
+ return {"FlowMessage.__error__": message.error}
53
+ else:
54
+ outputs = {}
55
+ for var in step.outputs:
56
+ if var.id in message.variables:
57
+ outputs[var.id] = message.variables[var.id]
58
+ else:
59
+ raise ValueError(
60
+ f"Output variable '{var.id}' not found in message -- caching can not be performed."
61
+ )
62
+ return outputs
63
+
64
+
65
+ def from_cache_value(
66
+ cache_value: dict[str, Any], message: FlowMessage
67
+ ) -> FlowMessage:
68
+ """Reconstructs a FlowMessage from cached output values."""
69
+ if "FlowMessage.__error__" in cache_value:
70
+ msg = message.model_copy(deep=True)
71
+ msg.error = cache_value["FlowMessage.__error__"]
72
+ return msg
73
+ else:
74
+ return message.copy_with_variables(cache_value)