Skip to content

Commit 08c99a0

Browse files
committed
wip
1 parent b7ccb25 commit 08c99a0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1154
-1181
lines changed

src/Agents/Agent.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,18 @@ public function stream(array $messages = [], array $input = []): ChatStreamResul
153153
$messages = $this->memory->getMessages()->merge($messages);
154154
$this->memory->setMessages($messages);
155155

156-
return $this->pipeline()->stream([
156+
/** @var \Cortex\LLM\Data\ChatStreamResult $result */
157+
$result = $this->pipeline()->stream([
157158
...$input,
158159
'messages' => $this->memory->getMessages(),
159160
]);
161+
162+
// Ensure that any nested ChatStreamResults are flattened
163+
// so that the stream is a single stream of chunks.
164+
// TODO: This breaks things like the JSON output parser.
165+
// return $result->flatten();
166+
167+
return $result;
160168
}
161169

162170
public function pipe(Pipeable|callable $pipeable): Pipeline

src/Events/ChatModelEnd.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
namespace Cortex\Events;
66

77
use Cortex\LLM\Data\ChatResult;
8-
use Cortex\LLM\Data\ChatGenerationChunk;
8+
use Cortex\LLM\Data\ChatStreamResult;
99

1010
readonly class ChatModelEnd
1111
{
1212
public function __construct(
13-
public ChatResult|ChatGenerationChunk $result,
13+
public ChatResult|ChatStreamResult $result,
1414
) {}
1515
}

src/Http/Controllers/AgentsController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function invoke(string $agent, Request $request): JsonResponse
3131
vsprintf('{"location": "%s", "conditions": "%s", "temperature": %s, "unit": "celsius"}', [
3232
$location,
3333
Arr::random(['sunny', 'cloudy', 'rainy', 'snowing']),
34-
random_int(10, 20),
34+
14,
3535
]),
3636
),
3737
],

src/LLM/Data/ChatGeneration.php

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
{
1818
public function __construct(
1919
public AssistantMessage $message,
20-
public int $index,
2120
public DateTimeInterface $createdAt,
2221
public FinishReason $finishReason,
2322
public mixed $parsedOutput = null,
@@ -28,7 +27,6 @@ public function cloneWithMessage(AssistantMessage $message): self
2827
{
2928
return new self(
3029
$message,
31-
$this->index,
3230
$this->createdAt,
3331
$this->finishReason,
3432
$this->parsedOutput,
@@ -40,7 +38,6 @@ public function cloneWithParsedOutput(mixed $parsedOutput): self
4038
{
4139
return new self(
4240
$this->message,
43-
$this->index,
4441
$this->createdAt,
4542
$this->finishReason,
4643
$parsedOutput,
@@ -53,7 +50,6 @@ public static function fromMessage(AssistantMessage $message, ?FinishReason $fin
5350
// TODO: move to FakeChatGeneration
5451
return new self(
5552
message: $message,
56-
index: 0,
5753
createdAt: new DateTimeImmutable(),
5854
finishReason: $finishReason ?? FinishReason::Stop,
5955
);
@@ -62,12 +58,11 @@ public static function fromMessage(AssistantMessage $message, ?FinishReason $fin
6258
public function toArray(): array
6359
{
6460
return [
65-
'index' => $this->index,
66-
'created_at' => $this->createdAt,
61+
'message' => $this->message->toArray(),
6762
'finish_reason' => $this->finishReason->value,
6863
'parsed_output' => $this->parsedOutput,
6964
'output_parser_error' => $this->outputParserError,
70-
'message' => $this->message,
65+
'created_at' => $this->createdAt,
7166
];
7267
}
7368
}

src/LLM/Data/ChatGenerationChunk.php

Lines changed: 23 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,65 +6,33 @@
66

77
use DateTimeInterface;
88
use Cortex\LLM\Enums\ChunkType;
9-
use Cortex\LLM\Enums\MessageRole;
109
use Cortex\LLM\Enums\FinishReason;
10+
use Illuminate\Contracts\Support\Arrayable;
1111
use Cortex\LLM\Data\Messages\AssistantMessage;
1212

13-
readonly class ChatGenerationChunk
13+
/**
14+
* @implements Arrayable<string, mixed>
15+
*/
16+
readonly class ChatGenerationChunk implements Arrayable
1417
{
15-
public ?ChunkType $type;
16-
1718
public function __construct(
1819
public string $id,
1920
public AssistantMessage $message,
20-
public int $index,
2121
public DateTimeInterface $createdAt,
22-
?ChunkType $type = null,
22+
public ChunkType $type,
2323
public ?FinishReason $finishReason = null,
2424
public ?Usage $usage = null,
2525
public string $contentSoFar = '',
2626
public bool $isFinal = false,
2727
public mixed $parsedOutput = null,
2828
public ?string $outputParserError = null,
29-
) {
30-
$this->type = $type ?? $this->resolveType();
31-
}
32-
33-
/**
34-
* Fallback method to resolve chunk type when not explicitly provided by the LLM driver.
35-
* This is a best-effort inference and may not be as accurate as driver-specific logic.
36-
* Prefer having the LLM driver provide the chunk type explicitly when possible.
37-
*/
38-
private function resolveType(): ChunkType
39-
{
40-
// Final chunks: Use finish reason to determine completion type
41-
if ($this->finishReason !== null) {
42-
return match ($this->finishReason) {
43-
FinishReason::ToolCalls => ChunkType::ToolInputEnd,
44-
default => ChunkType::Done,
45-
};
46-
}
47-
48-
// First chunk: Assistant message with no accumulated content
49-
if ($this->message->role() === MessageRole::Assistant && $this->contentSoFar === '') {
50-
return ChunkType::MessageStart;
51-
}
52-
53-
// Tool-related chunks: Has tool calls
54-
if ($this->message->toolCalls?->isNotEmpty()) {
55-
return ChunkType::ToolInputDelta;
56-
}
57-
58-
// Default: Treat as text delta
59-
return ChunkType::TextDelta;
60-
}
29+
) {}
6130

6231
public function cloneWithParsedOutput(mixed $parsedOutput): self
6332
{
6433
return new self(
6534
$this->id,
6635
$this->message,
67-
$this->index,
6836
$this->createdAt,
6937
$this->type,
7038
$this->finishReason,
@@ -75,4 +43,20 @@ public function cloneWithParsedOutput(mixed $parsedOutput): self
7543
$this->outputParserError,
7644
);
7745
}
46+
47+
public function toArray(): array
48+
{
49+
return [
50+
'id' => $this->id,
51+
'type' => $this->type->value,
52+
'message' => $this->message->toArray(),
53+
'finish_reason' => $this->finishReason?->value,
54+
'usage' => $this->usage?->toArray(),
55+
'content_so_far' => $this->contentSoFar,
56+
'is_final' => $this->isFinal,
57+
'parsed_output' => $this->parsedOutput,
58+
'output_parser_error' => $this->outputParserError,
59+
'created_at' => $this->createdAt,
60+
];
61+
}
7862
}

src/LLM/Data/ChatResult.php

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,26 @@
1111
*/
1212
readonly class ChatResult implements Arrayable
1313
{
14-
public ChatGeneration $generation;
15-
1614
public mixed $parsedOutput;
1715

1816
/**
19-
* @param array<int, ChatGeneration> $generations
2017
* @param array<int, mixed> $rawResponse
2118
*/
2219
public function __construct(
23-
public array $generations,
20+
public ChatGeneration $generation,
2421
public Usage $usage,
2522
public array $rawResponse = [],
2623
) {
27-
$this->generation = $generations[0];
28-
$this->parsedOutput = $generations[0]->parsedOutput;
24+
$this->parsedOutput = $this->generation->parsedOutput;
25+
}
26+
27+
public function cloneWithGeneration(ChatGeneration $generation): self
28+
{
29+
return new self(
30+
$generation,
31+
$this->usage,
32+
$this->rawResponse,
33+
);
2934
}
3035

3136
public function toArray(): array

src/LLM/Data/ChatStreamResult.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
namespace Cortex\LLM\Data;
66

77
use DateTimeImmutable;
8+
use Cortex\LLM\Enums\ChunkType;
89
use Cortex\LLM\Enums\FinishReason;
910
use Illuminate\Support\LazyCollection;
1011
use Cortex\LLM\Streaming\AgUiDataStream;
1112
use Cortex\LLM\Streaming\VercelDataStream;
1213
use Cortex\LLM\Streaming\VercelTextStream;
13-
use Cortex\LLM\Data\Messages\AssistantMessage;
1414
use Cortex\LLM\Contracts\StreamingProtocol;
15+
use Cortex\LLM\Data\Messages\AssistantMessage;
1516
use Symfony\Component\HttpFoundation\StreamedResponse;
1617

1718
/**
@@ -77,8 +78,8 @@ public static function fake(?string $string = null, ?ToolCallCollection $toolCal
7778
$chunk = new ChatGenerationChunk(
7879
id: 'fake-' . $index,
7980
message: new AssistantMessage($chunk, $toolCalls),
80-
index: $index,
8181
createdAt: new DateTimeImmutable(),
82+
type: ChunkType::TextDelta,
8283
finishReason: $isFinal ? FinishReason::Stop : null,
8384
usage: new Usage(
8485
promptTokens: 0,

src/LLM/Data/ResponseMetadata.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public function __construct(
1919
public ModelProvider $provider,
2020
public ?FinishReason $finishReason = null,
2121
public ?Usage $usage = null,
22+
public array $providerMetadata = [],
2223
) {}
2324

2425
/**
@@ -32,6 +33,7 @@ public function toArray(): array
3233
'provider' => $this->provider->value,
3334
'finish_reason' => $this->finishReason?->value,
3435
'usage' => $this->usage?->toArray(),
36+
'provider_metadata' => $this->providerMetadata,
3537
];
3638
}
3739
}

src/LLM/Drivers/AnthropicChat.php renamed to src/LLM/Drivers/Anthropic/AnthropicChat.php

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
declare(strict_types=1);
44

5-
namespace Cortex\LLM\Drivers;
5+
namespace Cortex\LLM\Drivers\Anthropic;
66

77
use Generator;
88
use Throwable;
@@ -15,6 +15,7 @@
1515
use Cortex\LLM\Contracts\Tool;
1616
use Cortex\Events\ChatModelEnd;
1717
use Cortex\LLM\Data\ChatResult;
18+
use Cortex\LLM\Enums\ChunkType;
1819
use Cortex\LLM\Enums\ToolChoice;
1920
use Anthropic\Testing\ClientFake;
2021
use Cortex\Events\ChatModelError;
@@ -148,15 +149,14 @@ protected function mapResponse(CreateResponse $response): ChatResult
148149
usage: $usage,
149150
),
150151
),
151-
index: 0,
152152
createdAt: new DateTimeImmutable(),
153153
finishReason: $finishReason,
154154
);
155155

156156
$generation = $this->applyOutputParserIfApplicable($generation);
157157

158158
$result = new ChatResult(
159-
[$generation],
159+
$generation,
160160
$usage,
161161
$response->toArray(), // @phpstan-ignore argument.type
162162
);
@@ -255,7 +255,7 @@ protected function mapStreamResponse(StreamResponse $response): ChatStreamResult
255255
collect($toolCallsSoFar)
256256
->map(function (array $toolCall): ToolCall {
257257
try {
258-
$arguments = json_decode($toolCall['function']['arguments'], true, flags: JSON_THROW_ON_ERROR);
258+
$arguments = json_decode((string) $toolCall['function']['arguments'], true, flags: JSON_THROW_ON_ERROR);
259259
} catch (JsonException) {
260260
$arguments = [];
261261
}
@@ -286,8 +286,8 @@ protected function mapStreamResponse(StreamResponse $response): ChatStreamResult
286286
usage: $usage,
287287
),
288288
),
289-
index: 0,
290289
createdAt: new DateTimeImmutable(),
290+
type: ChunkType::TextDelta, // TODO
291291
finishReason: $finishReason,
292292
usage: $usage,
293293
contentSoFar: $contentSoFar,
@@ -296,11 +296,7 @@ protected function mapStreamResponse(StreamResponse $response): ChatStreamResult
296296

297297
$chunk = $this->applyOutputParserIfApplicable($chunk);
298298

299-
$this->dispatchEvent(
300-
$chunk->isFinal
301-
? new ChatModelEnd($chunk)
302-
: new ChatModelStream($chunk),
303-
);
299+
$this->dispatchEvent(new ChatModelStream($chunk));
304300

305301
yield $chunk;
306302
}

src/LLM/Drivers/FakeChat.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public function invoke(
7171

7272
$currentGeneration = $currentGeneration->cloneWithMessage($message);
7373

74-
$result = new ChatResult([$currentGeneration], $usage);
74+
$result = new ChatResult($currentGeneration, $usage);
7575

7676
$this->dispatchEvent(new ChatModelEnd($result));
7777

0 commit comments

Comments
 (0)