小欣AI·服务台

概述

小欣AI·服务台是欣农互联的AI智能体平台,以类 OpenAI Responses API 的形态提供统一的 AI 调用入口。 平台底层适配 DeerFlow、Dify 等多种引擎,对应用暴露一致的 Java 接口,更换引擎只需一行配置。

修改记录

日期变更
2026-06-30新增 Assistants API:XnAiClient.assistants() 提供 create/list/retrieve/modify/delete Agent 管理接口,对齐 OpenAI Assistants API 规范;DeerFlow Provider 内部调 /api/agents 并完成字段映射(instructionssoulasst_{name}name);Dify 侧全方法抛 UnsupportedOperationException
2026-06-25Audio API 文档:新增 audio.transcribe 语音转文字接口、AudioRequest/AudioResponse 参数说明、热词管理(createVocabulary/updateVocabulary/deleteVocabulary/listVocabularies)完整文档与调用示例。
2026-06-25ToolCallItem.status 字段序列化透出(值 "in_progress" / "completed");DeerFlow updates 通道工具调用事件序列对齐 OpenAI 规范:type="ai" 消息含 tool_calls 时立即产出 OUTPUT_ITEM_ADDED + FUNCTION_CALL_ARGUMENTS_DONEtype="tool" 消息到达时产出 OUTPUT_ITEM_DONE(替代原来的单一 OUTPUT_ITEM_ADDED);ask_clarification 降级路径用 content 文本兜底 arguments。
2026-06-24新增语音转文字(STT)能力:XnAiClient.audio() 提供 transcribe() 语音转文字与热词管理接口,对齐 OpenAI Audio API;STT Provider 与 AI Provider 解耦(audio.provider 配置独立选择);首批实现阿里百炼 DashScope Provider,支持异步转写与 Hotword 热词增强。
2026-06-05修复续话(previousResponseId)场景下误返回上一轮的附件与工具调用:DeerFlow 续话时上游会在流开头重放上一轮历史,SDK 不再把其中上一轮的附件(container_file_citation)与工具调用(write_file/present_files 等)当本轮内容下发;续话只携带本轮真实产出,前端无需再自行去重。
2026-06-05可观测性增强:trace 增 sessionId(=会话标识,多轮续话归入 Langfuse Sessions 视图同一 session);流式 TTFT 改用原生 completionStartTime;新增可选配置 release / environment(版本/环境过滤)、sample-rate(采样率)、request-timeout-ms(上报独立超时)。
2026-06-03可观测性按 用户 / 租户 / Agent 三维度统计 token:新增可选 ResponseRequest.builder().userId(str) → Langfuse trace 的 userId(不传则不带、不再用租户编码顶替);租户改以 tag tenant=<code> 表达、Agent 以 tag agent=<name> 表达;trace.name 改用 SDK 方法名(如 Responses.create)。
2026-06-02新增「取消生成」:StreamHandle.awaitResponseId(timeoutMs) + responses().cancel(responseId);明确续话用 getResponseId()(含 threadId)、停止用 awaitResponseId()(含 runId)的取值区别;instructions 透传为 system message;修复同步 getOutputText() 误返回对话标题;品牌名更新。
2026-05-29附件操作与集成(上传 / 下载 / 在线预览,金蝶云苍穹后端 + 前端完整示例)。
2026-05-21新增 StreamHandle.getResponseId()(落库 / 续话);修复 output_text 增量泄漏;API 文档与上线链接优化。

设计原则

概念映射

SDK 概念OpenAI 对应说明
Responses.create()POST /responses创建模型响应(同步聚合 / 流式回调 / SSE 透传)
Responses.retrieve()GET /responses/{id}按 ID 取回历史响应
Responses.delete()DELETE /responses/{id}删除已存储的响应
Files.upload()POST /files上传文件供 Agent 引用
ResponseRequestRequest body请求参数对象,Builder 模式构建,不可变
ResponseResponse object聚合后的完整响应,含 output / usage
ResponseStreamEventSSE event归一化后的流式事件,统一 type 枚举

快速开始

三步:拿到客户端 → 构造请求 → 调用同步或流式接口。

⚠ XnAiClient 是线程安全的应用级单例。它内部持有 OkHttp 连接池、SSE 读取线程池等重量级资源。 每个请求都 new 一个会有显著开销,更严重的是流式调用: 若把 createStream / createStreamAsSse 放进 try (XnAiClient ...) { } 块内, 方法立即返回后外层 client.close() 会关闭线程池,正在后台读流的任务被腰斩 → 流瞬间断开。 正确做法:在 Spring @Bean / ServletContextListener 等容器初始化时构造一次,应用关闭时再 close。
JAVA
import xn.ai.platform.XnAiClient;
import xn.ai.platform.XnAiClientFactory;
import xn.ai.platform.responses.*;

// 应用启动时构造一次(Spring @Bean / ServletContextListener / static 字段)
static final XnAiClient client = XnAiClientFactory.create();

// 业务请求中复用
ResponseRequest req = ResponseRequest.builder()
    .agent("research-assistant")
    .input("用 100 字介绍 RAG")
    .mode("thinking")
    .temperature(0.3)
    .build();

Response r = client.responses().create(req);
System.out.println(r.getOutputText());

流式版本只需把 create 换成 createStream,并用 try-with-resources 阻塞等待:

JAVA
try (StreamHandle h = client.responses().createStream(req, new StreamCallback() {
    public void onEvent(ResponseStreamEvent ev) {
        if (ev.type() == ResponseStreamEvent.Type.OUTPUT_TEXT_DELTA) {
            System.out.print(ev.delta());
        }
    }
    public void onComplete(Response r)   { /* 流正常结束 */ }
    public void onError(Throwable t)    { t.printStackTrace(); }
})) {
    // close() 阻塞等待后台流任务自然结束
}

安装与依赖

MAVEN
<dependency>
  <groupId>com.xnlink</groupId>
  <artifactId>xnai-platform-sdk</artifactId>
  <version>1.0.0-SNAPSHOT</version>
</dependency>

XnAiClientFactory

SDK 客户端工厂,提供三个静态构造入口 + Provider 扩展注册钩子。

方法说明
static XnAiClient create() 零参构造,从 System.getProperty("xn.ai.*") 加载默认前缀配置。最常用。
static XnAiClient create(String prefix) 自定义系统属性前缀(多实例场景,如 xn.ai.tenantA.*)。
static XnAiClient create(XnAiConfig config) 显式传入手动构造的 XnAiConfig(测试用 / 配置中心拉取后回填)。会执行 config.validate()
static void register(String engine, AiClientProvider provider) 注册自定义 Provider。同名注册会覆盖(含覆盖内置实现,便于测试)。
static void unregister(String engine) 取消注册。主要用于测试隔离。

内置 Provider deerflow / dify 通过反射延迟加载,无需手动 register。

XnAiClient

SDK 客户端接口(AutoCloseable)。线程安全,应作为应用级单例使用。

方法说明
Responses responses() Responses 子接口:create / createStream / createStreamAsSse / retrieve / delete
Files files() Files 子接口:upload / list / retrieve / delete / getLimits
void refreshAuth() 显式刷新认证状态。DeerFlow:清空 Cookie + CSRF 后下次请求重新登录;Dify:no-op。凭证轮换或鉴权异常恢复时调用。
Object getNativeApi() 逃生舱口:返回 Provider 特定的原生客户端(DeerflowNativeApi / DifyNativeApi)。 ⚠ 使用即与 Provider 强耦合,引擎切换将失效。仅在 SDK 抽象暂不覆盖的能力时使用。
void close() 释放底层资源(OkHttp 连接池等)。幂等。应用关闭时调用一次即可,不要每请求 close。

XnAiConfig

SDK 配置对象。三种构造方式:

核心字段

字段setter说明
baseUrlsetBaseUrl(String)AI 引擎 base URL,例如 http://127.0.0.1:2026(必填)。
enginesetEngine(String)引擎名:deerflow / dify / 已 register 的自定义名。必填。
timeoutSecondssetTimeoutSeconds(int)请求超时(秒),默认 60。流式建议 ≥1800。
authUsername / authPasswordsetAuthUsername / setAuthPasswordDeerFlow 登录凭证。
authApiKeysputAuthApiKey(agent, key) / setAuthApiKeys(Map)Dify 按 agent 维度的 API Key 映射。
extraputExtra(key, value) / setExtra(Map)Provider 特定扩展配置(KV 字符串,由各 Provider 自行解析)。
retryPolicysetRetryPolicy(RetryPolicy)客户端重试策略。未显式设置时使用 RetryPolicy.defaults();显式传 null 表示禁用重试。

静态工厂方法

方法说明
static XnAiConfig fromSystemProperties()从默认前缀 xn.ai 加载。
static XnAiConfig fromSystemProperties(String prefix)从自定义前缀加载。
static XnAiConfig forTenant(String tenantCode)多租户专用:按 xn.ai.<tenantCode>.* 加载。
static XnAiConfig forTenant(String tenantCode, String basePrefix)自定义基础前缀的多租户加载。
void validate()校验必填字段,create(config) 内部已调用。

系统属性约定

默认前缀 xn.ai(常量 DEFAULT_PREFIX),完整 key 例:

PROPERTIES
xn.ai.base-url=http://127.0.0.1:2026
xn.ai.engine=deerflow
xn.ai.timeout-seconds=1800
xn.ai.auth.username=admin
xn.ai.auth.password=***

可观测性(Langfuse)配置

可选。开启后 SDK 会将每次调用上报到 Langfuse 作链路追踪,与主配置同前缀加载(多租户场景按各自前缀)。未开启则零开销、不影响主流程。

PROPERTIES
# 必填(enabled=true 时缺任一项则自动降级为关闭)
xn.ai.platform.langfuse.enabled=true
xn.ai.platform.langfuse.endpoint=http://langfuse.internal:3000
xn.ai.platform.langfuse.public-key=pk-lf-***
xn.ai.platform.langfuse.secret-key=sk-lf-***
# 可选(异步上报队列调优,给出的是默认值)
xn.ai.platform.langfuse.queue-capacity=1000
xn.ai.platform.langfuse.batch-size=20
xn.ai.platform.langfuse.flush-interval-ms=2000
# 可选(运维 / 成本,给出的是默认值)
xn.ai.platform.langfuse.release=               # 版本号 → trace.release,便于按版本过滤
xn.ai.platform.langfuse.environment=           # 环境标签(prod/staging/dev) → trace.environment
xn.ai.platform.langfuse.sample-rate=1.0        # 采样率 0..1;<=0 视为 1.0(全采)。关闭观测用 enabled=false
xn.ai.platform.langfuse.request-timeout-ms=10000 # 上报请求独立超时,不复用业务长超时

会话维度(Sessions):trace 的 sessionId 取会话标识(DeerFlow threadId / Dify conversation),同一会话多轮续话归到 Langfuse Sessions 视图的同一 session,可按整场对话聚合 token 与调用链。流式 TTFT 以原生 completionStartTime 上报,Langfuse 原生展示首 token 延迟。

调用时通过请求构建器 ResponseRequest.builder().traceId("<业务唯一ID>") 传入的 traceId(如前端透传)会作为 Langfuse trace 的主键:同一 traceId 的多次上报聚合到同一条 trace;该字段仅 SDK 内部消费、不透传给上游 Provider。不传则按内部规则生成。

按 用户 / 租户 / Agent 三维度统计 token:Langfuse trace 会写入以下维度,可在 Dashboard 分别过滤聚合 token 用量:

trace 的 name 为 SDK 方法名(如 Responses.create / Responses.createStream),便于按操作聚合。

多租户

同一进程承载多家租户时,给每个租户一份独立前缀,分别构造 client:

JAVA
// -Dxn.ai.tenantA.baseUrl=... -Dxn.ai.tenantA.engine=deerflow ...
// -Dxn.ai.tenantB.baseUrl=... -Dxn.ai.tenantB.engine=dify     ...

XnAiClient a = XnAiClientFactory.create(XnAiConfig.forTenant("tenantA"));
XnAiClient b = XnAiClientFactory.create(XnAiConfig.forTenant("tenantB"));

完整示例

覆盖三种调用方式:SSE 透传(最常见)、同步调用、流式回调。统一使用 try-with-resources + 按异常类型分发 HTTP 状态码。

1. SSE 透传 + 多轮对话

最常用的集成方式:Controller 接收前端请求,SDK 直接把归一化后的 OpenAI 标准 SSE 字节写入 HttpServletResponse.getOutputStream()input 支持两种形态:简单文本(String)用于单轮,List<Message> 用于自构造多轮历史或多模态输入。

JAVA
// client 在 Spring @Bean 中注入,本类持有引用(不要在方法里 new + close)
@Autowired private XnAiClient client;

@PostMapping(value = "/chat/stream", produces = "text/event-stream;charset=UTF-8")
public void chatStream(@RequestBody ChatRequest body,
                       HttpServletResponse response) throws IOException {

    response.setHeader("Cache-Control", "no-cache");
    response.setHeader("X-Accel-Buffering", "no");
    response.flushBuffer();   // 先把响应头刷出,再开始流式写入

    ResponseRequest req = ResponseRequest.builder()
        .agent(body.getAgent())
        .input(body.hasMessages() ? body.getMessages() : body.getInputText())
        .mode(body.getMode())
        .previousResponseId(body.getPrevId())
        .temperature(body.getTemperature())
        .maxOutputTokens(body.getMaxTokens())
        .store(body.getStore())
        .putMetadata("tenantCode", tenantCode)
        .putMetadata("traceId", traceId)
        .build();

    // ✅ 必须 try-with-resources:close() 阻塞等待后台流任务自然结束
    // 若不阻塞,方法立即返回 → Servlet 容器关闭 response → 后台写流时拿到 IOException
    try (StreamHandle h = client.responses().createStreamAsSse(req, response.getOutputStream())) {
        // getResponseId() 同步返回,与后台 SSE 流并发执行,不阻塞输出
        String responseId = h.getResponseId();
        conversationDao.save(body.getUserId(), responseId);  // 落库,供下轮 previousResponseId
    } catch (AuthenticationException e) {
        response.sendError(401, "AI 平台认证失败,请检查凭证");
    } catch (AuthorizationException e) {
        response.sendError(403, "无权访问该 Agent");
    } catch (RateLimitException e) {
        response.setHeader("Retry-After", String.valueOf(e.getRetryAfterSeconds()));
        response.sendError(429, "请求过于频繁,请稍后重试");
    } catch (NetworkException e) {
        response.sendError(504, "AI 平台连接超时");
    } catch (ValidationException e) {
        response.sendError(400, "请求参数有误: " + e.getMessage());
    } catch (AiPlatformException e) {
        response.sendError(502, "AI 平台异常");
    }
}

// ChatRequest DTO,前端可传 inputText 或 messages
static class ChatRequest {
    String agent;
    String inputText;          // 简单文本:.input(String)
    List<Message> messages;    // 多轮/多模态:.input(List<Message>)
    String mode;
    String prevId;
    Double temperature;
    Integer maxTokens;
    Boolean store;
    boolean hasMessages() { return messages != null && !messages.isEmpty(); }
    // getters/setters ...
}

2. 同步调用

JAVA
import xn.ai.platform.responses.Message;

// 脚本场景下用 try-with-resources(用完即关);Web 服务请用单例 client
try (XnAiClient client = XnAiClientFactory.create()) {

    // ① String 输入 — 单轮
    Response r1 = client.responses().create(
        ResponseRequest.builder()
            .agent("pig-farm-agent")
            .input("帮我分析最近一周的养殖数据")   // String
            .mode("thinking")
            .temperature(0.3)
            .topP(0.9)
            .maxOutputTokens(2000)
            .reasoning(Reasoning.of("medium", null))
            .text(TextConfig.of(null, "medium"))
            .parallelToolCalls(true)
            .truncation("auto")
            .store(true)
            .putMetadata("traceId", traceId)
            .build()
    );

    // ② List<Message> 输入 — 多轮自构造
    List<Message> messages = Arrays.asList(
        Message.user("第一个问题"),
        Message.assistant("上一轮的回复..."),
        Message.user("追问:能详细展开第二点吗?")
    );
    Response r2 = client.responses().create(
        ResponseRequest.builder()
            .agent("pig-farm-agent")
            .input(messages)                     // List<Message>
            .mode("pro")
            .build()
    );

} catch (AuthenticationException e) {
    // 通知管理员更新凭证
} catch (RateLimitException e) {
    // 延迟 e.getRetryAfterSeconds() 秒后重试
} catch (NetworkException e) {
    // 重试或降级
} catch (AiPlatformException e) {
    // e.getProviderName(), e.getRequestId(), e.getHttpStatus()
}

3. 流式回调(需解析事件)

JAVA
// client 为应用级单例(Spring @Bean / static 字段),不要在方法里新建

// ① String 输入 — 必须 try-with-resources 等流自然结束
try (StreamHandle h = client.responses().createStream(
        ResponseRequest.builder()
            .agent("pig-farm-agent")
            .input("帮我分析最近一周的养殖数据")  // String
            .mode("ultra")
            .build(),
        event -> {
            switch (event.type()) {
                case OUTPUT_TEXT_DELTA:
                    sendToFrontend(event.delta());
                    break;
                case RESPONSE_COMPLETED:
                    closeFrontend();
                    break;
                case RESPONSE_FAILED:
                    log.error("流异常: {}", event.response().getError().getMessage());
                    break;
                default:
                    break;
            }
        })) {
    // close() 阻塞等待流任务结束
} catch (AiPlatformException e) {
    log.error("连接失败", e);
}

// ② List<Message> 输入 — 多轮
List<Message> messages = Arrays.asList(
    Message.user("上轮数据有异常波动,帮我分析原因"),
    Message.assistant("波动来自供给端,需求侧平稳。"),
    Message.user("那供给端具体是哪个环节?")
);
try (StreamHandle h = client.responses().createStream(
        ResponseRequest.builder()
            .agent("pig-farm-agent")
            .input(messages)                     // List<Message>
            .mode("pro")
            .build(),
        event -> log.info("{}: {}", event.type(), event.delta()))) {
    // 阻塞至流结束
}

responses.create

POSTResponse create(ResponseRequest request)

同步调用:SDK 内部使用流式协议拉取,聚合所有事件后返回完整 Response

典型参数组合

  • agent + mode + 字符串 input:指定运行模式的一次性问答
  • agent + List<Message>:多轮自构造历史
  • agent + previousResponseId:基于上轮继续
  • agent + 多模态 Content[]:含图 / 文件

抛出

  • ValidationException · agent / input 缺失
  • AuthenticationException · 认证失败
  • RateLimitException · 限流
  • NetworkException · 网络 / 超时
  • ProviderException · 上游其它错误
JAVA
ResponseRequest req = ResponseRequest.builder()
    .agent("research-assistant")
    .mode("pro")               // "flash" / "thinking" / "pro" / "ultra"
    .input("对比 BM25 与向量检索")
    .temperature(0.3)
    .maxOutputTokens(800)
    .store(true)
    .build();

Response r = client.responses().create(req);

responses.createStream

STREAMStreamHandle createStream(ResponseRequest req, StreamCallback cb)

强类型流式调用。SDK 把上游 SSE 归一化为 ResponseStreamEvent,逐个投递给 StreamCallback

StreamCallback

方法触发时机
onEvent(ResponseStreamEvent)每一个归一化事件。
onComplete(Response)流正常结束(RESPONSE_COMPLETED),含最终聚合对象。
onError(Throwable)SDK / 网络层异常,等价于收到 ERROR 事件。

StreamHandle

方法说明
cancel()主动中止流,幂等。前端断开时务必调用,否则占用连接。
close()AutoCloseable,等价于 cancel()
isActive()是否仍在接收事件。
getResponseId() 同步返回本次请求的初始 Response ID(DeerFlow 下含 threadId、runId 可能为空),可立即调用、不阻塞。 续话 / 落库足够(续话只用 threadId);不可用于 cancel(可能缺 runId),取消请用 awaitResponseId()
DeerFlow 返回非 null;Dify 当前返回 null
awaitResponseId(long timeoutMs) 阻塞至 run_id 就绪,返回含 runId 的 Response ID(可直接 cancel);超时返回当前值(至少含 threadId),不抛异常。 用于「停止生成」:拿含 runId 的 id 落库,另一请求凭它 responses().cancel(id)

responses.createStreamAsSse

STREAMStreamHandle createStreamAsSse(ResponseRequest req, OutputStream out)

归一化后的标准 SSE 字节直接写入 OutputStream。内部由 SseWriter 负责格式输出: 上游原始 SSE → *SseNormalizer 归一化为 ResponseStreamEvent → 按 event:xxxdata:{...} 逐帧写出,同时注入 sequence_number、 映射上游错误码为 OpenAI 兼容格式、保留 function_call delta 为字符串增量。

Servlet 拿到 HttpServletResponse.getOutputStream() 即可转发:

JAVA
@PostMapping(value = "/chat", produces = "text/event-stream")
public void chat(HttpServletResponse resp, @RequestBody ChatDTO dto) throws IOException {
    resp.setHeader("Cache-Control", "no-cache");
    resp.setHeader("Connection", "keep-alive");
    resp.setHeader("X-Accel-Buffering", "no");   // 关掉 Nginx 缓冲

    ResponseRequest req = ResponseRequest.builder()
        .agent(dto.getAgent())
        .input(dto.getInput())
        .mode(dto.getMode())                 // "flash" / "thinking" / "pro" / "ultra"
        .previousResponseId(dto.getPrevId())
        .build();

    try (StreamHandle h = client.responses().createStreamAsSse(req, resp.getOutputStream())) {
        // 阻塞至流结束 / 取消
    }
}
OpenAI 兼容 · 该输出严格遵循 OpenAI Responses SSE(含 sequence_number、错误码、function_call 字符串增量), 可直接被 openai-node / openai-python 等官方客户端消费。

落库场景:续话 vs 停止生成,取值不同

关键区别:responseId 内部 = threadId + runId续话只用 threadId → getResponseId() 立即取即可; 停止生成(cancel)需要 runId → 必须用 awaitResponseId() 取含 runId 的版本。

① 续话落库(立即取,够用,不阻塞输出):

JAVA
try (StreamHandle h = client.responses().createStreamAsSse(req, resp.getOutputStream())) {
    String responseId = h.getResponseId();          // 立即返回(含 threadId),与 SSE 流并发
    conversationDao.save(userId, responseId);   // 落库,供下轮 previousResponseId
}

② 支持「停止生成」(取含 runId 的 id,供 cancel;通常等 <1s):

JAVA
// 对话端点:拿含 runId 的 id 落库
try (StreamHandle h = client.responses().createStreamAsSse(req, resp.getOutputStream())) {
    String responseId = h.awaitResponseId(3000);   // 阻塞至 run_id 就绪,含 runId
    sessionDao.save(sessionId, responseId);     // 供「停止」端点使用
}

// 停止端点(另一个请求):凭 responseId 服务端取消,跨实例可靠
public void stop(String sessionId) {
    String responseId = sessionDao.get(sessionId);
    client.responses().cancel(responseId);
}

responses.retrieve

GETResponse retrieve(String responseId)

按对外不透明 ID 取回历史响应。SDK 内部自动将平台原生 ID 编码为稳定统一的 responseId。

部分引擎不支持 retrieve 时抛 UnsupportedOperationException

responses.delete

DELETEvoid delete(String responseId)

删除一次响应,会一并释放底层 thread / conversation。

ResponseRequest 全部参数

统一通过 ResponseRequest.builder().xxx().build() 构造,构建后不可变。 字段对齐 OpenAI Responses,仅扩展 agent(必填)、modethinkingextraConfig

流式/同步由调用方法决定(create / createStream / createStreamAsSse),请求体本身无需 stream 字段。

字段Builder 方法说明
agent必填 .agent(str) 目标 Agent 标识。对应 AI 平台后台发布 Agent 时生成的调用凭据(名称或 ID)。 不传或传空串直接抛 ValidationException
input必填 .input(String)
.input(List<Message>)
用户输入,支持两种形态:
  • String:单轮文本,等价于 List.of(Message.user(text)),最常用
  • List<Message>:自构造完整对话历史(含 system / user / assistant 多轮),也可添加多模态 Content(图片 / 文件),详见Input 内容类型
传了 previousResponseId 时 input 通常只需本轮新消息。
model .model(str) 指定 LLM 型号,不传则使用 Agent 默认模型。
traceId .traceId(str) 外部 traceId(如前端透传),作为 Langfuse trace 主键以聚合同一会话的多次调用。仅 SDK 内部消费、不透传给上游 Provider;未开启 Langfuse 时无副作用。详见可观测性配置
userId .userId(str) 用户标识(可选),映射为 Langfuse trace 的 userId,用于按用户维度统计 token。不传则该 trace 不带 userId(不会用租户编码顶替)。仅 SDK 内部消费、不透传给上游 Provider。详见可观测性配置
mode .mode(str) 运行模式,一行设置即可驱动 thinking、推理深度、plan、子代理等多项参数, 无需手动拼 extraConfig
有效值(SDK 内置的四档预设):
thinking推理深度plan子代理适用场景
"flash"minimal快速轻量任务,速度优先
"thinking"low思考后行动,速度与准确平衡
"pro"medium思考+计划再执行,更精准
"ultra"high最强能力,复杂多步、可调用子代理分工
不传 null 则不展开任何参数,由 Agent 自身默认行为决定。
传入未知值:记录 WARN 日志并透传,不抛异常。
覆盖优先级extraConfig > thinking > mode 展开的默认值。 例如 mode("ultra").thinking(false):保留 ultra 的 plan + 子代理,但关闭 thinking。
断线续传:设置 mode 后客户端断开时上游继续执行任务, 需自行保存 resp_id 以便重连。
thinking .thinking(boolean) 思维链开关。设 true 开启扩展推理;与 mode 同时使用时,thinking 优先级高于 mode 展开的对应值。
instructions .instructions(str) System prompt。当前版本暂不支持,SDK 记录 WARN 日志并忽略,不抛异常。 如需 system prompt,请在 AI 平台的 Agent 定义中配置。
previousResponseId .previousResponseId(str) 多轮对话上下文锚点。传入上一轮 Response.getId()(SDK 黑盒字符串,无需解析), SDK 内部自动关联历史对话。传了后无需重复传历史 messages。传 null 或不传表示新会话。 ⚠ 只持久化 Response.getId() 返回值,不要尝试解析或重组;引擎切换后自构造的 ID 将不可用。
temperature .temperature(Double) 采样温度,0.0~2.0,值越大输出越随机。与 topP 通常二选一。
topP .topP(Double) 核采样参数,0.0~1.0。与 temperature 二选一。
maxOutputTokens .maxOutputTokens(Integer) 输出 token 截断阈值。达到后流式产生 RESPONSE_INCOMPLETEResponse.status="incomplete"
tools .tools(Object) 工具定义列表,SDK 原样透传。常用格式:List<Map>,每个含 type / name / description / parameters
toolChoice .toolChoice(Object) "auto" / "required" / "none",或指定 tool 对象(含 type:"function" + name)。
parallelToolCalls .parallelToolCalls(Boolean) 是否允许并行工具调用。
reasoning .reasoning(Reasoning) 推理模型配置,对齐 OpenAI Responses。用 Reasoning.of(effort, summary) 构造:
  • effort:推理深度 — "minimal" / "low" / "medium" / "high"
  • summary:推理摘要策略 — "auto" / "concise" / "detailed"
启用后 SSE 产生 REASONING_TEXT_DELTAREASONING_SUMMARY_TEXT_DELTA 事件。
text .text(TextConfig) 文本输出格式,对齐 OpenAI Responses,用 TextConfig.of(format, verbosity) 构造:
  • format:输出格式定义(Object 类型,如 Map.of("type", "json_object")
  • verbosity:输出详细程度 — "low" / "medium" / "high"
truncation .truncation(str) "auto" — 超长自动截断历史;"disabled" — 不截断。
store .store(Boolean) 是否在服务端持久化本次响应。默认 true。置 false 后无法 retrieve。
include .include(List<String>) 响应中包含的额外字段,如 List.of("reasoning.encrypted_content")
metadata .metadata(Map)
.putMetadata(key, val)
透传业务标识(SDK 不解析,原样回写)。典型用例:tenant / userId / traceId。putMetadata 可多次追加。
extraConfig .extraConfig(Map)
.putExtra(key, val)
引擎特定扩展参数,优先级最高,可覆盖 mode 展开的任意默认值。 建议优先用 mode,仅在确需精细控制时才使用此字段。

Input 内容类型

input 接受两种形态:纯字符串、或 List<Message>Message 持有 roleList<Content>

Message 工厂

Content 子类

类型用途
TextContent纯文本,new TextContent("hi")
InputImageContent图片,fileIdimageUrl 二选一。
InputFileContent文件附件。fileId(已上传)或本地 Path / InputStream(自动上传)。
CustomContent原始 JSON 直通,应付 SDK 暂未覆盖的扩展类型。
JAVA
List<Message> msgs = Arrays.asList(
    Message.system("你是一名审计师"),
    Message.user(
        new TextContent("分析这张报表"),
        new InputImageContent("file_abc", null)
    )
);

ResponseRequest req = ResponseRequest.builder()
    .agent("audit")
    .input(msgs)
    .build();

files.upload

POSTFileObject upload(FileUploadRequest req)

上传本地文件 / 字节流,返回 SDK 编码的统一 file_id,可在 InputImageContent / InputFileContent 中引用。

FileUploadRequest

字段说明
file Path本地文件路径。与 stream 二选一。
stream InputStream字节流。配合下方 filename
filename string显示名。流式上传时必填。
purpose string用途标签("assistants" / "vision" 等),由具体引擎解释。
previousResponseId string关联到已有会话,不传则新建。
agent string归属 Agent 标识。
user string用户标识,用于权限与统计。
JAVA
FileObject f = client.files().upload(FileUploadRequest.builder()
    .file(Paths.get("/tmp/report.pdf"))
    .purpose("assistants")
    .agent("doc-qa")
    .build());

System.out.println(f.getId());     // 不透明 ID
System.out.println(f.getBytes());

files.list

GETList<FileObject> list(String previousResponseId)

列出某会话已上传的文件。

files.retrieve

GETFileObject retrieve(String fileId)

查询单个文件元信息。部分引擎不支持时返回 null

files.download

GETFileDownload download(String fileId)

下载附件内容(用户上传的文件,或 Agent 生成的附件),返回流式 FileDownload(字节流 + 元数据)。 供业务后端转存,或经业务后端代理给前端在线预览。返回对象必须由调用方 close()(建议 try-with-resources)。

fileId 来自上传结果(FileObject.getId())或流式事件 response.output_text.annotation.added 里的 file_id。上传文件与生成文件共用同一下载逻辑。 DeerFlow 端点需登录态(由 SDK 内部鉴权),前端/业务后端无法直连,必须经此方法取字节

JAVA
// 业务后端:取字节转存(大文件流式,避免全载内存)
try (FileDownload dl = client.files().download(fileId)) {
    String name = dl.getFilename();
    String mime = dl.getContentType();      // 可空
    boolean inline = dl.isInline();        // 预览提示:活动内容(html/svg)为 false
    try (InputStream in = dl.getStream()) {
        // 拷贝到本地/对象存储,或直接写入 HTTP 响应回吐前端
    }
}

files.delete

DELETEvoid delete(String fileId)

files.getLimits

GETFileLimits getLimits()

当前引擎的上传限额。

语音转文字(Audio STT)

通过 XnAiClient.audio() 获取 Audio 实例,调用 transcribe() 将音频文件转写为文字。 支持异步转写(HTTP URL)和热词增强,对齐 OpenAI Audio API 规范。

配置

属性类型必填默认值说明
xn.ai.platform.audio.providerString-STT 引擎:dashscope
xn.ai.platform.audio.endpointString-STT 服务地址,如 https://dashscope.aliyuncs.com/api/v1
xn.ai.platform.audio.api-keyString-API 密钥
xn.ai.platform.audio.modelStringfun-asr默认语音模型

audio.transcribe

POSTAudioResponse transcribe(AudioRequest)

提交音频文件转写为文字。传入 fileUrl(远程 HTTP URL)走异步转写,返回 AudioResponse(含完整文本、分句、时长、语言)。

JAVA
// 传入远程音频 URL,带热词增强
AudioResponse resp = client.audio().transcribe(
    AudioRequest.builder()
        .fileUrl("https://oss.example.com/audio/query.wav")
        .language("zh")
        .hotwords(Arrays.asList(
            new Hotword("后备母猪", 5),
            new Hotword("死淘率", 4)
        ))
        .build());

System.out.println("转写结果: " + resp.getText());
System.out.println("语言: " + resp.getLanguage());
System.out.println("时长: " + resp.getDuration() + "s");
for (Sentence s : resp.getSentences()) {
    System.out.println(s.getBeginTime() + "-" + s.getEndTime() + "ms: " + s.getText());
}
JSON
AudioResponse {
  text: "后备母猪期末存栏282头",
  sentences: [
    { beginTime: 0, endTime: 3000, text: "后备母猪期末存栏282头" }
  ],
  language: "zh",
  duration: 3
}

AudioRequest 参数

字段类型必填说明
fileUrlString远程音频文件 HTTP URL(需外网可访问
filePath本地文件路径(需先上传 OSS)
modelString覆盖默认语音模型
languageString语言提示,如 "zh"
vocabularyIdString预注册热词表 ID(优先级高于 hotwords)
hotwordsList<Hotword>内联热词列表,每个含 text / weight(1-5, 默认4) / lang
enableDiarizationboolean说话人分离(仅异步接口支持)
enableTimestampboolean时间戳(仅异步接口支持)

fileUrl 必须是外网可访问的公网 URL(如阿里云 OSS 公网地址),DashScope 服务端拉取音频文件。内网地址 / localhost / 需鉴权的 CDN 会导致转写失败。

AudioResponse 字段

字段类型说明
textString完整转写文本
sentencesList<Sentence>分句结果,含 beginTime(ms) / endTime(ms) / speakerId / emotion
languageString识别出的语言代码
durationLong音频时长(秒)

热词管理

支持预注册热词表提升农业领域专有名词识别率。

JAVA
// 创建热词表
String vocabId = client.audio().createVocabulary(
    VocabularyRequest.builder()
        .name("pig-farm-terms")
        .model("fun-asr")
        .hotwords(Arrays.asList(
            new Hotword("后备母猪", 5),
            new Hotword("死淘率", 4)
        ))
        .build());

// 转写时引用
AudioResponse resp = client.audio().transcribe(
    AudioRequest.builder()
        .fileUrl("https://oss.example.com/audio/query.wav")
        .vocabularyId(vocabId)
        .build());

// 管理热词表
client.audio().listVocabularies();        // 查询全部
client.audio().deleteVocabulary(vocabId);  // 删除
POSTString createVocabulary(VocabularyRequest)
PUTvoid updateVocabulary(String id, VocabularyRequest)
DELETEvoid deleteVocabulary(String id)
GETList<Vocabulary> listVocabularies()

附件集成(金蝶云苍穹后端 · 照着实现)

铁约束 · DeerFlow 附件端点需登录态(由 SDK 内部鉴权管理),SDK 为嵌入式、不自起 HTTP 服务。 所以 前端与业务后端都不能直连 DeerFlow,附件字节全程经「业务后端 ↔ SDK」中转; file_id 是不透明串,前端只原样透传、不解析、不拼接 URL

整体链路

前端                业务后端(苍穹)                 SDK                    DeerFlow
 │ ① 选文件+提问 ────►│ /ai/chat                     │                        │
 │   (multipart)      ├─ Content.inputFile(流,名) ──► createStreamAsSse ────► /uploads + runs/stream
 │ ◄── ② SSE 直通 ────┤   (SDK 的 SSE 字节直接写回)   │  (含附件 annotation 帧) │
 │                    │                              │                        │
 │ ③ 点附件(file_id)─►│ /ai/file?fileId=&mode= ─────► files().download() ───► GET /artifacts/{path}
 │ ◄── ④ 字节+头 ─────┤   (设 Content-Disposition)    │  FileDownload(流)       │

业务后端需提供 2 个 HTTP 接口(契约)

接口方法 / 参数响应用途
/ai/chatPOST · multipart:message 文本、file 文件(可选)、previousResponseId(可选)text/event-stream(SDK SSE 直通)带附件提问 + 流式回答;附件以 annotation 帧下发
/ai/fileGET · fileId(必填,前端从 annotation 透传)、mode=preview|download文件字节 + Content-Type + Content-Disposition下载/预览附件(上传文件与生成文件通用)

接口一:/ai/chat — 上传 + 流式对话(SSE 直通)

createStreamAsSse 把 SDK 的 SSE 字节直接写进响应流,前端即可收到全部事件(含附件 annotation)。 用户选的文件用 Content.inputFile(流, 文件名) 内联到对话,SDK 自动上传到本次对话 thread,无需手动管 thread。

JAVA · 苍穹自定义 WebApi 插件(路由注册按苍穹规范)
// 在自定义 WebApi/服务的 doService(req, resp) 中(苍穹提供 javax.servlet 风格 req/resp)
public void doChat(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    // 1) 解析 multipart:文本 + 可选文件(苍穹/Commons-FileUpload 均可,这里示意)
    String message = req.getParameter("message");
    String prevRespId = req.getParameter("previousResponseId"); // 续话可空
    Part filePart = req.getPart("file");                       // 可空

    // 2) 组装对话内容:有文件则内联上传(SDK 自动同 thread)
    List<Content> contents = new ArrayList<>();
    if (filePart != null) {
        contents.add(Content.inputFile(filePart.getInputStream(), filePart.getSubmittedFileName()));
    }
    contents.add(Content.text(message));

    ResponseRequest aiReq = ResponseRequest.builder()
            .agent("doc-qa")
            .input(Arrays.asList(Message.user(contents)))
            .previousResponseId(prevRespId)   // null=新会话
            .mode("pro")
            .build();

    // 3) SSE 直通:SDK 把符合 OpenAI 规范的 SSE 字节写进 resp.getOutputStream()
    resp.setContentType("text/event-stream;charset=UTF-8");
    resp.setHeader("Cache-Control", "no-cache");
    resp.setHeader("X-Accel-Buffering", "no");  // 关闭代理缓冲,保证实时
    client.responses().createStreamAsSse(aiReq, resp.getOutputStream());
}
前端从该 SSE 流里收到 response.output_text.annotation.added 帧(含 file_id/filename)即代表"有附件生成"。具体前端解析见 SSE 事件参考 · 附件

接口二:/ai/file — 下载 / 预览代理

前端把 annotation 里的 file_id 原样传来;后端用 files().download(fileId) 取字节回吐。预览与下载只差 Content-Disposition

JAVA · 苍穹自定义 WebApi 插件
public void doFile(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    String fileId = req.getParameter("fileId");
    boolean wantPreview = "preview".equals(req.getParameter("mode"));

    try (FileDownload dl = client.files().download(fileId)) {
        // Content-Type
        resp.setContentType(dl.getContentType() != null
                ? dl.getContentType() : "application/octet-stream");
        if (dl.getContentLength() >= 0) {
            resp.setHeader("Content-Length", String.valueOf(dl.getContentLength()));
        }
        // 预览仅对可内联类型生效;活动内容(html/svg)isInline()=false,强制下载防 XSS
        boolean inline = wantPreview && dl.isInline();
        String disposition = (inline ? "inline" : "attachment");
        // 中文文件名用 RFC 5987 filename*,避免乱码
        String enc = URLEncoder.encode(dl.getFilename(), "UTF-8").replace("+", "%20");
        resp.setHeader("Content-Disposition",
                disposition + "; filename*=UTF-8''" + enc);

        // 流式拷贝(不全载内存)
        byte[] buf = new byte[8192];
        int n;
        InputStream in = dl.getStream();
        OutputStream out = resp.getOutputStream();
        while ((n = in.read(buf)) > 0) out.write(buf, 0, n);
        out.flush();
    } catch (ResourceNotFoundException e) {
        resp.sendError(404, "file not found");   // 文件不存在
    }
    // try-with-resources 自动 close FileDownload,释放底层连接
}

上传的另一种姿势(先上传后引用)

若需先单独上传、之后多轮引用:用 files().upload()file_id,但后续对话必须带 previousResponseId 保证同一 thread,否则 Agent 读不到文件。多数场景用上面的"对话内联"更简单。

JAVA
FileObject f = client.files().upload(FileUploadRequest.builder()
        .stream(filePart.getInputStream(), filePart.getSubmittedFileName())
        .agent("doc-qa").build());
String fileId = f.getId();  // 可下载、可在后续对话用 Content.inputFile(fileId)+previousResponseId 引用

关键坑点

Assistants — Agent 管理

通过 XnAiClient.assistants() 获取 Assistants 实例,管理 Agent 的创建、列表、获取、修改、删除。 接口形态与字段命名对齐 OpenAI Assistants API 规范, 业务系统只依赖 Assistants 接口,切换 AI 平台无需改代码。

DeerFlow 字段映射

SDK 在 Provider 层完成 OpenAI 接口与 DeerFlow /api/agents 端点之间的转换:

SDK(OpenAI 规范)DeerFlow 存储说明
id(如 asst_my-agentagent 目录名SDK 自动编解码,业务系统只看到 ID
nameagent 目录名create 时必填
instructionsSOUL.md 文件系统提示词,create/modify 可选
descriptionconfig.yaml → descriptioncreate/modify 可选
model—(不使用)DeerFlow 由 Agent 配置决定模型,SDK 接受但不发送
metadata.skillsconfig.yaml → skills[]逗号分隔字符串 ↔ 数组,参见 skills 配置
createdAt—(SDK 生成)Unix 秒时间戳,SDK 本地填充

Dify 行为

Dify 不支持 Agent 管理 API,engine=dify 时所有方法抛出 UnsupportedOperationException,消息指明使用 Dify 管理后台。

assistants.create

POSTAssistant create(AssistantRequest)

创建新 Agent。name 必填(DeerFlow 以此作为 agent 目录名),instructions / description 可选。

最简创建

JAVA
Assistant a = client.assistants().create(
    AssistantRequest.builder()
        .name("my-agent")
        .instructions("You are a helpful assistant.")
        .build());
// → POST /api/agents  body: {name, soul}
// → 返回 Assistant: id="asst_my-agent", instructions="You are..."

带描述和 skills

JAVA
Assistant a = client.assistants().create(
    AssistantRequest.builder()
        .name("pig-farm-expert")
        .description("猪场成本分析专家")
        .instructions("你是资深猪场成本分析专家,基于数据驱动决策。")
        .putMetadata("skills", "pig-farm-cost-analysis,data-analysis,deep-research")
        .build());
// config.yaml 结果:
//   name: pig-farm-expert
//   description: 猪场成本分析专家
//   skills:
//     - pig-farm-cost-analysis
//     - data-analysis
//     - deep-research

异常

条件异常
name 为空ValidationException
name 已存在ValidationException(后端 422)
未认证AuthenticationException(后端 401)
agents_api 未开启AuthorizationException(后端 403)

assistants.list

GETAssistantListResponse list()

列出当前用户所有 Agent,返回 OpenAI list 格式。

JAVA
AssistantListResponse list = client.assistants().list();
// → GET /api/agents
// → {"object":"list","data":[...],"first_id":"asst_...","last_id":"asst_...","has_more":false}

System.out.println("共 " + list.getData().size() + " 个 Agent");
for (Assistant a : list.getData()) {
    System.out.println(a.getId() + " | " + a.getName()
        + " | instructions="
        + (a.getInstructions() != null
            ? a.getInstructions().substring(0, Math.min(40, a.getInstructions().length()))
            : "null"));
    // skills 透传在 metadata 中
    String skills = a.getMetadata().get("skills");
    if (skills != null) System.out.println("  skills: " + skills);
}

assistants.retrieve

GETAssistant retrieve(String assistantId)

获取单个 Agent 的完整信息(含 instructionsmetadata.skills)。

JAVA
Assistant a = client.assistants().retrieve("asst_my-agent");
// → GET /api/agents/my-agent

System.out.println(a.getId());            // "asst_my-agent"
System.out.println(a.getName());          // "my-agent"
System.out.println(a.getDescription());   // "猪场成本分析专家"
System.out.println(a.getInstructions());  // SOUL.md 内容
System.out.println(a.getMetadata().get("skills")); // "skill-a,skill-b"

异常

条件异常
Agent 不存在ResourceNotFoundException(后端 404)
ID 格式非法(不由 asst_ 开头)ValidationException

assistants.modify

POSTAssistant modify(String assistantId, AssistantRequest)

修改 Agent,partial update——只更新显式设置的字段,省略的字段保持原值。

修改 soul(instructions)

JAVA
// 只改系统提示词,其他不动
Assistant updated = client.assistants().modify(
    "asst_my-agent",
    AssistantRequest.builder()
        .instructions("你是养猪专家,用通俗易懂的中文回答所有问题。")
        .build());
// → PUT /api/agents/my-agent  body: {soul: "你是养猪专家..."}

同时修改 description 和 instructions

JAVA
Assistant updated = client.assistants().modify(
    "asst_my-agent",
    AssistantRequest.builder()
        .description("更新后的描述")
        .instructions("新的系统提示词")
        .build());
// → PUT /api/agents/my-agent  body: {description, soul}

assistants.delete

DELETEvoid delete(String assistantId)

删除 Agent。幂等——Agent 不存在时视为成功,不抛异常。

JAVA
// 安全删除:先尝试删,不存在也不报错
client.assistants().delete("asst_my-agent");
// → DELETE /api/agents/my-agent

// 完整流程:创建 → 使用 → 清理
String testId = "asst_demo-test";
try { client.assistants().delete(testId); } catch (Exception e) {}
Assistant a = client.assistants().create(...);
// ... 测试 ...
client.assistants().delete(testId);

skills 配置

skills 是 DeerFlow agent config.yaml 中的标准字段(字符串数组)。 SDK 通过 metadata"skills" 键透传——逗号分隔的字符串 ↔ YAML 数组。

操作metadata.skills 值DeerFlow config.yaml
设置"skill-a,skill-b"skills: [skill-a, skill-b]
追加"skill-a,skill-b,skill-c"skills: [skill-a, skill-b, skill-c]
清空""skills: []
不传不设此 keyskills 字段不修改(保持原值)

创建时设置 skills

JAVA
Assistant a = client.assistants().create(
    AssistantRequest.builder()
        .name("cost-analyst")
        .instructions("你是成本分析专家。")
        .putMetadata("skills", "pig-farm-cost-analysis,data-analysis,deep-research")
        .build());
// config.yaml:
//   skills:
//     - pig-farm-cost-analysis
//     - data-analysis
//     - deep-research

修改 skills

JAVA
// 更新 skills 列表
client.assistants().modify("asst_cost-analyst",
    AssistantRequest.builder()
        .putMetadata("skills", "xn-api,pig-farm-metrics")
        .build());

// 清空 skills
client.assistants().modify("asst_cost-analyst",
    AssistantRequest.builder()
        .putMetadata("skills", "")
        .build());

读取 skills

JAVA
Assistant a = client.assistants().retrieve("asst_cost-analyst");
String skills = a.getMetadata().get("skills");
// → "xn-api,pig-farm-metrics"(或 null 表示后端未返回)

SSE 事件总览

所有事件由 ResponseStreamEvent.Type 枚举定义,sseName() 返回 OpenAI 兼容字符串。

前向兼容 · 未来 minor 版本可能新增事件类型。消费方对 type() 的 switch 必须带 default 分支, 并把未知事件视为无害进度信号

生命周期事件

枚举SSE 名触发时机
RESPONSE_CREATEDresponse.created分配 response_id 的瞬间,流的第一个事件。
RESPONSE_IN_PROGRESSresponse.in_progress进入内容生成阶段。
RESPONSE_COMPLETEDresponse.completed正常结束,含完整 outputusage
RESPONSE_FAILEDresponse.failed服务端业务失败。
RESPONSE_INCOMPLETEresponse.incomplete触发 max_tokens 截断等提前终止。
ERRORerror网络层 / SDK 异常,含 error()

输出项事件

枚举SSE 名说明
OUTPUT_ITEM_ADDEDresponse.output_item.added新 OutputItem 开始(含类型与 id,内容暂空)。event.outputIndex() 为该 item 在 output[] 中的真实位置(从 0 递增)。
OUTPUT_ITEM_DONEresponse.output_item.done当前 OutputItem 全部内容接收完毕。event.item() 含完整内容。
CONTENT_PART_ADDEDresponse.content_part.addeditem 内新增内容片段。
CONTENT_PART_DONEresponse.content_part.done当前内容片段结束。

文本与推理事件

枚举SSE 名说明
OUTPUT_TEXT_DELTAresponse.output_text.delta文本增量,delta() 取片段。event.item().getId() 为所属 MessageItem 的真实 id(非固定 msg_0),event.outputIndex() 为该 item 在 output[] 中的位置,event.contentIndex() 为 content part 序号。
OUTPUT_TEXT_DONEresponse.output_text.done当前文本片段结束,delta() 是完整文本(SSE payload 字段名为 text)。event.item()event.outputIndex() 与对应 DELTA 事件一致。
OUTPUT_TEXT_ANNOTATION_ADDEDresponse.output_text.annotation.added引用 / 注释 / 高亮。
REFUSAL_DELTAresponse.refusal.delta拒答增量。
REFUSAL_DONEresponse.refusal.done拒答结束。
REASONING_TEXT_DELTAresponse.reasoning_text.delta推理文本增量(思维链)。
REASONING_SUMMARY_TEXT_DELTAresponse.reasoning_summary_text.delta推理摘要增量。
REASONING_SUMMARY_TEXT_DONEresponse.reasoning_summary_text.done推理摘要结束。

工具调用事件

枚举SSE 名说明
FUNCTION_CALL_ARGUMENTS_DELTAresponse.function_call_arguments.delta工具入参 JSON 字符串增量。payload: item_id, output_index, delta
FUNCTION_CALL_ARGUMENTS_DONEresponse.function_call_arguments.done入参完整,可执行工具。payload: item_id, output_index, arguments
WEB_SEARCH_CALL_SEARCHINGresponse.web_search_call.searching联网搜索开始。
WEB_SEARCH_CALL_COMPLETEDresponse.web_search_call.completed联网搜索结束。
WEB_SEARCH_CALL_FAILEDresponse.web_search_call.failed联网搜索失败。
FILE_SEARCH_CALL_SEARCHINGresponse.file_search_call.searching文件搜索开始。
FILE_SEARCH_CALL_COMPLETEDresponse.file_search_call.completed文件搜索结束。
CODE_INTERPRETER_CALL_IN_PROGRESSresponse.code_interpreter_call.in_progress代码解释器准备执行。
CODE_INTERPRETER_CALL_CODE_DELTAresponse.code_interpreter_call_code.delta代码片段增量。
CODE_INTERPRETER_CALL_CODE_DONEresponse.code_interpreter_call_code.done代码完整。
CODE_INTERPRETER_CALL_INTERPRETINGresponse.code_interpreter_call.interpreting代码执行中。
CODE_INTERPRETER_CALL_COMPLETEDresponse.code_interpreter_call.completed代码执行完成。

典型工具调用事件序列

SEQUENCE
RESPONSE_CREATED
RESPONSE_IN_PROGRESS
OUTPUT_ITEM_ADDED                  ← 工具调用 item 开始
FUNCTION_CALL_ARGUMENTS_DELTA × N
FUNCTION_CALL_ARGUMENTS_DONE       ← 在此执行实际工具
OUTPUT_ITEM_DONE
OUTPUT_ITEM_ADDED                  ← 文本 item 开始
CONTENT_PART_ADDED
OUTPUT_TEXT_DELTA × N
OUTPUT_TEXT_DONE
CONTENT_PART_DONE
OUTPUT_ITEM_DONE
RESPONSE_COMPLETED

对象:ResponseStreamEvent

每个流式事件投递到 StreamCallback.onEvent(ResponseStreamEvent),全部字段通过 getter 访问:

Getter类型说明
type()ResponseStreamEvent.Type事件枚举,同时含 sseName() 返回 OpenAI 兼容字符串。
item()OutputItem与本事件关联的输出项(ITEM_ADDED/DONE、TEXT_DELTA/DONE 等携带)。
outputIndex()Integeritem 在 Response.output[] 中的位置(从 0 递增);非 item 事件为 null。
contentIndex()Integercontent part 在 item 内的序号;仅 TEXT_DELTA/DONE、CONTENT_PART_* 事件携带。
delta()String文本片段(TEXT_DELTA);OUTPUT_TEXT_DONE 时为完整文本(SSE payload 字段名 text)。
toolCall()FunctionCall工具调用信息(FUNCTION_CALL_ARGUMENTS_* 事件携带);含 id / name / arguments
response()Response完整响应对象(RESPONSE_CREATED / COMPLETED / FAILED 等生命周期事件携带)。
error()ResponseError错误详情(ERROR 事件携带);含 getMessage() / getOpenAiErrorCode()
rawData()Object上游原始 JSON 节点(CustomItem 场景使用)。

把 SSE 转发到前端

createStreamAsSse 写入 HttpServletResponse.getOutputStream(),前端用任意 OpenAI 兼容 SDK 直连即可。详细的前端样例见 SSE 协议教程

对象:Response

字段类型说明
idstring对外不透明 ID(由 SDK 编码)。
objectstring恒为 "response"
statusstringin_progress / completed / incomplete / failed
modelstring实际使用的模型名。
outputList<OutputItem>输出项,详见
outputTextstring所有 MessageItem 中文本拼接的便捷视图。
usageUsagetoken 统计。
createdAtInstantUnix 时间。
previousResponseIdstring上轮 ID(请求里传过的)。
metadataMap原样回写。
errorResponseErrorstatus=failed 时含 code/message/param/type。
incompleteDetailsIncompleteDetailsstatus=incomplete 时含原因。
reasoningReasoning推理过程(开启 thinking 时)。
text / tools / toolChoice / truncation / temperature / topP / maxOutputTokens / parallelToolCalls 原样回显请求参数。

对象:OutputItem

抽象基类,按 type 区分:

子类type关键字段
MessageItemmessagerole / content(含 OutputTextContent)
ReasoningItemreasoningsummary / 推理文本
ToolCallItemfunction_callname / arguments / call_id / status
CustomItem其它原始 JSON 直通

对象:Usage

字段说明
inputTokens输入 token 数。
outputTokens输出 token 数。
totalTokens合计。
inputTokensDetails / outputTokensDetails明细(缓存命中、推理 token 等,按引擎而定)。

对象:FileObject

字段类型说明
idstring统一文件 ID。
filenamestring原文件名。
byteslong字节数。
purposestring用途标签。
createdAtInstant创建时间。

对象:FileDownload

files().download() 的返回值,实现 Closeable用完必须 close(释放底层连接)。

方法类型说明
getStream()InputStream字节流;调用方读取并最终 close。
getFilename()string文件名(basename),用于 Content-Disposition。
getContentType()stringMIME,可空。
getContentLength()long字节数;未知为 -1。
isInline()boolean预览提示:可安全内联为 true;活动内容(HTML/SVG/XHTML)为 false(建议作附件下载)。

对象:FileLimits

字段类型说明
maxFilesint单会话最大文件数。
maxFileSizeByteslong单文件最大字节数。
maxTotalByteslong单会话累计最大字节数。
allowedMimeTypesList<String>MIME 白名单;空表示不限制。

错误处理

所有异常继承 AiPlatformException。建议 catch 基类后按子类细分:

异常典型 HTTP场景
ValidationException—(本地校验)请求参数非法(缺 agent / input、文件超限等)。
AuthenticationException401账号密码 / API Key 无效。
AuthorizationException403已登录但无权限访问该 Agent。
ResourceNotFoundException404responseId / fileId 不存在。
RateLimitException429上游限流,可读取 retryAfterSeconds
NetworkException连接 / 超时 / 协议错误。
ProviderException4xx/5xx未匹配前述类型的上游错误,getCode() / getStatus() 携带原始信息。
JAVA
try {
    Response r = client.responses().create(req);
} catch (RateLimitException e) {
    Thread.sleep(e.getRetryAfterSeconds() * 1000L);
} catch (AuthenticationException e) {
    client.refreshAuth();
} catch (AiPlatformException e) {
    log.error("AI 调用失败 code={}", e.getCode(), e);
}