From f81d18a517e87875f269d454f53e297b2a3ab7d9 Mon Sep 17 00:00:00 2001 From: MerCry Date: Thu, 26 Feb 2026 19:30:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0LLM=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=8C=81=E4=B9=85=E5=8C=96=E5=8A=9F=E8=83=BD=20[AC-AISVC-50]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LLM配置保存到 config/llm_config.json 文件 - 服务重启后自动加载已保存的配置 - 与嵌入模型配置保持一致的持久化机制 --- ai-service/app/services/llm/factory.py | 43 +++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/ai-service/app/services/llm/factory.py b/ai-service/app/services/llm/factory.py index d983c47..25f1073 100644 --- a/ai-service/app/services/llm/factory.py +++ b/ai-service/app/services/llm/factory.py @@ -5,8 +5,10 @@ LLM Provider Factory and Configuration Management. Design pattern: Factory pattern for pluggable LLM providers. """ +import json import logging from dataclasses import dataclass, field +from pathlib import Path from typing import Any from app.services.llm.base import LLMClient, LLMConfig @@ -14,6 +16,8 @@ from app.services.llm.openai_client import OpenAIClient logger = logging.getLogger(__name__) +LLM_CONFIG_FILE = Path("config/llm_config.json") + @dataclass class LLMProviderInfo: @@ -257,7 +261,7 @@ class LLMProviderFactory: class LLMConfigManager: """ Manager for LLM configuration. - [AC-ASA-16, AC-ASA-17, AC-ASA-18] Configuration management with hot-reload. + [AC-ASA-16, AC-ASA-17, AC-ASA-18] Configuration management with hot-reload and persistence. """ def __init__(self): @@ -274,12 +278,41 @@ class LLMConfigManager: "temperature": settings.llm_temperature, } self._client: LLMClient | None = None + + self._load_from_file() + + def _load_from_file(self) -> None: + """Load configuration from file if exists.""" + try: + if LLM_CONFIG_FILE.exists(): + with open(LLM_CONFIG_FILE, 'r', encoding='utf-8') as f: + saved = json.load(f) + self._current_provider = saved.get("provider", self._current_provider) + saved_config = saved.get("config", {}) + if saved_config: + self._current_config.update(saved_config) + logger.info(f"[AC-ASA-16] Loaded LLM config from file: provider={self._current_provider}") + except Exception as e: + logger.warning(f"[AC-ASA-16] Failed to load LLM config from file: {e}") + + def _save_to_file(self) -> None: + """Save configuration to file.""" + try: + LLM_CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True) + with open(LLM_CONFIG_FILE, 'w', encoding='utf-8') as f: + json.dump({ + "provider": self._current_provider, + "config": self._current_config, + }, f, indent=2, ensure_ascii=False) + logger.info(f"[AC-ASA-16] Saved LLM config to file: provider={self._current_provider}") + except Exception as e: + logger.error(f"[AC-ASA-16] Failed to save LLM config to file: {e}") def get_current_config(self) -> dict[str, Any]: """Get current LLM configuration.""" return { "provider": self._current_provider, - "config": self._current_config, + "config": self._current_config.copy(), } async def update_config( @@ -289,7 +322,7 @@ class LLMConfigManager: ) -> bool: """ Update LLM configuration. - [AC-ASA-16] Hot-reload configuration. + [AC-ASA-16] Hot-reload configuration with persistence. Args: provider: Provider name @@ -310,6 +343,8 @@ class LLMConfigManager: self._current_provider = provider self._current_config = validated_config + + self._save_to_file() logger.info(f"[AC-ASA-16] LLM config updated: provider={provider}") return True @@ -365,7 +400,7 @@ class LLMConfigManager: test_provider = provider or self._current_provider test_config = config if config else self._current_config - logger.info(f"[AC-ASA-17] Test connection: provider={test_provider}, config={test_config}") + logger.info(f"[AC-ASA-17] Test connection: provider={test_provider}, model={test_config.get('model')}") if test_provider not in LLM_PROVIDERS: return {