qtype 0.0.15__py3-none-any.whl → 0.1.0__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 +92 -71
- qtype/base/types.py +227 -7
- qtype/commands/convert.py +20 -8
- qtype/commands/generate.py +19 -27
- qtype/commands/run.py +54 -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 +58 -135
- qtype/interpreter/auth/aws.py +19 -9
- qtype/interpreter/auth/generic.py +93 -16
- qtype/interpreter/base/base_step_executor.py +429 -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 +75 -0
- qtype/interpreter/base/secrets.py +339 -0
- qtype/interpreter/base/step_cache.py +73 -0
- qtype/interpreter/base/stream_emitter.py +469 -0
- qtype/interpreter/conversions.py +455 -21
- qtype/interpreter/converters.py +73 -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 +75 -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 +353 -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 +147 -22
- qtype/interpreter/metadata_api.py +115 -0
- qtype/interpreter/resource_cache.py +5 -4
- 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 +328 -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 +59 -17
- qtype/semantic/visualize.py +28 -31
- {qtype-0.0.15.dist-info → qtype-0.1.0.dist-info}/METADATA +16 -3
- qtype-0.1.0.dist-info/RECORD +134 -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.15.dist-info/RECORD +0 -106
- /qtype/interpreter/ui/_next/static/{nUaw6_IwRwPqkzwe5s725 → 20HoJN6otZ_LyHLHpCPE6}/_ssgManifest.js +0 -0
- {qtype-0.0.15.dist-info → qtype-0.1.0.dist-info}/WHEEL +0 -0
- {qtype-0.0.15.dist-info → qtype-0.1.0.dist-info}/entry_points.txt +0 -0
- {qtype-0.0.15.dist-info → qtype-0.1.0.dist-info}/licenses/LICENSE +0 -0
- {qtype-0.0.15.dist-info → qtype-0.1.0.dist-info}/top_level.txt +0 -0
qtype/interpreter/flow.py
CHANGED
|
@@ -1,37 +1,162 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from typing import Any
|
|
5
4
|
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
from openinference.semconv.trace import (
|
|
6
|
+
OpenInferenceSpanKindValues,
|
|
7
|
+
SpanAttributes,
|
|
8
|
+
)
|
|
9
|
+
from opentelemetry import context as otel_context
|
|
10
|
+
from opentelemetry import trace
|
|
11
|
+
from opentelemetry.trace import Status, StatusCode
|
|
12
|
+
|
|
13
|
+
from qtype.interpreter.base import factory
|
|
14
|
+
from qtype.interpreter.base.executor_context import ExecutorContext
|
|
15
|
+
from qtype.interpreter.types import FlowMessage
|
|
16
|
+
from qtype.semantic.model import Flow
|
|
9
17
|
|
|
10
18
|
logger = logging.getLogger(__name__)
|
|
11
19
|
|
|
12
20
|
|
|
13
|
-
def
|
|
14
|
-
|
|
21
|
+
async def run_flow(
|
|
22
|
+
flow: Flow, initial: list[FlowMessage] | FlowMessage, **kwargs
|
|
23
|
+
) -> list[FlowMessage]:
|
|
24
|
+
"""
|
|
25
|
+
Main entrypoint for executing a flow.
|
|
15
26
|
|
|
16
27
|
Args:
|
|
17
|
-
flow: The flow to execute
|
|
18
|
-
|
|
19
|
-
**kwargs:
|
|
28
|
+
flow: The flow to execute
|
|
29
|
+
initial: Initial FlowMessage(s) to start execution
|
|
30
|
+
**kwargs: Dependencies including:
|
|
31
|
+
- context: ExecutorContext with cross-cutting concerns (optional)
|
|
32
|
+
- Other executor-specific dependencies
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
List of final FlowMessages after execution
|
|
20
36
|
"""
|
|
21
|
-
|
|
37
|
+
from qtype.interpreter.base.secrets import NoOpSecretManager
|
|
22
38
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
39
|
+
# Extract or create ExecutorContext
|
|
40
|
+
exec_context = kwargs.pop("context", None)
|
|
41
|
+
if exec_context is None:
|
|
42
|
+
exec_context = ExecutorContext(
|
|
43
|
+
secret_manager=NoOpSecretManager(),
|
|
44
|
+
tracer=trace.get_tracer(__name__),
|
|
27
45
|
)
|
|
28
46
|
|
|
29
|
-
|
|
30
|
-
|
|
47
|
+
# Use tracer from context
|
|
48
|
+
tracer = exec_context.tracer or trace.get_tracer(__name__)
|
|
31
49
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
50
|
+
# Start a span for the entire flow execution
|
|
51
|
+
span = tracer.start_span(
|
|
52
|
+
f"flow.{flow.id}",
|
|
53
|
+
attributes={
|
|
54
|
+
"flow.id": flow.id,
|
|
55
|
+
"flow.step_count": len(flow.steps),
|
|
56
|
+
SpanAttributes.OPENINFERENCE_SPAN_KIND: (
|
|
57
|
+
OpenInferenceSpanKindValues.CHAIN.value
|
|
58
|
+
),
|
|
59
|
+
},
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Make this span the active context so step spans will nest under it
|
|
63
|
+
# Only attach if span is recording (i.e., real tracer is configured)
|
|
64
|
+
ctx = trace.set_span_in_context(span)
|
|
65
|
+
token = otel_context.attach(ctx) if span.is_recording() else None
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
# 1. Get the execution plan is just the steps in order
|
|
69
|
+
execution_plan = flow.steps
|
|
70
|
+
|
|
71
|
+
# 2. Initialize the stream
|
|
72
|
+
if not isinstance(initial, list):
|
|
73
|
+
initial = [initial]
|
|
74
|
+
|
|
75
|
+
span.set_attribute("flow.input_count", len(initial))
|
|
76
|
+
|
|
77
|
+
# Record input variables for observability
|
|
78
|
+
if initial:
|
|
79
|
+
import json
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
input_vars = {
|
|
83
|
+
k: v for msg in initial for k, v in msg.variables.items()
|
|
84
|
+
}
|
|
85
|
+
span.set_attribute(
|
|
86
|
+
SpanAttributes.INPUT_VALUE,
|
|
87
|
+
json.dumps(input_vars, default=str),
|
|
88
|
+
)
|
|
89
|
+
span.set_attribute(
|
|
90
|
+
SpanAttributes.INPUT_MIME_TYPE, "application/json"
|
|
91
|
+
)
|
|
92
|
+
except Exception:
|
|
93
|
+
# If serialization fails, skip it
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
async def initial_stream():
|
|
97
|
+
for message in initial:
|
|
98
|
+
yield message
|
|
99
|
+
|
|
100
|
+
current_stream = initial_stream()
|
|
101
|
+
|
|
102
|
+
# 3. Chain executors together in the main loop
|
|
103
|
+
for step in execution_plan:
|
|
104
|
+
executor = factory.create_executor(step, exec_context, **kwargs)
|
|
105
|
+
output_stream = executor.execute(
|
|
106
|
+
current_stream,
|
|
107
|
+
)
|
|
108
|
+
current_stream = output_stream
|
|
109
|
+
|
|
110
|
+
# 4. Collect the final results from the last stream
|
|
111
|
+
final_results = [state async for state in current_stream]
|
|
112
|
+
|
|
113
|
+
# Record flow completion metrics
|
|
114
|
+
span.set_attribute("flow.output_count", len(final_results))
|
|
115
|
+
error_count = sum(1 for msg in final_results if msg.is_failed())
|
|
116
|
+
span.set_attribute("flow.error_count", error_count)
|
|
117
|
+
|
|
118
|
+
# Record output variables for observability
|
|
119
|
+
if final_results:
|
|
120
|
+
import json
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
output_vars = {
|
|
124
|
+
k: v
|
|
125
|
+
for msg in final_results
|
|
126
|
+
if not msg.is_failed()
|
|
127
|
+
for k, v in msg.variables.items()
|
|
128
|
+
}
|
|
129
|
+
span.set_attribute(
|
|
130
|
+
SpanAttributes.OUTPUT_VALUE,
|
|
131
|
+
json.dumps(output_vars, default=str),
|
|
132
|
+
)
|
|
133
|
+
span.set_attribute(
|
|
134
|
+
SpanAttributes.OUTPUT_MIME_TYPE, "application/json"
|
|
135
|
+
)
|
|
136
|
+
except Exception:
|
|
137
|
+
# If serialization fails, skip it
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
if error_count > 0:
|
|
141
|
+
span.set_status(
|
|
142
|
+
Status(
|
|
143
|
+
StatusCode.ERROR,
|
|
144
|
+
f"{error_count} of {len(final_results)} messages failed",
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
else:
|
|
148
|
+
span.set_status(Status(StatusCode.OK))
|
|
149
|
+
|
|
150
|
+
return final_results
|
|
151
|
+
|
|
152
|
+
except Exception as e:
|
|
153
|
+
# Record the exception and set error status
|
|
154
|
+
span.record_exception(e)
|
|
155
|
+
span.set_status(Status(StatusCode.ERROR, f"Flow failed: {e}"))
|
|
156
|
+
raise
|
|
157
|
+
finally:
|
|
158
|
+
# Detach the context and end the span
|
|
159
|
+
# Only detach if we successfully attached (span was recording)
|
|
160
|
+
if token is not None:
|
|
161
|
+
otel_context.detach(token)
|
|
162
|
+
span.end()
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Metadata API endpoints for flow discovery."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from fastapi import FastAPI
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from qtype.interpreter.typing import create_input_shape, create_output_shape
|
|
11
|
+
from qtype.semantic.model import Application, Flow
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FlowEndpoints(BaseModel):
|
|
15
|
+
"""Available endpoints for a flow."""
|
|
16
|
+
|
|
17
|
+
rest: str = Field(..., description="REST execution endpoint")
|
|
18
|
+
stream: str | None = Field(
|
|
19
|
+
None,
|
|
20
|
+
description="Streaming endpoint (SSE) if flow has an interface",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class FlowMetadata(BaseModel):
|
|
25
|
+
"""Metadata about a flow for frontend discovery."""
|
|
26
|
+
|
|
27
|
+
id: str = Field(..., description="Flow ID")
|
|
28
|
+
description: str | None = Field(None, description="Flow description")
|
|
29
|
+
interface_type: str | None = Field(
|
|
30
|
+
None,
|
|
31
|
+
description="Interface type: 'Complete' or 'Conversational'",
|
|
32
|
+
)
|
|
33
|
+
session_inputs: list[str] = Field(
|
|
34
|
+
default_factory=list,
|
|
35
|
+
description="Input variables that persist across session",
|
|
36
|
+
)
|
|
37
|
+
endpoints: FlowEndpoints = Field(
|
|
38
|
+
..., description="Available API endpoints"
|
|
39
|
+
)
|
|
40
|
+
input_schema: dict[str, Any] = Field(
|
|
41
|
+
..., description="JSON schema for input"
|
|
42
|
+
)
|
|
43
|
+
output_schema: dict[str, Any] = Field(
|
|
44
|
+
..., description="JSON schema for output"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def create_metadata_endpoints(app: FastAPI, application: Application) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Create metadata endpoints for flow discovery.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
app: FastAPI application instance
|
|
54
|
+
application: QType Application with flows
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
@app.get(
|
|
58
|
+
"/flows",
|
|
59
|
+
tags=["flows"],
|
|
60
|
+
summary="List all flows",
|
|
61
|
+
description="Get metadata for all available flows",
|
|
62
|
+
response_model=list[FlowMetadata],
|
|
63
|
+
)
|
|
64
|
+
async def list_flows() -> list[FlowMetadata]:
|
|
65
|
+
"""List all flows with their metadata."""
|
|
66
|
+
flows_metadata = []
|
|
67
|
+
|
|
68
|
+
for flow in application.flows:
|
|
69
|
+
metadata = _create_flow_metadata(flow)
|
|
70
|
+
flows_metadata.append(metadata)
|
|
71
|
+
|
|
72
|
+
return flows_metadata
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _create_flow_metadata(flow: Flow) -> FlowMetadata:
|
|
76
|
+
"""
|
|
77
|
+
Create metadata for a single flow.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
flow: Flow to create metadata for
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
FlowMetadata with all information
|
|
84
|
+
"""
|
|
85
|
+
# Determine interface type
|
|
86
|
+
interface_type = None
|
|
87
|
+
session_inputs = []
|
|
88
|
+
if flow.interface:
|
|
89
|
+
interface_type = flow.interface.type
|
|
90
|
+
session_inputs = [
|
|
91
|
+
var.id if hasattr(var, "id") else str(var)
|
|
92
|
+
for var in flow.interface.session_inputs
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
# Create schemas
|
|
96
|
+
input_model = create_input_shape(flow)
|
|
97
|
+
output_model = create_output_shape(flow)
|
|
98
|
+
|
|
99
|
+
# Determine streaming endpoint availability
|
|
100
|
+
stream_endpoint = (
|
|
101
|
+
f"/flows/{flow.id}/stream" if flow.interface is not None else None
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
return FlowMetadata(
|
|
105
|
+
id=flow.id,
|
|
106
|
+
description=flow.description,
|
|
107
|
+
interface_type=interface_type,
|
|
108
|
+
session_inputs=session_inputs,
|
|
109
|
+
endpoints=FlowEndpoints(
|
|
110
|
+
rest=f"/flows/{flow.id}",
|
|
111
|
+
stream=stream_endpoint,
|
|
112
|
+
),
|
|
113
|
+
input_schema=input_model.model_json_schema(),
|
|
114
|
+
output_schema=output_model.model_json_schema(),
|
|
115
|
+
)
|
|
@@ -2,12 +2,13 @@ import functools
|
|
|
2
2
|
import os
|
|
3
3
|
from typing import Any, Callable
|
|
4
4
|
|
|
5
|
-
from cachetools import
|
|
5
|
+
from cachetools import TTLCache # type: ignore[import-untyped]
|
|
6
6
|
|
|
7
|
-
# Global
|
|
7
|
+
# Global TTL cache with a reasonable default size and 55-minute TTL
|
|
8
8
|
_RESOURCE_CACHE_MAX_SIZE = int(os.environ.get("RESOURCE_CACHE_MAX_SIZE", 128))
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
_RESOURCE_CACHE_TTL = int(os.environ.get("RESOURCE_CACHE_TTL", 55 * 60))
|
|
10
|
+
_GLOBAL_RESOURCE_CACHE: TTLCache[Any, Any] = TTLCache(
|
|
11
|
+
maxsize=_RESOURCE_CACHE_MAX_SIZE, ttl=_RESOURCE_CACHE_TTL
|
|
11
12
|
)
|
|
12
13
|
|
|
13
14
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Stream and chat utilities for QType interpreter.
|
|
3
|
+
|
|
4
|
+
This package provides conversions between QType's internal streaming
|
|
5
|
+
events and external chat protocols like Vercel AI SDK.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from qtype.interpreter.stream.chat.converter import (
|
|
11
|
+
StreamEventConverter,
|
|
12
|
+
format_stream_events_as_sse,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = ["StreamEventConverter", "format_stream_events_as_sse"]
|