<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Provider on XEDCZQ的博客</title><link>https://xedczq.cn/tags/provider/</link><description>Recent content in Provider on XEDCZQ的博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Tue, 09 Jun 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://xedczq.cn/tags/provider/index.xml" rel="self" type="application/rss+xml"/><item><title>Ai面试项目：llm-provider模块</title><link>https://xedczq.cn/post/aiinterview_llmprovider/</link><pubDate>Tue, 09 Jun 2026 00:00:00 +0800</pubDate><guid>https://xedczq.cn/post/aiinterview_llmprovider/</guid><description>&lt;h2 id="llm-provider-模块设计与实现"&gt;&lt;a href="#llm-provider-%e6%a8%a1%e5%9d%97%e8%ae%be%e8%ae%a1%e4%b8%8e%e5%ae%9e%e7%8e%b0" class="header-anchor"&gt;&lt;/a&gt;Llm-provider 模块设计与实现
&lt;/h2&gt;&lt;p&gt;这篇笔记记录 &lt;code&gt;interview-guide&lt;/code&gt; 项目中 &lt;code&gt;llm-provider&lt;/code&gt; 模块的设计与接口实现。该模块负责统一管理大模型 Provider 配置，包括模型列表、默认模型、Embedding 能力、连通性测试，以及语音面试中 ASR/TTS 的运行时配置。&lt;/p&gt;
&lt;h2 id="模块能力概览"&gt;&lt;a href="#%e6%a8%a1%e5%9d%97%e8%83%bd%e5%8a%9b%e6%a6%82%e8%a7%88" class="header-anchor"&gt;&lt;/a&gt;模块能力概览
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Provider 管理：支持查询、创建、更新、删除大模型 Provider。&lt;/li&gt;
&lt;li&gt;双存储模式：兼容 DB 模式和 Legacy 配置文件模式。&lt;/li&gt;
&lt;li&gt;密钥保护：DB 模式下 API Key 使用 AES-GCM 加密存储，接口返回前统一脱敏。&lt;/li&gt;
&lt;li&gt;默认模型管理：区分默认 Chat Provider 和默认 Embedding Provider。&lt;/li&gt;
&lt;li&gt;缓存重载：Provider 变更后清空 &lt;code&gt;ChatClient&lt;/code&gt; 和 &lt;code&gt;EmbeddingModel&lt;/code&gt; 缓存，下次调用时重新构建。&lt;/li&gt;
&lt;li&gt;Embedding 校验：创建、更新和设置默认 Embedding Provider 时校验模型类型、维度和能力开关。&lt;/li&gt;
&lt;li&gt;连通性测试：支持对 LLM Provider 发起真实 HTTP 测试请求。&lt;/li&gt;
&lt;li&gt;语音配置管理：支持读取和更新 Qwen ASR/TTS 配置，并同步重载运行时服务。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="流程图"&gt;&lt;a href="#%e6%b5%81%e7%a8%8b%e5%9b%be" class="header-anchor"&gt;&lt;/a&gt;流程图
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;stateDiagram-v2
 [*] --&gt; ProviderConfigPage: 进入 LLM Provider 配置页

 ProviderConfigPage --&gt; ProviderListLoaded: GET /api/llm-provider/list
 ProviderListLoaded --&gt; ProviderDetailLoaded: GET /api/llm-provider/{id}

 ProviderListLoaded --&gt; CreatingProvider: POST /api/llm-provider
 CreatingProvider --&gt; ProviderSaved: 校验通过并保存
 CreatingProvider --&gt; Error: id重复 / 参数非法 / 写入失败

 ProviderDetailLoaded --&gt; UpdatingProvider: PUT /api/llm-provider/{id}
 UpdatingProvider --&gt; ProviderSaved: 局部字段更新成功
 UpdatingProvider --&gt; Error: Provider不存在 / 参数非法 / 写入失败

 ProviderSaved --&gt; RegistryReloaded: registry.reload()
 RegistryReloaded --&gt; ProviderListLoaded: 重新加载列表

 ProviderDetailLoaded --&gt; TestingProvider: POST /api/llm-provider/{id}/test
 TestingProvider --&gt; TestSuccess: 外部 LLM API 连接成功
 TestingProvider --&gt; TestFailed: 连接失败 / 鉴权失败 / 模型不可用
 TestSuccess --&gt; ProviderDetailLoaded
 TestFailed --&gt; ProviderDetailLoaded

 ProviderDetailLoaded --&gt; UpdatingDefaultChat: PUT /api/llm-provider/default-provider
 UpdatingDefaultChat --&gt; DefaultChatUpdated: Provider存在
 UpdatingDefaultChat --&gt; Error: Provider不存在 / defaultProvider为空

 ProviderDetailLoaded --&gt; UpdatingDefaultEmbedding: PUT /api/llm-provider/default-embedding-provider
 UpdatingDefaultEmbedding --&gt; DefaultEmbeddingUpdated: 支持 Embedding
 UpdatingDefaultEmbedding --&gt; Error: Provider不存在 / 不支持Embedding

 DefaultChatUpdated --&gt; RegistryReloaded
 DefaultEmbeddingUpdated --&gt; RegistryReloaded

 ProviderDetailLoaded --&gt; DeletingProvider: DELETE /api/llm-provider/{id}
 DeletingProvider --&gt; ProviderDeleted: 非默认Provider
 DeletingProvider --&gt; Error: Provider不存在 / 默认Provider不可删除
 ProviderDeleted --&gt; RegistryReloaded

 ProviderListLoaded --&gt; ManualReloading: POST /api/llm-provider/reload
 ManualReloading --&gt; RegistryReloaded

 ProviderConfigPage --&gt; VoiceConfigLoaded: GET /voice/asr 或 GET /voice/tts
 VoiceConfigLoaded --&gt; UpdatingVoiceConfig: PUT /voice/asr 或 PUT /voice/tts
 UpdatingVoiceConfig --&gt; VoiceConfigSaved: 写入YAML并reload ASR/TTS服务
 UpdatingVoiceConfig --&gt; Error: 配置写入失败

 VoiceConfigLoaded --&gt; TestingAsr: POST /voice/asr/test
 TestingAsr --&gt; AsrTestSuccess: WebSocket端口连接成功
 TestingAsr --&gt; AsrTestFailed: 连接失败
 AsrTestSuccess --&gt; VoiceConfigLoaded
 AsrTestFailed --&gt; VoiceConfigLoaded

 Error --&gt; ProviderConfigPage: 前端提示错误后返回配置页&lt;/pre&gt;&lt;h2 id="核心设计"&gt;&lt;a href="#%e6%a0%b8%e5%bf%83%e8%ae%be%e8%ae%a1" class="header-anchor"&gt;&lt;/a&gt;核心设计
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;llm-provider&lt;/code&gt; 模块的核心是把“模型配置读取、密钥保护、默认模型选择、运行时客户端缓存”放在同一套服务中管理。&lt;/p&gt;
&lt;p&gt;DB 模式下，Provider 配置来自数据库。服务层读取 &lt;code&gt;LlmProviderEntity&lt;/code&gt; 后，会先解密 API Key，再做脱敏，然后转换为 &lt;code&gt;ProviderDTO&lt;/code&gt; 返回给前端。API Key 明文只在服务端运行时短暂出现，不会通过接口返回。&lt;/p&gt;
&lt;p&gt;Legacy 模式下，Provider 配置来自 &lt;code&gt;ConfigurationProperties&lt;/code&gt;。创建、更新和删除时会同步修改 YAML 配置文件和 &lt;code&gt;.env&lt;/code&gt; 文件，并在修改完成后重载 Provider 注册表。&lt;/p&gt;
&lt;p&gt;模块通过 &lt;code&gt;rwLock&lt;/code&gt; 控制并发读写：查询类接口使用读锁，创建、更新、删除和默认值修改使用写锁，避免配置在读写过程中出现不一致。&lt;/p&gt;
&lt;h2 id="provider-列表查询"&gt;&lt;a href="#provider-%e5%88%97%e8%a1%a8%e6%9f%a5%e8%af%a2" class="header-anchor"&gt;&lt;/a&gt;Provider 列表查询
&lt;/h2&gt;&lt;h3 id="get-apillm-providerlist-获取全部-provider-列表"&gt;&lt;a href="#get-apillm-providerlist-%e8%8e%b7%e5%8f%96%e5%85%a8%e9%83%a8-provider-%e5%88%97%e8%a1%a8" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;GET /api/llm-provider/list&lt;/code&gt; 获取全部 Provider 列表
&lt;/h3&gt;&lt;p&gt;返回：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Result&amp;lt;List&amp;lt;ProviderDTO&amp;gt;&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;调用链：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;providerController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listProviders&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;providerService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listProviders&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;globalSettingRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;1L&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;providerRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;encryptionService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;处理流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Controller 调用 &lt;code&gt;listProviders()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Service 获取 &lt;code&gt;rwLock.readLock()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;DB 模式下先查询全局配置，用于判断默认 Chat Provider 和默认 Embedding Provider。&lt;/li&gt;
&lt;li&gt;查询全部 &lt;code&gt;LlmProviderEntity&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;遍历每个 Provider：
&lt;ul&gt;
&lt;li&gt;解密 API Key。&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;maskApiKey(...)&lt;/code&gt; 脱敏。&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;resolveEmbeddingDimensions(...)&lt;/code&gt; 解析向量维度，未配置时使用全局默认值。&lt;/li&gt;
&lt;li&gt;映射为 &lt;code&gt;ProviderDTO&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Legacy 模式下从 &lt;code&gt;properties.getProviders()&lt;/code&gt; 读取内存配置。&lt;/li&gt;
&lt;li&gt;返回 Provider 列表。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;关键点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DB 模式读取失败时会抛出 &lt;code&gt;BusinessException(PROVIDER_CONFIG_READ_FAILED)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;API Key 永远不会以明文返回给前端。&lt;/li&gt;
&lt;li&gt;当前存在一个问题：如果已启用 DB 存储 LLM 配置，更新配置文件和 API Key 后，即使重启项目也不会自动同步到 DB，除非关闭 DB 模式或清理数据库配置。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="get-apillm-providerid-获取单个-provider-详情"&gt;&lt;a href="#get-apillm-providerid-%e8%8e%b7%e5%8f%96%e5%8d%95%e4%b8%aa-provider-%e8%af%a6%e6%83%85" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;GET /api/llm-provider/{id}&lt;/code&gt; 获取单个 Provider 详情
&lt;/h3&gt;&lt;p&gt;返回：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Result&amp;lt;ProviderDTO&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;处理流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Controller 接收 Provider &lt;code&gt;id&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Service 获取读锁。&lt;/li&gt;
&lt;li&gt;DB 模式下查询全局配置和目标 Provider。&lt;/li&gt;
&lt;li&gt;Provider 不存在时抛出 &lt;code&gt;BusinessException(PROVIDER_NOT_FOUND)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;解密 API Key 后脱敏。&lt;/li&gt;
&lt;li&gt;解析 Embedding 维度并构建 &lt;code&gt;ProviderDTO&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Legacy 模式下从内存配置中按 &lt;code&gt;id&lt;/code&gt; 获取 Provider。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="provider-创建与更新"&gt;&lt;a href="#provider-%e5%88%9b%e5%bb%ba%e4%b8%8e%e6%9b%b4%e6%96%b0" class="header-anchor"&gt;&lt;/a&gt;Provider 创建与更新
&lt;/h2&gt;&lt;h3 id="post-apillm-provider-创建新-provider"&gt;&lt;a href="#post-apillm-provider-%e5%88%9b%e5%bb%ba%e6%96%b0-provider" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;POST /api/llm-provider&lt;/code&gt; 创建新 Provider
&lt;/h3&gt;&lt;p&gt;返回：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Result&amp;lt;Void&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;调用链：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;providerService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;providerRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;existsById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;validateEmbeddingConfig&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;encryptionService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;providerRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;处理流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Controller 接收 &lt;code&gt;CreateProviderRequest&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;@Valid&lt;/code&gt; 校验 &lt;code&gt;id&lt;/code&gt;、&lt;code&gt;baseUrl&lt;/code&gt;、&lt;code&gt;apiKey&lt;/code&gt;、&lt;code&gt;model&lt;/code&gt; 均不能为空。&lt;/li&gt;
&lt;li&gt;Service 开启事务并获取写锁。&lt;/li&gt;
&lt;li&gt;DB 模式下先检查 Provider ID 是否已存在。&lt;/li&gt;
&lt;li&gt;对 &lt;code&gt;baseUrl&lt;/code&gt;、&lt;code&gt;model&lt;/code&gt;、&lt;code&gt;apiKey&lt;/code&gt; 做二次非空校验。&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;validateEmbeddingConfig(...)&lt;/code&gt; 校验 Embedding 配置。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;encryptionService.encrypt(apiKey)&lt;/code&gt; 加密 API Key。&lt;/li&gt;
&lt;li&gt;保存 &lt;code&gt;LlmProviderEntity&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;registry.reload()&lt;/code&gt; 清空运行时缓存。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Legacy 模式处理：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;检查 &lt;code&gt;properties.getProviders()&lt;/code&gt; 中是否已有相同 ID。&lt;/li&gt;
&lt;li&gt;构建 &lt;code&gt;ProviderConfig&lt;/code&gt; 并写入内存 Map。&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;writeProviderToYaml(...)&lt;/code&gt; 写回 YAML。&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;writeEnvValue(...)&lt;/code&gt; 写入 &lt;code&gt;.env&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;registry.reload()&lt;/code&gt; 重载缓存。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Embedding 校验逻辑：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;supportsEmbedding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;embeddingModel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 抛出错误&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;looksLikeChatModel&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 抛出错误并给出推荐&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;embeddingDimensions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 抛出错误&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="put-apillm-providerid-更新-provider"&gt;&lt;a href="#put-apillm-providerid-%e6%9b%b4%e6%96%b0-provider" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;PUT /api/llm-provider/{id}&lt;/code&gt; 更新 Provider
&lt;/h3&gt;&lt;p&gt;返回：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Result&amp;lt;Void&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;调用链：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;providerService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updateProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;providerRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;validateEmbeddingConfig&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;encryptionService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newApiKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;providerRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;处理流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Controller 接收 Provider &lt;code&gt;id&lt;/code&gt; 和 &lt;code&gt;UpdateProviderRequest&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Service 开启事务并获取写锁。&lt;/li&gt;
&lt;li&gt;DB 模式下根据 &lt;code&gt;id&lt;/code&gt; 查询 Provider。&lt;/li&gt;
&lt;li&gt;Provider 不存在时抛出 &lt;code&gt;BusinessException(PROVIDER_NOT_FOUND)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;按字段更新配置：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;baseUrl&lt;/code&gt;：&lt;code&gt;null&lt;/code&gt; 表示不修改，空字符串非法。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model&lt;/code&gt;：&lt;code&gt;null&lt;/code&gt; 表示不修改，空字符串非法。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;apiKey&lt;/code&gt;：&lt;code&gt;null&lt;/code&gt; 表示不修改，空字符串非法，更新时重新加密。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;embeddingModel&lt;/code&gt;：允许传 &lt;code&gt;null&lt;/code&gt; 清除。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;embeddingDimensions&lt;/code&gt;：按请求值更新。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;supportsEmbedding&lt;/code&gt;：按请求值更新。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;temperature&lt;/code&gt;：按请求值更新。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;validateEmbeddingConfig(...)&lt;/code&gt; 做完整校验。&lt;/li&gt;
&lt;li&gt;保存实体并重载缓存。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UpdateProviderRequest&lt;/code&gt; 没有 &lt;code&gt;@Valid&lt;/code&gt;，所有字段都是可选字段。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;null&lt;/code&gt; 表示不更新。&lt;/li&gt;
&lt;li&gt;空字符串视为非法输入。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="provider-删除与重载"&gt;&lt;a href="#provider-%e5%88%a0%e9%99%a4%e4%b8%8e%e9%87%8d%e8%bd%bd" class="header-anchor"&gt;&lt;/a&gt;Provider 删除与重载
&lt;/h2&gt;&lt;h3 id="delete-apillm-providerid-删除-provider"&gt;&lt;a href="#delete-apillm-providerid-%e5%88%a0%e9%99%a4-provider" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;DELETE /api/llm-provider/{id}&lt;/code&gt; 删除 Provider
&lt;/h3&gt;&lt;p&gt;返回：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Result&amp;lt;Void&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;处理流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service 开启事务并获取写锁。&lt;/li&gt;
&lt;li&gt;DB 模式下读取全局设置。&lt;/li&gt;
&lt;li&gt;判断当前 Provider 是否为默认 Chat Provider 或默认 Embedding Provider。&lt;/li&gt;
&lt;li&gt;如果是默认 Provider，抛出 &lt;code&gt;BusinessException(PROVIDER_DEFAULT_CANNOT_DELETE)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;查询目标 Provider，确认存在后删除。&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;registry.reload()&lt;/code&gt; 清空运行时缓存。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Legacy 模式处理：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;检查是否为默认 Provider。&lt;/li&gt;
&lt;li&gt;从内存 Map 中删除配置。&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;removeProviderFromYaml(...)&lt;/code&gt; 删除 YAML 节点。&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;removeFromEnv(...)&lt;/code&gt; 删除 &lt;code&gt;.env&lt;/code&gt; 中的 API Key。&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;registry.reload()&lt;/code&gt; 重载缓存。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;保护机制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;默认 Chat Provider 和默认 Embedding Provider 不允许直接删除。&lt;/li&gt;
&lt;li&gt;必须先切换默认值，再删除原 Provider。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="post-apillm-providerreload-手动重载-provider-缓存"&gt;&lt;a href="#post-apillm-providerreload-%e6%89%8b%e5%8a%a8%e9%87%8d%e8%bd%bd-provider-%e7%bc%93%e5%ad%98" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;POST /api/llm-provider/reload&lt;/code&gt; 手动重载 Provider 缓存
&lt;/h3&gt;&lt;p&gt;返回：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Result&amp;lt;Void&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;处理逻辑：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;clientCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;embeddingModelCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;该接口不加锁。&lt;/li&gt;
&lt;li&gt;不开启事务。&lt;/li&gt;
&lt;li&gt;不访问 DB。&lt;/li&gt;
&lt;li&gt;只清空内存中的 &lt;code&gt;ChatClient&lt;/code&gt; 和 &lt;code&gt;EmbeddingModel&lt;/code&gt; 缓存。&lt;/li&gt;
&lt;li&gt;下次调用 &lt;code&gt;getChatClient()&lt;/code&gt; 或获取 Embedding 模型时按最新配置重新构建。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="provider-连通性测试"&gt;&lt;a href="#provider-%e8%bf%9e%e9%80%9a%e6%80%a7%e6%b5%8b%e8%af%95" class="header-anchor"&gt;&lt;/a&gt;Provider 连通性测试
&lt;/h2&gt;&lt;h3 id="post-apillm-provideridtest-测试-provider-连接"&gt;&lt;a href="#post-apillm-provideridtest-%e6%b5%8b%e8%af%95-provider-%e8%bf%9e%e6%8e%a5" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;POST /api/llm-provider/{id}/test&lt;/code&gt; 测试 Provider 连接
&lt;/h3&gt;&lt;p&gt;返回：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Result&amp;lt;ProviderTestResult&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;处理流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service 获取读锁。&lt;/li&gt;
&lt;li&gt;根据模式读取运行时配置：
&lt;ul&gt;
&lt;li&gt;DB 模式下调用 &lt;code&gt;getProviderRuntimeConfigOrThrow(id)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Legacy 模式下调用 &lt;code&gt;toRuntimeConfig(...)&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;构建 &lt;code&gt;RestClient&lt;/code&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;connectTimeout = 5s&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;readTimeout = 10s&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Header 中设置 &lt;code&gt;Authorization: Bearer {apiKey}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;构建测试请求体：&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;model&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;xxx&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;messages&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;role&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;user&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;content&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Reply with OK only.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;max_tokens&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="5"&gt;
&lt;li&gt;构建候选测试 URL：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;baseUrl + &amp;quot;/chat/completions&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;baseUrl&lt;/code&gt; 不含版本号，再尝试 &lt;code&gt;baseUrl + &amp;quot;/v1/chat/completions&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;依次向候选 URL 发送 POST 请求。&lt;/li&gt;
&lt;li&gt;任一 URL 成功时返回连接成功。&lt;/li&gt;
&lt;li&gt;全部失败时返回最后一次失败原因。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这是 Provider 管理中唯一会直接调用外部 LLM API 的接口。&lt;/li&gt;
&lt;li&gt;测试请求会发送真实 HTTP 请求。&lt;/li&gt;
&lt;li&gt;HTTP 错误会记录状态码和响应体，普通异常会记录异常类型和错误信息。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="默认-provider-管理"&gt;&lt;a href="#%e9%bb%98%e8%ae%a4-provider-%e7%ae%a1%e7%90%86" class="header-anchor"&gt;&lt;/a&gt;默认 Provider 管理
&lt;/h2&gt;&lt;h3 id="get-apillm-providerdefault-provider-获取默认-provider"&gt;&lt;a href="#get-apillm-providerdefault-provider-%e8%8e%b7%e5%8f%96%e9%bb%98%e8%ae%a4-provider" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;GET /api/llm-provider/default-provider&lt;/code&gt; 获取默认 Provider
&lt;/h3&gt;&lt;p&gt;返回：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Result&amp;lt;DefaultProviderDTO&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;处理流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service 获取读锁。&lt;/li&gt;
&lt;li&gt;DB 模式下查询 &lt;code&gt;globalSettingRepository.findById(1L)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;返回默认 Chat Provider ID 和默认 Embedding Provider ID。&lt;/li&gt;
&lt;li&gt;Legacy 模式下从 &lt;code&gt;properties.defaultProvider&lt;/code&gt; 和 &lt;code&gt;properties.defaultEmbeddingProvider&lt;/code&gt; 构建返回值。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;返回结构：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;defaultProvider&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;dashscope&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;defaultEmbeddingProvider&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;dashscope&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="put-apillm-providerdefault-provider-设置默认-chat-provider"&gt;&lt;a href="#put-apillm-providerdefault-provider-%e8%ae%be%e7%bd%ae%e9%bb%98%e8%ae%a4-chat-provider" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;PUT /api/llm-provider/default-provider&lt;/code&gt; 设置默认 Chat Provider
&lt;/h3&gt;&lt;p&gt;返回：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Result&amp;lt;Void&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;处理流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service 开启事务并获取写锁。&lt;/li&gt;
&lt;li&gt;读取 &lt;code&gt;request.defaultProvider()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;默认 Provider 为空时抛出 &lt;code&gt;BAD_REQUEST&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;查询目标 Provider，确认存在。&lt;/li&gt;
&lt;li&gt;DB 模式下更新 &lt;code&gt;GlobalSettingEntity.defaultChatProviderId&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;保存全局设置。&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;registry.reload()&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Legacy 模式处理：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;校验 Provider 存在。&lt;/li&gt;
&lt;li&gt;修改 &lt;code&gt;properties.setDefaultProvider(providerId)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;writeDefaultProviderToYaml(providerId)&lt;/code&gt; 写回配置。&lt;/li&gt;
&lt;li&gt;删除旧的 &lt;code&gt;module-defaults&lt;/code&gt; 配置。&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;registry.reload()&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="put-apillm-providerdefault-embedding-provider-设置默认-embedding-provider"&gt;&lt;a href="#put-apillm-providerdefault-embedding-provider-%e8%ae%be%e7%bd%ae%e9%bb%98%e8%ae%a4-embedding-provider" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;PUT /api/llm-provider/default-embedding-provider&lt;/code&gt; 设置默认 Embedding Provider
&lt;/h3&gt;&lt;p&gt;返回：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Result&amp;lt;Void&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;处理流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service 开启事务并获取写锁。&lt;/li&gt;
&lt;li&gt;读取 &lt;code&gt;request.defaultEmbeddingProvider()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;默认 Embedding Provider 为空时抛出 &lt;code&gt;BAD_REQUEST&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;查询目标 Provider，确认存在。&lt;/li&gt;
&lt;li&gt;校验该 Provider 支持 Embedding：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;supportsEmbedding&lt;/code&gt; 必须为 &lt;code&gt;true&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;embeddingModel&lt;/code&gt; 必须存在。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;validateEmbeddingConfig(...)&lt;/code&gt; 必须通过。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DB 模式下更新 &lt;code&gt;GlobalSettingEntity.defaultEmbeddingProviderId&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;保存全局设置。&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;registry.reload()&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;与默认 Chat Provider 的差异：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置默认 Embedding Provider 时多了 Embedding 能力校验。&lt;/li&gt;
&lt;li&gt;不支持 Embedding 的 Provider 不能被设置为默认向量服务。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="asr-配置管理"&gt;&lt;a href="#asr-%e9%85%8d%e7%bd%ae%e7%ae%a1%e7%90%86" class="header-anchor"&gt;&lt;/a&gt;ASR 配置管理
&lt;/h2&gt;&lt;h3 id="get-apillm-providervoiceasr-获取-asr-配置"&gt;&lt;a href="#get-apillm-providervoiceasr-%e8%8e%b7%e5%8f%96-asr-%e9%85%8d%e7%bd%ae" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;GET /api/llm-provider/voice/asr&lt;/code&gt; 获取 ASR 配置
&lt;/h3&gt;&lt;p&gt;返回：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Result&amp;lt;AsrConfigDTO&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;处理流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service 获取读锁。&lt;/li&gt;
&lt;li&gt;从 &lt;code&gt;VoiceInterviewProperties&lt;/code&gt; 读取 &lt;code&gt;voiceProperties.getQwen().getAsr()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;构建 &lt;code&gt;AsrConfigDTO&lt;/code&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;url&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;language&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;format&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sampleRate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;maskedApiKey&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;enableTurnDetection&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;turnDetectionType&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;turnDetectionThreshold&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;turnDetectionSilenceDurationMs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;VAD 相关参数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;返回脱敏后的 ASR 配置。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ASR 配置来源于 &lt;code&gt;VoiceInterviewProperties&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;配置前缀是 &lt;code&gt;app.voice-interview&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;该配置不走 DB。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="put-apillm-providervoiceasr-更新-asr-配置"&gt;&lt;a href="#put-apillm-providervoiceasr-%e6%9b%b4%e6%96%b0-asr-%e9%85%8d%e7%bd%ae" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;PUT /api/llm-provider/voice/asr&lt;/code&gt; 更新 ASR 配置
&lt;/h3&gt;&lt;p&gt;返回：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Result&amp;lt;Void&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;处理流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service 获取写锁。&lt;/li&gt;
&lt;li&gt;读取运行时 ASR 和 TTS 配置引用。&lt;/li&gt;
&lt;li&gt;按字段更新 ASR 配置：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;url&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;language&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;format&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sampleRate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;enableTurnDetection&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;turnDetectionType&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;turnDetectionThreshold&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;turnDetectionSilenceDurationMs&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果更新了 API Key，则同步更新 ASR 和 TTS：&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;asr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setApiKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;tts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setApiKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;updateEnvValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;AI_BAILIAN_API_KEY&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="5"&gt;
&lt;li&gt;调用 &lt;code&gt;writeAsrConfigToYaml(asr)&lt;/code&gt; 写回 YAML。&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;asrService.reload(voiceProperties)&lt;/code&gt; 重载 ASR 服务。&lt;/li&gt;
&lt;li&gt;如果 API Key 更新，则同步调用 &lt;code&gt;ttsService.reload(voiceProperties)&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;该方法没有 &lt;code&gt;@Transactional&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;ASR 和 TTS 共享百炼 API Key。&lt;/li&gt;
&lt;li&gt;修改 ASR 的 API Key 会同步影响 TTS。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="tts-配置管理"&gt;&lt;a href="#tts-%e9%85%8d%e7%bd%ae%e7%ae%a1%e7%90%86" class="header-anchor"&gt;&lt;/a&gt;TTS 配置管理
&lt;/h2&gt;&lt;h3 id="get-apillm-providervoicetts-获取-tts-配置"&gt;&lt;a href="#get-apillm-providervoicetts-%e8%8e%b7%e5%8f%96-tts-%e9%85%8d%e7%bd%ae" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;GET /api/llm-provider/voice/tts&lt;/code&gt; 获取 TTS 配置
&lt;/h3&gt;&lt;p&gt;返回：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Result&amp;lt;TtsConfigDTO&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;处理流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service 获取读锁。&lt;/li&gt;
&lt;li&gt;从 &lt;code&gt;VoiceInterviewProperties&lt;/code&gt; 读取 &lt;code&gt;voiceProperties.getQwen().getTts()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;构建 &lt;code&gt;TtsConfigDTO&lt;/code&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;model&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;maskedApiKey&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;voice&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;format&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sampleRate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;languageType&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;speechRate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;volume&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;返回脱敏后的 TTS 配置。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="put-apillm-providervoicetts-更新-tts-配置"&gt;&lt;a href="#put-apillm-providervoicetts-%e6%9b%b4%e6%96%b0-tts-%e9%85%8d%e7%bd%ae" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;PUT /api/llm-provider/voice/tts&lt;/code&gt; 更新 TTS 配置
&lt;/h3&gt;&lt;p&gt;返回：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Result&amp;lt;Void&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;处理流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service 获取写锁。&lt;/li&gt;
&lt;li&gt;读取运行时 ASR 和 TTS 配置引用。&lt;/li&gt;
&lt;li&gt;按字段更新 TTS 配置：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;model&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;voice&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;format&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sampleRate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;languageType&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;speechRate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;volume&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果更新了 API Key，则同步更新 TTS 和 ASR：&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;tts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setApiKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;asr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setApiKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;updateEnvValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;AI_BAILIAN_API_KEY&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="5"&gt;
&lt;li&gt;调用 &lt;code&gt;writeTtsConfigToYaml(tts)&lt;/code&gt; 写回 YAML。&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;ttsService.reload(voiceProperties)&lt;/code&gt; 重载 TTS 服务。&lt;/li&gt;
&lt;li&gt;如果 API Key 更新，则同步调用 &lt;code&gt;asrService.reload(voiceProperties)&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TTS 更新逻辑与 ASR 对称。&lt;/li&gt;
&lt;li&gt;ASR/TTS 的 API Key 始终联动更新。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="asr-连通性测试"&gt;&lt;a href="#asr-%e8%bf%9e%e9%80%9a%e6%80%a7%e6%b5%8b%e8%af%95" class="header-anchor"&gt;&lt;/a&gt;ASR 连通性测试
&lt;/h2&gt;&lt;h3 id="post-apillm-providervoiceasrtest-测试-asr-连接"&gt;&lt;a href="#post-apillm-providervoiceasrtest-%e6%b5%8b%e8%af%95-asr-%e8%bf%9e%e6%8e%a5" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;POST /api/llm-provider/voice/asr/test&lt;/code&gt; 测试 ASR 连接
&lt;/h3&gt;&lt;p&gt;返回：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Result&amp;lt;ProviderTestResult&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;处理流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service 获取读锁。&lt;/li&gt;
&lt;li&gt;从 &lt;code&gt;voiceProperties.getQwen().getAsr()&lt;/code&gt; 读取 ASR 配置。&lt;/li&gt;
&lt;li&gt;解析 WebSocket URL：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;wss&lt;/code&gt; 默认端口为 &lt;code&gt;443&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ws&lt;/code&gt; 默认端口为 &lt;code&gt;80&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;使用 TCP Socket 发起连接测试：&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="5"&gt;
&lt;li&gt;连接成功时返回：&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ProviderTestResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ASR WebSocket 连接成功: host&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="6"&gt;
&lt;li&gt;连接失败时返回失败原因。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;与 Provider 连通性测试的差异：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ASR 测试只做 TCP Socket 连接。&lt;/li&gt;
&lt;li&gt;不发送 WebSocket 握手。&lt;/li&gt;
&lt;li&gt;不调用真实 ASR 识别接口。&lt;/li&gt;
&lt;li&gt;Provider 测试会发送真实 HTTP 请求到 LLM 服务。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="缓存与运行时行为"&gt;&lt;a href="#%e7%bc%93%e5%ad%98%e4%b8%8e%e8%bf%90%e8%a1%8c%e6%97%b6%e8%a1%8c%e4%b8%ba" class="header-anchor"&gt;&lt;/a&gt;缓存与运行时行为
&lt;/h2&gt;&lt;p&gt;Provider 配置变更后都会调用 &lt;code&gt;registry.reload()&lt;/code&gt;。这个方法会清空内部缓存：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;clientCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;embeddingModelCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;因此，配置变更不会立即创建新的客户端，而是在下一次业务代码调用 Provider 时按需重建。这种方式避免了更新接口直接承担模型客户端初始化成本，也能保证旧配置不会长期停留在缓存中。&lt;/p&gt;
&lt;p&gt;需要注意的是，&lt;code&gt;reload&lt;/code&gt; 只负责清空缓存，不负责同步配置源。如果 DB 模式已经启用，系统会优先读取数据库配置，而不是重新从 YAML 或 &lt;code&gt;.env&lt;/code&gt; 导入配置。&lt;/p&gt;
&lt;h2 id="当前问题与优化方向"&gt;&lt;a href="#%e5%bd%93%e5%89%8d%e9%97%ae%e9%a2%98%e4%b8%8e%e4%bc%98%e5%8c%96%e6%96%b9%e5%90%91" class="header-anchor"&gt;&lt;/a&gt;当前问题与优化方向
&lt;/h2&gt;&lt;p&gt;当前模块已经支持 DB 模式和 Legacy 模式，但配置同步边界还需要进一步明确：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DB 模式启用后，YAML 和 &lt;code&gt;.env&lt;/code&gt; 的修改不会自动回写数据库。&lt;/li&gt;
&lt;li&gt;重启项目只能重新加载运行时配置，不能解决 DB 配置与文件配置不一致的问题。&lt;/li&gt;
&lt;li&gt;手动 &lt;code&gt;reload&lt;/code&gt; 只清空运行时缓存，不会重新导入配置源。&lt;/li&gt;
&lt;li&gt;ASR/TTS 配置仍来自 &lt;code&gt;VoiceInterviewProperties&lt;/code&gt;，与 Provider DB 配置不是同一套存储。&lt;/li&gt;
&lt;li&gt;ASR/TTS 更新方法没有事务，写 YAML、写 &lt;code&gt;.env&lt;/code&gt;、服务重载之间存在部分成功的可能。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;后续可按以下方向优化：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;增加 DB 模式下的配置导入接口，用于从 YAML 和 &lt;code&gt;.env&lt;/code&gt; 同步 Provider 到数据库。&lt;/li&gt;
&lt;li&gt;在启动阶段增加一次性迁移策略，明确 DB 优先还是配置文件优先。&lt;/li&gt;
&lt;li&gt;给 Provider 配置增加版本号或更新时间，便于排查缓存是否已刷新。&lt;/li&gt;
&lt;li&gt;将 ASR/TTS 配置纳入统一配置存储，减少双配置源带来的不一致。&lt;/li&gt;
&lt;li&gt;对 YAML 写入、&lt;code&gt;.env&lt;/code&gt; 写入和服务重载增加失败补偿或更明确的错误提示。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;llm-provider&lt;/code&gt; 模块承担了大模型能力的统一配置入口。它不仅管理 Chat Provider，还管理 Embedding Provider、默认模型、运行时缓存和语音 ASR/TTS 配置。模块的关键价值在于：把模型配置和业务调用解耦，让知识库、RAG 聊天、语音面试等上层能力都可以通过统一 Provider 注册表获取模型能力。后续重点是进一步梳理 DB 配置和文件配置的同步机制，让配置来源更清晰、运行时状态更可控。&lt;/p&gt;</description></item></channel></rss>