智能体面经分享
目录
- RAG(检索增强生成)部分
- Agent架构与设计
- 工具调用
- Agent评测与迭代
- AI工程化与研发流程
- Prompt工程 vs Context工程 vs Harness工程
- Transformer与深度学习基础
- 强化学习相关知识
- 大模型微调与训练
- 补充内容
- OpenClaw
RAG(检索增强生成)部分
RAG的具体流程是怎样的?底层用什么进行检索?检索的策略优化细节
数据准备
- 解析提取:清洗多格式文档(PDF、Word、HTML等),提取纯文本。
- 文本分块 (Chunking):将长文本切分为适合模型上下文长度的小块。
- 向量化 (Embedding):使用 Embedding 模型将文本块转化为高维向量。
- 要清楚自己用到的embedding模型是啥,这个模型优点是啥。
- 建库 (Indexing):将向量和元数据(Metadata)存入底层数据库。
- 用的向量数据库是啥,Pgvector还是Milvus这种专业向量数据库
- 建议说是用的Pgvector,要不然面试官可能会问Milvus的一些细节
- 可以准备一下常见的向量数据库之间的区别
检索阶段 (Retrieval)
- 接收用户的Query
- 使用同一个Embedding模型将Query向量化
- 在数据库中计算Query向量与文档块向量的相似度(如余弦相似度),召回Top-K个最相关的Chunk
生成阶段 (Generation)
- 将召回的Top-K文本块与用户的原始Query拼接进预设的Prompt模板中
- 将组装好的Prompt提交给LLM,LLM基于提供的上下文生成最终回答
检索细节
问题:100万条数据怎么检索的,肯定不能一个个计算余弦相似度。
ANN近似最近邻算法
对于大规模向量检索,暴力计算所有向量与查询向量的相似度(如余弦相似度)时间复杂度是O(N),当数据量达到百万级时计算开销太大。因此需要使用近似最近邻(Approximate Nearest Neighbor, ANN)算法,在检索精度和计算效率之间做平衡。
ANN算法的核心思想:
- 不要求找到完全准确的最近邻
- 而是找到"足够接近"的邻居
- 大大降低计算量
HNSW(Hierarchical Navigable Small World)
HNSW是ANN算法的一种高效实现,全称是"分层导航小世界"。它的灵感来源于现实生活中"六度分隔理论"——只要认识6个人,就能认识世界上任何人。
HNSW把向量索引构建成分层图结构:
- 上层图稀疏
- 下层图密集
具体来说,假设有100万条向量数据,HNSW可能构建三层结构:
- 第0层(最底层):100万个节点,节点之间连接密集,包含所有数据
- 第1层:10万个节点,从下层按一定概率采样得到
- 第2层(最高层):1万个节点,再次采样
检索过程详解
- 检索从最高层(第2层)开始,在1万个节点中贪婪搜索,找到距离查询向量最近的节点A
- 然后进入下一层(第1层),在节点A的邻居节点中继续搜索,找到更近的节点B
- 接着进入最底层(第0层),在节点B的邻居节点中继续搜索,直到找到距离查询向量最近的节点
什么时候检索结束?
检索结束的条件有两个:
- 达到预设的Top-K数量:比如要召回Top-10个最相关的文档,当找到10个符合条件的节点后,检索就可以结束
- 达到最大搜索深度:如果还没找到足够的Top-K节点,就会遍历到最底层(第0层),确保不会漏掉任何可能的结果
为什么比暴力匹配更快?
| 方式 | 时间复杂度 | 计算量(N=100万) | 特点 |
|---|---|---|---|
| 暴力匹配 | O(N) | 100万次向量点积运算 | 耗时可能达到几百毫秒甚至几秒 |
| HNSW | O(logN) | 几百到几千次 | 速度快几十倍甚至上百倍,精度损失很小(1%-5%) |
面试话术建议:
"当面试官问百万级数据怎么检索时,我会这样回答:首先暴力计算不现实,必须用ANN近似算法。我项目中用的是HNSW算法,它通过构建分层图结构来加速检索。具体来说,底层包含所有数据节点,上层按概率采样形成金字塔结构。检索时从顶层开始贪婪搜索,逐层向下精化,类似'先在大范围找区域,再在小范围找目标'。这样把检索复杂度从O(N)降到O(logN),百万级数据也能毫秒级返回。如果面试官继续问,我可以补充HNSW的参数调优,比如efConstruction控制构建精度,ef控制检索精度,这些都是tradeoff。"
检索策略优化细节
检索前 - 改造Query
查询重写:用LLM将用户的口语化、指代不清的提问重写为规范的检索词。
检索中 - 混合检索
结合向量检索(懂语义)和BM25(抓关键词,如专有名词、型号),通过Alpha参数调节权重,再进行结果合并。
检索后 (Post-retrieval) - 重排与过滤
Reranking (重排序):
- 召回的Top-K(比如20个)可能顺序不够准
- 引入专门的Reranker模型(如BGE-Reranker-large)
- 计算Query和这20个Chunk的交叉注意力,给出更精确的排序
- 最后只保留Top-3或Top-5喂给LLM
Chunk(文本切块)的方案中,除了简单按大小切分,还有哪些策略?
按规则/结构切分
- Markdown/HTML分割:利用文档原有的层级结构(如#,##,
<li>)进行切分,保证同一标题下的内容或同一个表格的完整性。大模型最喜欢的就是markdown文本 - 自然语言边界:基于段落(
\n\n)、句子(句号、问号)进行递归切分,优先保证句意完整 - 重叠切换:每个块之间有10%的重叠
语义切分
使用一些小模型比如GPT4-mini动态计算相邻两个句子的Embedding向量相似度,如果相似度突然出现"断崖式下跌",说明话题变了,就在这里切一刀。这种方法能产生长短不一但语义高度内聚的Chunk。
父子块策略
- 建库时:切得很碎(比如一句话一个Chunk),用于精准匹配Query,提高检索准确率
- 喂给模型时:一旦检索到这句话,不只把这句话给LLM,而是把包含这句话的整个段落(父块)甚至前后文提取出来给LLM。这样既保证了检索的精度,又提供了充足的上下文
摘要/总结切分
对大段落或整篇文档先让LLM生成一个浓缩摘要。将"摘要"进行向量化建库。检索时匹配摘要,匹配中后再把原文档调出来给模型。非常适合长文档阅读理解。
如果模型回答的内容跟检索到的文档不相关,通常是哪个环节出了问题?
1. Prompt工程环节
问题:约束力不够
Prompt中没有强硬地限制LLM必须且只能基于参考资料回答。
解决方法:
在Prompt中加入强烈指令,例如:
"你必须严格基于以下提供的文档回答问题。如果文档中没有相关信息,请直接回答'根据提供的资料无法回答',绝不能动用你自己的先验知识捏造答案。"
问题:丢失中间信息 (Lost in the Middle)
给LLM喂了太多检索到的文本(比如10个长文档),导致模型注意力机制忽略了中间的关键信息。
解决方法:
- 减少Top-K数量
- 使用Reranker将最相关的文档放在Prompt的最开头或最末尾
2. 检索数据质量环节
问题:召回了"假相关"的噪音
虽然计算出向量距离很近,但实际上包含的答案不完整,或者有大量干扰信息。
解决方法:
- 优化Chunk策略,增加Chunk的重叠部分
- 使用Metadata注入(在每个Chunk前自动加上标题或实体信息,上传文档阶段打标签)
Agent架构与设计
Agent和用户之间的多轮对话是怎么串联起来的?用户的两次独立HTTP请求,系统怎么识别它们属于同一个对话并拼接历史上下文?
多轮对话的核心是通过会话标识(conversation_id)+ 上下文存储机制来实现的。
会话标识机制:
- 系统会在用户第一次请求时生成一个唯一的会话id
- 后续每次请求都会携带这个ID
- 从而将多次独立的HTTP请求串联为同一个对话
存储方式:
- Redis:常用于在线会话
- MySQL:用来持久化会话记录
- 向量数据库(如pgvector):存储长期记忆、摘要这种
上下文拼接策略:
- 每次请求拼接历史对话 + 当前问题
- 实际上也会有提示词缓存(prompt cache)
- 一般来说,基本不变的内容,比如长期记忆这种适合作为提示词缓存,利用KV cache优化
Token控制:
- 使用滑动窗口,只保留最近N轮
- 超过设置的token数就进行历史摘要压缩
- 长短期记忆分层
大模型"重新规划"任务的具体含义是什么?底层状态机的状态是如何流转的?
Replanning: 执行失败 → 重新规划
常见的执行策略
| 策略 | 流程 |
|---|---|
| 直接执行 | 输入 → LLM → 输出 |
| ReAct(推理+行动) | Thought → Action → Observation → ... |
| Plan-and-Execute | 先生成完整计划 → 再逐步执行 |
| Replanning | 执行失败 → 重新规划 |
| Multi-Agent(多智能体) | Planner Agent任务分析规划 + Executor Agent执行 + Judge Agent打分 → 不达标则重新执行 |
Multi-Agent详解:
- Planner Agent:进行任务分析规划
- Executor Agent:去进行执行
- Judge Agent:用更高级的模型对执行结果进行打分
- 分数不达标,带着系统错误上下文回到分析阶段重新执行
- 从而实现一个自我纠错的闭环执行逻辑
系统运行状态下动态修改大模型配置时,具体支持修改哪些配置参数?
模型本身的参数
- temperature:低的话输出稳定性高
- top_p
- max_tokens等
模型本身的配置
- url
- api_key
Prompt配置
- system prompt
- 业务提示词模板
工具能力(Agent能力)
- Function Calling函数调用
- MCP工具列表
- Skill配置
长期记忆短期记忆
在Agent的记忆设计里:
短期记忆:
- 主要维护当前对话连续性
- 一般通过保留最近N轮消息或滑动窗口实现
长期记忆:
- 主要保存跨轮次仍有价值的信息
- 通常通过摘要压缩、向量检索或结构化存储实现
- 向量检索就是把之前的对话记录存储到向量数据库中,做RAG
核心权衡:
由于上下文窗口有限,记忆管理一定伴随信息压缩,所以不能追求完全无损,而是要在token成本和信息保真之间做权衡。
工程方案:
- 最近消息原样保留
- 历史消息异步摘要
- 原始记录持久化
这样可以尽量减少信息丢失对回答质量的影响。
工具调用
Function Calling真的是由大模型自己直接在调用的吗?
不是。Function Calling不是大模型"自己直接调用函数",而是大模型在输出中生成结构化调用意图。
流程示例:
比如问北京天气咋样,大模型只是输出结构化结果:
{
"tool_call": {
"name": "get_weather",
"arguments": {
"city": "北京"
}
}
}
由本地宿主程序负责真正执行,再把执行结果反馈给模型:
{
"temperature": "22C",
"condition": "sunny"
}
模型最终组织成人类可读的话输出:"北京今天晴,22摄氏度。"
MCP Server到底部署在哪里,特别是针对本地命令行工具,它是怎么启动的?
MCP协议的核心价值:解耦工具实现与Agent逻辑,避免大量胶水代码。
传统方式的问题:
- 每接入一个工具,Agent代码里都要写适配逻辑
- 比如要调用天气API、操作数据库、读写文件
- 每个都要单独写调用代码、处理返回格式、做异常处理
- 时间久了,Agent代码就变成了一个巨大的"胶水层",难以维护
MCP协议的解决方案:
- 把工具使用服务做成独立进程
- Agent只需要对接MCP协议这个统一的"传话筒"
- 工具的增删改都不影响Agent核心代码,实现了真正的插件化
MCP Server的三种部署方式
| 方式 | 适用场景 | 特点 |
|---|---|---|
| Stdio | 本地工具和命令行程序 | Agent启动时拉起MCP Server子进程,通过标准输入输出通信。简单、安全,不需要网络通信 |
| SSE | 远程服务且需要服务端推送 | MCP Server以网络服务形式运行,客户端通过HTTP长连接接入。适合监控告警、实时数据流等 |
| HTTP | 远程服务且无状态调用 | 客户端通过HTTP短连接调用,每次请求-响应后连接断开。适合简单的查询类工具 |
Stdio方式详解:
比如在Claude Code的配置文件中,可以配置一个filesystem的MCP Server,启动命令是"npx -y @modelcontextprotocol/server-filesystem"。
当Claude Code启动时:
- 执行这个命令拉起一个子进程
- 通过stdin把大模型的工具调用请求发给这个子进程
- 子进程执行完毕后通过stdout返回结果
原生的Function Calling、代码里硬编码调API、MCP协议以及Skill,这几个概念之间有什么关系?在具体场景下应该怎么做选择?
这四个概念是不同层级的抽象,从底层到高层依次是:
| 层级 | 概念 | 特点 | 适用场景 |
|---|---|---|---|
| 最底层 | 硬编码API调用 | 最灵活,但耦合度最高,每接入一个工具都要写适配代码 | 一次性需求或快速验证 |
| 第二层 | Function Calling | 调用时机由模型决定,更智能。但工具描述和调用逻辑还是要写在Agent代码里 | 需要模型决策调用时机 |
| 第三层 | MCP协议 | 标准化的工具调用协议,解决跨平台复用问题。同一个MCP Server可以被多个Agent复用 | 多Agent共享工具、跨平台复用 |
| 最高层 | Skill | 把Prompt模板、工具定义、执行流程、约束规则打包成一个完整的"能力包" | 标准化任务、团队协作 |
Skill示例:
比如一个"数据分析Skill"可能包含:
- 数据读取工具
- 清洗工具
- 可视化工具
- 以及一套完整的执行流程
用户只需要说"帮我分析这份数据",Skill就会自动完成整个流程。
高并发情况下,结合SSE流式交互,由于大模型响应RT很长,如何防止后端线程被大量阻塞拖死?
问题分析:
- 线程资源是有限的
- 但大模型响应时间是固定的
- 用同步模型会导致线程长时间被占用,无法服务新请求
解决方案的核心思路:
让Tomcat线程只负责接收请求,快速释放,把耗时的调用大模型操作交给独立的异步线程池或响应式框架处理,结果通过SSE推送给客户端。
方案一:异步Servlet
Spring Boot支持异步Servlet,核心是使用AsyncContext。
流程:
- 当请求进来时,Tomcat线程调用
request.startAsync()开启异步上下文 - 然后把任务提交到独立的线程池
- Tomcat线程立即释放去处理其他请求
- 异步线程池负责调用大模型API,通过SSE把结果推送给客户端
- 最后调用
asyncContext.complete()结束请求
优缺点:兼容Servlet规范,但还需要自己管理异步线程池。
方案二:Spring WebFlux响应式
WebFlux基于Reactor和Netty,采用事件驱动模型,完全非阻塞。
优点:
- Netty用少量的事件循环线程就能支撑大量并发连接
- 不会因为等待大模型响应而阻塞
- 代码简洁,只需要返回一个Flux流
- 框架自动处理背压和流控
缺点:学习曲线陡峭,需要理解响应式编程范式。
方案三:消息队列解耦
把请求发送到消息队列,由独立的消费者服务调用大模型API,结果通过WebSocket或轮询返回给客户端。
优缺点:彻底解耦了请求接收和模型调用,但架构复杂度较高,适合超大规模场景。
实际项目选择:
在实际项目中,我使用Spring WebFlux实现完全非阻塞的流式交互。Netty的事件驱动模型可以用少量线程支撑大量并发连接,即使大模型响应时间很长,也不会阻塞线程。同时,WebFlux的背压机制可以控制数据流速度,避免客户端处理不过来。
Agent评测与迭代(言之有理即可,能说出一部分就行)
在自己的Agent智能体工作平台项目中,用什么思路去对Agent效果做评测的?
核心概括为:"分层评测、多维指标、自动化为主"。
分层评测
| 层级 | 评测内容 |
|---|---|
| 基础能力层 | 评测原子能力(如工具调用准确率、参数提取规范度) |
| 业务流程层 | 评测多步编排任务(关注中间链路流转是否正确,最终目标是否达成) |
| 端到端效果层 | 评测最终用户体验(如响应时间、回答质量) |
多维指标体系
- 正确性:任务完成率、工具命中率
- 效率:平均耗时(RT)、Token消耗量
- 稳定性:错误率、超时率、重试率
- 用户体验:信息完整度、格式规范度
自动化为主
- 构建覆盖典型场景与边界情况的测试用例库
- 结合自动化评测脚本接入CI/CD流水线
- 实现代码变更后的低成本快速回归,替代人工验证
Agent升级迭代(新老版本替换)时,怎么保证新版本不会比旧版本更差?评测的具体方案怎么做?
建立可量化的评测基准 (Baseline)
- 在旧版本发布时,跑通测试用例库形成基线数据
- 引入LLM-as-a-Judge机制,从正确性、完整性等维度对新老版本的输出进行自动化打分比对(可辅以统计学t检验)
- 确保新版核心指标不退化
多阶段验证发布
| 阶段 | 内容 |
|---|---|
| 离线自动化卡点 | 跑赢基线数据是进入发布流程的硬性门槛 |
| A/B测试 | 双版本同时服务小部分流量,对比真实用户行为(采纳率、追问频次、差评率) |
| 灰度发布 | 逐步扩大放量比例(如5% → 20% → 全量),期间密切监控大盘错误率 |
应急回滚机制
- 始终保留旧版本镜像与配置
- 一旦灰度期间监控告警触发阈值,支持分钟级切流回滚
- 并捞取Bad Case作为下一轮优化的输入
使用更强的模型做裁判(LLM-as-a-Judge)、使用规则校验、以及人工评测,这三者各存在什么优缺点?怎么进行组合选择?
LLM-as-a-Judge
用更强的模型作为裁判,对被评测模型的输出打分。
| 优点 | 缺点 |
|---|---|
| 成本低,不需要人工参与,可以大规模自动化评测 | 可能存在偏见,裁判模型可能对某些类型的回答有偏好 |
| 速度快 | 需要选择足够强的模型做裁判,否则裁判本身的能力会成为瓶颈 |
| 覆盖广,可以评测主观性较强的维度(如流畅度、逻辑性、有帮助程度) | 难以评测事实准确性,裁判模型也可能不知道正确答案 |
规则校验
用预定义的规则检查输出是否符合预期。比如检查输出是否包含必要字段、格式是否正确、是否包含敏感词、工具调用参数是否合法。
| 优点 | 缺点 |
|---|---|
| 确定性强,规则写死了就不会有歧义 | 覆盖有限,只能检查规则覆盖到的情况 |
| 可解释性好,不通过的原因一目了然 | 维护成本高,新问题出现就要加新规则 |
| 执行速度快,几乎不需要额外成本 | 容易绕过,模型可能学会"钻空子" |
人工评测
由人类专家对模型输出进行评估。
| 优点 | 缺点 |
|---|---|
| 最准确,人类可以理解语义、判断事实、评估有帮助程度 | 成本最高,需要投入人力 |
| 可以发现新问题,人工评测过程中可能发现之前没预料到的case | 速度最慢,难以支撑快速迭代 |
| 可以作为其他评测方式的校准基准 | 可能存在主观性,不同评测人员标准可能不一致 |
常见组合做法
- 首先规则校验拦截一部分基础问题
- 然后LLM-as-a-Judge进一步评判
- 输出内容给用户时,可以设置点赞和点踩标识
- 将用户点踩不满意的内容进行进一步人工处理
AI工程化与研发流程
从需求到最终交付的整个研发流程中,你在每个环节具体是怎么使用AI的?
在我的研发流程中,AI贯穿了从需求分析到最终交付的每一个环节,但每个环节的使用方式和侧重点不同。
需求分析阶段
我会和AI进行对话式探讨:
- 拿到一个需求后,我先把需求描述给AI
- 让AI帮我分析需求的完整性、边界情况、潜在风险
- AI会提出一些我没想到的问题,比如"如果用户中途取消订单,库存怎么处理"、"并发情况下会不会超卖"
- 这种对话式的需求澄清帮我发现了很多隐藏的问题
- 然后我会让AI把复杂需求拆分成一个个小的功能点,每个功能点都有明确的输入输出和验收标准
设计阶段
我会让AI帮我设计技术方案:
- 比如设计一个秒杀系统,我会让AI给出架构选型建议、数据流设计、关键接口定义
- AI会基于它的知识库给出业界最佳实践,比如"用Redis预扣库存、用MQ异步下单、用本地消息表保证最终一致性"
- 我会和AI讨论方案的优缺点,最终确定技术选型
- 这个阶段AI的作用是提供思路和参考,但最终的决策权在我手里
编码阶段
我会让AI生成具体功能的代码:
- 对于每个拆分好的功能点,我会给AI明确的上下文:当前项目的架构风格、相关的已有代码、期望的接口定义
- AI生成代码后,我不会直接使用,而是先审查一遍
- 检查是否符合项目规范、是否有明显的Bug、是否处理了边界情况
- 对于复杂的逻辑,我会让AI解释代码的实现思路,确保我完全理解
测试阶段
我会让AI生成测试用例和测试代码:
- 我会告诉AI要测试的功能点、边界条件、异常场景
- AI会生成对应的单元测试代码
- 对于关键业务逻辑,我会要求AI生成覆盖各种分支的测试用例,包括正常流程、异常流程、边界值
代码审查阶段
我会让AI检查代码质量:
- 我会把写好的代码发给AI,让它检查是否有安全漏洞、性能问题、代码异味
- AI会指出一些潜在问题,比如"这里没有做参数校验,可能导致NPE"、"这个循环里每次都查数据库,可以批量查询优化"
文档编写阶段
我会让AI生成技术文档:
- 包括接口文档、设计文档、部署文档
- 我会提供关键信息,AI会组织成结构化的文档
- 这比我自己从头写要快很多,而且AI生成的文档结构清晰、表述规范
总结:
整个流程下来,AI在每个环节都扮演了"助手"的角色,但核心的决策和判断还是由我来做。AI的作用是提高效率、提供思路、发现盲点,而不是替代我思考。
在研发流程中,有没有设定一些规则、Skill或策略来对AI的生成进行约束?
有的。我在研发流程中使用了一套Skills体系来约束AI的生成行为,核心是让AI在特定场景下遵循特定的规范和流程,而不是自由发挥。
Superpowers的Skills体系
Superpowers是一个AI开发技能框架,它的核心逻辑是把AI的开发行为"纪律化"。
Superpowers把AI解决问题分为四个阶段:
| 阶段 | 内容 |
|---|---|
| 构思与规划阶段 | 输出设计文档,将工作拆成极小任务 |
| 开发与执行阶段 | Git工作区隔离,TDD测试驱动开发 |
| 调试阶段 | 四阶段根因分析法,系统性定位问题 |
| 审查与收尾阶段 | 代码审查,运行所有测试,清理工作区 |
TDD约束示例:
Superpowers强制要求AI遵循TDD(测试驱动开发)原则:
- 先写测试
- 看着测试失败
- 写最少代码让测试通过
- 重构
如果AI想跳过测试直接写业务代码,这个Skill会阻止它。
ui-ux-pro-max Skill
这个Skill的作用是让AI在前端开发时遵循UI/UX设计规范。
它会约束AI生成的:
- 界面布局
- 配色方案
- 交互逻辑
确保前端代码不仅功能正确,而且用户体验良好。
比如我让AI设计一个表单页面,ui-ux-pro-max会要求AI考虑:
- 表单验证提示的位置
- 按钮的点击状态
- 移动端的适配等细节
而不是只生成一个"能用但难看"的界面。
架构图绘制Skill
这个Skill让AI能够生成标准化的架构图,包括:
- 分层架构
- 组件关系
- 数据流向
我会让AI在项目初期生成架构图,作为团队沟通的基准文档。
Skill的核心价值:
把"经验性的最佳实践"固化成"可执行的规则":
- 比如TDD大家都知道好,但实际开发时很容易偷懒跳过
- 有了Skill的约束,AI会强制遵循这些实践
- 相当于有一个"不会偷懒的助手"
- 同时,Skill也让AI的输出更加可预测、可控制,减少了"AI自由发挥导致翻车"的情况
让AI先生成代码,再让AI自己去检查这段代码,这种做法在实际应用中有意义吗?
这种做法的核心价值在于"视角切换"。
| 角色 | 关注点 |
|---|---|
| AI生成代码时(实现者) | "怎么把功能做出来" |
| AI检查代码时(审查者) | "这段代码有什么问题" |
角色的切换会带来视角的变化,AI在审查模式下会关注一些生成时忽略的问题:
- 边界情况处理
- 异常场景覆盖
- 代码规范遵守
在Java语言中结合AI去实践TDD(测试驱动开发)是否好落地?
TDD是什么?
TDD(测试驱动开发)是一种开发方法论,核心流程是"红-绿-重构"三步循环:
- 先写一个会失败的测试用例(红灯)
- 然后写最少量的代码让测试通过(绿灯)
- 最后重构代码优化结构(重构)
TDD的好处:
- 测试先行保证了代码的可测试性
- 小步迭代降低了出错概率
- 重构步骤保证了代码质量
在Java中结合AI实践TDD是比较好落地的
原因一:Java生态的测试基础设施非常成熟
| 框架 | 作用 |
|---|---|
| JUnit | 标准测试框架 |
| Mockito | Mock框架 |
| Spring Boot Test | 集成测试框架 |
AI生成测试代码时,只需要遵循这些框架的API规范,就能生成可运行的测试用例。
原因二:Java的静态类型系统让AI更容易生成正确的代码
- Java的类型系统在编译期就能发现很多错误
- AI生成的测试代码如果有类型问题,编译器会直接报错
- 开发者可以立即修正
- 这降低了AI生成错误代码的风险
原因三:AI可以很好地遵循TDD的流程约束
在我的实践中:
- 我会让AI先分析需求,生成测试用例列表
- 然后逐个实现
- AI会按照"先写测试、再写实现"的顺序来,不会偷懒跳过测试
- 同时,AI生成的测试用例往往比我想到的更全面,覆盖了各种边界情况和异常场景
日常用AI辅助写代码时,如何去规避AI生成代码带来的隐藏Bug和难以测试排查的问题?
第一:永远不要盲目信任AI生成的代码
把AI生成的代码当作"一个初级同事写的代码"来审查:
- 逐行检查逻辑是否正确
- 边界情况是否处理
- 异常是否捕获
- 特别是涉及业务逻辑的部分,我会对照需求文档验证代码实现是否正确
第二:要求AI生成测试用例,用测试来验证代码
我会让AI在生成代码的同时生成对应的测试用例:
- 正常流程
- 异常流程
- 边界值
测试用例是代码正确性的"试金石",如果测试覆盖充分且全部通过,代码的可信度就大大提高。
第三:让AI解释关键逻辑的实现思路
对于复杂的代码片段,我会让AI解释:
- "为什么这样实现"
- "有没有考虑其他方案"
- "这个边界条件为什么这样处理"
通过解释,我可以判断AI是否真的理解了问题,还是在"蒙"一个答案。如果AI解释不清楚或者逻辑有漏洞,说明代码可能有问题。
第四:分步生成,逐步验证
对于复杂功能,我不会让AI一次性生成全部代码,而是拆分成多个小步骤:
- 每一步生成后立即验证
- 比如先让AI生成数据模型,验证正确后再生成数据访问层,再生成业务逻辑层
- 这样每一步的问题都能在小范围内定位,不会累积到最后才发现
第五:建立代码审查清单
我总结了一份AI代码审查清单,每次审查AI生成的代码时逐项检查:
| 检查项 | 说明 |
|---|---|
| 空指针处理 | 是否有NPE风险 |
| 参数校验 | 入参是否做了校验 |
| 异常捕获 | 异常是否被正确处理 |
| 事务边界 | 事务是否正确划分 |
| 并发安全 | 是否有线程安全问题 |
| SQL注入 | 是否有SQL注入风险 |
| 敏感信息泄露 | 是否泄露敏感信息 |
这个清单帮我系统性地发现问题,避免遗漏。
Prompt工程 vs Context工程 vs Harness工程
Prompt工程、Context工程和Harness工程,这三个的侧重点和区别是什么?
建议参考这个视频:www.bilibili.com/video/BV1Zk…
这三个工程代表了AI应用开发的三个不同层次,从描述任务到补充背景知识,再到构建执行闭环,是层层递进的关系。
Prompt工程
核心:向大模型描述"怎么完成这个任务"
本质:通过自然语言指令,把任务目标、执行方式、输出格式、约束条件告诉模型
技术手段:
| 手段 | 说明 |
|---|---|
| 零样本 | 直接通过指令描述任务,不提供示例 |
| 少样本 | 提供几个输入输出的示例,让模型模仿 |
| 举反例 | 告诉模型"不要这样做",通过负面案例约束模型行为 |
适用场景:通用任务、模型已有相关知识储备的任务
核心局限性:大模型的知识是有限的,缺乏专门领域的垂直背景知识
Context工程
核心:为大模型补充"完成任务所需的背景知识"
本质:解决Prompt工程的知识局限性,通过外部信息供给让模型具备领域知识
技术手段:
| 手段 | 说明 |
|---|---|
| RAG(检索增强生成) | 通过检索外部文档库来补充知识 |
| 长期记忆管理 | 把历史对话、用户偏好、项目背景等信息存储起来,需要时召回 |
| 动态上下文注入 | 根据当前任务阶段动态加载相关的工具文档、API说明、业务规则 |
| 上下文压缩与摘要 | 当信息量超过上下文窗口时,把历史信息压缩成摘要,保留关键信息 |
| 多源信息融合 | 把数据库查询结果、API返回数据、文档检索内容融合到上下文中 |
适用场景:垂直领域应用、需要外部知识的任务、多轮对话场景
解决的问题:"信息供给"问题,但仍然无法解决模型在执行过程中偏离目标的问题
Harness工程
核心:构建"任务执行的确定性闭环"
本质:在模型外围构建一套机制,保证复杂任务能够按计划执行,一旦出现偏差能够及时纠正
要解决的问题:当模型需要一步步执行复杂任务时,某一步出现偏差,后续步骤会基于错误的前提继续执行,导致偏差越来越大
典型架构(六层):
| 层级 | 作用 |
|---|---|
| 循环层 | 建立"观察→决策→行动→验证"的循环引擎 |
| 工具层 | 提供文件读写、API调用等原子能力 |
| 上下文层 | 作为信息防火墙控制输入质量 |
| 持久化层 | 管理跨会话的状态和记忆 |
| 验证层 | 在执行后自我验收,失败则重试 |
| 约束层 | 设定权限边界防止失控 |
适用场景:复杂任务编排、自动化工作流、需要多步执行的Agent
三者的关系
| 工程 | 解决的问题 |
|---|---|
| Prompt工程 | "怎么说清楚任务" |
| Context工程 | "怎么补充知识" |
| Harness工程 | "怎么保证执行" |
在实际项目中,三者是配合使用的:
- Prompt工程把任务描述清楚
- Context工程补充必要的背景知识
- Harness工程保证执行过程不偏离目标
在你自己的项目中,具体用到了哪些Harness工程的设计?
我的项目没有直接使用Anthropic的Harness框架,但实现了一套类似的自我纠错闭环机制,核心理念是一致的:在复杂任务执行过程中及时检测偏差并纠正,防止"一步错、步步错"。
具体实现:"规划→执行→验证→纠错"的闭环流程
规划阶段:
- 当用户提交一个复杂任务时,大模型分析任务目标
- 拆解成多个子步骤,生成执行计划
执行阶段:
- 系统按照计划依次执行每个子步骤
- 每一步都会记录执行结果和中间状态
验证阶段:
- 执行完一个阶段后,对执行结果进行打分评估
- 打分的维度包括:
- 任务完成度(是否达成了当前步骤的目标)
- 结果正确性(输出是否符合预期格式和约束)
- 资源消耗(Token和工具调用是否合理)
纠错机制:
- 如果分数达标,系统继续执行下一步
- 如果分数不达标,系统不会继续往下走,而是带着错误上下文回到规划阶段重新分析
- 具体来说,系统会把当前步骤的执行结果、错误信息、评分反馈给大模型
- 让大模型分析失败原因,调整执行计划,然后重新执行
核心价值:一旦发现偏差就立即纠正,而不是带着错误继续往下走,避免错误累积放大。
防止死循环的机制
| 机制 | 说明 |
|---|---|
| 最大执行步数限制 | 比如一个任务最多允许执行50步,如果50步内没有完成,系统会判定任务失败 |
| 单步重试次数限制 | 比如每个步骤最多重试3次,如果3次都失败,就跳过这个步骤或回退到上一步重新规划 |
Transformer与深度学习基础
Attention 注意力公式肯定得清楚吧,从公式可以看出来推理的时候,上下文一长,矩阵维度就会很大,并且也正是因为这个公式,所以会有大模型的丢失中间问题,并且推理的时候,是单向注意力的,根据这个公式,生成第N个字的时候需要和前N-1个字的K还有V矩阵做计算,所以我们可以进行一个KV缓存,把之前的字的KV矩阵缓存起来,这个就是KV cache优化。后面优化的时候也就有了提示词缓存。
建议阅读下面的内容了解:
先从整体架构说起
Transformer由编码器和解码器两部分组成:
- 编码器:负责理解输入序列
- 解码器:负责生成输出序列
在机器翻译任务中:
- 编码器把源语言句子编码成一系列向量表示
- 解码器根据这些表示逐个生成目标语言的词
编码器中的Self-Attention:双向的,每个位置可以关注所有位置,包括前面和后面的词
解码器中的Self-Attention:单向的,每个位置只能关注它之前的位置,不能看到后面的内容,这叫做因果掩码
GPT vs BERT
| 模型 | 使用的部分 | 注意力类型 | 适用任务 |
|---|---|---|---|
| GPT系列 | 只使用了解码器部分 | 单向注意力 | 生成式任务,逐字生成文本 |
| BERT | 只使用了编码器部分 | 双向注意力 | 理解任务,如文本分类、命名实体识别 |
GPT只使用解码器的原因:GPT是生成式模型,它的任务是逐字生成文本,不需要编码器来理解输入。GPT的解码器是单向注意力,生成第N个词时只能看到前N-1个词。
BERT只使用编码器的原因:BERT是双向注意力,每个词可以同时看到前面和后面的词,所以BERT更适合理解任务。
对比LSTM
Transformer最大的优势是并行计算。
| 模型 | 计算方式 | 序列长度1000时的计算 |
|---|---|---|
| LSTM | 循环神经网络,必须按顺序逐个处理 | 串行执行1000次前向计算 |
| Transformer | Self-Attention可以一次性计算所有位置之间的关系 | 只需要一次矩阵运算就能完成所有位置的注意力计算 |
这使得Transformer在GPU上的训练速度比LSTM快很多,也是大模型能够规模化训练的基础。
Self-Attention的计算过程示例
假设输入句子是"我希望我成为一名优秀的程序员",一共10个字。在编码器的Self-Attention中,每个字都会和这句话所有的字计算注意力分数。
以"为"这个字为例:
- 它会和"我、希、望、我、成、为、一、名、优、秀、的、程、序、员"这14个字分别计算注意力分数
- 计算过程:
- 先把"为"字通过线性变换得到Query向量
- 把其他每个字通过线性变换得到Key向量
- 然后用Query和每个Key做点积,得到14个注意力分数
- 这些分数经过Softmax归一化后,变成14个0到1之间的权重值,加起来等于1
- 权重值越大,说明"为"字越关注那个字
- 比如为"字可能更关注"成"和"程序员",因为语义上"成为程序员"是一个完整的搭配
- 然后用这14个权重值,对14个Value向量(每个字通过线性变换得到)做加权求和,得到"为"字经过注意力机制后的输出向量
- 这个输出向量融合了整句话的信息,但融合的程度不同,关注度高的字贡献更大
每个字都会经历同样的过程,一次性并行计算出来。这就是为什么Transformer能够并行处理整个序列,而LSTM必须逐个处理。
推理时的过程
在推理生成阶段,模型是逐字生成的,每次只能看到已经生成的词。
假设我们要让模型续写"我希望我成为一名优秀的程序员"后面的内容:
| 步骤 | 输入 | 注意力计算 | 预测 |
|---|---|---|---|
| 第一步 | "我" | "我"的Self-Attention(只有一个词,注意力就是自己) | 预测"希望" |
| 第二步 | "我希望" | 单向注意力,"我"可以看"我"和"希望",但"希望"只能看"我"和"希望" | 预测"我" |
| 第三步 | "我希望我" | 同样的过程,每个词只能看自己和前面的词 | 预测"成为" |
依此类推,每生成一个新词,都要把之前所有词重新计算一遍Self-Attention。
计算量:假设生成了N个词,计算量是1+2+3+...+N,也就是O(N²)。当N很大时,计算量会急剧增加。
KV Cache优化
KV Cache优化就是针对这个问题。
观察:每次生成新词时,之前词的Key和Value向量其实已经计算过了,只是被丢弃了。
KV Cache的做法:
- 把每个词的K和V向量缓存起来
- 生成第N个词时,只需要计算第N个词的Q、K、V
- 然后把新的K、V追加到缓存中
- 用缓存的K、V和新词的Q做注意力计算
效果:
- 每次生成的计算量从O(N)降到了O(1)
- 整个序列的计算量从O(N²)降到了O(N)
从注意力公式还可以看出几个问题
公式是:Attention(Q,K,V) = softmax(QK^T / √d) V
- QK^T的结果是一个L×L的矩阵,L是序列长度
- 当L很大时,这个矩阵的内存占用和计算量都会急剧增加
- 这就是大模型有上下文长度限制的原因
丢失中间问题:
- 注意力分数经过Softmax归一化后,每个位置分配给其他位置的注意力分数都很小
- 中间位置的信息容易被"稀释"
- 这就是大模型"丢失中间"问题的根源
- 模型倾向于关注开头和结尾的内容,中间部分的信息难以被充分关注
除了 KV cache 以外,还有Flash Attention是如何优化的?
Flash Attention解决的核心问题:显存带宽瓶颈
在标准的Self-Attention实现中,计算过程需要频繁访问GPU的高带宽内存(HBM):
- QK^T点积的结果是一个L×L的注意力矩阵
- 这个矩阵需要写入HBM
- 然后从HBM读取出来做Softmax
- 再把结果写入HBM
- 又从HBM读取出来和V做矩阵乘法
当序列长度L很大时:
- 这个L×L的矩阵非常占内存
- 而且频繁的HBM读写成为性能瓶颈
- GPU的计算速度很快,但大部分时间都在等待数据从HBM搬运过来
Flash Attention的核心思想:分块计算和减少HBM访问
它把Q、K、V矩阵分成小块:
- 每块大小刚好能放进GPU的SRAM(片上高速缓存)里
- 然后在SRAM里完成所有计算
- 最后只把最终结果写回HBM
这样就避免了中间结果(L×L的注意力矩阵)在HBM和SRAM之间来回搬运。
具体例子:
假设序列长度L=4096,头维度d=64:
- 标准实现需要计算一个4096×4096的注意力矩阵,大约128MB的显存
- 而且这个矩阵需要多次读写HBM
- Flash Attention把Q、K、V分成小块,比如每块128个token
- 每次只把128×128的小块注意力矩阵放进SRAM计算
- 算完一块就输出结果,不需要把整个4096×4096的大矩阵存下来
效果:显存占用从O(L²)降到了O(L),大大降低了显存需求。
Flash Attention还采用了在线Softmax技术
标准Softmax需要:
- 先算出所有分数的最大值
- 然后对每个分数减去最大值再做指数运算
- 这意味着必须等所有分数都算出来才能做Softmax
Flash Attention的在线Softmax:
- 可以在分块计算的过程中逐步更新最大值和归一化因子
- 不需要等所有分数都算出来
- 每处理一块新的注意力分数,就更新当前的最大值和累加和
- 最终结果和标准Softmax完全一致
Flash Attention 2:进一步优化并行性
第一代Flash Attention主要优化了显存访问,但并行度不够高。
Flash Attention 2的优化:
- 调整了线程块和工作分配
- 让不同的GPU线程块并行处理不同的查询块
- 提高了GPU利用率
- 同时减少了非矩阵乘法的计算
- 因为现代GPU有专门的矩阵乘法加速单元,非矩阵乘法操作效率较低
效果:Flash Attention 2比第一代快约2倍。
Flash Attention 3:针对H100 GPU的新特性优化
H100 GPU支持FP8低精度计算和异步操作。
Flash Attention 3的优化:
- 利用这些特性,在计算注意力矩阵的同时异步加载下一块数据
- 进一步隐藏了内存延迟
- 同时使用FP8精度计算,在保持数值稳定性的前提下提高了计算吞吐量
效果:Flash Attention 3比Flash Attention 2又快了约1.5到2倍。
总结Flash Attention的价值
| 优化点 | 效果 |
|---|---|
| 显存复杂度 | 从O(L²)降到了O(L),使得大模型能够处理更长的上下文 |
| 上下文长度 | 比如原来只能处理4K上下文,用了Flash Attention后可以处理32K甚至更长 |
| 推理速度 | 减少了HBM访问次数,推理速度大幅提升 |
现在Flash Attention已经成为大模型推理的标准优化手段,几乎所有主流框架都集成了这个技术。
还有可能问到的强化学习的相关知识
DPO GRPO PPO SFT 微调 RLHF
只需要简单了解有个印象就行:
SFT(监督微调)
SFT是后训练的第一步,核心是用高质量的标注数据教模型"怎么说话"。
具体做法:
- 开发者会人工编写大量"一问一答"的对话范例
- 比如用户问"请帮我写一封请假信",标注人员写出高质量的回答
- 这些数据喂给模型后,模型学会了从"文字接龙机器"变成"问答助手"
特点:
- SFT改变了模型的回答方式
- 但没有教模型"什么是好话、什么是坏话"
RLHF(基于人类反馈的强化学习)
RLHF的核心是让模型学会人类的偏好。
具体流程:
- 首先训练一个奖励模型,这个模型能够给回答打分,判断哪个回答更好
- 然后用强化学习算法(如PPO)优化大模型,让模型生成的回答能够获得更高的奖励分数
好处:能够捕捉人类偏好中难以用规则描述的标准,比如回答是否有帮助、是否安全、是否符合价值观
缺点:实现复杂、训练不稳定、需要大量人工标注偏好数据
PPO(近端策略优化)
PPO是RLHF中常用的强化学习算法。
核心思想:限制策略更新的幅度,避免模型在一次更新中变化太大导致训练崩溃
PPO需要同时在显存中加载四个模型:
| 模型 | 作用 |
|---|---|
| Actor模型 | 要训练的大模型 |
| Reference模型 | 初始状态的快照,防止模型偏离太远 |
| Reward模型 | 给回答打分 |
| Value模型 | 预测当前状态的预期得分 |
问题:内存开销大、实现复杂、超参数多
DPO(直接偏好优化)
DPO是对RLHF的简化。
核心发现:证明了可以直接用偏好数据优化策略,而不需要显式训练奖励模型
具体做法:
- DPO的核心是把"人类偏好A比B好"这种数据直接用于训练
- 通过对比学习让模型提高生成A的概率、降低生成B的概率
相比PPO:
- 只需要两个模型(Actor和Reference)
- 实现简单、训练稳定、不需要复杂的强化学习流程
缺点:对于需要精细控制的场景效果不如PPO
GRPO(组相对策略优化)
GRPO是DeepSeek提出的创新算法,核心是去掉了PPO中的Value模型。
具体做法:
- 让模型对同一个问题生成N个不同的回答
- 用奖励模型给这N个回答打分
- 计算平均分作为基线
- 得分高于平均的回答获得正向奖励,低于平均的回答获得负向惩罚
这种组内相对评分的方式:
- 替代了Value模型的预期得分预测
- 减少了约25%的内存开销
适用场景:特别适合数学、代码等答案可验证的领域,因为这类领域的奖励信号明确,组内比较更稳定
四者的关系和选择
| 方法 | 定位 | 特点 |
|---|---|---|
| SFT | 基础 | 教会模型说话的方式 |
| RLHF | 进阶 | 让模型学会人类偏好 |
| PPO | RLHF的经典实现 | 效果好但复杂 |
| DPO | PPO的简化版 | 适合资源有限的场景 |
| GRPO | PPO的优化版 | 特别适合可验证领域 |
实际应用顺序:
- 先用SFT让模型具备基本能力
- 再根据场景选择合适的偏好优化方法:
- 如果资源充足且需要精细控制,用PPO
- 如果追求简单稳定,用DPO
- 如果是数学代码等可验证领域,用GRPO
大模型微调与训练
垂直领域客服Agent,仅通过Prompt+RAG准确率只有70%,如何进一步提升?
第一:优化RAG的检索质量
70%的准确率可能是因为检索环节出了问题,召回的文档不够相关或不完整。
可以尝试:
- 更换更好的Embedding模型,比如从通用模型换成领域专用的Embedding模型
- 引入重排序机制,先用向量检索召回Top-K文档,再用Cross-Encoder精排,把最相关的文档排在前面
- 优化Chunk切分策略,避免关键信息被切断,可以尝试语义切分或重叠分块
- 增加元数据过滤,根据用户问题类型、业务模块等维度过滤文档,缩小检索范围
第二:优化Prompt的指令设计
可以尝试:
- 更明确的角色定义,让模型清楚自己是某个垂直领域的专家
- 给出Few-shot示例,展示高质量问答的输入输出模式
- 设置约束条件,比如"只根据检索到的文档回答,不要编造信息"
- 引入思维链,让模型先分析问题类型、再定位关键信息、最后组织回答
这些优化可能把准确率从70%提升到80%左右。
第三:构建领域知识图谱
RAG是基于相似度检索,但有些领域知识需要结构化的关系推理。
比如医疗领域,症状和疾病之间有复杂的关联关系,单纯靠向量检索可能找不到。知识图谱可以把这些关系显式存储,模型回答时可以沿着关系链推理。
知识图谱和RAG可以结合使用:
- 检索到的文档作为背景知识
- 知识图谱提供结构化推理
第四:引入多轮澄清机制
很多时候准确率低是因为用户问题本身不清晰或缺少上下文。
可以让Agent在回答前先判断问题是否明确,如果不明确就追问澄清。
比如用户问"这个药怎么吃",Agent可以追问"请问是哪种药?您的年龄和体重是多少?"这样获得更多信息后再回答,准确率会高很多。
第五:考虑SFT微调
如果以上方法都尝试过还是遇到瓶颈,就考虑SFT微调。
用真实的客服对话数据微调模型,让模型学会这个领域的回答风格、专业术语、常见问题的标准答案。
微调后效果:
- 模型对这个领域的理解会更深入
- 准确率可以提升到90%以上
注意事项:
- 微调需要高质量的标注数据,成本较高
- 而且需要定期更新以适应新的业务变化
有没有了解过SFT(监督微调)?在什么情况下选择继续优化RAG/Prompt,什么情况下选择做SFT?
选择RAG/Prompt优化的情况
- 数据量不足:没有足够的高质量标注数据来做微调
- 领域知识更新频繁:比如新闻、政策、产品信息,这些内容每天都在变,微调后模型很快就会过时,用RAG可以实时检索最新信息
- 需要可解释性:RAG可以告诉用户"我是根据这些文档回答的",用户可以验证信息来源,微调后的模型则是"黑盒"
- 资源有限:微调需要GPU算力和技术积累,RAG只需要一个向量数据库和检索服务
- 快速迭代验证:Prompt改了立刻生效,微调需要重新训练,周期较长
选择SFT微调的情况
- 有充足的高质量标注数据:比如积累了大量真实的客服对话记录,且标注质量可靠
- 需要模型学习特定的回答风格或格式:比如医疗领域要求回答严谨、法律领域要求引用法条,这些风格很难通过Prompt完全约束
- RAG/Prompt优化已经遇到瓶颈:准确率卡在某个水平上不去
- 需要降低推理成本:微调后可以用较小的模型达到较好的效果,减少对大模型的依赖
- 领域知识相对稳定:不需要频繁更新,比如医学基础知识、法律条文
实际决策顺序
- 先用Prompt工程快速验证,看能否达到基本要求
- 如果不够,加入RAG补充领域知识
- 如果RAG检索质量有问题,优化检索策略
- 如果这些都做了还是不够,且手头有高质量数据,就考虑SFT微调
- 如果微调后还需要更精细的控制,再考虑RLHF
这是一个渐进的过程,不要一上来就做微调,成本高且不一定有效。
SFT和RLHF(基于人类反馈的强化学习)有什么区别?分别适用于什么场景?
SFT(监督微调)
核心:用标注数据教模型"怎么说话"
具体做法:
- 开发者准备大量"问题-回答"对
- 用标准的监督学习方式训练模型
- 模型看到问题,输出回答,计算和标准回答的差异,反向传播更新参数
目标:让模型学会特定的输出格式、回答风格、领域知识
示例:用医疗问答数据微调,模型就学会用专业术语回答医疗问题
RLHF(基于人类反馈的强化学习)
核心:让模型学会"什么是好的回答"
具体做法:
- 它不是告诉模型"应该这样回答"
- 而是让模型自己探索,人类给出反馈"这个回答比那个好"
- 模型根据反馈调整策略
RLHF分三个阶段:
- 训练奖励模型,让人类对多个回答排序,训练一个能预测人类偏好的模型
- 用强化学习优化策略模型,让模型生成的回答能获得更高的奖励分数
- 可能还有拒绝采样、安全对齐等步骤
两者的核心区别
| 对比维度 | SFT | RLHF |
|---|---|---|
| 学习方式 | "模仿学习",模型学习标注数据的模式 | "偏好学习",模型学习人类的评价标准 |
| 目标 | 输出和标注尽可能接近 | 生成人类喜欢的回答,但具体怎么回答由模型自己探索 |
| 数据类型 | "正确答案" | "偏好排序" |
| 训练特点 | 训练稳定、实现简单,但上限受限于标注数据质量 | 能捕捉人类偏好中难以标注的标准,但训练复杂、容易不稳定 |
适用场景
SFT适合:
- 需要注入领域知识,比如让模型学会医学、法律知识
- 需要学习特定格式,比如让模型输出JSON、SQL
- 需要改变回答风格,比如让模型更简洁或更详细
RLHF适合:
- 需要优化主观质量,比如让回答更有帮助、更安全、更符合价值观
- SFT后需要进一步对齐,SFT教会模型说话,RLHF教会模型说好话
- 需要减少有害输出,比如让模型拒绝回答危险问题
实际应用顺序
通常先SFT再RLHF:
- 先用SFT让模型具备基本能力,学会领域知识和回答格式
- 再用RLHF优化输出质量,让回答更符合人类期望
直接做RLHF效果通常不好,因为模型还没有基本的回答能力,探索空间太大。
本地部署的模型是否有必要进行微调?为什么模型经过微调之后输出会更加稳定?
是否有必要微调?
不需要微调的情况:
如果只是做通用任务,比如日常对话、文本摘要、翻译,直接用开源模型(如Qwen、Llama、ChatGLM)通常就够了。这些模型已经在大规模数据上训练过,通用能力不错。
需要微调的情况:
-
垂直领域应用:比如医疗问答、法律咨询、金融分析。通用模型在这些领域的表现通常不好,因为训练数据中领域知识占比很小,模型对专业术语、业务规则都不熟悉。微调可以让模型学会领域知识,提高回答的准确性和专业性。
-
需要特定的输出格式或风格:比如让模型输出结构化的JSON、按照特定模板生成报告、用特定的语气和用户对话。这些需求通过Prompt也可以部分实现,但微调的效果更稳定、更可控。
-
对推理成本敏感:可以考虑微调小模型。用领域数据微调一个7B模型,可能在特定任务上接近未微调的70B模型效果,但推理成本低很多。
为什么微调后输出更稳定?
这涉及到模型学习的本质。
预训练模型的问题:
- 预训练模型是在海量通用数据上训练的,学到了各种领域的知识
- 但也意味着对任何特定领域都不够专精
- 当用户问一个垂直领域问题时,模型的参数空间里有很多可能的回答路径
- 模型不确定该走哪条,所以输出会有波动
微调的作用:
- 微调相当于在模型的参数空间里"聚焦"
- 用领域数据微调后,模型参数会朝着有利于该领域的方向调整
- 原本分散的注意力被收拢到领域相关的模式上
- 模型对领域问题的回答路径变得更确定
类比:就好比一个人什么都懂一点但都不精,回答问题时会犹豫;经过专业训练后,对专业问题的回答就变得果断和一致。
另外:
- 微调数据通常是高质量的、经过清洗的,格式统一、风格一致
- 模型学习这些数据后,输出也会继承这种一致性
- 而预训练数据来源复杂、质量参差不齐,模型学到的输出模式也是混杂的
训练过程中,训练集Loss在下降但验证集Loss反而上升,这说明了什么原因?如何解决?
(面试官问我研究生有没有发过深度学习相关的论文,然后给我出的题目,这个题目不想看可以不看)
这是典型的过拟合现象
现象解释:
- 训练集Loss下降说明模型在训练数据上表现越来越好
- 验证集Loss上升说明模型在未见过的数据上表现变差
- 这意味着模型开始"死记硬背"训练数据,而不是学习真正的规律
- 模型记住了训练样本的特征,但这些特征在新数据上不适用,泛化能力下降
过拟合的原因
- 训练数据太少:模型容量相对过大,模型有足够的能力记住每个训练样本
- 模型太复杂:参数量相对于数据量过大,模型学习到了训练数据中的噪声而不是真实模式
- 训练时间太长:模型在训练集上优化过度,把训练数据的噪声也学进去了
- 训练数据和验证数据分布不一致:模型学到的是训练集特有的分布,在新分布上不适用
解决方法
第一:增加训练数据
更多的数据可以让模型学到更普遍的规律,而不是记住个别样本。如果没有更多数据,可以用数据增强,比如同义词替换、回译、改写等方式生成新样本。
第二:减少模型复杂度
- 可以用更小的模型,减少层数或隐藏维度
- 或者用正则化技术:
- Dropout:在训练时随机丢弃一部分神经元,防止模型过度依赖某些特征
- 权重衰减:在损失函数中加入权重的L2范数,限制参数的大小
第三:早停
在训练过程中监控验证集Loss,当验证集Loss开始上升时就停止训练,使用验证集Loss最低时的模型参数。这是最简单有效的防止过拟合的方法。
第四:交叉验证
把数据分成多份,轮流用其中一份做验证集,其他做训练集,最后取平均效果。这样可以更准确地评估模型泛化能力,避免因数据划分偶然性导致的误判。
第五:检查数据质量
如果训练数据有标注错误或噪声,模型会学习这些错误模式,在新数据上表现差。需要清洗数据,去除明显错误的样本。
第六:学习率调整
如果学习率太大,模型可能在最优解附近震荡,无法收敛到好的解。可以尝试减小学习率,或用学习率调度器,训练后期逐渐减小学习率。
实际训练时的做法
我会同时用多种方法:
- 设置早停机制
- 加入Dropout和权重衰减
- 监控训练集和验证集的Loss曲线
- 如果发现过拟合趋势就提前停止或调整超参数
补充内容
DeepSeek-R1 的四阶段流水线:如何练出顶级推理能力?
也可以应对最近有没有阅读过一些技术报告、论文,都可以用这个回答:
DeepSeek-R1之所以在逻辑推理上极其惊艳,并不是一蹴而就的,而是走了一条非常精密的四阶段强化学习流水线:
第一阶段:冷启动 SFT
- 先用少量但极其高质量的"思维链 (CoT)"数据给模型打个样
- 让模型知道"哦,原来遇到复杂问题我要先这么拆解"
- 这给后续的强化学习提供一个稳固的起点
第二阶段:可验证领域的强化学习 (RL)
这是最关键的一步。
- 在数学和代码这两个领域,答案是对是错非常明确(代码跑得通就是对,跑不通就是错)
- DeepSeek使用了GRPO算法(相比传统的PPO算法,它大大减轻了系统开销)
- 疯狂让模型在这些领域试错
- 只要做对了就给奖励,模型为了拿到奖励,慢慢就"顿悟"出了强大的逻辑推理能力
第三阶段:拒绝采样微调
- 模型在第二步里产生了海量的推理过程(有好的也有坏的)
- 开发者把里面最优秀的轨迹挑选出来
- 重新作为SFT数据喂给它,进一步巩固它的好习惯
第四阶段:偏好反馈对齐
- 最后一步,融入安全性和有益性的反馈
- 确保模型虽然绝顶聪明,但不会去教人做炸弹,也不会满嘴脏话
- 完成最终的行为收敛
OpenClaw
OpenClaw 是一个开源的个人 AI Agent 框架。和普通聊天机器人不一样,它不是只负责回答问题,而是把"大模型推理能力"和"本地执行能力"结合起来,让 AI 能够在本地环境中完成一整套任务闭环。
核心能力:
- 可以常驻后台运行
- 通过Gateway接入飞书、Telegram、Discord等渠道接收指令
- 用模型做任务理解和规划
- 再由执行器去操作文件系统、终端、浏览器
- 同时通过Skills机制扩展能力
- 并把用户偏好、路径、历史任务等信息持久化保存下来
核心价值:把AI从"对话工具"推进到"可执行任务的代理系统"。
如果面试官追问"它和普通 AI 助手有什么区别",你可以答:
OpenClaw的差异点主要有四个:
-
本地优先:任务调度、文件处理和工具执行都尽量在本地完成,不像传统AI那样每一步都依赖网页对话和云端上传
-
常驻式Agent:可以24×7挂在后台,支持定时任务和事件触发,而不是"打开网页问一句、关掉就结束"
-
可扩展:通过Skill包扩展能力,Skill以SKILL.md为描述入口,还可以附带脚本或二进制工具
-
多渠道接入:用户可以在飞书、Telegram、Discord等已有沟通平台里直接下指令,而不必切换到特定App
Open claw 的实现原理
OpenClaw是一个开源的个人AI Agent框架,它不是传统的一问一答聊天机器人,而是一个能常驻后台、接收自然语言指令并在本地执行任务的代理系统。
核心架构(四部分)
| 部分 | 职责 |
|---|---|
| 模型层 | 理解任务、拆解步骤和生成行动计划 |
| 执行器 | 真正操作文件系统、终端和浏览器,把模型的计划落地 |
| Gateway和接入适配器 | 把飞书、Discord、Telegram等渠道的消息接进来再把结果发回去 |
| 持久化记忆层 | 存储用户偏好、路径和历史任务状态,支持跨会话延续 |
扩展方式:Skill机制
- Skill一般用SKILL.md来描述能力
- 也可以附带脚本或工具
- 因此比较适合做文件整理、自动化办公、监控提醒这类规则明确的任务
从原理上看
OpenClaw本质上是"大模型决策 + 本地工具执行 + 状态持久化 + 多渠道接入"的闭环Agent Runtime。
优势:本地优先、常驻运行、扩展性强
问题:部署成本、Token消耗和高权限带来的安全风险
OpenClaw 的单次任务流程可以概括为六步
第一步:接收指令
用户通过飞书、Discord之类的渠道输入一句自然语言指令,Gateway把它转成内部任务。
第二步:构造上下文
系统会把当前用户请求、历史偏好、路径信息、可能的技能能力说明等拼成上下文,一起送给模型。持久化记忆在这里发挥作用。
第三步:模型做规划
模型负责理解"用户要我做什么",并把任务分解成若干步骤,比如先读目录,再分类文件,再生成结果说明。
第四步:执行器执行动作
模型给出的动作并不会直接生效,而是由执行器调用本地能力去真正完成,比如操作浏览器、运行终端命令、处理文件。
第五步:必要时调用Skill
如果基础执行器能力不够,系统还可以调用Skill包。文档里说Skill是用Markdown作为接口定义的扩展包,包含SKILL.md描述文件,以及可选的脚本、工具或二进制程序。
第六步:返回结果并更新记忆
任务完成后,把结果发回原消息渠道,同时把本轮任务中的重要状态写入本地记忆,为下一轮服务。