donkit-llm 0.1.1__py3-none-any.whl → 0.1.2__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 +5 -0
- donkit/llm/claude_model.py +7 -5
- donkit/llm/donkit_model.py +238 -0
- donkit/llm/factory.py +105 -14
- donkit/llm/gemini_model.py +406 -0
- donkit/llm/model_abstract.py +27 -17
- donkit/llm/ollama_integration.py +442 -0
- donkit/llm/openai_model.py +179 -92
- donkit/llm/vertex_model.py +446 -178
- {donkit_llm-0.1.1.dist-info → donkit_llm-0.1.2.dist-info}/METADATA +3 -2
- donkit_llm-0.1.2.dist-info/RECORD +12 -0
- {donkit_llm-0.1.1.dist-info → donkit_llm-0.1.2.dist-info}/WHEEL +1 -1
- donkit_llm-0.1.1.dist-info/RECORD +0 -9
donkit/llm/__init__.py
CHANGED
|
@@ -23,6 +23,8 @@ from .openai_model import (
|
|
|
23
23
|
from .claude_model import ClaudeModel, ClaudeVertexModel
|
|
24
24
|
from .vertex_model import VertexAIModel, VertexEmbeddingModel
|
|
25
25
|
from .factory import ModelFactory
|
|
26
|
+
from .gemini_model import GeminiModel, GeminiEmbeddingModel
|
|
27
|
+
from .donkit_model import DonkitModel
|
|
26
28
|
|
|
27
29
|
__all__ = [
|
|
28
30
|
"ModelFactory",
|
|
@@ -52,4 +54,7 @@ __all__ = [
|
|
|
52
54
|
"ClaudeVertexModel",
|
|
53
55
|
"VertexAIModel",
|
|
54
56
|
"VertexEmbeddingModel",
|
|
57
|
+
"GeminiModel",
|
|
58
|
+
"GeminiEmbeddingModel",
|
|
59
|
+
"DonkitModel",
|
|
55
60
|
]
|
donkit/llm/claude_model.py
CHANGED
|
@@ -20,6 +20,8 @@ from .model_abstract import (
|
|
|
20
20
|
class ClaudeModel(LLMModelAbstract):
|
|
21
21
|
"""Anthropic Claude model implementation."""
|
|
22
22
|
|
|
23
|
+
name = "claude"
|
|
24
|
+
|
|
23
25
|
def __init__(
|
|
24
26
|
self,
|
|
25
27
|
model_name: str,
|
|
@@ -82,9 +84,9 @@ class ClaudeModel(LLMModelAbstract):
|
|
|
82
84
|
# Multimodal content
|
|
83
85
|
content_parts = []
|
|
84
86
|
for part in msg.content:
|
|
85
|
-
if part.
|
|
87
|
+
if part.content_type == ContentType.TEXT:
|
|
86
88
|
content_parts.append({"type": "text", "text": part.content})
|
|
87
|
-
elif part.
|
|
89
|
+
elif part.content_type == ContentType.IMAGE_URL:
|
|
88
90
|
# Claude expects base64 images, not URLs
|
|
89
91
|
content_parts.append(
|
|
90
92
|
{
|
|
@@ -95,7 +97,7 @@ class ClaudeModel(LLMModelAbstract):
|
|
|
95
97
|
},
|
|
96
98
|
}
|
|
97
99
|
)
|
|
98
|
-
elif part.
|
|
100
|
+
elif part.content_type == ContentType.IMAGE_BASE64:
|
|
99
101
|
content_parts.append(
|
|
100
102
|
{
|
|
101
103
|
"type": "image",
|
|
@@ -319,9 +321,9 @@ class ClaudeVertexModel(LLMModelAbstract):
|
|
|
319
321
|
# Multimodal content
|
|
320
322
|
content_parts = []
|
|
321
323
|
for part in msg.content:
|
|
322
|
-
if part.
|
|
324
|
+
if part.content_type == ContentType.TEXT:
|
|
323
325
|
content_parts.append({"type": "text", "text": part.content})
|
|
324
|
-
elif part.
|
|
326
|
+
elif part.content_type == ContentType.IMAGE_BASE64:
|
|
325
327
|
content_parts.append(
|
|
326
328
|
{
|
|
327
329
|
"type": "image",
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
from typing import Any, AsyncIterator
|
|
2
|
+
|
|
3
|
+
from donkit.ragops_api_gateway_client.client import RagopsAPIGatewayClient
|
|
4
|
+
from .model_abstract import (
|
|
5
|
+
EmbeddingRequest,
|
|
6
|
+
EmbeddingResponse,
|
|
7
|
+
FunctionCall,
|
|
8
|
+
GenerateRequest,
|
|
9
|
+
GenerateResponse,
|
|
10
|
+
LLMModelAbstract,
|
|
11
|
+
Message,
|
|
12
|
+
ModelCapability,
|
|
13
|
+
StreamChunk,
|
|
14
|
+
Tool,
|
|
15
|
+
ToolCall,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DonkitModel(LLMModelAbstract):
|
|
20
|
+
"""
|
|
21
|
+
Implementation of LLMModelAbstract that proxies requests via RagopsAPIGatewayClient.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
name = "donkit"
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
base_url: str,
|
|
29
|
+
api_token: str,
|
|
30
|
+
provider: str,
|
|
31
|
+
model_name: str | None = None,
|
|
32
|
+
project_id: str | None = None,
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
Initialize DonkitModel.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
base_url: Base URL for the API Gateway
|
|
39
|
+
api_token: API token for authentication
|
|
40
|
+
provider: The LLM provider name (e.g., "openai", "anthropic", "vertex")
|
|
41
|
+
model_name: The specific model identifier (e.g., "gpt-4o", "claude-3-opus")
|
|
42
|
+
project_id: The project ID for the gateway
|
|
43
|
+
"""
|
|
44
|
+
self.base_url = base_url
|
|
45
|
+
self.api_token = api_token
|
|
46
|
+
self.provider = provider
|
|
47
|
+
self._model_name = model_name
|
|
48
|
+
self.project_id = project_id
|
|
49
|
+
self._capabilities = self._determine_capabilities()
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def model_name(self) -> str:
|
|
53
|
+
return self._model_name
|
|
54
|
+
|
|
55
|
+
@model_name.setter
|
|
56
|
+
def model_name(self, value: str):
|
|
57
|
+
self._model_name = value
|
|
58
|
+
self._capabilities = self._determine_capabilities()
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def capabilities(self) -> ModelCapability:
|
|
62
|
+
return self._capabilities
|
|
63
|
+
|
|
64
|
+
def _determine_capabilities(self) -> ModelCapability:
|
|
65
|
+
"""
|
|
66
|
+
Estimate capabilities based on model name.
|
|
67
|
+
Since this is a proxy, we assume modern defaults but refine based on keywords.
|
|
68
|
+
"""
|
|
69
|
+
caps = (
|
|
70
|
+
ModelCapability.TEXT_GENERATION
|
|
71
|
+
| ModelCapability.STREAMING
|
|
72
|
+
| ModelCapability.STRUCTURED_OUTPUT
|
|
73
|
+
| ModelCapability.TOOL_CALLING
|
|
74
|
+
| ModelCapability.MULTIMODAL_INPUT
|
|
75
|
+
| ModelCapability.EMBEDDINGS
|
|
76
|
+
)
|
|
77
|
+
return caps
|
|
78
|
+
|
|
79
|
+
def _convert_message(self, msg: Message) -> dict:
|
|
80
|
+
"""Convert internal Message to dictionary format expected by the Gateway."""
|
|
81
|
+
result: dict[str, Any] = {"role": msg.role}
|
|
82
|
+
if isinstance(msg.content, str):
|
|
83
|
+
result["content"] = msg.content
|
|
84
|
+
else:
|
|
85
|
+
# Multimodal content processing
|
|
86
|
+
content_parts = []
|
|
87
|
+
for part in msg.content if msg.content else []:
|
|
88
|
+
content_parts.append(part.model_dump(exclude_none=True))
|
|
89
|
+
result["content"] = content_parts
|
|
90
|
+
if msg.tool_calls:
|
|
91
|
+
result["tool_calls"] = [tc.model_dump() for tc in msg.tool_calls]
|
|
92
|
+
if msg.tool_call_id:
|
|
93
|
+
result["tool_call_id"] = msg.tool_call_id
|
|
94
|
+
if msg.name:
|
|
95
|
+
result["name"] = msg.name
|
|
96
|
+
|
|
97
|
+
return result
|
|
98
|
+
|
|
99
|
+
def _convert_tools(self, tools: list[Tool]) -> list[dict]:
|
|
100
|
+
"""Convert internal Tool definitions to Gateway dictionary format."""
|
|
101
|
+
return [tool.model_dump(exclude_none=True) for tool in tools]
|
|
102
|
+
|
|
103
|
+
def _prepare_generate_kwargs(self, request: GenerateRequest) -> dict:
|
|
104
|
+
"""Prepare kwargs for generate/generate_stream calls."""
|
|
105
|
+
messages = [self._convert_message(msg) for msg in request.messages]
|
|
106
|
+
tools_payload = self._convert_tools(request.tools) if request.tools else None
|
|
107
|
+
|
|
108
|
+
kwargs: dict[str, Any] = {
|
|
109
|
+
"provider": self.provider,
|
|
110
|
+
"model_name": self._model_name,
|
|
111
|
+
"messages": messages,
|
|
112
|
+
"project_id": self.project_id,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if request.temperature is not None:
|
|
116
|
+
kwargs["temperature"] = request.temperature
|
|
117
|
+
if request.max_tokens is not None:
|
|
118
|
+
kwargs["max_tokens"] = request.max_tokens
|
|
119
|
+
if request.top_p is not None:
|
|
120
|
+
kwargs["top_p"] = request.top_p
|
|
121
|
+
if request.stop:
|
|
122
|
+
kwargs["stop"] = request.stop
|
|
123
|
+
if tools_payload:
|
|
124
|
+
kwargs["tools"] = tools_payload
|
|
125
|
+
if request.tool_choice:
|
|
126
|
+
if isinstance(request.tool_choice, (str, dict)):
|
|
127
|
+
kwargs["tool_choice"] = request.tool_choice
|
|
128
|
+
else:
|
|
129
|
+
kwargs["tool_choice"] = "auto"
|
|
130
|
+
if request.response_format:
|
|
131
|
+
kwargs["response_format"] = request.response_format
|
|
132
|
+
|
|
133
|
+
return kwargs
|
|
134
|
+
|
|
135
|
+
async def generate(self, request: GenerateRequest) -> GenerateResponse:
|
|
136
|
+
"""Generate a response using RagopsAPIGatewayClient."""
|
|
137
|
+
await self.validate_request(request)
|
|
138
|
+
|
|
139
|
+
kwargs = self._prepare_generate_kwargs(request)
|
|
140
|
+
|
|
141
|
+
async with RagopsAPIGatewayClient(
|
|
142
|
+
base_url=self.base_url,
|
|
143
|
+
api_token=self.api_token,
|
|
144
|
+
) as client:
|
|
145
|
+
response_dict = await client.generate(**kwargs)
|
|
146
|
+
|
|
147
|
+
# Gateway returns simplified format: {content, tool_calls, finish_reason, usage}
|
|
148
|
+
content = response_dict.get("content")
|
|
149
|
+
finish_reason = response_dict.get("finish_reason")
|
|
150
|
+
|
|
151
|
+
# Extract tool calls
|
|
152
|
+
tool_calls = None
|
|
153
|
+
if response_dict.get("tool_calls"):
|
|
154
|
+
tool_calls = [
|
|
155
|
+
ToolCall(
|
|
156
|
+
id=tc.get("id"),
|
|
157
|
+
type=tc.get("type", "function"),
|
|
158
|
+
function=FunctionCall(
|
|
159
|
+
name=tc.get("function", {}).get("name"),
|
|
160
|
+
arguments=tc.get("function", {}).get("arguments"),
|
|
161
|
+
),
|
|
162
|
+
)
|
|
163
|
+
for tc in response_dict["tool_calls"]
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
usage_data = response_dict.get("usage", {})
|
|
167
|
+
|
|
168
|
+
return GenerateResponse(
|
|
169
|
+
content=content,
|
|
170
|
+
tool_calls=tool_calls,
|
|
171
|
+
finish_reason=finish_reason,
|
|
172
|
+
usage={
|
|
173
|
+
"prompt_tokens": usage_data.get("prompt_tokens"),
|
|
174
|
+
"completion_tokens": usage_data.get("completion_tokens"),
|
|
175
|
+
"total_tokens": usage_data.get("total_tokens"),
|
|
176
|
+
}
|
|
177
|
+
if usage_data
|
|
178
|
+
else None,
|
|
179
|
+
metadata=response_dict.get("metadata"),
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
async def generate_stream(
|
|
183
|
+
self, request: GenerateRequest
|
|
184
|
+
) -> AsyncIterator[StreamChunk]:
|
|
185
|
+
"""Generate a streaming response using RagopsAPIGatewayClient."""
|
|
186
|
+
await self.validate_request(request)
|
|
187
|
+
|
|
188
|
+
kwargs = self._prepare_generate_kwargs(request)
|
|
189
|
+
|
|
190
|
+
async with RagopsAPIGatewayClient(
|
|
191
|
+
base_url=self.base_url,
|
|
192
|
+
api_token=self.api_token,
|
|
193
|
+
) as client:
|
|
194
|
+
# Iterate over the stream from client
|
|
195
|
+
async for chunk_dict in client.generate_stream(**kwargs):
|
|
196
|
+
content = chunk_dict.get("content")
|
|
197
|
+
finish_reason = chunk_dict.get("finish_reason")
|
|
198
|
+
|
|
199
|
+
tool_calls = None
|
|
200
|
+
if chunk_dict.get("tool_calls"):
|
|
201
|
+
tool_calls = [
|
|
202
|
+
ToolCall(
|
|
203
|
+
id=tc.get("id", ""),
|
|
204
|
+
type=tc.get("type", "function"),
|
|
205
|
+
function=FunctionCall(
|
|
206
|
+
name=tc.get("function", {}).get("name", ""),
|
|
207
|
+
arguments=tc.get("function", {}).get("arguments", ""),
|
|
208
|
+
),
|
|
209
|
+
)
|
|
210
|
+
for tc in chunk_dict["tool_calls"]
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
yield StreamChunk(
|
|
214
|
+
content=content,
|
|
215
|
+
tool_calls=tool_calls,
|
|
216
|
+
finish_reason=finish_reason,
|
|
217
|
+
metadata=chunk_dict.get("metadata", {}),
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
async def embed(self, request: EmbeddingRequest) -> EmbeddingResponse:
|
|
221
|
+
"""Generate embeddings using RagopsAPIGatewayClient."""
|
|
222
|
+
|
|
223
|
+
kwargs: dict[str, Any] = {
|
|
224
|
+
"provider": self.provider,
|
|
225
|
+
"input": request.input,
|
|
226
|
+
"model_name": self._model_name,
|
|
227
|
+
"project_id": self.project_id,
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if request.dimensions:
|
|
231
|
+
kwargs["dimensions"] = request.dimensions
|
|
232
|
+
async with RagopsAPIGatewayClient(
|
|
233
|
+
base_url=self.base_url,
|
|
234
|
+
api_token=self.api_token,
|
|
235
|
+
) as client:
|
|
236
|
+
response_dict = await client.embeddings(**kwargs)
|
|
237
|
+
|
|
238
|
+
return EmbeddingResponse(**response_dict)
|
donkit/llm/factory.py
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
from typing import Literal
|
|
2
2
|
|
|
3
|
-
from .claude_model import ClaudeModel
|
|
3
|
+
from .claude_model import ClaudeModel
|
|
4
|
+
from .claude_model import ClaudeVertexModel
|
|
5
|
+
from .donkit_model import DonkitModel
|
|
6
|
+
from .gemini_model import GeminiModel
|
|
4
7
|
from .model_abstract import LLMModelAbstract
|
|
5
|
-
from .openai_model import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from .vertex_model import VertexAIModel, VertexEmbeddingModel
|
|
8
|
+
from .openai_model import AzureOpenAIEmbeddingModel
|
|
9
|
+
from .openai_model import AzureOpenAIModel
|
|
10
|
+
from .openai_model import OpenAIEmbeddingModel
|
|
11
|
+
from .openai_model import OpenAIModel
|
|
12
|
+
from .vertex_model import VertexAIModel
|
|
13
|
+
from .vertex_model import VertexEmbeddingModel
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
class ModelFactory:
|
|
@@ -46,7 +48,7 @@ class ModelFactory:
|
|
|
46
48
|
|
|
47
49
|
@staticmethod
|
|
48
50
|
def create_embedding_model(
|
|
49
|
-
provider: Literal["openai", "azure_openai", "vertex"],
|
|
51
|
+
provider: Literal["openai", "azure_openai", "vertex", "custom", "default"],
|
|
50
52
|
model_name: str | None = None,
|
|
51
53
|
api_key: str | None = None,
|
|
52
54
|
**kwargs,
|
|
@@ -92,6 +94,35 @@ class ModelFactory:
|
|
|
92
94
|
base_url=base_url,
|
|
93
95
|
)
|
|
94
96
|
|
|
97
|
+
@staticmethod
|
|
98
|
+
def create_gemini_model(
|
|
99
|
+
model_name: str,
|
|
100
|
+
api_key: str | None = None,
|
|
101
|
+
project_id: str | None = None,
|
|
102
|
+
location: str = "us-central1",
|
|
103
|
+
use_vertex: bool = False,
|
|
104
|
+
) -> GeminiModel:
|
|
105
|
+
"""
|
|
106
|
+
Create a Gemini model instance.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
model_name: Model identifier (e.g., "gemini-2.0-flash-exp")
|
|
110
|
+
api_key: Google AI API key (for AI Studio)
|
|
111
|
+
project_id: GCP project ID (for Vertex AI)
|
|
112
|
+
location: GCP location (for Vertex AI)
|
|
113
|
+
use_vertex: Whether to use Vertex AI instead of AI Studio
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Configured Gemini model instance
|
|
117
|
+
"""
|
|
118
|
+
return GeminiModel(
|
|
119
|
+
model_name=model_name,
|
|
120
|
+
api_key=api_key,
|
|
121
|
+
project_id=project_id,
|
|
122
|
+
location=location,
|
|
123
|
+
use_vertex=use_vertex,
|
|
124
|
+
)
|
|
125
|
+
|
|
95
126
|
@staticmethod
|
|
96
127
|
def create_claude_vertex_model(
|
|
97
128
|
model_name: str,
|
|
@@ -118,14 +149,57 @@ class ModelFactory:
|
|
|
118
149
|
credentials=credentials,
|
|
119
150
|
)
|
|
120
151
|
|
|
152
|
+
@staticmethod
|
|
153
|
+
def create_donkit_model(
|
|
154
|
+
model_name: str,
|
|
155
|
+
api_key: str,
|
|
156
|
+
base_url: str = "http://localhost:9017",
|
|
157
|
+
provider: str = "default",
|
|
158
|
+
) -> DonkitModel:
|
|
159
|
+
"""Create a Donkit model that proxies through RagOps API Gateway.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
model_name: Name of the model to use
|
|
163
|
+
api_key: API key for authentication
|
|
164
|
+
base_url: Base URL of the RagOps API Gateway
|
|
165
|
+
provider: Provider to use (default: vertex)
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
DonkitModel instance
|
|
169
|
+
"""
|
|
170
|
+
return DonkitModel(
|
|
171
|
+
base_url=base_url,
|
|
172
|
+
api_token=api_key,
|
|
173
|
+
provider=provider,
|
|
174
|
+
model_name=model_name,
|
|
175
|
+
)
|
|
176
|
+
|
|
121
177
|
@staticmethod
|
|
122
178
|
def create_model(
|
|
123
179
|
provider: Literal[
|
|
124
|
-
"openai",
|
|
180
|
+
"openai",
|
|
181
|
+
"azure_openai",
|
|
182
|
+
"claude",
|
|
183
|
+
"claude_vertex",
|
|
184
|
+
"vertex",
|
|
185
|
+
"ollama",
|
|
186
|
+
"donkit",
|
|
125
187
|
],
|
|
126
|
-
model_name: str,
|
|
188
|
+
model_name: str | None,
|
|
127
189
|
credentials: dict,
|
|
128
190
|
) -> LLMModelAbstract:
|
|
191
|
+
if model_name is None:
|
|
192
|
+
default_models = {
|
|
193
|
+
"openai": "gpt-5-mini",
|
|
194
|
+
"azure_openai": "gpt-4.1-mini",
|
|
195
|
+
"claude": "claude-4-5-sonnet",
|
|
196
|
+
"claude_vertex": "claude-4-5-sonnet",
|
|
197
|
+
"gemini": "gemini-2.5-flash",
|
|
198
|
+
"vertex": "gemini-2.5-flash",
|
|
199
|
+
"ollama": "mistral",
|
|
200
|
+
"donkit": "default",
|
|
201
|
+
}
|
|
202
|
+
model_name = default_models.get(provider, "default")
|
|
129
203
|
if provider == "openai":
|
|
130
204
|
return ModelFactory.create_openai_model(
|
|
131
205
|
model_name=model_name,
|
|
@@ -136,11 +210,19 @@ class ModelFactory:
|
|
|
136
210
|
elif provider == "azure_openai":
|
|
137
211
|
return ModelFactory.create_azure_openai_model(
|
|
138
212
|
model_name=model_name,
|
|
139
|
-
api_key=credentials
|
|
140
|
-
azure_endpoint=credentials
|
|
213
|
+
api_key=credentials.get("api_key"),
|
|
214
|
+
azure_endpoint=credentials.get("azure_endpoint"),
|
|
141
215
|
api_version=credentials.get("api_version", "2024-08-01-preview"),
|
|
142
216
|
deployment_name=credentials.get("deployment_name"),
|
|
143
217
|
)
|
|
218
|
+
elif provider == "gemini":
|
|
219
|
+
return ModelFactory.create_gemini_model(
|
|
220
|
+
model_name=model_name,
|
|
221
|
+
api_key=credentials.get("api_key"),
|
|
222
|
+
project_id=credentials.get("project_id"),
|
|
223
|
+
location=credentials.get("location", "us-central1"),
|
|
224
|
+
use_vertex=credentials.get("use_vertex", False),
|
|
225
|
+
)
|
|
144
226
|
elif provider == "claude":
|
|
145
227
|
return ModelFactory.create_claude_model(
|
|
146
228
|
model_name=model_name,
|
|
@@ -162,10 +244,19 @@ class ModelFactory:
|
|
|
162
244
|
)
|
|
163
245
|
elif provider == "ollama":
|
|
164
246
|
# Ollama uses OpenAI-compatible API
|
|
247
|
+
ollama_url = credentials.get("ollama_url")
|
|
248
|
+
if "/v1" not in ollama_url:
|
|
249
|
+
ollama_url += "/v1"
|
|
165
250
|
return ModelFactory.create_openai_model(
|
|
166
251
|
model_name=model_name,
|
|
167
252
|
api_key=credentials.get("api_key", "ollama"),
|
|
168
|
-
base_url=
|
|
253
|
+
base_url=ollama_url,
|
|
254
|
+
)
|
|
255
|
+
elif provider == "donkit":
|
|
256
|
+
return ModelFactory.create_donkit_model(
|
|
257
|
+
model_name=model_name,
|
|
258
|
+
api_key=credentials["api_key"],
|
|
259
|
+
base_url=credentials["base_url"],
|
|
169
260
|
)
|
|
170
261
|
else:
|
|
171
262
|
raise ValueError(f"Unknown provider: {provider}")
|