mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: [BREAKING] Remove Request Interceptor Architecture - Simplify Sub-workflow Communication (#898)
* removed intercepts_request and simplified how interception is handled * parameterize SubWorkflowRequestInfo * revert back the field rename of RequestResponse * remove duplicate tests * ignore type error * remove SubWorkflowResponse * Remove SubWorkflowRequestInfo and update RequestInfoMessage with source_executor_id for correlation
This commit is contained in:
committed by
GitHub
Unverified
parent
39e071c430
commit
366a7f7d47
@@ -14,11 +14,10 @@ class SampleRequest(RequestInfoMessage):
|
||||
|
||||
|
||||
def test_decode_dataclass_with_nested_request() -> None:
|
||||
original = RequestResponse[SampleRequest, str].handled("approve")
|
||||
original = RequestResponse[SampleRequest, str].with_correlation(
|
||||
original,
|
||||
SampleRequest(request_id="abc", prompt="prompt"),
|
||||
"abc",
|
||||
original = RequestResponse[SampleRequest, str](
|
||||
data="approve",
|
||||
original_request=SampleRequest(request_id="abc", prompt="prompt"),
|
||||
request_id="abc",
|
||||
)
|
||||
|
||||
encoded = _encode_checkpoint_value(original)
|
||||
@@ -32,11 +31,10 @@ def test_decode_dataclass_with_nested_request() -> None:
|
||||
|
||||
|
||||
def test_is_instance_of_coerces_request_response_original_request_dict() -> None:
|
||||
response = RequestResponse[SampleRequest, str].handled("approve")
|
||||
response = RequestResponse[SampleRequest, str].with_correlation(
|
||||
response,
|
||||
SampleRequest(request_id="req-1", prompt="prompt"),
|
||||
"req-1",
|
||||
response = RequestResponse[SampleRequest, str](
|
||||
data="approve",
|
||||
original_request=SampleRequest(request_id="req-1", prompt="prompt"),
|
||||
request_id="req-1",
|
||||
)
|
||||
|
||||
# Simulate checkpoint decode fallback leaving a dict
|
||||
|
||||
@@ -171,11 +171,10 @@ def test_pending_requests_from_checkpoint_and_summary() -> None:
|
||||
request = SimpleApproval(prompt="Review draft", draft="Draft text", iteration=3)
|
||||
request.request_id = "req-42"
|
||||
|
||||
response = RequestResponse[SimpleApproval, str].handled("approve")
|
||||
response = RequestResponse[SimpleApproval, str].with_correlation(
|
||||
response,
|
||||
request,
|
||||
request.request_id,
|
||||
response = RequestResponse[SimpleApproval, str](
|
||||
data="approve",
|
||||
original_request=request,
|
||||
request_id=request.request_id,
|
||||
)
|
||||
|
||||
encoded_response = _encode_checkpoint_value(response)
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
|
||||
from typing_extensions import Never
|
||||
|
||||
from agent_framework import (
|
||||
Executor,
|
||||
WorkflowBuilder,
|
||||
WorkflowContext,
|
||||
WorkflowExecutor,
|
||||
handler,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SimpleRequest:
|
||||
"""Simple request for testing."""
|
||||
|
||||
text: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class SimpleResponse:
|
||||
"""Simple response for testing."""
|
||||
|
||||
result: str
|
||||
|
||||
|
||||
class SimpleSubExecutor(Executor):
|
||||
"""Simple executor for sub-workflow."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(id="simple_sub")
|
||||
|
||||
@handler
|
||||
async def process(self, request: SimpleRequest, ctx: WorkflowContext[Never, SimpleResponse]) -> None:
|
||||
"""Process a simple request."""
|
||||
# Just echo back with prefix and complete
|
||||
response = SimpleResponse(result=f"processed: {request.text}")
|
||||
await ctx.yield_output(response)
|
||||
|
||||
|
||||
class SimpleParent(Executor):
|
||||
"""Simple parent executor."""
|
||||
|
||||
result: SimpleResponse | None = None
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(id="simple_parent")
|
||||
|
||||
@handler
|
||||
async def start(self, text: str, ctx: WorkflowContext[SimpleRequest]) -> None:
|
||||
"""Start the process."""
|
||||
request = SimpleRequest(text=text)
|
||||
await ctx.send_message(request, target_id="sub_workflow")
|
||||
|
||||
@handler
|
||||
async def collect(self, response: SimpleResponse, ctx: WorkflowContext) -> None:
|
||||
"""Collect the result."""
|
||||
self.result = response
|
||||
|
||||
|
||||
async def test_simple_sub_workflow():
|
||||
"""Test the simplest possible sub-workflow."""
|
||||
# Create sub-workflow with dummy executor to satisfy validation
|
||||
sub_executor = SimpleSubExecutor()
|
||||
|
||||
class DummyExecutor(Executor):
|
||||
def __init__(self):
|
||||
super().__init__(id="dummy")
|
||||
|
||||
@handler
|
||||
async def process(self, message: object, ctx: WorkflowContext) -> None:
|
||||
pass # Do nothing
|
||||
|
||||
dummy = DummyExecutor()
|
||||
sub_workflow = (
|
||||
WorkflowBuilder()
|
||||
.set_start_executor(sub_executor)
|
||||
.add_edge(sub_executor, dummy) # Add edge to satisfy validation
|
||||
.build()
|
||||
)
|
||||
|
||||
# Create parent workflow
|
||||
parent = SimpleParent()
|
||||
workflow_executor = WorkflowExecutor(sub_workflow, id="sub_workflow")
|
||||
|
||||
main_workflow = (
|
||||
WorkflowBuilder()
|
||||
.set_start_executor(parent)
|
||||
.add_edge(parent, workflow_executor)
|
||||
.add_edge(workflow_executor, parent)
|
||||
.build()
|
||||
)
|
||||
|
||||
# Run the workflow
|
||||
await main_workflow.run("hello world")
|
||||
|
||||
# Check result
|
||||
assert parent.result is not None
|
||||
assert parent.result.result == "processed: hello world"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the simple test
|
||||
asyncio.run(test_simple_sub_workflow())
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
@@ -12,11 +11,11 @@ from agent_framework import (
|
||||
RequestInfoExecutor,
|
||||
RequestInfoMessage,
|
||||
RequestResponse,
|
||||
Workflow,
|
||||
WorkflowBuilder,
|
||||
WorkflowContext,
|
||||
WorkflowExecutor,
|
||||
handler,
|
||||
intercepts_request,
|
||||
)
|
||||
|
||||
|
||||
@@ -45,6 +44,61 @@ class ValidationResult:
|
||||
reason: str
|
||||
|
||||
|
||||
# Test helper functions
|
||||
def create_email_validation_workflow() -> Workflow:
|
||||
"""Create a standard email validation workflow."""
|
||||
email_validator = EmailValidator()
|
||||
email_request_info = RequestInfoExecutor(id="email_request_info")
|
||||
|
||||
return (
|
||||
WorkflowBuilder()
|
||||
.set_start_executor(email_validator)
|
||||
.add_edge(email_validator, email_request_info)
|
||||
.add_edge(email_request_info, email_validator)
|
||||
.build()
|
||||
)
|
||||
|
||||
|
||||
class BasicParent(Executor):
|
||||
"""Basic parent executor for simple sub-workflow tests."""
|
||||
|
||||
result: ValidationResult | None = Field(default=None)
|
||||
cache: dict[str, bool] = Field(default_factory=dict)
|
||||
|
||||
def __init__(self, cache: dict[str, bool] | None = None, **kwargs: Any):
|
||||
if cache is not None:
|
||||
kwargs["cache"] = cache
|
||||
super().__init__(id="basic_parent", **kwargs)
|
||||
|
||||
@handler
|
||||
async def start(self, email: str, ctx: WorkflowContext[EmailValidationRequest]) -> None:
|
||||
request = EmailValidationRequest(email=email)
|
||||
await ctx.send_message(request, target_id="email_workflow")
|
||||
|
||||
@handler
|
||||
async def handle_domain_request(
|
||||
self,
|
||||
request: DomainCheckRequest,
|
||||
ctx: WorkflowContext[RequestResponse[DomainCheckRequest, Any] | DomainCheckRequest],
|
||||
) -> None:
|
||||
"""Handle requests from sub-workflows with optional caching."""
|
||||
domain_request = request
|
||||
|
||||
if domain_request.domain in self.cache:
|
||||
# Return cached result
|
||||
response = RequestResponse(
|
||||
data=self.cache[domain_request.domain], original_request=request, request_id=request.request_id
|
||||
)
|
||||
await ctx.send_message(response, target_id=request.source_executor_id)
|
||||
else:
|
||||
# Not in cache, forward to external
|
||||
await ctx.send_message(request)
|
||||
|
||||
@handler
|
||||
async def collect(self, result: ValidationResult, ctx: WorkflowContext) -> None:
|
||||
self.result = result
|
||||
|
||||
|
||||
# Test executors
|
||||
class EmailValidator(Executor):
|
||||
"""Validates email addresses in a sub-workflow."""
|
||||
@@ -54,7 +108,7 @@ class EmailValidator(Executor):
|
||||
|
||||
@handler
|
||||
async def validate_request(
|
||||
self, request: EmailValidationRequest, ctx: WorkflowContext[RequestInfoMessage, ValidationResult]
|
||||
self, request: EmailValidationRequest, ctx: WorkflowContext[DomainCheckRequest, ValidationResult]
|
||||
) -> None:
|
||||
"""Validate an email address."""
|
||||
# Extract domain and check if it's approved
|
||||
@@ -101,17 +155,23 @@ class ParentOrchestrator(Executor):
|
||||
request = EmailValidationRequest(email=email)
|
||||
await ctx.send_message(request, target_id="email_workflow")
|
||||
|
||||
@intercepts_request
|
||||
async def check_domain(
|
||||
self, request: DomainCheckRequest, ctx: WorkflowContext[Any]
|
||||
) -> RequestResponse[DomainCheckRequest, bool]:
|
||||
"""Intercept domain check requests from sub-workflows."""
|
||||
# Check if we know this domain
|
||||
if request.domain in self.approved_domains:
|
||||
return RequestResponse[DomainCheckRequest, bool].handled(True)
|
||||
@handler
|
||||
async def handle_domain_request(
|
||||
self,
|
||||
request: DomainCheckRequest,
|
||||
ctx: WorkflowContext[RequestResponse[DomainCheckRequest, Any] | DomainCheckRequest],
|
||||
) -> None:
|
||||
"""Handle requests from sub-workflows."""
|
||||
domain_request = request
|
||||
|
||||
# We don't know this domain, forward to external
|
||||
return RequestResponse[DomainCheckRequest, bool].forward()
|
||||
# Check if we know this domain
|
||||
if domain_request.domain in self.approved_domains:
|
||||
# Send response back to sub-workflow
|
||||
response = RequestResponse(data=True, original_request=request, request_id=request.request_id)
|
||||
await ctx.send_message(response, target_id=request.source_executor_id)
|
||||
else:
|
||||
# We don't know this domain, forward to external
|
||||
await ctx.send_message(request)
|
||||
|
||||
@handler
|
||||
async def collect_result(self, result: ValidationResult, ctx: WorkflowContext) -> None:
|
||||
@@ -122,34 +182,10 @@ class ParentOrchestrator(Executor):
|
||||
async def test_basic_sub_workflow() -> None:
|
||||
"""Test basic sub-workflow execution without interception."""
|
||||
# Create sub-workflow
|
||||
email_validator = EmailValidator()
|
||||
email_request_info = RequestInfoExecutor(id="email_request_info")
|
||||
|
||||
validation_workflow = (
|
||||
WorkflowBuilder()
|
||||
.set_start_executor(email_validator)
|
||||
.add_edge(email_validator, email_request_info)
|
||||
.add_edge(email_request_info, email_validator)
|
||||
.build()
|
||||
)
|
||||
validation_workflow = create_email_validation_workflow()
|
||||
|
||||
# Create parent workflow without interception
|
||||
class SimpleParent(Executor):
|
||||
result: ValidationResult | None = Field(default=None)
|
||||
|
||||
def __init__(self, **kwargs: Any):
|
||||
super().__init__(id="simple_parent", **kwargs)
|
||||
|
||||
@handler
|
||||
async def start(self, email: str, ctx: WorkflowContext[EmailValidationRequest]) -> None:
|
||||
request = EmailValidationRequest(email=email)
|
||||
await ctx.send_message(request, target_id="email_workflow")
|
||||
|
||||
@handler
|
||||
async def collect(self, result: ValidationResult, ctx: WorkflowContext) -> None:
|
||||
self.result = result
|
||||
|
||||
parent = SimpleParent()
|
||||
parent = BasicParent()
|
||||
workflow_executor = WorkflowExecutor(validation_workflow, "email_workflow")
|
||||
main_request_info = RequestInfoExecutor(id="main_request_info")
|
||||
|
||||
@@ -159,7 +195,7 @@ async def test_basic_sub_workflow() -> None:
|
||||
.add_edge(parent, workflow_executor)
|
||||
.add_edge(workflow_executor, parent)
|
||||
.add_edge(workflow_executor, main_request_info)
|
||||
.add_edge(main_request_info, workflow_executor) # CRITICAL: For SubWorkflowResponse routing
|
||||
.add_edge(main_request_info, workflow_executor) # CRITICAL: For RequestResponse routing
|
||||
.build()
|
||||
)
|
||||
|
||||
@@ -184,21 +220,12 @@ async def test_basic_sub_workflow() -> None:
|
||||
|
||||
|
||||
async def test_sub_workflow_with_interception():
|
||||
"""Test sub-workflow with parent interception of requests."""
|
||||
"""Test sub-workflow with parent interception and conditional forwarding."""
|
||||
# Create sub-workflow
|
||||
email_validator = EmailValidator()
|
||||
email_request_info = RequestInfoExecutor(id="email_request_info")
|
||||
validation_workflow = create_email_validation_workflow()
|
||||
|
||||
validation_workflow = (
|
||||
WorkflowBuilder()
|
||||
.set_start_executor(email_validator)
|
||||
.add_edge(email_validator, email_request_info)
|
||||
.add_edge(email_request_info, email_validator)
|
||||
.build()
|
||||
)
|
||||
|
||||
# Create parent workflow with interception
|
||||
parent = ParentOrchestrator(approved_domains={"example.com", "internal.org"})
|
||||
# Create parent workflow with interception cache
|
||||
parent = BasicParent(cache={"example.com": True, "internal.org": True})
|
||||
workflow_executor = WorkflowExecutor(validation_workflow, "email_workflow")
|
||||
parent_request_info = RequestInfoExecutor(id="request_info")
|
||||
|
||||
@@ -208,29 +235,23 @@ async def test_sub_workflow_with_interception():
|
||||
.add_edge(parent, workflow_executor)
|
||||
.add_edge(workflow_executor, parent)
|
||||
.add_edge(parent, parent_request_info) # For forwarded requests
|
||||
.add_edge(parent_request_info, workflow_executor) # For SubWorkflowResponse routing
|
||||
.add_edge(parent_request_info, workflow_executor) # For RequestResponse routing
|
||||
.build()
|
||||
)
|
||||
|
||||
# Test 1: Email with known domain (intercepted)
|
||||
result = await main_workflow.run(["user@example.com"])
|
||||
|
||||
# Should complete without external requests
|
||||
# Test 1: Email with cached domain (intercepted)
|
||||
result = await main_workflow.run("user@example.com")
|
||||
request_events = result.get_request_info_events()
|
||||
assert len(request_events) == 0 # No external requests, handled internally
|
||||
assert len(request_events) == 0 # No external requests, handled from cache
|
||||
assert parent.result is not None
|
||||
assert parent.result.email == "user@example.com"
|
||||
assert parent.result.is_valid is True
|
||||
|
||||
assert len(parent.results) == 1
|
||||
assert parent.results[0].email == "user@example.com"
|
||||
assert parent.results[0].is_valid is True
|
||||
assert parent.results[0].reason == "Domain approved"
|
||||
|
||||
# Test 2: Email with unknown domain (forwarded)
|
||||
parent.results.clear()
|
||||
result = await main_workflow.run(["user@unknown.com"])
|
||||
|
||||
# Should have external request
|
||||
# Test 2: Email with unknown domain (forwarded to external)
|
||||
parent.result = None
|
||||
result = await main_workflow.run("user@unknown.com")
|
||||
request_events = result.get_request_info_events()
|
||||
assert len(request_events) == 1
|
||||
assert len(request_events) == 1 # Forwarded to external
|
||||
assert isinstance(request_events[0].data, DomainCheckRequest)
|
||||
assert request_events[0].data.domain == "unknown.com"
|
||||
|
||||
@@ -238,89 +259,18 @@ async def test_sub_workflow_with_interception():
|
||||
await main_workflow.send_responses({
|
||||
request_events[0].request_id: False # Domain not approved
|
||||
})
|
||||
assert parent.result is not None
|
||||
assert parent.result.email == "user@unknown.com"
|
||||
assert parent.result.is_valid is False
|
||||
|
||||
assert len(parent.results) == 1
|
||||
assert parent.results[0].email == "user@unknown.com"
|
||||
assert parent.results[0].is_valid is False
|
||||
assert parent.results[0].reason == "Domain not approved"
|
||||
|
||||
|
||||
async def test_conditional_forwarding() -> None:
|
||||
"""Test conditional forwarding with RequestResponse.forward()."""
|
||||
|
||||
class ConditionalParent(Executor):
|
||||
"""Parent that conditionally handles requests."""
|
||||
|
||||
cache: dict[str, bool] = Field(default_factory=lambda: {"cached.com": True})
|
||||
result: ValidationResult | None = Field(default=None)
|
||||
|
||||
def __init__(self, **kwargs: Any):
|
||||
super().__init__(id="conditional_parent", **kwargs)
|
||||
|
||||
@handler
|
||||
async def start(self, email: str, ctx: WorkflowContext[EmailValidationRequest]) -> None:
|
||||
request = EmailValidationRequest(email=email)
|
||||
await ctx.send_message(request, target_id="email_workflow")
|
||||
|
||||
@intercepts_request
|
||||
async def check_domain(
|
||||
self, request: DomainCheckRequest, ctx: WorkflowContext[Any]
|
||||
) -> RequestResponse[DomainCheckRequest, bool]:
|
||||
"""Check cache first, then forward if not found."""
|
||||
if request.domain in self.cache:
|
||||
# Return cached result
|
||||
return RequestResponse[DomainCheckRequest, bool].handled(self.cache[request.domain])
|
||||
|
||||
# Not in cache, forward to external
|
||||
return RequestResponse[DomainCheckRequest, bool].forward()
|
||||
|
||||
@handler
|
||||
async def collect(self, result: ValidationResult, ctx: WorkflowContext) -> None:
|
||||
self.result = result
|
||||
|
||||
# Setup workflows
|
||||
email_validator = EmailValidator()
|
||||
request_info = RequestInfoExecutor(id="request_info")
|
||||
|
||||
validation_workflow = (
|
||||
WorkflowBuilder()
|
||||
.set_start_executor(email_validator)
|
||||
.add_edge(email_validator, request_info)
|
||||
.add_edge(request_info, email_validator)
|
||||
.build()
|
||||
)
|
||||
|
||||
parent = ConditionalParent()
|
||||
workflow_executor = WorkflowExecutor(validation_workflow, "email_workflow")
|
||||
parent_request_info = RequestInfoExecutor(id="request_info")
|
||||
|
||||
main_workflow = (
|
||||
WorkflowBuilder()
|
||||
.set_start_executor(parent)
|
||||
.add_edge(parent, workflow_executor)
|
||||
.add_edge(workflow_executor, parent)
|
||||
.add_edge(parent, parent_request_info)
|
||||
.add_edge(parent_request_info, workflow_executor) # For SubWorkflowResponse routing
|
||||
.build()
|
||||
)
|
||||
|
||||
# Test cached domain
|
||||
result = await main_workflow.run("user@cached.com")
|
||||
# Test 3: Another cached domain
|
||||
parent.result = None
|
||||
result = await main_workflow.run("user@internal.org")
|
||||
request_events = result.get_request_info_events()
|
||||
assert len(request_events) == 0 # Handled from cache
|
||||
assert parent.result is not None
|
||||
assert parent.result.is_valid is True
|
||||
|
||||
# Test uncached domain
|
||||
parent.result = None
|
||||
result = await main_workflow.run("user@new.com")
|
||||
request_events = result.get_request_info_events()
|
||||
assert len(request_events) == 1 # Forwarded to external
|
||||
|
||||
await main_workflow.send_responses({request_events[0].request_id: True})
|
||||
assert parent.result is not None
|
||||
assert parent.result.is_valid is True
|
||||
|
||||
|
||||
async def test_workflow_scoped_interception() -> None:
|
||||
"""Test interception scoped to specific sub-workflows."""
|
||||
@@ -339,42 +289,41 @@ async def test_workflow_scoped_interception() -> None:
|
||||
await ctx.send_message(EmailValidationRequest(email=data["email1"]), target_id="workflow_a")
|
||||
await ctx.send_message(EmailValidationRequest(email=data["email2"]), target_id="workflow_b")
|
||||
|
||||
@intercepts_request(from_workflow="workflow_a")
|
||||
async def check_domain_a(
|
||||
self, request: DomainCheckRequest, ctx: WorkflowContext[Any]
|
||||
) -> RequestResponse[DomainCheckRequest, bool]:
|
||||
"""Strict rules for workflow A."""
|
||||
if request.domain == "strict.com":
|
||||
return RequestResponse[DomainCheckRequest, bool].handled(True)
|
||||
return RequestResponse[DomainCheckRequest, bool].forward()
|
||||
@handler
|
||||
async def handle_domain_request(
|
||||
self,
|
||||
request: DomainCheckRequest,
|
||||
ctx: WorkflowContext[RequestResponse[DomainCheckRequest, Any] | DomainCheckRequest],
|
||||
) -> None:
|
||||
domain_request = request
|
||||
|
||||
@intercepts_request(from_workflow="workflow_b")
|
||||
async def check_domain_b(
|
||||
self, request: DomainCheckRequest, ctx: WorkflowContext[Any]
|
||||
) -> RequestResponse[DomainCheckRequest, bool]:
|
||||
"""Lenient rules for workflow B."""
|
||||
if request.domain.endswith(".com"):
|
||||
return RequestResponse[DomainCheckRequest, bool].handled(True)
|
||||
return RequestResponse[DomainCheckRequest, bool].forward()
|
||||
if request.source_executor_id == "workflow_a":
|
||||
# Strict rules for workflow A
|
||||
if domain_request.domain == "strict.com":
|
||||
response = RequestResponse(data=True, original_request=request, request_id=request.request_id)
|
||||
await ctx.send_message(response, target_id=request.source_executor_id)
|
||||
else:
|
||||
# Forward to external
|
||||
await ctx.send_message(request)
|
||||
elif request.source_executor_id == "workflow_b":
|
||||
# Lenient rules for workflow B
|
||||
if domain_request.domain.endswith(".com"):
|
||||
response = RequestResponse(data=True, original_request=request, request_id=request.request_id)
|
||||
await ctx.send_message(response, target_id=request.source_executor_id)
|
||||
else:
|
||||
# Forward to external
|
||||
await ctx.send_message(request)
|
||||
else:
|
||||
# Unknown source, forward to external
|
||||
await ctx.send_message(request)
|
||||
|
||||
@handler
|
||||
async def collect(self, result: ValidationResult, ctx: WorkflowContext) -> None:
|
||||
self.results[result.email] = result
|
||||
|
||||
# Create two identical sub-workflows
|
||||
def create_validation_workflow():
|
||||
validator = EmailValidator()
|
||||
request_info = RequestInfoExecutor(id="request_info")
|
||||
return (
|
||||
WorkflowBuilder()
|
||||
.set_start_executor(validator)
|
||||
.add_edge(validator, request_info)
|
||||
.add_edge(request_info, validator)
|
||||
.build()
|
||||
)
|
||||
|
||||
workflow_a = create_validation_workflow()
|
||||
workflow_b = create_validation_workflow()
|
||||
workflow_a = create_email_validation_workflow()
|
||||
workflow_b = create_email_validation_workflow()
|
||||
|
||||
parent = MultiWorkflowParent()
|
||||
executor_a = WorkflowExecutor(workflow_a, "workflow_a")
|
||||
@@ -389,8 +338,8 @@ async def test_workflow_scoped_interception() -> None:
|
||||
.add_edge(executor_a, parent)
|
||||
.add_edge(executor_b, parent)
|
||||
.add_edge(parent, parent_request_info)
|
||||
.add_edge(parent_request_info, executor_a) # For SubWorkflowResponse routing
|
||||
.add_edge(parent_request_info, executor_b) # For SubWorkflowResponse routing
|
||||
.add_edge(parent_request_info, executor_a) # For RequestResponse routing
|
||||
.add_edge(parent_request_info, executor_b) # For RequestResponse routing
|
||||
.build()
|
||||
)
|
||||
|
||||
@@ -432,16 +381,7 @@ async def test_concurrent_sub_workflow_execution() -> None:
|
||||
self.results.append(result)
|
||||
|
||||
# Create sub-workflow for email validation
|
||||
email_validator = EmailValidator()
|
||||
email_request_info = RequestInfoExecutor(id="email_request_info")
|
||||
|
||||
validation_workflow = (
|
||||
WorkflowBuilder()
|
||||
.set_start_executor(email_validator)
|
||||
.add_edge(email_validator, email_request_info)
|
||||
.add_edge(email_request_info, email_validator)
|
||||
.build()
|
||||
)
|
||||
validation_workflow = create_email_validation_workflow()
|
||||
|
||||
# Create parent workflow
|
||||
processor = ConcurrentProcessor()
|
||||
@@ -454,7 +394,7 @@ async def test_concurrent_sub_workflow_execution() -> None:
|
||||
.add_edge(processor, workflow_executor)
|
||||
.add_edge(workflow_executor, processor)
|
||||
.add_edge(workflow_executor, parent_request_info) # For external requests
|
||||
.add_edge(parent_request_info, workflow_executor) # For SubWorkflowResponse routing
|
||||
.add_edge(parent_request_info, workflow_executor) # For RequestResponse routing
|
||||
.build()
|
||||
)
|
||||
|
||||
@@ -497,12 +437,3 @@ async def test_concurrent_sub_workflow_execution() -> None:
|
||||
|
||||
# Verify that concurrent executions were properly isolated
|
||||
# (This is implicitly tested by the fact that we got correct results for all emails)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run tests
|
||||
asyncio.run(test_basic_sub_workflow())
|
||||
asyncio.run(test_sub_workflow_with_interception())
|
||||
asyncio.run(test_conditional_forwarding())
|
||||
asyncio.run(test_workflow_scoped_interception())
|
||||
asyncio.run(test_concurrent_sub_workflow_execution())
|
||||
|
||||
@@ -95,7 +95,8 @@ def test_request_response_type() -> None:
|
||||
"""Test RequestResponse generic type checking."""
|
||||
|
||||
request_instance = RequestResponse[RequestInfoMessage, str](
|
||||
is_handled=False,
|
||||
data="approve",
|
||||
request_id="req-1",
|
||||
original_request=RequestInfoMessage(),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user