把 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 的核心依赖。

<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 文档在哪

直接看代码:

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。

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 的可用工具列表

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

完整的引擎初始化

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 时自动压缩

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

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

场景一:查订单

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 前

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

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

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 不是一次把所有工具都调一遍,而是根据上下文的实际需要动态决定调用顺序。

场景三:多轮会话

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:

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,标明格式、范围、是否必填。

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

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

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

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

.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,原来可以这么简单。


参考文档:

posted @ 2026-07-07 13:02  带刺的坐椅  阅读(41)  评论(0)    收藏  举报