feat: add input-required state handling example#190
feat: add input-required state handling example#190lbobinski wants to merge 4 commits intoa2aproject:mainfrom
Conversation
Summary of ChangesHello @lbobinski, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request enhances the A2A framework by providing a concrete example of how to manage multi-turn conversational flows where an agent needs to solicit further information from a user. It clearly illustrates the protocol for an agent to pause execution, request input, and then resume once the input is provided, thereby improving the framework's ability to handle interactive and stateful agent behaviors. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a valuable example demonstrating multi-turn interactions with the input-required task state. The client and server implementations are clear and effectively showcase the A2A protocol flow for this scenario. I've provided a few suggestions to improve code structure and robustness in the example files by refactoring duplicated logic and enhancing error handling.
There was a problem hiding this comment.
This block of code for sending a message and asserting the response is a task is very similar to the one on lines 52-59. This duplication can be avoided by extracting the logic into a helper function, which would make the main function more readable and easier to maintain.
Here's a possible helper function you could add:
func sendMessageAndExpectTask(ctx context.Context, client *a2aclient.Client, msg *a2a.Message, failMsg string) *a2a.Task {
resp, err := client.SendMessage(ctx, &a2a.MessageSendParams{Message: msg})
if err != nil {
log.Fatalf("%s: %v", failMsg, err)
}
task, ok := resp.(*a2a.Task)
if !ok {
log.Fatalf("Expected *a2a.Task, got %T", resp)
}
return task
}You could then use this helper for both the initial message and the follow-up input.
| log.Printf("Executing request. TaskID: %s", reqCtx.TaskID) | ||
|
|
||
| if reqCtx.StoredTask == nil { | ||
| // New task, ask for input | ||
| log.Println("New task. Asking for input.") | ||
| initialResp := a2a.NewMessageForTask(a2a.MessageRoleAgent, reqCtx, a2a.TextPart{Text: "Please provide more details."}) | ||
|
|
||
| // Set state to input-required and attach the message. | ||
| // Note: We MUST wrap the message in StatusUpdateEvent, otherwise sending a raw Message | ||
| // event will be interpreted as a stateless execution result and the task won't be persisted. | ||
| // We set Final=true to indicate this turn is over, so the server stops processing | ||
| // and the client receives the response. | ||
| statusUpdate := a2a.NewStatusUpdateEvent(reqCtx, a2a.TaskStateInputRequired, initialResp) | ||
| statusUpdate.Final = true | ||
|
|
||
| return q.Write(ctx, statusUpdate) | ||
| } | ||
|
|
||
| // Task exists, this must be the input we asked for | ||
| log.Println("Task exists. Completing task.") | ||
| response := a2a.NewMessageForTask(a2a.MessageRoleAgent, reqCtx, a2a.TextPart{Text: "Thank you for the input! Task completed."}) | ||
|
|
||
| // Complete the task | ||
| statusUpdate := a2a.NewStatusUpdateEvent(reqCtx, a2a.TaskStateCompleted, response) | ||
| statusUpdate.Final = true | ||
|
|
||
| return q.Write(ctx, statusUpdate) |
There was a problem hiding this comment.
The Execute function has two branches that both end up creating a StatusUpdateEvent, setting Final = true, and writing it to the queue. This common logic can be extracted to make the function's intent clearer and reduce duplication. You can determine the message and next state within the if/else block, and then construct and send the StatusUpdateEvent once at the end.
log.Printf("Executing request. TaskID: %s", reqCtx.TaskID)
var respMsg *a2a.Message
var nextState a2a.TaskState
if reqCtx.StoredTask == nil {
// New task, ask for input
log.Println("New task. Asking for input.")
respMsg = a2a.NewMessageForTask(a2a.MessageRoleAgent, reqCtx, a2a.TextPart{Text: "Please provide more details."})
nextState = a2a.TaskStateInputRequired
} else {
// Task exists, this must be the input we asked for
log.Println("Task exists. Completing task.")
respMsg = a2a.NewMessageForTask(a2a.MessageRoleAgent, reqCtx, a2a.TextPart{Text: "Thank you for the input! Task completed."})
nextState = a2a.TaskStateCompleted
}
statusUpdate := a2a.NewStatusUpdateEvent(reqCtx, nextState, respMsg)
statusUpdate.Final = true
return q.Write(ctx, statusUpdate)There was a problem hiding this comment.
The current logging for server shutdown doesn't distinguish between an actual server error and a graceful shutdown. http.Serve returns http.ErrServerClosed on graceful shutdown, and logging this can be misleading. It's better to explicitly check for this error and only log fatal errors.
if err := http.Serve(listener, mux); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server failed: %v", err)
}
Description
This PR adds a new example in
examples/inputrequiredthat demonstrates how to implement multi-turn interactions using theinput-requiredtask state.This example illustrates the core A2A protocol flow where an agent pauses execution to request additional information from the user before proceeding.
Key Changes
Server (
examples/inputrequired/server)AgentExecutorthat checks for existing task state.TaskStateInputRequired.TaskStateCompleted.Client (
examples/inputrequired/client)input-requiredsignal.TaskStateInputRequiredin the response.a2a.NewMessageForTaskto send the requested input, correctly maintaining theTaskIDandContextID.TaskStateCompleted.How to Run
Start the Server:
Run the Client: