---
status: 'Pending'
created: '2025-11-16'
owner: '@lv'
---

# 任务：重构LLM Provider架构 (V2 - 数据库中心化)

## 1. 任务目标

为解决当前系统大语言模型（LLM）配置的僵化问题，本次任务旨在重构LLM的配置和调用工作流。我们将实现一个以数据库为中心的、支持多供应商的、结构化的配置体系。该体系将允许每个分析模块都能按需选择其所需的LLM供应商和具体模型，同时保证整个系统的类型安全和数据一致性。

## 2. 新架构设计：配置即数据

我们将废弃所有基于本地文件的配置方案 (`analysis-config.json`, `llm-providers.json`)，并将所有配置信息作为结构化数据存入数据库。

### 2.1. 核心原则：Schema-in-Code

- **不新增数据表**: 我们将利用现有的 `system_config` 表及其 `JSONB` 字段来存储所有配置，无需修改数据库Schema。
- **强类型约束**: 所有配置的JSON结构，其“单一事实源”都将是在 **`common-contracts`** crate中定义的Rust Structs。所有服务都必须依赖这些共享的Structs来序列化和反序列化配置数据，从而在应用层面实现强类型约束。

### 2.2. `common-contracts`中的数据结构定义

将在`common-contracts`中创建一个新模块（例如 `config_models.rs`），定义如下结构：

```rust
// In: common-contracts/src/config_models.rs

use serde::{Serialize, Deserialize};
use std::collections::HashMap;

// 单个启用的模型
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct LlmModel {
    pub model_id: String,           // e.g., "gpt-4o"
    pub name: Option<String>,       // 别名，用于UI显示
    pub is_active: bool,            // 是否在UI中可选
}

// 单个LLM供应商的完整配置
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct LlmProvider {
    pub name: String,               // "OpenAI 官方"
    pub api_base_url: String,
    pub api_key: String,            // 直接明文存储
    pub models: Vec<LlmModel>,      // 该供应商下我们启用的模型列表
}

// 整个LLM Provider注册中心的数据结构
pub type LlmProvidersConfig = HashMap<String, LlmProvider>; // Key: provider_id, e.g., "openai_official"

// 单个分析模块的配置
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AnalysisModuleConfig {
    pub name: String,               // "看涨分析"
    pub provider_id: String,        // 引用 LlmProvidersConfig 的 Key
    pub model_id: String,           // 引用 LlmModel 中的 model_id
    pub prompt_template: String,
    pub dependencies: Vec<String>,
}

// 整个分析模块配置集合的数据结构
pub type AnalysisModulesConfig = HashMap<String, AnalysisModuleConfig>; // Key: module_id, e.g., "bull_case"
```

### 2.3. `system_config` 表中的数据存储

我们将使用两个`config_key`来存储这些结构序列化后的JSON：
1.  **Key: `"llm_providers"`**: 其`config_value`是一个序列化后的`LlmProvidersConfig`。
2.  **Key: `"analysis_modules"`**: 其`config_value`是一个序列化后的`AnalysisModulesConfig`。

## 3. 实施步骤

### 步骤 1: 更新 `common-contracts` (地基)

1.  在`common-contracts/src/`下创建`config_models.rs`文件。
2.  将上述所有Rust Structs定义添加到该文件中，并确保它们在`lib.rs`中被正确导出。

### 步骤 2: 重构 `data-persistence-service` (配置守门人)

1.  **移除 `config-service-rs`**: 该服务的功能将被`data-persistence-service`完全吸收和取代，可以准备将其从`docker-compose.yml`中移除。
2.  **实现新的CRUD API**:
    -   `GET /api/v1/configs/llm_providers`: 读取并返回`system_config`中key为`llm_providers`的JSON文档。
    -   `PUT /api/v1/configs/llm_providers`: 接收一个`LlmProvidersConfig`的JSON payload，**使用`common-contracts`中的Structs进行反序列化验证**，验证通过后，将其存入数据库。
    -   `GET /api/v1/configs/analysis_modules`: 读取并返回key为`analysis_modules`的JSON文档。
    -   `PUT /api/v1/configs/analysis_modules`: 接收一个`AnalysisModulesConfig`的JSON payload，进行验证后存入数据库。

### 步骤 3: 重构 `frontend` (管理UI)

1.  **创建LLM Provider管理页面**:
    -   提供一个表单，用于新增/编辑`LlmProvider`（对应`llm_providers`JSON中的一个顶级条目）。
    -   在每个Provider下，提供一个子表单来管理其`models`列表（增、删、改、切换`is_active`状态）。
    -   实现“自动发现模型”功能，调用`api-gateway`的模型发现端点，让用户可以从中选择模型加入列表。
2.  **更新分析模块配置页面**:
    -   为每个分析模块提供两个级联下拉框：
        1.  第一个下拉框选择`Provider` (数据来自`GET /api/v1/configs/llm_providers`)。
        2.  第二个下拉框根据第一个的选择，动态加载该Provider下所有`is_active: true`的`Model`。
    -   更新保存逻辑，以调用`PUT /api/v1/configs/analysis_modules`。

### 步骤 4: 更新 `api-gateway`

1.  **移除对`config-service-rs`的代理**。
2.  **代理新的配置API**: 将所有`/api/v1/configs/*`的请求正确地代理到`data-persistence-service`。
3.  **实现模型发现端点**:
    -   创建`GET /api/v1/discover-models/{provider_id}`。
    -   该端点会先调用`data-persistence-service`获取指定provider的`api_base_url`和`api_key`。
    -   然后使用这些信息向LLM供应商的官方`/models`接口发起请求，并将结果返回给前端。

### 步骤 5: 重构 `report-generator-service` (最终消费者)

1.  **移除旧配置**:
    -   修改`docker-compose.yml`，移除所有旧的`LLM_*`环境变量。
2.  **重构工作流**:
    -   当收到任务时（例如`bull_case`），它将：
        a.  并行调用`data-persistence-service`的`GET /api/v1/configs/llm_providers`和`GET /api/v1/configs/analysis_modules`接口，获取完整的配置。
        b.  **使用`common-contracts`中的Structs反序列化**这两个JSON响应，得到类型安全的`LlmProvidersConfig`和`AnalysisModulesConfig`对象。
        c.  通过`analysis_config["bull_case"]`找到`provider_id`和`model_id`。
        d.  通过`providers_config[provider_id]`找到对应的`api_base_url`和`api_key`。
        e.  动态创建`LlmClient`实例，并执行任务。

## 4. 验收标准

-   ✅ `common-contracts` crate中包含了所有新定义的配置Structs。
-   ✅ `data-persistence-service`提供了稳定、类型安全的API来管理存储在`system_config`表中的配置。
-   ✅ `config-service-rs`服务已安全移除。
-   ✅ 前端提供了一个功能完善的UI，用于管理LLM Providers、Models，并能将它们正确地指派给各个分析模块。
-   ✅ `report-generator-service`能够正确地、动态地使用数据库中的配置，为不同的分析模块调用不同的LLM Provider和模型。

## 6. 任务实施清单 (TODO List)

### 阶段一：定义数据契约 (`common-contracts`)
- [x] 在 `src` 目录下创建 `config_models.rs` 文件。
- [x] 在 `config_models.rs` 中定义 `LlmModel`, `LlmProvider`, `LlmProvidersConfig`, `AnalysisModuleConfig`, `AnalysisModulesConfig` 等所有Structs。
- [x] 在 `lib.rs` 中正确导出 `config_models` 模块，使其对其他服务可见。

### 阶段二：实现配置的持久化与服务 (`data-persistence-service`)
- [x] **[API]** 实现 `GET /api/v1/configs/llm_providers` 端点。
- [x] **[API]** 实现 `PUT /api/v1/configs/llm_providers` 端点，并确保使用 `common-contracts` 中的Structs进行反序列化验证。
- [x] **[API]** 实现 `GET /api/v1/configs/analysis_modules` 端点。
- [x] **[API]** 实现 `PUT /api/v1/configs/analysis_modules` 端点，并进行相应的验证。
- [x] **[系统]** 从 `docker-compose.yml` 中安全移除 `config-service-rs` 服务，因其功能已被本服务吸收。

### 阶段三：更新API网关与前端 (`api-gateway` & `frontend`)
- [x] **[api-gateway]** 更新路由配置，将所有 `/api/v1/configs/*` 的请求代理到 `data-persistence-service`。
- [x] **[api-gateway]** 实现 `GET /api/v1/discover-models/{provider_id}` 模型发现代理端点。
- [x] **[frontend]** 创建全新的“LLM Provider管理”页面UI骨架。
- [x] **[frontend]** 实现调用新配置API对LLM Providers和Models进行增、删、改、查的完整逻辑。
- [x] **[frontend]** 在Provider管理页面上，实现“自动发现模型”的功能按钮及其后续的UI交互。
- [x] **[frontend]** 重构“分析模块配置”页面，使用级联下拉框来选择Provider和Model。

### 阶段四：重构报告生成服务 (`report-generator-service`)
- [x] **[配置]** 从 `docker-compose.yml` 中移除所有旧的、全局的 `LLM_*` 环境变量。
- [x] **[核心逻辑]** 重构服务的工作流，实现从 `data-persistence-service` 动态获取`LlmProvidersConfig`和`AnalysisModulesConfig`。
- [x] **[核心逻辑]** 实现动态创建 `LlmClient` 实例的逻辑，使其能够根据任务需求使用不同的Provider配置。
