Извлечение структурированных данных в PHP на базе LLM. Создан для простоты, прозрачности и контроля.
Instructor — это библиотека, которая позволяет извлекать структурированные, проверенные данные из нескольких типов входных данных: текста, изображений или массивов последовательностей чатов в стиле OpenAI. Он основан на больших языковых моделях (LLM).
Инструктор упрощает интеграцию LLM в проекты PHP. Он справляется со сложностью извлечения структурированных данных из выходных данных LLM, поэтому вы можете сосредоточиться на построении логики приложения и выполнять итерации быстрее.
Instructor for PHP вдохновлен библиотекой Instructor для Python, созданной Джейсоном Лю.
Вот простое демонстрационное приложение CLI, использующее Instructor для извлечения структурированных данных из текста:
Structure
.Ознакомьтесь с реализациями на других языках ниже:
Если вы хотите перенести Instructor на другой язык, свяжитесь с нами в Твиттере, и мы будем рады помочь вам начать работу!
Instructor представляет три ключевых улучшения по сравнению с прямым использованием API.
Вы просто указываете класс PHP для извлечения данных с помощью «волшебства» завершения чата LLM. И все.
Инструктор снижает хрупкость кода, извлекающего информацию из текстовых данных, используя структурированные ответы LLM.
Instructor помогает вам писать более простой и понятный код — вам больше не придется определять длинные определения вызовов функций или писать код для назначения возвращаемого JSON целевым объектам данных.
Модель ответа, созданная LLM, может быть автоматически проверена в соответствии с набором правил. В настоящее время Instructor поддерживает только проверку Symfony.
Вы также можете предоставить объект контекста, чтобы использовать расширенные возможности валидатора.
Вы можете установить количество повторных попыток запросов.
Инструктор будет повторять запросы в случае ошибки проверки или десериализации указанное количество раз, пытаясь получить действительный ответ от LLM.
Установка Инструктора проста. Запустите следующую команду в своем терминале, и вы на пути к более удобной обработке данных!
composer require cognesy/instructor-php
Это простой пример, демонстрирующий, как Instructor извлекает структурированную информацию из предоставленного текста (или последовательности сообщений чата).
Класс модели ответа — это простой класс PHP с подсказками, определяющими типы полей объекта.
use Cognesy Instructor Instructor ;
// Step 0: Create .env file in your project root:
// OPENAI_API_KEY=your_api_key
// Step 1: Define target data structure(s)
class Person {
public string $ name ;
public int $ age ;
}
// Step 2: Provide content to process
$ text = " His name is Jason and he is 28 years old. " ;
// Step 3: Use Instructor to run LLM inference
$ person = ( new Instructor )-> respond (
messages: $ text ,
responseModel: Person ::class,
);
// Step 4: Work with structured response data
assert ( $ person instanceof Person ); // true
assert ( $ person -> name === ' Jason ' ); // true
assert ( $ person -> age === 28 ); // true
echo $ person -> name ; // Jason
echo $ person -> age ; // 28
var_dump ( $ person );
// Person {
// name: "Jason",
// age: 28
// }
ПРИМЕЧАНИЕ. Инструктор поддерживает классы/объекты в качестве моделей ответа. Если вы хотите извлечь простые типы или перечисления, вам необходимо обернуть их в скалярный адаптер — см. раздел ниже: Извлечение скалярных значений.
Инструктор позволяет вам определить несколько соединений API в файле llm.php
. Это полезно, если вы хотите использовать в своем приложении разные поставщики LLM или API.
Конфигурация по умолчанию находится в /config/llm.php
в корневом каталоге кодовой базы Instructor. Он содержит набор предопределенных подключений ко всем API-интерфейсам LLM, готовым к использованию Instructor.
Файл конфигурации определяет подключения к API LLM и их параметры. Он также определяет соединение по умолчанию, которое будет использоваться при вызове Instructor без указания клиентского соединения.
// This is fragment of /config/llm.php file
' defaultConnection ' => ' openai ' ,
// . . .
' connections ' => [
' anthropic ' => [ ... ],
' azure ' => [ ... ],
' cohere1 ' => [ ... ],
' cohere2 ' => [ ... ],
' fireworks ' => [ ... ],
' gemini ' => [ ... ],
' grok ' => [ ... ],
' groq ' => [ ... ],
' mistral ' => [ ... ],
' ollama ' => [
' providerType ' => LLMProviderType :: Ollama -> value ,
' apiUrl ' => ' http://localhost:11434/v1 ' ,
' apiKey ' => Env :: get ( ' OLLAMA_API_KEY ' , '' ),
' endpoint ' => ' /chat/completions ' ,
' defaultModel ' => ' qwen2.5:0.5b ' ,
' defaultMaxTokens ' => 1024 ,
' httpClient ' => ' guzzle-ollama ' , // use custom HTTP client configuration
],
' openai ' => [ ... ],
' openrouter ' => [ ... ],
' together ' => [ ... ],
// ...
Чтобы настроить доступные соединения, вы можете изменить существующие записи или добавить свои собственные.
Подключиться к LLM API через предопределенное соединение так же просто, как вызвать метод withClient
с именем соединения.
<?php
// ...
$ user = ( new Instructor )
-> withConnection ( ' ollama ' )
-> respond (
messages: " His name is Jason and he is 28 years old. " ,
responseModel: Person ::class,
);
// ...
Вы можете изменить расположение файлов конфигурации, которые будет использовать Инструктор, с помощью переменной среды INSTRUCTOR_CONFIG_PATH
. В качестве отправной точки можно использовать копии файлов конфигурации по умолчанию.
Инструктор предлагает способ использовать структурированные данные в качестве входных данных. Это полезно, если вы хотите использовать данные объекта в качестве входных данных и получить другой объект в результате вывода LLM.
Поле input
методов respond()
и request()
инструктора может быть объектом, а также массивом или просто строкой.
<?php
use Cognesy Instructor Instructor ;
class Email {
public function __construct (
public string $ address = '' ,
public string $ subject = '' ,
public string $ body = '' ,
) {}
}
$ email = new Email (
address: ' joe@gmail ' ,
subject: ' Status update ' ,
body: ' Your account has been updated. '
);
$ translation = ( new Instructor )-> respond (
input: $ email ,
responseModel: Email ::class,
prompt: ' Translate the text fields of email to Spanish. Keep other fields unchanged. ' ,
);
assert ( $ translation instanceof Email ); // true
dump ( $ translation );
// Email {
// address: "joe@gmail",
// subject: "Actualización de estado",
// body: "Su cuenta ha sido actualizada."
// }
?>
Инструктор проверяет результаты ответа LLM на соответствие правилам проверки, указанным в вашей модели данных.
Для получения дополнительной информации о доступных правилах проверки проверьте ограничения Symfony Validation.
use Symfony Component Validator Constraints as Assert ;
class Person {
public string $ name ;
#[ Assert PositiveOrZero ]
public int $ age ;
}
$ text = " His name is Jason, he is -28 years old. " ;
$ person = ( new Instructor )-> respond (
messages: [[ ' role ' => ' user ' , ' content ' => $ text ]],
responseModel: Person ::class,
);
// if the resulting object does not validate, Instructor throws an exception
Если указан параметр maxRetries и ответ LLM не соответствует критериям проверки, инструктор будет предпринимать последующие попытки вывода до тех пор, пока результаты не будут соответствовать требованиям или не будет достигнуто значение maxRetries.
Инструктор использует ошибки проверки, чтобы информировать LLM о проблемах, выявленных в ответе, чтобы LLM мог попытаться самостоятельно исправиться при следующей попытке.
use Symfony Component Validator Constraints as Assert ;
class Person {
#[ Assert Length (min: 3 )]
public string $ name ;
#[ Assert PositiveOrZero ]
public int $ age ;
}
$ text = " His name is JX, aka Jason, he is -28 years old. " ;
$ person = ( new Instructor )-> respond (
messages: [[ ' role ' => ' user ' , ' content ' => $ text ]],
responseModel: Person ::class,
maxRetries: 3 ,
);
// if all LLM's attempts to self-correct the results fail, Instructor throws an exception
Вы можете вызвать метод request()
, чтобы установить параметры запроса, а затем вызвать get()
чтобы получить ответ.
use Cognesy Instructor Instructor ;
$ instructor = ( new Instructor )-> request (
messages: " His name is Jason, he is 28 years old. " ,
responseModel: Person ::class,
);
$ person = $ instructor -> get ();
Instructor поддерживает потоковую передачу частичных результатов, что позволяет вам начать обработку данных, как только они станут доступны.
<?php
use Cognesy Instructor Instructor ;
$ stream = ( new Instructor )-> request (
messages: " His name is Jason, he is 28 years old. " ,
responseModel: Person ::class,
options: [ ' stream ' => true ]
)-> stream ();
foreach ( $ stream as $ partialPerson ) {
// process partial person data
echo $ partialPerson -> name ;
echo $ partialPerson -> age ;
}
// after streaming is done you can get the final, fully processed person object...
$ person = $ stream -> getLastUpdate ()
// . . . to, for example, save it to the database
$ db -> save ( $ person );
?>
Вы можете определить обратный вызов onPartialUpdate()
для получения частичных результатов, которые можно использовать для начала обновления пользовательского интерфейса до того, как LLM завершит вывод.
ПРИМЕЧАНИЕ. Частичные обновления не проверяются. Ответ проверяется только после его полного получения.
use Cognesy Instructor Instructor ;
function updateUI ( $ person ) {
// Here you get partially completed Person object update UI with the partial result
}
$ person = ( new Instructor )-> request (
messages: " His name is Jason, he is 28 years old. " ,
responseModel: Person ::class,
options: [ ' stream ' => true ]
)-> onPartialUpdate (
fn( $ partial ) => updateUI ( $ partial )
)-> get ();
// Here you get completed and validated Person object
$ this -> db -> save ( $ person ); // ...for example: save to DB
Вместо массива сообщений вы можете предоставить строку. Это полезно, если вы хотите извлечь данные из одного блока текста и сохранить простой код.
// Usually, you work with sequences of messages:
$ value = ( new Instructor )-> respond (
messages: [[ ' role ' => ' user ' , ' content ' => " His name is Jason, he is 28 years old. " ]],
responseModel: Person ::class,
);
// ...but if you want to keep it simple, you can just pass a string:
$ value = ( new Instructor )-> respond (
messages: " His name is Jason, he is 28 years old. " ,
responseModel: Person ::class,
);
Иногда мы просто хотим получить быстрые результаты без определения класса для модели ответа, особенно если мы пытаемся получить прямой и простой ответ в форме строки, целого числа, логического значения или числа с плавающей запятой. Инструктор предоставляет упрощенный API для таких случаев.
use Cognesy Instructor Extras Scalar Scalar ;
use Cognesy Instructor Instructor ;
$ value = ( new Instructor )-> respond (
messages: " His name is Jason, he is 28 years old. " ,
responseModel: Scalar :: integer ( ' age ' ),
);
var_dump ( $ value );
// int(28)
В этом примере мы извлекаем из текста одно целочисленное значение. Вы также можете использовать Scalar::string()
, Scalar::boolean()
и Scalar::float()
для извлечения других типов значений.
Кроме того, вы можете использовать адаптер Scalar для извлечения одного из предоставленных параметров с помощью Scalar::enum()
.
use Cognesy Instructor Extras Scalar Scalar ;
use Cognesy Instructor Instructor ;
enum ActivityType : string {
case Work = ' work ' ;
case Entertainment = ' entertainment ' ;
case Sport = ' sport ' ;
case Other = ' other ' ;
}
$ value = ( new Instructor )-> respond (
messages: " His name is Jason, he currently plays Doom Eternal. " ,
responseModel: Scalar :: enum ( ActivityType ::class, ' activityType ' ),
);
var_dump ( $ value );
// enum(ActivityType:Entertainment)
Sequence — это класс-оболочка, который можно использовать для представления списка объектов, извлекаемых Instructor из предоставленного контекста.
Обычно удобнее не создавать выделенный класс с одним свойством массива только для обработки списка объектов данного класса.
Дополнительной уникальной особенностью последовательностей является то, что их можно передавать в потоковом режиме для каждого завершенного элемента в последовательности, а не для любого обновления свойства.
class Person
{
public string $ name ;
public int $ age ;
}
$ text = <<<TEXT
Jason is 25 years old. Jane is 18 yo. John is 30 years old
and Anna is 2 years younger than him.
TEXT ;
$ list = ( new Instructor )-> respond (
messages: [[ ' role ' => ' user ' , ' content ' => $ text ]],
responseModel: Sequence :: of ( Person ::class),
options: [ ' stream ' => true ]
);
Дополнительную информацию о последовательностях см. в разделе «Последовательности».
Используйте подсказки типов PHP, чтобы указать тип извлекаемых данных.
Используйте типы, допускающие значение NULL, чтобы указать, что данное поле является необязательным.
class Person {
public string $ name ;
public ? int $ age ;
public Address $ address ;
}
Вы также можете использовать комментарии в стиле PHP DocBlock, чтобы указать тип извлекаемых данных. Это полезно, если вы хотите указать типы свойств для LLM, но не можете или не хотите применять тип на уровне кода.
class Person {
/** @var string */
public $ name ;
/** @var int */
public $ age ;
/** @var Address $address person's address */
public $ address ;
}
Дополнительную информацию см. в документации PHPDoc на веб-сайте DocBlock.
В настоящее время PHP не поддерживает дженерики или подсказки типов для указания типов элементов массива.
Используйте комментарии в стиле PHP DocBlock, чтобы указать тип элементов массива.
class Person {
// ...
}
class Event {
// ...
/** @var Person[] list of extracted event participants */
public array $ participants ;
// ...
}
Преподаватель может извлекать сложные структуры данных из текста. Ваша модель ответа может содержать вложенные объекты, массивы и перечисления.
use Cognesy Instructor Instructor ;
// define a data structures to extract data into
class Person {
public string $ name ;
public int $ age ;
public string $ profession ;
/** @var Skill[] */
public array $ skills ;
}
class Skill {
public string $ name ;
public SkillType $ type ;
}
enum SkillType {
case Technical = ' technical ' ;
case Other = ' other ' ;
}
$ text = " Alex is 25 years old software engineer, who knows PHP, Python and can play the guitar. " ;
$ person = ( new Instructor )-> respond (
messages: [[ ' role ' => ' user ' , ' content ' => $ text ]],
responseModel: Person ::class,
); // client is passed explicitly, can specify e.g. different base URL
// data is extracted into an object of given class
assert ( $ person instanceof Person ); // true
// you can access object's extracted property values
echo $ person -> name ; // Alex
echo $ person -> age ; // 25
echo $ person -> profession ; // software engineer
echo $ person -> skills [ 0 ]-> name ; // PHP
echo $ person -> skills [ 0 ]-> type ; // SkillType::Technical
// ...
var_dump ( $ person );
// Person {
// name: "Alex",
// age: 25,
// profession: "software engineer",
// skills: [
// Skill {
// name: "PHP",
// type: SkillType::Technical,
// },
// Skill {
// name: "Python",
// type: SkillType::Technical,
// },
// Skill {
// name: "guitar",
// type: SkillType::Other
// },
// ]
// }
Если вы хотите определить форму данных во время выполнения, вы можете использовать класс Structure
.
Структуры позволяют вам определять и изменять произвольную форму данных, которые будут извлечены с помощью LLM. Классы могут не подойти для этой цели наилучшим образом, поскольку их объявление или изменение во время выполнения невозможно.
С помощью структур вы можете динамически определять пользовательские формы данных, например, на основе пользовательского ввода или контекста обработки, чтобы указать информацию, которую LLM должен выводить из предоставленного текста или сообщений чата.
Пример ниже демонстрирует, как определить структуру и использовать ее в качестве модели ответа:
<?php
use Cognesy Instructor Extras Structure Field ;
use Cognesy Instructor Extras Structure Structure ;
enum Role : string {
case Manager = ' manager ' ;
case Line = ' line ' ;
}
$ structure = Structure :: define ( ' person ' , [
Field :: string ( ' name ' ),
Field :: int ( ' age ' ),
Field :: enum ( ' role ' , Role ::class),
]);
$ person = ( new Instructor )-> respond (
messages: ' Jason is 25 years old and is a manager. ' ,
responseModel: $ structure ,
);
// you can access structure data via field API...
assert ( $ person -> field ( ' name ' ) === ' Jason ' );
// ...or as structure object properties
assert ( $ person -> age === 25 );
?>
Дополнительную информацию см. в разделе «Структуры».
Вы можете указать модель и другие параметры, которые будут переданы в конечную точку OpenAI/LLM.
use Cognesy Instructor Features LLM Data LLMConfig ;
use Cognesy Instructor Features LLM Drivers OpenAIDriver ;
use Cognesy Instructor Instructor ;
// OpenAI auth params
$ yourApiKey = Env :: get ( ' OPENAI_API_KEY ' ); // use your own API key
// Create instance of OpenAI driver initialized with custom parameters
$ driver = new OpenAIDriver ( new LLMConfig (
apiUrl: ' https://api.openai.com/v1 ' , // you can change base URI
apiKey: $ yourApiKey ,
endpoint: ' /chat/completions ' ,
metadata: [ ' organization ' => '' ],
model: ' gpt-4o-mini ' ,
maxTokens: 128 ,
));
/// Get Instructor with the default client component overridden with your own
$ instructor = ( new Instructor )-> withDriver ( $ driver );
$ user = $ instructor -> respond (
messages: " Jason (@jxnlco) is 25 years old and is the admin of this project. He likes playing football and reading books. " ,
responseModel: User ::class,
model: ' gpt-3.5-turbo ' ,
options: [ ' stream ' => true ]
);
Instructor предлагает встроенную поддержку следующих поставщиков API:
Примеры использования можно найти в разделе Hub или в каталоге examples
в репозитории кода.
Вы можете использовать PHP DocBlocks (/** */) для предоставления дополнительных инструкций для LLM на уровне класса или поля, например, чтобы уточнить, чего вы ожидаете или как LLM должен обрабатывать ваши данные.
Инструктор извлекает комментарии PHP DocBlocks из определенного класса и свойства и включает их в спецификацию модели ответа, отправляемой в LLM.
Использование инструкций PHP DocBlocks не требуется, но иногда вам может потребоваться уточнить свои намерения по улучшению результатов вывода LLM.
/**
* Represents a skill of a person and context in which it was mentioned.
*/
class Skill {
public string $ name ;
/** @var SkillType $type type of the skill, derived from the description and context */
public SkillType $ type ;
/** Directly quoted, full sentence mentioning person's skill */
public string $ context ;
}
Вы можете использовать черту ValidationMixin, чтобы добавить возможность простой, настраиваемой проверки объекта данных.
use Cognesy Instructor Features Validation Traits ValidationMixin ;
class User {
use ValidationMixin ;
public int $ age ;
public int $ name ;
public function validate () : array {
if ( $ this -> age < 18 ) {
return [ " User has to be adult to sign the contract. " ];
}
return [];
}
}
Инструктор использует компонент проверки Symfony для проверки извлеченных данных. Вы можете использовать аннотацию #[Assert/Callback] для создания полностью настраиваемой логики проверки.
use Cognesy Instructor Instructor ;
use Symfony Component Validator Constraints as Assert ;
use Symfony