PHP 中的结构化数据提取,由法学硕士提供支持。专为简单、透明和控制而设计。
Instructor 是一个库,允许您从多种类型的输入中提取结构化的、经过验证的数据:文本、图像或 OpenAI 风格的聊天序列数组。它由大型语言模型 (LLM) 提供支持。
讲师简化了 PHP 项目中的 LLM 集成。它处理从 LLM 输出中提取结构化数据的复杂性,因此您可以专注于构建应用程序逻辑并更快地迭代。
Instructor for PHP 的灵感来自 Jason Liu 创建的 Python Instructor 库。
这是一个简单的 CLI 演示应用程序,使用 Instructor 从文本中提取结构化数据:
Structure
类的动态数据形状查看以下其他语言的实现:
如果您想将 Instructor 移植为其他语言,请在 Twitter 上联系我们,我们很乐意帮助您入门!
与直接使用 API 相比,讲师介绍了三个关键增强功能。
您只需指定一个 PHP 类即可通过 LLM 聊天完成的“魔力”将数据提取到其中。就是这样。
讲师利用结构化的 LLM 响应,降低了从文本数据中提取信息的代码的脆弱性。
讲师可帮助您编写更简单、更易于理解的代码 - 您不再需要定义冗长的函数调用定义或编写用于将返回的 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
// }
注意:教师支持类/对象作为响应模型。如果您想提取简单类型或枚举,则需要将它们包装在标量适配器中 - 请参阅下面的部分:提取标量值。
Instructor 允许您在llm.php
文件中定义多个 API 连接。当您想在应用程序中使用不同的 LLM 或 API 提供商时,这非常有用。
默认配置位于 Instructor 代码库根目录中的/config/llm.php
中。它包含一组与 Instructor 开箱即用支持的所有 LLM API 的预定义连接。
配置文件定义与 LLM API 的连接及其参数。它还指定在调用 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
环境变量更改 Instructor 使用的配置文件的位置。您可以使用默认配置文件的副本作为起点。
Instructor 提供了一种使用结构化数据作为输入的方法。当您想要使用对象数据作为输入并获取具有 LLM 推理结果的另一个对象时,这非常有用。
Instructor 的respond()
和request()
方法的input
字段可以是一个对象,也可以是一个数组或只是一个字符串。
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 验证约束。
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 完成推理之前开始更新 UI。
注意:部分更新未经验证。响应仅在完全收到后才进行验证。
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,
);
有时我们只是想快速获得结果,而不需要为响应模型定义类,特别是当我们试图以字符串、整数、布尔值或浮点形式获得直接、简单的答案时。 Instructor 为此类情况提供了简化的 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 ;
}
有关 DocBlock 网站上的更多详细信息,请参阅 PHPDoc 文档。
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 ]
);
讲师为以下 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 文档。
PHP 讲师处理您的请求时,会经历几个阶段:
Instructor 允许您通过事件接收请求和响应处理的每个阶段的详细信息。
(new Instructor)->onEvent(string $class, callable $callback)
方法 - 在调度指定类型的事件时接收回调(new Instructor)->wiretap(callable $callback)
方法 - 接收由 Instructor 调度的任何事件,可能对调试或性能分析有用接收事件可以帮助您监视执行过程,并使开发人员更容易理解和解决任何处理问题。
$ 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()
方法的签名表明responseModel
可以是字符串、对象或数组。
如果提供了string
值,则将其用作响应模型的类的名称。
教师检查类是否存在,并分析类和属性类型信息和文档注释,以生成指定 LLM 响应约束所需的模式。
提供响应模型类名称的最佳方法是使用NameOfTheClass::class
而不是字符串,从而使 IDE 能够执行类型检查、处理重构等。
如果提供了object
值,则将其视为响应模型的实例。教师检查实例的类,然后分析它及其属性类型数据以指定 LLM 响应约束。
如果提供了array
值,则它被视为原始 JSON 模式,因此允许讲师直接在 LLM 请求中使用它(在包装在适当的上下文中 - 例如函数调用之后)。
Instructor 需要有关 JSON 架构中每个嵌套对象的类的信息,以便它可以正确地将数据反序列化为适当的类型。
当您将 $responseModel 作为类名或实例传递时,讲师可以使用此信息,但原始 JSON 架构中缺少此信息。
当前的设计使用属性上的 JSON 模式$comment
字段来克服这个问题。教师期望开发人员使用$comment
字段提供目标类的完全限定名称,用于反序列化对象或枚举类型的属性数据。
Instructor 允许您通过查看类或实例实现的接口来自定义 $responseModel 值的处理:
CanProvideJsonSchema
- 实现能够提供 JSON Schema 或响应模型,覆盖 Instructor 的默认方法,即分析 $responseModel 值类信息,CanDeserializeSelf
- 实现自定义将来自 LLM 的响应从 JSON 反序列化为 PHP 对象的方式,CanValidateSelf
- 实现自定义验证反序列化对象的方式,CanTransformSelf
- 实现将经过验证的对象转换为调用者接收的目标值(例如,将简单类型从类解包为标量值)。 PHP 生态系统(目前)还没有强大的 Pydantic 等价物,而 Pydantic 是 Instructor for Python 的核心。
为了提供我们需要的基本功能,Instructor for PHP 利用了:
Instructor for PHP 与 PHP 8.2 或更高版本兼容,并且由于依赖性最小,应该适用于您选择的任何框架。
一些额外功能需要额外的依赖项:
如果您想提供帮助,请检查一些问题。欢迎所有贡献 - 代码改进、文档、错误报告、博客文章/文章或新的食谱和应用程序示例。
该项目根据 MIT 许可证条款获得许可。
如果您有任何疑问或需要帮助,请通过 Twitter 或 GitHub 与我联系。