aaronrussell
Omni - a universal Elixir client for LLM APIs
This thread is the home for updates and discussion across the Omni family of packages. What started as a single library for calling LLM APIs has grown into three packages that cover the full stack of building with LLMs in Elixir:
- omni - Universal Elixir client for LLM APIs. Streaming text generation, tool use, an structured output.
- omni_agent - Stateful LLM agents for Elixir - persistent, branching conversations, tool approval, and multi-session management.
- omni_tools - Ready-to-use tools for Omni-powered agents - filesystem, shell, REPL, web fetch, and web search.
Original post follows.
Hey everyone - I’ve been building with Elixir on and off for over 8 years, but somehow have never posted on the actual Elixir forum. Time to fix that…
Also, I’d love to share with you Omni - a library for working with LLM APIs across multiple providers through a unified interface. Anthropic, OpenAI, Google Gemini, Ollama, OpenRouter, and OpenCode Zen are supported out of the box.
# Resolve model
{:ok, model} = Omni.get_model(:anthropic, "claude-sonnet-4-6")
# Simple text generation
{:ok, response} = Omni.generate_text(model, "Hello!")
# Stream with composable callbacks
{:ok, stream} = Omni.stream_text(model, "Tell me a story")
{:ok, response} =
stream
|> Omni.StreamingResponse.on(:text_delta, &IO.write(&1.delta))
|> Omni.StreamingResponse.complete()
Tool use and structured outputs are supported. Pass tools in the context and Omni handles the execution loop automatically - calling the model, executing tool handlers, feeding results back, and repeating until the model is done. Structured output uses JSON Schema constraints with validation:
# Tool use - Omni manages the tool execution loop
{:ok, response} = Omni.generate_text(
model,
Omni.context(
messages: [Omni.message(role: :user, content: "What's the weather in London?")],
tools: [weather_tool]
)
)
# Structured output
alias Omni.Schema
{:ok, response} = Omni.generate_text(
model,
"Extract the contact details: Reach me at jane@example.com or call 01234 567890",
output: Schema.object(%{
email: Schema.string(description: "Email address"),
phone: Schema.string(description: "Phone number")
}, required: [:email, :phone])
)
Omni also offers a lightweight take on agents. Omni.Agent is a GenServer that manages its own conversation context and tool execution, and communicates with callers via standard process messages. You control behaviour through lifecycle callbacks. It’s a building block, not a framework - what you build on top (planning, memory, multi-agent orchestration) is your concern.
I know req_llm covers similar ground, which - slightly annoyingly - I didn’t realise existed until I was 90% of the way done with Omni
. On the surface they have quite similar APIs, and both use Req, but how they handle implementing providers is a little different. Omni separates providers (the endpoint, configuration and auth) and dialects (wire format translation). The dialect does the heavy lifting, and as most providers share a dialect, adding a new provider is typically a small, mostly-declarative module. Everything is streaming-first - generate_text is built on top of stream_text, so there’s one code path through each dialect.
Anyway, please check it out. Let me know if you have any questions.
Trending in Announcing
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #performance
- #security










First 10 of 24 Posts
egeersoz
How does this compare to (or where does it sit in relation to) Langchain (Elixir)?
aaronrussell
They’re both text generation focused - so fundamentally do the same thing. Just a different style and take.
Langchain has a few things Omni does not: multimodal, RAG text splitting, EEx prompt templates - and probably some other stuff. Omni is light-weight, only has 2 dependencies.
The main difference for users is the surface API. The mental model for Omni: is build a request, get a stream, consume it. For Langchain it’s build a chain, add some messages, run the chain. Omni’s style is functional, data oriented; Langchain’s is stateful structs, callbacks, framework-y.
Internally the big difference is how Omni splits Providers and Dialects into two things, which should make it relatively painless to add more providers over time. Langchain has one big fat module per provider which I think looks hard to maintain. In theory Omni could sit underneath Langchain and be that provider translation layer.
AndyL
Thanks a lot @aaronrussell for the explanation and for the library. Could you please compare/contrast Omni with ReqLLM?
aaronrussell
Regarding req_llm, they are honestly very similar. Both libraries attempt to solve the same problem and approach it in very similar ways. Both libraries are clearly influenced by the Vercel AI SDK, but in Omni I’ve taken a lot of inspiration from the Pi codebase, which I think steers it in a slightly leaner direction.
generate_text(model, context, inference_opts)andstream_text(model, context, inference_opts)Some gaps (some of these will narrow over time):
Lower level differences:
generate_text/3is a streaming request that is accumulated in one call. This means a dialect only needs to care about streaming requests - resulting in simpler implementations.AndyL
Thanks for your reply! There may be a couple other distinctions. With ReqLLM, Ollama integration is not an out-of-the-box option, but Omni docs show Ollama support. Also: I believe ReqLLM is integrated with Jido and Ash.
I’ll give Omni a try with Ollama!
aaronrussell
Yep there is an Ollama provider. A little config is needed:
Oh, and tool calling, reasoning etc is model dependent. That little 4b qwen model is pretty good for testing.
aaronrussell
Omni updates this week…
Omni v1.2.0
Omni.Agentand associated modules into it’s own package. omni lives as a stateless LLM API layer for any LLM provider. omni_agent becomes it’s own thing (see below).Omni Agent v0.1.0
Links
Omni - GitHub | Docs
Omni Agent - GitHub | Docs
jstimps
Thanks for releasing Omni. I like the approach, especially the low number of deps.
Question: With Omni, is it possible to receive the model’s tool selections without having Omni execute the tool itself? I wasn’t able to tell in the docs; they focus on the auto-execution loop, which makes sense for many cases, but I would like to have full control over the tool execution and resulting context additions.
aaronrussell
Yep two ways to do that. First and simplest is pass
max_steps: 1, eg:Also tools themselves can be schema-only - they don’t need a function handler attached. So if any of your tools don’t have a function handler then it will just stop there with a
stop_reason: :tool_useand then it’s up to you to handle the rest and feed the results back.aaronrussell
I’ve just released new versions of
omniandomni_agent… in fact, there’s a been a few updates since my last post so rolling it all into one.Omni v1.3.2
Omni.Codecmodule for serialisation of Omni structs to and from JSON-safe maps.xhighthinking level option.Omni Agent v0.3.0
Omni.Session- a process that wraps anOmni.Agentprocess, providing persistence, and conversation branching and navigation (edits and regenerations).Omni.Session.Storebehaviour and defaultOmni.Session.Store.FileSystemadapter.Omni.Session.Managersupervisor for managing multiple concurrent sessions.Omni.Session.Treefor holding branching conversations.:navigateand:treeevents).:message,:step, and:stateevents, while the:stopand:continueevents have been replaced by the:turnevent.Links
Omni - GitHub | Docs
Omni Agent - GitHub | Docs