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,478 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import base64
|
|
3
|
+
from typing import AsyncIterator
|
|
4
|
+
|
|
5
|
+
import google.genai as genai
|
|
6
|
+
from google.genai.types import Blob, Content, FunctionDeclaration, Part
|
|
7
|
+
from google.genai.types import Tool as GeminiTool
|
|
8
|
+
from google.oauth2 import service_account
|
|
9
|
+
|
|
10
|
+
from .model_abstract import (
|
|
11
|
+
ContentType,
|
|
12
|
+
EmbeddingRequest,
|
|
13
|
+
EmbeddingResponse,
|
|
14
|
+
FunctionCall,
|
|
15
|
+
GenerateRequest,
|
|
16
|
+
GenerateResponse,
|
|
17
|
+
LLMModelAbstract,
|
|
18
|
+
Message,
|
|
19
|
+
ModelCapability,
|
|
20
|
+
StreamChunk,
|
|
21
|
+
Tool,
|
|
22
|
+
ToolCall,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class VertexAIModel(LLMModelAbstract):
|
|
27
|
+
"""
|
|
28
|
+
Vertex AI model implementation using google-genai SDK.
|
|
29
|
+
|
|
30
|
+
Supports all models available on Vertex AI:
|
|
31
|
+
- Gemini models (gemini-1.5-pro, gemini-1.5-flash, gemini-2.0-flash-exp)
|
|
32
|
+
- Claude models via Vertex AI (claude-3-5-sonnet-v2@20241022, etc.)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
project_id: str,
|
|
38
|
+
model_name: str = "gemini-2.5-flash",
|
|
39
|
+
location: str = "us-central1",
|
|
40
|
+
credentials: dict | None = None,
|
|
41
|
+
):
|
|
42
|
+
"""
|
|
43
|
+
Initialize Vertex AI model via google-genai SDK.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
model_name: Model identifier (e.g., "gemini-2.0-flash-exp", "claude-3-5-sonnet-v2@20241022")
|
|
47
|
+
project_id: GCP project ID
|
|
48
|
+
location: GCP location (us-central1 for Gemini, us-east5 for Claude)
|
|
49
|
+
credentials: Optional service account credentials dict
|
|
50
|
+
"""
|
|
51
|
+
self._model_name = model_name
|
|
52
|
+
self._project_id = project_id
|
|
53
|
+
self._location = location
|
|
54
|
+
|
|
55
|
+
# Initialize client with Vertex AI
|
|
56
|
+
client_kwargs = {
|
|
57
|
+
"vertexai": True,
|
|
58
|
+
"project": project_id,
|
|
59
|
+
"location": location,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Add credentials if provided
|
|
63
|
+
if credentials:
|
|
64
|
+
creds = service_account.Credentials.from_service_account_info(
|
|
65
|
+
credentials, scopes=["https://www.googleapis.com/auth/cloud-platform"]
|
|
66
|
+
)
|
|
67
|
+
client_kwargs["credentials"] = creds
|
|
68
|
+
|
|
69
|
+
self.client = genai.Client(**client_kwargs)
|
|
70
|
+
self._capabilities = self._determine_capabilities()
|
|
71
|
+
|
|
72
|
+
def _determine_capabilities(self) -> ModelCapability:
|
|
73
|
+
"""Determine capabilities based on model name."""
|
|
74
|
+
caps = (
|
|
75
|
+
ModelCapability.TEXT_GENERATION
|
|
76
|
+
| ModelCapability.STREAMING
|
|
77
|
+
| ModelCapability.STRUCTURED_OUTPUT
|
|
78
|
+
| ModelCapability.TOOL_CALLING
|
|
79
|
+
| ModelCapability.VISION
|
|
80
|
+
| ModelCapability.MULTIMODAL_INPUT
|
|
81
|
+
| ModelCapability.AUDIO_INPUT
|
|
82
|
+
)
|
|
83
|
+
return caps
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def model_name(self) -> str:
|
|
87
|
+
return self._model_name
|
|
88
|
+
|
|
89
|
+
@model_name.setter
|
|
90
|
+
def model_name(self, value: str):
|
|
91
|
+
self._model_name = value
|
|
92
|
+
self._capabilities = self._determine_capabilities()
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def capabilities(self) -> ModelCapability:
|
|
96
|
+
return self._capabilities
|
|
97
|
+
|
|
98
|
+
def _convert_message(self, msg: Message) -> Content:
|
|
99
|
+
"""Convert internal Message to Vertex AI Content format."""
|
|
100
|
+
parts = []
|
|
101
|
+
|
|
102
|
+
if isinstance(msg.content, str):
|
|
103
|
+
parts.append(Part(text=msg.content))
|
|
104
|
+
else:
|
|
105
|
+
# Multimodal content
|
|
106
|
+
for part in msg.content:
|
|
107
|
+
if part.type == ContentType.TEXT:
|
|
108
|
+
parts.append(Part(text=part.content))
|
|
109
|
+
elif part.type == ContentType.IMAGE_URL:
|
|
110
|
+
# For URLs, we'd need to fetch and convert to inline data
|
|
111
|
+
parts.append(
|
|
112
|
+
Part(
|
|
113
|
+
inline_data=Blob(
|
|
114
|
+
mime_type=part.mime_type or "image/jpeg",
|
|
115
|
+
data=part.content.encode(),
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
elif part.type == ContentType.IMAGE_BASE64:
|
|
120
|
+
# part.content is base64 string; Vertex needs raw bytes
|
|
121
|
+
raw = base64.b64decode(part.content, validate=True)
|
|
122
|
+
parts.append(
|
|
123
|
+
Part(
|
|
124
|
+
inline_data=Blob(
|
|
125
|
+
mime_type=part.mime_type or "image/png",
|
|
126
|
+
data=raw,
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
elif part.type == ContentType.AUDIO_BASE64:
|
|
131
|
+
raw = base64.b64decode(part.content, validate=True)
|
|
132
|
+
parts.append(
|
|
133
|
+
Part(
|
|
134
|
+
inline_data=Blob(
|
|
135
|
+
mime_type=part.mime_type or "audio/wav",
|
|
136
|
+
data=raw,
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
elif part.type == ContentType.FILE_BASE64:
|
|
141
|
+
raw = base64.b64decode(part.content, validate=True)
|
|
142
|
+
parts.append(
|
|
143
|
+
Part(
|
|
144
|
+
inline_data=Blob(
|
|
145
|
+
mime_type=part.mime_type or "application/octet-stream",
|
|
146
|
+
data=raw,
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
return Content(role=msg.role, parts=parts)
|
|
151
|
+
|
|
152
|
+
def _convert_tools(self, tools: list[Tool]) -> list[GeminiTool]:
|
|
153
|
+
"""Convert internal Tool definitions to Vertex AI format."""
|
|
154
|
+
function_declarations = []
|
|
155
|
+
for tool in tools:
|
|
156
|
+
func_def = tool.function
|
|
157
|
+
# Clean schema: remove $ref and $defs (Vertex AI doesn't support them)
|
|
158
|
+
parameters = self._clean_json_schema(func_def.parameters)
|
|
159
|
+
|
|
160
|
+
function_declarations.append(
|
|
161
|
+
FunctionDeclaration(
|
|
162
|
+
name=func_def.name,
|
|
163
|
+
description=func_def.description,
|
|
164
|
+
parameters=parameters,
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
return [GeminiTool(function_declarations=function_declarations)]
|
|
169
|
+
|
|
170
|
+
def _clean_json_schema(self, schema: dict) -> dict:
|
|
171
|
+
"""
|
|
172
|
+
Remove $ref and $defs from JSON Schema as Vertex AI doesn't support them.
|
|
173
|
+
"""
|
|
174
|
+
if not isinstance(schema, dict):
|
|
175
|
+
return schema
|
|
176
|
+
|
|
177
|
+
cleaned = {}
|
|
178
|
+
for key, value in schema.items():
|
|
179
|
+
if key in ("$ref", "$defs", "definitions"):
|
|
180
|
+
continue
|
|
181
|
+
if isinstance(value, dict):
|
|
182
|
+
cleaned[key] = self._clean_json_schema(value)
|
|
183
|
+
elif isinstance(value, list):
|
|
184
|
+
cleaned[key] = [
|
|
185
|
+
self._clean_json_schema(item) if isinstance(item, dict) else item
|
|
186
|
+
for item in value
|
|
187
|
+
]
|
|
188
|
+
else:
|
|
189
|
+
cleaned[key] = value
|
|
190
|
+
|
|
191
|
+
return cleaned
|
|
192
|
+
|
|
193
|
+
async def generate(self, request: GenerateRequest) -> GenerateResponse:
|
|
194
|
+
"""Generate a response using Vertex AI."""
|
|
195
|
+
await self.validate_request(request)
|
|
196
|
+
|
|
197
|
+
# Separate system message from conversation
|
|
198
|
+
system_instruction = None
|
|
199
|
+
messages = []
|
|
200
|
+
for msg in request.messages:
|
|
201
|
+
if msg.role == "system":
|
|
202
|
+
system_instruction = msg.content if isinstance(msg.content, str) else ""
|
|
203
|
+
else:
|
|
204
|
+
messages.append(self._convert_message(msg))
|
|
205
|
+
|
|
206
|
+
config_kwargs = {}
|
|
207
|
+
if request.temperature is not None:
|
|
208
|
+
config_kwargs["temperature"] = request.temperature
|
|
209
|
+
if request.max_tokens is not None:
|
|
210
|
+
config_kwargs["max_output_tokens"] = request.max_tokens
|
|
211
|
+
if request.top_p is not None:
|
|
212
|
+
config_kwargs["top_p"] = request.top_p
|
|
213
|
+
if request.stop:
|
|
214
|
+
config_kwargs["stop_sequences"] = request.stop
|
|
215
|
+
if request.response_format:
|
|
216
|
+
# Vertex AI uses response_mime_type and response_schema
|
|
217
|
+
config_kwargs["response_mime_type"] = "application/json"
|
|
218
|
+
if "schema" in request.response_format:
|
|
219
|
+
config_kwargs["response_schema"] = self._clean_json_schema(
|
|
220
|
+
request.response_format["schema"]
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Build config object
|
|
224
|
+
config = (
|
|
225
|
+
genai.types.GenerateContentConfig(**config_kwargs)
|
|
226
|
+
if config_kwargs
|
|
227
|
+
else None
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Add tools to config if present
|
|
231
|
+
if request.tools:
|
|
232
|
+
if config is None:
|
|
233
|
+
config = genai.types.GenerateContentConfig()
|
|
234
|
+
config.tools = self._convert_tools(request.tools)
|
|
235
|
+
|
|
236
|
+
# Add system instruction to config if present
|
|
237
|
+
if system_instruction:
|
|
238
|
+
if config is None:
|
|
239
|
+
config = genai.types.GenerateContentConfig()
|
|
240
|
+
config.system_instruction = system_instruction
|
|
241
|
+
|
|
242
|
+
response = await self.client.aio.models.generate_content(
|
|
243
|
+
model=self._model_name,
|
|
244
|
+
contents=messages,
|
|
245
|
+
config=config,
|
|
246
|
+
)
|
|
247
|
+
# Extract content
|
|
248
|
+
content = None
|
|
249
|
+
if response.text:
|
|
250
|
+
content = response.text
|
|
251
|
+
|
|
252
|
+
# Extract tool calls
|
|
253
|
+
tool_calls = None
|
|
254
|
+
if response.candidates and response.candidates[0].content.parts:
|
|
255
|
+
function_calls = []
|
|
256
|
+
for part in response.candidates[0].content.parts:
|
|
257
|
+
if not hasattr(part, "function_call") or not part.function_call:
|
|
258
|
+
continue
|
|
259
|
+
fc = part.function_call
|
|
260
|
+
args_dict = dict(fc.args) if fc.args else {}
|
|
261
|
+
function_calls.append(
|
|
262
|
+
ToolCall(
|
|
263
|
+
id=fc.name,
|
|
264
|
+
type="function",
|
|
265
|
+
function=FunctionCall(
|
|
266
|
+
name=fc.name,
|
|
267
|
+
arguments=json.dumps(args_dict),
|
|
268
|
+
),
|
|
269
|
+
)
|
|
270
|
+
)
|
|
271
|
+
if function_calls:
|
|
272
|
+
tool_calls = function_calls
|
|
273
|
+
|
|
274
|
+
# Extract finish reason
|
|
275
|
+
finish_reason = None
|
|
276
|
+
if response.candidates:
|
|
277
|
+
finish_reason = str(response.candidates[0].finish_reason)
|
|
278
|
+
|
|
279
|
+
# Extract usage
|
|
280
|
+
usage = None
|
|
281
|
+
if response.usage_metadata:
|
|
282
|
+
usage = {
|
|
283
|
+
"prompt_tokens": response.usage_metadata.prompt_token_count,
|
|
284
|
+
"completion_tokens": response.usage_metadata.candidates_token_count,
|
|
285
|
+
"total_tokens": response.usage_metadata.total_token_count,
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return GenerateResponse(
|
|
289
|
+
content=content,
|
|
290
|
+
tool_calls=tool_calls,
|
|
291
|
+
finish_reason=finish_reason,
|
|
292
|
+
usage=usage,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
async def generate_stream(
|
|
296
|
+
self, request: GenerateRequest
|
|
297
|
+
) -> AsyncIterator[StreamChunk]:
|
|
298
|
+
"""Generate a streaming response using Vertex AI."""
|
|
299
|
+
await self.validate_request(request)
|
|
300
|
+
|
|
301
|
+
# Separate system message from conversation
|
|
302
|
+
system_instruction = None
|
|
303
|
+
messages = []
|
|
304
|
+
for msg in request.messages:
|
|
305
|
+
if msg.role == "system":
|
|
306
|
+
system_instruction = msg.content if isinstance(msg.content, str) else ""
|
|
307
|
+
else:
|
|
308
|
+
messages.append(self._convert_message(msg))
|
|
309
|
+
|
|
310
|
+
config_kwargs = {}
|
|
311
|
+
if request.temperature is not None:
|
|
312
|
+
config_kwargs["temperature"] = request.temperature
|
|
313
|
+
if request.max_tokens is not None:
|
|
314
|
+
config_kwargs["max_output_tokens"] = request.max_tokens
|
|
315
|
+
if request.top_p is not None:
|
|
316
|
+
config_kwargs["top_p"] = request.top_p
|
|
317
|
+
if request.stop:
|
|
318
|
+
config_kwargs["stop_sequences"] = request.stop
|
|
319
|
+
if request.response_format:
|
|
320
|
+
config_kwargs["response_mime_type"] = "application/json"
|
|
321
|
+
if "schema" in request.response_format:
|
|
322
|
+
config_kwargs["response_schema"] = self._clean_json_schema(
|
|
323
|
+
request.response_format["schema"]
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# Build config object
|
|
327
|
+
config = (
|
|
328
|
+
genai.types.GenerateContentConfig(**config_kwargs)
|
|
329
|
+
if config_kwargs
|
|
330
|
+
else None
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
# Add tools to config if present
|
|
334
|
+
if request.tools:
|
|
335
|
+
if config is None:
|
|
336
|
+
config = genai.types.GenerateContentConfig()
|
|
337
|
+
config.tools = self._convert_tools(request.tools)
|
|
338
|
+
|
|
339
|
+
# Add system instruction to config if present
|
|
340
|
+
if system_instruction:
|
|
341
|
+
if config is None:
|
|
342
|
+
config = genai.types.GenerateContentConfig()
|
|
343
|
+
config.system_instruction = system_instruction
|
|
344
|
+
|
|
345
|
+
model_name = self._model_name
|
|
346
|
+
stream = await self.client.aio.models.generate_content_stream(
|
|
347
|
+
model=model_name,
|
|
348
|
+
contents=messages,
|
|
349
|
+
config=config,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
async for chunk in stream:
|
|
353
|
+
content = None
|
|
354
|
+
if chunk.text:
|
|
355
|
+
content = chunk.text
|
|
356
|
+
|
|
357
|
+
# Extract tool calls from chunk
|
|
358
|
+
tool_calls = None
|
|
359
|
+
if chunk.candidates and chunk.candidates[0].content.parts:
|
|
360
|
+
function_calls = []
|
|
361
|
+
for part in chunk.candidates[0].content.parts:
|
|
362
|
+
if not hasattr(part, "function_call") or not part.function_call:
|
|
363
|
+
continue
|
|
364
|
+
fc = part.function_call
|
|
365
|
+
args_dict = dict(fc.args) if fc.args else {}
|
|
366
|
+
function_calls.append(
|
|
367
|
+
ToolCall(
|
|
368
|
+
id=fc.name,
|
|
369
|
+
type="function",
|
|
370
|
+
function=FunctionCall(
|
|
371
|
+
name=fc.name,
|
|
372
|
+
arguments=json.dumps(args_dict),
|
|
373
|
+
),
|
|
374
|
+
)
|
|
375
|
+
)
|
|
376
|
+
if function_calls:
|
|
377
|
+
tool_calls = function_calls
|
|
378
|
+
|
|
379
|
+
finish_reason = None
|
|
380
|
+
if chunk.candidates:
|
|
381
|
+
finish_reason = str(chunk.candidates[0].finish_reason)
|
|
382
|
+
|
|
383
|
+
if content or tool_calls or finish_reason:
|
|
384
|
+
yield StreamChunk(
|
|
385
|
+
content=content,
|
|
386
|
+
tool_calls=tool_calls,
|
|
387
|
+
finish_reason=finish_reason,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
class VertexEmbeddingModel(LLMModelAbstract):
|
|
392
|
+
"""
|
|
393
|
+
Vertex AI embedding model using google-genai SDK with advanced features.
|
|
394
|
+
"""
|
|
395
|
+
|
|
396
|
+
def __init__(
|
|
397
|
+
self,
|
|
398
|
+
project_id: str,
|
|
399
|
+
model_name: str = "text-multilingual-embedding-002",
|
|
400
|
+
location: str = "us-central1",
|
|
401
|
+
credentials: dict | None = None,
|
|
402
|
+
output_dimensionality: int | None = None,
|
|
403
|
+
batch_size: int = 100,
|
|
404
|
+
task_type: str = "RETRIEVAL_DOCUMENT",
|
|
405
|
+
):
|
|
406
|
+
self._model_name = model_name
|
|
407
|
+
self._project_id = project_id
|
|
408
|
+
self._location = location
|
|
409
|
+
self._output_dimensionality = output_dimensionality
|
|
410
|
+
self._batch_size = batch_size
|
|
411
|
+
self._task_type = task_type
|
|
412
|
+
|
|
413
|
+
client_kwargs = {
|
|
414
|
+
"vertexai": True,
|
|
415
|
+
"project": project_id,
|
|
416
|
+
"location": location,
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if credentials:
|
|
420
|
+
creds = service_account.Credentials.from_service_account_info(
|
|
421
|
+
credentials, scopes=["https://www.googleapis.com/auth/cloud-platform"]
|
|
422
|
+
)
|
|
423
|
+
client_kwargs["credentials"] = creds
|
|
424
|
+
|
|
425
|
+
self.client = genai.Client(**client_kwargs)
|
|
426
|
+
|
|
427
|
+
@property
|
|
428
|
+
def model_name(self) -> str:
|
|
429
|
+
return self._model_name
|
|
430
|
+
|
|
431
|
+
@property
|
|
432
|
+
def capabilities(self) -> ModelCapability:
|
|
433
|
+
return ModelCapability.EMBEDDINGS
|
|
434
|
+
|
|
435
|
+
async def generate(self, request: GenerateRequest) -> GenerateResponse:
|
|
436
|
+
raise NotImplementedError("Embedding models do not support text generation")
|
|
437
|
+
|
|
438
|
+
async def generate_stream(
|
|
439
|
+
self, request: GenerateRequest
|
|
440
|
+
) -> AsyncIterator[StreamChunk]:
|
|
441
|
+
raise NotImplementedError("Embedding models do not support text generation")
|
|
442
|
+
|
|
443
|
+
async def embed(self, request: EmbeddingRequest) -> EmbeddingResponse:
|
|
444
|
+
inputs = [request.input] if isinstance(request.input, str) else request.input
|
|
445
|
+
|
|
446
|
+
all_embeddings: list[list[float]] = []
|
|
447
|
+
|
|
448
|
+
for i in range(0, len(inputs), self._batch_size):
|
|
449
|
+
batch = inputs[i : i + self._batch_size]
|
|
450
|
+
|
|
451
|
+
config_kwargs = {}
|
|
452
|
+
if self._output_dimensionality:
|
|
453
|
+
config_kwargs["output_dimensionality"] = self._output_dimensionality
|
|
454
|
+
if self._task_type:
|
|
455
|
+
config_kwargs["task_type"] = self._task_type
|
|
456
|
+
|
|
457
|
+
config = (
|
|
458
|
+
genai.types.EmbedContentConfig(**config_kwargs)
|
|
459
|
+
if config_kwargs
|
|
460
|
+
else None
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
try:
|
|
464
|
+
response = await self.client.aio.models.embed_content(
|
|
465
|
+
model=self._model_name,
|
|
466
|
+
contents=batch,
|
|
467
|
+
config=config,
|
|
468
|
+
)
|
|
469
|
+
except Exception as e:
|
|
470
|
+
raise Exception(f"Failed to embed batch: {e}")
|
|
471
|
+
|
|
472
|
+
embeddings = [emb.values for emb in response.embeddings]
|
|
473
|
+
all_embeddings.extend(embeddings)
|
|
474
|
+
|
|
475
|
+
return EmbeddingResponse(
|
|
476
|
+
embeddings=all_embeddings,
|
|
477
|
+
usage=None,
|
|
478
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: donkit-llm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unified LLM model implementations for Donkit (OpenAI, Azure OpenAI, Claude, Vertex AI)
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Donkit AI
|
|
7
|
+
Author-email: opensource@donkit.ai
|
|
8
|
+
Requires-Python: >=3.12,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Requires-Dist: anthropic[vertex] (>=0.42.0,<0.43.0)
|
|
14
|
+
Requires-Dist: google-auth (>=2.0.0,<3.0.0)
|
|
15
|
+
Requires-Dist: google-genai (>=1.38.0,<2.0.0)
|
|
16
|
+
Requires-Dist: openai (>=2.1.0,<3.0.0)
|
|
17
|
+
Requires-Dist: pydantic (>=2.8.0,<3.0.0)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
donkit/llm/__init__.py,sha256=VLnsTe_cB_FkKQbutGkEGVXOGevbrfgICRjqTXq4nsA,1193
|
|
2
|
+
donkit/llm/claude_model.py,sha256=RCvqaFSIaWJvcmW7y706Kc8l6YKdP0xR3chYRR-otV8,16589
|
|
3
|
+
donkit/llm/factory.py,sha256=PRRIY3sMOxgBTX-5Ecci62TWW9k9MP7lyhHyoo4dZeM,5676
|
|
4
|
+
donkit/llm/model_abstract.py,sha256=-v_hDMzKS6_sZ3kWY6WUIP1LoAvXQvn7YZC9Hy4lWWo,7861
|
|
5
|
+
donkit/llm/openai_model.py,sha256=nGTPjzsldILsFG6GlU5ZNwsTfaWT9rsApvpNpxLhbAc,19395
|
|
6
|
+
donkit/llm/vertex_model.py,sha256=z14briPYZt3qmxJZdJYvPKyGSHZxLdNKiF1N2_FNoZc,17066
|
|
7
|
+
donkit_llm-0.1.0.dist-info/METADATA,sha256=rIuOQ0ZALJXKIxqMbqA0_lJn2puEaHfV5TXnegO-CaY,668
|
|
8
|
+
donkit_llm-0.1.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
9
|
+
donkit_llm-0.1.0.dist-info/RECORD,,
|