最近 LLM 应用的开发非常火热,许多后端团队都开始接到任务,要开发所谓的 AI “Harness” 项目,比如内部 RAG 知识库,或是能调用内部 API 的智能助理。
随之而来的第一个问题就是技术选型:是选用 LangChain4j、Spring AI 这类新兴框架,享受开箱即用的便利?还是直接用 OpenAI、Anthropic 的原生 SDK,自己来搭骨架?
如果这个问题没想清楚,轻则项目开发过程会很别扭,重则上线后发现,后续的扩展和维护都成了大包袱。这篇文章就来帮你算清楚这笔账。如果你也面临类似选择,建议收藏一下,文末附的决策清单能帮你少走很多弯路。
在讨论技术选型前,我们先统一一下“行话”。AI “Harness”这个词没有官方定义,它在工程领域通常指用于“驾驭”和“约束”核心组件的支撑系统。在 AI 应用开发中,Harness 是围绕大模型(LLM)这台“发动机”的配套设施,包括:
- Prompt 管理:组织、拼接、管理和版本化提示词。
- 上下文管理:维护多轮对话的记忆。
- 知识检索(RAG):从向量数据库、ES 等知识库中获取数据,提供给模型。
- 工具调用(Tool Calling):让模型调用预定义的函数或 API 来执行任务。
- 结果解析与校验:确保模型输出符合预期的格式(如 JSON)且内容合规。
LLM 自身只是“大脑”,Harness 则是连接它与现实世界的“躯干和神经系统”。
框架的吸引力
处理前面提到的那些工作时,Spring AI 和 LangChain4j 这类框架的出现,就像是及时雨。它们的核心理念可以概括为“封装”。
例如,要连接向量数据库,框架提供了统一的 VectorStore 接口。无论是 Chroma、Milvus 还是 PGVector,切换数据库基本只需修改配置。想实现 RAG?框架也已经封装好了“读取-拆分-向量化-存储-检索”的完整流程。在 Spring AI 里,甚至只用一个 @AiService 注解,就能将接口方法变成与 LLM 交互的 Agent。
这感觉就像玩乐高,只要照着图纸拼,很快就能搭出一个不错的模型。在需要快速验证想法(PoC),或开发标准化应用(如简单的文档问答机器人)时,框架的效率优势非常明显。
框架的代价
这种便利的代价,是你放弃了部分控制权。框架这套“乐高”积木用起来方便,但也定下了规矩:你只能用它提供的积木,并按它指定的方式拼接。“约定优于配置”的另一面是,一旦需求超出“约定”范围,麻烦就来了。
- 抽象泄漏 (Leaky Abstraction):框架封装了 OpenAI 调用,但如果 OpenAI API 增加一个实验性新参数,而框架没更新,你就用不了。同理,你想实现一套精细的、基于业务状态的动态重试逻辑,但框架的
RetryTemplate模块不支持,你就得绕路,有时甚至要修改框架源码。 - 调试黑盒:一个简单的调用,背后可能经过了框架内十几个对象的传递和处理。一旦出问题,整个
Stack Trace会非常深,让你难以定位问题源于你的代码、框架 Bug 还是底层模型的行为。 - 性能与定制:层层封装必然带来性能损耗,对毫秒级响应的场景可能无法接受。当你想精细化操作 Prompt 或实现复杂的非线性推理流程(如思维树),框架提供的“链式”或“序列”模型反而成了束缚。
原生 SDK 的优势
聊完框架的“坑”,另一条路是回归本质,直接用官方的最小化 SDK,比如 openai-java。这么做的核心是“少即是多”。
用原生 SDK,项目里就没有“魔法”了。每次调用大语言模型(LLM),你都得自己构建 ChatCompletionRequest 对象,设置 model、messages、tools 等参数再发送。这样做的好处很明显:
- 完全透明可控:你能控制发给模型的每个 token,API 支持的任何参数都能用。底层的 HTTP 细节,如超时、代理,也可以通过配置
OkHttpClient来精细调整。 - 性能最优:你的代码和 LLM API 之间只隔着一层薄薄的 SDK,本质上就是
HttpClient加上 JSON 序列化和反序列化。没有多余的抽象层,也就没有性能损耗。 - 调试简单:出了问题,看看请求日志和响应报文,基本就能定位。调用栈清晰,责任边界明确。
- 架构自由:上层应用(Harness)完全由你设计,用什么 Prompt 模板引擎、设计什么 Agent 工作流、怎么做状态管理,都由你决定,为未来的复杂需求留足了扩展空间。
代码见真章
用原生 SDK 发起 Function Calling 的伪代码,能让你直观感受到这种“掌控感”。
// 1. 定义你的工具(Java 函数)
@Tool(name = "get_weather", description = "查询指定城市的天气")
public WeatherResponse getWeather(WeatherRequest request) {
// ... 调用天气服务API
return new WeatherResponse(request.getCity(), "晴朗", "25℃");
}
// 2. 在你的服务中,构建请求
public String chatWithFunctionCalling(String userInput) {
// 从注解中提取工具定义,转换成 OpenAI 需要的格式
List<ChatCompletionTool> tools = ToolExtractor.extract(this.getClass());
// 构建请求,这里的一切都由你精确控制
ChatCompletionRequest request = ChatCompletionRequest.builder()
.model("gpt-4-turbo")
.messages(List.of(new ChatMessage(ChatMessageRole.USER, userInput)))
.tools(tools) // 把你的工具告诉模型
.toolChoice("auto") // 让模型自己决定是否调用工具
.build();
// 3. 发起调用,获得响应
ChatCompletionChoice choice = openAiApi.createChatCompletion(request).getChoices().get(0);
// 4. 解析响应,决定是直接回答,还是调用工具
// ... 这里是你的核心业务逻辑,比如检查 choice.getFinishReason() 是否为 TOOL_CALLS
// 如果是,就解析 tool_calls,执行本地 Java 函数,然后把结果再发给模型进行总结
// ...
}
不妨对照检查一下你项目里的具体实现。很多“偶现”问题,根源就是不清楚框架在背后做了哪些“默认”操作。而使用原生 SDK,所有步骤都必须由你亲手写明(explicit),一切都清清楚楚。
选择原生 SDK,意味着要当“手艺人”,而不是“装配工”。框架处理好的底层工作,现在都得自己动手。
需要从头构建:
- 编排层 (Orchestration):如何实现循环,让模型能连续调用多个工具?怎么处理工具的并行调用?当一个 Agent 调用另一个 Agent,调用链如何设计?
- 状态管理 (State Management):在复杂的 Agent 交互中,如何持久化并传递上下文、思考过程及工具调用结果?用简单的
Map,还是需要更健壮的状态机? - 通用能力抽象:以 RAG 流程为例,需要自己写代码连接向量数据库,处理文档加载和切分。这些重复的轮子,现在必须自己再造一遍。
这条路对团队的架构设计和抽象能力要求更高。如果团队习惯了在框架内填空,直接切换过来,项目初期可能会很混乱,产出大量难以维护的“胶水代码”。
决策清单
到底该怎么选?这不是一个非黑即白的问题。下次开技术评审会时,可以直接用下面的问题来引导团队做出更理性的判断。这篇文章也适合转给你的技术负责人或同事一起讨论。
1. 项目目标:“快”还是“好”?
- 要快速上线或验证产品(PoC, MVP):首选框架。用框架快速搭起架子,跑通核心功能,先生存下来。
- 想打造战略级或核心产品:认真考虑原生 SDK。把核心交互逻辑抓在自己手里,方便以后长期做深入优化和扩展。
2. 需求复杂度:“标准”还是“奇葩”?
- 需求很标准(如:简单问答、文档总结、格式转换):框架很合适。这些是框架的长处。
- 需求高度定制(如:复杂 Agent 协作、自定义推理逻辑、对延迟或 Token 成本敏感):用原生 SDK。用框架反而处处受限。
3. 团队能力:“全栈”还是“专精”?
- 团队熟悉 Spring 全家桶,但缺从零设计系统的经验:可以从
Spring AI上手,过渡平滑,学习成本低。 - 团队有资深架构师,成员设计能力强:果断选原生 SDK,让他们放手去设计更贴合业务的架构。
如何选择:框架 vs. 原生 SDK
选择使用框架还是原生SDK,取决于你对社区生态和项目风险的不同考量。
如果你想紧跟社区潮流,利用生态红利,那么选择 Spring AI 这样的框架是合理的。它背靠强大的 Spring 生态,整合能力强,发展前景看好。
反之,如果你更担心项目因框架停止维护或发展跑偏而被“套牢”,那么原生SDK会是更稳妥的选择。毕竟,无论上层框架如何变化,底层的API通常会保持稳定。
混合模式:框架与原生的结合
经验丰富的团队,通常不会只选框架或原生其中一种,而是采用混合模式。
一个典型策略是:用框架处理标准化的“脏活累活”,用原生 SDK 控制“核心环节”。
举个例子:
- RAG 流程中的文档加载和向量检索,标准化程度高,可以用
Spring AI的DocumentReader和VectorStore接口来处理,省去大量样板代码。 - 但拿到检索结果后,构建 Prompt、与 LLM 交互这一步,最好用原生 SDK 实现。因为这部分涉及的 Prompt Engineering、Function Calling、错误处理和重试策略,最需要精细控制。
这种“混合动力”的思路,既能利用框架生态的便利,又能保持关键路径的完全掌控。这体现了资深工程师解决问题的思路:不迷信银弹,只选择最合适的工具组合。
总结
关于 Java AI Harness 的技术选型,记住以下三点:
-
没有银弹:框架和原生 SDK 各有优劣,前者重效率,后者重控制。你需要想清楚,项目现阶段更看重哪一点。
-
动态视角:技术选型并非一成不变。项目初期可以用框架快速启动,当业务变复杂后,再用原生 SDK 逐步替换框架中的“黑盒”部分。
-
核心在人:工具是次要的,团队的能力和认知才是成败关键。优秀的架构师用原生 SDK 也能搭出比框架更好用的系统;反之,不理解业务和技术本质,再好的框架也可能被用得一团糟。
技术文章不能只讲概念,不给方法。希望本文能帮你理清思路。如果觉得有帮助,欢迎点赞、转发给你的同事或团队,相信这份决策清单能为你们的技术讨论带来价值。当然,如果你有不同见解或踩过更深的坑,欢迎在评论区分享。