<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>原理 on Renxin's Blog</title><link>https://renxinblog.cn/tags/%E5%8E%9F%E7%90%86/</link><description>Recent content in 原理 on Renxin's Blog</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Fri, 10 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://renxinblog.cn/tags/%E5%8E%9F%E7%90%86/index.xml" rel="self" type="application/rss+xml"/><item><title>RAG 是什么？从一条文档的完整旅程说起</title><link>https://renxinblog.cn/post/rag-overview/</link><pubDate>Fri, 10 Jul 2026 00:00:00 +0000</pubDate><guid>https://renxinblog.cn/post/rag-overview/</guid><description>&lt;p&gt;你有没有想过一个问题——&lt;/p&gt;
&lt;p&gt;LLM 训练时读了几万亿字的互联网数据，几乎什么都知道。但它不知道你的业务数据——你的产品手册、售后政策、内部文档、客服对话记录。这些它没读过。&lt;/p&gt;
&lt;p&gt;怎么让它知道该知道的？&lt;/p&gt;
&lt;p&gt;这就是 RAG 要解决的问题。这篇文章从为什么需要 RAG 开始，一直讲到它的完整架构和最容易翻车的地方。&lt;/p&gt;
&lt;h2 id="llm-什么都知道但不知道你的业务"&gt;LLM 什么都知道，但不知道你的业务
&lt;/h2&gt;&lt;p&gt;LLM 有两个先天缺陷，在实际落地中很难绕过去。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;缺陷&lt;/th&gt;
					&lt;th&gt;表现&lt;/th&gt;
					&lt;th&gt;影响&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;知识截止&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;训练数据停在某个时间点&lt;/td&gt;
					&lt;td&gt;问最新政策、实时数据，它不知道&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;不知道私有数据&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;没读过你的内部文档、产品手册、客服记录&lt;/td&gt;
					&lt;td&gt;业务场景下基本不可用&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;怎么解决？目前有三条路可以走。&lt;/p&gt;
&lt;h3 id="方案一微调fine-tuning"&gt;方案一：微调（Fine-tuning）
&lt;/h3&gt;&lt;p&gt;拿业务数据继续训练模型，让它把新知识学进去。&lt;/p&gt;
&lt;p&gt;听起来最直接，但实际操作成本很高。你需要准备高质量的训练数据、处理算力资源，而且每次业务文档更新了，又得重新训一遍。更麻烦的是，微调可能会破坏模型原有的通用能力——模型学会了你的业务术语，但写邮件的能力反而变差了。&lt;/p&gt;
&lt;h3 id="方案二塞长上下文long-context"&gt;方案二：塞长上下文（Long Context）
&lt;/h3&gt;&lt;p&gt;把所有资料都塞进 Prompt 里，让模型自己翻。&lt;/p&gt;
&lt;p&gt;实现最简单，不用动模型。现在有些模型支持 128K、1M 甚至更长的上下文，看起来够用。但实际效果没那么理想：一是 Token 成本会随着内容量线性增长；二是内容太多之后，模型很难从海量信息中找到准确的那一段——这就是著名的&amp;quot;大海捞针&amp;quot;问题。上下文再长，检索精度是会下降的。&lt;/p&gt;
&lt;h3 id="方案三rag检索增强生成"&gt;方案三：RAG（检索增强生成）
&lt;/h3&gt;&lt;p&gt;每次提问的时候，先去知识库里检索相关内容，拼到 Prompt 里，再让 LLM 基于这些材料回答。&lt;/p&gt;
&lt;p&gt;RAG 是目前最主流的方案。原因很实在：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;成本低&lt;/strong&gt;：不需要重新训练模型，省了算力&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;更新快&lt;/strong&gt;：知识库加个文档就行，不需要重训&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可追溯&lt;/strong&gt;：LLM 是看着你给的资料回答的，能知道它引用的是哪份文档&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;幻觉少&lt;/strong&gt;：基于具体材料生成，比凭空回答可靠得多&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当然，RAG 也有自己的问题——它依赖检索质量。如果检索到的内容不对或者不完整，LLM 材料再好也回答不对。后面会详细讲这个。&lt;/p&gt;
&lt;h2 id="rag-不是搜索引擎它和搜索引擎的区别"&gt;RAG 不是搜索引擎——它和搜索引擎的区别
&lt;/h2&gt;&lt;p&gt;很多人第一次接触 RAG 的反应是：这不就是搜索引擎吗？用户搜关键词，系统返回匹配的文档——有什么新鲜的？&lt;/p&gt;
&lt;p&gt;这个理解对了一半，错的一半恰好是 RAG 的核心。&lt;/p&gt;
&lt;h3 id="搜索引擎esbm25怎么工作"&gt;搜索引擎（ES/BM25）怎么工作
&lt;/h3&gt;&lt;p&gt;你搜一个关键词，搜索引擎在你的文档库里找到包含这个词的所有文档，按匹配度排序返回。它做的是&lt;strong&gt;关键词精确匹配&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比如说你搜&amp;quot;2025年Q3营收&amp;quot;，它能找到包含这几个词的那份财报。但如果你问&amp;quot;去年第三季度营收怎么样？&amp;quot;，它可能找不到——因为文档里写的是&amp;quot;2025年Q3营收同比增长15.3%&amp;quot;，关键词对不上。&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;%%{init: {'theme':'neutral'}}%%
graph LR
 A["搜：去年第三季度营收怎么样？"] --&gt; B[ES 关键词匹配]
 B --&gt; C["找到「去年」「第三」「季度」「营收」"]
 C --&gt; D[❌ 文档写的是「Q3营收同比增长15.3%」&lt;br/&gt;关键词对不上，找不到]&lt;/pre&gt;&lt;p&gt;这是一直以来搜索引擎的运作方式。在&amp;quot;搜文档、找文件&amp;quot;的场景下非常好用，但在&amp;quot;回答问题&amp;quot;的场景下就显得力不从心。&lt;/p&gt;
&lt;h3 id="rag向量检索怎么工作"&gt;RAG（向量检索）怎么工作
&lt;/h3&gt;&lt;p&gt;RAG 不一样。它先把你的文档库转成一组向量（可以理解成一组数字坐标），用户提问时也把问题转成向量，然后计算问题向量和文档向量之间的距离。距离越近，说明内容越相关。&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;%%{init: {'theme':'neutral'}}%%
graph LR
 A["同样的问题：去年第三季度营收怎么样？"] --&gt; B[RAG 向量检索]
 B --&gt; C[把问题和文档都转成向量&lt;br/&gt;计算语义距离]
 C --&gt; D[✅ 匹配到「Q3营收同比增长15.3%」&lt;br/&gt;语义相近，找到了]&lt;/pre&gt;&lt;p&gt;&amp;ldquo;去年第三季度营收怎么样？&amp;ldquo;和&amp;quot;2025年Q3营收同比增长15.3%&amp;ldquo;在字面上没有共同的关键词，但在语义上是同一件事。向量检索能捕捉到这种语义上的相近。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;维度&lt;/th&gt;
					&lt;th&gt;搜索引擎（ES/BM25）&lt;/th&gt;
					&lt;th&gt;RAG（向量检索）&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;匹配方式&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;关键词精确匹配&lt;/td&gt;
					&lt;td&gt;语义匹配&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;搜&amp;quot;苹果&amp;rdquo;&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;返回含&amp;quot;苹果&amp;quot;二字的所有文档&lt;/td&gt;
					&lt;td&gt;能区分你在问水果还是手机品牌&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;输出&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;文档列表&lt;/td&gt;
					&lt;td&gt;相关内容 + LLM 生成的自然语言回答&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;适合场景&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;搜文档、找文件&lt;/td&gt;
					&lt;td&gt;回答问题、知识问答&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;局限性&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;同义词、近义词不识别&lt;/td&gt;
					&lt;td&gt;对数字/专有名词匹配不如精确搜索&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;所以搜索引擎和 RAG 不是替代关系，而是互补关系。生产环境最常用的方案是&lt;strong&gt;混合检索&lt;/strong&gt;：向量检索负责语义匹配，BM25 负责精确匹配，两者互补。&lt;/p&gt;
&lt;p&gt;但语义检索有一个前提条件：内容必须被正确地理解和切分。这就引出了下一个问题。&lt;/p&gt;
&lt;h2 id="一条文档在-rag-系统中的完整旅程"&gt;一条文档在 RAG 系统中的完整旅程
&lt;/h2&gt;&lt;p&gt;语义匹配的效果，很大程度上取决于文档在入库时被处理成了什么样子。一条文档从上传到最终被 LLM 用来回答问题，中间经历了一整套加工管道。&lt;/p&gt;
&lt;a href="https://renxinblog.cn/images/rag-pipeline.svg" target="_blank"&gt;
 &lt;figure&gt;&lt;img src="https://renxinblog.cn/images/rag-pipeline.svg"
 			alt="RAG系统完整管道图" width="100%"&gt;&lt;figcaption&gt;
 			&lt;p&gt;RAG 系统完整管道：入库阶段 → 检索阶段（点击图片查看大图）&lt;/p&gt;
 		&lt;/figcaption&gt;
 &lt;/figure&gt;

&lt;/a&gt;
&lt;h3 id="入库阶段让机器能读懂文档"&gt;入库阶段：让机器能&amp;quot;读懂&amp;quot;文档
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;环节&lt;/th&gt;
					&lt;th&gt;做什么&lt;/th&gt;
					&lt;th&gt;为什么重要&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;文档解析&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;把 PDF、Word、HTML 等格式转成纯文本&lt;/td&gt;
					&lt;td&gt;PDF 看着是文字，实际是一堆坐标指令。解析不好，后面的所有环节都白费&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;文本清洗&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;去噪、去重、格式归一化&lt;/td&gt;
					&lt;td&gt;把乱码、多余的空格、无关的页眉页脚清掉&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;分块&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;把长文本切成语义完整的片段&lt;/td&gt;
					&lt;td&gt;不能整篇检索——太大了检索不准，太小了语境不够&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;向量化&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;用 Embedding 模型把文本片段转成向量&lt;/td&gt;
					&lt;td&gt;向量是语义匹配的基础&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;存入向量库&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;把向量和原文存入向量数据库&lt;/td&gt;
					&lt;td&gt;供后续检索使用&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="检索阶段找到相关内容并让-llm-回答"&gt;检索阶段：找到相关内容并让 LLM 回答
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;环节&lt;/th&gt;
					&lt;th&gt;做什么&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;问题向量化&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;把用户提问也转成向量&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;向量检索&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;在向量库里找距离最近的文档片段&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;重排序&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;对检索结果重新精排，提升 Top 结果的准确率&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;拼入 Prompt&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;把检索到的内容拼到 Prompt 里，加上指令让 LLM 基于材料回答&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="为什么检索质量是关键"&gt;为什么检索质量是关键
&lt;/h3&gt;&lt;p&gt;整个管道里，最核心的瓶颈不是 LLM 本身，而是&lt;strong&gt;检索质量&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Andrew Ng 在一个分享里提过一个数据：优化 RAG 系统时，检索质量的提升对最终答案准确率的影响远大于模型本身的升级。换句话说，用更强的模型不能弥补检索结果差的问题——模型再聪明，材料给错了也答不对。&lt;/p&gt;
&lt;p&gt;而检索质量又依赖于前面的入库环节：文档解析是否正确、分块是否合理、向量化是否准确。这是一个完整的链路，短板出在任何一环都会影响最终效果。&lt;/p&gt;
&lt;h2 id="最容易在哪里翻车"&gt;最容易在哪里翻车？
&lt;/h2&gt;&lt;p&gt;把各个环节按翻车概率排序，前三个是：&lt;/p&gt;
&lt;h3 id="1-文档解析最大的坑"&gt;1. 文档解析——最大的坑
&lt;/h3&gt;&lt;p&gt;你传上去的文档是 PDF。PDF 看着是文本，但本质上是一堆坐标指令的集合——&amp;ldquo;在坐标 (x,y) 处渲染字符 A，在 (x+5,y) 处渲染字符 B&amp;rdquo;。它并不知道什么是段落、什么是表格、什么是页眉。&lt;/p&gt;
&lt;p&gt;遇到以下几种 PDF，解析很容易翻车：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;PDF 类型&lt;/th&gt;
					&lt;th style="text-align: center"&gt;难度&lt;/th&gt;
					&lt;th&gt;翻车方式&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;数字原生 PDF&lt;/td&gt;
					&lt;td style="text-align: center"&gt;⭐ 简单&lt;/td&gt;
					&lt;td&gt;基本没问题&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;扫描件 PDF（图片）&lt;/td&gt;
					&lt;td style="text-align: center"&gt;⭐⭐⭐ 中等&lt;/td&gt;
					&lt;td&gt;需要 OCR，识别率取决于清晰度&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;双栏/多栏排版&lt;/td&gt;
					&lt;td style="text-align: center"&gt;⭐⭐⭐⭐ 困难&lt;/td&gt;
					&lt;td&gt;阅读顺序恢复错误，左右栏混在一起&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;含表格 PDF&lt;/td&gt;
					&lt;td style="text-align: center"&gt;⭐⭐⭐⭐⭐ 极难&lt;/td&gt;
					&lt;td&gt;无线框表格、合并单元格、跨页表格，很容易解析错位&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;含公式 PDF&lt;/td&gt;
					&lt;td style="text-align: center"&gt;⭐⭐⭐⭐⭐ 极难&lt;/td&gt;
					&lt;td&gt;LaTeX 公式、化学结构式，专用模型才能处理好&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="2-分块切得不合适"&gt;2. 分块——切得不合适
&lt;/h3&gt;&lt;p&gt;解析完的文本不能整篇扔进去检索。整篇检索的问题：一篇文档可能几千字，但用户问的只是其中一小段。整篇匹配会让不相关的内容稀释相关内容的信号。&lt;/p&gt;
&lt;p&gt;但切得太细也不行。比如只切一两句话——检索时可能命中了，但上下文缺失，LLM 看了也不知道前因后果。&lt;/p&gt;
&lt;p&gt;这就是分块的核心矛盾：&lt;strong&gt;检索精度 vs 上下文完整性&lt;/strong&gt;。切小了检索精准但语境不足，切大了语境完整但检索不精准。&lt;/p&gt;
&lt;p&gt;实际生产中有好几种分块策略来解决这个矛盾——按固定大小切、按语义边界切、按文档结构切、父子分块等等。每种策略各有优缺点，选型取决于你的文档类型和场景。&lt;/p&gt;
&lt;h3 id="3-检索精确匹配-vs-语义匹配的盲区"&gt;3. 检索——精确匹配 vs 语义匹配的盲区
&lt;/h3&gt;&lt;p&gt;即使解析和分块都做好了，检索环节仍然可能翻车。典型场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;找不到相关内容&lt;/strong&gt;：你的文档里确实有答案，但向量检索没把它排到前面&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;找到太多噪音&lt;/strong&gt;：检索返回了大量不太相关的内容，稀释了有效信号&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数字/专有名词匹配弱&lt;/strong&gt;：向量检索对&amp;quot;Q3-2025营收同比增长率15.3%&amp;ldquo;这种精确表达不如关键词搜索&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这也是为什么生产环境通常用混合检索——语义 + 关键词组合，取长补短。&lt;/p&gt;
&lt;h2 id="回头看看"&gt;回头看看
&lt;/h2&gt;&lt;p&gt;把整篇文章的问题串起来：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;LLM 不知道你的业务数据
 → 三种方案对比，RAG 最实用（成本低、更新快、可追溯）
 → RAG 不是搜索引擎，是语义匹配
 → 但语义匹配的前提是内容被正确加工
 → 入库阶段：解析 → 清洗 → 分块 → 向量化 → 存储
 → 检索阶段：检索 → 重排序 → 拼入 Prompt → 生成
 → 最容易翻车的地方：解析、分块、检索
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;RAG 看起来简单——&amp;ldquo;查资料再回答&amp;rdquo;——但做了才发现，每一步都有坑。理解了这个全貌，后续不管是选型还是排查问题，都知道问题可能出在哪一环。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;参考资料&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Andrew Ng, &lt;em&gt;Building RAG Agents with LLMs&lt;/em&gt;, DeepLearning.AI, 2025 — 检索质量对 RAG 效果影响的分享&lt;/li&gt;
&lt;li&gt;Lewis et al., &lt;em&gt;Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks&lt;/em&gt;, arXiv:2005.11401, 2020 — RAG 论文原文&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>向量化原理——为什么向量能算语义相似度</title><link>https://renxinblog.cn/post/embedding-principle/</link><pubDate>Fri, 10 Jul 2026 00:00:00 +0000</pubDate><guid>https://renxinblog.cn/post/embedding-principle/</guid><description>&lt;p&gt;你有没有想过一个问题——&lt;/p&gt;
&lt;p&gt;RAG 的核心是「语义匹配」：你说「去年第三季度营收怎么样」，它能找到文档里写的「Q3 营收同比增长 15.3%」。字面不一样，但意思一样。&lt;/p&gt;
&lt;p&gt;这到底是怎么做到的？一段文字怎么变成一个「向量」？为什么两个向量离得近就代表意思相近？&lt;/p&gt;
&lt;p&gt;这篇文章从最基础的概念讲起，不涉及复杂的数学公式。&lt;/p&gt;
&lt;h2 id="人能判断语义相似度机器也能"&gt;人能判断语义相似度，机器也能
&lt;/h2&gt;&lt;p&gt;先想一个简单的问题：人和人之间怎么判断两句话意思差不多？&lt;/p&gt;
&lt;p&gt;「今天天气真好」和「今天阳光明媚」——你一看就知道它们说的是一回事。&lt;/p&gt;
&lt;p&gt;背后的过程很复杂（你用了多年的语言经验、对词汇的理解、对语境的把握），但直觉上很简单：你在大脑里给这两句话各自建立了一个「语义位置」，发现它们靠得很近。&lt;/p&gt;
&lt;p&gt;机器做同样的事，只是用的方法不一样——它用&lt;strong&gt;向量&lt;/strong&gt;来表示语义位置。&lt;/p&gt;
&lt;h2 id="向量是什么一组数字而已"&gt;向量是什么？一组数字而已
&lt;/h2&gt;&lt;p&gt;向量听起来很高深，其实就是一组数字。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[0.52, -0.13, 0.87, -0.42, 0.11, ...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;你可以把向量想象成&lt;strong&gt;语义空间中的一个坐标点&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比如我们想象一个二维的语义空间，每一段话都对应一个坐标点：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;「今天天气真好」→ 坐标 (0.9, 0.3)
「今天阳光明媚」→ 坐标 (0.8, 0.4)
「苹果很好吃」 → 坐标 (0.1, 0.2)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在语义空间里，「今天天气真好」和「今天阳光明媚」靠得近（意思相近），和「苹果很好吃」离得远（不相关）。&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://renxinblog.cn/images/semantic-space.svg"
			alt="语义空间坐标示意图" width="80%"&gt;&lt;figcaption&gt;
			&lt;p&gt;二维语义空间示意：意思相近的文本在空间中靠得近&lt;/p&gt;
		&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;当然，现实中的语义向量不是两维，而是几百甚至几千维——但原理一样。两个向量在空间中的距离越近，代表它们的意思越相近。&lt;/p&gt;
&lt;h2 id="语义从哪来embedding-模型怎么训练的"&gt;语义从哪来：Embedding 模型怎么训练的
&lt;/h2&gt;&lt;p&gt;刚才的水果例子是我手动编的维度（甜度、大小）。但在真实场景中，不可能有人来给每个词标注「甜度 = 0.9」这种数据。&lt;/p&gt;
&lt;p&gt;那向量的数值从哪来？答案是 Embedding 模型自己学出来的。&lt;/p&gt;
&lt;p&gt;训练方法的核心思路很简单：&lt;strong&gt;出现在相似语境中的词，应该有相似的向量&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;一个经典的例子：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;「国王」和「女王」经常出现在类似的句子里（「__ 统治着国家」「__ 戴着一顶王冠」）。所以它们的向量应该离得近。&lt;/p&gt;
&lt;p&gt;「国王」和「苹果」很少出现在同一个语境里。所以它们的向量应该离得远。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;训练时，模型会读海量的文本，对每个词做一个调整：如果两个词经常出现在相似的上下文里，就把它们的向量拉近一点；如果很少同时出现，就把它们推远一点。&lt;/p&gt;
&lt;p&gt;经过无数次这样的调整，模型最终学会了一个语义空间——意思相近的词，在空间里靠得近；意思不相关的词，离得远。&lt;/p&gt;
&lt;p&gt;这一步就是 Embedding 模型做的事。你给它输入一段文本，它返回一组向量。&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;%%{init: {'theme':'neutral'}}%%
graph LR
 A["输入文本&lt;br/&gt;去年第三季度赚了多少"] --&gt; B[Embedding 模型]
 B --&gt; C["输出向量&lt;br/&gt;[0.23, -0.56, 0.91, ...]"]

 style A fill:#dbeafe,stroke:#3b82f6,color:#1e3a5f
 style B fill:#d1fae5,stroke:#10b981,color:#064e3b
 style C fill:#fce7f3,stroke:#ec4899,color:#831843&lt;/pre&gt;&lt;h2 id="如何判断两个向量离得近"&gt;如何判断两个向量离得近
&lt;/h2&gt;&lt;p&gt;有了向量之后，怎么计算两段文字的相似度？&lt;/p&gt;
&lt;p&gt;最常用的方法叫&lt;strong&gt;余弦相似度&lt;/strong&gt;（Cosine Similarity）。你不需要记住公式，只要理解它的直觉：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;两个向量在空间中有一个夹角。夹角越小，说明方向越一致，内容越相似。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;完全相同的文本 → 夹角 0°，相似度 = 1&lt;/li&gt;
&lt;li&gt;完全不相关的文本 → 夹角 90°，相似度 ≈ 0&lt;/li&gt;
&lt;li&gt;意思相反的文本 → 夹角 180°，相似度 = -1&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;%%{init: {'theme':'neutral'}}%%
graph LR
 A["文本A: 今天天气真好"] --&gt; C[Embedding 模型]
 B["文本B: 今天阳光明媚"] --&gt; C
 C --&gt; D["向量A: [0.8, 0.5, ...]&lt;br/&gt;向量B: [0.7, 0.6, ...]"]
 D --&gt; E["余弦相似度 = 0.92&lt;br/&gt;✅ 意思相近"]

 style A fill:#dbeafe,stroke:#3b82f6,color:#1e3a5f
 style B fill:#dbeafe,stroke:#3b82f6,color:#1e3a5f
 style C fill:#d1fae5,stroke:#10b981,color:#064e3b
 style D fill:#fce7f3,stroke:#ec4899,color:#831843
 style E fill:#d1fae5,stroke:#10b981,color:#064e3b&lt;/pre&gt;&lt;h2 id="回到-rag向量检索就是这样工作的"&gt;回到 RAG：向量检索就是这样工作的
&lt;/h2&gt;&lt;p&gt;RAG 的检索过程，就是把上面的逻辑倒过来：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;入库时&lt;/strong&gt;：把每篇文档的每个片段都通过 Embedding 模型转成向量，存进向量数据库&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查询时&lt;/strong&gt;：把用户的问题也转成向量&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;匹配时&lt;/strong&gt;：计算问题向量和所有文档向量的余弦相似度，找出最接近的那几个片段&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;用户问题：去年第三季度我们赚了多少钱？
 │
 ▼ Embedding 模型
 │
[0.23, -0.56, 0.91, ...]
 │
 ▼ 跟库里所有文档向量算相似度
 │
文档中匹配到的内容：Q3营收同比增长15.3%（相似度 0.87）
 去年全年营收1.2亿元（相似度 0.45）
 公司成立于2018年（相似度 0.12）
 │
 ▼ 取 Top-K，拼入 Prompt
 │
LLM 基于这些内容生成回答
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;向量检索不只是「找到包含关键词的文档」，而是「找到意思最相近的内容」。这也解释了为什么在 RAG 中，&lt;strong&gt;分块和向量化的质量决定了检索的天花板&lt;/strong&gt;——如果文档切得不好、向量化得不准确，后面用多好的 LLM 也弥补不了。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;参考资料&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Mikolov et al., &lt;em&gt;Efficient Estimation of Word Representations in Vector Space&lt;/em&gt;, arXiv:1301.3781, 2013 — Word2Vec，向量化技术的奠基论文&lt;/li&gt;
&lt;li&gt;Reimers &amp;amp; Gurevych, &lt;em&gt;Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks&lt;/em&gt;, EMNLP 2019 — 现代 Embedding 模型的基础架构&lt;/li&gt;
&lt;li&gt;Datawhale, &lt;em&gt;hello-agents&lt;/em&gt;, 第八章 记忆与检索, &lt;a class="link" href="https://github.com/datawhalechina/hello-agents" target="_blank" rel="noopener"
 &gt;https://github.com/datawhalechina/hello-agents&lt;/a&gt; — 从认知科学到向量检索的实践教程&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>从聊天到干活：LLM 是怎么一步步变成 Agent 的</title><link>https://renxinblog.cn/post/from-chat-to-agent/</link><pubDate>Wed, 08 Jul 2026 00:00:00 +0000</pubDate><guid>https://renxinblog.cn/post/from-chat-to-agent/</guid><description>&lt;p&gt;你有没有想过一个问题——&lt;/p&gt;
&lt;p&gt;几年前我们用 LLM，还只是拿来聊天查资料，把它当做一个更高效的搜索引擎。你问它一句，它答一句，答完就结束了。怎么现在它们突然就能干活了？调接口、读文件、写代码、自己决定下一步做什么——这些东西跟文本生成有什么关系？&lt;/p&gt;
&lt;p&gt;答案是：从「聊天」到「干活」，中间叠加了好几层工程手段。每一层解决一个问题，又会暴露一个更深层的问题。我们这篇文章就沿着这条问题链往下分析。&lt;/p&gt;
&lt;h2 id="第一层llm-本身什么也干不了"&gt;第一层：LLM 本身什么也干不了
&lt;/h2&gt;&lt;p&gt;我们都知道 LLM 的本质是一个&lt;strong&gt;一次性的文本预测器&lt;/strong&gt;：你给它输入一段文本，它就输出一段文本，然后结束。总体而言，它有四个根本限制：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;限制&lt;/th&gt;
					&lt;th&gt;什么意思&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;无状态&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;每次调用是独立的。不记得上一次聊了什么，除非把历史都塞回去&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;无行动能力&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;只能输出文字。不能调 API、不能读文件、不能执行命令&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;知识截止&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;训练完之后就停在那个时间点了，不知道最新消息&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;单步推理&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;复杂任务需要多步，它只能走一步&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这些限制在简单问答场景下不明显——你问一句它答一句。但一旦任务变成「帮我排查一下这个 bug」，它需要：读文件 → 看报错 → 查文档 → 改代码 → 跑测试 → 看结果。这根本不是一次 LLM 调用能完成的。&lt;/p&gt;
&lt;p&gt;那么：&lt;strong&gt;怎么让 LLM 做一个完整的事？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;答案是：不给它加任何东西，它确实什么也干不了。但你可以围绕它叠加工程层——就像给一个只有大脑的躯体装上感官、手脚和神经系统。&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;%%{init: {'theme':'neutral'}}%%
graph TD
 subgraph Agent[Agent]
 direction TB
 subgraph 工程层[工程层]
 FC[Function Calling&lt;br/&gt;让模型能输出工具调用] --&gt; RA[ReAct 循环&lt;br/&gt;持续行动 + 反馈]
 RA --&gt; CM[上下文管理&lt;br/&gt;不让循环撑爆窗口]
 CM --&gt; MEM[记忆系统&lt;br/&gt;跨会话复用经验]
 MEM --&gt; HAR[Harness&lt;br/&gt;统一框架装好一切]
 end

 subgraph 核心[核心 · LLM]
 LLM[LLM&lt;br/&gt;文本预测器]
 end

 核心 --&gt;|叠加| 工程层
 end

 style Agent fill:#f8fafc,stroke:#64748b,color:#0f172a
 style 核心 fill:#dbeafe,stroke:#3b82f6,color:#1e3a5f
 style 工程层 fill:#d1fae5,stroke:#059669,color:#064e3b
 style LLM fill:#eff6ff,stroke:#60a5fa,color:#1e3a5f
 style FC fill:#ecfdf5,stroke:#34d399,color:#064e3b
 style RA fill:#ecfdf5,stroke:#34d399,color:#064e3b
 style CM fill:#ecfdf5,stroke:#34d399,color:#064e3b
 style MEM fill:#ecfdf5,stroke:#34d399,color:#064e3b
 style HAR fill:#ecfdf5,stroke:#34d399,color:#064e3b&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;蓝色&lt;/strong&gt;是 LLM 核心，&lt;strong&gt;绿色&lt;/strong&gt;是工程层，整个大框就是 &lt;strong&gt;Agent&lt;/strong&gt;。后面的章节就按这个顺序一层一层拆开来看。&lt;/p&gt;
&lt;h2 id="第二层function-calling让-llm说出要调什么工具"&gt;第二层：Function Calling——让 LLM「说」出要调什么工具
&lt;/h2&gt;&lt;p&gt;在 Function Calling 出现之前，想让 LLM 调用工具，做法很原始。&lt;/p&gt;
&lt;p&gt;你要在 prompt 里告诉 LLM：「你有哪些工具可以用、每个工具是干什么的、参数是什么」。然后 LLM 在回答的时候，把工具调用&lt;strong&gt;写在文本里&lt;/strong&gt;——比如输出一段 JSON，或者一段特定格式的文字。你在代码里写一堆正则表达式去匹配这段文字，解析出工具名和参数，再去执行。&lt;/p&gt;
&lt;p&gt;这个方法的问题很明显：LLM 输出文本是不受约束的。今天它输出 &lt;code&gt;{&amp;quot;tool&amp;quot;: &amp;quot;get_weather&amp;quot;, &amp;quot;args&amp;quot;: {&amp;quot;city&amp;quot;: &amp;quot;北京&amp;quot;}}&lt;/code&gt;，明天它可能输出 &lt;code&gt;让我来查一下北京的天气吧！[调用：get_weather(北京)]&lt;/code&gt;。格式稍微一变，你的正则就匹配不上了。而且每换一个模型，输出的格式习惯不一样，你的正则又得重写。&lt;/p&gt;
&lt;p&gt;2023 年中，OpenAI 推出了 Function Calling。这个改动看似不大，但影响深远。&lt;/p&gt;
&lt;p&gt;OpenAI 做的事其实就两件：第一，在模型微调阶段加入了大量「用户提问 → 模型输出 tool_calls」的训练样本，让模型学会在合适的时机输出结构化工具调用；第二，在 API 层面加了一个 &lt;code&gt;tools&lt;/code&gt; 参数，开发者直接把工具定义传进去，模型输出里就会自动带上 &lt;code&gt;tool_calls&lt;/code&gt; 字段。&lt;/p&gt;
&lt;p&gt;至于微调数据具体怎么构造、工具调用的能力边界在哪里——这是个可以单独写一篇文章的话题，这里先不展开。&lt;/p&gt;
&lt;p&gt;Function Calling 做的事很简单：&lt;strong&gt;模型在输出时，除了生成文本，还能输出一个结构化的对象&lt;/strong&gt;，里面明确指定了要调什么工具、传什么参数。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// 以前的写法：模型用文字描述
输出：&amp;#34;让我查一下天气。\n\n```json\n{\&amp;#34;tool\&amp;#34;: \&amp;#34;get_weather\&amp;#34;, \&amp;#34;args\&amp;#34;: {\&amp;#34;city\&amp;#34;: \&amp;#34;北京\&amp;#34;}}\n```&amp;#34;

// Function Calling：模型直接输出结构化调用
输出：{
 tool_calls: [{
 name: &amp;#34;get_weather&amp;#34;,
 args: { city: &amp;#34;北京&amp;#34; }
 }]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;区别在哪？第一个是「模型用文字描述了自己想做什么」，第二个是模型&lt;strong&gt;结构化地输出函数名称和参数列表&lt;/strong&gt;——API 层收到后直接按这个字段去路由和执行，不需要任何文本解析。&lt;/p&gt;
&lt;p&gt;说到这里你可能会好奇：LLM 不是拿对话和文本训练出来的吗，它怎么会「知道」要输出结构化的 tool_calls？&lt;/p&gt;
&lt;p&gt;答案在微调阶段。OpenAI 在推出 Function Calling 之前，在微调数据里加入了大量「用户提问 → 模型输出 tool_calls」的训练样本。模型在预训练时就已经见过代码和 JSON，微调只是教它&lt;strong&gt;在什么时机输出&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;%%{init: {'theme':'neutral'}}%%
graph LR
 subgraph 训练流程[LLM 训练流程]
 direction LR
 A[预训练&lt;br/&gt;海量文本] --&gt; B[监督微调 SFT&lt;br/&gt;指令数据]
 B --&gt; C[偏好对齐 RLHF&lt;br/&gt;人类偏好]
 C --&gt; D[工具调用微调&lt;br/&gt;tool_calls 样本]
 end

 style 训练流程 fill:#f8fafc,stroke:#64748b,color:#0f172a
 style A fill:#eff6ff,stroke:#60a5fa,color:#1e3a5f
 style B fill:#dbeafe,stroke:#3b82f6,color:#1e3a5f
 style C fill:#d1fae5,stroke:#10b981,color:#064e3b
 style D fill:#ecfdf5,stroke:#34d399,color:#064e3b&lt;/pre&gt;&lt;p&gt;最后一个阶段是关键。更直白一点：&lt;strong&gt;模型不是「学会调工具」，而是学会了「在需要调工具的场景下，输出一段看起来像 tool_calls 的 token 序列」。&lt;/strong&gt; 它以为自己只是在接着写文本，但对使用者来说，它在干活。&lt;/p&gt;
&lt;p&gt;清楚了 Function Calling 的原理之后，一个新的问题自然浮现了：&lt;strong&gt;光能说一次还不够——一个完整的任务往往需要多步操作：查了数据、分析、再查、再分析。能不能让模型不止调一次，而是一直干下去直到任务完成？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这就引出了下一层：&lt;strong&gt;ReAct 循环。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="第三层react-循环从一次调用到持续行动"&gt;第三层：ReAct 循环——从一次调用到持续行动
&lt;/h2&gt;&lt;p&gt;一次 Function Calling 只能完成一步。要完成一个复杂任务，需要多步。这时候就需要一个循环。&lt;/p&gt;
&lt;p&gt;ReAct（Reasoning + Acting）是 2022 年一篇论文提出的思路，核心骨架就是一个持续循环：&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;%%{init: {'theme':'neutral'}}%%
graph LR
 A[观察当前状态&lt;br/&gt;Observation] --&gt; B[推理下一步&lt;br/&gt;Thought]
 B --&gt; C{任务完成？}
 C --&gt;|否| D[执行工具调用&lt;br/&gt;Action]
 D --&gt; A
 C --&gt;|是| E[输出最终答案]

 style A fill:#eff6ff,stroke:#60a5fa,color:#1e3a5f
 style B fill:#dbeafe,stroke:#3b82f6,color:#1e3a5f
 style C fill:#fef9c3,stroke:#fbbf24,color:#92400e
 style D fill:#d1fae5,stroke:#10b981,color:#064e3b
 style E fill:#ecfdf5,stroke:#34d399,color:#064e3b&lt;/pre&gt;&lt;p&gt;每一轮循环做三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Thought&lt;/strong&gt;：LLM 推理当前步骤。比如「发现了这个 bug，需要先看一下这个文件的源代码」&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Action&lt;/strong&gt;：LLM 输出一个工具调用。比如 &lt;code&gt;read_file(&amp;quot;src/main.py&amp;quot;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Observation&lt;/strong&gt;：工具执行结果塞回下一轮 prompt，LLM 看到了继续推理&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;用代码实现也就几十行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;build_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;observation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parse_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;final_answer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;observation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;execute_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;observation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个循环就是所有现代 Agent 的基座。Claude Code、Codex、Manus、Cline——不管上面叠了多少层，最底层跑的都是这个循环。&lt;/p&gt;
&lt;p&gt;ReAct 循环很好用，任务越复杂跑的轮数就越多。但跑起来之后，人们很快发现一个问题：每轮都要把全部历史塞回 prompt，跑了 8 轮之后光历史记录可能就已经占了 30K token。而且很多早期的对话其实已经没用了——第一轮的推理早就被后续结果覆盖了。&lt;/p&gt;
&lt;p&gt;Token 越积越多，效率越来越低——能不能在保留必要上下文的同时，不让循环撑爆窗口？&lt;/p&gt;
&lt;p&gt;解决方法有几个方向：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;策略&lt;/th&gt;
					&lt;th&gt;做法&lt;/th&gt;
					&lt;th&gt;适用场景&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;滑动窗口&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;只保留最近 N 轮对话，丢弃最旧的&lt;/td&gt;
					&lt;td&gt;简单任务，早期对话不重要&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;摘要压缩&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;把旧对话用 LLM 压缩成一段摘要&lt;/td&gt;
					&lt;td&gt;需要长期记忆的任务&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;结构化上下文&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;把历史信息按类型组织，LLM 按需读取&lt;/td&gt;
					&lt;td&gt;复杂任务&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;渐进性披露&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;不一次给全部信息，先给概要再展开&lt;/td&gt;
					&lt;td&gt;长文档分析&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Mitchell Hashimoto（Harness Engineering 提出者）说过一句话：&lt;strong&gt;长上下文不是银弹。关键是上下文的质量和组织方式，不是长度。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对话管理解决了「一次任务内」的持久化问题。但关掉会话之后呢？下次遇到同样的问题，能不能复用上次的排查经验？&lt;/p&gt;
&lt;p&gt;这就引出了下一层：&lt;strong&gt;记忆系统。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="第五层记忆让经验跨会话复用"&gt;第五层：记忆——让经验跨会话复用
&lt;/h2&gt;&lt;p&gt;想象一个场景：上周花了半天排查了一个连接池超时问题，找到了根因。这周又遇到一个类似的报错，但你又得从头排查一遍——因为上次的排查结果在上次对话里，已经关了，没了。&lt;/p&gt;
&lt;p&gt;这就是记忆要解决的问题。&lt;/p&gt;
&lt;p&gt;记忆可以分为几个层次：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;层次&lt;/th&gt;
					&lt;th&gt;存储方式&lt;/th&gt;
					&lt;th&gt;生命周期&lt;/th&gt;
					&lt;th&gt;示例&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;上下文窗口&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;prompt 里的消息列表&lt;/td&gt;
					&lt;td&gt;当前会话&lt;/td&gt;
					&lt;td&gt;刚才的对话历史&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;文件存储&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Markdown / JSON 文件&lt;/td&gt;
					&lt;td&gt;持久化&lt;/td&gt;
					&lt;td&gt;MEMORY.md、USER.md&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;向量数据库&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Embedding 检索&lt;/td&gt;
					&lt;td&gt;持久化&lt;/td&gt;
					&lt;td&gt;RAG 知识库&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;结构化数据库&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;PostgreSQL 等&lt;/td&gt;
					&lt;td&gt;持久化&lt;/td&gt;
					&lt;td&gt;业务实体、修复记录&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;关键点：&lt;strong&gt;分层不是替代关系，是互补关系。&lt;/strong&gt; 文件层存结构化知识，向量层存语义可检索的内容，上下文层存当前会话的状态。实际工程中，通常会为每个 Agent 分配独立的数据库 schema，同时把技能和记忆以文件形式备份到代码仓库——既能在会话中快速检索，又能在仓库里版本回溯。&lt;/p&gt;
&lt;p&gt;有了循环、上下文管理、记忆，LLM 终于能完整地完成一个任务了。但这些东西是一块一块拼上去的——Function Calling、ReAct、上下文管理、记忆，各是一套独立的组件。到了这一步，人们开始想：能不能把这些零散的组件装进一个统一的框架里？&lt;/p&gt;
&lt;h2 id="第六层harness给-llm-装一个操作系统"&gt;第六层：Harness——给 LLM 装一个操作系统
&lt;/h2&gt;&lt;p&gt;Harness Engineering 的核心思想是：&lt;strong&gt;围绕 LLM 构建一个「操作系统」——包括工具系统、上下文管理、权限控制、反馈回路、可观测性。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;没有 Harness 的话，做 Agent 就是 LLM + 循环 + 几个工具，拼起来就完事了。Harness 的视角不一样——它认为 Agent 能不能用好，瓶颈往往不在模型本身，而在于&lt;strong&gt;怎么装&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;LangChain 的 Deep Agents 项目有一个实验很能说明问题：仅仅优化 Harness（不改模型），Terminal Bench 2.0 排名从第 30 位直接跃升至第 5 位。&lt;/p&gt;
&lt;p&gt;Harness 有四条铁律：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;工具签名即文档&lt;/strong&gt;——每个工具的名称、参数、返回值本身就是使用说明&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果必须可验证&lt;/strong&gt;——每一步的输出需要有明确的验证标准&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;错误不可沉默&lt;/strong&gt;——所有失败必须可见并结构化回传（Error-as-Data）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;渐进性披露&lt;/strong&gt;——不要让 Agent 同时面对所有信息，按需呈现&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;除此之外，权限控制、反馈回路、可观测性也都是 Harness 的重要组成部分——这些是值得单独展开的话题，这里先不深入。&lt;/p&gt;
&lt;p&gt;总结一下就是：&lt;strong&gt;Agent = LLM + Harness&lt;/strong&gt;。LLM 负责推理，Harness 负责把它装好，让它能干活。&lt;/p&gt;
&lt;h2 id="回头看整条演化路径"&gt;回头看整条演化路径
&lt;/h2&gt;&lt;p&gt;把这几层串起来，就是一条清晰的演化路径：&lt;/p&gt;
&lt;a href="https://renxinblog.cn/images/llm-to-agent-evolution.svg" target="_blank"&gt;
 &lt;figure&gt;&lt;img src="https://renxinblog.cn/images/llm-to-agent-evolution.svg"
 			alt="LLM到Agent的六层演化路径" width="85%"&gt;&lt;figcaption&gt;
 			&lt;p&gt;LLM → Agent 演化路径：六层叠加，每层解决上一层的新问题（点击图片查看大图）&lt;/p&gt;
 		&lt;/figcaption&gt;
 &lt;/figure&gt;

&lt;/a&gt;
&lt;p&gt;每一层都在解决上一层暴露出来的新问题。LLM 本身没有变「更聪明」，但加上这些工程组件之后，它从一个只会聊天的文本预测器，变成了一个能干活、能调接口、能写代码、能自己决定下一步的 Agent。&lt;/p&gt;
&lt;p&gt;从 Function Calling 到 ReAct 到 Harness，每一层都不复杂。就像搭积木——每一块都很简单，但搭对了顺序，就能搭出一个能干活的东西来。&lt;/p&gt;</description></item></channel></rss>