qtype 0.0.10__py3-none-any.whl → 0.0.12__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 (61) hide show
  1. qtype/application/__init__.py +12 -0
  2. qtype/application/commons/__init__.py +7 -0
  3. qtype/{converters → application/converters}/tools_from_module.py +2 -2
  4. qtype/application/converters/types.py +33 -0
  5. qtype/{dsl/document.py → application/documentation.py} +2 -0
  6. qtype/application/facade.py +160 -0
  7. qtype/base/__init__.py +14 -0
  8. qtype/base/exceptions.py +49 -0
  9. qtype/base/logging.py +39 -0
  10. qtype/base/types.py +29 -0
  11. qtype/commands/convert.py +64 -49
  12. qtype/commands/generate.py +59 -4
  13. qtype/commands/run.py +109 -72
  14. qtype/commands/serve.py +42 -28
  15. qtype/commands/validate.py +25 -42
  16. qtype/commands/visualize.py +51 -37
  17. qtype/dsl/__init__.py +9 -0
  18. qtype/dsl/base_types.py +8 -0
  19. qtype/dsl/custom_types.py +6 -4
  20. qtype/dsl/model.py +185 -50
  21. qtype/dsl/validator.py +9 -4
  22. qtype/interpreter/api.py +96 -40
  23. qtype/interpreter/auth/__init__.py +3 -0
  24. qtype/interpreter/auth/aws.py +234 -0
  25. qtype/interpreter/auth/cache.py +67 -0
  26. qtype/interpreter/auth/generic.py +103 -0
  27. qtype/interpreter/batch/flow.py +95 -0
  28. qtype/interpreter/batch/sql_source.py +95 -0
  29. qtype/interpreter/batch/step.py +63 -0
  30. qtype/interpreter/batch/types.py +41 -0
  31. qtype/interpreter/batch/utils.py +179 -0
  32. qtype/interpreter/conversions.py +21 -10
  33. qtype/interpreter/resource_cache.py +4 -2
  34. qtype/interpreter/steps/decoder.py +13 -9
  35. qtype/interpreter/steps/llm_inference.py +7 -9
  36. qtype/interpreter/steps/prompt_template.py +1 -1
  37. qtype/interpreter/streaming_helpers.py +3 -3
  38. qtype/interpreter/typing.py +47 -11
  39. qtype/interpreter/ui/404/index.html +1 -1
  40. qtype/interpreter/ui/404.html +1 -1
  41. qtype/interpreter/ui/index.html +1 -1
  42. qtype/interpreter/ui/index.txt +1 -1
  43. qtype/loader.py +15 -16
  44. qtype/semantic/generate.py +91 -39
  45. qtype/semantic/model.py +183 -52
  46. qtype/semantic/resolver.py +4 -4
  47. {qtype-0.0.10.dist-info → qtype-0.0.12.dist-info}/METADATA +5 -1
  48. {qtype-0.0.10.dist-info → qtype-0.0.12.dist-info}/RECORD +58 -44
  49. qtype/commons/generate.py +0 -93
  50. qtype/converters/types.py +0 -66
  51. qtype/semantic/errors.py +0 -4
  52. /qtype/{commons → application/commons}/tools.py +0 -0
  53. /qtype/{commons → application/converters}/__init__.py +0 -0
  54. /qtype/{converters → application/converters}/tools_from_api.py +0 -0
  55. /qtype/{converters → interpreter/batch}/__init__.py +0 -0
  56. /qtype/interpreter/ui/_next/static/{Jb2murBlt2XkN6punrQbE → OT8QJQW3J70VbDWWfrEMT}/_buildManifest.js +0 -0
  57. /qtype/interpreter/ui/_next/static/{Jb2murBlt2XkN6punrQbE → OT8QJQW3J70VbDWWfrEMT}/_ssgManifest.js +0 -0
  58. {qtype-0.0.10.dist-info → qtype-0.0.12.dist-info}/WHEEL +0 -0
  59. {qtype-0.0.10.dist-info → qtype-0.0.12.dist-info}/entry_points.txt +0 -0
  60. {qtype-0.0.10.dist-info → qtype-0.0.12.dist-info}/licenses/LICENSE +0 -0
  61. {qtype-0.0.10.dist-info → qtype-0.0.12.dist-info}/top_level.txt +0 -0
qtype/dsl/model.py CHANGED
@@ -14,7 +14,11 @@ from pydantic import (
14
14
  )
15
15
 
16
16
  import qtype.dsl.domain_types as domain_types
17
- from qtype.dsl.base_types import PrimitiveTypeEnum, StrictBaseModel
17
+ from qtype.dsl.base_types import (
18
+ PrimitiveTypeEnum,
19
+ StepCardinality,
20
+ StrictBaseModel,
21
+ )
18
22
  from qtype.dsl.domain_types import ChatContent, ChatMessage, Embedding
19
23
 
20
24
 
@@ -116,7 +120,7 @@ class Model(StrictBaseModel):
116
120
  """Describes a generative model configuration, including provider and model ID."""
117
121
 
118
122
  id: str = Field(..., description="Unique ID for the model.")
119
- auth: AuthorizationProvider | str | None = Field(
123
+ auth: AuthProviderType | str | None = Field(
120
124
  default=None,
121
125
  description="AuthorizationProvider used for model access.",
122
126
  )
@@ -172,6 +176,10 @@ class Step(StrictBaseModel, ABC):
172
176
  """Base class for components that take inputs and produce outputs."""
173
177
 
174
178
  id: str = Field(..., description="Unique ID of this component.")
179
+ cardinality: StepCardinality = Field(
180
+ default=StepCardinality.one,
181
+ description="Does this step emit 1 (one) or 0...N (many) instances of the outputs?",
182
+ )
175
183
  inputs: list[Variable | str] | None = Field(
176
184
  default=None,
177
185
  description="Input variables required by this step.",
@@ -261,7 +269,7 @@ class APITool(Tool):
261
269
  default="GET",
262
270
  description="HTTP method to use (GET, POST, PUT, DELETE, etc.).",
263
271
  )
264
- auth: AuthorizationProvider | str | None = Field(
272
+ auth: AuthProviderType | str | None = Field(
265
273
  default=None,
266
274
  description="Optional AuthorizationProvider for API authentication.",
267
275
  )
@@ -315,6 +323,11 @@ class Flow(Step):
315
323
  default=None, description="Optional description of the flow."
316
324
  )
317
325
 
326
+ cardinality: StepCardinality = Field(
327
+ default=StepCardinality.auto,
328
+ description="The cardinality of the flow, inferred from its steps when set to 'auto'.",
329
+ )
330
+
318
331
  mode: Literal["Complete", "Chat"] = "Complete"
319
332
 
320
333
  steps: list[StepType | str] = Field(
@@ -368,34 +381,95 @@ class Decoder(Step):
368
381
  #
369
382
 
370
383
 
371
- class AuthorizationProvider(StrictBaseModel):
372
- """Defines how tools or providers authenticate with APIs, such as OAuth2 or API keys."""
384
+ class AuthorizationProvider(StrictBaseModel, ABC):
385
+ """Base class for authentication providers."""
373
386
 
374
387
  id: str = Field(
375
388
  ..., description="Unique ID of the authorization configuration."
376
389
  )
377
- api_key: str | None = Field(
378
- default=None, description="API key if using token-based auth."
379
- )
380
- client_id: str | None = Field(
381
- default=None, description="OAuth2 client ID."
382
- )
383
- client_secret: str | None = Field(
384
- default=None, description="OAuth2 client secret."
385
- )
390
+ type: str = Field(..., description="Authorization method type.")
391
+
392
+
393
+ class APIKeyAuthProvider(AuthorizationProvider):
394
+ """API key-based authentication provider."""
395
+
396
+ type: Literal["api_key"] = "api_key"
397
+ api_key: str = Field(..., description="API key for authentication.")
386
398
  host: str | None = Field(
387
399
  default=None, description="Base URL or domain of the provider."
388
400
  )
401
+
402
+
403
+ class OAuth2AuthProvider(AuthorizationProvider):
404
+ """OAuth2 authentication provider."""
405
+
406
+ type: Literal["oauth2"] = "oauth2"
407
+ client_id: str = Field(..., description="OAuth2 client ID.")
408
+ client_secret: str = Field(..., description="OAuth2 client secret.")
409
+ token_url: str = Field(..., description="Token endpoint URL.")
389
410
  scopes: list[str] | None = Field(
390
411
  default=None, description="OAuth2 scopes required."
391
412
  )
392
- token_url: str | None = Field(
393
- default=None, description="Token endpoint URL."
413
+
414
+
415
+ class AWSAuthProvider(AuthorizationProvider):
416
+ """AWS authentication provider supporting multiple credential methods."""
417
+
418
+ type: Literal["aws"] = "aws"
419
+
420
+ # Method 1: Access key/secret/session
421
+ access_key_id: str | None = Field(
422
+ default=None, description="AWS access key ID."
394
423
  )
395
- type: str = Field(
396
- ..., description="Authorization method, e.g., 'oauth2' or 'api_key'."
424
+ secret_access_key: str | None = Field(
425
+ default=None, description="AWS secret access key."
426
+ )
427
+ session_token: str | None = Field(
428
+ default=None,
429
+ description="AWS session token for temporary credentials.",
397
430
  )
398
431
 
432
+ # Method 2: Profile
433
+ profile_name: str | None = Field(
434
+ default=None, description="AWS profile name from credentials file."
435
+ )
436
+
437
+ # Method 3: Role assumption
438
+ role_arn: str | None = Field(
439
+ default=None, description="ARN of the role to assume."
440
+ )
441
+ role_session_name: str | None = Field(
442
+ default=None, description="Session name for role assumption."
443
+ )
444
+ external_id: str | None = Field(
445
+ default=None, description="External ID for role assumption."
446
+ )
447
+
448
+ # Common AWS settings
449
+ region: str | None = Field(default=None, description="AWS region.")
450
+
451
+ @model_validator(mode="after")
452
+ def validate_aws_auth(self) -> "AWSAuthProvider":
453
+ """Validate AWS authentication configuration."""
454
+ # At least one auth method must be specified
455
+ has_keys = self.access_key_id and self.secret_access_key
456
+ has_profile = self.profile_name
457
+ has_role = self.role_arn
458
+
459
+ if not (has_keys or has_profile or has_role):
460
+ raise ValueError(
461
+ "AWSAuthProvider must specify at least one authentication method: "
462
+ "access keys, profile name, or role ARN."
463
+ )
464
+
465
+ # If assuming a role, need either keys or profile for base credentials
466
+ if has_role and not (has_keys or has_profile):
467
+ raise ValueError(
468
+ "Role assumption requires base credentials (access keys or profile)."
469
+ )
470
+
471
+ return self
472
+
399
473
 
400
474
  class TelemetrySink(StrictBaseModel):
401
475
  """Defines an observability endpoint for collecting telemetry data from the QType runtime."""
@@ -403,7 +477,7 @@ class TelemetrySink(StrictBaseModel):
403
477
  id: str = Field(
404
478
  ..., description="Unique ID of the telemetry sink configuration."
405
479
  )
406
- auth: AuthorizationProvider | str | None = Field(
480
+ auth: AuthProviderType | str | None = Field(
407
481
  default=None,
408
482
  description="AuthorizationProvider used to authenticate telemetry data transmission.",
409
483
  )
@@ -455,7 +529,7 @@ class Application(StrictBaseModel):
455
529
  )
456
530
 
457
531
  # External integrations
458
- auths: list[AuthorizationProvider] | None = Field(
532
+ auths: list[AuthProviderType] | None = Field(
459
533
  default=None,
460
534
  description="List of authorization providers used for API access.",
461
535
  )
@@ -480,6 +554,58 @@ class Application(StrictBaseModel):
480
554
  )
481
555
 
482
556
 
557
+ #
558
+ # ---------------- Data Pipeline Components ----------------
559
+ #
560
+
561
+
562
+ class Source(Step):
563
+ """Base class for data sources"""
564
+
565
+ id: str = Field(..., description="Unique ID of the data source.")
566
+ cardinality: Literal[StepCardinality.many] = Field(
567
+ default=StepCardinality.many,
568
+ description="Sources always emit 0...N instances of the outputs.",
569
+ )
570
+
571
+
572
+ class SQLSource(Source):
573
+ """SQL database source that executes queries and emits rows."""
574
+
575
+ query: str = Field(
576
+ ..., description="SQL query to execute. Inputs are injected as params."
577
+ )
578
+ connection: str = Field(
579
+ ...,
580
+ description="Database connection string or reference to auth provider. Typically in SQLAlchemy format.",
581
+ )
582
+ auth: AuthProviderType | str | None = Field(
583
+ default=None,
584
+ description="Optional AuthorizationProvider for database authentication.",
585
+ )
586
+
587
+ @model_validator(mode="after")
588
+ def validate_sql_source(self) -> "SQLSource":
589
+ """Validate SQL source configuration."""
590
+ if self.outputs is None:
591
+ raise ValueError(
592
+ "SQLSource must define output variables that match the result columns."
593
+ )
594
+ return self
595
+
596
+
597
+ class Sink(Step):
598
+ """Base class for data sinks"""
599
+
600
+ id: str = Field(..., description="Unique ID of the data sink.")
601
+ # Remove cardinality field - it's always one for sinks
602
+ # ...existing code...
603
+ cardinality: Literal[StepCardinality.one] = Field(
604
+ default=StepCardinality.one,
605
+ description="Flows always emit exactly one instance of the outputs.",
606
+ )
607
+
608
+
483
609
  #
484
610
  # ---------------- Retrieval Augmented Generation Components ----------------
485
611
  #
@@ -493,13 +619,19 @@ class Index(StrictBaseModel, ABC):
493
619
  default=None,
494
620
  description="Index-specific configuration and connection parameters.",
495
621
  )
496
- auth: AuthorizationProvider | str | None = Field(
622
+ auth: AuthProviderType | str | None = Field(
497
623
  default=None,
498
624
  description="AuthorizationProvider for accessing the index.",
499
625
  )
500
626
  name: str = Field(..., description="Name of the index/collection/table.")
501
627
 
502
628
 
629
+ class IndexUpsert(Sink):
630
+ index: IndexType | str = Field(
631
+ ..., description="Index to upsert into (object or ID reference)."
632
+ )
633
+
634
+
503
635
  class VectorIndex(Index):
504
636
  """Vector database index for similarity search using embeddings."""
505
637
 
@@ -531,6 +663,7 @@ class VectorSearch(Search):
531
663
  """Performs vector similarity search against a vector index."""
532
664
 
533
665
  default_top_k: int | None = Field(
666
+ default=50,
534
667
  description="Number of top results to retrieve if not provided in the inputs.",
535
668
  )
536
669
 
@@ -539,7 +672,7 @@ class VectorSearch(Search):
539
672
  """Set default input and output variables if none provided."""
540
673
  if self.inputs is None:
541
674
  self.inputs = [
542
- Variable(id="top_k", type=PrimitiveTypeEnum.number),
675
+ Variable(id="top_k", type=PrimitiveTypeEnum.int),
543
676
  Variable(id="query", type=PrimitiveTypeEnum.text),
544
677
  ]
545
678
 
@@ -570,6 +703,16 @@ ToolType = Union[
570
703
  PythonFunctionTool,
571
704
  ]
572
705
 
706
+ # Create a union type for all source types
707
+ SourceType = Union[SQLSource,]
708
+
709
+ # Create a union type for all authorization provider types
710
+ AuthProviderType = Union[
711
+ APIKeyAuthProvider,
712
+ AWSAuthProvider,
713
+ OAuth2AuthProvider,
714
+ ]
715
+
573
716
  # Create a union type for all step types
574
717
  StepType = Union[
575
718
  Agent,
@@ -578,9 +721,12 @@ StepType = Union[
578
721
  Decoder,
579
722
  DocumentSearch,
580
723
  Flow,
724
+ IndexUpsert,
581
725
  LLMInference,
582
726
  PromptTemplate,
583
727
  PythonFunctionTool,
728
+ SQLSource,
729
+ Sink,
584
730
  VectorSearch,
585
731
  ]
586
732
 
@@ -602,10 +748,10 @@ ModelType = Union[
602
748
  #
603
749
 
604
750
 
605
- class AuthorizationProviderList(RootModel[list[AuthorizationProvider]]):
751
+ class AuthorizationProviderList(RootModel[list[AuthProviderType]]):
606
752
  """Schema for a standalone list of authorization providers."""
607
753
 
608
- root: list[AuthorizationProvider]
754
+ root: list[AuthProviderType]
609
755
 
610
756
 
611
757
  class IndexList(RootModel[list[IndexType]]):
@@ -638,35 +784,24 @@ class VariableList(RootModel[list[Variable]]):
638
784
  root: list[Variable]
639
785
 
640
786
 
641
- class Document(
642
- RootModel[
643
- Union[
644
- Agent,
645
- Application,
646
- AuthorizationProviderList,
647
- Flow,
648
- IndexList,
649
- ModelList,
650
- ToolList,
651
- TypeList,
652
- VariableList,
653
- ]
654
- ]
655
- ):
787
+ DocumentType = Union[
788
+ Agent,
789
+ Application,
790
+ AuthorizationProviderList,
791
+ Flow,
792
+ IndexList,
793
+ ModelList,
794
+ ToolList,
795
+ TypeList,
796
+ VariableList,
797
+ ]
798
+
799
+
800
+ class Document(RootModel[DocumentType]):
656
801
  """Schema for any valid QType document structure.
657
802
 
658
803
  This allows validation of standalone lists of components, individual components,
659
804
  or full QType application specs. Supports modular composition and reuse.
660
805
  """
661
806
 
662
- root: Union[
663
- Agent,
664
- Application,
665
- AuthorizationProviderList,
666
- Flow,
667
- IndexList,
668
- ModelList,
669
- ToolList,
670
- TypeList,
671
- VariableList,
672
- ]
807
+ root: DocumentType
qtype/dsl/validator.py CHANGED
@@ -80,7 +80,7 @@ def _update_map_with_unique_check(
80
80
  # This is a special case where we do not want to add the string itself.
81
81
  continue
82
82
  # Note: There is no current abstraction for the `id` field, so we assume it exists.
83
- obj_id = obj.id
83
+ obj_id = obj.id # type: ignore[attr-defined]
84
84
  # If the object already exists in the map, we check if it is the same object.
85
85
  # If it is not the same object, we raise an error.
86
86
  # This ensures that we do not have duplicate components with the same ID.
@@ -449,9 +449,14 @@ def validate(
449
449
  f"Chat flow {flow.id} must have at least one input variable of type ChatMessage."
450
450
  )
451
451
  if (
452
- len(flow.outputs) != 1
453
- or flow.outputs[0].type != qtype.dsl.domain_types.ChatMessage
454
- ): # type: ignore
452
+ not flow.outputs
453
+ or len(flow.outputs) != 1
454
+ or (
455
+ isinstance(flow.outputs[0], dsl.Variable)
456
+ and flow.outputs[0].type
457
+ != qtype.dsl.domain_types.ChatMessage
458
+ )
459
+ ):
455
460
  raise QTypeValidationError(
456
461
  f"Chat flow {flow.id} must have exactly one output variable of type ChatMessage."
457
462
  )
qtype/interpreter/api.py CHANGED
@@ -1,11 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import logging
3
4
  from pathlib import Path
4
5
 
5
- from fastapi import FastAPI, HTTPException
6
+ import pandas as pd
7
+ from fastapi import FastAPI, HTTPException, Query
6
8
  from fastapi.middleware.cors import CORSMiddleware
7
9
  from fastapi.staticfiles import StaticFiles
8
10
 
11
+ from qtype.dsl.base_types import StepCardinality
12
+ from qtype.interpreter.batch.flow import batch_execute_flow
13
+ from qtype.interpreter.batch.types import BatchConfig, ErrorMode
9
14
  from qtype.interpreter.flow import execute_flow
10
15
  from qtype.interpreter.typing import (
11
16
  create_input_type_model,
@@ -82,52 +87,103 @@ class APIExecutor:
82
87
  """Create a dynamic POST endpoint for a specific flow."""
83
88
  flow_id = flow.id
84
89
 
90
+ # determine if this is a batch inference
91
+ is_batch = flow.cardinality == StepCardinality.many
92
+
85
93
  # Create dynamic request and response models for this flow
86
- RequestModel = create_input_type_model(flow)
87
- ResponseModel = create_output_type_model(flow)
94
+ RequestModel = create_input_type_model(flow, is_batch)
95
+ ResponseModel = create_output_type_model(flow, is_batch)
88
96
 
89
97
  # Create the endpoint function with proper model binding
90
- def execute_flow_endpoint(request: RequestModel) -> ResponseModel: # type: ignore
91
- try:
92
- # Make a copy of the flow to avoid modifying the original
93
- # TODO: Use session to ensure memory is not used across requests.
94
- flow_copy = flow.model_copy(deep=True)
95
- # Set input values on the flow variables
96
- if flow_copy.inputs:
97
- for var in flow_copy.inputs:
98
- # Get the value from the request using the variable ID
99
- request_dict = request.model_dump() # type: ignore
100
- if var.id in request_dict:
101
- var.value = getattr(request, var.id)
102
- elif not var.is_set():
103
- raise HTTPException(
104
- status_code=400,
105
- detail=f"Required input '{var.id}' not provided",
106
- )
107
-
108
- # Execute the flow
109
- result_vars = execute_flow(flow_copy)
110
-
111
- # Extract output values
112
- outputs = {var.id: var.value for var in result_vars}
113
-
114
- response_data = {
115
- "flow_id": flow_id,
116
- "outputs": outputs,
117
- "status": "success",
118
- }
119
-
120
- # Return the response using the dynamic model
121
- return ResponseModel(**response_data) # type: ignore
122
-
123
- except Exception as e:
124
- raise HTTPException(
125
- status_code=500, detail=f"Flow execution failed: {str(e)}"
126
- )
98
+ if is_batch:
99
+
100
+ def execute_flow_endpoint( # type: ignore
101
+ request: RequestModel, # type: ignore
102
+ error_mode: ErrorMode = Query(
103
+ default=ErrorMode.FAIL,
104
+ description="Error handling mode for batch processing",
105
+ ),
106
+ ) -> ResponseModel: # type: ignore
107
+ try:
108
+ # Make a copy of the flow to avoid modifying the original
109
+ # TODO: Use session to ensure memory is not used across requests.
110
+ flow_copy = flow.model_copy(deep=True)
111
+ # convert the inputs into a dataframe with a single row
112
+ inputs = pd.DataFrame(
113
+ [i.model_dump() for i in request.inputs] # type: ignore
114
+ )
115
+
116
+ # Execute the flow
117
+ results, errors = batch_execute_flow(
118
+ flow_copy,
119
+ inputs,
120
+ batch_config=BatchConfig(error_mode=error_mode),
121
+ )
122
+
123
+ response_data = {
124
+ "flow_id": flow_id,
125
+ "outputs": results.to_dict(orient="records"),
126
+ "errors": errors.to_dict(orient="records"),
127
+ "num_results": len(results),
128
+ "num_errors": len(errors),
129
+ "num_inputs": len(inputs),
130
+ "status": "success" if len(errors) == 0 else "partial",
131
+ }
132
+
133
+ # Return the response using the dynamic model
134
+ return ResponseModel(**response_data) # type: ignore
135
+
136
+ except Exception as e:
137
+ logging.error("Batch Flow Execution Failed", exc_info=e)
138
+ raise HTTPException(
139
+ status_code=500,
140
+ detail=f"Batch flow execution failed: {str(e)}",
141
+ )
142
+ else:
143
+
144
+ def execute_flow_endpoint(request: RequestModel) -> ResponseModel: # type: ignore
145
+ try:
146
+ # Make a copy of the flow to avoid modifying the original
147
+ # TODO: Use session to ensure memory is not used across requests.
148
+ flow_copy = flow.model_copy(deep=True)
149
+ # Set input values on the flow variables
150
+ if flow_copy.inputs:
151
+ for var in flow_copy.inputs:
152
+ # Get the value from the request using the variable ID
153
+ request_dict = request.model_dump() # type: ignore
154
+ if var.id in request_dict:
155
+ var.value = getattr(request, var.id)
156
+ elif not var.is_set():
157
+ raise HTTPException(
158
+ status_code=400,
159
+ detail=f"Required input '{var.id}' not provided",
160
+ )
161
+ return flow_copy
162
+ # Execute the flow
163
+ result_vars = execute_flow(flow_copy)
164
+
165
+ # Extract output values
166
+ outputs = {var.id: var.value for var in result_vars}
167
+
168
+ response_data = {
169
+ "flow_id": flow_id,
170
+ "outputs": outputs,
171
+ "status": "success",
172
+ }
173
+
174
+ # Return the response using the dynamic model
175
+ return ResponseModel(**response_data) # type: ignore
176
+
177
+ except Exception as e:
178
+ raise HTTPException(
179
+ status_code=500,
180
+ detail=f"Flow execution failed: {str(e)}",
181
+ )
127
182
 
128
183
  # Set the function annotations properly for FastAPI
129
184
  execute_flow_endpoint.__annotations__ = {
130
185
  "request": RequestModel,
186
+ "error_mode": ErrorMode,
131
187
  "return": ResponseModel,
132
188
  }
133
189
 
@@ -0,0 +1,3 @@
1
+ """
2
+ Authorization context managers for QType interpreter.
3
+ """