作为一个写了 7 年 Java 的后端开发,我原本以为 AI 就是调个 API 的事儿。直到我真的动手做了个智能质检系统,才发现坑比 Spring 的注解还多。这篇文章记录了我从"Hello World"到能跟老板吹"我们用了 RAG 架构"的全过程。看完你可能还是不会训练大模型,但至少知道怎么让它不胡说八道了。
一、事情的起因:老板说我们要搞 AI
事情是这样的。
某天晨会,老板突然宣布:"兄弟们,AI 是大趋势,我们也要搞!"
我内心 OS:又来。上次这么说还是 2019 年搞区块链,最后做了个内部积分系统,现在还在跑 cron 任务发积分。
但这次不一样。老板是认真的,而且预算到位了。
于是,我这个写 CRUD 起家的 Java 开发,被推到了 AI 项目第一线。
我的 AI 知识储备当时是这样的:
-
知道 GPT 很火
-
知道 ChatGPT 能写代码(但我不用,怕它把我卖了)
-
知道"transformer"是个深度学习模型(和 Java 的 Transformer 框架没关系)
就这,敢接项目?
嗯,接了。毕竟工资到位,玻璃心碎。
二、第一关:Transformer 到底是什么鬼
2.1 面试时的我
面试官:"请讲讲 Transformer 的原理。"
我(自信满满):"Transformer 是 Spring 的一个模块,用于..."
面试官:"我说的是深度学习那个 Transformer。"
我:"..."
那一刻,我感受到了 35 岁危机的预兆。
2.2 恶补 Transformer
Transformer 架构是 Google 2017 年提出的论文《Attention Is All You Need》的核心。
论文标题翻译成人话:别搞那些复杂的 RNN、LSTM 了,注意力机制就够了。
核心组件有三个:
(1)Self-Attention(自注意力机制)
作用:让模型在处理每个词的时候,能"看"整句话,判断哪些词跟当前词关系最大。
例子:
"小明把球踢进了球门,因为它太猛了"
当模型处理"它"的时候,Self-Attention 会计算:
-
"它" ↔ "球" → 关联度高 ✅
-
"它" ↔ "小明" → 关联度低
-
"它" ↔ "球门" → 关联度低
代码理解版(伪代码,别抄):
# Self-Attention 简化版
def self_attention(query, key, value):
# 计算相似度
scores = query @ key.T / sqrt(d_k)
# 归一化
weights = softmax(scores)
# 加权求和
output = weights @ value
return output
人话总结:就是让每个词跟句子里所有词"握手",握得紧的就是相关词。
(2)Multi-Head Attention(多头注意力)
为什么需要:单个 Self-Attention 只能捕捉一种关系,但语言里关系是多维的。
类比:
-
一个人读书只看字面意思
-
8 个人同时读——一个看语法、一个看逻辑、一个看情感……把 8 个人的发现合在一起,理解就更全面
代码(还是伪代码):
# Multi-Head = 多个 Self-Attention 并行
def multi_head_attention(q, k, v, num_heads=8):
heads = []
for i in range(num_heads):
# 每个头学习不同的注意力模式
head_i = self_attention(q_i, k_i, v_i)
heads.append(head_i)
# 把所有头的结果拼起来
return concat(heads) @ W_o
(3)位置编码(Positional Encoding)
问题:Transformer 是并行处理的,天然丢失了词的顺序。
没有位置编码的后果:
"狗咬了人" 和 "人咬了狗" → 模型觉得一样
解决方案:给每个词加上位置信息(用正弦/余弦函数生成)。
# 位置编码简化版
PE(pos, 2i) = sin(pos / 10000^(2i/d))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d))
人话:就是给每个位置的词生成一个独特的"位置指纹",加到词向量上。
三、第二关:大模型 API 集成
3.1 选哪个模型?
2026 年了,主流大模型有这么几个:
| 模型 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| GPT-4o | 能力强、生态好 | 贵、国内访问不稳定 | 预算充足、海外业务 |
| Claude | 长文本强、安全 | API 配额有限 | 文档分析、长文处理 |
| DeepSeek | 国产、便宜、能力强 | 偶尔抽风 | 国内业务、成本敏感 |
| Qwen(通义千问) | 中文优化、阿里生态 | 英文稍弱 | 中文场景、阿里云用户 |
我们的选择:DeepSeek + Qwen 双备份。
原因:便宜,而且国产模型这几年进步真的很大。
3.2 第一行代码:Hello World
// 别笑,Java 调大模型 API 真的就这么简单
public class LlmClient {
private final HttpClient httpClient = HttpClient.newHttpClient();
private final String apiKey = System.getenv("DEEPSEEK_API_KEY");
public String chat(String prompt) throws Exception {
var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.deepseek.com/v1/chat/completions"))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("""
{
"model": "deepseek-chat",
"messages": [{"role": "user", "content": "%s"}]
}
""".formatted(prompt.replace(""", "\"")))
.build();
var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
return extractContent(response.body());
}
}
测试:
var client = new LlmClient();
var result = client.chat("你好,请用一句话介绍你自己");
System.out.println(result);
// 输出:你好!我是 DeepSeek,一个人工智能助手...
那一刻,我感觉自己是个 AI 工程师了。
(后来才知道,这只是一切的开始)
四、第三关:Prompt Engineering(提示词工程)
4.1 什么是 Prompt Engineering?
定义:就是怎么跟大模型说话,让它听懂人话。
类比:
-
你让实习生干活,说"帮我弄一下那个"vs"把 Q3 销售数据做成 Excel 表格,按地区分组,下班前给我"
-
后者得到的结果肯定更好
大模型也一样:你问得越清楚,它回答得越好。
4.2 Zero-Shot vs Few-Shot
Zero-Shot(零样本)
直接问,不给例子:
问题:这句话的情感是正面还是负面?
"这个手机续航太差了,一天充三次"
模型可能回答:负面 ✅(大多数时候能行)
Few-Shot(少样本)
给几个例子再问:
请将用户评价分类为"正面/负面/中性"。
评价:"服务态度特别好,下次还来" → 正面
评价:"一般般,没什么特别的" → 中性
评价:"等了一个小时才上菜" → 负面
评价:"这个手机续航太差了,一天充三次" →
模型回答:负面 ✅(更稳定)
我们的质检系统用的就是 Few-Shot:
String fewShotPrompt = """
请判断以下客服对话是否违规。违规类型包括:辱骂客户、承诺收益、泄露隐私。
示例 1(违规 - 辱骂客户):
客服:你这个脑子怎么这么笨
判定:违规
原因:辱骂客户
示例 2(违规 - 承诺收益):
客服:这个产品保证年化 20%
判定:违规
原因:承诺不当收益
示例 3(合规):
客服:我帮您查询一下
判定:合规
现在请判断:
客服:%s
判定:
""".formatted(customerServiceText);
效果:从 70% 准确率提升到 92%。
4.3 CoT(Chain of Thought,思维链)
问题:大模型有时候会"跳步骤",直接给答案,容易错。
CoT 的作用:让它"一步一步想"。
对比:
❌ 直接问:
问:一个商店有 23 个苹果,卖出去 7 个,又进货 15 个,还剩多少?
答:31(可能算错)
✅ 用 CoT:
问:一个商店有 23 个苹果,卖出去 7 个,又进货 15 个,还剩多少?
请一步一步思考。
答:
1. 初始:23 个
2. 卖出 7 个:23 - 7 = 16 个
3. 进货 15 个:16 + 15 = 31 个
4. 最终答案:31 个
我们的用法:
String cotPrompt = """
请逐步分析这段客服对话:
1. 识别客服和客户的发言
2. 判断是否有违规话术
3. 如果有,指出具体哪句话违反了哪条规则
4. 给出风险等级判定(high/medium/low)
对话内容:%s
""".formatted(dialogText);
五、第四关:RAG(检索增强生成)
5.1 为什么需要 RAG?
问题:大模型有两大硬伤:
-
知识过时:训练数据有截止日期,不知道最新信息
-
幻觉:会一本正经地胡说八道
例子:
问:2025 年小米汽车 SU7 的销量是多少?
模型:(瞎编一个数字)
RAG 的思路:先"查资料",再"用资料回答"。
类比:
- LLM 单独用 = 考试不让带书,全凭记忆 → 容易瞎写
- RAG = 开卷考试,先翻书找到相关内容,再组织答案 → 准确得多
5.2 RAG 架构
用户提问
↓
① 检索(Retrieval)
问题 → 向量化 → 去向量数据库里找最相关的文档片段
↓
② 增强(Augmented)
把检索到的原文片段 + 用户问题一起拼成 Prompt
↓
③ 生成(Generation)
LLM 基于真实资料生成准确回答
5.3 向量数据库选型
主流选择:
| 数据库 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| Milvus | 开源、功能全、支持多种索引 | 部署复杂、资源消耗大 | 生产环境、大规模数据 |
| FAISS | Facebook 出品、性能强 | 需要自己封装、无持久化 | 原型验证、小规模数据 |
| Pinecone | 托管服务、开箱即用 | 贵、数据出境问题 | 海外业务、预算充足 |
| Chroma | 轻量、易上手 | 功能相对简单 | 个人项目、小团队 |
我们的选择:Milvus(数据量大,需要生产级稳定性)
5.4 代码实现
// 向量化 + 检索简化版
public class RagService {
private final MilvusClient milvusClient;
private final EmbeddingClient embeddingClient;
// 检索相关文档
public List<String> retrieve(String query, int topK) {
// 1. 把问题向量化
float[] queryVector = embeddingClient.embed(query);
// 2. 去 Milvus 里搜最相似的文档
var searchParams = SearchParams.newBuilder()
.withVector(queryVector)
.withTopK(topK)
.withMetricType(MetricType.COSINE)
.build();
var results = milvusClient.search("knowledge_base", searchParams);
// 3. 提取文档内容
return results.stream()
.map(r -> r.getEntity().get("content"))
.toList();
}
// 生成回答
public String answer(String query) {
// 检索相关文档
var docs = retrieve(query, 3);
String context = String.join("\n\n", docs);
// 拼成 Prompt
String prompt = """
请根据以下资料回答问题。如资料中无相关信息,请回复"未找到"。
资料:
%s
问题:%s
回答:
""".formatted(context, query);
return llmClient.chat(prompt);
}
}
效果:幻觉问题大幅减少,回答有据可查。
六、第五关:AI Agent 与 Function Calling
6.1 什么是 AI Agent?
定义:能让大模型调用工具、执行动作的系统。
类比:
-
普通大模型 = 只能说话的顾问
-
AI Agent = 能说话 + 能动手的助理
例子:
用户:帮我查一下张三的合同条款
普通大模型:抱歉,我没有这个信息。
AI Agent:(调用 query_contract 工具)
张三的合同条款如下:...
6.2 Function Calling
原理:定义工具函数,让模型在需要时调用。
我们的质检系统用 Function Calling 实现规则引擎与模型协同:
// 定义工具函数
List<FunctionDefinition> tools = List.of(
FunctionDefinition.builder()
.name("query_customer_policy")
.description("查询客户合同条款")
.parameters(JsonSchema.builder()
.addProperty("customer_id", SchemaType.STRING)
.required(List.of("customer_id"))
.build())
.build(),
FunctionDefinition.builder()
.name("check_risk_level")
.description("根据规则引擎判断风险等级")
.parameters(JsonSchema.builder()
.addProperty("text", SchemaType.STRING)
.required(List.of("text"))
.build())
.build()
);
// 模型调用
ToolCall toolCall = model.chat(messages, tools);
if (toolCall.getName().equals("query_customer_policy")) {
String customerId = toolCall.getArgs().get("customer_id");
Policy policy = policyService.query(customerId);
// 把结果回填给模型继续分析
messages.add(Message.of("tool", policy.toJson()));
var finalAnswer = model.chat(messages, tools);
}
效果:模型不再是"纯聊天",能真正执行业务逻辑。
七、第六关:可观测性与模型评估
7.1 为什么要监控?
问题:
-
模型回答质量怎么评估?
-
用户反馈不好怎么定位问题?
-
成本超支了怎么优化?
答案:监控、监控、还是监控。
7.2 LangSmith
LangSmith是 LangChain 推出的大模型可观测性平台。
功能:
-
追踪每次调用(输入、输出、耗时、成本)
-
评估回答质量(自动打分 + 人工标注)
-
调试 Prompt(对比不同版本效果)
-
成本分析(按模型、按功能统计)
我们没用 LangSmith(预算有限),但自己搞了个简化版:
// 简化的监控埋点
@Component
public class LlmMetrics {
private final MeterRegistry meterRegistry;
// 记录每次调用
public void recordCall(String model, String function, double latencyMs, int tokens) {
meterRegistry.counter("llm.calls.total",
"model", model,
"function", function).increment();
meterRegistry.timer("llm.latency",
"model", model,
"function", function).record(latencyMs, TimeUnit.MILLISECONDS);
meterRegistry.summary("llm.tokens.used",
"model", model).record(tokens);
}
// 记录用户反馈
public void recordFeedback(String callId, int rating, String comment) {
meterRegistry.gauge("llm.feedback.rating",
Tag.of("call_id", callId)).record(rating);
// 评论存到 ES 方便后续分析
}
}
配合 Grafana 看板:
- 每日调用量
- 平均响应时间
- 用户满意度趋势
- 成本分布
八、踩坑总结
8.1 坑 1:Token 计费比想象中贵
预期:一天几百块够了吧?
现实:上线第一天,账单 3000+。
原因:
-
没限制最大 Token 数
-
有些请求被恶意刷
-
重复调用没缓存
解决方案:
-
设置
max_tokens上限 -
加 Redis 缓存(相同问题直接返回)
-
限流(单用户 QPS 限制)
8.2 坑 2:模型会"记住"对话历史
问题:多轮对话时,历史消息会累积,Token 越来越多。
解决方案:
- 设置最大历史轮数(如最近 5 轮)
- 用摘要压缩历史(让模型总结之前的对话)
8.3 坑 3:中文模型对专业术语理解差
问题:金融、法律等专业领域,模型容易理解偏差。
解决方案:
- Few-Shot 里加专业术语解释
- 关键判断走规则引擎,模型只做辅助
8.4 坑 4:向量检索的"语义相似"不等于"业务相关"
问题:向量相似度高,但业务上可能不相关。
例子:
-
用户问"怎么退款"
-
检索到"退款流程说明"(相关)✅
-
也检索到"退款政策历史"(不相关)❌
解决方案:
-
混合检索:向量 + 关键词(BM25)
-
加 Reranker 精排
九、给 Java 开发的 AI 入门建议
9.1 学习路线
第 1 周:调 API(Hello World)
第 2 周:Prompt Engineering(Zero-Shot → Few-Shot → CoT)
第 3 周:RAG 架构(向量化 + 检索 + 生成)
第 4 周:Agent + Function Calling
第 5 周:可观测性与优化
9.2 推荐资源
| 类型 | 资源 | 备注 |
|---|---|---|
| 文档 | LangChain 官方文档 | 有 Python/JS 版本 |
| 课程 | 吴恩达《AI For Everyone》 | 免费,入门友好 |
| 实践 | Hugging Face | 模型、数据集、Demo |
| 社区 | Reddit r/MachineLearning | 跟进最新进展 |
| 中文 | 知乎"大模型"话题 | 国内实践案例 |
9.3 心态建议
- 别被术语吓到:Transformer、Attention、Embedding,都是纸老虎
- 先跑起来再优化:第一版能跑就行,别追求完美
- 承认局限性:大模型不是万能的,该用规则就用规则
- 保持学习:这个领域变化太快,一个月不学就落后了
十、结语
从"Transformer 是 Spring 模块"到能跟老板吹"我们用了 RAG 架构",我走了 3 个月。
最大的收获:AI 不是魔法,是工程。
它需要:
-
清晰的架构设计
-
严谨的代码实现
-
持续的监控优化
-
以及对局限性的清醒认知
最后送大家一句话:
不会被 AI 取代的,是那些会用 AI 的人。
共勉。