上周有个做了8年Java的朋友问我:你转AI了,还用Java吗?
我说,用。不仅用,Java在我整个Agent系统里是编排核心。但LLM调用层,我交给了Python。
他愣了一下:那你不嫌麻烦?两套语言,两套部署,两套配置?
我理解他的困惑。做Java的人天然倾向于"一个技术栈搞定一切"——Spring Boot能调API吧?能发HTTP吧?那LLM不就是个API调用?为什么还要拉Python进来?
但做了两周Agent之后,我的判断很明确:不是Java做不到,是Python生态在LLM层没有平替。而Java在业务编排层,Python同样替代不了。 这不是选边站,是职责分离。
Python在LLM层的三个杀手锏
1. 结构化输出:instructor + Pydantic
Agent调LLM不是拿一段自由文本就完事。我的系统里,选题推荐要返回候选列表+评分+理由,草稿生成要返回大纲+正文+标题候选。这些都是强Schema约束的JSON。
Python有instructor这个库,直接把Pydantic模型绑到LLM调用上:
-
LLM返回的JSON自动校验成Pydantic对象
-
校验失败自动重试(可配重试次数和策略)
-
支持嵌套模型、枚举约束、字段描述注入prompt
Java里你要自己写JSON解析 + 校验 + 重试循环,而且LLM输出的JSON格式不稳定是常态——漏字段、类型错、枚举值拼错——这些边界情况instructor帮你全兜了,Java里你得自己兜。
2. 模型路由:litellm
我的Agent需要在不同模型间切换——qwen-plus做日常选题,qwen-max做深度草稿,未来可能接入GPT-4o做质检。litellm统一了调用接口:
-
传
openai/qwen-plus走阿里云,传openai/gpt-4o走OpenAI -
自动处理不同provider的参数差异(streaming、function calling格式)
-
内置token计数、错误重试、fallback链
-
一个字符串切换模型,业务代码零改动
Java里没有等价方案。你可以用Spring AI的ChatClient做模型抽象,但在provider路由、自动重试、多模型fallback这块,成熟度和litellm差距明显。
3. 提示工程工具链
Python生态的langchain + instructor + pydantic组合,做提示工程非常自然:
-
Prompt模板管理 + 变量注入
-
Few-shot样例绑定到Pydantic模型
-
输出格式约束(JSON Schema → Pydantic → 自动校验)
Spring AI在追赶,PromptTemplate + BeanOutputConverter已经有基础能力,但在结构化输出的易用性和可靠性上,Python生态领先至少一个身位。
一句话:Python在"跟LLM对话"这件事上,工具链优势是碾压级的。不是Java做不到,是你要自己造的轮子太多,而且造出来还不圆。
Java在业务编排层的三个硬实力
但反过来,为什么不让Python把活全干了?
1. 有状态业务流程编排
一次内容生成要经过:接收请求→校验→鉴权→查人设→调Python选题→调Python生成→RAG检索→质检→拼装响应→记审计。每一步都有业务逻辑,每一步都可能回滚或降级。
Spring Boot的依赖注入、事务管理、异常体系,处理这种流程编排比FastAPI成熟得多。尤其是"草稿→审核→确认→发布"这种多步骤流程,Java的状态管理是Python难以替代的。
2. 类型安全与配置管理
6年Java做下来的直觉:能在编译期发现的问题,绝不留到运行期。DTO字段名、接口参数、枚举值——Java的类型系统帮你在编译期就拦住不一致。Python的动态类型在LLM调用层是优势(灵活适配各种模型输出),但在业务层是负债(字段名拼错、类型不匹配,运行时才爆)。
3. 团队经验复用
这是最现实的原因。我6年Java经验,做业务编排闭着眼写。用Python写编排层,光是依赖注入和配置管理就要踩一堆坑。技术选型不只看语言能力,还要看团队经验。Java团队做Agent,编排层留在Java是最高效的选择。
架构总览
plaintext
┌──────────────────────────────────────────────────────┐
│ 前端 (Vue3) │
└──────────────────────┬───────────────────────────────┘
│ HTTP
┌──────────────────────▼───────────────────────────────┐
│ Java 编排层 (Spring Boot) │
│ ┌──────────┬──────────┬──────────┬───────────────┐ │
│ │ 鉴权 │ 参数校验 │ 人设查询 │ 流程编排 │ │
│ │ 权限控制 │ DTO约束 │ 配置管理 │ 状态管理 │ │
│ └──────────┴──────────┴──────────┴───────────────┘ │
│ ┌──────────┬──────────┬──────────┬───────────────┐ │
│ │ RAG调度 │ 质检 │ 审计日志 │ 发布管理 │ │
│ └──────────┴──────────┴──────────┴───────────────┘ │
└──────────────────────┬───────────────────────────────┘
│ HTTP (内部接口)
┌──────────────────────▼───────────────────────────────┐
│ Python 推理层 (FastAPI + litellm) │
│ ┌──────────┬──────────┬──────────┬───────────────┐ │
│ │ 选题推荐 │ 草稿生成 │ 人设DNA │ 结构化输出 │ │
│ │ LLM路由 │ 模型切换 │ 分析 │ (instructor) │ │
│ └──────────┴──────────┴──────────┴───────────────┘ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ litellm (模型路由层) │ │
│ │ openai/qwen-plus openai/qwen-max ... │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────┬───────────────────────────────┘
│
┌──────────────────────▼───────────────────────────────┐
│ Milvus (向量检索) + Redis + MySQL │
└──────────────────────────────────────────────────────┘
核心原则:Java拥有业务状态,Python拥有模型调用。
Python不存业务数据,不维护session,不做权限判断——它是无状态的计算服务。Java不碰模型调用细节,不指定模型名,不管理token——LLM的全部复杂性封装在Python内部。
职责边界对照
维度
Java编排层
Python推理层
状态
有状态(session、任务队列、审核流程)
无状态(纯计算服务)
鉴权
✅ 拥有
❌ 不关心
LLM调用
❌ 不碰
✅ 拥有
模型选择
❌ 不管
✅ litellm路由
结构化输出
❌ 不处理
✅ instructor校验
RAG调度
✅ 触发检索、拼装结果
✅ 执行向量查询
审计日志
✅ 全链路记录
❌ 只返回结果
配置管理
✅ 人设、平台、发布规则
✅ 模型、API Key、向量库
部署
独立进程
独立进程
扩缩容
按业务并发
按LLM调用QPS
边界清晰,才能各改各的。Java改业务逻辑不影响Python,Python换模型不影响Java。如果边界模糊——Java硬编码模型名,或者Python缓存用户状态——改一边就炸另一边。
双语言架构最大的坑:契约不一致,而且静默失败
双语言架构最可怕的不是"两套代码要维护",而是两边对同一件事的理解不一样,但系统不会报错。
我踩过一个典型案例:Python侧的LLM调用配置和Java侧的配置变量名不一致。Java配了一个名字,Python认的是另一个名字。两边都没报错——Python侧LLM调用失败后,自动回退到启发式逻辑继续跑,输出了看起来"还行"的结果。
我用了好几天才发现LLM根本没被调通,选题推荐一直是硬编码在兜底。
这种问题在单体应用里不可能发生。 同一个进程、同一份配置,变量名不对编译就报错。但双语言架构里,Java和Python各管各的配置,两边的"契约"全靠人肉对齐。对不齐的时候,系统不会红警,只会悄悄降级。
这暴露了双语言架构的一个本质问题:Agent系统的失败模式是"静默降级",不是"显式报错"。 LLM调用失败 → 回退启发式 → 输出看着还行 → 你以为链路通了其实没通。传统微服务里接口挂了返回500,你立刻知道。Agent系统里,失败被静默消化了。
我的解法:在链路里加可观测性标记。 每次生成的结果里带一个usedLlm字段——true表示LLM真正参与了,false表示回退兜底。这样不用看日志,光看输出就知道链路是否通畅。比任何自动化测试都直观。
契约管理的长期解法
目前这块还是靠人肉保证的。我在思考的长期解法:
-
接口契约文件化 — Java和Python共享一份接口定义(JSON Schema或OpenAPI),两边从同一份定义生成DTO/Model
-
CI校验 — 每次MR自动比对Java DTO和Python Pydantic Model的字段一致性,不一致就红灯
-
集成测试 — 端到端测试不只校验"输出对不对",还校验"LLM是否真正被调用了"(检查usedLlm标记)
第3点最关键——传统的集成测试只测"功能对不对",Agent系统的集成测试还要测"链路有没有被静默降级"。
给Java同行的建议
如果你也是Java背景在做Agent:
不要试图用Java做所有事。 LLM生态在Python,硬用Java做结构化输出和模型路由,你会在工具链上花比业务逻辑更多的时间。不如把LLM调用层交给Python,Java做编排——你6年的Spring Boot经验在编排层是碾压级优势。
但也不要全交给Python。 业务编排、权限控制、状态管理——这些是Java的主场。Python做这些事你要重新学一套最佳实践,而且你团队的Java经验在编排层可以直接复用。
从第一天就把契约管理做好。 双语言架构的失败通常不是因为"某个语言不行",而是"两边对接口的理解不一致"。建立明确的接口契约,比事后补文档有效一百倍。
在链路里加可观测性标记。 任何可能静默降级的地方,都要有标记让外部能感知到。usedLlm这种字段比任何日志都有用——你一眼就知道这次生成是LLM做的还是兜底逻辑。
关注我,少走3个月弯路