PHPackages                             adrienbrault/instructrice - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. adrienbrault/instructrice

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

adrienbrault/instructrice
=========================

Typed LLM Outputs in PHP

309134[2 issues](https://github.com/adrienbrault/instructrice/issues)[2 PRs](https://github.com/adrienbrault/instructrice/pulls)PHP

Since Apr 23Pushed 1y ago1 watchersCompare

[ Source](https://github.com/adrienbrault/instructrice)[ Packagist](https://packagist.org/packages/adrienbrault/instructrice)[ RSS](/packages/adrienbrault-instructrice/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependenciesVersions (5)Used By (0)

👩‍🏫 adrienbrault/instructrice
=============================

[](#‍-adrienbraultinstructrice)

[![GitHub Actions](https://github.com/adrienbrault/instructrice/workflows/Tests/badge.svg)](https://github.com/adrienbrault/instructrice/actions?query=workflow%3A%22Tests%22+branch%3Amain)[![Packagist](https://camo.githubusercontent.com/2932f001000a3f340919e0512405a7a32a0ba4512186e4e5abc9f95e8dfb3b74/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f61647269656e627261756c742f696e737472756374726963652e737667)](https://packagist.org/packages/adrienbrault/instructrice)[![License](https://camo.githubusercontent.com/556626604fd292c1219bf4e04e783764f8e48bd4b8813702695ec5e3acaa8991/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f6f70656e61692d7068702f636c69656e74)](./LICENSE)

Typing LLM completions
----------------------

[](#typing-llm-completions)

Best in class LLMs are able to output JSON following a schema you provide, usually JSON-Schema. This significantly expands the ways you can leverage LLMs in your application!

Think of the input as:

- A context, anything that is or can be converted to text, like emails/pdfs/html/xlsx
- A schema, "Here is the form you need to fill to complete your task"
- An optional prompt, giving a specific task, rules, etc

And the output/outcome is whichever structure best matches your use case and domain.

The [python instructor cookbook](https://python.useinstructor.com/examples/) has interesting examples.

Introduction
------------

[](#introduction)

Instructrice is a PHP library that simplifies working with structured output from LLMs in a type-safe manner.

Features:

- Flexible schema options:
    - Classes using [api-platform/json-schema](https://github.com/api-platform/json-schema)
    - Dynamically generated types [PSL](https://github.com/azjezz/psl)\\[Type](https://github.com/azjezz/psl/blob/next/src/Psl/Type/README.md)
    - Or a JSON-Schema array generated by a third party library, or in plain PHP
- [symfony/serializer](https://symfony.com/doc/current/components/serializer.html) integration to deserialize LLMs outputs
- Streaming first:
    - As a developer you can be more productive with faster feedback loops than waiting for outputs to complete. This also makes slower local models more usable.
    - You can provide a much better and snappier UX to your users.
    - The headaches of parsing incomplete JSON are handled for you.
- [A set of pre-configured LLMs](#supported-providers) with the best available settings. Set your API keys and switch between different providers and models without having to think about the model name, json mode, function calling, etc.

A [Symfony Bundle](https://github.com/adrienbrault/instructrice-bundle) is also available.

Installation and Usage
----------------------

[](#installation-and-usage)

```
$ composer require adrienbrault/instructrice:@dev
```

```
use AdrienBrault\Instructrice\InstructriceFactory;
use AdrienBrault\Instructrice\LLM\Provider\Ollama;
use AdrienBrault\Instructrice\LLM\Provider\OpenAi;
use AdrienBrault\Instructrice\LLM\Provider\Anthropic;

$instructrice = InstructriceFactory::create(
    defaultLlm: Ollama::HERMES2THETA_LLAMA3_8B,
    apiKeys: [ // Unless you inject keys here, api keys will be fetched from environment variables
        OpenAi::class => $openAiApiKey,
        Anthropic::class => $anthropicApiKey,
    ],
);
```

### List of object

[](#list-of-object)

```
use AdrienBrault\Instructrice\Attribute\Prompt;

class Character
{
    // The prompt annotation lets you add instructions specific to a property
    #[Prompt('Just the first name.')]
    public string $name;
    public ?string $rank = null;
}

$characters = $instructrice->getList(
    Character::class,
    'Colonel Jack O\'Neil walks into a bar and meets Major Samanta Carter. They call Teal\'c to join them.',
);

/*
dump($characters);
array:3 [
  0 => Character^ {
    +name: "Jack"
    +rank: "Colonel"
  }
  1 => Character^ {
    +name: "Samanta"
    +rank: "Major"
  }
  2 => Character^ {
    +name: "Teal'c"
    +rank: null
  }
]
*/
```

### Object

[](#object)

```
$character = $instructrice->get(
    type: Character::class,
    context: 'Colonel Jack O\'Neil.',
);

/*
dump($character);
Character^ {
  +name: "Jack"
  +rank: "Colonel"
}
*/
```

### Dynamic Schema

[](#dynamic-schema)

```
$label = $instructrice->get(
    type: [
        'type' => 'string',
        'enum' => ['positive', 'neutral', 'negative'],
    ],
    context: 'Amazing great cool nice',
    prompt: 'Sentiment analysis',
);

/*
dump($label);
"positive"
*/
```

You can also use third party json schema libraries like [goldspecdigital/oooas](https://github.com/goldspecdigital/oooas) to generate the schema:

- [examples/oooas.php](examples/oooas.php)

    CleanShot.2024-04-18.at.14.11.39.mp4    Supported providers
-------------------

[](#supported-providers)

ProviderEnvironment VariablesEnumAPI Key Creation URL[Ollama](https://ollama.com)`OLLAMA_HOST`[Ollama](src/LLM/Provider/Ollama.php)[OpenAI](https://openai.com/pricing)`OPENAI_API_KEY`[OpenAi](src/LLM/Provider/OpenAi.php)[API Key Management](https://platform.openai.com/api-keys)[Anthropic](https://www.anthropic.com/api)`ANTHROPIC_API_KEY`[Anthropic](src/LLM/Provider/Anthropic.php)[API Key Management](https://console.anthropic.com/settings/keys)[Mistral](https://mistral.ai/technology/#pricing)`MISTRAL_API_KEY`[Mistral](src/LLM/Provider/Mistral.php)[API Key Management](https://console.mistral.ai/api-keys/)[Fireworks AI](https://fireworks.ai/pricing)`FIREWORKS_API_KEY`[Fireworks](src/LLM/Provider/Fireworks.php)[API Key Management](https://fireworks.ai/api-keys)[Groq](https://wow.groq.com)`GROQ_API_KEY`[Groq](src/LLM/Provider/Groq.php)[API Key Management](https://console.groq.com/keys)[Together AI](https://www.together.ai/pricing)`TOGETHER_API_KEY`[Together](src/LLM/Provider/Together.php)[API Key Management](https://api.together.xyz/settings/api-keys)[Deepinfra](https://deepinfra.com/pricing)`DEEPINFRA_API_KEY`[Deepinfra](src/LLM/Provider/DeepInfra.php)[API Key Management](https://deepinfra.com/dash/api_keys)[Perplexity](https://docs.perplexity.ai/docs/pricing)`PERPLEXITY_API_KEY`[Perplexity](src/LLM/Provider/Perplexity.php)[API Key Management](https://www.perplexity.ai/settings/api)[Anyscale](https://docs.endpoints.anyscale.com/pricing/)`ANYSCALE_API_KEY`[Anyscale](src/LLM/Provider/Anyscale.php)[API Key Management](https://app.endpoints.anyscale.com/credentials)[OctoAI](https://octo.ai/docs/getting-started/pricing-and-billing#text-gen-solution)`OCTOAI_API_KEY`[OctoAI](src/LLM/Provider/OctoAI.php)[API Key Management](https://octoai.cloud/settings)The supported providers are Enums, which you can pass to the `llm` argument of `InstructriceFactory::create`:

```
use AdrienBrault\Instructrice\InstructriceFactory;
use AdrienBrault\Instructrice\LLM\Provider\OpenAi;

$instructrice->get(
    ...,
    llm: OpenAi::GPT_4T, // API Key will be fetched from the OPENAI_API_KEY environment variable
);
```

Supported models
----------------

[](#supported-models)

Strategy📄 Text🧩 JSON🚀 FunctionCommercial usage 💼✅ Yes⚠️ Yes, but❌ Nope### Open Weights

[](#open-weights)

#### Foundation

[](#foundation)

💼ctx[Ollama](https://ollama.com/library)[Mistral](https://docs.mistral.ai/getting-started/models/)[Fireworks](https://fireworks.ai/models)[Groq](https://console.groq.com/docs/models)[Together](https://docs.together.ai/docs/inference-models)[DeepInfra](https://deepinfra.com/models/text-generation)[Perplexity](https://docs.perplexity.ai/docs/model-cards)Anyscale[OctoAI](https://octoai.cloud/text?selectedTags=Chat)[Mistral 7B](https://huggingface.co/mistralai/Mistral-7B-Instruct-v0.2)[✅](https://www.apache.org/licenses/LICENSE-2.0)32k🧩🧩 68/s📄 98/s📄 88/s !ctx=16k!🧩🧩[Mixtral 8x7B](https://huggingface.co/mistralai/Mixtral-8x7B-Instruct-v0.1)[✅](https://www.apache.org/licenses/LICENSE-2.0)32k🧩🧩 44/s🧩 237/s🚀 560/s🚀 99/s📄 119/s !ctx=16k!🧩🧩[Mixtral 8x22B](https://huggingface.co/mistralai/Mixtral-8x22B-Instruct-v0.1)[✅](https://www.apache.org/licenses/LICENSE-2.0)65k🧩🧩 77/s🧩 77/s📄 52/s🧩 40/s📄 62/s !ctx=16k!🧩🧩[Phi-3-Mini-4K](https://huggingface.co/microsoft/Phi-3-mini-4k-instruct)[✅](https://en.wikipedia.org/wiki/MIT_License)4k🧩[Phi-3-Mini-128K](https://huggingface.co/microsoft/Phi-3-mini-128k-instruct)[✅](https://en.wikipedia.org/wiki/MIT_License)128k🧩[Phi-3-Medium-4K](https://huggingface.co/microsoft/Phi-3-medium-4k-instruct)[✅](https://en.wikipedia.org/wiki/MIT_License)4k🧩[Phi-3-Medium-128K](https://huggingface.co/microsoft/Phi-3-medium-128k-instruct)[✅](https://en.wikipedia.org/wiki/MIT_License)128k🧩[Qwen2 0.5B](https://huggingface.co/Qwen/Qwen2-0.5B-Instruct)[✅](https://www.apache.org/licenses/LICENSE-2.0)32k🧩[Qwen2 1.5B](https://huggingface.co/Qwen/Qwen2-1.5B-Instruct)[✅](https://www.apache.org/licenses/LICENSE-2.0)32k🧩[Qwen2 7B](https://huggingface.co/Qwen/Qwen2-7B-Instruct)[✅](https://www.apache.org/licenses/LICENSE-2.0)128k🧩[Llama3 8B](https://huggingface.co/meta-llama/Meta-Llama-3-8B-Instruct)[⚠️](https://github.com/meta-llama/llama3/blob/main/LICENSE)8k📄🧩 280/s🚀 800/s📄 194/s🧩 133/s📄 121/s🧩🧩[Llama3 70B](https://huggingface.co/meta-llama/Meta-Llama-3-70B-Instruct)[⚠️](https://github.com/meta-llama/llama3/blob/main/LICENSE)8k🧩🧩 116/s🚀 270/s📄 105/s🧩 26/s📄 42/s🧩🧩[Llama 3.1 8B](https://huggingface.co/meta-llama/Meta-Llama-3.1-8B-Instruct)\[⚠️\]\[llama31\_license\]128k🧩🧩📄📄🧩📄[Llama 3.1 70B](https://huggingface.co/meta-llama/Meta-Llama-3.1-70B-Instruct)\[⚠️\]\[llama31\_license\]128k🧩🧩📄📄🧩📄[Llama 3.1 405B](https://huggingface.co/meta-llama/Meta-Llama-3.1-405B-Instruct)\[⚠️\]\[llama31\_license\]128k🧩🧩📄📄📄[Gemma 7B](https://huggingface.co/google/gemma-7b-it)⚠️8k🚀 800/s📄 118/s🧩 64/s🧩[DBRX](https://huggingface.co/databricks/dbrx-instruct)[⚠️](https://www.databricks.com/legal/open-model-license)32k🧩 50/s📄 72/s🧩[Qwen2 72B](https://huggingface.co/Qwen/Qwen2-72B-Instruct)[⚠️](https://github.com/QwenLM/Qwen/blob/main/Tongyi%20Qianwen%20LICENSE%20AGREEMENT)128k🧩[Qwen1.5 32B](https://huggingface.co/Qwen/Qwen1.5-32B-Chat)[⚠️](https://github.com/QwenLM/Qwen/blob/main/Tongyi%20Qianwen%20LICENSE%20AGREEMENT)32k📄🧩[Command R](https://huggingface.co/CohereForAI/c4ai-command-r)[❌](https://en.wikipedia.org/wiki/Creative_Commons_NonCommercial_license)128k📄[Command R+](https://huggingface.co/CohereForAI/c4ai-command-r-plus)[❌](https://en.wikipedia.org/wiki/Creative_Commons_NonCommercial_license)128k📄Throughputs from  .

#### Fine Tune

[](#fine-tune)

💼ctxBase[Ollama](https://ollama.com/library)[Fireworks](https://fireworks.ai/models)[Together](https://docs.together.ai/docs/inference-models)[DeepInfra](https://deepinfra.com/models/text-generation)[OctoAI](https://ollama.com/library)[Hermes 2 Pro Mistral 7B](https://huggingface.co/NousResearch/Hermes-2-Pro-Mistral-7B)[✅](https://www.apache.org/licenses/LICENSE-2.0)Mistral 7B🧩🧩🧩[FireFunction V1](https://huggingface.co/fireworks-ai/firefunction-v1)[✅](https://www.apache.org/licenses/LICENSE-2.0)Mixtral 8x7B🚀WizardLM 2 7B[✅](https://www.apache.org/licenses/LICENSE-2.0)Mistral 7B🧩WizardLM 2 8x22B[✅](https://www.apache.org/licenses/LICENSE-2.0)Mixtral 8x7B📄🧩🧩[Capybara 34B](https://huggingface.co/NousResearch/Nous-Capybara-34B)[✅](https://www.apache.org/licenses/LICENSE-2.0)200kYi 34B🧩[Hermes 2 Pro Llama3 8B](https://huggingface.co/NousResearch/Hermes-2-Pro-Llama-3-8B)[⚠️](https://github.com/meta-llama/llama3/blob/main/LICENSE)Llama3 8B📄[Hermes 2 Theta Llama3 8B](https://huggingface.co/NousResearch/Hermes-2-Theta-Llama-3-8B)[⚠️](https://github.com/meta-llama/llama3/blob/main/LICENSE)Llama3 8B📄[Dolphin 2.9](https://huggingface.co/cognitivecomputations/dolphin-2.9-llama3-8b)[⚠️](https://github.com/meta-llama/llama3/blob/main/LICENSE)8kLlama3 8B🧩📄🧩### Proprietary

[](#proprietary)

ProviderModelctxMistralLarge32k✅ 26/sOpenAIGPT-4o128k🚀 83/sOpenAIGPT-4o mini128k🚀 140/sOpenAIGPT-4 Turbo128k🚀 28/sOpenAIGPT-3.5 Turbo16k🚀 72/sAnthropicClaude 3 Haiku200k📄 88/sAnthropicClaude 3 Sonnet200k📄 59/sAnthropicClaude 3 Opus200k📄 26/sGoogleGemini 1.5 Flash1000k🧩 136/sGoogleGemini 1.5 Pro1000k🧩 57/sPerplexitySonar Small Chat16k📄PerplexitySonar Small Online12k📄PerplexitySonar Medium Chat16k📄PerplexitySonar Medium Online12k📄Throughputs from  .

Automate updating these tables by scraping  , along with chatboard arena elo.? Would be a good use case / showcase of this library/cli?

### Custom Models

[](#custom-models)

#### Ollama

[](#ollama)

If you want to use an Ollama model that is not available in the enum, you can use the `Ollama::create` static method:

```
use AdrienBrault\Instructrice\LLM\LLMConfig;
use AdrienBrault\Instructrice\LLM\Cost;
use AdrienBrault\Instructrice\LLM\OpenAiJsonStrategy;
use AdrienBrault\Instructrice\LLM\Provider\Ollama;

$instructrice->get(
    ...,
    llm: Ollama::create(
        'codestral:22b-v0.1-q5_K_M', // check its license first!
        32000,
    ),
);
```

#### OpenAI

[](#openai)

You can also use any OpenAI compatible api by passing an [LLMConfig](src/LLM/LLMConfig.php):

```
use AdrienBrault\Instructrice\LLM\LLMConfig;
use AdrienBrault\Instructrice\LLM\Cost;
use AdrienBrault\Instructrice\LLM\OpenAiJsonStrategy;

$instructrice->get(
    ...,
    llm: new LLMConfig(
        uri: 'https://api.together.xyz/v1/chat/completions',
        model: 'meta-llama/Llama-3-70b-chat-hf',
        contextWindow: 8000,
        label: 'Llama 3 70B',
        provider: 'Together',
        cost: Cost::create(0.9),
        strategy: OpenAiJsonStrategy::JSON,
        headers: [
            'Authorization' => 'Bearer ' . $apiKey,
        ]
    ),
);
```

#### DSN

[](#dsn)

You may configure the LLM using a DSN:

- the scheme is the provider: `openai`, `openai-http`, `anthropic`, `google`
- the password is the api key
- the host, port and path are the api endpoints without the scheme
- the query string:
    - `model` is the model name
    - `context` is the context window
    - `strategy` is the strategy to use:
        - `json` for json mode with the schema in the prompt only
        - `json_with_schema` for json mode with probably the completion perfectly constrained to the schema
        - `tool_any`
        - `tool_auto`
        - `tool_function`

Examples:

```
use AdrienBrault\Instructrice\InstructriceFactory;

$instructrice = InstructriceFactory::create(
    defaultLlm: 'openai://:api_key@api.openai.com/v1/chat/completions?model=gpt-3.5-turbo&strategy=tool_auto&context=16000'
);

$instructrice->get(
    ...,
    llm: 'openai-http://localhost:11434?model=adrienbrault/nous-hermes2theta-llama3-8b&strategy=json&context=8000'
);

$instructrice->get(
    ...,
    llm: 'openai://:api_key@api.fireworks.ai/inference/v1/chat/completions?model=accounts/fireworks/models/llama-v3-70b-instruct&context=8000&strategy=json_with_schema'
);

$instructrice->get(
    ...,
    llm: 'google://:api_key@generativelanguage.googleapis.com/v1beta/models?model=gemini-1.5-flash&context=1000000'
);

$instructrice->get(
    ...,
    llm: 'anthropic://:api_key@api.anthropic.com?model=claude-3-haiku-20240307&context=200000'
);
```

#### LLMInterface

[](#llminterface)

You may also implement [LLMInterface](src/LLM/LLMInterface.php).

Acknowledgements
----------------

[](#acknowledgements)

Obviously inspired by [instructor-php](https://github.com/cognesy/instructor-php/) and [instructor](https://python.useinstructor.com).

> How is it different from instructor php?

Both libraries essentially do the same thing:

- Automatic schema generation from classes
- Multiple LLM/Providers abstraction/support
- Many strategies to extract data: function calling, json mode, etc
- Automatic deserialization/hydration
- Maybe validation/retries later for this lib.

However, instructice differs with:

- Streaming first.
- Preconfigured provider+llms, to not have to worry about:
    - Json mode, function calling, etc
    - The best prompt format to use
    - Your options for local models
    - Whether streaming works. For example, groq can only do streaming without json-mode/function calling.
- PSR-3 logging
- Guzzle+symfony/http-client support
- No messages. You just pass context, prompt.
    - I am hoping that this choice enables cool things later like supporting few-shots examples, evals, etc
- More flexible schema options
- Higher level abstraction. You aren't able to provide a list of messages, while it is possible with `instructor-php`.

Notes/Ideas
-----------

[](#notesideas)

Things to look into:

- [Unstructured](https://unstructured-io.github.io/unstructured/installation/docker.html)
- [Llama Parse](https://github.com/run-llama/llama_parse)
- [EMLs](https://en.wikipedia.org/wiki/Email#Filename_extensions)
- [jina-ai/reader](https://github.com/jina-ai/reader) -&gt; This is awesome, `$client->request('GET', 'https://r.jina.ai/' . $url)`
- [firecrawl](https://www.firecrawl.dev)

[DSPy](https://github.com/stanfordnlp/dspy) is very interesting. There are great ideas to be inspired by.

Ideally this library is good to prototype with, but can support more advanced extraction workflows with few shot examples, some sort of eval system, generating samples/output like DSPy, etc

Would be cool to have a CLI, that accepts a FQCN and a context.

```
instructrice get "App\Entity\Customer" "$(cat some_email_body.md)"

```

Autosave all input/schema/output in sqlite db. Like [llm](https://llm.datasette.io/en/stable/logging.html)? Leverage that to test examples, add few shots, evals?

###  Health Score

23

—

LowBetter than 27% of packages

Maintenance26

Infrequent updates — may be unmaintained

Popularity25

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity24

Early-stage or recently created project

 Bus Factor1

Top contributor holds 99.2% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/014b3653b1f38cb85fa553f23e32a10a9584fee5dc80c7be2d01900202045033?d=identicon)[AdrienBrault](/maintainers/AdrienBrault)

---

Top Contributors

[![adrienbrault](https://avatars.githubusercontent.com/u/611271?v=4)](https://github.com/adrienbrault "adrienbrault (240 commits)")[![mykiwi](https://avatars.githubusercontent.com/u/334432?v=4)](https://github.com/mykiwi "mykiwi (2 commits)")

### Embed Badge

![Health badge](/badges/adrienbrault-instructrice/health.svg)

```
[![Health](https://phpackages.com/badges/adrienbrault-instructrice/health.svg)](https://phpackages.com/packages/adrienbrault-instructrice)
```

###  Alternatives

[qinchen/web-utils

A web application common utils

111.4k](/packages/qinchen-web-utils)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
