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
|
@@ -180,7 +180,7 @@ def parse_duration_string(duration: str) -> int:
|
|
|
180
180
|
|
|
181
181
|
def format_datetime(timestamp: datetime, format_string: str) -> str:
|
|
182
182
|
"""
|
|
183
|
-
Format a timestamp using a custom format string.
|
|
183
|
+
Format a timestamp using a custom format string that can be passed to strftime.
|
|
184
184
|
|
|
185
185
|
Args:
|
|
186
186
|
timestamp: Datetime object to format.
|
|
@@ -17,7 +17,7 @@ from openapi_parser.specification import (
|
|
|
17
17
|
Security,
|
|
18
18
|
)
|
|
19
19
|
|
|
20
|
-
from qtype.
|
|
20
|
+
from qtype.base.types import PrimitiveTypeEnum
|
|
21
21
|
from qtype.dsl.model import (
|
|
22
22
|
APIKeyAuthProvider,
|
|
23
23
|
APITool,
|
|
@@ -344,9 +344,9 @@ def to_api_tool(
|
|
|
344
344
|
endpoint=endpoint,
|
|
345
345
|
method=operation.method.value.upper(),
|
|
346
346
|
auth=auth.id if auth else None, # Use auth ID string instead of object
|
|
347
|
-
inputs=inputs
|
|
348
|
-
outputs=outputs
|
|
349
|
-
parameters=parameters
|
|
347
|
+
inputs=inputs,
|
|
348
|
+
outputs=outputs,
|
|
349
|
+
parameters=parameters,
|
|
350
350
|
)
|
|
351
351
|
|
|
352
352
|
|
|
@@ -394,7 +394,7 @@ def to_authorization_provider(
|
|
|
394
394
|
}
|
|
395
395
|
)
|
|
396
396
|
if any(flow.scopes for flow in security.flows.values())
|
|
397
|
-
else
|
|
397
|
+
else [],
|
|
398
398
|
)
|
|
399
399
|
case _:
|
|
400
400
|
raise ValueError(
|
|
@@ -5,7 +5,7 @@ from typing import Any, Type, Union, get_args, get_origin
|
|
|
5
5
|
from pydantic import BaseModel
|
|
6
6
|
|
|
7
7
|
from qtype.application.converters.types import PYTHON_TYPE_TO_PRIMITIVE_TYPE
|
|
8
|
-
from qtype.
|
|
8
|
+
from qtype.base.types import PrimitiveTypeEnum
|
|
9
9
|
from qtype.dsl.model import (
|
|
10
10
|
CustomType,
|
|
11
11
|
ListType,
|
|
@@ -159,7 +159,7 @@ def _create_tool_from_function(
|
|
|
159
159
|
module_path=func_info["module"],
|
|
160
160
|
function_name=func_name,
|
|
161
161
|
description=description,
|
|
162
|
-
inputs=inputs
|
|
162
|
+
inputs=inputs,
|
|
163
163
|
outputs=outputs,
|
|
164
164
|
)
|
|
165
165
|
|
|
@@ -1,47 +1,18 @@
|
|
|
1
|
-
from datetime import date, datetime, time
|
|
2
|
-
|
|
3
|
-
from qtype.dsl.base_types import PrimitiveTypeEnum
|
|
4
|
-
|
|
5
|
-
"""
|
|
6
|
-
Mapping of QType primitive types to Python types for internal representations.
|
|
7
1
|
"""
|
|
8
|
-
|
|
9
|
-
PrimitiveTypeEnum.audio: bytes,
|
|
10
|
-
PrimitiveTypeEnum.boolean: bool,
|
|
11
|
-
PrimitiveTypeEnum.bytes: bytes,
|
|
12
|
-
PrimitiveTypeEnum.date: date,
|
|
13
|
-
PrimitiveTypeEnum.datetime: datetime,
|
|
14
|
-
PrimitiveTypeEnum.int: int,
|
|
15
|
-
PrimitiveTypeEnum.file: bytes, # Use bytes for file content
|
|
16
|
-
PrimitiveTypeEnum.float: float,
|
|
17
|
-
PrimitiveTypeEnum.image: bytes, # Use bytes for image data
|
|
18
|
-
PrimitiveTypeEnum.text: str,
|
|
19
|
-
PrimitiveTypeEnum.time: time, # Use time for time representation
|
|
20
|
-
PrimitiveTypeEnum.video: bytes, # Use bytes for video data
|
|
21
|
-
}
|
|
2
|
+
Re-export type mappings from DSL layer for backward compatibility.
|
|
22
3
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
str: PrimitiveTypeEnum.text,
|
|
27
|
-
int: PrimitiveTypeEnum.int,
|
|
28
|
-
float: PrimitiveTypeEnum.float,
|
|
29
|
-
date: PrimitiveTypeEnum.date,
|
|
30
|
-
datetime: PrimitiveTypeEnum.datetime,
|
|
31
|
-
time: PrimitiveTypeEnum.time,
|
|
32
|
-
# TODO: decide on internal representation for images, video, and audio, or use annotation/hinting
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def python_type_for_list(element_type: PrimitiveTypeEnum) -> type:
|
|
37
|
-
"""
|
|
38
|
-
Get the Python list type for a given QType primitive element type.
|
|
4
|
+
This module maintains the application layer's interface while delegating
|
|
5
|
+
to the DSL layer where these mappings are now defined.
|
|
6
|
+
"""
|
|
39
7
|
|
|
40
|
-
|
|
41
|
-
|
|
8
|
+
from qtype.dsl.types import (
|
|
9
|
+
PRIMITIVE_TO_PYTHON_TYPE,
|
|
10
|
+
PYTHON_TYPE_TO_PRIMITIVE_TYPE,
|
|
11
|
+
python_type_for_list,
|
|
12
|
+
)
|
|
42
13
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
""
|
|
46
|
-
|
|
47
|
-
|
|
14
|
+
__all__ = [
|
|
15
|
+
"PRIMITIVE_TO_PYTHON_TYPE",
|
|
16
|
+
"PYTHON_TYPE_TO_PRIMITIVE_TYPE",
|
|
17
|
+
"python_type_for_list",
|
|
18
|
+
]
|
|
@@ -5,7 +5,7 @@ from pathlib import Path
|
|
|
5
5
|
from typing import Any, Type, Union, get_args, get_origin
|
|
6
6
|
|
|
7
7
|
import qtype.dsl.model as dsl
|
|
8
|
-
from qtype.
|
|
8
|
+
from qtype.base.types import PrimitiveTypeEnum
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def _format_type_name(field_type: Any) -> str:
|
qtype/application/facade.py
CHANGED
|
@@ -2,74 +2,90 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import logging
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Any
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
from pydantic import BaseModel
|
|
10
|
-
|
|
11
|
-
from qtype.base.logging import get_logger
|
|
12
|
-
from qtype.base.types import CustomTypeRegistry, DocumentRootType, PathLike
|
|
13
|
-
from qtype.dsl.base_types import StepCardinality
|
|
14
|
-
from qtype.dsl.model import Application as DSLApplication
|
|
15
|
-
from qtype.dsl.model import DocumentType
|
|
16
|
-
from qtype.interpreter.batch.types import BatchConfig
|
|
9
|
+
from qtype.base.types import PathLike
|
|
17
10
|
from qtype.semantic.model import Application as SemanticApplication
|
|
18
|
-
from qtype.semantic.model import
|
|
11
|
+
from qtype.semantic.model import DocumentType as SemanticDocumentType
|
|
12
|
+
|
|
13
|
+
# Note: There should be _zero_ imports here at the top that import qtype.interpreter.
|
|
14
|
+
# That's the whole point of this facade - to avoid importing optional
|
|
15
|
+
# dependencies unless these methods are called.
|
|
19
16
|
|
|
20
|
-
logger =
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
21
18
|
|
|
22
19
|
|
|
23
20
|
class QTypeFacade:
|
|
24
21
|
"""
|
|
25
|
-
Simplified interface for
|
|
22
|
+
Simplified interface for qtype operations.
|
|
26
23
|
|
|
27
|
-
This facade
|
|
28
|
-
|
|
24
|
+
This facade provides lazy-loading wrappers for operations that require
|
|
25
|
+
optional dependencies (interpreter package), allowing base qtype to work
|
|
26
|
+
without those dependencies installed.
|
|
29
27
|
"""
|
|
30
28
|
|
|
31
|
-
def
|
|
32
|
-
|
|
33
|
-
) -> tuple[DocumentRootType, CustomTypeRegistry]:
|
|
34
|
-
from qtype.loader import load_document
|
|
35
|
-
|
|
36
|
-
return load_document(Path(path).read_text(encoding="utf-8"))
|
|
37
|
-
|
|
38
|
-
def telemetry(self, spec: SemanticApplication) -> None:
|
|
39
|
-
if spec.telemetry:
|
|
29
|
+
def telemetry(self, spec: SemanticDocumentType) -> None:
|
|
30
|
+
if isinstance(spec, SemanticApplication) and spec.telemetry:
|
|
40
31
|
logger.info(
|
|
41
32
|
f"Telemetry enabled with endpoint: {spec.telemetry.endpoint}"
|
|
42
33
|
)
|
|
43
34
|
# Register telemetry if needed
|
|
44
35
|
from qtype.interpreter.telemetry import register
|
|
45
36
|
|
|
46
|
-
register(spec.telemetry, spec.id)
|
|
37
|
+
register(spec.telemetry, self.secret_manager(spec), spec.id)
|
|
47
38
|
|
|
48
|
-
def
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"""Load a document and return the resolved semantic model."""
|
|
52
|
-
from qtype.loader import load
|
|
39
|
+
def secret_manager(self, spec: SemanticDocumentType):
|
|
40
|
+
"""
|
|
41
|
+
Create a secret manager based on the specification.
|
|
53
42
|
|
|
54
|
-
|
|
55
|
-
|
|
43
|
+
Args:
|
|
44
|
+
spec: SemanticDocumentType specification
|
|
56
45
|
|
|
57
|
-
|
|
46
|
+
Returns:
|
|
47
|
+
Secret manager instance
|
|
48
|
+
"""
|
|
49
|
+
from qtype.interpreter.base.secrets import create_secret_manager
|
|
50
|
+
|
|
51
|
+
if isinstance(spec, SemanticApplication):
|
|
52
|
+
return create_secret_manager(spec.secret_manager)
|
|
53
|
+
else:
|
|
54
|
+
raise ValueError(
|
|
55
|
+
"Can't create secret manager for non-Application spec"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
async def execute_workflow(
|
|
58
59
|
self,
|
|
59
60
|
path: PathLike,
|
|
60
|
-
inputs: dict |
|
|
61
|
+
inputs: dict | Any,
|
|
61
62
|
flow_name: str | None = None,
|
|
62
|
-
batch_config: BatchConfig | None = None,
|
|
63
63
|
**kwargs: Any,
|
|
64
|
-
) ->
|
|
65
|
-
"""
|
|
64
|
+
) -> Any:
|
|
65
|
+
"""
|
|
66
|
+
Execute a complete workflow from document to results.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
path: Path to the QType specification file
|
|
70
|
+
inputs: Dictionary of input values or DataFrame for batch
|
|
71
|
+
flow_name: Optional name of flow to execute
|
|
72
|
+
**kwargs: Additional dependencies for execution
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
DataFrame with results (one row per input)
|
|
76
|
+
"""
|
|
77
|
+
import pandas as pd
|
|
78
|
+
|
|
79
|
+
from qtype.semantic.loader import load
|
|
80
|
+
|
|
66
81
|
logger.info(f"Executing workflow from {path}")
|
|
67
82
|
|
|
68
83
|
# Load the semantic application
|
|
69
|
-
semantic_model, type_registry =
|
|
84
|
+
semantic_model, type_registry = load(Path(path))
|
|
85
|
+
assert isinstance(semantic_model, SemanticApplication)
|
|
70
86
|
self.telemetry(semantic_model)
|
|
71
87
|
|
|
72
|
-
# Find the flow to execute
|
|
88
|
+
# Find the flow to execute
|
|
73
89
|
if flow_name:
|
|
74
90
|
target_flow = None
|
|
75
91
|
for flow in semantic_model.flows:
|
|
@@ -83,49 +99,54 @@ class QTypeFacade:
|
|
|
83
99
|
target_flow = semantic_model.flows[0]
|
|
84
100
|
else:
|
|
85
101
|
raise ValueError("No flows found in application")
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
)
|
|
93
|
-
from qtype.interpreter.batch.flow import batch_execute_flow
|
|
94
|
-
|
|
95
|
-
batch_config = batch_config or BatchConfig()
|
|
96
|
-
results, errors = batch_execute_flow(
|
|
97
|
-
target_flow, inputs, batch_config, **kwargs
|
|
98
|
-
) # type: ignore
|
|
99
|
-
return results
|
|
102
|
+
|
|
103
|
+
# Convert inputs to DataFrame (normalize single dict to 1-row DataFrame)
|
|
104
|
+
if isinstance(inputs, dict):
|
|
105
|
+
input_df = pd.DataFrame([inputs])
|
|
106
|
+
elif isinstance(inputs, pd.DataFrame):
|
|
107
|
+
input_df = inputs
|
|
100
108
|
else:
|
|
101
|
-
|
|
109
|
+
raise ValueError(
|
|
110
|
+
f"Inputs must be dict or DataFrame, got {type(inputs)}"
|
|
111
|
+
)
|
|
102
112
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
113
|
+
# Create session
|
|
114
|
+
from qtype.interpreter.converters import (
|
|
115
|
+
dataframe_to_flow_messages,
|
|
116
|
+
flow_messages_to_dataframe,
|
|
117
|
+
)
|
|
118
|
+
from qtype.interpreter.types import Session
|
|
108
119
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
120
|
+
session = Session(
|
|
121
|
+
session_id=kwargs.pop("session_id", "default"),
|
|
122
|
+
conversation_history=kwargs.pop("conversation_history", []),
|
|
123
|
+
)
|
|
112
124
|
|
|
113
|
-
|
|
114
|
-
|
|
125
|
+
# Convert DataFrame to FlowMessages
|
|
126
|
+
initial_messages = dataframe_to_flow_messages(input_df, session)
|
|
115
127
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
# Wrap DSLApplication in Document if needed
|
|
119
|
-
wrapped_document: BaseModel = document
|
|
120
|
-
if isinstance(document, DSLApplication):
|
|
121
|
-
from qtype.dsl.model import Document
|
|
128
|
+
# Execute the flow
|
|
129
|
+
from opentelemetry import trace
|
|
122
130
|
|
|
123
|
-
|
|
124
|
-
from
|
|
131
|
+
from qtype.interpreter.base.executor_context import ExecutorContext
|
|
132
|
+
from qtype.interpreter.flow import run_flow
|
|
125
133
|
|
|
126
|
-
|
|
127
|
-
|
|
134
|
+
secret_manager = self.secret_manager(semantic_model)
|
|
135
|
+
context = ExecutorContext(
|
|
136
|
+
secret_manager=secret_manager,
|
|
137
|
+
tracer=trace.get_tracer(__name__),
|
|
128
138
|
)
|
|
139
|
+
results = await run_flow(
|
|
140
|
+
target_flow,
|
|
141
|
+
initial_messages,
|
|
142
|
+
context=context,
|
|
143
|
+
**kwargs,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Convert results back to DataFrame
|
|
147
|
+
results_df = flow_messages_to_dataframe(results, target_flow)
|
|
148
|
+
|
|
149
|
+
return results_df
|
|
129
150
|
|
|
130
151
|
def generate_aws_bedrock_models(self) -> list[dict[str, Any]]:
|
|
131
152
|
"""
|
qtype/base/types.py
CHANGED
|
@@ -3,11 +3,24 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import pathlib
|
|
6
|
-
|
|
6
|
+
import types
|
|
7
|
+
import typing
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import (
|
|
10
|
+
Any,
|
|
11
|
+
Generic,
|
|
12
|
+
Literal,
|
|
13
|
+
Optional,
|
|
14
|
+
Type,
|
|
15
|
+
TypeVar,
|
|
16
|
+
Union,
|
|
17
|
+
get_args,
|
|
18
|
+
get_origin,
|
|
19
|
+
)
|
|
7
20
|
|
|
8
21
|
from pydantic import BaseModel
|
|
9
|
-
|
|
10
|
-
from
|
|
22
|
+
from pydantic import ConfigDict as PydanticConfigDict
|
|
23
|
+
from pydantic import Field, model_validator
|
|
11
24
|
|
|
12
25
|
# JSON-serializable value types
|
|
13
26
|
JSONValue = Union[
|
|
@@ -20,10 +33,217 @@ JSONValue = Union[
|
|
|
20
33
|
list["JSONValue"],
|
|
21
34
|
]
|
|
22
35
|
|
|
23
|
-
# Configuration dictionary type
|
|
24
|
-
ConfigDict = dict[str, Any]
|
|
25
|
-
|
|
26
36
|
# Path-like type (string or Path object)
|
|
27
37
|
PathLike = Union[str, pathlib.Path]
|
|
38
|
+
|
|
28
39
|
CustomTypeRegistry = dict[str, Type[BaseModel]]
|
|
29
|
-
|
|
40
|
+
# Configuration dictionary type
|
|
41
|
+
ConfigDict = dict[str, Any]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ---------------- Shared Base Types and Enums ----------------
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class PrimitiveTypeEnum(str, Enum):
|
|
48
|
+
"""Represents the type of data a user or system input can accept within the DSL."""
|
|
49
|
+
|
|
50
|
+
audio = "audio"
|
|
51
|
+
boolean = "boolean"
|
|
52
|
+
bytes = "bytes"
|
|
53
|
+
citation_document = "citation_document"
|
|
54
|
+
citation_url = "citation_url"
|
|
55
|
+
date = "date"
|
|
56
|
+
datetime = "datetime"
|
|
57
|
+
int = "int"
|
|
58
|
+
file = "file"
|
|
59
|
+
float = "float"
|
|
60
|
+
image = "image"
|
|
61
|
+
text = "text"
|
|
62
|
+
time = "time"
|
|
63
|
+
video = "video"
|
|
64
|
+
thinking = "thinking"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class StepCardinality(str, Enum):
|
|
68
|
+
"""Does this step emit 1 (one) or 0...N (many) items?"""
|
|
69
|
+
|
|
70
|
+
one = "one"
|
|
71
|
+
many = "many"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
ReferenceT = TypeVar("ReferenceT")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class Reference(BaseModel, Generic[ReferenceT]):
|
|
78
|
+
"""Represents a reference to another component by its ID."""
|
|
79
|
+
|
|
80
|
+
# model_config = PydanticConfigDict(extra="forbid")
|
|
81
|
+
|
|
82
|
+
ref: str = Field(..., alias="$ref")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _contains_reference_and_str(type_hint: Any) -> bool:
|
|
86
|
+
"""Check if type contains both Reference and str in a union."""
|
|
87
|
+
# Get union args (handles Union, | syntax, and Optional)
|
|
88
|
+
origin = get_origin(type_hint)
|
|
89
|
+
if origin not in (Union, None) and not isinstance(
|
|
90
|
+
type_hint, types.UnionType
|
|
91
|
+
):
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
args = get_args(type_hint)
|
|
95
|
+
if not args:
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
has_str = str in args
|
|
99
|
+
has_ref = any(
|
|
100
|
+
get_origin(arg) is Reference
|
|
101
|
+
or (hasattr(arg, "__mro__") and Reference in arg.__mro__)
|
|
102
|
+
for arg in args
|
|
103
|
+
)
|
|
104
|
+
return has_str and has_ref
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _should_transform_field(type_hint: Any) -> tuple[bool, bool]:
|
|
108
|
+
"""
|
|
109
|
+
Check if field should be transformed.
|
|
110
|
+
Returns: (should_transform, is_list)
|
|
111
|
+
"""
|
|
112
|
+
# Check direct union: Reference[T] | str
|
|
113
|
+
if _contains_reference_and_str(type_hint):
|
|
114
|
+
return True, False
|
|
115
|
+
|
|
116
|
+
# Check list of union: list[Reference[T] | str]
|
|
117
|
+
origin = get_origin(type_hint)
|
|
118
|
+
if origin is list:
|
|
119
|
+
args = get_args(type_hint)
|
|
120
|
+
if args and _contains_reference_and_str(args[0]):
|
|
121
|
+
return True, True
|
|
122
|
+
|
|
123
|
+
# Check optional list: list[Reference[T] | str] | None
|
|
124
|
+
if origin is Union or isinstance(type_hint, types.UnionType):
|
|
125
|
+
for arg in get_args(type_hint):
|
|
126
|
+
if get_origin(arg) is list:
|
|
127
|
+
inner_args = get_args(arg)
|
|
128
|
+
if inner_args and _contains_reference_and_str(inner_args[0]):
|
|
129
|
+
return True, True
|
|
130
|
+
|
|
131
|
+
return False, False
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class StrictBaseModel(BaseModel):
|
|
135
|
+
"""Base model with extra fields forbidden."""
|
|
136
|
+
|
|
137
|
+
model_config = PydanticConfigDict(extra="forbid")
|
|
138
|
+
|
|
139
|
+
@model_validator(mode="before")
|
|
140
|
+
@classmethod
|
|
141
|
+
def normalize_string_references(cls, data: Any) -> Any:
|
|
142
|
+
"""
|
|
143
|
+
Normalize string references to Reference objects before validation.
|
|
144
|
+
|
|
145
|
+
Transforms:
|
|
146
|
+
- `field: "ref_id"` -> `field: {"$ref": "ref_id"}`
|
|
147
|
+
- `field: ["ref1", "ref2"]` -> `field: [{"$ref": "ref1"}, {"$ref": "ref2"}]`
|
|
148
|
+
|
|
149
|
+
Only applies to fields typed as `Reference[T] | str` or `list[Reference[T] | str]`.
|
|
150
|
+
"""
|
|
151
|
+
if not isinstance(data, dict):
|
|
152
|
+
return data
|
|
153
|
+
|
|
154
|
+
# Get type hints (evaluates ForwardRefs)
|
|
155
|
+
hints = typing.get_type_hints(cls)
|
|
156
|
+
|
|
157
|
+
# Transform fields
|
|
158
|
+
for field_name, field_value in data.items():
|
|
159
|
+
if field_name == "type" or field_name not in hints:
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
should_transform, is_list = _should_transform_field(
|
|
163
|
+
hints[field_name]
|
|
164
|
+
)
|
|
165
|
+
if not should_transform:
|
|
166
|
+
continue
|
|
167
|
+
|
|
168
|
+
if is_list and isinstance(field_value, list):
|
|
169
|
+
data[field_name] = [
|
|
170
|
+
{"$ref": item} if isinstance(item, str) else item
|
|
171
|
+
for item in field_value
|
|
172
|
+
]
|
|
173
|
+
elif not is_list and isinstance(field_value, str):
|
|
174
|
+
data[field_name] = {"$ref": field_value}
|
|
175
|
+
|
|
176
|
+
return data
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class BatchConfig(BaseModel):
|
|
180
|
+
"""Configuration for batch execution.
|
|
181
|
+
|
|
182
|
+
Attributes:
|
|
183
|
+
num_workers: Number of async workers for batch operations.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
batch_size: int = Field(
|
|
187
|
+
default=25,
|
|
188
|
+
description="Max number of rows to send to a step at a time",
|
|
189
|
+
gt=0,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class ConcurrencyConfig(BaseModel):
|
|
194
|
+
"""Configuration for concurrent processing.
|
|
195
|
+
|
|
196
|
+
Attributes:
|
|
197
|
+
num_workers: Number of async workers for batch operations.
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
num_workers: int = Field(
|
|
201
|
+
default=1,
|
|
202
|
+
description="Number of async workers for batch operations",
|
|
203
|
+
gt=0,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class BatchableStepMixin(BaseModel):
|
|
208
|
+
"""A mixin for steps that support concurrent batch processing."""
|
|
209
|
+
|
|
210
|
+
batch_config: BatchConfig = Field(
|
|
211
|
+
default_factory=BatchConfig,
|
|
212
|
+
description="Configuration for processing the input stream in batches. If omitted, the step processes items one by one.",
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class CacheConfig(BaseModel):
|
|
217
|
+
directory: PathLike = Field(
|
|
218
|
+
default=pathlib.Path("./.qtype-cache"),
|
|
219
|
+
description="Base cache directory.",
|
|
220
|
+
)
|
|
221
|
+
namespace: Optional[str] = Field(
|
|
222
|
+
default=None, description="Logical namespace for cache keys."
|
|
223
|
+
)
|
|
224
|
+
on_error: Literal["Cache", "Drop"] = "Drop"
|
|
225
|
+
version: str = Field(
|
|
226
|
+
default="1.0", description="Bump to invalidate old cache."
|
|
227
|
+
)
|
|
228
|
+
compress: bool = Field(default=False, description="Compress stored data.")
|
|
229
|
+
ttl: Optional[int] = Field(
|
|
230
|
+
default=None, description="Optional time-to-live in seconds."
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class CachedStepMixin(BaseModel):
|
|
235
|
+
"""A mixin for steps that support caching."""
|
|
236
|
+
|
|
237
|
+
cache_config: CacheConfig | None = Field(
|
|
238
|
+
default=None,
|
|
239
|
+
description="Configuration for caching step outputs. If omitted, caching is disabled.",
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class ConcurrentStepMixin(BaseModel):
|
|
244
|
+
"""A mixin for steps that support concurrent processing."""
|
|
245
|
+
|
|
246
|
+
concurrency_config: ConcurrencyConfig = Field(
|
|
247
|
+
default_factory=ConcurrencyConfig,
|
|
248
|
+
description="Configuration for processing the input stream concurrently. If omitted, the step processes items sequentially.",
|
|
249
|
+
)
|
qtype/cli.py
CHANGED
qtype/commands/convert.py
CHANGED
|
@@ -8,12 +8,26 @@ import argparse
|
|
|
8
8
|
import logging
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
|
-
from qtype.
|
|
12
|
-
from qtype.dsl.model import Application, ToolList
|
|
11
|
+
from qtype.dsl.model import Application, Document, ToolList
|
|
13
12
|
|
|
14
13
|
logger = logging.getLogger(__name__)
|
|
15
14
|
|
|
16
15
|
|
|
16
|
+
def _convert_to_yaml(doc: Application | ToolList) -> str:
|
|
17
|
+
"""Convert a document to YAML format."""
|
|
18
|
+
from pydantic_yaml import to_yaml_str
|
|
19
|
+
|
|
20
|
+
# Wrap in Document if needed
|
|
21
|
+
if isinstance(doc, Application):
|
|
22
|
+
wrapped = Document(root=doc)
|
|
23
|
+
else:
|
|
24
|
+
wrapped = doc
|
|
25
|
+
|
|
26
|
+
# NOTE: We use exclude_none but NOT exclude_unset because discriminator
|
|
27
|
+
# fields like 'type' have default values and must be included in output
|
|
28
|
+
return to_yaml_str(wrapped, exclude_none=True)
|
|
29
|
+
|
|
30
|
+
|
|
17
31
|
def convert_api(args: argparse.Namespace) -> None:
|
|
18
32
|
"""Convert API specification to qtype format."""
|
|
19
33
|
from qtype.application.converters.tools_from_api import tools_from_api
|
|
@@ -36,9 +50,8 @@ def convert_api(args: argparse.Namespace) -> None:
|
|
|
36
50
|
types=types,
|
|
37
51
|
auths=auths,
|
|
38
52
|
)
|
|
39
|
-
#
|
|
40
|
-
|
|
41
|
-
content = facade.convert_document(doc)
|
|
53
|
+
# Convert to YAML format
|
|
54
|
+
content = _convert_to_yaml(doc)
|
|
42
55
|
|
|
43
56
|
# Write to file or stdout
|
|
44
57
|
if args.output:
|
|
@@ -79,9 +92,8 @@ def convert_module(args: argparse.Namespace) -> None:
|
|
|
79
92
|
root=list(tools),
|
|
80
93
|
)
|
|
81
94
|
|
|
82
|
-
#
|
|
83
|
-
|
|
84
|
-
content = facade.convert_document(doc)
|
|
95
|
+
# Convert to YAML format
|
|
96
|
+
content = _convert_to_yaml(doc)
|
|
85
97
|
|
|
86
98
|
# Write to file or stdout
|
|
87
99
|
if args.output:
|