Packages
req_llm
1.26.0
1.28.0
1.27.0
1.26.0
1.25.0
1.24.0
1.23.0
1.22.0
1.21.1
1.21.0
1.20.0
1.19.0
1.18.0
1.17.1
1.17.0
1.16.0
1.15.0
1.14.0
1.13.0
1.12.0
1.11.0
1.10.0
1.9.0
1.8.0
1.7.1
1.7.0
1.6.0
1.5.1
1.5.0
1.4.1
1.4.0
1.3.0
1.2.0
1.1.0
1.0.0
1.0.0-rc.8
1.0.0-rc.7
1.0.0-rc.6
1.0.0-rc.5
1.0.0-rc.4
1.0.0-rc.3
1.0.0-rc.2
1.0.0-rc.1
Composable Elixir library for LLM interactions built on Req & Finch
Current section
Files
Jump to
Current section
Files
guides/getting-started.livemd
# ReqLLM • Getting Started
## Section
[](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2Fagentjido%2Freq_llm%2Fblob%2Fmain%2Fguides%2Fgetting-started.livemd)
```elixir
Mix.install([
{:req_llm, github: "agentjido/req_llm"},
{:kino, "~> 0.14.2"}
])
```
## Setup
### API Key Configuration
Enter your API keys for the providers you want to use. At least one provider key is required.
**Tip:** You can also add keys to Livebook secrets as `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, or `GROQ_API_KEY` to avoid re-entering them each time. Livebook exposes these secrets to the notebook with an `LB_` prefix.
```elixir
openai_key_input = Kino.Input.password("OpenAI API Key")
anthropic_key_input = Kino.Input.password("Anthropic API Key (optional)")
groq_key_input = Kino.Input.password("Groq API Key (optional)")
Kino.Layout.grid([openai_key_input, anthropic_key_input, groq_key_input], columns: 1)
```
```elixir
available_models = [
{"openai:gpt-5", "OpenAI GPT-5"},
{"openai:gpt-5-mini", "OpenAI GPT-5 Mini"},
{"anthropic:claude-sonnet-4-6", "Anthropic Claude Sonnet 4.6"},
{"anthropic:claude-haiku-4-5", "Anthropic Claude Haiku 4.5"},
{"groq:openai/gpt-oss-120b", "Groq GPT-OSS 120B"},
{"groq:openai/gpt-oss-20b", "Groq GPT-OSS 20B"}
]
model_input = Kino.Input.select("Select Model", available_models)
```
```elixir
provider_configurations = [
{"openai", :openai, "OPENAI_API_KEY", openai_key_input},
{"anthropic", :anthropic, "ANTHROPIC_API_KEY", anthropic_key_input},
{"groq", :groq, "GROQ_API_KEY", groq_key_input}
]
key_statuses =
Map.new(provider_configurations, fn {provider_name, provider, env_var, input} ->
livebook_key = System.get_env("LB_" <> env_var)
key_status =
cond do
is_binary(livebook_key) and livebook_key != "" ->
ReqLLM.put_key(ReqLLM.Keys.config_key(provider), livebook_key)
{:ok, :livebook}
true ->
case ReqLLM.Keys.get(provider) do
{:ok, _key, source} ->
{:ok, source}
{:error, _reason} ->
input_key = Kino.Input.read(input)
if is_binary(input_key) and input_key != "" do
ReqLLM.put_key(ReqLLM.Keys.config_key(provider), input_key)
{:ok, :manual}
else
:missing
end
end
end
{provider_name, key_status}
end)
selected_model = Kino.Input.read(model_input)
provider = selected_model |> String.split(":") |> List.first()
key_status = Map.get(key_statuses, provider, :missing)
has_key? = match?({:ok, _source}, key_status)
key_source =
case key_status do
{:ok, :livebook} -> "Livebook secret"
{:ok, :system} -> "environment variable"
{:ok, :application} -> "application configuration"
{:ok, :manual} -> "manual input"
_ -> "unknown"
end
if has_key? do
Kino.Markdown.new("""
✅ **Configuration Complete**
Provider: `#{provider}`
Model: `#{selected_model}`
Key source: #{key_source}
""")
else
Kino.Markdown.new("""
⚠️ **Missing API Key**
Please enter the API key for **#{provider}** above, or add it to Livebook secrets as `#{String.upcase(provider)}_API_KEY`.
""")
end
```
## 1) Basic Text Generation
Simple, synchronous text generation. The model returns complete text after processing.
```elixir
prompt_input = Kino.Input.textarea("Enter your prompt", default: "Explain functional programming in one sentence.")
```
```elixir
prompt = Kino.Input.read(prompt_input)
text = ReqLLM.generate_text!(selected_model, prompt)
Kino.Markdown.new("""
**Response:**
#{text}
""")
```
## 2) Streaming Text
Tokens arrive in real-time as the model generates them. Perfect for chat interfaces.
```elixir
stream_prompt_input = Kino.Input.textarea("Enter your streaming prompt", default: "Write a 3-4 sentence upbeat intro to Elixir.")
```
```elixir
stream_prompt = Kino.Input.read(stream_prompt_input)
{:ok, response} = ReqLLM.stream_text(selected_model, stream_prompt)
response.stream
|> Stream.filter(&(&1.type == :content))
|> Enum.each(fn chunk ->
IO.write(chunk.text)
end)
:ok
```
## 3) Structured Object Generation
Generate validated, type-safe data structures from prompts using schemas.
```elixir
object_prompt_input = Kino.Input.textarea("Enter prompt for object generation", default: "Create a profile for a software engineer named Alice who is 32 years old")
```
```elixir
object_prompt = Kino.Input.read(object_prompt_input)
schema = [
name: [type: :string, required: true, doc: "Person's name"],
age: [type: :pos_integer, required: true, doc: "Person's age"],
occupation: [type: :string, doc: "Person's occupation"],
location: [type: :string, doc: "Person's location"]
]
{:ok, response} = ReqLLM.generate_object(selected_model, object_prompt, schema)
object = ReqLLM.Response.object(response)
json_output = Jason.encode!(object, pretty: true)
Kino.Markdown.new("""
**Generated Object:**
`#{json_output}`
**Fields:**
- **Name:** #{object["name"]}
- **Age:** #{object["age"]}
- **Occupation:** #{object["occupation"] || "Not specified"}
- **Location:** #{object["location"] || "Not specified"}
""")
```
## 4) Function Calling / Tools
Enable models to call functions and use tools to perform actions.
```elixir
tools_prompt_input = Kino.Input.textarea("Enter prompt for tool calling", default: "What's the weather in Paris in Celsius? What time is it?")
```
```elixir
tools_prompt = Kino.Input.read(tools_prompt_input)
weather_tool = ReqLLM.tool(
name: "get_weather",
description: "Get the current weather for a location",
parameter_schema: [
location: [type: :string, required: true, doc: "City name or location"],
unit: [type: :string, default: "fahrenheit", doc: "Temperature unit (celsius or fahrenheit)"]
],
callback: fn args ->
location = args["location"] || args[:location]
unit = args["unit"] || args[:unit] || "fahrenheit"
temp = if unit == "celsius", do: "22°C", else: "72°F"
{:ok, "The weather in #{location} is #{temp}, sunny with clear skies."}
end
)
time_tool = ReqLLM.tool(
name: "get_time",
description: "Get the current time",
parameter_schema: [],
callback: fn _args ->
time = DateTime.utc_now() |> DateTime.to_string()
{:ok, "The current UTC time is #{time}"}
end
)
tools = [weather_tool, time_tool]
{:ok, response} = ReqLLM.generate_text(selected_model, tools_prompt, tools: tools)
tool_calls = ReqLLM.Response.tool_calls(response)
output = if tool_calls != [] do
results = Enum.map(tool_calls, fn tool_call ->
%{name: name, arguments: arguments} = ReqLLM.ToolCall.to_map(tool_call)
{:ok, result} = ReqLLM.ToolCall.execute(tool_call, tools)
"**#{name}** (#{inspect(arguments)})\n→ #{result}"
end)
Enum.join(results, "\n\n")
else
ReqLLM.Response.text(response)
end
Kino.Markdown.new("""
**Tool Calls:**
#{output}
""")
```
## 5) Embeddings
Convert text into vector representations for similarity search and semantic analysis.
```elixir
embedding_text_input = Kino.Input.textarea("Enter text to embed", default: "Elixir is a dynamic, functional programming language")
```
```elixir
embedding_text = Kino.Input.read(embedding_text_input)
embedding_model = "openai:text-embedding-3-small"
{:ok, embedding} = ReqLLM.Embedding.embed(embedding_model, embedding_text)
dimensions = length(embedding)
preview = Enum.take(embedding, 8)
Kino.Markdown.new("""
**Embedding Generated:**
- **Dimensions:** #{dimensions}
- **First 8 values:** `#{inspect(preview)}`
- **Total values:** #{dimensions} floating point numbers
Embeddings can be used for:
- Semantic similarity comparisons
- Document search and retrieval
- Clustering related content
- Recommendation systems
""")
```
<!-- livebook:{"offset":7816,"stamp":{"token":"XCP.n0_OLGGywE7KdRjliywrGs2LXv9SgvznJ0R11yS0hbsxD7Z28CJfO1uYSDNHu5GL2GCJi090J5pdc_WhRNQ_2yqP9uOe8irO7NwxTZpWYOWvNgJb1Za8Z0CM8HYUSEHM-PbzJJqK2tu0uh4EomFX","version":2}} -->