Structured prompting for LLMs. InstructorLite is a fork, spiritual successor and almost an entire rewrite of instructor_ex library.
InstructorLite provides basic building blocks to embed LLMs into your application. It uses Ecto schemas to make sure LLM output has a predictable shape and can play nicely with deterministic application logic. For an example of what can be built with InstructorLite, check out Handwave
InstructorLite is designed to be:
InstructorLite is tested to be compatible with the following providers: OpenAI, Anthropic, Gemini and any Chat Completions-compatible APIs, such as Grok.
InstructorLite can be boiled down to these features:
Any of the features above can be used independently.
Define an instruction, which is a normal Ecto schema with an extra use Instructor.Instruction call.
defmodule UserInfo do
use Ecto.Schema
use InstructorLite.Instruction
@primary_key false
embedded_schema do
field(:name, :string)
field(:age, :integer)
end
end
Now let's use InstructorLite.instruct/2 to fill the schema from unstructured text:
iex> InstructorLite.instruct(%{
input: [
%{role: "user", content: "John Doe is forty-two years old"}
]
},
response_model: UserInfo,
adapter_context: [api_key: Application.fetch_env!(:instructor_lite, :openai_key)]
)
{:ok, %UserInfo{name: "John Doe", age: 42}}
iex> InstructorLite.instruct(%{
messages: [
%{role: "user", content: "John Doe is forty-two years old"}
]
},
response_model: UserInfo,
adapter: InstructorLite.Adapters.Anthropic,
adapter_context: [api_key: Application.fetch_env!(:instructor_lite, :anthropic_key)]
)
{:ok, %UserInfo{name: "John Doe", age: 42}}
iex> InstructorLite.instruct(%{
prompt: "John Doe is forty-two years old"
},
response_model: UserInfo,
adapter: InstructorLite.Adapters.Llamacpp,
adapter_context: [url: Application.fetch_env!(:instructor_lite, :llamacpp_url)]
)
{:ok, %UserInfo{name: "John Doe", age: 42}}
iex> InstructorLite.instruct(%{
contents: [
%{
role: "user",
parts: [%{text: "John Doe is forty-two years old"}]
}
]
},
response_model: UserInfo,
json_schema: %{
type: "object",
required: [:age, :name],
properties: %{name: %{type: "string"}, age: %{type: "integer"}},
},
adapter: InstructorLite.Adapters.Gemini,
adapter_context: [
api_key: Application.fetch_env!(:instructor_lite, :gemini_key)
]
)
{:ok, %UserInfo{name: "John Doe", age: 42}}
Grok API is compatible with OpenAI Chat Completions endpoint, so we can use
the ChatCompletionsCompatible adapter with Grok's url and model_name
iex> InstructorLite.instruct(%{
model: "grok-3-latest",
messages: [
%{role: "user", content: "John Doe is forty-two years old"}
]
},
response_model: UserInfo,
adapter: InstructorLite.Adapters.ChatCompletionsCompatible,
adapter_context: [
url: "https://api.x.ai/v1/chat/completions",
api_key: Application.fetch_env!(:instructor_lite, :grok_key)
]
)
{:ok, %UserInfo{name: "John Doe", age: 42}}
InstructorLite does not access the application environment for configuration options like adapter or API key. Instead, they're passed as options when needed. Note that different adapters may require different options, so make sure to check their documentation.
InstructorLite is hand-written by a human and all external contributions are vetted by a human. And said human is committed to keep it this way for foreseeable future. This comes with both advantages and drawbacks. The library may be prone to silly human errors and poor judgement, but at the same time it is likely won't explode in complexity overnight or undergo a full rewrite every couple of months. Tune your expectations accordingly!
InstructorLite very explicitly doesn't pursue the following goals:
In your mix.exs, add :instructor_lite to your list of dependencies:
def deps do
[
{:instructor_lite, "~> 1.3.0"}
]
end
Optionally, include Req HTTP client (used by default) and Jason (for Elixir older than 1.18):
def deps do
[
{:req, "~> 0.5 or ~> 1.0"},
{:jason, "~> 1.4"}
]
end
Elixir
100.0%
Structured prompting for LLMs. InstructorLite is a fork, spiritual successor and almost an entire rewrite of instructor_ex library.
InstructorLite provides basic building blocks to embed LLMs into your application. It uses Ecto schemas to make sure LLM output has a predictable shape and can play nicely with deterministic application logic. For an example of what can be built with InstructorLite, check out Handwave
InstructorLite is designed to be:
InstructorLite is tested to be compatible with the following providers: OpenAI, Anthropic, Gemini and any Chat Completions-compatible APIs, such as Grok.
InstructorLite can be boiled down to these features:
Any of the features above can be used independently.
Define an instruction, which is a normal Ecto schema with an extra use Instructor.Instruction call.
defmodule UserInfo do
use Ecto.Schema
use InstructorLite.Instruction
@primary_key false
embedded_schema do
field(:name, :string)
field(:age, :integer)
end
end
Now let's use InstructorLite.instruct/2 to fill the schema from unstructured text:
iex> InstructorLite.instruct(%{
input: [
%{role: "user", content: "John Doe is forty-two years old"}
]
},
response_model: UserInfo,
adapter_context: [api_key: Application.fetch_env!(:instructor_lite, :openai_key)]
)
{:ok, %UserInfo{name: "John Doe", age: 42}}
iex> InstructorLite.instruct(%{
messages: [
%{role: "user", content: "John Doe is forty-two years old"}
]
},
response_model: UserInfo,
adapter: InstructorLite.Adapters.Anthropic,
adapter_context: [api_key: Application.fetch_env!(:instructor_lite, :anthropic_key)]
)
{:ok, %UserInfo{name: "John Doe", age: 42}}
iex> InstructorLite.instruct(%{
prompt: "John Doe is forty-two years old"
},
response_model: UserInfo,
adapter: InstructorLite.Adapters.Llamacpp,
adapter_context: [url: Application.fetch_env!(:instructor_lite, :llamacpp_url)]
)
{:ok, %UserInfo{name: "John Doe", age: 42}}
iex> InstructorLite.instruct(%{
contents: [
%{
role: "user",
parts: [%{text: "John Doe is forty-two years old"}]
}
]
},
response_model: UserInfo,
json_schema: %{
type: "object",
required: [:age, :name],
properties: %{name: %{type: "string"}, age: %{type: "integer"}},
},
adapter: InstructorLite.Adapters.Gemini,
adapter_context: [
api_key: Application.fetch_env!(:instructor_lite, :gemini_key)
]
)
{:ok, %UserInfo{name: "John Doe", age: 42}}
Grok API is compatible with OpenAI Chat Completions endpoint, so we can use
the ChatCompletionsCompatible adapter with Grok's url and model_name
iex> InstructorLite.instruct(%{
model: "grok-3-latest",
messages: [
%{role: "user", content: "John Doe is forty-two years old"}
]
},
response_model: UserInfo,
adapter: InstructorLite.Adapters.ChatCompletionsCompatible,
adapter_context: [
url: "https://api.x.ai/v1/chat/completions",
api_key: Application.fetch_env!(:instructor_lite, :grok_key)
]
)
{:ok, %UserInfo{name: "John Doe", age: 42}}
InstructorLite does not access the application environment for configuration options like adapter or API key. Instead, they're passed as options when needed. Note that different adapters may require different options, so make sure to check their documentation.
InstructorLite is hand-written by a human and all external contributions are vetted by a human. And said human is committed to keep it this way for foreseeable future. This comes with both advantages and drawbacks. The library may be prone to silly human errors and poor judgement, but at the same time it is likely won't explode in complexity overnight or undergo a full rewrite every couple of months. Tune your expectations accordingly!
InstructorLite very explicitly doesn't pursue the following goals:
In your mix.exs, add :instructor_lite to your list of dependencies:
def deps do
[
{:instructor_lite, "~> 1.3.0"}
]
end
Optionally, include Req HTTP client (used by default) and Jason (for Elixir older than 1.18):
def deps do
[
{:req, "~> 0.5 or ~> 1.0"},
{:jason, "~> 1.4"}
]
end
Elixir
100.0%