qtype 0.0.12__py3-none-any.whl → 0.1.7__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 +476 -11
- qtype/application/converters/tools_from_module.py +38 -14
- qtype/application/converters/types.py +15 -30
- qtype/application/documentation.py +1 -1
- qtype/application/facade.py +102 -85
- qtype/base/types.py +227 -7
- qtype/cli.py +5 -1
- qtype/commands/convert.py +52 -6
- qtype/commands/generate.py +44 -4
- qtype/commands/run.py +78 -36
- qtype/commands/serve.py +74 -44
- qtype/commands/validate.py +37 -14
- qtype/commands/visualize.py +46 -25
- qtype/dsl/__init__.py +6 -5
- qtype/dsl/custom_types.py +1 -1
- qtype/dsl/domain_types.py +86 -5
- qtype/dsl/linker.py +384 -0
- qtype/dsl/loader.py +315 -0
- qtype/dsl/model.py +753 -264
- qtype/dsl/parser.py +200 -0
- qtype/dsl/types.py +50 -0
- qtype/interpreter/api.py +63 -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 +91 -0
- qtype/interpreter/base/factory.py +84 -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 +495 -24
- qtype/interpreter/converters.py +79 -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/bedrock_reranker_executor.py +195 -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 +123 -0
- qtype/interpreter/executors/document_search_executor.py +113 -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 +165 -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 +232 -0
- qtype/interpreter/executors/invoke_embedding_executor.py +104 -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 +172 -22
- qtype/interpreter/logging_progress.py +61 -0
- 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/{OT8QJQW3J70VbDWWfrEMT → 20HoJN6otZ_LyHLHpCPE6}/_buildManifest.js +1 -1
- qtype/interpreter/ui/_next/static/chunks/434-b2112d19f25c44ff.js +36 -0
- qtype/interpreter/ui/_next/static/chunks/{964-ed4ab073db645007.js → 964-2b041321a01cbf56.js} +1 -1
- qtype/interpreter/ui/_next/static/chunks/app/{layout-5ccbc44fd528d089.js → layout-a05273ead5de2c41.js} +1 -1
- 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/chunks/{main-6d261b6c5d6fb6c2.js → main-e26b9cb206da2cac.js} +1 -1
- qtype/interpreter/ui/_next/static/chunks/webpack-08642e441b39b6c2.js +1 -0
- qtype/interpreter/ui/_next/static/css/8a8d1269e362fef7.css +3 -0
- qtype/interpreter/ui/_next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
- qtype/interpreter/ui/icon.png +0 -0
- qtype/interpreter/ui/index.html +1 -1
- qtype/interpreter/ui/index.txt +5 -5
- qtype/semantic/checker.py +643 -0
- qtype/semantic/generate.py +268 -85
- qtype/semantic/loader.py +95 -0
- qtype/semantic/model.py +535 -163
- qtype/semantic/resolver.py +63 -19
- qtype/semantic/visualize.py +50 -35
- {qtype-0.0.12.dist-info → qtype-0.1.7.dist-info}/METADATA +22 -5
- qtype-0.1.7.dist-info/RECORD +137 -0
- qtype/dsl/base_types.py +0 -38
- qtype/dsl/validator.py +0 -464
- qtype/interpreter/batch/__init__.py +0 -0
- qtype/interpreter/batch/flow.py +0 -95
- qtype/interpreter/batch/sql_source.py +0 -95
- qtype/interpreter/batch/step.py +0 -63
- qtype/interpreter/batch/types.py +0 -41
- qtype/interpreter/batch/utils.py +0 -179
- 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 -150
- qtype/interpreter/steps/prompt_template.py +0 -54
- qtype/interpreter/steps/search.py +0 -24
- qtype/interpreter/steps/tool.py +0 -53
- qtype/interpreter/streaming_helpers.py +0 -123
- qtype/interpreter/ui/_next/static/chunks/736-7fc606e244fedcb1.js +0 -36
- qtype/interpreter/ui/_next/static/chunks/app/page-c72e847e888e549d.js +0 -1
- qtype/interpreter/ui/_next/static/chunks/ba12c10f-22556063851a6df2.js +0 -1
- qtype/interpreter/ui/_next/static/chunks/webpack-8289c17c67827f22.js +0 -1
- qtype/interpreter/ui/_next/static/css/a262c53826df929b.css +0 -3
- qtype/interpreter/ui/_next/static/media/569ce4b8f30dc480-s.p.woff2 +0 -0
- qtype/interpreter/ui/favicon.ico +0 -0
- qtype/loader.py +0 -389
- qtype-0.0.12.dist-info/RECORD +0 -105
- /qtype/interpreter/ui/_next/static/{OT8QJQW3J70VbDWWfrEMT → 20HoJN6otZ_LyHLHpCPE6}/_ssgManifest.js +0 -0
- {qtype-0.0.12.dist-info → qtype-0.1.7.dist-info}/WHEEL +0 -0
- {qtype-0.0.12.dist-info → qtype-0.1.7.dist-info}/entry_points.txt +0 -0
- {qtype-0.0.12.dist-info → qtype-0.1.7.dist-info}/licenses/LICENSE +0 -0
- {qtype-0.0.12.dist-info → qtype-0.1.7.dist-info}/top_level.txt +0 -0
qtype/commands/run.py
CHANGED
|
@@ -7,11 +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
|
-
import magic
|
|
14
14
|
import pandas as pd
|
|
15
|
+
from pydantic.warnings import UnsupportedFieldAttributeWarning
|
|
15
16
|
|
|
16
17
|
from qtype.application.facade import QTypeFacade
|
|
17
18
|
from qtype.base.exceptions import InterpreterError, LoadError, ValidationError
|
|
@@ -19,14 +20,45 @@ from qtype.base.exceptions import InterpreterError, LoadError, ValidationError
|
|
|
19
20
|
logger = logging.getLogger(__name__)
|
|
20
21
|
|
|
21
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
|
+
|
|
22
32
|
def read_data_from_file(file_path: str) -> pd.DataFrame:
|
|
23
33
|
"""
|
|
24
34
|
Reads a file into a pandas DataFrame based on its MIME type.
|
|
25
35
|
"""
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
|
|
38
|
+
import magic
|
|
39
|
+
|
|
26
40
|
mime_type = magic.Magic(mime=True).from_file(file_path)
|
|
27
41
|
|
|
28
42
|
if mime_type == "text/csv":
|
|
29
|
-
|
|
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("")
|
|
46
|
+
elif mime_type == "text/plain":
|
|
47
|
+
# For text/plain, use file extension to determine format
|
|
48
|
+
file_ext = Path(file_path).suffix.lower()
|
|
49
|
+
if file_ext == ".csv":
|
|
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("")
|
|
53
|
+
elif file_ext == ".json":
|
|
54
|
+
return pd.read_json(file_path)
|
|
55
|
+
else:
|
|
56
|
+
raise ValueError(
|
|
57
|
+
(
|
|
58
|
+
f"Unsupported text/plain file extension: {file_ext}. "
|
|
59
|
+
"Supported extensions: .csv, .json"
|
|
60
|
+
)
|
|
61
|
+
)
|
|
30
62
|
elif mime_type == "application/json":
|
|
31
63
|
return pd.read_json(file_path)
|
|
32
64
|
elif mime_type in [
|
|
@@ -48,6 +80,8 @@ def run_flow(args: Any) -> None:
|
|
|
48
80
|
Args:
|
|
49
81
|
args: Arguments passed from the command line or calling context.
|
|
50
82
|
"""
|
|
83
|
+
import asyncio
|
|
84
|
+
|
|
51
85
|
facade = QTypeFacade()
|
|
52
86
|
spec_path = Path(args.spec)
|
|
53
87
|
|
|
@@ -65,53 +99,56 @@ def run_flow(args: Any) -> None:
|
|
|
65
99
|
logger.error(f"❌ Invalid JSON input: {e}")
|
|
66
100
|
return
|
|
67
101
|
|
|
68
|
-
# Execute the workflow using the facade
|
|
69
|
-
|
|
70
|
-
|
|
102
|
+
# Execute the workflow using the facade (now async, returns DataFrame)
|
|
103
|
+
result_df = asyncio.run(
|
|
104
|
+
facade.execute_workflow(
|
|
105
|
+
spec_path,
|
|
106
|
+
flow_name=args.flow,
|
|
107
|
+
inputs=input,
|
|
108
|
+
show_progress=args.progress,
|
|
109
|
+
)
|
|
71
110
|
)
|
|
72
111
|
|
|
73
112
|
logger.info("✅ Flow execution completed successfully")
|
|
74
113
|
|
|
75
|
-
#
|
|
76
|
-
if
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
114
|
+
# Display results
|
|
115
|
+
if len(result_df) > 0:
|
|
116
|
+
logger.info(f"Processed {len(result_df)} em")
|
|
117
|
+
|
|
118
|
+
# Remove 'row' and 'error' columns for display if all errors are None
|
|
119
|
+
display_df = result_df.copy()
|
|
120
|
+
if (
|
|
121
|
+
"error" in display_df.columns
|
|
122
|
+
and display_df["error"].isna().all()
|
|
123
|
+
):
|
|
124
|
+
display_df = display_df.drop(columns=["error"])
|
|
125
|
+
if "row" in display_df.columns:
|
|
126
|
+
display_df = display_df.drop(columns=["row"])
|
|
127
|
+
|
|
128
|
+
if len(display_df) > 1:
|
|
129
|
+
logger.info(f"\nResults:\n{display_df[0:10].to_string()}\n...")
|
|
130
|
+
else:
|
|
131
|
+
# Print the first row with column_name: value one per line
|
|
132
|
+
fmt_str = []
|
|
133
|
+
for col, val in display_df.iloc[0].items():
|
|
134
|
+
fmt_str.append(f"{col}: {val}")
|
|
135
|
+
fmt_str = "\n".join(fmt_str)
|
|
136
|
+
logger.info(f"\nResults:\n{fmt_str}")
|
|
137
|
+
|
|
138
|
+
# Save the output
|
|
139
|
+
if args.output:
|
|
140
|
+
# Save full DataFrame with row and error columns
|
|
141
|
+
result_df.to_parquet(args.output)
|
|
142
|
+
logger.info(f"Output saved to {args.output}")
|
|
95
143
|
else:
|
|
96
144
|
logger.info("Flow completed with no output")
|
|
97
145
|
|
|
98
|
-
# save the output
|
|
99
|
-
if isinstance(result, pd.DataFrame) and args.output:
|
|
100
|
-
result.to_parquet(args.output)
|
|
101
|
-
logger.info(f"Output DataFrame saved to {args.output}")
|
|
102
|
-
elif args.output:
|
|
103
|
-
with open(args.output, "w") as f:
|
|
104
|
-
json.dump(result, f, indent=2)
|
|
105
|
-
logger.info(f"Output saved to {args.output}")
|
|
106
|
-
|
|
107
146
|
except LoadError as e:
|
|
108
147
|
logger.error(f"❌ Failed to load document: {e}")
|
|
109
148
|
except ValidationError as e:
|
|
110
149
|
logger.error(f"❌ Validation failed: {e}")
|
|
111
150
|
except InterpreterError as e:
|
|
112
151
|
logger.error(f"❌ Execution failed: {e}")
|
|
113
|
-
except Exception as e:
|
|
114
|
-
logger.error(f"❌ Unexpected error: {e}", exc_info=True)
|
|
115
152
|
|
|
116
153
|
|
|
117
154
|
def parser(subparsers: argparse._SubParsersAction) -> None:
|
|
@@ -153,6 +190,11 @@ def parser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
153
190
|
default=None,
|
|
154
191
|
help="Path to save output data. If input is a DataFrame, output will be saved as parquet. If single result, saved as JSON.",
|
|
155
192
|
)
|
|
193
|
+
cmd_parser.add_argument(
|
|
194
|
+
"--progress",
|
|
195
|
+
action="store_true",
|
|
196
|
+
help="Show progress bars during flow execution.",
|
|
197
|
+
)
|
|
156
198
|
|
|
157
199
|
cmd_parser.add_argument(
|
|
158
200
|
"spec", type=str, help="Path to the QType YAML spec file."
|
qtype/commands/serve.py
CHANGED
|
@@ -6,61 +6,91 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import argparse
|
|
8
8
|
import logging
|
|
9
|
+
import os
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import Any
|
|
11
12
|
|
|
12
13
|
import uvicorn
|
|
13
14
|
|
|
14
|
-
from qtype.
|
|
15
|
-
from qtype.
|
|
15
|
+
from qtype.base.exceptions import ValidationError
|
|
16
|
+
from qtype.semantic.loader import load
|
|
17
|
+
from qtype.semantic.model import Application
|
|
16
18
|
|
|
17
19
|
logger = logging.getLogger(__name__)
|
|
18
20
|
|
|
19
21
|
|
|
20
|
-
def
|
|
22
|
+
def create_api_app() -> Any:
|
|
23
|
+
"""Factory function to create FastAPI app.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
FastAPI application instance.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
RuntimeError: If QTYPE_SPEC_PATH not set in environment.
|
|
30
|
+
ValidationError: If spec is not an Application document.
|
|
31
|
+
"""
|
|
32
|
+
from qtype.interpreter.api import APIExecutor
|
|
33
|
+
|
|
34
|
+
spec_path_str = os.environ["_QTYPE_SPEC_PATH"]
|
|
35
|
+
|
|
36
|
+
spec_path = Path(spec_path_str)
|
|
37
|
+
logger.info(f"Loading spec: {spec_path}")
|
|
38
|
+
|
|
39
|
+
semantic_model, _ = load(spec_path)
|
|
40
|
+
if not isinstance(semantic_model, Application):
|
|
41
|
+
raise ValidationError("Can only serve Application documents")
|
|
42
|
+
|
|
43
|
+
logger.info(f"✅ Successfully loaded spec: {spec_path}")
|
|
44
|
+
|
|
45
|
+
# Derive name from spec filename
|
|
46
|
+
name = spec_path.name.replace(".qtype.yaml", "").replace("_", " ").title()
|
|
47
|
+
|
|
48
|
+
# Get host/port from environment (set by uvicorn)
|
|
49
|
+
host = os.environ["_QTYPE_HOST"]
|
|
50
|
+
port = int(os.environ["_QTYPE_PORT"])
|
|
51
|
+
|
|
52
|
+
# Create server info for OpenAPI spec
|
|
53
|
+
servers = [
|
|
54
|
+
{
|
|
55
|
+
"url": f"http://{host}:{port}",
|
|
56
|
+
"description": "Development server",
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
api_executor = APIExecutor(semantic_model, host, port)
|
|
61
|
+
return api_executor.create_app(
|
|
62
|
+
name=name,
|
|
63
|
+
ui_enabled=True,
|
|
64
|
+
servers=servers,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def serve(args: argparse.Namespace) -> None:
|
|
21
69
|
"""Run a QType YAML spec file as an API.
|
|
22
70
|
|
|
23
71
|
Args:
|
|
24
|
-
args: Arguments passed from the command line
|
|
72
|
+
args: Arguments passed from the command line.
|
|
25
73
|
"""
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
fastapi_app = api_executor.create_app(
|
|
47
|
-
name=name, ui_enabled=not args.disable_ui
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
# Start the server
|
|
51
|
-
uvicorn.run(
|
|
52
|
-
fastapi_app, host=args.host, port=args.port, log_level="info"
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
except LoadError as e:
|
|
56
|
-
logger.error(f"❌ Failed to load document: {e}")
|
|
57
|
-
exit(1)
|
|
58
|
-
except ValidationError as e:
|
|
59
|
-
logger.error(f"❌ Validation failed: {e}")
|
|
60
|
-
exit(1)
|
|
61
|
-
except Exception as e:
|
|
62
|
-
logger.error(f"❌ Unexpected error starting server: {e}")
|
|
63
|
-
exit(1)
|
|
74
|
+
# Set environment variables for factory function
|
|
75
|
+
os.environ["_QTYPE_SPEC_PATH"] = args.spec
|
|
76
|
+
os.environ["_QTYPE_HOST"] = args.host
|
|
77
|
+
os.environ["_QTYPE_PORT"] = str(args.port)
|
|
78
|
+
|
|
79
|
+
logger.info(
|
|
80
|
+
f"Starting server on {args.host}:{args.port}"
|
|
81
|
+
f"{' (reload enabled)' if args.reload else ''}"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Use factory mode with import string
|
|
85
|
+
uvicorn.run(
|
|
86
|
+
"qtype.commands.serve:create_api_app",
|
|
87
|
+
factory=True,
|
|
88
|
+
host=args.host,
|
|
89
|
+
port=args.port,
|
|
90
|
+
log_level="info",
|
|
91
|
+
reload=args.reload,
|
|
92
|
+
reload_includes=[args.spec] if args.reload else None,
|
|
93
|
+
)
|
|
64
94
|
|
|
65
95
|
|
|
66
96
|
def parser(subparsers: argparse._SubParsersAction) -> None:
|
|
@@ -76,9 +106,9 @@ def parser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
76
106
|
cmd_parser.add_argument("-p", "--port", type=int, default=8000)
|
|
77
107
|
cmd_parser.add_argument("-H", "--host", type=str, default="localhost")
|
|
78
108
|
cmd_parser.add_argument(
|
|
79
|
-
"--
|
|
109
|
+
"--reload",
|
|
80
110
|
action="store_true",
|
|
81
|
-
help="
|
|
111
|
+
help="Enable auto-reload on code changes (default: False).",
|
|
82
112
|
)
|
|
83
113
|
cmd_parser.set_defaults(func=serve)
|
|
84
114
|
|
qtype/commands/validate.py
CHANGED
|
@@ -10,8 +10,12 @@ import sys
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
|
-
from qtype.application.facade import QTypeFacade
|
|
14
13
|
from qtype.base.exceptions import LoadError, SemanticError, ValidationError
|
|
14
|
+
from qtype.dsl.linker import DuplicateComponentError, ReferenceNotFoundError
|
|
15
|
+
from qtype.dsl.loader import YAMLLoadError
|
|
16
|
+
from qtype.dsl.model import Application as DSLApplication
|
|
17
|
+
from qtype.dsl.model import Document
|
|
18
|
+
from qtype.semantic.loader import load
|
|
15
19
|
|
|
16
20
|
logger = logging.getLogger(__name__)
|
|
17
21
|
|
|
@@ -26,33 +30,52 @@ def main(args: Any) -> None:
|
|
|
26
30
|
Exits:
|
|
27
31
|
Exits with code 1 if validation fails.
|
|
28
32
|
"""
|
|
29
|
-
facade = QTypeFacade()
|
|
30
33
|
spec_path = Path(args.spec)
|
|
31
34
|
|
|
32
35
|
try:
|
|
33
|
-
#
|
|
34
|
-
|
|
36
|
+
# Load and validate the document (works for all document types)
|
|
37
|
+
# This includes: YAML parsing, Pydantic validation, linking, and semantic checks
|
|
38
|
+
loaded_data, custom_types = load(spec_path)
|
|
35
39
|
logger.info("✅ Validation successful - document is valid.")
|
|
36
40
|
|
|
37
|
-
# If printing is requested, load and print the document
|
|
38
|
-
if args.print:
|
|
39
|
-
try:
|
|
40
|
-
print(loaded_data.model_dump_json(indent=2, exclude_none=True)) # type: ignore
|
|
41
|
-
except Exception as e:
|
|
42
|
-
logger.warning(f"Could not print document: {e}")
|
|
43
|
-
|
|
44
41
|
except LoadError as e:
|
|
45
42
|
logger.error(f"❌ Failed to load document: {e}")
|
|
46
43
|
sys.exit(1)
|
|
44
|
+
except YAMLLoadError as e:
|
|
45
|
+
# YAML syntax errors
|
|
46
|
+
logger.error(f"❌ {e}")
|
|
47
|
+
sys.exit(1)
|
|
48
|
+
except DuplicateComponentError as e:
|
|
49
|
+
# Duplicate ID errors during linking
|
|
50
|
+
logger.error(f"❌ {e}")
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
except ReferenceNotFoundError as e:
|
|
53
|
+
# Reference resolution errors during linking
|
|
54
|
+
logger.error(f"❌ {e}")
|
|
55
|
+
sys.exit(1)
|
|
56
|
+
except ValueError as e:
|
|
57
|
+
# Pydantic validation errors from parse_document
|
|
58
|
+
logger.error(f"❌ {e}")
|
|
59
|
+
sys.exit(1)
|
|
47
60
|
except ValidationError as e:
|
|
48
61
|
logger.error(f"❌ Validation failed: {e}")
|
|
49
62
|
sys.exit(1)
|
|
50
63
|
except SemanticError as e:
|
|
51
64
|
logger.error(f"❌ Semantic validation failed: {e}")
|
|
52
65
|
sys.exit(1)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
66
|
+
|
|
67
|
+
# If printing is requested, load and print the document
|
|
68
|
+
if args.print:
|
|
69
|
+
from pydantic_yaml import to_yaml_str
|
|
70
|
+
|
|
71
|
+
# Wrap in Document if it's a DSL Application
|
|
72
|
+
if isinstance(loaded_data, DSLApplication):
|
|
73
|
+
wrapped = Document(root=loaded_data)
|
|
74
|
+
else:
|
|
75
|
+
wrapped = loaded_data
|
|
76
|
+
logging.info(
|
|
77
|
+
to_yaml_str(wrapped, exclude_unset=True, exclude_none=True)
|
|
78
|
+
)
|
|
56
79
|
|
|
57
80
|
|
|
58
81
|
def parser(subparsers: argparse._SubParsersAction) -> None:
|
qtype/commands/visualize.py
CHANGED
|
@@ -6,13 +6,17 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import argparse
|
|
8
8
|
import logging
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
9
11
|
import tempfile
|
|
10
12
|
import webbrowser
|
|
11
13
|
from pathlib import Path
|
|
12
14
|
from typing import Any
|
|
13
15
|
|
|
14
|
-
from qtype.application.facade import QTypeFacade
|
|
15
16
|
from qtype.base.exceptions import LoadError, ValidationError
|
|
17
|
+
from qtype.semantic.loader import load
|
|
18
|
+
from qtype.semantic.model import Application
|
|
19
|
+
from qtype.semantic.visualize import visualize_application
|
|
16
20
|
|
|
17
21
|
logger = logging.getLogger(__name__)
|
|
18
22
|
|
|
@@ -27,12 +31,13 @@ def main(args: Any) -> None:
|
|
|
27
31
|
Exits:
|
|
28
32
|
Exits with code 1 if visualization fails.
|
|
29
33
|
"""
|
|
30
|
-
facade = QTypeFacade()
|
|
31
34
|
spec_path = Path(args.spec)
|
|
32
35
|
|
|
33
36
|
try:
|
|
34
|
-
#
|
|
35
|
-
|
|
37
|
+
# Load and generate visualization
|
|
38
|
+
semantic_model, _ = load(spec_path)
|
|
39
|
+
assert isinstance(semantic_model, Application)
|
|
40
|
+
mermaid_content = visualize_application(semantic_model)
|
|
36
41
|
|
|
37
42
|
if args.output:
|
|
38
43
|
# Write to file
|
|
@@ -41,26 +46,45 @@ def main(args: Any) -> None:
|
|
|
41
46
|
logger.info(f"✅ Visualization saved to {output_path}")
|
|
42
47
|
|
|
43
48
|
if not args.no_display:
|
|
44
|
-
#
|
|
49
|
+
# Check if mmdc is available
|
|
50
|
+
if shutil.which("mmdc") is None:
|
|
51
|
+
logger.error(
|
|
52
|
+
"❌ mmdc command not found. Please install mermaid-cli."
|
|
53
|
+
)
|
|
54
|
+
logger.info(
|
|
55
|
+
"Install with: npm install -g @mermaid-js/mermaid-cli"
|
|
56
|
+
)
|
|
57
|
+
exit(1)
|
|
58
|
+
|
|
59
|
+
# Create temporary directory and run mmdc from within it
|
|
45
60
|
try:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
# Create a temporary directory
|
|
62
|
+
temp_dir = tempfile.mkdtemp()
|
|
63
|
+
temp_dir_path = Path(temp_dir)
|
|
64
|
+
|
|
65
|
+
# Write mermaid file with simple names in the temp directory
|
|
66
|
+
mmd_file_path = temp_dir_path / "diagram.mmd"
|
|
67
|
+
svg_file_path = temp_dir_path / "diagram.svg"
|
|
68
|
+
|
|
69
|
+
mmd_file_path.write_text(mermaid_content, encoding="utf-8")
|
|
70
|
+
|
|
71
|
+
# Run mmdc from within the temporary directory
|
|
72
|
+
subprocess.run(
|
|
73
|
+
["mmdc", "-i", "diagram.mmd", "-o", "diagram.svg"],
|
|
74
|
+
cwd=temp_dir,
|
|
75
|
+
capture_output=True,
|
|
76
|
+
text=True,
|
|
77
|
+
check=True,
|
|
62
78
|
)
|
|
63
|
-
|
|
79
|
+
|
|
80
|
+
logger.info(
|
|
81
|
+
f"Opening visualization in browser: {svg_file_path}"
|
|
82
|
+
)
|
|
83
|
+
webbrowser.open(f"file://{svg_file_path}")
|
|
84
|
+
|
|
85
|
+
except subprocess.CalledProcessError as e:
|
|
86
|
+
logger.error(f"❌ Failed to generate SVG: {e.stderr}")
|
|
87
|
+
exit(1)
|
|
64
88
|
|
|
65
89
|
except LoadError as e:
|
|
66
90
|
logger.error(f"❌ Failed to load document: {e}")
|
|
@@ -68,9 +92,6 @@ def main(args: Any) -> None:
|
|
|
68
92
|
except ValidationError as e:
|
|
69
93
|
logger.error(f"❌ Visualization failed: {e}")
|
|
70
94
|
exit(1)
|
|
71
|
-
except Exception as e:
|
|
72
|
-
logger.error(f"❌ Unexpected error: {e}")
|
|
73
|
-
exit(1)
|
|
74
95
|
|
|
75
96
|
|
|
76
97
|
def parser(subparsers: argparse._SubParsersAction) -> None:
|
qtype/dsl/__init__.py
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
from .
|
|
5
|
+
# Import all public symbols from submodules
|
|
6
|
+
# The star import is intentional here - it's controlled by __all__ in each module
|
|
7
|
+
from qtype.base.types import Reference # noqa: F401
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
from .domain_types import * # noqa: F403, F401
|
|
10
|
+
from .loader import YAMLLoadError # noqa: F401
|
|
11
|
+
from .model import * # noqa: F403, F401
|
qtype/dsl/custom_types.py
CHANGED
|
@@ -2,7 +2,7 @@ from typing import Any, ForwardRef, Type, Union
|
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel, create_model
|
|
4
4
|
|
|
5
|
-
from qtype.
|
|
5
|
+
from qtype.dsl.types import PRIMITIVE_TO_PYTHON_TYPE
|
|
6
6
|
|
|
7
7
|
# --- This would be in your interpreter's logic ---
|
|
8
8
|
|
qtype/dsl/domain_types.py
CHANGED
|
@@ -5,7 +5,7 @@ from typing import Any
|
|
|
5
5
|
|
|
6
6
|
from pydantic import Field
|
|
7
7
|
|
|
8
|
-
from qtype.
|
|
8
|
+
from qtype.base.types import PrimitiveTypeEnum, StrictBaseModel
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class Embedding(StrictBaseModel):
|
|
@@ -14,11 +14,12 @@ class Embedding(StrictBaseModel):
|
|
|
14
14
|
vector: list[float] = Field(
|
|
15
15
|
..., description="The vector representation of the embedding."
|
|
16
16
|
)
|
|
17
|
-
|
|
18
|
-
None, description="The original
|
|
17
|
+
content: Any | None = Field(
|
|
18
|
+
None, description="The original content that was embedded."
|
|
19
19
|
)
|
|
20
|
-
metadata: dict[str,
|
|
21
|
-
|
|
20
|
+
metadata: dict[str, Any] = Field(
|
|
21
|
+
default_factory=dict,
|
|
22
|
+
description="Metadata associated with the embedding.",
|
|
22
23
|
)
|
|
23
24
|
|
|
24
25
|
|
|
@@ -57,3 +58,83 @@ class ChatMessage(StrictBaseModel):
|
|
|
57
58
|
...,
|
|
58
59
|
description="The content blocks of the chat message, which can include text, images, or other media.",
|
|
59
60
|
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class RAGDocument(StrictBaseModel):
|
|
64
|
+
"""A standard, built-in representation of a document used in Retrieval-Augmented Generation (RAG)."""
|
|
65
|
+
|
|
66
|
+
content: Any = Field(..., description="The main content of the document.")
|
|
67
|
+
file_id: str = Field(..., description="An unique identifier for the file.")
|
|
68
|
+
file_name: str = Field(..., description="The name of the file.")
|
|
69
|
+
uri: str | None = Field(
|
|
70
|
+
None, description="The URI where the document can be found."
|
|
71
|
+
)
|
|
72
|
+
metadata: dict[str, Any] = Field(
|
|
73
|
+
default_factory=dict,
|
|
74
|
+
description="Metadata associated with the document.",
|
|
75
|
+
)
|
|
76
|
+
type: PrimitiveTypeEnum = Field(
|
|
77
|
+
...,
|
|
78
|
+
description="The type of the document content (e.g., 'text', 'image').",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class RAGChunk(Embedding):
|
|
83
|
+
"""A standard, built-in representation of a chunk of a document used in Retrieval-Augmented Generation (RAG)."""
|
|
84
|
+
|
|
85
|
+
chunk_id: str = Field(
|
|
86
|
+
..., description="An unique identifier for the chunk."
|
|
87
|
+
)
|
|
88
|
+
document_id: str = Field(
|
|
89
|
+
..., description="The identifier of the parent document."
|
|
90
|
+
)
|
|
91
|
+
vector: list[float] | None = Field(
|
|
92
|
+
None, description="Optional vector embedding for the chunk."
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class SearchResult(StrictBaseModel):
|
|
97
|
+
"""A standard, built-in representation of a search result."""
|
|
98
|
+
|
|
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(
|
|
117
|
+
..., description="The RAG chunk returned as a search result."
|
|
118
|
+
)
|
|
119
|
+
doc_id: str = Field(
|
|
120
|
+
...,
|
|
121
|
+
description="The document ID (duplicated from content.document_id).",
|
|
122
|
+
)
|
|
123
|
+
score: float = Field(
|
|
124
|
+
...,
|
|
125
|
+
description="The similarity score of the chunk with respect to the query.",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class AggregateStats(StrictBaseModel):
|
|
130
|
+
"""A standard, built-in representation of aggregate statistics."""
|
|
131
|
+
|
|
132
|
+
num_successful: int = Field(
|
|
133
|
+
..., description="The count of successful messages processed."
|
|
134
|
+
)
|
|
135
|
+
num_failed: int = Field(
|
|
136
|
+
..., description="The count of failed messages processed."
|
|
137
|
+
)
|
|
138
|
+
num_total: int = Field(
|
|
139
|
+
..., description="The total count of messages processed."
|
|
140
|
+
)
|