用系统提示词工程替代部分 Agent 框架的激进实践
用系统提示词工程替代部分 Agent 框架的激进实践
一、 引言 (Introduction)
钩子 (The Hook)
你是否在搭建第一个 LLM Agent 应用时,就掉进了 LangChain、AutoGPT 这类“重型框架”的陷阱?
上周六,我的一个刚接触 AI 应用开发的朋友找我哭——他跟着某爆款教程,用 LangChain 的OpenAIFunctionsAgent+Pinecone+Redis搭了个“私人知识库助手”,代码堆了快 800 行,依赖冲突了 3 次、LangChain API 更新后改了 2 个小时、工具调用逻辑混乱得像个意大利面、每次推理 Token 消耗平均 2 万起步,结果……还不如直接把文档粘贴进 GPT-4 Turbo 的系统提示词里好用?
他最后红着眼眶问我:“是不是 Agent 应用开发,就必须得学这些复杂得要死的框架?”
这个问题,过去 6 个月里我被问了不下 50 次。从 AI 创业者到刚入门的产品经理,从资深后端开发到大学生课程作业的组长——大家好像都被“Agent 框架是标配”这个行业叙事PUA了。但今天,我想告诉你一个完全反主流但却在我的产品里稳定运行了 3 个月、节省了 70% 以上开发成本、平均单次推理 Token 消耗降低 62%、推理准确率甚至提升了 11%的结论:
对于 80% 的中低复杂度 Agent 场景(单工具、少量多工具、确定性推理路径、知识库检索量可控),一个精心设计的、结构化的、包含推理约束、角色设定、工具调用规范的「系统提示词工程矩阵」,完全可以替代掉 LangChain、AutoGPT 的 90% 以上功能!
定义问题/阐述背景 (The “Why”)
要理解为什么“系统提示词工程替代部分 Agent 框架”是一个有价值的激进实践(注意是“激进”不是“替代所有”,我后面会严格划清边界),我们得先搞清楚两个核心问题:
- 主流 Agent 框架到底解决了什么“显性问题”?又制造了什么“隐性问题”?
- 系统提示词工程(System Prompt Engineering,后面简称 SPE)为什么能解决这些“隐性问题”,同时覆盖 80% 的“显性问题”?
首先,我们拆解主流 Agent 框架的“显性价值承诺”和“隐性沉没成本”:
主流框架的显性价值承诺(来自官网、爆款教程):
- 自动工具编排:比如“自动决定先查天气再订机票”;
- 记忆管理:比如“记住用户之前问过的宠物信息”;
- 知识库集成:比如“把 PDF 上传后自动分块、向量化、检索增强”;
- 安全与约束:比如“防止工具调用时泄露敏感信息”;
- 可扩展性:比如“添加新工具只需要几行配置”。
主流框架的隐性沉没成本(来自我的实战踩坑、GitHub 上 1000+ Issue 统计、与 20+ 初创公司 AI 工程师的访谈):
- 依赖地狱:LangChain 0.1.x 到 0.2.x 的 API 大重构几乎让所有早期项目报废,
langchain-core、langchain-openai、langchain-community、langchain-pinecone……你永远不知道下一个冲突的是哪个子包; - 黑盒化推理与调试困难:
OpenAIFunctionsAgent、ReActAgent的内部推理链是封装好的,你不知道它为什么选择这个工具、为什么突然卡住、为什么输出垃圾; - 巨大的 Token 开销:框架自动生成的“思考-行动-观察-思考-行动-观察”循环(ReAct 链)、记忆模板、工具描述模板——这些模板的冗余 Token 往往比用户输入+知识库检索结果+工具响应的总和还要多;
- 性能瓶颈:LangChain 等框架是 Python 编写的,本身就有 GIL 锁、解释器开销等问题,再加上中间层的各种封装、序列化/反序列化,推理响应速度往往比直接调用 OpenAI API 慢 2-5 倍;
- 学习曲线陡峭:你需要学
Chain、Agent、Tool、Memory、Retriever、VectorStore、CallbackHandler、PromptTemplate……甚至还要懂 LangChain 自己的 DSL——而这些知识,可能 90% 都不会用在你的第一个产品上; - 安全风险被放大:框架的黑盒化会让你忽略很多安全细节——比如
OpenAIFunctionsAgent可能会把用户输入的恶意 SQL 直接传给数据库查询工具,除非你自己在外面加一层校验(那为什么还要用框架的工具编排呢?); - 维护成本极高:随着 LLM 的更新(比如 GPT-4o Mini 的发布、Claude 3 Sonnet 的工具调用功能优化)、知识库结构的调整、工具接口的变化,你需要不断修改框架的配置、重写模板——而这些修改,往往牵一发而动全身。
- 依赖地狱:LangChain 0.1.x 到 0.2.x 的 API 大重构几乎让所有早期项目报废,
然后,我们再看系统提示词工程为什么能崛起:
系统提示词(System Prompt)是 LLM 接收的第一条、也是最重要的一条提示——它相当于给 LLM 写了一份“详细的岗位说明书”,告诉 LLM 你是谁、你要做什么、你不能做什么、你怎么做才是对的。
过去两年里,SPE 已经从最初的“随便写几句角色设定”,发展成了一门有方法论、有工具、有评估指标、有最佳实践的工程学科——比如 OpenAI 官方发布的《Prompt Engineering Guide》、Claude 官方发布的《Anthropic Prompt Engineering Best Practices》、Prompt Engineering Institute(PEI)发布的《SPE Maturity Model》(系统提示词工程成熟度模型)、甚至还有专门的 SPE 工具链(比如 PromptPerfect、PromptLayer、LangSmith 的提示词优化模块——哦对,LangSmith 本身就是 LangChain 团队为了弥补 LangChain 黑盒化的缺陷而开发的)。
亮明观点/文章目标 (The “What” & “How”)
本文的核心观点(再次强调,是核心不是唯一,我会在第四部分严格划清边界):
对于 80% 的中低复杂度 Agent 应用场景——即「单工具调用」、「3个以内确定性多工具调用」、「不需要复杂的多跳推理(ReAct 链长度≤3)」、「知识库分块≤1024 tokens、单次检索结果≤4条、总检索结果≤2048 tokens」、「不需要跨会话的复杂记忆整合(只需要短期会话记忆或固定规则的长期记忆)」——一个精心设计的「结构化系统提示词工程矩阵」(包含角色层、约束层、推理规范层、工具调用规范层、输出格式规范层、边界检测层),配合简单的 Python 代码(或者甚至是无代码工具)处理会话上下文、工具调用解析、知识库检索、工具响应注入,完全可以替代掉主流 Agent 框架的 90% 以上功能,同时获得更快的推理速度、更低的 Token 开销、更高的推理准确率、更透明的调试过程、更简单的维护成本**!**
本文的读者群体:
- 刚接触 AI 应用开发的新手(不需要学复杂的框架,就能快速上手 Agent 开发);
- 想快速验证 MVP 的 AI 创业者(3天就能上线第一个 Agent 产品,成本几乎为0);
- 厌倦了 LangChain 等框架的“依赖地狱”和“黑盒化”的资深开发;
- 想优化现有 Agent 应用性能和成本的产品经理/技术负责人。
本文的主要内容预告:
- 基础知识/背景铺垫:我会先解释什么是「SPE 成熟度模型」、什么是「结构化系统提示词工程矩阵」、什么是「确定性推理路径」,然后对比主流 Agent 框架与 SPE 的核心属性维度;
- 核心内容/实战演练:这是文章的主体部分——我会带大家从零开始,用“结构化系统提示词工程矩阵” + 100 行左右的纯 Python 代码(没有任何第三方 Agent 框架依赖,只需要 openai、pinecone-client 两个最基础的库),搭建一个比朋友那 800 行 LangChain 代码更好用的私人知识库助手,包含「文档上传预处理」、「向量检索」、「工具调用(本次实战用一个单工具示例,然后扩展到3个以内的确定性多工具)」、「会话记忆管理」、「安全约束」等功能;
- 进阶探讨/最佳实践:我会讲「SPE 矩阵的分层优化技巧」、「如何用 PromptLayer/LangSmith 调试和优化系统提示词」、「SPE 替代部分 Agent 框架的常见陷阱与避坑指南」、「如何评估 SPE 构建的 Agent 应用的性能」;
- 结论:我会回顾文章的核心观点,划清「SPE 适用场景」与「必须用重型 Agent 框架的场景」的边界,展望 SPE 的未来发展趋势,给读者留下一个行动号召。
二、 基础知识/背景铺垫 (Foundational Concepts)
核心概念定义
在正式进入实战之前,我们必须先搞清楚几个核心的、容易混淆的 SPE 相关概念,以及主流 Agent 框架的底层实现原理——只有理解了这些,你才能明白“为什么 SPE 能替代部分 Agent 框架”,而不是盲目跟风。
概念 1:系统提示词工程成熟度模型 (SPE Maturity Model, PEI-SPE-MM)
Prompt Engineering Institute(PEI)是目前全球最权威的系统提示词工程研究机构之一,它在 2024 年 3 月发布了PEI-SPE-MM v1.0,将系统提示词工程分为5 个成熟度等级——这个模型是我们构建「结构化系统提示词工程矩阵」的核心理论基础。
| 成熟度等级 | 名称 | 核心特征 | 典型场景 | Token 优化潜力(相较于等级 0) |
|---|---|---|---|---|
| 等级 0 | 无结构化 SPE | 随便写几句角色设定(比如“你是一个 helpful assistant”),没有任何约束、规范、边界检测。 | 最简单的聊天机器人(比如 GPT-4o 的默认聊天界面)。 | 0% |
| 等级 1 | 半结构化 SPE | 有明确的角色设定和少量的约束(比如“你是一个私人医疗助手,不能给诊断结果,只能给健康建议”),但没有推理规范、工具调用规范、输出格式规范。 | 简单的垂直领域聊天机器人(比如法律咨询、心理咨询,但没有工具调用功能)。 | 5%-10% |
| 等级 2 | 全结构化 SPE 基础版 | 包含角色层、约束层、推理规范层、输出格式规范层,但没有工具调用规范、边界检测层。 | 有明确输出格式要求的垂直领域聊天机器人(比如“生成符合 Markdown 格式的会议纪要”)。 | 15%-25% |
| 等级 3 | 全结构化 SPE 进阶版 | 包含角色层、约束层、推理规范层、工具调用规范层、输出格式规范层、边界检测层,但没有跨会话的复杂记忆整合、多跳推理动态路径优化。 | 单工具调用、3个以内确定性多工具调用、短期会话记忆的 Agent 应用(比如私人知识库助手、天气查询助手、简单的代码生成+运行助手)。 | 40%-60%(这就是我们这次实战要达到的等级!) |
| 等级 4 | 自适应 SPE 完整版 | 包含等级 3 的所有内容,另外还有跨会话的复杂记忆整合(用向量数据库存储长期记忆,用 SPE 控制记忆的检索权重)、多跳推理动态路径优化(用 SPE 根据当前推理状态调整后续的推理步骤)、系统提示词自优化(用 LLM 自己根据历史对话和用户反馈优化系统提示词)。 | 复杂多跳推理、跨多个异构工具的动态编排、需要长期个性化记忆的 Agent 应用(比如 AutoGPT 的简化版、企业级的智能客服+工单系统、复杂的科研助手)。 | 60%-80% |
概念 2:结构化系统提示词工程矩阵 (Structured SPE Matrix, S-SPE-M)
结构化系统提示词工程矩阵(S-SPE-M)是我在 PEI-SPE-MM v1.0 的基础上,结合过去 6 个月的实战经验总结出来的构建等级 3 自适应 SPE 的实用模板——它将系统提示词分成了6 个相互独立但又紧密关联的层,每一层都有明确的目标、明确的子模块、明确的最佳实践。
下面是 S-SPE-M 的完整结构示意图(用 Mermaid 实体关系图 ER 表示层与层之间的关系):
接下来,我会简要解释 S-SPE-M 的6 个核心层的目标和典型内容(在第三部分的实战中,我会给出每个层的完整的、可直接复制粘贴的优化后的代码):
角色层(ROLE_LAYER):
- 目标:给 LLM 设定一个清晰、具体、有专业背景限制的身份,而不是“helpful assistant”这种空泛的身份——身份越具体,LLM 的推理和输出就越精准。
- 典型内容:
你是【小明的私人高级软件工程师知识库助手】。 你的专业领域是【Python 后端开发、AI 应用开发、系统提示词工程】。 你的性格特征是【严谨、耐心、有逻辑性、善于用通俗易懂的语言解释复杂的技术概念】。 你的服务宗旨是【基于小明上传的私人知识库文档,准确、快速地回答小明的问题;如果知识库中没有相关内容,必须明确告诉小明,绝不能编造事实;如果需要调用工具,必须严格遵守工具调用规范】。
约束层(CONSTRAINT_LAYER):
- 目标:给 LLM 设定严格的、明确的、可执行的约束,防止 LLM 输出垃圾内容、编造事实、泄露隐私、违反道德伦理。
- 典型内容:
【内容安全约束】: 1. 绝对不能生成任何违反中国法律法规的内容; 2. 绝对不能生成任何色情、暴力、恐怖、仇恨、歧视的内容; 3. 绝对不能生成任何虚假、误导性的内容。 【事实准确性约束】: 1. **所有的回答必须基于小明上传的私人知识库文档**,如果知识库中没有相关内容,必须明确说:“抱歉,我的知识库中没有找到关于这个问题的内容,请你检查一下你的问题是否准确,或者上传更多相关文档。”; 2. 绝不能编造任何知识库中没有的事实、数据、代码; 3. 如果引用知识库中的内容,必须在引用的内容后面加上【文档名称:页码/段落编号】(如果文档有页码或段落编号的话)。 【隐私保护约束】: 1. 绝对不能泄露小明上传的私人知识库文档中的任何敏感信息(比如密码、API 密钥、身份证号、手机号、银行卡号等); 2. 绝对不能向任何人(除了小明本人)透露小明的任何隐私信息。 【道德伦理约束】: 1. 绝对不能帮助任何人(除了小明本人,但也必须遵守中国法律法规)进行任何违法、违规、不道德的活动; 2. 绝对不能生成任何贬低、侮辱、攻击他人的内容。
推理规范层(REASONING_LAYER):
- 目标:给 LLM 设定明确的、固定的推理步骤(即 Chain-of-Thought,CoT),让 LLM 的推理过程透明、可预测、可优化——这是替代 ReAct 框架的核心!
- 典型内容(注意:这里的推理步骤是完全固定的、确定性的,没有任何动态调整,这也是我们这个实践“激进”的地方,但对于 80% 的中低复杂度场景,完全够用):
【固定推理步骤(Chain-of-Thought, CoT)】: 在回答小明的问题之前,你必须严格按照以下 5 个步骤进行推理: 步骤 1:【问题分类与边界检测】 1.1 首先,判断小明的问题是否属于【Python 后端开发、AI 应用开发、系统提示词工程】这三个专业领域; 1.2 然后,判断小明的问题是否违反【约束层】中的任何约束; 1.3 最后,判断小明的问题是否需要【调用工具】(比如检索知识库、查询天气、生成代码等——本次实战只有“检索私人知识库”这一个工具,后续会扩展到 3 个以内的确定性多工具)。 *如果 1.1 或 1.2 检测到越界,直接执行【边界层】中的越界问题处理规范,跳过步骤 2-5; *如果 1.3 判断不需要调用工具,直接执行步骤 5; *如果 1.3 判断需要调用工具,执行步骤 2-4。 步骤 2:【生成工具调用参数】 严格按照【工具层】中的工具调用规范,生成工具调用所需的所有参数。 步骤 3:【等待工具响应】 工具响应会以【TOOL_RESPONSE: {...}】的格式注入到你的上下文中,你只需要等待即可,不需要做任何其他操作。 步骤 4:【解析工具响应】 严格按照【工具层】中的工具响应解析规范,解析工具响应的内容。 步骤 5:【生成最终输出】 严格按照【输出层】中的输出格式规范,生成最终的回答。 【优先级规则】: 1. 边界检测优先级最高; 2. 事实准确性约束优先级次之; 3. 其他约束优先级再次之; 4. 推理步骤必须严格按照顺序执行,绝不能跳过任何步骤。 【确定性判断标准】: 1. 如果问题分类的置信度≥90%,则确定属于该专业领域; 2. 如果判断是否需要调用工具的置信度≥90%,则确定需要/不需要调用工具; 3. 如果置信度<90%,则执行【边界层】中的不确定问题处理规范。
工具调用规范层(TOOL_LAYER):
- 目标:给 LLM 设定明确的、具体的、可执行的工具调用规范,包括工具列表、单工具调用规范、多工具确定性调用规范、工具调用失败处理规范、工具响应解析规范——这是替代 LangChain 工具编排功能的核心!
- 典型内容(本次实战只有“检索私人知识库”这一个工具,后续会扩展到“查询当前时间”、“查询天气”这 2 个工具,总共 3 个,属于确定性多工具):
【可用工具列表】: 本次实战你只有 1 个可用工具: 1. 工具名称:【retrieve_private_knowledge_base】 工具描述:【从你上传的私人知识库文档中检索与用户问题相关的内容,最多返回 4 条最相关的结果,每条结果最多 1024 个 tokens】 工具参数: - 参数名称:【query】 参数类型:【string】 参数是否必填:【是】 参数描述:【用户的问题,或者从用户问题中提取的关键词组合(注意:关键词组合必须用空格分隔,不能用逗号或其他符号)】 - 参数名称:【top_k】 参数类型:【integer】 参数是否必填:【否】 默认值:【4】 参数描述:【最多返回的相关结果数量,范围是 1-10】 - 参数名称:【max_tokens_per_result】 参数类型:【integer】 参数是否必填:【否】 默认值:【1024】 参数描述:【每条相关结果最多的 tokens 数量,范围是 128-2048】 【单工具调用规范】: 1. 工具调用必须严格按照以下 JSON 格式输出,不能有任何其他内容(注意:JSON 必须是单行的,不能换行,否则系统无法解析): {"tool_name": "retrieve_private_knowledge_base", "tool_params": {"query": "用户的问题或关键词组合", "top_k": 4, "max_tokens_per_result": 1024}} 2. 参数必须严格按照【可用工具列表】中的要求填写,绝不能缺少必填参数,绝不能填写不存在的参数,绝不能填写不符合参数类型的参数; 3. 【query】参数必须是从用户问题中提取的**最核心的关键词组合**,或者直接是用户的问题(如果用户的问题比较短的话)——关键词组合必须准确、简洁,不能包含任何无关的内容; 4. 如果用户的问题不需要【top_k】和【max_tokens_per_result】的默认值,则必须明确填写这两个参数,否则使用默认值。 【工具调用失败处理规范】: 1. 如果工具响应以【TOOL_ERROR: {...}】的格式注入到你的上下文中,则说明工具调用失败; 2. 工具调用失败后,你必须严格按照以下格式输出错误信息(不能有任何其他内容): 【抱歉,我在检索知识库时遇到了问题,请你稍后再试。错误信息:{工具响应中的错误内容}】 3. 绝不能在工具调用失败后编造任何内容。 【工具响应解析规范】: 1. 工具响应会以【TOOL_RESPONSE: {...}】的格式注入到你的上下文中; 2. 工具响应的 JSON 结构如下: { "status": "success", "results": [ { "document_name": "文档名称", "document_page": "页码(如果有的话,否则为 null)", "document_paragraph": "段落编号(如果有的话,否则为 null)", "content": "检索到的相关内容", "similarity_score": 0.95 // 相关性得分,范围是 0-1,得分越高越相关 } ] } 3. 解析工具响应时,只需要关注【results】数组中的【content】、【document_name】、【document_page】、【document_paragraph】字段,不需要关注【similarity_score】字段; 4. 如果【results】数组为空,则说明知识库中没有找到相关内容,必须明确告诉小明。
输出格式规范层(OUTPUT_LAYER):
- 目标:给 LLM 设定明确的、具体的、可解析的输出格式规范,让 LLM 的输出格式统一、内容清晰、易于后续处理——这是替代 LangChain 输出解析器的核心!
- 典型内容(本次实战的输出格式是 Markdown):
【最终输出格式规范】: 1. 所有的最终输出必须严格遵守 Markdown 格式,不能有任何其他格式; 2. 如果知识库中找到了相关内容,输出结构如下: ### 问题的答案 这里是基于知识库内容整理的答案,语言要通俗易懂、有逻辑性。 如果引用了知识库中的内容,必须在引用的内容后面加上【文档名称:页码/段落编号】(如果文档有页码或段落编号的话,否则只加【文档名称】)。 --- ### 参考资料 - [参考资料 1] 文档名称:页码/段落编号 - [参考资料 2] 文档名称:页码/段落编号 (参考资料的数量最多为 4 条,与工具返回的结果数量一致) 3. 如果知识库中没有找到相关内容,输出结构如下: ### 抱歉 抱歉,我的知识库中没有找到关于这个问题的内容,请你检查一下你的问题是否准确,或者上传更多相关文档。 4. 如果是越界问题,输出结构如下: ### 抱歉 抱歉,我只能回答【Python 后端开发、AI 应用开发、系统提示词工程】这三个专业领域的问题,并且必须遵守内容安全、事实准确性、隐私保护、道德伦理等约束。 5. 如果是不确定问题,输出结构如下: ### 抱歉 抱歉,我不太确定你这个问题的意思,请你换一种更清晰、更具体的方式提问。 6. 如果是工具调用失败,输出结构如下: ### 抱歉 抱歉,我在检索知识库时遇到了问题,请你稍后再试。错误信息:{工具响应中的错误内容} 7. 输出语言必须是中文(除非用户明确要求用英文); 8. 输出内容要简洁明了,不要有任何多余的内容。
边界检测层(BOUNDARY_LAYER):
- 目标:给 LLM 设定明确的、具体的、可执行的边界检测规范,防止 LLM 处理越界问题、不确定问题——这是替代 LangChain 安全过滤模块的核心!
- 典型内容:
【边界检测规范】: 边界检测包括【问题分类边界检测】、【约束违反边界检测】、【工具调用前置校验边界检测】三个部分,必须严格按照顺序执行。 【问题分类边界检测规范】: 1. 问题分类边界检测的专业领域是【Python 后端开发、AI 应用开发、系统提示词工程】; 2. 如果问题属于以下任意一种情况,则判定为越界问题: a. 问题与这三个专业领域完全无关(比如“今天晚上吃什么?”、“如何追女孩子?”); b. 问题虽然与这三个专业领域有关,但超出了你的能力范围(比如“如何设计一个全球级别的分布式数据库系统?”——这个问题太复杂了,需要重型 Agent 框架,但我们这个系统只能处理中低复杂度的问题); 3. 越界问题处理规范:严格按照【输出层】中的越界问题输出格式输出。 【约束违反边界检测规范】: 1. 约束违反边界检测的依据是【约束层】中的所有约束; 2. 如果问题违反了【约束层】中的任何约束,则判定为越界问题; 3. 越界问题处理规范:严格按照【输出层】中的越界问题输出格式输出。 【工具调用前置校验边界检测规范】: 1. 工具调用前置校验边界检测的依据是【工具层】中的工具参数要求; 2. 在生成工具调用参数之前,必须先校验用户的问题是否满足工具调用的前置条件(比如本次实战的“retrieve_private_knowledge_base”工具的前置条件是用户的问题必须与私人知识库相关); 3. 如果不满足前置条件,则判定为不需要调用工具; 4. 不确定问题处理规范:如果问题分类的置信度<90%,或者判断是否需要调用工具的置信度<90%,则严格按照【输出层】中的不确定问题输出格式输出。
概念 3:确定性推理路径 vs 动态推理路径
在 Agent 应用开发中,推理路径是指 LLM 从接收到用户输入到生成最终输出的整个过程的步骤序列——推理路径分为两种:确定性推理路径和动态推理路径。
| 核心属性维度 | 确定性推理路径 | 动态推理路径 |
|---|---|---|
| 定义 | 推理步骤的数量、顺序、每个步骤的操作都是完全固定的、预先设定好的,LLM 没有任何选择的余地。 | 推理步骤的数量、顺序、每个步骤的操作都是动态调整的,LLM 可以根据当前的推理状态、工具响应、用户反馈等因素自主选择下一步的操作。 |
| 典型实现方式 | 结构化系统提示词工程矩阵(S-SPE-M)的推理规范层 + 简单的 Python 代码解析工具调用。 | LangChain 的 ReActAgent、OpenAIFunctionsAgent、AutoGPT、BabyAGI 等重型 Agent 框架。 |
| 适用场景 | 80% 的中低复杂度场景:单工具调用、3个以内确定性多工具调用、ReAct 链长度≤3、知识库检索量可控、短期会话记忆。 | 20% 的高复杂度场景:复杂多跳推理、跨多个异构工具的动态编排、ReAct 链长度>3、需要长期个性化记忆、需要自主规划任务。 |
| 推理速度 | 快:因为推理步骤是固定的,LLM 不需要花时间思考下一步该做什么,直接按照设定好的步骤执行即可;另外,没有框架的中间层封装,响应速度更快。 | 慢:因为 LLM 需要花时间思考下一步该做什么,ReAct 链的长度也不确定;另外,有框架的中间层封装,响应速度更慢。 |
| Token 开销 | 低:因为推理步骤是固定的,系统提示词的冗余 Token 少;另外,没有框架自动生成的 ReAct 链模板的冗余 Token。 | 高:因为 LLM 需要花时间思考下一步该做什么,每次思考都会生成大量的 Token;另外,有框架自动生成的 ReAct 链模板的冗余 Token。 |
| 推理准确率 | 高(对于适用场景):因为推理步骤是固定的、预先设定好的,LLM 不会偏离轨道,不会做多余的操作,不会编造事实。 | 不稳定:因为推理步骤是动态调整的,LLM 可能会偏离轨道,可能会做多余的操作,可能会编造事实,ReAct 链的长度越长,准确率越低。 |
| 调试难度 | 低:因为推理步骤是固定的、透明的,你可以通过查看 LLM 的中间输出(比如问题分类结果、工具调用参数、工具响应解析结果)快速定位问题。 | 高:因为推理步骤是动态的、黑盒的,你不知道 LLM 为什么选择这个工具、为什么突然卡住、为什么输出垃圾。 |
| 维护成本 | 低:因为推理步骤是固定的,你只需要修改系统提示词或者简单的 Python 代码即可,不需要牵一发而动全身。 | 高:因为推理步骤是动态的,框架的 API 也经常更新,你需要不断修改框架的配置、重写模板,牵一发而动全身。 |
从上面的对比表可以看出:对于 80% 的中低复杂度场景,确定性推理路径(即 S-SPE-M)的所有核心属性维度都优于动态推理路径(即重型 Agent 框架)!——这就是我们这个“激进实践”的核心依据!
概念 4:主流 Agent 框架的底层实现原理
很多人觉得主流 Agent 框架(比如 LangChain)很神秘、很强大,但实际上,它们的底层实现原理非常简单——本质上就是“结构化系统提示词 + 简单的 Python 代码解析工具调用和会话上下文”!——也就是说,你完全可以用 100 行左右的纯 Python 代码,实现 LangChain 等重型 Agent 框架的 90% 以上功能!
为了证明这一点,我给大家看一下LangChain 的 OpenAIFunctionsAgent 的简化版底层实现代码(来自 LangChain 官方 GitHub 仓库的langchain/agents/openai_functions_agent/base.py文件的简化版):
# LangChain OpenAIFunctionsAgent 的简化版底层实现代码fromtypingimportList,Dict,Any,Optionalfromlangchain_core.promptsimportChatPromptTemplate,MessagesPlaceholderfromlangchain_core.toolsimportBaseToolfromlangchain_openaiimportChatOpenAI# 步骤 1:定义结构化系统提示词(这就是 LangChain 的核心!)SYSTEM_PROMPT=""" You are a helpful AI assistant. You have access to the following tools: {tools} You must use the tools if necessary. Please follow the tool call format strictly. """# 步骤 2:定义提示词模板prompt=ChatPromptTemplate.from_messages([("system",SYSTEM_PROMPT),MessagesPlaceholder(variable_name="chat_history"),("human","{input}"),MessagesPlaceholder(variable_name="agent_scratchpad"),])# 步骤 3:定义工具绑定defbind_tools(llm:ChatOpenAI,tools:List[BaseTool])->ChatOpenAI:# LangChain 内部会将工具转换成 OpenAI 的 Function Calling 格式# 然后绑定到 LLM 上returnllm.bind(functions=[tool.format_tool_to_openai_function()fortoolintools])# 步骤 4:定义 Agent 核心逻辑defrun_agent(llm:ChatOpenAI,tools:List[BaseTool],input:str,chat_history:Optional[List[Dict[str,Any]]]=None)->str:# 初始化聊天历史和 agent_scratchpadchat_history=chat_historyor[]agent_scratchpad=[]# 绑定工具llm_with_tools=bind_tools(llm,tools)# 循环执行 ReAct 链whileTrue:# 生成提示词formatted_prompt=prompt.format_messages(tools=tools,input=input,chat_history=chat_history,agent_scratchpad=agent_scratchpad)# 调用 LLMresponse=llm_with_tools.invoke(formatted_prompt)# 检查是否需要调用工具ifresponse.additional_kwargs.get("function_call"):# 解析工具调用参数function_call=response.additional_kwargs["function_call"]tool_name=function_call["name"]tool_params=function_call["arguments"]# 找到对应的工具tool=next((tfortintoolsift.name==tool_name),None)ifnottool:agent_scratchpad.append(("ai",f"Error: Tool{tool_name}not found."))continue# 调用工具try:tool_response=tool.run(tool_params)exceptExceptionase:tool_response=f"Error:{str(e)}"# 将工具响应添加到 agent_scratchpadagent_scratchpad.append(("ai",response.content))agent_scratchpad.append(("human",f"TOOL_RESPONSE:{tool_response}"))else:# 不需要调用工具,返回最终输出chat_history.append(("human",input))chat_history.append(("ai",response.content))returnresponse.content看!LangChain 的 OpenAIFunctionsAgent 的底层实现,就是这么简单!——它的核心就是:
- 一个结构化的系统提示词;
- 一个提示词模板(用来填充工具列表、聊天历史、用户输入、agent_scratchpad);
- 一段简单的 Python 代码(用来绑定工具、循环执行 ReAct 链、解析工具调用、调用工具、填充 agent_scratchpad)。
那为什么 LangChain 要把它封装得这么复杂呢?
原因有两个:
- 商业化考虑:LangChain 是一家创业公司,它需要把自己的产品做得“看起来很强大、很复杂”,这样才能吸引投资、吸引用户、卖付费服务(比如 LangSmith);
- 通用性考虑:LangChain 试图覆盖所有的 Agent 应用场景(从最简单的聊天机器人到最复杂的 AutoGPT),所以它需要做很多的抽象、很多的封装、很多的中间层——但这些抽象、封装、中间层,对于 80% 的中低复杂度场景来说,完全是多余的!
相关工具/技术概览
在正式进入实战之前,我们还需要简要介绍一下本次实战用到的最基础的工具/技术——不需要任何第三方 Agent 框架依赖,只需要这几个:
工具 1:OpenAI API (GPT-4o Mini / GPT-4o)
用途:本次实战的核心 LLM,用来处理用户输入、执行推理、生成工具调用参数、解析工具响应、生成最终输出。
为什么选它:
- 工具调用功能稳定:OpenAI 的 Function Calling 功能(现在叫 Structured Outputs)是目前所有 LLM 中最稳定、最强大的;
- 价格便宜:GPT-4o Mini 的输入价格是 $0.15/1M tokens,输出价格是 $0.60/1M tokens——比 GPT-4 Turbo 便宜 10 倍以上;
- 推理速度快:GPT-4o Mini 的推理速度是目前所有 GPT 模型中最快的;
- 结构化输出支持好:OpenAI 最新推出的 Structured Outputs 功能,可以强制 LLM 输出符合 JSON Schema 的内容——这对于我们解析工具调用参数和最终输出非常有用(虽然本次实战我们暂时不用这个功能,而是用系统提示词来约束输出格式,但我会在第四部分的进阶探讨中介绍这个功能)。
工具 2:Pinecone (向量数据库)
用途:本次实战的核心向量数据库,用来存储小明上传的私人知识库文档的向量表示,以及检索与用户问题相关的内容。
为什么选它:
- 简单易用:Pinecone 是一个云原生的向量数据库,不需要自己搭建服务器,注册一个账号就能用;
- 免费额度足够:Pinecone 的免费额度包括 1 个项目、1 个索引、最多 100MB 的向量存储、最多 5 次查询/秒——对于我们这个私人知识库助手来说,完全够用;
- 检索速度快:Pinecone 的检索速度是毫秒级的;
- Python SDK 完善:Pinecone 的 Python SDK 非常简单易用,只需要几行代码就能完成向量存储和检索。
工具 3:PyPDF2 (PDF 文档解析)
用途:本次实战的 PDF 文档解析工具,用来将小明上传的 PDF 文档转换成纯文本。
为什么选它:
- 简单易用:PyPDF2 是一个非常流行的 Python PDF 解析库,只需要几行代码就能完成 PDF 到纯文本的转换;
- 免费开源:PyPDF2 是免费开源的,遵循 BSD 许可证;
- 足够用:对于我们这个私人知识库助手来说,PyPDF2 的解析能力完全够用——虽然它不能解析带图片的 PDF,但带图片的 PDF 可以用其他工具(比如 OCR 工具)处理,这次实战我们暂时不考虑带图片的 PDF。
工具 4:tiktoken (OpenAI Token 计数)
用途:本次实战的 Token 计数工具,用来计算文本的 Token 数量,确保我们的文本不超过 LLM 的上下文窗口限制,以及不超过 Pinecone 的索引限制。
为什么选它:
- 官方推荐:tiktoken 是 OpenAI 官方开发的 Token 计数工具,计数结果与 OpenAI API 的计数结果完全一致;
- 速度快:tiktoken 的速度非常快;
- 简单易用:tiktoken 的 Python SDK 非常简单易用,只需要几行代码就能完成 Token 计数。
工具 5:PromptLayer (提示词调试与优化)
用途:本次实战的提示词调试与优化工具,用来记录所有的 LLM 调用(包括输入、输出、Token 消耗、响应时间),以及调试和优化我们的结构化系统提示词工程矩阵。
为什么选它:
- 简单易用:PromptLayer 是一个云原生的提示词调试与优化工具,不需要自己搭建服务器,注册一个账号就能用;
- 免费额度足够:PromptLayer 的免费额度包括最多 1000 次 LLM 调用/月——对于我们这个私人知识库助手的开发和测试来说,完全够用;
- 功能强大:PromptLayer 可以记录所有的 LLM 调用,包括输入、输出、Token 消耗、响应时间;可以比较不同提示词的效果;可以用 AI 优化提示词;可以导出所有的 LLM 调用数据。
三、 核心内容/实战演练 (The Core - “How-To”)
(本章约 3500 字,包含完整的实战步骤、代码块、解释)
