通过配置调优 Agent 行为
当同一个服务二进制需要承载多个 Agent 配置、切换模型配置或 model pool,或在不改 Rust 代码的情况下调优插件行为时,使用托管配置。新增 Tool、新增 Plugin、自定义 provider factory 仍然放在代码里;provider、model、agent、MCP server 和类型化 section 值放在配置里。
这是“运行时调优”的主参考。工具写好、runtime 启动后,大部分行为变化都应该是配置改动:
system prompt、model 选择、工具描述覆盖、允许工具、reasoning effort、context
policy、reminder 节奏、ToolSearch / deferred-tool 策略、Skill 激活元数据和
delegates。同一个 server binary 承载多个 Agent 配置;切换行为是
PUT /v1/config/agents/:id 或在管理控制台 Save,而不是重新部署。
本文假设服务端已经把 ConfigStore 接入 ServerState,并且要引用的插件已经注册到 runtime plugin registry。
| 层级 | 位置 | 用途 |
|---|---|---|
| Provider | /v1/config/providers/{id} | Adapter、API key 来源、base URL、timeout |
| Model config | /v1/config/models/{id} | 稳定 model id -> ModelSpec(provider id、上游模型名、capabilities、定价) |
| Model pool | /v1/config/model-pools/{id} | 稳定 pool id -> 有序 ModelSpec 成员、粘性路由与故障切换策略 |
| Agent | /v1/config/agents/{id} | Prompt、稳定 model_id、轮数、工具、插件、上下文策略 |
| Tool override | /v1/config/tools/{id}/overrides | runtime-safe 的工具描述覆盖 |
| MCP server | /v1/config/mcp-servers/{id} | 外部 MCP server 连接 |
| Skill | /v1/config/skills/{id} | 可复用指令、参数与允许工具集合 |
| Plugin section | AgentSpec.sections | 按 PluginConfigKey::KEY 归档的每 Agent 类型化配置 |
| Runtime code | AgentRuntimeBuilder | 注册工具、provider factory、插件、backend |
托管配置 runtime 支持的 provider adapter 会在 GET /v1/capabilities 的
supported_adapters 中返回。该列表由当前链接的 genai 版本在运行时推导;
上游 dependency 支持新 adapter 后,这里会随之出现。
本地 Agent 执行通过稳定注册表 ID 解析 model 与 provider:
AgentSpec.model_id -> ModelSpec { provider_id, upstream_model } -> ProviderSpec { adapter, api_key, base_url, timeout_secs } -> LlmExecutorAgentSpec.model_id 不是上游 provider 模型名。它是 Agent 和客户端使用的稳定 ModelSpec.id。ModelSpec.upstream_model 才是发送给 provider API 的模型字符串。
配置写入会先编译成候选 registry snapshot 并完成校验,然后再发布。新的 run 使用最新发布的 snapshot。已经开始的 run 保持启动时绑定的 snapshot。Audit-log restore 是例外:它只把恢复出的 payload 写入 editing store;当它应该对新 run 生效时,再通过普通 Save / PUT 发布。
如果部署使用 VersionedRegistryStore,已发布 runtime snapshot 是不可变的,
durable run 会携带 resolution_id,用于 resume/replay 时重新选择同一个已发布
graph。Record revision 与 audit restore 让配置历史可追溯;这和“手动把某个
editing-store 版本 pin 为生产版本”不是同一件事。
Endpoint-backed Agent 会跳过本地 provider、model、plugin 和 tool 解析链,改由选中的远程 backend 执行其 endpoint 配置。
最小托管配置
Section titled “最小托管配置”创建或更新 provider。省略 api_key 时,provider adapter 会使用自身环境变量。将 api_key 设为 null 或 "" 会清除已保存 key。
curl -sS -X PUT http://localhost:3000/v1/config/providers/anthropic-prod \ -H 'content-type: application/json' \ -d '{ "id": "anthropic-prod", "adapter": "anthropic", "base_url": null, "timeout_secs": 300 }'创建一个把稳定 model id 绑定到该 provider 的 ModelSpec:
curl -sS -X PUT http://localhost:3000/v1/config/models/research-default \ -H 'content-type: application/json' \ -d '{ "id": "research-default", "provider_id": "anthropic-prod", "upstream_model": "claude-sonnet-4-20250514" }'如果需要 provider 或 quota 故障切换,可以再添加一个 ModelSpec,创建
ModelPoolSpec,然后让 Agent 指向 pool id,而不是单个 model id:
curl -sS -X PUT http://localhost:3000/v1/config/models/research-fallback \ -H 'content-type: application/json' \ -d '{ "id": "research-fallback", "provider_id": "anthropic-prod", "upstream_model": "claude-3-5-haiku-20241022" }'
curl -sS -X PUT http://localhost:3000/v1/config/model-pools/research-pool \ -H 'content-type: application/json' \ -d '{ "id": "research-pool", "members": [ { "model_id": "research-default", "weight": 3 }, { "model_id": "research-fallback", "role": "failover_only" } ], "routing": { "home": "deterministic", "sticky_scope": "thread" }, "switch": { "on_circuit_open": true, "on_quota": true, "quota_retry_after_threshold_secs": 10, "max_switches_per_session": 2 } }'创建引用该稳定 model 或 pool id 的 Agent:
curl -sS -X PUT http://localhost:3000/v1/config/agents/research-assistant \ -H 'content-type: application/json' \ -d '{ "id": "research-assistant", "model_id": "research-pool", "system_prompt": "You help with source-grounded research. Ask before using destructive tools.", "max_rounds": 12, "reasoning_effort": "medium", "plugin_ids": ["permission"], "allowed_tools": ["web_search", "read_document", "summarize"], "context_policy": { "max_context_tokens": 120000, "max_output_tokens": 8192, "min_recent_messages": 8, "enable_prompt_cache": true, "autocompact_threshold": 90000, "compaction_mode": "keep_recent_raw_suffix", "compaction_raw_suffix_messages": 2 } }'用 sections 调优
Section titled “用 sections 调优”AgentSpec.sections 承载类型化插件或解析器配置。key 是 PluginConfigKey::KEY 声明的稳定字符串;value 必须匹配读取该 key 的消费者 schema。
{ "sections": { "retry": { "max_retries": 2, "backoff_base_ms": 500 }, "permission": { "default_behavior": "ask", "rules": [ { "tool": "read_document", "behavior": "allow" }, { "tool": "web_search", "behavior": "ask" }, { "tool": "delete_*", "behavior": "deny" } ] }, "reminder": { "rules": [ { "tool": "Edit(file_path ~ '*.toml')", "output": "any", "message": { "target": "suffix_system", "content": "You edited a TOML file. Run cargo check before finishing." } } ] }, "generative-ui": { "catalog_id": "https://a2ui.org/specification/v0_8/standard_catalog_definition.json", "examples": "Use compact components for status summaries and forms." }, "deferred_tools": { "enabled": true, "default_mode": "deferred", "rules": [ { "tool": "summarize", "mode": "eager" } ], "beta_overhead": 1136.0 }, "compaction": { "summarizer_system_prompt": "You are a conversation summarizer. Preserve decisions, facts, tool results, and unresolved tasks.", "summarizer_user_prompt": "Update the cumulative conversation summary.\n\n<existing-summary>\n{previous_summary}\n</existing-summary>\n\n<new-conversation>\n{messages}\n</new-conversation>", "summary_max_tokens": 1024, "summary_model": "claude-3-haiku", "min_savings_ratio": 0.3 } }}常用 key:
| Key | 消费方 | 说明 |
|---|---|---|
retry | Resolver | 所选模型或 pool member 的 retry/backoff 策略。 |
permission | Permission plugin | 默认 allow/ask/deny 行为和有序工具规则。 |
reminder | Reminder plugin | 按工具和输出匹配并注入 system 或 conversation context 的规则。 |
generative-ui | Generative UI plugin | A2UI catalog id、examples 或完整 prompt instructions。 |
deferred_tools | Deferred tools plugin | 决定哪些 schema 保持 eager、哪些通过 ToolSearch 查找、已提升工具何时重新延迟。 |
skills | Skills discovery plugin | 可选 Skill allowlist,用于注入给模型看的 skill catalog;Skill 内容与激活元数据在 SkillSpec。 |
compaction | Context compaction plugin | 摘要 prompt、摘要模型和可接受的节省阈值。 |
context_policy 是 AgentSpec 顶层字段,不是 section。设置它会启用上下文 transform 和上下文压缩。可选的 compaction section 只用于调优压缩时使用的 summarizer。
plugin_ids 和 section key 不同。plugin_ids 使用插件注册表 ID,例如 permission、reminder、ext-deferred-tools。deferred_tools section key 是 deferred tools 插件的配置 key。
使用插件 section 时,需要保证对应插件 id 已经写入 plugin_ids。retry 由 resolver 读取;当
context_policy 启用内置上下文压缩插件时,compaction 可用。
有些插件也接受构造期默认值。例如 ReminderPlugin::new(rules) 会安装全局默认 reminder 规则;每个 Agent 的 reminder section 则通过 ReminderConfigKey 校验,并在运行时追加到默认规则之后。全局基线用构造期默认值,单个 Agent 的调优用 AgentSpec.sections。
当前只有 deferred-tools 插件实现了 ToolSearch。Skill 通过 catalog 注入并由
skill 工具激活,没有单独的 SkillSearch 工具。Sub-agent 通过
AgentSpec.delegates 显式声明,没有单独的 AgentSearch 工具。
常规 Agent 应保持 active_hook_filter 为空。非空 filter 会禁用未列入 descriptor 名称的插件 hooks、插件工具和 request transforms;它主要用于有意收窄某个 Agent 的活跃行为。
- 先确定稳定的
providers、models和agentsid。客户端按 agent id 调用,Agent 按稳定的ModelSpec.id引用模型。 - 同 provider 内切换模型时改
ModelSpec.upstream_model。需要切换 provider 时,使用另一个ModelSpec或ModelPoolSpec。 - 用
system_prompt、max_rounds、max_continuation_retries、reasoning_effort和context_policy调整整体循环行为。 - 用
allowed_tools和excluded_tools限制可见工具。 - 用
permission规则处理运行时 allow/ask/deny 决策。 - 用
model-pools配置 fallback 成员;用retry调整每个成员的重试策略。 - 只有需要对应行为时,才添加
reminder、generative-ui、deferred_tools和compactionsection。 - 通过
/v1/config/*发布配置;写入成功后,新建 run 才会使用新的 snapshot。
- 对外保持
AgentSpec.id、ModelSpec.id和ProviderSpec.id稳定。 - 使用 canonical 字段:
model_id、provider_id、upstream_model。旧的model、provider不是托管配置字段。 - 将
InferenceOverride.upstream_model视为同 provider 内的覆盖。它不会重新解析AgentSpec.model_id,也不能切换 provider executor;model pool backed agent 会拒绝这个覆盖。 - 生成配置前查询
/v1/config/{namespace}/$schema,并通过/v1/capabilities查看插件config_schemas。AgentSpec、ModelSpec和多个 section 类型会拒绝未知字段。 - 只要插件已注册且 section value 匹配 schema,新增 section 属于兼容变更。不合法 section 会在 runtime snapshot 发布前失败。
- 移除 plugin id 但保留对应 section 不会激活该插件;未被消费的 section key 会作为可能的拼写错误记录日志。
- 已经开始的 run 保持启动时的 snapshot。验证配置变更时,应在写入成功后新建 run。
验证循环 —— 改、跑、看
Section titled “验证循环 —— 改、跑、看”只有配置真的落到下一个 run,配置面才有意义。下面是验证步骤。服务全程不重启。
1. 用原 prompt 跑一次
Section titled “1. 用原 prompt 跑一次”curl -sS -X POST http://localhost:3000/v1/runs \ -H 'content-type: application/json' \ -d '{ "agent_id": "research-assistant", "thread_id": "verify-thread", "messages": [{"role": "user", "content": "找一篇关于珊瑚白化的同行评审文献。"}] }' | jq -r '.response'留意回答的口吻、引用方式与工具选择。
2. 只改 prompt —— 同一 id,不重启
Section titled “2. 只改 prompt —— 同一 id,不重启”curl -sS -X PUT http://localhost:3000/v1/config/agents/research-assistant \ -H 'content-type: application/json' \ -d '{ "id": "research-assistant", "model_id": "research-default", "system_prompt": "你是一个怀疑型研究助理。没有至少两篇独立同行评审文献时拒绝回答;每条都要附引用。", "max_rounds": 12, "plugin_ids": ["permission"], "allowed_tools": ["web_search", "read_document", "summarize"] }'PUT 返回校验通过、已发布的配置。没有 build、没有重新部署、没有 SIGHUP。
3. 再跑一次 —— 观察变化
Section titled “3. 再跑一次 —— 观察变化”curl -sS -X POST http://localhost:3000/v1/runs \ -H 'content-type: application/json' \ -d '{ "agent_id": "research-assistant", "thread_id": "verify-thread-2", "messages": [{"role": "user", "content": "找一篇关于珊瑚白化的同行评审文献。"}] }' | jq -r '.response'新 run 用的是步骤 2 发布的 snapshot。对比两次回答 —— 第二次应该要求两个源、拒绝单源回答。
用全新 thread_id 排除前轮上下文残留;唯一变量是 prompt 改动。
哪些字段可以中途改
Section titled “哪些字段可以中途改”| 改动 | 进行中的 run | 下一个 run |
|---|---|---|
system_prompt | 保持旧 prompt | 新 prompt |
allowed_tools / excluded_tools | 保持旧集合 | 新集合 |
max_rounds、reasoning_effort | 保持旧值 | 新值 |
model_id(只换 binding) | 保持旧 binding | 新 binding |
plugin_ids(新增) | 保持旧集合 | 下一轮起新插件生效 |
plugin_ids(移除) | 保持旧集合 | 被移除插件的 hook 停止触发 |
sections.<plugin>(由 PluginConfigKey 校验) | 保持旧值 | 新的按 key 校验后的值 |
已经开始的 run 永远跑完启动时的 snapshot —— 这是契约。要不等 run drain 就验证改动,用全新 thread_id 起一个新 run。