把 OpenAPI 接入 Agent Harness:零代码让 Agent 听懂你的 REST API

引言------为什么需要让 AI 听懂 REST API

在过去两年里,我和团队接手了好几个「上了年纪」的 Java 项目。这些系统有一个共同特征:业务逻辑完整,API 齐全,Swagger 文档也写得规规矩矩,但前端换了一波人,文档却没人维护了。新来的同事要查一个订单接口,得翻好几个 YAML 文件,再对着代码比对字段名。

更要命的是,当我们打算在这些系统上引入 AI 能力时,遇到了一个非常实际的矛盾------LLM 不理解 REST API

大语言模型固然强大,但它在推理时无法凭空知道你的订单系统有哪些接口、每个接口的入参是什么、响应结构长什么样。让它去查询订单状态,它可能会胡编一个接口路径出来。传统做法是为每个接口写工具函数(Tool),注册到 Agent 里。这个思路没错,但当你面对一个拥有上百个 REST 端点的系统时,一个一个写 Tool 注册------成本太高了。

Solon AI Harness 的 addApiServer 就是为了解决这个问题而生的。它能从 OpenAPI(Swagger)描述文件或文档 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 的核心依赖。

xml 复制代码
<dependency>
    <groupId>org.noear</groupId>
    <artifactId>solon-ai-harness</artifactId>
    <version>${solon.version}</version>
</dependency>

这里 ${solon.version} 请替换成你项目实际使用的 Solon 版本,建议使用 v4.0.2 以上。Harness 本身依赖了 LLM 调用层、Agent 会话管理和 Tool 调度框架,不需要额外引入其他库。

实现------用 addApiServer 构建 AI 引擎

构建 HarnessEngine

Harness 的入口是 HarnessEngine,我们需要给它配置三样东西:

  1. 模型:Agent 背后的大脑,负责理解用户意图并决定调用哪个工具
  2. 工作目录:Harness 存放会话和缓存的工作区
  3. API 源:告诉 Harness 你的 OpenAPI 文档在哪

直接看代码:

java 复制代码
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 Map<String, 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。

java 复制代码
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 会自动做以下几件事:

  1. 拉取 OpenAPI 文档(支持 JSON 和 YAML,兼容 OpenAPI 3.0 / 3.1 / Swagger 2.0)
  2. 解析每个路径、方法、参数、请求体和响应结构
  3. 自动生成对应的 Tool 描述和参数签名
  4. 注册到 Agent 的可用工具列表

整个过程完全不需要你写一行工具代码。二十个接口如此,两百个接口也是如此。

完整的引擎初始化

java 复制代码
public static HarnessEngine createEngine() {
    AgentSessionProvider sessionProvider = new AgentSessionProvider() {
        private final Map<String, 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)
        .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 时自动压缩

实战------用自然语言查询订单和库存

引擎搭好了,来看实际对话效果。

场景一:查订单

java 复制代码
public void demoQueryOrder() {
    HarnessEngine engine = createEngine();
    String question = "帮我查一下订单 20250320001 的物流状态,看看今天能不能到。";
    var result = engine.prompt(question).call();
    System.out.println(result.getContent());
}

引擎收到这个 prompt 后,内部工作流程如下:

  1. 大模型判断意图属于「查询订单物流」
  2. 从 OpenAPI 生成的 Tool 列表中选中对应的 GET /api/orders/{orderId}/logistics
  3. LLM 自动提取参数 orderId = "20250320001"
  4. Harness 组装 HTTP 请求
  5. 拿到响应 JSON 后,LLM 把结构化数据转成自然语言回答

输出类似:

复制代码
订单 20250320001 的物流状态如下:

- 承运方:顺丰速运
- 运单号:SF1234567890
- 当前状态:运输中
- 最新节点:2025-03-20 14:30,已到达【上海市浦东新区分拨中心】
- 预计送达:今天(3月21日)18:00 前

目前看今天能到,建议您保持电话畅通。

场景二:查库存 + 组合意图

java 复制代码
String question = "SKU 88002 还有库存吗?如果缺货的话,什么时候能补上?";
var result = engine.prompt(question).call();

Harness 会自动判断需要调用 GET /api/inventory/{sku} 这个接口。如果 OpenAPI 中还有一个补货计划接口,初次查询发现库存为 0,LLM 会主动发起第二次工具调用查询补货计划,然后整合给出回答。

复制代码
SKU 88002(商品:极简蓝牙耳机)当前库存为 0。

根据补货计划,下一批预计在 3 月 25 日到货,数量 500 件。建议您开启到货提醒。

LLM 不是一次把所有工具都调一遍,而是根据上下文的实际需要动态决定调用顺序。

场景三:多轮会话

java 复制代码
public void demoMultiTurn() {
    HarnessEngine engine = createEngine();
    AgentSession session = engine.getSession("order-002");

    var r1 = engine.prompt("帮我查一下订单 20250320001 的物流")
        .session(session).call();
    System.out.println(r1.getContent());

    // Agent 记得上文讨论的是同一个订单
    var r2 = engine.prompt("这个订单什么时候能到?")
        .session(session).call();
    System.out.println(r2.getContent());
}

关键点在于 session 对象------Harness 会把历史消息存到 session 中,LLM 看到上文就知道「这个订单」指的是 20250320001

进阶------注册自定义业务工具补充 OpenAPI

addApiServer 解决了 80% 的通用问题,但总有一些逻辑无法用纯粹的 REST API 表达。比如一个「极速发货」操作,内部涉及:检查支付状态 → 检查库存 → 调拨 → 更新状态 → 发通知,不适合暴露为单个 REST 端点。

这时可以用 extensionAdd 注册自定义 Tool:

java 复制代码
import org.noear.solon.ai.harness.tool.*;

public class ExpeditedShippingTool extends AbstractTool {
    public ExpeditedShippingTool() {
        super("expedited_shipping", "对已支付的订单执行极速发货。需要订单 ID 和发货仓库。");
    }

    @Override
    public ToolResult execute(Map<String, Object> args) {
        String orderId = (String) args.get("orderId");
        String warehouse = (String) args.get("warehouse");
        // 业务逻辑...
        return ToolResult.success("订单 " + orderId + " 已执行极速发货。");
    }
}

// 注册到引擎
engine.extensionAdd((agentName, agentBuilder) -> {
    agentBuilder.defaultToolAdd(new ExpeditedShippingTool());
});

注册后,Agent 的工具列表就变成了「OpenAPI 自动生成的接口」+「自定义 Tool」,LLM 会根据意图自动选择用哪个。

最佳实践

经过多个项目的落地,我总结了几条实用的经验:

1. 文档质量决定 Agent 表现

addApiServer 的输入是 OpenAPI 文档,输出就是 Agent 的工具集。每个接口的 summarydescription 要写清楚,参数要有明确的 description,标明格式、范围、是否必填。

yaml 复制代码
/api/orders/{orderId}:
  get:
    summary: 查询订单详情
    description: 根据订单 ID 获取订单完整信息,包括商品、金额、状态等。
    parameters:
      - name: orderId
        in: path
        required: true
        description: 订单编号,格式为 YYYYMMDD + 6 位流水号
        schema: { type: string }

2. 设置合理的系统提示词

systemPrompt 建议包含:角色定义、能力边界、输出规范、安全约束。

3. 生产环境务必开启沙箱

java 复制代码
.sandboxEnabled(true)

沙箱会限制 Agent 的工具调用范围,防止 LLM 幻觉导致意外调用写接口。

4. 合理使用会话管理

面向最终用户时,建议用 AgentSessionProvider 实现持久化存储。同时设置 compressionThreshold 避免长对话撑爆上下文。

5. 自定义 Tool 粒度适中

一个 Tool 不要做太多事情,否则 LLM 难以理解。反过来,也尽量不要一个接口一个 Tool------既然有 addApiServer 了,没必要重复造轮子。自定义 Tool 只封装那些需要业务逻辑组合或特殊权限控制的操作。

总结

这篇文章从实际问题出发,完整走了一遍 Solon AI Harness 接入 OpenAPI 的流程。

回顾核心要点:

  • addApiServer 是零代码方案,传入 OpenAPI 文档地址,自动生成 Tool,Agent 直接用自然语言调用
  • 引擎配置灵活 ,通过 systemPromptmodelAddsessionProvider 等 API 精确控制 Agent 行为
  • 多轮会话和记忆管理让 Agent 具备上下文感知能力
  • extensionAdd 提供扩展点,当 OpenAPI 覆盖不了业务场景时,可以注册自定义 Tool
  • 最佳实践集中在文档质量、提示词设计、安全沙箱三个方面

在我带团队落地 Harness 的几个项目中,addApiServer 是让大家最「爽」的功能。以前接入一个十几接口的系统,配置 Tool 要半天;现在改两行配置、传一个文档 URL 就搞定了。更重要的是,API 变化时不需要改 Agent 代码------更新 OpenAPI 文档就行了。

如果你手头正有一个带 Swagger/OpenAPI 文档的 Java 系统,强烈建议试试用 Harness 架一层 AI 能力。你会惊讶地发现,让大模型「听懂」你的 REST API,原来可以这么简单。


参考文档:

相关推荐
香蕉鼠片1 小时前
机器学习经典任务
深度学习·ai
Geek-Chow1 小时前
微服务认证与授权:01 — 概念
java·前端·数据库
战场小包1 小时前
AI 写代码越来越快,慢下来的那个人变成了我
人工智能·agent
KobeSacre2 小时前
DelayQueue 源码
java·开发语言
Wang's Blog2 小时前
JavaWeb快速入门: Filter与Listener核心详解
java·filter·listener
zfoo-framework2 小时前
mongodb性能调优 1.从慢查询分析索引 2.掌握索引最左匹配原则!!!
java
AIGS0012 小时前
企业AI落地:从RAG到AgentRAG的技术跃迁
java·人工智能·人工智能ai大模型应用
阿拉斯攀登2 小时前
企业知识库实战:从 0 到 1 落地全记录
agent·ai编程·loop·rag