跳转到内容

通过配置调优 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}/overridesruntime-safe 的工具描述覆盖
MCP server/v1/config/mcp-servers/{id}外部 MCP server 连接
Skill/v1/config/skills/{id}可复用指令、参数与允许工具集合
Plugin sectionAgentSpec.sectionsPluginConfigKey::KEY 归档的每 Agent 类型化配置
Runtime codeAgentRuntimeBuilder注册工具、provider factory、插件、backend

托管配置 runtime 支持的 provider adapter 会在 GET /v1/capabilitiessupported_adapters 中返回。该列表由当前链接的 genai 版本在运行时推导; 上游 dependency 支持新 adapter 后,这里会随之出现。

本地 Agent 执行通过稳定注册表 ID 解析 model 与 provider:

AgentSpec.model_id
-> ModelSpec { provider_id, upstream_model }
-> ProviderSpec { adapter, api_key, base_url, timeout_secs }
-> LlmExecutor

AgentSpec.model_id 不是上游 provider 模型名。它是 Agent 和客户端使用的稳定 ModelSpec.idModelSpec.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 配置。

创建或更新 provider。省略 api_key 时,provider adapter 会使用自身环境变量。将 api_key 设为 null"" 会清除已保存 key。

Terminal window
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

Terminal window
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:

Terminal window
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:

Terminal window
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
}
}'

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消费方说明
retryResolver所选模型或 pool member 的 retry/backoff 策略。
permissionPermission plugin默认 allow/ask/deny 行为和有序工具规则。
reminderReminder plugin按工具和输出匹配并注入 system 或 conversation context 的规则。
generative-uiGenerative UI pluginA2UI catalog id、examples 或完整 prompt instructions。
deferred_toolsDeferred tools plugin决定哪些 schema 保持 eager、哪些通过 ToolSearch 查找、已提升工具何时重新延迟。
skillsSkills discovery plugin可选 Skill allowlist,用于注入给模型看的 skill catalog;Skill 内容与激活元数据在 SkillSpec
compactionContext compaction plugin摘要 prompt、摘要模型和可接受的节省阈值。

context_policyAgentSpec 顶层字段,不是 section。设置它会启用上下文 transform 和上下文压缩。可选的 compaction section 只用于调优压缩时使用的 summarizer。

plugin_ids 和 section key 不同。plugin_ids 使用插件注册表 ID,例如 permissionreminderext-deferred-toolsdeferred_tools section key 是 deferred tools 插件的配置 key。 使用插件 section 时,需要保证对应插件 id 已经写入 plugin_idsretry 由 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 的活跃行为。

  1. 先确定稳定的 providersmodelsagents id。客户端按 agent id 调用,Agent 按稳定的 ModelSpec.id 引用模型。
  2. 同 provider 内切换模型时改 ModelSpec.upstream_model。需要切换 provider 时,使用另一个 ModelSpecModelPoolSpec
  3. system_promptmax_roundsmax_continuation_retriesreasoning_effortcontext_policy 调整整体循环行为。
  4. allowed_toolsexcluded_tools 限制可见工具。
  5. permission 规则处理运行时 allow/ask/deny 决策。
  6. model-pools 配置 fallback 成员;用 retry 调整每个成员的重试策略。
  7. 只有需要对应行为时,才添加 remindergenerative-uideferred_toolscompaction section。
  8. 通过 /v1/config/* 发布配置;写入成功后,新建 run 才会使用新的 snapshot。
  • 对外保持 AgentSpec.idModelSpec.idProviderSpec.id 稳定。
  • 使用 canonical 字段:model_idprovider_idupstream_model。旧的 modelprovider 不是托管配置字段。
  • InferenceOverride.upstream_model 视为同 provider 内的覆盖。它不会重新解析 AgentSpec.model_id,也不能切换 provider executor;model pool backed agent 会拒绝这个覆盖。
  • 生成配置前查询 /v1/config/{namespace}/$schema,并通过 /v1/capabilities 查看插件 config_schemasAgentSpecModelSpec 和多个 section 类型会拒绝未知字段。
  • 只要插件已注册且 section value 匹配 schema,新增 section 属于兼容变更。不合法 section 会在 runtime snapshot 发布前失败。
  • 移除 plugin id 但保留对应 section 不会激活该插件;未被消费的 section key 会作为可能的拼写错误记录日志。
  • 已经开始的 run 保持启动时的 snapshot。验证配置变更时,应在写入成功后新建 run。

只有配置真的落到下一个 run,配置面才有意义。下面是验证步骤。服务全程不重启。

Terminal window
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,不重启”
Terminal window
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。

Terminal window
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 改动。

改动进行中的 run下一个 run
system_prompt保持旧 prompt新 prompt
allowed_tools / excluded_tools保持旧集合新集合
max_roundsreasoning_effort保持旧值新值
model_id(只换 binding)保持旧 binding新 binding
plugin_ids(新增)保持旧集合下一轮起新插件生效
plugin_ids(移除)保持旧集合被移除插件的 hook 停止触发
sections.<plugin>(由 PluginConfigKey 校验)保持旧值新的按 key 校验后的值

已经开始的 run 永远跑完启动时的 snapshot —— 这是契约。要不等 run drain 就验证改动,用全新 thread_id 起一个新 run。