qtype 0.0.12__py3-none-any.whl → 0.1.3__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.
- qtype/application/commons/tools.py +1 -1
- qtype/application/converters/tools_from_api.py +476 -11
- qtype/application/converters/tools_from_module.py +38 -14
- qtype/application/converters/types.py +15 -30
- qtype/application/documentation.py +1 -1
- qtype/application/facade.py +102 -85
- qtype/base/types.py +227 -7
- qtype/cli.py +5 -1
- qtype/commands/convert.py +52 -6
- qtype/commands/generate.py +44 -4
- qtype/commands/run.py +78 -36
- qtype/commands/serve.py +74 -44
- qtype/commands/validate.py +37 -14
- qtype/commands/visualize.py +46 -25
- qtype/dsl/__init__.py +6 -5
- qtype/dsl/custom_types.py +1 -1
- qtype/dsl/domain_types.py +86 -5
- qtype/dsl/linker.py +384 -0
- qtype/dsl/loader.py +315 -0
- qtype/dsl/model.py +751 -263
- qtype/dsl/parser.py +200 -0
- qtype/dsl/types.py +50 -0
- qtype/interpreter/api.py +63 -136
- qtype/interpreter/auth/aws.py +19 -9
- qtype/interpreter/auth/generic.py +93 -16
- qtype/interpreter/base/base_step_executor.py +436 -0
- qtype/interpreter/base/batch_step_executor.py +171 -0
- qtype/interpreter/base/exceptions.py +50 -0
- qtype/interpreter/base/executor_context.py +91 -0
- qtype/interpreter/base/factory.py +84 -0
- qtype/interpreter/base/progress_tracker.py +110 -0
- qtype/interpreter/base/secrets.py +339 -0
- qtype/interpreter/base/step_cache.py +74 -0
- qtype/interpreter/base/stream_emitter.py +469 -0
- qtype/interpreter/conversions.py +471 -22
- qtype/interpreter/converters.py +79 -0
- qtype/interpreter/endpoints.py +355 -0
- qtype/interpreter/executors/agent_executor.py +242 -0
- qtype/interpreter/executors/aggregate_executor.py +93 -0
- qtype/interpreter/executors/bedrock_reranker_executor.py +195 -0
- qtype/interpreter/executors/decoder_executor.py +163 -0
- qtype/interpreter/executors/doc_to_text_executor.py +112 -0
- qtype/interpreter/executors/document_embedder_executor.py +107 -0
- qtype/interpreter/executors/document_search_executor.py +113 -0
- qtype/interpreter/executors/document_source_executor.py +118 -0
- qtype/interpreter/executors/document_splitter_executor.py +105 -0
- qtype/interpreter/executors/echo_executor.py +63 -0
- qtype/interpreter/executors/field_extractor_executor.py +165 -0
- qtype/interpreter/executors/file_source_executor.py +101 -0
- qtype/interpreter/executors/file_writer_executor.py +110 -0
- qtype/interpreter/executors/index_upsert_executor.py +232 -0
- qtype/interpreter/executors/invoke_embedding_executor.py +92 -0
- qtype/interpreter/executors/invoke_flow_executor.py +51 -0
- qtype/interpreter/executors/invoke_tool_executor.py +358 -0
- qtype/interpreter/executors/llm_inference_executor.py +272 -0
- qtype/interpreter/executors/prompt_template_executor.py +78 -0
- qtype/interpreter/executors/sql_source_executor.py +106 -0
- qtype/interpreter/executors/vector_search_executor.py +91 -0
- qtype/interpreter/flow.py +173 -22
- qtype/interpreter/logging_progress.py +61 -0
- qtype/interpreter/metadata_api.py +115 -0
- qtype/interpreter/resource_cache.py +5 -4
- qtype/interpreter/rich_progress.py +225 -0
- qtype/interpreter/stream/chat/__init__.py +15 -0
- qtype/interpreter/stream/chat/converter.py +391 -0
- qtype/interpreter/{chat → stream/chat}/file_conversions.py +2 -2
- qtype/interpreter/stream/chat/ui_request_to_domain_type.py +140 -0
- qtype/interpreter/stream/chat/vercel.py +609 -0
- qtype/interpreter/stream/utils/__init__.py +15 -0
- qtype/interpreter/stream/utils/build_vercel_ai_formatter.py +74 -0
- qtype/interpreter/stream/utils/callback_to_stream.py +66 -0
- qtype/interpreter/stream/utils/create_streaming_response.py +18 -0
- qtype/interpreter/stream/utils/default_chat_extract_text.py +20 -0
- qtype/interpreter/stream/utils/error_streaming_response.py +20 -0
- qtype/interpreter/telemetry.py +135 -8
- qtype/interpreter/tools/__init__.py +5 -0
- qtype/interpreter/tools/function_tool_helper.py +265 -0
- qtype/interpreter/types.py +330 -0
- qtype/interpreter/typing.py +83 -89
- qtype/interpreter/ui/404/index.html +1 -1
- qtype/interpreter/ui/404.html +1 -1
- qtype/interpreter/ui/_next/static/{OT8QJQW3J70VbDWWfrEMT → 20HoJN6otZ_LyHLHpCPE6}/_buildManifest.js +1 -1
- qtype/interpreter/ui/_next/static/chunks/434-b2112d19f25c44ff.js +36 -0
- qtype/interpreter/ui/_next/static/chunks/{964-ed4ab073db645007.js → 964-2b041321a01cbf56.js} +1 -1
- qtype/interpreter/ui/_next/static/chunks/app/{layout-5ccbc44fd528d089.js → layout-a05273ead5de2c41.js} +1 -1
- qtype/interpreter/ui/_next/static/chunks/app/page-8c67d16ac90d23cb.js +1 -0
- qtype/interpreter/ui/_next/static/chunks/ba12c10f-546f2714ff8abc66.js +1 -0
- qtype/interpreter/ui/_next/static/chunks/{main-6d261b6c5d6fb6c2.js → main-e26b9cb206da2cac.js} +1 -1
- qtype/interpreter/ui/_next/static/chunks/webpack-08642e441b39b6c2.js +1 -0
- qtype/interpreter/ui/_next/static/css/8a8d1269e362fef7.css +3 -0
- qtype/interpreter/ui/_next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
- qtype/interpreter/ui/icon.png +0 -0
- qtype/interpreter/ui/index.html +1 -1
- qtype/interpreter/ui/index.txt +5 -5
- qtype/semantic/checker.py +643 -0
- qtype/semantic/generate.py +268 -85
- qtype/semantic/loader.py +95 -0
- qtype/semantic/model.py +535 -163
- qtype/semantic/resolver.py +63 -19
- qtype/semantic/visualize.py +50 -35
- {qtype-0.0.12.dist-info → qtype-0.1.3.dist-info}/METADATA +21 -4
- qtype-0.1.3.dist-info/RECORD +137 -0
- qtype/dsl/base_types.py +0 -38
- qtype/dsl/validator.py +0 -464
- qtype/interpreter/batch/__init__.py +0 -0
- qtype/interpreter/batch/flow.py +0 -95
- qtype/interpreter/batch/sql_source.py +0 -95
- qtype/interpreter/batch/step.py +0 -63
- qtype/interpreter/batch/types.py +0 -41
- qtype/interpreter/batch/utils.py +0 -179
- qtype/interpreter/chat/chat_api.py +0 -237
- qtype/interpreter/chat/vercel.py +0 -314
- qtype/interpreter/exceptions.py +0 -10
- qtype/interpreter/step.py +0 -67
- qtype/interpreter/steps/__init__.py +0 -0
- qtype/interpreter/steps/agent.py +0 -114
- qtype/interpreter/steps/condition.py +0 -36
- qtype/interpreter/steps/decoder.py +0 -88
- qtype/interpreter/steps/llm_inference.py +0 -150
- qtype/interpreter/steps/prompt_template.py +0 -54
- qtype/interpreter/steps/search.py +0 -24
- qtype/interpreter/steps/tool.py +0 -53
- qtype/interpreter/streaming_helpers.py +0 -123
- qtype/interpreter/ui/_next/static/chunks/736-7fc606e244fedcb1.js +0 -36
- qtype/interpreter/ui/_next/static/chunks/app/page-c72e847e888e549d.js +0 -1
- qtype/interpreter/ui/_next/static/chunks/ba12c10f-22556063851a6df2.js +0 -1
- qtype/interpreter/ui/_next/static/chunks/webpack-8289c17c67827f22.js +0 -1
- qtype/interpreter/ui/_next/static/css/a262c53826df929b.css +0 -3
- qtype/interpreter/ui/_next/static/media/569ce4b8f30dc480-s.p.woff2 +0 -0
- qtype/interpreter/ui/favicon.ico +0 -0
- qtype/loader.py +0 -389
- qtype-0.0.12.dist-info/RECORD +0 -105
- /qtype/interpreter/ui/_next/static/{OT8QJQW3J70VbDWWfrEMT → 20HoJN6otZ_LyHLHpCPE6}/_ssgManifest.js +0 -0
- {qtype-0.0.12.dist-info → qtype-0.1.3.dist-info}/WHEEL +0 -0
- {qtype-0.0.12.dist-info → qtype-0.1.3.dist-info}/entry_points.txt +0 -0
- {qtype-0.0.12.dist-info → qtype-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {qtype-0.0.12.dist-info → qtype-0.1.3.dist-info}/top_level.txt +0 -0
qtype/semantic/generate.py
CHANGED
|
@@ -1,27 +1,45 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import inspect
|
|
3
3
|
import subprocess
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from functools import partial
|
|
4
6
|
from pathlib import Path
|
|
5
7
|
from textwrap import dedent
|
|
6
|
-
from typing import Any, Literal, Union, get_args, get_origin
|
|
8
|
+
from typing import Annotated, Any, Literal, Union, get_args, get_origin
|
|
7
9
|
|
|
8
10
|
import networkx as nx
|
|
9
11
|
|
|
12
|
+
import qtype.base.types as base_types
|
|
10
13
|
import qtype.dsl.model as dsl
|
|
11
|
-
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _is_dsl_type(type_obj: Any) -> bool:
|
|
17
|
+
"""Check if a type is a DSL type that should be converted to semantic."""
|
|
18
|
+
if not hasattr(type_obj, "__name__"):
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
# Check if it's defined in the DSL module
|
|
22
|
+
return (
|
|
23
|
+
hasattr(type_obj, "__module__")
|
|
24
|
+
and (
|
|
25
|
+
type_obj.__module__ == dsl.__name__
|
|
26
|
+
or type_obj.__module__ == base_types.__name__
|
|
27
|
+
)
|
|
28
|
+
and not type_obj.__name__.startswith("_")
|
|
29
|
+
)
|
|
30
|
+
|
|
12
31
|
|
|
13
32
|
FIELDS_TO_IGNORE = {"Application.references"}
|
|
14
33
|
TYPES_TO_IGNORE = {
|
|
15
34
|
"CustomType",
|
|
16
35
|
"DecoderFormat",
|
|
17
36
|
"Document",
|
|
18
|
-
"
|
|
37
|
+
"ListType",
|
|
19
38
|
"PrimitiveTypeEnum",
|
|
20
39
|
"StrictBaseModel",
|
|
21
|
-
"StructuralTypeEnum",
|
|
22
40
|
"TypeDefinition",
|
|
41
|
+
"ToolParameter",
|
|
23
42
|
"Variable",
|
|
24
|
-
"VariableType",
|
|
25
43
|
}
|
|
26
44
|
|
|
27
45
|
FROZEN_TYPES = {
|
|
@@ -31,6 +49,7 @@ FROZEN_TYPES = {
|
|
|
31
49
|
"Index",
|
|
32
50
|
"Memory",
|
|
33
51
|
"Model",
|
|
52
|
+
"Tool",
|
|
34
53
|
"VectorIndex",
|
|
35
54
|
}
|
|
36
55
|
|
|
@@ -76,7 +95,6 @@ def generate_semantic_model(args: argparse.Namespace) -> None:
|
|
|
76
95
|
cls.__module__ == dsl.__name__
|
|
77
96
|
and not name.startswith("_")
|
|
78
97
|
and name not in TYPES_TO_IGNORE
|
|
79
|
-
and not name.endswith("List")
|
|
80
98
|
):
|
|
81
99
|
dsl_classes.append((name, cls))
|
|
82
100
|
|
|
@@ -115,20 +133,23 @@ def generate_semantic_model(args: argparse.Namespace) -> None:
|
|
|
115
133
|
dedent("""
|
|
116
134
|
from __future__ import annotations
|
|
117
135
|
|
|
118
|
-
from
|
|
136
|
+
from functools import partial
|
|
137
|
+
from typing import Any, Literal, Union
|
|
119
138
|
|
|
120
|
-
from pydantic import BaseModel, Field,
|
|
139
|
+
from pydantic import BaseModel, Field, RootModel
|
|
121
140
|
|
|
122
|
-
# Import enums and type aliases
|
|
123
|
-
from qtype.
|
|
141
|
+
# Import enums, mixins, and type aliases
|
|
142
|
+
from qtype.base.types import BatchableStepMixin, BatchConfig, CachedStepMixin, ConcurrencyConfig, ConcurrentStepMixin # noqa: F401
|
|
124
143
|
from qtype.dsl.model import ( # noqa: F401
|
|
125
144
|
CustomType,
|
|
126
145
|
DecoderFormat,
|
|
146
|
+
ListType,
|
|
127
147
|
PrimitiveTypeEnum,
|
|
128
148
|
StepCardinality,
|
|
129
|
-
|
|
149
|
+
ToolParameter
|
|
130
150
|
)
|
|
131
151
|
from qtype.dsl.model import Variable as DSLVariable # noqa: F401
|
|
152
|
+
from qtype.dsl.model import VariableType # noqa: F401
|
|
132
153
|
from qtype.semantic.base_types import ImmutableModel
|
|
133
154
|
|
|
134
155
|
""").lstrip()
|
|
@@ -149,37 +170,18 @@ def generate_semantic_model(args: argparse.Namespace) -> None:
|
|
|
149
170
|
# Write classes
|
|
150
171
|
f.write("\n\n".join(generated))
|
|
151
172
|
|
|
152
|
-
# Write the
|
|
153
|
-
f.write("\n\n")
|
|
173
|
+
# Write the DocumentType
|
|
154
174
|
f.write(
|
|
155
|
-
dedent(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
cardinality: StepCardinality = Field(
|
|
166
|
-
StepCardinality.auto,
|
|
167
|
-
description="The cardinality of the flow, inferred from its steps when set to 'auto'.",
|
|
168
|
-
)
|
|
169
|
-
mode: Literal["Complete", "Chat"] = Field("Complete")
|
|
170
|
-
steps: list[Step] = Field(..., description="List of steps or step IDs.")
|
|
171
|
-
|
|
172
|
-
@model_validator(mode="after")
|
|
173
|
-
def infer_cardinality(self) -> "Flow":
|
|
174
|
-
if self.cardinality == StepCardinality.auto:
|
|
175
|
-
self.cardinality = StepCardinality.one
|
|
176
|
-
for step in self.steps:
|
|
177
|
-
if step.cardinality == StepCardinality.many:
|
|
178
|
-
self.cardinality = StepCardinality.many
|
|
179
|
-
break
|
|
180
|
-
return self
|
|
181
|
-
|
|
182
|
-
''').lstrip()
|
|
175
|
+
dedent("""\n\n
|
|
176
|
+
DocumentType = Union[
|
|
177
|
+
Application,
|
|
178
|
+
AuthorizationProviderList,
|
|
179
|
+
ModelList,
|
|
180
|
+
ToolList,
|
|
181
|
+
TypeList,
|
|
182
|
+
VariableList,
|
|
183
|
+
]
|
|
184
|
+
""")
|
|
183
185
|
)
|
|
184
186
|
|
|
185
187
|
# Format the file with Ruff
|
|
@@ -197,51 +199,142 @@ def format_with_ruff(file_path: str) -> None:
|
|
|
197
199
|
subprocess.run(["isort", file_path], check=True)
|
|
198
200
|
|
|
199
201
|
|
|
202
|
+
def _get_union_args(type_annotation):
|
|
203
|
+
"""Extract union args from a type, handling Annotated types."""
|
|
204
|
+
if get_origin(type_annotation) is Annotated:
|
|
205
|
+
# For Annotated[Union[...], ...], get the Union part
|
|
206
|
+
union_type = get_args(type_annotation)[0]
|
|
207
|
+
return get_args(union_type)
|
|
208
|
+
else:
|
|
209
|
+
return get_args(type_annotation)
|
|
210
|
+
|
|
211
|
+
|
|
200
212
|
DSL_ONLY_UNION_TYPES = {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
213
|
+
_get_union_args(dsl.ToolType): "Tool",
|
|
214
|
+
_get_union_args(dsl.StepType): "Step",
|
|
215
|
+
_get_union_args(dsl.AuthProviderType): "AuthorizationProvider",
|
|
216
|
+
_get_union_args(dsl.SecretManagerType): "SecretManager",
|
|
217
|
+
_get_union_args(dsl.SourceType): "Source",
|
|
218
|
+
_get_union_args(dsl.IndexType): "Index",
|
|
219
|
+
_get_union_args(dsl.ModelType): "Model",
|
|
205
220
|
}
|
|
206
221
|
|
|
207
222
|
|
|
223
|
+
def _is_dsl_only_union(args_without_str_none: tuple) -> tuple[bool, str]:
|
|
224
|
+
"""
|
|
225
|
+
Check if union represents a DSL-only type pattern.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
args_without_str_none: Union args with str and None filtered out
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Tuple of (is_dsl_only, semantic_type_name)
|
|
232
|
+
"""
|
|
233
|
+
if args_without_str_none and args_without_str_none in DSL_ONLY_UNION_TYPES:
|
|
234
|
+
return True, DSL_ONLY_UNION_TYPES[args_without_str_none]
|
|
235
|
+
return False, ""
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _resolve_optional_collection(args: tuple, has_none: bool) -> str | None:
|
|
239
|
+
"""
|
|
240
|
+
Handle list|None -> list pattern (empty collection = None).
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
args: Union type arguments
|
|
244
|
+
has_none: Whether None is in the union
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
Resolved type name if pattern matches, None otherwise
|
|
248
|
+
"""
|
|
249
|
+
if len(args) == 2 and has_none:
|
|
250
|
+
collection_types = [
|
|
251
|
+
arg for arg in args if get_origin(arg) in {list, dict}
|
|
252
|
+
]
|
|
253
|
+
if collection_types:
|
|
254
|
+
return dsl_to_semantic_type_name(collection_types[0])
|
|
255
|
+
return None
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _is_id_reference_pattern(
|
|
259
|
+
args: tuple, has_str: bool, has_secret_ref: bool
|
|
260
|
+
) -> bool:
|
|
261
|
+
"""
|
|
262
|
+
Check if union represents an ID reference pattern (str | Type).
|
|
263
|
+
|
|
264
|
+
ID references allow DSL to use string IDs that get resolved to objects
|
|
265
|
+
in the semantic model. Exception: str | SecretReference stays as-is.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
args: Union type arguments
|
|
269
|
+
has_str: Whether str is in the union
|
|
270
|
+
has_secret_ref: Whether SecretReference is in the union
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
True if this is an ID reference pattern
|
|
274
|
+
"""
|
|
275
|
+
return (
|
|
276
|
+
any(_is_dsl_type(arg) for arg in args)
|
|
277
|
+
and has_str
|
|
278
|
+
and not has_secret_ref
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _strip_str_from_union(args: tuple) -> tuple:
|
|
283
|
+
"""
|
|
284
|
+
Remove str component from union for ID reference pattern.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
args: Union type arguments
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
Args with str filtered out
|
|
291
|
+
"""
|
|
292
|
+
return tuple(arg for arg in args if arg is not str)
|
|
293
|
+
|
|
294
|
+
|
|
208
295
|
def _transform_union_type(args: tuple) -> str:
|
|
209
|
-
"""
|
|
296
|
+
"""
|
|
297
|
+
Transform Union types, handling string ID references and special cases.
|
|
298
|
+
|
|
299
|
+
This function handles the semantic type generation for union types,
|
|
300
|
+
with special handling for:
|
|
301
|
+
- DSL-only types (e.g., ToolType -> Tool)
|
|
302
|
+
- ID references (str | SomeType -> SomeType)
|
|
303
|
+
- SecretReference (str | SecretReference stays as-is)
|
|
304
|
+
- Optional types (Type | None)
|
|
210
305
|
|
|
306
|
+
Args:
|
|
307
|
+
args: Tuple of types in the union
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
String representation of the semantic type
|
|
311
|
+
"""
|
|
312
|
+
# Import SecretReference for direct type comparison
|
|
313
|
+
from qtype.dsl.model import SecretReference
|
|
314
|
+
|
|
315
|
+
# Pre-compute type characteristics
|
|
211
316
|
args_without_str_none = tuple(
|
|
212
317
|
arg for arg in args if arg is not str and arg is not type(None)
|
|
213
318
|
)
|
|
214
319
|
has_none = any(arg is type(None) for arg in args)
|
|
215
320
|
has_str = any(arg is str for arg in args)
|
|
321
|
+
has_secret_ref = any(arg is SecretReference for arg in args)
|
|
216
322
|
|
|
217
|
-
#
|
|
218
|
-
|
|
219
|
-
if
|
|
220
|
-
if has_none
|
|
221
|
-
# If we have a DSL type and None, we return the DSL type with None
|
|
222
|
-
return DSL_ONLY_UNION_TYPES[args_without_str_none] + " | None"
|
|
223
|
-
else:
|
|
224
|
-
# Note we don't handle the case where we have a DSL type and str,
|
|
225
|
-
# because that would indicate a reference to an ID, which we handle separately.
|
|
226
|
-
return DSL_ONLY_UNION_TYPES[args_without_str_none]
|
|
227
|
-
|
|
228
|
-
# Handle the case where we have a list | None, which in the dsl is needed, but here we will just have an empty list.
|
|
229
|
-
if len(args) == 2:
|
|
230
|
-
list_elems = [
|
|
231
|
-
arg for arg in args if get_origin(arg) in set([list, dict])
|
|
232
|
-
]
|
|
233
|
-
if len(list_elems) > 0 and has_none:
|
|
234
|
-
# If we have a list and None, we return the list type
|
|
235
|
-
# This is to handle cases like List[SomeType] | None
|
|
236
|
-
# which in the DSL is needed, but here we will just have an empty list.
|
|
237
|
-
return dsl_to_semantic_type_name(list_elems[0])
|
|
238
|
-
|
|
239
|
-
# If the union contains a DSL type and a str, we need to drop the str
|
|
240
|
-
if any(_is_dsl_type(arg) for arg in args) and has_str:
|
|
241
|
-
# There is a DSL type and a str, which indicates something that can reference an ID.
|
|
242
|
-
# drop the str
|
|
243
|
-
args = tuple(arg for arg in args if arg is not str)
|
|
323
|
+
# Handle DSL-only union types (e.g., ToolType -> Tool)
|
|
324
|
+
is_dsl_only, dsl_semantic_name = _is_dsl_only_union(args_without_str_none)
|
|
325
|
+
if is_dsl_only:
|
|
326
|
+
return dsl_semantic_name + " | None" if has_none else dsl_semantic_name
|
|
244
327
|
|
|
328
|
+
# Handle list | None -> list (empty list is equivalent to None)
|
|
329
|
+
if resolved_collection := _resolve_optional_collection(args, has_none):
|
|
330
|
+
return resolved_collection
|
|
331
|
+
|
|
332
|
+
# Handle ID references: str | SomeType -> SomeType
|
|
333
|
+
# Exception: str | SecretReference should remain as-is
|
|
334
|
+
if _is_id_reference_pattern(args, has_str, has_secret_ref):
|
|
335
|
+
args = _strip_str_from_union(args)
|
|
336
|
+
|
|
337
|
+
# Convert remaining types to semantic type names
|
|
245
338
|
return " | ".join(dsl_to_semantic_type_name(a) for a in args)
|
|
246
339
|
|
|
247
340
|
|
|
@@ -259,6 +352,27 @@ def dsl_to_semantic_type_name(field_type: Any) -> str:
|
|
|
259
352
|
origin = get_origin(field_type)
|
|
260
353
|
args = get_args(field_type)
|
|
261
354
|
|
|
355
|
+
# Handle Reference types - unwrap to get the actual type
|
|
356
|
+
# Reference[T] is a Pydantic generic model, so get_origin returns None
|
|
357
|
+
# Instead, check if Reference is in the MRO
|
|
358
|
+
if (
|
|
359
|
+
hasattr(field_type, "__mro__")
|
|
360
|
+
and base_types.Reference in field_type.__mro__
|
|
361
|
+
):
|
|
362
|
+
# Reference[T] becomes just T in semantic model
|
|
363
|
+
# The actual type parameter is in __pydantic_generic_metadata__ for Pydantic generic models
|
|
364
|
+
if hasattr(field_type, "__pydantic_generic_metadata__"):
|
|
365
|
+
metadata = field_type.__pydantic_generic_metadata__
|
|
366
|
+
if "args" in metadata and metadata["args"]:
|
|
367
|
+
return dsl_to_semantic_type_name(metadata["args"][0])
|
|
368
|
+
return "Any" # Fallback for untyped Reference
|
|
369
|
+
|
|
370
|
+
# Handle Annotated types - extract the underlying type
|
|
371
|
+
if origin is Annotated:
|
|
372
|
+
# For Annotated[SomeType, ...], we want to process SomeType
|
|
373
|
+
if args:
|
|
374
|
+
return dsl_to_semantic_type_name(args[0])
|
|
375
|
+
|
|
262
376
|
if origin is Union or (
|
|
263
377
|
hasattr(field_type, "__class__")
|
|
264
378
|
and field_type.__class__.__name__ == "UnionType"
|
|
@@ -270,7 +384,13 @@ def dsl_to_semantic_type_name(field_type: Any) -> str:
|
|
|
270
384
|
# Format literal values
|
|
271
385
|
literal_values = []
|
|
272
386
|
for arg in args:
|
|
273
|
-
if isinstance(arg,
|
|
387
|
+
if isinstance(arg, Enum):
|
|
388
|
+
# Keep the enum reference for semantic models (e.g., StepCardinality.one)
|
|
389
|
+
# not the string value
|
|
390
|
+
enum_class_name = arg.__class__.__name__
|
|
391
|
+
enum_value_name = arg.name
|
|
392
|
+
literal_values.append(f"{enum_class_name}.{enum_value_name}")
|
|
393
|
+
elif isinstance(arg, str):
|
|
274
394
|
literal_values.append(f'"{arg}"')
|
|
275
395
|
else:
|
|
276
396
|
literal_values.append(str(arg))
|
|
@@ -319,16 +439,35 @@ def generate_semantic_class(class_name: str, cls: type) -> str:
|
|
|
319
439
|
if inspect.isabstract(cls):
|
|
320
440
|
inheritance += ", ABC"
|
|
321
441
|
|
|
322
|
-
#
|
|
442
|
+
# Collect all base classes from DSL and base_types modules
|
|
443
|
+
base_classes = []
|
|
323
444
|
for base in cls.__bases__:
|
|
324
445
|
if (
|
|
325
446
|
hasattr(base, "__module__")
|
|
326
|
-
and
|
|
447
|
+
and (
|
|
448
|
+
base.__module__ == dsl.__name__
|
|
449
|
+
or base.__module__ == base_types.__name__
|
|
450
|
+
)
|
|
327
451
|
and base.__name__ not in TYPES_TO_IGNORE
|
|
328
452
|
and not base.__name__.startswith("_")
|
|
329
453
|
):
|
|
330
|
-
|
|
331
|
-
|
|
454
|
+
base_classes.append(base)
|
|
455
|
+
|
|
456
|
+
# Build inheritance string
|
|
457
|
+
if base_classes:
|
|
458
|
+
# Process DSL classes first, then mixins
|
|
459
|
+
dsl_bases = [
|
|
460
|
+
b.__name__ for b in base_classes if b.__module__ == dsl.__name__
|
|
461
|
+
]
|
|
462
|
+
mixin_bases = [
|
|
463
|
+
b.__name__
|
|
464
|
+
for b in base_classes
|
|
465
|
+
if b.__module__ == base_types.__name__
|
|
466
|
+
]
|
|
467
|
+
|
|
468
|
+
if dsl_bases:
|
|
469
|
+
# Inherit from the DSL class
|
|
470
|
+
semantic_base = dsl_bases[0]
|
|
332
471
|
if inspect.isabstract(cls):
|
|
333
472
|
inheritance = f"ABC, {semantic_base}"
|
|
334
473
|
else:
|
|
@@ -336,7 +475,15 @@ def generate_semantic_class(class_name: str, cls: type) -> str:
|
|
|
336
475
|
if semantic_name == "Tool":
|
|
337
476
|
# Tools should inherit from Step and be immutable
|
|
338
477
|
inheritance = f"{semantic_base}, ImmutableModel"
|
|
339
|
-
|
|
478
|
+
|
|
479
|
+
# Add mixins to the inheritance - must come BEFORE BaseModel for correct MRO
|
|
480
|
+
if mixin_bases:
|
|
481
|
+
if inheritance == "BaseModel":
|
|
482
|
+
# Mixins must come before BaseModel
|
|
483
|
+
inheritance = f"{', '.join(mixin_bases)}, BaseModel"
|
|
484
|
+
else:
|
|
485
|
+
# If we have other bases, append mixins
|
|
486
|
+
inheritance = f"{inheritance}, {', '.join(mixin_bases)}"
|
|
340
487
|
|
|
341
488
|
# Get field information from the class - only fields defined on this class, not inherited
|
|
342
489
|
fields = []
|
|
@@ -350,6 +497,7 @@ def generate_semantic_class(class_name: str, cls: type) -> str:
|
|
|
350
497
|
field_info = cls.model_fields[field_name]
|
|
351
498
|
field_type = field_info.annotation
|
|
352
499
|
field_default = field_info.default
|
|
500
|
+
field_default_factory = field_info.default_factory
|
|
353
501
|
field_description = getattr(field_info, "description", None)
|
|
354
502
|
|
|
355
503
|
# Transform the field type
|
|
@@ -365,7 +513,11 @@ def generate_semantic_class(class_name: str, cls: type) -> str:
|
|
|
365
513
|
|
|
366
514
|
# Create field definition
|
|
367
515
|
field_def = create_field_definition(
|
|
368
|
-
field_name,
|
|
516
|
+
field_name,
|
|
517
|
+
semantic_type,
|
|
518
|
+
field_default,
|
|
519
|
+
field_default_factory,
|
|
520
|
+
field_description,
|
|
369
521
|
)
|
|
370
522
|
fields.append(field_def)
|
|
371
523
|
|
|
@@ -387,6 +539,7 @@ def create_field_definition(
|
|
|
387
539
|
field_name: str,
|
|
388
540
|
field_type: str,
|
|
389
541
|
field_default: Any,
|
|
542
|
+
field_default_factory: Any,
|
|
390
543
|
field_description: str | None,
|
|
391
544
|
) -> str:
|
|
392
545
|
"""Create a field definition string."""
|
|
@@ -397,11 +550,29 @@ def create_field_definition(
|
|
|
397
550
|
|
|
398
551
|
# Handle default values
|
|
399
552
|
# Check for PydanticUndefined (required field)
|
|
400
|
-
from enum import Enum
|
|
401
|
-
|
|
402
553
|
from pydantic_core import PydanticUndefined
|
|
403
554
|
|
|
404
|
-
|
|
555
|
+
# Check if there's a default_factory
|
|
556
|
+
if field_default_factory is not None:
|
|
557
|
+
# Handle default_factory - check if it's a partial
|
|
558
|
+
if isinstance(field_default_factory, partial):
|
|
559
|
+
# For partial, we need to serialize it properly
|
|
560
|
+
func_name = field_default_factory.func.__name__
|
|
561
|
+
# Get the keyword arguments from the partial
|
|
562
|
+
kwargs_str = ", ".join(
|
|
563
|
+
f"{k}={v}" if not isinstance(v, str) else f'{k}="{v}"'
|
|
564
|
+
for k, v in field_default_factory.keywords.items()
|
|
565
|
+
)
|
|
566
|
+
default_part = (
|
|
567
|
+
f"default_factory=partial({func_name}, {kwargs_str})"
|
|
568
|
+
)
|
|
569
|
+
else:
|
|
570
|
+
# Regular factory function
|
|
571
|
+
factory_name = getattr(
|
|
572
|
+
field_default_factory, "__name__", str(field_default_factory)
|
|
573
|
+
)
|
|
574
|
+
default_part = f"default_factory={factory_name}"
|
|
575
|
+
elif field_default is PydanticUndefined or field_default is ...:
|
|
405
576
|
default_part = "..."
|
|
406
577
|
elif field_default is None:
|
|
407
578
|
default_part = "None"
|
|
@@ -426,7 +597,12 @@ def create_field_definition(
|
|
|
426
597
|
default_part = str(field_default)
|
|
427
598
|
|
|
428
599
|
# Create Field definition
|
|
429
|
-
field_parts
|
|
600
|
+
# If using default_factory, don't include it in field_parts list initially
|
|
601
|
+
if field_default_factory is not None:
|
|
602
|
+
field_parts = []
|
|
603
|
+
else:
|
|
604
|
+
field_parts = [default_part]
|
|
605
|
+
|
|
430
606
|
if field_description:
|
|
431
607
|
# Escape quotes and handle multiline descriptions
|
|
432
608
|
escaped_desc = field_description.replace('"', '\\"').replace(
|
|
@@ -436,6 +612,13 @@ def create_field_definition(
|
|
|
436
612
|
if alias_part:
|
|
437
613
|
field_parts.append(alias_part.lstrip(", "))
|
|
438
614
|
|
|
439
|
-
|
|
615
|
+
# Handle default_factory in the Field() call
|
|
616
|
+
if field_default_factory is not None:
|
|
617
|
+
if field_parts:
|
|
618
|
+
field_def = f"Field({default_part}, {', '.join(field_parts)})"
|
|
619
|
+
else:
|
|
620
|
+
field_def = f"Field({default_part})"
|
|
621
|
+
else:
|
|
622
|
+
field_def = f"Field({', '.join(field_parts)})"
|
|
440
623
|
|
|
441
624
|
return f" {field_name}: {field_type} = {field_def}"
|
qtype/semantic/loader.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Semantic model loading and resolution.
|
|
3
|
+
|
|
4
|
+
This is the main entry point for loading QType specifications.
|
|
5
|
+
Coordinates the pipeline: load → parse → link → resolve → check
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from qtype.base.types import CustomTypeRegistry
|
|
13
|
+
from qtype.dsl.linker import link
|
|
14
|
+
from qtype.dsl.loader import load_yaml_file, load_yaml_string
|
|
15
|
+
from qtype.dsl.parser import parse_document
|
|
16
|
+
from qtype.semantic.checker import check
|
|
17
|
+
from qtype.semantic.model import DocumentType
|
|
18
|
+
from qtype.semantic.resolver import resolve
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load(
|
|
22
|
+
source: str | Path,
|
|
23
|
+
) -> tuple[DocumentType, CustomTypeRegistry]:
|
|
24
|
+
"""
|
|
25
|
+
Load a QType YAML file, validate it, and return resolved semantic model.
|
|
26
|
+
|
|
27
|
+
This function coordinates the complete loading pipeline:
|
|
28
|
+
1. Load YAML (with env vars and includes)
|
|
29
|
+
2. Parse into DSL models (with custom type building)
|
|
30
|
+
3. Link references (resolve IDs to objects)
|
|
31
|
+
4. Resolve to semantic models (DSL → IR)
|
|
32
|
+
5. Check semantic rules
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
source: File path (str or Path) to load. Use load_from_string()
|
|
36
|
+
for raw YAML content.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Tuple of (SemanticDocumentType, CustomTypeRegistry)
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
YAMLLoadError: If YAML parsing fails
|
|
43
|
+
ValueError: If validation fails
|
|
44
|
+
DuplicateComponentError: If duplicate IDs found
|
|
45
|
+
ReferenceNotFoundError: If reference resolution fails
|
|
46
|
+
QTypeSemanticError: If semantic rules violated
|
|
47
|
+
"""
|
|
48
|
+
# Load from file path
|
|
49
|
+
if isinstance(source, Path):
|
|
50
|
+
yaml_data = load_yaml_file(source)
|
|
51
|
+
else:
|
|
52
|
+
# Assume str is a file path
|
|
53
|
+
yaml_data = load_yaml_file(source)
|
|
54
|
+
|
|
55
|
+
dsl_doc, types = parse_document(yaml_data)
|
|
56
|
+
linked_doc = link(dsl_doc)
|
|
57
|
+
semantic_doc = resolve(linked_doc)
|
|
58
|
+
check(semantic_doc)
|
|
59
|
+
return semantic_doc, types
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def load_from_string(
|
|
63
|
+
content: str, base_path: str | Path | None = None
|
|
64
|
+
) -> tuple[DocumentType, CustomTypeRegistry]:
|
|
65
|
+
"""
|
|
66
|
+
Load a QType YAML from string content.
|
|
67
|
+
|
|
68
|
+
This function coordinates the complete loading pipeline:
|
|
69
|
+
1. Load YAML (with env vars and includes)
|
|
70
|
+
2. Parse into DSL models (with custom type building)
|
|
71
|
+
3. Link references (resolve IDs to objects)
|
|
72
|
+
4. Resolve to semantic models (DSL → IR)
|
|
73
|
+
5. Check semantic rules
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
content: Raw YAML content as string
|
|
77
|
+
base_path: Base path for resolving relative includes (default: cwd)
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Tuple of (SemanticDocumentType, CustomTypeRegistry)
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
YAMLLoadError: If YAML parsing fails
|
|
84
|
+
ValueError: If validation fails
|
|
85
|
+
DuplicateComponentError: If duplicate IDs found
|
|
86
|
+
ReferenceNotFoundError: If reference resolution fails
|
|
87
|
+
QTypeSemanticError: If semantic rules violated
|
|
88
|
+
"""
|
|
89
|
+
yaml_data = load_yaml_string(content, base_path=base_path)
|
|
90
|
+
|
|
91
|
+
dsl_doc, types = parse_document(yaml_data)
|
|
92
|
+
linked_doc = link(dsl_doc)
|
|
93
|
+
semantic_doc = resolve(linked_doc)
|
|
94
|
+
check(semantic_doc)
|
|
95
|
+
return semantic_doc, types
|