"""Use-case oriented LLM gateway. This is a small compatibility layer over the existing provider and JSON helper functions. It gives new code a stable place to request model capabilities while older agents continue to use LangChain directly during the transition. """ from __future__ import annotations from dataclasses import dataclass from typing import Any, Callable, TypeVar from pydantic import BaseModel from app.core.dependencies import get_llm_provider, get_llm_provider_fast from app.core.llm_call import allm_json_call, llm_json_call from app.core.llm_telemetry import langchain_invoke_span T = TypeVar("T", bound=BaseModel) @dataclass(frozen=True) class LlmUseCase: name: str fast: bool = False max_tokens: int | None = None temperature: float | None = None model: str | None = None class LlmGateway: """Facade for text and JSON LLM calls.""" def provider_for(self, use_case: LlmUseCase | None = None): if use_case and use_case.fast: return get_llm_provider_fast() return get_llm_provider() def langchain_llm_for(self, use_case: LlmUseCase | None = None) -> Any | None: provider = self.provider_for(use_case) return getattr(provider, "langchain_llm", None) async def chat_text( self, messages: list[dict], *, use_case: LlmUseCase | None = None, temperature: float | None = None, model: str | None = None, max_tokens: int | None = None, ) -> str: provider = self.provider_for(use_case) resolved_temperature = ( temperature if temperature is not None else ( use_case.temperature if use_case and use_case.temperature is not None else 0.7 ) ) resolved_model = model if model is not None else (use_case.model if use_case else None) agent_name = use_case.name if use_case else "llm_gateway.chat" kwargs = dict( messages=messages, temperature=resolved_temperature, model=resolved_model, max_tokens=( max_tokens if max_tokens is not None else (use_case.max_tokens if use_case else None) ), ) # DeepSeekProvider.complete 已包 langchain_invoke_span,避免双层 span from app.adapters.llm.deepseek import DeepSeekLLMProvider if isinstance(provider, DeepSeekLLMProvider): return await provider.complete(**kwargs) provider_label = type(provider).__name__.replace("Provider", "").lower() or "unknown" with langchain_invoke_span( agent=agent_name, provider=provider_label, model=resolved_model or "unknown", call_type="chat", ): return await provider.complete(**kwargs) async def json_object( self, prompt: str, schema: type[T], *, use_case: LlmUseCase, fallback_factory: Callable[[], T] | None = None, ) -> T: return await allm_json_call( self.langchain_llm_for(use_case), prompt, schema, max_tokens=use_case.max_tokens or 1024, agent=use_case.name, fallback_factory=fallback_factory, ) def sync_json_object( self, prompt: str, schema: type[T], *, use_case: LlmUseCase, fallback_factory: Callable[[], T] | None = None, ) -> T: return llm_json_call( self.langchain_llm_for(use_case), prompt, schema, max_tokens=use_case.max_tokens or 1024, agent=use_case.name, fallback_factory=fallback_factory, ) __all__ = ["LlmGateway", "LlmUseCase"]