forgeoptimizer 1.0.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.
- forgecli/__init__.py +4 -0
- forgecli/build/__init__.py +135 -0
- forgecli/build/apply.py +218 -0
- forgecli/build/caveman_optimize.py +37 -0
- forgecli/build/diff_extract.py +218 -0
- forgecli/build/llm.py +167 -0
- forgecli/build/optimize.py +37 -0
- forgecli/build/pipeline.py +76 -0
- forgecli/build/retrieval.py +157 -0
- forgecli/build/summarize.py +76 -0
- forgecli/build/test_run.py +75 -0
- forgecli/builder/__init__.py +11 -0
- forgecli/builder/builder.py +53 -0
- forgecli/builder/editor.py +36 -0
- forgecli/builder/formatter.py +31 -0
- forgecli/cli/__init__.py +5 -0
- forgecli/cli/bootstrap.py +281 -0
- forgecli/cli/commands_graph.py +137 -0
- forgecli/cli/commands_wrappers.py +63 -0
- forgecli/cli/main.py +122 -0
- forgecli/cli/ui.py +56 -0
- forgecli/config/__init__.py +28 -0
- forgecli/config/loader.py +85 -0
- forgecli/config/settings.py +204 -0
- forgecli/config/writer.py +90 -0
- forgecli/core/__init__.py +35 -0
- forgecli/core/container.py +68 -0
- forgecli/core/context.py +54 -0
- forgecli/core/credentials.py +114 -0
- forgecli/core/errors.py +27 -0
- forgecli/core/events.py +67 -0
- forgecli/core/logging.py +47 -0
- forgecli/core/models.py +159 -0
- forgecli/core/plugins.py +56 -0
- forgecli/core/service.py +22 -0
- forgecli/docs/__init__.py +5 -0
- forgecli/docs/generator.py +119 -0
- forgecli/engine/__init__.py +105 -0
- forgecli/engine/context.py +171 -0
- forgecli/engine/defaults.py +89 -0
- forgecli/engine/events.py +185 -0
- forgecli/engine/execution.py +526 -0
- forgecli/engine/plugins.py +146 -0
- forgecli/engine/runner.py +155 -0
- forgecli/engine/stages/__init__.py +33 -0
- forgecli/engine/stages/caveman_optimizer.py +47 -0
- forgecli/engine/stages/context_optimizer.py +44 -0
- forgecli/engine/stages/execution_engine_stage.py +108 -0
- forgecli/engine/stages/git_engine.py +30 -0
- forgecli/engine/stages/intent_analyzer.py +38 -0
- forgecli/engine/stages/model_router.py +66 -0
- forgecli/engine/stages/planning_engine.py +45 -0
- forgecli/engine/stages/repository_analyzer.py +54 -0
- forgecli/engine/stages/validation_engine.py +89 -0
- forgecli/git/__init__.py +9 -0
- forgecli/git/repo.py +55 -0
- forgecli/git/service.py +45 -0
- forgecli/graph/__init__.py +56 -0
- forgecli/graph/backend_graphify.py +453 -0
- forgecli/graph/edge.py +19 -0
- forgecli/graph/graph.py +85 -0
- forgecli/graph/graphify.py +412 -0
- forgecli/graph/indexer.py +82 -0
- forgecli/graph/node.py +47 -0
- forgecli/graph/repository.py +164 -0
- forgecli/memory/__init__.py +12 -0
- forgecli/memory/cache.py +61 -0
- forgecli/memory/history.py +138 -0
- forgecli/memory/store.py +87 -0
- forgecli/optimizer/__init__.py +14 -0
- forgecli/optimizer/caveman/__init__.py +173 -0
- forgecli/optimizer/caveman/cli.py +64 -0
- forgecli/optimizer/caveman/decorator.py +67 -0
- forgecli/optimizer/caveman/factory.py +51 -0
- forgecli/optimizer/caveman/ruleset.py +156 -0
- forgecli/optimizer/caveman/state.py +52 -0
- forgecli/optimizer/chunker.py +85 -0
- forgecli/optimizer/optimizer.py +81 -0
- forgecli/optimizer/ponytail/__init__.py +183 -0
- forgecli/optimizer/ponytail/cli.py +168 -0
- forgecli/optimizer/ponytail/decorator.py +70 -0
- forgecli/optimizer/ponytail/factory.py +51 -0
- forgecli/optimizer/ponytail/ruleset.py +168 -0
- forgecli/optimizer/ponytail/state.py +53 -0
- forgecli/optimizer/ranker.py +37 -0
- forgecli/optimizer/summarizer.py +44 -0
- forgecli/orchestrator/__init__.py +707 -0
- forgecli/planner/__init__.py +56 -0
- forgecli/planner/agent.py +61 -0
- forgecli/planner/plan.py +65 -0
- forgecli/planner/planner.py +17 -0
- forgecli/planner/render.py +271 -0
- forgecli/planner/serialize.py +106 -0
- forgecli/planner/software.py +834 -0
- forgecli/platform/__init__.py +91 -0
- forgecli/platform/core.py +201 -0
- forgecli/platform/deps.py +354 -0
- forgecli/platform/paths.py +236 -0
- forgecli/platform/shell.py +176 -0
- forgecli/platform/update.py +252 -0
- forgecli/plugins/__init__.py +251 -0
- forgecli/prompts/__init__.py +11 -0
- forgecli/prompts/loader.py +28 -0
- forgecli/prompts/registry.py +32 -0
- forgecli/prompts/renderer.py +23 -0
- forgecli/providers/__init__.py +39 -0
- forgecli/providers/anthropic.py +206 -0
- forgecli/providers/base.py +206 -0
- forgecli/providers/builtin.py +26 -0
- forgecli/providers/conversation.py +78 -0
- forgecli/providers/google.py +295 -0
- forgecli/providers/http_base.py +211 -0
- forgecli/providers/mock.py +113 -0
- forgecli/providers/openai.py +202 -0
- forgecli/providers/openai_compatible.py +728 -0
- forgecli/providers/router.py +345 -0
- forgecli/providers/router_state.py +89 -0
- forgecli/review/__init__.py +45 -0
- forgecli/review/analyzer.py +124 -0
- forgecli/review/analyzers/__init__.py +1 -0
- forgecli/review/analyzers/architecture.py +258 -0
- forgecli/review/analyzers/complexity.py +167 -0
- forgecli/review/analyzers/dead_code.py +262 -0
- forgecli/review/analyzers/duplicates.py +163 -0
- forgecli/review/analyzers/performance.py +165 -0
- forgecli/review/analyzers/security.py +251 -0
- forgecli/review/finding.py +68 -0
- forgecli/review/report.py +311 -0
- forgecli/review/repository.py +131 -0
- forgecli/review/suggestions.py +98 -0
- forgecli/runtime/__init__.py +6 -0
- forgecli/runtime/cache_store.py +77 -0
- forgecli/runtime/prepare.py +199 -0
- forgecli/runtime/wrappers.py +152 -0
- forgecli/sdk/__init__.py +132 -0
- forgecli/sdk/events.py +206 -0
- forgecli/sdk/interfaces.py +282 -0
- forgecli/sdk/loader.py +243 -0
- forgecli/sdk/manager.py +693 -0
- forgecli/sdk/manifest.py +397 -0
- forgecli/sdk/sandbox.py +197 -0
- forgecli/sdk/version.py +316 -0
- forgecli/templates/__init__.py +9 -0
- forgecli/templates/engine.py +37 -0
- forgecli/templates/registry.py +32 -0
- forgecli/utils/__init__.py +19 -0
- forgecli/utils/fs.py +91 -0
- forgecli/utils/ids.py +11 -0
- forgecli/utils/io.py +27 -0
- forgecli/utils/paths.py +70 -0
- forgecli/utils/timing.py +25 -0
- forgeoptimizer-1.0.0.dist-info/METADATA +169 -0
- forgeoptimizer-1.0.0.dist-info/RECORD +156 -0
- forgeoptimizer-1.0.0.dist-info/WHEEL +4 -0
- forgeoptimizer-1.0.0.dist-info/entry_points.txt +2 -0
- forgeoptimizer-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
"""OpenAI-compatible AI providers (OpenRouter, Groq, Mistral, Ollama, LM Studio, vLLM)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from forgecli.providers.base import ModelInfo
|
|
11
|
+
from forgecli.providers.http_base import HTTPChatProvider, messages_to_openai
|
|
12
|
+
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
# OpenRouter
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class OpenRouterConfig:
|
|
19
|
+
api_key_env: str = "OPENROUTER_API_KEY"
|
|
20
|
+
base_url: str = "https://openrouter.ai/api/v1"
|
|
21
|
+
default_model: str = "glm-5.2"
|
|
22
|
+
max_tokens: int = 4096
|
|
23
|
+
temperature: float = 0.2
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class OpenRouterProvider(HTTPChatProvider):
|
|
27
|
+
name = "openrouter"
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
config: OpenRouterConfig | None = None,
|
|
32
|
+
*,
|
|
33
|
+
api_key: str | None = None,
|
|
34
|
+
base_url: str | None = None,
|
|
35
|
+
client: httpx.AsyncClient | None = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
super().__init__(
|
|
38
|
+
config or OpenRouterConfig(),
|
|
39
|
+
api_key=api_key,
|
|
40
|
+
base_url=base_url,
|
|
41
|
+
client=client,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def _default_base_url(self) -> str:
|
|
45
|
+
return "https://openrouter.ai/api/v1"
|
|
46
|
+
|
|
47
|
+
def _chat_url(self) -> str:
|
|
48
|
+
return f"{self._base_url}/chat/completions"
|
|
49
|
+
|
|
50
|
+
def _format_request(self, request: Any) -> dict[str, Any]:
|
|
51
|
+
body = {
|
|
52
|
+
"model": request.model or self.config.default_model,
|
|
53
|
+
"messages": messages_to_openai(request.messages),
|
|
54
|
+
"temperature": request.temperature if request.temperature is not None else self.config.temperature,
|
|
55
|
+
"max_tokens": request.max_tokens or self.config.max_tokens,
|
|
56
|
+
}
|
|
57
|
+
return body
|
|
58
|
+
|
|
59
|
+
def _parse_response(self, payload: dict[str, Any]) -> Any:
|
|
60
|
+
# standard OpenAI response parsing
|
|
61
|
+
from forgecli.providers.openai import OpenAIProvider
|
|
62
|
+
# Re-use OpenAI's parser logic
|
|
63
|
+
temp_provider = OpenAIProvider()
|
|
64
|
+
return temp_provider._parse_response(payload)
|
|
65
|
+
|
|
66
|
+
def _known_models(self) -> list[ModelInfo]:
|
|
67
|
+
from forgecli.core.models import MODEL_CATALOG
|
|
68
|
+
return [
|
|
69
|
+
ModelInfo(id=m.id, name=m.display_name, context_window=128_000, supports_tools=True)
|
|
70
|
+
for m in MODEL_CATALOG if m.provider == "openrouter"
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
# Groq
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class GroqConfig:
|
|
80
|
+
api_key_env: str = "GROQ_API_KEY"
|
|
81
|
+
base_url: str = "https://api.groq.com/openai/v1"
|
|
82
|
+
default_model: str = "llama-4-scout"
|
|
83
|
+
max_tokens: int = 4096
|
|
84
|
+
temperature: float = 0.2
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class GroqProvider(HTTPChatProvider):
|
|
88
|
+
name = "groq"
|
|
89
|
+
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
92
|
+
config: GroqConfig | None = None,
|
|
93
|
+
*,
|
|
94
|
+
api_key: str | None = None,
|
|
95
|
+
base_url: str | None = None,
|
|
96
|
+
client: httpx.AsyncClient | None = None,
|
|
97
|
+
) -> None:
|
|
98
|
+
super().__init__(
|
|
99
|
+
config or GroqConfig(),
|
|
100
|
+
api_key=api_key,
|
|
101
|
+
base_url=base_url,
|
|
102
|
+
client=client,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def _default_base_url(self) -> str:
|
|
106
|
+
return "https://api.groq.com/openai/v1"
|
|
107
|
+
|
|
108
|
+
def _chat_url(self) -> str:
|
|
109
|
+
return f"{self._base_url}/chat/completions"
|
|
110
|
+
|
|
111
|
+
def _format_request(self, request: Any) -> dict[str, Any]:
|
|
112
|
+
return {
|
|
113
|
+
"model": request.model or self.config.default_model,
|
|
114
|
+
"messages": messages_to_openai(request.messages),
|
|
115
|
+
"temperature": request.temperature if request.temperature is not None else self.config.temperature,
|
|
116
|
+
"max_tokens": request.max_tokens or self.config.max_tokens,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
def _parse_response(self, payload: dict[str, Any]) -> Any:
|
|
120
|
+
from forgecli.providers.openai import OpenAIProvider
|
|
121
|
+
temp_provider = OpenAIProvider()
|
|
122
|
+
return temp_provider._parse_response(payload)
|
|
123
|
+
|
|
124
|
+
def _known_models(self) -> list[ModelInfo]:
|
|
125
|
+
from forgecli.core.models import MODEL_CATALOG
|
|
126
|
+
return [
|
|
127
|
+
ModelInfo(id=m.id, name=m.display_name, context_window=128_000, supports_tools=True)
|
|
128
|
+
for m in MODEL_CATALOG if m.provider == "groq"
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# ---------------------------------------------------------------------------
|
|
133
|
+
# Mistral
|
|
134
|
+
# ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
@dataclass
|
|
137
|
+
class MistralConfig:
|
|
138
|
+
api_key_env: str = "MISTRAL_API_KEY"
|
|
139
|
+
base_url: str = "https://api.mistral.ai/v1"
|
|
140
|
+
default_model: str = "mistral-large"
|
|
141
|
+
max_tokens: int = 4096
|
|
142
|
+
temperature: float = 0.2
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class MistralProvider(HTTPChatProvider):
|
|
146
|
+
name = "mistral"
|
|
147
|
+
|
|
148
|
+
def __init__(
|
|
149
|
+
self,
|
|
150
|
+
config: MistralConfig | None = None,
|
|
151
|
+
*,
|
|
152
|
+
api_key: str | None = None,
|
|
153
|
+
base_url: str | None = None,
|
|
154
|
+
client: httpx.AsyncClient | None = None,
|
|
155
|
+
) -> None:
|
|
156
|
+
super().__init__(
|
|
157
|
+
config or MistralConfig(),
|
|
158
|
+
api_key=api_key,
|
|
159
|
+
base_url=base_url,
|
|
160
|
+
client=client,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def _default_base_url(self) -> str:
|
|
164
|
+
return "https://api.mistral.ai/v1"
|
|
165
|
+
|
|
166
|
+
def _chat_url(self) -> str:
|
|
167
|
+
return f"{self._base_url}/chat/completions"
|
|
168
|
+
|
|
169
|
+
def _format_request(self, request: Any) -> dict[str, Any]:
|
|
170
|
+
return {
|
|
171
|
+
"model": request.model or self.config.default_model,
|
|
172
|
+
"messages": messages_to_openai(request.messages),
|
|
173
|
+
"temperature": request.temperature if request.temperature is not None else self.config.temperature,
|
|
174
|
+
"max_tokens": request.max_tokens or self.config.max_tokens,
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
def _parse_response(self, payload: dict[str, Any]) -> Any:
|
|
178
|
+
from forgecli.providers.openai import OpenAIProvider
|
|
179
|
+
temp_provider = OpenAIProvider()
|
|
180
|
+
return temp_provider._parse_response(payload)
|
|
181
|
+
|
|
182
|
+
def _known_models(self) -> list[ModelInfo]:
|
|
183
|
+
from forgecli.core.models import MODEL_CATALOG
|
|
184
|
+
return [
|
|
185
|
+
ModelInfo(id=m.id, name=m.display_name, context_window=128_000, supports_tools=True)
|
|
186
|
+
for m in MODEL_CATALOG if m.provider == "mistral"
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# ---------------------------------------------------------------------------
|
|
191
|
+
# Local Providers
|
|
192
|
+
# ---------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
class LocalProvider(HTTPChatProvider):
|
|
195
|
+
"""Generic base class for local providers that support dynamic model list query."""
|
|
196
|
+
|
|
197
|
+
def _chat_url(self) -> str:
|
|
198
|
+
return f"{self._base_url}/chat/completions"
|
|
199
|
+
|
|
200
|
+
def _format_request(self, request: Any) -> dict[str, Any]:
|
|
201
|
+
return {
|
|
202
|
+
"model": request.model or self.config.default_model,
|
|
203
|
+
"messages": messages_to_openai(request.messages),
|
|
204
|
+
"temperature": request.temperature if request.temperature is not None else self.config.temperature,
|
|
205
|
+
"max_tokens": request.max_tokens or self.config.max_tokens,
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
def _parse_response(self, payload: dict[str, Any]) -> Any:
|
|
209
|
+
from forgecli.providers.openai import OpenAIProvider
|
|
210
|
+
temp_provider = OpenAIProvider()
|
|
211
|
+
return temp_provider._parse_response(payload)
|
|
212
|
+
|
|
213
|
+
async def list_models(self) -> list[ModelInfo]:
|
|
214
|
+
try:
|
|
215
|
+
response = await self._client.get(
|
|
216
|
+
f"{self._base_url}/models",
|
|
217
|
+
headers=self._auth_headers(),
|
|
218
|
+
timeout=5.0
|
|
219
|
+
)
|
|
220
|
+
if response.status_code == 200:
|
|
221
|
+
data = response.json()
|
|
222
|
+
models_list = data.get("data", [])
|
|
223
|
+
return [
|
|
224
|
+
ModelInfo(
|
|
225
|
+
id=str(m["id"]),
|
|
226
|
+
name=str(m.get("id")),
|
|
227
|
+
context_window=128_000,
|
|
228
|
+
supports_tools=True,
|
|
229
|
+
supports_vision=True
|
|
230
|
+
)
|
|
231
|
+
for m in models_list if isinstance(m, dict) and m.get("id") is not None
|
|
232
|
+
]
|
|
233
|
+
except Exception:
|
|
234
|
+
pass
|
|
235
|
+
return self._known_models()
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@dataclass
|
|
239
|
+
class OllamaConfig:
|
|
240
|
+
api_key_env: str = "OLLAMA_API_KEY"
|
|
241
|
+
base_url: str = "http://localhost:11434/v1"
|
|
242
|
+
default_model: str = "llama3"
|
|
243
|
+
max_tokens: int = 4096
|
|
244
|
+
temperature: float = 0.2
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class OllamaProvider(LocalProvider):
|
|
248
|
+
name = "ollama"
|
|
249
|
+
|
|
250
|
+
def __init__(
|
|
251
|
+
self,
|
|
252
|
+
config: OllamaConfig | None = None,
|
|
253
|
+
*,
|
|
254
|
+
api_key: str | None = None,
|
|
255
|
+
base_url: str | None = None,
|
|
256
|
+
client: httpx.AsyncClient | None = None,
|
|
257
|
+
) -> None:
|
|
258
|
+
super().__init__(
|
|
259
|
+
config or OllamaConfig(),
|
|
260
|
+
api_key=api_key,
|
|
261
|
+
base_url=base_url,
|
|
262
|
+
client=client,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
def _default_base_url(self) -> str:
|
|
266
|
+
return "http://localhost:11434/v1"
|
|
267
|
+
|
|
268
|
+
async def list_models(self) -> list[ModelInfo]:
|
|
269
|
+
try:
|
|
270
|
+
import subprocess
|
|
271
|
+
result = subprocess.run(
|
|
272
|
+
["ollama", "list"],
|
|
273
|
+
capture_output=True,
|
|
274
|
+
text=True,
|
|
275
|
+
check=False
|
|
276
|
+
)
|
|
277
|
+
if result.returncode == 0:
|
|
278
|
+
lines = result.stdout.strip().split("\n")
|
|
279
|
+
if len(lines) > 1:
|
|
280
|
+
models = []
|
|
281
|
+
for line in lines[1:]:
|
|
282
|
+
parts = line.split()
|
|
283
|
+
if parts:
|
|
284
|
+
model_id = parts[0]
|
|
285
|
+
models.append(
|
|
286
|
+
ModelInfo(
|
|
287
|
+
id=model_id,
|
|
288
|
+
name=model_id,
|
|
289
|
+
context_window=8192,
|
|
290
|
+
supports_tools=True
|
|
291
|
+
)
|
|
292
|
+
)
|
|
293
|
+
if models:
|
|
294
|
+
return models
|
|
295
|
+
except Exception:
|
|
296
|
+
pass
|
|
297
|
+
return await super().list_models()
|
|
298
|
+
|
|
299
|
+
def _known_models(self) -> list[ModelInfo]:
|
|
300
|
+
from forgecli.core.models import MODEL_CATALOG
|
|
301
|
+
return [
|
|
302
|
+
ModelInfo(id=m.id, name=m.display_name, context_window=8192, supports_tools=True)
|
|
303
|
+
for m in MODEL_CATALOG if m.provider == "ollama"
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@dataclass
|
|
308
|
+
class LMStudioConfig:
|
|
309
|
+
api_key_env: str = "LMSTUDIO_API_KEY"
|
|
310
|
+
base_url: str = "http://localhost:1234/v1"
|
|
311
|
+
default_model: str = "local-model"
|
|
312
|
+
max_tokens: int = 4096
|
|
313
|
+
temperature: float = 0.2
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class LMStudioProvider(LocalProvider):
|
|
317
|
+
name = "lmstudio"
|
|
318
|
+
|
|
319
|
+
def __init__(
|
|
320
|
+
self,
|
|
321
|
+
config: LMStudioConfig | None = None,
|
|
322
|
+
*,
|
|
323
|
+
api_key: str | None = None,
|
|
324
|
+
base_url: str | None = None,
|
|
325
|
+
client: httpx.AsyncClient | None = None,
|
|
326
|
+
) -> None:
|
|
327
|
+
super().__init__(
|
|
328
|
+
config or LMStudioConfig(),
|
|
329
|
+
api_key=api_key,
|
|
330
|
+
base_url=base_url,
|
|
331
|
+
client=client,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
def _default_base_url(self) -> str:
|
|
335
|
+
return "http://localhost:1234/v1"
|
|
336
|
+
|
|
337
|
+
def _known_models(self) -> list[ModelInfo]:
|
|
338
|
+
from forgecli.core.models import MODEL_CATALOG
|
|
339
|
+
return [
|
|
340
|
+
ModelInfo(id=m.id, name=m.display_name, context_window=8192, supports_tools=True)
|
|
341
|
+
for m in MODEL_CATALOG if m.provider == "lmstudio"
|
|
342
|
+
]
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
@dataclass
|
|
346
|
+
class VllmConfig:
|
|
347
|
+
api_key_env: str = "VLLM_API_KEY"
|
|
348
|
+
base_url: str = "http://localhost:8000/v1"
|
|
349
|
+
default_model: str = "local-model"
|
|
350
|
+
max_tokens: int = 4096
|
|
351
|
+
temperature: float = 0.2
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class VllmProvider(LocalProvider):
|
|
355
|
+
name = "vllm"
|
|
356
|
+
|
|
357
|
+
def __init__(
|
|
358
|
+
self,
|
|
359
|
+
config: VllmConfig | None = None,
|
|
360
|
+
*,
|
|
361
|
+
api_key: str | None = None,
|
|
362
|
+
base_url: str | None = None,
|
|
363
|
+
client: httpx.AsyncClient | None = None,
|
|
364
|
+
) -> None:
|
|
365
|
+
super().__init__(
|
|
366
|
+
config or VllmConfig(),
|
|
367
|
+
api_key=api_key,
|
|
368
|
+
base_url=base_url,
|
|
369
|
+
client=client,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
def _default_base_url(self) -> str:
|
|
373
|
+
return "http://localhost:8000/v1"
|
|
374
|
+
|
|
375
|
+
def _known_models(self) -> list[ModelInfo]:
|
|
376
|
+
from forgecli.core.models import MODEL_CATALOG
|
|
377
|
+
return [
|
|
378
|
+
ModelInfo(id=m.id, name=m.display_name, context_window=8192, supports_tools=True)
|
|
379
|
+
for m in MODEL_CATALOG if m.provider == "vllm"
|
|
380
|
+
]
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
# ---------------------------------------------------------------------------
|
|
384
|
+
# MiniMax
|
|
385
|
+
# ---------------------------------------------------------------------------
|
|
386
|
+
|
|
387
|
+
@dataclass
|
|
388
|
+
class MiniMaxConfig:
|
|
389
|
+
api_key_env: str = "MINIMAX_API_KEY"
|
|
390
|
+
base_url: str = "https://api.minimaxi.chat/v1"
|
|
391
|
+
default_model: str = "abab6.5g-chat"
|
|
392
|
+
max_tokens: int = 4096
|
|
393
|
+
temperature: float = 0.2
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
class MiniMaxProvider(HTTPChatProvider):
|
|
397
|
+
name = "minimax"
|
|
398
|
+
|
|
399
|
+
def __init__(
|
|
400
|
+
self,
|
|
401
|
+
config: MiniMaxConfig | None = None,
|
|
402
|
+
*,
|
|
403
|
+
api_key: str | None = None,
|
|
404
|
+
base_url: str | None = None,
|
|
405
|
+
client: httpx.AsyncClient | None = None,
|
|
406
|
+
) -> None:
|
|
407
|
+
super().__init__(
|
|
408
|
+
config or MiniMaxConfig(),
|
|
409
|
+
api_key=api_key,
|
|
410
|
+
base_url=base_url,
|
|
411
|
+
client=client,
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
def _default_base_url(self) -> str:
|
|
415
|
+
return "https://api.minimaxi.chat/v1"
|
|
416
|
+
|
|
417
|
+
def _chat_url(self) -> str:
|
|
418
|
+
return f"{self._base_url}/chat/completions"
|
|
419
|
+
|
|
420
|
+
def _format_request(self, request: Any) -> dict[str, Any]:
|
|
421
|
+
return {
|
|
422
|
+
"model": request.model or self.config.default_model,
|
|
423
|
+
"messages": messages_to_openai(request.messages),
|
|
424
|
+
"temperature": request.temperature if request.temperature is not None else self.config.temperature,
|
|
425
|
+
"max_tokens": request.max_tokens or self.config.max_tokens,
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
def _parse_response(self, payload: dict[str, Any]) -> Any:
|
|
429
|
+
from forgecli.providers.openai import OpenAIProvider
|
|
430
|
+
temp_provider = OpenAIProvider()
|
|
431
|
+
return temp_provider._parse_response(payload)
|
|
432
|
+
|
|
433
|
+
def _known_models(self) -> list[ModelInfo]:
|
|
434
|
+
from forgecli.core.models import MODEL_CATALOG
|
|
435
|
+
return [
|
|
436
|
+
ModelInfo(id=m.id, name=m.display_name, context_window=128_000, supports_tools=True)
|
|
437
|
+
for m in MODEL_CATALOG if m.provider == "minimax"
|
|
438
|
+
]
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
# ---------------------------------------------------------------------------
|
|
442
|
+
# xAI (Grok)
|
|
443
|
+
# ---------------------------------------------------------------------------
|
|
444
|
+
|
|
445
|
+
@dataclass
|
|
446
|
+
class XaiConfig:
|
|
447
|
+
api_key_env: str = "XAI_API_KEY"
|
|
448
|
+
base_url: str = "https://api.x.ai/v1"
|
|
449
|
+
default_model: str = "grok-2"
|
|
450
|
+
max_tokens: int = 4096
|
|
451
|
+
temperature: float = 0.2
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
class XaiProvider(HTTPChatProvider):
|
|
455
|
+
name = "xai"
|
|
456
|
+
|
|
457
|
+
def __init__(
|
|
458
|
+
self,
|
|
459
|
+
config: XaiConfig | None = None,
|
|
460
|
+
*,
|
|
461
|
+
api_key: str | None = None,
|
|
462
|
+
base_url: str | None = None,
|
|
463
|
+
client: httpx.AsyncClient | None = None,
|
|
464
|
+
) -> None:
|
|
465
|
+
super().__init__(
|
|
466
|
+
config or XaiConfig(),
|
|
467
|
+
api_key=api_key,
|
|
468
|
+
base_url=base_url,
|
|
469
|
+
client=client,
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
def _default_base_url(self) -> str:
|
|
473
|
+
return "https://api.x.ai/v1"
|
|
474
|
+
|
|
475
|
+
def _chat_url(self) -> str:
|
|
476
|
+
return f"{self._base_url}/chat/completions"
|
|
477
|
+
|
|
478
|
+
def _format_request(self, request: Any) -> dict[str, Any]:
|
|
479
|
+
return {
|
|
480
|
+
"model": request.model or self.config.default_model,
|
|
481
|
+
"messages": messages_to_openai(request.messages),
|
|
482
|
+
"temperature": request.temperature if request.temperature is not None else self.config.temperature,
|
|
483
|
+
"max_tokens": request.max_tokens or self.config.max_tokens,
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
def _parse_response(self, payload: dict[str, Any]) -> Any:
|
|
487
|
+
from forgecli.providers.openai import OpenAIProvider
|
|
488
|
+
temp_provider = OpenAIProvider()
|
|
489
|
+
return temp_provider._parse_response(payload)
|
|
490
|
+
|
|
491
|
+
def _known_models(self) -> list[ModelInfo]:
|
|
492
|
+
from forgecli.core.models import MODEL_CATALOG
|
|
493
|
+
return [
|
|
494
|
+
ModelInfo(id=m.id, name=m.display_name, context_window=128_000, supports_tools=True)
|
|
495
|
+
for m in MODEL_CATALOG if m.provider == "xai"
|
|
496
|
+
]
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
# ---------------------------------------------------------------------------
|
|
500
|
+
# Together AI
|
|
501
|
+
# ---------------------------------------------------------------------------
|
|
502
|
+
|
|
503
|
+
@dataclass
|
|
504
|
+
class TogetherConfig:
|
|
505
|
+
api_key_env: str = "TOGETHER_API_KEY"
|
|
506
|
+
base_url: str = "https://api.together.xyz/v1"
|
|
507
|
+
default_model: str = "llama-3.1-70b"
|
|
508
|
+
max_tokens: int = 4096
|
|
509
|
+
temperature: float = 0.2
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
class TogetherProvider(HTTPChatProvider):
|
|
513
|
+
name = "together"
|
|
514
|
+
|
|
515
|
+
def __init__(
|
|
516
|
+
self,
|
|
517
|
+
config: TogetherConfig | None = None,
|
|
518
|
+
*,
|
|
519
|
+
api_key: str | None = None,
|
|
520
|
+
base_url: str | None = None,
|
|
521
|
+
client: httpx.AsyncClient | None = None,
|
|
522
|
+
) -> None:
|
|
523
|
+
super().__init__(
|
|
524
|
+
config or TogetherConfig(),
|
|
525
|
+
api_key=api_key,
|
|
526
|
+
base_url=base_url,
|
|
527
|
+
client=client,
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
def _default_base_url(self) -> str:
|
|
531
|
+
return "https://api.together.xyz/v1"
|
|
532
|
+
|
|
533
|
+
def _chat_url(self) -> str:
|
|
534
|
+
return f"{self._base_url}/chat/completions"
|
|
535
|
+
|
|
536
|
+
def _format_request(self, request: Any) -> dict[str, Any]:
|
|
537
|
+
return {
|
|
538
|
+
"model": request.model or self.config.default_model,
|
|
539
|
+
"messages": messages_to_openai(request.messages),
|
|
540
|
+
"temperature": request.temperature if request.temperature is not None else self.config.temperature,
|
|
541
|
+
"max_tokens": request.max_tokens or self.config.max_tokens,
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
def _parse_response(self, payload: dict[str, Any]) -> Any:
|
|
545
|
+
from forgecli.providers.openai import OpenAIProvider
|
|
546
|
+
temp_provider = OpenAIProvider()
|
|
547
|
+
return temp_provider._parse_response(payload)
|
|
548
|
+
|
|
549
|
+
def _known_models(self) -> list[ModelInfo]:
|
|
550
|
+
from forgecli.core.models import MODEL_CATALOG
|
|
551
|
+
return [
|
|
552
|
+
ModelInfo(id=m.id, name=m.display_name, context_window=128_000, supports_tools=True)
|
|
553
|
+
for m in MODEL_CATALOG if m.provider == "together"
|
|
554
|
+
]
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
# ---------------------------------------------------------------------------
|
|
558
|
+
# Fireworks AI
|
|
559
|
+
# ---------------------------------------------------------------------------
|
|
560
|
+
|
|
561
|
+
@dataclass
|
|
562
|
+
class FireworksConfig:
|
|
563
|
+
api_key_env: str = "FIREWORKS_API_KEY"
|
|
564
|
+
base_url: str = "https://api.fireworks.ai/inference/v1"
|
|
565
|
+
default_model: str = "llama-3.1-70b"
|
|
566
|
+
max_tokens: int = 4096
|
|
567
|
+
temperature: float = 0.2
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
class FireworksProvider(HTTPChatProvider):
|
|
571
|
+
name = "fireworks"
|
|
572
|
+
|
|
573
|
+
def __init__(
|
|
574
|
+
self,
|
|
575
|
+
config: FireworksConfig | None = None,
|
|
576
|
+
*,
|
|
577
|
+
api_key: str | None = None,
|
|
578
|
+
base_url: str | None = None,
|
|
579
|
+
client: httpx.AsyncClient | None = None,
|
|
580
|
+
) -> None:
|
|
581
|
+
super().__init__(
|
|
582
|
+
config or FireworksConfig(),
|
|
583
|
+
api_key=api_key,
|
|
584
|
+
base_url=base_url,
|
|
585
|
+
client=client,
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
def _default_base_url(self) -> str:
|
|
589
|
+
return "https://api.fireworks.ai/inference/v1"
|
|
590
|
+
|
|
591
|
+
def _chat_url(self) -> str:
|
|
592
|
+
return f"{self._base_url}/chat/completions"
|
|
593
|
+
|
|
594
|
+
def _format_request(self, request: Any) -> dict[str, Any]:
|
|
595
|
+
return {
|
|
596
|
+
"model": request.model or self.config.default_model,
|
|
597
|
+
"messages": messages_to_openai(request.messages),
|
|
598
|
+
"temperature": request.temperature if request.temperature is not None else self.config.temperature,
|
|
599
|
+
"max_tokens": request.max_tokens or self.config.max_tokens,
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
def _parse_response(self, payload: dict[str, Any]) -> Any:
|
|
603
|
+
from forgecli.providers.openai import OpenAIProvider
|
|
604
|
+
temp_provider = OpenAIProvider()
|
|
605
|
+
return temp_provider._parse_response(payload)
|
|
606
|
+
|
|
607
|
+
def _known_models(self) -> list[ModelInfo]:
|
|
608
|
+
from forgecli.core.models import MODEL_CATALOG
|
|
609
|
+
return [
|
|
610
|
+
ModelInfo(id=m.id, name=m.display_name, context_window=128_000, supports_tools=True)
|
|
611
|
+
for m in MODEL_CATALOG if m.provider == "fireworks"
|
|
612
|
+
]
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
# ---------------------------------------------------------------------------
|
|
616
|
+
# Cohere
|
|
617
|
+
# ---------------------------------------------------------------------------
|
|
618
|
+
|
|
619
|
+
@dataclass
|
|
620
|
+
class CohereConfig:
|
|
621
|
+
api_key_env: str = "COHERE_API_KEY"
|
|
622
|
+
base_url: str = "https://api.cohere.com/v1"
|
|
623
|
+
default_model: str = "command-r-plus"
|
|
624
|
+
max_tokens: int = 4096
|
|
625
|
+
temperature: float = 0.2
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
class CohereProvider(HTTPChatProvider):
|
|
629
|
+
name = "cohere"
|
|
630
|
+
|
|
631
|
+
def __init__(
|
|
632
|
+
self,
|
|
633
|
+
config: CohereConfig | None = None,
|
|
634
|
+
*,
|
|
635
|
+
api_key: str | None = None,
|
|
636
|
+
base_url: str | None = None,
|
|
637
|
+
client: httpx.AsyncClient | None = None,
|
|
638
|
+
) -> None:
|
|
639
|
+
super().__init__(
|
|
640
|
+
config or CohereConfig(),
|
|
641
|
+
api_key=api_key,
|
|
642
|
+
base_url=base_url,
|
|
643
|
+
client=client,
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
def _default_base_url(self) -> str:
|
|
647
|
+
return "https://api.cohere.com/v1"
|
|
648
|
+
|
|
649
|
+
def _chat_url(self) -> str:
|
|
650
|
+
return f"{self._base_url}/chat/completions"
|
|
651
|
+
|
|
652
|
+
def _format_request(self, request: Any) -> dict[str, Any]:
|
|
653
|
+
return {
|
|
654
|
+
"model": request.model or self.config.default_model,
|
|
655
|
+
"messages": messages_to_openai(request.messages),
|
|
656
|
+
"temperature": request.temperature if request.temperature is not None else self.config.temperature,
|
|
657
|
+
"max_tokens": request.max_tokens or self.config.max_tokens,
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
def _parse_response(self, payload: dict[str, Any]) -> Any:
|
|
661
|
+
from forgecli.providers.openai import OpenAIProvider
|
|
662
|
+
temp_provider = OpenAIProvider()
|
|
663
|
+
return temp_provider._parse_response(payload)
|
|
664
|
+
|
|
665
|
+
def _known_models(self) -> list[ModelInfo]:
|
|
666
|
+
from forgecli.core.models import MODEL_CATALOG
|
|
667
|
+
return [
|
|
668
|
+
ModelInfo(id=m.id, name=m.display_name, context_window=128_000, supports_tools=True)
|
|
669
|
+
for m in MODEL_CATALOG if m.provider == "cohere"
|
|
670
|
+
]
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
# ---------------------------------------------------------------------------
|
|
674
|
+
# NVIDIA NIM
|
|
675
|
+
# ---------------------------------------------------------------------------
|
|
676
|
+
|
|
677
|
+
@dataclass
|
|
678
|
+
class NvidiaConfig:
|
|
679
|
+
api_key_env: str = "NVIDIA_API_KEY"
|
|
680
|
+
base_url: str = "https://integrate.api.nvidia.com/v1"
|
|
681
|
+
default_model: str = "llama-3.1-70b"
|
|
682
|
+
max_tokens: int = 4096
|
|
683
|
+
temperature: float = 0.2
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
class NvidiaProvider(HTTPChatProvider):
|
|
687
|
+
name = "nvidia"
|
|
688
|
+
|
|
689
|
+
def __init__(
|
|
690
|
+
self,
|
|
691
|
+
config: NvidiaConfig | None = None,
|
|
692
|
+
*,
|
|
693
|
+
api_key: str | None = None,
|
|
694
|
+
base_url: str | None = None,
|
|
695
|
+
client: httpx.AsyncClient | None = None,
|
|
696
|
+
) -> None:
|
|
697
|
+
super().__init__(
|
|
698
|
+
config or NvidiaConfig(),
|
|
699
|
+
api_key=api_key,
|
|
700
|
+
base_url=base_url,
|
|
701
|
+
client=client,
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
def _default_base_url(self) -> str:
|
|
705
|
+
return "https://integrate.api.nvidia.com/v1"
|
|
706
|
+
|
|
707
|
+
def _chat_url(self) -> str:
|
|
708
|
+
return f"{self._base_url}/chat/completions"
|
|
709
|
+
|
|
710
|
+
def _format_request(self, request: Any) -> dict[str, Any]:
|
|
711
|
+
return {
|
|
712
|
+
"model": request.model or self.config.default_model,
|
|
713
|
+
"messages": messages_to_openai(request.messages),
|
|
714
|
+
"temperature": request.temperature if request.temperature is not None else self.config.temperature,
|
|
715
|
+
"max_tokens": request.max_tokens or self.config.max_tokens,
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
def _parse_response(self, payload: dict[str, Any]) -> Any:
|
|
719
|
+
from forgecli.providers.openai import OpenAIProvider
|
|
720
|
+
temp_provider = OpenAIProvider()
|
|
721
|
+
return temp_provider._parse_response(payload)
|
|
722
|
+
|
|
723
|
+
def _known_models(self) -> list[ModelInfo]:
|
|
724
|
+
from forgecli.core.models import MODEL_CATALOG
|
|
725
|
+
return [
|
|
726
|
+
ModelInfo(id=m.id, name=m.display_name, context_window=128_000, supports_tools=True)
|
|
727
|
+
for m in MODEL_CATALOG if m.provider == "nvidia"
|
|
728
|
+
]
|