"""统一获取普通模型或绑定结构化输出的模型""" from functools import cache from typing import Any, TypeVar, overload from langchain_core.runnables import Runnable from langchain_openai import ChatOpenAI from pydantic import BaseModel from app.config import settings OutputModel = TypeVar("OutputModel", bound=BaseModel) @cache def _get_base_llm() -> ChatOpenAI: """只缓存基础客户端,不因输出 schema 不同而重复创建连接""" if not settings.llm_api_key.strip() or not settings.llm_model.strip(): raise ValueError("未配置 LLM_API_KEY 或 LLM_MODEL") return ChatOpenAI( model=settings.llm_model, api_key=settings.llm_api_key, base_url=settings.llm_base_url, timeout=settings.llm_timeout, max_retries=settings.llm_max_retries, ) # 下面两个 @overload 只给类型检查器和编辑器看,运行时不会执行 # 不传 schema:返回普通聊天模型,输出是自由文本 @overload def get_llm() -> ChatOpenAI: ... # 传入 Pydantic 类:返回结构化调用器,ainvoke 的结果就是该类的实例 # OutputModel 让返回类型跟着传入的类走,例如 get_llm(Requirement) 得到 Runnable[..., Requirement] @overload def get_llm(schema: type[OutputModel]) -> Runnable[Any, OutputModel]: ... # 真正的实现:schema 用 type[BaseModel] | None 覆盖上面两种调用 # 返回类型由 overload 决定,这里不再重复标注 def get_llm(schema: type[BaseModel] | None = None) -> Any: llm = _get_base_llm() if schema is None: return llm return llm.with_structured_output(schema, method="function_calling", strict=False)