"""LLM Protocol - 数据类与抽象基类""" from abc import ABC, abstractmethod from dataclasses import dataclass, field @dataclass class TokenUsage: """Token 使用量。 U2: 添加 cache_read_input_tokens / cache_creation_input_tokens 字段, 用于 Anthropic prompt cache 命中率监控(OTel prompt_cache.hit/miss metric)。 非 Anthropic provider 这两个字段保持默认 0。 """ prompt_tokens: int = 0 completion_tokens: int = 0 # U2 — Anthropic prompt cache:从 prompt cache 读取的 token 数(命中时 > 0) cache_read_input_tokens: int = 0 # U2 — Anthropic prompt cache:写入 prompt cache 的 token 数 cache_creation_input_tokens: int = 0 @property def total_tokens(self) -> int: return self.prompt_tokens + self.completion_tokens @property def cache_hit(self) -> bool: """是否命中 prompt cache(cache_read_input_tokens > 0)""" return self.cache_read_input_tokens > 0 @dataclass class ToolCall: """工具调用""" id: str name: str arguments: dict[str, object] @dataclass class LLMRequest: """LLM 请求""" messages: list[dict[str, str]] model: str tools: list[dict[str, object]] | None = None tool_choice: str = "auto" temperature: float = 0.7 max_tokens: int = 2000 timeout: float | None = None def __init__( self, messages: list[dict[str, str]], model: str, tools: list[dict[str, object]] | None = None, tool_choice: str = "auto", temperature: float = 0.7, max_tokens: int = 2000, timeout: float | None = None, cache: dict[str, object] | None = None, **kwargs: object, ): self.messages = messages self.model = model self.tools = tools self.tool_choice = tool_choice self.temperature = temperature self.max_tokens = max_tokens self.timeout = timeout self._extra = kwargs # U17 — LiteLLM cache 参数(cache_key 或 no-cache),透传到 litellm.acompletion self._cache: dict[str, object] | None = cache @dataclass class StreamChunk: """LLM 流式响应块""" content: str # Delta content model: str tool_calls: list[ToolCall] = field( default_factory=list ) # Accumulated tool calls (only in final chunk) usage: TokenUsage | None = None # Only in final chunk is_final: bool = False # True for the last chunk @dataclass class LLMResponse: """LLM 响应""" content: str model: str usage: TokenUsage tool_calls: list[ToolCall] = field(default_factory=list) latency_ms: float = 0.0 cache_hit: bool = False # U17 — 缓存命中标记,用于 usage tracking(cost=0) @property def has_tool_calls(self) -> bool: return len(self.tool_calls) > 0 class LLMProvider(ABC): """LLM Provider 抽象基类""" @abstractmethod async def chat(self, request: LLMRequest) -> LLMResponse: """发送 chat 请求并返回响应""" ... async def chat_stream(self, request: LLMRequest): """Stream chat response. Override in subclasses that support streaming. Yields StreamChunk objects. Default implementation falls back to non-streaming chat and yields a single chunk. """ response = await self.chat(request) yield StreamChunk( content=response.content, model=response.model, tool_calls=response.tool_calls, usage=response.usage, is_final=True, )