Agent 使用文档
Agent 是 tRPC-Agent-Go 框架的核心执行单元,负责处理用户输入并生成相应的响应。每个 Agent 都实现了统一的接口,支持流式输出和回调机制。
框架提供了多种类型的 Agent,包括 LLMAgent、ChainAgent、ParallelAgent、CycleAgent 和 GraphAgent。本文重点介绍 LLMAgent,其他 Agent 类型以及多 Agent 系统的详细介绍请参考 Multi-Agent 。
快速开始
推荐使用方式:Runner
我们强烈推荐使用 Runner 来执行 Agent,而不是直接调用 Agent 接口。Runner 提供了更友好的接口,集成了 Session、Memory 等服务,让使用更加简单。
📖 了解更多: 详细的使用方法请参考 Runner
本示例使用 OpenAI 的 GPT-4o-mini 模型。在开始之前,请确保您已准备好相应的 OPENAI_API_KEY 并通过环境变量导出:
export OPENAI_API_KEY = "your_api_key"
此外,框架还支持兼容 OpenAI API 的模型,可通过环境变量进行配置:
export OPENAI_BASE_URL = "your_api_base_url"
export OPENAI_API_KEY = "your_api_key"
创建模型实例
首先需要创建一个模型实例,这里使用 OpenAI 的 GPT-4o-mini 模型:
import "trpc.group/trpc-go/trpc-agent-go/model/openai"
modelName := flag . String ( "model" , "gpt-4o-mini" , "Name of the model to use" )
flag . Parse ()
// 创建 OpenAI 模型实例
modelInstance := openai . New ( * modelName , openai . Options {})
配置生成参数
设置模型的生成参数,包括最大 token 数、温度以及是否使用流式输出等:
import "trpc.group/trpc-go/trpc-agent-go/model"
maxTokens := 1000
temperature := 0.7
genConfig := model . GenerationConfig {
MaxTokens : & maxTokens , // 最大生成 token 数
Temperature : & temperature , // 温度参数,控制输出的随机性
Stream : true , // 启用流式输出
}
如果没有显式传入 llmagent.WithGenerationConfig(...),LLMAgent
默认会透传零值 model.GenerationConfig{},因此默认是非流式
(Stream=false)。如果你需要流式输出,请显式设置
Stream: true,或在单次请求上使用 agent.WithStream(true)。
某些上层封装可能会自行设置不同的默认值,例如 OpenClaw 会显式开启流式。
创建 LLMAgent
使用模型实例和配置创建 LLMAgent,同时设置 Agent 的 Description 与 Instruction。
Description 用于描述 Agent 的基本功能和特性,Instruction 则定义了 Agent 在执行任务时应遵循的具体指令和行为准则。
import "trpc.group/trpc-go/trpc-agent-go/agent/llmagent"
llmAgent := llmagent . New (
"demo-agent" , // Agent 名称
llmagent . WithModel ( modelInstance ), // 设置模型
llmagent . WithDescription ( "A helpful AI assistant for demonstrations" ), // 设置描述
llmagent . WithInstruction ( "Be helpful, concise, and informative in your responses" ), // 设置指令
llmagent . WithGenerationConfig ( genConfig ), // 设置生成参数
)
Session 历史中的合成错误文案
错误事件没有 assistant content 时,Runner 可能补充通用文案,使事件仍可展示,
并能作为完整的 user/assistant 轮次持久化。ErrorMessage 插件也可以为同一目的
提供自定义文案。这类内容是框架合成的展示数据,并不是模型输出。
默认情况下,LLMAgent 不会删除或改写对外发送及持久化事件中的 assistant content
与结构化错误,但在构造后续模型请求时会省略框架合成的错误文案。如果省略后出现
相邻 user 消息,请求投影层会在本地合并它们,以维持模型服务可接受的消息序列。
升级前的 Session 会通过一组尽力而为的特征识别:事件带有结构化错误、Runner
完全一致的通用兜底文案及其 "error" finish reason,并且没有其他消息载荷。
无标记事件不具备确定的来源信息,因此,与这组旧版特征完全一致的真实响应无法
区分;如果这类响应必须继续对模型可见,请启用下方兼容选项。
如果应用明确依赖旧版上下文行为,可以显式恢复:
llmAgent := llmagent . New (
"demo-agent" ,
llmagent . WithModel ( modelInstance ),
llmagent . WithIncludeSyntheticErrorMessages ( true ),
)
GraphAgent 从 Session 历史初始化图消息状态时采用相同默认行为,并提供对应的
graphagent.WithIncludeSyntheticErrorMessages 兼容选项。详见
Graph 指南 。
占位符变量(状态注入)
LLMAgent 会自动在 Instruction 和可选的 SystemPrompt 中注入状态。支持的占位符语法:
{key}:替换为会话状态中键 key 对应的字符串值(可通过 invocation.Session.SetState("key", ...) 或 SessionService 写入)
{key?}:可选;如果不存在,替换为空字符串
{user:subkey} / {app:subkey} / {temp:subkey}:访问用户/应用/临时命名空间(SessionService 会把 app/user 作用域的状态合并进 session,并带上前缀)
{invocation:subkey} :替换为 fmt.Sprintf("%+v",invocation.state["subkey"]) 的值,(可以通过 invocation.SetState(k,v) 来设置)。
{runtime:subkey}:读取 RunOptions.RuntimeState 中的请求级状态(通过 agent.WithRuntimeState 或 agent.MergeRuntimeState 设置)
注意:
对于非可选的 {key},若找不到则保留原样(便于 LLM 感知缺失上下文)
所有受支持的占位符都可以在右花括号前添加 ? 表示可选,例如 {runtime:document?}
无前缀及 app/user/temp 前缀读取会话状态;invocation: 和 runtime: 只读取各自的状态,不会回退到会话状态
RuntimeState 中的字符串按原文注入,基本类型使用 JSON 文本表示,对象和数组以 JSON 注入
示例:
llm := llmagent . New (
"research-agent" ,
llmagent . WithModel ( modelInstance ),
llmagent . WithInstruction (
"You are a research assistant. Focus: {research_topics}. " +
"User interests: {user:topics?}. App banner: {app:banner?}." +
"Invocation case: {invocation:case}. Draft: {runtime:document?}" ,
),
)
inv := agent . NewInvocation ()
inv . SetState ( "case" , "case-1" )
runOptions := [] agent . RunOption {
agent . WithRuntimeState ( map [ string ] any { "document" : "Current draft" }),
}
// 通过 SessionService 初始化状态(用户态/应用态 + 会话本地键)
_ = sessionService . UpdateUserState ( ctx , session . UserKey { AppName : app , UserID : user }, session . StateMap {
"topics" : [] byte ( "quantum computing, cryptography" ),
})
_ = sessionService . UpdateAppState ( ctx , app , session . StateMap {
"banner" : [] byte ( "Research Mode" ),
})
// 无前缀键直接存到 session.State
_ , _ = sessionService . CreateSession ( ctx , session . Key { AppName : app , UserID : user , SessionID : sid }, session . StateMap {
"research_topics" : [] byte ( "AI, ML, DL" ),
})
进一步阅读:
示例:examples/placeholder、examples/outputkey
Session API:docs/mkdocs/zh/session/index.md
使用 Runner 执行 Agent
使用 Runner 来执行 Agent,这是推荐的使用方式:
import "trpc.group/trpc-go/trpc-agent-go/runner"
// 创建 Runner
runner := runner . NewRunner ( "demo-app" , llmAgent )
// 直接发送消息,无需创建复杂的 Invocation
message := model . NewUserMessage ( "Hello! Can you tell me about yourself?" )
eventChan , err := runner . Run ( ctx , "user-001" , "session-001" , message )
if err != nil {
log . Fatalf ( "执行 Agent 失败: %v" , err )
}
中断 Agent 运行(取消)
在 Go 里,context.Context(常命名为 ctx)不仅用于“传参”,还可以携带:
取消信号 (调用 cancel())
截止时间 (deadline / timeout)
框架会用 ctx 来安全地停止正在运行的 agent。
如何停止一个正在运行的 agent
取消你传给 Runner.Run 的同一个 ctx。不要只停止读取事件通道。
ctx , cancel := context . WithCancel ( context . Background ())
defer cancel ()
// 假设 r 是通过 runner.NewRunner(...) 创建的 runner.Runner。
eventCh , err := r . Run ( ctx , "user-001" , "session-001" , message )
if err != nil {
return err
}
go func () {
time . Sleep ( 2 * time . Second )
cancel ()
}()
for range eventCh {
// 一直读到通道关闭:要么 ctx 被取消,要么 run 正常结束。
}
Ctrl+C(命令行程序)
ctx , stop := signal . NotifyContext ( context . Background (), os . Interrupt )
defer stop ()
// 假设 r 是通过 runner.NewRunner(...) 创建的 runner.Runner。
eventCh , err := r . Run ( ctx , "user-001" , "session-001" , message )
if err != nil {
return err
}
for range eventCh {
}
取消是“协作式”的:你的代码需要检查 ctx.Done() 并尽快返回。
自定义 Agent 在长循环里要 select 监听 ctx.Done()。
Tool 做网络/DB 调用时建议传入 ctx(这样这些调用也能被取消)。
更完整的 run 控制说明(requestID cancel、StopError、超时等)见
docs/mkdocs/zh/runner.md。
消息可见性选项
当前 Agent 可在需要时根据不同场景控制其对其他 Agent 生成的消息以及历史会话消息的可见性进行管理,可通过相关选项配置进行管理。
在与 model 交互时仅将可见的内容输入给模型。
TIPS:
- 不同 sessionID 的消息在任何场景下都是互不可见的,以下管控策略均针对同一个 sessionID 的消息
- invocation.Message 在任何场景下均可见
- 未配置选项时,默认值为 FullContext
配置:
- llmagent.WithMessageFilterMode(MessageFilterMode):
- FullContext: 所有能通过 filterKey 做前缀匹配的消息
- RequestContext: 仅包含当前请求周期内通过 filterKey 前缀匹配的消息
- IsolatedRequest: 仅包含当前请求周期内通过 filterKey 完全匹配的消息
- IsolatedInvocation: 仅包含当前 invocation 周期内通过 filterKey 完全匹配的消息
推荐用法示例(该用法仅基于高级用法基础之上做了简化配置):
taskagentA := llmagent . New (
"coordinator" ,
llmagent . WithModel ( modelInstance ),
// 对 taskagentA、taskagentB 生成的所有消息可见(包含同一 sessionID 的历史会话消息)
llmagent . WithMessageFilterMode ( llmagent . FullContext )
// 对 taskagentA、taskagentB 当前 runner.Run 期间生成的所有消息可见(不包含历史会话消息)
llmagent . WithMessageFilterMode ( llmagent . RequestContext )
// 仅对 taskagentA 当前 runner.Run 期间生成的消息可见(不包含自己的历史会话消息)
llmagent . WithMessageFilterMode ( llmagent . IsolatedRequest )
// agent 执性顺序:taskagentA-invocation1 -> taskagentB-invocation2 -> taskagentA-invocation3(当前执行阶段)
// 仅对 taskagentA 当前 taskagentA-invocation3 期间生成的消息可见(不包含自己的历史会话消息以及 taskagentA-invocation1 期间生成的消息)
llmagent . WithMessageFilterMode ( llmagent . IsolatedInvocation )
)
taskagentB := llmagent . New (
"coordinator" ,
llmagent . WithModel ( modelInstance ),
// 对 taskagentA、taskagentB 生成的所有消息可见(包含同一 sessionID 的历史会话消息)
llmagent . WithMessageFilterMode ( llmagent . FullContext ),
// 对 taskagentA、taskagentB 当前 runner.Run 期间生成的所有消息可见(不包含历史会话消息)
llmagent . WithMessageFilterMode ( llmagent . RequestContext ),
// 仅对 taskagentB 当前 runner.Run 期间生成的消息可见(不包含自己的历史会话消息)
llmagent . WithMessageFilterMode ( llmagent . IsolatedRequest ),
// agent 执性顺序:taskagentA-invocation1 -> taskagentB-invocation2 -> taskagentA-invocation3 -> taskagentB-invocation4(当前执行阶段)
// 仅对 taskagentB 当前 taskagentB-invocation4 期间生成的消息可见(不包含自己的历史会话消息以及 taskagentB-invocation2 期间生成的消息)
llmagent . WithMessageFilterMode ( llmagent . IsolatedInvocation ),
)
// 循环执行 taskagentA、taskagentB
cycleAgent := cycleagent . New (
"coordinator" ,
llmagent . WithModel ( modelInstance ),
llmagent . WithSubAgents ([] agent . Agent { taskagentA , taskagentB }),
llmagent . WithMessageFilterMode ( llmagent . FullContext )
)
// 创建 Runner
runner := runner . NewRunner ( "demo-app" , cycleAgent )
// 直接发送消息,无需创建复杂的 Invocation
message := model . NewUserMessage ( "Hello! Can you tell me about yourself?" )
eventChan , err := runner . Run ( ctx , "user-001" , "session-001" , message )
if err != nil {
log . Fatalf ( "执行 Agent 失败: %v" , err )
}
高阶用法示例:
可以单独通过 WithMessageTimelineFilterMode、WithMessageBranchFilterMode控制当前 agent 对历史消息与其他 agent 生成的消息可见性。
当前 agent 在与模型交互时,最终将同时满足两个条件的消息输入给模型。
配置:
- WithMessageTimelineFilterMode: 时间维度可见性控制
- TimelineFilterAll: 包含历史消息以及当前请求中所生成的消息
- TimelineFilterCurrentRequest: 仅包含当前请求 (一次 runner.Run 为一次请求) 中所生成的消息
- TimelineFilterCurrentInvocation: 仅包含当前 invocation 上下文中生成的消息
- WithMessageBranchFilterMode: 按 FilterKey 层级控制可见性
- BranchFilterModePrefix(默认):层级匹配(祖先/自己/子孙都算匹配)
- BranchFilterModeSubtree:仅包含当前 key 及其子孙(不含父级,更适合严格隔离)
- BranchFilterModeExact:仅包含
Event.FilterKey == Invocation.eventFilterKey
- BranchFilterModeAll:忽略 FilterKey,包含全部消息
llmAgent := llmagent . New (
"demo-agent" , // Agent 名称
llmagent . WithModel ( modelInstance ), // 设置模型
llmagent . WithDescription ( "A helpful AI assistant for demonstrations" ), // 设置描述
llmagent . WithInstruction ( "Be helpful, concise, and informative in your responses" ), // 设置指令
llmagent . WithGenerationConfig ( genConfig ), // 设置生成参数
// 设置传给模型的消息过滤模式,最终传给模型的消息需同时满足 WithMessageTimelineFilterMode 与 WithMessageBranchFilterMode 条件
// 时间维度过滤条件
// 默认值:llmagent.TimelineFilterAll
// 可选值:
// - llmagent.TimelineFilterAll: 包含历史消息以及当前请求中所生成的消息
// - llmagent.TimelineFilterCurrentRequest: 仅包含当前请求中所生成的消息
// - llmagent.TimelineFilterCurrentInvocation: 仅包含当前 invocation 上下文中生成的消息
llmagent . WithMessageTimelineFilterMode ( llmagent . TimelineFilterAll ),
// 分支维度过滤条件
// 默认值:llmagent.BranchFilterModePrefix
// 可选值:
// - llmagent.BranchFilterModePrefix: 层级匹配(祖先/自己/子孙都算匹配)
// - llmagent.BranchFilterModeSubtree: 仅包含当前 key 及其子孙(不含父级)
// - llmagent.BranchFilterModeExact: 仅包含
// Event.FilterKey == Invocation.eventFilterKey
// - llmagent.BranchFilterModeAll: 忽略 FilterKey,包含全部消息
llmagent . WithMessageBranchFilterMode ( llmagent . BranchFilterModePrefix ),
)
推理内容模式(DeepSeek 思考模式)
当使用具有思考/推理能力的模型(如 DeepSeek)时,模型会同时输出 reasoning_content(思维链)和 content(最终回答)。根据 DeepSeek API 文档 ,在多轮对话中,不应将上一轮的 reasoning_content 发送给模型。
LLMAgent 提供 WithReasoningContentMode 来控制对话历史中 reasoning_content 的处理方式:
可用模式:
模式
常量
描述
丢弃之前轮次
ReasoningContentModeDiscardPreviousTurns
丢弃之前请求轮次的 reasoning_content,保留当前请求的。(默认,推荐)
保留全部
ReasoningContentModeKeepAll
保留历史中的所有 reasoning_content(用于调试)。
全部丢弃
ReasoningContentModeDiscardAll
丢弃历史中的所有 reasoning_content,以最大化节省带宽。
使用示例:
// DeepSeek 思考模式的推荐配置。
agent := llmagent . New (
"deepseek-agent" ,
llmagent . WithModel ( deepseekModel ),
llmagent . WithInstruction ( "You are a helpful assistant." ),
// 丢弃之前轮次的 reasoning_content(推荐用于 DeepSeek)。
llmagent . WithReasoningContentMode ( llmagent . ReasoningContentModeDiscardPreviousTurns ),
)
工作原理:
keep_all :所有 reasoning_content 都保留在会话历史中。如果需要保留思维链用于调试或分析,请使用此模式。
discard_previous_turns :在构建新请求的消息列表时,属于之前请求的消息的 reasoning_content 会被清除。当前请求内的消息(例如在工具调用循环期间)保留其 reasoning_content。这遵循 DeepSeek 的建议。
discard_all :在发送给模型之前,所有历史消息的 reasoning_content 都会被清除。
注意: 此选项仅影响发送给模型之前对历史消息的处理方式。当前响应的 reasoning_content 始终会被捕获并存储在会话事件中。
工具调用记录历史模式
默认情况下,LLMAgent 会在后续请求中继续把历史工具调用及其对应工具结果发送给模型。这是最保守、兼容性最强的行为;但在包含大量已完成工具调用轮次的长会话中,这些历史 tool transcript 可能会占用不必要的上下文。
LLMAgent 提供 WithToolTranscriptMode 来控制已完成历史工具调用/工具结果对在模型请求中的投影方式:
模式
常量
描述
保留全部
ToolTranscriptModeKeepAll
在模型请求中保留所有历史工具调用和工具结果记录。(默认)
省略之前已完成记录
ToolTranscriptModeOmitPreviousCompleted
省略之前请求中已完成的工具调用/工具结果对,同时保留当前请求或未完成的工具轮次。
使用示例:
agent := llmagent . New (
"assistant" ,
llmagent . WithModel ( modelInstance ),
llmagent . WithInstruction ( "You are a helpful assistant." ),
// 省略之前请求中已完成的工具调用记录,降低 prompt 大小。
llmagent . WithToolTranscriptMode ( llmagent . ToolTranscriptModeOmitPreviousCompleted ),
)
工作原理:
keep_all :所有历史工具调用和工具结果都会保留在模型请求中。
omit_previous_completed :构建新请求的消息列表时,之前请求中已经完整闭环的工具调用/工具结果对会从投影出的历史消息中省略。
省略模式会保守处理以下情况:
当前请求内的工具调用循环会保留,确保模型仍能看到正在处理的工具调用。
未完成的历史工具调用会保留,避免生成无效的 tool-call transcript。
如果被省略的工具调用事件里带有普通 assistant 文本,这部分文本会保留;只移除工具调用及其匹配的工具结果。
session event 不会被删除。该模式只影响发送给模型请求的消息投影。
当历史中已完成的工具调用记录不再需要被模型逐字读取、且希望降低 prompt 大小时,可以启用 ToolTranscriptModeOmitPreviousCompleted。如果后续回答仍需要精确查看之前的原始工具调用或工具结果,请保留默认的 ToolTranscriptModeKeepAll。
它和 context compaction 不同:tool transcript mode 可以在请求投影中省略完整的历史工具调用/工具结果对;context compaction 则保留 tool result 消息形态,只压缩较大的工具结果内容。
委托可见性选项
在构建多 Agent(智能体)系统(Agent 之间的任务委托)时,LLMAgent 提供“默认占位消息”的统一配置。转移(transfer)事件始终包含提示文本,并统一打上 transfer 标签,前端(UI, User Interface)可按标签过滤。
llmagent.WithDefaultTransferMessage(string)
配置当模型未提供 message 时的“转移默认消息”。
传入空字符串表示“禁用默认消息注入”;传入非空字符串表示“启用并使用该字符串作为默认消息”。
用法示例:
coordinator := llmagent . New (
"coordinator" ,
llmagent . WithModel ( modelInstance ),
llmagent . WithSubAgents ([] agent . Agent { mathAgent , weatherAgent }),
// 转移提示事件总是会输出(带有 `transfer` 标签),如需隐藏可在 UI 层按标签过滤
// 当模型未传 message 时,自定义默认消息(传空字符串可禁用)
llmagent . WithDefaultTransferMessage ( "Handing off to the specialist" ),
)
说明:
这些选项不会改变真实的委托/切换逻辑,只影响“对外可见的提示文本”或“是否注入默认占位消息”。
转移提示事件统一以 Response.Object == "agent.transfer" 输出;如需在 UI 层隐藏系统级提示,可直接过滤该对象类型的事件。
工具后提示词注入(Post-tool Prompt)
当模型调用工具时,工具输出会以 role=tool 消息追加到对话中。某些模型在看到工具结果后,可能会输出“基于工具结果……”这类元说明,或暴露内部过程。
为了让工具调用后的回复更自然,LLMAgent 会在启用该功能时,向系统消息注入一段“工具后(post-tool)”提示词。这段提示词会从第一次模型请求起稳定存在,后续工具调用轮次不会再改写靠前的 prompt 前缀,因此更利于复用服务端 prompt cache。
默认:开启,使用框架内置提示词。
自定义注入文本:llmagent.WithPostToolPrompt("...")。
完全禁用注入:llmagent.WithEnablePostToolPrompt(false)。
示例:
agent := llmagent . New (
"assistant" ,
llmagent . WithModel ( modelInstance ),
llmagent . WithTools ([] tool . Tool { myTool }),
// 禁用框架默认的工具后提示词注入。
llmagent . WithEnablePostToolPrompt ( false ),
)
调用次数限制(安全机制)
为防止 Agent 陷入无限循环或过度消耗资源,LLMAgent 提供了两个可选的调用次数限制配置:
可用配置:
配置项
说明
llmagent.WithMaxLLMCalls(n)
限制每次调用的 LLM 调用次数上限。当 n > 0 时生效,n <= 0 时不限制(默认)。
llmagent.WithMaxToolIterations(n)
限制每次调用的工具迭代次数上限。当 n > 0 时生效,n <= 0 时不限制(默认)。
llmagent.WithLLMCallLimitFinalization(instruction)
将 WithMaxLLMCalls 允许的最后一次调用用于不带工具的最终回复。
llmagent.WithToolIterationLimitFinalization(instruction)
在 WithMaxToolIterations 允许的最后一轮完全由框架执行的工具调用之后请求一次不带工具的最终回复,前提是当前 invocation 和 LLM 调用预算仍允许下一次调用。
使用示例:
agent := llmagent . New (
"safe-agent" ,
llmagent . WithModel ( modelInstance ),
llmagent . WithTools ([] tool . Tool { myTool }),
// 限制最多调用 10 次 LLM。
llmagent . WithMaxLLMCalls ( 10 ),
// 限制最多进行 5 轮工具调用迭代。
llmagent . WithMaxToolIterations ( 5 ),
// 选择在两类上限处生成不带工具的收尾回复。
// 空字符串表示使用框架默认 instruction。
llmagent . WithLLMCallLimitFinalization ( "" ),
llmagent . WithToolIterationLimitFinalization ( "" ),
)
行为说明:
未配置 finalization option 时,现有行为保持不变:
WithMaxLLMCalls :调用次数超过限制时返回 StopError。
WithMaxToolIterations :工具迭代次数超过限制时发送 flow_error 响应事件。
两个 finalization option 相互独立,且都需要显式选择。传入 "" 时使用框架默认的收尾 instruction;传入非空字符串时使用调用方提供的 instruction。
LLM 上限收尾会占用 MaxLLMCalls 内的最后一次调用;工具迭代上限收尾会在最后一轮允许的工具调用后使用下一次 LLM 调用。
工具迭代上限收尾要求达到上限的这一轮中所有工具调用都由框架执行。如果其中任何调用是 external tool,或被 WithToolExecutionFilter 延后给调用方执行,该回复仍会计入 MaxToolIterations,但当前运行会沿用 caller-executed tool 的既有生命周期并直接结束,不再发起收尾调用。调用方后续继续执行时会创建新的 invocation,并使用独立的限制计数。
MaxLLMCalls 始终是严格的外层硬预算,收尾调用也计入其中。因此组合使用工具上限收尾和 WithMaxLLMCalls 时,需要预留一次 LLM 调用。
最后一次模型请求会在消息尾部追加一条临时 user instruction,而不会修改已有 system prompt。BeforeModel callback 可以看到该消息,但它不会作为真实 user event 发送或持久化。
收尾期间,框架会在 BeforeModel callback 前移除工具及强制工具选择字段,并在 callback 后再次清理。如果模型仍然返回工具调用,框架会拒绝该调用且不会执行工具。
如果两个收尾策略在同一次 LLM 调用上同时满足条件,优先使用 LLM 上限对应的 instruction。
两个限制相互独立,可以单独使用或组合使用。
这些限制是每次调用级别的,不同的 runner.Run() 调用会各自独立计数。
(*agent.Invocation).ToolIterationCount() 提供工具迭代上限执行计数器的只读访问。MaxToolIterations 非正数时该值始终为 0;超过上限且未执行工具的那次 tool-call response 也会计入;Clone() 从 0 重新开始,View() 则保留当前值。它不是通用的工具使用量指标。
推荐用法:
如果你希望 LLMAgent 在工具调用失败后自动补一次或多次重试,可以配置 llmagent.WithToolCallRetryPolicy(...)。
policy := & tool . RetryPolicy {
MaxAttempts : 2 ,
InitialInterval : 200 * time . Millisecond ,
BackoffFactor : 2.0 ,
MaxInterval : time . Second ,
}
agent := llmagent . New (
"assistant" ,
llmagent . WithModel ( modelInstance ),
llmagent . WithTools ([] tool . Tool { myTool }),
llmagent . WithToolCallRetryPolicy ( policy ),
)
说明:
默认关闭;未配置时行为与历史版本保持一致。
当前仅对 CallableTool 生效;StreamableTool 暂不支持。
重试只作用于当前这次工具调用,不会重跑整个 Agent。
默认判定只重试常见瞬时 raw error,如 io.EOF、io.ErrUnexpectedEOF、网络超时。
如果需要把结果级失败也纳入重试,可通过 tool.RetryPolicy.RetryOn 自定义。
可运行示例:
处理事件流
runner.Run() 返回的 eventChan 是一个事件通道,Agent 执行过程中会持续向这个通道发送 Event 对象。
每个 Event 包含了某个时刻的执行状态信息:LLM 生成的内容、工具调用的请求和结果、错误信息等。通过遍历事件通道,你可以实时获取 Agent 的执行进展(详见下方 Event 章节)。
通过事件通道接收执行结果:
// 1. 获取事件通道(立即返回,开始异步执行)
eventChan , err := runner . Run ( ctx , userID , sessionID , message )
if err != nil {
log . Fatalf ( "failed to run agent: %v" , err )
}
// 2. 处理事件流(实时接收执行结果)
for event := range eventChan {
// 检查错误
if event . Error != nil {
log . Printf ( "error: %s" , event . Error . Message )
continue
}
// 处理响应内容
if len ( event . Response . Choices ) > 0 {
choice := event . Response . Choices [ 0 ]
// 流式内容(实时显示)
if choice . Delta . Content != "" {
fmt . Print ( choice . Delta . Content )
}
// 工具调用信息
for _ , toolCall := range choice . Message . ToolCalls {
fmt . Printf ( "calling tool: %s\n" , toolCall . Function . Name )
}
}
// 检查当前这条响应是否已完整结束
if event . IsFinalResponse () {
fmt . Println ()
break
}
}
上面的示例使用 event.IsFinalResponse(),是因为它只关心“当前这条回复何时
完整输出完”。如果你需要等待整次 Runner.Run 真正结束,例如
tool.response 后可能还有后续处理,或在 GraphAgent 中还要等待图上其他节点
完成,请改用 event.IsRunnerCompletion() 作为退出条件。
该示例的完整代码可见 examples/runner
为什么推荐使用 Runner?
更简单的接口 :无需创建复杂的 Invocation 对象
集成服务 :自动集成 Session、Memory 等服务
更好的管理 :统一管理 Agent 的执行流程
生产就绪 :适合生产环境使用
💡 提示: 想了解更多 Runner 的详细用法和高级功能?请查看 Runner
高级用法:直接使用 Agent
如果你需要更细粒度的控制,也可以直接使用 Agent 接口,但这需要创建 Invocation 对象:
核心概念
Invocation(高级用法)
Invocation 是 Agent 执行流程的上下文对象,包含了单次调用所需的所有信息。注意:这是高级用法,推荐使用 Runner 来简化操作。
import "trpc.group/trpc-go/trpc-agent-go/agent"
// 创建 Invocation 对象(高级用法)
invocation := agent . NewInvocation (
agent . WithInvocationAgent ( r . agent ), // Agent 实例
agent . WithInvocationSession ( & session . Session { ID : "session-001" }), // Session
agent . WithInvocationEndInvocation ( false ), // 是否结束调用
agent . WithInvocationMessage ( model . NewUserMessage ( "User input" )), // 用户消息
agent . WithInvocationModel ( modelInstance ), // 使用的模型
)
// 直接调用 Agent(高级用法)
ctx := context . Background ()
eventChan , err := llmAgent . Run ( ctx , invocation )
if err != nil {
log . Fatalf ( "执行 Agent 失败: %v" , err )
}
什么时候使用直接调用?
需要完全控制执行流程
自定义 Session 和 Memory 管理
实现特殊的调用逻辑
调试和测试场景
// Invocation 是 Agent 执行流程的上下文对象,包含单次调用所需的全部信息
type Invocation struct {
// Agent 指定要调用的 Agent 实例
Agent Agent
// AgentName 标识要调用的 Agent 实例名称
AgentName string
// InvocationID 为每次调用提供唯一标识
InvocationID string
// Branch 用于分层事件过滤的分支标识符
Branch string
// EndInvocation 标识是否结束调用
EndInvocation bool
// Session 维护对话上下文状态
Session * session . Session
// Model 指定要使用的模型实例
Model model . Model
// Message 是用户发送给 Agent 的具体内容
Message model . Message
// RunOptions 是 Run 方法的选项配置
RunOptions RunOptions
// TransferInfo 支持 Agent 间的控制权转移
TransferInfo * TransferInfo
// 结构化输出配置(可选)
StructuredOutput * model . StructuredOutput
StructuredOutputType reflect . Type
// 为本次调用注入的服务
MemoryService memory . Service
ArtifactService artifact . Service
// 内部通知:当事件写入会话时发出通知
noticeChanMap map [ string ] chan any
noticeMu * sync . Mutex
// 内部:事件过滤键与父调用(用于嵌套流程)
eventFilterKey string
parent * Invocation
// 调用级状态(延迟初始化,通过 stateMu 保护并发)
state map [ string ] any
stateMu sync . RWMutex
// 可选的调用级安全限制(通常由 LLMAgent 在 setupInvocation 中设置)。
MaxLLMCalls int
MaxToolIterations int
// 与 MaxLLMCalls / MaxToolIterations 配套使用的内部计数器。
llmCallCount int
toolIterationCount int
}
Invocation State
Invocation 提供了通用的状态存储机制,用于在单次调用的生命周期内共享数据。这对于 callbacks、middleware 或任何需要在 invocation 级别存储临时数据的场景都很有用。
核心方法:
// 设置状态值
inv . SetState ( key string , value any )
// 获取状态值
value , ok := inv . GetState ( key string )
// 删除状态值
inv . DeleteState ( key string )
特点:
Invocation 级作用域 :状态自动限定在单次 Invocation 内
线程安全 :内置 RWMutex 保护,支持并发访问
懒初始化 :首次使用时才分配内存
通用性强 :可用于 callbacks、middleware、自定义逻辑等多种场景
使用示例:
版本要求
结构化回调 API(推荐)需要 trpc-agent-go >= 0.6.0 。
// 在 BeforeAgentCallback 中存储数据
// 注意:结构化回调 API 需要 trpc-agent-go >= 0.6.0
callbacks := agent . NewCallbacks ()
callbacks . RegisterBeforeAgent ( func ( ctx context . Context , args * agent . BeforeAgentArgs ) ( * agent . BeforeAgentResult , error ) {
args . Invocation . SetState ( "agent:start_time" , time . Now ())
args . Invocation . SetState ( "custom:request_id" , "req-123" )
return nil , nil
})
// 在 AfterAgentCallback 中读取数据
callbacks . RegisterAfterAgent ( func ( ctx context . Context , args * agent . AfterAgentArgs ) ( * agent . AfterAgentResult , error ) {
if startTime , ok := args . Invocation . GetState ( "agent:start_time" ); ok {
duration := time . Since ( startTime .( time . Time ))
log . Printf ( "Execution took: %v" , duration )
args . Invocation . DeleteState ( "agent:start_time" )
}
return nil , nil
})
推荐的键名约定:
Agent 回调:"agent:xxx"
Model 回调:"model:xxx"
Tool 回调:"tool:toolName:xxx"
中间件:"middleware:xxx"
自定义逻辑:"custom:xxx"
详细的使用说明和更多示例请参考 Callbacks 。
Event
Event 是 Agent 执行过程中产生的实时反馈,通过 Event 流实时报告执行进展。
Event 主要有以下类型:
模型对话事件
工具调用与响应事件
Agent 转移事件
错误事件
// Event 是 Agent 执行过程中产生的实时反馈,通过 Event 流实时报告执行进展
type Event struct {
// Response 包含模型的响应内容、工具调用结果和统计信息
* model . Response
// InvocationID 关联到具体的调用
InvocationID string `json:"invocationId"`
// Author 是事件的来源,例如 Agent 或工具
Author string `json:"author"`
// ID 是事件的唯一标识
ID string `json:"id"`
// Timestamp 记录事件发生的时间
Timestamp time . Time `json:"timestamp"`
// Branch 用于分层事件过滤的分支标识符
Branch string `json:"branch,omitempty"`
// RequiresCompletion 标识此事件是否需要完成信号
RequiresCompletion bool `json:"requiresCompletion,omitempty"`
// LongRunningToolIDs 是长时间运行函数调用的 ID 集合,Agent 客户端可以通过此字段了解哪个函数调用是长时间运行的,仅对函数调用事件有效
LongRunningToolIDs map [ string ] struct {} `json:"longRunningToolIDs,omitempty"`
}
Event 的流式特性让你能够实时看到 Agent 的工作过程,就像和一个真人对话一样自然。你只需要遍历 Event 流,检查每个 Event 的内容和状态,就能完整地处理 Agent 的执行结果。
Agent 接口
Agent 接口定义了所有 Agent 必须实现的核心行为。这个接口让你能够统一使用不同类型的 Agent,同时支持工具调用和子 Agent 管理。
type Agent interface {
// Run 接收执行上下文和调用信息,返回一个事件通道。通过这个通道,你可以实时接收 Agent 的执行进展和结果
Run ( ctx context . Context , invocation * Invocation ) ( <- chan * event . Event , error )
// Tools 返回此 Agent 可以访问和执行的工具列表
Tools () [] tool . Tool
// Info 方法提供 Agent 的基本信息,包括名称和描述,便于识别和管理
Info () Info
// SubAgents 返回此 Agent 可用的子 Agent 列表
// SubAgents 和 FindSubAgent 方法支持 Agent 之间的协作。一个 Agent 可以将任务委托给其他 Agent,构建复杂的多 Agent 系统
SubAgents () [] Agent
// FindSubAgent 通过名称查找子 Agent
FindSubAgent ( name string ) Agent
}
框架提供了多种类型的 Agent 实现,包括 LLMAgent、ChainAgent、ParallelAgent、CycleAgent 和 GraphAgent,不同类型 Agent 以及多 Agent 系统的详细介绍请参考 Multi-Agent 。
Callbacks
Callbacks 提供了丰富的回调机制,让你能够在 Agent 执行的关键节点注入自定义逻辑。
版本要求
结构化回调 API(推荐)需要 trpc-agent-go >= 0.6.0 。
回调类型
框架提供了三种类型的回调:
Agent Callbacks :在 Agent 执行前后触发
// 使用 agent.NewCallbacks() 创建回调
callbacks := agent . NewCallbacks ()
Model Callbacks :在模型调用前后触发
// 使用 model.NewCallbacks() 创建回调
callbacks := model . NewCallbacks ()
Tool Callbacks :在工具调用前后触发
// 使用 tool.NewCallbacks() 创建回调
callbacks := tool . NewCallbacks ()
使用示例
// 创建 Agent 回调(使用结构化 API)
// 注意:结构化回调 API 需要 trpc-agent-go >= 0.6.0
callbacks := agent . NewCallbacks ()
callbacks . RegisterBeforeAgent ( func ( ctx context . Context , args * agent . BeforeAgentArgs ) ( * agent . BeforeAgentResult , error ) {
log . Printf ( "Agent %s 开始执行" , args . Invocation . AgentName )
return nil , nil
})
callbacks . RegisterAfterAgent ( func ( ctx context . Context , args * agent . AfterAgentArgs ) ( * agent . AfterAgentResult , error ) {
if args . Error != nil {
log . Printf ( "Agent %s 执行出错: %v" , args . Invocation . AgentName , args . Error )
} else {
log . Printf ( "Agent %s 执行完成" , args . Invocation . AgentName )
}
return nil , nil
})
// 在 llmAgent 中使用回调
llmagent := llmagent . New ( "llmagent" , llmagent . WithAgentCallbacks ( callbacks ))
回调机制让你能够精确控制 Agent 的执行过程,实现更复杂的业务逻辑。
结构化输出
结构化输出确保 Agent 的响应符合预定义的格式,使其更易于解析和程序化处理。框架提供了多种结构化输出方法,每种方法适用于不同的使用场景。
结构化输出方法对比
特性
WithStructuredOutputJSONSchema
WithStructuredOutputJSON
WithOutputSchema
WithOutputKey
工具使用
✅ 允许
✅ 允许
❌ 禁用
✅ 允许
Schema 类型
用户提供的 JSON Schema
从 Go 结构体自动生成
用户提供的 JSON Schema
不适用
输出类型
非类型化 (map/interface{})
类型化 (Go 结构体)
非类型化 (map/interface{})
字符串/字节
Schema 验证
✅ 由 LLM 验证
✅ 由 LLM 验证
✅ 由 LLM 验证
❌ 无
数据位置
Event.StructuredOutput
Event.StructuredOutput
模型响应内容
Session State
主要用途
灵活 schema + 工具
类型安全的结构化输出
简单的结构化响应
状态存储和流程控制
Provider 兼容性
框架允许同时配置工具与 WithStructuredOutputJSONSchema 或
WithStructuredOutputJSON,但模型服务也必须支持组合使用工具调用与原生结构化输出。
部分 OpenAI 兼容端点会接受 tools 和 response_format: json_schema,
却把 JSON 约束应用于整个生成过程。在这种情况下,约束解码可能会抑制模型专用的
工具调用语法,最终返回符合 schema 的 JSON,却没有实际发起任何工具调用。
因此,HTTP 请求成功且 JSON 校验通过,并不能证明所需工具已执行。当结果正确性依赖
工具执行时,应同时检查工具调用与工具结果事件。如果端点不能可靠支持该组合,
可将操作拆成两次调用:先在不启用原生结构化输出的情况下调用工具,再禁用工具并
生成结构化的最终答复。第二次调用必须通过延续相同的 session 或消息历史来获取
第一次调用的证据,或者显式包含第一次调用的工具调用与工具结果消息。
相关后端讨论包括
vLLM #39929 ,该 issue
跟踪 response_format 抑制自动工具调用的问题;以及
SGLang #21593 ,该 PR
修复了约束解码与模型专用工具调用格式之间的冲突。
WithStructuredOutputJSONSchema
提供用户自定义的 JSON schema 用于结构化输出,同时允许使用工具 。这是需要结构化输出和工具能力的 Agent 的最灵活选项。
注意:
- “允许使用工具”表示 Agent 仍可发起工具调用(包括 Skills 的
skill_load 与 workspace_exec)。
- 当模型需要调用工具时,可能会先返回工具调用事件而不是最终 JSON;
只有最终答复才需要满足 schema,并且必须是单个 JSON 对象。
示例:
schema := map [ string ] any {
"type" : "object" ,
"properties" : map [ string ] any {
"name" : map [ string ] any {
"type" : "string" ,
"description" : "Product name" ,
},
"price" : map [ string ] any {
"type" : "number" ,
"minimum" : 0 ,
},
"category" : map [ string ] any {
"type" : "string" ,
"enum" : [] string { "electronics" , "clothing" , "food" },
},
},
"required" : [] string { "name" , "price" },
}
agent := llmagent . New (
"shopping-agent" ,
llmagent . WithModel ( modelInstance ),
llmagent . WithStructuredOutputJSONSchema (
"shopping_output" , // Name
schema , // JSON schema
true , // Strict mode
"Product information" , // Description
),
llmagent . WithTools ([] tool . Tool { searchTool , calculatorTool }), // Tools are allowed!
)
// Access untyped output from events
for event := range eventCh {
if event . StructuredOutput != nil {
data := event . StructuredOutput .( map [ string ] any )
name := data [ "name" ].( string )
price := data [ "price" ].( float64 )
fmt . Printf ( "Product: %s, Price: $%.2f\n" , name , price )
}
}
最适合:
- 需要结构化输出和工具使用的复杂 Agent
- 使用外部 JSON schema(来自 API、数据库、配置文件)
- 使用动态 schema 进行原型开发
- 渐进式类型场景
WithStructuredOutputJSON
从 Go 结构体自动生成 JSON schema 并返回类型化输出。提供编译时类型安全。
注意:
- 当模型需要调用工具时,可能会先返回工具调用事件而不是最终 JSON;
只有最终答复才需要满足 schema,并且必须是单个 JSON 对象。
示例:
type ProductInfo struct {
Name string `json:"name"`
Price float64 `json:"price"`
Category string `json:"category"`
}
agent := llmagent . New (
"shopping-agent" ,
llmagent . WithModel ( modelInstance ),
llmagent . WithStructuredOutputJSON (
new ( ProductInfo ), // Auto-generates schema
true , // Strict mode
"Product information" , // Description
),
llmagent . WithTools ([] tool . Tool { searchTool }), // Tools are allowed
)
// Access typed output from events
for event := range eventCh {
if event . StructuredOutput != nil {
product := event . StructuredOutput .( * ProductInfo )
fmt . Printf ( "Product: %s, Price: $%.2f\n" , product . Name , product . Price )
}
}
最适合:
- 具有明确定义的 Go 结构体的类型安全应用
- 清晰的代码集成
- 编译时类型检查
WithOutputSchema (遗留)
类似于 WithStructuredOutputJSONSchema,但禁用所有工具 。这是为了向后兼容而保留的遗留方法。
示例:
agent := llmagent . New (
"weather-agent" ,
llmagent . WithModel ( modelInstance ),
llmagent . WithOutputSchema ( weatherSchema ),
// llmagent.WithTools(...) // ❌ Tools are disabled!
)
限制:
- ❌ 无法使用工具、函数调用或 RAG
- ❌ 响应在模型内容中(需要解析)
迁移提示: 如果需要工具能力,迁移到 WithStructuredOutputJSONSchema:
// Old: Tools disabled
agent := llmagent . New (
"agent" ,
llmagent . WithOutputSchema ( schema ),
)
// New: Tools enabled
agent := llmagent . New (
"agent" ,
llmagent . WithStructuredOutputJSONSchema (
"agent_output" , // Name
schema , // JSON schema
true , // Strict mode
"Agent output" , // Description
),
llmagent . WithTools ([] tool . Tool { myTool1 , myTool2 }), // ✅ Now works!
)
WithOutputKey
将 Agent 输出存储在会话状态的特定键下,适用于输出需要被下游 Agent 访问的工作流。
示例:
researchAgent := llmagent . New (
"researcher" ,
llmagent . WithModel ( modelInstance ),
llmagent . WithOutputKey ( "research_findings" ),
)
writerAgent := llmagent . New (
"writer" ,
llmagent . WithModel ( modelInstance ),
llmagent . WithInstruction ( "Based on research: {research_findings}, write a summary." ),
)
// Chain agents using session state
chain := chainagent . New ( "pipeline" , chainagent . WithSubAgents ([] agent . Agent {
researchAgent ,
writerAgent ,
}))
最适合:
- 带数据传递的多 Agent 工作流
- 会话状态管理
- 在下游 Agent 中使用占位符变量访问
选择合适的方法
场景
推荐方法
需要工具 + 结构化输出
WithStructuredOutputJSONSchema 或 WithStructuredOutputJSON
类型安全至关重要
WithStructuredOutputJSON
使用外部 schema
WithStructuredOutputJSONSchema
简单的结构化响应(无工具)
WithOutputSchema
多 Agent 工作流
WithOutputKey
快速原型开发
WithStructuredOutputJSONSchema
示例:
- examples/structuredoutput/ - 演示 WithStructuredOutputJSON(类型化)
- examples/outputschema/ - 演示 WithOutputSchema(遗留)
- examples/outputkey/ - 演示 WithOutputKey(会话状态)
进阶使用
框架提供了 Runner、Session 和 Memory 等高级功能,用于构建更复杂的 Agent 系统。
Runner 是推荐的使用方式 ,它负责管理 Agent 的执行流程,串联了 Session/Memory Service 等能力,提供了更友好的接口。
Session Service 用于管理会话状态,支持对话历史记录和上下文维护。
Memory Service 用于记录用户的偏好信息,支持个性化体验。
推荐阅读顺序:
Runner - 学习推荐的使用方式
Session - 了解会话管理
Multi-Agent - 学习多 Agent 系统
提示词脚手架(“Rules”/ 上下文注入模式)
很多 Agent 产品会把“rules”做成一个显式功能(项目记忆、按回合约束、按路径匹配的规则等)。但在工程上,“rules”并不是一个稳定、统一的框架层概念:不同产品对它的语义选择差异很大(system vs user role、放在历史前还是贴近最新 user、是否持久化、如何做文件范围选择、对 prompt cache 的影响等)。
tRPC‑Agent‑Go 选择不在 core 引入一等 Rules 抽象,而是提供更通用的 提示词/上下文注入原语 ,让你用组合方式实现自己产品想要的 “rules” 语义。
如何选择合适的原语
稳定的全局约束(常见的“全局规则”,推荐):
Agent 级配置:llmagent.WithGlobalInstruction(...) / llmagent.WithInstruction(...)
单次请求覆盖(不修改 agent 实例):agent.WithGlobalInstruction(...) / agent.WithInstruction(...)
单次请求、非持久化上下文,注入在 session history 之前 (更适合作为背景 seed/context):
agent.WithInjectedContextMessages([]model.Message{...})
单次请求、非持久化上下文,注入在 贴近最新用户回合 (适合作为本轮“rules/动态约束”):
agent.WithLateContextMessages([]model.Message{...})
需要完全控制最终消息序列:
用结构化 BeforeModel 回调重写 request.Messages(见 docs/mkdocs/zh/callbacks.md)。
消息位置(高层语义)
Content request processor 组装最终请求消息大致遵循下面顺序:
System prompt / instructions(稳定前缀)
Few-shot 示例(如果配置,会插入到前导 system block 之后)
Injected context messages(WithInjectedContextMessages)—— 在历史之前
Session history(会话的 canonical transcript)
Late context messages(WithLateContextMessages)—— 插入到最后一个 user message 之前 (如果当前请求里没有 user message,则会插入到前导 system block 之后)
6.(如果当前回合已有)属于当前回合的 tool/assistant tail
这种 “late” 放置方式适合动态、每轮变化的规则:它能让规则贴近本轮用户请求,同时尽量保持前缀稳定(更利于 prompt cache)。
注意事项与最佳实践
建议 late context 使用 role=user。在消息序列中间插入 role=system 并非所有 provider 都支持,也可能与消息校验/修复逻辑产生不兼容。
WithInjectedContextMessages 与 WithLateContextMessages 都 不会持久化 到 session transcript(只影响本次模型请求)。
在 multi-agent 场景中,这些选项属于 RunOptions,会随 invocation clone 传播到子调用;如果需要“只对某个 agent 生效”,推荐用回调按 invocation.AgentName 过滤实现。
可运行示例:examples/prompt/late_context_messages。
运行时动态更新 Instruction
你可以在 Agent 已经创建并被 Runner 使用的情况下,动态更新其行为文案:
Instruction:用于约束 Agent 行为的说明文本(追加到系统消息中)。
Global Instruction(系统提示词):系统级前言(作为系统消息的前缀)。
两者都可以在已有的 LLMAgent 实例上动态设置,新值会作用于后续的模型请求。
示例
import (
"context"
"trpc.group/trpc-go/trpc-agent-go/agent/llmagent"
"trpc.group/trpc-go/trpc-agent-go/model"
"trpc.group/trpc-go/trpc-agent-go/model/openai"
"trpc.group/trpc-go/trpc-agent-go/runner"
)
// 1)服务启动时只构建一次模型与 Agent
mdl := openai . New ( "gpt-4o-mini" , openai . Options {})
llm := llmagent . New (
"support-bot" ,
llmagent . WithModel ( mdl ),
llmagent . WithInstruction ( "Be helpful and concise." ),
)
run := runner . NewRunner ( "my-app" , llm )
// 2)运行中根据用户在后台修改的提示词,动态更新
llm . SetInstruction ( "Translate all user inputs to French." )
llm . SetGlobalInstruction ( "System: Safety first. No PII leakage." )
// 3)之后的对话轮次将使用最新的提示词
msg := model . NewUserMessage ( "Where is the nearest museum?" )
ch , err := run . Run ( context . Background (), "u1" , "s1" , msg )
_ = ch ; _ = err
注意
线程安全:上述设置方法是并发安全的,可在服务处理请求时调用。
同一轮次内的效果:若一次调用过程中会触发多次模型请求(例如工具调用后再次提问),更新可能会对同一轮后续的请求生效。若需要“每次调用内保持稳定”,可在调用开始时确定或冻结提示词。
单次请求覆盖:在 Runner.Run(...) 里传 agent.WithInstruction(...) / agent.WithGlobalInstruction(...),仅对当前请求生效,不会修改 Agent 实例。
按模型覆盖:如果 Agent 可能切换模型,可用 llmagent.WithModelInstructions / llmagent.WithModelGlobalInstructions(或对应的 setter)按 model.Info().Name 覆盖提示词;未命中映射时回退到 Agent 默认提示词。
个性化上下文:若需按用户/会话动态注入内容,优先使用指令中的占位符加会话状态注入(见上文“占位符变量”一节)。
按模型覆盖提示词
如果一个 Agent 会在运行时切换不同模型,你可以按模型为 Instruction
与 Global Instruction(系统提示词)配置不同的文本。
匹配逻辑是:先用当前模型的 model.Info().Name 查映射;命中则使用映射值;
否则回退到 Agent 的默认提示词。
示例
import (
"trpc.group/trpc-go/trpc-agent-go/agent/llmagent"
"trpc.group/trpc-go/trpc-agent-go/model"
"trpc.group/trpc-go/trpc-agent-go/model/openai"
)
models := map [ string ] model . Model {
"gpt-4o-mini" : openai . New ( "gpt-4o-mini" ),
"gpt-4o" : openai . New ( "gpt-4o" ),
}
llm := llmagent . New (
"support-bot" ,
llmagent . WithModels ( models ),
llmagent . WithModel ( models [ "gpt-4o-mini" ]), // Default model.
// Fallback prompts when no mapping exists.
llmagent . WithGlobalInstruction ( "System: You are a helpful assistant." ),
llmagent . WithInstruction ( "Start every answer with DEFAULT:" ),
// Per-model prompt mapping.
llmagent . WithModelGlobalInstructions ( map [ string ] string {
"gpt-4o-mini" : "System: You are in FAST mode." ,
"gpt-4o" : "System: You are in SMART mode." ,
}),
llmagent . WithModelInstructions ( map [ string ] string {
"gpt-4o-mini" : "Start every answer with FAST:" ,
"gpt-4o" : "Start every answer with SMART:" ,
}),
)
另见:examples/model/promptmap。
另一种方式:用占位符驱动动态 System Prompt
如果不想在运行时调用 setter,也可以把 Instruction 写成模板,然后用会话状态(Session/App/User/Temp)来“喂”值。指令处理器会在每次请求时注入占位符。
模式
持久化“按用户”:写到 user:*,在模板里用 {user:key} 引用
持久化“按应用”:写到 app:*,在模板里用 {app:key} 引用
会话内临时:写入会话的 temp:* 命名空间,模板用 {temp:key}
引用(不属于 user:*/app:* 的持久化配置;常见用法是每轮覆盖)
示例:按用户动态提示词
import (
"context"
"trpc.group/trpc-go/trpc-agent-go/agent/llmagent"
"trpc.group/trpc-go/trpc-agent-go/runner"
"trpc.group/trpc-go/trpc-agent-go/session"
"trpc.group/trpc-go/trpc-agent-go/session/inmemory"
)
svc := inmemory . NewSessionService ()
app , user , sid := "my-app" , "u1" , "s1"
// 1)在指令模板里引用用户态 key
llm := llmagent . New (
"dyn-agent" ,
llmagent . WithInstruction ( "{user:system_prompt}" ),
)
run := runner . NewRunner ( app , llm , runner . WithSessionService ( svc ))
// 2)当用户在后台改设置时,更新用户态状态
_ = svc . UpdateUserState ( context . Background (), session . UserKey { AppName : app , UserID : user }, session . StateMap {
"system_prompt" : [] byte ( "You are a helpful assistant. Always answer in English." ),
})
// 3)后续运行会通过占位符读取最新值
_ , _ = run . Run ( context . Background (), user , sid , model . NewUserMessage ( "Hi!" ))
示例:通过前置回调注入本轮临时值(temp)
版本要求
结构化回调 API(推荐)需要 trpc-agent-go >= 0.6.0 。
// 注意:结构化回调 API 需要 trpc-agent-go >= 0.6.0
callbacks := agent . NewCallbacks ()
callbacks . RegisterBeforeAgent ( func ( ctx context . Context , args * agent . BeforeAgentArgs ) ( * agent . BeforeAgentResult , error ) {
if args . Invocation != nil && args . Invocation . Session != nil {
// 为本次运行写入临时指令
args . Invocation . Session . SetState ( "temp:sys" , [] byte ( "Translate to French." ))
}
return nil , nil
})
llm := llmagent . New (
"temp-agent" ,
llmagent . WithInstruction ( "{temp:sys}" ),
llmagent . WithAgentCallbacks ( callbacks ), // 需要 trpc-agent-go >= 0.6.0
)
注意事项
内存版 UpdateUserState 出于安全设计禁止写 temp:*;需要会话内
临时值时,通过 invocation.Session.SetState 写入(例如通过回调)。
占位符是在“请求时”解析;只要你换了存储的值,下一次模型请求就会用新值,无需重建 Agent。
静态结构导出
框架提供了 Agent 的静态结构导出能力,可用于结构检查、可视化、配置工具和结构诊断等需要稳定节点图与 surface 基线的场景。
可以通过 agent/structure 导出规范化快照:
import "trpc.group/trpc-go/trpc-agent-go/agent/structure"
snapshot , err := structure . Export ( ctx , llmAgent )
if err != nil {
log . Fatalf ( "导出结构失败: %v" , err )
}
fmt . Println ( snapshot . StructureID )
fmt . Println ( snapshot . EntryNodeID )
fmt . Println ( len ( snapshot . Nodes ), len ( snapshot . Edges ), len ( snapshot . Surfaces ))
导出的快照包含:
Nodes:当前 Agent 结构中的稳定静态节点
Edges:节点之间静态上可能出现的连接
Surfaces:稳定可编辑的基线面,例如 instruction、model、tool、skill
完整示例见 examples/graph/structure_export 。
按 nodeID 覆盖运行时 surface
除了 agent.WithInstruction(...)、agent.WithGlobalInstruction(...) 这种“整次
runner.Run(...) 调用级”的覆盖方式之外,框架还支持在一次运行中按稳定
nodeID 精确覆盖指定节点的运行时 surface。
这个能力适合这些场景:
只想临时改某个 graph 节点的指令,不影响整棵 Agent。
同一次 runner.Run(...) 里,同时给多个节点设置不同的 instruction、few-shot 或 tools。
在 chain、parallel、cycle、graph、team、swarm 等嵌套结构中,精确命中某个子节点。
入口 API:
var patch agent . SurfacePatch
patch . SetInstruction ( "Answer in one short paragraph." )
patch . SetFewShot ([][] model . Message {
{
model . NewUserMessage ( "Summarize this report." ),
model . NewAssistantMessage ( "Provide a short executive summary first." ),
},
})
events , err := r . Run (
ctx ,
userID ,
sessionID ,
model . NewUserMessage ( "Please analyze the latest findings." ),
agent . WithSurfacePatchForNode ( nodeID , patch ),
)
使用步骤
推荐按下面的顺序使用:
先通过 structure.Export(...) 导出静态结构快照。
从 snapshot.EntryNodeID 或 snapshot.Nodes 中找到目标节点的稳定 nodeID。
构造 agent.SurfacePatch,只设置这次运行需要覆盖的 surface。
在 runner.Run(...) 中传入一个或多个 agent.WithSurfacePatchForNode(...)。
示例:
snapshot , err := structure . Export ( ctx , workflowAgent )
if err != nil {
return err
}
var plannerNodeID string
for _ , node := range snapshot . Nodes {
if node . Name == "planner" {
plannerNodeID = node . NodeID
break
}
}
if plannerNodeID == "" {
return errors . New ( "planner node not found" )
}
var patch agent . SurfacePatch
patch . SetInstruction ( "Plan in at most three steps." )
_ , err = r . Run (
ctx ,
userID ,
sessionID ,
model . NewUserMessage ( "Arrange a trip to Hangzhou next Friday." ),
agent . WithSurfacePatchForNode ( plannerNodeID , patch ),
)
注意事项:
调用方应优先使用 structure.Export(...) 导出的 nodeID,不要依赖手写路径。
这个能力只对当前这一次 runner.Run(...) 生效,不会修改 Agent 的静态定义。
agent.WithInstruction(...) / agent.WithGlobalInstruction(...) 仍然保留,适合“整次运行统一覆盖根 Agent 提示词”的场景;需要按节点精确控制时,再使用 WithSurfacePatchForNode(...)。
可覆盖的 surface
SurfacePatch 目前提供这些 setter:
SetInstruction(string):覆盖该节点本次运行的 instruction。
SetGlobalInstruction(string):覆盖该节点本次运行的 global instruction。
SetFewShot([][]model.Message):覆盖该节点本次运行的 few-shot 示例。
SetModel(model.Model):覆盖该节点本次运行使用的模型实例。
SetTools([]tool.Tool):覆盖该节点本次运行的工具 surface。它表达的是“替换本次运行可见的工具集合”,不是只改工具描述文本。
AppendTools([]tool.Tool):给该节点本次运行的工具 surface 增量追加工具。它会保留节点已有工具,并把传入的工具追加到后面。
SetSkillRepository(skill.Repository):覆盖该节点本次运行的 skill repository;传 nil 可显式禁用该节点的 skill surface。
不是每种节点都支持全部 surface,常见情况如下:
根 LLMAgent:支持 instruction、global_instruction、few_shot、model、tool、skill。
graph 的 LLM 节点:支持 instruction、few_shot、model、tool。
graph 的 Tools 节点:支持 tool。
graph 子 Agent、chain、parallel、cycle、team、swarm 中的子节点:是否可覆盖,取决于命中的那个具体子节点本身支持哪些 surface。
在同一次运行中覆盖多个节点
如果要在同一次 runner.Run(...) 里同时覆盖多个节点,直接重复传多个
agent.WithSurfacePatchForNode(...) 即可,不需要额外的批量 API。
snapshot , err := structure . Export ( ctx , workflowAgent )
if err != nil {
return err
}
nodeIDs := make ( map [ string ] string )
for _ , node := range snapshot . Nodes {
nodeIDs [ node . Name ] = node . NodeID
}
var plannerPatch agent . SurfacePatch
plannerPatch . SetInstruction ( "Produce no more than three candidate plans." )
var reviewerPatch agent . SurfacePatch
reviewerPatch . SetInstruction ( "Reject any plan that lacks cost analysis." )
var toolsPatch agent . SurfacePatch
toolsPatch . AppendTools ([] tool . Tool { priceTool })
_ , err = r . Run (
ctx ,
userID ,
sessionID ,
model . NewUserMessage ( "Plan a team offsite in Suzhou next month." ),
agent . WithSurfacePatchForNode ( nodeIDs [ "planner" ], plannerPatch ),
agent . WithSurfacePatchForNode ( nodeIDs [ "reviewer" ], reviewerPatch ),
agent . WithSurfacePatchForNode ( nodeIDs [ "search_tools" ], toolsPatch ),
)
对同一 nodeID 多次传 WithSurfacePatchForNode(...) 时,框架会按 surface 类型合并:
不同 surface 类型彼此合并。
同一种 surface 类型后传入的值覆盖先传入的值。
工具追加 patch 是增量语义。重复调用 AppendTools(...) 会继续追加到
有效工具集合;后续再调用 SetTools(...) 会替换该节点之前追加的工具。
组合结构与嵌套结构
这套能力不只适用于单个 LLMAgent。在这些结构里也建议使用同样的方法:
graph:可覆盖图中的 LLM 节点、Tools 节点,以及图中挂载的子 Agent 根节点。
chain、parallel、cycle:可覆盖任意导出出来的子节点。
team / swarm:可覆盖 coordinator、成员节点,以及 transfer 后到达的成员节点。
在这些场景里,最稳妥的做法仍然是:
先 structure.Export(...)
再使用导出的 nodeID
最后在 runner.Run(...) 中传入 patch
这样用户只需要记住一套规则:nodeID 由结构导出提供,运行时 patch 由 WithSurfacePatchForNode(...) 提供。
执行图导出
框架可以为单次 runner.Run 调用导出执行图,用来观察这次运行里哪些节点真的执行了、各个步骤之间如何依赖,以及每个步骤看到的输入和输出快照。
需要在运行时显式开启:
import (
"trpc.group/trpc-go/trpc-agent-go/agent"
"trpc.group/trpc-go/trpc-agent-go/model"
"trpc.group/trpc-go/trpc-agent-go/runner"
)
r := runner . NewRunner ( "demo-app" , ag )
events , err := r . Run (
ctx ,
"user-1" ,
"session-1" ,
model . NewUserMessage ( "hello" ),
agent . WithExecutionTraceEnabled ( true ),
)
if err != nil {
log . Fatalf ( "运行失败: %v" , err )
}
for evt := range events {
if evt != nil && evt . IsRunnerCompletion () && evt . ExecutionTrace != nil {
fmt . Println ( evt . ExecutionTrace . RootAgentName )
fmt . Println ( evt . ExecutionTrace . Status )
fmt . Println ( len ( evt . ExecutionTrace . Steps ))
}
}
执行图挂在 runner completion event 上,属于进程内工件,默认不会参与序列化。
每个步骤都会带上这些稳定字段:
NodeID:本次执行对应的静态节点路径
NodeType:节点的语义类型(function、llm、tool 或 agent),与静态结构中的节点类型一致;agent 表示 Agent 执行单元(包括 LLMAgent),llm 表示显式 LLM 操作节点
PredecessorStepIDs:这次运行里该步骤的直接前驱步骤
Input 和 Output:步骤输入输出的稳定文本快照
Error:步骤失败时记录的终态错误
完整示例见 examples/graph/execution_trace 。
2026-08-27 08:58:00
2025-08-25 07:06:16