diff --git a/gui/flask_server.py b/gui/flask_server.py index 2ed8787..9e97c6d 100644 --- a/gui/flask_server.py +++ b/gui/flask_server.py @@ -863,6 +863,15 @@ def api_get_system_status(): except Exception as e: return jsonify({'server': False, 'digital_human': False, 'remote_audio': False, 'error': str(e)}), 500 +@__app.route('/api/model-router-status', methods=['GET']) +def api_model_router_status(): + """获取大小模型交互路由的配置状态""" + try: + from llm.model_router import get_route_info + return jsonify(get_route_info()) + except Exception as e: + return jsonify({'enabled': False, 'error': str(e)}), 500 + @__app.route('/api/get-audio-config', methods=['GET']) def api_get_audio_config(): """获取麦克风和扬声器的配置状态""" diff --git a/llm/model_router.py b/llm/model_router.py new file mode 100644 index 0000000..5bd13e5 --- /dev/null +++ b/llm/model_router.py @@ -0,0 +1,258 @@ +# -*- coding: utf-8 -*- +""" +大小模型交互路由模块 + +设计思路: +- 小模型(轻量/低成本):处理简单问答、闲聊、意图识别等低复杂度任务 +- 大模型(强推理):处理复杂推理、工具编排、多步规划、长文生成等高复杂度任务 +- 路由器:基于小模型的意图分类结果,决定由哪个模型处理当前请求 + +路由策略: +1. 小模型先对用户输入进行意图分类和复杂度评估 +2. 简单任务(闲聊、问候、简单事实问答)由小模型直接回复 +3. 复杂任务(推理、工具调用、代码、分析)升级到大模型处理 +4. 小模型置信度不足时主动升级到大模型 +""" + +import json +import threading +from typing import Optional, Tuple, Literal + +from langchain_openai import ChatOpenAI +from langchain_core.messages import HumanMessage, SystemMessage + +from utils import util +import utils.config_util as cfg + +# 路由决策类型 +RouteDecision = Literal["small", "large"] + +# 模块级锁,保护 LLM 实例的懒加载 +_init_lock = threading.Lock() + +# 懒加载的模型实例 +_small_llm: Optional[ChatOpenAI] = None +_large_llm: Optional[ChatOpenAI] = None + +# 上次初始化时使用的配置指纹,用于检测配置变更后重建实例 +_small_llm_fingerprint: Optional[str] = None +_large_llm_fingerprint: Optional[str] = None + + +ROUTE_CLASSIFY_PROMPT = """\ +你是一个任务复杂度分类器。根据用户的输入,判断该任务应该由"小模型"还是"大模型"处理。 + +## 分类标准 + +**小模型处理(输出 "small")**: +- 日常问候、闲聊、打招呼 +- 简单事实性问答(天气、时间、基础常识) +- 简短的情感回应、安慰、鼓励 +- 简单的信息查询或确认 +- 上下文不复杂的简短对话 + +**大模型处理(输出 "large")**: +- 需要多步推理或逻辑分析的问题 +- 代码编写、调试、技术问题 +- 需要调用工具/搜索/计算的任务 +- 长文创作、文案撰写、翻译 +- 复杂的数据分析或总结 +- 涉及专业领域知识的深度问答 +- 用户明确要求"详细分析"、"深入讲解"等 +- 多轮追问、需要综合上下文的复杂对话 + +## 输出格式 + +严格按照以下JSON格式输出,不要输出任何其他内容: +{"route": "small"或"large", "confidence": 0.0到1.0之间的数字, "reason": "简短的分类理由"} +""" + + +def _build_fingerprint(api_key: Optional[str], base_url: Optional[str], model: Optional[str]) -> str: + """构建配置指纹,用于检测配置变更。""" + return f"{api_key}|{base_url}|{model}" + + +def _ensure_small_llm() -> Optional[ChatOpenAI]: + """懒加载并返回小模型实例,配置变更时自动重建。""" + global _small_llm, _small_llm_fingerprint + + api_key = cfg.small_model_api_key + base_url = cfg.small_model_base_url + model = cfg.small_model_engine + + if not all([api_key, base_url, model]): + return None + + fp = _build_fingerprint(api_key, base_url, model) + if _small_llm is not None and _small_llm_fingerprint == fp: + return _small_llm + + with _init_lock: + # double-check + if _small_llm is not None and _small_llm_fingerprint == fp: + return _small_llm + try: + _small_llm = ChatOpenAI( + model=model, + base_url=base_url, + api_key=api_key, + streaming=True, + ) + _small_llm_fingerprint = fp + util.log(1, f"小模型已初始化: {model} @ {base_url}") + except Exception as exc: + util.log(1, f"小模型初始化失败: {exc}") + _small_llm = None + return _small_llm + + +def _ensure_large_llm() -> Optional[ChatOpenAI]: + """懒加载并返回大模型实例(即原有的主模型),配置变更时自动重建。""" + global _large_llm, _large_llm_fingerprint + + api_key = cfg.key_gpt_api_key + base_url = cfg.gpt_base_url + model = cfg.gpt_model_engine + + if not all([api_key, base_url, model]): + return None + + fp = _build_fingerprint(api_key, base_url, model) + if _large_llm is not None and _large_llm_fingerprint == fp: + return _large_llm + + with _init_lock: + if _large_llm is not None and _large_llm_fingerprint == fp: + return _large_llm + try: + _large_llm = ChatOpenAI( + model=model, + base_url=base_url, + api_key=api_key, + streaming=True, + ) + _large_llm_fingerprint = fp + util.log(1, f"大模型已初始化: {model} @ {base_url}") + except Exception as exc: + util.log(1, f"大模型初始化失败: {exc}") + _large_llm = None + return _large_llm + + +def is_enabled() -> bool: + """检查大小模型交互功能是否已启用且配置完整。""" + cfg.load_config() + if not cfg.model_interaction_enabled: + return False + # 小模型必须有独立配置 + if not all([cfg.small_model_api_key, cfg.small_model_base_url, cfg.small_model_engine]): + return False + # 大模型(主模型)必须有配置 + if not all([cfg.key_gpt_api_key, cfg.gpt_base_url, cfg.gpt_model_engine]): + return False + return True + + +def classify_request(content: str, has_tools: bool = False) -> Tuple[RouteDecision, float, str]: + """ + 使用小模型对用户请求进行意图分类,决定路由方向。 + + Args: + content: 用户输入文本 + has_tools: 当前是否有可用的 MCP 工具 + + Returns: + (route_decision, confidence, reason) + - route_decision: "small" 或 "large" + - confidence: 0.0~1.0 置信度 + - reason: 分类理由 + """ + # 如果有工具可用,直接路由到大模型(工具编排需要强推理能力) + if has_tools: + return "large", 1.0, "检测到可用工具,需要大模型进行工具编排" + + small_llm = _ensure_small_llm() + if small_llm is None: + return "large", 1.0, "小模型不可用,回退到大模型" + + try: + messages = [ + SystemMessage(content=ROUTE_CLASSIFY_PROMPT), + HumanMessage(content=content), + ] + response = small_llm.invoke(messages) + result_text = response.content.strip() + + # 尝试解析 JSON(兼容模型可能输出的 markdown code block) + if result_text.startswith("```"): + # 去掉 ```json ... ``` 包裹 + lines = result_text.split("\n") + result_text = "\n".join( + line for line in lines if not line.strip().startswith("```") + ) + + result = json.loads(result_text) + route = result.get("route", "large") + confidence = float(result.get("confidence", 0.5)) + reason = result.get("reason", "") + + # 置信度不足时升级到大模型 + if route == "small" and confidence < 0.7: + util.log(1, f"[路由] 小模型置信度不足({confidence:.2f}),升级到大模型: {reason}") + return "large", confidence, f"置信度不足,升级: {reason}" + + util.log(1, f"[路由] 决策={route}, 置信度={confidence:.2f}, 理由={reason}") + return route, confidence, reason + + except json.JSONDecodeError as exc: + util.log(1, f"[路由] 分类结果解析失败: {exc}, 原始输出: {result_text}") + return "large", 0.5, "分类结果解析失败,回退到大模型" + except Exception as exc: + util.log(1, f"[路由] 分类请求失败: {exc}") + return "large", 0.5, f"分类异常,回退到大模型: {exc}" + + +def get_llm_for_route(route: RouteDecision) -> ChatOpenAI: + """ + 根据路由决策返回对应的 LLM 实例。 + + Args: + route: "small" 或 "large" + + Returns: + ChatOpenAI 实例 + """ + if route == "small": + llm = _ensure_small_llm() + if llm is not None: + return llm + util.log(1, "[路由] 小模型不可用,回退到大模型") + + llm = _ensure_large_llm() + if llm is not None: + return llm + + # 最终兜底:使用模块级的全局 llm(原有逻辑) + util.log(1, "[路由] 大小模型均不可用,使用全局 llm 实例") + from llm.nlp_cognitive_stream import llm as global_llm + return global_llm + + +def get_route_info() -> dict: + """返回当前路由配置状态信息,供 API / 调试使用。""" + cfg.load_config() + return { + "enabled": is_enabled(), + "model_interaction_enabled": bool(cfg.model_interaction_enabled), + "small_model": { + "engine": cfg.small_model_engine, + "base_url": cfg.small_model_base_url, + "configured": bool(cfg.small_model_engine and cfg.small_model_base_url and cfg.small_model_api_key), + }, + "large_model": { + "engine": cfg.gpt_model_engine, + "base_url": cfg.gpt_base_url, + "configured": bool(cfg.gpt_model_engine and cfg.gpt_base_url and cfg.key_gpt_api_key), + }, + } diff --git a/llm/nlp_cognitive_stream.py b/llm/nlp_cognitive_stream.py index 79d8292..93d3185 100644 --- a/llm/nlp_cognitive_stream.py +++ b/llm/nlp_cognitive_stream.py @@ -53,6 +53,7 @@ from core import content_db from core import stream_manager from core import member_db from faymcp import runtime_bridge as mcp_runtime +from llm import model_router # 加载配置 cfg.load_config() @@ -2500,8 +2501,9 @@ def question(content, username, observation=None): return final_stream_done - def run_direct_llm() -> bool: + def run_direct_llm(target_llm=None) -> bool: nonlocal full_response_text, accumulated_text, is_first_sentence, messages_buffer + use_llm = target_llm if target_llm is not None else llm try: summary_state: AgentState = { "request": content, @@ -2518,7 +2520,7 @@ def question(content, username, observation=None): } final_messages = _build_final_messages(summary_state) - stream_response_chunks(llm.stream(final_messages)) + stream_response_chunks(use_llm.stream(final_messages)) return True except Exception as exc: util.log(1, f"请求失败: {type(exc).__name__}: {exc}") @@ -2533,12 +2535,32 @@ def question(content, username, observation=None): if not sm.should_stop_generation(username, conversation_id=conversation_id): send_prestart_content() - workflow_success = False - if tool_registry: - workflow_success = run_workflow(tool_registry) + # === 大小模型交互路由 === + route_decision = None + routed_llm = None + if model_router.is_enabled(): + try: + route_decision, confidence, reason = model_router.classify_request( + content, has_tools=bool(tool_registry) + ) + routed_llm = model_router.get_llm_for_route(route_decision) + util.log(1, f"[大小模型路由] 决策={route_decision}, 置信度={confidence:.2f}, 理由={reason}") + except Exception as exc: + util.log(1, f"[大小模型路由] 路由异常,回退默认流程: {exc}") + route_decision = None + routed_llm = None - if (not tool_registry or not workflow_success) and not sm.should_stop_generation(username, conversation_id=conversation_id): - run_direct_llm() + workflow_success = False + if route_decision == "small": + # 小模型直接回复,跳过工具工作流 + run_direct_llm(target_llm=routed_llm) + else: + # 大模型路径:先尝试工具工作流,失败则走直接 LLM + if tool_registry: + workflow_success = run_workflow(tool_registry) + + if (not tool_registry or not workflow_success) and not sm.should_stop_generation(username, conversation_id=conversation_id): + run_direct_llm(target_llm=routed_llm) if not sm.should_stop_generation(username, conversation_id=conversation_id): finalize_stream(force_end=True) diff --git a/utils/config_util.py b/utils/config_util.py index 76ff9da..ffbe38e 100644 --- a/utils/config_util.py +++ b/utils/config_util.py @@ -82,11 +82,17 @@ config_json_path = None use_bionic_memory = None # Embedding API 配置全局变量 -embedding_api_model = None -embedding_api_base_url = None -embedding_api_key = None - -SYSTEM_CONFIG_ENV_KEY = 'FAY_SYSTEM_CONF_JSON' +embedding_api_model = None +embedding_api_base_url = None +embedding_api_key = None + +# 小模型配置全局变量(大小模型交互) +small_model_api_key = None +small_model_base_url = None +small_model_engine = None +model_interaction_enabled = None + +SYSTEM_CONFIG_ENV_KEY = 'FAY_SYSTEM_CONF_JSON' # 避免重复加载配置中心导致日志刷屏 _last_loaded_project_id = None @@ -129,76 +135,76 @@ def _refresh_config_center(): if env_project_id: CONFIG_SERVER['PROJECT_ID'] = env_project_id -_refresh_config_center() - - -def _config_parser_to_dict(parser): - data = {} - if parser is None: - return data - for section in parser.sections(): - data[section] = {} - for key, value in parser.items(section): - data[section][key] = value - return data - - -def _dict_to_config_parser(data): - parser = ConfigParser() - if not isinstance(data, dict): - return parser - for section, items in data.items(): - if not isinstance(items, dict): - continue - if not parser.has_section(section): - parser.add_section(section) - for key, value in items.items(): - parser.set(section, str(key), '' if value is None else str(value)) - return parser - - -def _save_system_config_to_env(parser, project_id=None, source='local'): - if parser is None: - return - payload = { - 'meta': { - 'project_id': project_id, - 'source': source, - }, - 'sections': _config_parser_to_dict(parser), - } - try: - os.environ[SYSTEM_CONFIG_ENV_KEY] = json.dumps(payload, ensure_ascii=False) - except Exception as e: - util.log(2, f"save system.conf to env failed: {str(e)}") - - -def _load_system_config_from_env(expected_project_id=None): - raw_value = os.getenv(SYSTEM_CONFIG_ENV_KEY) - if not raw_value: - return None - - try: - payload = json.loads(raw_value) - meta = {} - sections = payload - if isinstance(payload, dict) and isinstance(payload.get('sections'), dict): - meta = payload.get('meta') or {} - sections = payload.get('sections') or {} - - env_project_id = meta.get('project_id') - if expected_project_id and env_project_id != expected_project_id: - return None - - parser = _dict_to_config_parser(sections) - if parser.sections(): - return parser - except Exception as e: - util.log(2, f"load system.conf from env failed: {str(e)}") - - return None - -def load_config_from_api(project_id=None): +_refresh_config_center() + + +def _config_parser_to_dict(parser): + data = {} + if parser is None: + return data + for section in parser.sections(): + data[section] = {} + for key, value in parser.items(section): + data[section][key] = value + return data + + +def _dict_to_config_parser(data): + parser = ConfigParser() + if not isinstance(data, dict): + return parser + for section, items in data.items(): + if not isinstance(items, dict): + continue + if not parser.has_section(section): + parser.add_section(section) + for key, value in items.items(): + parser.set(section, str(key), '' if value is None else str(value)) + return parser + + +def _save_system_config_to_env(parser, project_id=None, source='local'): + if parser is None: + return + payload = { + 'meta': { + 'project_id': project_id, + 'source': source, + }, + 'sections': _config_parser_to_dict(parser), + } + try: + os.environ[SYSTEM_CONFIG_ENV_KEY] = json.dumps(payload, ensure_ascii=False) + except Exception as e: + util.log(2, f"save system.conf to env failed: {str(e)}") + + +def _load_system_config_from_env(expected_project_id=None): + raw_value = os.getenv(SYSTEM_CONFIG_ENV_KEY) + if not raw_value: + return None + + try: + payload = json.loads(raw_value) + meta = {} + sections = payload + if isinstance(payload, dict) and isinstance(payload.get('sections'), dict): + meta = payload.get('meta') or {} + sections = payload.get('sections') or {} + + env_project_id = meta.get('project_id') + if expected_project_id and env_project_id != expected_project_id: + return None + + parser = _dict_to_config_parser(sections) + if parser.sections(): + return parser + except Exception as e: + util.log(2, f"load system.conf from env failed: {str(e)}") + + return None + +def load_config_from_api(project_id=None): global CONFIG_SERVER """ @@ -317,6 +323,11 @@ def load_config(force_reload=False): global embedding_api_base_url global embedding_api_key + global small_model_api_key + global small_model_base_url + global small_model_engine + global model_interaction_enabled + global CONFIG_SERVER global system_conf_path global config_json_path @@ -327,34 +338,34 @@ def load_config(force_reload=False): _refresh_config_center() - env_project_id = os.getenv('FAY_CONFIG_CENTER_ID') - explicit_config_center = bool(env_project_id) - using_config_center = explicit_config_center - env_system_config = None if force_reload else _load_system_config_from_env( - expected_project_id=env_project_id if explicit_config_center else None - ) - if ( - env_project_id - and not force_reload + env_project_id = os.getenv('FAY_CONFIG_CENTER_ID') + explicit_config_center = bool(env_project_id) + using_config_center = explicit_config_center + env_system_config = None if force_reload else _load_system_config_from_env( + expected_project_id=env_project_id if explicit_config_center else None + ) + if ( + env_project_id + and not force_reload and _last_loaded_config is not None and _last_loaded_project_id == env_project_id and _last_loaded_from_api ): return _last_loaded_config - default_system_conf_path = os.path.join(os.getcwd(), 'system.conf') - default_config_json_path = os.path.join(os.getcwd(), 'config.json') - cache_system_conf_path = os.path.join(os.getcwd(), 'cache_data', 'system.conf') - cache_config_json_path = os.path.join(os.getcwd(), 'cache_data', 'config.json') - root_system_conf_exists = env_system_config is not None or os.path.exists(default_system_conf_path) - root_config_json_exists = os.path.exists(default_config_json_path) - root_config_complete = root_system_conf_exists and root_config_json_exists + default_system_conf_path = os.path.join(os.getcwd(), 'system.conf') + default_config_json_path = os.path.join(os.getcwd(), 'config.json') + cache_system_conf_path = os.path.join(os.getcwd(), 'cache_data', 'system.conf') + cache_config_json_path = os.path.join(os.getcwd(), 'cache_data', 'config.json') + root_system_conf_exists = env_system_config is not None or os.path.exists(default_system_conf_path) + root_config_json_exists = os.path.exists(default_config_json_path) + root_config_complete = root_system_conf_exists and root_config_json_exists - # 构建system.conf和config.json相关路径. - config_center_fallback = False - if using_config_center: - system_conf_path = cache_system_conf_path - config_json_path = cache_config_json_path + # 构建system.conf和config.json相关路径. + config_center_fallback = False + if using_config_center: + system_conf_path = cache_system_conf_path + config_json_path = cache_config_json_path else: if ( system_conf_path is None @@ -365,11 +376,11 @@ def load_config(force_reload=False): system_conf_path = default_system_conf_path config_json_path = default_config_json_path - if not root_config_complete: - cache_ready = (env_system_config is not None or os.path.exists(cache_system_conf_path)) and os.path.exists(cache_config_json_path) - if (not _bootstrap_loaded_from_api) or (not cache_ready): - using_config_center = True - config_center_fallback = True + if not root_config_complete: + cache_ready = (env_system_config is not None or os.path.exists(cache_system_conf_path)) and os.path.exists(cache_config_json_path) + if (not _bootstrap_loaded_from_api) or (not cache_ready): + using_config_center = True + config_center_fallback = True system_conf_path = cache_system_conf_path config_json_path = cache_config_json_path else: @@ -380,132 +391,132 @@ def load_config(force_reload=False): loaded_from_api = False api_attempted = False if using_config_center: - if explicit_config_center: - util.log(1, f"检测到配置中心参数,优先加载项目配置: {CONFIG_SERVER['PROJECT_ID']}") - else: - util.log(1, f"未检测到本地system.conf或config.json,尝试从配置中心加载配置: {CONFIG_SERVER['PROJECT_ID']}") - api_config = load_config_from_api(CONFIG_SERVER['PROJECT_ID']) + if explicit_config_center: + util.log(1, f"检测到配置中心参数,优先加载项目配置: {CONFIG_SERVER['PROJECT_ID']}") + else: + util.log(1, f"未检测到本地system.conf或config.json,尝试从配置中心加载配置: {CONFIG_SERVER['PROJECT_ID']}") + api_config = load_config_from_api(CONFIG_SERVER['PROJECT_ID']) api_attempted = True if api_config: - util.log(1, "成功从配置中心加载配置") - system_config = api_config['system_config'] - env_system_config = system_config - _save_system_config_to_env(system_config, project_id=CONFIG_SERVER['PROJECT_ID'], source='api') - config = api_config['config'] + util.log(1, "成功从配置中心加载配置") + system_config = api_config['system_config'] + env_system_config = system_config + _save_system_config_to_env(system_config, project_id=CONFIG_SERVER['PROJECT_ID'], source='api') + config = api_config['config'] loaded_from_api = True if config_center_fallback: _bootstrap_loaded_from_api = True - # 将配置中心配置缓存到本地文件. - system_conf_path = cache_system_conf_path - config_json_path = cache_config_json_path - save_api_config_to_local( - api_config, - system_conf_path, - config_json_path, - save_config_json=not os.path.exists(config_json_path), - save_system_conf=False - ) - forced_loaded = True + # 将配置中心配置缓存到本地文件. + system_conf_path = cache_system_conf_path + config_json_path = cache_config_json_path + save_api_config_to_local( + api_config, + system_conf_path, + config_json_path, + save_config_json=not os.path.exists(config_json_path), + save_system_conf=False + ) + forced_loaded = True _warn_public_config_once() else: - util.log(2, "配置中心加载失败,尝试使用缓存配置") + util.log(2, "配置中心加载失败,尝试使用缓存配置") - sys_conf_exists = env_system_config is not None or os.path.exists(system_conf_path) - config_json_exists = os.path.exists(config_json_path) + sys_conf_exists = env_system_config is not None or os.path.exists(system_conf_path) + config_json_exists = os.path.exists(config_json_path) # 如果任一本地文件不存在,直接尝试从API加载 if (not sys_conf_exists or not config_json_exists) and not forced_loaded: if using_config_center: if not api_attempted: - util.log(1, "配置中心缓存缺失,尝试从配置中心加载配置...") + util.log(1, "配置中心缓存缺失,尝试从配置中心加载配置...") api_config = load_config_from_api(CONFIG_SERVER['PROJECT_ID']) api_attempted = True if api_config: - util.log(1, "成功从配置中心加载配置") - system_config = api_config['system_config'] - env_system_config = system_config - _save_system_config_to_env(system_config, project_id=CONFIG_SERVER['PROJECT_ID'], source='api') - config = api_config['config'] + util.log(1, "成功从配置中心加载配置") + system_config = api_config['system_config'] + env_system_config = system_config + _save_system_config_to_env(system_config, project_id=CONFIG_SERVER['PROJECT_ID'], source='api') + config = api_config['config'] loaded_from_api = True if config_center_fallback: _bootstrap_loaded_from_api = True - # 将配置中心配置缓存到本地文件. - system_conf_path = cache_system_conf_path - config_json_path = cache_config_json_path - save_api_config_to_local( - api_config, - system_conf_path, - config_json_path, - save_config_json=not os.path.exists(config_json_path), - save_system_conf=False - ) + # 将配置中心配置缓存到本地文件. + system_conf_path = cache_system_conf_path + config_json_path = cache_config_json_path + save_api_config_to_local( + api_config, + system_conf_path, + config_json_path, + save_config_json=not os.path.exists(config_json_path), + save_system_conf=False + ) _warn_public_config_once() else: - # 使用项目配置或全局项目配置作为回退来源. - util.log(1, f"本地配置文件不完整,尝试从API加载配置...") - api_config = load_config_from_api(CONFIG_SERVER['PROJECT_ID']) + # 使用项目配置或全局项目配置作为回退来源. + util.log(1, f"本地配置文件不完整,尝试从API加载配置...") + api_config = load_config_from_api(CONFIG_SERVER['PROJECT_ID']) if api_config: - util.log(1, "成功从配置中心加载配置") - system_config = api_config['system_config'] - env_system_config = system_config - _save_system_config_to_env(system_config, project_id=CONFIG_SERVER['PROJECT_ID'], source='api') - config = api_config['config'] + util.log(1, "成功从配置中心加载配置") + system_config = api_config['system_config'] + env_system_config = system_config + _save_system_config_to_env(system_config, project_id=CONFIG_SERVER['PROJECT_ID'], source='api') + config = api_config['config'] loaded_from_api = True - # 将配置中心配置缓存到本地文件. - system_conf_path = cache_system_conf_path - config_json_path = cache_config_json_path - save_api_config_to_local( - api_config, - system_conf_path, - config_json_path, - save_config_json=not os.path.exists(config_json_path), - save_system_conf=False - ) + # 将配置中心配置缓存到本地文件. + system_conf_path = cache_system_conf_path + config_json_path = cache_config_json_path + save_api_config_to_local( + api_config, + system_conf_path, + config_json_path, + save_config_json=not os.path.exists(config_json_path), + save_system_conf=False + ) _warn_public_config_once() - sys_conf_exists = env_system_config is not None or os.path.exists(system_conf_path) - config_json_exists = os.path.exists(config_json_path) + sys_conf_exists = env_system_config is not None or os.path.exists(system_conf_path) + config_json_exists = os.path.exists(config_json_path) if using_config_center and (not sys_conf_exists or not config_json_exists): if _last_loaded_config is not None and _last_loaded_from_api: - util.log(2, "配置中心缓存不可用,继续使用内存中的配置") + util.log(2, "配置中心缓存不可用,继续使用内存中的配置") return _last_loaded_config if config_center_fallback and using_config_center and (not sys_conf_exists or not config_json_exists): - cache_ready = (env_system_config is not None or os.path.exists(cache_system_conf_path)) and os.path.exists(cache_config_json_path) + cache_ready = (env_system_config is not None or os.path.exists(cache_system_conf_path)) and os.path.exists(cache_config_json_path) if cache_ready: - util.log(2, "配置中心不可用,回退使用缓存配置") + util.log(2, "配置中心不可用,回退使用缓存配置") using_config_center = False system_conf_path = cache_system_conf_path config_json_path = cache_config_json_path else: - util.log(2, "配置中心不可用,回退使用本地配置文件") + util.log(2, "配置中心不可用,回退使用本地配置文件") using_config_center = False system_conf_path = default_system_conf_path config_json_path = default_config_json_path - sys_conf_exists = env_system_config is not None or os.path.exists(system_conf_path) - config_json_exists = os.path.exists(config_json_path) + sys_conf_exists = env_system_config is not None or os.path.exists(system_conf_path) + config_json_exists = os.path.exists(config_json_path) # 如果本地文件存在,从本地文件加载 # 加载system.conf - if env_system_config is not None: - system_config = env_system_config - else: - system_config = ConfigParser() - if os.path.exists(system_conf_path): - system_config.read(system_conf_path, encoding='UTF-8') - _save_system_config_to_env( - system_config, - project_id=CONFIG_SERVER['PROJECT_ID'] if using_config_center else None, - source='api' if using_config_center else 'local' - ) - - if not system_config.has_section('key'): - system_config.add_section('key') + if env_system_config is not None: + system_config = env_system_config + else: + system_config = ConfigParser() + if os.path.exists(system_conf_path): + system_config.read(system_conf_path, encoding='UTF-8') + _save_system_config_to_env( + system_config, + project_id=CONFIG_SERVER['PROJECT_ID'] if using_config_center else None, + source='api' if using_config_center else 'local' + ) + + if not system_config.has_section('key'): + system_config.add_section('key') # 从system.conf中读取所有配置项 key_ali_nls_key_id = system_config.get('key', 'ali_nls_key_id', fallback=None) @@ -538,6 +549,12 @@ def load_config(force_reload=False): embedding_api_base_url = gpt_base_url # 复用 LLM base_url embedding_api_key = key_gpt_api_key # 复用 LLM api_key + # 读取小模型配置(大小模型交互),未配置时复用大模型设置 + small_model_api_key = system_config.get('key', 'small_model_api_key', fallback=None) + small_model_base_url = system_config.get('key', 'small_model_base_url', fallback=None) + small_model_engine = system_config.get('key', 'small_model_engine', fallback=None) + model_interaction_enabled = system_config.get('key', 'model_interaction_enabled', fallback='false').lower() == 'true' + start_mode = system_config.get('key', 'start_mode', fallback=None) fay_url = system_config.get('key', 'fay_url', fallback=None) # 如果fay_url为空或None,则动态获取本机IP地址 @@ -548,12 +565,12 @@ def load_config(force_reload=False): # 更新system_config中的值,但不写入文件 if not system_config.has_section('key'): system_config.add_section('key') - system_config.set('key', 'fay_url', fay_url) - _save_system_config_to_env( - system_config, - project_id=CONFIG_SERVER['PROJECT_ID'] if using_config_center else None, - source='api' if using_config_center else 'local' - ) + system_config.set('key', 'fay_url', fay_url) + _save_system_config_to_env( + system_config, + project_id=CONFIG_SERVER['PROJECT_ID'] if using_config_center else None, + source='api' if using_config_center else 'local' + ) # 读取用户配置 with codecs.open(config_json_path, encoding='utf-8') as f: @@ -601,6 +618,12 @@ def load_config(force_reload=False): 'embedding_api_base_url': embedding_api_base_url, 'embedding_api_key': embedding_api_key, + # 小模型配置(大小模型交互) + 'small_model_api_key': small_model_api_key, + 'small_model_base_url': small_model_base_url, + 'small_model_engine': small_model_engine, + 'model_interaction_enabled': model_interaction_enabled, + 'source': 'api' if using_config_center else 'local' # 标记配置来源 } @@ -610,42 +633,42 @@ def load_config(force_reload=False): return config_dict -def save_api_config_to_local(api_config, system_conf_path, config_json_path, save_config_json=True, save_system_conf=False): - """ - Persist API config to local files. +def save_api_config_to_local(api_config, system_conf_path, config_json_path, save_config_json=True, save_system_conf=False): + """ + Persist API config to local files. - Args: - api_config: API response dict. - system_conf_path: Path to system.conf. - config_json_path: Path to config.json. - save_config_json: Whether to write config.json. + Args: + api_config: API response dict. + system_conf_path: Path to system.conf. + config_json_path: Path to config.json. + save_config_json: Whether to write config.json. """ try: - # 确保目录存在. - if save_system_conf and system_conf_path: - os.makedirs(os.path.dirname(system_conf_path), exist_ok=True) - os.makedirs(os.path.dirname(config_json_path), exist_ok=True) - - # 始终刷新 system.conf. - if save_system_conf and system_conf_path: - with open(system_conf_path, 'w', encoding='utf-8') as f: - api_config['system_config'].write(f) - - # 默认只在首次下载时保存config.json. - if save_config_json: - with codecs.open(config_json_path, 'w', encoding='utf-8') as f: - json.dump(api_config['config'], f, ensure_ascii=False, indent=4) - if save_system_conf and system_conf_path: - util.log(1, f"cached config center files to local: {system_conf_path}, {config_json_path}") - return - if save_config_json: - util.log(1, f"cached config center config.json to local: {config_json_path}") - return - return - - util.log(1, f"已将配置中心配置缓存到本地文件: {system_conf_path} 和 {config_json_path}") - except Exception as e: - util.log(2, f"保存配置中心配置到本地文件时出错: {str(e)}") + # 确保目录存在. + if save_system_conf and system_conf_path: + os.makedirs(os.path.dirname(system_conf_path), exist_ok=True) + os.makedirs(os.path.dirname(config_json_path), exist_ok=True) + + # 始终刷新 system.conf. + if save_system_conf and system_conf_path: + with open(system_conf_path, 'w', encoding='utf-8') as f: + api_config['system_config'].write(f) + + # 默认只在首次下载时保存config.json. + if save_config_json: + with codecs.open(config_json_path, 'w', encoding='utf-8') as f: + json.dump(api_config['config'], f, ensure_ascii=False, indent=4) + if save_system_conf and system_conf_path: + util.log(1, f"cached config center files to local: {system_conf_path}, {config_json_path}") + return + if save_config_json: + util.log(1, f"cached config center config.json to local: {config_json_path}") + return + return + + util.log(1, f"已将配置中心配置缓存到本地文件: {system_conf_path} 和 {config_json_path}") + except Exception as e: + util.log(2, f"保存配置中心配置到本地文件时出错: {str(e)}") @synchronized def save_config(config_data):