donkit-llm 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- donkit/llm/__init__.py +55 -0
- donkit/llm/claude_model.py +484 -0
- donkit/llm/factory.py +164 -0
- donkit/llm/model_abstract.py +256 -0
- donkit/llm/openai_model.py +587 -0
- donkit/llm/vertex_model.py +478 -0
- donkit_llm-0.1.0.dist-info/METADATA +17 -0
- donkit_llm-0.1.0.dist-info/RECORD +9 -0
- donkit_llm-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from enum import Enum, Flag, auto
|
|
3
|
+
from typing import Any, AsyncIterator, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ModelCapability(Flag):
|
|
9
|
+
"""Capabilities that a model can support."""
|
|
10
|
+
|
|
11
|
+
TEXT_GENERATION = auto()
|
|
12
|
+
STREAMING = auto()
|
|
13
|
+
MULTIMODAL_INPUT = auto() # Images, files, audio
|
|
14
|
+
STRUCTURED_OUTPUT = auto() # JSON schema / response format
|
|
15
|
+
TOOL_CALLING = auto() # Function calling
|
|
16
|
+
EMBEDDINGS = auto()
|
|
17
|
+
VISION = auto() # Specifically image understanding
|
|
18
|
+
AUDIO_INPUT = auto()
|
|
19
|
+
FILE_INPUT = auto()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ContentType(str, Enum):
|
|
23
|
+
"""Types of content that can be included in messages."""
|
|
24
|
+
|
|
25
|
+
TEXT = "text"
|
|
26
|
+
IMAGE_URL = "image_url"
|
|
27
|
+
IMAGE_BASE64 = "image_base64"
|
|
28
|
+
FILE_URL = "file_url"
|
|
29
|
+
FILE_BASE64 = "file_base64"
|
|
30
|
+
AUDIO_URL = "audio_url"
|
|
31
|
+
AUDIO_BASE64 = "audio_base64"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ContentPart(BaseModel):
|
|
35
|
+
"""A single part of message content (text, image, file, etc.)."""
|
|
36
|
+
|
|
37
|
+
type: ContentType
|
|
38
|
+
content: str # Text, URL, or base64 data
|
|
39
|
+
mime_type: str | None = None # For files/images/audio
|
|
40
|
+
metadata: dict[str, Any] | None = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Message(BaseModel):
|
|
44
|
+
"""A single message in a conversation."""
|
|
45
|
+
|
|
46
|
+
role: Literal["system", "user", "assistant", "tool"]
|
|
47
|
+
content: str | list[ContentPart] # Simple text or multimodal parts
|
|
48
|
+
name: str | None = None # For tool/function messages
|
|
49
|
+
tool_call_id: str | None = None # For tool response messages
|
|
50
|
+
tool_calls: list["ToolCall"] | None = None # For assistant requesting tools
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ToolCall(BaseModel):
|
|
54
|
+
"""A tool/function call requested by the model."""
|
|
55
|
+
|
|
56
|
+
id: str
|
|
57
|
+
type: Literal["function"] = "function"
|
|
58
|
+
function: "FunctionCall"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class FunctionCall(BaseModel):
|
|
62
|
+
"""Details of a function call."""
|
|
63
|
+
|
|
64
|
+
name: str
|
|
65
|
+
arguments: str # JSON string of arguments
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Tool(BaseModel):
|
|
69
|
+
"""Definition of a tool/function available to the model."""
|
|
70
|
+
|
|
71
|
+
type: Literal["function"] = "function"
|
|
72
|
+
function: "FunctionDefinition"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class FunctionDefinition(BaseModel):
|
|
76
|
+
"""Definition of a function that can be called."""
|
|
77
|
+
|
|
78
|
+
name: str
|
|
79
|
+
description: str
|
|
80
|
+
parameters: dict[str, Any] # JSON Schema for parameters
|
|
81
|
+
strict: bool | None = None # For strict schema adherence (OpenAI)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class GenerateRequest(BaseModel):
|
|
85
|
+
"""Request for text generation."""
|
|
86
|
+
|
|
87
|
+
messages: list[Message]
|
|
88
|
+
temperature: float | None = None
|
|
89
|
+
max_tokens: int | None = None
|
|
90
|
+
top_p: float | None = None
|
|
91
|
+
stop: list[str] | None = None
|
|
92
|
+
tools: list[Tool] | None = None # Available tools for function calling
|
|
93
|
+
tool_choice: str | dict[str, Any] | None = None # "auto", "none", or specific tool
|
|
94
|
+
response_format: dict[str, Any] | None = None # For structured output (JSON schema)
|
|
95
|
+
stream: bool = False
|
|
96
|
+
metadata: dict[str, Any] | None = None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class GenerateResponse(BaseModel):
|
|
100
|
+
"""Response from text generation."""
|
|
101
|
+
|
|
102
|
+
content: str | None = None # Generated text
|
|
103
|
+
tool_calls: list[ToolCall] | None = None # Requested tool calls
|
|
104
|
+
finish_reason: str | None = None # "stop", "length", "tool_calls", etc.
|
|
105
|
+
usage: dict[str, int | None] | None = None # Token usage stats
|
|
106
|
+
metadata: dict[str, Any] | None = None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class StreamChunk(BaseModel):
|
|
110
|
+
"""A chunk of streamed response."""
|
|
111
|
+
|
|
112
|
+
content: str | None = None
|
|
113
|
+
tool_calls: list[ToolCall] | None = None # Partial or complete tool calls
|
|
114
|
+
finish_reason: str | None = None
|
|
115
|
+
metadata: dict[str, Any] | None = None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class EmbeddingRequest(BaseModel):
|
|
119
|
+
"""Request for embeddings."""
|
|
120
|
+
|
|
121
|
+
input: str | list[str] # Text(s) to embed
|
|
122
|
+
dimensions: int | None = None # Output dimensionality (if supported)
|
|
123
|
+
metadata: dict[str, Any] | None = None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class EmbeddingResponse(BaseModel):
|
|
127
|
+
"""Response from embedding generation."""
|
|
128
|
+
|
|
129
|
+
embeddings: list[list[float]] # List of embedding vectors
|
|
130
|
+
usage: dict[str, int] | None = None
|
|
131
|
+
metadata: dict[str, Any] | None = None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class LLMModelAbstract(ABC):
|
|
135
|
+
"""
|
|
136
|
+
Abstract base class for all LLM model providers.
|
|
137
|
+
|
|
138
|
+
Provides a unified interface for:
|
|
139
|
+
- Text generation (sync and streaming)
|
|
140
|
+
- Multimodal input (images, files, audio)
|
|
141
|
+
- Structured output (JSON schema)
|
|
142
|
+
- Tool/function calling
|
|
143
|
+
- Embeddings
|
|
144
|
+
|
|
145
|
+
Not all models support all capabilities. Use `supports_capability()` to check.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
@abstractmethod
|
|
150
|
+
def model_name(self) -> str:
|
|
151
|
+
"""Return the model name/identifier."""
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
@abstractmethod
|
|
156
|
+
def capabilities(self) -> ModelCapability:
|
|
157
|
+
"""Return the capabilities supported by this model."""
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
def supports_capability(self, capability: ModelCapability) -> bool:
|
|
161
|
+
"""Check if this model supports a specific capability."""
|
|
162
|
+
return bool(self.capabilities & capability)
|
|
163
|
+
|
|
164
|
+
@abstractmethod
|
|
165
|
+
async def generate(self, request: GenerateRequest) -> GenerateResponse:
|
|
166
|
+
"""
|
|
167
|
+
Generate a response for the given request.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
request: The generation request with messages, tools, etc.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
The generated response with content and/or tool calls.
|
|
174
|
+
|
|
175
|
+
Raises:
|
|
176
|
+
NotImplementedError: If the model doesn't support text generation.
|
|
177
|
+
ValueError: If request contains unsupported features.
|
|
178
|
+
"""
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
@abstractmethod
|
|
182
|
+
async def generate_stream(
|
|
183
|
+
self, request: GenerateRequest
|
|
184
|
+
) -> AsyncIterator[StreamChunk]:
|
|
185
|
+
"""
|
|
186
|
+
Generate a streaming response for the given request.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
request: The generation request with messages, tools, etc.
|
|
190
|
+
|
|
191
|
+
Yields:
|
|
192
|
+
Chunks of the generated response.
|
|
193
|
+
|
|
194
|
+
Raises:
|
|
195
|
+
NotImplementedError: If the model doesn't support streaming.
|
|
196
|
+
ValueError: If request contains unsupported features.
|
|
197
|
+
"""
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
async def embed(self, request: EmbeddingRequest) -> EmbeddingResponse:
|
|
201
|
+
"""
|
|
202
|
+
Generate embeddings for the given input.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
request: The embedding request with input text(s).
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
The embedding vectors.
|
|
209
|
+
|
|
210
|
+
Raises:
|
|
211
|
+
NotImplementedError: If the model doesn't support embeddings.
|
|
212
|
+
"""
|
|
213
|
+
raise NotImplementedError(
|
|
214
|
+
f"Model {self.model_name} does not support embeddings"
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
async def validate_request(self, request: GenerateRequest) -> None:
|
|
218
|
+
"""
|
|
219
|
+
Validate that the request is compatible with this model's capabilities.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
request: The generation request to validate.
|
|
223
|
+
|
|
224
|
+
Raises:
|
|
225
|
+
ValueError: If request contains unsupported features.
|
|
226
|
+
"""
|
|
227
|
+
# Check multimodal input
|
|
228
|
+
has_multimodal = any(isinstance(msg.content, list) for msg in request.messages)
|
|
229
|
+
if has_multimodal and not self.supports_capability(
|
|
230
|
+
ModelCapability.MULTIMODAL_INPUT
|
|
231
|
+
):
|
|
232
|
+
raise ValueError(
|
|
233
|
+
f"Model {self.model_name} does not support multimodal input"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Check tool calling
|
|
237
|
+
if request.tools and not self.supports_capability(ModelCapability.TOOL_CALLING):
|
|
238
|
+
raise ValueError(f"Model {self.model_name} does not support tool calling")
|
|
239
|
+
|
|
240
|
+
# Check structured output
|
|
241
|
+
if request.response_format and not self.supports_capability(
|
|
242
|
+
ModelCapability.STRUCTURED_OUTPUT
|
|
243
|
+
):
|
|
244
|
+
raise ValueError(
|
|
245
|
+
f"Model {self.model_name} does not support structured output"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# Check streaming
|
|
249
|
+
if request.stream and not self.supports_capability(ModelCapability.STREAMING):
|
|
250
|
+
raise ValueError(f"Model {self.model_name} does not support streaming")
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# Update forward references for Pydantic models
|
|
254
|
+
Message.model_rebuild()
|
|
255
|
+
ToolCall.model_rebuild()
|
|
256
|
+
Tool.model_rebuild()
|