feat: Nvidia API direkt-Anbindung mit Thinking-Mode Support

Neues Server-Setting NvidiaApiKey – wenn gesetzt, wird direkt
integrate.api.nvidia.com verwendet statt g4f.space Proxy.
Thinking-Modelle (kimi-k2, qwq, deepseek-r1) erhalten automatisch
chat_template_kwargs und reasoning-Output wird als klappbare
Denkprozess-Bubble im Chat angezeigt.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MelekiandClaude Opus 4.6 committed 2026-03-25 09:09:02 +01:00
1 parent 1101bb83a0
commit 4960b57ec7
6 files changed
+176 -3

No files matched your search

+84 -3
View File
@@ -127,7 +127,11 @@ public class OpenAiService
/// </summary>
public async Task RefreshModelsFromApis()
{
_nvidiaModels = await FetchModels("https://g4f.space/api/nvidia/models", "Nvidia");
var nvidiaApiKey = _settings.Get("NvidiaApiKey");
if (!string.IsNullOrEmpty(nvidiaApiKey))
_nvidiaModels = await FetchModels("https://integrate.api.nvidia.com/v1/models", "Nvidia", nvidiaApiKey);
else
_nvidiaModels = await FetchModels("https://g4f.space/api/nvidia/models", "Nvidia");
_pollinationsModels = await FetchModels("https://text.pollinations.ai/models", "Pollinations");
var geminiApiKey = _settings.Get("GeminiApiKey");
@@ -303,6 +307,9 @@ public class OpenAiService
["tool_choice"] = "auto"
};
if (provider == "nvidia" && IsNvidiaThinkingModel(modelId))
payload["chat_template_kwargs"] = new { thinking = true };
var jsonPayload = JsonSerializer.Serialize(payload);
var httpContent = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
@@ -333,10 +340,12 @@ public class OpenAiService
using var reader = new System.IO.StreamReader(stream);
var fullText = new StringBuilder();
var thinkingText = new StringBuilder();
// Tool-Call Akkumulatoren: id → (name, arguments)
var toolCallAccumulators = new Dictionary<int, (string Id, string Name, StringBuilder Args, string ThoughtSignature)>();
string thoughtSignature = null;
var textStarted = false;
var thinkingStarted = false;
var stopReason = "end_turn";
string line;
@@ -367,12 +376,34 @@ public class OpenAiService
if (!choice.TryGetProperty("delta", out var delta)) continue;
// Reasoning/Thinking-Delta (Nvidia Thinking-Modelle)
if (delta.TryGetProperty("reasoning", out var reasoningProp) && reasoningProp.ValueKind == JsonValueKind.String)
{
var chunk = reasoningProp.GetString();
if (!string.IsNullOrEmpty(chunk))
{
if (!thinkingStarted)
{
await SseHelper.TryWriteSse(response, "thinking_start", new { }, sseCt);
thinkingStarted = true;
}
thinkingText.Append(chunk);
await SseHelper.TryWriteSse(response, "thinking_delta", new { text = chunk }, sseCt);
}
}
// Text-Delta
if (delta.TryGetProperty("content", out var contentProp) && contentProp.ValueKind == JsonValueKind.String)
{
var chunk = contentProp.GetString();
if (!string.IsNullOrEmpty(chunk))
{
// Thinking-Block beenden falls noch offen
if (thinkingStarted)
{
await SseHelper.TryWriteSse(response, "thinking_end", new { }, sseCt);
thinkingStarted = false;
}
if (!textStarted)
{
await SseHelper.TryWriteSse(response, "text_start", new { index = 0 }, sseCt);
@@ -383,8 +414,6 @@ public class OpenAiService
}
}
// Tool-Call Deltas akkumulieren
if (delta.TryGetProperty("tool_calls", out var toolCallsProp))
{
@@ -465,10 +494,26 @@ public class OpenAiService
}
}
// Thinking-End senden falls noch offen
if (thinkingStarted)
await SseHelper.TryWriteSse(response, "thinking_end", new { }, sseCt);
// Text-End senden falls Text gestreamt wurde
if (textStarted)
await SseHelper.TryWriteSse(response, "text_end", new { }, sseCt);
// Thinking-Text in DB speichern
var finalThinking = thinkingText.ToString();
if (!string.IsNullOrEmpty(finalThinking))
{
await _db.SaveMessage(new MessageRecord
{
ChainId = chainId, Iteration = iteration,
Role = "assistant", Type = "thinking",
Content = finalThinking
});
}
// Text in DB speichern
var finalText = fullText.ToString();
if (!string.IsNullOrEmpty(finalText))
@@ -669,6 +714,9 @@ public class OpenAiService
["tool_choice"] = "auto"
};
if (provider == "nvidia" && IsNvidiaThinkingModel(modelId))
payload["chat_template_kwargs"] = new { thinking = true };
var jsonPayload = JsonSerializer.Serialize(payload);
var httpContent = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
@@ -709,6 +757,21 @@ public class OpenAiService
var message = choice.GetProperty("message");
var finishReason = choice.TryGetProperty("finish_reason", out var frProp) ? frProp.GetString() ?? "" : "";
// Reasoning/Thinking extrahieren (Nvidia Thinking-Modelle)
if (message.TryGetProperty("reasoning", out var reasoningProp) && reasoningProp.ValueKind == JsonValueKind.String)
{
var reasoning = reasoningProp.GetString() ?? "";
if (!string.IsNullOrEmpty(reasoning))
{
await _db.SaveMessage(new MessageRecord
{
ChainId = ctx.ChainId, Iteration = iterations,
Role = "assistant", Type = "thinking",
Content = reasoning
});
}
}
// Text extrahieren
if (message.TryGetProperty("content", out var contentProp) && contentProp.ValueKind == JsonValueKind.String)
{
@@ -909,12 +972,30 @@ public class OpenAiService
if (!string.IsNullOrEmpty(apiKeyOverride))
providerConfig = (providerConfig.BaseUrl, apiKeyOverride);
if (provider == "nvidia" && string.IsNullOrEmpty(providerConfig.ApiKey))
{
var nvidiaKey = _settings.Get("NvidiaApiKey");
if (!string.IsNullOrEmpty(nvidiaKey))
providerConfig = ("https://integrate.api.nvidia.com/v1", nvidiaKey);
}
if (provider == "gemini" && string.IsNullOrEmpty(providerConfig.ApiKey))
providerConfig = (providerConfig.BaseUrl, _settings.Get("GeminiApiKey"));
return (provider, modelId, providerConfig);
}
/// <summary>
/// Prüft ob ein Nvidia-Modell Thinking-Mode unterstützt (chat_template_kwargs).
/// </summary>
private static bool IsNvidiaThinkingModel(string modelId)
{
// Modelle mit Extended-Thinking-Support bei Nvidia
return modelId.Contains("kimi-k2", StringComparison.OrdinalIgnoreCase)
|| modelId.Contains("qwq", StringComparison.OrdinalIgnoreCase)
|| modelId.Contains("deepseek-r1", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Konvertiert Anthropic-Format Messages in OpenAI-kompatibles Format.
/// </summary>