feat: 添加大小模型交互路由功能

实现小模型路由 + 大模型深度处理的分层架构:
- 新增 llm/model_router.py 路由模块,小模型负责意图分类和复杂度评估
- 简单任务(闲聊、问候等)由小模型直接处理,复杂任务升级到大模型
- config_util.py 新增 small_model_api_key/base_url/engine 等配置项
- nlp_cognitive_stream.py 集成路由决策逻辑,支持按路由结果选择模型
- flask_server.py 新增 /api/model-router-status 接口查询路由状态

https://claude.ai/code/session_01DSLxgwX1HFtgRrkeoj7uYL
This commit is contained in:
Claude
2026-04-09 14:25:24 +00:00
parent 28b1862d06
commit 140011b8a9
4 changed files with 539 additions and 227 deletions
+9
View File
@@ -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():
"""获取麦克风和扬声器的配置状态"""
+258
View File
@@ -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),
},
}
+29 -7
View File
@@ -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)
+243 -220
View File
@@ -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):