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.
Files changed (126) hide show
  1. qtype/application/commons/tools.py +1 -1
  2. qtype/application/converters/tools_from_api.py +5 -5
  3. qtype/application/converters/tools_from_module.py +2 -2
  4. qtype/application/converters/types.py +14 -43
  5. qtype/application/documentation.py +1 -1
  6. qtype/application/facade.py +92 -71
  7. qtype/base/types.py +227 -7
  8. qtype/commands/convert.py +20 -8
  9. qtype/commands/generate.py +19 -27
  10. qtype/commands/run.py +54 -36
  11. qtype/commands/serve.py +74 -54
  12. qtype/commands/validate.py +34 -8
  13. qtype/commands/visualize.py +46 -22
  14. qtype/dsl/__init__.py +6 -5
  15. qtype/dsl/custom_types.py +1 -1
  16. qtype/dsl/domain_types.py +65 -5
  17. qtype/dsl/linker.py +384 -0
  18. qtype/dsl/loader.py +315 -0
  19. qtype/dsl/model.py +612 -363
  20. qtype/dsl/parser.py +200 -0
  21. qtype/dsl/types.py +50 -0
  22. qtype/interpreter/api.py +58 -135
  23. qtype/interpreter/auth/aws.py +19 -9
  24. qtype/interpreter/auth/generic.py +93 -16
  25. qtype/interpreter/base/base_step_executor.py +429 -0
  26. qtype/interpreter/base/batch_step_executor.py +171 -0
  27. qtype/interpreter/base/exceptions.py +50 -0
  28. qtype/interpreter/base/executor_context.py +74 -0
  29. qtype/interpreter/base/factory.py +117 -0
  30. qtype/interpreter/base/progress_tracker.py +75 -0
  31. qtype/interpreter/base/secrets.py +339 -0
  32. qtype/interpreter/base/step_cache.py +73 -0
  33. qtype/interpreter/base/stream_emitter.py +469 -0
  34. qtype/interpreter/conversions.py +455 -21
  35. qtype/interpreter/converters.py +73 -0
  36. qtype/interpreter/endpoints.py +355 -0
  37. qtype/interpreter/executors/agent_executor.py +242 -0
  38. qtype/interpreter/executors/aggregate_executor.py +93 -0
  39. qtype/interpreter/executors/decoder_executor.py +163 -0
  40. qtype/interpreter/executors/doc_to_text_executor.py +112 -0
  41. qtype/interpreter/executors/document_embedder_executor.py +75 -0
  42. qtype/interpreter/executors/document_search_executor.py +122 -0
  43. qtype/interpreter/executors/document_source_executor.py +118 -0
  44. qtype/interpreter/executors/document_splitter_executor.py +105 -0
  45. qtype/interpreter/executors/echo_executor.py +63 -0
  46. qtype/interpreter/executors/field_extractor_executor.py +160 -0
  47. qtype/interpreter/executors/file_source_executor.py +101 -0
  48. qtype/interpreter/executors/file_writer_executor.py +110 -0
  49. qtype/interpreter/executors/index_upsert_executor.py +228 -0
  50. qtype/interpreter/executors/invoke_embedding_executor.py +92 -0
  51. qtype/interpreter/executors/invoke_flow_executor.py +51 -0
  52. qtype/interpreter/executors/invoke_tool_executor.py +353 -0
  53. qtype/interpreter/executors/llm_inference_executor.py +272 -0
  54. qtype/interpreter/executors/prompt_template_executor.py +78 -0
  55. qtype/interpreter/executors/sql_source_executor.py +106 -0
  56. qtype/interpreter/executors/vector_search_executor.py +91 -0
  57. qtype/interpreter/flow.py +147 -22
  58. qtype/interpreter/metadata_api.py +115 -0
  59. qtype/interpreter/resource_cache.py +5 -4
  60. qtype/interpreter/stream/chat/__init__.py +15 -0
  61. qtype/interpreter/stream/chat/converter.py +391 -0
  62. qtype/interpreter/{chat → stream/chat}/file_conversions.py +2 -2
  63. qtype/interpreter/stream/chat/ui_request_to_domain_type.py +140 -0
  64. qtype/interpreter/stream/chat/vercel.py +609 -0
  65. qtype/interpreter/stream/utils/__init__.py +15 -0
  66. qtype/interpreter/stream/utils/build_vercel_ai_formatter.py +74 -0
  67. qtype/interpreter/stream/utils/callback_to_stream.py +66 -0
  68. qtype/interpreter/stream/utils/create_streaming_response.py +18 -0
  69. qtype/interpreter/stream/utils/default_chat_extract_text.py +20 -0
  70. qtype/interpreter/stream/utils/error_streaming_response.py +20 -0
  71. qtype/interpreter/telemetry.py +135 -8
  72. qtype/interpreter/tools/__init__.py +5 -0
  73. qtype/interpreter/tools/function_tool_helper.py +265 -0
  74. qtype/interpreter/types.py +328 -0
  75. qtype/interpreter/typing.py +83 -89
  76. qtype/interpreter/ui/404/index.html +1 -1
  77. qtype/interpreter/ui/404.html +1 -1
  78. qtype/interpreter/ui/_next/static/{nUaw6_IwRwPqkzwe5s725 → 20HoJN6otZ_LyHLHpCPE6}/_buildManifest.js +1 -1
  79. qtype/interpreter/ui/_next/static/chunks/{393-8fd474427f8e19ce.js → 434-b2112d19f25c44ff.js} +3 -3
  80. qtype/interpreter/ui/_next/static/chunks/app/page-8c67d16ac90d23cb.js +1 -0
  81. qtype/interpreter/ui/_next/static/chunks/ba12c10f-546f2714ff8abc66.js +1 -0
  82. qtype/interpreter/ui/_next/static/css/8a8d1269e362fef7.css +3 -0
  83. qtype/interpreter/ui/icon.png +0 -0
  84. qtype/interpreter/ui/index.html +1 -1
  85. qtype/interpreter/ui/index.txt +4 -4
  86. qtype/semantic/checker.py +583 -0
  87. qtype/semantic/generate.py +262 -83
  88. qtype/semantic/loader.py +95 -0
  89. qtype/semantic/model.py +436 -159
  90. qtype/semantic/resolver.py +59 -17
  91. qtype/semantic/visualize.py +28 -31
  92. {qtype-0.0.15.dist-info → qtype-0.1.0.dist-info}/METADATA +16 -3
  93. qtype-0.1.0.dist-info/RECORD +134 -0
  94. qtype/dsl/base_types.py +0 -38
  95. qtype/dsl/validator.py +0 -465
  96. qtype/interpreter/batch/__init__.py +0 -0
  97. qtype/interpreter/batch/file_sink_source.py +0 -162
  98. qtype/interpreter/batch/flow.py +0 -95
  99. qtype/interpreter/batch/sql_source.py +0 -92
  100. qtype/interpreter/batch/step.py +0 -74
  101. qtype/interpreter/batch/types.py +0 -41
  102. qtype/interpreter/batch/utils.py +0 -178
  103. qtype/interpreter/chat/chat_api.py +0 -237
  104. qtype/interpreter/chat/vercel.py +0 -314
  105. qtype/interpreter/exceptions.py +0 -10
  106. qtype/interpreter/step.py +0 -67
  107. qtype/interpreter/steps/__init__.py +0 -0
  108. qtype/interpreter/steps/agent.py +0 -114
  109. qtype/interpreter/steps/condition.py +0 -36
  110. qtype/interpreter/steps/decoder.py +0 -88
  111. qtype/interpreter/steps/llm_inference.py +0 -171
  112. qtype/interpreter/steps/prompt_template.py +0 -54
  113. qtype/interpreter/steps/search.py +0 -24
  114. qtype/interpreter/steps/tool.py +0 -219
  115. qtype/interpreter/streaming_helpers.py +0 -123
  116. qtype/interpreter/ui/_next/static/chunks/app/page-7e26b6156cfb55d3.js +0 -1
  117. qtype/interpreter/ui/_next/static/chunks/ba12c10f-22556063851a6df2.js +0 -1
  118. qtype/interpreter/ui/_next/static/css/b40532b0db09cce3.css +0 -3
  119. qtype/interpreter/ui/favicon.ico +0 -0
  120. qtype/loader.py +0 -390
  121. qtype-0.0.15.dist-info/RECORD +0 -106
  122. /qtype/interpreter/ui/_next/static/{nUaw6_IwRwPqkzwe5s725 → 20HoJN6otZ_LyHLHpCPE6}/_ssgManifest.js +0 -0
  123. {qtype-0.0.15.dist-info → qtype-0.1.0.dist-info}/WHEEL +0 -0
  124. {qtype-0.0.15.dist-info → qtype-0.1.0.dist-info}/entry_points.txt +0 -0
  125. {qtype-0.0.15.dist-info → qtype-0.1.0.dist-info}/licenses/LICENSE +0 -0
  126. {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 qtype.interpreter.exceptions import InterpreterError
7
- from qtype.interpreter.step import execute_step
8
- from qtype.semantic.model import Flow, Variable
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 execute_flow(flow: Flow, **kwargs: dict[Any, Any]) -> list[Variable]:
14
- """Execute a flow based on the provided arguments.
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
- inputs: The input variables for the flow.
19
- **kwargs: Additional keyword arguments.
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
- logger.debug(f"Executing step: {flow.id} with kwargs: {kwargs}")
37
+ from qtype.interpreter.base.secrets import NoOpSecretManager
22
38
 
23
- unset_inputs = [input for input in flow.inputs if not input.is_set()]
24
- if unset_inputs:
25
- raise InterpreterError(
26
- f"The following inputs are required but have no values: {', '.join([input.id for input in unset_inputs])}"
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
- for step in flow.steps:
30
- execute_step(step, **kwargs)
47
+ # Use tracer from context
48
+ tracer = exec_context.tracer or trace.get_tracer(__name__)
31
49
 
32
- unset_outputs = [output for output in flow.outputs if not output.is_set()]
33
- if unset_outputs:
34
- raise InterpreterError(
35
- f"The following outputs are required but have no values: {', '.join([output.id for output in unset_outputs])}"
36
- )
37
- return flow.outputs
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 LRUCache # type: ignore[import-untyped]
5
+ from cachetools import TTLCache # type: ignore[import-untyped]
6
6
 
7
- # Global LRU cache with a reasonable default size
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
- _GLOBAL_RESOURCE_CACHE: LRUCache[Any, Any] = LRUCache(
10
- maxsize=_RESOURCE_CACHE_MAX_SIZE
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"]