{"id":13982452,"url":"https://github.com/cognesy/instructor-php","last_synced_at":"2026-04-01T17:55:22.066Z","repository":{"id":224538750,"uuid":"763463572","full_name":"cognesy/instructor-php","owner":"cognesy","description":"Unified LLM API, structured data outputs with LLMs, and agent SDK - in PHP","archived":false,"fork":false,"pushed_at":"2026-03-30T09:17:19.000Z","size":84652,"stargazers_count":312,"open_issues_count":3,"forks_count":24,"subscribers_count":7,"default_branch":"main","last_synced_at":"2026-03-30T10:31:03.691Z","etag":null,"topics":["agents","ai","ai-agents","genai","llm","php"],"latest_commit_sha":null,"homepage":"https://instructorphp.com","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cognesy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2024-02-26T10:47:00.000Z","updated_at":"2026-03-30T09:58:33.000Z","dependencies_parsed_at":"2025-11-30T14:04:08.198Z","dependency_job_id":null,"html_url":"https://github.com/cognesy/instructor-php","commit_stats":{"total_commits":522,"total_committers":7,"mean_commits":74.57142857142857,"dds":"0.019157088122605415","last_synced_commit":"5b2107d7c0e919dca9742cbd85a90b656788df32"},"previous_names":["cognesy/instructor-php"],"tags_count":194,"template":false,"template_full_name":null,"purl":"pkg:github/cognesy/instructor-php","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cognesy%2Finstructor-php","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cognesy%2Finstructor-php/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cognesy%2Finstructor-php/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cognesy%2Finstructor-php/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cognesy","download_url":"https://codeload.github.com/cognesy/instructor-php/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cognesy%2Finstructor-php/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31290687,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agents","ai","ai-agents","genai","llm","php"],"created_at":"2024-08-09T05:01:22.083Z","updated_at":"2026-04-01T17:55:22.051Z","avatar_url":"https://github.com/cognesy.png","language":"PHP","funding_links":[],"categories":["LLMs \u0026 AI APIs","Table of Contents","Natural Language Processing"],"sub_categories":["Recommended core stack","LLMs","Structured Output \u0026 Validation"],"readme":"# Instructor for PHP\r\n\r\nStructured data extraction in PHP, powered by LLMs. Designed for simplicity, transparency, and control.\r\n\r\n\r\n## What is Instructor?\r\n\r\nInstructor is a library that allows you to extract structured, validated data from multiple types of inputs: text, images or OpenAI style chat sequence arrays. It is powered by Large Language Models (LLMs).\r\n\r\nInstructor simplifies LLM integration in PHP projects. It handles the complexity of extracting structured data from LLM outputs, so you can focus on building your application logic and iterate faster.\r\n\r\nInstructor for PHP is inspired by the [Instructor](https://jxnl.github.io/instructor/) library for Python created by [Jason Liu](https://twitter.com/jxnlco).\r\n\r\n![image](./docs/images/concept.png)\r\n\r\nHere's a simple CLI demo app using Instructor to extract structured data from text:\r\n\r\n![image](./docs/images/extraction.gif)\r\n\r\n\r\n\r\n\r\n## Feature highlights\r\n\r\n### Core features\r\n\r\n- Get structured responses from LLMs without writing boilerplate code\r\n- Validation of returned data\r\n- Automated retries in case of errors when LLM responds with invalid data\r\n- Integrate LLM support into your existing PHP code with minimal friction - no framework, no extensive code changes\r\n\r\n### Flexible inputs\r\n\r\n- Process various types of input data: text, series of chat messages or images using the same, simple API\r\n- 'Structured-to-structured' processing - provide object or array as an input and get object with the results of inference back\r\n- Demonstrate examples to improve the quality of inference\r\n\r\n### Customization\r\n\r\n- Define response data model the way you want: type-hinted classes, JSON Schema arrays, or dynamic data shapes with `Structure` class\r\n- Customize prompts and retry prompts\r\n- Use attributes or PHP DocBlocks to provide additional instructions for LLM\r\n- Customize response model processing by providing your own implementation of schema, deserialization, validation and transformation interfaces\r\n\r\n### Sync and streaming support\r\n\r\n- Supports both synchronous or streaming responses\r\n- Get partial updates \u0026 stream completed sequence items\r\n\r\n### Observability\r\n\r\n- Get detailed insight into internal processing via events\r\n- Debug mode to see the details of LLM API requests and responses\r\n\r\n### Support for multiple LLMs / API providers\r\n\r\n- Easily switch between LLM providers\r\n- Support for most popular LLM APIs (incl. OpenAI, Gemini, Anthropic, Cohere, Azure, Groq, Mistral, Fireworks AI, Together AI)\r\n- OpenRouter support - access to 100+ language models\r\n- Use local models with Ollama\r\n\r\n### Other capabilities\r\n\r\n- Developer friendly LLM context caching for reduced costs and faster inference (for Anthropic models)\r\n- Developer friendly data extraction from images (for OpenAI, Anthropic and Gemini models)\r\n\r\n### Documentation and examples\r\n\r\n- Learn more from growing documentation and 50+ cookbooks\r\n\r\n\r\n\r\n## Instructor in Other Languages\r\n\r\nCheck out implementations in other languages below:\r\n\r\n- [Python](https://www.github.com/jxnl/instructor) (original)\r\n- [Javascript](https://github.com/instructor-ai/instructor-js) (port)\r\n- [Elixir](https://github.com/thmsmlr/instructor_ex/) (port)\r\n\r\nIf you want to port Instructor to another language, please reach out to us on [Twitter](https://twitter.com/jxnlco) we'd love to help you get started!\r\n\r\n\r\n## How Instructor Enhances Your Workflow\r\n\r\nInstructor introduces three key enhancements compared to direct API usage.\r\n\r\n### Response Model\r\n\r\nYou just specify a PHP class to extract data into via the 'magic' of LLM chat completion. And that's it.\r\n\r\nInstructor reduces brittleness of the code extracting the information from textual data by leveraging structured LLM responses.\r\n\r\nInstructor helps you write simpler, easier to understand code - you no longer have to define lengthy function call definitions or write code for assigning returned JSON into target data objects.\r\n\r\n### Validation\r\n\r\nResponse model generated by LLM can be automatically validated, following set of rules. Currently, Instructor supports only Symfony validation.\r\n\r\nYou can also provide a context object to use enhanced validator capabilities.\r\n\r\n### Max Retries\r\n\r\nYou can set the number of retry attempts for requests.\r\n\r\nInstructor will repeat requests in case of validation or deserialization error up to the specified number of times, trying to get a valid response from LLM.\r\n\r\n\r\n## Get Started\r\n\r\nInstalling Instructor is simple. Run following command in your terminal, and you're on your way to a smoother data handling experience!\r\n\r\n```bash\r\ncomposer require cognesy/instructor-php\r\n```\r\n\r\n\r\n## Usage\r\n\r\n\r\n### Basic example\r\n\r\nThis is a simple example demonstrating how Instructor retrieves structured information from provided text (or chat message sequence).\r\n\r\nResponse model class is a plain PHP class with typehints specifying the types of fields of the object.\r\n\r\n```php\r\nuse Cognesy\\Instructor\\Instructor;\r\n\r\n// Step 0: Create .env file in your project root:\r\n// OPENAI_API_KEY=your_api_key\r\n\r\n// Step 1: Define target data structure(s)\r\nclass Person {\r\n    public string $name;\r\n    public int $age;\r\n}\r\n\r\n// Step 2: Provide content to process\r\n$text = \"His name is Jason and he is 28 years old.\";\r\n\r\n// Step 3: Use Instructor to run LLM inference\r\n$person = (new Instructor)-\u003erespond(\r\n    messages: $text,\r\n    responseModel: Person::class,\r\n);\r\n\r\n// Step 4: Work with structured response data\r\nassert($person instanceof Person); // true\r\nassert($person-\u003ename === 'Jason'); // true\r\nassert($person-\u003eage === 28); // true\r\n\r\necho $person-\u003ename; // Jason\r\necho $person-\u003eage; // 28\r\n\r\nvar_dump($person);\r\n// Person {\r\n//     name: \"Jason\",\r\n//     age: 28\r\n// }    \r\n```\r\n\u003e **NOTE:** Instructor supports classes / objects as response models. In case you want to extract simple types or enums, you need to wrap them in Scalar adapter - see section below: Extracting Scalar Values.\r\n\u003e\r\n\r\n\r\n### Connecting to various LLM API providers\r\n\r\nInstructor allows you to define multiple API connections in `llm.php` file.\r\nThis is useful when you want to use different LLMs or API providers in your application.\r\n\r\nDefault configuration is located in `/config/llm.php` in the root directory\r\nof Instructor codebase. It contains a set of predefined connections to all LLM APIs\r\nsupported out-of-the-box by Instructor.\r\n\r\nConfig file defines connections to LLM APIs and their parameters. It also specifies\r\nthe default connection to be used when calling Instructor without specifying\r\nthe client connection.\r\n\r\n```php\r\n/* This is fragment of /config/llm.php file */\r\n    'defaultConnection' =\u003e 'openai',\r\n    //...\r\n    'connections' =\u003e [\r\n        'anthropic' =\u003e [ ... ],\r\n        'cohere2' =\u003e [ ... ],\r\n        'gemini' =\u003e [ ... ],\r\n        'ollama' =\u003e [\r\n            'clientType' =\u003e ClientType::Ollama-\u003evalue,\r\n            'apiUrl' =\u003e Env::get('OLLAMA_API_URL', 'http://localhost:11434/v1'),\r\n            'apiKey' =\u003e Env::get('OLLAMA_API_KEY', ''),\r\n            'defaultModel' =\u003e Env::get('OLLAMA_DEFAULT_MODEL', 'gemma2:2b'),\r\n            'defaultMaxTokens' =\u003e Env::get('OLLAMA_DEFAULT_MAX_TOKENS', 1024),\r\n            'connectTimeout' =\u003e Env::get('OLLAMA_CONNECT_TIMEOUT', 3),\r\n            'requestTimeout' =\u003e Env::get('OLLAMA_REQUEST_TIMEOUT', 30),\r\n        ],\r\n    // ...\r\n```\r\nTo customize the available connections you can either modify existing entries or\r\nadd your own.\r\n\r\nConnecting to LLM API via predefined connection is as simple as calling `withClient`\r\nmethod with the connection name.\r\n\r\n```php\r\n\u003c?php\r\n// ...\r\n$user = (new Instructor)\r\n    -\u003ewithConnection('ollama')\r\n    -\u003erespond(\r\n        messages: \"His name is Jason and he is 28 years old.\",\r\n        responseModel: Person::class,\r\n    );\r\n// ...\r\n```\r\n\r\nYou can change the location of the configuration files for Instructor to use via\r\n`INSTRUCTOR_CONFIG_PATH` environment variable. You can use copies of the default\r\nconfiguration files as a starting point.\r\n\r\n\r\n\r\n### Structured-to-structured processing\r\n\r\nInstructor offers a way to use structured data as an input. This is\r\nuseful when you want to use object data as input and get another object\r\nwith a result of LLM inference.\r\n\r\nThe `input` field of Instructor's `respond()` and `request()` methods\r\ncan be an object, but also an array or just a string.\r\n\r\n```php\r\n\u003c?php\r\nuse Cognesy\\Instructor\\Instructor;\r\n\r\nclass Email {\r\n    public function __construct(\r\n        public string $address = '',\r\n        public string $subject = '',\r\n        public string $body = '',\r\n    ) {}\r\n}\r\n\r\n$email = new Email(\r\n    address: 'joe@gmail',\r\n    subject: 'Status update',\r\n    body: 'Your account has been updated.'\r\n);\r\n\r\n$translation = (new Instructor)-\u003erespond(\r\n    input: $email,\r\n    responseModel: Email::class,\r\n    prompt: 'Translate the text fields of email to Spanish. Keep other fields unchanged.',\r\n);\r\n\r\nassert($translation instanceof Email); // true\r\ndump($translation);\r\n// Email {\r\n//     address: \"joe@gmail\",\r\n//     subject: \"Actualización de estado\",\r\n//     body: \"Su cuenta ha sido actualizada.\"\r\n// }\r\n?\u003e\r\n```\r\n\r\n\r\n### Validation\r\n\r\nInstructor validates results of LLM response against validation rules specified in your data model.\r\n\r\n\u003e For further details on available validation rules, check [Symfony Validation constraints](https://symfony.com/doc/current/validation.html#constraints).\r\n\r\n```php\r\nuse Symfony\\Component\\Validator\\Constraints as Assert;\r\n\r\nclass Person {\r\n    public string $name;\r\n    #[Assert\\PositiveOrZero]\r\n    public int $age;\r\n}\r\n\r\n$text = \"His name is Jason, he is -28 years old.\";\r\n$person = (new Instructor)-\u003erespond(\r\n    messages: [['role' =\u003e 'user', 'content' =\u003e $text]],\r\n    responseModel: Person::class,\r\n);\r\n\r\n// if the resulting object does not validate, Instructor throws an exception\r\n```\r\n\r\n\r\n### Max Retries\r\n\r\nIn case maxRetries parameter is provided and LLM response does not meet validation criteria, Instructor will make subsequent inference attempts until results meet the requirements or maxRetries is reached.\r\n\r\nInstructor uses validation errors to inform LLM on the problems identified in the response, so that LLM can try self-correcting in the next attempt.\r\n\r\n```php\r\nuse Symfony\\Component\\Validator\\Constraints as Assert;\r\n\r\nclass Person {\r\n    #[Assert\\Length(min: 3)]\r\n    public string $name;\r\n    #[Assert\\PositiveOrZero]\r\n    public int $age;\r\n}\r\n\r\n$text = \"His name is JX, aka Jason, he is -28 years old.\";\r\n$person = (new Instructor)-\u003erespond(\r\n    messages: [['role' =\u003e 'user', 'content' =\u003e $text]],\r\n    responseModel: Person::class,\r\n    maxRetries: 3,\r\n);\r\n\r\n// if all LLM's attempts to self-correct the results fail, Instructor throws an exception\r\n```\r\n\r\n\r\n### Alternative ways to call Instructor\r\n\r\nYou can call `request()` method to set the parameters of the request and then call `get()` to get the response.\r\n\r\n```php\r\nuse Cognesy\\Instructor\\Instructor;\r\n\r\n$instructor = (new Instructor)-\u003erequest(\r\n    messages: \"His name is Jason, he is 28 years old.\",\r\n    responseModel: Person::class,\r\n);\r\n$person = $instructor-\u003eget();\r\n```\r\n\r\n\r\n### Streaming support\r\n\r\nInstructor supports streaming of partial results, allowing you to start\r\nprocessing the data as soon as it is available.\r\n\r\n```php\r\n\u003c?php\r\nuse Cognesy\\Instructor\\Instructor;\r\n\r\n$stream = (new Instructor)-\u003erequest(\r\n    messages: \"His name is Jason, he is 28 years old.\",\r\n    responseModel: Person::class,\r\n    options: ['stream' =\u003e true]\r\n)-\u003estream();\r\n\r\nforeach ($stream as $partialPerson) {\r\n    // process partial person data\r\n    echo $partialPerson-\u003ename;\r\n    echo $partialPerson-\u003eage;\r\n}\r\n\r\n// after streaming is done you can get the final, fully processed person object...\r\n$person = $stream-\u003egetLastUpdate()\r\n// ...to, for example, save it to the database\r\n$db-\u003esave($person);\r\n?\u003e\r\n```\r\n\r\n\r\n\r\n### Partial results\r\n\r\nYou can define `onPartialUpdate()` callback to receive partial results that can be used to start updating UI before LLM completes the inference.\r\n\r\n\u003e NOTE: Partial updates are not validated. The response is only validated after it is fully received.\r\n\r\n```php\r\nuse Cognesy\\Instructor\\Instructor;\r\n\r\nfunction updateUI($person) {\r\n    // Here you get partially completed Person object update UI with the partial result\r\n}\r\n\r\n$person = (new Instructor)-\u003erequest(\r\n    messages: \"His name is Jason, he is 28 years old.\",\r\n    responseModel: Person::class,\r\n    options: ['stream' =\u003e true]\r\n)-\u003eonPartialUpdate(\r\n    fn($partial) =\u003e updateUI($partial)\r\n)-\u003eget();\r\n\r\n// Here you get completed and validated Person object\r\n$this-\u003edb-\u003esave($person); // ...for example: save to DB\r\n```\r\n\r\n\r\n\r\n## Shortcuts \r\n\r\n### String as Input\r\n\r\nYou can provide a string instead of an array of messages. This is useful when you want to extract data from a single block of text and want to keep your code simple.\r\n\r\n```php\r\n// Usually, you work with sequences of messages:\r\n\r\n$value = (new Instructor)-\u003erespond(\r\n    messages: [['role' =\u003e 'user', 'content' =\u003e \"His name is Jason, he is 28 years old.\"]],\r\n    responseModel: Person::class,\r\n);\r\n\r\n// ...but if you want to keep it simple, you can just pass a string:\r\n\r\n$value = (new Instructor)-\u003erespond(\r\n    messages: \"His name is Jason, he is 28 years old.\",\r\n    responseModel: Person::class,\r\n);\r\n```\r\n\r\n\r\n### Extracting Scalar Values\r\n\r\nSometimes we just want to get quick results without defining a class for the response model, especially if we're trying to get a straight, simple answer in a form of string, integer, boolean or float. Instructor provides a simplified API for such cases.\r\n\r\n```php\r\nuse Cognesy\\Instructor\\Extras\\Scalar\\Scalar;\r\nuse Cognesy\\Instructor\\Instructor;\r\n\r\n$value = (new Instructor)-\u003erespond(\r\n    messages: \"His name is Jason, he is 28 years old.\",\r\n    responseModel: Scalar::integer('age'),\r\n);\r\n\r\nvar_dump($value);\r\n// int(28)\r\n```\r\n\r\nIn this example, we're extracting a single integer value from the text. You can also use `Scalar::string()`, `Scalar::boolean()` and `Scalar::float()` to extract other types of values.\r\n\r\n\r\n### Extracting Enum Values\r\n\r\nAdditionally, you can use Scalar adapter to extract one of the provided options by using `Scalar::enum()`.\r\n\r\n```php\r\nuse Cognesy\\Instructor\\Extras\\Scalar\\Scalar;\r\nuse Cognesy\\Instructor\\Instructor;\r\n\r\nenum ActivityType : string {\r\n    case Work = 'work';\r\n    case Entertainment = 'entertainment';\r\n    case Sport = 'sport';\r\n    case Other = 'other';\r\n}\r\n\r\n$value = (new Instructor)-\u003erespond(\r\n    messages: \"His name is Jason, he currently plays Doom Eternal.\",\r\n    responseModel: Scalar::enum(ActivityType::class, 'activityType'),\r\n);\r\n\r\nvar_dump($value);\r\n// enum(ActivityType:Entertainment)\r\n```\r\n\r\n\r\n### Extracting Sequences of Objects\r\n\r\nSequence is a wrapper class that can be used to represent a list of objects to\r\nbe extracted by Instructor from provided context.\r\n\r\nIt is usually more convenient not create a dedicated class with a single array\r\nproperty just to handle a list of objects of a given class.\r\n\r\nAdditional, unique feature of sequences is that they can be streamed per each\r\ncompleted item in a sequence, rather than on any property update.\r\n\r\n```php\r\nclass Person\r\n{\r\n    public string $name;\r\n    public int $age;\r\n}\r\n\r\n$text = \u003c\u003c\u003cTEXT\r\n    Jason is 25 years old. Jane is 18 yo. John is 30 years old\r\n    and Anna is 2 years younger than him.\r\nTEXT;\r\n\r\n$list = (new Instructor)-\u003erespond(\r\n    messages: [['role' =\u003e 'user', 'content' =\u003e $text]],\r\n    responseModel: Sequence::of(Person::class),\r\n    options: ['stream' =\u003e true]\r\n);\r\n```\r\n\r\nSee more about sequences in the [Sequences](docs/sequences.md) section.\r\n\r\n\r\n\r\n## Specifying Data Model\r\n\r\n### Type Hints\r\n\r\nUse PHP type hints to specify the type of extracted data.\r\n\r\n\u003e Use nullable types to indicate that given field is optional.\r\n\r\n```php\r\n    class Person {\r\n        public string $name;\r\n        public ?int $age;\r\n        public Address $address;\r\n    }\r\n```\r\n\r\n### DocBlock type hints\r\n\r\nYou can also use PHP DocBlock style comments to specify the type of extracted data. This is useful when you want to specify property types for LLM, but can't or don't want to enforce type at the code level.\r\n\r\n```php\r\nclass Person {\r\n    /** @var string */\r\n    public $name;\r\n    /** @var int */\r\n    public $age;\r\n    /** @var Address $address person's address */\r\n    public $address;\r\n}\r\n```\r\n\r\nSee PHPDoc documentation for more details on [DocBlock website](https://docs.phpdoc.org/3.0/guide/getting-started/what-is-a-docblock.html#what-is-a-docblock).\r\n\r\n\r\n### Typed Collections / Arrays\r\n\r\nPHP currently [does not support generics](https://wiki.php.net/rfc/generics) or typehints to specify array element types.\r\n\r\nUse PHP DocBlock style comments to specify the type of array elements.\r\n\r\n```php\r\nclass Person {\r\n    // ...\r\n}\r\n\r\nclass Event {\r\n    // ...\r\n    /** @var Person[] list of extracted event participants */\r\n    public array $participants;\r\n    // ...\r\n}\r\n```\r\n\r\n\r\n### Complex data extraction\r\n\r\nInstructor can retrieve complex data structures from text. Your response model can contain nested objects, arrays, and enums.\r\n\r\n```php\r\nuse Cognesy\\Instructor\\Instructor;\r\n\r\n// define a data structures to extract data into\r\nclass Person {\r\n    public string $name;\r\n    public int $age;\r\n    public string $profession;\r\n    /** @var Skill[] */\r\n    public array $skills;\r\n}\r\n\r\nclass Skill {\r\n    public string $name;\r\n    public SkillType $type;\r\n}\r\n\r\nenum SkillType {\r\n    case Technical = 'technical';\r\n    case Other = 'other';\r\n}\r\n\r\n$text = \"Alex is 25 years old software engineer, who knows PHP, Python and can play the guitar.\";\r\n\r\n$person = (new Instructor)-\u003erespond(\r\n    messages: [['role' =\u003e 'user', 'content' =\u003e $text]],\r\n    responseModel: Person::class,\r\n); // client is passed explicitly, can specify e.g. different base URL\r\n\r\n// data is extracted into an object of given class\r\nassert($person instanceof Person); // true\r\n\r\n// you can access object's extracted property values\r\necho $person-\u003ename; // Alex\r\necho $person-\u003eage; // 25\r\necho $person-\u003eprofession; // software engineer\r\necho $person-\u003eskills[0]-\u003ename; // PHP\r\necho $person-\u003eskills[0]-\u003etype; // SkillType::Technical\r\n// ...\r\n\r\nvar_dump($person);\r\n// Person {\r\n//     name: \"Alex\",\r\n//     age: 25,\r\n//     profession: \"software engineer\",\r\n//     skills: [\r\n//         Skill {\r\n//              name: \"PHP\",\r\n//              type: SkillType::Technical,\r\n//         },\r\n//         Skill {\r\n//              name: \"Python\",\r\n//              type: SkillType::Technical,\r\n//         },\r\n//         Skill {\r\n//              name: \"guitar\",\r\n//              type: SkillType::Other\r\n//         },\r\n//     ]\r\n// }\r\n```\r\n\r\n\r\n### Dynamic data schemas\r\n\r\nIf you want to define the shape of data during runtime, you can use `Structure` class.\r\n\r\nStructures allow you to define and modify arbitrary shape of data to be extracted by\r\nLLM. Classes may not be the best fit for this purpose, as declaring or changing them\r\nduring execution is not possible.\r\n\r\nWith structures, you can define custom data shapes dynamically, for example based\r\non the user input or context of the processing, to specify the information you need\r\nLLM to infer from the provided text or chat messages.\r\n\r\nExample below demonstrates how to define a structure and use it as a response model:\r\n\r\n```php\r\n\u003c?php\r\nuse Cognesy\\Instructor\\Extras\\Structure\\Field;\r\nuse Cognesy\\Instructor\\Extras\\Structure\\Structure;\r\n\r\nenum Role : string {\r\n    case Manager = 'manager';\r\n    case Line = 'line';\r\n}\r\n\r\n$structure = Structure::define('person', [\r\n    Field::string('name'),\r\n    Field::int('age'),\r\n    Field::enum('role', Role::class),\r\n]);\r\n\r\n$person = (new Instructor)-\u003erespond(\r\n    messages: 'Jason is 25 years old and is a manager.',\r\n    responseModel: $structure,\r\n);\r\n\r\n// you can access structure data via field API...\r\nassert($person-\u003efield('name') === 'Jason');\r\n// ...or as structure object properties\r\nassert($person-\u003eage === 25);\r\n?\u003e\r\n```\r\n\r\nFor more information see [Structures](docs/structures.md) section.\r\n\r\n\r\n\r\n\r\n## Changing LLM model and options\r\n\r\nYou can specify model and other options that will be passed to OpenAI / LLM endpoint.\r\n\r\n```php\r\nuse Cognesy\\Instructor\\Features\\LLM\\Data\\LLMConfig;\r\nuse Cognesy\\Instructor\\Features\\LLM\\Drivers\\OpenAIDriver;\r\nuse Cognesy\\Instructor\\Instructor;\r\n\r\n// OpenAI auth params\r\n$yourApiKey = Env::get('OPENAI_API_KEY'); // use your own API key\r\n\r\n// Create instance of OpenAI driver initialized with custom parameters\r\n$driver = new OpenAIDriver(new LLMConfig(\r\n    apiUrl: 'https://api.openai.com/v1', // you can change base URI\r\n    apiKey: $yourApiKey,\r\n    endpoint: '/chat/completions',\r\n    metadata: ['organization' =\u003e ''],\r\n    model: 'gpt-4o-mini',\r\n    maxTokens: 128,\r\n));\r\n\r\n/// Get Instructor with the default client component overridden with your own\r\n$instructor = (new Instructor)-\u003ewithDriver($driver);\r\n\r\n$user = $instructor-\u003erespond(\r\n    messages: \"Jason (@jxnlco) is 25 years old and is the admin of this project. He likes playing football and reading books.\",\r\n    responseModel: User::class,\r\n    model: 'gpt-3.5-turbo',\r\n    options: ['stream' =\u003e true ]\r\n);\r\n```\r\n\r\n\r\n\r\n### Support for language models and API providers\r\n\r\nInstructor offers out of the box support for following API providers:\r\n\r\n- Anthropic\r\n- Azure OpenAI\r\n- Cohere\r\n- Fireworks AI\r\n- Groq\r\n- Mistral\r\n- Ollama (on localhost)\r\n- OpenAI\r\n- OpenRouter\r\n- Together AI\r\n\r\nFor usage examples, check Hub section or `examples` directory in the code repository.\r\n\r\n\r\n\r\n\r\n## Using DocBlocks as Additional Instructions for LLM\r\n\r\nYou can use PHP DocBlocks (/** */) to provide additional instructions for LLM at class or field level, for example to clarify what you expect or how LLM should process your data.\r\n\r\nInstructor extracts PHP DocBlocks comments from class and property defined and includes them in specification of response model sent to LLM.\r\n\r\nUsing PHP DocBlocks instructions is not required, but sometimes you may want to clarify your intentions to improve LLM's inference results.\r\n\r\n```php\r\n/**\r\n * Represents a skill of a person and context in which it was mentioned. \r\n */\r\nclass Skill {\r\n    public string $name;\r\n    /** @var SkillType $type type of the skill, derived from the description and context */\r\n    public SkillType $type;\r\n    /** Directly quoted, full sentence mentioning person's skill */\r\n    public string $context;\r\n}\r\n```\r\n\r\n\r\n\r\n\r\n## Customizing Validation\r\n\r\n### ValidationMixin\r\n\r\nYou can use ValidationMixin trait to add ability of easy, custom data object validation.\r\n\r\n```php\r\nuse Cognesy\\Instructor\\Features\\Validation\\Traits\\ValidationMixin;\r\n\r\nclass User {\r\n    use ValidationMixin;\r\n\r\n    public int $age;\r\n    public int $name;\r\n\r\n    public function validate() : array {\r\n        if ($this-\u003eage \u003c 18) {\r\n            return [\"User has to be adult to sign the contract.\"];\r\n        }\r\n        return [];\r\n    }\r\n}\r\n```\r\n\r\n### Validation Callback\r\n\r\nInstructor uses Symfony validation component to validate extracted data. You can use #[Assert/Callback] annotation to build fully customized validation logic.\r\n\r\n```php\r\nuse Cognesy\\Instructor\\Instructor;\r\nuse Symfony\\Component\\Validator\\Constraints as Assert;\r\nuse Symfony\\Component\\Validator\\Context\\ExecutionContextInterface;\r\n\r\nclass UserDetails\r\n{\r\n    public string $name;\r\n    public int $age;\r\n    \r\n    #[Assert\\Callback]\r\n    public function validateName(ExecutionContextInterface $context, mixed $payload) {\r\n        if ($this-\u003ename !== strtoupper($this-\u003ename)) {\r\n            $context-\u003ebuildViolation(\"Name must be in uppercase.\")\r\n                -\u003eatPath('name')\r\n                -\u003esetInvalidValue($this-\u003ename)\r\n                -\u003eaddViolation();\r\n        }\r\n    }\r\n}\r\n\r\n$user = (new Instructor)-\u003erespond(\r\n    messages: [['role' =\u003e 'user', 'content' =\u003e 'jason is 25 years old']],\r\n    responseModel: UserDetails::class,\r\n    maxRetries: 2\r\n);\r\n\r\nassert($user-\u003ename === \"JASON\");\r\n```\r\n\r\nSee [Symfony docs](https://symfony.com/doc/current/reference/constraints/Callback.html) for more details on how to use Callback constraint.\r\n\r\n\r\n\r\n\r\n\r\n\r\n## Internals\r\n\r\n### Lifecycle\r\n\r\nAs Instructor for PHP processes your request, it goes through several stages:\r\n\r\n 1. Initialize and self-configure (with possible overrides defined by developer).\r\n 2. Analyze classes and properties of the response data model specified by developer.\r\n 3. Encode data model into a schema that can be provided to LLM.\r\n 4. Execute request to LLM using specified messages (content) and response model metadata.\r\n 5. Receive a response from LLM or multiple partial responses (if streaming enabled).\r\n 6. Deserialize response received from LLM into originally requested classes and their properties.\r\n 7. In case response contained incomplete or corrupted data - if errors are encountered, create feedback message for LLM and requests regeneration of the response.\r\n 8. Execute validations defined by developer for the data model - if any of them fail, create feedback message for LLM and requests regeneration of the response.\r\n 9. Repeat the steps 4-8, unless specified limit of retries has been reached or response passes validation\r\n\r\n\r\n### Receiving notification on internal events\r\n\r\nInstructor allows you to receive detailed information at every stage of request and response processing via events.\r\n\r\n * `(new Instructor)-\u003eonEvent(string $class, callable $callback)` method - receive callback when specified type of event is dispatched\r\n * `(new Instructor)-\u003ewiretap(callable $callback)` method - receive any event dispatched by Instructor, may be useful for debugging or performance analysis\r\n\r\nReceiving events can help you to monitor the execution process and makes it easier for a developer to understand and resolve any processing issues.\r\n\r\n```php\r\n$instructor = (new Instructor)\r\n    // see requests to LLM\r\n    -\u003eonEvent(RequestSentToLLM::class, fn($e) =\u003e dump($e))\r\n    // see responses from LLM\r\n    -\u003eonEvent(ResponseReceivedFromLLM::class, fn($event) =\u003e dump($event))\r\n    // see all events in console-friendly format\r\n    -\u003ewiretap(fn($event) =\u003e dump($event-\u003etoConsole()));\r\n\r\n$instructor-\u003erespond(\r\n    messages: \"What is the population of Paris?\",\r\n    responseModel: Scalar::integer(),\r\n);\r\n// check your console for the details on the Instructor execution\r\n```\r\n\r\n### Response Models\r\n\r\nInstructor is able to process several types of input provided as response model, giving you more flexibility on how you interact with the library.\r\n\r\nThe signature of `respond()` method of Instructor states the `responseModel` can be either string, object or array.\r\n\r\n#### Handling string $responseModel value\r\n\r\nIf `string` value is provided, it is used as a name of the class of the response model.\r\n\r\nInstructor checks if the class exists and analyzes the class \u0026 properties type information \u0026 doc comments to generate a schema needed to specify LLM response constraints.\r\n\r\nThe best way to provide the name of the response model class is to use `NameOfTheClass::class` instead of string, making it possible for IDE to execute type checks, handle refactorings, etc.\r\n\r\n\r\n#### Handling object $responseModel value\r\n\r\nIf `object` value is provided, it is considered an instance of the response model. Instructor checks the class of the instance, then analyzes it and its property type data to specify LLM response constraints.\r\n\r\n\r\n#### Handling array $responseModel value\r\n\r\nIf `array` value is provided, it is considered a raw JSON Schema, therefore allowing Instructor to use it directly in LLM requests (after wrapping in appropriate context - e.g. function call).\r\n\r\nInstructor requires information on the class of each nested object in your JSON Schema, so it can correctly deserialize the data into appropriate type.\r\n\r\nThis information is available to Instructor when you are passing $responseModel as a class name or an instance, but it is missing from raw JSON Schema.\r\n\r\nCurrent design uses JSON Schema `$comment` field on property to overcome this. Instructor expects developer to use `$comment` field to provide fully qualified name of the target class to be used to deserialize property data of object or enum type.\r\n\r\n\r\n### Response model contracts\r\n\r\nInstructor allows you to customize processing of $responseModel value also by looking at the interfaces the class or instance implements:\r\n\r\n - `CanProvideJsonSchema` - implement to be able to provide JSON Schema or the response model, overriding the default approach of Instructor, which is analyzing $responseModel value class information,\r\n - `CanDeserializeSelf` - implement to customize the way the response from LLM is deserialized from JSON into PHP object, \r\n - `CanValidateSelf` - implement to customize the way the deserialized object is validated,\r\n - `CanTransformSelf` - implement to transform the validated object into target value received by the caller (e.g. unwrap simple type from a class to a scalar value).\r\n\r\n\r\n\r\n\r\n\r\n\r\n## Additional Notes\r\n\r\nPHP ecosystem does not (yet) have a strong equivalent of [Pydantic](https://pydantic.dev/), which is at the core of Instructor for Python.\r\n\r\nTo provide an essential functionality we needed here Instructor for PHP leverages:\r\n- base capabilities of [PHP type system](https://www.php.net/manual/en/language.types.type-system.php),\r\n- [PHP reflection](https://www.php.net/manual/en/book.reflection.php),\r\n- [PHP DocBlock](https://docs.phpdoc.org/2.9/references/phpdoc/index.html) type hinting conventions,\r\n- [Symfony](https://symfony.com/doc/current/index.html) serialization and validation capabilities\r\n\r\n\r\n\r\n## Dependencies\r\n\r\nInstructor for PHP is compatible with PHP 8.2 or later and, due to minimal dependencies, should work with any framework of your choice.\r\n\r\n - [Guzzle](https://docs.guzzlephp.org/)\r\n - [Symfony components](https://symfony.com/)\r\n   * symfony/property-access\r\n   * symfony/property-info\r\n   * symfony/serializer\r\n   * symfony/type-info\r\n   * symfony/validator\r\n - adbario/php-dot-notation\r\n - phpdocumentor/reflection-docblock\r\n - phpstan/phpdoc-parser\r\n - vlucas/phpdotenv\r\n\r\nAdditional dependencies are required for some extras:\r\n - spatie/array-to-xml\r\n - gioni06/gpt3-tokenizer\r\n\r\n## TODOs\r\n\r\n - [ ] Async support\r\n - [ ] Documentation\r\n\r\n\r\n## Contributing\r\n\r\nIf you want to help, check out some of the issues. All contributions are welcome - code improvements, documentation, bug reports, blog posts / articles, or new cookbooks and application examples.\r\n\r\n\r\n## License\r\n\r\nThis project is licensed under the terms of the MIT License.\r\n\r\n\r\n## Support\r\n\r\nIf you have any questions or need help, please reach out to me on [Twitter](https://twitter.com/ddebowczyk) or [GitHub](https://github.com/cognesy/instructor-php/issues).\r\n\r\n\r\n\r\n## Contributors\r\n\r\n\u003c!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --\u003e\r\n\u003c!-- prettier-ignore-start --\u003e\r\n\u003c!-- markdownlint-disable --\u003e\r\n\r\n\u003c!-- markdownlint-restore --\u003e\r\n\u003c!-- prettier-ignore-end --\u003e\r\n\r\n\u003c!-- ALL-CONTRIBUTORS-LIST:END --\u003e\r\n\r\n\u003ca href=\"https://github.com/cognesy/instructor-php/graphs/contributors\"\u003e\r\n  \u003cimg alt=\"Contributors\" src=\"https://contrib.rocks/image?repo=cognesy/instructor-php\" /\u003e\r\n\u003c/a\u003e\r\n\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcognesy%2Finstructor-php","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcognesy%2Finstructor-php","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcognesy%2Finstructor-php/lists"}