fix: synthetisches tool_result bei übergangenem ask_user_choice
Wenn der User Text schreibt statt eine Choice zu wählen, endet die vorherige Chain mit einem offenen ask_user_choice tool_use ohne passendes tool_result. Anthropic API wirft dann 400. FixOrphanedAskUserChoice() erkennt diesen Fall in BuildHistoryFromDb und fügt ein synthetisches tool_result ein.
This commit is contained in:
1 parent
148d1459a3
commit
bdb7f54c74
1 file changed
+49
@@ -593,6 +593,9 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop?
|
||||
}
|
||||
}
|
||||
|
||||
// Post-Processing: Offener ask_user_choice tool_use ohne tool_result → synthetisches tool_result einfügen
|
||||
FixOrphanedAskUserChoice(history);
|
||||
|
||||
// Post-Processing: Orphaned web_search server_tool_use entfernen
|
||||
// Bei alten DB-Einträgen wurde web_search_tool_result nur als Sources-Array gespeichert,
|
||||
// nicht als Raw-JSON → BuildHistoryFromDb überspringt sie (Array-Check) →
|
||||
@@ -677,6 +680,52 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop?
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prüft ob die History mit einem offenen ask_user_choice tool_use endet (kein passendes tool_result).
|
||||
/// Falls ja, wird ein synthetisches tool_result eingefügt – verhindert Anthropic API 400.
|
||||
/// Passiert wenn der User einfach Text schreibt statt eine Choice zu wählen.
|
||||
/// </summary>
|
||||
private static void FixOrphanedAskUserChoice(List<object> history)
|
||||
{
|
||||
if (history.Count == 0) return;
|
||||
|
||||
var last = history[history.Count - 1];
|
||||
if (last is not Dictionary<string, object> lastDict) return;
|
||||
if (!lastDict.TryGetValue("role", out var lastRole) || lastRole?.ToString() != "assistant") return;
|
||||
if (!lastDict.TryGetValue("content", out var lastContent) || lastContent is not List<object> lastContentList) return;
|
||||
|
||||
// Sammle alle ask_user_choice tool_use IDs die noch kein tool_result haben
|
||||
var orphanedIds = new List<string>();
|
||||
foreach (var block in lastContentList)
|
||||
{
|
||||
var blockJson = JsonSerializer.Serialize(block);
|
||||
JsonElement el;
|
||||
try { el = JsonSerializer.Deserialize<JsonElement>(blockJson); }
|
||||
catch { continue; }
|
||||
|
||||
if (!el.TryGetProperty("type", out var typeProp) || typeProp.GetString() != "tool_use") continue;
|
||||
if (!el.TryGetProperty("name", out var nameProp) || nameProp.GetString() != ToolService.AskUserChoiceToolName) continue;
|
||||
if (!el.TryGetProperty("id", out var idProp)) continue;
|
||||
orphanedIds.Add(idProp.GetString() ?? "");
|
||||
}
|
||||
|
||||
if (orphanedIds.Count == 0) return;
|
||||
|
||||
// Synthetisches tool_result einfügen
|
||||
var syntheticResults = orphanedIds.Select(id => (object)new
|
||||
{
|
||||
type = "tool_result",
|
||||
tool_use_id = id,
|
||||
content = "Der User hat keine Option gewählt und stattdessen eine neue Nachricht geschrieben."
|
||||
}).ToList();
|
||||
|
||||
history.Add(new Dictionary<string, object>
|
||||
{
|
||||
["role"] = "user",
|
||||
["content"] = syntheticResults
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Baut einen SubAgentContext für Preparer/Review/Finalizer.
|
||||
/// Gemeinsame Logik: Agent laden, Model auflösen, SystemPrompt bauen, ToolChoice bestimmen.
|
||||
|
||||
Reference in new issue
Block a user