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 统一封装:
- 解析:从模型输出中提取 JSON(兼容 markdown 包裹、前后多余文字)
- 校验:反序列化为目标对象,校验字段
- 修复型重试:失败时把错误原因注入 prompt,让模型自修复后重试
- 指标跟踪:接入 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
]
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 → 期望的证据找全了没?(找全/找了一半/一个没找)
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 生成回答
↓
流式输出给用户
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 幻觉问题
幻觉:模型一本正经胡说八道,明明没有相关知识也编造答案。
项目防御手段:
- Query Rewrite 改写用户意图
- 动态 Top-K 矩阵(短/中/长 Query 差异化配置)
- 短查询阈值提升至 0.25
- 无结果时返回标准化话术,不让模型自由发挥
# 七、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 → 落库
↓
前端轮询/订阅任务状态
2
3
4
5
6
7
# 8.2 防御性编程
LLM 输出不稳定,每一层都要兜底:
- 输出层:JSON 解析失败 → 修复型重试
- 流式层:断线 → 保存已接收部分
- 评估层:部分批次失败 → 降级输出部分报告
- 向量化层:单批失败 → 错误隔离,不影响整体
# 8.3 Prompt 注入防护
用户输入直接拼入 prompt 是高危操作,攻击者可以"劫持"模型行为。项目三层纵深防御:
- 输入净化:正则匹配 4 种注入模式并清除
- 系统提示词加固:追加 UUID 动态分隔符,明确区分"系统指令"与"用户输入"
- 输出护栏:检测"顺从短语"(如"好的,我会..."),拦截被劫持的输出
# 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 变化大,网上大部分教程可能已过时。