fix: send_to_session für alle ExecuteTool-Pfade + generelle FixOrphanedToolUse
- AnthropicService: db/chatService/anthropicService/userId/telegramService auch im RunAgentChat (Zeile 899) und ChatNonStreaming (Zeile 1105) ExecuteTool-Aufruf durchreichen – Telegram-Flow nutzt ChatNonStreaming, hatte zuvor null-Parameter - ChatService: FixOrphanedAskUserChoice → FixOrphanedToolUse ersetzt Prüft gesamte History auf orphaned tool_use-Blöcke (nicht nur ask_user_choice am Ende) Synthetische tool_results werden eingefügt um Anthropic API 400 zu verhindern - ToolService: Debug-Logging für send_to_session nach /tmp/gott_send_to_session.log
This commit is contained in:
1 parent
2486eee047
commit
e374c70c89
3 files changed
+98
-40
No files matched your search
+80
-38
@@ -593,8 +593,8 @@ 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: Offene tool_use-Blöcke ohne tool_result → synthetische tool_results einfügen
|
||||
FixOrphanedToolUse(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,
|
||||
@@ -681,49 +681,91 @@ 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.
|
||||
/// Prüft die gesamte History auf tool_use-Blöcke ohne passendes tool_result in der nächsten user-Message.
|
||||
/// Falls gefunden, werden synthetische tool_results eingefügt – verhindert Anthropic API 400.
|
||||
/// Betrifft alle Tools, nicht nur ask_user_choice (z.B. wenn eine Chain abbricht ohne tool_result zu speichern).
|
||||
/// </summary>
|
||||
private static void FixOrphanedAskUserChoice(List<object> history)
|
||||
private static void FixOrphanedToolUse(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)
|
||||
for (int i = 0; i < history.Count; i++)
|
||||
{
|
||||
var blockJson = JsonSerializer.Serialize(block);
|
||||
JsonElement el;
|
||||
try { el = JsonSerializer.Deserialize<JsonElement>(blockJson); }
|
||||
catch { continue; }
|
||||
if (history[i] is not Dictionary<string, object> assistantDict) continue;
|
||||
if (!assistantDict.TryGetValue("role", out var role) || role?.ToString() != "assistant") continue;
|
||||
if (!assistantDict.TryGetValue("content", out var content) || content is not List<object> contentList) 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() ?? "");
|
||||
// Alle tool_use IDs in dieser assistant-Message sammeln
|
||||
var toolUseIds = new List<string>();
|
||||
foreach (var block in contentList)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(block);
|
||||
JsonElement el;
|
||||
try { el = JsonSerializer.Deserialize<JsonElement>(json); }
|
||||
catch { continue; }
|
||||
|
||||
if (!el.TryGetProperty("type", out var t) || t.GetString() != "tool_use") continue;
|
||||
if (!el.TryGetProperty("id", out var idProp)) continue;
|
||||
var id = idProp.GetString();
|
||||
if (!string.IsNullOrEmpty(id))
|
||||
toolUseIds.Add(id);
|
||||
}
|
||||
|
||||
if (toolUseIds.Count == 0) continue;
|
||||
|
||||
// Prüfen ob die nächste user-Message die tool_results enthält
|
||||
var coveredIds = new HashSet<string>();
|
||||
if (i + 1 < history.Count &&
|
||||
history[i + 1] is Dictionary<string, object> nextDict &&
|
||||
nextDict.TryGetValue("role", out var nextRole) && nextRole?.ToString() == "user" &&
|
||||
nextDict.TryGetValue("content", out var nextContent) && nextContent is List<object> nextContentList)
|
||||
{
|
||||
foreach (var block in nextContentList)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(block);
|
||||
JsonElement el;
|
||||
try { el = JsonSerializer.Deserialize<JsonElement>(json); }
|
||||
catch { continue; }
|
||||
|
||||
if (!el.TryGetProperty("type", out var t) || t.GetString() != "tool_result") continue;
|
||||
if (!el.TryGetProperty("tool_use_id", out var tid)) continue;
|
||||
var toolUseId = tid.GetString();
|
||||
if (!string.IsNullOrEmpty(toolUseId))
|
||||
coveredIds.Add(toolUseId);
|
||||
}
|
||||
}
|
||||
|
||||
// Orphaned IDs = tool_use ohne passendes tool_result
|
||||
var orphanedIds = toolUseIds.Where(id => !coveredIds.Contains(id)).ToList();
|
||||
if (orphanedIds.Count == 0) continue;
|
||||
|
||||
// Synthetische tool_results bauen
|
||||
var syntheticResults = orphanedIds.Select(id => (object)new
|
||||
{
|
||||
type = "tool_result",
|
||||
tool_use_id = id,
|
||||
content = "Tool-Ergebnis nicht verfügbar (Chain wurde unterbrochen)."
|
||||
}).ToList();
|
||||
|
||||
// Falls nächste Message eine user-Message ist, synthetische results dort prependen
|
||||
if (i + 1 < history.Count &&
|
||||
history[i + 1] is Dictionary<string, object> existingUser &&
|
||||
existingUser.TryGetValue("role", out var er) && er?.ToString() == "user" &&
|
||||
existingUser.TryGetValue("content", out var ec) && ec is List<object> existingContent)
|
||||
{
|
||||
existingContent.InsertRange(0, syntheticResults);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Neue user-Message mit synthetischen results einfügen
|
||||
history.Insert(i + 1, new Dictionary<string, object>
|
||||
{
|
||||
["role"] = "user",
|
||||
["content"] = syntheticResults
|
||||
});
|
||||
i++; // Eingefügte Message überspringen
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
Reference in new issue
Block a user