Administrator
发布于 2026-07-15 / 1051 阅读
7

AI 时代 Java 架构师的能力模型

这个月面了 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 万行,主要集中在 ChatClientToolCallingManagerVectorStore 这三块。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 架构师的能力模型》本身不算多难,难的是线上真出问题那十分钟里的判断。经验都是这么来的。

参考