OpenAI: Server-Key-Setting, Filter-Fix fuer o-Reihe, ehrlicher Modell-Refresh
Der Modell-Refresh war fuer OpenAI faktisch ein No-Op: der Key wurde nur aus
der api_keys-Tabelle geholt, und ohne Key blieb die alte Liste im Speicher
stehen und wurde direkt wieder in models_cache.json geschrieben - waehrend der
Endpoint pauschal Erfolg meldete.
- ServerSettingsService: neues Setting OpenAiApiKey (OpenAI war der einzige
Provider ohne Server-Key, analog Gemini/MiniMax/z.ai)
- OpenAiService: ResolveOpenAiKey() (Setting -> api_keys), HasServerOpenAiKey(),
ResolveProviderConfig zieht den Setting-Key auch im Chat
- OpenAiService: IsOpenAiChatModel() erfasst die o-Reihe inkl. bare IDs
(o1, o3, kuenftig o5) - StartsWith("o3-") hat die verschluckt
- RefreshModelsFromApis() liefert ProviderRefreshResult[] pro Provider;
ModelsEndpoint gibt providers[] zurueck, agent-page.js meldet uebersprungene
Provider als Fehler-Toast statt pauschalem Erfolg
- ChatEndpoint/TelegramService/CronjobService: OpenAI ohne Agent-Key faellt auf
den Server-Key zurueck statt hart abzubrechen
- ARCHITECTURE.md nachgezogen
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f38f3a42d1
commit
7305a5a50f
8 files changed
+136
-31
No files matched your search
+10
-2
@@ -624,6 +624,8 @@ Neben Anthropic Claude unterstützt Gott drei **OpenAI-kompatible Provider**. De
|
|||||||
| `nvidia:` | Nvidia (g4f.space Proxy) | `g4f.space/api/nvidia/v1` | keiner |
|
| `nvidia:` | Nvidia (g4f.space Proxy) | `g4f.space/api/nvidia/v1` | keiner |
|
||||||
| `pollinations:` | Pollinations | `text.pollinations.ai/openai` | keiner |
|
| `pollinations:` | Pollinations | `text.pollinations.ai/openai` | keiner |
|
||||||
| `gemini:` | Google Gemini | `generativelanguage.googleapis.com/v1beta/openai` | `GeminiApiKey` |
|
| `gemini:` | Google Gemini | `generativelanguage.googleapis.com/v1beta/openai` | `GeminiApiKey` |
|
||||||
|
| `openai:` | OpenAI | `api.openai.com/v1` | `agent_api_keys`, sonst Server-Setting `OpenAiApiKey` |
|
||||||
|
| `minimax:` | MiniMax | `api.minimax.io/v1` | `MinimaxApiKey` |
|
||||||
|
|
||||||
### Model-String-Format
|
### Model-String-Format
|
||||||
|
|
||||||
@@ -2042,10 +2044,16 @@ Wenn ein **Sub-Agent** (Preparer, Review, Finalizer) keinem API-Key zugewiesen i
|
|||||||
|
|
||||||
Dasselbe Muster gilt für **Cronjobs** (`CronjobService.ExecuteCronjob` + `SendCronjobNotification`) und **Telegram** (`TelegramService.HandleMessage`) – exakt die **gleiche Provider-/Key-Dreiteilung** wie im `ChatEndpoint`:
|
Dasselbe Muster gilt für **Cronjobs** (`CronjobService.ExecuteCronjob` + `SendCronjobNotification`) und **Telegram** (`TelegramService.HandleMessage`) – exakt die **gleiche Provider-/Key-Dreiteilung** wie im `ChatEndpoint`:
|
||||||
- **Anthropic ohne zugewiesenen Key** → `ChatNonStreamingViaCli()` (CLI-Fallback), sofern der User `CanUseCli`/`IsAdmin` hat und die CLI verfügbar ist
|
- **Anthropic ohne zugewiesenen Key** → `ChatNonStreamingViaCli()` (CLI-Fallback), sofern der User `CanUseCli`/`IsAdmin` hat und die CLI verfügbar ist
|
||||||
- **`openai` ohne Key** → harter Fehler (kein Server-Key, kein CLI-Äquivalent)
|
- **`openai` ohne Agent-Key** → Server-Setting `OpenAiApiKey` (in `ResolveProviderConfig` aufgelöst, analog Gemini/MiniMax); fehlt auch das → harter Fehler (kein CLI-Äquivalent)
|
||||||
- **keyless Provider (nvidia/gemini/minimax/pollinations)** → Request wird ohne User-Key durchgelassen; der **Server-seitige** Key wird in `ResolveProviderConfig` aufgelöst
|
- **keyless Provider (nvidia/gemini/minimax/pollinations)** → Request wird ohne User-Key durchgelassen; der **Server-seitige** Key wird in `ResolveProviderConfig` aufgelöst
|
||||||
|
|
||||||
Der frühere harte Abbruch („Kein API-Key zugewiesen") blockte irrtümlich **jeden** nicht-leeren Provider – jetzt blockiert nur noch `openai` ohne Key.
|
Der frühere harte Abbruch („Kein API-Key zugewiesen") blockte irrtümlich **jeden** nicht-leeren Provider – jetzt blockiert nur noch `openai` ganz ohne Key (weder Agent-Key noch `OpenAiApiKey`). Die Prüfung erfolgt im `CronjobService` über `OpenAiService.HasServerOpenAiKey()`, in `ChatEndpoint`/`TelegramService` direkt über `settings.Get("OpenAiApiKey")`.
|
||||||
|
|
||||||
|
### Modell-Refresh (`POST /api/models/refresh`)
|
||||||
|
|
||||||
|
`RefreshModelsFromApis()` gibt pro Provider ein `ProviderRefreshResult` (`provider`, `loaded`, `count`, `note`) zurück; der Endpoint liefert das als `providers[]` mit. Wird ein Provider **übersprungen** (kein Key, API leer), bleibt seine **alte Liste im Cache stehen** – das wird jetzt explizit gemeldet (Log + Toast im Agent-Formular), statt pauschal „Modelle neu geladen" zu behaupten.
|
||||||
|
|
||||||
|
Der OpenAI-Key für die Modell-Liste kommt aus `OpenAiApiKey` (Setting), sonst aus dem ersten aktiven `openai`-Eintrag der `api_keys`-Tabelle (`ResolveOpenAiKey()`). Der Chat-Modell-Filter (`IsOpenAiChatModel`) erfasst `gpt-*`, `chatgpt-*` und die komplette o-Reihe **inklusive bare IDs** (`o1`, `o3`, künftig `o5`) – der frühere `StartsWith("o3-")`-Filter hat die verschluckt.
|
||||||
|
|
||||||
### Python-Detection für MCP-Config
|
### Python-Detection für MCP-Config
|
||||||
|
|
||||||
|
|||||||
@@ -263,10 +263,12 @@ public static class ChatEndpoint
|
|||||||
// OpenAI-kompatibler Provider – Key des Users für diesen Agent verwenden
|
// OpenAI-kompatibler Provider – Key des Users für diesen Agent verwenden
|
||||||
string openAiKeyOverride = userApiKey?.ApiKey;
|
string openAiKeyOverride = userApiKey?.ApiKey;
|
||||||
|
|
||||||
// OpenAI-Provider braucht zwingend einen Key
|
// OpenAI-Provider braucht zwingend einen Key: Agent-Key, sonst Server-Setting
|
||||||
if (ctx.Provider == "openai" && string.IsNullOrEmpty(openAiKeyOverride))
|
// 'OpenAiApiKey' (analog Gemini/MiniMax – wird in ResolveProviderConfig gezogen).
|
||||||
|
if (ctx.Provider == "openai" && string.IsNullOrEmpty(openAiKeyOverride)
|
||||||
|
&& string.IsNullOrEmpty(settings.Get("OpenAiApiKey")))
|
||||||
{
|
{
|
||||||
await SseHelper.TryWriteSse(res, "error", new { error = "Kein API-Key für diesen Agent zugewiesen. Bitte einen OpenAI-Key zuweisen." }, sseCt);
|
await SseHelper.TryWriteSse(res, "error", new { error = "Kein OpenAI-Key vorhanden: weder dem Agent zugewiesen noch als Server-Setting 'OpenAiApiKey' gesetzt." }, sseCt);
|
||||||
await SseHelper.TryWriteSse(res, "done", new { chainId = ctx.ChainId }, sseCt);
|
await SseHelper.TryWriteSse(res, "done", new { chainId = ctx.ChainId }, sseCt);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,20 @@ public static class ModelsEndpoint
|
|||||||
|
|
||||||
private static async Task<IResult> HandleRefresh(OpenAiService openAiService)
|
private static async Task<IResult> HandleRefresh(OpenAiService openAiService)
|
||||||
{
|
{
|
||||||
await openAiService.RefreshModelsFromApis();
|
var report = await openAiService.RefreshModelsFromApis();
|
||||||
return Results.Json(new { success = true, message = "Modelle neu geladen und gecacht." });
|
var loaded = report.Where(r => r.Loaded).ToList();
|
||||||
|
var skipped = report.Where(r => !r.Loaded).ToList();
|
||||||
|
|
||||||
|
var message = $"{loaded.Count} Provider neu geladen"
|
||||||
|
+ (skipped.Count > 0
|
||||||
|
? $", {skipped.Count} übersprungen: {string.Join(", ", skipped.Select(r => $"{r.Provider} ({r.Note})"))}"
|
||||||
|
: ".");
|
||||||
|
|
||||||
|
return Results.Json(new
|
||||||
|
{
|
||||||
|
success = true,
|
||||||
|
message,
|
||||||
|
providers = report.Select(r => new { provider = r.Provider, loaded = r.Loaded, count = r.Count, note = r.Note }).ToList()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,9 +111,10 @@ public class CronjobService : BackgroundService
|
|||||||
useCliFallback = true;
|
useCliFallback = true;
|
||||||
_logger.LogInformation("Cronjob '{Name}': Kein API-Key für Agent {AgentId}, verwende CLI-Fallback", job.Name, ctx.AgentId);
|
_logger.LogInformation("Cronjob '{Name}': Kein API-Key für Agent {AgentId}, verwende CLI-Fallback", job.Name, ctx.AgentId);
|
||||||
}
|
}
|
||||||
else if (ctx.Provider == "openai")
|
else if (ctx.Provider == "openai" && !_openAiService.HasServerOpenAiKey())
|
||||||
{
|
{
|
||||||
throw new Exception($"Kein API-Key für Agent {ctx.AgentId} zugewiesen (User {job.UserId}). OpenAI benötigt zwingend einen zugewiesenen API-Key.");
|
// Ohne Agent-Key greift der Server-Key aus dem Setting 'OpenAiApiKey' (analog Gemini/MiniMax).
|
||||||
|
throw new Exception($"Kein OpenAI-Key für Agent {ctx.AgentId} (User {job.UserId}): weder zugewiesen noch als Server-Setting 'OpenAiApiKey' gesetzt.");
|
||||||
}
|
}
|
||||||
// sonst: nvidia/gemini/minimax/pollinations → ctx.ApiToken bleibt leer,
|
// sonst: nvidia/gemini/minimax/pollinations → ctx.ApiToken bleibt leer,
|
||||||
// der Server-seitige Provider-Key wird in ResolveProviderConfig aufgelöst.
|
// der Server-seitige Provider-Key wird in ResolveProviderConfig aufgelöst.
|
||||||
@@ -233,9 +234,9 @@ public class CronjobService : BackgroundService
|
|||||||
notifyUseCliFallback = true;
|
notifyUseCliFallback = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (notifyCtx.Provider == "openai")
|
else if (notifyCtx.Provider == "openai" && !_openAiService.HasServerOpenAiKey())
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Notify-Agent-Reaktion für Cronjob '{Name}' übersprungen: OpenAI benötigt zwingend einen Key", job.Name);
|
_logger.LogWarning("Notify-Agent-Reaktion für Cronjob '{Name}' übersprungen: kein OpenAI-Key (weder zugewiesen noch Server-Setting 'OpenAiApiKey')", job.Name);
|
||||||
canRunNotifyAgent = false;
|
canRunNotifyAgent = false;
|
||||||
}
|
}
|
||||||
// sonst: nvidia/gemini/minimax/pollinations → notifyCtx.ApiToken bleibt leer (Server-Key-Auflösung).
|
// sonst: nvidia/gemini/minimax/pollinations → notifyCtx.ApiToken bleibt leer (Server-Key-Auflösung).
|
||||||
|
|||||||
+87
-16
@@ -149,37 +149,99 @@ public class OpenAiService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Lädt Modelle von allen Provider-APIs und speichert sie im Cache.
|
/// Ergebnis eines Modell-Refreshs pro Provider – damit der Aufrufer (und die UI)
|
||||||
|
/// sieht, was tatsächlich geladen und was übersprungen wurde.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task RefreshModelsFromApis()
|
public class ProviderRefreshResult
|
||||||
{
|
{
|
||||||
|
public string Provider { get; set; }
|
||||||
|
public bool Loaded { get; set; } // true = frisch von der API geladen
|
||||||
|
public int Count { get; set; } // Anzahl Modelle nach dem Refresh
|
||||||
|
public string Note { get; set; } // Grund bei Skip/Fehler, sonst ""
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Löst den OpenAI-Key auf: bevorzugt das Server-Setting (OpenAiApiKey, analog
|
||||||
|
/// Gemini/MiniMax), sonst der erste aktive 'openai'-Key aus der api_keys-Tabelle.
|
||||||
|
/// </summary>
|
||||||
|
public bool HasServerOpenAiKey() => !string.IsNullOrEmpty(_settings.Get("OpenAiApiKey"));
|
||||||
|
|
||||||
|
public async Task<string> ResolveOpenAiKey()
|
||||||
|
{
|
||||||
|
var fromSettings = _settings.Get("OpenAiApiKey");
|
||||||
|
if (!string.IsNullOrEmpty(fromSettings)) return fromSettings;
|
||||||
|
return await _db.GetFirstActiveKeyByProvider("openai");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Chat-Modelle von OpenAI filtern: gpt-*, chatgpt-* und die o-Reihe.
|
||||||
|
/// Wichtig: auch bare IDs ohne Bindestrich (o1, o3, künftig o5) – der alte
|
||||||
|
/// Filter (StartsWith("o3-")) hat die verschluckt.
|
||||||
|
/// </summary>
|
||||||
|
private static bool IsOpenAiChatModel(string id)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(id)) return false;
|
||||||
|
if (id.StartsWith("gpt-", StringComparison.OrdinalIgnoreCase)) return true;
|
||||||
|
if (id.StartsWith("chatgpt-", StringComparison.OrdinalIgnoreCase)) return true;
|
||||||
|
// o-Reihe: 'o' + Ziffer, danach Ende oder '-'/'.' (o1, o3, o3-mini, o4-mini, o5.1)
|
||||||
|
if (id.Length >= 2 && (id[0] == 'o' || id[0] == 'O') && char.IsDigit(id[1]))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lädt Modelle von allen Provider-APIs und speichert sie im Cache.
|
||||||
|
/// Gibt pro Provider zurück, ob geladen oder übersprungen wurde.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<List<ProviderRefreshResult>> RefreshModelsFromApis()
|
||||||
|
{
|
||||||
|
var report = new List<ProviderRefreshResult>();
|
||||||
|
void Report(string provider, bool loaded, int count, string note = "")
|
||||||
|
{
|
||||||
|
report.Add(new ProviderRefreshResult { Provider = provider, Loaded = loaded, Count = count, Note = note });
|
||||||
|
Console.WriteLine($"[OpenAiService] {provider}: {(loaded ? "geladen" : "übersprungen")} – {count} Modelle{(string.IsNullOrEmpty(note) ? "" : $" ({note})")}");
|
||||||
|
}
|
||||||
|
|
||||||
var nvidiaApiKey = _settings.Get("NvidiaApiKey");
|
var nvidiaApiKey = _settings.Get("NvidiaApiKey");
|
||||||
if (!string.IsNullOrEmpty(nvidiaApiKey))
|
if (!string.IsNullOrEmpty(nvidiaApiKey))
|
||||||
_nvidiaModels = await FetchModels("https://integrate.api.nvidia.com/v1/models", "Nvidia", nvidiaApiKey);
|
_nvidiaModels = await FetchModels("https://integrate.api.nvidia.com/v1/models", "Nvidia", nvidiaApiKey);
|
||||||
else
|
else
|
||||||
_nvidiaModels = await FetchModels("https://g4f.space/api/nvidia/models", "Nvidia");
|
_nvidiaModels = await FetchModels("https://g4f.space/api/nvidia/models", "Nvidia");
|
||||||
|
Report("nvidia", _nvidiaModels.Count > 0, _nvidiaModels.Count,
|
||||||
|
_nvidiaModels.Count == 0 ? "API lieferte keine Modelle" : "");
|
||||||
|
|
||||||
_pollinationsModels = await FetchModels("https://text.pollinations.ai/models", "Pollinations");
|
_pollinationsModels = await FetchModels("https://text.pollinations.ai/models", "Pollinations");
|
||||||
|
Report("pollinations", _pollinationsModels.Count > 0, _pollinationsModels.Count,
|
||||||
|
_pollinationsModels.Count == 0 ? "API lieferte keine Modelle" : "");
|
||||||
|
|
||||||
var geminiApiKey = _settings.Get("GeminiApiKey");
|
var geminiApiKey = _settings.Get("GeminiApiKey");
|
||||||
if (!string.IsNullOrEmpty(geminiApiKey))
|
if (!string.IsNullOrEmpty(geminiApiKey))
|
||||||
|
{
|
||||||
_geminiModels = await FetchModels("https://generativelanguage.googleapis.com/v1beta/openai/models", "Gemini", geminiApiKey);
|
_geminiModels = await FetchModels("https://generativelanguage.googleapis.com/v1beta/openai/models", "Gemini", geminiApiKey);
|
||||||
|
Report("gemini", _geminiModels.Count > 0, _geminiModels.Count,
|
||||||
|
_geminiModels.Count == 0 ? "Key gesetzt, aber API lieferte nichts" : "");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
Console.WriteLine("[OpenAiService] GeminiApiKey nicht konfiguriert – Gemini-Modelle nicht geladen.");
|
Report("gemini", false, _geminiModels.Count, "GeminiApiKey nicht konfiguriert – alte Liste bleibt");
|
||||||
|
|
||||||
// OpenAI: Key aus DB holen (erster aktiver OpenAI-Key)
|
// OpenAI: Key aus Settings (OpenAiApiKey), sonst aus der api_keys-Tabelle.
|
||||||
var openaiKey = await _db.GetFirstActiveKeyByProvider("openai");
|
var openaiKey = await ResolveOpenAiKey();
|
||||||
if (!string.IsNullOrEmpty(openaiKey))
|
if (!string.IsNullOrEmpty(openaiKey))
|
||||||
{
|
{
|
||||||
var allOpenAi = await FetchModels("https://api.openai.com/v1/models", "OpenAI", openaiKey);
|
var allOpenAi = await FetchModels("https://api.openai.com/v1/models", "OpenAI", openaiKey);
|
||||||
// Nur Chat-Modelle filtern (gpt-*, o1-*, o3-*, o4-*, chatgpt-*)
|
if (allOpenAi.Count > 0)
|
||||||
_openaiModels = allOpenAi
|
{
|
||||||
.Where(m => m.Id.StartsWith("gpt-") || m.Id.StartsWith("o1-") || m.Id.StartsWith("o3-") || m.Id.StartsWith("o4-") || m.Id.StartsWith("chatgpt-"))
|
_openaiModels = allOpenAi
|
||||||
.OrderBy(m => m.Id)
|
.Where(m => IsOpenAiChatModel(m.Id))
|
||||||
.ToList();
|
.OrderBy(m => m.Id)
|
||||||
Console.WriteLine($"[OpenAiService] {_openaiModels.Count} OpenAI Chat-Modelle gefiltert (von {allOpenAi.Count} total).");
|
.ToList();
|
||||||
|
Report("openai", true, _openaiModels.Count, $"gefiltert aus {allOpenAi.Count} total");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Report("openai", false, _openaiModels.Count, "Key gesetzt, aber /v1/models lieferte nichts (Key ungültig?) – alte Liste bleibt");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
Console.WriteLine("[OpenAiService] Kein aktiver OpenAI API-Key in DB – OpenAI-Modelle nicht geladen.");
|
Report("openai", false, _openaiModels.Count, "kein OpenAI-Key (Setting 'OpenAiApiKey' oder api_keys-Tabelle) – alte Liste bleibt");
|
||||||
|
|
||||||
// MiniMax: Key aus Settings (MinimaxApiKey). Versuche /v1/models, sonst statische Fallback-Liste.
|
// MiniMax: Key aus Settings (MinimaxApiKey). Versuche /v1/models, sonst statische Fallback-Liste.
|
||||||
var minimaxKey = _settings.Get("MinimaxApiKey");
|
var minimaxKey = _settings.Get("MinimaxApiKey");
|
||||||
@@ -187,22 +249,26 @@ public class OpenAiService
|
|||||||
{
|
{
|
||||||
var fetched = await FetchModels("https://api.minimax.io/v1/models", "MiniMax", minimaxKey);
|
var fetched = await FetchModels("https://api.minimax.io/v1/models", "MiniMax", minimaxKey);
|
||||||
_minimaxModels = fetched.Count > 0 ? fetched : MinimaxFallbackModels;
|
_minimaxModels = fetched.Count > 0 ? fetched : MinimaxFallbackModels;
|
||||||
if (fetched.Count == 0)
|
Report("minimax", fetched.Count > 0, _minimaxModels.Count,
|
||||||
Console.WriteLine($"[OpenAiService] MiniMax /v1/models leer/nicht erreichbar – nutze Fallback-Liste ({_minimaxModels.Count} Modelle).");
|
fetched.Count == 0 ? "/v1/models leer – Fallback-Liste" : "");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Ohne Key: statische Fallback-Liste anbieten, damit das Dropdown nicht leer ist.
|
// Ohne Key: statische Fallback-Liste anbieten, damit das Dropdown nicht leer ist.
|
||||||
_minimaxModels = MinimaxFallbackModels;
|
_minimaxModels = MinimaxFallbackModels;
|
||||||
Console.WriteLine($"[OpenAiService] MinimaxApiKey nicht konfiguriert – nutze Fallback-Liste ({_minimaxModels.Count} Modelle).");
|
Report("minimax", false, _minimaxModels.Count, "MinimaxApiKey nicht konfiguriert – Fallback-Liste");
|
||||||
}
|
}
|
||||||
|
|
||||||
// z.ai / GLM: Key aus Settings (ZaiApiKey). OpenAI-kompatibler /models-Endpoint von z.ai.
|
// z.ai / GLM: Key aus Settings (ZaiApiKey). OpenAI-kompatibler /models-Endpoint von z.ai.
|
||||||
var zaiKey = _settings.Get("ZaiApiKey");
|
var zaiKey = _settings.Get("ZaiApiKey");
|
||||||
if (!string.IsNullOrEmpty(zaiKey))
|
if (!string.IsNullOrEmpty(zaiKey))
|
||||||
|
{
|
||||||
_glmModels = await FetchModels("https://api.z.ai/api/paas/v4/models", "GLM (z.ai)", zaiKey);
|
_glmModels = await FetchModels("https://api.z.ai/api/paas/v4/models", "GLM (z.ai)", zaiKey);
|
||||||
|
Report("glm", _glmModels.Count > 0, _glmModels.Count,
|
||||||
|
_glmModels.Count == 0 ? "Key gesetzt, aber API lieferte nichts" : "");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
Console.WriteLine("[OpenAiService] ZaiApiKey nicht konfiguriert – GLM-Modelle nicht geladen.");
|
Report("glm", false, _glmModels.Count, "ZaiApiKey nicht konfiguriert – alte Liste bleibt");
|
||||||
|
|
||||||
// Cache speichern
|
// Cache speichern
|
||||||
try
|
try
|
||||||
@@ -224,6 +290,8 @@ public class OpenAiService
|
|||||||
{
|
{
|
||||||
Console.WriteLine($"[OpenAiService] Fehler beim Speichern des Modelle-Cache: {ex.Message}");
|
Console.WriteLine($"[OpenAiService] Fehler beim Speichern des Modelle-Cache: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return report;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<ProviderModel>> FetchModels(string url, string providerName, string apiKey = "")
|
private async Task<List<ProviderModel>> FetchModels(string url, string providerName, string apiKey = "")
|
||||||
@@ -1530,6 +1598,9 @@ public class OpenAiService
|
|||||||
if (provider == "minimax" && string.IsNullOrEmpty(providerConfig.ApiKey))
|
if (provider == "minimax" && string.IsNullOrEmpty(providerConfig.ApiKey))
|
||||||
providerConfig = (providerConfig.BaseUrl, _settings.Get("MinimaxApiKey"));
|
providerConfig = (providerConfig.BaseUrl, _settings.Get("MinimaxApiKey"));
|
||||||
|
|
||||||
|
if (provider == "openai" && string.IsNullOrEmpty(providerConfig.ApiKey))
|
||||||
|
providerConfig = (providerConfig.BaseUrl, _settings.Get("OpenAiApiKey"));
|
||||||
|
|
||||||
return (provider, modelId, providerConfig);
|
return (provider, modelId, providerConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ public class ServerSettingsService
|
|||||||
new SettingDefinition( "HuggingFaceToken", "string", "", "HuggingFace Token (optional, erhöht Rate-Limits)", "password" ),
|
new SettingDefinition( "HuggingFaceToken", "string", "", "HuggingFace Token (optional, erhöht Rate-Limits)", "password" ),
|
||||||
new SettingDefinition( "NvidiaApiKey", "string", "", "Nvidia API-Key (direkt statt g4f.space Proxy)", "password" ),
|
new SettingDefinition( "NvidiaApiKey", "string", "", "Nvidia API-Key (direkt statt g4f.space Proxy)", "password" ),
|
||||||
new SettingDefinition( "MinimaxApiKey", "string", "", "MiniMax API-Key (für minimax-Provider im Chat)", "password" ),
|
new SettingDefinition( "MinimaxApiKey", "string", "", "MiniMax API-Key (für minimax-Provider im Chat)", "password" ),
|
||||||
|
new SettingDefinition( "OpenAiApiKey", "string", "", "OpenAI API-Key (für openai-Provider im Chat + Modell-Liste)", "password" ),
|
||||||
new SettingDefinition( "ZaiApiKey", "string", "", "z.ai API-Key (für GLM-Modelle über die Claude CLI)", "password" ),
|
new SettingDefinition( "ZaiApiKey", "string", "", "z.ai API-Key (für GLM-Modelle über die Claude CLI)", "password" ),
|
||||||
new SettingDefinition( "ScannerKeyTypes", "string_array", "anthropic_oauth,krea_ai", "Key-Scanner: Aktive Key-Typen", "checkboxes" ),
|
new SettingDefinition( "ScannerKeyTypes", "string_array", "anthropic_oauth,krea_ai", "Key-Scanner: Aktive Key-Typen", "checkboxes" ),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -201,9 +201,11 @@ public class TelegramService
|
|||||||
|
|
||||||
useCliFallback = true;
|
useCliFallback = true;
|
||||||
}
|
}
|
||||||
else if (ctx.Provider == "openai")
|
else if (ctx.Provider == "openai" && string.IsNullOrEmpty(_settings.Get("OpenAiApiKey")))
|
||||||
{
|
{
|
||||||
await SendMessage(chatId, botToken, "❌ Kein API-Key für diesen Agent zugewiesen. OpenAI benötigt zwingend einen Key – der Bot-Besitzer muss im Web-UI einen Key zuweisen.");
|
// Ohne Agent-Key greift der Server-Key aus dem Setting 'OpenAiApiKey'
|
||||||
|
// (analog Gemini/MiniMax). Fehlt auch der, ist OpenAI nicht nutzbar.
|
||||||
|
await SendMessage(chatId, botToken, "❌ Kein OpenAI-Key vorhanden: weder dem Agent zugewiesen noch als Server-Setting 'OpenAiApiKey' gesetzt.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// sonst: nvidia/gemini/minimax/pollinations → ctx.ApiToken bleibt leer (Server-Key-Auflösung).
|
// sonst: nvidia/gemini/minimax/pollinations → ctx.ApiToken bleibt leer (Server-Key-Auflösung).
|
||||||
|
|||||||
@@ -66,10 +66,17 @@ registerPage('agent', {
|
|||||||
refreshModelsBtn.disabled = true;
|
refreshModelsBtn.disabled = true;
|
||||||
const prevValue = modelEl.value;
|
const prevValue = modelEl.value;
|
||||||
try {
|
try {
|
||||||
await apiRefreshModels();
|
const res = await apiRefreshModels();
|
||||||
await loadAllModels();
|
await loadAllModels();
|
||||||
modelEl.value = prevValue; // vorherige Auswahl wiederherstellen (falls noch vorhanden)
|
modelEl.value = prevValue; // vorherige Auswahl wiederherstellen (falls noch vorhanden)
|
||||||
showToast('Modell-Listen neu geladen');
|
// Ehrlich melden, was tatsächlich geladen wurde – ein übersprungener Provider
|
||||||
|
// (z.B. OpenAI ohne Key) darf nicht als Erfolg durchgehen.
|
||||||
|
const skipped = (res && res.providers || []).filter(p => !p.loaded);
|
||||||
|
if (skipped.length) {
|
||||||
|
showToast('Neu geladen, übersprungen: ' + skipped.map(p => p.provider + ' – ' + p.note).join('; '), 'error');
|
||||||
|
} else {
|
||||||
|
showToast('Modell-Listen neu geladen');
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showToast(err.message || 'Fehler beim Neu-Laden der Modelle', 'error');
|
showToast(err.message || 'Fehler beim Neu-Laden der Modelle', 'error');
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in new issue
Block a user