前言
本文旨在记录和分享这些思考,希望能为关注单Agent多工具协作、工具开发、黄金上下文构建、多Agent系统等方向的同行提供一些参考与启发,也以此作为我个人的技术笔记。
这些内容是在2025年6月到写下这段文字(8月末)期间,我在生产生活中感受到的启发。
在深入AI应用开发之前,我主要关注于传统的后端及UI开发领域。分享这段经历,是希望说明本文的视角更侧重于AI在软件工程中的实际应用,而非纯粹的理论研究。因此,文中对部分AI概念的定义可能更偏向工程实践的解读,建议读者以批判性思维阅读,并参考多方资料进行佐证。
从agent的能力范围说起
为了构建讨论的共同基础,我们先引用Oracle对AI Agent的定义:
什么是 AI Agent? | Oracle 中国
AI Agent是一个能够感知环境、采取行动并从经验中学习的软件实体。我们可以将其视为一个能够根据人类设定的方向,自主执行任务的数字助手或机器人。根据这一定义,一个Agent应具备行动、目标、学习、记忆、感知、推理等核心能力。
接下来,我将结合代码生成(例如bolt.new全自动生成前端项目)这一具体场景,分享我对这些能力的工程化理解。
- 行动,这里专门指与所在环境交互,能够产生副作用。比如修改一段代码(modify),比如创建一个文件(create file),比如执行一段shell命令以达到复杂的功能(rm -rf src/lib/ && mkdir -p src/lib/)。
- 目标/效用,首先我要阐述一个迷思,在orcale的文章提到,要能够通过效用能力来衡量agent实现目标的程度。但事实上,很多结果例如一个AI生成的前端项目无法用静态的方法评估实现的完整性和正确性,更可靠的方式其实是用专门的agent去评估(之后会描述流程)。因此这里我将"目标/效用"能力定义为,agent需要能够根据原始输入(包括定义的目标,包括环境状态)去列举to-do list,在一个个Task中更新to-do list中的pending/progressing/completed状态,并推动自己前进知道这个agent认为自己已经完成,可以交付。
- 学习,我们应用的LLM能力一般是去请求厂家提供的API,很可惜厂家的模型都是固定的,一般不会去进化。因此为了让agent去获得专业领域知识,例如我要让agent在UI方面永远使用shadcn,学习到一些“为LLM自动生成前端项目”而特意设计的流程trick,我可以在system instruction或者user prompt去带入这些(当然不是无脑的带入,之后会详细描述)。请注意,这些并不设计记忆功能,因为他们是与上下文(context)无关的。
- 记忆力,由上下文context提供,来自过去的对话记录和当前的环境。另外,很多场景下LLM context并不能容纳所有的知识(例如deepseek V3 只有64K),在应用中还会去使用RAG,graphRAG去寻找黄金上下文(更加有效而且更加节省token)
- 感知,只指的是感知环境。可以由tool提供让agent主动感知环境,例如list_files, read_file, glimpse_screen。一些agent的流程中也会在每次构建单次user prompt的时刻,将环境中的信息带入从而被迫的感知环境。
- 推理,笔者暂时无法理解这个能力该如何达到,我的实践是让LLM给出做某个行为的依据(thinking),单词请求给出过去对话记录(包括thinking)和todo-list,让stateless 的LLM知道自己在做什么,最明智的决定下一步。实话实说,“推理”这个能力貌似只能够在大模型本身层面实现。
prompt工程 - 有效的输入获得优秀的输出
选择协议:为什么是XML
在 LLM 应用开发中,如何有效约束模型输出是一个核心挑战。尽管 JSON 和 Protobuf 在传统 Web 开发中占据主导地位,但在 LLM 场景下,XML 却展现出独特优势。
正如文章 从Cline原理看AI Agent设计的一般范式 - 知乎
所述,XML 具备以下天然优势:
- XML天生适合人类阅读和流式输出。当LLM生成到
</content>标签时,程序就能立刻截取内容写入文件,完全不需要等整个响应结束。- 实现流式写文件特性。
- 人类友好的读取方式。相比 JSON 的简洁语法,XML 的显式结构更适合表达复杂的嵌套关系
当然也有例外,比如Google的Google AI平台允许用户使用JSON Schema,但是schema发到后台之后,再怎么与模型本身处理就不得而知了, 其后端处理机制仍不透明。综合考虑,XML 仍是当前 LLM 输出协议的最优选择。
总之,具有明确打开和关闭标签的XML语法提供了清晰的背景并降低了歧义。这有助于LLM解释查询或答案的不同部分。 相反,JSON的简单结构(指定和字符使用相同的语法)会导致误解。
XML是显示层次数据和复杂结构的理想选择。 如果LLMS替换复杂的指令或嵌套数据(比如调用Tool要用到的参数),则此功能至关重要。 XML的明确定义的结构使模型更容易理解不同数据元素之间的关系。
system prompt
System prompt 可以理解为 Agent 的"基础认知层"——虽然每次 LLM 请求都需要携带这些内容,但它们构成了 Agent 在特定场景下的稳定知识体系。
例如以下资源很好的制定了一个针对本文讨论的自动编码的良好System Prompt
Roo-Code/src/core/prompts/system.ts at main · RooCodeInc/Roo-Code
其中包含以下核心Section:
- 角色定位:明确 Agent 的身份和职责范围
- 工具使用规范:定义可用工具集及其调用规则
- 能力边界:明确告知 Agent 的能力限制
- 硬性约束:不可违背的行为准则
- 系统信息:操作系统、工作目录等环境参数
- 任务目标:当前需要完成的具体任务
- 扩展规则:如语言偏好、代码风格等个性化设置
以下是一次完整的system prompt实际内容, TOKEN 7720
https://gist.github.com/scarletborder/79c026fa2c1fc3f678f7862062a67d82
在多 Agent 协作场景中,功能相近的 Agent(如"自动编辑"和"仅询问"模式), 他们很大程度上可以复用部分自然语言的描述,可以通过条件语句复用部分提示内容,通过全局开关控制特定功能的可见性。例如告知允许使用某种能力,严肃告知你没有某种能力。
user prompt
User prompt 是在每次LLM请求中是动态变化的,其构成遵循以下模式:
zip(thinkings + 之前messages) + (thinkings + 最近messages) + 被动感知环境内容
thinkings
thinkings在本场景中是通过Rules, 约束LLM在每次输出的最开始时候,先阐述做出工具调用决策的理由和思考。
被动感知环境内容
这里环境内容需要包含的东西有很多,可以参考以下资源定义的多个要素
- 最近编辑文件列表:帮助 Agent 理解当前工作重点
- TODO List:驱动 Agent 循环执行的核心机制, 将在后文讲解。
- 工作区状态:项目结构和文件系统信息
例如几份示例
- 暂时没有创建todo_list : https://gist.github.com/scarletborder/c814a4368b8866890be76cc91739f0e6
- 已经创建了todo_list,TOKEN 1189 : https://gist.github.com/scarletborder/94ad4405fb6f5c7ad5ec13e49b179281
其他的话
system prompt确实很长,能有将近10k token。最好的方法是微调一个模型,学习这些知识,这才是真正的学习。
我不清楚但是很怀疑,像cursor和copilot这些工具实际上调用的是专用经过微调的模型。另外也有可能是LLM API Provider为这些产品提供了input cache,system prompt文本不易改变,缓存命中率很高。
tool - 为agent提供能力
一个有效agent运转需要五花八门的tools提供各种各样的能力。Tool 是 Agent 与环境交互的桥梁。
Agent -> Tool
在实践中,LLM 的输出被严格限制为"工具调用指令 + 参数"的形式。每轮对话中,LLM 仅输出:
<thinking>决策理由</thinking>
<tool_call>工具名称</tool_call>
<parameters>调用参数</parameters>
关于工具粒度的设计,业界尚无定论:
- 细粒度方案:工具数量多,每个工具职责单一
- 粗粒度方案:工具数量少,每个工具功能丰富
无论采用哪种方案,每个工具都应具备明确的能力边界。例如:
read_file(path)- 读取指定文件内容write_file(path, content)- 将内容写入指定文件
Tool -> Agent
工具本身是无状态的执行单元,仅根据 LLM 的输出决定自己的行为。而 instruction prompt 和 user prompt 的生成则由 Agent 框架通过预定义的流程控制,这涉及到:
- 工具输入输出的捕获和格式化
- 历史消息的压缩和筛选
- 每轮对话中新的环境状态
tool本身只将LLM输出的内容来作为自己的输入。
tool描述
tool这么强,如何让LLM知道这些一个个tool应该在什么时机去调度,能做什么呢,如何调度?
为了让 LLM 准确理解和调用工具,需要在 system prompt 中嵌入规范的工具描述。一个完整的工具描述应包含:
- 功能简介:简明扼要说明工具用途
- 参数定义:详细说明每个参数的类型、格式和约束
- 使用指南:描述调用时机和注意事项
- 示例用法:提供典型的调用案例
以下文件很好的说明了这一点
Roo-Code/src/core/prompts/tools/write-to-file.ts at c479678d8e006818d4ea2f24f5d6df0eb1872e1c · RooCodeInc/Roo-Code
展示了一个标准的工具描述实现,其中使用 XML 格式定义了清晰的调用规范。
施工中
agent 循环
在接收到LLM流输出,如何知道合适调度tool, 怎么调度tool
因为使用xml, 所以可以很清晰地知道API返回的流式输出文字应该对应什么部分.
例如,如果当前xml标签是thinking,那么tool调度将仅仅将文字显示到UI上,让用户看个乐,知道应用正在思考.
当然我们的AI IDE没有这种情形"流式返回的部分chunk文字确实有用",假设一个情景,xai的那个数字人,在那种情景下流式输出的部分chunk就极为重要,因为我们不能等待请求结束后再一遍读,必须是输出一点,数字人读一点.假设他们的场景中,xml标签是output-voice,那么就不必等到流式相对结束(接受到匹配的xml标签)后再输出. 注意我这里说的是"相对结束",因为xml标签匹配到后不一定流式stream网络请求结束,例如在一条消息中输出多个工具调度.
tool list分支
task, 子task
一个task的完成
todo list驱动的task
上下文与记忆
agent漫谈
我接手的项目使用到了非常多的agent,他们在不同的时机做不同的事情,例如有些agent负责编写代码,有些 agent负责修复代码,有些agent负责测试样例。
评论区 (0)