Przeglądaj źródła

docs: 新增文章《Beyond Chatbots:ReAct 框架如何让 LLM 进化成真正的 Agent》,分享AI Agent的实践经验与ReAct框架的核心机制

gamehu 3 miesięcy temu
rodzic
commit
a3c647e1e1

+ 3 - 0
source/_posts/AI-Harness-Harness-Engineering-AI时代的软件工程核心能力.md

@@ -627,6 +627,7 @@ AI 开发的核心资产正在转移:
 - [Promptfoo](https://github.com/promptfoo/promptfoo) - 开源 Prompt 测试框架
 - [DeepEval](https://github.com/confident-ai/deepeval) - Python LLM 评估框架
 - [LangChain Evaluation](https://python.langchain.com/docs/guides/evaluation/) - LangChain 评估指南
+- [I Improved 15 LLMs at Coding in One Afternoon. Only the Harness Changed.](https://blog.can.ac/2026/02/12/the-harness-problem/)
 
 ### 深度解读
 
@@ -634,6 +635,8 @@ AI 开发的核心资产正在转移:
 - [OpenAI Harness Engineering - InfoQ](https://www.infoq.com/news/2026/02/openai-harness-engineering-codex/) - 中文技术解读
 - [LLM Testing Framework Guide](https://www.leanware.co/insights/llm-testing-framework-guide) - 测试框架最佳实践
 - [How to Evaluate LLMs](https://aiengineerlab.in/models/evaluation/) - 实用评估方法
+- [Building Effective AI Coding Agents for the Terminal:Scaffolding, Harness, Context Engineering, and Lessons Learned](https://arxiv.org/pdf/2603.05344)
+- [Harness engineering for coding agent users](https://martinfowler.com/articles/harness-engineering.html)
 
 ### 官方工程博客
 

+ 435 - 0
source/_posts/Agent-production-landing-plan.md

@@ -0,0 +1,435 @@
+---
+title: Agent 生产落地:从 demo 到产品的血泪经验
+date: 2026-04-07 21:30:00
+author: Gamehu
+tags:
+  - AI
+  - Agent
+  - LLM
+  - RAG
+categories:
+  - 架构设计
+---
+
+<div class="tag-container">
+  <span class="ai-tag">AI</span>
+  <span class="sub-tag">Agent</span>
+</div>
+<div class="article-quote">
+这篇是我agent实践记录的第一篇,主要聊聊我前段时间在做的一个宠物医疗"病历质控 Agent"从原型做到生产落地的大体情况,细节后续有时间会逐一整理。不是什么高大上的理论,就是实打实的踩坑记录和权衡取舍。
+</div>
+
+## 前言
+
+虽然正儿八经做架构设计不到两年,但是我越来越笃信一个理念:**好的架构不是"一步到位"的 perfection,而是在成本约束下的渐进式演进。**
+
+尤其是在构建 **AI Agent 系统** 时,这个理念更为重要。Agent 不同于传统的 API 调用或批处理任务,它是一个能**自主决策、持续学习、与环境交互**的智能体。这意味着:
+
+- **它不是静态的**:今天有效的策略,明天可能需要调整
+- **它是有状态的**:需要维护上下文、记忆、反馈循环
+- **它是渐进的**:从简单的固定工作流(Workflow)到复杂的自主决策(ReAct),需要一步步验证
+
+这里的成本不只是钱,还包括:
+- **实现成本**:团队对 Agent 模式的理解、Prompt Engineering 能力、RAG 调优经验
+- **部署成本**:LLM 推理的延迟和费用、向量数据库的运维、多模型路由的复杂度  
+- **机会成本**:为了做通用 Agent 而放弃的专用模型,值不值得
+
+但我说的渐进式演进,**绝不是无脑地给自己挖坑、留技术债的借口**——当然,这话也不能说太绝。
+
+实际上,**有时候我会故意选择留一些"可控的技术债",或者接受部分妥协**。比如明知道某个设计将来可能需要推翻重来,但如果当前阶段的目标只是快速验证(打单、demo、MVP),那我可能干脆就不考虑那么远的扩展性。等真到了需要推倒重来的那一天,说明业务已经跑通了,那点重构成本完全值得。
+
+还有一种情况:**完全推翻重来也没关系**。比如早期为了抢时间做的 demo,本来就是用来验证需求的,验证完了直接扔掉重写,反而比在一堆妥协之上缝缝补补更干净。
+
+所以关键不是"不能留技术债",而是**清醒地知道自己在做什么选择**——是为了赶时间而主动承担的技术债?还是为了偷懒而无意间埋的雷?前者可控,后者致命。
+
+每一阶段都要有清晰的"逃生通道"和"回滚方案",但具体是渐进演进还是推倒重来,**得看场景,不能教条**。
+
+<div class="divider-tech"></div>
+
+## 先说说背景
+
+我们是在构建一个**病历质控 Agent**——核心职责是:医生写完病历后,Agent 自动评估质量,识别潜在风险并给出改进建议。
+
+这看起来就是调个 LLM API,但实际上要做一个能**持续运行、自主决策、从反馈中学习**的 Agent 系统,坑很多:
+
+- **医疗场景容错率极低**:漏了过敏史或剂量错误,就是事故
+- **需要可解释**:必须说明为啥判定有问题,依据是什么  
+- **需要可控制**:不能让它胡说,得有明确边界
+- **需要可进化**:能从医生反馈中学习,越用越准
+
+所以我们设计了两阶段路线:**Phase 1 固定工作流** → **Phase 2 ReAct Agent**,渐进式演进。
+
+<div class="ai-tag">AI Agent 生产落地</div>
+
+## 技术选型:不同阶段,不同选择
+
+技术选型不是非黑即白,而是**看阶段、看目标**。我们的演进路径经历了三个阶段:
+
+### 阶段一:预研验证(LangChain / LangGraph + Python)
+
+刚开始预研时,我们的目标其实挺大:想做一个 **`1 + N` 的 AI 能力底座**。
+
+`1` 是一个可微调、可沉淀领域能力的小模型,用来理解宠物医疗里的术语、病历结构、诊疗知识;`N` 是围绕业务长出来的一组 Agent,比如病历质控 Agent、用药检查 Agent、诊疗辅助 Agent、话术合规 Agent。
+
+这个阶段最大的问题不是"怎么写一个 HTTP 调用",而是 Agent 的边界到底在哪:
+
+- 要不要多 Agent 协作?
+- 工具调用怎么编排?
+- 状态怎么保存?
+- 什么时候需要 human-in-the-loop?
+- 失败后怎么恢复?
+
+所以当时选了 LangChain / LangGraph 这类流行框架。LangChain 更像 LLM 应用开发生态,模型调用、工具封装、RAG、Agent 都有;LangGraph 更偏 Agent 编排,适合探索长流程、状态化、多工具调用这些问题。
+
+这时候用成熟生态没问题,因为核心目标是**跑通 Agent 的各种可能性,快速验证边界**。但这只是预研工具,不是生产承诺。等后面发现病历质控主链路其实是固定 Workflow,不需要复杂 Agent 编排时,我们才逐步做减法。
+
+### 阶段二:打单/MVP(Spring AI + Java)
+
+预研验证通过后,需要**给客户、投资人演示**。但目前主要的技术栈是 Java,团队内当前资源也有限,没法快速用 Python  demo 去交付。
+
+于是迁移到 Spring AI。这时候的目标是**快速产出可演示的产品**,框架封装得好,开发速度快,哪怕有点"魔法"、有点性能损耗,也先不管。
+
+### 阶段三:生产落地(WebClient + Jackson)
+
+真正要上线生产了,目标变成了**稳定、可控、可运维**。这时候 Spring AI 的问题暴露出来了:
+- 对我们这个固定 Workflow 来说,框架抽象带来的收益不大
+- 真正出问题时,我更想直接看到 HTTP 请求、响应、超时、重试和 JSON 解析细节
+- 我们暂时不需要复杂的 Advisor、Tool Calling、Memory 抽象,自己控制反而更简单
+
+这不是说 Spring AI 不适合生产。相反,如果你需要快速接入多模型、结构化输出、工具调用、RAG 编排,它是一个很好的选择。
+
+但在我们的场景里,控制流很固定,业务风险又高,于是做减法,直接上 `WebClient + Jackson`。自己写 HTTP 调用、自己处理 JSON,代码多了一点,但超时、重试、日志、Schema 校验、错误码映射,**每一层都能看得见、控得住**。
+
+| 阶段 | 目标 | 选型 | 理由 |
+|-----|------|------|------|
+| 预研 | 探索 1+N Agent 边界 | LangChain / LangGraph (Python) | 生态成熟,适合快速验证多 Agent、工具调用、状态编排 |
+| 打单 | 快速交付 | Spring AI (Java) | 适配技术栈,演示效果好 |
+| 生产 | 稳定可控 | WebClient + Jackson | 少一层 AI 框架抽象,排查简单,长期维护成本低 |
+
+你看,**没有绝对的好坏,只有适合不适合当前阶段**。
+
+当然,基础设施层的选择是贯穿始终的——这些一旦选定就不容易改,所以一开始就做了减法:
+
+| 最初的念头 | 最终的方案 | 理由 |
+|-----------|-----------|------|
+| Qdrant 向量库 | <span class="highlight-text">pgvector</span> | 已有的 PostgreSQL 加个插件就搞定,零新增基础设施;Qdrant 留作规模上来后的迁移选项 |
+| 独立的 Schema 隔离 | <span class="highlight-text">表名前缀 agent_</span> | 同团队同代码库,schema 隔离是过度设计 |
+| 只用在线 API | <span class="highlight-text">先 DeepSeek API,私有化再本地 vLLM/Qwen3</span> | API 性价比高、交付快;只有客户明确要求私有化时,才值得承担本地推理成本 |
+| text2vec-base-chinese | <span class="highlight-text">Qwen3 Embedding</span> | 中英混合、长文本、RAG 检索效果更稳,后续可接 Qwen3 Reranker |
+
+生产环境跟 POC 最大的区别就是:**能少一个组件就少一个组件**。每多一个依赖,就是多一个故障点。
+
+模型部署方式这块,我反而觉得不是最核心的选择。
+
+这里说的在线 API,不是指国外 API。国外 API 肯定不在我们的候选里,不是模型能力问题,而是数据合规、网络稳定性、客户接受度都绕不过去。主路径更现实的选择是 DeepSeek API:接入快,成本可控,也不用一开始就背 GPU 服务器和推理运维的包袱。
+
+本地部署一开始并不是首选。真要自己上 vLLM、上 Qwen3,本质上是在买一套推理基础设施:GPU、显存、并发、排队、监控、扩容、模型升级,哪一个都不是免费的。对 MVP 和早期生产来说,性价比远不如 API。
+
+后来之所以补本地部署方案,是因为有客户明确提了私有化诉求,希望模型和数据都在本地环境里跑。那我们就把 LLM 调用层抽成统一接口:默认走 DeepSeek API,私有化部署时切到 vLLM + Qwen3。这个切换并不复杂,真正复杂的是 Agent 的流程、工具边界、RAG 命中、反馈闭环和安全护栏。
+
+换句话说:**模型可以换,Agent 设计不能乱**。这篇文章真正想讲的重点,也不是“到底用哪个模型”,而是怎么把 Agent 设计贴到宠物医疗的真实业务流程上。
+
+这里很多人会问:那为什么不用 Qdrant?
+
+Qdrant 是一个很强的向量数据库,尤其适合:
+
+- 数据量上来以后,需要更稳定的低延迟 ANN 检索
+- metadata filter 很复杂,比如按医院、科室、宠物类型、疾病分类、时间范围一起过滤
+- 想做 dense + sparse hybrid search,甚至多向量检索、late interaction rerank
+- 向量检索已经变成系统核心能力,需要单独扩容、监控、调参
+
+但我现在还是先选 pgvector。原因很现实:我们已有 PostgreSQL,病历、反馈、样本、审计日志都在里面。Phase 1 的样本量也就几百到几万级,pgvector + HNSW 足够跑。医疗场景里,**事务一致性、审计链路、少一个运维组件**,比单纯追求向量检索性能更重要。
+
+所以我的判断是:**pgvector 是当前阶段的正确选择,Qdrant 是下一阶段的迁移选项**。如果后面样本库涨到百万级,或者我们发现复杂过滤下 pgvector 召回/延迟开始不稳,再把检索层抽成独立服务迁到 Qdrant。这样迁移成本可控,也不会太早把系统复杂度抬上去。
+
+## RAG 设计:向量检索不是万能药
+
+做医疗质控,核心是怎么让 AI "懂业务"。我们用了 RAG(检索增强生成)来做知识注入,但这里面有几个细节值得说说。
+
+### 两种检索策略
+
+我们设计了两条检索路径,分别对应不同的业务场景:
+
+**第一种:Few-shot 样本检索**(对应 `agent_few_shot_examples` 表)
+
+根据病历内容,从历史优质病例里找相似的。Embedding 首选 `Qwen3 Embedding`,存 pgvector。
+
+这里有个坑:**不能把整份病历一股脑塞进去做 embedding**。病历是长文本,而且字段语义差异很大。主诉、诊断、用药、检查结果、医嘱,混在一起向量化,很容易把关键风险冲淡。
+
+所以我们按字段拆 chunk:
+
+- 主诉/现病史:用于找相似病例
+- 诊断:用于找诊疗规范
+- 用药:用于找剂量、禁忌、配伍规则
+- 化验结果:用于找参考值和异常解释
+
+第一版用纯向量召回,后面会加 keyword + vector 的混合检索,再接 Qwen3 Reranker 做二次排序。医疗场景里,召回率比“看起来很准”更重要,漏掉一条禁忌才是真正要命的问题。
+
+```sql
+-- pgvector 用法很简单
+CREATE EXTENSION IF NOT EXISTS vector;
+
+ALTER TABLE agent_few_shot_examples
+  ADD COLUMN embedding vector(1024); -- Qwen3-Embedding-0.6B 默认 1024 维
+
+-- 语义相似度检索
+SELECT *, 1 - (embedding <=> CAST(:embedding AS vector)) AS similarity
+FROM agent_few_shot_examples
+ORDER BY embedding <=> CAST(:embedding AS vector)
+LIMIT :limit;
+```
+
+这只是最小示例。生产里还得建索引,不然样本库一大,延迟会很难看:
+
+```sql
+-- HNSW 构建慢一点、吃内存一点,但通常有更好的召回/延迟平衡
+CREATE INDEX agent_few_shot_examples_embedding_hnsw
+ON agent_few_shot_examples
+USING hnsw (embedding vector_cosine_ops);
+```
+
+pgvector 还有 IVFFlat,构建更快,但要调 lists/probes。我们的选择是:样本量还不大时直接 HNSW,先把线上查询延迟和召回稳定住。
+
+**第二种:知识库检索**(对应 `agent_knowledge_base` 表)
+
+药典、诊疗规范、化验参考值。注意:**图片不参与向量检索**,存 OSS 里,检索命中后返回链接即可。
+
+### Embedding 模型的选择
+
+embedding 模型我现在首选 `Qwen3 Embedding`,原因很直接:
+
+- 跟 Qwen3 主模型体系更一致,RAG 链路里少一点“模型语义空间错位”
+- 对中英混合、长文本、多任务检索更稳
+- 后续可以接 Qwen3 Reranker,把“多召回一点”变成“排序更准一点”
+
+`text2vec-base-chinese` 不是不能用。它的优点是轻、便宜、CPU 能跑,适合做边缘部署或降级方案。但如果目标是病历质控这种高风险场景,我会把它放在 fallback,而不是主路径。
+
+```python
+# 示例:Embedding 服务可以独立部署,主 Java 服务只通过 HTTP 调用
+from flask import Flask, jsonify, request
+from sentence_transformers import SentenceTransformer
+
+app = Flask(__name__)
+model = SentenceTransformer('Qwen/Qwen3-Embedding-0.6B')
+
+@app.route('/embed', methods=['POST'])
+def embed():
+    text = request.json['text']
+    return jsonify({'embedding': model.encode(text).tolist()})
+```
+
+<div class="tip">
+<strong>小提示</strong>:向量检索的召回率比精度更重要。我们宁可多召回几条再让 LLM 筛选,也不要漏掉相关的样本。
+</div>
+
+## 多模型路由:Agent 也得"看菜吃饭"
+
+不同任务对模型能力要求不同,我们做了 `LlmRouter`,让 Agent 按需选择。
+
+这里路由的重点不是把代码写死到某个模型厂商,而是按任务能力分层:
+
+| 任务 | 复杂度 | 默认接入 | 私有化替代 | 理由 |
+|-----|-------|----------|------------|------|
+| 病历质控 | 高(需推理) | DeepSeek API | Qwen3-32B | 理解上下文、判断合规性 |
+| 用药建议 | 高(需知识) | DeepSeek API | Qwen3-32B | 剂量计算、配伍禁忌 |
+| 话术合规 | 低 | DeepSeek API 低成本配置 | Qwen3-7B | 敏感词检测,不需要每次都上高推理成本 |
+| 诊疗辅助 | 高(需对话分析) | DeepSeek API | Qwen3-32B | 根据问诊对话分析可能病症,给出检查和处置建议 |
+
+```java
+@Component
+public class LlmRouter {
+    private final Map<AgentType, ModelProfile> profiles;
+    private final LlmClient llmClient;
+
+    public LlmRouter(LlmClient llmClient) {
+        this.llmClient = llmClient; // 默认 DeepSeek API,私有化时切到 vLLM/Qwen3 实现
+        this.profiles = Map.of(
+            AgentType.RECORD_EVAL,       ModelProfile.HIGH_REASONING,
+            AgentType.DRUG_CHECK,        ModelProfile.HIGH_REASONING,
+            AgentType.SCRIPT_COMPLIANCE, ModelProfile.FAST_CHECK,
+            AgentType.DIAGNOSIS_ASSIST,  ModelProfile.HIGH_REASONING
+        );
+    }
+
+    public LlmResponse chat(AgentType type, List<Message> messages) {
+        return llmClient.chat(profiles.get(type), messages);
+    }
+}
+```
+
+这样做以后,默认线上可以走 DeepSeek API;客户要私有化,就换成 vLLM + Qwen3 的 `LlmClient` 实现。Agent 的业务代码不用跟着模型一起改。
+
+成本降低 60%,不是因为某个模型突然变魔法了,而是因为任务被拆清楚了。高风险、高推理任务用高能力档;低风险、规则性任务用低成本档。生产环境嘛,能省则省,但不能省到影响医疗安全。
+
+当然,这句话不能只靠感觉。我们内部看的是一组固定评测集:
+
+| 指标 | 为什么看它 |
+|-----|-----------|
+| 高风险漏检率 | 医疗场景最怕漏掉过敏、剂量、禁忌这类问题 |
+| 误报率 | 误报太多,医生会直接不看 |
+| 医生采纳率 | 真实业务反馈,比离线 benchmark 更接近产品价值 |
+| P95 延迟 | 病历保存后多久能看到质控报告 |
+| 单次评估成本 | 模型路由到底有没有省钱 |
+
+尤其是高风险漏检率,只要这个指标变差,成本省再多也不能上。医疗场景不是推荐系统,不能用“整体点击率提升”那套逻辑糊弄自己。
+
+## Phase 1 vs Phase 2:Agent 的渐进式落地
+
+这里想重点聊聊落地节奏。我们分两阶段走:
+
+### Phase 1:固定工作流(已上线)
+
+先做"傻瓜式"的固定流程:
+
+1. 病历保存 → 触发评估事件 → Redis Stream
+2. Worker 消费消息 → PromptBuilder 拼装提示词
+3. RAG 检索相似样本 → 注入 Prompt
+4. 调用 LLM → 返回 JSON 格式的评估结果
+5. 前端展示报告 → 医生可反馈(采纳/拒绝/评分)
+
+这个阶段的 Agent 没有"自主决策"能力,就是按固定套路走。但好处是**可控、可预期、好调试**。出问题了一步步排查,不会遇到那种"Agent 突然抽风"的诡异 bug。
+
+### Phase 2:ReAct Agent(灰度中)
+
+等工作流稳定了,再逐步引入 **ReAct 循环**,让 Agent 能主动调用工具:
+
+- 药典查询(判断用药合理性)
+- 历史病历检索(了解既往病史)
+- 诊疗规范检索(判断诊断是否规范)
+- 化验参考值(判断检查结果)
+
+```java
+// ReAct 循环核心
+public EvaluationResult run(MedicalRecord record) {
+    List<Message> context = buildInitialContext(record);
+
+    for (int turn = 0; turn < MAX_TURNS; turn++) {
+        // LLM 推理
+        LlmResponse resp = llmRouter.chat(AgentType.RECORD_EVAL, context);
+
+        if (resp.isFinished())
+            return parseResult(resp.content());  // 输出结论
+
+        // 否则调用工具
+        ToolCall call = resp.getToolCall();
+        String toolResult = toolRouter.invoke(call);
+
+        // 工具结果回传上下文
+        context.add(Message.assistant(resp.content(), call));
+        context.add(Message.tool(call.id(), toolResult));
+    }
+    return fallbackToWorkflowOrHumanReview(context);  // 超出轮次后降级,不强行给医疗结论
+}
+```
+
+有个 `agent.enabled` 开关可以切模式,万一 Phase 2 出问题,随时回滚到 Phase 1。
+
+<div class="decision">
+<strong>经验之谈</strong>:Agent 系统一定要设计"逃生通道"。再聪明的 Agent 也可能犯错,得让人工随时能接管。
+</div>
+
+## 生产护栏:比模型更重要的东西
+
+Agent 上生产,模型能力只是一半。另一半是护栏。
+
+我们做了几件很朴素但很关键的事:
+
+1. **结构化输出校验**:LLM 必须返回符合 JSON Schema 的结果,解析失败重试一次,再失败就转人工复核
+2. **Prompt 和规则版本化**:每次评估都记录 prompt 版本、规则版本、模型版本,出了问题能回放
+3. **RAG 命中留痕**:命中了哪些样本、哪些知识条目、相似度多少,都落库
+4. **高风险结论必须有依据**:涉及过敏、剂量、禁忌、诊疗规范,不能只有 LLM 一句话,必须能指回规则或知识库
+5. **工具调用白名单**:ReAct 能调用哪些工具、每个工具最大超时、最大轮次,都写死在配置里
+6. **灰度开关和降级路径**:Phase 2 出问题,马上切回 Phase 1;LLM 出问题,转人工复核,不让系统硬撑
+
+这些东西不酷,但这才是生产环境真正救命的地方。
+
+## 进化机制:让 Agent 自己长本事
+
+最让我们团队骄傲的,是这个"每周进化"机制。
+
+传统的 AI 项目上线了就完事了,效果衰减没人管。我们设计了一个定时任务(每周日凌晨 3 点):
+
+1. **统计反馈数据**:过去一周医生的采纳率、评分
+2. **提炼高质量样本**:采纳率>80% 且 评分>80 的案例,自动提升为 Few-shot 样本
+3. **分析 Bad Case**:高拒绝率的案例,让 LLM 分析原因,生成候选规则
+4. **淘汰低效样本**:长期不被采纳的样本自动下线
+
+这就形成了一个闭环:Agent 评估 → 医生反馈 → 自动学习 → Agent 能力提升 → 更多评估 → 更多反馈...
+
+但这里必须克制:**Agent 不能自己改生产规则**。
+
+我们做的是“自动发现,人工发布”。LLM 可以帮忙总结 Bad Case,生成候选 prompt 规则,推荐哪些样本应该提升为 Few-shot,但这些变更要先进审核队列。医生或业务专家确认后,才会发布到生产版本。每次发布都有版本号,也能回滚。
+
+上线两个月,样本库从初始的 50 条涨到了 300+ 条,采纳率从 65% 提升到了 82%。关键不是“零人工干预”,而是**把人工精力用在审核和校准上,而不是每天手工翻 Bad Case**。这才是可持续的进化。
+
+<div class="ai-tag">进化飞轮</div>
+
+## 部署架构:能复用就复用
+
+最后简单说下部署。我们的原则是:**默认不新增重基础设施,客户要求私有化时再补本地推理层**。
+
+默认 SaaS 形态是这样:
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│                    现有基础设施                               │
+├─────────────────────────────────────────────────────────────┤
+│  PostgreSQL (+ pgvector 插件)  ←  已有实例,加向量列即可       │
+│  Redis                        ←  已有,新增 Stream key       │
+│  Nacos                        ←  已有,服务注册发现           │
+└─────────────────────────────────────────────────────────────┘
+                            │
+                            ▼
+┌─────────────────────────────────────────────────────────────┐
+│  ai-agent-service (新服务,2C/2G)                            │
+│  ├── 固定工作流 (Phase 1)                                    │
+│  ├── RAG 检索模块                                            │
+│  └── ReAct Agent (Phase 2,可开关)                           │
+└─────────────────────────────────────────────────────────────┘
+           │                                   │
+           ▼                                   ▼
+┌──────────────────────┐          ┌────────────────────────────┐
+│  DeepSeek API         │          │  embedding 服务             │
+│  默认 LLM 调用入口     │          │  Qwen3 Embedding 独立部署    │
+└──────────────────────┘          └────────────────────────────┘
+```
+
+如果客户要求私有化部署,再把 DeepSeek API 那一层替换成本地推理服务:
+
+```
+DeepSeek API
+    ↓
+vLLM + Qwen3(客户私有化环境)
+```
+
+这个替换只发生在 `LlmClient` 实现层,不影响 Agent 的工作流、工具调用、RAG 检索和反馈闭环。也正因为这样,我们没有一开始就为了“看起来更自主可控”去买 GPU 堆本地模型。早期最贵的不是 API 调用费,而是团队把精力消耗在推理运维上,却还没证明 Agent 真的贴合业务。
+
+## 写在最后
+
+做 Agent 生产落地,最大的感悟是:**克制比炫技更重要**。
+
+我们当然可以搞个多 Agent 协作系统,每个 Agent 都有自己的记忆、工具、规划能力,看起来很酷。但现实是,大部分业务场景不需要那么复杂。一个固定的 Workflow + RAG 增强 + LLM 推理,能解决 80% 的问题。这就是我们 Phase 1 的选择。
+
+剩下的 20%,才是需要 ReAct、自主决策、工具调用的场景。而且即使做 Agent,也要给足控制手段——开关、降级、人工介入,一样都不能少。
+
+Agent 不是魔法,它是一个**需要工程化打磨的智能系统**。把预期放低一点,把工程做实一点,反而更容易成功。
+
+<div class="divider-stars"></div>
+
+**参考链接:**
+- [pgvector 文档](https://github.com/pgvector/pgvector)
+- [Qdrant 文档](https://qdrant.tech/documentation/)
+- [Qwen3 Embedding](https://qwenlm.github.io/blog/qwen3-embedding/)
+- [text2vec-base-chinese](https://huggingface.co/shibing624/text2vec-base-chinese)
+- [Qwen3 技术报告](https://qwenlm.github.io/blog/qwen3/)
+
+**关联阅读:**
+- [AI Agent 架构深度解析:原理、模式与生产落地](/2026/03/30/AI%20Agent%20架构深度解析:原理、模式与生产落地/)
+- [Agentic 入门:让 AI 不再一把梭,而是像人一样反复干活](/2026/03/24/Agentic%20入门:让%20AI%20不再一把梭,而是像人一样反复干活/)
+- [Anthropic:长期运行 Agent 应用的 Harness 设计](/2026/03/25/Anthropic:长期运行-Agent-应用的-Harness-设计/)
+- [Context Engineering:比提示词工程更底层的那件事](/2026/03/26/Context-Engineering-上下文工程/)
+- [RAG 深度解析:从原理到生产落地的完整指南](/2026/04/02/RAG%20深度解析:从原理到生产落地的完整指南/)
+
+---
+
+*如果你也在做 Agent 落地,欢迎交流。踩过的坑,说不定能帮到你。*

+ 323 - 0
source/_posts/ReAct框架深度解析:Beyond-Chatbots.md

@@ -0,0 +1,323 @@
+---
+title: Beyond Chatbots:ReAct 框架如何让 LLM 进化成真正的 Agent
+author: Gamehu
+date: 2026-04-12 22:00:00
+tags:
+  - AI
+  - Agent
+  - ReAct
+  - LLM
+  - 架构设计
+categories:
+  - AI
+---
+
+<div class="tag-container">
+  <span class="ai-tag">AI</span>
+  <span class="sub-tag">ReAct</span>
+  <span class="sub-tag">Agent</span>
+</div>
+
+## 前言
+
+> **⚠️ 时间线说明:** 这篇文章实际上是在《{% post_link Agent-production-landing-plan 'Agent 生产落地:从 demo 到产品的血泪经验' %}》之前完成的,一直躺在草稿箱里忘了发。阅读顺序上,建议先看这篇理解 ReAct 原理,再看那篇的生产实践经验。
+
+想象一下你现在要煮盆饺子。烧水、打料内心独白支配着每一个动作:"水开了,我该下饺子了。"当发现缺少关键调料,内心独白会调整策略:"没辣椒了?我用老干妈代替。"面对技术难点时,这个思维过程会触发外部行动:"我不确定需要煮多久,我需要打开美食APP查下。"
+
+这种边想边做、边做边调整的能力,是人类解决问题的基本方式。但传统的大语言模型(LLM)完全没有这种能力——它们更像一个闭卷考试的学生,只能凭记忆一口气写到底,错了也没法改。
+
+在人工智能领域,传统的大语言模型(LLM)一直像静态黑盒一样运作。虽然这些模型擅长语言模仿,但它们缺乏功能性的"工作记忆"和与世界动态交互的能力。这种隔离滋生了两种系统性失败:<span class="highlight-text">"幻觉循环"</span>,即模型自信地编造事实;以及<span class="highlight-text">"错误传播"</span>,即单一的逻辑失误会毁掉整个回答。
+
+**ReAct(Reason + Act)框架代表了 AI 架构的范式转变。** 通过在孤立认知与外部执行之间搭建结构性桥梁,ReAct 将 LLM 从对话者转变为能够规划、执行和自我纠正的自主 Agent。
+
+这篇是我学习 ReAct 的笔记,想把它讲清楚,也给自己留个档。
+
+---
+
+## 一、ReAct 到底在解决什么问题
+
+传统的 AI 能力其实是分裂的。要么你用 Chain-of-Thought(CoT)让模型在脑子里一步步推理,要么你用 Action-generation 让模型调用外部工具——但两者各自有坑:
+
+**纯推理(CoT)的问题:** 模型完全依赖内部参数知识,是个封闭系统。它没法查资料,没法更新自己的认知,结果就是自信满满地编事实(幻觉),而且一步错步步错(错误传播)。
+
+**纯行动(Act-only)的问题:** 模型可以调用工具,但缺乏显式的长期规划能力。它没法跟踪子目标,也没法系统地探索环境,很容易陷入重复或无意义的操作。
+
+**ReAct 的做法是:把两者缝合起来。** 推理痕迹帮助模型诱导、跟踪和更新行动计划,处理异常情况;而行动让模型能够对接外部信息源(Wikipedia API、浏览器、数据库)来获取实时信息。
+
+
+
+### 核心机制:反馈循环
+
+ReAct 把 Agent 的活动结构化成一个三段式交替序列:
+
+```
+Thought(思考)→ Action(行动)→ Observation(观察)→ Thought(再思考)...
+```
+
+{% asset_img Xnip2026-04-13_10-13-37.jpg ReAct 反馈循环示意图 %}
+
+1. **Thought:** LLM 用思维链把大任务拆成可管理的小任务,分析数据,决定下一步做什么
+2. **Action:** 模型输出使用某个预定义工具的意图(比如搜索数据库)。注意:LLM 自己不执行工具,只输出工具名和参数
+3. **Observation:** 外部系统执行工具,把结果反馈给 LLM
+
+Agent 基于这个观察重新评估进度,生成下一个 Thought。循环往复,直到输出最终答案。
+
+这个循环看起来直白,但效果非常显著。在 HotpotQA 基准测试里,传统 CoT 的幻觉率(编瞎话的概率)是 14%,上了 ReAct 之后直接降到 6%[^1]。
+
+<div class="article-quote">
+<strong>关键洞察:</strong>ReAct 不是让模型"更聪明",而是给它一套"查资料→推理→再查资料"的机制,让它有机会认错和修正。
+</div>
+
+---
+
+## 二、五个核心要点:为什么 ReAct 有效
+
+### 要点 1:推理与行动的协同
+
+ReAct 范式打破了传统上推理与执行的二分。其核心是形式化表达 **Â = A ∪ L**。在这个等式中,Agent 的行动空间从物理或程序步骤(A)扩展到包含内部的"语言空间"(L)。通过将"思考"视为模型轨迹中的另一种行动类型,ReAct 允许 Agent 生成口头推理痕迹——或称"想法"——与特定任务的动作交错进行。
+
+这种协同使模型能够诱导、跟踪和更新计划,同时实时处理异常。正如 Yao 等人在其奠基性研究中所言:
+
+<div class="article-quote">
+"这种'行动'与'推理'之间的紧密协同使人类能够快速学习新任务,并在前所未有的情况下进行稳健的决策或推理。"
+</div>
+
+通过交错这些痕迹,模型使用推理来决定检索什么信息,而由此产生的观察又为后续推理提供基础。
+
+### 要点 2:通过观察环节打破幻觉循环
+
+传统的思维链(Chain-of-Thought, CoT)提示鼓励模型"一步步思考",但它完全依赖模型的内部参数知识——一个容易"幻觉"不存在事实的封闭系统。ReAct 通过引入强制的 **Observation(观察)** 步骤来解决这个问题,将模型锚定在外部现实中。
+
+在知识密集型任务中,ReAct 使用特定的动作词汇——包括 `search[entity]`、`lookup[string]` 和 `finish[answer]`——与 Wikipedia 等外部实体交互。这种迭代的 Thought-Action-Observation 循环迫使模型在继续之前根据真实知识库验证其逻辑。结果是统计学上显著的改进:在 HotpotQA 基准测试中,误报率(幻觉事实或逻辑的频率)从标准 CoT 的 **14%** 暴跌至 ReAct 框架下的 **6%**[^1]。这种锚定将 Agent 从创意作家转变为可信的调查者。
+
+### 要点 3:代理技能的效率悖论
+
+ReAct 扩展分析中最深刻的见解之一是:<span class="highlight-text">"代理技能"通常比原始模型大小更具参数效率。</span> 我们在"小模型悖论"中看到了突破:教模型如何推理和行动,比强迫它在权重中记忆海量事实更具泛化能力。
+
+数据揭示了一个惊人的层级关系:一个较小的 PaLM-8B 模型,仅在 3,000 条 ReAct 轨迹上微调,就能胜过完全依赖提示的巨大 PaLM-540B 模型。更令人印象深刻的是,微调的 62B 模型超过了 540B 提示版本的性能[^1]。这表明 AI 的未来不在于构建越来越大的记忆数据仓库,而在于提炼模型导航工具集和环境的程序能力。
+
+| 模型 | 方式 | 表现 |
+|------|------|------|
+| PaLM-540B | Zero-shot 提示 | 基准线 |
+| PaLM-8B | ReAct 微调(3,000 轨迹) | **超越 540B** |
+| PaLM-62B | ReAct 微调 | **大幅超越 540B** |
+
+*数据来源:Yao et al., *ReAct: Synergizing Reasoning and Acting in Language Models*[^1]*
+
+### 要点 4:可解释性:终于能看懂 AI 在想什么了
+
+传统模型的黑盒问题一直让人头疼。输入一个问题,输出一个答案,中间发生了什么?不知道。
+
+ReAct 的"草稿本(scratchpad)"机制解决了这个问题。每一轮循环的 Thought、Action、Observation 都被记录下来,形成一条完整的"思维链"。
+
+如果出错了,你能精确定位到是哪一步出的问题:
+
+- **推理错误**:模型陷入了重复循环,比如一直问同一个问题
+- **检索错误**:外部 API 返回了不相关的信息
+- **行动错误**:调用了错误的工具或参数
+
+更有意思的是,这种透明性支持**人在回路(Human-in-the-loop)**的干预。这有点像你带新人:他在那自言自语分析问题,你说"这一步想偏了",他马上就能纠正。
+
+ReAct 通过其人类可读的"草稿本",为 AI 的"黑盒"提供了一个罕见的窗口。Agent 过程的每一步都被记录为想法、行动和观察的序列,提供了以前不可能的诊断水平。
+
+如果 Agent 失败,开发者可以精确定位确切的失败模式——比如模型陷入重复循环的"推理错误",或外部 API 返回无信息结果的"搜索错误"。这种透明性还促进了<span class="highlight-text">"人在回路(Human-in-the-loop)"</span>的纠正。在 ALFWorld 环境(一个基于文本的任务模拟器)中,研究人员证明,人类可以通过简单编辑草稿本上的"想法"来引导失败的 Agent 回到正轨——删除幻觉或添加提示——使 AI 能够立即重新校准其计划[^1]。
+
+**示例:ReAct 轨迹**
+
+```
+Thought: 用户询问 2024 年诺贝尔物理学奖得主,我需要确认最新获奖信息。
+Action: search[2024 Nobel Prize in Physics winner]
+Observation: John J. Hopfield and Geoffrey Hinton were awarded...
+Thought: 好的,我已获得准确信息,现在可以回答用户了。
+Action: finish[约翰·霍普菲尔德和杰弗里·辛顿]
+```
+
+### 要点 5:复杂环境的动态规划
+
+虽然标准 LLM 在多步骤任务上挣扎,但 ReAct 在 ALFWorld(合成家庭任务)和 WebShop(拥有 118 万产品的在线购物环境)等交互式环境中表现出色。与遵循 rigid 脚本的"仅行动(Act-only)"Agent 不同,ReAct Agent 利用动态推理将高级目标分解为可管理的子目标。
+
+所以ReAct 的真正威力,在处理多步骤、需要探索的任务时才会显现出来。
+
+ALFWorld 里有个典型任务:”把一个发烫的平底锅放到冷却架上”。这看起来简单,但模型需要:
+
+ - 找到平底锅(可能在灶台上)
+ - 判断它是不是”发烫”的状态
+ - 找到冷却架(可能在橱柜里)
+ - 规划一条合理的移动路径
+  
+传统”只行动不思考”的 Agent(Act-only)在这种任务上成功率只有 45%,而 ReAct 能达到 71%[^1]。
+
+ReAct 利用预训练的"常识知识"——例如,知道胡椒罐更可能在橱柜里而不是冰箱里——以手术般的效率探索环境。通过维护其进度的内部记录,ReAct Agent 避免了困扰纯反应式系统的无产出循环。
+
+---
+
+## 三、如何构建一个 ReAct Agent
+
+你可以用 LangChain、LangGraph 或 BeeAI 等开源框架快速搭建(比如 LangChain 的 `ZERO_SHOT_REACT_DESCRIPTION`),也可以从零用纯 Python 手写。下面是手写版本的逻辑拆解:
+
+### 步骤 1:定义工具集
+
+首先确定模型能用的具体行动。写 Python 函数(计算器、搜索等),然后把它们的签名和描述提取成 LLM 能理解的格式(JSON 或字符串)。
+
+```python
+# 示例工具定义
+tools = {
+    "search": {
+        "description": "搜索 Wikipedia 获取实体信息",
+        "parameters": {"entity": "要搜索的关键词"}
+    },
+    "calculator": {
+        "description": "执行数学计算",
+        "parameters": {"expression": "数学表达式"}
+    }
+}
+```
+
+### 步骤 2:编写 ReAct 提示词
+
+ReAct Agent 极度依赖特定的提示技巧来结构化输出。系统提示必须:
+
+- 引导模型一步步思考,交错想法和行动
+- 提供可用工具列表
+- 定义严格的输出格式,通常用标签如 `Thought:`、`Action:`、`Action Input:`、`Observation:`
+
+**提示词模板示例:**
+
+```
+你是一个能够使用工具的 AI 助手。按以下格式解决问题:
+
+Thought: 分析当前情况,决定下一步行动
+Action: 要使用的工具名称(从 [{tool_names}] 中选择)
+Action Input: 传递给工具的参数
+Observation: 工具执行结果
+...(重复 Thought/Action/Observation 直到得出结论)
+Thought: 我现在知道最终答案了
+Final Answer: 用户的最终答案
+
+可用工具:
+{tools}
+
+开始:
+Question: {input}
+```
+
+### 步骤 3:构建执行循环
+
+把提示词和用户问题传给 LLM 后,你需要一个程序循环来处理执行:
+
+1. **解析输出:** 拦截 LLM 的文本输出,提取 `Action`(工具名)和 `Action Input`(参数)
+2. **执行工具:** 你的 Python 代码调用对应的函数
+3. **返回观察:** 把函数结果格式化为 `Observation`,追加到对话历史
+4. **重复:** 把更新后的上下文传回 LLM,让它基于新数据生成下一个 `Thought`
+5. **终止:** 当 LLM 确定有最终答案并使用 "Finish" 动作或输出最终响应标签时,循环结束
+
+**伪代码示意:**
+
+```python
+max_iterations = 10
+history = prompt_template.format(input=user_question)
+
+for i in range(max_iterations):
+    response = llm.generate(history)
+    
+    # 解析 Thought/Action/Observation
+    thought = extract_thought(response)
+    action = extract_action(response)
+    action_input = extract_action_input(response)
+    
+    if action == "Finish":
+        return action_input  # 最终答案
+    
+    # 执行工具
+    observation = execute_tool(action, action_input)
+    
+    # 追加到历史
+    history += f"\nThought: {thought}\nAction: {action}\nAction Input: {action_input}\nObservation: {observation}"
+```
+
+---
+
+## 四、生产环境的优化技巧
+
+要让 ReAct Agent 稳定、高效地运行,避免常见陷阱,你需要以下优化:
+
+### 1. 使用少样本示例(Few-Shot Exemplars)
+
+虽然可以构建"零样本"ReAct Agent,但性能会在你加入人工编写的完整 Thought-Action-Observation 轨迹示例后显著提升。提供 **3 到 6 个** 高质量、任务特定的示例,能教会模型如何格式化想法、分解任务、处理不同工具。
+
+<div class="highlight-text">
+技巧:示例要覆盖成功路径和典型的错误恢复场景,让模型学会"碰壁后怎么绕路"。
+</div>
+
+### 2. 与 Chain-of-Thought 和自一致性结合
+
+研究表明,ReAct 高度依赖搜索结果质量;如果工具返回无用数据,模型的推理会跑偏。反过来,标准 CoT 受困于幻觉。
+
+**最佳实践是构建混合系统:**
+
+- 如果 ReAct Agent 在给定步数内无法返回答案,回退到内部 CoT 推理(使用 Self-Consistency,即采样多条 CoT 路径并取多数答案)
+- 如果 CoT-SC 置信度低,切换到 ReAct 收集外部事实
+
+这种"双保险"策略在知识密集型任务上效果最好。
+
+### 3. 设置严格的循环限制
+
+因为框架本质上是反馈循环,ReAct Agent 有时会陷入重复生成相同想法和行动的困境,导致高延迟和 token 成本爆炸。你必须在代码中设置最大迭代次数或特定结束条件(如置信度阈值)来防止无限循环。
+
+```python
+# 示例:多层防护
+MAX_ITERATIONS = 10
+MAX_COST = 0.5  # 美元
+seen_actions = set()
+
+for step in range(MAX_ITERATIONS):
+    action_signature = (action_name, action_input)
+    if action_signature in seen_actions:
+        # 检测到循环,触发终止或切换策略
+        break
+    seen_actions.add(action_signature)
+```
+
+### 4. 微调实现可扩展性
+
+纯粹从上下文提示中学习复杂推理和行动是困难的,尤其是对较小语言模型。然而,在几千条正确的 ReAct 轨迹上微调较小模型(如 8B 参数模型),能让它们大幅超越仅依赖标准提示的更大模型(如 PaLM-540B)。
+
+**工程建议:**
+- 收集生产环境中的高质量轨迹作为微调数据
+- 使用 LoRA 等高效微调技术降低计算成本
+- 小模型+ReAct 往往比大模型裸跑更省算力、更可控
+
+### 5. 实现人在回路(Human-in-the-Loop)纠正
+
+因为 ReAct 生成可读的、 verbalized 的推理痕迹,系统具有高度可解释性。你可以设计 Agent 允许人类检查其内心独白。如果模型产生了幻觉想法或走错了路,人类可以直接编辑生成的 `Thought` 文本。这能纠正模型的后续行为,引导它成功,无需重写代码或手动执行动作。
+
+**典型应用场景:**
+- 客服 Agent 遇到边界案例时,人工客服介入修正推理方向
+- 代码生成 Agent 逻辑走偏时,程序员编辑 Thought 引导它回到正轨
+- 研究 Agent 检索到无关信息时,研究员删除幻觉并提供正确线索
+
+---
+
+## 五、写在最后
+
+ReAct 给我的最大启发,不是技术细节,而是一种设计哲学的转变。
+
+以前我们总想造一个"什么都知道"的 AI——把人类知识全塞进模型参数里。ReAct 走的是另一条路:<span class="highlight-text">承认模型不可能全知,给它一套"查资料+推理"的机制,让它像人一样边做边学</span>。
+
+当前 AI 的最先进水平并不依赖单一框架,而是协同的切换方法。最有能力的系统将 ReAct 与 CoT-自一致性结合,对逻辑任务利用内部参数知识,当内部置信度低时切换到 ReAct 进行外部锚定。
+
+随着我们从对话式聊天机器人过渡到能够浏览网页、使用软件工具和管理工作流的自主 Agent,Thought-Action-Observation 循环正在成为安全和可靠性的行业标准。我们不再只是构建会说话的模型;我们正在构建为了行动而思考的 Agent。
+
+
+
+---
+
+**参考**
+
+[^1]: [arxiv.org/abs/2210.03629](https://arxiv.org/abs/2210.03629)
+
+[^2]: [ReAct Prompting - Prompting Guide](https://www.promptingguide.ai/techniques/react)
+
+[^3]: [What is a ReAct Agent? - IBM](https://www.ibm.com/think/topics/react-agent)
+
+[^4]: [ReAct Paper (PDF) - arXiv](https://arxiv.org/pdf/2210.03629)

BIN
source/_posts/ReAct框架深度解析:Beyond-Chatbots/Xnip2026-04-13_10-13-37.jpg