菜鸟AI - 让提示词生成更简单! 全站导航 全站导航
AI工具安装 新手教程 进阶教程 辅助资源 AI提示词 热点资讯 技术资讯 产业资讯 内容生成 模型技术 AI信息库

已有账号?

首页 > 资讯 > BoxAgnts Agent多轮对话与工具技能调用深度评测
其他资讯

BoxAgnts Agent多轮对话与工具技能调用深度评测

2026-05-31
阅读 0
热度 0
作者 菜鸟AI编辑部
摘要

摘要

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

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

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

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


用户输入:"帮我读一下 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:


// boxagnts-workspace/src/config.rs
pub struct AgentDefinition {
    pub description: Option,    // 描述
    pub model: Option,          // 模型覆盖
    pub temperature: Option,       // 温度覆盖
    pub prompt: Option,         // 系统提示前缀
    pub access: String,                 // 权限:full / read-only / search-only
    pub visible: bool,                  // 是否在 @agent 自动补全中显示
    pub max_turns: Option,         // 最大轮次覆盖
    pub color: Option,          // 终端显示颜色
}

预置的三个 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 的最前面:


// 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 可以覆盖模型和最大轮次:


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 中:


pub async fn run_query_loop(
    client: &AnthropicClient,           // API 客户端
    messages: &mut Vec,       // 对话历史(可变引用)
    tools: &[Box],            // 工具集合
    tool_ctx: &ToolContext,             // 工具执行上下文
    config: &QueryConfig,               // 循环配置
    cost_tracker: Arc,     // 成本追踪
    event_tx: Option>, // 事件推送
    cancel_token: CancellationToken,    // 取消信号
    pending_messages: Option<&mut Vec>, // 待处理消息队列
) -> QueryOutcome

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

参数 设计意图
client 单一入口,但内部通过 ProviderRegistry 可切换 20+ 模型
messages: &mut Vec 直接修改对话历史,每次迭代追加内容
tools: &[Box] 类型擦除的工具集合,AI 通过名称调用
tool_ctx 携带 work_dir、allowed_hosts 等沙箱配置
event_tx 实时推送每轮状态给 Dashboard / TUI
cancel_token 用户可随时中断循环
pending_messages 执行中插入命令(如用户在工具执行时发送新消息)

主循环的五步节拍


┌─────────────────────────────────────────────┐
│                     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:


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 的结构是有层次的:


┌──────────────────────────────────────┐
│ 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 的解法很巧妙:


// 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% 时:


// 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 来"总结"对话历史:


原始对话(可能几千条消息)
     │
     ▼
压缩 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


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 ha ve 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" 时,对话进入工具执行阶段:


┌──────────────────────────────────────────────┐
│  Phase 1: 顺序执行 PreToolUse 预处理          │
│ (每个 tool block 顺序处理,可中断执行)      │
├──────────────────────────────────────────────┤
│  Phase 2: 并行执行非阻塞工具                  │
│  join_all(futures) → 所有工具并发运行          │
│ (阻塞的工具返回预计算的错误结果)            │
└──────────────────────────────────────────────┘

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


execute_tool:工具分发的核心


// boxagnts-query/src/lib.rs
async fn execute_tool(
    name: &str,
    input: &Value,
    tools: &[Box],
    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 模式:


                 ┌──────────────────┐
                 │   Manager Agent  │
                 │  (Opus 等强模型) │
                 │  只做规划和分配   │
                 └────────┬─────────┘
                          │
             ┌────────────┼──────────────┐
             ▼            ▼              ▼
        ┌──────────┐ ┌──────────┐ ┌──────────┐
        │ Executor │ │ Executor │ │ Executor │
        │ (Sonnet) │ │ (Sonnet) │ │ (Sonnet) │
        │ 子任务 A │ │ 子任务 B │ │ 子任务 C │
        └──────────┘ └──────────┘ └──────────┘
           并行执行,各自有独立上下文

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


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 文件:


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 的实现


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) = ¶ms.args {
            content.replace("$ARGUMENTS", args)
        } else {
            content.replace("$ARGUMENTS", "")
        };

        ToolResult::success(prompt)
    }
}

Skill 的双层搜索路径

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


async fn skill_search_dirs(ctx: &ToolContext) -> Vec {
    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


# 代码审查 Skill 模板

请审查:$ARGUMENTS

检查要点:
1. 函数是否过长(>50 行)
2. 是否有未处理的 Result/Option
3. 是否有不必要的 .clone()
4. 命名是否符合 Rust 惯例

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


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

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


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 的多轮对话是一个复杂的控制系统:


System Prompt → API 调用 → 流式解析 → 工具检测 → 工具执行 → 结果注入 → 再次调用
   ↑                                                                         │
   └───────────────────────── 循环直到 end_turn ─────────────────────────────┘

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

机制 解决的问题
Agent 定义系统 多角色、多模型切换
System prompt 构建 Agent 世界观 + prompt caching
max_tokens 恢复 长输出被截断
auto_compact(结构化摘要) 上下文超窗口
tool_result_budget 工具结果堆积
fallback_model 主模型过载
托管 Agent 模式 超复杂任务分解
Skill 系统 专业知识参数化注入
并行工具执行 多步操作加速

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

相关资源

  • BoxAgnts:github.com/guyoung/box…
  • Anthropic Tool Use 文档:docs.anthropic.com/en/docs/bui…

来源:互联网

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

同类文章推荐

相关文章推荐

更多