摘要:Java 程序员转向 AI,不等于从头去卷模型训练,也不等于只会调用一个聊天接口。真正适合大多数后端开发者的方向,是 AI 应用工程:把模型接进业务系统,用 RAG 连接私有知识,用 Tool Calling 执行确定性能力,再用权限、评估、观测和成本控制把 Demo 变成产品。本文给出一条 12 周可执行路线,包含技术地图、Spring AI 代码、RAG 与工具调用示例、项目设计、踩坑复盘和求职作品集建议。
开场:我也被"要不要先学高数"劝退过
很多 Java 程序员第一次认真看 AI,搜索结果通常会把人带到两个极端。
一个极端是算法路线:线性代数、概率论、反向传播、Transformer、分布式训练、CUDA。课程表一打开,像要重新读一遍大学。
另一个极端是"零代码 AI":拖几个节点、写一段提示词,半小时搭出聊天机器人。演示确实能跑,但当你想接公司的用户体系、权限、订单、知识库和审计日志时,很快又会发现只拖节点不够。
我后来才把这件事想明白:AI 行业不是只有模型算法工程师,也需要大量 AI 应用工程师。
算法工程师关注模型怎么训练、怎么微调、怎么优化推理;AI 应用工程师关注模型如何可靠地进入真实业务。两者有交集,但起点、日常工作和评价标准不同。
对于已经做过 Java 后端的人,最现实的转型不是清空技能树,而是在原来的工程能力上增加一层"模型能力":
text
已有能力:Java + Spring Boot + MySQL + Redis + MQ + Docker
新增能力:Prompt + 模型 API + RAG + Tool Calling + Agent + 评估
工程补强:权限 + 观测 + 成本 + 安全 + 人工兜底
所以本文标题里的答案是:
不做算法研究,也能进入 AI 应用开发;但"不学算法"不等于"不懂原理",更不等于只会复制 API 示例。
一、先分清三条路线,别用错别人的学习清单
| 路线 | 主要工作 | 常见核心能力 | Java 后端转型难度 |
|---|---|---|---|
| 模型/算法工程 | 训练、微调、评测、推理优化 | Python、PyTorch、数学、GPU、论文 | 较高,需要系统补课 |
| AI 平台工程 | 模型网关、资源调度、推理服务、数据平台 | 云原生、分布式、GPU、可观测性 | 中等,后端经验可迁移 |
| AI 应用工程 | RAG、Agent、工作流、业务集成、评估 | 后端、模型 API、检索、工具、产品思维 | 最适合多数 Java 开发者 |
这三条路线没有高低之分。问题只在于你的目标是什么。
如果你真的喜欢读论文、训练模型、研究损失函数,那应该走算法路线,数学不能绕开。如果你更擅长理解业务、设计接口、处理并发和数据一致性,那么 AI 应用工程更能复用已有优势。
现实项目里,一个企业知识助手要解决的问题往往是:
- 用户能查哪些文档;
- 文档更新后如何重建索引;
- 回答必须引用哪一版制度;
- 模型超时后如何降级;
- 一次问答花了多少 Token;
- 哪些答案需要人工确认;
- 用户反馈如何进入评估集;
- 服务如何接入已有 SSO、审计和监控。
这些问题没有一道要求你手推梯度,但每一道都需要工程判断。
二、Java 程序员已有的能力,比想象中更值钱
转型时最容易产生的错觉是:"Python 才是 AI,Java 经验没用了。"
如果目标是训练模型,Python 生态确实更主流;但在企业应用层,Java 的价值非常直接。
| 你已有的 Java 能力 | 在 AI 应用中的对应位置 |
|---|---|
| Spring Boot 接口开发 | 模型网关、问答 API、管理后台 |
| MySQL / PostgreSQL | 会话、反馈、文档元数据、评估结果 |
| Redis | 会话缓存、限流、幂等、短期记忆 |
| MQ / 异步任务 | 文档解析、Embedding、批量评测 |
| 权限模型 | 知识库隔离、工具调用授权 |
| 日志与链路追踪 | 模型调用、检索、工具执行的 Trace |
| Docker / CI/CD | Dify、向量库、观测平台部署 |
| 领域建模 | 把模糊的"智能助手"拆成稳定业务能力 |
真正需要补的是模型这一层的特殊性:
- 输出具有概率性,同样输入不一定逐字相同;
- 上下文有长度和成本,不能无限塞资料;
- 模型会生成看似合理但没有证据的内容;
- 工具调用是"模型提出意图,系统执行动作",权限不能交给模型决定;
- 效果不能只靠"我试了一下挺好",需要评估集。
这些差异决定了 AI 应用不是普通 CRUD 加一个接口,但它仍然是工程。
三、AI 应用开发的完整技术地图
我把需要学习的内容分成七层:
#mermaid-svg-numiixutvcmELdx6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-numiixutvcmELdx6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-numiixutvcmELdx6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-numiixutvcmELdx6 .error-icon{fill:#552222;}#mermaid-svg-numiixutvcmELdx6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-numiixutvcmELdx6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-numiixutvcmELdx6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-numiixutvcmELdx6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-numiixutvcmELdx6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-numiixutvcmELdx6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-numiixutvcmELdx6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-numiixutvcmELdx6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-numiixutvcmELdx6 .marker.cross{stroke:#333333;}#mermaid-svg-numiixutvcmELdx6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-numiixutvcmELdx6 p{margin:0;}#mermaid-svg-numiixutvcmELdx6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-numiixutvcmELdx6 .cluster-label text{fill:#333;}#mermaid-svg-numiixutvcmELdx6 .cluster-label span{color:#333;}#mermaid-svg-numiixutvcmELdx6 .cluster-label span p{background-color:transparent;}#mermaid-svg-numiixutvcmELdx6 .label text,#mermaid-svg-numiixutvcmELdx6 span{fill:#333;color:#333;}#mermaid-svg-numiixutvcmELdx6 .node rect,#mermaid-svg-numiixutvcmELdx6 .node circle,#mermaid-svg-numiixutvcmELdx6 .node ellipse,#mermaid-svg-numiixutvcmELdx6 .node polygon,#mermaid-svg-numiixutvcmELdx6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-numiixutvcmELdx6 .rough-node .label text,#mermaid-svg-numiixutvcmELdx6 .node .label text,#mermaid-svg-numiixutvcmELdx6 .image-shape .label,#mermaid-svg-numiixutvcmELdx6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-numiixutvcmELdx6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-numiixutvcmELdx6 .rough-node .label,#mermaid-svg-numiixutvcmELdx6 .node .label,#mermaid-svg-numiixutvcmELdx6 .image-shape .label,#mermaid-svg-numiixutvcmELdx6 .icon-shape .label{text-align:center;}#mermaid-svg-numiixutvcmELdx6 .node.clickable{cursor:pointer;}#mermaid-svg-numiixutvcmELdx6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-numiixutvcmELdx6 .arrowheadPath{fill:#333333;}#mermaid-svg-numiixutvcmELdx6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-numiixutvcmELdx6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-numiixutvcmELdx6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-numiixutvcmELdx6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-numiixutvcmELdx6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-numiixutvcmELdx6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-numiixutvcmELdx6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-numiixutvcmELdx6 .cluster text{fill:#333;}#mermaid-svg-numiixutvcmELdx6 .cluster span{color:#333;}#mermaid-svg-numiixutvcmELdx6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-numiixutvcmELdx6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-numiixutvcmELdx6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-numiixutvcmELdx6 .icon-shape,#mermaid-svg-numiixutvcmELdx6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-numiixutvcmELdx6 .icon-shape p,#mermaid-svg-numiixutvcmELdx6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-numiixutvcmELdx6 .icon-shape .label rect,#mermaid-svg-numiixutvcmELdx6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-numiixutvcmELdx6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-numiixutvcmELdx6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-numiixutvcmELdx6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 业务与产品层
场景、用户、指标、风险
应用编排层
Prompt、Workflow、Agent
上下文层
会话、RAG、记忆、权限
模型接入层
Chat、Embedding、多模态
工具与数据层
API、数据库、搜索、MCP
工程治理层
评估、观测、成本、安全
基础设施层
Docker、向量库、模型网关
很多教程从第四层"调用模型"开始,到模型返回一句话就结束。真正的项目难点集中在第三层和第六层:给模型什么上下文,以及如何证明结果可用。
1. 模型基础:懂输入输出,不急着研究训练
入门阶段至少要理解这些词:
- Token:模型处理文本的基本单位,直接影响上下文和成本;
- Context Window:一次请求能携带的信息上限;
- Temperature:影响输出随机性,但不是"准确率旋钮";
- System/User/Assistant Message:对话中不同来源的消息;
- Structured Output:让结果遵守 JSON Schema 或对象结构;
- Embedding:把文本映射成向量,用于相似度检索;
- Tool Calling:模型生成结构化工具调用意图,由程序执行;
- RAG:先检索外部资料,再基于资料生成回答。
你不需要第一周推导注意力公式,但需要知道模型为什么会"说得像真的"、为什么把整本手册塞进 Prompt 不是好方案。
2. 应用框架:Spring AI 与 LangChain4j 二选一先学
Spring AI 提供 ChatClient、模型接口、向量库、Tool Calling、Advisor 和可观测能力,使用方式比较贴近 Spring 开发者。LangChain4j 也为 Java 提供统一模型 API、AI Services、RAG、Chat Memory 和 Tools。
初学者不必同时精通两个框架。选一个做完项目,再比较抽象差异。框架只是脚手架,重点仍是请求、上下文、检索、工具和评估。
3. 低代码平台:Dify 用来提速,不用来替代工程判断
Dify 很适合快速验证:
- 提示词和变量是否够用;
- 工作流应该分几步;
- 哪种模型适合任务;
- 知识库检索效果如何;
- 产品界面是否有人愿意用。
但上线后仍要考虑账号、权限、数据源、版本、备份和监控。我的建议是:用 Dify 验证流程,用 Java 承接稳定业务边界。
四、第一段可运行代码:用 Spring AI 做一个最小问答接口
下面示例刻意保持简单,具体模型 Provider、版本和密钥配置以所选服务的官方文档为准。依赖版本建议由项目 BOM 统一管理,不要从旧博客复制一个固定版本。
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
配置密钥时使用环境变量:
yaml
spring:
ai:
openai:
api-key: ${AI_API_KEY}
不要把真实密钥写进 application.yml 再提交到 Git。
Controller 可以写成:
java
@RestController
@RequestMapping("/api/ai")
class AiChatController {
private final ChatClient chatClient;
AiChatController(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("""
你是 Java 技术助手。
不确定时必须明确说明,不要编造类名、版本和 API。
给出代码时同时说明验证方式。
""")
.build();
}
@GetMapping("/explain")
Map<String, String> explain(@RequestParam String question) {
String answer = chatClient.prompt()
.user(question)
.call()
.content();
return Map.of("answer", answer);
}
}
这段代码能让你理解最基本的调用链:
text
HTTP 请求 -> 业务服务 -> ChatClient -> 模型 Provider -> 文本结果
但它离生产还很远。至少还缺:
- 输入长度限制;
- 身份认证和速率限制;
- 超时、重试与熔断;
- 敏感内容处理;
- 调用日志与成本统计;
- 输出校验;
- 降级方案。
第一周能跑通接口就够了,不要把 Demo 当产品。
五、第二段关键代码:让模型返回对象,而不是一段"差不多的 JSON"
真实业务更需要结构化结果。例如把客服工单分为 BUG、CONSULT、COMPLAINT,同时给出优先级和摘要。
java
public record TicketResult(
TicketType type,
int priority,
String summary,
boolean needHumanReview) {
}
public enum TicketType {
BUG, CONSULT, COMPLAINT, OTHER
}
调用时明确规则:
java
TicketResult result = chatClient.prompt()
.system("""
你负责工单预分类。
priority 只能是 1 到 5。
涉及退款、账号封禁、法律威胁时,
needHumanReview 必须为 true。
""")
.user(ticketContent)
.call()
.entity(TicketResult.class);
这里仍然不能省略程序校验:
java
void validate(TicketResult result) {
if (result.priority() < 1 || result.priority() > 5) {
throw new IllegalArgumentException("invalid priority");
}
if (result.summary() == null || result.summary().isBlank()) {
throw new IllegalArgumentException("summary is required");
}
}
一个很重要的工程原则是:
模型输出是外部输入,哪怕它长得像 Java 对象,也要按不可信数据处理。
如果分类会直接触发退款、封号或生产变更,模型只能给建议,不能绕过人工或确定性规则。
六、RAG:Java 程序员最值得优先掌握的 AI 场景
RAG 的基本流程并不神秘:
大模型 向量库 Java应用 用户 大模型 向量库 Java应用 用户 #mermaid-svg-pzhLfkqU34GKFGjW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-pzhLfkqU34GKFGjW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pzhLfkqU34GKFGjW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pzhLfkqU34GKFGjW .error-icon{fill:#552222;}#mermaid-svg-pzhLfkqU34GKFGjW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pzhLfkqU34GKFGjW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pzhLfkqU34GKFGjW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pzhLfkqU34GKFGjW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pzhLfkqU34GKFGjW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pzhLfkqU34GKFGjW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pzhLfkqU34GKFGjW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pzhLfkqU34GKFGjW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pzhLfkqU34GKFGjW .marker.cross{stroke:#333333;}#mermaid-svg-pzhLfkqU34GKFGjW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pzhLfkqU34GKFGjW p{margin:0;}#mermaid-svg-pzhLfkqU34GKFGjW .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-pzhLfkqU34GKFGjW text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-pzhLfkqU34GKFGjW .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-pzhLfkqU34GKFGjW .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-pzhLfkqU34GKFGjW .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-pzhLfkqU34GKFGjW .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-pzhLfkqU34GKFGjW #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-pzhLfkqU34GKFGjW .sequenceNumber{fill:white;}#mermaid-svg-pzhLfkqU34GKFGjW #sequencenumber{fill:#333;}#mermaid-svg-pzhLfkqU34GKFGjW #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-pzhLfkqU34GKFGjW .messageText{fill:#333;stroke:none;}#mermaid-svg-pzhLfkqU34GKFGjW .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-pzhLfkqU34GKFGjW .labelText,#mermaid-svg-pzhLfkqU34GKFGjW .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-pzhLfkqU34GKFGjW .loopText,#mermaid-svg-pzhLfkqU34GKFGjW .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-pzhLfkqU34GKFGjW .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-pzhLfkqU34GKFGjW .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-pzhLfkqU34GKFGjW .noteText,#mermaid-svg-pzhLfkqU34GKFGjW .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-pzhLfkqU34GKFGjW .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-pzhLfkqU34GKFGjW .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-pzhLfkqU34GKFGjW .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-pzhLfkqU34GKFGjW .actorPopupMenu{position:absolute;}#mermaid-svg-pzhLfkqU34GKFGjW .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-pzhLfkqU34GKFGjW .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-pzhLfkqU34GKFGjW .actor-man circle,#mermaid-svg-pzhLfkqU34GKFGjW line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-pzhLfkqU34GKFGjW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 提问用问题做相似度检索返回相关文档片段做权限/版本/来源过滤问题 + 证据片段 + 回答规则带引用的答案答案、来源、置信提示
RAG 的难点不是"接一个向量数据库",而是证据治理:
- 文档是否是当前有效版本;
- 不同用户是否能看到不同资料;
- Chunk 是否切断了完整语义;
- 检索到的是"相关"内容还是"可作为答案依据"的内容;
- 没找到资料时,系统是否愿意说不知道;
- 回答里能否展示来源。
以 Spring AI 的思路为例,可以通过 Advisor 把向量检索接入 ChatClient。不同版本的具体 API 可能变化,下面展示的是结构而不是让你机械复制:
java
@Service
class PolicyAssistant {
private final ChatClient chatClient;
PolicyAssistant(ChatClient.Builder builder, VectorStore vectorStore) {
this.chatClient = builder
.defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore).build())
.build();
}
String answer(String tenantId, String question) {
return chatClient.prompt()
.system("""
只能依据检索到的制度回答。
如果证据不足,回答"当前资料无法确认"。
不要把其他租户或过期制度作为依据。
""")
.user(question)
.advisors(advisor -> advisor.param(
"FILTER_EXPRESSION",
"tenant_id == '%s' && enabled == true".formatted(tenantId)
))
.call()
.content();
}
}
生产代码中,租户过滤不能只写在 Prompt 里。Prompt 是行为提示,不是安全边界。真正的权限必须在检索层和数据层执行。
RAG 入门项目的正确顺序
不要第一天就导入十万份 PDF。更好的顺序是:
- 选 20 份结构清晰、你熟悉答案的文档;
- 手写 30 个问题和标准证据;
- 先测纯关键词检索;
- 再加入 Embedding 检索;
- 比较不同 Chunk、TopK 和重排;
- 记录"没找到""找错版本""答非所问";
- 最后才扩大文档量。
没有问题集,你只能凭感觉调 RAG;有了问题集,优化才有方向。
七、Tool Calling:模型负责选择,Java 负责执行
很多人把 Tool Calling 误解成"模型直接调用数据库"。更准确的过程是:
- 程序把工具名称、描述和参数结构告诉模型;
- 模型根据问题生成调用意图;
- Java 程序校验参数和权限;
- 程序执行真实方法;
- 执行结果返回给模型组织语言。
例如查询物流:
java
@Component
class LogisticsTools {
private final LogisticsService logisticsService;
LogisticsTools(LogisticsService logisticsService) {
this.logisticsService = logisticsService;
}
@Tool(description = "根据订单号查询物流,只能用于当前登录用户自己的订单")
LogisticsView queryLogistics(String orderNo) {
String userId = CurrentUser.requiredUserId();
if (!logisticsService.belongsTo(orderNo, userId)) {
throw new AccessDeniedException("order does not belong to current user");
}
return logisticsService.query(orderNo);
}
}
注册工具后,模型可以决定何时请求调用:
java
String answer = chatClient.prompt()
.user("帮我查订单 SO20260725001 到哪里了")
.tools(logisticsTools)
.call()
.content();
注意权限校验写在工具方法里,而不是相信模型会遵守"只能查自己的订单"。模型可能选错工具、填错参数,也可能受到提示注入影响。工具执行层必须像普通 API 一样做认证、授权、参数校验、审计和限流。
对于删除、付款、发消息、改生产配置等动作,还应加入人工确认和幂等机制。
八、Agent:先做固定工作流,再做自主规划
初学者很容易被 Agent 吸引,因为它看起来最像"真正的 AI":自己拆任务、自己调用工具、自己循环。
但越自主,越需要护栏。我的建议是:
text
单次问答
-> 结构化输出
-> 固定工作流
-> 带条件分支的工作流
-> 有限工具调用
-> 最后才是自主 Agent
如果一个业务流程本来就能明确写成:
#mermaid-svg-j6WkrmWzqKFSKm8x{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-j6WkrmWzqKFSKm8x .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-j6WkrmWzqKFSKm8x .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-j6WkrmWzqKFSKm8x .error-icon{fill:#552222;}#mermaid-svg-j6WkrmWzqKFSKm8x .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-j6WkrmWzqKFSKm8x .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-j6WkrmWzqKFSKm8x .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-j6WkrmWzqKFSKm8x .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-j6WkrmWzqKFSKm8x .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-j6WkrmWzqKFSKm8x .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-j6WkrmWzqKFSKm8x .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-j6WkrmWzqKFSKm8x .marker{fill:#333333;stroke:#333333;}#mermaid-svg-j6WkrmWzqKFSKm8x .marker.cross{stroke:#333333;}#mermaid-svg-j6WkrmWzqKFSKm8x svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-j6WkrmWzqKFSKm8x p{margin:0;}#mermaid-svg-j6WkrmWzqKFSKm8x .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-j6WkrmWzqKFSKm8x .cluster-label text{fill:#333;}#mermaid-svg-j6WkrmWzqKFSKm8x .cluster-label span{color:#333;}#mermaid-svg-j6WkrmWzqKFSKm8x .cluster-label span p{background-color:transparent;}#mermaid-svg-j6WkrmWzqKFSKm8x .label text,#mermaid-svg-j6WkrmWzqKFSKm8x span{fill:#333;color:#333;}#mermaid-svg-j6WkrmWzqKFSKm8x .node rect,#mermaid-svg-j6WkrmWzqKFSKm8x .node circle,#mermaid-svg-j6WkrmWzqKFSKm8x .node ellipse,#mermaid-svg-j6WkrmWzqKFSKm8x .node polygon,#mermaid-svg-j6WkrmWzqKFSKm8x .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-j6WkrmWzqKFSKm8x .rough-node .label text,#mermaid-svg-j6WkrmWzqKFSKm8x .node .label text,#mermaid-svg-j6WkrmWzqKFSKm8x .image-shape .label,#mermaid-svg-j6WkrmWzqKFSKm8x .icon-shape .label{text-anchor:middle;}#mermaid-svg-j6WkrmWzqKFSKm8x .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-j6WkrmWzqKFSKm8x .rough-node .label,#mermaid-svg-j6WkrmWzqKFSKm8x .node .label,#mermaid-svg-j6WkrmWzqKFSKm8x .image-shape .label,#mermaid-svg-j6WkrmWzqKFSKm8x .icon-shape .label{text-align:center;}#mermaid-svg-j6WkrmWzqKFSKm8x .node.clickable{cursor:pointer;}#mermaid-svg-j6WkrmWzqKFSKm8x .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-j6WkrmWzqKFSKm8x .arrowheadPath{fill:#333333;}#mermaid-svg-j6WkrmWzqKFSKm8x .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-j6WkrmWzqKFSKm8x .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-j6WkrmWzqKFSKm8x .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-j6WkrmWzqKFSKm8x .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-j6WkrmWzqKFSKm8x .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-j6WkrmWzqKFSKm8x .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-j6WkrmWzqKFSKm8x .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-j6WkrmWzqKFSKm8x .cluster text{fill:#333;}#mermaid-svg-j6WkrmWzqKFSKm8x .cluster span{color:#333;}#mermaid-svg-j6WkrmWzqKFSKm8x div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-j6WkrmWzqKFSKm8x .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-j6WkrmWzqKFSKm8x rect.text{fill:none;stroke-width:0;}#mermaid-svg-j6WkrmWzqKFSKm8x .icon-shape,#mermaid-svg-j6WkrmWzqKFSKm8x .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-j6WkrmWzqKFSKm8x .icon-shape p,#mermaid-svg-j6WkrmWzqKFSKm8x .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-j6WkrmWzqKFSKm8x .icon-shape .label rect,#mermaid-svg-j6WkrmWzqKFSKm8x .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-j6WkrmWzqKFSKm8x .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-j6WkrmWzqKFSKm8x .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-j6WkrmWzqKFSKm8x :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
读取工单
分类
高风险?
进入人工队列
检索知识库
生成回复草稿
规则校验
人工发送
那就先用固定工作流。固定流程更容易测试、计费、审计和回滚。只有当任务路径确实无法预先枚举,而且模型自主选择工具能带来明显收益时,才考虑 Agent。
Agent 的核心不是"能跑起来",而是:
- 最大步骤数;
- 最大 Token 和费用;
- 可用工具白名单;
- 每个工具的权限;
- 失败重试与停止条件;
- 人工确认点;
- 全链路 Trace。
九、12 周学习路线:每周必须有产物
下面这条路线按每周 8~12 小时设计。时间不是硬标准,重点是每一阶段都要交付可以演示和验证的东西。
| 周次 | 学习重点 | 必须产出的东西 |
|---|---|---|
| 第 1 周 | Token、消息、参数、上下文、幻觉 | 模型调用命令与错误记录 |
| 第 2 周 | Prompt、结构化输出、输出校验 | 工单分类 API |
| 第 3 周 | Spring AI 或 LangChain4j | 流式聊天后端 |
| 第 4 周 | 会话、限流、超时、重试 | 带 Redis 会话的聊天服务 |
| 第 5 周 | Embedding、Chunk、向量检索 | 20 份文档的小知识库 |
| 第 6 周 | RAG、引用、过滤、重排 | 带来源的制度问答 |
| 第 7 周 | Tool Calling、权限、幂等 | 只读订单查询助手 |
| 第 8 周 | Dify Workflow / Chatflow | 同一场景的低代码原型 |
| 第 9 周 | Agent、停止条件、人工确认 | 有限步骤的工单助手 |
| 第 10 周 | 评估集、准确率、拒答率 | 50 道题的自动评测报告 |
| 第 11 周 | Trace、成本、延迟、安全 | 调用看板和数据脱敏方案 |
| 第 12 周 | Docker、文档、演示、复盘 | 可部署作品集项目 |
第 1~2 周:不要追新模型,先把输入输出搞清楚
目标是能够解释一次请求为什么成功、为什么失败。练习错误处理、超时、限流和结构化输出。把每次失败记录下来,比收藏模型排行榜更有用。
第 3~4 周:把模型接进一个正常的 Java 服务
加入 Controller、Service、配置、异常、测试和日志。你要证明自己做的是服务,不是命令行截图。此时可以补充流式输出,但不要为了"打字机效果"牺牲错误处理。
第 5~6 周:专心做 RAG
学习文档解析、切片、Embedding、向量检索、元数据过滤、TopK、重排和引用。每改一个参数,都用固定问题集比较,而不是凭肉眼感觉。
第 7~9 周:从工具调用到工作流
先做只读工具,再做需要确认的写操作。用 Dify 快速验证节点设计,再把关键权限和业务能力留在 Java 服务中。Agent 必须设置步数和费用上限。
第 10~12 周:补齐"能上线"的能力
学习评估、Trace、Token 统计、错误样本回放、提示注入防护和部署。一个能展示失败案例与改进记录的项目,比只展示成功对话更有说服力。
十、推荐做的三个项目,难度逐级上升
项目 1:Java 项目文档问答助手
资料只选 README、接口文档和部署手册。功能包括:
- 文档上传与版本;
- 语义检索;
- 回答引用;
- 无证据拒答;
- 用户反馈;
- 管理员查看失败问题。
这个项目能覆盖 RAG 基础,同时不会被复杂业务拖垮。
项目 2:售后工单智能分流
输入一条工单,输出分类、优先级、摘要和是否人工处理。之后检索知识库,生成回复草稿。
重点不是生成文案,而是:
- 结构化输出校验;
- 高风险规则兜底;
- 人工审核;
- 分类混淆矩阵;
- Prompt 版本对比。
项目 3:企业订单查询 Agent
让用户用自然语言查询订单、物流和退款进度。只开放只读工具,并按登录用户做权限过滤。
项目亮点可以包括:
- 工具白名单;
- 参数校验;
- 操作 Trace;
- 超时与降级;
- 多轮上下文;
- 恶意提示测试。
这三个项目都不需要训练模型,但足够体现 AI 应用工程能力。
十一、最常见的 8 个学习误区
误区 1:先把所有数学补完再开始
没有项目,数学知识很难建立落点。应用开发可以先跑通模型、RAG 和工具调用,再根据问题补相似度、概率和评估知识。
误区 2:只会聊天界面,不会 API
会用聊天产品不等于会开发 AI 应用。至少要理解鉴权、请求、流式响应、错误码、超时、成本和数据策略。
误区 3:框架 API 背得很熟,却说不清 RAG
框架版本会变,流程和权衡更稳定。面试时能解释为什么要切片、过滤、重排和拒答,比背方法名更重要。
误区 4:知识库能回答就算完成
必须测试错误版本、权限隔离、无答案问题和提示注入。只演示命中的问题,无法证明系统可靠。
误区 5:把 Prompt 当全部技术
Prompt 很重要,但真实效果还取决于上下文、检索、工具、模型、数据、评估和产品交互。
误区 6:一上来就做全自动 Agent
固定工作流还没跑稳,就让模型自主循环,问题只会更难定位。先确定性,后自主性。
误区 7:只追求"本地免费"
本地模型减少了部分 API 成本,但增加硬件、运维和效果验证成本。隐私、性能、质量和维护要一起算。
误区 8:作品集只放截图
好的作品集应该有架构图、启动方式、测试集、指标、已知限制和失败复盘。对工程岗位来说,这些比一张聊天截图有价值。
十二、到底需要学多少 Python 和算法
Python:建议会读、会改、会写小脚本
即使主项目用 Java,Python 仍然常用于:
- 数据清洗;
- 批量评测;
- 文档处理;
- 调模型 SDK;
- 运行开源示例。
不必先学到 Web 框架和复杂异步,但要能处理 JSON、CSV、HTTP 和基本包管理。
数学:先理解,再按岗位加深
应用工程入门阶段至少理解:
- 向量和余弦相似度大概表示什么;
- 精确率、召回率为什么会权衡;
- 平均值之外为什么还看 P95 延迟;
- 抽样评估为什么可能有偏差;
- 随机性为什么影响复现。
如果后续转模型工程,再系统补线性代数、概率统计、微积分和优化。学习顺序应该服务目标,而不是制造入场焦虑。
十三、判断自己是否入门的清单
如果下面问题能独立回答大半,你已经不是"只会调 API":
- 能否解释模型、Prompt、RAG、Tool Calling、Agent 的边界?
- 能否让模型稳定返回 Java 对象并做校验?
- 能否设计一个带引用和拒答的 RAG?
- 能否在检索层执行租户权限,而不是只写 Prompt?
- 能否为工具调用加入认证、幂等和人工确认?
- 能否建立 50 条左右的评估集?
- 能否记录一次请求的模型、Prompt、检索、工具、耗时和 Token?
- 能否说明什么时候该用 Dify,什么时候该写 Java?
- 能否展示三个失败样本以及如何改进?
- 能否把项目用 Docker 启动并写清部署步骤?
总结:转型不是推倒重来,而是把后端能力向前延伸
普通 Java 程序员转 AI 应用开发,最值得利用的是已经拥有的工程底座。你知道接口要鉴权、数据库要建模、消息要幂等、服务要监控、变更要回滚。这些经验不会因为模型会生成代码而贬值,反而会成为 AI Demo 与真实产品之间的分水岭。
一条务实的路线是:
text
模型 API
-> 结构化输出
-> Java 服务化
-> RAG
-> Tool Calling
-> 固定工作流
-> 有限 Agent
-> 评估、观测、安全和部署
不要等"全部学会"才开始。选一个你熟悉的业务问题,用两周做出第一个能验证的小项目;再用十周把它补成一个有权限、有测试、有指标、有复盘的作品。
AI 应用工程不是算法路线的简化版,而是一条独立的工程路线。真正的门槛从来不是会不会背 Transformer,而是能不能让一个不确定的模型,在确定的业务系统里安全、稳定、可验证地工作。
/guides/knowledge-base/retrieval)