LangChain.rb — Chains, Agents, and Memory for Ruby AI Apps

ruby dev.to

This is post #21 in the Ruby for AI series. We've been building everything from scratch — API calls, RAG pipelines, agents, streaming. That's great for understanding. But sometimes you want a framework that handles the plumbing. Enter LangChain.rb — Ruby's port of the popular LangChain library.

What Is LangChain.rb?

LangChain.rb gives you pre-built abstractions for common AI patterns: LLM clients, prompt templates, chains, vector search, agents, and conversation memory. Instead of wiring up OpenAI calls and pgvector queries manually, you get composable building blocks.

Install it:

gem install langchainrb
# or in your Gemfile:
gem "langchainrb"
Enter fullscreen mode Exit fullscreen mode

Basic LLM Usage

Start with a simple LLM call:

require "langchain"

llm = Langchain::LLM::OpenAI.new(
  api_key: ENV["OPENAI_API_KEY"],
  default_options: { temperature: 0.7, chat_model: "gpt-4o" }
)

response = llm.chat(messages: [{ role: "user", content: "Explain Ruby blocks in one paragraph." }])
puts response.chat_completion
Enter fullscreen mode Exit fullscreen mode

LangChain.rb supports multiple providers out of the box:

# Anthropic
llm = Langchain::LLM::Anthropic.new(api_key: ENV["ANTHROPIC_API_KEY"])

# Ollama (local models)
llm = Langchain::LLM::Ollama.new(url: "http://localhost:11434")

# Google Gemini
llm = Langchain::LLM::GoogleGemini.new(api_key: ENV["GEMINI_API_KEY"])
Enter fullscreen mode Exit fullscreen mode

Swap providers without changing your application code. That's the point.

Prompt Templates

Hardcoded prompts get messy fast. Use templates:

template = Langchain::Prompt::PromptTemplate.new(
  template: "You are a {role}. Explain {topic} to a beginner in {style} style.",
  input_variables: ["role", "topic", "style"]
)

prompt = template.format(role: "senior Ruby developer", topic: "metaprogramming", style: "conversational")
puts prompt
# => "You are a senior Ruby developer. Explain metaprogramming to a beginner in conversational style."

response = llm.chat(messages: [{ role: "user", content: prompt }])
Enter fullscreen mode Exit fullscreen mode

You can also save and load templates from JSON files:

# Save
template.save(file_path: "prompts/explain.json")

# Load
loaded = Langchain::Prompt.load_from_path(file_path: "prompts/explain.json")
Enter fullscreen mode Exit fullscreen mode

Conversation Memory

Chatbots need to remember context. LangChain.rb provides memory objects that track conversation history:

llm = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])

# ConversationMemory stores full message history
memory = Langchain::Memory::ConversationMemory.new(
  llm: llm,
  messages: []
)

# Add messages
memory.add_message(role: "user", content: "My name is Sarah.")
memory.add_message(role: "assistant", content: "Nice to meet you, Sarah!")
memory.add_message(role: "user", content: "What's my name?")

# The LLM sees the full history
puts memory.messages
# => [{role: "user", content: "My name is Sarah."}, ...]
Enter fullscreen mode Exit fullscreen mode

For long conversations, use WindowMemory to keep only the last N messages:

memory = Langchain::Memory::ConversationMemory.new(
  llm: llm,
  messages: [],
  strategy: :sliding_window,
  window_size: 10  # Keep last 10 messages
)
Enter fullscreen mode Exit fullscreen mode

Vector Search Integration

LangChain.rb wraps vector databases for embedding and search. Here's pgvector:

# Connect to your vector store
pgvector = Langchain::Vectorsearch::Pgvector.new(
  url: ENV["DATABASE_URL"],
  index_name: "documents",
  llm: llm
)

# Create the schema
pgvector.create_default_schema

# Add documents — automatically embeds them
pgvector.add_texts(
  texts: [
    "Ruby was created by Yukihiro Matsumoto in 1995.",
    "Rails is a web framework that follows convention over configuration.",
    "ActiveRecord is an ORM that maps database tables to Ruby classes."
  ]
)

# Semantic search
results = pgvector.similarity_search(query: "Who created Ruby?", k: 2)
results.each { |doc| puts doc.content }
Enter fullscreen mode Exit fullscreen mode

It handles embedding generation, storage, and similarity search in one interface. Also supports Pinecone, Weaviate, Qdrant, and Chroma.

RAG with Ask

Remember the RAG system we built from scratch in post #18? LangChain.rb does it in a few lines:

pgvector = Langchain::Vectorsearch::Pgvector.new(
  url: ENV["DATABASE_URL"],
  index_name: "knowledge_base",
  llm: llm
)

# ask() = retrieve relevant docs + send to LLM with context
answer = pgvector.ask(question: "How does ActiveRecord handle migrations?")
puts answer.chat_completion
Enter fullscreen mode Exit fullscreen mode

The ask method retrieves relevant chunks, constructs a prompt with context, and sends it to the LLM. One method call for the entire RAG pipeline.

Agents with Tools

LangChain.rb includes an agent framework with tool use:

# Define tools the agent can use
search_tool = Langchain::Tool::GoogleSearch.new(api_key: ENV["SERPAPI_KEY"])
calculator = Langchain::Tool::Calculator.new

# Create an agent with tools
agent = Langchain::Agent::ReActAgent.new(
  llm: llm,
  tools: [search_tool, calculator]
)

# The agent decides which tools to use
response = agent.run(question: "What is the mass of the Earth in kilograms divided by 2?")
puts response
Enter fullscreen mode Exit fullscreen mode

The ReAct agent follows a Reason-Act loop: it thinks about what tool to use, calls it, observes the result, and decides the next step. Just like we built manually in post #19 — but packaged and ready to go.

You can also build custom tools:

class WeatherTool < Langchain::Tool::Base
  define_function :get_weather,
    description: "Get current weather for a city",
    parameters: {
      type: "object",
      properties: {
        city: { type: "string", description: "City name" }
      },
      required: ["city"]
    }

  def get_weather(city:)
    # Your weather API call here
    "72°F and sunny in #{city}"
  end
end

agent = Langchain::Agent::ReActAgent.new(
  llm: llm,
  tools: [WeatherTool.new]
)
Enter fullscreen mode Exit fullscreen mode

Using LangChain.rb in Rails

Integrate it as a service object:

# app/services/ai_assistant.rb
class AiAssistant
  def initialize
    @llm = Langchain::LLM::OpenAI.new(
      api_key: Rails.application.credentials.openai_api_key,
      default_options: { temperature: 0.7, chat_model: "gpt-4o" }
    )
    @vectorstore = Langchain::Vectorsearch::Pgvector.new(
      url: ENV["DATABASE_URL"],
      index_name: "app_knowledge",
      llm: @llm
    )
  end

  def chat(user_message, conversation_history: [])
    messages = conversation_history + [{ role: "user", content: user_message }]
    response = @llm.chat(messages: messages)
    response.chat_completion
  end

  def search_and_answer(question)
    @vectorstore.ask(question: question).chat_completion
  end
end
Enter fullscreen mode Exit fullscreen mode

Then use it anywhere in your app:

# In a controller
assistant = AiAssistant.new
answer = assistant.search_and_answer("How do I reset my password?")
Enter fullscreen mode Exit fullscreen mode

When to Use LangChain.rb vs. Rolling Your Own

Use LangChain.rb when:

  • You need to support multiple LLM providers
  • You want quick prototyping with RAG, agents, or memory
  • The built-in abstractions fit your use case

Roll your own when:

  • You need fine-grained control over prompts and API calls
  • Performance is critical and you want to minimize overhead
  • Your pattern doesn't fit LangChain's abstractions

There's no shame in using both. Use LangChain.rb for the boring plumbing and custom code where it matters.

What You Learned

LangChain.rb gives you LLM abstraction, prompt templates, conversation memory, vector search, RAG, and agents — all in idiomatic Ruby. It's not magic. It's the same patterns we've been building, packaged into a library.

Next up: image generation in Rails with DALL-E and Stability AI.

Read Full Tutorial open_in_new
arrow_back Back to Tutorials