第 00 篇:从手写到框架,边界在哪
这是第二阶段的导读,不写代码,只交代三件事:
第一阶段留下了什么账、这一阶段要解决什么、动手之前你必须先准备好什么。
一、先结一下第一阶段的账
第一阶段我们没有用任何框架,全程手写:
| 01 | 用最原始的 HTTP 请求,把 OpenAI 兼容接口调通 |
| 02 | 把大模型的核心概念(token、上下文、温度、流式)逐条搞清楚 |
| 03 | 用 JDK 自带的 HttpClient 封装 Chat 请求 |
| 04 | 用 WebClient 把它重构成一个可复用的客户端 |
| 05 | 让它稳定地吐出结构化 JSON |
| 06 | 用 SSE 把流式输出接起来 |
| 07 | 手算余弦相似度,第一次接触 Embedding |
| 08 | 超时、重试、限流、降级,一层层往上包 |
| 09~12 | 摘要、分类、字段抽取、工单回复四个实践 |
| 13 | 把这些东西收拢成一个 AI 能力网关 |
做完之后有两件事变得很清楚。
第一件:这套东西大部分是重复劳动。
请求怎么拼、响应怎么解、重试怎么写、流怎么接——换个项目,还是要从头来一遍。
每个团队都在写同样的几百行代码,而且都写得不太一样。
第二件:光有链路还不够。
模型很聪明,但它不知道你们公司的报销标准是几天、不知道这份合同的历史版本改过什么。
它需要一条通道,能查到“我们自己的资料”。
第二阶段就是来处理这两件事的。
二、框架接走了哪些活
Spring AI 提供了下面这些抽象。注意每一条都对应第一阶段我们手写过的东西:
| ChatClient | 03 / 04 篇的 OpenAiClient | 请求构造、响应解析、多轮消息管理、流式 |
| EmbeddingModel | 07 篇手写的向量化 | 批量、重试、维度管理 |
| VectorStore | 07 篇手写的余弦相似度 | 写入、相似度检索、元数据过滤 |
| Advisor | 08 篇手写的装饰链 | 在调用前后插入逻辑(日志、改写、检索) |
| DocumentReader / Splitter | 没有,新的 | 文档解析与分块 |
| Tool Calling | 没有,新的 | 让模型调用你的函数 |
所以不要以为学框架就是学一套新东西。 你第一阶段理解的那些细节,
在这一阶段会变成“框架内部的某个扩展点”——你已经知道里面在发生什么了,
这是本系列坚持先手写、再上框架的原因。
三、框架接不走的活
这一节比上一节重要。框架再好,下面这些事永远是你自己的:
| 你的业务知识 | 向量库里放什么、怎么切、怎么标元数据,框架管不了 |
| 多租户与权限 | 谁在什么场景下能看哪些文档,这是业务规则 |
| 引用溯源 | 答案里哪句话来自哪篇文档,需要你自己维护链路 |
| 数据脱敏 | 入库前脱敏还是返回前脱敏,影响检索效果,要自己权衡 |
| 评测与兜底 | 答得对不对、答不出来怎么办,框架不提供答案 |
| 成本与限流 | 谁在用、用多少、超了怎么办 |
一句话概括:框架解决“怎么调”,不解决“调什么、给谁调、调完算不算对”。
第二阶段后面那些“企业级治理”的篇目,讲的全是框架接不走的部分——
这也是真正区分“能跑 demo”和“能上线”的地方。
四、这一阶段要建立的一个新观念
第一阶段我们反复强调一句话:模型输出是不可信输入。
第二阶段要再加一条:检索回来的内容,同样不可信。
检索的本质是“拿相似度换召回”。它会犯两种错:
- 召回错了:把隔壁租户的文档、或者跟问题无关但字面很像的段落召回来
- 漏了:真正该用的那段话,因为分块切坏了或者相似度不够,没被召回
第一种错误会直接导致数据泄露,第二种会导致模型“凭空编答案”。
把这两条合起来——模型的输出不可信 + 检索的结果不可信——
才是能上线的系统该有的警惕。
五、本阶段全程使用真实云服务
这一点请特别注意:这一阶段的每一个示例,调用的都是真实云端模型,没有模拟服务。
你看到的每一次 token 消耗、每一个限流错误、每一段真实耗时,都是真的。
因为下面这些东西,只有在真实环境里才会遇到:
- 推理模型的思维链把 max_tokens 吃光,返回空白正文
- 共享网关的限流
- “OpenAI 兼容协议”其实并不覆盖所有能力(重排、图像生成就覆盖不到)
- 不同厂商的 base-url 该不该带 /v1,规则刚好相反
本阶段用到两家平台:
| 商汤 SenseNova | 文本对话、视觉理解、图像生成 |
| 阿里云百炼 | 向量化(embedding,1024 维)、重排(rerank) |
5.1 为什么是“两家”而不是“一家”
因为没有任何一家能同时满足本阶段的全部需求:
SenseNova 是这一阶段对话模型的主力(glm-5.2,1M 上下文),
它的模型列表里有文本、图像输入、图像输出三类,但一个 embedding 模型都没有。
而 RAG 没有向量化就跑不起来,所以向量化和重排必须另找百炼。
这件事的意义不只是“凑齐功能”——它顺带证明了一个工程上的判断:
不同厂商的模型可以拼在同一个工程里,
而且这件事靠改配置就能做到,不用动代码。
这条判断后面会一直被用到,因为 RAG 的每个环节(向量化、重排、生成)
用的往往不是同一家的模型,能不能拼装决定了架构能不能落地。
5.2 那 Mock 呢
本系列的仓库里确实保留了一个本地模拟服务 mock-llm-server,
但它在这一阶段只是“兜底”,不是主线。
它存在的唯一理由是:你暂时没有申请到 API Key,或者需要完全断网演示。
这种情况下可以把 profile 切成 local,用本地的确定性响应把流程跑通,
等拿到 Key 再切回 cloud。
它的向量维度已对齐到 1024,所以能在不重建索引的情况下与真实服务来回切换。
不过有一点要提醒:切换过去之后,你看到的就不再是真实模型的输出、真实的 token 消耗、
真实的报错了。所有“实际问题”都在真实环境里,兜底模式帮不了你解决它们。
六、动手之前:环境准备清单
在动手写第一个 Spring AI 示例之前,请确保下面这张表全绿。
每一项的做法都在 setup/ 目录的四篇文档里。
| JDK 21 | java -version | 21.0.x |
| JDK 21 被 Maven 认到 | mvn -v 里的 Java version | 21.0.x(不是 1.8) |
| Maven | mvn -v | 3.9.x |
| Git | git –version | 2.5x.x |
| Elasticsearch | curl http://127.0.0.1:9200 | 返回 JSON,且不需要密码 |
| Kibana | 浏览器开 http://127.0.0.1:5601 | 中文界面 |
| SenseNova Key | echo %SENSENOVA_API_KEY% | sk- 开头 |
| 百炼 Key | echo %DASHSCOPE_API_KEY% | sk- 开头 |
| 文本对话通 | 环境准备文档里“验证文本对话”那条 curl | content 是正常中文回答 |
| 向量化通 | 环境准备文档里“验证向量化”那条 curl | 得到一个 1024 长度的数组 |
两个最容易卡住的地方,提前提醒:
java -version 是 21,但 mvn -v 里是 1.8。
因为你机器上还装着 JDK 8。Maven 认的是 JAVA_HOME 而不是 java 命令,
把 JAVA_HOME 指到 21 就行,不用卸载 8(作者机器上两个是共存的)。
ES 第一次启动前一定要先关掉 xpack 安全。
顺序反了的话,ES 会先生成证书、给你一个随机密码,后面想改免密就得删目录重来。
七、这一阶段的地图
本阶段分六个部分,顺序是按“能不能独立跑通”排的:
| 一、框架与上手 | Spring AI 是什么、和 LangChain4j 的差别、第一个 ChatClient | 从第一阶段的代码直接过渡 |
| 二、RAG 全流程 | 加载、分块、向量化、入库、检索、重排、拼上下文、生成 | 主线,每一步都跑真实数据 |
| 三、企业级治理 | 多租户隔离、权限过滤、引用溯源、数据脱敏 | 框架接不走的部分 |
| 四、四大实践 | 知识库问答、客服助手、合同审查、代码搜索 | 把前面的东西用起来 |
| 五、多模态 | 视觉理解、图像生成 | RAG 之外的扩展能力 |
| 六、综合复盘 | 能力网关、向量库选型判定、两阶段对照 | 收口 |
阅读建议
- 第二部分是本阶段的重点,占的篇幅最多。 如果时间有限,
至少把“向量化 → 入库 → 检索 → 重排”这四步走完,这是 RAG 的骨架。 - 第三部分不要跳过。 很多人做 RAG 只做到“能召回”就停了,
但多租户和权限是能不能上线的前提——出一次跨租户召回就是事故。 - 第五部分(多模态)可以最后看,它和 RAG 主线是并行的,不依赖前面的进度。
八、最后一句
第一阶段的 08 篇讲的是“超时重试限流降级”——
那套韧性装饰链是自己一层层包出来的。
到了框架时代,有的变成了配置项,有的变成了 Advisor。
框架的价值不是让你少写代码,而是让你少写“和你业务无关的代码”。
剩下那些——你的知识、你的权限、你的成本控制——一样都不会少。
下面开始。





