-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Okay, here are the proposals and corresponding GitHub issue drafts for the next two major points identified:
- Addressing the synchronous
flow.run()call within the async Task Manager. - Refining Pydantic models and input/output handling for full A2A compliance.
1. Proposal: Ensure Non-Blocking Flow Execution in Async Task Manager
1. Introduction & Problem Statement
The PocketFlowTaskManager.on_send_task method in a2aflow/tasks.py is defined as async def because it's called from the async FastAPI request handler. However, within this method, the core agent logic is invoked using the synchronous self.flow.run(shared).
If the underlying PocketFlow Flow (self.flow) or any of its constituent Nodes perform blocking operations (e.g., synchronous network calls, significant CPU computation, synchronous file I/O), this call will block the entire FastAPI/Uvicorn event loop. This negates the benefits of using an async web framework, severely limiting the server's concurrency and responsiveness under load.
2. Goals
- Ensure that the execution of the PocketFlow
Flowwithinon_send_taskdoes not block the main async event loop. - Maintain compatibility with both synchronous and asynchronous PocketFlow
Flowdefinitions provided by the user. - Improve the overall performance and scalability of the
A2AServer.
3. Proposed Architecture & Solutions
We need to modify the invocation of the flow within PocketFlowTaskManager.on_send_task. The approach depends on the nature of the underlying self.flow:
-
Option A (Preferred if Flow is Async): Use
run_async- If the
self.flowprovided by the user is an instance ofpocketflow.AsyncFlow(or a subclass likeA2AFlowwhich inherits from it), replaceself.flow.run(shared)withawait self.flow.run_async(shared). - This leverages PocketFlow's native async execution path, allowing async nodes within the flow to yield control correctly.
- If the
-
Option B (For Sync Flows): Use
asyncio.to_thread- If the
self.flowis a standard synchronouspocketflow.Flow, we cannot directly awaitrun(). - Replace
self.flow.run(shared)withawait asyncio.to_thread(self.flow.run, shared). - This executes the potentially blocking synchronous
runmethod in a separate thread from asyncio's thread pool, preventing it from blocking the main event loop.
- If the
-
Combined Approach (Recommended for Flexibility):
- Check the type of
self.flowat runtime withinon_send_task. - If it's an
AsyncFlow, use Option A (await self.flow.run_async(shared)). - Otherwise (if it's a sync
Flow), use Option B (await asyncio.to_thread(self.flow.run, shared)). - This allows users to provide either type of flow without needing different server configurations.
- Check the type of
4. Implementation Steps
- Modify
PocketFlowTaskManager.on_send_task:- Locate the
self.flow.run(shared)call. - Implement the "Combined Approach" described above using an
isinstancecheck:from pocketflow import AsyncFlow # Add import at the top import asyncio # Add import at the top # ... inside on_send_task ... if isinstance(self.flow, AsyncFlow): await self.flow.run_async(shared) else: await asyncio.to_thread(self.flow.run, shared) # ... rest of the method ...
- Locate the
- Update
AsyncA2ANode: EnsureAsyncA2ANodeis correctly defined and used if async flows are expected (already present incore.py, appears okay). - Review Examples: Update examples like
streaming_agent.pyto ensure they provide anAsyncFlowto the server if they useAsyncNodes. - Update Tests: Modify
tests/test_server.py(specifically the integration tests usingMockFlowor similar) to correctlyawaittheon_send_taskcall and potentially mockrun_asyncorasyncio.to_threadas needed.
5. Impact & Benefits
- Performance: Prevents blocking of the async event loop, significantly improving server concurrency and throughput.
- Responsiveness: Ensures the server remains responsive to other requests even while a long-running task is processing.
- Correctness: Properly integrates PocketFlow execution (both sync and async) into the async server environment.
- Flexibility: Allows users to build A2A agents using either synchronous or asynchronous PocketFlow graphs.
6. Potential Considerations
- Running synchronous code via
asyncio.to_threadhas overhead. If performance is absolutely critical and the flow logic can be made fully async, usingAsyncFlowandrun_asyncis generally more efficient. - Error handling within the
to_threadcall needs to be robust.
GitHub Issue Draft (Issue 1: Sync in Async)
Title: Bug: Synchronous flow.run() blocks event loop in async PocketFlowTaskManager.on_send_task
Labels: bug, performance, async, server, task-manager
Body:
Problem Description:
The PocketFlowTaskManager.on_send_task method is an async def function, but it currently calls the synchronous self.flow.run(shared). If the underlying PocketFlow Flow or its nodes perform blocking operations, this call blocks the FastAPI/Uvicorn event loop, severely impacting server concurrency and responsiveness.
Location: a2aflow/tasks.py, within the PocketFlowTaskManager.on_send_task method.
Expected Behavior:
The invocation of the PocketFlow Flow should be non-blocking, allowing the async server to handle other requests concurrently.
Proposed Solution:
Modify PocketFlowTaskManager.on_send_task to execute the flow asynchronously:
- Check if
self.flowis an instance ofpocketflow.AsyncFlow. - If yes, use
await self.flow.run_async(shared). - If no (it's a sync
pocketflow.Flow), useawait asyncio.to_thread(self.flow.run, shared).
Acceptance Criteria:
- The call to execute the PocketFlow
Flowwithinon_send_taskno longer blocks the event loop. - The server remains responsive to other requests (e.g.,
tasks/get) while a task is being processed viatasks/send. - Both synchronous and asynchronous PocketFlow
Flowdefinitions can be successfully executed by theA2AServer. - Tests are updated to reflect and verify the asynchronous execution pattern.
Impact:
Fixing this is critical for server performance, scalability, and correct async operation.
2. Proposal: Align Models and I/O Handling with A2A Specification
1. Introduction & Problem Statement
The current implementation has several discrepancies regarding A2A data structures and handling:
- Incomplete Models: Pydantic models in
a2aflow/models.pyanda2aflow/tasks.pyare simplified and do not fully represent the A2A specification (specification/json/a2a.json), notably lacking detailedParttypes (TextPart,FilePart,DataPart) and theArtifactstructure. - Fragile Input Parsing:
A2ANode._get_user_queryincore.pyunsafely assumes the first part of the input message exists and is text, making it prone to errors with multi-part messages or non-text inputs. - Limited Output Formatting: The
PocketFlowTaskManagercurrently assumes simple text output (a2a_output_parts = [{"type": "text", "text": result}]). There's no defined way for PocketFlow nodes to signal the creation of multi-modalPartsorArtifactsfor inclusion in the A2ATaskresponse. - Model Location: A2A-specific Pydantic models are split between
models.pyandtasks.py, leading to potential confusion.
2. Goals
- Achieve full alignment of A2AFlow's data models with the
a2a.jsonspecification, particularly forMessage,Part, andArtifact. - Implement robust parsing of multi-modal A2A input messages.
- Define and implement a clear convention for PocketFlow nodes to produce multi-modal A2A outputs (multiple parts, artifacts).
- Consolidate A2A-specific Pydantic models into a single location (
a2aflow/models.py). - Enable A2AFlow agents to correctly handle and generate multi-modal content as per the A2A spec.
3. Proposed Architecture & Solutions
-
Consolidate and Enhance Models:
- Move all A2A-specific Pydantic models (including
Task,TaskStatus,SendTaskRequest,SendTaskResponsecurrently intasks.py) toa2aflow/models.py. - Define detailed models in
models.pybased ona2a.json:TextPart,FileContent,FilePart,DataPart.Part = Annotated[Union[TextPart, FilePart, DataPart], Field(discriminator="type")](using Pydantic v2 features).Messagemodel updated to useList[Part]instead of simplecontent: str.Artifactmodel withparts: List[Part].- Update
Taskmodel to use the revisedMessageandArtifactmodels and align other fields (status,error, etc.) precisely with the spec. - Update
AgentCardand other relevant models for full field alignment.
- Move all A2A-specific Pydantic models (including
-
Robust Input Parsing:
- Refactor
A2ANode.prep(or introduce a new helper method called byprep). - Instead of just
_get_user_query, parse the entirerequest.params.message.partslist. - Iterate through the parts and populate the
sharedstore based on part type and the node'sSUPPORTED_CONTENT_TYPES. - Convention Example: Populate
shared['a2a_input_text'](string, concatenated text parts),shared['a2a_input_files'](list ofFileContentobjects),shared['a2a_input_data'](list ofDataPartdicts). Nodes would then access these specific keys in theirexec.
- Refactor
-
Structured Output Formatting:
- Define a convention for nodes to write results back to
shared. - Convention Example: Nodes write to
shared['a2a_output_parts'](a list ofPartmodel instances) and/orshared['a2a_output_artifacts'](a list ofArtifactmodel instances). - Modify
PocketFlowTaskManager.on_send_task(afterflow.run/run_async) to:- Read
shared['a2a_output_parts']andshared['a2a_output_artifacts']. - Construct the A2A
Task.status.message(using the parts) andTask.artifactsbased on these shared variables. - If these keys aren't present, default to a simple text response based on
shared['result']as a fallback.
- Read
- Define a convention for nodes to write results back to
4. Implementation Steps
- Refactor
models.py/tasks.py: Move models, implement detailedPartandArtifactstructures, alignTask,Message,AgentCardetc., witha2a.json. - Refactor
core.py: UpdateA2ANode.prep(or add a new parsing method) to robustly handlemessage.partsand populatesharedaccording to the new input convention. UpdateMultiModalNodeaccordingly. - Refactor
tasks.py: UpdatePocketFlowTaskManager.on_send_taskto readshared['a2a_output_parts']andshared['a2a_output_artifacts']and use them to construct theTaskobject'smessageandartifactsfields, falling back toshared['result']if necessary. - Update Examples: Modify examples (
multi_turn_agent.py, etc.) to use the new conventions if they need to handle multi-modal input/output or rely on specific A2A structures. - Update Tests: Adapt tests (
test_core.py,test_server.py) to use the new detailed models and verify correct parsing and formatting of multi-part messages and artifacts.
5. Impact & Benefits
- A2A Compliance: Achieves full compliance with the A2A specification for message, part, and artifact structures.
- Multi-Modal Capability: Enables A2AFlow agents to robustly send and receive text, files (bytes/URI), and structured data.
- Robustness: Makes input handling less prone to errors from unexpected message formats.
- Clarity: Consolidates models and provides clear conventions for data exchange between A2A and PocketFlow.
GitHub Issue Draft (Issue 2: Models & I/O Handling)
Title: Enhance: Align Models & I/O Handling with Full A2A Specification
Labels: enhancement, a2a-compliance, models, multi-modal, parsing
Body:
Problem Description:
- Incomplete Models: Pydantic models in
a2aflow/models.pyandtasks.pyare simplified and do not fully represent the A2A spec (a2a.json), missing detailedParttypes (TextPart,FilePart,DataPart),Artifact, and precise fields inTask,Message, etc. - Fragile Input Parsing:
A2ANode._get_user_queryincore.pyassumes the first message part exists and is text, failing on multi-part or non-text inputs. - Limited Output Formatting: No clear convention exists for PocketFlow nodes to produce multi-modal
PartsorArtifactsthat thePocketFlowTaskManagercan format into the A2ATaskresponse. - Model Location: A2A models are currently split between
models.pyandtasks.py.
Expected Behavior:
A2AFlow should use Pydantic models that accurately reflect the a2a.json specification. Input parsing should handle various Part types robustly. A clear mechanism should exist for PocketFlow nodes to generate structured A2A output including multiple Parts and Artifacts.
Proposed Solution:
- Consolidate & Enhance Models: Move all A2A models to
a2aflow/models.pyand define detailedTextPart,FilePart,DataPart,Artifact, and updateMessage,Task,AgentCardto fully matcha2a.json. - Robust Input Parsing: Refactor
A2ANode.prep(or add a helper) to iterate throughmessage.parts, handle different types, and populatesharedclearly (e.g.,shared['a2a_input_text'],shared['a2a_input_files']). - Structured Output Convention: Define a convention (e.g., nodes write lists of
Part/Artifactinstances toshared['a2a_output_parts']/shared['a2a_output_artifacts']). UpdatePocketFlowTaskManagerto read these keys and format theTaskresponse accordingly.
Acceptance Criteria:
- Pydantic models in
a2aflow/models.pyaccurately reflect the structures inspecification/json/a2a.json. A2ANodecan successfully parse A2A input messages containing multiple parts of different types (text, file, data).- PocketFlow nodes can produce outputs that result in A2A
Taskresponses containing multiplePartsandArtifacts. PocketFlowTaskManagercorrectly formats the finalTaskobject based on the conventions defined for thesharedstore.- Examples and tests are updated to reflect and verify these changes.
Impact:
Enables full A2A compliance for message structures, supports multi-modal agents, increases robustness, and clarifies data handling conventions.