<?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 Blog</title><link>https://xedczq.cn/en/tags/provider/</link><description>Recent content in Provider on XEDCZQ Blog</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Tue, 09 Jun 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://xedczq.cn/en/tags/provider/index.xml" rel="self" type="application/rss+xml"/><item><title>AI Interview Project: llm-provider Module</title><link>https://xedczq.cn/en/post/aiinterview_llmprovider/</link><pubDate>Tue, 09 Jun 2026 00:00:00 +0800</pubDate><guid>https://xedczq.cn/en/post/aiinterview_llmprovider/</guid><description>&lt;h2 id="llm-provider-module-design-and-implementation"&gt;&lt;a href="#llm-provider-module-design-and-implementation" class="header-anchor"&gt;&lt;/a&gt;Llm-provider Module Design and Implementation
&lt;/h2&gt;&lt;p&gt;This note records the design and API implementation of the &lt;code&gt;llm-provider&lt;/code&gt; module in the &lt;code&gt;interview-guide&lt;/code&gt; project. This module is responsible for unified management of large model Provider configuration, including model lists, default models, Embedding capability, connectivity testing, and ASR/TTS runtime configuration for voice interviews.&lt;/p&gt;
&lt;h2 id="module-capability-overview"&gt;&lt;a href="#module-capability-overview" class="header-anchor"&gt;&lt;/a&gt;Module Capability Overview
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Provider management: supports querying, creating, updating, and deleting LLM Providers.&lt;/li&gt;
&lt;li&gt;Dual storage modes: supports both DB mode and Legacy configuration-file mode.&lt;/li&gt;
&lt;li&gt;Secret protection: in DB mode, API Keys are encrypted with AES-GCM and masked before being returned by APIs.&lt;/li&gt;
&lt;li&gt;Default model management: separates the default Chat Provider from the default Embedding Provider.&lt;/li&gt;
&lt;li&gt;Cache reload: after Provider changes, clears &lt;code&gt;ChatClient&lt;/code&gt; and &lt;code&gt;EmbeddingModel&lt;/code&gt; caches and rebuilds them on next use.&lt;/li&gt;
&lt;li&gt;Embedding validation: validates model type, dimensions, and capability switches when creating, updating, or setting the default Embedding Provider.&lt;/li&gt;
&lt;li&gt;Connectivity testing: supports sending real HTTP test requests to LLM Providers.&lt;/li&gt;
&lt;li&gt;Voice configuration management: supports reading and updating Qwen ASR/TTS configuration and reloading runtime services.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="flowchart"&gt;&lt;a href="#flowchart" class="header-anchor"&gt;&lt;/a&gt;Flowchart
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;stateDiagram-v2
 [*] --&gt; ProviderConfigPage: Open LLM Provider config page

 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: Validation passed and saved
 CreatingProvider --&gt; Error: duplicated id / invalid params / write failure

 ProviderDetailLoaded --&gt; UpdatingProvider: PUT /api/llm-provider/{id}
 UpdatingProvider --&gt; ProviderSaved: Partial fields updated
 UpdatingProvider --&gt; Error: Provider missing / invalid params / write failure

 ProviderSaved --&gt; RegistryReloaded: registry.reload()
 RegistryReloaded --&gt; ProviderListLoaded: Reload list

 ProviderDetailLoaded --&gt; TestingProvider: POST /api/llm-provider/{id}/test
 TestingProvider --&gt; TestSuccess: External LLM API connected
 TestingProvider --&gt; TestFailed: connection failed / auth failed / model unavailable
 TestSuccess --&gt; ProviderDetailLoaded
 TestFailed --&gt; ProviderDetailLoaded

 ProviderDetailLoaded --&gt; UpdatingDefaultChat: PUT /api/llm-provider/default-provider
 UpdatingDefaultChat --&gt; DefaultChatUpdated: Provider exists
 UpdatingDefaultChat --&gt; Error: Provider missing / defaultProvider empty

 ProviderDetailLoaded --&gt; UpdatingDefaultEmbedding: PUT /api/llm-provider/default-embedding-provider
 UpdatingDefaultEmbedding --&gt; DefaultEmbeddingUpdated: Embedding supported
 UpdatingDefaultEmbedding --&gt; Error: Provider missing / Embedding unsupported

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

 ProviderDetailLoaded --&gt; DeletingProvider: DELETE /api/llm-provider/{id}
 DeletingProvider --&gt; ProviderDeleted: Not a default Provider
 DeletingProvider --&gt; Error: Provider missing / default Provider cannot be deleted
 ProviderDeleted --&gt; RegistryReloaded

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

 ProviderConfigPage --&gt; VoiceConfigLoaded: GET /voice/asr or GET /voice/tts
 VoiceConfigLoaded --&gt; UpdatingVoiceConfig: PUT /voice/asr or PUT /voice/tts
 UpdatingVoiceConfig --&gt; VoiceConfigSaved: Write YAML and reload ASR/TTS service
 UpdatingVoiceConfig --&gt; Error: config write failure

 VoiceConfigLoaded --&gt; TestingAsr: POST /voice/asr/test
 TestingAsr --&gt; AsrTestSuccess: WebSocket port connected
 TestingAsr --&gt; AsrTestFailed: connection failed
 AsrTestSuccess --&gt; VoiceConfigLoaded
 AsrTestFailed --&gt; VoiceConfigLoaded

 Error --&gt; ProviderConfigPage: Show error and return to config page&lt;/pre&gt;&lt;h2 id="core-design"&gt;&lt;a href="#core-design" class="header-anchor"&gt;&lt;/a&gt;Core Design
&lt;/h2&gt;&lt;p&gt;The core of the &lt;code&gt;llm-provider&lt;/code&gt; module is managing model configuration reads, secret protection, default model selection, and runtime client caches in one service.&lt;/p&gt;
&lt;p&gt;In DB mode, Provider configuration comes from the database. After the service reads &lt;code&gt;LlmProviderEntity&lt;/code&gt;, it decrypts the API Key, masks it, and converts the result into &lt;code&gt;ProviderDTO&lt;/code&gt; for the frontend. The plaintext API Key only appears briefly at runtime on the server side and is never returned by the API.&lt;/p&gt;
&lt;p&gt;In Legacy mode, Provider configuration comes from &lt;code&gt;ConfigurationProperties&lt;/code&gt;. Create, update, and delete operations also modify the YAML configuration file and &lt;code&gt;.env&lt;/code&gt; file, then reload the Provider registry after the update.&lt;/p&gt;
&lt;p&gt;The module uses &lt;code&gt;rwLock&lt;/code&gt; to control concurrent reads and writes. Query APIs use a read lock, while create, update, delete, and default-value updates use a write lock to avoid inconsistent configuration during concurrent access.&lt;/p&gt;
&lt;h2 id="provider-list-query"&gt;&lt;a href="#provider-list-query" class="header-anchor"&gt;&lt;/a&gt;Provider List Query
&lt;/h2&gt;&lt;h3 id="get-apillm-providerlist-get-all-providers"&gt;&lt;a href="#get-apillm-providerlist-get-all-providers" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;GET /api/llm-provider/list&lt;/code&gt; Get All Providers
&lt;/h3&gt;&lt;p&gt;Returns:&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;Call chain:&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;Flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Controller calls &lt;code&gt;listProviders()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Service acquires &lt;code&gt;rwLock.readLock()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In DB mode, it first queries global settings to identify the default Chat Provider and default Embedding Provider.&lt;/li&gt;
&lt;li&gt;It queries all &lt;code&gt;LlmProviderEntity&lt;/code&gt; records.&lt;/li&gt;
&lt;li&gt;It iterates over each Provider:
&lt;ul&gt;
&lt;li&gt;Decrypts the API Key.&lt;/li&gt;
&lt;li&gt;Calls &lt;code&gt;maskApiKey(...)&lt;/code&gt; to mask it.&lt;/li&gt;
&lt;li&gt;Calls &lt;code&gt;resolveEmbeddingDimensions(...)&lt;/code&gt; to resolve vector dimensions, using the global default when not configured.&lt;/li&gt;
&lt;li&gt;Maps it to &lt;code&gt;ProviderDTO&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;In Legacy mode, it reads in-memory configuration from &lt;code&gt;properties.getProviders()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It returns the Provider list.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Key points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DB read failures throw &lt;code&gt;BusinessException(PROVIDER_CONFIG_READ_FAILED)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;API Keys are never returned to the frontend in plaintext.&lt;/li&gt;
&lt;li&gt;There is a current issue: if DB storage is enabled for LLM configuration, changes to configuration files and API Keys will not automatically sync to DB even after restarting the project, unless DB mode is disabled or the database configuration is cleaned.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="get-apillm-providerid-get-a-single-provider"&gt;&lt;a href="#get-apillm-providerid-get-a-single-provider" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;GET /api/llm-provider/{id}&lt;/code&gt; Get a Single Provider
&lt;/h3&gt;&lt;p&gt;Returns:&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;Flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Controller receives the Provider &lt;code&gt;id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Service acquires the read lock.&lt;/li&gt;
&lt;li&gt;In DB mode, it queries global settings and the target Provider.&lt;/li&gt;
&lt;li&gt;If the Provider does not exist, it throws &lt;code&gt;BusinessException(PROVIDER_NOT_FOUND)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It decrypts the API Key and masks it.&lt;/li&gt;
&lt;li&gt;It resolves Embedding dimensions and builds &lt;code&gt;ProviderDTO&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In Legacy mode, it gets the Provider by &lt;code&gt;id&lt;/code&gt; from in-memory configuration.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="provider-creation-and-update"&gt;&lt;a href="#provider-creation-and-update" class="header-anchor"&gt;&lt;/a&gt;Provider Creation and Update
&lt;/h2&gt;&lt;h3 id="post-apillm-provider-create-a-provider"&gt;&lt;a href="#post-apillm-provider-create-a-provider" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;POST /api/llm-provider&lt;/code&gt; Create a Provider
&lt;/h3&gt;&lt;p&gt;Returns:&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;Call chain:&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;Flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Controller receives &lt;code&gt;CreateProviderRequest&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Valid&lt;/code&gt; ensures &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;baseUrl&lt;/code&gt;, &lt;code&gt;apiKey&lt;/code&gt;, and &lt;code&gt;model&lt;/code&gt; are not blank.&lt;/li&gt;
&lt;li&gt;Service starts a transaction and acquires the write lock.&lt;/li&gt;
&lt;li&gt;In DB mode, it first checks whether the Provider ID already exists.&lt;/li&gt;
&lt;li&gt;It performs secondary non-blank validation on &lt;code&gt;baseUrl&lt;/code&gt;, &lt;code&gt;model&lt;/code&gt;, and &lt;code&gt;apiKey&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It calls &lt;code&gt;validateEmbeddingConfig(...)&lt;/code&gt; to validate Embedding configuration.&lt;/li&gt;
&lt;li&gt;It encrypts the API Key with &lt;code&gt;encryptionService.encrypt(apiKey)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It saves &lt;code&gt;LlmProviderEntity&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It calls &lt;code&gt;registry.reload()&lt;/code&gt; to clear runtime caches.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Legacy mode handling:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Checks whether &lt;code&gt;properties.getProviders()&lt;/code&gt; already contains the same ID.&lt;/li&gt;
&lt;li&gt;Builds &lt;code&gt;ProviderConfig&lt;/code&gt; and puts it into the in-memory Map.&lt;/li&gt;
&lt;li&gt;Calls &lt;code&gt;writeProviderToYaml(...)&lt;/code&gt; to write back to YAML.&lt;/li&gt;
&lt;li&gt;Calls &lt;code&gt;writeEnvValue(...)&lt;/code&gt; to write to &lt;code&gt;.env&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Calls &lt;code&gt;registry.reload()&lt;/code&gt; to reload caches.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Embedding validation logic:&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;// throw error&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;// throw error and recommend a model&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;// throw error&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-update-a-provider"&gt;&lt;a href="#put-apillm-providerid-update-a-provider" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;PUT /api/llm-provider/{id}&lt;/code&gt; Update a Provider
&lt;/h3&gt;&lt;p&gt;Returns:&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;Call chain:&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;Flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Controller receives Provider &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;UpdateProviderRequest&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Service starts a transaction and acquires the write lock.&lt;/li&gt;
&lt;li&gt;In DB mode, it queries the Provider by &lt;code&gt;id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the Provider does not exist, it throws &lt;code&gt;BusinessException(PROVIDER_NOT_FOUND)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It updates fields selectively:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;baseUrl&lt;/code&gt;: &lt;code&gt;null&lt;/code&gt; means no update; empty string is illegal.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model&lt;/code&gt;: &lt;code&gt;null&lt;/code&gt; means no update; empty string is illegal.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;apiKey&lt;/code&gt;: &lt;code&gt;null&lt;/code&gt; means no update; empty string is illegal; re-encrypted when updated.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;embeddingModel&lt;/code&gt;: can be &lt;code&gt;null&lt;/code&gt; to clear it.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;embeddingDimensions&lt;/code&gt;: updated from the request value.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;supportsEmbedding&lt;/code&gt;: updated from the request value.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;temperature&lt;/code&gt;: updated from the request value.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;It calls &lt;code&gt;validateEmbeddingConfig(...)&lt;/code&gt; for full validation.&lt;/li&gt;
&lt;li&gt;It saves the entity and reloads caches.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UpdateProviderRequest&lt;/code&gt; does not use &lt;code&gt;@Valid&lt;/code&gt;, and all fields are optional.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;null&lt;/code&gt; means do not update.&lt;/li&gt;
&lt;li&gt;Empty strings are treated as invalid input.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="provider-deletion-and-reload"&gt;&lt;a href="#provider-deletion-and-reload" class="header-anchor"&gt;&lt;/a&gt;Provider Deletion and Reload
&lt;/h2&gt;&lt;h3 id="delete-apillm-providerid-delete-a-provider"&gt;&lt;a href="#delete-apillm-providerid-delete-a-provider" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;DELETE /api/llm-provider/{id}&lt;/code&gt; Delete a Provider
&lt;/h3&gt;&lt;p&gt;Returns:&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;Flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service starts a transaction and acquires the write lock.&lt;/li&gt;
&lt;li&gt;In DB mode, it reads global settings.&lt;/li&gt;
&lt;li&gt;It checks whether the current Provider is the default Chat Provider or default Embedding Provider.&lt;/li&gt;
&lt;li&gt;If it is a default Provider, it throws &lt;code&gt;BusinessException(PROVIDER_DEFAULT_CANNOT_DELETE)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It queries the target Provider, confirms it exists, and deletes it.&lt;/li&gt;
&lt;li&gt;It calls &lt;code&gt;registry.reload()&lt;/code&gt; to clear runtime caches.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Legacy mode handling:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Checks whether the Provider is the default Provider.&lt;/li&gt;
&lt;li&gt;Removes the configuration from the in-memory Map.&lt;/li&gt;
&lt;li&gt;Calls &lt;code&gt;removeProviderFromYaml(...)&lt;/code&gt; to remove the YAML node.&lt;/li&gt;
&lt;li&gt;Calls &lt;code&gt;removeFromEnv(...)&lt;/code&gt; to remove the API Key line from &lt;code&gt;.env&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Calls &lt;code&gt;registry.reload()&lt;/code&gt; to reload caches.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Protection mechanism:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Default Chat Provider and default Embedding Provider cannot be deleted directly.&lt;/li&gt;
&lt;li&gt;The default value must be switched before deleting the original Provider.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="post-apillm-providerreload-manually-reload-provider-cache"&gt;&lt;a href="#post-apillm-providerreload-manually-reload-provider-cache" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;POST /api/llm-provider/reload&lt;/code&gt; Manually Reload Provider Cache
&lt;/h3&gt;&lt;p&gt;Returns:&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;Logic:&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;Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This API does not acquire a lock.&lt;/li&gt;
&lt;li&gt;It does not start a transaction.&lt;/li&gt;
&lt;li&gt;It does not access the database.&lt;/li&gt;
&lt;li&gt;It only clears in-memory &lt;code&gt;ChatClient&lt;/code&gt; and &lt;code&gt;EmbeddingModel&lt;/code&gt; caches.&lt;/li&gt;
&lt;li&gt;The next call to &lt;code&gt;getChatClient()&lt;/code&gt; or Embedding model retrieval rebuilds clients from the latest configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="provider-connectivity-test"&gt;&lt;a href="#provider-connectivity-test" class="header-anchor"&gt;&lt;/a&gt;Provider Connectivity Test
&lt;/h2&gt;&lt;h3 id="post-apillm-provideridtest-test-provider-connection"&gt;&lt;a href="#post-apillm-provideridtest-test-provider-connection" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;POST /api/llm-provider/{id}/test&lt;/code&gt; Test Provider Connection
&lt;/h3&gt;&lt;p&gt;Returns:&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;Flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service acquires the read lock.&lt;/li&gt;
&lt;li&gt;It reads runtime configuration based on the current mode:
&lt;ul&gt;
&lt;li&gt;In DB mode, calls &lt;code&gt;getProviderRuntimeConfigOrThrow(id)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In Legacy mode, calls &lt;code&gt;toRuntimeConfig(...)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;It builds a &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;It builds the test request body:&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;It builds candidate test URLs:
&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;If &lt;code&gt;baseUrl&lt;/code&gt; does not contain a version number, also try &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;It sends POST requests to candidate URLs in order.&lt;/li&gt;
&lt;li&gt;If any URL succeeds, it returns success.&lt;/li&gt;
&lt;li&gt;If all URLs fail, it returns the last failure reason.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This is the only Provider management API that directly calls an external LLM API.&lt;/li&gt;
&lt;li&gt;The test sends a real HTTP request.&lt;/li&gt;
&lt;li&gt;HTTP errors record status code and response body, while other exceptions record exception type and message.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="default-provider-management"&gt;&lt;a href="#default-provider-management" class="header-anchor"&gt;&lt;/a&gt;Default Provider Management
&lt;/h2&gt;&lt;h3 id="get-apillm-providerdefault-provider-get-default-providers"&gt;&lt;a href="#get-apillm-providerdefault-provider-get-default-providers" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;GET /api/llm-provider/default-provider&lt;/code&gt; Get Default Providers
&lt;/h3&gt;&lt;p&gt;Returns:&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;Flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service acquires the read lock.&lt;/li&gt;
&lt;li&gt;In DB mode, it queries &lt;code&gt;globalSettingRepository.findById(1L)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It returns the default Chat Provider ID and default Embedding Provider ID.&lt;/li&gt;
&lt;li&gt;In Legacy mode, it builds the response from &lt;code&gt;properties.defaultProvider&lt;/code&gt; and &lt;code&gt;properties.defaultEmbeddingProvider&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Response structure:&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-set-default-chat-provider"&gt;&lt;a href="#put-apillm-providerdefault-provider-set-default-chat-provider" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;PUT /api/llm-provider/default-provider&lt;/code&gt; Set Default Chat Provider
&lt;/h3&gt;&lt;p&gt;Returns:&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;Flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service starts a transaction and acquires the write lock.&lt;/li&gt;
&lt;li&gt;It reads &lt;code&gt;request.defaultProvider()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the default Provider is empty, it throws &lt;code&gt;BAD_REQUEST&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It queries the target Provider and confirms it exists.&lt;/li&gt;
&lt;li&gt;In DB mode, it updates &lt;code&gt;GlobalSettingEntity.defaultChatProviderId&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It saves global settings.&lt;/li&gt;
&lt;li&gt;It calls &lt;code&gt;registry.reload()&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Legacy mode handling:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Validates that the Provider exists.&lt;/li&gt;
&lt;li&gt;Updates &lt;code&gt;properties.setDefaultProvider(providerId)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Calls &lt;code&gt;writeDefaultProviderToYaml(providerId)&lt;/code&gt; to write configuration back.&lt;/li&gt;
&lt;li&gt;Removes the old &lt;code&gt;module-defaults&lt;/code&gt; configuration.&lt;/li&gt;
&lt;li&gt;Calls &lt;code&gt;registry.reload()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="put-apillm-providerdefault-embedding-provider-set-default-embedding-provider"&gt;&lt;a href="#put-apillm-providerdefault-embedding-provider-set-default-embedding-provider" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;PUT /api/llm-provider/default-embedding-provider&lt;/code&gt; Set Default Embedding Provider
&lt;/h3&gt;&lt;p&gt;Returns:&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;Flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service starts a transaction and acquires the write lock.&lt;/li&gt;
&lt;li&gt;It reads &lt;code&gt;request.defaultEmbeddingProvider()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the default Embedding Provider is empty, it throws &lt;code&gt;BAD_REQUEST&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It queries the target Provider and confirms it exists.&lt;/li&gt;
&lt;li&gt;It validates that the Provider supports Embedding:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;supportsEmbedding&lt;/code&gt; must be &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;embeddingModel&lt;/code&gt; must exist.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;validateEmbeddingConfig(...)&lt;/code&gt; must pass.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;In DB mode, it updates &lt;code&gt;GlobalSettingEntity.defaultEmbeddingProviderId&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It saves global settings.&lt;/li&gt;
&lt;li&gt;It calls &lt;code&gt;registry.reload()&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Difference from default Chat Provider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Setting the default Embedding Provider includes additional Embedding capability validation.&lt;/li&gt;
&lt;li&gt;A Provider that does not support Embedding cannot be set as the default vector service.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="asr-configuration-management"&gt;&lt;a href="#asr-configuration-management" class="header-anchor"&gt;&lt;/a&gt;ASR Configuration Management
&lt;/h2&gt;&lt;h3 id="get-apillm-providervoiceasr-get-asr-configuration"&gt;&lt;a href="#get-apillm-providervoiceasr-get-asr-configuration" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;GET /api/llm-provider/voice/asr&lt;/code&gt; Get ASR Configuration
&lt;/h3&gt;&lt;p&gt;Returns:&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;Flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service acquires the read lock.&lt;/li&gt;
&lt;li&gt;It reads &lt;code&gt;voiceProperties.getQwen().getAsr()&lt;/code&gt; from &lt;code&gt;VoiceInterviewProperties&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It builds &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-related parameters&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;It returns the masked ASR configuration.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ASR configuration comes from &lt;code&gt;VoiceInterviewProperties&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The configuration prefix is &lt;code&gt;app.voice-interview&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;This configuration does not use DB storage.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="put-apillm-providervoiceasr-update-asr-configuration"&gt;&lt;a href="#put-apillm-providervoiceasr-update-asr-configuration" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;PUT /api/llm-provider/voice/asr&lt;/code&gt; Update ASR Configuration
&lt;/h3&gt;&lt;p&gt;Returns:&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;Flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service acquires the write lock.&lt;/li&gt;
&lt;li&gt;It reads runtime ASR and TTS configuration references.&lt;/li&gt;
&lt;li&gt;It updates ASR fields selectively:
&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;If API Key is updated, it synchronizes ASR and 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;It calls &lt;code&gt;writeAsrConfigToYaml(asr)&lt;/code&gt; to write back to YAML.&lt;/li&gt;
&lt;li&gt;It calls &lt;code&gt;asrService.reload(voiceProperties)&lt;/code&gt; to reload ASR.&lt;/li&gt;
&lt;li&gt;If API Key is updated, it also calls &lt;code&gt;ttsService.reload(voiceProperties)&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This method does not have &lt;code&gt;@Transactional&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;ASR and TTS share the Bailian API Key.&lt;/li&gt;
&lt;li&gt;Updating the ASR API Key also affects TTS.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="tts-configuration-management"&gt;&lt;a href="#tts-configuration-management" class="header-anchor"&gt;&lt;/a&gt;TTS Configuration Management
&lt;/h2&gt;&lt;h3 id="get-apillm-providervoicetts-get-tts-configuration"&gt;&lt;a href="#get-apillm-providervoicetts-get-tts-configuration" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;GET /api/llm-provider/voice/tts&lt;/code&gt; Get TTS Configuration
&lt;/h3&gt;&lt;p&gt;Returns:&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;Flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service acquires the read lock.&lt;/li&gt;
&lt;li&gt;It reads &lt;code&gt;voiceProperties.getQwen().getTts()&lt;/code&gt; from &lt;code&gt;VoiceInterviewProperties&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It builds &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;It returns the masked TTS configuration.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="put-apillm-providervoicetts-update-tts-configuration"&gt;&lt;a href="#put-apillm-providervoicetts-update-tts-configuration" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;PUT /api/llm-provider/voice/tts&lt;/code&gt; Update TTS Configuration
&lt;/h3&gt;&lt;p&gt;Returns:&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;Flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service acquires the write lock.&lt;/li&gt;
&lt;li&gt;It reads runtime ASR and TTS configuration references.&lt;/li&gt;
&lt;li&gt;It updates TTS fields selectively:
&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;If API Key is updated, it synchronizes TTS and 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;It calls &lt;code&gt;writeTtsConfigToYaml(tts)&lt;/code&gt; to write back to YAML.&lt;/li&gt;
&lt;li&gt;It calls &lt;code&gt;ttsService.reload(voiceProperties)&lt;/code&gt; to reload TTS.&lt;/li&gt;
&lt;li&gt;If API Key is updated, it also calls &lt;code&gt;asrService.reload(voiceProperties)&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TTS update logic is symmetrical with ASR.&lt;/li&gt;
&lt;li&gt;ASR/TTS API Keys are always updated together.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="asr-connectivity-test"&gt;&lt;a href="#asr-connectivity-test" class="header-anchor"&gt;&lt;/a&gt;ASR Connectivity Test
&lt;/h2&gt;&lt;h3 id="post-apillm-providervoiceasrtest-test-asr-connection"&gt;&lt;a href="#post-apillm-providervoiceasrtest-test-asr-connection" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;POST /api/llm-provider/voice/asr/test&lt;/code&gt; Test ASR Connection
&lt;/h3&gt;&lt;p&gt;Returns:&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;Flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Service acquires the read lock.&lt;/li&gt;
&lt;li&gt;It reads ASR configuration from &lt;code&gt;voiceProperties.getQwen().getAsr()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It parses the WebSocket URL:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;wss&lt;/code&gt; defaults to port &lt;code&gt;443&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ws&lt;/code&gt; defaults to port &lt;code&gt;80&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;It runs a TCP Socket connection test:&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;On success, it returns:&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 connection succeeded: 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;On failure, it returns the failure reason.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Differences from Provider connectivity testing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ASR testing only checks TCP Socket connectivity.&lt;/li&gt;
&lt;li&gt;It does not send a WebSocket handshake.&lt;/li&gt;
&lt;li&gt;It does not call the real ASR recognition API.&lt;/li&gt;
&lt;li&gt;Provider testing sends a real HTTP request to the LLM service.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="cache-and-runtime-behavior"&gt;&lt;a href="#cache-and-runtime-behavior" class="header-anchor"&gt;&lt;/a&gt;Cache and Runtime Behavior
&lt;/h2&gt;&lt;p&gt;After Provider configuration changes, &lt;code&gt;registry.reload()&lt;/code&gt; is called. This method clears internal caches:&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;Therefore, configuration changes do not immediately create new clients. Instead, clients are rebuilt on demand the next time business code uses the Provider. This avoids forcing update APIs to bear the initialization cost of model clients and ensures old configuration does not remain in cache for too long.&lt;/p&gt;
&lt;p&gt;It is important to note that &lt;code&gt;reload&lt;/code&gt; only clears caches; it does not synchronize configuration sources. If DB mode is enabled, the system prioritizes database configuration instead of re-importing from YAML or &lt;code&gt;.env&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="current-issues-and-optimization-directions"&gt;&lt;a href="#current-issues-and-optimization-directions" class="header-anchor"&gt;&lt;/a&gt;Current Issues and Optimization Directions
&lt;/h2&gt;&lt;p&gt;The module already supports DB mode and Legacy mode, but the configuration synchronization boundary still needs to be clarified:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;After DB mode is enabled, changes to YAML and &lt;code&gt;.env&lt;/code&gt; are not automatically written back to the database.&lt;/li&gt;
&lt;li&gt;Restarting the project only reloads runtime configuration and cannot resolve inconsistencies between DB configuration and file configuration.&lt;/li&gt;
&lt;li&gt;Manual &lt;code&gt;reload&lt;/code&gt; only clears runtime caches and does not re-import configuration sources.&lt;/li&gt;
&lt;li&gt;ASR/TTS configuration still comes from &lt;code&gt;VoiceInterviewProperties&lt;/code&gt;, which is not the same storage as Provider DB configuration.&lt;/li&gt;
&lt;li&gt;ASR/TTS update methods have no transaction, so writing YAML, writing &lt;code&gt;.env&lt;/code&gt;, and reloading services may partially succeed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Future improvements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add a configuration import API for DB mode to sync Providers from YAML and &lt;code&gt;.env&lt;/code&gt; into the database.&lt;/li&gt;
&lt;li&gt;Add a one-time startup migration strategy and clearly define whether DB or configuration files have priority.&lt;/li&gt;
&lt;li&gt;Add a version number or update time to Provider configuration to help diagnose whether caches have refreshed.&lt;/li&gt;
&lt;li&gt;Move ASR/TTS configuration into unified configuration storage to reduce inconsistencies caused by multiple sources.&lt;/li&gt;
&lt;li&gt;Add failure compensation or clearer error messages for YAML writes, &lt;code&gt;.env&lt;/code&gt; writes, and service reloads.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="summary"&gt;&lt;a href="#summary" class="header-anchor"&gt;&lt;/a&gt;Summary
&lt;/h2&gt;&lt;p&gt;The &lt;code&gt;llm-provider&lt;/code&gt; module is the unified configuration entry for large model capabilities. It manages not only Chat Providers, but also Embedding Providers, default models, runtime caches, and voice ASR/TTS configuration. Its key value is decoupling model configuration from business calls, allowing upper-layer features such as knowledgebase, RAG chat, and voice interview to obtain model capabilities through the unified Provider registry. The next focus is to further clarify DB and file-configuration synchronization so configuration sources are clearer and runtime state is more controllable.&lt;/p&gt;</description></item></channel></rss>