Skip to content

Commit 43f2f3b

Browse files
committed
Close two gaps in the new streaming assembler tests
The before-name test checked the id and name of every reported partial in a loop, which passes on an empty list; it now also requires at least one partial, without pinning which fragments are reported (that waits for the fix of the withheld prefix). The empty-choices usage test dereferenced tokenUsage() directly, so a mutant that skips the usage chunk failed with a NullPointerException instead of an assertion; it now asserts the usage is present first. Shown red by mutation: suppressing every partial after a withheld fragment fails with 'Expected: is not an empty collection', returning early on empty choices with 'Expected: is not null'; unchanged 13/13 green. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBFFnrhtvXiUeyxjmUc4R2
1 parent 2fbffa3 commit 43f2f3b

2 files changed

Lines changed: 7 additions & 2 deletions

File tree

‎BUGHUNT-FOUND.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,7 @@ Repos plus eins. Geprüfte Klassen ohne Fund stehen in [`BUGHUNT-CHECKED.md`](BU
884884
- **Reproduktion:** `LangChain4jMappingTest#streamingParametersRequestTheUsageChunk`
885885
- **Rot-Grün-Nachweis:** ohne Fix rot („Expected: is <true> but: was <false>“), mit Fix grün (22/22 der Klasse, skipped 0); Gegenprobe per `git stash` rot (1 Fehlschlag), nach `pop` grün; alle Modultests grün (`StreamingChunkAssemblerTest` 7/7, `JsonSchemaElementSerializerTest` 8/8, Integrationstests ohne Modell übersprungen). Spotless/SpotBugs sind im Modul nicht deklariert (siehe F92).
886886
- **CI-Bedarf:** Der Integrationsjob `test-java-llama-langchain4j-integration` läuft mit Modell; ein Ende-zu-Ende-Nachweis der Zählung im Stream wäre dort möglich, ist aber nicht Voraussetzung.
887-
- **Nachtrag (Branch-Review 2026-10-20):** Der Fix prüfte nur die Anfrageseite; kein Test fütterte den Usage-Chunk in der Form, die upstream mit `include_usage` sendet – ein eigener letzter Chunk mit leerem `choices`-Array, `usage` und `timings` nach dem Ende-Chunk (Fund J14-3). `StreamingChunkAssemblerTest#usageChunkWithEmptyChoicesCarriesTokenUsage` füttert genau die b11639-Folge (Inhalt, Ende-Chunk mit `finish_reason` und leerem `delta`, Usage-Chunk mit `id`/`model`/`object`/`system_fingerprint`) und pinnt 7/3 Token, das Teilstück `ok` und dass der Endegrund den Chunk ohne Wahl übersteht. Endegrund ist `length`, nicht `stop` wie im Vorschlag: ein fehlender Endegrund wird auf `STOP` abgebildet, mit `stop` bliebe ein Zurücksetzen unsichtbar. Ein reiner Absicherungstest ohne Verhaltensänderung, rot nur unter Mutation: `chunk.get("choices").get(0)` vor dem Lesen der Nutzung bzw. ein frühes `return` bei leerem `choices` (`NullPointerException … ChatResponse.tokenUsage() is null`), ein Zurücksetzen des Endegrunds auf einem Chunk ohne Wahl (`Expected: is <LENGTH> but: was <STOP>`); unverändert 13/13 grün.
887+
- **Nachtrag (Branch-Review 2026-10-20):** Der Fix prüfte nur die Anfrageseite; kein Test fütterte den Usage-Chunk in der Form, die upstream mit `include_usage` sendet – ein eigener letzter Chunk mit leerem `choices`-Array, `usage` und `timings` nach dem Ende-Chunk (Fund J14-3). `StreamingChunkAssemblerTest#usageChunkWithEmptyChoicesCarriesTokenUsage` füttert genau die b11639-Folge (Inhalt, Ende-Chunk mit `finish_reason` und leerem `delta`, Usage-Chunk mit `id`/`model`/`object`/`system_fingerprint`) und pinnt 7/3 Token, das Teilstück `ok` und dass der Endegrund den Chunk ohne Wahl übersteht. Endegrund ist `length`, nicht `stop` wie im Vorschlag: ein fehlender Endegrund wird auf `STOP` abgebildet, mit `stop` bliebe ein Zurücksetzen unsichtbar. Ein reiner Absicherungstest ohne Verhaltensänderung, rot nur unter Mutation: `chunk.get("choices").get(0)` vor dem Lesen der Nutzung bzw. ein frühes `return` bei leerem `choices` (`Expected: is not null` auf `tokenUsage()`; ohne die bei der Prüfung ergänzte `notNullValue()`-Assertion war es eine `NullPointerException`), ein Zurücksetzen des Endegrunds auf einem Chunk ohne Wahl (`Expected: is <LENGTH> but: was <STOP>`); unverändert 13/13 grün.
888888
- **Status:** gefixt
889889
- **Commit:** java-llama.cpp `6c2adf8`
890890

@@ -1193,7 +1193,7 @@ Repos plus eins. Geprüfte Klassen ohne Fund stehen in [`BUGHUNT-CHECKED.md`](BU
11931193
- **Reproduktion:** `StreamingChunkAssemblerTest#toolCallDeltaWithoutArguments_doesNotFailTheStream`, `#toolCallDeltaBeforeItsName_doesNotFailTheStream`
11941194
- **Rot-Grün-Nachweis:** ohne Fix beide rot („Unexpected exception thrown: java.lang.IllegalArgumentException: partialArguments cannot be null or empty“ bzw. „… name cannot be null or blank“), mit Fix grün (2/2, skipped 0); Gegenprobe per `git stash` rot (2), nach `pop` grün. Ganzes Modul grün (`StreamingChunkAssemblerTest` 9/9, `LangChain4jMappingTest` 23/23, `JsonSchemaElementSerializerTest` 8/8, `TestModelPathsTest` 8/8; die Integrationstests überspringen sich ohne Modell wie zuvor). Das Modul hat weder Spotless noch SpotBugs.
11951195
- **CI-Bedarf:** `JllamaToolCallingIntegrationTest#streamedRequiredToolCallComesBackAsToolExecutionRequest` (Job `test-java-llama-langchain4j-integration`, kein Release-Gate) im nächsten Lauf ansehen: ob er vor dem Fix rot war, hängt davon ab, ob das CI-Modell den Namen allein in einem Fragment sendet.
1196-
- **Nachtrag (Branch-Review 2026-10-20):** Die beiden Tests oben prüften nur, dass nichts wirft, und die fertige Anfrage; welche Fragmente als `PartialToolCall` gemeldet werden und mit welcher Id, war ungeprüft (Fund J14-2). `#toolCallDeltaWithoutArguments_doesNotFailTheStream` pinnt jetzt genau ein gemeldetes Teilstück (Index 0, Id `call_1`, Name `get_weather`, Argumente `{}`), einen `CompleteToolCall` und langchain4js Vertrag über den neuen Helfer `assertPartialsMakeUpTheCompleteCall` (die Argumentstücke ergeben aneinandergehängt die Argumente des fertigen Aufrufs, jedes trägt dessen Id und Namen). `#toolCallDeltaBeforeItsName_doesNotFailTheStream` prüft nur, dass jedes gemeldete Teilstück die endgültige Id und den Namen trägt: der Vertrag ist dort heute rot (`Expected: is "{\"city\":1}" but: was "ty\":1}"`, das Präfix vor dem Namen wird nie gemeldet, Fund J14-1) und kommt mit dessen Fix; das heutige einzelne Teilstück wird bewusst nicht festgeschrieben. Gegenprobe rot: die Id aus dem aktuellen Fragment statt aus dem gesammelten Aufruf (`Expected: is "call_1" but: was ""`), das Kopf-Fragment mit einem Platzhalter-Argument `" "` gemeldet (`Expected: is <1> but: was <2>`); unverändert 12/12 grün.
1196+
- **Nachtrag (Branch-Review 2026-10-20):** Die beiden Tests oben prüften nur, dass nichts wirft, und die fertige Anfrage; welche Fragmente als `PartialToolCall` gemeldet werden und mit welcher Id, war ungeprüft (Fund J14-2). `#toolCallDeltaWithoutArguments_doesNotFailTheStream` pinnt jetzt genau ein gemeldetes Teilstück (Index 0, Id `call_1`, Name `get_weather`, Argumente `{}`), einen `CompleteToolCall` und langchain4js Vertrag über den neuen Helfer `assertPartialsMakeUpTheCompleteCall` (die Argumentstücke ergeben aneinandergehängt die Argumente des fertigen Aufrufs, jedes trägt dessen Id und Namen). `#toolCallDeltaBeforeItsName_doesNotFailTheStream` prüft nur, dass mindestens ein Teilstück gemeldet wird (`is(not(empty()))`, damit die Schleife nicht leer durchläuft; bei der Prüfung ergänzt, rot gegen eine Mutante, die nach einem zurückgehaltenen Fragment nichts mehr meldet: `Expected: is not an empty collection`) und dass jedes gemeldete Teilstück die endgültige Id und den Namen trägt: der Vertrag ist dort heute rot (`Expected: is "{\"city\":1}" but: was "ty\":1}"`, das Präfix vor dem Namen wird nie gemeldet, Fund J14-1) und kommt mit dessen Fix; das heutige einzelne Teilstück wird bewusst nicht festgeschrieben. Gegenprobe rot: die Id aus dem aktuellen Fragment statt aus dem gesammelten Aufruf (`Expected: is "call_1" but: was ""`), das Kopf-Fragment mit einem Platzhalter-Argument `" "` gemeldet (`Expected: is <1> but: was <2>`); unverändert 12/12 grün.
11971197
- **Status:** gefixt
11981198
- **Commit:** java-llama.cpp `f7f3f0f`
11991199

‎llama-langchain4j/src/test/java/net/ladenthin/llama/langchain4j/StreamingChunkAssemblerTest.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import static org.hamcrest.Matchers.containsString;
1111
import static org.hamcrest.Matchers.empty;
1212
import static org.hamcrest.Matchers.is;
13+
import static org.hamcrest.Matchers.not;
14+
import static org.hamcrest.Matchers.notNullValue;
1315
import static org.hamcrest.Matchers.nullValue;
1416
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
1517
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -191,7 +193,9 @@ void toolCallDeltaBeforeItsName_doesNotFailTheStream() {
191193
// Every partial that is reported carries the final id and name. Which fragments are reported
192194
// here is not pinned: the prefix that arrives before the name is not reported at all, so the
193195
// partials do not make up the arguments yet (assertPartialsMakeUpTheCompleteCall is red).
196+
// At least one is reported, so the loop below cannot pass on an empty list.
194197
assertThat(handler.completeToolCalls.size(), is(1));
198+
assertThat(handler.partialToolCalls, is(not(empty())));
195199
for (PartialToolCall partial : handler.partialToolCalls) {
196200
assertThat(partial.id(), is("call_1"));
197201
assertThat(partial.name(), is("get_weather"));
@@ -367,6 +371,7 @@ void usageChunkWithEmptyChoicesCarriesTokenUsage() {
367371
+ "\"timings\":{\"cache_n\":0,\"prompt_n\":7,\"predicted_n\":3}}");
368372
ChatResponse response = assembler.complete();
369373

374+
assertThat(response.tokenUsage(), is(notNullValue()));
370375
assertThat(response.tokenUsage().inputTokenCount(), is(7));
371376
assertThat(response.tokenUsage().outputTokenCount(), is(3));
372377
// "length", not "stop": no finish reason maps to STOP, so a reset would hide behind "stop".

0 commit comments

Comments
 (0)