UniLM.jl

A unified Julia interface for large language models.

CI codecov Aqua QA Stable Dev

What is UniLM.jl?

UniLM.jl provides a Julian, type-safe interface to LLM providers via the OpenAI-compatible API standard — covering the Chat Completions API, the Responses API, Image Generation, Embeddings, and MCP. Works with OpenAI, Azure, Gemini, Mistral, Ollama, vLLM, LM Studio, and any OpenAI-compatible provider.

Key Features

  • 🗣️ Chat Completions — stateful conversations with automatic history management
  • 🔮 Responses API — OpenAI's newer API with built-in tools, multi-turn chaining, and reasoning
  • 🖼️ Image Generation — create images from text prompts with gpt-image-1.5
  • 🔧 Tool/Function Calling — first-class support for function tools in both APIs, with automated tool_loop
  • 🔌 MCP (Model Context Protocol) — connect to MCP servers or build your own, with seamless tool loop integration
  • 📊 Embeddings — text embedding generation
  • 🌊 Streaming — real-time token streaming with do-block syntax
  • 📐 Structured Output — JSON Schema–constrained generation
  • ☁️ Multi-Backend — OpenAI, Azure OpenAI, and Google Gemini
  • Type Safety — invalid states are unrepresentable; tested with JET.jl and Aqua.jl

Two APIs, One Package

FeatureChat CompletionsResponses API
Stateful conversationsChat + push!previous_response_id
System promptMessage(Val(:system), ...)instructions kwarg
Tool callingGPTTool / GPTToolCallFunctionTool / function_tool
Web searchWebSearchTool
File searchFileSearchTool
Streamingstream=true + callbackdo-block syntax
Structured outputResponseFormatTextConfig / json_schema_format
Reasoning (O-series)Reasoning
Automated tool looptool_loop!tool_loop
MCP integrationmcp_tools bridgeMCPTool / mcp_tool

Installation

using Pkg
Pkg.add(url="https://github.com/algunion/UniLM.jl")

Or in the Pkg REPL:

pkg> add https://github.com/algunion/UniLM.jl

Quick Example

Building requests — these construct objects locally without calling the API:

using UniLM
using JSON

# Chat Completions request
chat = Chat(model="gpt-5.2")
push!(chat, Message(Val(:system), "You are a Julia expert."))
push!(chat, Message(Val(:user), "Explain multiple dispatch in one sentence."))
println("Chat has ", length(chat), " messages, model: ", chat.model)
println("Request body preview:")
println(JSON.json(chat))
Chat has 2 messages, model: gpt-5.2
Request body preview:
{"messages":[{"role":"system","content":"You are a Julia expert."},{"role":"user","content":"Explain multiple dispatch in one sentence."}],"model":"gpt-5.2"}
# Responses API request
r = Respond(input="What makes Julia special?")
println("Respond model: ", r.model)
println(JSON.json(r))
Respond model: gpt-5.2
{"model":"gpt-5.2","input":"What makes Julia special?"}
# Image Generation request
ig = ImageGeneration(prompt="A watercolor Julia logo", quality="high")
println("Image model: ", ig.model)
println(JSON.json(ig))
Image model: gpt-image-1.5
{"quality":"high","prompt":"A watercolor Julia logo","model":"gpt-image-1.5"}

With a valid API key, actual API calls return structured results:

Responses API (recommended for new code):

result = respond("Explain Julia's multiple dispatch in 2-3 sentences.")
if result isa ResponseSuccess
    println(output_text(result))
else
    println("Request failed — ", output_text(result))
end
Julia uses multiple dispatch to choose which method of a function to run based on the types of *all* its arguments, not just the first one. You can define several methods with the same function name but different type signatures, and Julia picks the most specific match at runtime. This makes it natural to write generic code while still getting specialized, high-performance implementations for particular type combinations.

Chat Completions:

chat = Chat(model="gpt-4o-mini")
push!(chat, Message(Val(:system), "You are a concise Julia programming tutor."))
push!(chat, Message(Val(:user), "What is multiple dispatch? Answer in 2-3 sentences."))
result = chatrequest!(chat)
if result isa LLMSuccess
    println(result.message.content)
else
    println("Request failed — see result for details")
end
Multiple dispatch is a programming paradigm where the method that gets executed is determined by the runtime types of all its arguments, not just the first one. This allows for more flexible and modular code, as functions can be specialized based on the types of all input parameters. In Julia, multiple dispatch is a core feature that enables efficient and expressive programming, facilitating polymorphism and code reuse.

Image Generation:

result = generate_image(
    "A watercolor painting of a friendly robot reading a Julia programming book",
    size="1024x1024", quality="medium"
)
println("Success: ", result isa ImageSuccess)
if result isa ImageSuccess
    save_image(image_data(result)[1], joinpath(@__DIR__, "assets", "generated_robot.png"))
    println("Saved to assets/generated_robot.png")
else
    println("Image generation failed — see result for details")
end
Success: true
Saved to assets/generated_robot.png

Generated robot reading Julia

Next Steps