Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Upgrade a2a to spec v0.2.3#2144

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Kludex merged 28 commits intopydantic:mainfromphysicsrob:part1-upgrade-a2a-v0.2.3
Jul 10, 2025

Conversation

physicsrob
Copy link
Contributor

Summary

This PR upgrades the fasta2a implementation from A2A protocol v0.1 to v0.2.3. The upgrade introduces significant improvements to conversation continuity through the new A2Acontext_id concept and implements a dual-purpose storage architecture for efficient multi-turn conversations.

Note: This is a breaking change and is not backward compatible. However, given the limited adoption of the previous protocol version, this change prioritizes alignment with the current A2A specification.

Key Changes

Protocol Updates

  • Renamed endpoint fromtasks/send tomessage/send
  • Replacedtype fields withkind throughout the schema
  • Switched from client-generated to server-generated task IDs
  • Updated Message and Task structures to v0.2.3 format
  • Replacedsession_id withcontext_id for conversation continuity and adherence to A2A spec

Conversation Model Enhancements

  • Context-based conversations: Introducedcontext_id to group related messages across multiple tasks
  • Dual-purpose storage:
    • Task storage maintains A2A protocol compliance
    • Context storage preserves rich agent state (tool calls, thinking, etc.)
  • Conversation continuity: Multi-turn conversations now span multiple task executions
  • Storage API improvements:
    • Addedupdate_context() andget_context() methods for agent-specific state
    • Modifiedupdate_task() to acceptnew_messages andnew_artifacts lists
    • Removed deprecatedmessage parameter fromupdate_task()

Schema Enhancements

  • Addedid field toPushNotificationConfig for server-assigned identifiers
  • Added new task states:rejected andauth-required
  • Implemented proper file handling withFileWithBytes andFileWithUri
  • Added type guards (is_task(),is_message()) for better type safety

Artifact Improvements

  • Support for bothTextPart andDataPart in artifacts based on output type
  • Added unique artifact IDs for better tracking
  • Dual output approach:
    • Agent results stored as Messages (for conversation history)
    • Agent results stored as Artifacts (for durable outputs)
  • Enhanced metadata including JSON schema for Pydantic models

AgentWorker Implementation

  • Loads full pydantic-ai message history from context storage
  • Preserves complete conversation state including tool calls and responses
  • Converts between A2A and pydantic-ai message formats as needed
  • Ensures task state validation (only processes 'submitted' tasks)

Test Changes

  • Updated all tests to work with the new protocol format
  • Added test for Pydantic model outputs with JSON schema metadata
  • Added test for monotonic message history growth across contexts
  • Renamed test directory fromtests/fasta2a/ totests/test_fasta2a/ to avoid import conflicts

Breaking Changes

  1. Storage API changes:

    • New required methods:update_context() andget_context()
    • Task submission now requirescontext_id
    • update_task() signature changed
  2. Message format changes:

    • typekind throughout
    • session_idcontext_id
    • New required fields in various message types
  3. Endpoint changes:

    • tasks/sendmessage/send

- Update protocol methods: tasks/send → message/send- Replace 'type' with 'kind' throughout schema- Replace 'session_id' with 'context_id' for conversation tracking- Add Message and Part types (TextPart, FilePart, DataPart)- Implement dual message/artifact approach for agent outputs- Add metadata to artifacts including type info and JSON schema- Add proper error handling with task state updates- Add NotImplementedError stubs for streaming methods- Rename test directory to avoid import conflicts
- Test that Pydantic model outputs are correctly serialized as DataPart- Verify metadata includes type name and JSON schema- Ensure dual message/artifact approach works for complex types- Confirm that both message history and artifacts contain the data
- Add update_context() and get_context() methods to Storage- Store full pydantic-ai message history (including tool calls) in context- Preserve conversation state across multiple tasks with same context_id- Update docs to explain task vs context distinction- Add test for monotonic message history growth- Clean up run_task: remove history_length, add state check, fix comments
@hyperlint-aiHyperlint AI
Copy link
Contributor

PR Change Summary

Upgraded the fasta2a implementation to A2A protocol v0.2.3, introducing significant enhancements for conversation continuity and a dual-purpose storage architecture.

  • Renamed endpoint fromtasks/send tomessage/send and replacedtype fields withkind in the schema.
  • Introducedcontext_id for improved conversation continuity and updated storage architecture for multi-turn conversations.
  • Implemented new methods in the Storage API and made breaking changes to message formats and task submissions.

Modified Files

  • docs/a2a.md

How can I customize these reviews?

Check out theHyperlint AI Reviewer docs for more information on how to customize the review.

If you just want to ignore it on this PR, you can add thehyperlint-ignore label to the PR. Future changes won't trigger a Hyperlint review.

Note specifically for link checks, we only check the first 30 links in a file and we cache the results for several hours (for instance, if you just added a page, you might experience this). Our recommendation is to addhyperlint-ignore to the PR to ignore the link check for this PR.

@physicsrob
Copy link
ContributorAuthor

@Kludex
I noticed your message "Working on this" -- just FYI I spend a bunch of time on yesterday working on pulling out just the upgrade. Here's a draft PR:.

I was going to spend a couple more hours cleaning up before submitting a PR, but it's fairly close. I did end up changing the approach pretty significantly to conversation continuity. There was a pretty big fundamental limitation in the previous incarnation: Since only A2A messages were persisted / retrieved that meant that follow-up tasks could only see messages that were converted to/from A2A's format, and that's fundamentally going to be lossy. In particular tool call results from previous tasks are going to be invisible to the agent for subsequent calls. I personally think that's a big limitation worth addressing.

The one area I'm very much less convinced of my approach: Whether final results should be an artifact or both an artifact and a message. Right now in the PR it generates both. But I was probably going to make it just an artifact

Comment on lines 128 to 130
elif a2a_request['method'] == 'tasks/send': # type: ignore[comparison-overlap]
# Legacy method - no longer supported
raise NotImplementedError('tasks/send is deprecated. Use message/send instead.')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

There's no need for this. Just drop it.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

👍


payload = SendTaskRequest(jsonrpc='2.0', id=None, method='tasks/send', params=task)
content = a2a_request_ta.dump_json(payload, by_alias=True)
request_id = str(uuid.uuid4())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

What is this ID? Is it therequest_id?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Yes, this is the request_id for JSON-RPC. Let me know if you think a clarifying comment would be helpful, or if there's something off about the variable names

Comment on lines 315 to 316
description: NotRequired[str]
"""A description of the data."""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I don't see any description on the DataPart on the specification.

Suggested change
description:NotRequired[str]
"""A description of the data."""

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

yeah not sure how that ended up there. Fixed. thx.

"""A fully formed piece of content exchanged between a client and a remote agent as part of a Message or an Artifact.

Each Part has its own content type and metadata.
"""

TaskState: TypeAlias = Literal['submitted', 'working', 'input-required', 'completed', 'canceled', 'failed', 'unknown']
TaskState: TypeAlias = Literal[
'submitted', 'working', 'input-required', 'completed', 'canceled', 'failed', 'rejected', 'auth-required'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Unknown is still in the specification.

Suggested change
'submitted','working','input-required','completed','canceled','failed','rejected','auth-required'
'submitted','working','input-required','completed','canceled','failed','rejected','auth-required','unknown'

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

👍

Comment on lines 650 to 657
def is_task(response: Task | Message) -> TypeGuard[Task]:
"""Type guard to check if a response is a Task."""
return 'id' in response and 'status' in response and 'context_id' in response and response.get('kind') == 'task'


def is_message(response: Task | Message) -> TypeGuard[Message]:
"""Type guard to check if a response is a Message."""
return 'role' in response and 'parts' in response and response.get('kind') == 'message'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This seems to be for the tests. There's no need to include those in the schema.

Also, on the tests, just check the kind istask ormessage. It's enough to infer the type for the type checker.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

👍

async def send_message(self, request: SendMessageRequest) -> SendMessageResponse:
"""Send a message using the A2A v0.2.3 protocol."""
request_id = request['id']
task_id = str(uuid.uuid4())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This should be generated in thesubmit_task - so we have the task id in thetask object after.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Agreed. 👍

Comment on lines 168 to 169
"""Stream messages using Server-Sent Events. Not implemented."""
raise NotImplementedError('message/stream method is not implemented yet.')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Suggested change
"""Stream messages using Server-Sent Events. Not implemented."""
raiseNotImplementedError('message/stream method is not implemented yet.')
"""Stream messages using Server-Sent Events."""
raiseNotImplementedError('message/stream method is not implemented yet.')

physicsroband others added15 commitsJuly 7, 2025 21:15
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
…ation  - Change DataPart.data type from Any to dict[str, Any] per A2A spec  - Wrap non-dict agent results as {"result": <data>} for consistency  - Remove DataPart.description field (not in spec)  - Improve message vs artifact separation:    - String outputs appear in both messages and artifacts    - Structured data only appears as artifacts (not duplicated in messages)  - Update tests to reflect new behavior  - Update docs to clarify artifact handling
@physicsrob
Copy link
ContributorAuthor

@Kludex Just finished my updates.

Changes:

  1. I believe I addressed all of your first round of feedback (thanks!)
  2. A little bit of misc tidying on my part
  3. Changed the logic for creating for handling agent results. The new logic is that string results are treated as messages AND also result in a TextPart artifact, whereas structured results are treated as artifacts only and do not show up in the message history.

@physicsrobphysicsrob changed the title[DRAFT] Upgrade a2a v0.2.3Upgrade a2a to spec v0.2.3Jul 8, 2025
Copy link
Member

@KludexKludex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'm reverting the test name directory change. Please avoid changes outside the scope of the PR.

Comment on lines 121 to 124
elif a2a_request['method'] == 'message/stream':
raise NotImplementedError(
'message/stream method is not implemented yet. Streaming support will be added in a future update.'
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

We already handle all the non supported methods a bit down here, so I'll drop this for now.

) -> SendMessageResponse:
"""Send a message using the A2A protocol.

Returns a JSON-RPC response containing either a result (Task | Message) or an error.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I understand by the spec is possible to return both, but we always return Task.

Comment on lines 276 to 277
data:str
"""Thebase64 encoded data."""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It's bytes now.

"""Update the state of a task. Appends artifacts and messages, if specified."""

@abstractmethod
async def update_context(self, context_id: str, context: Any) -> None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Any is usually a sign that something is wrong.

We can make storage to be generic oncontext.

Comment on lines 125 to 128
if task is None:
raise ValueError(f'Task {params["id"]} not found')
if 'context_id' not in task:
raise ValueError('Task must have a context_id')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Acontext_id is always provided in a task - by type definition.

Comment on lines -121 to -122
# TODO(Marcelo): We need to have a way to communicate when the task is set to `input-required`. Maybe
# a custom `output_type` with a `more_info_required` field, or something like that.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

You removed my TODO, but I don't think we solve it?

@KludexKludex requested a review fromCopilotJuly 8, 2025 07:28
Copy link

@CopilotCopilotAI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Pull Request Overview

This PR upgrades the A2A implementation from protocol v0.1 to v0.2.3 to support conversation continuity via a newcontext_id, dual-purpose storage for protocol tasks and agent context, and updated schema and endpoints to match the v0.2.3 specification.

  • Introducecontext_id in place ofsession_id and rename endpointtasks/sendmessage/send
  • Implement dual storage API withget_context()/update_context() alongside task storage
  • Enhance schema with new task states (rejected,auth-required), named artifacts (artifact_id), part discriminators (kind), and message identifiers

Reviewed Changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
tests/test_a2a.pyUpdated tests to usesend_message,kind,message_id,context_id, and validate JSON schema metadata
pydantic_ai_slim/pydantic_ai/agent.pySwapped import ofProviderAgentProvider and updatedto_a2a() parameter type
pydantic_ai_slim/pydantic_ai/_a2a.pyOverhauledAgentWorker to load/update context, convert messages and artifacts under new schema
fasta2a/fasta2a/worker.pyMinor signature rename in abstractbuild_message_history
fasta2a/fasta2a/task_manager.pyReplacedsend_task* withsend_message/stream_message, adapted parameters to v0.2.3
fasta2a/fasta2a/storage.pyAddedupdate_context()/get_context(), switched storage to trackcontext_id and new message/artifact lists
fasta2a/fasta2a/schema.pyUpdated TypedDicts: addedkind,context_id,message_id, new security schemes, task states, artifact IDs, etc.
fasta2a/fasta2a/client.pyRenamedsend_tasksend_message, updated request/response adapters
fasta2a/fasta2a/applications.pyUpdated agent card schema and route handling formessage/send
docs/a2a.mdDocumentedcontext_id, dual-purpose storage, and new artifact behavior
Comments suppressed due to low confidence (1)

fasta2a/fasta2a/task_manager.py:114

  • [nitpick] The client still usestasks/get for retrieval butmessage/send for creation, which may be confusing. For symmetry withmessage/send, consider renamingtasks/getmessage/get or update documentation to clarify the mixed-use.
    async def send_message(self, request: SendMessageRequest) -> SendMessageResponse:

if task is None:
raise ValueError(f'Task {params["id"]} not found')

# TODO(Marcelo): Should we lock `run_task` on the `context_id`?
Copy link
Preview

CopilotAIJul 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Concurrent calls torun_task for the samecontext_id can race when updating context storage. Consider adding a per-context_id lock or semaphore to serialize context updates.

Copilot uses AI. Check for mistakes.

Comment on lines 117 to 118
# Generic parameters are reversed compared to Agent because AgentDepsT has a default
class AgentWorker(Worker, Generic[WorkerOutputT, AgentDepsT]):
Copy link
Preview

CopilotAIJul 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

[nitpick] The generic parameter order is reversed compared toAgent[Deps, Output]. Aligning the parameter order withAgent (i.e.Generic[AgentDepsT, WorkerOutputT]) would reduce confusion in type annotations.

Suggested change
#Generic parameters are reversed compared toAgentbecause AgentDepsT has a default
classAgentWorker(Worker,Generic[WorkerOutputT,AgentDepsT]):
#Aligning generic parameter order withAgentfor consistency
classAgentWorker(Worker,Generic[AgentDepsT,WorkerOutputT]):

Copilot uses AI. Check for mistakes.

else:
# For structured data, create a DataPart
try:
# Try using TypeAdapter for proper serialization
Copy link
Preview

CopilotAIJul 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

[nitpick] The fallback for structured data does not handle PydanticBaseModel instances explicitly. Consider detectingisinstance(result, BaseModel) and usingresult.model_dump() (or equivalent) to preserve all fields.

Copilot uses AI. Check for mistakes.

@holtskinner
Copy link

holtskinner commentedJul 8, 2025
edited
Loading

NOTE: The A2A Protocol has been updated to v0.2.5, I would recommend adding in the updates between versions 0.2.3 and 0.2.5https://github.com/a2aproject/A2A/releases

In addition, it could make sense to change FastA2A over to use the officialA2A Python SDK under the hood to make it easier to update to newer versions of the protocol.

@Kludex
Copy link
Member

NOTE: The A2A Protocol has been updated to v0.2.5, I would recommend adding in the updates between versions 0.2.3 and 0.2.5a2aproject/A2A/releases

In addition, it could make sense to change FastA2A over to use the officialA2A Python SDK under the hood to make it easier to update to newer versions of the protocol.

I'll do that.


I'm moving the FastA2A to its own repository.

@KludexKludexenabled auto-merge (squash)July 10, 2025 06:14
@KludexKludex merged commit72bce93 intopydantic:mainJul 10, 2025
17 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

Copilot code reviewCopilotCopilot left review comments

@KludexKludexKludex approved these changes

Assignees

@KludexKludex

Labels
None yet
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

3 participants
@physicsrob@holtskinner@Kludex

[8]ページ先頭

©2009-2025 Movatter.jp