qtype 0.1.0__tar.gz → 0.1.1__tar.gz

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 (140) hide show
  1. {qtype-0.1.0/qtype.egg-info → qtype-0.1.1}/PKG-INFO +1 -1
  2. {qtype-0.1.0 → qtype-0.1.1}/pyproject.toml +1 -1
  3. {qtype-0.1.0 → qtype-0.1.1}/qtype/application/facade.py +2 -2
  4. {qtype-0.1.0 → qtype-0.1.1}/qtype/cli.py +4 -0
  5. {qtype-0.1.0 → qtype-0.1.1}/qtype/commands/run.py +22 -3
  6. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/base/base_step_executor.py +8 -1
  7. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/base/progress_tracker.py +35 -0
  8. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/base/step_cache.py +3 -2
  9. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/conversions.py +19 -13
  10. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/converters.py +5 -1
  11. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/executors/document_embedder_executor.py +36 -4
  12. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/executors/document_splitter_executor.py +1 -1
  13. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/executors/index_upsert_executor.py +2 -2
  14. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/executors/invoke_embedding_executor.py +2 -2
  15. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/executors/invoke_tool_executor.py +6 -1
  16. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/flow.py +13 -1
  17. qtype-0.1.1/qtype/interpreter/rich_progress.py +225 -0
  18. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/types.py +2 -0
  19. {qtype-0.1.0 → qtype-0.1.1}/qtype/semantic/resolver.py +4 -2
  20. {qtype-0.1.0 → qtype-0.1.1/qtype.egg-info}/PKG-INFO +1 -1
  21. {qtype-0.1.0 → qtype-0.1.1}/qtype.egg-info/SOURCES.txt +1 -0
  22. {qtype-0.1.0 → qtype-0.1.1}/LICENSE +0 -0
  23. {qtype-0.1.0 → qtype-0.1.1}/README.md +0 -0
  24. {qtype-0.1.0 → qtype-0.1.1}/qtype/__init__.py +0 -0
  25. {qtype-0.1.0 → qtype-0.1.1}/qtype/application/__init__.py +0 -0
  26. {qtype-0.1.0 → qtype-0.1.1}/qtype/application/commons/__init__.py +0 -0
  27. {qtype-0.1.0 → qtype-0.1.1}/qtype/application/commons/tools.py +0 -0
  28. {qtype-0.1.0 → qtype-0.1.1}/qtype/application/converters/__init__.py +0 -0
  29. {qtype-0.1.0 → qtype-0.1.1}/qtype/application/converters/tools_from_api.py +0 -0
  30. {qtype-0.1.0 → qtype-0.1.1}/qtype/application/converters/tools_from_module.py +0 -0
  31. {qtype-0.1.0 → qtype-0.1.1}/qtype/application/converters/types.py +0 -0
  32. {qtype-0.1.0 → qtype-0.1.1}/qtype/application/documentation.py +0 -0
  33. {qtype-0.1.0 → qtype-0.1.1}/qtype/base/__init__.py +0 -0
  34. {qtype-0.1.0 → qtype-0.1.1}/qtype/base/exceptions.py +0 -0
  35. {qtype-0.1.0 → qtype-0.1.1}/qtype/base/logging.py +0 -0
  36. {qtype-0.1.0 → qtype-0.1.1}/qtype/base/types.py +0 -0
  37. {qtype-0.1.0 → qtype-0.1.1}/qtype/commands/__init__.py +0 -0
  38. {qtype-0.1.0 → qtype-0.1.1}/qtype/commands/convert.py +0 -0
  39. {qtype-0.1.0 → qtype-0.1.1}/qtype/commands/generate.py +0 -0
  40. {qtype-0.1.0 → qtype-0.1.1}/qtype/commands/serve.py +0 -0
  41. {qtype-0.1.0 → qtype-0.1.1}/qtype/commands/validate.py +0 -0
  42. {qtype-0.1.0 → qtype-0.1.1}/qtype/commands/visualize.py +0 -0
  43. {qtype-0.1.0 → qtype-0.1.1}/qtype/dsl/__init__.py +0 -0
  44. {qtype-0.1.0 → qtype-0.1.1}/qtype/dsl/custom_types.py +0 -0
  45. {qtype-0.1.0 → qtype-0.1.1}/qtype/dsl/domain_types.py +0 -0
  46. {qtype-0.1.0 → qtype-0.1.1}/qtype/dsl/linker.py +0 -0
  47. {qtype-0.1.0 → qtype-0.1.1}/qtype/dsl/loader.py +0 -0
  48. {qtype-0.1.0 → qtype-0.1.1}/qtype/dsl/model.py +0 -0
  49. {qtype-0.1.0 → qtype-0.1.1}/qtype/dsl/parser.py +0 -0
  50. {qtype-0.1.0 → qtype-0.1.1}/qtype/dsl/types.py +0 -0
  51. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/__init__.py +0 -0
  52. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/api.py +0 -0
  53. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/auth/__init__.py +0 -0
  54. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/auth/aws.py +0 -0
  55. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/auth/cache.py +0 -0
  56. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/auth/generic.py +0 -0
  57. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/base/batch_step_executor.py +0 -0
  58. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/base/exceptions.py +0 -0
  59. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/base/executor_context.py +0 -0
  60. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/base/factory.py +0 -0
  61. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/base/secrets.py +0 -0
  62. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/base/stream_emitter.py +0 -0
  63. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/endpoints.py +0 -0
  64. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/executors/agent_executor.py +0 -0
  65. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/executors/aggregate_executor.py +0 -0
  66. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/executors/decoder_executor.py +0 -0
  67. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/executors/doc_to_text_executor.py +0 -0
  68. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/executors/document_search_executor.py +0 -0
  69. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/executors/document_source_executor.py +0 -0
  70. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/executors/echo_executor.py +0 -0
  71. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/executors/field_extractor_executor.py +0 -0
  72. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/executors/file_source_executor.py +0 -0
  73. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/executors/file_writer_executor.py +0 -0
  74. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/executors/invoke_flow_executor.py +0 -0
  75. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/executors/llm_inference_executor.py +0 -0
  76. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/executors/prompt_template_executor.py +0 -0
  77. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/executors/sql_source_executor.py +0 -0
  78. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/executors/vector_search_executor.py +0 -0
  79. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/metadata_api.py +0 -0
  80. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/resource_cache.py +0 -0
  81. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/stream/chat/__init__.py +0 -0
  82. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/stream/chat/converter.py +0 -0
  83. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/stream/chat/file_conversions.py +0 -0
  84. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/stream/chat/ui_request_to_domain_type.py +0 -0
  85. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/stream/chat/vercel.py +0 -0
  86. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/stream/utils/__init__.py +0 -0
  87. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/stream/utils/build_vercel_ai_formatter.py +0 -0
  88. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/stream/utils/callback_to_stream.py +0 -0
  89. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/stream/utils/create_streaming_response.py +0 -0
  90. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/stream/utils/default_chat_extract_text.py +0 -0
  91. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/stream/utils/error_streaming_response.py +0 -0
  92. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/telemetry.py +0 -0
  93. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/tools/__init__.py +0 -0
  94. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/tools/function_tool_helper.py +0 -0
  95. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/typing.py +0 -0
  96. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/404/index.html +0 -0
  97. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/404.html +0 -0
  98. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/20HoJN6otZ_LyHLHpCPE6/_buildManifest.js +0 -0
  99. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/20HoJN6otZ_LyHLHpCPE6/_ssgManifest.js +0 -0
  100. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/chunks/434-b2112d19f25c44ff.js +0 -0
  101. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/chunks/4bd1b696-cf72ae8a39fa05aa.js +0 -0
  102. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/chunks/964-2b041321a01cbf56.js +0 -0
  103. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/chunks/app/_not-found/page-e110d2a9d0a83d82.js +0 -0
  104. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/chunks/app/layout-a05273ead5de2c41.js +0 -0
  105. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/chunks/app/page-8c67d16ac90d23cb.js +0 -0
  106. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/chunks/ba12c10f-546f2714ff8abc66.js +0 -0
  107. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/chunks/framework-7c95b8e5103c9e90.js +0 -0
  108. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/chunks/main-app-6fc6346bc8f7f163.js +0 -0
  109. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/chunks/main-e26b9cb206da2cac.js +0 -0
  110. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/chunks/pages/_app-0a0020ddd67f79cf.js +0 -0
  111. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/chunks/pages/_error-03529f2c21436739.js +0 -0
  112. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -0
  113. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/chunks/webpack-08642e441b39b6c2.js +0 -0
  114. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/css/8a8d1269e362fef7.css +0 -0
  115. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
  116. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/media/747892c23ea88013-s.woff2 +0 -0
  117. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/media/8d697b304b401681-s.woff2 +0 -0
  118. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
  119. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/media/9610d9e46709d722-s.woff2 +0 -0
  120. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/_next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
  121. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/file.svg +0 -0
  122. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/globe.svg +0 -0
  123. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/icon.png +0 -0
  124. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/index.html +0 -0
  125. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/index.txt +0 -0
  126. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/next.svg +0 -0
  127. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/vercel.svg +0 -0
  128. {qtype-0.1.0 → qtype-0.1.1}/qtype/interpreter/ui/window.svg +0 -0
  129. {qtype-0.1.0 → qtype-0.1.1}/qtype/semantic/__init__.py +0 -0
  130. {qtype-0.1.0 → qtype-0.1.1}/qtype/semantic/base_types.py +0 -0
  131. {qtype-0.1.0 → qtype-0.1.1}/qtype/semantic/checker.py +0 -0
  132. {qtype-0.1.0 → qtype-0.1.1}/qtype/semantic/generate.py +0 -0
  133. {qtype-0.1.0 → qtype-0.1.1}/qtype/semantic/loader.py +0 -0
  134. {qtype-0.1.0 → qtype-0.1.1}/qtype/semantic/model.py +0 -0
  135. {qtype-0.1.0 → qtype-0.1.1}/qtype/semantic/visualize.py +0 -0
  136. {qtype-0.1.0 → qtype-0.1.1}/qtype.egg-info/dependency_links.txt +0 -0
  137. {qtype-0.1.0 → qtype-0.1.1}/qtype.egg-info/entry_points.txt +0 -0
  138. {qtype-0.1.0 → qtype-0.1.1}/qtype.egg-info/requires.txt +0 -0
  139. {qtype-0.1.0 → qtype-0.1.1}/qtype.egg-info/top_level.txt +0 -0
  140. {qtype-0.1.0 → qtype-0.1.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qtype
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: DSL for Generative AI Prototyping
5
5
  Author-email: Lou Kratz <lou.kratz+qtype@bazaarvoice.com>
6
6
  License-Expression: Apache-2.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "qtype"
3
- version = "0.1.0"
3
+ version = "0.1.1"
4
4
  description = "DSL for Generative AI Prototyping"
5
5
  authors = [{ name="Lou Kratz", email="lou.kratz+qtype@bazaarvoice.com" }]
6
6
  readme = "README.md"
@@ -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 = get_logger("application.facade")
17
+ logger = logging.getLogger(__name__)
18
18
 
19
19
 
20
20
  class QTypeFacade:
@@ -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:
@@ -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"]:
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.
@@ -87,7 +98,10 @@ def run_flow(args: Any) -> None:
87
98
  # Execute the workflow using the facade (now async, returns DataFrame)
88
99
  result_df = asyncio.run(
89
100
  facade.execute_workflow(
90
- spec_path, flow_name=args.flow, inputs=input
101
+ spec_path,
102
+ flow_name=args.flow,
103
+ inputs=input,
104
+ show_progress=args.progress,
91
105
  )
92
106
  )
93
107
 
@@ -95,7 +109,7 @@ def run_flow(args: Any) -> None:
95
109
 
96
110
  # Display results
97
111
  if len(result_df) > 0:
98
- logger.info(f"Processed {len(result_df)} input(s)")
112
+ logger.info(f"Processed {len(result_df)} em")
99
113
 
100
114
  # Remove 'row' and 'error' columns for display if all errors are None
101
115
  display_df = result_df.copy()
@@ -108,7 +122,7 @@ def run_flow(args: Any) -> None:
108
122
  display_df = display_df.drop(columns=["row"])
109
123
 
110
124
  if len(display_df) > 1:
111
- logger.info(f"\nResults:\n{display_df.to_string()}")
125
+ logger.info(f"\nResults:\n{display_df[0:10].to_string()}\n...")
112
126
  else:
113
127
  # Print the first row with column_name: value one per line
114
128
  fmt_str = []
@@ -172,6 +186,11 @@ def parser(subparsers: argparse._SubParsersAction) -> None:
172
186
  default=None,
173
187
  help="Path to save output data. If input is a DataFrame, output will be saved as parquet. If single result, saved as JSON.",
174
188
  )
189
+ cmd_parser.add_argument(
190
+ "--progress",
191
+ action="store_true",
192
+ help="Show progress bars during flow execution.",
193
+ )
175
194
 
176
195
  cmd_parser.add_argument(
177
196
  "spec", type=str, help="Path to the QType YAML spec file."
@@ -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)
@@ -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 openai import BaseModel
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
 
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import importlib
4
+ import uuid
4
5
  from typing import Any
5
6
 
6
7
  from llama_index.core.base.embeddings.base import BaseEmbedding
@@ -305,7 +306,8 @@ def to_embedding_model(model: Model) -> BaseEmbedding:
305
306
  )
306
307
 
307
308
  bedrock_embedding: BaseEmbedding = BedrockEmbedding(
308
- model_name=model.model_id if model.model_id else model.id
309
+ model_name=model.model_id if model.model_id else model.id,
310
+ max_retries=100,
309
311
  )
310
312
  return bedrock_embedding
311
313
  elif model.provider == "openai":
@@ -506,26 +508,30 @@ def to_text_splitter(splitter: DocumentSplitter) -> Any:
506
508
  Raises:
507
509
  InterpreterError: If the splitter class cannot be found or instantiated.
508
510
  """
509
- from llama_index.core.node_parser import SentenceSplitter
510
511
 
511
- # Map common splitter names to their classes
512
- splitter_classes = {
513
- "SentenceSplitter": SentenceSplitter,
514
- }
512
+ module_path = "llama_index.core.node_parser"
513
+ class_name = splitter.splitter_name
514
+ try:
515
+ reader_module = importlib.import_module(module_path)
516
+ splitter_class = getattr(reader_module, class_name)
517
+ except (ImportError, AttributeError) as e:
518
+ raise ImportError(
519
+ f"Failed to import reader class '{class_name}' from '{module_path}': {e}"
520
+ ) from e
521
+ from llama_index.core.schema import BaseNode
515
522
 
516
- # Get the splitter class
517
- splitter_class = splitter_classes.get(splitter.splitter_name)
523
+ # TODO: let the user specify a custom ID namespace
524
+ namespace = uuid.UUID("12345678-1234-5678-1234-567812345678")
518
525
 
519
- if splitter_class is None:
520
- raise InterpreterError(
521
- f"Unsupported text splitter: {splitter.splitter_name}. "
522
- f"Supported splitters: {', '.join(splitter_classes.keys())}"
523
- )
526
+ def id_func(i: int, doc: BaseNode) -> str:
527
+ u = uuid.uuid5(namespace, f"{doc.node_id}_{i}")
528
+ return str(u)
524
529
 
525
530
  # Prepare arguments for the splitter
526
531
  splitter_args = {
527
532
  "chunk_size": splitter.chunk_size,
528
533
  "chunk_overlap": splitter.chunk_overlap,
534
+ "id_func": id_func,
529
535
  **splitter.args,
530
536
  }
531
537
 
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import pandas as pd
6
+ from pydantic import BaseModel
6
7
 
7
8
  from qtype.interpreter.types import FlowMessage, Session
8
9
  from qtype.semantic.model import Flow
@@ -54,7 +55,10 @@ def flow_messages_to_dataframe(
54
55
  # Extract output variables
55
56
  for var in flow.outputs:
56
57
  if var.id in message.variables:
57
- row_data[var.id] = message.variables[var.id]
58
+ value = message.variables[var.id]
59
+ if isinstance(value, BaseModel):
60
+ value = value.model_dump()
61
+ row_data[var.id] = value
58
62
  else:
59
63
  row_data[var.id] = None
60
64
 
@@ -1,5 +1,14 @@
1
1
  from typing import AsyncIterator
2
2
 
3
+ from botocore.exceptions import ClientError
4
+ from llama_index.core.base.embeddings.base import BaseEmbedding
5
+ from tenacity import (
6
+ retry,
7
+ retry_if_exception,
8
+ stop_after_attempt,
9
+ wait_exponential,
10
+ )
11
+
3
12
  from qtype.dsl.domain_types import RAGChunk
4
13
  from qtype.interpreter.base.base_step_executor import StepExecutor
5
14
  from qtype.interpreter.base.executor_context import ExecutorContext
@@ -8,6 +17,13 @@ from qtype.interpreter.types import FlowMessage
8
17
  from qtype.semantic.model import DocumentEmbedder
9
18
 
10
19
 
20
+ def is_throttling_error(e):
21
+ return (
22
+ isinstance(e, ClientError)
23
+ and e.response["Error"]["Code"] == "ThrottlingException"
24
+ )
25
+
26
+
11
27
  class DocumentEmbedderExecutor(StepExecutor):
12
28
  """Executor for DocumentEmbedder steps."""
13
29
 
@@ -24,7 +40,25 @@ class DocumentEmbedderExecutor(StepExecutor):
24
40
  )
25
41
  self.step: DocumentEmbedder = step
26
42
  # Initialize the embedding model once for the executor
27
- self.embedding_model = to_embedding_model(self.step.model)
43
+ self.embedding_model: BaseEmbedding = to_embedding_model(
44
+ self.step.model
45
+ )
46
+
47
+ # TODO: properly abstract this into a mixin
48
+ @retry(
49
+ retry=retry_if_exception(is_throttling_error),
50
+ wait=wait_exponential(multiplier=0.5, min=1, max=30),
51
+ stop=stop_after_attempt(10),
52
+ )
53
+ async def _embed(self, text: str) -> list[float]:
54
+ """Generate embedding for the given text using the embedding model.
55
+
56
+ Args:
57
+ text: The text to embed.
58
+ Returns:
59
+ The embedding vector as a list of floats.
60
+ """
61
+ return await self.embedding_model.aget_text_embedding(text=text)
28
62
 
29
63
  async def process_message(
30
64
  self,
@@ -52,9 +86,7 @@ class DocumentEmbedderExecutor(StepExecutor):
52
86
  )
53
87
 
54
88
  # Generate embedding for the chunk content
55
- vector = self.embedding_model.get_text_embedding(
56
- text=str(chunk.content)
57
- )
89
+ vector = await self._embed(str(chunk.content))
58
90
 
59
91
  # Create the output chunk with the vector
60
92
  embedded_chunk = RAGChunk(
@@ -72,7 +72,7 @@ class DocumentSplitterExecutor(StepExecutor):
72
72
  llama_doc = LlamaDocument(
73
73
  text=content_text,
74
74
  metadata=document.metadata or {},
75
- id_=document.file_id,
75
+ doc_id=document.file_id,
76
76
  )
77
77
 
78
78
  # Split the document using the LlamaIndex splitter
@@ -65,7 +65,7 @@ class IndexUpsertExecutor(BatchedStepExecutor):
65
65
  Yields:
66
66
  FlowMessages: Success messages after upserting to the index
67
67
  """
68
- logger.info(
68
+ logger.debug(
69
69
  f"Executing IndexUpsert step: {self.step.id} with batch size: {len(batch)}"
70
70
  )
71
71
 
@@ -102,7 +102,7 @@ class IndexUpsertExecutor(BatchedStepExecutor):
102
102
  else: # document index
103
103
  await self._upsert_to_document_index(items_to_upsert)
104
104
 
105
- logger.info(
105
+ logger.debug(
106
106
  f"Successfully upserted {len(items_to_upsert)} items "
107
107
  f"to {self.index_type} index in batch"
108
108
  )
@@ -58,13 +58,13 @@ class InvokeEmbeddingExecutor(StepExecutor):
58
58
  if input_type == PrimitiveTypeEnum.text:
59
59
  if not isinstance(input_value, str):
60
60
  input_value = str(input_value)
61
- vector = self.embedding_model.get_text_embedding(
61
+ vector = await self.embedding_model.aget_text_embedding(
62
62
  text=input_value
63
63
  )
64
64
  content = input_value
65
65
  elif input_type == PrimitiveTypeEnum.image:
66
66
  # For image embeddings
67
- vector = self.embedding_model.get_image_embedding(
67
+ vector = await self.embedding_model.aget_image_embedding(
68
68
  image_path=input_value
69
69
  )
70
70
  content = input_value
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import asyncio
3
4
  import importlib
5
+ import inspect
4
6
  import logging
5
7
  import time
6
8
  import uuid
@@ -86,7 +88,10 @@ class ToolExecutionMixin:
86
88
  )
87
89
  )
88
90
 
89
- result = function(**inputs)
91
+ if inspect.iscoroutinefunction(function):
92
+ result = await function(**inputs)
93
+ else:
94
+ result = await asyncio.to_thread(function, **inputs)
90
95
  await tool_ctx.complete(result)
91
96
  return result
92
97
 
@@ -12,6 +12,7 @@ from opentelemetry.trace import Status, StatusCode
12
12
 
13
13
  from qtype.interpreter.base import factory
14
14
  from qtype.interpreter.base.executor_context import ExecutorContext
15
+ from qtype.interpreter.rich_progress import RichProgressCallback
15
16
  from qtype.interpreter.types import FlowMessage
16
17
  from qtype.semantic.model import Flow
17
18
 
@@ -19,7 +20,10 @@ logger = logging.getLogger(__name__)
19
20
 
20
21
 
21
22
  async def run_flow(
22
- flow: Flow, initial: list[FlowMessage] | FlowMessage, **kwargs
23
+ flow: Flow,
24
+ initial: list[FlowMessage] | FlowMessage,
25
+ show_progress: bool = False,
26
+ **kwargs,
23
27
  ) -> list[FlowMessage]:
24
28
  """
25
29
  Main entrypoint for executing a flow.
@@ -38,11 +42,16 @@ async def run_flow(
38
42
 
39
43
  # Extract or create ExecutorContext
40
44
  exec_context = kwargs.pop("context", None)
45
+ progress_callback = RichProgressCallback() if show_progress else None
41
46
  if exec_context is None:
42
47
  exec_context = ExecutorContext(
43
48
  secret_manager=NoOpSecretManager(),
44
49
  tracer=trace.get_tracer(__name__),
50
+ on_progress=progress_callback,
45
51
  )
52
+ else:
53
+ if exec_context.on_progress is None and show_progress:
54
+ exec_context.on_progress = progress_callback
46
55
 
47
56
  # Use tracer from context
48
57
  tracer = exec_context.tracer or trace.get_tracer(__name__)
@@ -110,6 +119,9 @@ async def run_flow(
110
119
  # 4. Collect the final results from the last stream
111
120
  final_results = [state async for state in current_stream]
112
121
 
122
+ # Close the progress bars if any
123
+ if progress_callback is not None:
124
+ progress_callback.close()
113
125
  # Record flow completion metrics
114
126
  span.set_attribute("flow.output_count", len(final_results))
115
127
  error_count = sum(1 for msg in final_results if msg.is_failed())