<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>LLM on XEDCZQ Blog</title><link>https://xedczq.cn/en/tags/llm/</link><description>Recent content in LLM 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/llm/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><item><title>Transformer 20-Step Visual Study Notes</title><link>https://xedczq.cn/en/post/transformerexplainer/</link><pubDate>Fri, 05 Jun 2026 23:10:00 +0800</pubDate><guid>https://xedczq.cn/en/post/transformerexplainer/</guid><description>&lt;img src="https://xedczq.cn/img/transformer-explainer/steps/step-03.jpg" alt="Featured image of post Transformer 20-Step Visual Study Notes" /&gt;&lt;h1 id="transformer-20-step-visual-study-notes"&gt;&lt;a href="#transformer-20-step-visual-study-notes" class="header-anchor"&gt;&lt;/a&gt;Transformer 20-Step Visual Study Notes
&lt;/h1&gt;&lt;p&gt;This note is based on the interactive explanation from &lt;a class="link" href="https://poloclub.github.io/transformer-explainer/" target="_blank" rel="noopener"
 &gt;Transformer Explainer&lt;/a&gt;. It follows the site&amp;rsquo;s 20 steps and organizes them into a study note for understanding how GPT-style Transformers perform next-token prediction. The site uses GPT-2 small as the example model and visualizes the full pipeline from input tokens to output probabilities.&lt;/p&gt;
&lt;p&gt;First, remember one sentence: &lt;strong&gt;the core task of GPT-style Transformers is next-token prediction&lt;/strong&gt;. Given the prompt:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Data visualization empowers users to
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The model needs to answer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;What is the most likely next token?
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To answer this, a Transformer goes through tokenization, embedding, positional encoding, repeated Transformer blocks, self-attention, MLP, logits, probability distribution, sampling strategy, and other steps.&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;All screenshots in this note are taken from &lt;a class="link" href="https://poloclub.github.io/transformer-explainer/" target="_blank" rel="noopener"
 &gt;Transformer Explainer&lt;/a&gt;, developed by the Georgia Tech Polo Club team. They are used here for personal study notes. It is best to read this together with the original interactive site.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="what-is-transformer"&gt;&lt;a href="#what-is-transformer" class="header-anchor"&gt;&lt;/a&gt;What Is Transformer
&lt;/h2&gt;&lt;p&gt;Transformer is the most common foundation architecture for modern large language models. Text-generation models such as GPT, Llama, and Gemini can all be understood as expanded versions of the Transformer architecture.&lt;/p&gt;
&lt;p&gt;Its most important ability is not &amp;ldquo;memorizing answers&amp;rdquo;, but learning language patterns from large amounts of text and then predicting the next token from context at inference time. This prediction is repeated: predict one token, append it to the text, then predict the next one.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 1: What is Transformer" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-01.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Source: Transformer Explainer, &lt;a class="link" href="https://poloclub.github.io/transformer-explainer/" target="_blank" rel="noopener"
 &gt;https://poloclub.github.io/transformer-explainer/&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="how-transformers-work"&gt;&lt;a href="#how-transformers-work" class="header-anchor"&gt;&lt;/a&gt;How Transformers Work
&lt;/h2&gt;&lt;p&gt;When a Transformer generates text, it does not write an entire paragraph at once. It generates step by step. Each step performs the same task: &lt;strong&gt;predict a probability distribution for the next token based on the existing context&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For example, if the current input is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Data visualization empowers users to
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The model may assign the highest probability to &lt;code&gt;visualize&lt;/code&gt;, while also assigning relatively high probabilities to tokens such as &lt;code&gt;create&lt;/code&gt;, &lt;code&gt;see&lt;/code&gt;, and &lt;code&gt;make&lt;/code&gt;. The final selected token is also affected by sampling parameters such as temperature, top-k, and top-p.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 2: How Transformers Work" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-02.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Source: Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="transformer-architecture"&gt;&lt;a href="#transformer-architecture" class="header-anchor"&gt;&lt;/a&gt;Transformer Architecture
&lt;/h2&gt;&lt;p&gt;A text-generation Transformer can be divided into three major parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Embedding&lt;/strong&gt;: converts human-readable text into vectors the model can process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transformer Blocks&lt;/strong&gt;: repeatedly refine each token representation, mainly through Self-Attention and MLP.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Output Probabilities&lt;/strong&gt;: converts the final vector into probabilities over all tokens in the vocabulary.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At a high level, the information flow is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;text input -&amp;gt; token -&amp;gt; embedding -&amp;gt; Transformer Blocks -&amp;gt; logits -&amp;gt; probabilities -&amp;gt; sample next token
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img alt="Step 3: Transformer Architecture" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-03.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Source: Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="embedding"&gt;&lt;a href="#embedding" class="header-anchor"&gt;&lt;/a&gt;Embedding
&lt;/h2&gt;&lt;p&gt;The model cannot directly understand strings. Embedding converts each token into a sequence of numbers, or a vector. This vector is not manually designed; it is learned during training.&lt;/p&gt;
&lt;p&gt;If two tokens often appear in similar contexts, their embeddings tend to be closer in high-dimensional space. You can think of embedding as an internal &amp;ldquo;semantic coordinate&amp;rdquo; used by the model.&lt;/p&gt;
&lt;p&gt;GPT-2 small has a hidden dimension of 768, so each token is represented as a 768-dimensional vector.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 4: Embedding" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-04.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Source: Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="token-embedding"&gt;&lt;a href="#token-embedding" class="header-anchor"&gt;&lt;/a&gt;Token Embedding
&lt;/h2&gt;&lt;p&gt;Tokenization splits input text into tokens. A token can be a full word or a subword. For example, &lt;code&gt;empowers&lt;/code&gt; in the example is split into &lt;code&gt;em&lt;/code&gt; and &lt;code&gt;powers&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Each token has a unique ID. GPT-2 has a vocabulary size of 50,257, so its token embedding matrix is roughly:&lt;/p&gt;
$$
50257 \times 768
$$&lt;p&gt;After the model gets the token ID, it looks up the corresponding 768-dimensional vector from this large matrix.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 5: Token Embedding" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-05.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Source: Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="positional-encoding"&gt;&lt;a href="#positional-encoding" class="header-anchor"&gt;&lt;/a&gt;Positional Encoding
&lt;/h2&gt;&lt;p&gt;Self-Attention does not naturally know sequence order. If the model only receives a set of token vectors, it does not know which token comes first and which comes later.&lt;/p&gt;
&lt;p&gt;So positional encoding is needed. GPT-2 uses learnable positional embeddings and adds the semantic token vector and the position vector:&lt;/p&gt;
$$
x_i = \text{TokenEmbedding}_i + \text{PositionEmbedding}_i
$$&lt;p&gt;This lets the model know both &amp;ldquo;what this token is&amp;rdquo; and &amp;ldquo;where it is.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 6: Positional Encoding" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-06.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Source: Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="repetitive-transformer-blocks"&gt;&lt;a href="#repetitive-transformer-blocks" class="header-anchor"&gt;&lt;/a&gt;Repetitive Transformer Blocks
&lt;/h2&gt;&lt;p&gt;Embedding is only the input representation, not a fully contextualized semantic representation. The real context modeling happens inside Transformer Blocks.&lt;/p&gt;
&lt;p&gt;GPT-2 small has 12 Transformer Blocks. Each block roughly contains:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multi-Head Self-Attention: lets tokens exchange information.&lt;/li&gt;
&lt;li&gt;MLP: nonlinearly processes each token representation.&lt;/li&gt;
&lt;li&gt;Residual, LayerNorm, and Dropout: stabilize training and improve generalization.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The meaning of stacking multiple layers is that lower layers tend to capture local and lexical information, while higher layers more easily form complex semantic and task-related representations.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 7: Repetitive Transformer Blocks" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-07.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Source: Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="multi-head-self-attention"&gt;&lt;a href="#multi-head-self-attention" class="header-anchor"&gt;&lt;/a&gt;Multi-Head Self Attention
&lt;/h2&gt;&lt;p&gt;The goal of Self-Attention is to let each token update itself based on context. For example, the token &lt;code&gt;to&lt;/code&gt; has different meanings in different sentences. It needs to &amp;ldquo;look at&amp;rdquo; the previous context &lt;code&gt;Data visualization empowers users&lt;/code&gt; to form a more accurate representation.&lt;/p&gt;
&lt;p&gt;Multi-Head means the model does not use just one attention perspective. It uses multiple heads in parallel. GPT-2 small has 12 attention heads. Different heads can learn different relationships, such as syntactic relations, short-distance collocations, and long-distance semantic dependencies.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 8: Multi-Head Self Attention" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-08.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Source: Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="query-key-value"&gt;&lt;a href="#query-key-value" class="header-anchor"&gt;&lt;/a&gt;Query, Key, Value
&lt;/h2&gt;&lt;p&gt;Self-Attention maps each token&amp;rsquo;s input vector into three vectors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Query (Q)&lt;/strong&gt;: what information the current token wants to search for.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Key (K)&lt;/strong&gt;: features by which each token can be matched by others.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Value (V)&lt;/strong&gt;: the actual information content to be aggregated and passed along.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They come from linear transformations:&lt;/p&gt;
$$
Q = XW_Q,\quad K = XW_K,\quad V = XW_V
$$&lt;p&gt;A simple analogy is a search engine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Query is the search query.&lt;/li&gt;
&lt;li&gt;Key is the page title or index.&lt;/li&gt;
&lt;li&gt;Value is the page content.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The model first computes relevance between Query and Key, then reads Value with weighted aggregation.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 9: Query, Key, Value" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-09.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Source: Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="multi-head"&gt;&lt;a href="#multi-head" class="header-anchor"&gt;&lt;/a&gt;Multi-Head
&lt;/h2&gt;&lt;p&gt;GPT-2 small has an embedding dimension of 768 and 12 attention heads, so each head usually processes:&lt;/p&gt;
$$
768 / 12 = 64
$$&lt;p&gt;The benefit of multi-head attention is learning multiple relationships in parallel. One head may focus on adjacent words, another on subject-verb relationships, and another on more distant semantic hints.&lt;/p&gt;
&lt;p&gt;Multiple heads are not duplicate work; they give the model multiple &amp;ldquo;viewing angles.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 10: Multi-head" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-10.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Source: Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="masked-self-attention"&gt;&lt;a href="#masked-self-attention" class="header-anchor"&gt;&lt;/a&gt;Masked Self Attention
&lt;/h2&gt;&lt;p&gt;GPT-style models generate text from left to right. When predicting the current position, they must not peek at future tokens, so causal mask, also called masked self-attention, is used.&lt;/p&gt;
&lt;p&gt;The core formula is:&lt;/p&gt;
$$
\text{Attention}(Q,K,V)=\text{softmax}\left(\frac{QK^T}{\sqrt{d_k}} + M\right)V
$$&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$QK^T$: computes pairwise similarity between tokens.&lt;/li&gt;
&lt;li&gt;$\sqrt{d_k}$: scaling factor that prevents dot products from becoming too large and making softmax too sharp.&lt;/li&gt;
&lt;li&gt;$M$: mask matrix that sets future positions to $-\infty$.&lt;/li&gt;
&lt;li&gt;softmax: turns scores into probabilities.&lt;/li&gt;
&lt;li&gt;multiplying by $V$: aggregates information according to attention weights.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Step 11: Masked Self Attention" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-11.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Source: Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="attention-output-and-concatenation"&gt;&lt;a href="#attention-output-and-concatenation" class="header-anchor"&gt;&lt;/a&gt;Attention Output and Concatenation
&lt;/h2&gt;&lt;p&gt;Each head outputs a context-enhanced token representation. Since GPT-2 small has 12 heads, it produces 12 sets of results.&lt;/p&gt;
&lt;p&gt;The model then concatenates the outputs from these heads and applies a linear projection back to the original hidden dimension 768:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;head_1, head_2, ..., head_12 -&amp;gt; concat -&amp;gt; linear projection
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The purpose is to let different heads extract information separately, then fuse those perspectives into one unified representation.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 12: Attention Output &amp; Concatenation" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-12.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Source: Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="mlp"&gt;&lt;a href="#mlp" class="header-anchor"&gt;&lt;/a&gt;MLP
&lt;/h2&gt;&lt;p&gt;Attention handles information flow between tokens. MLP processes each token&amp;rsquo;s own representation nonlinearly.&lt;/p&gt;
&lt;p&gt;GPT-2&amp;rsquo;s MLP usually contains two linear transformations with GELU activation in between:&lt;/p&gt;
$$
\text{MLP}(x)=W_2\cdot \text{GELU}(W_1x+b_1)+b_2
$$&lt;p&gt;The first layer expands the dimension from 768 to 3072, and the second layer compresses it back to 768. Expanding the dimension lets the model represent more complex features in a higher-dimensional space.&lt;/p&gt;
&lt;p&gt;Note that MLP does not communicate across tokens like Attention. It processes each token independently.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 13: MLP" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-13.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Source: Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="output-logit"&gt;&lt;a href="#output-logit" class="header-anchor"&gt;&lt;/a&gt;Output Logit
&lt;/h2&gt;&lt;p&gt;After all Transformer Blocks, the model takes the output vector at the last position and uses it to predict the next token.&lt;/p&gt;
&lt;p&gt;This vector goes through the final linear layer and is mapped to the vocabulary size:&lt;/p&gt;
$$
\text{logits}=h_{\text{last}}W_{\text{vocab}}+b
$$&lt;p&gt;GPT-2 has a vocabulary size of 50,257, so logits are a vector of length 50,257. Each number corresponds to the raw score of a candidate token.&lt;/p&gt;
&lt;p&gt;A logit is not a probability. It can be any real number and must go through softmax to become a probability distribution.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 14: Output Logit" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-14.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Source: Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="probabilities"&gt;&lt;a href="#probabilities" class="header-anchor"&gt;&lt;/a&gt;Probabilities
&lt;/h2&gt;&lt;p&gt;Softmax converts logits into probabilities:&lt;/p&gt;
$$
p_i=\frac{e^{z_i}}{\sum_j e^{z_j}}
$$&lt;p&gt;After conversion:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every token probability is between 0 and 1.&lt;/li&gt;
&lt;li&gt;The probabilities of all tokens sum to 1.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the figure, after the example input, the model considers tokens such as &lt;code&gt;visualize&lt;/code&gt;, &lt;code&gt;create&lt;/code&gt;, &lt;code&gt;see&lt;/code&gt;, and &lt;code&gt;make&lt;/code&gt; to be likely next tokens.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 15: Probabilities" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-15.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Source: Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="temperature"&gt;&lt;a href="#temperature" class="header-anchor"&gt;&lt;/a&gt;Temperature
&lt;/h2&gt;&lt;p&gt;Temperature scales logits before softmax:&lt;/p&gt;
$$
p_i=\frac{\exp(z_i/T)}{\sum_j \exp(z_j/T)}
$$&lt;p&gt;Where $T$ is the temperature:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$T &amp;lt; 1$: the probability distribution becomes sharper. High-score tokens are more likely to be selected, and output is more stable.&lt;/li&gt;
&lt;li&gt;$T = 1$: logits are not additionally adjusted.&lt;/li&gt;
&lt;li&gt;$T &amp;gt; 1$: the probability distribution becomes flatter. Low-probability tokens have more chances to be selected, and output becomes more diverse.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In simple terms, lower temperature is more conservative, while higher temperature is more divergent.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 16: Temperature" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-16.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Source: Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="sampling-strategy"&gt;&lt;a href="#sampling-strategy" class="header-anchor"&gt;&lt;/a&gt;Sampling Strategy
&lt;/h2&gt;&lt;p&gt;After obtaining a probability distribution, the model still needs to decide how to choose the next token. Common strategies include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Greedy Search&lt;/strong&gt;: always choose the highest-probability token. Stable, but can be rigid.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Top-k&lt;/strong&gt;: keep only the k highest-probability tokens, then sample from them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Top-p&lt;/strong&gt;: keep the smallest token set whose cumulative probability reaches p. Also called nucleus sampling.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Top-k is more like a fixed candidate pool, while top-p is a dynamic candidate pool. In practice, temperature and top-k/top-p are often tuned together.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 17: Sampling Strategy" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-17.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Source: Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="residual-connection"&gt;&lt;a href="#residual-connection" class="header-anchor"&gt;&lt;/a&gt;Residual Connection
&lt;/h2&gt;&lt;p&gt;A residual connection adds a layer&amp;rsquo;s input directly to its output:&lt;/p&gt;
$$
y = x + F(x)
$$&lt;p&gt;Its purpose is to preserve original information and make gradients pass through deep networks more easily. Without residual connections, training becomes harder as the model gets deeper, and information from early layers is more likely to be lost.&lt;/p&gt;
&lt;p&gt;In Transformers, residual connections usually surround both Attention and MLP.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 18: Residual Connection" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-18.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Source: Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="layer-normalization"&gt;&lt;a href="#layer-normalization" class="header-anchor"&gt;&lt;/a&gt;Layer Normalization
&lt;/h2&gt;&lt;p&gt;Layer Normalization normalizes the values inside a token vector, making the mean and variance more stable:&lt;/p&gt;
$$
\text{LayerNorm}(x)=\gamma\frac{x-\mu}{\sqrt{\sigma^2+\epsilon}}+\beta
$$&lt;p&gt;It reduces training instability and makes the input distribution of each layer more controllable. GPT-2 uses a pre-norm style: LayerNorm is applied before entering Attention and MLP.&lt;/p&gt;
&lt;p&gt;Intuitively, LayerNorm is like adjusting the numerical scale to a more suitable range before each key computation.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Step 19: Layer Normalization" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-19.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Source: Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="dropout"&gt;&lt;a href="#dropout" class="header-anchor"&gt;&lt;/a&gt;Dropout
&lt;/h2&gt;&lt;p&gt;Dropout is a regularization method used during training. It randomly sets part of the connections or activations to zero, preventing the model from over-relying on certain local features.&lt;/p&gt;
&lt;p&gt;The intuition is: during training, do not let the model follow the exact same path every time, forcing it to learn more robust representations.&lt;/p&gt;
&lt;p&gt;Important notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dropout is mainly used during training.&lt;/li&gt;
&lt;li&gt;Dropout is disabled during inference.&lt;/li&gt;
&lt;li&gt;Many newer large models use less Dropout than early models because their training data is extremely large.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Step 20: Dropout" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://xedczq.cn/img/transformer-explainer/steps/step-20.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Source: Transformer Explainer&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="one-flowchart-summary"&gt;&lt;a href="#one-flowchart-summary" class="header-anchor"&gt;&lt;/a&gt;One Flowchart Summary
&lt;/h2&gt;&lt;p&gt;The inference flow of a GPT-style Transformer can be compressed into this chain:&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 A["Input text"] --&gt; B["Tokenization"]
 B --&gt; C["Token Embedding"]
 C --&gt; D["Positional Encoding"]
 D --&gt; E["Transformer Block x N"]
 E --&gt; F["Multi-Head Self-Attention"]
 F --&gt; G["MLP"]
 G --&gt; H["Final Linear"]
 H --&gt; I["Logits"]
 I --&gt; J["Softmax Probabilities"]
 J --&gt; K["Temperature / Top-k / Top-p"]
 K --&gt; L["Sample next token"]&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="key-differences-from-rnn"&gt;&lt;a href="#key-differences-from-rnn" class="header-anchor"&gt;&lt;/a&gt;Key Differences from RNN
&lt;/h2&gt;&lt;p&gt;Combining this with the previous RNN note, the difference can be understood like this:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Aspect&lt;/th&gt;
 &lt;th&gt;RNN&lt;/th&gt;
 &lt;th&gt;Transformer&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Information transfer&lt;/td&gt;
 &lt;td&gt;Passed step by step through hidden state&lt;/td&gt;
 &lt;td&gt;Self-Attention lets tokens directly read one another&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Parallelism&lt;/td&gt;
 &lt;td&gt;Strong time-step dependency, hard to parallelize&lt;/td&gt;
 &lt;td&gt;Tokens in the same layer can be computed in parallel&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Long-distance dependency&lt;/td&gt;
 &lt;td&gt;Long path, easy to decay&lt;/td&gt;
 &lt;td&gt;Any positions can directly connect&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Context representation&lt;/td&gt;
 &lt;td&gt;Compressed into hidden state&lt;/td&gt;
 &lt;td&gt;Keeps explicit token representations for the whole context&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Large-model training&lt;/td&gt;
 &lt;td&gt;Less efficient to scale&lt;/td&gt;
 &lt;td&gt;Better suited to large-scale GPU/TPU matrix computation&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This is why modern LLMs mainly use Transformers: they are not only strong in modeling, but also better engineered for large-scale pretraining.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="sources"&gt;&lt;a href="#sources" class="header-anchor"&gt;&lt;/a&gt;Sources
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://poloclub.github.io/transformer-explainer/" target="_blank" rel="noopener"
 &gt;Transformer Explainer: LLM Transformer Model Visually Explained&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/poloclub/transformer-explainer" target="_blank" rel="noopener"
 &gt;Transformer Explainer GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://arxiv.org/abs/2408.04619" target="_blank" rel="noopener"
 &gt;Transformer Explainer Paper, arXiv:2408.04619&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Vaswani et al., &lt;a class="link" href="https://arxiv.org/abs/1706.03762" target="_blank" rel="noopener"
 &gt;Attention Is All You Need&lt;/a&gt;, 2017&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>