mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 05:51:57 +00:00
115 lines
5.9 KiB
Python
115 lines
5.9 KiB
Python
"""起草用哪个模型:数据目录的「用模型起草」和按配方导入的 AI 起草共用这一处取值。
|
||
|
||
顺序(哪一级写了就整组用哪一级,不跨级拼接入和模型):
|
||
|
||
1. 设置里的起草模型(catalog_draft 一组:{provider, model}),填了接入或模型就用它;
|
||
2. 否则沿用助手的模型设置(copilot 一组,「编排」页助手栏的「助手使用的模型」);
|
||
3. 再没有就是默认接入(providers/factory.resolve_provider 的回退)。
|
||
|
||
这里只决定「用哪个接入、哪个模型」。额度、思考、温度由两边各自定:数据目录起草保留思考、额度 32768
|
||
(data/catalog.resolve_draft_model),配方 AI 起草照旧关思考(data/recipe_ai.resolve_draft_model)。
|
||
|
||
**为什么不用 get_chat_model。** 它在没有接入时静默退回演示模型,拿演示模型起草出来的东西会被当成真的。
|
||
两边都自己解析接入:没有、停用、只有演示接入、缺 key,都在调用模型之前判定为不可用,说清是哪一级的设置出了问题。
|
||
|
||
数据层不导入接口层:设置键在这里定义,api/settings.py 从这里取(助手那一项和 api/copilot.py 同值,有测试核对)。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass
|
||
from typing import Any
|
||
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
#: 设置里起草模型那一组的键
|
||
CATALOG_DRAFT_SETTING_KEY = "catalog_draft"
|
||
#: 助手的模型设置键(与 api/copilot.py 的 COPILOT_SETTING_KEY 同值)
|
||
COPILOT_SETTING_KEY = "copilot"
|
||
#: 起草模型在设置页上的位置。报错里让人去这里改;和前端设置页放这一组的地方一致
|
||
DRAFT_MODEL_PLACE = "「设置 → 偏好设置」的「起草模型」"
|
||
|
||
#: 取自哪一级:draft 设置里的起草模型、assistant 助手的模型、default 默认接入
|
||
ORIGINS = ("draft", "assistant", "default")
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class DraftModelChoice:
|
||
"""起草用的接入和模型(都可以为 None:接入为 None 时按模型名找、再退回默认接入;模型为 None 时用接入的默认模型)。"""
|
||
|
||
provider: str | None
|
||
model: str | None
|
||
origin: str
|
||
|
||
|
||
def _text(value: Any) -> str | None:
|
||
"""设置里的一项:字符串去掉首尾空白,空串和别的类型(存坏了的)都当没填。"""
|
||
return value.strip() or None if isinstance(value, str) else None
|
||
|
||
|
||
def setting_pair(stored: Any) -> tuple[str | None, str | None]:
|
||
"""库里存的一组 → (接入, 模型),规范化同 _text。"""
|
||
stored = stored if isinstance(stored, dict) else {}
|
||
return _text(stored.get("provider")), _text(stored.get("model"))
|
||
|
||
|
||
async def draft_model_choice(session: AsyncSession) -> DraftModelChoice:
|
||
"""按上面的顺序取。只读设置,不查接入在不在(resolve_provider 去查)。"""
|
||
from app.db.models import Setting
|
||
|
||
for key, origin in ((CATALOG_DRAFT_SETTING_KEY, "draft"), (COPILOT_SETTING_KEY, "assistant")):
|
||
row = await session.get(Setting, key)
|
||
provider, model = setting_pair(row.value if row is not None else None)
|
||
if provider or model:
|
||
return DraftModelChoice(provider, model, origin)
|
||
return DraftModelChoice(None, None, "default")
|
||
|
||
|
||
def draft_unavailable(e: BaseException, origin: str) -> str:
|
||
"""取到的接入用不了(点名的不存在、已停用,缺 key、没有模型……)时给人看的那句话。
|
||
|
||
沿用助手的模型(或默认接入)时,和助手取不到模型时同一句(factory.assistant_unavailable);用的是设置里的起草
|
||
模型时,照实说是起草模型那一项、去设置页哪里改。"""
|
||
from app.core.errors import not_configured
|
||
from app.providers.factory import NAMED_FIX, assistant_unavailable, model_fact
|
||
|
||
if origin != "draft":
|
||
return assistant_unavailable(e)
|
||
code = getattr(e, "code", "")
|
||
if code in NAMED_FIX:
|
||
said = f"起草模型使用的{getattr(e, 'reason', '') or e}。"
|
||
if owner := getattr(e, "model_owner", ""):
|
||
return (f"{said}{NAMED_FIX[code]};{model_fact(getattr(e, 'name', ''), owner)},"
|
||
f"也可在{DRAFT_MODEL_PLACE}中把它填进模型一栏")
|
||
return f"{said}{NAMED_FIX[code]},或在{DRAFT_MODEL_PLACE}中重新选择"
|
||
return (f"起草模型尚未配置完成:{not_configured(e, with_hint=False)}。请到「设置 → 模型接入」检查,"
|
||
f"或在{DRAFT_MODEL_PLACE}中重新选择")
|
||
|
||
|
||
class DraftModelUnavailable(RuntimeError):
|
||
"""没有可用于起草的模型。message 给人看;在调用模型之前判定。"""
|
||
|
||
|
||
async def resolve_draft_provider(session: AsyncSession, *, none_reason: str, mock_reason: str) -> tuple[Any, DraftModelChoice]:
|
||
"""(接入, 取值)。用不了时抛 DraftModelUnavailable:没有任何启用的接入说 none_reason,只有演示接入说 mock_reason
|
||
(两边各有各的说法),点名的接入不存在、已停用按取自哪一级说(draft_unavailable)。
|
||
|
||
只解析接入,不建模型对象:建的时候缺 key 之类的问题由调用方经 draft_unavailable 说。"""
|
||
from app.providers.factory import ProviderNotConfigured, resolve_provider
|
||
|
||
choice = await draft_model_choice(session)
|
||
try:
|
||
# 存的接入名不存在、已停用,只存了模型名、提供它的接入已停用:resolve_provider 都会报出来
|
||
provider = await resolve_provider(session, choice.provider, choice.model)
|
||
except ProviderNotConfigured as e:
|
||
raise DraftModelUnavailable(draft_unavailable(e, choice.origin)) from e
|
||
if provider is None:
|
||
raise DraftModelUnavailable(none_reason)
|
||
if provider.kind == "mock":
|
||
raise DraftModelUnavailable(mock_reason)
|
||
return provider, choice
|
||
|
||
|
||
__all__ = ["CATALOG_DRAFT_SETTING_KEY", "COPILOT_SETTING_KEY", "DRAFT_MODEL_PLACE", "DraftModelChoice",
|
||
"DraftModelUnavailable", "draft_model_choice", "draft_unavailable", "resolve_draft_provider",
|
||
"setting_pair"]
|