Skip to content

feat: add input-required state handling example#190

Open
lbobinski wants to merge 4 commits intoa2aproject:mainfrom
lbobinski:examples/inputrequired-example
Open

feat: add input-required state handling example#190
lbobinski wants to merge 4 commits intoa2aproject:mainfrom
lbobinski:examples/inputrequired-example

Conversation

@lbobinski
Copy link

Description

This PR adds a new example in examples/inputrequired that demonstrates how to implement multi-turn interactions using the input-required task 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)

  • Implements an AgentExecutor that checks for existing task state.
  • Flow:
    1. Receives a new task -> Replies with a question and sets state to TaskStateInputRequired.
    2. Receives a follow-up message for the existing task -> Processes the input and sets state to TaskStateCompleted.

Client (examples/inputrequired/client)

  • Demonstrates how a client should handle the input-required signal.
  • Flow:
    1. Sends initial request.
    2. Detects TaskStateInputRequired in the response.
    3. Uses a2a.NewMessageForTask to send the requested input, correctly maintaining the TaskID and ContextID.
    4. Verifies the task moves to TaskStateCompleted.

How to Run

  1. Start the Server:

    go run examples/inputrequired/server/main.go
  2. Run the Client:

    go run examples/inputrequired/client/main.go

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @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

  • New Example for Input-Required State: Introduced a new example (examples/inputrequired) demonstrating multi-turn interactions where an agent requests additional input from the user using the input-required task state.
  • Server-Side Implementation: The server-side component (examples/inputrequired/server) implements an AgentExecutor that initially responds with an input-required state and then processes the follow-up input to complete the task.
  • Client-Side Handling: The client-side component (examples/inputrequired/client) showcases how to detect the TaskStateInputRequired signal and correctly send a follow-up message with the required input, maintaining TaskID and ContextID.
  • Documentation Update: The README.md has been updated to include a reference to this new inputrequired example, guiding users to understand agent state transitions and user input handling.
Changelog
  • README.md
    • Added a new line referencing the inputrequired example for agent state transitions and user input handling.
  • examples/inputrequired/client/main.go
    • New file added, implementing a client that initiates a task, handles an input-required response, and sends a follow-up message to complete the task.
    • Demonstrates the use of a2a.NewMessageForTask to maintain task context for subsequent messages.
  • examples/inputrequired/server/main.go
    • New file added, implementing an A2A server with an agentExecutor.
    • The Execute method handles new tasks by setting TaskStateInputRequired and awaiting further input.
    • Processes subsequent messages for existing tasks to transition to TaskStateCompleted.
Activity
  • Pull request created by lbobinski with the title "feat: add input-required state handling example".
  • The pull request description details the purpose, key changes in both server and client, and provides instructions on how to run the new example.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

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.

Comment on lines 73 to 80
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

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.

Comment on lines 35 to 61
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)
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

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)

Comment on lines 93 to 94
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

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)
	}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments