为什么需要让 AI 听懂 REST API 在过去两年里我和团队接手了好几个「上了年纪」的 Java 项目。这些系统有一个共同特征业务逻辑完整API 齐全Swagger 文档也写得规规矩矩但前端换了一波人文档却没人维护了。新来的同事要查一个订单接口得翻好几个 YAML 文件再对着代码比对字段名。更要命的是当我们打算在这些系统上引入 AI 能力时遇到了一个非常实际的矛盾——LLM 不理解 REST API。大语言模型固然强大但它在推理时无法凭空知道你的订单系统有哪些接口、每个接口的入参是什么、响应结构长什么样。让它去查询订单状态它可能会胡编一个接口路径出来。传统做法是为每个接口写工具函数Tool注册到 Agent 里。这个思路没错但当你面对一个拥有上百个 REST 端点的系统时一个一个写 Tool 注册——成本太高了。Solon AI Harness 的 addApiServer 就是为了解决这个问题而生的。它能从 OpenAPISwagger描述文件或文档 URL 中自动读取 API 定义自动为每个端点生成 Tool 签名Agent 直接通过自然语言调用。零代码让大模型听懂你的 REST API。这篇文章我会用一个完整的订单系统场景带你从头把 OpenAPI 接入 Harness。读完你就能在自己的项目里用起来。场景描述——一个遗留订单系统的窘境假设我们有一个典型的电商订单系统叫 order-service。它暴露了大概二十几个 REST 接口涵盖订单 CRUD、库存查询、物流跟踪、售后处理等模块。这个系统有几样「遗产」接口文档存在一个内网地址 http://internal-api-doc/order-service/v3/api-docs基于 OpenAPI 3.0代码没有全部迁移到微服务部分逻辑还是单体团队想引入 AI 助手但没人力给每个接口写 Agent Tool这个窘境在不少 Java 项目里都出现过——API 定义得很规范但 AI/LLM 不认识它们。我们需要做的是把 Harness 引擎架设在系统前面告诉它「这是一份 OpenAPI 文档」然后 Agent 就能听懂「帮我查一下订单 20250320001 的物流状态」这样的自然语言请求。添加依赖第一步在 pom.xml 中加入 Solon AI Harness 的核心依赖。org.noear solon-ai-harness ${solon.version} 这里 ${solon.version} 请替换成你项目实际使用的 Solon 版本建议使用 v4.0.2 以上。Harness 本身依赖了 LLM 调用层、Agent 会话管理和 Tool 调度框架不需要额外引入其他库。实现——用 addApiServer 构建 AI 引擎构建 HarnessEngineHarness 的入口是 HarnessEngine我们需要给它配置三样东西模型Agent 背后的大脑负责理解用户意图并决定调用哪个工具工作目录Harness 存放会话和缓存的工作区API 源告诉 Harness 你的 OpenAPI 文档在哪直接看代码import org.noear.solon.ai.harness.HarnessEngine;import org.noear.solon.ai.harness.permission.ToolPermission;import org.noear.solon.ai.chat.ChatConfig;import org.noear.solon.ai.agent.session.AgentSessionProvider;import org.noear.solon.ai.agent.session.InMemoryAgentSession;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;public class OrderAiEngine {public static HarnessEngine create() { // 会话提供者 AgentSessionProvider sessionProvider new AgentSessionProvider() { private final MapString, AgentSession sessionMap new ConcurrentHashMap(); Override public AgentSession getSession(String instanceId) { return sessionMap.computeIfAbsent( instanceId, k - InMemoryAgentSession.of(k) ); } }; // 构建引擎 HarnessEngine engine HarnessEngine.of(work, ./harness-home) .systemPrompt(你是一个订单系统 AI 助手。 你可以查询订单、查询库存、跟踪物流、处理售后。 使用 OpenAPI 工具时按照文档说明传递参数。 如果用户意图不明确主动追问确认。) .sessionProvider(sessionProvider) .modelAdd(new ChatConfig().then(slf - { slf.setApiUrl(https://api.openai.com/v1); slf.setApiKey(sk-xxxxxx); slf.setModel(gpt-4o-mini); })) .sandboxEnabled(true); return engine; }}几个关键点HarnessEngine.of(workspace, harnessHome) 需要两个目录参数workspace 定位工作目录harnessHome 放配置和框架产物systemPrompt 用来约束 Agent 行为会注入到每次对话的系统消息中生产环境建议开启 sandboxEnabled(true)避免 Agent 执行危险操作接入 OpenAPI——addApiServer 的核心用法现在是最关键的一步让 Harness 理解你的 REST API。import org.noear.solon.ai.harness.source.ApiSource;// 在引擎上注册 OpenAPI 源engine.addApiServer(new ApiSource().then(s - {s.setDocUrl(“http://internal-api-doc/order-service/v3/api-docs”);s.setApiBaseUrl(“http://order-service.internal:8080”);}));就这么简单。ApiSource 通过 setDocUrl 指定 OpenAPI 文档地址setApiBaseUrl 指定实际调用的 Base URL。Harness 会自动做以下几件事拉取 OpenAPI 文档支持 JSON 和 YAML兼容 OpenAPI 3.0 / 3.1 / Swagger 2.0解析每个路径、方法、参数、请求体和响应结构自动生成对应的 Tool 描述和参数签名注册到 Agent 的可用工具列表整个过程完全不需要你写一行工具代码。二十个接口如此两百个接口也是如此。完整的引擎初始化public static HarnessEngine createEngine() {AgentSessionProvider sessionProvider new AgentSessionProvider() {private final MapString, AgentSession sessionMap new ConcurrentHashMap();Overridepublic AgentSession getSession(String instanceId) {return sessionMap.computeIfAbsent(instanceId, k - InMemoryAgentSession.of(k));}};HarnessEngine engine HarnessEngine.of(work, ./harness-home) .systemPrompt(你是一个订单系统 AI 助手。 你可以查询订单、查询库存、跟踪物流。 始终使用 OpenAPI 工具获取实时数据。 如果参数不完整询问用户补充。) .sessionProvider(sessionProvider) .modelAdd(new ChatConfig().then(slf - { slf.setApiUrl(https://api.openai.com/v1); slf.setApiKey(sk-xxxxxx); slf.setModel(gpt-4o-mini); })) .sandboxEnabled(true) .memoryEnabled(true) .maxTurns(10) .compressionThreshold(30, 30_000) .addApiServer(new ApiSource().then(s - { s.setDocUrl(http://internal-api-doc/order-service/v3/api-docs); s.setApiBaseUrl(http://order-service.internal:8080); })); return engine;}memoryEnabled(true)Agent 在多轮对话中记住上下文maxTurns(10)限制对话最大轮次compressionThreshold(30, 30_000)超过 30 条消息或 30000 token 时自动压缩实战——用自然语言查询订单和库存引擎搭好了来看实际对话效果。场景一查订单public void demoQueryOrder() {HarnessEngine engine createEngine();String question “帮我查一下订单 20250320001 的物流状态看看今天能不能到。”;var result engine.prompt(question).call();System.out.println(result.getContent());}引擎收到这个 prompt 后内部工作流程如下大模型判断意图属于「查询订单物流」从 OpenAPI 生成的 Tool 列表中选中对应的 GET /api/orders/{orderId}/logisticsLLM 自动提取参数 orderId “20250320001”Harness 组装 HTTP 请求拿到响应 JSON 后LLM 把结构化数据转成自然语言回答输出类似订单 20250320001 的物流状态如下承运方顺丰速运运单号SF1234567890当前状态运输中最新节点2025-03-20 14:30已到达【上海市浦东新区分拨中心】预计送达今天3月21日18:00 前目前看今天能到建议您保持电话畅通。场景二查库存 组合意图String question “SKU 88002 还有库存吗如果缺货的话什么时候能补上”;var result engine.prompt(question).call();Harness 会自动判断需要调用 GET /api/inventory/{sku} 这个接口。如果 OpenAPI 中还有一个补货计划接口初次查询发现库存为 0LLM 会主动发起第二次工具调用查询补货计划然后整合给出回答。SKU 88002商品极简蓝牙耳机当前库存为 0。根据补货计划下一批预计在 3 月 25 日到货数量 500 件。建议您开启到货提醒。LLM 不是一次把所有工具都调一遍而是根据上下文的实际需要动态决定调用顺序。场景三多轮会话public void demoMultiTurn() {HarnessEngine engine createEngine();AgentSession session engine.getSession(“order-002”);var r1 engine.prompt(帮我查一下订单 20250320001