Skip to content

Commit de62ab6

Browse files
authored
feat: More work on Anthropic and OpenAI responses (#5)
* feat: Anthropic * tests * tests * tweaks * tweaks * tweaks * tweaks * tweaks * add openai responses * wip
1 parent 8a1c209 commit de62ab6

32 files changed

+1594
-286
lines changed

.github/workflows/static-analysis.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,33 @@ jobs:
4545
4646
- name: Run phpstan
4747
run: vendor/bin/phpstan analyse -c phpstan.dist.neon --no-progress --error-format=github
48+
49+
type-coverage:
50+
runs-on: ubuntu-latest
51+
steps:
52+
- name: Checkout code
53+
uses: actions/checkout@v5
54+
55+
- name: Setup PHP
56+
uses: shivammathur/setup-php@v2
57+
with:
58+
php-version: 8.3
59+
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
60+
61+
- name: Get Composer Cache Directory
62+
id: composer-cache
63+
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
64+
65+
- name: Cache Composer dependencies
66+
uses: actions/cache@v4
67+
with:
68+
path: ${{ steps.composer-cache.outputs.dir }}
69+
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
70+
restore-keys: |
71+
${{ runner.os }}-composer-
72+
73+
- name: Install dependencies
74+
run: composer install --prefer-dist --no-interaction --no-progress
75+
76+
- name: Check type coverage
77+
run: vendor/bin/pest --type-coverage --min=100

src/LLM/AbstractLLM.php

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
use Cortex\Contracts\OutputParser;
1919
use Cortex\Support\Traits\CanPipe;
2020
use Cortex\Exceptions\LLMException;
21+
use Cortex\LLM\Data\ChatGeneration;
2122
use Cortex\JsonSchema\SchemaFactory;
2223
use Cortex\ModelInfo\Data\ModelInfo;
2324
use Cortex\LLM\Data\ChatStreamResult;
2425
use Cortex\Exceptions\PipelineException;
26+
use Cortex\LLM\Data\ChatGenerationChunk;
2527
use Cortex\ModelInfo\Enums\ModelFeature;
2628
use Cortex\JsonSchema\Types\ObjectSchema;
2729
use Cortex\LLM\Data\Messages\UserMessage;
@@ -50,6 +52,8 @@ abstract class AbstractLLM implements LLM
5052

5153
protected ?StructuredOutputConfig $structuredOutputConfig = null;
5254

55+
protected StructuredOutputMode $structuredOutputMode = StructuredOutputMode::Auto;
56+
5357
protected ?OutputParser $outputParser = null;
5458

5559
protected ?string $outputParserError = null;
@@ -64,6 +68,8 @@ abstract class AbstractLLM implements LLM
6468

6569
protected bool $shouldApplyFormatInstructions = false;
6670

71+
protected bool $shouldParseOutput = true;
72+
6773
/**
6874
* @var array<\Cortex\ModelInfo\Enums\ModelFeature>
6975
*/
@@ -116,15 +122,19 @@ public function output(OutputParser $parser): Pipeline
116122
/**
117123
* @param array<int, \Cortex\LLM\Contracts\Tool|\Cortex\JsonSchema\Contracts\Schema|\Closure|string> $tools
118124
*/
119-
public function withTools(array $tools, ToolChoice|string $toolChoice = ToolChoice::Auto): static
120-
{
125+
public function withTools(
126+
array $tools,
127+
ToolChoice|string $toolChoice = ToolChoice::Auto,
128+
bool $allowParallelToolCalls = true,
129+
): static {
121130
$this->supportsFeatureOrFail(ModelFeature::ToolCalling);
122131

123132
$this->toolConfig = $tools === []
124133
? null
125134
: new ToolConfig(
126135
Utils::toToolCollection($tools)->all(),
127136
$toolChoice,
137+
$allowParallelToolCalls,
128138
);
129139

130140
return $this;
@@ -133,9 +143,15 @@ public function withTools(array $tools, ToolChoice|string $toolChoice = ToolChoi
133143
/**
134144
* Add a tool to the LLM.
135145
*/
136-
public function addTool(Tool|Closure|string $tool, ToolChoice|string $toolChoice = ToolChoice::Auto): static
137-
{
138-
return $this->withTools([...($this->toolConfig->tools ?? []), $tool], $toolChoice);
146+
public function addTool(
147+
Tool|Closure|string $tool,
148+
ToolChoice|string $toolChoice = ToolChoice::Auto,
149+
bool $allowParallelToolCalls = true,
150+
): static {
151+
return $this->withTools([
152+
...($this->toolConfig->tools ?? []),
153+
$tool,
154+
], $toolChoice, $allowParallelToolCalls);
139155
}
140156

141157
/**
@@ -166,6 +182,7 @@ public function withStructuredOutput(
166182
bool $strict = true,
167183
StructuredOutputMode $outputMode = StructuredOutputMode::Auto,
168184
): static {
185+
$this->structuredOutputMode = $outputMode;
169186
[$schema, $outputParser] = $this->resolveSchemaAndOutputParser($output, $strict);
170187

171188
$this->withOutputParser($outputParser);
@@ -386,6 +403,64 @@ protected static function applyFormatInstructions(
386403
return $messages;
387404
}
388405

406+
public function shouldParseOutput(bool $shouldParseOutput = true): static
407+
{
408+
$this->shouldParseOutput = $shouldParseOutput;
409+
410+
return $this;
411+
}
412+
413+
protected function applyOutputParserIfApplicable(
414+
ChatGeneration|ChatGenerationChunk $generationOrChunk,
415+
): ChatGeneration|ChatGenerationChunk {
416+
if ($this->shouldParseOutput && $this->outputParser !== null) {
417+
try {
418+
$parsedOutput = $this->outputParser->parse($generationOrChunk);
419+
$generationOrChunk = $generationOrChunk->cloneWithParsedOutput($parsedOutput);
420+
} catch (OutputParserException $e) {
421+
$this->outputParserError = $e->getMessage();
422+
}
423+
}
424+
425+
return $generationOrChunk;
426+
}
427+
428+
/**
429+
* Apply the format instructions to the messages if applicable.
430+
*
431+
* @return \Cortex\LLM\Data\Messages\MessageCollection<int, \Cortex\LLM\Contracts\Message>
432+
*/
433+
protected function applyFormatInstructionsIfApplicable(
434+
MessageCollection $messages,
435+
): MessageCollection {
436+
if ($this->shouldApplyFormatInstructions && $formatInstructions = $this->outputParser?->formatInstructions()) {
437+
return static::applyFormatInstructions($messages, $formatInstructions);
438+
}
439+
440+
return $messages;
441+
}
442+
443+
/**
444+
* Resolve the messages to be used for the LLM.
445+
*
446+
* @param \Cortex\LLM\Data\Messages\MessageCollection|\Cortex\LLM\Contracts\Message|non-empty-array<int, \Cortex\LLM\Contracts\Message|\Cortex\LLM\Data\Messages\MessagePlaceholder>|string $messages
447+
*
448+
* @throws \Cortex\Exceptions\LLMException If no messages are provided
449+
*
450+
* @return \Cortex\LLM\Data\Messages\MessageCollection<int, \Cortex\LLM\Contracts\Message>
451+
*/
452+
protected function resolveMessages(
453+
MessageCollection|Message|array|string $messages,
454+
): MessageCollection {
455+
$messages = Utils::toMessageCollection($messages)->withoutPlaceholders();
456+
457+
if ($messages->isEmpty()) {
458+
throw new LLMException('You must provide at least one message to the LLM.');
459+
}
460+
461+
return $this->applyFormatInstructionsIfApplicable($messages);
462+
}
463+
389464
/**
390465
* Resolve the schema and output parser from the given output type.
391466
*

src/LLM/CacheDecorator.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,13 @@ public function ignoreFeatures(bool $ignoreModelFeatures = true): static
128128
return $this;
129129
}
130130

131+
public function shouldParseOutput(bool $shouldParseOutput = true): static
132+
{
133+
$this->llm = $this->llm->shouldParseOutput($shouldParseOutput);
134+
135+
return $this;
136+
}
137+
131138
public function withModel(string $model): static
132139
{
133140
$this->llm = $this->llm->withModel($model);

src/LLM/Contracts/LLM.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,11 @@ public function getFeatures(): array;
150150
* Get the model info for the LLM.
151151
*/
152152
public function getModelInfo(): ?ModelInfo;
153+
154+
/**
155+
* Set whether the output should be parsed.
156+
* This may be set to false when called in a pipeline context and output parsing
157+
* is done as part of the next pipeable.
158+
*/
159+
public function shouldParseOutput(bool $shouldParseOutput = true): static;
153160
}

src/LLM/Contracts/Tool.php

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

77
use Cortex\LLM\Data\ToolCall;
8-
use Cortex\JsonSchema\Contracts\Schema;
8+
use Cortex\JsonSchema\Types\ObjectSchema;
99
use Cortex\LLM\Data\Messages\ToolMessage;
1010

1111
interface Tool
@@ -23,7 +23,7 @@ public function description(): string;
2323
/**
2424
* Get the schema of the tool.
2525
*/
26-
public function schema(): Schema;
26+
public function schema(): ObjectSchema;
2727

2828
/**
2929
* Get the formatted output of the tool.

src/LLM/Data/Messages/AssistantMessage.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public function __construct(
2727
public ?ToolCallCollection $toolCalls = null,
2828
public ?string $name = null,
2929
public ?ResponseMetadata $metadata = null,
30+
public ?string $id = null,
3031
) {
3132
$this->role = MessageRole::Assistant;
3233
}

src/LLM/Data/Messages/Content/ReasoningContent.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
readonly class ReasoningContent extends AbstractContent
88
{
99
public function __construct(
10+
public string $id,
1011
public string $reasoning,
1112
) {}
1213
}

src/LLM/Data/Messages/Content/TextContent.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@
1010
readonly class TextContent extends AbstractContent
1111
{
1212
public function __construct(
13-
public string $text,
13+
public ?string $text = null,
1414
) {}
1515

1616
#[Override]
1717
public function variables(): array
1818
{
19-
return Utils::findVariables($this->text);
19+
return Utils::findVariables($this->text ?? '');
2020
}
2121

2222
#[Override]
2323
public function replaceVariables(array $variables): self
2424
{
25-
return new self(Utils::replaceVariables($this->text, $variables));
25+
return new self(Utils::replaceVariables($this->text ?? '', $variables));
2626
}
2727
}

src/LLM/Data/ToolConfig.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@
1414
public function __construct(
1515
public array $tools,
1616
public ToolChoice|string $toolChoice = ToolChoice::Auto,
17+
public bool $allowParallelToolCalls = true,
1718
) {}
1819
}

src/LLM/Data/Usage.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public function __construct(
1717
public int $promptTokens,
1818
public ?int $completionTokens = null,
1919
public ?int $cachedTokens = null,
20+
public ?int $reasoningTokens = null,
2021
?int $totalTokens = null,
2122
public ?float $inputCost = null,
2223
public ?float $outputCost = null,

0 commit comments

Comments
 (0)