[Go to site: main page, start]

Skip to content

智能体工作流的人工输入

Supported in ADKPython v2.0.0Go v2.0.0

能够在数据输入、决策验证或操作授权等环节请求人工输入,是许多智能体驱动工作流的重要组成部分。ADK 中基于图的工作流可以包含专门为获取人工输入而设计的人机交互(HITL)节点。这些节点不需要运行人工智能(AI)模型,从而使输入过程更具可预测性和可靠性。

开始使用

你可以使用 RequestInput 类和一个文本提示在图中实现人工输入节点。以下代码示例展示了如何在 Workflow 图中添加人工输入节点:

from google.adk.events import RequestInput
from google.adk import Workflow

def step1(): # 人工输入步骤
  yield RequestInput(message="Enter a number:")

def step2(node_input):
  return node_input * 2

root_agent = Workflow(
    name="root_agent",
    edges=[('START', step1, step2)],
)

在此代码示例中,step1 会暂停智能体的执行,直到系统收到用户的输入。一旦系统收到用户的输入,该输入就会被传递到下一个节点。

在 ADK Go v2.0.0 中,HITL 图节点通过 workflow.NewEmittingFunctionNodeworkflow.ResumeOrRequestInput 构建。这是 Python 中 RequestInput 节点的直接等价物:

  • 首次执行时,workflow.ResumeOrRequestInput 发出一个 session.RequestInput 事件(以 Event.RequestedInput 的形式呈现)并返回 ErrNodeInterrupted,从而暂停工作流。
  • 在人工回复后,节点会从顶部重新调用RerunOnResume: &true),ResumeOrRequestInput 返回回复内容,该内容通过 event.Output 作为类型化输入流向下一个节点。
// newGraphHITLWorkflow demonstrates a graph HITL node using
// workflow.NewEmittingFunctionNode and workflow.ResumeOrRequestInput.
//
// This is the Go equivalent of the Python RequestInput node:
//
//  def step1():  # Human input step
//      yield RequestInput(message="Enter a number:")
//
//  def step2(node_input):
//      return node_input * 2
//
//  root_agent = Workflow(
//      name="root_agent",
//      edges=[('START', step1, step2)],
//  )
//
// On the first pass, step1Node emits a RequestInput event and pauses the
// workflow (ErrNodeInterrupted). After the human replies, the node is re-run
// and ResumeOrRequestInput returns the reply, which flows as typed input to
// step2Node via event.Output.
func newGraphHITLWorkflow() (agent.Agent, error) {
    rerun := true

    // step1Node: pauses for human input on the first pass, returns the
    // human's reply on resume. workflow.ResumeOrRequestInput handles both
    // phases — no manual re-entry bookkeeping needed.
    step1Node := workflow.NewEmittingFunctionNode[any, string]("step1",
        func(ctx agent.Context, _ any, emit func(*session.Event) error) (string, error) {
            reply, err := workflow.ResumeOrRequestInput(ctx, emit, session.RequestInput{
                InterruptID: "enter_number",
                Message:     "Enter a number:",
            })
            if err != nil {
                // ErrNodeInterrupted on first pass — workflow pauses here.
                return "", err
            }
            // On resume, reply is the human's text response.
            number, _ := reply.(string)
            return number, nil
        },
        workflow.NodeConfig{RerunOnResume: &rerun},
    )

    // step2Node: receives the human's input as its typed string input via
    // event.Output and doubles the number.
    step2Node := workflow.NewFunctionNode("step2",
        func(_ agent.Context, input string) (string, error) {
            return fmt.Sprintf("You entered: %s (doubled: %s%s)", input, input, input), nil
        },
        workflow.NodeConfig{},
    )

    return workflowagent.New(workflowagent.Config{
        Name:        "root_agent",
        Description: "Pauses for a number from the user, then doubles it.",
        Edges:       workflow.Chain(workflow.Start, step1Node, step2Node),
    })
}

配置选项

人工输入节点可以使用 RequestInput 类,支持以下配置选项:

  • message 向用户提供的说明人工输入请求的文本。
  • payload 作为人工输入请求一部分的结构化数据。
  • response_schema 人工响应必须遵循的数据结构。

注意:响应 schema 输入限制

对于 response_schema 设置,RequestInput 类不会自动重新格式化人工响应以匹配指定的数据结构。人工响应必须以指定格式提供。为了获得更好的用户体验,可以考虑提供用户界面来收集结构化数据,或使用智能体节点将非结构化数据转换为所需的格式。

session.RequestInput 携带以下字段,它们与 Python 的 RequestInput 参数直接对应:

  • InterruptIDstring):此暂停点的唯一标识符。使用稳定的前缀加 UUID 来避免跨工作流运行时的冲突。等同于 Python 中的隐式中断 ID。
  • Messagestring):显示给用户的人类可读提示。等同于 Python 的 message 参数。
  • Payloadany):可选的结构化数据,随提示一起发送,以便客户端渲染额外的上下文。等同于 Python 的 payload 参数。

workflow.NodeConfig.RerunOnResume 控制恢复时的行为:

  • &true:节点主体从顶部重新执行;ResumeOrRequestInput 在第二次执行时返回人工回复。使用 ResumeOrRequestInput 的节点必须设置此项。
  • &falsenil(叶子节点默认值):回复被路由到节点的后继节点作为输入,跳过被中断的节点。

注意:来自客户端的结构化响应

ADK Go 不会自动解析或验证人工回复负载的结构。如果你的工作流需要结构化反馈,请在前端界面或下游智能体节点中对响应进行验证,然后再执行后续操作。

人工输入示例

以下代码示例展示了更详细的人工输入请求。

请求带消息和负载的输入

以下代码示例展示了如何在工作流节点中构建 RequestInput 对象,包括 负载响应 schema。在此示例中,ActivitiesList 预期由一个组成活动列表的智能体节点完成,而 get_user_feedback() 节点向用户请求反馈。

class ActivitiesList(BaseModel):
   """行程应为每个活动的字典列表。每个活动包含名称和描述"""
   itinerary: List[Dict[str, str]]

class UserFeedback(BaseModel):
   """用户预期的响应结构。"""
   user_response: str

async def get_user_feedback(node_input: ActivitiesList):
   """
   获取用户对智能体初始行程的意见,以便扩展、更改列表或退出循环
   """
   message = (
       f"""
       这是你推荐的基础行程:\n{node_input}\n\n
       这些项目中哪些吸引了你(如果有)?
       """
   )

   yield RequestInput(
       message=message,
       payload=node_input,
        response_schema=UserFeedback,
   )

以下代码示例展示了一个三节点图:一个构建器节点生成结构化行程,一个 HITL 节点将其作为 Payload 与提示一起发送,最后一个节点根据用户的反馈执行操作。Payload 字段允许客户端在用户回复之前渲染完整的行程:

// ItineraryItem represents a single activity in a travel plan.
type ItineraryItem struct {
    Name        string `json:"name"`
    Description string `json:"description"`
}

// newItineraryReviewWorkflow demonstrates a graph HITL node that sends a
// structured payload alongside the input prompt so the client can render
// additional context for the user. This mirrors Python's:
//
//  async def get_user_feedback(node_input: ActivitiesList):
//      yield RequestInput(
//          message="Which items appeal to you?",
//          payload=node_input,
//          response_schema=UserFeedback,
//      )
func newItineraryReviewWorkflow() (agent.Agent, error) {
    rerun := true

    // buildItineraryNode: generates an itinerary and passes it to the HITL
    // node as its typed output via event.Output.
    buildItineraryNode := workflow.NewFunctionNode("build_itinerary",
        func(_ agent.Context, _ any) ([]ItineraryItem, error) {
            return []ItineraryItem{
                {Name: "Eiffel Tower", Description: "Iconic iron lattice tower."},
                {Name: "Louvre Museum", Description: "World's largest art museum."},
                {Name: "Seine River Cruise", Description: "Scenic boat tour of Paris."},
            }, nil
        },
        workflow.NodeConfig{},
    )

    // reviewNode: sends the itinerary as payload alongside the prompt so the
    // client can display it. On resume, the human's selection is returned.
    reviewNode := workflow.NewEmittingFunctionNode[[]ItineraryItem, string]("get_user_feedback",
        func(ctx agent.Context, itinerary []ItineraryItem, emit func(*session.Event) error) (string, error) {
            reply, err := workflow.ResumeOrRequestInput(ctx, emit, session.RequestInput{
                InterruptID: "itinerary_review",
                Message:     fmt.Sprintf("Here is your recommended itinerary (%d activities). Which items appeal to you?", len(itinerary)),
                Payload:     itinerary, // structured payload rendered by the client
            })
            if err != nil {
                // ErrNodeInterrupted on first pass — workflow pauses here.
                return "", err
            }
            feedback, _ := reply.(string)
            return feedback, nil
        },
        workflow.NodeConfig{RerunOnResume: &rerun},
    )

    // finalNode: receives the user's feedback and produces a confirmation.
    finalNode := workflow.NewFunctionNode("finalize",
        func(_ agent.Context, feedback string) (string, error) {
            return fmt.Sprintf("Itinerary finalised with your feedback: %q", feedback), nil
        },
        workflow.NodeConfig{},
    )

    return workflowagent.New(workflowagent.Config{
        Name:        "concierge_workflow",
        Description: "Builds an itinerary, asks the user for feedback, then finalises.",
        Edges:       workflow.Chain(workflow.Start, buildItineraryNode, reviewNode, finalNode),
    })
}

工具确认:LLM 智能体中的审批提示

工具确认是一种独立的、LLM 智能体级别的机制,用于是/否审批提示。与图 HITL 节点不同,工具确认在 llmagent 工具函数内部工作,而不是作为独立的图节点。当你希望 LLM 智能体在执行特定工具调用之前暂停并请求审批时,这个机制非常有用。

以下代码示例展示了如何在工作流节点中构建 RequestInput 对象,包括 响应 schema

async def initial_prompt(ctx: Context):
   """请求用户提供行程信息"""
   input_message = """
       这是一个交互式礼宾工作流,旨在为你在所选城市制定一份出色的行程。
       如果你能提供一些关于你自己的信息或你通常的需求,我可以更好地为你个性化定制行程。
       例如,输入你的:
           城市(必填),
           年龄,
           兴趣爱好,
           你喜欢的景点示例
   """
   yield RequestInput(message=input_message, response_schema=str)

functiontool.Config 中设置 RequireConfirmation: true 可在工具执行前进行静态的是/否审批,或者从工具内部调用 ctx.RequestConfirmation 来设置自定义提示消息:

// DoubleNumberArgs holds the input for the doubleNumber tool.
type DoubleNumberArgs struct {
    Number int `json:"number" jsonschema:"The number to double."`
}

// DoubleNumberResults holds the output of the doubleNumber tool.
type DoubleNumberResults struct {
    Result int `json:"result"`
}

// doubleNumber is a tool that doubles the given number.
// Because RequireConfirmation is true, the framework automatically pauses
// execution and emits an "adk_request_confirmation" event to the client before
// running the tool. The client must reply with a FunctionResponse confirming
// or denying the action.
func doubleNumber(_ agent.Context, args DoubleNumberArgs) (DoubleNumberResults, error) {
    return DoubleNumberResults{Result: args.Number * 2}, nil
}

// newSimpleHITLAgent creates an LLM agent with a tool that always requires
// user confirmation before it executes (tool-confirmation pattern).
func newSimpleHITLAgent(ctx context.Context) (agent.Agent, error) {
    model, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
    if err != nil {
        return nil, fmt.Errorf("failed to create model: %w", err)
    }

    doubleNumberTool, err := functiontool.New(
        functiontool.Config{
            Name:                "double_number",
            Description:         "Doubles the given number. Requires user approval before running.",
            RequireConfirmation: true,
        },
        doubleNumber,
    )
    if err != nil {
        return nil, fmt.Errorf("failed to create tool: %w", err)
    }

    return llmagent.New(llmagent.Config{
        Name:        "double_number_agent",
        Model:       model,
        Instruction: "You are a helpful assistant. When asked to double a number, use the double_number tool.",
        Tools:       []tool.Tool{doubleNumberTool},
    })
}

使用自定义提示和手动重入处理:

// BookFlightArgs holds the input for the bookFlight tool.
type BookFlightArgs struct {
    Origin      string `json:"origin"      jsonschema:"Departure airport code."`
    Destination string `json:"destination" jsonschema:"Arrival airport code."`
    Date        string `json:"date"        jsonschema:"Travel date in YYYY-MM-DD format."`
}

// BookFlightResults holds the outcome of the bookFlight tool.
type BookFlightResults struct {
    Status        string `json:"status"`
    ConfirmNumber string `json:"confirm_number,omitempty"`
}

// bookFlight is a tool that pauses for human approval before completing a
// booking (tool-confirmation pattern with a custom hint message).
func bookFlight(ctx agent.Context, args BookFlightArgs) (BookFlightResults, error) {
    if confirmation := ctx.ToolConfirmation(); confirmation != nil {
        if !confirmation.Confirmed {
            return BookFlightResults{Status: "Booking cancelled by user."}, nil
        }
        return BookFlightResults{
            Status:        "Booking confirmed.",
            ConfirmNumber: "FLT-20251031",
        }, nil
    }

    hint := fmt.Sprintf(
        "The agent wants to book a flight from %s to %s on %s. Do you approve?",
        args.Origin, args.Destination, args.Date,
    )
    if err := ctx.RequestConfirmation(hint, nil); err != nil {
        return BookFlightResults{}, fmt.Errorf("failed to request confirmation: %w", err)
    }
    return BookFlightResults{Status: "Awaiting user approval."}, nil
}

// newHITLWithHintAgent creates an LLM agent whose bookFlight tool manually
// requests confirmation with a descriptive hint (tool-confirmation pattern).
func newHITLWithHintAgent(ctx context.Context) (agent.Agent, error) {
    model, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
    if err != nil {
        return nil, fmt.Errorf("failed to create model: %w", err)
    }

    bookFlightTool, err := functiontool.New(
        functiontool.Config{
            Name:        "book_flight",
            Description: "Books a flight between two airports on a given date.",
        },
        bookFlight,
    )
    if err != nil {
        return nil, fmt.Errorf("failed to create tool: %w", err)
    }

    return llmagent.New(llmagent.Config{
        Name:        "flight_booking_agent",
        Model:       model,
        Instruction: "You are a flight booking assistant. Help the user book flights.",
        Tools:       []tool.Tool{bookFlightTool},
    })
}