177 lines
4.9 KiB
Python
177 lines
4.9 KiB
Python
"""
|
|
Base LLM client interface.
|
|
[AC-AISVC-02, AC-AISVC-06] Abstract interface for LLM providers.
|
|
|
|
Design reference: design.md Section 8.1 - LLMClient interface
|
|
- generate(prompt, params) -> text
|
|
- stream_generate(prompt, params) -> iterator[delta]
|
|
"""
|
|
|
|
from abc import ABC, abstractmethod
|
|
from collections.abc import AsyncGenerator
|
|
from dataclasses import dataclass, field
|
|
from typing import Any
|
|
|
|
|
|
@dataclass
|
|
class LLMConfig:
|
|
"""
|
|
Configuration for LLM client.
|
|
[AC-AISVC-02] Supports configurable model parameters.
|
|
"""
|
|
model: str = "gpt-4o-mini"
|
|
max_tokens: int = 2048
|
|
temperature: float = 0.7
|
|
top_p: float = 1.0
|
|
timeout_seconds: int = 30
|
|
max_retries: int = 3
|
|
extra_params: dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass
|
|
class ToolCall:
|
|
"""
|
|
Represents a function call from the LLM.
|
|
Used in Function Calling mode.
|
|
"""
|
|
id: str
|
|
name: str
|
|
arguments: dict[str, Any]
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
import json
|
|
return {
|
|
"id": self.id,
|
|
"type": "function",
|
|
"function": {
|
|
"name": self.name,
|
|
"arguments": json.dumps(self.arguments, ensure_ascii=False),
|
|
}
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class LLMResponse:
|
|
"""
|
|
Response from LLM generation.
|
|
[AC-AISVC-02] Contains generated content and metadata.
|
|
"""
|
|
content: str | None = None
|
|
model: str = ""
|
|
usage: dict[str, int] = field(default_factory=dict)
|
|
finish_reason: str = "stop"
|
|
tool_calls: list[ToolCall] = field(default_factory=list)
|
|
metadata: dict[str, Any] = field(default_factory=dict)
|
|
|
|
@property
|
|
def has_tool_calls(self) -> bool:
|
|
"""Check if response contains tool calls."""
|
|
return len(self.tool_calls) > 0
|
|
|
|
|
|
@dataclass
|
|
class LLMStreamChunk:
|
|
"""
|
|
Streaming chunk from LLM.
|
|
[AC-AISVC-06, AC-AISVC-07] Incremental output for SSE streaming.
|
|
"""
|
|
delta: str
|
|
model: str
|
|
finish_reason: str | None = None
|
|
tool_calls_delta: list[dict[str, Any]] = field(default_factory=list)
|
|
metadata: dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass
|
|
class ToolDefinition:
|
|
"""
|
|
Tool definition for Function Calling.
|
|
Compatible with OpenAI/DeepSeek function calling format.
|
|
"""
|
|
name: str
|
|
description: str
|
|
parameters: dict[str, Any]
|
|
type: str = "function"
|
|
|
|
def to_openai_format(self) -> dict[str, Any]:
|
|
"""Convert to OpenAI tools format."""
|
|
return {
|
|
"type": self.type,
|
|
"function": {
|
|
"name": self.name,
|
|
"description": self.description,
|
|
"parameters": self.parameters,
|
|
}
|
|
}
|
|
|
|
|
|
class LLMClient(ABC):
|
|
"""
|
|
Abstract base class for LLM clients.
|
|
[AC-AISVC-02, AC-AISVC-06] Provides unified interface for different LLM providers.
|
|
|
|
Design reference: design.md Section 8.2 - Plugin points
|
|
- OpenAICompatibleClient / LocalModelClient can be swapped
|
|
"""
|
|
|
|
@abstractmethod
|
|
async def generate(
|
|
self,
|
|
messages: list[dict[str, str]],
|
|
config: LLMConfig | None = None,
|
|
tools: list[ToolDefinition] | None = None,
|
|
tool_choice: str | dict[str, Any] | None = None,
|
|
**kwargs: Any,
|
|
) -> LLMResponse:
|
|
"""
|
|
Generate a non-streaming response.
|
|
[AC-AISVC-02] Returns complete response for ChatResponse.
|
|
|
|
Args:
|
|
messages: List of chat messages with 'role' and 'content'.
|
|
config: Optional LLM configuration overrides.
|
|
tools: Optional list of tools for function calling.
|
|
tool_choice: Tool choice strategy ("auto", "none", or specific tool).
|
|
**kwargs: Additional provider-specific parameters.
|
|
|
|
Returns:
|
|
LLMResponse with generated content, tool_calls, and metadata.
|
|
|
|
Raises:
|
|
LLMException: If generation fails.
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
async def stream_generate(
|
|
self,
|
|
messages: list[dict[str, str]],
|
|
config: LLMConfig | None = None,
|
|
tools: list[ToolDefinition] | None = None,
|
|
tool_choice: str | dict[str, Any] | None = None,
|
|
**kwargs: Any,
|
|
) -> AsyncGenerator[LLMStreamChunk, None]:
|
|
"""
|
|
Generate a streaming response.
|
|
[AC-AISVC-06, AC-AISVC-07] Yields incremental chunks for SSE.
|
|
|
|
Args:
|
|
messages: List of chat messages with 'role' and 'content'.
|
|
config: Optional LLM configuration overrides.
|
|
tools: Optional list of tools for function calling.
|
|
tool_choice: Tool choice strategy ("auto", "none", or specific tool).
|
|
**kwargs: Additional provider-specific parameters.
|
|
|
|
Yields:
|
|
LLMStreamChunk with incremental content.
|
|
|
|
Raises:
|
|
LLMException: If generation fails.
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
async def close(self) -> None:
|
|
"""Close the client and release resources."""
|
|
pass
|