fix(openai): Auto-Context, Tool-Result-Matching, Payload-Preview, leere Bubbles

- Auto-Context (Embedding-Suche) für OpenAI-Provider aktiviert: BuildAutoContextAsync
  auf internal static refactored und in StreamChat + ChatNonStreaming integriert
- Tool-Results mit Role="tool" (OpenAI-Konvention) werden in BuildHistoryFromDb
  jetzt korrekt erkannt (vorher nur Role="user"/Anthropic)
- Payload-Preview zeigt für OpenAI-Provider echtes API-Format (function-Wrapper,
  tool_choice:"auto", max_tokens, stream_options, extra_body)
- Tool-Input (Argumente) wird im SSE tool_end-Event mitgesendet (fehlte im OpenAI-Pfad)
- Leere Bubbles durch Whitespace-only Text nach </think>-Extraktion behoben
  (Trim in DB-Save, BuildHistoryFromDb und Frontend-Guard)
- History-Toggle-Bug: var→const in for-Loop (Block-Scoping für Closures),
  tool_result-Matching per data-tool-call-id statt blindes letztes Element
- Non-Streaming max_tokens für MiniMax auf 32768 + extra_body.reasoning_split

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MelekiandClaude Opus 4.6 committed 2026-05-14 11:16:40 +02:00
1 parent ecddbe5f36
commit 1fe04025d3
9 files changed
+164 -42

No files matched your search

+52 -6
View File
@@ -317,8 +317,54 @@ public static class SessionEndpoint
var toolChoice = hasTools || !string.IsNullOrEmpty(agent.WebSearchVersion) ? "any" : null;
var effectiveChoice = toolChoice == "any" ? "any" : "auto";
// Payload zusammenbauen (Anthropic-Format)
var payload = new Dictionary<string, object>
// OpenAI-kompatible Provider: Payload im tatsächlichen API-Format anzeigen
if (previewProvider)
{
var (provider, _) = OpenAiService.ParseModelString(modelId);
var maxTokens = provider == "minimax" ? 32768 : 16384;
// Messages konvertieren (Anthropic → OpenAI-Format)
var openAiMessages = OpenAiService.BuildApiMessages(systemPromptText, messages);
// Tools konvertieren (input_schema → function/parameters)
var openAiTools = tools.Count > 0
? OpenAiService.ConvertTo_openAiToolDefs(tools.ToArray())
: new List<object>();
var payload = new Dictionary<string, object>
{
["model"] = modelId,
["max_tokens"] = maxTokens,
["messages"] = openAiMessages,
["stream"] = true,
["stream_options"] = new { include_usage = true }
};
if (openAiTools.Count > 0)
{
payload["tools"] = openAiTools;
payload["tool_choice"] = "auto";
}
if (provider == "minimax")
payload["extra_body"] = new { reasoning_split = true };
if (provider == "nvidia")
{
var nvidiaModelId = modelId.Contains(":") ? modelId.Split(':')[1] : modelId;
if (nvidiaModelId.Contains("kimi-k2", StringComparison.OrdinalIgnoreCase)
|| nvidiaModelId.Contains("qwq", StringComparison.OrdinalIgnoreCase)
|| nvidiaModelId.Contains("deepseek-r1", StringComparison.OrdinalIgnoreCase))
payload["chat_template_kwargs"] = new { thinking = true };
}
ctx.Response.ContentType = "application/json; charset=utf-8";
await ctx.Response.WriteAsync(JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true }));
return;
}
// Anthropic-Format Payload
var anthropicPayload = new Dictionary<string, object>
{
["model"] = modelId,
["max_tokens"] = 16384,
@@ -327,16 +373,16 @@ public static class SessionEndpoint
};
if (systemPrompt.Count > 0)
payload["system"] = systemPrompt;
anthropicPayload["system"] = systemPrompt;
if (tools.Count > 0)
{
payload["tools"] = tools;
payload["tool_choice"] = new { type = effectiveChoice };
anthropicPayload["tools"] = tools;
anthropicPayload["tool_choice"] = new { type = effectiveChoice };
}
ctx.Response.ContentType = "application/json; charset=utf-8";
await ctx.Response.WriteAsync(JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true }));
await ctx.Response.WriteAsync(JsonSerializer.Serialize(anthropicPayload, new JsonSerializerOptions { WriteIndented = true }));
}
private static async Task HandleStreaming(HttpContext ctx, DatabaseService db, StreamingTracker streamingTracker)