ForcePilot/backend/package/yuxi/agents/models.py

85 lines
3.4 KiB
Python
Raw Normal View History

from langchain.chat_models import BaseChatModel
from pydantic import SecretStr
2025-03-25 05:40:07 +08:00
2026-06-11 22:48:28 +08:00
from yuxi import config as sys_config
2026-05-29 22:19:58 +08:00
from yuxi.models.providers.cache import model_cache
from yuxi.utils import get_docker_safe_url
from yuxi.utils.logging_config import logger
2025-03-29 17:33:09 +08:00
# 这些提供商的 OpenAI 兼容流式接口,在 LangGraph v3 事件流累积工具调用时会丢失
# tool_call 的关键字段siliconflow MiniMax 丢 name、alibaba 百炼丢 id空值会被写入
# checkpoint导致工具结果无法按 tool_call_id 关联、工具状态永远停留在“进行中”。
# 对这些提供商在工具调用阶段禁用流式(正文回答仍流式)以拿到完整 tool_call。
# 该缺陷属 LangChain v3 流式协议上游问题langchain#37420 / langchainjs#10937 /
# langgraphjs#2496截至 langchain-core 1.4.4 仍未修复,待上游修复后可移除本处理。
_NON_STREAMING_TOOL_CALL_PROVIDERS = ("siliconflow", "alibaba")
def _requires_non_streaming_tool_calls(provider_id: str, model_id: str) -> bool:
return provider_id.startswith(_NON_STREAMING_TOOL_CALL_PROVIDERS)
2026-06-11 22:48:28 +08:00
def resolve_chat_model_spec(model_spec: str | None, *, fallback: str | None = None) -> str:
"""解析空模型配置,不吞掉已经配置但无效的模型值。
这里仅处理模型为空时的优先级请求或配置值调用方 fallback系统默认模型
具体模型是否存在是否为聊天模型仍由 model_cache 校验
"""
for candidate in (model_spec, fallback, sys_config.default_model):
if isinstance(candidate, str) and candidate.strip():
return candidate.strip()
raise ValueError("model spec 不能为空")
def load_chat_model(fully_specified_name: str | None, **kwargs) -> BaseChatModel:
fully_specified_name = resolve_chat_model_spec(fully_specified_name)
info = model_cache.get_model_info(fully_specified_name)
if not info:
available_specs = model_cache.get_all_specs("chat")
available_ids = [item.spec for item in available_specs[:10]]
raise ValueError(
f"Unknown model spec: '{fully_specified_name}'. "
f"Available chat models ({len(available_specs)}): {available_ids}"
)
if info.model_type != "chat":
raise ValueError(f"Model {fully_specified_name} is not a chat model (type={info.model_type})")
api_key = info.api_key
base_url = get_docker_safe_url(info.base_url)
logger.debug(f"Loading model {fully_specified_name} with provider_type={info.provider_type}")
if info.provider_type == "anthropic":
from langchain_anthropic import ChatAnthropic
return ChatAnthropic(
model=info.model_id,
api_key=SecretStr(api_key),
base_url=base_url,
**kwargs,
)
if info.provider_type == "gemini":
from langchain_google_genai import ChatGoogleGenerativeAI
return ChatGoogleGenerativeAI(
model=info.model_id,
google_api_key=SecretStr(api_key),
**kwargs,
)
from langchain_openai import ChatOpenAI
openai_kwargs = dict(kwargs)
if _requires_non_streaming_tool_calls(info.provider_id, info.model_id):
openai_kwargs.setdefault("disable_streaming", "tool_calling")
return ChatOpenAI(
model=info.model_id,
api_key=SecretStr(api_key),
base_url=base_url,
stream_usage=True,
**openai_kwargs,
)