沉梦手记 沉梦手记
首页
  • 基础篇
  • 集合篇
  • 并发篇
  • JVM
  • 新特性
  • 计算机网络
  • 操作系统
  • 数据结构与算法
  • 数据库基础
  • MySql
  • Redis
  • 达梦数据库
  • Spring
  • SpringBoot
  • Mybatis
  • Shiro
  • 设计须知
  • UML画图
  • 权限校验
  • 设计模式
  • API网关
  • 网络通信
  • 消息队列
  • SpringCloud
  • 分布式事务
  • 云存储
  • 搜索引擎
  • 前端基础
  • Vue
  • React
  • 开发工具篇
  • 工具库篇
  • 开发技巧篇
  • 工具类系列
  • 运维与部署
  • 音视频处理
  • 随笔
  • 博客搭建
  • 网站收藏箱
  • 断墨寻径摘录
  • 费曼学习法
Github (opens new window)

沉梦听雨

时间是最好的浸渍剂,而沉淀是最好的提纯器🚀
首页
  • 基础篇
  • 集合篇
  • 并发篇
  • JVM
  • 新特性
  • 计算机网络
  • 操作系统
  • 数据结构与算法
  • 数据库基础
  • MySql
  • Redis
  • 达梦数据库
  • Spring
  • SpringBoot
  • Mybatis
  • Shiro
  • 设计须知
  • UML画图
  • 权限校验
  • 设计模式
  • API网关
  • 网络通信
  • 消息队列
  • SpringCloud
  • 分布式事务
  • 云存储
  • 搜索引擎
  • 前端基础
  • Vue
  • React
  • 开发工具篇
  • 工具库篇
  • 开发技巧篇
  • 工具类系列
  • 运维与部署
  • 音视频处理
  • 随笔
  • 博客搭建
  • 网站收藏箱
  • 断墨寻径摘录
  • 费曼学习法
Github (opens new window)
  • 脚手架搭建
  • 瑞吉外卖

  • 黑马点评

  • vue-blog

  • 沉梦接口开放平台

  • 用户中心

  • 聚合搜索平台

  • 仿12306项目

  • 壁纸小程序项目

  • AI 智能面试辅助平台

    • 项目开发笔记
    • AI基础概念
      • 一、LLM(大语言模型)基础
        • 1.1 什么是 LLM
        • DashScope 是什么
        • 1.2 Token 与上下文窗口
        • 1.3 Temperature 与采样参数
      • 二、Prompt 工程
        • 2.1 Prompt 的组成
        • 2.2 Prompt 框架:RISEN
        • 2.3 Prompt 模板:StringTemplate
      • 三、结构化输出(Structured Output)
        • 3.1 为什么需要结构化输出
        • 3.2 项目的解法:StructuredOutputInvoker
      • 四、流式输出(SSE / Streaming)
        • 4.1 为什么要流式
        • 4.2 SSE 协议
        • 4.3 流式输出的工程坑
      • 五、Embedding 与向量检索
        • 5.1 什么是 Embedding
        • 5.2 向量相似度
        • 5.3 pgvector
        • 5.4 检索召回率与准确率的权衡
        • 这些值是怎么调出来的
        • 三个测评指标通俗解释
        • Hit@K(命中率)——"前 K 个里有没有找到正确答案"
        • MRR(平均倒数排名)——"正确答案排在第几名"
        • EvidenceRecall@K(证据召回率)——"期望的证据找全了没有"
        • 三个指标的关系
        • 为什么短查询要"低阈值 + 大候选集"
      • 六、RAG(检索增强生成)
        • 6.1 什么是 RAG
        • 6.2 RAG 完整流程
        • 6.3 文档分块(Chunking)
        • 6.4 Query Rewrite(查询改写)
        • 6.5 RAG 幻觉问题
      • 七、Advisor 链(Spring AI)
        • 7.1 什么是 Advisor
        • 7.2 项目的 Advisor 链设计
      • 八、AI 工程化的核心原则
        • 8.1 异步化
        • 8.2 防御性编程
        • 8.3 Prompt 注入防护
        • 8.4 成本控制
      • 九、概念速查表
      • 十、推荐学习资料
    • 学习计划
  • RuoYi-Vue

目录

AI基础概念

# AI 基础概念(Java 后端视角)

本文面向有 Java 后端经验、但未接触过 Java + AI 联合开发的读者,梳理本项目涉及的核心 AI 概念,帮助理解代码中"为什么要写这么多防御性逻辑"。

# 一、LLM(大语言模型)基础

# 1.1 什么是 LLM

LLM(Large Language Model)是基于 Transformer 架构、通过海量文本预训练得到的语言模型。本项目通过 Spring AI 调用阿里 DashScope 等厂商提供的 LLM 服务,实现简历分析、面试出题、评估打分等能力。

# DashScope 是什么

DashScope 是阿里云的大模型服务平台(对外品牌名"百炼"),提供各类 AI 模型的 API 调用。可以理解为"AI 模型的云超市"——你不需要自己部署模型,按调用量付费即可。

项目用到的 DashScope 能力:

能力 模型 项目用途
对话生成(Chat) qwen3.5-flash(通义千问) 简历分析、面试出题、评估打分、RAG 问答
文本向量化(Embedding) text-embedding-v3 知识库文档向量化(1024 维)
语音识别(ASR) - 语音面试:录音转文字
语音合成(TTS) - 语音面试:文字转语音

类比理解:DashScope 之于 AI 模型,就像 AWS 之于云服务器。你不用关心模型怎么部署,只要调 API、按量付费。国内类似平台还有智谱(GLM)、DeepSeek、Kimi 等,项目通过 LlmProviderRegistry 支持多厂商切换。

关键认知:LLM 不是一个"稳定的 API",而是一个"概率性输出引擎"——同样输入可能得到不同输出,且可能出错、胡说、格式不合规。这种不确定性是后续所有工程化设计的根本原因。

# 1.2 Token 与上下文窗口

  • Token:LLM 处理文本的最小单位,约等于 1.5 个汉字或 0.75 个英文单词。计费按 token 计算。
  • 上下文窗口(Context Window):模型单次能"看到"的 token 上限。超出窗口的内容会被截断或报错。

项目中的体现:

  • 面试评估一次要处理 20+ 题答案,单次塞入会超出模型上下文限制 → 设计分批评估流水线(8 题/批)+ 总结归并。
  • RAG 检索结果拼接 prompt 时也要控制总 token 数,避免超出窗口。

# 1.3 Temperature 与采样参数

  • Temperature:控制输出随机性,0 = 最确定(适合结构化输出、评估打分),1+ = 更发散(适合创意写作)。
  • Top-P:核采样,与 Temperature 配合控制多样性。

项目中的体现:评估打分场景必须用低 Temperature,否则同一答案多次评分结果不一致;出题场景可以适当提高,增加题目多样性。

# 二、Prompt 工程

# 2.1 Prompt 的组成

一个完整的 Prompt 通常包含:

部分 作用 项目示例
System Prompt 设定模型角色与全局规则 "你是资深面试官,按 RISEN 框架评估"
User Prompt 用户具体请求 "请评估以下答案:..."
Context 检索到的参考资料(RAG 场景) 知识库召回的文档片段
History 多轮对话历史 面试追问场景的上下文

# 2.2 Prompt 框架:RISEN

项目评估模块基于 RISEN 框架 设计 Prompt:

  • Role:角色(资深面试官)
  • Instructions:指令(评估答案质量)
  • Steps:步骤(按维度逐一评分 → 给出理由 → 总结)
  • End state:终点状态(输出 JSON 格式报告)
  • Negative constraints:否定约束("不得给出满分"、"不得使用模糊表述")

为什么需要否定约束:LLM 有"讨好用户"的倾向,容易给高分、说好话。否定约束能有效压制这种"打高分"倾向。

# 2.3 Prompt 模板:StringTemplate

项目将 Prompt 模板放在 app/src/main/resources/prompts/,使用 StringTemplate .st 文件,变量通过占位符注入,与代码解耦。修改 prompt 不需要重新打包代码。

# 三、结构化输出(Structured Output)

# 3.1 为什么需要结构化输出

LLM 本质上输出的是"自然语言文本",但业务系统需要"结构化数据"(JSON)来做后续处理。让模型输出 JSON 是一件不稳定的事:

  • 可能输出多余文字("好的,以下是结果:{...}")
  • 可能字段缺失或类型错误
  • 可能 JSON 不闭合
  • 可能被 markdown 代码块包裹

# 3.2 项目的解法:StructuredOutputInvoker

common/ai/StructuredOutputInvoker.java 统一封装:

  1. 解析:从模型输出中提取 JSON(兼容 markdown 包裹、前后多余文字)
  2. 校验:反序列化为目标对象,校验字段
  3. 修复型重试:失败时把错误原因注入 prompt,让模型自修复后重试
  4. 指标跟踪:接入 Micrometer 按业务链路跟踪重试成功率与延迟

关键认知:永远不要假设 LLM 一次就能返回合规 JSON,必须有兜底重试机制。

# 四、流式输出(SSE / Streaming)

# 4.1 为什么要流式

LLM 生成一个完整回答可能需要 10-30s,如果等全部生成完再返回,用户体验极差。流式输出让模型"边生成边返回",用户看到字一个一个出现。

# 4.2 SSE 协议

本项目用 SSE(Server-Sent Events) 实现流式输出:

  • 基于 HTTP 长连接,服务端持续推送 data: xxx\n\n 格式的事件
  • 浏览器原生支持 EventSource 接收
  • 单向通信(服务端 → 客户端),适合 LLM 流式场景

# 4.3 流式输出的工程坑

  • 断线丢失:流式中途断线,已生成的内容会丢失 → 项目设计"预创建占位 + 流完成落库"策略
  • 无结果模板浪费:RAG 检索无结果时模型会输出长篇拒答模板 → 设计探测窗口算法缓冲前 120 字符匹配模板,命中则立即替换
  • 虚拟线程适配:SSE 长连接 + LLM 调用都是 I/O 密集型,项目用 Java 21 虚拟线程替代线程池

# 五、Embedding 与向量检索

# 5.1 什么是 Embedding

Embedding(嵌入):把文本转换成固定维度的浮点数向量,使"语义相近"的文本在向量空间中距离也相近。

  • 输入:"Java 多线程的实现方式"
  • 输出:1024 维浮点数向量 [0.023, -0.114, 0.887, ...]

# 5.2 向量相似度

常用距离度量:

度量 含义 项目配置
Cosine 余弦相似度,关注方向 项目使用 COSINE 距离类型
L2 欧氏距离,关注绝对距离 -
Inner Product 内积,关注绝对值 -

# 5.3 pgvector

pgvector 是 PostgreSQL 的向量检索扩展,让 PG 支持 vector 类型与相似度查询。

项目配置:

  • 向量维度:1024
  • 距离类型:COSINE
  • 索引:HNSW(Hierarchical Navigable Small World,图索引,查询快但建索引慢)

# 5.4 检索召回率与准确率的权衡

  • Top-K:返回最相似的 K 个结果。K 太小可能漏掉相关内容,K 太大会引入噪声。
  • minScore:相似度阈值,低于此值的结果过滤掉。

项目按去空格后的字符长度(compactLength)分档,配置差异化 Top-K 与 minScore(源码:KnowledgeBaseQueryProperties.java + KnowledgeBaseQueryService.resolveSearchParams):

Query 长度(去空格后) Top-K minScore 原因
短(≤4 字) 20 0.25 短查询向量化信息少、相似度分数普遍偏低,降低阈值避免漏召回 + 扩大候选集保证相关结果进入
中(5-12 字) 12 0.28 语义相对明确,标准阈值不会漏召回,12 个候选够用
长(>12 字) 8 0.28 语义明确、相似度分数高,少召回即可,减少噪声与计算量

配置项对应:topkShort=20 / topkMedium=12 / topkLong=8,minScoreShort=0.25 / minScoreDefault=0.28。中、长 Query 共用 minScoreDefault=0.28。

# 这些值是怎么调出来的

不是拍脑袋定的,是项目用 RAG 测评集(app/src/test/resources/rag-eval/dataset-v1.jsonl,40 条样本:dev 30 条调参 + holdout 10 条验证)跑出来的。基线报告在 app/src/test/resources/rag-eval/baselines/,生产配置对应 rag-eval-rewrite-only-1788407548196.md:Hit@K 93.75%、MRR 0.7081、EvidenceRecall 0.9219。

# 三个测评指标通俗解释

测评集里每条样本长这样(以 fact-07 为例):

问题:Redis 的 RDB 和 AOF 两种持久化方式各自的原理是什么?
期望证据锚点:[
  "RDB 是某一时刻的全量二进制快照",   ← 锚点1
  "AOF 记录每条写命令"               ← 锚点2
]
1
2
3
4
5

检索会返回 Top-K 个文档片段,然后看这些片段里有没有命中"证据锚点"。

# Hit@K(命中率)——"前 K 个里有没有找到正确答案"

最简单的指标:前 K 个结果里至少命中一个证据锚点的样本,占全部样本的比例。

样本 检索结果 是否命中
fact-01 第 1 个片段就包含期望证据 ✅ 命中
fact-07 第 5 个片段包含期望证据 ✅ 命中
fact-09 Top-K 里一个证据都没找到 ❌ 未命中

项目 32 条知识库内样本里 30 条命中 → Hit@K = 30/32 = 93.75%

只看"有没有命中",不看排在第几名。

# MRR(平均倒数排名)——"正确答案排在第几名"

Hit@K 只看有没有命中,MRR 进一步看命中位置靠不靠前。排名越靠前,得分越高:

样本 命中排名 得分 计算方式
fact-01 第 1 名 1.0 1/1
fact-04 第 2 名 0.5 1/2
fact-07 第 5 名 0.2 1/5
fact-08 第 8 名 0.125 1/8
fact-09 未命中 0 0

所有样本得分取平均 = MRR。项目 MRR = 0.7081,说明大部分命中都排在前面几名。

排第 1 名得 1 分,排第 5 名只剩 0.2 分,排越后面扣得越狠。这个指标鼓励"把最相关的排在最前面"。

# EvidenceRecall@K(证据召回率)——"期望的证据找全了没有"

有些问题有多个证据锚点(比如跨段题 chunk-01 要找 3 个证据),Hit@K 只要有 1 个命中就算通过,但 EvidenceRecall 看的是找全了多少:

样本 期望锚点数 命中数 Recall
fact-07 2(RDB 原理 + AOF 原理) 2 1.0 全找到了
chunk-06 2(事故根因 + 改进措施) 1 0.5 只找到一半
fact-09 2(回表定义 + 二级索引) 0 0.0 一个没找到

所有样本取平均 = EvidenceRecall@K。项目 0.9219,说明大部分证据都找全了。

Hit@K 是"有没有找到",EvidenceRecall 是"找全了没有"。

# 三个指标的关系

Hit@K          → 有没有命中?(有/无)
MRR            → 命中排在第几名?(排名越前越好)
EvidenceRecall → 期望的证据找全了没?(找全/找了一半/一个没找)
1
2
3

调参时三个指标要一起看:比如把 minScore 调高,Hit@K 可能下降(漏召回),但 MRR 可能上升(过滤掉噪声后相关结果排名更靠前)。项目的配置就是在三者之间找平衡。

# 为什么短查询要"低阈值 + 大候选集"

反直觉的点:minScore 是相似度下限,值越大越严格(0.28 比 0.25 过滤掉更多结果)。

短查询(如多轮对话里的"那节点变少之后还会退化回去吗?""分片键当时为什么这么选?")向量化后信息量少,与文档的相似度分数普遍偏低。如果用 0.28 的阈值,相关结果会被一起过滤掉(漏召回)。所以项目判断短查询的主要矛盾是漏召回而非噪声,选择放宽阈值 + 扩大候选集,噪声交给 Query Rewrite 改写后再检索 + LLM 自己判断。

# 六、RAG(检索增强生成)

# 6.1 什么是 RAG

RAG(Retrieval-Augmented Generation):在调用 LLM 前,先从知识库检索相关文档,把检索结果拼入 prompt,让模型"基于参考资料"回答,减少幻觉。

# 6.2 RAG 完整流程

用户提问
   ↓
Query Rewrite(查询改写,优化检索意图)
   ↓
Embedding(向量化查询)
   ↓
向量检索(pgvector,Top-K + 阈值过滤)
   ↓
拼接 Prompt(系统提示 + 检索结果 + 用户问题)
   ↓
LLM 生成回答
   ↓
流式输出给用户
1
2
3
4
5
6
7
8
9
10
11
12
13

# 6.3 文档分块(Chunking)

知识库文档不能整篇向量化(超出 token 限制、检索精度差),需要分块:

  • token-aware 分块:按 token 数切分(项目采用)
  • 固定字符分块:按字符数切分
  • 语义分块:按段落/句子切分

项目配置:块大小直接影响检索精度,通过 knowledgeBaseId 前置过滤确保多知识库场景下的检索隔离。

# 6.4 Query Rewrite(查询改写)

用户提问可能口语化、指代不清("那它怎么用?"),直接向量化检索效果差。Query Rewrite 让 LLM 先把查询改写成更规范、更完整的检索语句。

# 6.5 RAG 幻觉问题

幻觉:模型一本正经胡说八道,明明没有相关知识也编造答案。

项目防御手段:

  1. Query Rewrite 改写用户意图
  2. 动态 Top-K 矩阵(短/中/长 Query 差异化配置)
  3. 短查询阈值提升至 0.25
  4. 无结果时返回标准化话术,不让模型自由发挥

# 七、Advisor 链(Spring AI)

# 7.1 什么是 Advisor

Spring AI 的 Advisor 类似于 Servlet Filter,可以在 LLM 调用前后插入自定义逻辑:

  • 调用前:修改 prompt、注入上下文、记录指标
  • 调用后:处理输出、重试、转换格式

# 7.2 项目的 Advisor 链设计

4 种 Advisor 通过 yml 独立开关,按场景构建不同 ChatClient 注册不同 Advisor 组合:

业务场景 Advisor 组合 工具集
简历分析 A + B 工具集 1
模拟面试 A + C 工具集 2
知识库问答 A + D 工具集 3
评估打分 B + D 工具集 4

优势:新增场景无需硬编码,只需 yml 配置组合。

# 八、AI 工程化的核心原则

# 8.1 异步化

LLM 调用耗时 10-15s,绝不能放在请求链路和数据库事务内。项目用 Redis Stream 异步消费:

用户请求 → 立即返回任务ID(200ms)
              ↓
        Redis Stream 投递任务
              ↓
        Consumer 异步消费 → 调用 LLM → 落库
              ↓
        前端轮询/订阅任务状态
1
2
3
4
5
6
7

# 8.2 防御性编程

LLM 输出不稳定,每一层都要兜底:

  • 输出层:JSON 解析失败 → 修复型重试
  • 流式层:断线 → 保存已接收部分
  • 评估层:部分批次失败 → 降级输出部分报告
  • 向量化层:单批失败 → 错误隔离,不影响整体

# 8.3 Prompt 注入防护

用户输入直接拼入 prompt 是高危操作,攻击者可以"劫持"模型行为。项目三层纵深防御:

  1. 输入净化:正则匹配 4 种注入模式并清除
  2. 系统提示词加固:追加 UUID 动态分隔符,明确区分"系统指令"与"用户输入"
  3. 输出护栏:检测"顺从短语"(如"好的,我会..."),拦截被劫持的输出

# 8.4 成本控制

LLM 调用按 token 计费,必须控制冗余调用:

  • 简历重复上传:基于 SHA-256 文件哈希在解析前拦截
  • 多轮面试去重:已考知识点注入 prompt 避免重复出题
  • 限流:Redis + Lua 多维度滑动窗口限流

# 九、概念速查表

概念 一句话解释 项目中的位置
LLM 概率性输出引擎,不稳定 所有 AI 调用
Token 文本最小单位,计费依据 上下文控制
上下文窗口 单次能处理的 token 上限 分批评估
Prompt 给模型的输入指令 prompts/*.st
RISEN 框架 Prompt 设计框架 评估模块
结构化输出 让模型返回 JSON StructuredOutputInvoker
SSE 流式输出协议 面试流式对话
Embedding 文本转向量 知识库向量化
pgvector PG 向量检索扩展 知识库存储
HNSW 向量图索引 知识库索引
RAG 检索增强生成 知识库问答
Query Rewrite 查询改写 RAG 检索
幻觉 模型编造内容 RAG 防御
Advisor LLM 调用拦截器 Advisor 链
分块 文档切分 知识库入库

# 十、推荐学习资料

  • Spring AI 官方文档:https://docs.spring.io/spring-ai/reference/ (必看,版本更新快,二手博客容易过时)
  • pgvector GitHub:https://github.com/pgvector/pgvector
  • OpenAI Cookbook:理解 LLM 通用概念(Prompt、Embedding、RAG)
  • 阿里 DashScope 文档:本项目使用的 LLM 服务商

⚠️ 学习 AI 相关内容优先看官方文档,Spring AI 1.0 才正式发布,2.0 API 变化大,网上大部分教程可能已过时。

上次更新: 2026/11/4 11:30:46
项目开发笔记
学习计划

← 项目开发笔记 学习计划→

Theme by Vdoing | Copyright © 2023-2026 沉梦听雨 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式