From 711ce06fc44599c438c4478a0f978cf7e35d70f7 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Thu, 11 Sep 2025 09:39:45 -0600 Subject: [PATCH 1/7] When running the toText method, return the first content candidate that has text. This ensures reasoning_content isn't returned --- src/Results/DTO/GenerativeAiResult.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Results/DTO/GenerativeAiResult.php b/src/Results/DTO/GenerativeAiResult.php index 4102ec38..1995ab5a 100644 --- a/src/Results/DTO/GenerativeAiResult.php +++ b/src/Results/DTO/GenerativeAiResult.php @@ -9,6 +9,7 @@ use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Files\DTO\File; use WordPress\AiClient\Messages\DTO\Message; +use WordPress\AiClient\Messages\Enums\MessagePartChannelEnum; use WordPress\AiClient\Providers\DTO\ProviderMetadata; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; use WordPress\AiClient\Results\Contracts\ResultInterface; @@ -199,7 +200,7 @@ public function hasMultipleCandidates(): bool } /** - * Converts the first candidate to text. + * Converts the first content candidate to text. * * @since 0.1.0 * @@ -208,15 +209,18 @@ public function hasMultipleCandidates(): bool */ public function toText(): string { - $message = $this->candidates[0]->getMessage(); - foreach ($message->getParts() as $part) { - $text = $part->getText(); - if ($text !== null) { - return $text; + foreach ($this->candidates as $candidate) { + $message = $candidate->getMessage(); + foreach ($message->getParts() as $part) { + $channel = $part->getChannel(); + $text = $part->getText(); + if (MessagePartChannelEnum::content() === $channel && $text !== null) { + return $text; + } } } - throw new RuntimeException('No text content found in first candidate'); + throw new RuntimeException('No text content found in the candidates'); } /** From 744a8ebcbb330834bfd406baee9448ef13475096 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Thu, 11 Sep 2025 09:40:39 -0600 Subject: [PATCH 2/7] When running the toTexts method, only return content candidates that have text. This ensures reasoning_content isn't returned --- src/Results/DTO/GenerativeAiResult.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Results/DTO/GenerativeAiResult.php b/src/Results/DTO/GenerativeAiResult.php index 1995ab5a..92f980f3 100644 --- a/src/Results/DTO/GenerativeAiResult.php +++ b/src/Results/DTO/GenerativeAiResult.php @@ -320,7 +320,7 @@ public function toMessage(): Message } /** - * Converts all candidates to text array. + * Converts all content candidates to a text array. * * @since 0.1.0 * @@ -332,8 +332,9 @@ public function toTexts(): array foreach ($this->candidates as $candidate) { $message = $candidate->getMessage(); foreach ($message->getParts() as $part) { + $channel = $part->getChannel(); $text = $part->getText(); - if ($text !== null) { + if (MessagePartChannelEnum::content() === $channel && $text !== null) { $texts[] = $text; break; } From 334bf1c234adff767ba06e8fec92114ea4ec4508 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Thu, 11 Sep 2025 09:44:47 -0600 Subject: [PATCH 3/7] Fix broken tests --- tests/unit/Builders/PromptBuilderTest.php | 6 +++--- tests/unit/Results/DTO/GenerativeAiResultTest.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/Builders/PromptBuilderTest.php b/tests/unit/Builders/PromptBuilderTest.php index b2d699a6..864d67b9 100644 --- a/tests/unit/Builders/PromptBuilderTest.php +++ b/tests/unit/Builders/PromptBuilderTest.php @@ -1601,7 +1601,7 @@ public function testGenerateTextThrowsExceptionWhenNoParts(): void $builder->usingModel($model); $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('No text content found in first candidate'); + $this->expectExceptionMessage('No text content found in the candidates'); $builder->generateText(); } @@ -1635,7 +1635,7 @@ public function testGenerateTextThrowsExceptionWhenPartHasNoText(): void $builder->usingModel($model); $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('No text content found in first candidate'); + $this->expectExceptionMessage('No text content found in the candidates'); $builder->generateText(); } @@ -2534,7 +2534,7 @@ public function testGenerateTextWithNonStringPartThrowsException(): void $builder->usingModel($model); $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('No text content found in first candidate'); + $this->expectExceptionMessage('No text content found in the candidates'); $builder->generateText(); } diff --git a/tests/unit/Results/DTO/GenerativeAiResultTest.php b/tests/unit/Results/DTO/GenerativeAiResultTest.php index f4de622b..5e650639 100644 --- a/tests/unit/Results/DTO/GenerativeAiResultTest.php +++ b/tests/unit/Results/DTO/GenerativeAiResultTest.php @@ -212,7 +212,7 @@ public function testToTextThrowsExceptionWhenNoTextContent(): void ); $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('No text content found in first candidate'); + $this->expectExceptionMessage('No text content found in the candidates'); $result->toText(); } From f43dd7ad98f57a33c4c698886c14738dcd0d9e86 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Thu, 11 Sep 2025 09:53:26 -0600 Subject: [PATCH 4/7] Add new tests --- .../Results/DTO/GenerativeAiResultTest.php | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/unit/Results/DTO/GenerativeAiResultTest.php b/tests/unit/Results/DTO/GenerativeAiResultTest.php index 5e650639..ff3c1e92 100644 --- a/tests/unit/Results/DTO/GenerativeAiResultTest.php +++ b/tests/unit/Results/DTO/GenerativeAiResultTest.php @@ -11,6 +11,7 @@ use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Messages\DTO\MessagePart; use WordPress\AiClient\Messages\DTO\ModelMessage; +use WordPress\AiClient\Messages\Enums\MessagePartChannelEnum; use WordPress\AiClient\Messages\Enums\MessagePartTypeEnum; use WordPress\AiClient\Messages\Enums\MessageRoleEnum; use WordPress\AiClient\Providers\DTO\ProviderMetadata; @@ -189,6 +190,36 @@ public function testToText(): void $this->assertEquals($text, $result->toText()); } + /** + * Tests toText method with both reasoning_content and content channels. + * + * @return void + */ + public function testToTextWithReasoningContent(): void + { + $reasoningContent = 'Let me think about this step by step...'; + $actualContent = 'This is the final answer.'; + + $message = new ModelMessage([ + new MessagePart($reasoningContent, MessagePartChannelEnum::thought()), + new MessagePart($actualContent, MessagePartChannelEnum::content()) + ]); + $candidate = new Candidate($message, FinishReasonEnum::stop()); + $tokenUsage = new TokenUsage(10, 8, 18); + + $result = new GenerativeAiResult( + 'result_with_reasoning', + [$candidate], + $tokenUsage, + $this->createTestProviderMetadata(), + $this->createTestModelMetadata() + ); + + // toText() should only return content from the 'content' channel, not 'thought' channel + $this->assertEquals($actualContent, $result->toText()); + $this->assertNotEquals($reasoningContent, $result->toText()); + } + /** * Tests toText throws exception when no text content. * @@ -430,6 +461,50 @@ public function testToTextsWithMultipleCandidates(): void $this->assertEquals($texts, $result->toTexts()); } + /** + * Tests toTexts method with both reasoning_content and content channels. + * + * @return void + */ + public function testToTextsWithReasoningContent(): void + { + $reasoningContent1 = 'Let me think about the first question...'; + $actualContent1 = 'This is the first answer.'; + $reasoningContent2 = 'Now for the second question...'; + $actualContent2 = 'This is the second answer.'; + + $message1 = new ModelMessage([ + new MessagePart($reasoningContent1, MessagePartChannelEnum::thought()), + new MessagePart($actualContent1, MessagePartChannelEnum::content()) + ]); + $message2 = new ModelMessage([ + new MessagePart($reasoningContent2, MessagePartChannelEnum::thought()), + new MessagePart($actualContent2, MessagePartChannelEnum::content()) + ]); + + $candidates = [ + new Candidate($message1, FinishReasonEnum::stop()), + new Candidate($message2, FinishReasonEnum::stop()) + ]; + $tokenUsage = new TokenUsage(20, 15, 35); + + $result = new GenerativeAiResult( + 'result_texts_with_reasoning', + $candidates, + $tokenUsage, + $this->createTestProviderMetadata(), + $this->createTestModelMetadata() + ); + + // toTexts() should only return content from the 'content' channel, not 'thought' channel + $expectedTexts = [$actualContent1, $actualContent2]; + $this->assertEquals($expectedTexts, $result->toTexts()); + + // Verify reasoning content is not included + $this->assertNotContains($reasoningContent1, $result->toTexts()); + $this->assertNotContains($reasoningContent2, $result->toTexts()); + } + /** * Tests toFiles method with multiple candidates. * From 26920e69e34db30d6198a72b74de9cc5922a5399 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Thu, 11 Sep 2025 13:49:28 -0600 Subject: [PATCH 5/7] Fix the toText method to ensure we only pull content from the first candidate. Simplify how we check the channel. Update tests --- src/Results/DTO/GenerativeAiResult.php | 19 ++++++++----------- tests/unit/Builders/PromptBuilderTest.php | 6 +++--- .../Results/DTO/GenerativeAiResultTest.php | 2 +- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/Results/DTO/GenerativeAiResult.php b/src/Results/DTO/GenerativeAiResult.php index 92f980f3..c33f0e24 100644 --- a/src/Results/DTO/GenerativeAiResult.php +++ b/src/Results/DTO/GenerativeAiResult.php @@ -9,7 +9,6 @@ use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Files\DTO\File; use WordPress\AiClient\Messages\DTO\Message; -use WordPress\AiClient\Messages\Enums\MessagePartChannelEnum; use WordPress\AiClient\Providers\DTO\ProviderMetadata; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; use WordPress\AiClient\Results\Contracts\ResultInterface; @@ -209,18 +208,16 @@ public function hasMultipleCandidates(): bool */ public function toText(): string { - foreach ($this->candidates as $candidate) { - $message = $candidate->getMessage(); - foreach ($message->getParts() as $part) { - $channel = $part->getChannel(); - $text = $part->getText(); - if (MessagePartChannelEnum::content() === $channel && $text !== null) { - return $text; - } + $message = $this->candidates[0]->getMessage(); + foreach ($message->getParts() as $part) { + $channel = $part->getChannel(); + $text = $part->getText(); + if ($channel->isContent() && $text !== null) { + return $text; } } - throw new RuntimeException('No text content found in the candidates'); + throw new RuntimeException('No text content found in first candidate'); } /** @@ -334,7 +331,7 @@ public function toTexts(): array foreach ($message->getParts() as $part) { $channel = $part->getChannel(); $text = $part->getText(); - if (MessagePartChannelEnum::content() === $channel && $text !== null) { + if ($channel->isContent() && $text !== null) { $texts[] = $text; break; } diff --git a/tests/unit/Builders/PromptBuilderTest.php b/tests/unit/Builders/PromptBuilderTest.php index 864d67b9..b2d699a6 100644 --- a/tests/unit/Builders/PromptBuilderTest.php +++ b/tests/unit/Builders/PromptBuilderTest.php @@ -1601,7 +1601,7 @@ public function testGenerateTextThrowsExceptionWhenNoParts(): void $builder->usingModel($model); $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('No text content found in the candidates'); + $this->expectExceptionMessage('No text content found in first candidate'); $builder->generateText(); } @@ -1635,7 +1635,7 @@ public function testGenerateTextThrowsExceptionWhenPartHasNoText(): void $builder->usingModel($model); $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('No text content found in the candidates'); + $this->expectExceptionMessage('No text content found in first candidate'); $builder->generateText(); } @@ -2534,7 +2534,7 @@ public function testGenerateTextWithNonStringPartThrowsException(): void $builder->usingModel($model); $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('No text content found in the candidates'); + $this->expectExceptionMessage('No text content found in first candidate'); $builder->generateText(); } diff --git a/tests/unit/Results/DTO/GenerativeAiResultTest.php b/tests/unit/Results/DTO/GenerativeAiResultTest.php index ff3c1e92..7ac08b5b 100644 --- a/tests/unit/Results/DTO/GenerativeAiResultTest.php +++ b/tests/unit/Results/DTO/GenerativeAiResultTest.php @@ -243,7 +243,7 @@ public function testToTextThrowsExceptionWhenNoTextContent(): void ); $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('No text content found in the candidates'); + $this->expectExceptionMessage('No text content found in first candidate'); $result->toText(); } From 94b74fd96d38a2a9086ade94c7c194aa598850f0 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Thu, 11 Sep 2025 13:58:14 -0600 Subject: [PATCH 6/7] Update the toFile and toFiles methods to check for the content channel --- src/Results/DTO/GenerativeAiResult.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Results/DTO/GenerativeAiResult.php b/src/Results/DTO/GenerativeAiResult.php index c33f0e24..5eb3d6c1 100644 --- a/src/Results/DTO/GenerativeAiResult.php +++ b/src/Results/DTO/GenerativeAiResult.php @@ -221,7 +221,7 @@ public function toText(): string } /** - * Converts the first candidate to a file. + * Converts the first content candidate to a file. * * @since 0.1.0 * @@ -232,8 +232,9 @@ public function toFile(): File { $message = $this->candidates[0]->getMessage(); foreach ($message->getParts() as $part) { + $channel = $part->getChannel(); $file = $part->getFile(); - if ($file !== null) { + if ($channel->isContent() && $file !== null) { return $file; } } @@ -353,8 +354,9 @@ public function toFiles(): array foreach ($this->candidates as $candidate) { $message = $candidate->getMessage(); foreach ($message->getParts() as $part) { + $channel = $part->getChannel(); $file = $part->getFile(); - if ($file !== null) { + if ($channel->isContent() && $file !== null) { $files[] = $file; break; } From cfdbec70d66b2ff0273254d11990b91b4e190b03 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Thu, 11 Sep 2025 13:26:34 -0700 Subject: [PATCH 7/7] Update doc block descriptions. --- src/Results/DTO/GenerativeAiResult.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Results/DTO/GenerativeAiResult.php b/src/Results/DTO/GenerativeAiResult.php index 5eb3d6c1..45de1a3d 100644 --- a/src/Results/DTO/GenerativeAiResult.php +++ b/src/Results/DTO/GenerativeAiResult.php @@ -199,7 +199,9 @@ public function hasMultipleCandidates(): bool } /** - * Converts the first content candidate to text. + * Converts the first candidate to text. + * + * Only text from the content channel is considered. Text within model thought or reasoning is ignored. * * @since 0.1.0 * @@ -221,7 +223,9 @@ public function toText(): string } /** - * Converts the first content candidate to a file. + * Converts the first candidate to a file. + * + * Only files from the content channel are considered. Files within model thought or reasoning are ignored. * * @since 0.1.0 * @@ -318,7 +322,7 @@ public function toMessage(): Message } /** - * Converts all content candidates to a text array. + * Converts all candidates to text. * * @since 0.1.0 *