这个月面了 12 个人,一个 offer 都没发
7 月我们团队要扩两个人,我前后面了 12 个候选。八年以上经验、带过项目、简历上都写着"熟悉大模型应用开发"。
结果不太好看。问"RAG 怎么做的",八个人能完整背出切分、向量化、召回、重排这条链路;再问"你怎么验证召回效果变好了",六个人答不上来。有两个说得上来,但一说具体数字就开始含糊。
我不是想吐槽候选人。真正让我反思的是:我们挂在招聘网站上的 JD,和他们答不上来的那部分,其实是同一套东西——我们也不知道自己到底要什么。
这篇是我和团队复盘之后整理的,关于 AI 时代 Java 架构师的能力到底变了什么。入行第八年,我现在负责 AI 平台和中间件,这些是过去半年的真实体会。
先说清楚:大部分核心能力没变
这两年"AI 时代架构师"这类标题太多了,容易给人一种"以前的东西都作废了"的错觉。我不同意。
我们团队今年做的三件最硬的事:Agent 幂等体系、推理服务网关、知识库版本管理。拆开看,用到的核心技术是幂等设计、Saga 补偿、连接池与超时治理、索引与版本控制、成本归因。这些全是我 2019 年做交易系统时就在写的东西,一个字没过时。
真正的变化在于:这些"旧"能力的权重被抬高了,同时新增了三项"新"能力。
新增能力一:不确定性工程
这是我认为最本质的变化。
传统后端系统的行为是确定的。输入 A,输出 B,错了就是 bug,能复现,能修。AI 系统不是——同样的输入,明天可能给出不一样的输出,而且没有"错"这个明确概念,只有"好一点"和"差一点"。
这意味着一整套工程方法要重建。最典型的是评测。我们以前写代码有单元测试,断言是 assertEquals;现在要写的是"评分",是概率意义上的好。
// 我们内部的评测框架,核心就这一个接口
public interface Evaluator {
/** 返回 0~1 的分数,以及打分的理由(理由很重要,用于人工抽检) */
Score evaluate(Case c, Prediction p);
}
// 一个简单的组合评测器
Evaluator rag = Evaluator.compose(
new FaithfulnessEvaluator(llm), // 答案是否忠于召回内容,权重最高
new RelevanceEvaluator(llm), // 答案是否回答了问题
new CitationHitEvaluator(), // 引用的文档片段对不对(规则判定,便宜)
weight(0.4, 0.3, 0.3)
);
光有打分还不够,你得知道分数变化是不是显著的。我见过太多次"改了个 prompt,评测分从 87.2 涨到 88.1,上线!"——640 条样本的评测,1 个百分点的差异基本在噪声范围内。
// 加了 bootstrap 重采样,算置信区间
EvalResult r = runner.run(evaluator, cases, Repetition.of(5));
System.out.printf("score=%.3f CI95=[%.3f, %.3f] n=%d%n",
r.mean(), r.ciLow(), r.ciHigh(), r.size());
// 改动前后对比,用配对检验而不是直接比均值
Diff d = Diff.pairedTest(before, after);
if (!d.significant(0.05)) {
System.out.println("差异不显著,别上线");
}
这个东西花了我两周才想明白。之前我们上过一次 prompt 改动,评测涨了 0.9 分,线上用户投诉率涨了 1.4 个百分点。事后看,那 0.9 分纯粹是随机波动。
不确定性工程的另一面是降级与兜底设计。模型不可用、超时、输出格式不合规、内容审核拦截,这些不再是异常路径,是每天都会发生的主路径。我们的 Agent 网关里,兜底逻辑的代码量已经超过正常路径。
新增能力二:成本与性能的量化建模
以前我们优化系统,目标是 QPS、延迟、机器数量。现在多了一个维度:token。
这个维度很讨厌,因为它不由你控制。QPS 是外部给的,token 消耗是模型自己决定的,而模型的行为会随 prompt、随上下文、随温度漂移。
我的做法是建一个成本模型,把每个 Agent 的单次调用成本拆开,并且要能归因到业务。
public final class CostModel {
/** 单次调用成本,单位:元 */
public BigDecimal estimate(Request r) {
int in = r.systemTok() + r.toolDefTok() + r.historyTok() + r.ragTok();
int out = r.expectedOutputTok();
BigDecimal input = price.inputPerMToken(r.model()).multiply(bd(in)).divide(M);
BigDecimal output = price.outputPerMToken(r.model()).multiply(bd(out)).divide(M);
// 缓存命中部分按折扣价,这一项经常被人忘掉
BigDecimal cached = input.multiply(bd(r.cacheHitRatio()))
.multiply(ONE.subtract(CACHE_DISCOUNT));
return input.subtract(cached).add(output)
.add(rerankCost(r))
.add(guardCost(r))
.add(vectorSearchCost(r));
}
}
有了这个模型,架构评审会上很多争论会自动消失。上个月有同学提议给客服 Agent 加一层"意图澄清"的多轮确认,我算了一下:日均 300 万次调用,每轮多花 ¥0.004,一个月 ¥3.6 万,而它能挽回的错误率大约 0.3%。折合每条有效挽回成本 ¥0.4。这个数字摆出来,大家一致决定先只在高风险意图(退款、改地址)上开。
这个能力在面试里最好验证,也最容易暴露问题。我现在必问的一题是:"给你一个日均 100 万次调用的 Agent,单次成本 2 毛,老板要求降一半,你怎么拆?"答得上来的人,通常做过真实项目。
新增能力三:与模型协作的"接口设计"能力
这条有点抽象,我举个例子。
我们有个工具是"查询用户的订单列表"。第一版返回完整的 JSON,20 个字段,一条订单 380 token。Agent 拿到之后经常抓不住重点,或者把 JSON 里的技术字段(比如 statusEnum)直接念给用户。
后来改了返回格式:
// 改前:直接把 DO 序列化
{"orderNo":"SO20260628009","statusEnum":3,"gmtCreate":"2026-06-28T10:22:11Z", ...}
// 改后:面向模型的可读格式 + 结构化锚点
[1] 订单 SO20260628009 保温杯(蓝色) x1 ¥129.00 已发货 06-28 下单 可申请退款
token 从 380 降到 62,同时模型引用订单号的准确率从 84% 提到 97%。
这件事的本质是:模型是一个新的"调用方",它有和人类、和其他服务都不同的接口偏好。它喜欢紧凑、语义完整、有明确锚点的文本,不喜欢嵌套 JSON 和枚举数字。
我们现在给所有 Agent 专用的 API 都定了一条规矩:返回体里必须有一行"人(和模型)能直接读懂"的摘要,结构化字段作为补充放在后面。
哪些旧能力反而更值钱了
列三个我感受最深的。
并发与异步编程。虚拟线程在 JDK 21 落地、结构化并发在 JDK 25 转正之后,写高并发 Agent 编排的门槛降了很多,但"哪些任务能并行、并行的结果怎么聚合、失败怎么取消"这些判断反而更关键了。我们一个典型的 Agent 请求要并发调 6~9 个外部服务,用结构化并发写起来是这样的:
try (var scope = StructuredTaskScope.open(
Joiner.<Result>allSuccessfulOrThrow(),
cfg.withTimeout(Duration.ofSeconds(8)))) {
Subtask<Result> order = scope.fork(() -> orderSvc.query(uid));
Subtask<Result> coupon = scope.fork(() -> couponSvc.query(uid));
Subtask<Result> risk = scope.fork(() -> riskSvc.score(uid));
scope.join();
return assemble(order.get(), coupon.get(), risk.get());
}
超时自动取消所有子任务,不用自己管 thread pool 和 future 泄漏。这段代码在 JDK 21 里要写 40 行,现在 8 行。工具简化了,设计判断没有简化。
数据一致性与事务。我在另一篇里写过 Agent 幂等和 Saga,那套东西完全是传统分布式事务的思维。今年我们出的最严重的一次事故(重复退款 1.29 万),根因就是团队里没人意识到工具调用是 RPC。
可观测性。OpenTelemetry GenAI 语义约定出来之后,AI 链路的追踪终于有了标准。但"往哪个 span 上挂 token 数""怎么把一次对话的 12 个 span 串成一棵可读的树"这类设计,还是老本行。
学习方法:我这半年怎么跟上来的
说说具体做法,不一定适合所有人。
读源码,但只读一个。今年上半年我把 Spring AI 的核心代码过了一遍,大概 2.3 万行,主要集中在 ChatClient、ToolCallingManager、VectorStore 这三块。LangChain4j 和 Embabel 我只跑了 demo,没读源码。深度理解一个框架的设计取舍,比泛泛了解三个框架有用得多——框架之间的思想是可以迁移的,细节不是。
做可量化的小实验。我给自己定的规矩是每周至少做一个能出数字的验证。比如"工具定义从 41 个收敛到 19 个,token 降多少、漏选率多少"。这些数字积累半年,就是团队的知识库。我们内部文档现在有 60 多页是这类实验记录,新人入职直接看这个。
写下来。我这个博客写了三年,最大的收益不是流量,是逼自己把模糊的想法写成清晰的文字。很多时候我以为自己懂了,一写就发现逻辑有洞。
保持怀疑。今年看了太多"XX 框架颠覆一切"的文章。我的做法是:任何声称的效果,我都自己跑一遍。上个月有篇文章说某种 chunking 策略能提升召回 15%,我们用自己 640 条的数据集跑,提升 2.3%,且置信区间包含 0。
明确不建议做的
- 不要把时间花在 prompt 技巧上。什么"加上'深呼吸'能提升准确率"这类,收益不可复现,也无法沉淀成团队资产;
- 不要追新框架。今年上半年我数了一下,Java AI 领域新出的框架和工具超过 20 个。我们只用 Spring AI 一个,够用;
- 不要为了用 AI 而用 AI。我们团队今年砍掉了两个 Agent 项目,因为算完账发现规则引擎就能解决,还更可靠。砍掉它们省下来的钱比做的项目赚的多。
写在后面
现在回头看,《AI 时代 Java 架构师的能力模型》本身不算多难,难的是线上真出问题那十分钟里的判断。经验都是这么来的。