qtype 0.1.0__py3-none-any.whl → 0.1.2__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/facade.py +16 -17
- qtype/cli.py +5 -1
- qtype/commands/generate.py +1 -1
- qtype/commands/run.py +28 -5
- qtype/dsl/domain_types.py +24 -3
- qtype/dsl/model.py +56 -3
- qtype/interpreter/base/base_step_executor.py +8 -1
- qtype/interpreter/base/executor_context.py +18 -1
- qtype/interpreter/base/factory.py +33 -66
- qtype/interpreter/base/progress_tracker.py +35 -0
- qtype/interpreter/base/step_cache.py +3 -2
- qtype/interpreter/conversions.py +34 -19
- qtype/interpreter/converters.py +19 -13
- qtype/interpreter/executors/bedrock_reranker_executor.py +195 -0
- qtype/interpreter/executors/document_embedder_executor.py +36 -4
- qtype/interpreter/executors/document_search_executor.py +37 -46
- qtype/interpreter/executors/document_splitter_executor.py +1 -1
- qtype/interpreter/executors/field_extractor_executor.py +10 -5
- qtype/interpreter/executors/index_upsert_executor.py +115 -111
- qtype/interpreter/executors/invoke_embedding_executor.py +2 -2
- qtype/interpreter/executors/invoke_tool_executor.py +6 -1
- qtype/interpreter/flow.py +47 -32
- qtype/interpreter/rich_progress.py +225 -0
- qtype/interpreter/types.py +2 -0
- qtype/semantic/checker.py +79 -19
- qtype/semantic/model.py +43 -3
- qtype/semantic/resolver.py +4 -2
- {qtype-0.1.0.dist-info → qtype-0.1.2.dist-info}/METADATA +12 -11
- {qtype-0.1.0.dist-info → qtype-0.1.2.dist-info}/RECORD +33 -31
- {qtype-0.1.0.dist-info → qtype-0.1.2.dist-info}/WHEEL +0 -0
- {qtype-0.1.0.dist-info → qtype-0.1.2.dist-info}/entry_points.txt +0 -0
- {qtype-0.1.0.dist-info → qtype-0.1.2.dist-info}/licenses/LICENSE +0 -0
- {qtype-0.1.0.dist-info → qtype-0.1.2.dist-info}/top_level.txt +0 -0
qtype/application/facade.py
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
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
|
-
from qtype.base.logging import get_logger
|
|
9
9
|
from qtype.base.types import PathLike
|
|
10
10
|
from qtype.semantic.model import Application as SemanticApplication
|
|
11
11
|
from qtype.semantic.model import DocumentType as SemanticDocumentType
|
|
@@ -14,7 +14,7 @@ from qtype.semantic.model import DocumentType as SemanticDocumentType
|
|
|
14
14
|
# That's the whole point of this facade - to avoid importing optional
|
|
15
15
|
# dependencies unless these methods are called.
|
|
16
16
|
|
|
17
|
-
logger =
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class QTypeFacade:
|
|
@@ -27,13 +27,13 @@ class QTypeFacade:
|
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
29
|
def telemetry(self, spec: SemanticDocumentType) -> None:
|
|
30
|
+
from qtype.interpreter.telemetry import register
|
|
31
|
+
|
|
30
32
|
if isinstance(spec, SemanticApplication) and spec.telemetry:
|
|
31
33
|
logger.info(
|
|
32
34
|
f"Telemetry enabled with endpoint: {spec.telemetry.endpoint}"
|
|
33
35
|
)
|
|
34
36
|
# Register telemetry if needed
|
|
35
|
-
from qtype.interpreter.telemetry import register
|
|
36
|
-
|
|
37
37
|
register(spec.telemetry, self.secret_manager(spec), spec.id)
|
|
38
38
|
|
|
39
39
|
def secret_manager(self, spec: SemanticDocumentType):
|
|
@@ -75,11 +75,17 @@ class QTypeFacade:
|
|
|
75
75
|
DataFrame with results (one row per input)
|
|
76
76
|
"""
|
|
77
77
|
import pandas as pd
|
|
78
|
+
from opentelemetry import trace
|
|
78
79
|
|
|
80
|
+
from qtype.interpreter.base.executor_context import ExecutorContext
|
|
81
|
+
from qtype.interpreter.converters import (
|
|
82
|
+
dataframe_to_flow_messages,
|
|
83
|
+
flow_messages_to_dataframe,
|
|
84
|
+
)
|
|
85
|
+
from qtype.interpreter.flow import run_flow
|
|
86
|
+
from qtype.interpreter.types import Session
|
|
79
87
|
from qtype.semantic.loader import load
|
|
80
88
|
|
|
81
|
-
logger.info(f"Executing workflow from {path}")
|
|
82
|
-
|
|
83
89
|
# Load the semantic application
|
|
84
90
|
semantic_model, type_registry = load(Path(path))
|
|
85
91
|
assert isinstance(semantic_model, SemanticApplication)
|
|
@@ -100,7 +106,10 @@ class QTypeFacade:
|
|
|
100
106
|
else:
|
|
101
107
|
raise ValueError("No flows found in application")
|
|
102
108
|
|
|
109
|
+
logger.info(f"Executing flow {target_flow.id} from {path}")
|
|
110
|
+
|
|
103
111
|
# Convert inputs to DataFrame (normalize single dict to 1-row DataFrame)
|
|
112
|
+
|
|
104
113
|
if isinstance(inputs, dict):
|
|
105
114
|
input_df = pd.DataFrame([inputs])
|
|
106
115
|
elif isinstance(inputs, pd.DataFrame):
|
|
@@ -111,12 +120,6 @@ class QTypeFacade:
|
|
|
111
120
|
)
|
|
112
121
|
|
|
113
122
|
# 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
|
|
119
|
-
|
|
120
123
|
session = Session(
|
|
121
124
|
session_id=kwargs.pop("session_id", "default"),
|
|
122
125
|
conversation_history=kwargs.pop("conversation_history", []),
|
|
@@ -126,12 +129,8 @@ class QTypeFacade:
|
|
|
126
129
|
initial_messages = dataframe_to_flow_messages(input_df, session)
|
|
127
130
|
|
|
128
131
|
# Execute the flow
|
|
129
|
-
from opentelemetry import trace
|
|
130
|
-
|
|
131
|
-
from qtype.interpreter.base.executor_context import ExecutorContext
|
|
132
|
-
from qtype.interpreter.flow import run_flow
|
|
133
|
-
|
|
134
132
|
secret_manager = self.secret_manager(semantic_model)
|
|
133
|
+
|
|
135
134
|
context = ExecutorContext(
|
|
136
135
|
secret_manager=secret_manager,
|
|
137
136
|
tracer=trace.get_tracer(__name__),
|
qtype/cli.py
CHANGED
|
@@ -7,6 +7,10 @@ import importlib
|
|
|
7
7
|
import logging
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
+
from qtype.base.logging import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger("application.facade")
|
|
13
|
+
|
|
10
14
|
try:
|
|
11
15
|
from importlib.metadata import entry_points
|
|
12
16
|
except ImportError:
|
|
@@ -131,7 +135,7 @@ def main() -> None:
|
|
|
131
135
|
# Set logging level based on user input
|
|
132
136
|
logging.basicConfig(
|
|
133
137
|
level=getattr(logging, args.log_level),
|
|
134
|
-
format="%(levelname)s: %(message)s",
|
|
138
|
+
format="%(asctime)s - %(levelname)s: %(message)s",
|
|
135
139
|
)
|
|
136
140
|
|
|
137
141
|
# Dispatch to the selected subcommand
|
qtype/commands/generate.py
CHANGED
|
@@ -188,7 +188,7 @@ def parser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
188
188
|
|
|
189
189
|
has_semantic_deps = True
|
|
190
190
|
except ImportError:
|
|
191
|
-
logger.
|
|
191
|
+
logger.debug(
|
|
192
192
|
"NetworkX or Ruff is not installed. Skipping semantic model generation."
|
|
193
193
|
)
|
|
194
194
|
has_semantic_deps = False
|
qtype/commands/run.py
CHANGED
|
@@ -7,10 +7,12 @@ from __future__ import annotations
|
|
|
7
7
|
import argparse
|
|
8
8
|
import json
|
|
9
9
|
import logging
|
|
10
|
+
import warnings
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from typing import Any
|
|
12
13
|
|
|
13
14
|
import pandas as pd
|
|
15
|
+
from pydantic.warnings import UnsupportedFieldAttributeWarning
|
|
14
16
|
|
|
15
17
|
from qtype.application.facade import QTypeFacade
|
|
16
18
|
from qtype.base.exceptions import InterpreterError, LoadError, ValidationError
|
|
@@ -18,6 +20,15 @@ from qtype.base.exceptions import InterpreterError, LoadError, ValidationError
|
|
|
18
20
|
logger = logging.getLogger(__name__)
|
|
19
21
|
|
|
20
22
|
|
|
23
|
+
# Supress specific pydantic warnings that llamaindex needs to fix
|
|
24
|
+
warnings.filterwarnings("ignore", category=UnsupportedFieldAttributeWarning)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# supress qdrant logging
|
|
28
|
+
for name in ["httpx", "urllib3", "qdrant_client", "opensearch"]:
|
|
29
|
+
logging.getLogger(name).setLevel(logging.WARNING)
|
|
30
|
+
|
|
31
|
+
|
|
21
32
|
def read_data_from_file(file_path: str) -> pd.DataFrame:
|
|
22
33
|
"""
|
|
23
34
|
Reads a file into a pandas DataFrame based on its MIME type.
|
|
@@ -29,12 +40,16 @@ def read_data_from_file(file_path: str) -> pd.DataFrame:
|
|
|
29
40
|
mime_type = magic.Magic(mime=True).from_file(file_path)
|
|
30
41
|
|
|
31
42
|
if mime_type == "text/csv":
|
|
32
|
-
|
|
43
|
+
# TODO: Restore na values and convert to optional once we support them https://github.com/bazaarvoice/qtype/issues/101
|
|
44
|
+
df = pd.read_csv(file_path)
|
|
45
|
+
return df.fillna("")
|
|
33
46
|
elif mime_type == "text/plain":
|
|
34
47
|
# For text/plain, use file extension to determine format
|
|
35
48
|
file_ext = Path(file_path).suffix.lower()
|
|
36
49
|
if file_ext == ".csv":
|
|
37
|
-
|
|
50
|
+
# TODO: Restore na values and convert to optional once we support them https://github.com/bazaarvoice/qtype/issues/101
|
|
51
|
+
df = pd.read_csv(file_path)
|
|
52
|
+
return df.fillna("")
|
|
38
53
|
elif file_ext == ".json":
|
|
39
54
|
return pd.read_json(file_path)
|
|
40
55
|
else:
|
|
@@ -87,7 +102,10 @@ def run_flow(args: Any) -> None:
|
|
|
87
102
|
# Execute the workflow using the facade (now async, returns DataFrame)
|
|
88
103
|
result_df = asyncio.run(
|
|
89
104
|
facade.execute_workflow(
|
|
90
|
-
spec_path,
|
|
105
|
+
spec_path,
|
|
106
|
+
flow_name=args.flow,
|
|
107
|
+
inputs=input,
|
|
108
|
+
show_progress=args.progress,
|
|
91
109
|
)
|
|
92
110
|
)
|
|
93
111
|
|
|
@@ -95,7 +113,7 @@ def run_flow(args: Any) -> None:
|
|
|
95
113
|
|
|
96
114
|
# Display results
|
|
97
115
|
if len(result_df) > 0:
|
|
98
|
-
logger.info(f"Processed {len(result_df)}
|
|
116
|
+
logger.info(f"Processed {len(result_df)} em")
|
|
99
117
|
|
|
100
118
|
# Remove 'row' and 'error' columns for display if all errors are None
|
|
101
119
|
display_df = result_df.copy()
|
|
@@ -108,7 +126,7 @@ def run_flow(args: Any) -> None:
|
|
|
108
126
|
display_df = display_df.drop(columns=["row"])
|
|
109
127
|
|
|
110
128
|
if len(display_df) > 1:
|
|
111
|
-
logger.info(f"\nResults:\n{display_df.to_string()}")
|
|
129
|
+
logger.info(f"\nResults:\n{display_df[0:10].to_string()}\n...")
|
|
112
130
|
else:
|
|
113
131
|
# Print the first row with column_name: value one per line
|
|
114
132
|
fmt_str = []
|
|
@@ -172,6 +190,11 @@ def parser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
172
190
|
default=None,
|
|
173
191
|
help="Path to save output data. If input is a DataFrame, output will be saved as parquet. If single result, saved as JSON.",
|
|
174
192
|
)
|
|
193
|
+
cmd_parser.add_argument(
|
|
194
|
+
"--progress",
|
|
195
|
+
action="store_true",
|
|
196
|
+
help="Show progress bars during flow execution.",
|
|
197
|
+
)
|
|
175
198
|
|
|
176
199
|
cmd_parser.add_argument(
|
|
177
200
|
"spec", type=str, help="Path to the QType YAML spec file."
|
qtype/dsl/domain_types.py
CHANGED
|
@@ -93,12 +93,33 @@ class RAGChunk(Embedding):
|
|
|
93
93
|
)
|
|
94
94
|
|
|
95
95
|
|
|
96
|
-
class
|
|
97
|
-
"""A standard, built-in representation of a search result
|
|
96
|
+
class SearchResult(StrictBaseModel):
|
|
97
|
+
"""A standard, built-in representation of a search result."""
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
content: Any = Field(..., description="The content of the search result.")
|
|
100
|
+
doc_id: str = Field(
|
|
101
|
+
...,
|
|
102
|
+
description="The identifier of the document from which the result was retrieved.",
|
|
103
|
+
)
|
|
104
|
+
score: float = Field(
|
|
105
|
+
...,
|
|
106
|
+
description="The relevance score of the search result with respect to the query.",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class RAGSearchResult(SearchResult):
|
|
111
|
+
"""A standard, built-in representation of a search result from a RAG vector search.
|
|
112
|
+
|
|
113
|
+
Note: doc_id is duplicated from content.document_id for convenience.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
content: RAGChunk = Field(
|
|
100
117
|
..., description="The RAG chunk returned as a search result."
|
|
101
118
|
)
|
|
119
|
+
doc_id: str = Field(
|
|
120
|
+
...,
|
|
121
|
+
description="The document ID (duplicated from content.document_id).",
|
|
122
|
+
)
|
|
102
123
|
score: float = Field(
|
|
103
124
|
...,
|
|
104
125
|
description="The similarity score of the chunk with respect to the query.",
|
qtype/dsl/model.py
CHANGED
|
@@ -573,6 +573,10 @@ class FieldExtractor(Step):
|
|
|
573
573
|
...,
|
|
574
574
|
description="JSONPath expression to extract data from the input. Uses jsonpath-ng syntax.",
|
|
575
575
|
)
|
|
576
|
+
fail_on_missing: bool = Field(
|
|
577
|
+
default=True,
|
|
578
|
+
description="Whether to raise an error if the JSONPath matches no data. If False, returns None.",
|
|
579
|
+
)
|
|
576
580
|
|
|
577
581
|
|
|
578
582
|
class InvokeTool(Step, ConcurrentStepMixin):
|
|
@@ -1077,6 +1081,14 @@ class DocumentIndex(Index):
|
|
|
1077
1081
|
...,
|
|
1078
1082
|
description="URL endpoint for the search cluster (e.g., https://my-cluster.es.amazonaws.com).",
|
|
1079
1083
|
)
|
|
1084
|
+
id_field: str | None = Field(
|
|
1085
|
+
default=None,
|
|
1086
|
+
description=(
|
|
1087
|
+
"Field name to use as document ID. "
|
|
1088
|
+
"If not specified, auto-detects from: _id, id, doc_id, document_id, or uuid. "
|
|
1089
|
+
"If all are missing, a UUID is generated."
|
|
1090
|
+
),
|
|
1091
|
+
)
|
|
1080
1092
|
|
|
1081
1093
|
|
|
1082
1094
|
class Search(Step, ABC):
|
|
@@ -1089,15 +1101,18 @@ class Search(Step, ABC):
|
|
|
1089
1101
|
index: Reference[IndexType] | str = Field(
|
|
1090
1102
|
..., description="Index to search against (object or ID reference)."
|
|
1091
1103
|
)
|
|
1104
|
+
default_top_k: int | None = Field(
|
|
1105
|
+
default=10,
|
|
1106
|
+
description="Number of top results to retrieve if not provided in the inputs.",
|
|
1107
|
+
)
|
|
1092
1108
|
|
|
1093
1109
|
|
|
1094
1110
|
class VectorSearch(Search, BatchableStepMixin):
|
|
1095
1111
|
"""Performs vector similarity search against a vector index."""
|
|
1096
1112
|
|
|
1097
1113
|
type: Literal["VectorSearch"] = "VectorSearch"
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
description="Number of top results to retrieve if not provided in the inputs.",
|
|
1114
|
+
index: Reference[VectorIndex] | str = Field(
|
|
1115
|
+
..., description="Index to search against (object or ID reference)."
|
|
1101
1116
|
)
|
|
1102
1117
|
|
|
1103
1118
|
|
|
@@ -1105,6 +1120,43 @@ class DocumentSearch(Search, ConcurrentStepMixin):
|
|
|
1105
1120
|
"""Performs document search against a document index."""
|
|
1106
1121
|
|
|
1107
1122
|
type: Literal["DocumentSearch"] = "DocumentSearch"
|
|
1123
|
+
index: Reference[DocumentIndex] | str = Field(
|
|
1124
|
+
..., description="Index to search against (object or ID reference)."
|
|
1125
|
+
)
|
|
1126
|
+
query_args: dict[str, Any] = Field(
|
|
1127
|
+
default={
|
|
1128
|
+
"type": "best_fields",
|
|
1129
|
+
"fields": ["*"],
|
|
1130
|
+
},
|
|
1131
|
+
description="The arguments (other than 'query') to specify to the query shape (see https://docs.opensearch.org/latest/query-dsl/full-text/multi-match/).",
|
|
1132
|
+
)
|
|
1133
|
+
|
|
1134
|
+
|
|
1135
|
+
class Reranker(Step):
|
|
1136
|
+
"""Reranks a list of documents based on relevance to a query using an LLM."""
|
|
1137
|
+
|
|
1138
|
+
type: Literal["Reranker"] = "Reranker"
|
|
1139
|
+
|
|
1140
|
+
|
|
1141
|
+
# TODO: create a reranker that supports llamaindex rerankers...
|
|
1142
|
+
|
|
1143
|
+
|
|
1144
|
+
class BedrockReranker(Reranker, ConcurrentStepMixin):
|
|
1145
|
+
"""Reranks documents using an AWS Bedrock model."""
|
|
1146
|
+
|
|
1147
|
+
type: Literal["BedrockReranker"] = "BedrockReranker"
|
|
1148
|
+
auth: Reference[AWSAuthProvider] | str | None = Field(
|
|
1149
|
+
default=None,
|
|
1150
|
+
description="AWS authorization provider for Bedrock access.",
|
|
1151
|
+
)
|
|
1152
|
+
model_id: str = Field(
|
|
1153
|
+
...,
|
|
1154
|
+
description="Bedrock model ID to use for reranking. See https://docs.aws.amazon.com/bedrock/latest/userguide/rerank-supported.html",
|
|
1155
|
+
)
|
|
1156
|
+
num_results: int | None = Field(
|
|
1157
|
+
default=None,
|
|
1158
|
+
description="Return this many results.",
|
|
1159
|
+
)
|
|
1108
1160
|
|
|
1109
1161
|
|
|
1110
1162
|
# Create a union type for all tool types
|
|
@@ -1146,6 +1198,7 @@ StepType = Annotated[
|
|
|
1146
1198
|
Union[
|
|
1147
1199
|
Agent,
|
|
1148
1200
|
Aggregate,
|
|
1201
|
+
BedrockReranker,
|
|
1149
1202
|
Decoder,
|
|
1150
1203
|
DocToTextConverter,
|
|
1151
1204
|
DocumentEmbedder,
|
|
@@ -212,7 +212,6 @@ class StepExecutor(ABC):
|
|
|
212
212
|
num_workers = (
|
|
213
213
|
self.step.concurrency_config.num_workers # type: ignore[attr-defined]
|
|
214
214
|
)
|
|
215
|
-
|
|
216
215
|
span.set_attribute("step.concurrency", num_workers)
|
|
217
216
|
|
|
218
217
|
# Prepare messages for processing (batching hook)
|
|
@@ -331,6 +330,11 @@ class StepExecutor(ABC):
|
|
|
331
330
|
cached_result = self.cache.get(key)
|
|
332
331
|
if cached_result is not None:
|
|
333
332
|
result = [from_cache_value(d, message) for d in cached_result] # type: ignore
|
|
333
|
+
self.progress.increment_cache(
|
|
334
|
+
self.context.on_progress,
|
|
335
|
+
hit_delta=len(result),
|
|
336
|
+
miss_delta=0,
|
|
337
|
+
)
|
|
334
338
|
# cache hit
|
|
335
339
|
for msg in result:
|
|
336
340
|
yield msg
|
|
@@ -341,6 +345,9 @@ class StepExecutor(ABC):
|
|
|
341
345
|
buf.append(output_msg)
|
|
342
346
|
yield output_msg
|
|
343
347
|
|
|
348
|
+
self.progress.increment_cache(
|
|
349
|
+
self.context.on_progress, hit_delta=0, miss_delta=len(buf)
|
|
350
|
+
)
|
|
344
351
|
# store the results in the cache of there are no errors or if instructed to do so
|
|
345
352
|
if (
|
|
346
353
|
all(not msg.is_failed() for msg in buf)
|
|
@@ -7,7 +7,8 @@ concerns threaded through the execution pipeline.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
-
from
|
|
10
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
11
|
+
from dataclasses import dataclass, field
|
|
11
12
|
|
|
12
13
|
from opentelemetry.trace import Tracer
|
|
13
14
|
|
|
@@ -51,6 +52,9 @@ class ExecutorContext:
|
|
|
51
52
|
on_progress: Optional callback for progress updates during execution.
|
|
52
53
|
tracer: OpenTelemetry tracer for distributed tracing and observability.
|
|
53
54
|
Defaults to a no-op tracer if telemetry is not configured.
|
|
55
|
+
thread_pool: Shared thread pool for running synchronous operations
|
|
56
|
+
in async contexts. Defaults to a pool with 100 threads to support
|
|
57
|
+
high concurrency workloads without thread exhaustion.
|
|
54
58
|
|
|
55
59
|
Example:
|
|
56
60
|
```python
|
|
@@ -72,3 +76,16 @@ class ExecutorContext:
|
|
|
72
76
|
on_stream_event: StreamingCallback | None = None
|
|
73
77
|
on_progress: ProgressCallback | None = None
|
|
74
78
|
tracer: Tracer | None = None
|
|
79
|
+
thread_pool: ThreadPoolExecutor = field(
|
|
80
|
+
default_factory=lambda: ThreadPoolExecutor(max_workers=100)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def cleanup(self) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Clean up resources held by the context.
|
|
86
|
+
|
|
87
|
+
This should be called when the context is no longer needed to ensure
|
|
88
|
+
proper cleanup of the thread pool and any other resources.
|
|
89
|
+
"""
|
|
90
|
+
if self.thread_pool:
|
|
91
|
+
self.thread_pool.shutdown(wait=True)
|
|
@@ -1,48 +1,7 @@
|
|
|
1
|
-
from qtype.interpreter.executors.agent_executor import AgentExecutor
|
|
2
|
-
from qtype.interpreter.executors.aggregate_executor import AggregateExecutor
|
|
3
|
-
from qtype.interpreter.executors.decoder_executor import DecoderExecutor
|
|
4
|
-
from qtype.interpreter.executors.doc_to_text_executor import (
|
|
5
|
-
DocToTextConverterExecutor,
|
|
6
|
-
)
|
|
7
|
-
from qtype.interpreter.executors.document_embedder_executor import (
|
|
8
|
-
DocumentEmbedderExecutor,
|
|
9
|
-
)
|
|
10
|
-
from qtype.interpreter.executors.document_search_executor import (
|
|
11
|
-
DocumentSearchExecutor,
|
|
12
|
-
)
|
|
13
|
-
from qtype.interpreter.executors.document_source_executor import (
|
|
14
|
-
DocumentSourceExecutor,
|
|
15
|
-
)
|
|
16
|
-
from qtype.interpreter.executors.document_splitter_executor import (
|
|
17
|
-
DocumentSplitterExecutor,
|
|
18
|
-
)
|
|
19
|
-
from qtype.interpreter.executors.echo_executor import EchoExecutor
|
|
20
|
-
from qtype.interpreter.executors.field_extractor_executor import (
|
|
21
|
-
FieldExtractorExecutor,
|
|
22
|
-
)
|
|
23
|
-
from qtype.interpreter.executors.file_source_executor import FileSourceExecutor
|
|
24
|
-
from qtype.interpreter.executors.file_writer_executor import FileWriterExecutor
|
|
25
|
-
from qtype.interpreter.executors.index_upsert_executor import (
|
|
26
|
-
IndexUpsertExecutor,
|
|
27
|
-
)
|
|
28
|
-
from qtype.interpreter.executors.invoke_embedding_executor import (
|
|
29
|
-
InvokeEmbeddingExecutor,
|
|
30
|
-
)
|
|
31
|
-
from qtype.interpreter.executors.invoke_flow_executor import InvokeFlowExecutor
|
|
32
|
-
from qtype.interpreter.executors.invoke_tool_executor import InvokeToolExecutor
|
|
33
|
-
from qtype.interpreter.executors.llm_inference_executor import (
|
|
34
|
-
LLMInferenceExecutor,
|
|
35
|
-
)
|
|
36
|
-
from qtype.interpreter.executors.prompt_template_executor import (
|
|
37
|
-
PromptTemplateExecutor,
|
|
38
|
-
)
|
|
39
|
-
from qtype.interpreter.executors.sql_source_executor import SQLSourceExecutor
|
|
40
|
-
from qtype.interpreter.executors.vector_search_executor import (
|
|
41
|
-
VectorSearchExecutor,
|
|
42
|
-
)
|
|
43
1
|
from qtype.semantic.model import (
|
|
44
2
|
Agent,
|
|
45
3
|
Aggregate,
|
|
4
|
+
BedrockReranker,
|
|
46
5
|
Decoder,
|
|
47
6
|
DocToTextConverter,
|
|
48
7
|
DocumentEmbedder,
|
|
@@ -67,29 +26,30 @@ from qtype.semantic.model import (
|
|
|
67
26
|
from .batch_step_executor import StepExecutor
|
|
68
27
|
from .executor_context import ExecutorContext
|
|
69
28
|
|
|
70
|
-
#
|
|
71
|
-
|
|
29
|
+
# Lazy-load executor classes only when needed
|
|
30
|
+
# This avoids importing heavy dependencies until actually required
|
|
72
31
|
EXECUTOR_REGISTRY = {
|
|
73
|
-
Agent: AgentExecutor,
|
|
74
|
-
Aggregate: AggregateExecutor,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
32
|
+
Agent: "qtype.interpreter.executors.agent_executor.AgentExecutor",
|
|
33
|
+
Aggregate: "qtype.interpreter.executors.aggregate_executor.AggregateExecutor",
|
|
34
|
+
BedrockReranker: "qtype.interpreter.executors.bedrock_reranker_executor.BedrockRerankerExecutor",
|
|
35
|
+
Decoder: "qtype.interpreter.executors.decoder_executor.DecoderExecutor",
|
|
36
|
+
DocToTextConverter: "qtype.interpreter.executors.doc_to_text_executor.DocToTextConverterExecutor",
|
|
37
|
+
DocumentEmbedder: "qtype.interpreter.executors.document_embedder_executor.DocumentEmbedderExecutor",
|
|
38
|
+
DocumentSearch: "qtype.interpreter.executors.document_search_executor.DocumentSearchExecutor",
|
|
39
|
+
DocumentSource: "qtype.interpreter.executors.document_source_executor.DocumentSourceExecutor",
|
|
40
|
+
DocumentSplitter: "qtype.interpreter.executors.document_splitter_executor.DocumentSplitterExecutor",
|
|
41
|
+
Echo: "qtype.interpreter.executors.echo_executor.EchoExecutor",
|
|
42
|
+
FieldExtractor: "qtype.interpreter.executors.field_extractor_executor.FieldExtractorExecutor",
|
|
43
|
+
FileSource: "qtype.interpreter.executors.file_source_executor.FileSourceExecutor",
|
|
44
|
+
FileWriter: "qtype.interpreter.executors.file_writer_executor.FileWriterExecutor",
|
|
45
|
+
IndexUpsert: "qtype.interpreter.executors.index_upsert_executor.IndexUpsertExecutor",
|
|
46
|
+
InvokeEmbedding: "qtype.interpreter.executors.invoke_embedding_executor.InvokeEmbeddingExecutor",
|
|
47
|
+
InvokeFlow: "qtype.interpreter.executors.invoke_flow_executor.InvokeFlowExecutor",
|
|
48
|
+
InvokeTool: "qtype.interpreter.executors.invoke_tool_executor.InvokeToolExecutor",
|
|
49
|
+
LLMInference: "qtype.interpreter.executors.llm_inference_executor.LLMInferenceExecutor",
|
|
50
|
+
PromptTemplate: "qtype.interpreter.executors.prompt_template_executor.PromptTemplateExecutor",
|
|
51
|
+
SQLSource: "qtype.interpreter.executors.sql_source_executor.SQLSourceExecutor",
|
|
52
|
+
VectorSearch: "qtype.interpreter.executors.vector_search_executor.VectorSearchExecutor",
|
|
93
53
|
}
|
|
94
54
|
|
|
95
55
|
|
|
@@ -107,11 +67,18 @@ def create_executor(
|
|
|
107
67
|
Returns:
|
|
108
68
|
StepExecutor: Configured executor instance
|
|
109
69
|
"""
|
|
110
|
-
|
|
111
|
-
if not
|
|
70
|
+
executor_path = EXECUTOR_REGISTRY.get(type(step))
|
|
71
|
+
if not executor_path:
|
|
112
72
|
raise ValueError(
|
|
113
73
|
f"No executor found for step type: {type(step).__name__}"
|
|
114
74
|
)
|
|
115
75
|
|
|
76
|
+
# Lazy-load the executor class
|
|
77
|
+
module_path, class_name = executor_path.rsplit(".", 1)
|
|
78
|
+
import importlib
|
|
79
|
+
|
|
80
|
+
module = importlib.import_module(module_path)
|
|
81
|
+
executor_class = getattr(module, class_name)
|
|
82
|
+
|
|
116
83
|
# This assumes the constructor takes the step, context, then dependencies
|
|
117
84
|
return executor_class(step, context, **dependencies)
|
|
@@ -20,6 +20,8 @@ class ProgressTracker:
|
|
|
20
20
|
self.items_processed = 0
|
|
21
21
|
self.items_in_error = 0
|
|
22
22
|
self.total_items = total_items
|
|
23
|
+
self.cache_hits = None
|
|
24
|
+
self.cache_misses = None
|
|
23
25
|
|
|
24
26
|
@property
|
|
25
27
|
def items_succeeded(self) -> int:
|
|
@@ -36,6 +38,8 @@ class ProgressTracker:
|
|
|
36
38
|
on_progress: ProgressCallback | None,
|
|
37
39
|
processed_delta: int,
|
|
38
40
|
error_delta: int,
|
|
41
|
+
hit_delta: int | None = None,
|
|
42
|
+
miss_delta: int | None = None,
|
|
39
43
|
) -> None:
|
|
40
44
|
"""
|
|
41
45
|
Update progress counters and invoke the progress callback.
|
|
@@ -51,6 +55,19 @@ class ProgressTracker:
|
|
|
51
55
|
self.items_processed += processed_delta
|
|
52
56
|
self.items_in_error += error_delta
|
|
53
57
|
|
|
58
|
+
if hit_delta is not None:
|
|
59
|
+
self.cache_hits = (
|
|
60
|
+
self.cache_hits + hit_delta
|
|
61
|
+
if self.cache_hits is not None
|
|
62
|
+
else hit_delta
|
|
63
|
+
)
|
|
64
|
+
if miss_delta is not None:
|
|
65
|
+
self.cache_misses = (
|
|
66
|
+
self.cache_misses + miss_delta
|
|
67
|
+
if self.cache_misses is not None
|
|
68
|
+
else miss_delta
|
|
69
|
+
)
|
|
70
|
+
|
|
54
71
|
if on_progress:
|
|
55
72
|
on_progress(
|
|
56
73
|
self.step_id,
|
|
@@ -58,6 +75,8 @@ class ProgressTracker:
|
|
|
58
75
|
self.items_in_error,
|
|
59
76
|
self.items_succeeded,
|
|
60
77
|
self.total_items,
|
|
78
|
+
self.cache_hits,
|
|
79
|
+
self.cache_misses,
|
|
61
80
|
)
|
|
62
81
|
|
|
63
82
|
def update_for_message(
|
|
@@ -73,3 +92,19 @@ class ProgressTracker:
|
|
|
73
92
|
on_progress: Optional callback to notify of progress updates
|
|
74
93
|
"""
|
|
75
94
|
self.update(on_progress, 1, 1 if message.is_failed() else 0)
|
|
95
|
+
|
|
96
|
+
def increment_cache(
|
|
97
|
+
self,
|
|
98
|
+
on_progress: ProgressCallback | None,
|
|
99
|
+
hit_delta: int = 0,
|
|
100
|
+
miss_delta: int = 0,
|
|
101
|
+
) -> None:
|
|
102
|
+
"""
|
|
103
|
+
Increment cache hit/miss counters.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
on_progress: Optional callback to notify of progress updates
|
|
107
|
+
hit_delta: Number of cache hits to add
|
|
108
|
+
miss_delta: Number of cache misses to add
|
|
109
|
+
"""
|
|
110
|
+
self.update(on_progress, 0, 0, hit_delta, miss_delta)
|
|
@@ -4,7 +4,8 @@ import pathlib
|
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
6
|
import diskcache as dc
|
|
7
|
-
from
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
from pydantic.json import pydantic_encoder
|
|
8
9
|
|
|
9
10
|
from qtype.base.types import CacheConfig
|
|
10
11
|
from qtype.interpreter.types import FlowMessage
|
|
@@ -41,7 +42,7 @@ def cache_key(message: FlowMessage, step: Step) -> str:
|
|
|
41
42
|
raise ValueError(
|
|
42
43
|
f"Input variable '{var.id}' not found in message -- caching can not be performed."
|
|
43
44
|
)
|
|
44
|
-
input_str = json.dumps(inputs, sort_keys=True)
|
|
45
|
+
input_str = json.dumps(inputs, sort_keys=True, default=pydantic_encoder)
|
|
45
46
|
return hashlib.sha256(input_str.encode("utf-8")).hexdigest()
|
|
46
47
|
|
|
47
48
|
|