qtype 0.0.12__py3-none-any.whl → 0.1.3__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 (137) hide show
  1. qtype/application/commons/tools.py +1 -1
  2. qtype/application/converters/tools_from_api.py +476 -11
  3. qtype/application/converters/tools_from_module.py +38 -14
  4. qtype/application/converters/types.py +15 -30
  5. qtype/application/documentation.py +1 -1
  6. qtype/application/facade.py +102 -85
  7. qtype/base/types.py +227 -7
  8. qtype/cli.py +5 -1
  9. qtype/commands/convert.py +52 -6
  10. qtype/commands/generate.py +44 -4
  11. qtype/commands/run.py +78 -36
  12. qtype/commands/serve.py +74 -44
  13. qtype/commands/validate.py +37 -14
  14. qtype/commands/visualize.py +46 -25
  15. qtype/dsl/__init__.py +6 -5
  16. qtype/dsl/custom_types.py +1 -1
  17. qtype/dsl/domain_types.py +86 -5
  18. qtype/dsl/linker.py +384 -0
  19. qtype/dsl/loader.py +315 -0
  20. qtype/dsl/model.py +751 -263
  21. qtype/dsl/parser.py +200 -0
  22. qtype/dsl/types.py +50 -0
  23. qtype/interpreter/api.py +63 -136
  24. qtype/interpreter/auth/aws.py +19 -9
  25. qtype/interpreter/auth/generic.py +93 -16
  26. qtype/interpreter/base/base_step_executor.py +436 -0
  27. qtype/interpreter/base/batch_step_executor.py +171 -0
  28. qtype/interpreter/base/exceptions.py +50 -0
  29. qtype/interpreter/base/executor_context.py +91 -0
  30. qtype/interpreter/base/factory.py +84 -0
  31. qtype/interpreter/base/progress_tracker.py +110 -0
  32. qtype/interpreter/base/secrets.py +339 -0
  33. qtype/interpreter/base/step_cache.py +74 -0
  34. qtype/interpreter/base/stream_emitter.py +469 -0
  35. qtype/interpreter/conversions.py +471 -22
  36. qtype/interpreter/converters.py +79 -0
  37. qtype/interpreter/endpoints.py +355 -0
  38. qtype/interpreter/executors/agent_executor.py +242 -0
  39. qtype/interpreter/executors/aggregate_executor.py +93 -0
  40. qtype/interpreter/executors/bedrock_reranker_executor.py +195 -0
  41. qtype/interpreter/executors/decoder_executor.py +163 -0
  42. qtype/interpreter/executors/doc_to_text_executor.py +112 -0
  43. qtype/interpreter/executors/document_embedder_executor.py +107 -0
  44. qtype/interpreter/executors/document_search_executor.py +113 -0
  45. qtype/interpreter/executors/document_source_executor.py +118 -0
  46. qtype/interpreter/executors/document_splitter_executor.py +105 -0
  47. qtype/interpreter/executors/echo_executor.py +63 -0
  48. qtype/interpreter/executors/field_extractor_executor.py +165 -0
  49. qtype/interpreter/executors/file_source_executor.py +101 -0
  50. qtype/interpreter/executors/file_writer_executor.py +110 -0
  51. qtype/interpreter/executors/index_upsert_executor.py +232 -0
  52. qtype/interpreter/executors/invoke_embedding_executor.py +92 -0
  53. qtype/interpreter/executors/invoke_flow_executor.py +51 -0
  54. qtype/interpreter/executors/invoke_tool_executor.py +358 -0
  55. qtype/interpreter/executors/llm_inference_executor.py +272 -0
  56. qtype/interpreter/executors/prompt_template_executor.py +78 -0
  57. qtype/interpreter/executors/sql_source_executor.py +106 -0
  58. qtype/interpreter/executors/vector_search_executor.py +91 -0
  59. qtype/interpreter/flow.py +173 -22
  60. qtype/interpreter/logging_progress.py +61 -0
  61. qtype/interpreter/metadata_api.py +115 -0
  62. qtype/interpreter/resource_cache.py +5 -4
  63. qtype/interpreter/rich_progress.py +225 -0
  64. qtype/interpreter/stream/chat/__init__.py +15 -0
  65. qtype/interpreter/stream/chat/converter.py +391 -0
  66. qtype/interpreter/{chat → stream/chat}/file_conversions.py +2 -2
  67. qtype/interpreter/stream/chat/ui_request_to_domain_type.py +140 -0
  68. qtype/interpreter/stream/chat/vercel.py +609 -0
  69. qtype/interpreter/stream/utils/__init__.py +15 -0
  70. qtype/interpreter/stream/utils/build_vercel_ai_formatter.py +74 -0
  71. qtype/interpreter/stream/utils/callback_to_stream.py +66 -0
  72. qtype/interpreter/stream/utils/create_streaming_response.py +18 -0
  73. qtype/interpreter/stream/utils/default_chat_extract_text.py +20 -0
  74. qtype/interpreter/stream/utils/error_streaming_response.py +20 -0
  75. qtype/interpreter/telemetry.py +135 -8
  76. qtype/interpreter/tools/__init__.py +5 -0
  77. qtype/interpreter/tools/function_tool_helper.py +265 -0
  78. qtype/interpreter/types.py +330 -0
  79. qtype/interpreter/typing.py +83 -89
  80. qtype/interpreter/ui/404/index.html +1 -1
  81. qtype/interpreter/ui/404.html +1 -1
  82. qtype/interpreter/ui/_next/static/{OT8QJQW3J70VbDWWfrEMT → 20HoJN6otZ_LyHLHpCPE6}/_buildManifest.js +1 -1
  83. qtype/interpreter/ui/_next/static/chunks/434-b2112d19f25c44ff.js +36 -0
  84. qtype/interpreter/ui/_next/static/chunks/{964-ed4ab073db645007.js → 964-2b041321a01cbf56.js} +1 -1
  85. qtype/interpreter/ui/_next/static/chunks/app/{layout-5ccbc44fd528d089.js → layout-a05273ead5de2c41.js} +1 -1
  86. qtype/interpreter/ui/_next/static/chunks/app/page-8c67d16ac90d23cb.js +1 -0
  87. qtype/interpreter/ui/_next/static/chunks/ba12c10f-546f2714ff8abc66.js +1 -0
  88. qtype/interpreter/ui/_next/static/chunks/{main-6d261b6c5d6fb6c2.js → main-e26b9cb206da2cac.js} +1 -1
  89. qtype/interpreter/ui/_next/static/chunks/webpack-08642e441b39b6c2.js +1 -0
  90. qtype/interpreter/ui/_next/static/css/8a8d1269e362fef7.css +3 -0
  91. qtype/interpreter/ui/_next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
  92. qtype/interpreter/ui/icon.png +0 -0
  93. qtype/interpreter/ui/index.html +1 -1
  94. qtype/interpreter/ui/index.txt +5 -5
  95. qtype/semantic/checker.py +643 -0
  96. qtype/semantic/generate.py +268 -85
  97. qtype/semantic/loader.py +95 -0
  98. qtype/semantic/model.py +535 -163
  99. qtype/semantic/resolver.py +63 -19
  100. qtype/semantic/visualize.py +50 -35
  101. {qtype-0.0.12.dist-info → qtype-0.1.3.dist-info}/METADATA +21 -4
  102. qtype-0.1.3.dist-info/RECORD +137 -0
  103. qtype/dsl/base_types.py +0 -38
  104. qtype/dsl/validator.py +0 -464
  105. qtype/interpreter/batch/__init__.py +0 -0
  106. qtype/interpreter/batch/flow.py +0 -95
  107. qtype/interpreter/batch/sql_source.py +0 -95
  108. qtype/interpreter/batch/step.py +0 -63
  109. qtype/interpreter/batch/types.py +0 -41
  110. qtype/interpreter/batch/utils.py +0 -179
  111. qtype/interpreter/chat/chat_api.py +0 -237
  112. qtype/interpreter/chat/vercel.py +0 -314
  113. qtype/interpreter/exceptions.py +0 -10
  114. qtype/interpreter/step.py +0 -67
  115. qtype/interpreter/steps/__init__.py +0 -0
  116. qtype/interpreter/steps/agent.py +0 -114
  117. qtype/interpreter/steps/condition.py +0 -36
  118. qtype/interpreter/steps/decoder.py +0 -88
  119. qtype/interpreter/steps/llm_inference.py +0 -150
  120. qtype/interpreter/steps/prompt_template.py +0 -54
  121. qtype/interpreter/steps/search.py +0 -24
  122. qtype/interpreter/steps/tool.py +0 -53
  123. qtype/interpreter/streaming_helpers.py +0 -123
  124. qtype/interpreter/ui/_next/static/chunks/736-7fc606e244fedcb1.js +0 -36
  125. qtype/interpreter/ui/_next/static/chunks/app/page-c72e847e888e549d.js +0 -1
  126. qtype/interpreter/ui/_next/static/chunks/ba12c10f-22556063851a6df2.js +0 -1
  127. qtype/interpreter/ui/_next/static/chunks/webpack-8289c17c67827f22.js +0 -1
  128. qtype/interpreter/ui/_next/static/css/a262c53826df929b.css +0 -3
  129. qtype/interpreter/ui/_next/static/media/569ce4b8f30dc480-s.p.woff2 +0 -0
  130. qtype/interpreter/ui/favicon.ico +0 -0
  131. qtype/loader.py +0 -389
  132. qtype-0.0.12.dist-info/RECORD +0 -105
  133. /qtype/interpreter/ui/_next/static/{OT8QJQW3J70VbDWWfrEMT → 20HoJN6otZ_LyHLHpCPE6}/_ssgManifest.js +0 -0
  134. {qtype-0.0.12.dist-info → qtype-0.1.3.dist-info}/WHEEL +0 -0
  135. {qtype-0.0.12.dist-info → qtype-0.1.3.dist-info}/entry_points.txt +0 -0
  136. {qtype-0.0.12.dist-info → qtype-0.1.3.dist-info}/licenses/LICENSE +0 -0
  137. {qtype-0.0.12.dist-info → qtype-0.1.3.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
- return pd.read_csv(file_path)
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
- result = facade.execute_workflow(
70
- spec_path, flow_name=args.flow, inputs=input, batch_config=None
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
- # Print results
76
- if isinstance(result, pd.DataFrame):
77
- logging.info("Output DataFrame:")
78
- logging.info(result)
79
- elif (
80
- result
81
- and hasattr(result, "__iter__")
82
- and not isinstance(result, str)
83
- ):
84
- # If result is a list of variables or similar
85
- try:
86
- for item in result:
87
- if hasattr(item, "id") and hasattr(item, "value"):
88
- logger.info(f"Output {item.id}: {item.value}")
89
- else:
90
- logger.info(f"Result: {item}")
91
- except TypeError:
92
- logger.info(f"Result: {result}")
93
- elif isinstance(result, str):
94
- logger.info(f"Result: {result}")
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.application.facade import QTypeFacade
15
- from qtype.base.exceptions import LoadError, ValidationError
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 serve(args: Any) -> None:
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 or calling context.
72
+ args: Arguments passed from the command line.
25
73
  """
26
- facade = QTypeFacade()
27
-
28
- try:
29
- # Use facade to load and validate the document
30
- spec_path = Path(args.spec)
31
- logger.info(f"Loading and validating spec: {spec_path}")
32
-
33
- semantic_model, type_registry = facade.load_semantic_model(spec_path)
34
- logger.info(f"✅ Successfully loaded spec: {spec_path}")
35
-
36
- # Import APIExecutor and create the FastAPI app
37
- from qtype.interpreter.api import APIExecutor
38
-
39
- # Get the name from the spec filename
40
- name = (
41
- spec_path.name.replace(".qtype.yaml", "").replace("_", " ").title()
42
- )
43
-
44
- logger.info(f"Starting server for: {name}")
45
- api_executor = APIExecutor(semantic_model)
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
- "--disable-ui",
109
+ "--reload",
80
110
  action="store_true",
81
- help="Disable the UI for the QType application.",
111
+ help="Enable auto-reload on code changes (default: False).",
82
112
  )
83
113
  cmd_parser.set_defaults(func=serve)
84
114
 
@@ -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
- # Use the facade for validation - it will raise exceptions on errors
34
- loaded_data = facade.load_and_validate(spec_path)
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
- except Exception as e:
54
- logger.error(f"❌ Unexpected error during validation: {e}")
55
- sys.exit(1)
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:
@@ -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
- # Generate visualization using the facade
35
- mermaid_content = facade.visualize_application(spec_path)
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
- # Create temporary HTML file and open in browser
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
- import mermaid as md # type: ignore[import-untyped]
47
-
48
- mm = md.Mermaid(mermaid_content)
49
- html_content = mm._repr_html_()
50
-
51
- with tempfile.NamedTemporaryFile(
52
- mode="w", suffix=".html", delete=False, encoding="utf-8"
53
- ) as f:
54
- f.write(html_content)
55
- temp_file = f.name
56
-
57
- logger.info(f"Opening visualization in browser: {temp_file}")
58
- webbrowser.open(f"file://{temp_file}")
59
- except ImportError:
60
- logger.warning(
61
- "❌ Mermaid library not installed. Cannot display in browser."
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
- logger.info("Install with: pip install mermaid")
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
- from .base_types import * # noqa: F403
6
- from .domain_types import * # noqa: F403
7
- from .model import * # noqa: F403
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
- # Note: Validation logic has been moved to qtype.semantic package
10
- # to avoid circular dependencies
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.application.converters.types import PRIMITIVE_TO_PYTHON_TYPE
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.dsl.base_types import PrimitiveTypeEnum, StrictBaseModel
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
- source_text: str | None = Field(
18
- None, description="The original text that was embedded."
17
+ content: Any | None = Field(
18
+ None, description="The original content that was embedded."
19
19
  )
20
- metadata: dict[str, str] | None = Field(
21
- None, description="Optional metadata associated with the embedding."
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
+ )