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.
- qtype/application/__init__.py +12 -0
- qtype/application/commons/__init__.py +7 -0
- qtype/{converters → application/converters}/tools_from_module.py +2 -2
- qtype/application/converters/types.py +33 -0
- qtype/{dsl/document.py → application/documentation.py} +2 -0
- qtype/application/facade.py +160 -0
- qtype/base/__init__.py +14 -0
- qtype/base/exceptions.py +49 -0
- qtype/base/logging.py +39 -0
- qtype/base/types.py +29 -0
- qtype/commands/convert.py +64 -49
- qtype/commands/generate.py +59 -4
- qtype/commands/run.py +109 -72
- qtype/commands/serve.py +42 -28
- qtype/commands/validate.py +25 -42
- qtype/commands/visualize.py +51 -37
- qtype/dsl/__init__.py +9 -0
- qtype/dsl/base_types.py +8 -0
- qtype/dsl/custom_types.py +6 -4
- qtype/dsl/model.py +185 -50
- qtype/dsl/validator.py +9 -4
- qtype/interpreter/api.py +96 -40
- qtype/interpreter/auth/__init__.py +3 -0
- qtype/interpreter/auth/aws.py +234 -0
- qtype/interpreter/auth/cache.py +67 -0
- qtype/interpreter/auth/generic.py +103 -0
- qtype/interpreter/batch/flow.py +95 -0
- qtype/interpreter/batch/sql_source.py +95 -0
- qtype/interpreter/batch/step.py +63 -0
- qtype/interpreter/batch/types.py +41 -0
- qtype/interpreter/batch/utils.py +179 -0
- qtype/interpreter/conversions.py +21 -10
- qtype/interpreter/resource_cache.py +4 -2
- qtype/interpreter/steps/decoder.py +13 -9
- qtype/interpreter/steps/llm_inference.py +7 -9
- qtype/interpreter/steps/prompt_template.py +1 -1
- qtype/interpreter/streaming_helpers.py +3 -3
- qtype/interpreter/typing.py +47 -11
- qtype/interpreter/ui/404/index.html +1 -1
- qtype/interpreter/ui/404.html +1 -1
- qtype/interpreter/ui/index.html +1 -1
- qtype/interpreter/ui/index.txt +1 -1
- qtype/loader.py +15 -16
- qtype/semantic/generate.py +91 -39
- qtype/semantic/model.py +183 -52
- qtype/semantic/resolver.py +4 -4
- {qtype-0.0.10.dist-info → qtype-0.0.12.dist-info}/METADATA +5 -1
- {qtype-0.0.10.dist-info → qtype-0.0.12.dist-info}/RECORD +58 -44
- qtype/commons/generate.py +0 -93
- qtype/converters/types.py +0 -66
- qtype/semantic/errors.py +0 -4
- /qtype/{commons → application/commons}/tools.py +0 -0
- /qtype/{commons → application/converters}/__init__.py +0 -0
- /qtype/{converters → application/converters}/tools_from_api.py +0 -0
- /qtype/{converters → interpreter/batch}/__init__.py +0 -0
- /qtype/interpreter/ui/_next/static/{Jb2murBlt2XkN6punrQbE → OT8QJQW3J70VbDWWfrEMT}/_buildManifest.js +0 -0
- /qtype/interpreter/ui/_next/static/{Jb2murBlt2XkN6punrQbE → OT8QJQW3J70VbDWWfrEMT}/_ssgManifest.js +0 -0
- {qtype-0.0.10.dist-info → qtype-0.0.12.dist-info}/WHEEL +0 -0
- {qtype-0.0.10.dist-info → qtype-0.0.12.dist-info}/entry_points.txt +0 -0
- {qtype-0.0.10.dist-info → qtype-0.0.12.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
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:
|
|
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:
|
|
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
|
-
"""
|
|
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
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
-
|
|
393
|
-
|
|
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
|
-
|
|
396
|
-
|
|
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:
|
|
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[
|
|
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:
|
|
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.
|
|
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[
|
|
751
|
+
class AuthorizationProviderList(RootModel[list[AuthProviderType]]):
|
|
606
752
|
"""Schema for a standalone list of authorization providers."""
|
|
607
753
|
|
|
608
|
-
root: list[
|
|
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
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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:
|
|
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
|
-
|
|
453
|
-
or flow.outputs
|
|
454
|
-
|
|
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
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
#
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|