Извлечение структурированных данных в 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
с именем соединения.
// ...
$ 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()
инструктора может быть объектом, а также массивом или просто строкой.
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 поддерживает потоковую передачу частичных результатов, что позволяет вам начать обработку данных, как только они станут доступны.
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 = <<
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 должен получить из предоставленного текста или сообщений чата.
Пример ниже демонстрирует, как определить структуру и использовать ее в качестве модели ответа:
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 Component Validator Context ExecutionContextInterface ;
class UserDetails
{
public string $ name ;
public int $ age ;
#[ Assert Callback ]
public function validateName ( ExecutionContextInterface $ context , mixed $ payload ) {
if ( $ this -> name !== strtoupper ( $ this -> name )) {
$ context -> buildViolation ( " Name must be in uppercase. " )
-> atPath ( ' name ' )
-> setInvalidValue ( $ this -> name )
-> addViolation ();
}
}
}
$ user = ( new Instructor )-> respond (
messages: [[ ' role ' => ' user ' , ' content ' => ' jason is 25 years old ' ]],
responseModel: UserDetails ::class,
maxRetries: 2
);
assert ( $ user -> name === " JASON " );
См. документацию Symfony для получения более подробной информации о том, как использовать ограничение обратного вызова.
Когда Instructor for PHP обрабатывает ваш запрос, он проходит несколько этапов:
Инструктор позволяет получать подробную информацию на каждом этапе обработки запросов и ответов через события.
(new Instructor)->onEvent(string $class, callable $callback)
— получение обратного вызова при отправке события указанного типа(new Instructor)->wiretap(callable $callback)
— получение любого события, отправленного инструктором, может быть полезно для отладки или анализа производительности.Получение событий может помочь вам отслеживать процесс выполнения, а разработчику будет проще понять и решить любые проблемы с обработкой.
$ instructor = ( new Instructor )
// see requests to LLM
-> onEvent ( RequestSentToLLM ::class, fn( $ e ) => dump ( $ e ))
// see responses from LLM
-> onEvent ( ResponseReceivedFromLLM ::class, fn( $ event ) => dump ( $ event ))
// see all events in console-friendly format
-> wiretap (fn( $ event ) => dump ( $ event -> toConsole ()));
$ instructor -> respond (
messages: " What is the population of Paris? " ,
responseModel: Scalar :: integer (),
);
// check your console for the details on the Instructor execution
Instructor может обрабатывать несколько типов входных данных, предоставленных в виде модели ответа, что дает вам большую гибкость при взаимодействии с библиотекой.
В сигнатуре метода respond()
Instructor указано, что responseModel
может быть строкой, объектом или массивом.
Если указано string
значение, оно используется как имя класса модели ответа.
Инструктор проверяет, существует ли класс, и анализирует информацию о классе и типе свойств, а также комментарии к документации, чтобы создать схему, необходимую для указания ограничений ответа LLM.
Лучший способ указать имя класса модели ответа — использовать NameOfTheClass::class
вместо строки, что позволит IDE выполнять проверки типов, обрабатывать рефакторинг и т. д.
Если указано значение object
, оно считается экземпляром модели ответа. Инструктор проверяет класс экземпляра, затем анализирует его и данные типа его свойства, чтобы указать ограничения ответа LLM.
Если указано значение array
, оно считается необработанной схемой JSON, что позволяет инструктору использовать его непосредственно в запросах LLM (после переноса в соответствующий контекст — например, вызов функции).
Инструктору требуется информация о классе каждого вложенного объекта в вашей схеме JSON, чтобы он мог правильно десериализовать данные в соответствующий тип.
Эта информация доступна Инструктору, когда вы передаете $responseModel как имя класса или экземпляр, но она отсутствует в необработанной схеме JSON.
Для решения этой проблемы в текущей конструкции используется поле $comment
JSON Schema для свойства. Инструктор ожидает, что разработчик будет использовать поле $comment
для предоставления полного имени целевого класса, который будет использоваться для десериализации данных свойств объекта или типа перечисления.
Instructor позволяет вам настроить обработку значения $responseModel, также просматривая интерфейсы, реализуемые классом или экземпляром:
CanProvideJsonSchema
— реализация, позволяющая предоставлять схему JSON или модель ответа, переопределяя подход Instructor по умолчанию, который анализирует информацию о классе значений $responseModel.CanDeserializeSelf
— реализация для настройки способа десериализации ответа от LLM из JSON в объект PHP,CanValidateSelf
— реализация для настройки способа проверки десериализованного объекта,CanTransformSelf
— реализация для преобразования проверенного объекта в целевое значение, полученное вызывающей стороной (например, развертывание простого типа из класса в скалярное значение). В экосистеме PHP (пока) нет сильного эквивалента Pydantic, который лежит в основе Instructor for Python.
Чтобы обеспечить необходимую функциональность, которая нам нужна, Instructor for PHP использует:
Instructor for PHP совместим с PHP 8.2 или более поздней версии и из-за минимальных зависимостей должен работать с любой платформой по вашему выбору.
Для некоторых дополнений требуются дополнительные зависимости:
Если вы хотите помочь, ознакомьтесь с некоторыми вопросами. Приветствуются любые вклады — улучшения кода, документация, отчеты об ошибках, сообщения/статьи в блогах или новые кулинарные книги и примеры приложений.
Этот проект лицензируется в соответствии с условиями лицензии MIT.
Если у вас есть какие-либо вопросы или вам нужна помощь, свяжитесь со мной в Twitter или GitHub.