Spring AI 接入阿里云百炼模型内置工具

Spring AI 接入阿里云百炼模型内置工具:联网搜索与网页抓取实战

一、解决什么问题

项目后端(backend,Spring Boot 3 + Spring AI)接入阿里云百炼(DashScope)模型后,我们想做到这样一件事:

用户在智能体里问"查一下 2026 年 Qwen 最新版本发布了什么功能 ",模型不要靠训练数据里的旧知识硬答,而是自己上网搜索 ;如果网页里有相关文章,还能打开网页抓取正文,再基于真实内容回答。

这就涉及阿里云百炼的"模型自带工具 ":web_search(联网搜索)、web_extractor(网页抓取)等。它们不是我们自己在后端用爬虫实现的,而是百炼平台在服务端替模型执行的官方工具。

读完这篇笔记你会掌握:

  1. 模型自带工具和传统 Function Calling 有什么区别;
  2. 百炼为这类工具提供了哪几条 API 通道、各自参数怎么传;
  3. 先用 curl 把协议调通,再看本项目 BailianResponsesClientDashScopeProvider 等代码是怎么落地成产品功能的;
  4. 常见坑:模型不支持、参数没生效、工具结果太大、SSE 断流、计费差异等。

二、先建立直觉:什么是"模型自带工具"

2.1 一个通俗类比

想象你雇了一个"全能助理"。传统 Function Calling 是:助理不会开飞机,但你给他一本飞行手册,并把飞机停在楼下 ,他看完手册自己按步骤操作------工具执行发生在你这边的场地

模型自带工具则是:助理的公司本身就提供"查资料组" ,助理打个内线电话说"帮我查一下",公司后台 查完把资料送到助理桌上。助理不需要知道搜索引擎怎么写、网页怎么抓,他只需要告诉公司要开这些服务

2.2 Function Calling 与平台内置工具的对比

对比项 自定义 Function Calling / @Tool 平台内置工具(web_search / web_extractor)
工具由谁执行 我们自己的后端执行 百炼服务端执行
怎么"告诉模型" 随请求发送 JSON Schema 工具定义 平台约定好名字,直接开启(enable_searchtools
需要写什么代码 每个工具一个 Java 方法 基本不用实现,只做参数映射与事件解析
适用能力 查库、调公司 API、业务逻辑 联网搜索、网页抓取、代码解释器、PDF 解析等通用能力
数据可达性 取决于我们的执行环境 平台具备公网检索与抓取能力
灵活性 高,想干什么自己写 低,平台给什么用什么
计费 只花模型 token 模型 token + 部分工具按次计费(见 FAQ)

项目里的 SqlQueryToolget_db_schema / query_database)就是第一类;本笔记的主角是第二类。

2.3 为什么优先用平台自带工具

如果自己用 Jsoup 写爬虫(项目 crawl/tool/CrawlTool.java 就是这么做的),要处理反爬、页面结构变化、正文提取、多轮搜索重试等一堆事。而百炼把"搜索 + 抓取"做成了服务端工具:

  • 搜索由平台搜索引擎执行,质量稳定;
  • web_extractor 能把网页正文抽出来喂给模型;
  • 模型可以在"搜索 → 读网页 → 再搜索 → 总结"之间自主规划,官方称之为 agent 式检索。

代价是:受平台支持的模型与协议限制、工具按次计费、抓取内容会占用输入 token。

三、百炼内置工具的三条 API 通道

阿里云官方把联网搜索/网页抓取分成三种调用方式,参数差异很大,新手最容易在这里搞混

3.1 原生 DashScope 协议

地址形如 https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation,使用官方 dashscope SDK 的 Generation,通过参数开启:

json 复制代码
{
  "model": "qwen-plus",
  "parameters": {
    "enable_search": true,
    "search_options": {
      "search_strategy": "max",
      "forced_search": true
    }
  }
}

这个通道功能最全(能返回搜索来源、角标引用等),但它不是 OpenAI 兼容协议。我们的项目统一用 Spring AI 的 OpenAI 兼容客户端,所以没有走这条路,作为知识了解即可。

3.2 OpenAI 兼容 - Chat Completions(老式 enable_search

地址形如 https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions,就是 OpenAI 的 /chat/completions。由于 enable_search 不是 OpenAI 标准参数:

  • Python 里要放 extra_body
  • Node.js 里可以直接放顶层;
  • Spring AI 里要放到 extraBody(见后文代码)。
json 复制代码
{
  "model": "qwen-plus",
  "messages": [
    { "role": "user", "content": "杭州明天天气如何" }
  ],
  "enable_search": true,
  "search_options": {
    "search_strategy": "max",
    "forced_search": true
  }
}

特点:

  • 实现简单,在原有聊天请求上加一个参数即可;
  • 适合老一代文本模型 (如 qwen-plusqwen-max 等)做普通联网搜索;
  • 不支持返回结构化搜索来源(兼容协议限制);
  • 网页抓取若走这条路,需要用 search_strategy: agent_max 且仅部分思考模式模型支持(如 qwen3-max 思考模式、qwen3.5-plus)。

3.3 OpenAI 兼容 - Responses API(内置工具主通道,推荐)

地址:https://dashscope.aliyuncs.com/compatible-mode/v1/responses

Responses API 是 OpenAI 在 Chat Completions 基础上演进的"智能体原生"接口,百炼官方称它支持内置联网搜索、网页抓取、代码解释器、文搜图、图搜图 等工具。启用方式是把工具名放进 tools 数组:

json 复制代码
{
  "model": "qwen3.8-max",
  "input": "请访问阿里云百炼代码解释器部分的官方文档,并总结主要内容",
  "tools": [
    { "type": "web_search" },
    { "type": "web_extractor" }
  ],
  "enable_thinking": true,
  "stream": true
}

注意这里工具是 web_search / web_extractor不是 enable_search------两种协议的开关长得完全不一样。

特点:

  • 内置工具执行过程会以 SSE 事件流返回,我们能看到"模型决定搜索、搜索到什么、抓了哪个网页";
  • 官方网页抓取文档推荐使用 Responses API;
  • 本项目实测:qwen3.8-max 这类新一代模型走 Chat Completions 的 enable_search 不生效,走 Responses API 才可靠(代码注释里有记录);
  • 仅部分模型支持,且模型清单会随平台迭代变化,要以官方文档与控制台为准。

3.4 三条路怎么选

需求 推荐通道 说明
老模型快速加联网搜索 Chat Completions + enable_search 改动最小,先 curl 验证
新版 Qwen 模型 + 搜索/抓取/代码解释 Responses API + tools 本项目主路径
需要搜索来源、角标等丰富能力 原生 DashScope 需放弃 OpenAI 兼容层,另写客户端

四、先动手:curl 把协议调通

不管用什么框架,先 curl 验证"参数 + 模型"组合是否生效,能省掉大量"代码没毛病但平台不认"的排查时间。

4.1 验证 Chat Completions 联网搜索

bash 复制代码
curl https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen-plus",
    "messages": [
      { "role": "user", "content": "杭州明天天气如何?" }
    ],
    "enable_search": true,
    "search_options": {
      "search_strategy": "max",
      "forced_search": true
    }
  }'

如果回答内容明显带出"根据搜索结果/来源"等实时信息,说明生效。

4.2 验证 Responses API 搜索 + 抓取

bash 复制代码
curl https://dashscope.aliyuncs.com/compatible-mode/v1/responses \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3.8-max",
    "input": "请打开 https://help.aliyun.com/zh/model-studio/web-extractor 并总结网页抓取功能的启用方式",
    "tools": [
      { "type": "web_search" },
      { "type": "web_extractor" }
    ],
    "enable_thinking": true,
    "stream": true
  }'

能观察到类似下面的 SSE 事件序列,就说明链路通了:

text 复制代码
event: response.reasoning_summary_text.delta
data: {"type":"response.reasoning_summary_text.delta","delta":"用户让我访问指定网页......"}

event: response.output_item.done
data: {"type":"response.output_item.done","item":{"type":"web_search_call", ...}}

event: response.output_item.done
data: {"type":"response.output_item.done","item":{"type":"web_extractor_call", ...}}

event: response.output_text.delta
data: {"type":"response.output_text.delta","delta":"根据阿里云官方文档......"}

五、关键参数逐个解释

  • Chat Completions / 原生 DashScope:顶层布尔参数 enable_search: true
  • Responses API:tools 数组里放 {"type": "web_search"} / {"type": "web_extractor"}

两者互不通用,封装适配层时要按协议分开翻译。

5.2 enable_thinking:思考模式

部分模型/工具组合要求先开思考模式(如 qwen3-max 系列用 agent_max 抓网页时)。项目里的实现把 enable_thinking: true 和内置工具一起注入,理由写在 DashScopeProvider 注释里:

内置工具要求开启思考模式,与默认关闭思考互斥。

而项目普通文本生成链路(LlmService.buildChatModel)默认是关闭思考 的(防止思考 token 挤占正文)。所以"开内置工具"必须显式把思考参数覆盖回来,并且要在合并 extraBody 时放最后,保证优先级。

5.3 search_options.search_strategy:搜索量级

不同协议能设置的策略不同,官方目前主要取值:

取值 含义 注意
turbo(默认) 平衡速度与效果 大多数场景够用
max 更全面、多源搜索 更慢、可能更贵
agent 多轮搜索 + 模型整合 适用 Qwen3-Max/3.5/3.6/3.7 部分模型;不支持 Qwen3.8 等
agent_max agent 基础上支持网页抓取 旧 Chat Completions 路径开抓取用;仅个别思考模式模型

策略随模型系列不断变化,务必以官方页面为准,代码里不要写死"一定支持"。

5.4 forced_search:强制搜索

forced_search: true 表示用户既然勾选了联网,就必须真的搜索核实,而不是让模型"看心情"决定搜不搜。本项目在 Chat Completions 兜底参数里就注入了它。

网页抓取工具本身没有"抓哪个 URL"的来源,通常是模型先搜索、拿到候选 URL,再决定打开哪个。官方文档明确:Responses API 开启抓取时要同时添加 web_search 。前端只勾了 web_extractor 而漏掉 web_search,容易造成调用空转或报错。

六、项目里是怎么落地的(读代码)

仓库地址:D:\workspace\springai-web-novel。版本:Spring Boot 3.5.8 + Spring AI 1.1.2 + Spring AI Alibaba 1.1.2.2。

6.1 整体链路

复制代码
用户提问:请帮我查某本书的最新豆瓣评分
   │  POST /api/agent/apps/chat(SSE)
   ▼
BasicAgentExecutor.streamExecute
   │  模型 provider=DASHSCOPE 且 enabledTools 含 web_search/web_extractor
   ▼
llmService.shouldUseResponsesApi(resolvedModel) == true
   ▼
BailianResponsesClient.streamResponses
   │  POST https://dashscope.aliyuncs.com/compatible-mode/v1/responses
   │  body: model / input(system+user) / tools / enable_thinking / stream
   ▼
百炼服务端:思考 → 执行搜索 → 抓网页 → 组织回答
   │  SSE:reasoning / *_call(done) / output_text.delta / completed
   ▼
事件映射为 delta / reasoning / tool_call / tool_result / done
   │
   ▼
AgentChatController 转发 SSE → 前端打字机效果 + 工具调用气泡

6.2 第一步:模型管理页把"内置工具"勾选存下来

前端文件:frontend/src/constants/modelProviders.ts

每个服务商预设里声明了支持的工具:

ts 复制代码
export const MODEL_TOOL_LABELS: Record<string, string> = {
  web_search: '联网搜索',
  pdf_parsing: 'PDF 解析',
  web_extractor: '网页抓取',
}

{
  key: 'DASHSCOPE',
  label: '阿里云百炼',
  baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
  tools: ['web_search', 'pdf_parsing', 'web_extractor'],
}

管理员/用户模型页(AdminModelsView.vueUserModelsView.vue)根据 tools 渲染复选框,勾选结果存进表单的 enabledTools 数组。

小提醒:pdf_parsing 目前只出现在前端清单和后端注释里,DashScopeProvider 真正映射的是 web_search / web_extractor 两个;想支持 PDF 解析需要继续扩展适配器,别被复选框误导。

6.3 第二步:enabledTools 落库并一路带到模型解析结果

数据库字段(schema-mysql.sql):

sql 复制代码
`enabled_tools` TEXT COMMENT '启用的供应商内置工具列表(JSON数组),如 web_search/pdf_parsing/web_extractor'

对应实体(backend/src/main/java/com/zhanghui/domain/AiModel.java):

java 复制代码
@Convert(converter = JsonConverter.class)
private List<String> enabledTools = new ArrayList<>();

模型服务商把解密后的模型信息解析成 ResolvedModel record,enabledTools 就在其中:

java 复制代码
public record ResolvedModel(
    ...,
    String provider,          // DASHSCOPE / DEEPSEEK ...
    BigDecimal topP,
    List<String> enabledTools // 启用的供应商内置工具列表
) {}

6.4 第三步:DashScopeProvider 做平台差异翻译

项目把"不同模型服务商的差异"收敛到 ModelProvider 接口,每个服务商一个实现,避免在核心调用链路上到处 if-else。看 backend/src/main/java/com/zhanghui/service/provider/DashScopeProvider.java,核心是三个方法:

java 复制代码
public static final String TOOL_WEB_SEARCH = "web_search";
public static final String TOOL_WEB_EXTRACTOR = "web_extractor";

// ① 内置工具 → Chat Completions 请求体扩展参数(老协议兜底)
public Map<String, Object> toolParams(ResolvedModel m) {
    boolean search = m.enabledTools().contains(TOOL_WEB_SEARCH);
    boolean extractor = m.enabledTools().contains(TOOL_WEB_EXTRACTOR);
    ...
    params.put("enable_search", true);
    if (extractor) {
        params.put("search_options", Map.of(
            "search_strategy", "agent_max",
            "forced_search", true));
    }
    params.put("enable_thinking", true);
    return params;
}

// ② 内置工具 → Responses API tools 列表
public List<String> responsesTools(ResolvedModel m) {
    // 遍历 enabledTools,只挑 web_search / web_extractor
}

// ③ 是否必须走 Responses API
public boolean preferResponsesApi(ResolvedModel m) {
    return m.enabledTools().contains(TOOL_WEB_SEARCH)
        || m.enabledTools().contains(TOOL_WEB_EXTRACTOR);
}

也就是说,适配器同时维护了两套翻译:

  • 如果将来有人强制走 Chat Completions,就把 enable_search 那套参数塞进 extraBody
  • 但框架判断 preferResponsesApi 为 true 时,会直接切到 Responses 路径,把工具名放进 tools 数组。

这解释了为什么代码里既有 enable_search 又有 tools------它们是两条协议通道,不是同一件事

6.5 第四步:执行器分流

backend/src/main/java/com/zhanghui/agent/executor/BasicAgentExecutor.javastreamExecute

java 复制代码
ResolvedModel resolvedModel = agentService.resolveRuntimeModel(userId, config);
// 百炼 + 启用平台内置工具时走 Responses API 专用路径
if (llmService.shouldUseResponsesApi(resolvedModel)) {
    return streamViaResponsesApi(config, resolvedModel, userMessage, requestVariables);
}

streamViaResponsesApi 做的事很轻:渲染系统提示词(支持 {变量} 占位符),然后调 bailianResponsesClient.streamResponses(...),最后拼一个 done 事件。因为内置工具由百炼服务端执行,本地不需要 Spring AI 的 function calling 循环。

普通链路(不启用内置工具)仍走 ChatClient + ToolCallingManager 的本地工具循环,与内置工具路径互不干扰。

6.6 第五步:自研 Responses SSE 客户端

Spring AI 1.1.x 的 OpenAI 模块还不提供 Responses API 封装,而百炼内置工具又必须走 Responses,所以项目手写了一个轻量客户端:

backend/src/main/java/com/zhanghui/service/BailianResponsesClient.java

请求体构造(关键片段):

java 复制代码
private Map<String, Object> buildRequestBody(ResolvedModel m, String systemPrompt, String userMessage) {
    List<Map<String, Object>> input = new ArrayList<>();
    if (systemPrompt != null && !systemPrompt.isBlank()) {
        input.add(Map.of("role", "system", "content", systemPrompt));
    }
    input.add(Map.of("role", "user", "content", userMessage));

    // 内置工具:只注入 web_search / web_extractor
    List<Map<String, String>> tools = new ArrayList<>();
    for (String tool : m.enabledTools()) {
        if (ModelProvider.TOOL_WEB_SEARCH.equals(tool)
                || ModelProvider.TOOL_WEB_EXTRACTOR.equals(tool)) {
            tools.add(Map.of("type", tool));
        }
    }

    Map<String, Object> body = new HashMap<>();
    body.put("model", m.model());
    body.put("input", input);
    body.put("tools", tools);
    body.put("enable_thinking", true);  // 内置工具要求思考模式
    body.put("stream", true);
    return body;
}

发送与解析用 JDK 原生 HttpClient(HTTP/1.1),原因注释写得很清楚:

Reactor Netty WebClient 与本 SSE 流式响应存在兼容问题(实测 Connection reset,curl 正常)。

逐行读取 data: 前缀的 SSE JSON,做事件映射:

java 复制代码
case "response.output_text.delta" ->            // 正文增量 → delta
case "response.reasoning_summary_text.delta" -> // 思考增量 → reasoning
case "response.output_item.done" -> {
    // item.type 以 _call 结尾(web_search_call / web_extractor_call ...)
    // goal 为搜索/抓取目标;output 或 action.sources 为结果
    // → tool_call + tool_result(结果超 4000 字符截断)
}

整体超时放宽到 5 分钟 (搜索/抓取慢),工具结果默认截断为 4000 字符,防止抓来的长网页把上下文和前端撑爆。

URL 拼接细节也很关键:前端预设填的 Base URL 是 https://dashscope.aliyuncs.com/compatible-mode/v1,工具类 BaseUrlUtil.normalize 会把尾部 /v1 剥掉,再分别拼:

  • Chat Completions:.../compatible-mode/v1/chat/completions
  • Responses:.../compatible-mode/v1/responses

如果直接拿带 /v1 的地址再拼一次,就会拼出 /v1/v1/responses 这类错误路径。

6.7 第六步:事件统一成 AgentChatEvent

backend/src/main/java/com/zhanghui/agent/domain/AgentChatEvent.java 定义了全项目统一的事件模型:

事件类型 含义 data
delta 正文增量 {text}
reasoning 思考增量 {text}
tool_call 模型决定调用工具 {id, name, arguments}
tool_result 工具执行结果 {name, result}
done 本轮完成 {status, model}

AgentChatController 把事件逐个转成 SSE 推给前端,前端就能一边显示思考过程,一边把 web_search_call / web_extractor_call 渲染成"工具调用"气泡,最后把正文打字机式打出来。

七、如果自己从零接入,最小三步

不管是不是用本项目,思路都是这三步:

第一步:确认模型支持 + 用 curl 验证

打开官方"联网搜索 / 网页抓取"文档,确认你打算用的模型在支持列表里;再用第四节 curl 验证,别直接写代码。

第二步:请求里带上工具开关

  • 简单联网搜索且模型支持:Chat Completions 请求加 enable_search: true
  • 新版模型 + 网页抓取:Responses API 的 toolsweb_search + web_extractor,并 enable_thinking: true

第三步:解析流式事件,而不是只取最终文本

内置工具的价值在于"模型自己决定搜什么、抓什么"。Responses API 会把决策过程以 response.output_item.done 等事件流出来,建议:

  1. response.output_text.delta → 展示正文;
  2. response.reasoning_summary_text.delta → 展示思考;
  3. response.output_item.doneitem.type_call 结尾 → 记录工具名、目标、结果;
  4. 对抓取结果做长度截断,防止上下文爆炸。

八、在本项目里测试

  1. 启动后端与前端,进入"模型管理",新增/编辑百炼模型,服务商选"阿里云百炼";
  2. 勾选"联网搜索"和"网页抓取",模型 ID 建议填支持 Responses 内置工具的型号(如 qwen3.8-max),保存;
  3. 确保该模型被设为默认或备用模型;
  4. 打开任意 Agent 应用(或新建一个),在对话里问一个时效性问题,例如"查一下阿里云百炼最新内置工具有哪些";
  5. 观察事件流:应出现 reasoning(思考)→ web_search_call / web_extractor_call(工具气泡)→ delta 正文;
  6. 对照后端日志,能看到请求的 tools 与 extraBody 参数。

九、FAQ 与踩坑记录

Q1:勾了工具,但小说大纲/章节生成没联网?

本项目目前只在 Agent 应用对话链路 里做内置工具分流;LlmService.completeText / streamStory 等普通生成链路不会注入平台工具参数。如果想让写作链路也联网,需要在对应调用点把 platformToolParams 合并进 extraBody,或另走 Responses 客户端。

Q2:为什么工具选了 web_extractor 却没效果?

先确认是否同时勾了 web_search。官方要求两者配对使用;只开抓取没有搜索来源可抓。另外确认模型是 Responses API 支持列表里的型号。

Q3:为什么 qwen3.8-maxenable_search 没反应?

项目代码注释记录了这个实测结论:新一代模型走 Chat Completions 的 enable_search 不生效,应走 Responses API。写代码前先 curl 验证,不同模型/协议的兼容矩阵变化很快。

Q4:Spring AI 官方有没有 Responses API 客户端?

在项目使用的 Spring AI 1.1.x 中没有可用封装,所以自研了 BailianResponsesClient。实现时注意:

  • 用 JDK HttpClient + HTTP/1.1 解析 SSE(实测 WebClient 会 Connection reset);
  • 整体超时放宽到 5 分钟;
  • 工具结果要截断。

Q5:会不会很烧钱?

两部分费用:

  • 抓取的网页内容会拼进提示词,按模型输入 token 计费;
  • 工具调用费用:联网搜索按次收费(以官方页面为准,北京地域文档示例为每千次 4 元),网页抓取文档标注"限时免费"。

日常查询建议用 turbo 策略控制成本,只有研究/报告场景再上 max / agent

Q6:工具执行结果能直接给前端看吗?

可以。Responses API 流式事件里带着工具名、目标(goal)和结果/来源,项目把它们映射为 tool_call / tool_result 事件,前端按气泡渲染。要注意个别字段(output / action.sources)不同工具不一样,解析要做兜底。

Q7:enable_thinking 和"用户要求关闭思考"冲突怎么办?

内置工具要求思考模式,就必须让平台参数最后合并并覆盖。BasicAgentExecutor.buildChatOptions 的合并顺序是:先合并用户思考开关,再把平台内置工具参数放最后,所以"启用内置工具时思考必然打开"。

Q8:为什么 Responses 路径目前"记不住"多轮对话?

streamViaResponsesApi 目前是单轮请求(系统指令 + 当前用户消息),没有把本地会话历史回传给百炼,也没有走 previous_response_id 机制。需要多轮记忆时,要么把历史消息拼进 input,要么利用 Responses API 的 previous_response_id(官方说明响应 id 7 天有效),这一点写代码时要想清楚产品取舍。

十、参考文档

平台支持模型、策略与价格会持续变化,落地前记得以官方文档和控制台"模型广场"为准。

相关推荐
pnoker4 天前
IoT DC3 AI 能力:Agentic Center 的设计与边界
java·人工智能·物联网·大模型·spring ai
LayZhangStrive6 天前
Agent开发 - MCP Client使用MCP Server的几种方式(Spring AI)
java·spring ai·mcp
递归尽头是星辰11 天前
Spring AI 实战:Function Calling,Agent 的底层工具层工程化实践
agent·functioncalling·spring ai·java 大模型工程化
递归尽头是星辰12 天前
大模型 Agent 知识体系:Java 开发者视角下的原理、架构与选型边界
react·智能体·spring ai·java 后端·大模型 agent
TizzyGoodhealth14 天前
从零到一搭建Spring‑AI原生Tool‑Calling AI Agent|架构对比+完整实战源码+踩坑实录
java·ai·知识库·rag·ai agent·spring ai
中间件XL16 天前
ai-agent框架spring ai alibaba (三)外部调用II-2 分布式MCP
spring ai·springaialibaba·分布式mcp·nacos3
QCodingDev19 天前
Spring AI Alibaba ReAct Agent实战:从Tool Calling到Agent,企业AI复杂业务该如何设计?
java·人工智能·agent·ai编程·spring ai
还是鼠鼠1 个月前
Spring AI ChatClient详解:创建第一个AI客户端
java·springboot·spring ai·chatclient
还是鼠鼠1 个月前
Spring AI连接DeepSeek与通义千问:application.yaml配置详解
java·通义千问·spring ai·deepseek