feat: make chat model routing configurable
This commit is contained in:
@@ -294,6 +294,7 @@ def create_model(
|
||||
allow_summary=payload.allowSummary,
|
||||
allow_report=payload.allowReport,
|
||||
allow_fixed_info=payload.allowFixedInfo,
|
||||
allow_simple_knowledge=payload.allowSimpleKnowledge,
|
||||
allow_deep_chat=payload.allowDeepChat,
|
||||
enabled=0,
|
||||
is_default=0,
|
||||
@@ -346,6 +347,7 @@ def update_model(
|
||||
model.allow_summary = payload.allowSummary
|
||||
model.allow_report = payload.allowReport
|
||||
model.allow_fixed_info = payload.allowFixedInfo
|
||||
model.allow_simple_knowledge = payload.allowSimpleKnowledge
|
||||
model.allow_deep_chat = payload.allowDeepChat
|
||||
db.add(model)
|
||||
OperationLogService.write(db, admin_id=current_admin.id, module="model", action="update", target_id=model.id)
|
||||
@@ -502,6 +504,7 @@ def _model_dict(model: ModelConfig) -> dict:
|
||||
"allowSummary": model.allow_summary,
|
||||
"allowReport": model.allow_report,
|
||||
"allowFixedInfo": model.allow_fixed_info,
|
||||
"allowSimpleKnowledge": model.allow_simple_knowledge,
|
||||
"allowDeepChat": model.allow_deep_chat,
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ class ModelConfig(Base):
|
||||
allow_summary: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
|
||||
allow_report: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
|
||||
allow_fixed_info: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
|
||||
allow_simple_knowledge: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
|
||||
allow_deep_chat: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
|
||||
|
||||
|
||||
|
||||
@@ -182,6 +182,7 @@ class ModelSaveRequest(BaseModel):
|
||||
allowSummary: int = Field(default=1, ge=0, le=1)
|
||||
allowReport: int = Field(default=1, ge=0, le=1)
|
||||
allowFixedInfo: int = Field(default=1, ge=0, le=1)
|
||||
allowSimpleKnowledge: int = Field(default=1, ge=0, le=1)
|
||||
allowDeepChat: int = Field(default=1, ge=0, le=1)
|
||||
|
||||
|
||||
|
||||
@@ -136,6 +136,7 @@ class AgentDebugService:
|
||||
chat_route = ModelRoutingService.resolve_chat(
|
||||
db,
|
||||
[chunk.knowledge_type for chunk in rag_result.chunks],
|
||||
rag_result.question,
|
||||
)
|
||||
automatic_route = default_model is not None and requested_model.id == default_model.id
|
||||
if automatic_route:
|
||||
|
||||
@@ -146,6 +146,7 @@ class ChatService:
|
||||
failed_route = ModelRoutingService.resolve_chat(
|
||||
db,
|
||||
[chunk.knowledge_type for chunk in rag_result.chunks] if rag_result is not None else [],
|
||||
rag_result.question if rag_result is not None else normalized_question,
|
||||
)
|
||||
AiRequestLogService.write_failed(
|
||||
db,
|
||||
|
||||
@@ -441,6 +441,7 @@ def _model_log_context(
|
||||
route = ModelRoutingService.resolve_chat(
|
||||
db,
|
||||
[chunk.knowledge_type for chunk in rag_result.chunks] if rag_result is not None else [],
|
||||
rag_result.question if rag_result is not None else "",
|
||||
)
|
||||
return (
|
||||
route.reason,
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
from typing import Literal, cast
|
||||
|
||||
from sqlalchemy import desc, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.ai_config import ModelConfig, SystemConfig
|
||||
|
||||
ModelScenario = Literal["report", "summary", "fixed_info", "deep_chat"]
|
||||
ModelScenario = Literal["report", "summary", "fixed_info", "simple_knowledge", "deep_chat"]
|
||||
ChatRoutingMode = Literal["off", "fixed_only", "conservative"]
|
||||
|
||||
SCENARIO_LABELS: dict[ModelScenario, str] = {
|
||||
"report": "周期报告",
|
||||
"summary": "摘要沉淀",
|
||||
"fixed_info": "固定信息",
|
||||
"simple_knowledge": "简单知识问答",
|
||||
"deep_chat": "深度对话",
|
||||
}
|
||||
|
||||
@@ -21,9 +23,59 @@ _SCENARIO_FIELDS = {
|
||||
"report": ModelConfig.allow_report,
|
||||
"summary": ModelConfig.allow_summary,
|
||||
"fixed_info": ModelConfig.allow_fixed_info,
|
||||
"simple_knowledge": ModelConfig.allow_simple_knowledge,
|
||||
"deep_chat": ModelConfig.allow_deep_chat,
|
||||
}
|
||||
|
||||
_SIMPLE_KNOWLEDGE_TYPES = {"course", "qa", "general"}
|
||||
_SIMPLE_QUERY_MARKERS = (
|
||||
"是什么",
|
||||
"有哪些",
|
||||
"有什么",
|
||||
"包括什么",
|
||||
"定义",
|
||||
"含义",
|
||||
"区别",
|
||||
"时间",
|
||||
"几点",
|
||||
"在哪里",
|
||||
"多少",
|
||||
"多久",
|
||||
"步骤",
|
||||
"怎么操作",
|
||||
"怎么练",
|
||||
"注意事项",
|
||||
"作业",
|
||||
"功课",
|
||||
)
|
||||
_DEEP_QUERY_MARKERS = (
|
||||
"我",
|
||||
"自己",
|
||||
"感觉",
|
||||
"感受",
|
||||
"情绪",
|
||||
"身体",
|
||||
"害怕",
|
||||
"担心",
|
||||
"焦虑",
|
||||
"难受",
|
||||
"痛苦",
|
||||
"卡住",
|
||||
"怎么办",
|
||||
"为什么",
|
||||
"关系",
|
||||
"孩子",
|
||||
"父母",
|
||||
"伴侣",
|
||||
"创伤",
|
||||
"建议",
|
||||
"分析",
|
||||
"帮我",
|
||||
"适合我",
|
||||
"对不对",
|
||||
"确认一下",
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ModelRoute:
|
||||
@@ -90,14 +142,20 @@ class ModelRoutingService:
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def resolve_chat(cls, db: Session, knowledge_types: list[str]) -> ModelRoute:
|
||||
def resolve_chat(
|
||||
cls,
|
||||
db: Session,
|
||||
knowledge_types: list[str],
|
||||
question: str = "",
|
||||
) -> ModelRoute:
|
||||
normalized_types = {item.strip().lower() for item in knowledge_types if item and item.strip()}
|
||||
mode = cls.chat_routing_mode(db)
|
||||
if normalized_types == {"fixed"}:
|
||||
if not cls._config_bool(db, "fixed_info_model_routing_enabled", True):
|
||||
if mode == "off":
|
||||
return ModelRoute(
|
||||
model=cls.default_model(db),
|
||||
scenario="deep_chat",
|
||||
reason="正式聊天:仅召回固定信息类知识库,但固定信息模型分流开关已关闭;使用默认主模型",
|
||||
reason="正式聊天:仅召回固定信息类知识库,但正式问答模型分流开关已关闭;使用默认主模型",
|
||||
fallback_used=False,
|
||||
question_type="fixed_info",
|
||||
)
|
||||
@@ -110,6 +168,16 @@ class ModelRoutingService:
|
||||
question_type="fixed_info",
|
||||
)
|
||||
|
||||
if mode == "conservative" and cls._is_simple_knowledge_query(question, normalized_types):
|
||||
route = cls.resolve(db, "simple_knowledge")
|
||||
return ModelRoute(
|
||||
model=route.model,
|
||||
scenario=route.scenario,
|
||||
reason=f"正式聊天:命中知识库且符合保守简单知识规则;{route.reason}",
|
||||
fallback_used=route.fallback_used,
|
||||
question_type="simple_knowledge",
|
||||
)
|
||||
|
||||
default = cls.default_model(db)
|
||||
if not normalized_types:
|
||||
reason = "正式聊天:未召回知识库;使用默认主模型"
|
||||
@@ -128,9 +196,35 @@ class ModelRoutingService:
|
||||
question_type=question_type,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def chat_routing_mode(cls, db: Session) -> ChatRoutingMode:
|
||||
value = cls._config_value(db, "chat_model_routing_mode")
|
||||
if value in {"off", "fixed_only", "conservative"}:
|
||||
return cast(ChatRoutingMode, value)
|
||||
# Compatibility with the previous emergency switch.
|
||||
return "fixed_only" if cls._config_bool(db, "fixed_info_model_routing_enabled", True) else "off"
|
||||
|
||||
@staticmethod
|
||||
def _is_simple_knowledge_query(question: str, knowledge_types: set[str]) -> bool:
|
||||
normalized_question = "".join(question.split()).lower()
|
||||
if not normalized_question or len(normalized_question) > 80:
|
||||
return False
|
||||
if not knowledge_types or not knowledge_types.issubset(_SIMPLE_KNOWLEDGE_TYPES):
|
||||
return False
|
||||
if any(marker in normalized_question for marker in _DEEP_QUERY_MARKERS):
|
||||
return False
|
||||
return any(marker in normalized_question for marker in _SIMPLE_QUERY_MARKERS)
|
||||
|
||||
@staticmethod
|
||||
def _config_bool(db: Session, key: str, default: bool) -> bool:
|
||||
value = ModelRoutingService._config_value(db, key)
|
||||
if value is None:
|
||||
return default
|
||||
return value.lower() in {"1", "true", "yes", "on", "启用"}
|
||||
|
||||
@staticmethod
|
||||
def _config_value(db: Session, key: str) -> str | None:
|
||||
config = db.scalar(select(SystemConfig).where(SystemConfig.config_key == key))
|
||||
if config is None or not config.config_value.strip():
|
||||
return default
|
||||
return config.config_value.strip().lower() in {"1", "true", "yes", "on", "启用"}
|
||||
return None
|
||||
return config.config_value.strip()
|
||||
|
||||
@@ -35,6 +35,7 @@ class ModelClientService:
|
||||
route = ModelRoutingService.resolve_chat(
|
||||
db,
|
||||
[chunk.knowledge_type for chunk in rag_result.chunks],
|
||||
rag_result.question,
|
||||
)
|
||||
model = route.model
|
||||
mock_model_enabled = _system_config_bool(db, "mock_model_enabled", get_settings().mock_model_enabled)
|
||||
|
||||
@@ -154,6 +154,7 @@ def _chat_route(db: Session, rag_result: RagResult) -> ModelRoute:
|
||||
return ModelRoutingService.resolve_chat(
|
||||
db,
|
||||
[chunk.knowledge_type for chunk in rag_result.chunks],
|
||||
rag_result.question,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user