Skip to content

Commit 776097a

Browse files
committed
Fixes after refactoring
1 parent b6717bc commit 776097a

File tree

14 files changed

+229
-79
lines changed

14 files changed

+229
-79
lines changed

evals/LLMModes/run.php

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,24 +50,25 @@
5050
// 'gemini-oai',
5151
//];
5252
$modes = [
53-
// OutputMode::Text,
54-
// OutputMode::MdJson,
53+
OutputMode::Text,
54+
OutputMode::MdJson,
5555
OutputMode::Json,
5656
OutputMode::JsonSchema,
5757
OutputMode::Tools,
58-
// OutputMode::Unrestricted
58+
OutputMode::Unrestricted,
5959
];
6060
$stream = [
6161
false,
62-
// true,
62+
true,
6363
];
6464

65+
$executor = (new RunInference($data));
66+
// ->withDebugPreset('on')
67+
// ->wiretap(fn($e) => $e->printLog());
68+
6569
$experiment = new Experiment(
6670
cases: InferenceCases::only($presets, $modes, $stream),
67-
//cases: InferenceCases::all(),
68-
executor: (new RunInference($data))
69-
->withDebugPreset('on'),
70-
//->wiretap(fn($e) => $e->printLog()),
71+
executor: $executor,
7172
processors: [
7273
new CompanyEval(
7374
key: 'execution.is_correct',

examples/B02_LLMAdvanced/CustomLLMDriver/run.php

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,26 @@
2525
// we will use existing, bundled driver as an example, but you can provide any class that implements
2626
// a required interface (CanHandleInference)
2727

28-
Inference::registerDriver('custom-driver', fn($config, $httpClient, $events) => new class($config, $httpClient, $events) extends OpenAIDriver {
29-
public function handle(InferenceRequest $request): HttpClientResponse {
30-
// some extra functionality to demonstrate our driver is being used
31-
echo ">>> Handling request...\n";
32-
return parent::handle($request);
28+
Inference::registerDriver(
29+
name: 'custom-driver',
30+
driver: fn($config, $httpClient, $events) => new class($config, $httpClient, $events) extends OpenAIDriver {
31+
public function handle(InferenceRequest $request): HttpClientResponse {
32+
// some extra functionality to demonstrate our driver is being used
33+
echo ">>> Handling request...\n";
34+
return parent::handle($request);
35+
}
3336
}
34-
});
37+
);
3538

3639
// Create instance of LLM client initialized with custom parameters
3740
$config = new LLMConfig(
38-
apiUrl : 'https://api.openai.com/v1',
39-
apiKey : Env::get('OPENAI_API_KEY'),
40-
endpoint: '/chat/completions', defaultModel: 'gpt-4o-mini', defaultMaxTokens: 128, httpClientPreset: 'guzzle', driver: 'custom-driver',
41+
apiUrl: 'https://api.openai.com/v1',
42+
apiKey: Env::get('OPENAI_API_KEY'),
43+
endpoint: '/chat/completions',
44+
defaultModel: 'gpt-4o-mini',
45+
defaultMaxTokens: 128,
46+
httpClientPreset: 'guzzle',
47+
driver: 'custom-driver',
4148
);
4249

4350
$answer = (new Inference)

examples/B02_LLMAdvanced/ReasoningContent/run.php

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,30 @@
1616
require 'examples/boot.php';
1717

1818
use Cognesy\Polyglot\LLM\Inference;
19+
use Cognesy\Utils\Str;
1920

2021
// EXAMPLE 1: regular API, allows to customize inference options
21-
//$response = (new Inference)
22-
//// ->wiretap(fn($e) => $e->print())
23-
// ->using('deepseek-r')
24-
// ->withMessages([['role' => 'user', 'content' => 'What is the capital of France. Answer with just a name.']])
25-
// ->withMaxTokens(256)
26-
// ->response();
27-
//
28-
//echo "\nCASE #1: Sync response\n";
29-
//echo "USER: What is capital of France\n";
30-
//echo "ASSISTANT: {$response->content()}\n";
31-
//echo "REASONING: {$response->reasoningContent()}\n";
32-
//assert($response->content() !== '');
33-
//assert(Str::contains($response->content(), 'Paris'));
34-
//assert($response->reasoningContent() !== '');
22+
$response = (new Inference)
23+
//->withDebugPreset('on')
24+
//->wiretap(fn($e) => $e->print())
25+
->using('deepseek-r')
26+
->withMessages([['role' => 'user', 'content' => 'What is the capital of France. Answer with just a name.']])
27+
->withMaxTokens(256)
28+
->response();
29+
30+
echo "\nCASE #1: Sync response\n";
31+
echo "USER: What is capital of France\n";
32+
echo "ASSISTANT: {$response->content()}\n";
33+
echo "REASONING: {$response->reasoningContent()}\n";
34+
assert($response->content() !== '');
35+
assert(Str::contains($response->content(), 'Paris'));
36+
assert($response->reasoningContent() !== '');
3537

3638

3739
// EXAMPLE 2: streaming response
3840
$stream = (new Inference)
39-
->withDebugPreset('on')
41+
//->withDebugPreset('on')
42+
//->wiretap(fn($e) => $e->print())
4043
->using('deepseek-r') // optional, default is set in /config/llm.php
4144
->with(
4245
messages: [['role' => 'user', 'content' => 'What is capital of Brasil. Answer with just a name.']],
@@ -53,8 +56,8 @@
5356
}
5457
echo "\n";
5558
echo "REASONING: {$stream->final()->reasoningContent()}\n";
56-
//assert($stream->final()->reasoningContent() !== '');
57-
//assert($stream->final()->content() !== '');
58-
//assert(Str::contains($stream->final()->content(), 'Brasília'));
59+
assert($stream->final()->reasoningContent() !== '');
60+
assert($stream->final()->content() !== '');
61+
assert(Str::contains($stream->final()->content(), 'Brasília'));
5962
?>
6063
```

packages/instructor/src/Core/RequestHandler.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ public function responseFor(StructuredOutputRequest $request) : LLMResponse {
6868
OutputMode::Tools => $llmResponse->toolCalls()->first()?->argsAsJson()
6969
?? $llmResponse->content() // fallback if no tool calls - some LLMs return just a string
7070
?? '',
71-
default => Json::fromString($llmResponse->content())->toString(), // OutputMode::MdJson, OutputMode::Json, OutputMode::JsonSchema
71+
// for OutputMode::MdJson, OutputMode::Json, OutputMode::JsonSchema try extracting JSON from content
72+
// and replacing original content with it
73+
default => Json::fromString($llmResponse->content())->toString(),
7274
});
7375
$partialResponses = [];
7476
$processingResult = $this->processResponse($request, $llmResponse, $partialResponses);

packages/polyglot/src/LLM/Contracts/CanHandleInference.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,35 @@
99

1010
interface CanHandleInference
1111
{
12+
/**
13+
* Handles an inference request and returns a response.
14+
*
15+
* @param InferenceRequest $request The inference request to handle.
16+
* @return HttpClientResponse The response from the HTTP client.
17+
*/
1218
public function handle(InferenceRequest $request) : HttpClientResponse;
19+
20+
/**
21+
* Converts the response data into an LLMResponse object.
22+
*
23+
* @param array $data The response data to convert.
24+
* @return LLMResponse|null The converted LLMResponse object or null if conversion fails.
25+
*/
1326
public function fromResponse(array $data): ?LLMResponse;
27+
28+
/**
29+
* Converts a stream response into a PartialLLMResponse object.
30+
*
31+
* @param array $data The stream response data to convert.
32+
* @return PartialLLMResponse|null The converted PartialLLMResponse object or null if conversion fails.
33+
*/
1434
public function fromStreamResponse(array $data) : ?PartialLLMResponse;
35+
36+
/**
37+
* Converts a string of stream data into a string or false if there's no more data.
38+
*
39+
* @param string $data The stream data to convert.
40+
* @return string|bool The converted data, or false if no more data is available.
41+
*/
1542
public function fromStreamData(string $data): string|bool;
1643
}

packages/polyglot/src/LLM/Data/CachedContext.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,13 @@ public function isEmpty() : bool {
4545
&& empty($this->toolChoice)
4646
&& empty($this->responseFormat);
4747
}
48+
49+
public function clone() : self {
50+
return new self(
51+
messages: $this->messages->toArray(),
52+
tools: $this->tools,
53+
toolChoice: $this->toolChoice,
54+
responseFormat: $this->responseFormat,
55+
);
56+
}
4857
}

packages/polyglot/src/LLM/Data/LLMResponse.php

Lines changed: 84 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
class LLMResponse
1313
{
1414
private mixed $value = null;
15+
private array $partialResponses = [];
16+
private bool $isPartial = false;
1517

1618
public function __construct(
1719
private string $content = '',
@@ -32,8 +34,16 @@ public function __construct(
3234
* @param PartialLLMResponse[] $partialResponses
3335
* @return LLMResponse
3436
*/
35-
public static function fromPartialResponses(array $partialResponses) : LLMResponse {
36-
return (new self)->makeFromPartialResponses($partialResponses);
37+
public static function fromPartialResponses(array $partialResponses = []) : self {
38+
$newResponse = new self();
39+
foreach ($partialResponses as $partialResponse) {
40+
if ($partialResponse === null) {
41+
continue;
42+
}
43+
$newResponse->applyPartialResponse($partialResponse);
44+
}
45+
$newResponse->toolCalls = ToolCalls::fromArray(self::makeTools($partialResponses));
46+
return $newResponse;
3747
}
3848

3949
// PUBLIC ////////////////////////////////////////////////
@@ -150,6 +160,10 @@ public function finishReason() : LLMFinishReason {
150160
return LLMFinishReason::fromText($this->finishReason);
151161
}
152162

163+
public function hasFinishReason() : bool {
164+
return $this->finishReason !== '';
165+
}
166+
153167
public function responseData() : array {
154168
return $this->responseData;
155169
}
@@ -161,61 +175,98 @@ public function toArray() : array {
161175
'finishReason' => $this->finishReason,
162176
'toolCalls' => $this->toolCalls?->toArray() ?? [],
163177
'usage' => $this->usage->toArray(),
178+
// raw response data
164179
'responseData' => $this->responseData,
165180
];
166181
}
167182

183+
public function clone() : self {
184+
return new self(
185+
content: $this->content,
186+
finishReason: $this->finishReason,
187+
toolCalls: $this->toolCalls?->clone(),
188+
reasoningContent: $this->reasoningContent,
189+
usage: $this->usage?->clone(),
190+
responseData: $this->responseData,
191+
);
192+
}
193+
168194
// INTERNAL //////////////////////////////////////////////
169195

170196
/**
171-
* @param PartialLLMResponse[] $partialResponses
172-
* @return LLMResponse
197+
* Apply a partial response to the current response.
198+
* This will accumulate content, reasoning content, usage,
199+
* and response data.
200+
*
201+
* @param PartialLLMResponse $partialResponse
173202
*/
174-
private function makeFromPartialResponses(array $partialResponses = []) : self {
175-
if (empty($partialResponses)) {
176-
return $this;
177-
}
178-
179-
$content = '';
180-
$reasoningContent = '';
181-
foreach($partialResponses as $partialResponse) {
182-
if ($partialResponse === null) {
183-
continue;
184-
}
185-
$content .= $partialResponse->contentDelta;
186-
$reasoningContent .= $partialResponse->reasoningContentDelta;
203+
private function applyPartialResponse(PartialLLMResponse $partialResponse) : void {
204+
$this->content .= $partialResponse->contentDelta ?? '';
205+
$this->reasoningContent .= $partialResponse->reasoningContentDelta ?? '';
206+
$this->finishReason = $partialResponse->finishReason ?? $this->finishReason;
207+
$this->usage()->accumulate($partialResponse->usage);
208+
if (!empty($partialResponse->responseData)) {
187209
$this->responseData[] = $partialResponse->responseData;
188-
$this->usage()->accumulate($partialResponse->usage);
189-
$this->finishReason = $partialResponse->finishReason;
190-
}
191-
$this->content = $content;
192-
$this->reasoningContent = $reasoningContent;
193-
194-
$tools = $this->makeTools($partialResponses);
195-
if (!empty($tools)) {
196-
$this->toolCalls = ToolCalls::fromArray($tools);
197210
}
198-
return $this;
199211
}
200212

201-
private function makeTools(array $partialResponses): array {
213+
/**
214+
* Make a list of tool calls from the partial responses.
215+
*
216+
* @param PartialLLMResponse[] $partialResponses
217+
* @return array
218+
*/
219+
private static function makeTools(array $partialResponses): array {
202220
$tools = [];
203221
$currentTool = '';
204222
foreach ($partialResponses as $partialResponse) {
205223
if ($partialResponse === null) {
206224
continue;
207225
}
208-
if (('' !== ($partialResponse->toolName ?? ''))
209-
&& ($currentTool !== ($partialResponse->toolName ?? ''))) {
210-
$currentTool = $partialResponse->toolName ?? '';
226+
// if the tool name changes, start a new tool call
227+
if ($partialResponse->hasToolName()
228+
&& ($currentTool !== ($partialResponse->toolName()))) {
229+
$currentTool = $partialResponse->toolName();
211230
$tools[$currentTool] = '';
212231
}
232+
// append the tool arguments to it
213233
if ('' !== $currentTool) {
214-
if (('' !== ($partialResponse->toolArgs ?? ''))) {
215-
$tools[$currentTool] .= $partialResponse->toolArgs ?? '';
234+
if ($partialResponse->hasToolArgs()) {
235+
$tools[$currentTool] .= $partialResponse->toolArgs();
216236
}
217237
}
218238
}
219239
return $tools;
220240
}
221241
}
242+
243+
// /**
244+
// * @param PartialLLMResponse[] $partialResponses
245+
// * @return LLMResponse
246+
// */
247+
// private function makeFromPartialResponses(array $partialResponses = []) : self {
248+
// if (empty($partialResponses)) {
249+
// return $this;
250+
// }
251+
//
252+
// $content = '';
253+
// $reasoningContent = '';
254+
// foreach($partialResponses as $partialResponse) {
255+
// if ($partialResponse === null) {
256+
// continue;
257+
// }
258+
// $content .= $partialResponse->contentDelta;
259+
// $reasoningContent .= $partialResponse->reasoningContentDelta;
260+
// $this->responseData[] = $partialResponse->responseData;
261+
// $this->usage()->accumulate($partialResponse->usage);
262+
// $this->finishReason = $partialResponse->finishReason;
263+
// }
264+
// $this->content = $content;
265+
// $this->reasoningContent = $reasoningContent;
266+
//
267+
// $tools = self::makeTools($partialResponses);
268+
// if (!empty($tools)) {
269+
// $this->toolCalls = ToolCalls::fromArray($tools);
270+
// }
271+
// return $this;
272+
// }

packages/polyglot/src/LLM/Data/PartialLLMResponse.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,15 @@ public function usage() : Usage {
7878
return $this->usage ?? new Usage();
7979
}
8080

81+
public function hasToolArgs() : bool {
82+
// do not change to not empty, as it will return true for '0'
83+
return '' !== ($this->toolArgs ?? '');
84+
}
85+
86+
public function hasToolName() : string {
87+
return '' !== ($this->toolName ?? '');
88+
}
89+
8190
public function toArray() : array {
8291
return [
8392
'content_delta' => $this->contentDelta,
@@ -90,4 +99,12 @@ public function toArray() : array {
9099
'response_data' => $this->responseData,
91100
];
92101
}
102+
103+
public function toolName() : string {
104+
return $this->toolName ?? '';
105+
}
106+
107+
public function toolArgs() : string {
108+
return $this->toolArgs ?? '';
109+
}
93110
}

0 commit comments

Comments
 (0)