qtype 0.0.16__py3-none-any.whl → 0.1.1__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 +5 -5
- qtype/application/converters/tools_from_module.py +2 -2
- qtype/application/converters/types.py +14 -43
- qtype/application/documentation.py +1 -1
- qtype/application/facade.py +94 -73
- qtype/base/types.py +227 -7
- qtype/cli.py +4 -0
- qtype/commands/convert.py +20 -8
- qtype/commands/generate.py +19 -27
- qtype/commands/run.py +73 -36
- qtype/commands/serve.py +74 -54
- qtype/commands/validate.py +34 -8
- qtype/commands/visualize.py +46 -22
- qtype/dsl/__init__.py +6 -5
- qtype/dsl/custom_types.py +1 -1
- qtype/dsl/domain_types.py +65 -5
- qtype/dsl/linker.py +384 -0
- qtype/dsl/loader.py +315 -0
- qtype/dsl/model.py +612 -363
- qtype/dsl/parser.py +200 -0
- qtype/dsl/types.py +50 -0
- qtype/interpreter/api.py +57 -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 +74 -0
- qtype/interpreter/base/factory.py +117 -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 +462 -22
- qtype/interpreter/converters.py +77 -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/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 +122 -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 +160 -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 +228 -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 +159 -22
- 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/{nUaw6_IwRwPqkzwe5s725 → 20HoJN6otZ_LyHLHpCPE6}/_buildManifest.js +1 -1
- qtype/interpreter/ui/_next/static/chunks/{393-8fd474427f8e19ce.js → 434-b2112d19f25c44ff.js} +3 -3
- 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/css/8a8d1269e362fef7.css +3 -0
- qtype/interpreter/ui/icon.png +0 -0
- qtype/interpreter/ui/index.html +1 -1
- qtype/interpreter/ui/index.txt +4 -4
- qtype/semantic/checker.py +583 -0
- qtype/semantic/generate.py +262 -83
- qtype/semantic/loader.py +95 -0
- qtype/semantic/model.py +436 -159
- qtype/semantic/resolver.py +63 -19
- qtype/semantic/visualize.py +28 -31
- {qtype-0.0.16.dist-info → qtype-0.1.1.dist-info}/METADATA +16 -3
- qtype-0.1.1.dist-info/RECORD +135 -0
- qtype/dsl/base_types.py +0 -38
- qtype/dsl/validator.py +0 -465
- qtype/interpreter/batch/__init__.py +0 -0
- qtype/interpreter/batch/file_sink_source.py +0 -162
- qtype/interpreter/batch/flow.py +0 -95
- qtype/interpreter/batch/sql_source.py +0 -92
- qtype/interpreter/batch/step.py +0 -74
- qtype/interpreter/batch/types.py +0 -41
- qtype/interpreter/batch/utils.py +0 -178
- 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 -171
- qtype/interpreter/steps/prompt_template.py +0 -54
- qtype/interpreter/steps/search.py +0 -24
- qtype/interpreter/steps/tool.py +0 -219
- qtype/interpreter/streaming_helpers.py +0 -123
- qtype/interpreter/ui/_next/static/chunks/app/page-7e26b6156cfb55d3.js +0 -1
- qtype/interpreter/ui/_next/static/chunks/ba12c10f-22556063851a6df2.js +0 -1
- qtype/interpreter/ui/_next/static/css/b40532b0db09cce3.css +0 -3
- qtype/interpreter/ui/favicon.ico +0 -0
- qtype/loader.py +0 -390
- qtype-0.0.16.dist-info/RECORD +0 -106
- /qtype/interpreter/ui/_next/static/{nUaw6_IwRwPqkzwe5s725 → 20HoJN6otZ_LyHLHpCPE6}/_ssgManifest.js +0 -0
- {qtype-0.0.16.dist-info → qtype-0.1.1.dist-info}/WHEEL +0 -0
- {qtype-0.0.16.dist-info → qtype-0.1.1.dist-info}/entry_points.txt +0 -0
- {qtype-0.0.16.dist-info → qtype-0.1.1.dist-info}/licenses/LICENSE +0 -0
- {qtype-0.0.16.dist-info → qtype-0.1.1.dist-info}/top_level.txt +0 -0
qtype/dsl/domain_types.py
CHANGED
|
@@ -5,7 +5,7 @@ from typing import Any
|
|
|
5
5
|
|
|
6
6
|
from pydantic import Field
|
|
7
7
|
|
|
8
|
-
from qtype.
|
|
8
|
+
from qtype.base.types import PrimitiveTypeEnum, StrictBaseModel
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class Embedding(StrictBaseModel):
|
|
@@ -14,11 +14,12 @@ class Embedding(StrictBaseModel):
|
|
|
14
14
|
vector: list[float] = Field(
|
|
15
15
|
..., description="The vector representation of the embedding."
|
|
16
16
|
)
|
|
17
|
-
|
|
18
|
-
None, description="The original
|
|
17
|
+
content: Any | None = Field(
|
|
18
|
+
None, description="The original content that was embedded."
|
|
19
19
|
)
|
|
20
|
-
metadata: dict[str,
|
|
21
|
-
|
|
20
|
+
metadata: dict[str, Any] = Field(
|
|
21
|
+
default_factory=dict,
|
|
22
|
+
description="Metadata associated with the embedding.",
|
|
22
23
|
)
|
|
23
24
|
|
|
24
25
|
|
|
@@ -57,3 +58,62 @@ class ChatMessage(StrictBaseModel):
|
|
|
57
58
|
...,
|
|
58
59
|
description="The content blocks of the chat message, which can include text, images, or other media.",
|
|
59
60
|
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class RAGDocument(StrictBaseModel):
|
|
64
|
+
"""A standard, built-in representation of a document used in Retrieval-Augmented Generation (RAG)."""
|
|
65
|
+
|
|
66
|
+
content: Any = Field(..., description="The main content of the document.")
|
|
67
|
+
file_id: str = Field(..., description="An unique identifier for the file.")
|
|
68
|
+
file_name: str = Field(..., description="The name of the file.")
|
|
69
|
+
uri: str | None = Field(
|
|
70
|
+
None, description="The URI where the document can be found."
|
|
71
|
+
)
|
|
72
|
+
metadata: dict[str, Any] = Field(
|
|
73
|
+
default_factory=dict,
|
|
74
|
+
description="Metadata associated with the document.",
|
|
75
|
+
)
|
|
76
|
+
type: PrimitiveTypeEnum = Field(
|
|
77
|
+
...,
|
|
78
|
+
description="The type of the document content (e.g., 'text', 'image').",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class RAGChunk(Embedding):
|
|
83
|
+
"""A standard, built-in representation of a chunk of a document used in Retrieval-Augmented Generation (RAG)."""
|
|
84
|
+
|
|
85
|
+
chunk_id: str = Field(
|
|
86
|
+
..., description="An unique identifier for the chunk."
|
|
87
|
+
)
|
|
88
|
+
document_id: str = Field(
|
|
89
|
+
..., description="The identifier of the parent document."
|
|
90
|
+
)
|
|
91
|
+
vector: list[float] | None = Field(
|
|
92
|
+
None, description="Optional vector embedding for the chunk."
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class RAGSearchResult(StrictBaseModel):
|
|
97
|
+
"""A standard, built-in representation of a search result from a RAG vector search."""
|
|
98
|
+
|
|
99
|
+
chunk: RAGChunk = Field(
|
|
100
|
+
..., description="The RAG chunk returned as a search result."
|
|
101
|
+
)
|
|
102
|
+
score: float = Field(
|
|
103
|
+
...,
|
|
104
|
+
description="The similarity score of the chunk with respect to the query.",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class AggregateStats(StrictBaseModel):
|
|
109
|
+
"""A standard, built-in representation of aggregate statistics."""
|
|
110
|
+
|
|
111
|
+
num_successful: int = Field(
|
|
112
|
+
..., description="The count of successful messages processed."
|
|
113
|
+
)
|
|
114
|
+
num_failed: int = Field(
|
|
115
|
+
..., description="The count of failed messages processed."
|
|
116
|
+
)
|
|
117
|
+
num_total: int = Field(
|
|
118
|
+
..., description="The total count of messages processed."
|
|
119
|
+
)
|
qtype/dsl/linker.py
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
from typing import Any, Dict, Type
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, RootModel
|
|
4
|
+
|
|
5
|
+
import qtype.base.types as base_types
|
|
6
|
+
import qtype.dsl.domain_types
|
|
7
|
+
import qtype.dsl.model as dsl
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class QTypeValidationError(Exception):
|
|
11
|
+
"""Raised when there's an error during QType validation."""
|
|
12
|
+
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DuplicateComponentError(QTypeValidationError):
|
|
17
|
+
"""Raised when there are duplicate components with the same ID."""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
obj_id: str,
|
|
22
|
+
found_obj: qtype.dsl.domain_types.StrictBaseModel,
|
|
23
|
+
existing_obj: qtype.dsl.domain_types.StrictBaseModel,
|
|
24
|
+
):
|
|
25
|
+
super().__init__(
|
|
26
|
+
f"Duplicate component with ID {obj_id} found:\n"
|
|
27
|
+
+ str(found_obj.model_dump_json())
|
|
28
|
+
+ "\nAlready exists:\n"
|
|
29
|
+
+ str(existing_obj.model_dump_json())
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ComponentNotFoundError(QTypeValidationError):
|
|
34
|
+
"""Raised when a component is not found in the DSL Application."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, component_name: str):
|
|
37
|
+
super().__init__(
|
|
38
|
+
f"Component with name '{component_name}' not found in the DSL Application."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ReferenceNotFoundError(QTypeValidationError):
|
|
43
|
+
"""Raised when a reference is not found in the lookup map."""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
reference: str,
|
|
48
|
+
type_hint: str | None = None,
|
|
49
|
+
available_refs: list[str] | None = None,
|
|
50
|
+
):
|
|
51
|
+
if type_hint:
|
|
52
|
+
msg = (
|
|
53
|
+
f"Reference '{reference}' not found for type '{type_hint}'.\n"
|
|
54
|
+
)
|
|
55
|
+
else:
|
|
56
|
+
msg = f"Reference '{reference}' not found.\n"
|
|
57
|
+
|
|
58
|
+
# Add helpful suggestions if we have available references
|
|
59
|
+
if available_refs:
|
|
60
|
+
# Find similar names
|
|
61
|
+
similar = [
|
|
62
|
+
ref
|
|
63
|
+
for ref in available_refs
|
|
64
|
+
if reference.lower() in ref.lower()
|
|
65
|
+
or ref.lower() in reference.lower()
|
|
66
|
+
]
|
|
67
|
+
if similar:
|
|
68
|
+
msg += f"Did you mean one of these? {', '.join(similar[:5])}"
|
|
69
|
+
elif len(available_refs) <= 10:
|
|
70
|
+
msg += f"Available references: {', '.join(available_refs)}"
|
|
71
|
+
else:
|
|
72
|
+
msg += (
|
|
73
|
+
f"There are {len(available_refs)} available "
|
|
74
|
+
"references. Check your spelling."
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
super().__init__(msg)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _update_map_with_unique_check(
|
|
81
|
+
current_map: Dict[str, qtype.dsl.domain_types.StrictBaseModel],
|
|
82
|
+
new_objects: list[qtype.dsl.domain_types.StrictBaseModel],
|
|
83
|
+
) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Update a map with new objects, ensuring unique IDs.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
current_map: The current map of objects by ID.
|
|
89
|
+
new_objects: List of new objects to add to the map.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Updated map with new objects added, ensuring unique IDs.
|
|
93
|
+
"""
|
|
94
|
+
for obj in new_objects:
|
|
95
|
+
if obj is None:
|
|
96
|
+
# If the object is None, we skip it.
|
|
97
|
+
continue
|
|
98
|
+
if isinstance(obj, str) or isinstance(obj, base_types.Reference):
|
|
99
|
+
# If the object is a string, we assume it is an ID and skip it.
|
|
100
|
+
# This is a special case where we do not want to add the string itself.
|
|
101
|
+
continue
|
|
102
|
+
# Note: There is no current abstraction for the `id` field, so we assume it exists.
|
|
103
|
+
obj_id = obj.id # type: ignore[attr-defined]
|
|
104
|
+
# If the object already exists in the map, we check if it is the same object.
|
|
105
|
+
# If it is not the same object, we raise an error.
|
|
106
|
+
# This ensures that we do not have duplicate components with the same ID.
|
|
107
|
+
if obj_id in current_map and id(current_map[obj_id]) != id(obj):
|
|
108
|
+
raise DuplicateComponentError(obj.id, obj, current_map[obj_id]) # type: ignore
|
|
109
|
+
else:
|
|
110
|
+
current_map[obj_id] = obj
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _collect_components_from_object(
|
|
114
|
+
obj: qtype.dsl.domain_types.StrictBaseModel,
|
|
115
|
+
) -> list[qtype.dsl.domain_types.StrictBaseModel]:
|
|
116
|
+
"""
|
|
117
|
+
Collect all components from an object that have IDs.
|
|
118
|
+
This includes the object itself and any nested components.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
obj: The object to extract components from.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
List of components with IDs.
|
|
125
|
+
"""
|
|
126
|
+
components = []
|
|
127
|
+
|
|
128
|
+
# Add the object itself if it has an ID
|
|
129
|
+
if hasattr(obj, "id"):
|
|
130
|
+
components.append(obj)
|
|
131
|
+
|
|
132
|
+
# For Flow, also collect embedded steps, inputs, and outputs
|
|
133
|
+
if isinstance(obj, dsl.Flow):
|
|
134
|
+
components.extend(obj.steps or []) # type: ignore
|
|
135
|
+
components.extend(obj.variables or []) # type: ignore
|
|
136
|
+
|
|
137
|
+
return components
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _update_maps_with_embedded_objects(
|
|
141
|
+
lookup_map: Dict[str, qtype.dsl.domain_types.StrictBaseModel],
|
|
142
|
+
embedded_objects: list[qtype.dsl.domain_types.StrictBaseModel],
|
|
143
|
+
) -> None:
|
|
144
|
+
"""
|
|
145
|
+
Update lookup maps with embedded objects.
|
|
146
|
+
Embedded objects are when the user specifies the object and not just the ID.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
lookup_maps: The current lookup maps to update.
|
|
150
|
+
embedded_objects: List of embedded objects to add to the maps.
|
|
151
|
+
"""
|
|
152
|
+
for obj in embedded_objects:
|
|
153
|
+
components = _collect_components_from_object(obj)
|
|
154
|
+
_update_map_with_unique_check(lookup_map, components)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _build_lookup_maps(
|
|
158
|
+
document: Any,
|
|
159
|
+
lookup_map: Dict[str, qtype.dsl.domain_types.StrictBaseModel]
|
|
160
|
+
| None = None,
|
|
161
|
+
) -> Dict[str, qtype.dsl.domain_types.StrictBaseModel]:
|
|
162
|
+
"""
|
|
163
|
+
Build lookup map for all objects in a DSL Document.
|
|
164
|
+
This function creates a dictionary of id -> component, where each key is a
|
|
165
|
+
component id and the value is the component.
|
|
166
|
+
|
|
167
|
+
Works with any Document type (Application, Flow, *List types, etc.).
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
document: The DSL Document to build lookup maps for.
|
|
171
|
+
Can be Application, Flow, or any RootModel list type.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Dict[str, dsl.StrictBaseModel]: A dictionary of lookup maps
|
|
175
|
+
|
|
176
|
+
Throws:
|
|
177
|
+
QTypeValidationError: If there are duplicate components with the same ID.
|
|
178
|
+
"""
|
|
179
|
+
if lookup_map is None:
|
|
180
|
+
lookup_map = {}
|
|
181
|
+
|
|
182
|
+
# Handle Application specially since it has multiple component lists
|
|
183
|
+
if isinstance(document, dsl.Application):
|
|
184
|
+
component_names = {
|
|
185
|
+
f
|
|
186
|
+
for f in dsl.Application.model_fields.keys()
|
|
187
|
+
if f not in {"id", "references", "description"}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for component_name in component_names:
|
|
191
|
+
if not hasattr(document, component_name):
|
|
192
|
+
raise ComponentNotFoundError(component_name)
|
|
193
|
+
components = getattr(document, component_name) or []
|
|
194
|
+
if not isinstance(components, list):
|
|
195
|
+
components = [components] # Ensure we have a list
|
|
196
|
+
_update_map_with_unique_check(lookup_map, components)
|
|
197
|
+
_update_maps_with_embedded_objects(lookup_map, components)
|
|
198
|
+
|
|
199
|
+
# Handle references (which can contain nested Applications or other documents)
|
|
200
|
+
for ref in document.references or []:
|
|
201
|
+
ref = ref.root # type: ignore
|
|
202
|
+
_build_lookup_maps(ref, lookup_map)
|
|
203
|
+
|
|
204
|
+
lookup_map[document.id] = document
|
|
205
|
+
|
|
206
|
+
# Handle RootModel list types (e.g., AuthorizationProviderList, IndexList, etc.)
|
|
207
|
+
elif hasattr(document, "root") and isinstance(
|
|
208
|
+
getattr(document, "root"), list
|
|
209
|
+
):
|
|
210
|
+
root_list = getattr(document, "root")
|
|
211
|
+
_update_map_with_unique_check(lookup_map, root_list)
|
|
212
|
+
_update_maps_with_embedded_objects(lookup_map, root_list)
|
|
213
|
+
|
|
214
|
+
# Handle single component documents (e.g., Flow, Agent, etc.)
|
|
215
|
+
else:
|
|
216
|
+
components = _collect_components_from_object(document)
|
|
217
|
+
_update_map_with_unique_check(lookup_map, components)
|
|
218
|
+
|
|
219
|
+
return lookup_map
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _resolve_reference(
|
|
223
|
+
ref: str, type_hint: Type, lookup_map: Dict[str, Any]
|
|
224
|
+
) -> Any:
|
|
225
|
+
"""
|
|
226
|
+
Resolve a single reference string to its object.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
ref: The reference ID to resolve
|
|
230
|
+
type_hint: Type hint for better error messages
|
|
231
|
+
lookup_map: Map of component IDs to objects
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
The resolved object
|
|
235
|
+
|
|
236
|
+
Raises:
|
|
237
|
+
ReferenceNotFoundError: If the reference cannot be found
|
|
238
|
+
"""
|
|
239
|
+
resolved_obj = lookup_map.get(ref)
|
|
240
|
+
if resolved_obj is None:
|
|
241
|
+
available_refs = list(lookup_map.keys())
|
|
242
|
+
raise ReferenceNotFoundError(ref, str(type_hint), available_refs)
|
|
243
|
+
return resolved_obj
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _resolve_rootmodel_references(
|
|
247
|
+
model: RootModel, lookup_map: Dict[str, Any]
|
|
248
|
+
) -> None:
|
|
249
|
+
"""
|
|
250
|
+
Resolve references in a RootModel (list-based documents).
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
model: RootModel instance to resolve
|
|
254
|
+
lookup_map: Map of component IDs to objects
|
|
255
|
+
"""
|
|
256
|
+
root_list = model.root # type: ignore
|
|
257
|
+
if not isinstance(root_list, list):
|
|
258
|
+
return
|
|
259
|
+
|
|
260
|
+
for i, item in enumerate(root_list):
|
|
261
|
+
match item:
|
|
262
|
+
case base_types.Reference():
|
|
263
|
+
root_list[i] = _resolve_reference(
|
|
264
|
+
item.ref, type(item), lookup_map
|
|
265
|
+
)
|
|
266
|
+
case BaseModel():
|
|
267
|
+
_resolve_all_references(item, lookup_map)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _resolve_list_references(
|
|
271
|
+
field_value: list, lookup_map: Dict[str, Any]
|
|
272
|
+
) -> None:
|
|
273
|
+
"""
|
|
274
|
+
Resolve references within a list field.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
field_value: List to process
|
|
278
|
+
lookup_map: Map of component IDs to objects
|
|
279
|
+
"""
|
|
280
|
+
for i, item in enumerate(field_value):
|
|
281
|
+
match item:
|
|
282
|
+
case base_types.Reference():
|
|
283
|
+
field_value[i] = _resolve_reference(
|
|
284
|
+
item.ref, type(item), lookup_map
|
|
285
|
+
)
|
|
286
|
+
case BaseModel():
|
|
287
|
+
_resolve_all_references(item, lookup_map)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _resolve_dict_references(
|
|
291
|
+
field_value: dict, lookup_map: Dict[str, Any]
|
|
292
|
+
) -> None:
|
|
293
|
+
"""
|
|
294
|
+
Resolve references within a dict field.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
field_value: Dict to process
|
|
298
|
+
lookup_map: Map of component IDs to objects
|
|
299
|
+
"""
|
|
300
|
+
for k, v in field_value.items():
|
|
301
|
+
match v:
|
|
302
|
+
case base_types.Reference():
|
|
303
|
+
field_value[k] = _resolve_reference(v.ref, type(v), lookup_map)
|
|
304
|
+
case BaseModel():
|
|
305
|
+
_resolve_all_references(v, lookup_map)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _resolve_all_references(
|
|
309
|
+
model: BaseModel,
|
|
310
|
+
lookup_map: Dict[str, Any],
|
|
311
|
+
) -> None:
|
|
312
|
+
"""
|
|
313
|
+
Walk a Pydantic model tree and resolve all Reference objects.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
model: The model to process
|
|
317
|
+
lookup_map: Map of component IDs to objects
|
|
318
|
+
"""
|
|
319
|
+
# Check if this is a RootModel (list-based document like ModelList, ToolList, etc.)
|
|
320
|
+
if isinstance(model, RootModel):
|
|
321
|
+
_resolve_rootmodel_references(model, lookup_map)
|
|
322
|
+
return
|
|
323
|
+
|
|
324
|
+
# For regular BaseModel types, iterate over fields
|
|
325
|
+
for field_name, field_value in model.__iter__():
|
|
326
|
+
match field_value:
|
|
327
|
+
case base_types.Reference():
|
|
328
|
+
setattr(
|
|
329
|
+
model,
|
|
330
|
+
field_name,
|
|
331
|
+
_resolve_reference(
|
|
332
|
+
field_value.ref, type(field_value), lookup_map
|
|
333
|
+
),
|
|
334
|
+
)
|
|
335
|
+
case BaseModel():
|
|
336
|
+
_resolve_all_references(field_value, lookup_map)
|
|
337
|
+
case list() if len(field_value) > 0:
|
|
338
|
+
_resolve_list_references(field_value, lookup_map)
|
|
339
|
+
case dict():
|
|
340
|
+
_resolve_dict_references(field_value, lookup_map)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def link(document: dsl.DocumentType) -> dsl.DocumentType:
|
|
344
|
+
"""
|
|
345
|
+
Links (resolves) all ID references in a DSL Document to their actual objects.
|
|
346
|
+
|
|
347
|
+
Works with any DocumentType:
|
|
348
|
+
- Application: Full application with all components
|
|
349
|
+
- Flow: Individual flow definition
|
|
350
|
+
- Agent: Individual agent definition
|
|
351
|
+
- AuthorizationProviderList: List of authorization providers
|
|
352
|
+
- IndexList: List of indexes
|
|
353
|
+
- ModelList: List of models
|
|
354
|
+
- ToolList: List of tools
|
|
355
|
+
- TypeList: List of custom types
|
|
356
|
+
- VariableList: List of variables
|
|
357
|
+
|
|
358
|
+
IMPORTANT: The returned object breaks the type safety of the original.
|
|
359
|
+
All Reference[T] fields will be replaced with actual T objects, which
|
|
360
|
+
violates the original type signatures. This is intentional for the
|
|
361
|
+
linking phase before transformation to semantic IR.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
document: Any valid DSL DocumentType (one of the 9 possible document structures).
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
The same document with all internal references resolved to actual objects.
|
|
368
|
+
|
|
369
|
+
Raises:
|
|
370
|
+
DuplicateComponentError: If there are duplicate components with the same ID.
|
|
371
|
+
ReferenceNotFoundError: If a reference cannot be resolved.
|
|
372
|
+
ComponentNotFoundError: If an expected component is missing.
|
|
373
|
+
"""
|
|
374
|
+
|
|
375
|
+
# First, make a lookup map of all objects in the document.
|
|
376
|
+
# This ensures that all object ids are unique.
|
|
377
|
+
lookup_map = _build_lookup_maps(document)
|
|
378
|
+
|
|
379
|
+
# Now we resolve all ID references in the document.
|
|
380
|
+
# All DocumentType variants are BaseModel instances (including RootModel-based *List types)
|
|
381
|
+
if isinstance(document, BaseModel):
|
|
382
|
+
_resolve_all_references(document, lookup_map)
|
|
383
|
+
|
|
384
|
+
return document # type: ignore[return-value]
|