Skip to content

Commit ed91996

Browse files
committed
Consistent structured output, inference, embeddings, http client facades/builders
1 parent 17e838b commit ed91996

File tree

67 files changed

+1039
-1370
lines changed

Some content is hidden

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

67 files changed

+1039
-1370
lines changed

bin/clean-composer.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env bash
2+
set -e # stops script on first error
3+
4+
for dir in packages/*; do
5+
if [ -f "$dir/composer.json" ]; then
6+
echo "🔍 Removing composer caches and ./vendor/* in $dir"
7+
composer --working-dir="$dir" clear-cache
8+
composer --working-dir="$dir" dump-autoload
9+
rm -rf "$dir/vendor/"*
10+
fi
11+
done

config/llm.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,20 @@
147147
'contextLength' => 128_000,
148148
'maxOutputLength' => 2048,
149149
],
150+
'huggingface' => [
151+
'providerType' => 'huggingface',
152+
'apiUrl' => 'https://router.huggingface.co/{providerId}/v1',
153+
'apiKey' => Env::get('HUGGINGFACE_API_KEY', ''),
154+
'endpoint' => '/chat/completions',
155+
'metadata' => [
156+
'providerId' => 'hf-inference/models/microsoft/phi-4',
157+
],
158+
'defaultModel' => 'microsoft/phi-4',
159+
'defaultOutputMode' => 'text',
160+
'defaultMaxTokens' => 1024,
161+
'contextLength' => 32_000,
162+
'maxOutputLength' => 4096,
163+
],
150164
'meta' => [
151165
'providerType' => 'meta',
152166
'apiUrl' => 'https://openrouter.ai/api/v1',

docs/release-notes/v1.0.0.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
- (all) Multiple breaking changes - proceed with caution
2-
- (all) Common conventions for working with StructuredOutput, Inference, and Embeddings classes
32
- (instructor) the `Instructor` class is being replaced with `StructuredOutput` class; the old class will be kept for some time to allow for a smooth transition.
3+
- (all) Common conventions for working with StructuredOutput, Inference, and Embeddings classes
44
- (examples) All examples have been updated to use the new `StructuredOutput` class and recommended create(), generate() methods
55
- (docs) Updated documentation to reflect the new `StructuredOutput` class and its usage
66
- (instructor) Extracted structured output config into a separate file config/structured.php (and removed from config/llm.php)
@@ -15,5 +15,6 @@
1515
- (polyglot) Corrections in inference drivers, fixed defects in JSON/JSON Schema modes
1616
- (polyglot) Fixed error in selection of embeddings driver
1717
- (polyglot) Added `withDebug()` support to Embeddings class
18+
- (polyglot) Added experimental support for HuggingFace inference API
1819
- (all) Multiple changes, improvements and refactorings in the codebase
1920
- (all) Updated docs and examples to reflect the latest changes

examples/A02_Advanced/CustomClientParameters/run.php

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
use Cognesy\Http\Data\HttpClientConfig;
1919
use Cognesy\Http\Drivers\SymfonyDriver;
20-
use Cognesy\Http\HttpClientFactory;
20+
use Cognesy\Http\HttpClient;
2121
use Cognesy\Instructor\StructuredOutput;
2222
use Cognesy\Polyglot\LLM\Data\LLMConfig;
2323
use Cognesy\Polyglot\LLM\Enums\OutputMode;
@@ -30,19 +30,9 @@ class User {
3030
public string $name;
3131
}
3232

33-
// Create instance of LLM connection preset initialized with custom parameters
34-
$llmConfig = new LLMConfig(
35-
apiUrl: 'https://api.deepseek.com',
36-
apiKey: Env::get('DEEPSEEK_API_KEY'),
37-
endpoint: '/chat/completions',
38-
model: 'deepseek-chat',
39-
maxTokens: 128,
40-
httpClient: 'symfony',
41-
providerType: 'openai-compatible',
42-
);
43-
4433
// Build fully customized HTTP client
4534
$events = new EventDispatcher();
35+
4636
$httpConfig = new HttpClientConfig(
4737
httpClientType: 'symfony',
4838
connectTimeout: 5,
@@ -52,15 +42,31 @@ class User {
5242
poolTimeout: 60,
5343
failOnError: true,
5444
);
55-
$driver = new SymfonyDriver(
56-
config: $httpConfig,
57-
clientInstance: SymfonyHttpClient::create(['http_version' => '2.0']),
58-
events: $events,
45+
46+
$customClient = (new HttpClient)
47+
->withEventDispatcher($events)
48+
->withEventListener($events)
49+
->withDriver(new SymfonyDriver(
50+
config: $httpConfig,
51+
clientInstance: SymfonyHttpClient::create(['http_version' => '2.0']),
52+
events: $events,
53+
));
54+
55+
// Create instance of LLM connection preset initialized with custom parameters
56+
$llmConfig = new LLMConfig(
57+
apiUrl: 'https://api.deepseek.com',
58+
apiKey: Env::get('DEEPSEEK_API_KEY'),
59+
endpoint: '/chat/completions',
60+
model: 'deepseek-chat',
61+
maxTokens: 128,
62+
httpClient: 'symfony',
63+
providerType: 'openai-compatible',
5964
);
60-
$customClient = (new HttpClientFactory($events))->fromDriver($driver);
6165

6266
// Get Instructor with the default client component overridden with your own
6367
$structuredOutput = (new StructuredOutput)
68+
->withEventDispatcher($events)
69+
->withEventListener($events)
6470
->withLLMConfig($llmConfig)
6571
->withHttpClient($customClient);
6672

@@ -74,6 +80,7 @@ class User {
7480
)
7581
->withStreaming()
7682
->get();
83+
7784
dump($user);
7885

7986
assert(isset($user->name));

examples/A02_Advanced/ProvidingExamples/run.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class User {
3232
$user = (new StructuredOutput)
3333
// let's dump the request data to see how examples are used in requests
3434
->onEvent(HttpRequestSent::class, fn($event) => dump($event))
35-
->withMessage("Our user Jason is 25 years old.")
35+
->withMessages("Our user Jason is 25 years old.")
3636
->withResponseClass(User::class)
3737
->withExamples([
3838
new Example(
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
---
2+
title: 'Hugging Face'
3+
docname: 'huggingface'
4+
---
5+
6+
## Overview
7+
8+
You can use Instructor to parse structured output from LLMs using Hugging Face API.
9+
This example demonstrates how to parse user data into a structured model using
10+
JSON Schema.
11+
12+
## Example
13+
14+
```php
15+
<?php
16+
require 'examples/boot.php';
17+
18+
use Cognesy\Instructor\StructuredOutput;
19+
use Cognesy\Polyglot\LLM\Enums\OutputMode;
20+
21+
enum UserType : string {
22+
case Guest = 'guest';
23+
case User = 'user';
24+
case Admin = 'admin';
25+
}
26+
27+
class User {
28+
public string $firstName;
29+
public UserType $role;
30+
/** @var string[] */
31+
public array $hobbies;
32+
public string $username;
33+
public ?int $age;
34+
}
35+
36+
// Get Instructor with specified LLM client connection
37+
// See: /config/llm.php to check or change LLM client connection configuration details
38+
$structuredOutput = (new StructuredOutput)->using('huggingface')->withDebug();
39+
40+
$user = $structuredOutput
41+
->with(
42+
messages: "Jason (@jxnlco) is 25 years old. He is the admin of this project. He likes playing football and reading books.",
43+
responseModel: User::class,
44+
prompt: 'Parse the user data to JSON, respond using following JSON Schema: <|json_schema|>',
45+
examples: [[
46+
'input' => 'Ive got email Frank - their developer, who\'s 30. He asked to come back to him frank@hk.ch. Btw, he plays on drums!',
47+
'output' => ['firstName' => 'Frank', 'age' => 30, 'username' => 'frank@hk.ch', 'role' => 'user', 'hobbies' => ['playing drums'],],
48+
],[
49+
'input' => 'We have a meeting with John, our new admin who likes surfing. He is 19 years old - check his profile: @jx90.',
50+
'output' => ['firstName' => 'John', 'role' => 'admin', 'hobbies' => ['surfing'], 'username' => 'jx90', 'age' => 19],
51+
]],
52+
//model: 'deepseek-ai/DeepSeek-R1-0528-Qwen3-8B',
53+
maxRetries: 2,
54+
options: ['temperature' => 0.5],
55+
mode: OutputMode::Json,
56+
)->get();
57+
58+
print("Completed response model:\n\n");
59+
60+
dump($user);
61+
62+
assert(isset($user->firstName));
63+
assert(isset($user->role));
64+
assert(isset($user->age));
65+
assert(isset($user->hobbies));
66+
assert(isset($user->username));
67+
assert(is_array($user->hobbies));
68+
assert(count($user->hobbies) > 0);
69+
assert($user->role === UserType::Admin);
70+
assert($user->age === 25);
71+
assert($user->firstName === 'Jason');
72+
assert(in_array($user->username, ['jxnlco', '@jxnlco']));
73+
?>
74+
```

examples/B02_LLMAdvanced/CustomClientParameters/run.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@
1616

1717
use Cognesy\Http\Data\HttpClientConfig;
1818
use Cognesy\Http\Drivers\SymfonyDriver;
19-
use Cognesy\Http\HttpClientFactory;
19+
use Cognesy\Http\HttpClient;
2020
use Cognesy\Polyglot\LLM\Data\LLMConfig;
2121
use Cognesy\Polyglot\LLM\Inference;
2222
use Cognesy\Utils\Env;
23-
use Cognesy\Utils\Events\EventDispatcher;
2423
use Cognesy\Utils\Str;
2524
use Symfony\Component\HttpClient\HttpClient as SymfonyHttpClient;
2625

@@ -36,7 +35,6 @@
3635
);
3736

3837
// Build fully customized HTTP client
39-
$events = new EventDispatcher();
4038
$httpConfig = new HttpClientConfig(
4139
httpClientType: 'symfony',
4240
connectTimeout: 5,
@@ -49,9 +47,8 @@
4947
$driver = new SymfonyDriver(
5048
config: $httpConfig,
5149
clientInstance: SymfonyHttpClient::create(['http_version' => '2.0']),
52-
events: $events,
5350
);
54-
$customClient = (new HttpClientFactory($events))->fromDriver($driver);
51+
$customClient = (new HttpClient)->withDriver($driver);
5552

5653
$answer = (new Inference)
5754
->withConfig($config)

examples/B02_LLMAdvanced/CustomLLMDriver/run.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,20 @@
1818
use Cognesy\Polyglot\LLM\Data\LLMConfig;
1919
use Cognesy\Polyglot\LLM\Drivers\OpenAI\OpenAIDriver;
2020
use Cognesy\Polyglot\LLM\Inference;
21-
use Cognesy\Polyglot\LLM\LLM;
2221
use Cognesy\Polyglot\LLM\InferenceRequest;
2322
use Cognesy\Utils\Env;
2423
use Cognesy\Utils\Str;
2524

2625
// we will use existing, bundled driver as an example, but you can provide any class that implements
2726
// a required interface (CanHandleInference)
2827

29-
LLM::registerDriver('custom-driver', fn($config, $httpClient, $events) => new class($config, $httpClient, $events) extends OpenAIDriver {
28+
Inference::registerDriver('custom-driver', fn($config, $httpClient, $events) => new class($config, $httpClient, $events) extends OpenAIDriver {
3029
public function handle(InferenceRequest $request): HttpClientResponse {
3130
// some extra functionality to demonstrate our driver is being used
3231
echo ">>> Handling request...\n";
3332
return parent::handle($request);
3433
}
35-
}
36-
);
34+
});
3735

3836
// Create instance of LLM client initialized with custom parameters
3937
$config = new LLMConfig(
@@ -48,7 +46,7 @@ public function handle(InferenceRequest $request): HttpClientResponse {
4846

4947
$answer = (new Inference)
5048
->withConfig($config)
51-
->withMessage(['role' => 'user', 'content' => 'What is the capital of France'])
49+
->withMessages([['role' => 'user', 'content' => 'What is the capital of France']])
5250
->withOptions(['max_tokens' => 64])
5351
->get();
5452

examples/B02_LLMAdvanced/DSN/run.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
use Cognesy\Polyglot\LLM\Inference;
1919
use Cognesy\Utils\Str;
2020

21-
$answer = Inference
22-
::fromDSN('preset=xai,model=grok-2')
21+
$answer = (new Inference)
22+
->withDSN('preset=xai,model=grok-2')
2323
->with(
2424
messages: [['role' => 'user', 'content' => 'What is the capital of France']],
2525
options: ['max_tokens' => 64]

examples/B02_LLMAdvanced/ReasoningContent/run.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@
2525
->using('deepseek-r') // optional, default is set in /config/llm.php
2626
->with(
2727
messages: [['role' => 'user', 'content' => 'What is the capital of France. Answer with just a name.']],
28-
options: ['max_tokens' => 64]
28+
options: ['max_tokens' => 128]
2929
)
3030
->response();
31+
dd($response);
3132
echo "\nCASE #1: Sync response\n";
3233
echo "USER: What is capital of France\n";
3334
echo "ASSISTANT: {$response->content()}\n";
@@ -39,11 +40,11 @@
3940
// EXAMPLE 2: streaming response
4041
$stream = (new Inference)
4142
->using('deepseek-r') // optional, default is set in /config/llm.php
42-
->withStreaming()
4343
->with(
4444
messages: [['role' => 'user', 'content' => 'What is capital of Brasil. Answer with just a name.']],
4545
options: ['max_tokens' => 128]
4646
)
47+
->withStreaming()
4748
->stream();
4849

4950
echo "\nCASE #2: Streamed response\n";

0 commit comments

Comments
 (0)