Harness gegen stillen Verlust gehaertet (#547-#551)
Fuenf Befunde mit einem gemeinsamen Fehlermodus: Keiner erzeugt eine Fehlermeldung. Der Lauf wird fertig, das Ergebnis sieht plausibel aus, und nur die Qualitaet ist schlechter. #547 Der Begleittext zum Werkzeugaufruf ging verloren. AssistantWithToolCalls baute eine neue Nachricht aus nur den Aufrufen; der Text, den das Modell daneben geschrieben hatte, fiel weg. Damit verlor das Modell zwischen zwei Schritten seinen eigenen Plan, und im Audit stand, dass ein Werkzeug lief, nicht warum. #548 finish_reason wurde nirgends gelesen. Eine an der Ausgabegrenze abgeschnittene Antwort galt als fertiges Ergebnis. Das trifft besonders Laeufe im Hintergrund, wo niemand zusieht. Neuer Status Truncated: Das Teilergebnis geht weiter, aber gekennzeichnet. #549 reasoning_details wurde verworfen. Bei Werkzeugnutzung muss der Denkverlauf unveraendert zurueck, sonst beginnt ein Thinking-Modell jedes Mal von vorn. Wird jetzt als rohes JSON durchgereicht - ohne Auslegung, weil Reihenfolge und Signatur daran haengen. #550 Die Anfrage verlangt jetzt einen Anbieter, der alle mitgeschickten Parameter beherrscht. Ohne das durfte ein Anbieter cache_control, reasoning oder sogar tools stillschweigend uebergehen - der Lauf lieferte dann eine gewoehnliche Textantwort, wo ein Werkzeugaufruf hingehoert haette. Dazu usage.include: der abgerechnete Betrag schlaegt die Eigenrechnung, die als Rueckfallebene bleibt. Denktiefe je Agent einstellbar, Vorgabe off. #551 Werkzeuge eines Zuges laufen gleichzeitig, sofern sie sich dafuer freigeben. Die Vorgabe von IsParallelSafe ist false, weil die falsche Antwort hier nicht auffaellt: Zwei schreibende Aufrufe auf derselben Datei scheitern nicht zuverlaessig, sondern gelegentlich. Freigegeben ist bisher nur WebFetch - es liest ausschliesslich. Die Reihenfolge der Ergebnisse folgt den Aufrufen, nicht der Fertigstellung; daran haengen die tool_call_id-Zuordnung und der Cache-Praefix. 27 neue Tests, 887 gesamt, keine Regression. Build ohne Warnungen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -76,6 +76,52 @@ internal sealed class FakeChatClient : IChatCompletionClient
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Antwortet mit einem an der Ausgabegrenze abgeschnittenen Text (#548).
|
||||
/// </summary>
|
||||
public FakeChatClient RespondsWithTruncatedText(string text, string finishReason = "length")
|
||||
{
|
||||
_responses.Enqueue(_ => new ChatResponse
|
||||
{
|
||||
Id = "resp",
|
||||
Choices =
|
||||
[
|
||||
new Choice
|
||||
{
|
||||
Index = 0,
|
||||
Message = new ChatMessage { Role = "assistant", Content = text },
|
||||
FinishReason = finishReason
|
||||
}
|
||||
],
|
||||
Usage = new Usage { PromptTokens = 100, CompletionTokens = 20, TotalTokens = 120 }
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Antwortet mit Werkzeugaufrufen UND Begleittext bzw. Denkverlauf — der Fall, den
|
||||
/// #547 und #549 betreffen.
|
||||
/// </summary>
|
||||
public FakeChatClient RespondsWithToolCallAndContent(
|
||||
string toolName,
|
||||
string content,
|
||||
string? reasoning = null,
|
||||
string? reasoningDetailsJson = null,
|
||||
string argumentsJson = "{}")
|
||||
{
|
||||
_responses.Enqueue(_ =>
|
||||
{
|
||||
var response = ToolCallResponse([(toolName, argumentsJson, null)]);
|
||||
var message = response.Choices[0].Message;
|
||||
message.Content = content;
|
||||
message.Reasoning = reasoning;
|
||||
if (reasoningDetailsJson is not null)
|
||||
message.ReasoningDetails = JsonDocument.Parse(reasoningDetailsJson).RootElement.Clone();
|
||||
return response;
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Antwort für alle Aufrufe, die über die programmierte Folge hinausgehen.</summary>
|
||||
public FakeChatClient AlwaysRespondsWithText(string text)
|
||||
{
|
||||
@@ -143,6 +189,11 @@ internal sealed class FakeChatClient : IChatCompletionClient
|
||||
MaxTokens = request.MaxTokens,
|
||||
ToolChoice = request.ToolChoice,
|
||||
Tools = request.Tools?.ToList(),
|
||||
// Die Gateway-Felder gehören mitkopiert, sonst prüft ein Test die Härtung
|
||||
// am Wire-Format vorbei — dieselbe Falle wie bei CacheBreakpoint.
|
||||
Provider = request.Provider,
|
||||
Usage = request.Usage,
|
||||
Reasoning = request.Reasoning,
|
||||
Messages = request.Messages.Select(CloneMessage).ToList()
|
||||
};
|
||||
|
||||
@@ -153,6 +204,8 @@ internal sealed class FakeChatClient : IChatCompletionClient
|
||||
ToolCallId = m.ToolCallId,
|
||||
// Muss mitkopiert werden, sonst prüfen Caching-Tests am Wire-Format vorbei.
|
||||
CacheBreakpoint = m.CacheBreakpoint,
|
||||
Reasoning = m.Reasoning,
|
||||
ReasoningDetails = m.ReasoningDetails,
|
||||
ToolCalls = m.ToolCalls?.Select(tc => new ToolCall
|
||||
{
|
||||
Id = tc.Id,
|
||||
|
||||
Reference in New Issue
Block a user