BoxAgnts介绍(6)——Agent多轮对话及Tool、Skill调用

如果你只和 ChatGPT 聊过天,你可能会觉得 AI Agent 就是"把 prompt 发给 API,把回复显示出来"。

真实情况要复杂得多。下面是 BoxAgnts 中一个完整的 Agent 交互流程:

php 复制代码
用户输入:"帮我读一下 config.toml,把 port 改成 9090"

  


1. 用户消息加入对话历史

2. 构建 system prompt(工具列表 + 技能列表 + AGENTS.md + Agent 角色定义)

3. 调用 LLM API → 流式接收响应

4. AI 决定调用工具:tool_use("read", {path: "config.toml"})

5. 执行 read 工具(WASM 沙箱内)

6. 工具结果注入对话历史

7. 再次调用 API → AI 分析配置

8. AI 决定调用工具:tool_use("edit", {path: "config.toml", old: "port = 8080", new: "port = 9090"})

9. 执行 edit 工具

10. 工具结果注入对话

11. 再次调用 API → AI 回复:"已将端口从 8080 改为 9090"

12. end_turn → 对话结束

这个过程涉及 3 次 API 调用、2 次工具执行、流式推送、上下文管理。本文拆解每个环节的设计和实现。


Agent 定义:给 Agent 一个"身份"

在开始推理循环之前,需要先定义 Agent 的"角色"。BoxAgnts 内置了三个预置 Agent:

rust 复制代码
// boxagnts-workspace/src/config.rs

pub struct AgentDefinition {

    pub description: Option<String>,    // 描述

    pub model: Option<String>,          // 模型覆盖

    pub temperature: Option<f64>,       // 温度覆盖

    pub prompt: Option<String>,         // 系统提示前缀

    pub access: String,                 // 权限:full / read-only / search-only

    pub visible: bool,                  // 是否在 @agent 自动补全中显示

    pub max_turns: Option<u32>,         // 最大轮次覆盖

    pub color: Option<String>,          // 终端显示颜色

}

预置的三个 Agent 角色:

| Agent | 权限 | prompt 特征 | 适用场景 |

|-------|------|------------|----------|

| build | full | "You are the build agent. Focus on implementing..." | 编码、修改文件 |

| plan | read-only | "You are the plan agent. You can read files and analyze..." | 代码分析、架构设计 |

| explore | search-only | "Fast search-only agent for code exploration" | 快速搜索、文件定位 |

Agent prompt 如何注入

Agent 定义中的 prompt 字段会在查询循环启动时被注入到 system prompt 的最前面:

rust 复制代码
// boxagnts-query/src/query.rs

if let Some(ref agent) = config.agent_definition {

    if let Some(ref agent_prompt) = agent.prompt {

        patched.system_prompt = Some(match &config.system_prompt {

            Some(existing) => format!("{}\n\n{}", agent_prompt, existing),

            None => agent_prompt.clone(),

        });

    }

}

同时,Agent 可以覆盖模型和最大轮次:

rust 复制代码
let effective_model = if let Some(ref agent) = config.agent_definition {

    agent.model.clone().unwrap_or_else(|| config.model.clone())

} else {

    config.model.clone()

};

  


let effective_max_turns = config.agent_definition

    .as_ref()

    .and_then(|a| a.max_turns)

    .unwrap_or(config.max_turns);

这意味着用户可以通过 Agent 定义实现"同一个会话中不同阶段使用不同模型和角色"------比如规划阶段用 read-only 的慢思考模型,执行阶段用 full-access 的快速模型。


run_query_loop:Agent 的心脏

run_query_loop() 是 BoxAgnts 中最核心的函数,位于 boxagnts-query crate 中:

rust 复制代码
pub async fn run_query_loop(

    client: &AnthropicClient,        // API 客户端

    messages: &mut Vec<Message>,     // 对话历史(可变引用)

    tools: &[Box<dyn Tool>],         // 工具集合

    tool_ctx: &ToolContext,          // 工具执行上下文

    config: &QueryConfig,            // 循环配置

    cost_tracker: Arc<CostTracker>,  // 成本追踪

    event_tx: Option<mpsc::UnboundedSender<QueryEvent>>, // 事件推送

    cancel_token: CancellationToken, // 取消信号

    pending_messages: Option<&mut Vec<String>>, // 待处理消息队列

) -> QueryOutcome

这个函数签名本身就是一篇架构文档。每个参数都是一个设计决策:

| 参数 | 设计意图 |

|------|----------|

| client | 单一入口,但内部通过 ProviderRegistry 可切换 20+ 模型 |

| messages: &mut Vec<Message> | 直接修改对话历史,每次迭代追加内容 |

| tools: &[Box<dyn Tool>] | 类型擦除的工具集合,AI 通过名称调用 |

| tool_ctx | 携带 work_dir、allowed_hosts 等沙箱配置 |

| event_tx | 实时推送每轮状态给 Dashboard / TUI |

| cancel_token | 用户可随时中断循环 |

| pending_messages | 执行中插入命令(如用户在工具执行时发送新消息) |

主循环的五步节拍

arduino 复制代码
┌─────────────────────────────────────────────┐

│                  loop {                       │

│                                               │

│  ① 检查终止条件                                │

│     · turn > max_turns ? → EndTurn           │

│     · cancel_token ?    → Cancelled          │

│     · budget exceeded?  → BudgetExceeded     │

│                                               │

│  ② 预处理消息                                │

│     · drain pending_messages queue           │

│     · apply_tool_result_budget (截断旧结果)    │

│     · auto_compact (上下文压缩)               │

│                                               │

│  ③ 构建 system prompt + 调用 LLM API         │

│     · 注入 Agent 定义 / AGENTS.md            │

│     · 构建 CreateMessageRequest              │

│     · 流式接收 StreamEvent                   │

│     · 累积 text / thinking / tool_use blocks │

│                                               │

│  ④ 处理响应                                  │

│     · end_turn → 返回                         │

│     · tool_use → 并行执行工具 → 结果注入 → 继续 │

│     · max_tokens → 恢复对话 → 继续             │

│                                               │

│  ⑤ 错误恢复                                  │

│     · overloaded → switch fallback model     │

│     · stream stall → retry (最多 2 次)        │

│                                               │

│  }                                            │

└─────────────────────────────────────────────┘

System Prompt 构建:Agent 的"世界观"

在每一轮 API 调用前,BoxAgnts 都会构建完整的 system prompt:

rust 复制代码
fn build_system_prompt(config: &QueryConfig) -> SystemPrompt {

    let opts = SystemPromptOptions {

        custom_system_prompt: config.system_prompt.clone(),     // 用户自定义

        append_system_prompt: config.append_system_prompt.clone(), // 追加内容

        output_style: config.output_style,                      // 输出风格

        custom_output_style_prompt: config.output_style_prompt.clone(),

        working_directory: config.working_directory.clone(),    // 当前工作目录

        ..Default::default()

    };

  


    let text = boxagnts_core::system_prompt::build_system_prompt(&opts);

    SystemPrompt::Text(text)

}

System prompt 的结构是有层次的:

scss 复制代码
┌──────────────────────────────────────┐

│ Agent 角色定义 (build/plan/explore)   │  ← AgentDefinition.prompt

├──────────────────────────────────────┤

│ 核心能力声明                          │

│ · 可用工具列表 (16+ 个)               │  ← 由 tools 参数动态生成

│ · 技能列表                           │  ← 由 SkillTool 发现

│ · 输出格式要求                        │

│ · 安全边界                           │

├──────────────────────────────────────┤

│ AGENTS.md 内容                       │  ← 用户项目级行为规范

├──────────────────────────────────────┤

│ 动态边界标记                          │

│ --- 以上缓存,以下不缓存 ---          │

├──────────────────────────────────────┤

│ 会话特定信息                          │  ← 当前工作目录、时间等

└──────────────────────────────────────┘

--- 以上缓存,以下不缓存 --- 这个分割线是一个聪明的设计------Anthropic API 支持 prompt caching,缓存以上部分可以显著降低每次 API 调用的 token 成本。


max_tokens 恢复:Agent 的"断点续传"

当 AI 的回复达到 max_tokens 限制时,模型会中途切断输出。普通 API 调用到这里就结束了------但 Agent 不能停。

BoxAgnts 的解法很巧妙:

rust 复制代码
// boxagnts-query/src/query.rs

const MAX_TOKENS_RECOVERY_LIMIT: u32 = 3;

  


const MAX_TOKENS_RECOVERY_MSG: &str =

    "Output token limit hit. Resume directly --- no apology, no recap of what \

     you were doing. Pick up mid-thought if that is where the cut happened. \

     Break remaining work into smaller pieces.";

当检测到 stop_reason == "max_tokens" 时:

  1. 将部分回复作为 assistant 消息加入对话

  2. 追加一条特殊的 user 消息(MAX_TOKENS_RECOVERY_MSG

  3. 继续循环------模型会从中断处继续生成

提示词里的细节值得注意------"no apology, no recap"------因为 LLM 被中断后的本能反应是"抱歉,我刚才被打断了,让我重新开始..."。这会导致无用输出。这条提示词直接禁止了这种模式。


auto_compact:当上下文太长时

LLM 的上下文窗口是有限的。当对话越来越长,工具结果越积越多,总有塞不下的时刻。

BoxAgnts 的响应是自动压缩。触发条件是当 token 估算达到上下文窗口的 90% 时:

rust 复制代码
// boxagnts-query/src/compact.rs

const AUTOCOMPACT_TRIGGER_FRACTION: f64 = 0.90;

const WARNING_PCT: f64 = 0.80;   // 80% 时警告

const CRITICAL_PCT: f64 = 0.95;  // 95% 时严重警告

压缩策略的核心是调用另一个 LLM 来"总结"对话历史:

sql 复制代码
原始对话(可能几千条消息)

      │

      ▼

压缩 Prompt(NO_TOOLS_PREAMBLE → 强制总结模式)

      │

      ▼

LLM 生成结构化摘要:

  · Primary Request and Intent    (用户原始请求)

  · Key Technical Concepts        (关键技术概念)

  · Files and Code Sections       (涉及的文件和代码段)

  · Errors and fixes              (遇到的错误和修复)

  · Pending Tasks                 (待完成任务)

  · Current Work                  (当前进度)

      │

      ▼

摘要替换早期对话历史,最近 10 条消息保留原文

压缩 prompt 中有一个关键设计------NO_TOOLS_PREAMBLE

sql 复制代码
CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.

- Do NOT use Read, Bash, Grep, Glob, Edit, Write, or ANY other tool.

- You already have all the context you need in the conversation above.

- Tool calls will be REJECTED and will waste your only turn.

如果压缩的 LLM 尝试调用工具,整个压缩就白费了。这个 preamble 防止了这种元递归。


Tool 执行:从 AI 决定到运行结果

当 LLM 返回 stop_reason == "tool_use" 时,对话进入工具执行阶段:

yaml 复制代码
┌──────────────────────────────────────────────┐

│  Phase 1: 顺序执行 PreToolUse 预处理           │

│  (每个 tool block 顺序处理,可中断执行)        │

├──────────────────────────────────────────────┤

│  Phase 2: 并行执行非阻塞工具                    │

│  join_all(futures) → 所有工具并发运行           │

│  (阻塞的工具返回预计算的错误结果)               │

└──────────────────────────────────────────────┘

关键设计点:工具结果以 user 消息格式注入。这利用了 LLM 的消息角色语义------Assistant 发起了工具调用,User(即系统代用户)返回了工具结果。模型将此理解为"用户回答了你的请求",自然地进行下一轮推理。


execute_tool:工具分发的核心

rust 复制代码
// boxagnts-query/src/lib.rs

async fn execute_tool(

    name: &str,

    input: &Value,

    tools: &[Box<dyn Tool>],

    ctx: &ToolContext,

) -> ToolResult {

    let tool = tools.iter().find(|t| t.name() == name);

  


    match tool {

        Some(tool) => {

            debug!(tool = name, "Executing tool");

            tool.execute(input.clone(), ctx).await

        }

        None => {

            warn!(tool = name, "Unknown tool requested");

            ToolResult::error(format!("Unknown tool: {}", name))

        }

    }

}

极其简单的实现------一个线性查找。tools 向量通常只有十几个元素,线性查找的开销可以忽略。简洁比复杂更可靠。


托管 Agent 模式:Manager-Executor 架构

当任务复杂度超出单个 Agent 的能力范围时,BoxAgnts 提供了托管 Agent 模式:

scss 复制代码
                    ┌──────────────────┐

                    │  Manager Agent   │

                    │  (Opus 等强模型)   │

                    │  只做规划和分配     │

                    └────────┬─────────┘

                             │

              ┌──────────────┼──────────────┐

              ▼              ▼              ▼

        ┌──────────┐  ┌──────────┐  ┌──────────┐

        │ Executor │  │ Executor │  │ Executor │

        │ (Sonnet)  │  │ (Sonnet)  │  │ (Sonnet)  │

        │ 子任务 A   │  │ 子任务 B   │  │ 子任务 C   │

        └──────────┘  └──────────┘  └──────────┘

            并行执行,各自有独立上下文

Manager 的 system prompt 被注入托管模式指令:

rust 复制代码
pub fn managed_agent_system_prompt(config: &ManagedAgentConfig) -> String {

    format!(r#"

## Managed Agent Mode

  


You are the MANAGER in a manager-executor architecture.

  


### Your Role

- You coordinate work but do NOT execute tasks directly.

- Delegate all implementation work to executor agents.

- Each executor uses model `{executor_model}` with up to {max_turns} turns.

- You may run up to {max_concurrent} executors in parallel.

  


### Workflow

1. Analyze the user's request and break into sub-tasks.

2. Spawn executors using the Agent tool.

3. Review results. If insufficient, spawn follow-up executors.

4. Synthesize all results into a coherent response.

"#, ...)

}

Manager 自己不执行工具------它只做规划、分配和结果合成。Executor 是普通的 Agent 实例,拥有完整的工具集。这个模式将"思考"和"执行"分离,既避免了单 Agent 的上下文膨胀,又实现了真正的并行处理。


Skill 系统:让 Agent 学会"专业技能"

Tool 是 Agent 的"手"------读文件、写文件、执行命令。Skill 是 Agent 的"专业知识"------代码审查方法论、CSS 重构指南、前端组件模板。

Skill 的文件格式

一个 Skill 就是一个 SKILL.md 文件:

objectivec 复制代码
app/extensions/skills/

├── code-review/SKILL.md

├── css-refactor-advisor/SKILL.md

├── current-weather/SKILL.md

├── weather-forecast/SKILL.md

└── front-component-generator/SKILL.md

SkillTool 的实现

rust 复制代码
pub struct SkillTool;

  


#[async_trait]

impl Tool for SkillTool {

    fn name(&self) -> &str { "skill-tool" }

  


    async fn execute(&self, input: Value, ctx: &ToolContext) -> ToolResult {

        let params: SkillInput = serde_json::from_value(input)?;

  


        // "skill": "list" → 列出所有可用技能

        if params.skill == "list" {

            return list_skills(&dirs).await;

        }

  


        // 查找并读取 SKILL.md

        let (skill_path, raw) = find_and_read_skill(&skill_name, &dirs).await?;

  


        // 去除 YAML frontmatter

        let content = strip_frontmatter(&raw);

  


        // 替换 $ARGUMENTS 占位符

        let prompt = if let Some(args) = &params.args {

            content.replace("$ARGUMENTS", args)

        } else {

            content.replace("$ARGUMENTS", "")

        };

  


        ToolResult::success(prompt)

    }

}

Skill 的双层搜索路径

Skill 的搜索优先工作空间目录,然后才是应用扩展目录:

rust 复制代码
async fn skill_search_dirs(ctx: &ToolContext) -> Vec<PathBuf> {

    let mut dirs = vec![

        ctx.get_workspace_extensions_dir().await.join("skills")  // 项目级

    ];

    dirs.push(ctx.get_app_extensions_dir().await.join("skills")); // 全局级

    dirs

}

这意味着你可以在项目目录下定义项目专用的 Skill(如"理解这个项目的 build system"),同时使用全局 Skill(如"通用的代码审查标准")。项目级 Skill 优先于全局 Skill。

$ARGUMENTS 占位符

Skill 模板中最关键的机制是 $ARGUMENTS

markdown 复制代码
# 代码审查 Skill 模板

  


请审查:$ARGUMENTS

  


检查要点:

1. 函数是否过长(>50 行)

2. 是否有未处理的 Result/Option

3. 是否有不必要的 .clone()

4. 命名是否符合 Rust 惯例

AI 调用时传入 args: "src/main.rs"$ARGUMENTS 就被替换为 src/main.rs。这让 Skill 从"静态知识"变成了"参数化工具"。


流式推送:让用户看到 Agent 在"思考"

整个查询循环通过 event_tx 通道实时推送状态:

rust 复制代码
pub enum QueryEvent {

    Token { text: String },                    // 逐 token 推送

    ToolStart { tool_name, tool_id, input },   // 工具开始

    ToolEnd { tool_name, tool_id, result },    // 工具结束

    Status(String),                            // 状态消息

}

这些事件通过 WebSocket 实时推送到 Dashboard 前端,用户可以看到 Agent 的每一步决策------不是面对一个黑箱。


小结

AI Agent 的多轮对话是一个复杂的控制系统:

sql 复制代码
System Prompt → API 调用 → 流式解析 → 工具检测 → 工具执行 → 结果注入 → 再次调用

     ↑                                                                         │

     └───────────────────────── 循环直到 end_turn ─────────────────────────────┘

这个循环的鲁棒性取决于:

| 机制 | 解决的问题 |

|------|-----------|

| Agent 定义系统 | 多角色、多模型切换 |

| System prompt 构建 | Agent 世界观 + prompt caching |

| max_tokens 恢复 | 长输出被截断 |

| auto_compact(结构化摘要) | 上下文超窗口 |

| tool_result_budget | 工具结果堆积 |

| fallback_model | 主模型过载 |

| 托管 Agent 模式 | 超复杂任务分解 |

| Skill 系统 | 专业知识参数化注入 |

| 并行工具执行 | 多步操作加速 |

每一个机制都对应一个真实的生产问题。把它们做对,Agent 才能从"能跑"变成"可靠"。

相关资源

相关推荐
Web极客码1 小时前
AI的下一个风口:智能助力超越ChatGPT
服务器·人工智能·ai编程
人工智能培训2 小时前
数字孪生赋能建筑行业 解锁工程全周期智慧管理
大数据·人工智能·机器学习·prompt·agent
AirGo.3 小时前
Eino Skill 机制架构分析
agent·skill·eino
吾AI科技3 小时前
Agent的诞生(二):让模型开始调用工具
ai·agent·智能体·deepseek
lili00123 小时前
AI编程三件套CI集成与质量门禁:从“看起来对“到“证据确凿“
java·人工智能·python·ci/cd·ai编程
大家的林语冰3 小时前
AI 遥控代码截图,录制终端动画,定制自动化批量制图流程,解放你的双手~
前端·ai编程·trae
是烨笙啊3 小时前
AI编程:项目管理
ide·人工智能·ai编程
是烨笙啊3 小时前
AI编程:胶水编程
ai编程
久违 °11 小时前
【AI-Agent】TagMatrix 数据标注工具开发
人工智能·数据分析·go·agent·数据隐私