fix(openai): ask_user_choice-Intercept, Interleaved Thinking in History, MiniMax-Debug-Log
OpenAI-kompatible Provider (MiniMax, DeepSeek-R1, QwQ, Kimi K2, etc.) verloren bei
jeder neuen Iteration ihre eigene Chain-of-Thought, weil thinking-Messages aus der
DB-History rausgefiltert wurden. Folge: Modell kündigt Action an, ruft Tool aber
nicht auf → Stream endet mit finish_reason:stop ohne Tool-Call. Zusätzlich fehlte
der ask_user_choice-Intercept (Tool wurde als unbekannt abgelehnt) und es gab keine
Diagnose-Möglichkeit für unerwartete Stop-Verhalten.
- ChatService.BuildHistoryFromDb(includeThinking): neuer Parameter, fügt
thinking-Messages als {type:"thinking"}-Block in die Assistant-Message ein.
Text/Tool-Use derselben Iteration werden via AppendToLastMessage angehängt
→ ein Assistant-Block enthält [thinking, text, tool_use…].
- OpenAiService.ConvertMessage + StreamChat + ChatNonStreaming: Thinking als
<think>...</think>-Prefix dem Assistant-Content voranstellen (Per-Iteration und
beim DB-History-Aufbau). Konvention von MiniMax-Docs, wird auch von DeepSeek-R1,
QwQ, Kimi K2 verstanden.
- ChatEndpoint, SessionEndpoint, CronjobService, TelegramService, ToolService:
includeThinking je nach Provider durchgereicht. Anthropic-Pfad bleibt aus
(lehnt thinking-Blöcke ohne Signature ab).
- OpenAiService: ask_user_choice-Intercept im Streaming-Pfad (analog
AnthropicService.Streaming) – Tool nicht ausführen, SSE user_choice rausschicken,
mit stopReason awaiting_user_choice finalisieren und return.
- OpenAiService: MiniMax-SSE-Debug-Log in data/openai-debug/ (JSONL: Header +
Roh-data-Zeilen + Summary mit promptTokens/completionTokens/finishReason/
toolCalls/duration) – nur für provider=="minimax", nicht-blockierend.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
1 parent
d66f8d2e73
commit
ecddbe5f36
7 files changed
+226
-20
No files matched your search
@@ -185,8 +185,13 @@ public static class ChatEndpoint
|
||||
} // !skipPreparer
|
||||
}
|
||||
|
||||
// History für den Session-Agent: OHNE Sub-Agent-Messages (saubere History)
|
||||
var messages = await chatService.BuildHistoryFromDb(sessionId, promptBlocks: ctx.PromptBlocks);
|
||||
// History für den Session-Agent: OHNE Sub-Agent-Messages (saubere History).
|
||||
// includeThinking=true für OpenAI-kompatible Provider → Thinking wird als
|
||||
// <think>...</think>-Prefix dem Assistant-Text vorangestellt (laut MiniMax-Docs essentiell
|
||||
// für Tool-Calling-Reliability / Interleaved Thinking). Anthropic-Pfad braucht das nicht,
|
||||
// weil dort thinking-Blöcke nur mit Signatur akzeptiert werden (die wir nicht persistieren).
|
||||
var includeThinkingForProvider = !string.IsNullOrEmpty(ctx.Provider);
|
||||
var messages = await chatService.BuildHistoryFromDb(sessionId, promptBlocks: ctx.PromptBlocks, includeThinking: includeThinkingForProvider);
|
||||
|
||||
// Bei Choice-Response: toolChoice auf "auto" setzen, damit Claude frei antworten kann
|
||||
var effectiveToolChoice = isChoiceResponse ? "auto" : ctx.ToolChoice;
|
||||
|
||||
@@ -287,8 +287,9 @@ public static class SessionEndpoint
|
||||
// Model
|
||||
var modelId = ChatService.ResolveModelId(agent.Model);
|
||||
|
||||
// Messages aus der DB (exakt wie beim Chat)
|
||||
var messages = await chatService.BuildHistoryFromDb(session.SessionId, promptBlocks: promptBlocks);
|
||||
// Messages aus der DB (exakt wie beim Chat) – includeThinking je nach Provider
|
||||
var previewProvider = OpenAiService.IsOpenAiProvider(modelId);
|
||||
var messages = await chatService.BuildHistoryFromDb(session.SessionId, promptBlocks: promptBlocks, includeThinking: previewProvider);
|
||||
|
||||
// Tools (exakt wie AddToolsToPayload es machen würde)
|
||||
var hasWorkspace = !string.IsNullOrEmpty(workspace);
|
||||
|
||||
+26
-6
@@ -232,7 +232,7 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop?
|
||||
/// includeTag: Wenn gesetzt, werden Messages mit diesem Tag NICHT ausgefiltert
|
||||
/// (z.B. "preparer" damit der Preparer seine eigenen vorherigen Messages sieht).
|
||||
/// </summary>
|
||||
public async Task<List<object>> BuildHistoryFromDb(string sessionId, string includeTag = null, List<PromptBlock> promptBlocks = null)
|
||||
public async Task<List<object>> BuildHistoryFromDb(string sessionId, string includeTag = null, List<PromptBlock> promptBlocks = null, bool includeThinking = false)
|
||||
{
|
||||
var chainsList = (await _db.GetActiveChains(sessionId)).ToList();
|
||||
// chronologisch sortieren (älteste zuerst)
|
||||
@@ -359,15 +359,35 @@ Für welche Plattform soll die App entwickelt werden? Web, Mobile oder Desktop?
|
||||
if (content.Count > 0)
|
||||
history.Add(new Dictionary<string, object> { ["role"] = "user", ["content"] = content });
|
||||
}
|
||||
else if (msg.Role == "assistant" && msg.Type == "thinking" && includeThinking)
|
||||
{
|
||||
// Thinking-Inhalt für OpenAI-kompatible Provider mitführen.
|
||||
// Wird in OpenAiService.ConvertMessage als <think>...</think>
|
||||
// dem Assistant-Text vorangestellt (laut MiniMax-Docs: "the content field
|
||||
// will contain <think> tag content, which must be preserved completely"
|
||||
// → essential für Interleaved Thinking, Tool-Calling-Reliability,
|
||||
// siehe https://www.minimax.io/news/why-is-interleaved-thinking-important-for-m2).
|
||||
// Anthropic-Pfad: includeThinking=false → Block wird übersprungen,
|
||||
// weil Anthropic-API thinking-Blöcke ohne Signature ablehnt.
|
||||
if (!string.IsNullOrEmpty(msg.Content))
|
||||
{
|
||||
// NEUE assistant message starten; text/tool_use derselben Iteration
|
||||
// werden via AppendToLastMessage angehängt → ein assistant-Block enthält
|
||||
// dann [thinking, text, tool_use…].
|
||||
history.Add(new Dictionary<string, object>
|
||||
{
|
||||
["role"] = "assistant",
|
||||
["content"] = new List<object> { new { type = "thinking", thinking = msg.Content } }
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (msg.Role == "assistant" && msg.Type == "text")
|
||||
{
|
||||
if (!string.IsNullOrEmpty(msg.Content))
|
||||
{
|
||||
history.Add(new Dictionary<string, object>
|
||||
{
|
||||
["role"] = "assistant",
|
||||
["content"] = new List<object> { new { type = "text", text = msg.Content } }
|
||||
});
|
||||
// Wenn die letzte Message bereits assistant ist (z.B. mit Thinking-Block
|
||||
// aus derselben Iteration), Text dort anhängen statt neue Message zu starten.
|
||||
AppendToLastMessage(history, "assistant", new { type = "text", text = msg.Content });
|
||||
}
|
||||
}
|
||||
else if (msg.Role == "assistant" && msg.Type == "tool_use")
|
||||
|
||||
@@ -95,8 +95,9 @@ public class CronjobService : BackgroundService
|
||||
throw new Exception($"Kein API-Key für Agent {ctx.AgentId} zugewiesen (User {job.UserId})");
|
||||
ctx.ApiToken = userApiKey.ApiKey;
|
||||
|
||||
// History aus DB laden
|
||||
var history = await _chatService.BuildHistoryFromDb(job.SessionId, promptBlocks: ctx.PromptBlocks);
|
||||
// History aus DB laden. includeThinking=true für OpenAI-kompatible Provider →
|
||||
// <think>...</think>-Prefix wird dem Assistant-Text vorangestellt.
|
||||
var history = await _chatService.BuildHistoryFromDb(job.SessionId, promptBlocks: ctx.PromptBlocks, includeThinking: !string.IsNullOrEmpty(ctx.Provider));
|
||||
|
||||
// User-Nachricht hinzufügen
|
||||
var userContent = new List<object> { new { type = "text", text = job.Prompt } };
|
||||
|
||||
+181
-6
@@ -387,6 +387,41 @@ public class OpenAiService
|
||||
using var stream = await httpResp.Content.ReadAsStreamAsync(workCt);
|
||||
using var reader = new System.IO.StreamReader(stream);
|
||||
|
||||
// Diagnose-Logging für MiniMax: jede SSE-data-Line + Header + Summary in JSONL.
|
||||
// Verzeichnis: data/openai-debug/{provider}-{ts}-{chainId8}.jsonl
|
||||
System.IO.StreamWriter debugWriter = null;
|
||||
string debugLogPath = null;
|
||||
long debugLineCount = 0;
|
||||
string lastFinishReason = null;
|
||||
if (provider == "minimax")
|
||||
{
|
||||
try
|
||||
{
|
||||
var dataDir = System.IO.Path.Combine(AppContext.BaseDirectory, "data", "openai-debug");
|
||||
System.IO.Directory.CreateDirectory(dataDir);
|
||||
var ts = DateTime.UtcNow.ToString("yyyyMMdd-HHmmss-fff");
|
||||
var chainShort = chainId.Length >= 8 ? chainId.Substring(0, 8) : chainId;
|
||||
debugLogPath = System.IO.Path.Combine(dataDir, $"{provider}-{ts}-{chainShort}.jsonl");
|
||||
debugWriter = new System.IO.StreamWriter(
|
||||
new System.IO.FileStream(debugLogPath, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.Read),
|
||||
new UTF8Encoding(false))
|
||||
{ AutoFlush = true };
|
||||
var header = JsonSerializer.Serialize(new
|
||||
{
|
||||
meta = true,
|
||||
provider,
|
||||
model = modelId,
|
||||
chainId,
|
||||
iteration,
|
||||
promptMessages = apiMessages.Count,
|
||||
maxTokens,
|
||||
started = DateTime.UtcNow.ToString("o")
|
||||
});
|
||||
await debugWriter.WriteLineAsync(header);
|
||||
}
|
||||
catch { debugWriter = null; debugLogPath = null; }
|
||||
}
|
||||
|
||||
var fullText = new StringBuilder();
|
||||
var thinkingText = new StringBuilder();
|
||||
// Tool-Call Akkumulatoren: id → (name, arguments)
|
||||
@@ -409,6 +444,19 @@ public class OpenAiService
|
||||
if (!line.StartsWith("data:")) continue;
|
||||
|
||||
var data = line[5..].Trim();
|
||||
|
||||
// Diagnose-Log: rohe data-Zeile als JSONL-Record
|
||||
if (debugWriter != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var rec = JsonSerializer.Serialize(new { t = DateTime.UtcNow.ToString("o"), raw = data });
|
||||
await debugWriter.WriteLineAsync(rec);
|
||||
debugLineCount++;
|
||||
}
|
||||
catch { /* Debug-Log darf den Stream nicht killen */ }
|
||||
}
|
||||
|
||||
if (data == "[DONE]") break;
|
||||
|
||||
JsonElement parsed;
|
||||
@@ -425,6 +473,7 @@ public class OpenAiService
|
||||
if (choice.TryGetProperty("finish_reason", out var finishProp) && finishProp.ValueKind != JsonValueKind.Null)
|
||||
{
|
||||
var fr = (finishProp.GetString() ?? "").ToLowerInvariant();
|
||||
lastFinishReason = fr;
|
||||
if (fr == "tool_calls" || fr == "function_call") stopReason = "tool_use";
|
||||
else if (fr == "stop") stopReason = "end_turn";
|
||||
else if (fr == "length") { stopReason = "max_tokens"; gotLengthFinish = true; }
|
||||
@@ -655,6 +704,41 @@ public class OpenAiService
|
||||
catch (InvalidOperationException) { /* Unerwartetes JSON-Format von Provider – Zeile überspringen */ }
|
||||
}
|
||||
|
||||
// Diagnose-Log: Summary am Ende des Streams
|
||||
if (debugWriter != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var toolCallSummaries = new List<object>();
|
||||
foreach (var (idx, (tcId, tcName, tcArgs, _)) in toolCallAccumulators)
|
||||
{
|
||||
toolCallSummaries.Add(new { idx, id = tcId, name = tcName, argsLen = tcArgs.Length });
|
||||
}
|
||||
var summary = JsonSerializer.Serialize(new
|
||||
{
|
||||
summary = true,
|
||||
chainId,
|
||||
iteration,
|
||||
promptTokens = usage.InputTokens,
|
||||
completionTokens = usage.OutputTokens,
|
||||
finishReason = lastFinishReason,
|
||||
stopReason,
|
||||
toolCalls = toolCallSummaries,
|
||||
toolCallCount = toolCallAccumulators.Count,
|
||||
textLen = fullText.Length,
|
||||
thinkingLen = thinkingText.Length,
|
||||
sseLines = debugLineCount,
|
||||
durationMs = sw.ElapsedMilliseconds,
|
||||
ended = DateTime.UtcNow.ToString("o")
|
||||
});
|
||||
await debugWriter.WriteLineAsync(summary);
|
||||
await debugWriter.FlushAsync();
|
||||
debugWriter.Dispose();
|
||||
}
|
||||
catch { try { debugWriter?.Dispose(); } catch { } }
|
||||
debugWriter = null;
|
||||
}
|
||||
|
||||
// Inline-Think-Buffer-Rest flushen (falls Stream mit partiellem Tag-Fragment endete)
|
||||
if (inlineThinkBuf.Length > 0)
|
||||
{
|
||||
@@ -769,17 +853,62 @@ public class OpenAiService
|
||||
});
|
||||
}
|
||||
|
||||
// Thinking als <think>...</think>-Prefix dem finalText voranstellen, damit
|
||||
// das Modell in nachfolgenden Iterationen seine eigene Chain-of-Thought sieht
|
||||
// (essential für MiniMax-M2, DeepSeek-R1, QwQ, Kimi K2 etc. – sonst bricht
|
||||
// Tool-Calling-Reliability ein, siehe MiniMax-Doku zu Interleaved Thinking).
|
||||
var historyContent = string.IsNullOrEmpty(finalText) ? "" : finalText;
|
||||
if (!string.IsNullOrEmpty(finalThinking))
|
||||
historyContent = $"<think>\n{finalThinking}\n</think>\n\n{historyContent}";
|
||||
|
||||
apiMessages.Add(new
|
||||
{
|
||||
role = "assistant",
|
||||
content = string.IsNullOrEmpty(finalText) ? "" : finalText,
|
||||
content = historyContent,
|
||||
tool_calls = toolCallsForHistory
|
||||
});
|
||||
|
||||
// Tool-Results sammeln
|
||||
var toolResults = new List<object>();
|
||||
bool awaitingChoice = false;
|
||||
foreach (var (_, (tcId, tcName, tcArgs, _)) in toolCallAccumulators)
|
||||
{
|
||||
// ask_user_choice: NICHT ausführen, sondern als SSE-Event an Client senden.
|
||||
// Analog zum Anthropic-Pfad (AnthropicService.Streaming.cs:546-576).
|
||||
// tool_result kommt asynchron vom Client via ChatEndpoint (ChoiceToolCallId+ChoiceResult).
|
||||
if (tcName == ToolService.AskUserChoiceToolName)
|
||||
{
|
||||
JsonElement choiceInput;
|
||||
try { choiceInput = JsonSerializer.Deserialize<JsonElement>(tcArgs.ToString()); }
|
||||
catch { choiceInput = JsonSerializer.Deserialize<JsonElement>("{}"); }
|
||||
|
||||
var question = choiceInput.TryGetProperty("question", out var qProp) ? qProp.GetString() ?? "" : "";
|
||||
var choices = new List<string>();
|
||||
if (choiceInput.TryGetProperty("choices", out var cProp))
|
||||
{
|
||||
if (cProp.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var ch in cProp.EnumerateArray())
|
||||
choices.Add(ch.GetString() ?? "");
|
||||
}
|
||||
else if (cProp.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
// Fallback: Modell hat einen einzelnen String statt Array gesendet
|
||||
choices.Add(cProp.GetString() ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
await SseHelper.TryWriteSse(response, "user_choice", new
|
||||
{
|
||||
toolCallId = tcId,
|
||||
question,
|
||||
choices
|
||||
}, sseCt);
|
||||
|
||||
awaitingChoice = true;
|
||||
continue; // Kein Execute, kein tool_result Save
|
||||
}
|
||||
|
||||
// Tool-Start SSE
|
||||
await SseHelper.TryWriteSse(response, "tool_start", new { name = tcName, id = tcId }, sseCt);
|
||||
|
||||
@@ -818,6 +947,24 @@ public class OpenAiService
|
||||
});
|
||||
}
|
||||
|
||||
// Wenn ask_user_choice aufgerufen wurde → finalize mit awaiting_user_choice und return.
|
||||
// Die Choice-Antwort kommt async vom Client und triggert eine neue Chain.
|
||||
if (awaitingChoice)
|
||||
{
|
||||
await FinalizeChain(chain, usage, iteration, "awaiting_user_choice", sw);
|
||||
await SseHelper.TryWriteSse(response, "done", new
|
||||
{
|
||||
model = modelString,
|
||||
inputTokens = usage.InputTokens,
|
||||
outputTokens = usage.OutputTokens,
|
||||
iterations = iteration,
|
||||
chainId,
|
||||
durationMs = sw.ElapsedMilliseconds,
|
||||
stopReason = "awaiting_user_choice"
|
||||
}, sseCt);
|
||||
return;
|
||||
}
|
||||
|
||||
// Tool-Results in History einfügen
|
||||
foreach (var tr in toolResults)
|
||||
apiMessages.Add(tr);
|
||||
@@ -945,11 +1092,13 @@ public class OpenAiService
|
||||
var finishReason = choice.TryGetProperty("finish_reason", out var frProp) ? frProp.GetString() ?? "" : "";
|
||||
|
||||
// Reasoning/Thinking extrahieren (Nvidia Thinking-Modelle)
|
||||
string iterationThinking = null;
|
||||
if (message.TryGetProperty("reasoning", out var reasoningProp) && reasoningProp.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var reasoning = reasoningProp.GetString() ?? "";
|
||||
if (!string.IsNullOrEmpty(reasoning))
|
||||
{
|
||||
iterationThinking = reasoning;
|
||||
await _db.SaveMessage(new MessageRecord
|
||||
{
|
||||
ChainId = ctx.ChainId, Iteration = iterations,
|
||||
@@ -982,12 +1131,18 @@ public class OpenAiService
|
||||
break;
|
||||
}
|
||||
|
||||
// Assistant-Message mit tool_calls in History
|
||||
// Assistant-Message mit tool_calls in History.
|
||||
// Thinking als <think>...</think>-Prefix dem content voranstellen, damit das Modell
|
||||
// in nachfolgenden Iterationen seine eigene Chain-of-Thought sieht.
|
||||
var assistantMsg = JsonSerializer.Deserialize<JsonElement>(message.GetRawText());
|
||||
var iterContent = message.TryGetProperty("content", out var c) && c.ValueKind == JsonValueKind.String ? c.GetString() ?? "" : "";
|
||||
if (!string.IsNullOrEmpty(iterationThinking))
|
||||
iterContent = $"<think>\n{iterationThinking}\n</think>\n\n{iterContent}";
|
||||
|
||||
apiMessages.Add(new
|
||||
{
|
||||
role = "assistant",
|
||||
content = message.TryGetProperty("content", out var c) && c.ValueKind == JsonValueKind.String ? c.GetString() ?? "" : "",
|
||||
content = iterContent,
|
||||
tool_calls = JsonSerializer.Deserialize<List<object>>(toolCallsProp.GetRawText())
|
||||
});
|
||||
|
||||
@@ -1073,6 +1228,7 @@ public class OpenAiService
|
||||
if (contentProp.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
var textParts = new StringBuilder();
|
||||
var thinkingParts = new StringBuilder();
|
||||
var toolCalls = new List<object>();
|
||||
var toolResults = new List<object>();
|
||||
|
||||
@@ -1081,7 +1237,15 @@ public class OpenAiService
|
||||
if (!block.TryGetProperty("type", out var typeProp)) continue;
|
||||
var blockType = typeProp.GetString() ?? "";
|
||||
|
||||
if (blockType == "text" && block.TryGetProperty("text", out var textProp))
|
||||
if (blockType == "thinking" && block.TryGetProperty("thinking", out var thinkingProp))
|
||||
{
|
||||
// Thinking-Inhalt sammeln; wird unten als <think>...</think>-Prefix dem
|
||||
// Assistant-Text vorangestellt. So bekommen Thinking-Modelle (MiniMax-M2,
|
||||
// DeepSeek-R1, QwQ, Kimi K2 etc.) ihre eigene Chain-of-Thought zurück, was
|
||||
// laut Provider-Docs entscheidend für Tool-Calling-Reliability ist.
|
||||
thinkingParts.Append(thinkingProp.GetString());
|
||||
}
|
||||
else if (blockType == "text" && block.TryGetProperty("text", out var textProp))
|
||||
{
|
||||
textParts.Append(textProp.GetString());
|
||||
}
|
||||
@@ -1122,14 +1286,25 @@ public class OpenAiService
|
||||
}
|
||||
}
|
||||
|
||||
// Thinking als <think>...</think>-Prefix dem Text voranstellen (MiniMax-Konvention,
|
||||
// wird auch von DeepSeek-R1, QwQ, Kimi K2 etc. verstanden).
|
||||
var thinkingStr = thinkingParts.ToString();
|
||||
string PrependThinking(string baseText)
|
||||
{
|
||||
if (string.IsNullOrEmpty(thinkingStr)) return baseText;
|
||||
var prefix = $"<think>\n{thinkingStr}\n</think>\n\n";
|
||||
return string.IsNullOrEmpty(baseText) ? prefix : prefix + baseText;
|
||||
}
|
||||
|
||||
// Assistant-Message mit tool_calls
|
||||
if (toolCalls.Count > 0)
|
||||
{
|
||||
var text = textParts.ToString().Trim();
|
||||
var combinedTc = PrependThinking(text);
|
||||
return new
|
||||
{
|
||||
role = "assistant",
|
||||
content = string.IsNullOrEmpty(text) ? "" : text,
|
||||
content = string.IsNullOrEmpty(combinedTc) ? "" : combinedTc,
|
||||
tool_calls = toolCalls
|
||||
};
|
||||
}
|
||||
@@ -1138,7 +1313,7 @@ public class OpenAiService
|
||||
if (toolResults.Count > 0)
|
||||
return toolResults; // List<object> wird im Aufrufer per OfType<List<object>> erkannt
|
||||
|
||||
var combined = textParts.ToString().Trim();
|
||||
var combined = PrependThinking(textParts.ToString().Trim());
|
||||
if (string.IsNullOrEmpty(combined)) return null;
|
||||
return new { role, content = combined };
|
||||
}
|
||||
|
||||
@@ -184,7 +184,9 @@ public class TelegramService
|
||||
|
||||
try
|
||||
{
|
||||
var history = await _chatService.BuildHistoryFromDb(session.SessionId, promptBlocks: ctx.PromptBlocks);
|
||||
// includeThinking=true für OpenAI-kompatible Provider → <think>...</think>-Prefix
|
||||
// im Assistant-Text (essential für MiniMax/DeepSeek/QwQ Tool-Calling-Reliability).
|
||||
var history = await _chatService.BuildHistoryFromDb(session.SessionId, promptBlocks: ctx.PromptBlocks, includeThinking: !string.IsNullOrEmpty(ctx.Provider));
|
||||
history.Add(new Dictionary<string, object>
|
||||
{
|
||||
["role"] = "user",
|
||||
|
||||
@@ -814,7 +814,9 @@ public class ToolService
|
||||
|
||||
// History aufbauen – User-Message NICHT in DB speichern (wie Cronjob)
|
||||
// Stattdessen als kontextualisierte Nachricht in die History injizieren
|
||||
var history = await ctx.ChatService.BuildHistoryFromDb(targetSessionId, promptBlocks: targetCtx.PromptBlocks);
|
||||
// includeThinking=true für OpenAI-kompatible Provider → <think>...</think>-Prefix
|
||||
// im Assistant-Text. Anthropic ignoriert das (lehnt Blöcke ohne Signatur ab).
|
||||
var history = await ctx.ChatService.BuildHistoryFromDb(targetSessionId, promptBlocks: targetCtx.PromptBlocks, includeThinking: !string.IsNullOrEmpty(targetCtx.Provider));
|
||||
var senderAgent = ctx.AgentId != null ? await ctx.Db.GetAgent(ctx.AgentId) : null;
|
||||
var senderName = senderAgent?.Name ?? ctx.AgentId ?? "Unbekannter Agent";
|
||||
var senderInfo = $"[Nachricht von Agent \"{senderName}\"]: {message}";
|
||||
|
||||
Reference in new issue
Block a user