提示词工程——Agent的大脑设定
掌握如何通过 System Prompt 定义 Agent 的角色、约束和输出格式。这是 Agent 的“意识形态”。
作为一名 React 开发者,你已经掌握了构建复杂交互式应用的核心能力。其实 Agent(智能体)的开发逻辑与 React 有异曲同工之妙:React 是根据 State(状态)渲染 UI,而 Agent 是根据 Prompt 和 Context(上下文)推导 Action(行动)。
在 React 开发中,我们习惯通过 props 或 context 来向组件传递配置;在 Agent 开发中,System Prompt 就相当于 Agent 的“底层配置(System Config)”。
它不仅仅是用户输入,而是定义 Agent 灵魂和边界的指令。
作为一个 React 开发者,你可以把对话消息想象成一个数组:
JAVASCRIPT
const messages = [
{ role: "system", content: "你是一个专业的港口调度员..." }, // 这是配置层
{ role: "user", content: "今天天气如何?" } // 这是数据层
];
如果我们要写一个 “天气查询 Agent”:
方案 A(误区):System Prompt 为空,用户问“今天天气”,模型可能直接回答。
方案 B(Agent 化):System Prompt 告诉模型:“你是一个查询助手。如果用户问天气,你不准直接回答,必须调用
get_weather工具。”
思考一下:
如果在代码里,我们把 role: "system" 的内容改成了:“你现在是一个傲娇的猫娘,且回复内容必须包含 Emoji”,而用户输入的 role: "user" 依然是“今天天气”。
你觉得大模型输出的风格会由哪一个 role 决定?
在开发 Agent 时,System Prompt 就像是你的“应用配置文件”或“Redux 的初始状态声明”。它为整个对话设定了基础规则,且优先级极高。用户(User)的输入是在这个规则框架内进行的“数据流”。
第一阶段:掌握 Agent 的“指令集”
要写出一个 Agent,System Prompt 必须包含以下三要素:
Role (角色):定义它是谁(例如:你是一个专业的前端自动化测试员)。
Constraints (约束):规定它不能做什么,或者必须遵循什么(例如:只准回答代码相关问题;只能以 JSON 格式输出)。
Task & Flow (任务流程):规定它如何处理输入(例如:先分析用户的意图,再决定是否调用工具)。
作为一个 React 开发者,你一定很熟悉 JSON。Agent 经常需要输出结构化的数据(比如为了让你的前端组件能解析)。
为了让 Agent 真正成为一个可用的“后端服务”,我们需要在 System Prompt 中加入极强悍的约束。
核心技巧:输出协议化
你可以把 System Prompt 看作是定义 API Response Schema。
比如,我们需要它输出这样的格式:
JSON
{
"summary": "候选人前端基础扎实",
"score": 80,
"strengths": ["React Hooks 熟练", "工程化经验丰富"]
}
思考一下,如果你要在 System Prompt 中下达死命令,确保模型不吐露任何废话,你会怎么写?
在开发 Agent 时,我们不仅要告诉模型“做(Do)”,更要告诉它“禁止(Don't)”。
这也是 React 开发中类似 Type Check 或 Schema Validation 的逻辑。
场景练习:
你正在为一个“智能客服 Agent”写 System Prompt。你的约束是:
只能回答公司业务相关问题。
用户如果问其他问题(比如“帮我写个 Python 脚本”),模型必须返回特定的错误 JSON 格式:
{"error": "SCOPE_ERROR", "msg": "对不起,我无法协助非业务问题。"}。
请问,你在 System Prompt 中会如何定义这种“边界拦截”的逻辑?
这种“边界拦截”逻辑在 Agent 开发中被称为 Guardrails(护栏)。
在 System Prompt 中,你会这样写:
“如果用户的输入不属于业务范畴(如:代码、诗歌、生活琐事),那么你必须且只能返回以下 JSON 格式:
{"error": "SCOPE_ERROR", "msg": "..."}。”
这种 “If-Then” 的确定性逻辑,就是将大模型从一个“聊天机器人”转变为一个“可靠的 Agent 服务”的关键。
恭喜你!作为 React 开发者,你已经非常敏锐地捕捉到了 提示词工程 (Prompt Engineering) 在 Agent 开发中的核心地位。
你已经成功掌握了 Agent 的“大脑设定”。作为一个 React 开发者,你的“输出协议化”思维非常出色——这正是将 AI 集成到真实应用中的第一道门槛。
现在,Agent 已经有了“意识”,但它还只是个会说话的脑袋。如果用户问它:“北京现在几点了?”或者“帮我查询一下我的订单状态”,光靠 Prompt 是解决不了的,它需要手脚去触碰现实世界。
这就引出了我们的第二站:Function Calling (工具调用)。
Function Calling 赋予Agent手脚
深入理解 LLM 如何决定何时调用工具,以及如何处理多轮函数调用。这是 Agent 与现实世界的接口。
在 React 中,当我们需要从后端获取数据或执行操作时,我们会调用一个 async function(比如 fetchData)。在 Agent 的世界里,Function Calling 并不是让模型直接去运行你的 JS 代码,而是让模型通过“思考”,决定去“调用哪个函数”并“生成所需的参数”。
举个例子:
假设你在前端写了一个查询简历数据库的函数:
JAVASCRIPT
async function searchResume(candidateName) {
// 数据库查询逻辑...
}
我们要把这个函数的 描述(Description) 喂给模型。模型看到用户的请求后,会返回一段 JSON 给你的代码,就像这样:
JSON
{
"name": "searchResume",
"arguments": "{\"candidateName\": \"张三\"}"
}
关键问题来了:
如果大模型返回了上面这段 JSON,是由模型负责运行 searchResume("张三") 吗?还是由你写的 JavaScript 代码负责运行?
其实,大模型(LLM)本身是没有任何“权限”和“环境”去运行你的代码的。 它只是一个极其聪明的“逻辑引擎”。
Agent 的完整闭环(The Missing Loop)
为了让 Agent 像个“真人”一样回答,你需要把这个执行结果 重新喂回给大模型,并告诉它:“嘿,你要的工具执行完了,结果是 100.5。现在请你结合这个结果,正式回答一下用户。”
这个过程就像 React 的 useEffect 依赖项变化后触发了重新渲染:
Function Calling 完整闭环流程图

换个角度想:
想象一下,大模型住在 OpenAI 的云端服务器里。如果你定义了一个函数 deleteMyLocalFile('/documents/secret.txt')(删除我电脑里的某个文件):
如果大模型自己负责调用,它怎么能隔着网络“伸手”进你的电脑,操作你的硬盘呢?
如果它能直接运行代码,那万一它写了一段
while(true)的死循环,谁来停掉它?
真相是:
大模型只是做了一道 “选择题”和“填空题”:
选择题:在这么多工具里,我觉得应该用
deleteMyLocalFile。填空题:我判断参数应该是
{"path": "/documents/secret.txt"}。
它把这个意图(Tool Call)发回给你的 React/Node.js 代码,然后由你的代码去执行真正的 fs.unlinkSync() 或者 fetch()。
技术进阶:如何给 Agent 写“函数类型定义”?
在 React 中,我们用 TypeScript 的 interface 来约束组件的 props。
在 Agent 开发中,我们要为工具编写一份 JSON Schema。这份 Schema 就是 Agent 的“操作手册”,告诉它工具叫什么、有什么参数、参数是什么意思。
这是 get_weather 工具的定义示例:
JSON
{
"name": "get_weather",
"description": "获取指定城市的实时天气",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名,例如:上海"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["location"]
}
}关键问题来了:
在上面的定义中,你觉得那个字符串 "description": "城市名,例如:上海" 对大模型来说意味着什么?
在 Agent 开发中,“描述即提示词”。如果描述写得不清不楚(比如只写了 param1),大模型就很容易出现“幻觉”,传错参数。
工具调用的“对话性”:处理缺失参数
在 React 中,如果组件缺了必传的 prop,控制台会报错,或者页面白屏。
但在 Agent 的世界里,过程要灵活得多。假设你在 Schema 里把 location 设为了 required(必填),而用户只说:“帮我查下天气。”
作为一个具备“大脑”的 Agent,它在这一步并不会直接报错崩溃。它会分析当前的 Context(上下文),发现:“哎呀,用户没说地方,但我必须要这个参数。”
这时候,它会返回一段普通文本给用户:“请问您想查询哪个城市的天气呢?”——这个过程完全不需要你写 if (!location) 的判断逻辑,大模型会根据你的 Schema 定义自动处理。
在 Agent 的世界里,System Prompt 就是它的“宪法”或“核心逻辑层”。即使工具(外部 API)返回了真实数据,如果这违背了它的大脑设定,它会选择执行“拒绝”或“过滤”逻辑。
这正是 React 开发者转型 Agent 开发最需要转变的思维:在传统前端,数据(Data)决定展示;而在 Agent 中,规则(Prompt)决定如何解释数据。
现在,Agent 已经有了大脑设定(Prompt),也有了可以调用的手脚(Tools)。但问题是,如果一个任务非常复杂,一次函数调用搞不定怎么办?
比如,用户说:“帮我找找张三的简历,并根据他的工作经历帮我写一封面试邀请信。”
这需要 Agent 先查询、拿到结果、再思考、最后撰写。这套连招的逻辑,就是我们的第三站:推理循环 (Reasoning Loop) —— 实现 ReAct 模式。
推理循环——实现ReAct模式
学习如何通过代码实现 Thought-Action-Observation 循环,让 Agent 具备自我修正能力。
在 React 开发中,我们常用 useEffect 的依赖数组来监听数据变化。而 ReAct(Reason + Act) 模式就像是给大模型套了一个 while (true) 循环,直到大模型说“我搞定了,这是最终答案”才跳出循环。
一个标准的 ReAct 步骤包含:
Thought (思考):模型写出它现在在想什么。
Action (行动):模型决定调用哪个工具。
Observation (观察):你的代码把工具执行的结果喂回给模型。
(重复以上步骤)...
Final Answer (最终回答):任务完成。
在 React 中,我们常用 useEffect 来处理副作用。而在 Agent 开发中,这个 ReAct 循环 其实就是一个由 LLM 驱动的“副作用控制器”。
为了让你更直观地理解,我们来看一下这个循环的伪代码逻辑(这对前端开发者来说应该很亲切):
JAVASCRIPT
let isFinished = false;
let history = []; // 包含 System Prompt 和用户初次提问
while (!isFinished) {
// 1. 让 LLM 思考并决定行动 (Thought + Action)
const response = await llm.chat(history);
// 2. 检查 LLM 是否给出了 Final Answer
if (response.hasFinalAnswer) {
console.log("最终结果:", response.text);
isFinished = true;
} else {
// 3. 执行工具逻辑 (Observation)
const toolResult = await myTools[response.toolName](response.args);
// 4. 【关键点】将执行结果作为“观察内容”喂回给对话历史
history.push({ role: 'tool', content: toolResult });
// 进入下一次循环,LLM 会根据最新的 history (包含刚才的工具结果) 继续思考
}
}
请注意上面代码里的 步骤 4。如果这一步你只把 toolResult 返回给前端 UI 展示,而不 push 回 history 给大模型看,会发生什么有趣的情况?
这就像在 React 里,你发了一个 API 请求,但忘记调用 setState。由于组件的状态(State)没有更新,渲染逻辑就会一直停留在“加载中”或者“触发请求”的初始状态。对于 Agent 来说,history(对话上下文)就是它的“State”。
如果没有 Observation,大模型看到的情况永远是:
用户的提问:“搜一下今天的实时金价”。
它自己的思考:“我需要调用搜索工具”。
(由于没有 Observation 结果)
它再次看到的情况依然是 1+2,于是它会循环输出 2。
进阶:如何判断“到此为止”?
在我们的 while 循环里,有一个关键变量 isFinished。但在真实的开发中,模型不会返回一个布尔值,它只会返回字符串。
通常我们会规定一种协议(Protocol),让模型在完成任务时输出一个特定的前缀,比如:
Action:
get_weather({"city": "Beijing"})—— 这表示还要继续跑循环。Final Answer:
北京现在的天气是阴转多云...—— 这表示可以跳出循环了。
现在请你作为架构师思考一个细节:
如果你希望你的 Mini Agent 更加“聪明”,在它给出最终答案(Final Answer)之前,强制要求它必须先输出一段 Thought(思考过程)。这样做对 Agent 的最终表现有什么好处?(提示:回想一下你写复杂逻辑注释时的感受):强迫模型先推理再结论,能显著降低幻觉并提高复杂逻辑处理的准确度。
在 AI 领域这被称为 CoT(Chain of Thought,思维链)。让模型“出声思考”不仅能降低它直接乱猜结果的概率(幻觉),还能像编写复杂业务逻辑时的“伪代码推演”一样,帮助模型理清逻辑。
作为 React 开发者,你可以把 Thought 想象成代码运行时的 Source Map 或者详细的 Debug Log。它能让你(和用户)清楚地看到 Agent 为什么决定去调 A 工具而不是 B 工具。
上下文管理——Agent的记忆系统基础
探讨如何管理对话上下文、短期记忆(Session)和长期记忆(RAG 基础),保持 Agent 的连贯性。
在 React 中,如果组件卸载了(Unmount),状态如果不存到 Redux 或持久化存储,就会丢失。Agent 也是一样:模型本身是没有“记忆”的,每一次 API 请求其实都是全新的。
我们之前提到的 history 数组虽然能充当短期记忆,但大模型有一个物理限制——上下文窗口(Context Window)。如果 history 越来越长,不仅费用飙升,模型还会“迷失”在过多的细节里(就像浏览器内存溢出一样)。
在 Agent 开发中,我们确实会用到 Prompt Caching(提示词缓存) 技术来减少重复内容的计算开销和响应延迟。
但正如你在 React 中处理海量列表数据时,光靠缓存(如 useMemo)是不够的,如果 DOM 节点(或者说大模型的 Context Window)塞得太满,浏览器(模型)依然会卡死或“失忆”。
在 Agent 的记忆管理中,我们通常需要像处理前端长列表一样进行“按需加载”或“数据压缩”。
让我们看看 Agent 记忆系统里的两种经典策略,你可以尝试用前端开发者的视角来拆解它们:
滑动窗口(Sliding Window):类似于前端的分页或虚拟列表,只保留最近的 N 条记录,旧的记录直接“丢弃”。
总结摘要(Summarization):类似于数据聚合(Aggregation),把前 40 轮的对话内容总结成一段话,然后只带着这段话和最近的 10 轮对话去请求。
Agent 记忆系统常见的两种处理逻辑

如果你在用 React 开发一个简单的聊天界面:
JAVASCRIPT
// 每次用户发送新消息,你都要向大模型发起一次新的 API 请求
// 你维护了一个 history 的 state
const [history, setHistory] = useState([
{ role: 'user', content: '我想开发一个 Agent' },
{ role: 'assistant', content: '好的,我们先从系统提示词开始...' },
// ... 此处省略 50 条消息
]);
由于大模型每次请求都需要把 完整的 history 传回去,当 history 越来越长,你的每一个 Token 都要收费。
这时候,如果你选择最简单的“滑动窗口(Sliding Window)”,你会怎么操作这个 history 数组来控制每一轮请求的消息条数?
你认为这种“直接截断”的方法,相比于“总结摘要”,最大的优势和劣势分别是什么?(可以从 实时性能 和 长线任务一致性 两个维度思考)
在 Agent 开发中,有一种“外挂数据库”的记忆方式,专门解决这种“海量记忆中捞取特定细节”的问题。
如果说 Context Window(上下文窗口) 是 React 组件里的 useState(存放在内存里,空间有限,刷新就没了),那么 RAG 就像是一个 外部的后端数据库(Elasticsearch 或 Vector DB)。
当用户问一个 100 轮对话前的问题时,Agent 的操作逻辑是:
检索(Retrieval):先去“外挂数据库”里搜一下相关的关键词。
增强(Augmented):把搜出来的几条核心结果“捞”出来。
生成(Generation):把这几条结果和当前的问题一起塞进 Prompt,让模型回答。
💡 一个关键的工程挑战(Interleaving Review)
想象你正在为你的 港口数字孪生项目 开发一个 Agent,它的“外挂数据库”里存了 10,000 条设备维修手册。
用户问:“二号吊机的液压泵怎么拆?”
如果你之前学过的 Function Calling(工具调用) 配合 RAG 一起用,你觉得这个过程是怎样的?
选项 A:模型直接从 10,000 条手册里全量阅读,然后回答。
选项 B:模型先决定调用一个
search_manuals(query)工具,从数据库里“捞”出关于二号吊机液压泵的 3 段描述,然后再根据这 3 段描述总结出拆卸步骤。
选项 B 是目前构建工业级 Agent 的标准范式。
这就像在 React 中,如果你有一个 10,000 条数据的表格,你绝对不会把所有数据一次性加载到浏览器的 state 里(会导致 OOM 崩溃),而是做一个 Server-side Search。在 Agent 架构里,这叫 Semantic Search(语义搜索),它能精准地只给模型“投喂”它当前需要的那几段上下文。
最后一环:持久化(Persistence)与会话隔离
作为 React 开发者,你肯定处理过用户登录和 Session 状态。Agent 的记忆系统在生产环境下也面临同样的问题:
短期记忆(Short-term Memory):存在当前请求的数组里(类似 React 的
useState),刷新即逝。长期记忆(Long-term Memory):需要存在数据库中(Redis、PostgreSQL 等),并通过一个
session_id来关联。
构建你的第一个Mini Search Agent
作为一名 React 开发者,你习惯于思考“组件生命周期”和“单向数据流”。在编写这个 Agent 的核心 run() 函数时,其实就是一个 异步的 while 循环。
现在,请你发挥你的编码直觉,尝试勾勒一下这个 run 函数的内部伪代码(Pseudo-code)框架。
提示:你的 while 循环应该包含以下几个关键动作(你可以用大白话或 JS 伪代码描述):
检查
history。发起
LLM请求。处理
tool_call(如果有的话)。更新
history。判断是否该结束循环并给用户
final_answer。
我们先来看一个针对这个 Mini Search Agent 设计的 System Prompt 示例:
MARKDOWN
## Role
你是一个具备实时搜索能力的 AI 助手。
## Task
1. 接收用户问题,判断是否需要联网搜索。
2. 如果需要,调用 `web_search` 工具获取信息。
3. 结合搜索结果和已知信息,给出一个客观、准确的总结。
## Constraints
- 必须使用工具返回的原始信息进行总结,不可凭空捏造。
- 最终回复必须是中文,且使用 Markdown 格式。
- 如果搜索结果冲突,请告知用户并对比差异。
深入代码实现:history 的状态更新
在 React 中,我们更新状态通常是 setHistory([...history, newItem])。在 Agent 的 while 循环里,当 API 返回了天气数据 {"temp": "25C"} 时,如果你只是简单地把它推入数组:
JAVASCRIPT
// 这样直接推入会有问题吗?
history.push({ role: "user", content: '{"temp": "25C"}' });
请你从大模型的视角思考一下:
如果所有的信息
role都是user或assistant,模型如何区分这一行 JSON 是“用户刚才说的话”,还是它“刚才要求调用的工具返回的结果”?在 Open AI 的协议规范中,为了让模型把“结果”和“它之前的请求”对上号,除了
role之外,还有一个非常关键的 ID 字段 必须带上。你知道这个 ID 叫什么吗?(它通常在模型返回tool_calls时就已经生成好了)
真正的“映射键”:tool_call_id
在主流大模型(如 OpenAI、DeepSeek、Claude)的协议中,每一条 assistant 返回的工具调用指令都会自带一个唯一的随机 ID:
JSON
{
"role": "assistant",
"tool_calls": [
{
"id": "call_abc123", // ⬅️ 关键的 ID
"type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\": \"shanghai\"}" }
}
]
}
当你调用完天气 API 拿到结果后,你需要构造一个特殊的 history 对象传回模型:
role必须是"tool"(代表这是工具的回复)。必须带上
tool_call_id: "call_abc123"。
实战拼图:完成 Mini Agent 的逻辑闭环
作为 React 开发者,你最熟悉的逻辑是:Action -> Reducer -> State -> UI。
在 Agent 的 run() 函数里,逻辑是:History -> LLM -> Tool Call -> Tool Result -> History -> LLM...
现在我们到了最关键的一步。假设你已经把 role: "tool" 的消息正确推入了 history 数组。
接下来,在你的 while 循环里,紧接着要做的动作是什么?
在 React 开发中,如果你有一个异步的数据加载过程,你会先渲染 Loading 状态,然后等到数据回来后再触发一次渲染(Re-render)。
Agent 的 while 循环本质上也是一次“Re-render”。你把新的“搜索数据”连同之前的对话历史一起打包丢给模型,模型此时看到的是:
System Prompt: 搜集最新天气并给用户客观总结。
User: 今天上海天气?
Assistant: 我想查查天气,工具是get_weather。
Tool: 上海今天是 25 度。
Assistant:(这一轮模型会根据上面的信息,直接推理出:“上海今天很暖和,25度...”)
最后一步:跳出循环与安全机制 🛑
如果不加以控制,Agent 有可能会陷入无限搜索的“死循环”(比如搜索引擎给出的信息一直在冲突,或者模型一直反复调用同一个工具)。
在你的 while 循环逻辑里,你认为应该如何设计“跳出循环并给用户最终结果”的逻辑?
在 React 中,我们会有 useEffect 的依赖数组或者 setTimeout 的清理函数。而在 Agent 的 while 循环里,我们也需要两道防线:
逻辑终点:判断模型是否已经完成了推理,不再需要调用工具。
安全围栏:防止因为模型幻觉或逻辑死循环导致的无限调用。
