ActiveSupport-style LLM utilities for Ruby that make AI operations feel like native Ruby.
ruby_llm-text provides intuitive one-liner utility methods for common LLM tasks including text summarization, translation, data extraction, classification, grammar correction, sentiment analysis, key point extraction, text rewriting, and question answering. It integrates seamlessly with the ruby_llm ecosystem, providing a simple interface without requiring chat objects, message arrays, or configuration boilerplate.
Add this line to your application's Gemfile:
gem 'ruby_llm-text'And then execute:
$ bundle installOr install it yourself as:
$ gem install ruby_llm-textrequire 'ruby_llm/text'
# Configure ruby_llm with your API key
RubyLLM.configure do |config|
config.openai_api_key = ENV["OPENAI_API_KEY"]
end
# Summarize text
long_article = "This is a very long article about..."
summary = RubyLLM::Text.summarize(long_article)
# Translate text
greeting = RubyLLM::Text.translate("Bonjour le monde", to: "en")
# Extract structured data
text = "My name is John and I am 30 years old."
data = RubyLLM::Text.extract(text, schema: { name: :string, age: :integer })
# Classify text
review = "This product is amazing!"
sentiment = RubyLLM::Text.classify(review, categories: ["positive", "negative", "neutral"])
# Fix grammar and spelling
corrected = RubyLLM::Text.fix_grammar("Their going to the stor tommorow")
# Get sentiment with confidence
sentiment_analysis = RubyLLM::Text.sentiment("I love this product!")
# => {"label" => "positive", "confidence" => 0.95}
# Extract key points from long text
points = RubyLLM::Text.key_points("Long meeting notes...", max_points: 3)Condense text into a shorter summary.
RubyLLM::Text.summarize(text, length: :medium, max_words: nil, model: nil)Parameters:
text(String): The text to summarizelength(Symbol|String): Predefined length (:short,:medium,:detailed) or custom descriptionmax_words(Integer, optional): Maximum word count for summarymodel(String, optional): Specific model to use
Examples:
# Basic usage
RubyLLM::Text.summarize("Long article text...")
# With length option
RubyLLM::Text.summarize(text, length: :short)
# With word limit
RubyLLM::Text.summarize(text, length: :medium, max_words: 50)
# Custom length description
RubyLLM::Text.summarize(text, length: "bullet points")Translate text between languages.
RubyLLM::Text.translate(text, to:, from: nil, model: nil)Parameters:
text(String): The text to translateto(String): Target language (e.g., "en", "spanish", "français")from(String, optional): Source language for better accuracymodel(String, optional): Specific model to use
Examples:
# Basic translation
RubyLLM::Text.translate("Bonjour", to: "en")
# With source language specified
RubyLLM::Text.translate("Hola mundo", to: "en", from: "es")
# Natural language specifications
RubyLLM::Text.translate("Hello", to: "french")Extract structured data from unstructured text.
RubyLLM::Text.extract(text, schema:, model: nil)Parameters:
text(String): The text to extract data fromschema(Hash): Data structure specificationmodel(String, optional): Specific model to use
Schema Types:
:string- Text fields:integer,:number- Numeric fields:boolean- True/false fields:array- List fields
Examples:
# Extract person details
text = "John Smith is 30 years old and works as a software engineer in San Francisco."
schema = {
name: :string,
age: :integer,
profession: :string,
location: :string
}
data = RubyLLM::Text.extract(text, schema: schema)
# Extract product information
product_text = "iPhone 15 Pro costs $999 and has excellent reviews"
product_schema = {
name: :string,
price: :number,
currency: :string,
reviews: :array
}
product_data = RubyLLM::Text.extract(product_text, schema: product_schema)Classify text into predefined categories.
RubyLLM::Text.classify(text, categories:, model: nil)Parameters:
text(String): The text to classifycategories(Array): List of possible categoriesmodel(String, optional): Specific model to use
Examples:
# Sentiment analysis
review = "This product exceeded my expectations!"
sentiment = RubyLLM::Text.classify(review,
categories: ["positive", "negative", "neutral"]
)
# Topic classification
article = "The stock market reached new highs today..."
topic = RubyLLM::Text.classify(article,
categories: ["technology", "finance", "sports", "politics"]
)
# Priority classification
email = "URGENT: Server is down and customers can't access the site"
priority = RubyLLM::Text.classify(email,
categories: ["low", "medium", "high", "critical"]
)Correct grammar, spelling, and punctuation errors.
RubyLLM::Text.fix_grammar(text, explain: false, preserve_style: false, model: nil)Parameters:
text(String): The text to correctexplain(Boolean, optional): Return explanations of changes made (default: false)preserve_style(Boolean, optional): Keep original tone and style (default: false)model(String, optional): Specific model to use
Examples:
# Basic grammar correction
RubyLLM::Text.fix_grammar("Their going to the stor tommorow")
# => "They're going to the store tomorrow"
# With explanations
result = RubyLLM::Text.fix_grammar("bad grammer here", explain: true)
# => {"corrected" => "bad grammar here", "changes" => ["grammer → grammar"]}
# Preserve casual style
RubyLLM::Text.fix_grammar("hey whats up", preserve_style: true)
# => "Hey, what's up?" (keeps casual tone)Analyze sentiment with confidence scores.
RubyLLM::Text.sentiment(text, categories: ["positive", "negative", "neutral"], simple: false, model: nil)Parameters:
text(String): The text to analyzecategories(Array, optional): Custom sentiment categories (default: positive/negative/neutral)simple(Boolean, optional): Return just the label without confidence (default: false)model(String, optional): Specific model to use
Examples:
# Basic sentiment analysis with confidence
RubyLLM::Text.sentiment("I love this product!")
# => {"label" => "positive", "confidence" => 0.95}
# Simple mode (just the label)
RubyLLM::Text.sentiment("Great service!", simple: true)
# => "positive"
# Custom categories
RubyLLM::Text.sentiment("I'm excited about tomorrow!",
categories: ["excited", "calm", "worried", "neutral"])
# => {"label" => "excited", "confidence" => 0.92}Extract main points from longer text.
RubyLLM::Text.key_points(text, max_points: nil, format: :sentences, model: nil)Parameters:
text(String): The text to extract points frommax_points(Integer, optional): Maximum number of points to extractformat(Symbol, optional): Output format (:sentences,:bullets,:numbers)model(String, optional): Specific model to use
Examples:
# Basic key points extraction
meeting_notes = "We discussed budget, hiring, and marketing plans..."
points = RubyLLM::Text.key_points(meeting_notes)
# => ["Budget allocation reviewed", "New hiring plans discussed", ...]
# Limit number of points
RubyLLM::Text.key_points(text, max_points: 3)
# Different formats
RubyLLM::Text.key_points(text, format: :bullets) # Returns clean text
RubyLLM::Text.key_points(text, format: :numbers) # Returns clean text
RubyLLM::Text.key_points(text, format: :sentences) # Returns clean sentencesTransform text tone and style.
RubyLLM::Text.rewrite(text, tone: nil, style: nil, instruction: nil, model: nil)Parameters:
text(String): The text to rewritetone(Symbol|String, optional): Target tone (:professional,:casual,:academic,:creative,:brief)style(Symbol|String, optional): Target style (:concise,:detailed,:formal,:informal)instruction(String, optional): Custom rewriting instructionmodel(String, optional): Specific model to use
Examples:
# Change tone
RubyLLM::Text.rewrite("hey whats up", tone: :professional)
# => "Good morning. How are you doing?"
# Change style
RubyLLM::Text.rewrite("This is a long explanation...", style: :concise)
# => "Brief explanation."
# Custom instructions
RubyLLM::Text.rewrite("Hello there", instruction: "make it sound like a pirate")
# => "Ahoy there, matey!"
# Combine multiple transformations
RubyLLM::Text.rewrite(text, tone: :professional, style: :concise)Answer questions based on provided text.
RubyLLM::Text.answer(text, question, include_confidence: false, model: nil)Parameters:
text(String): The text to search for answersquestion(String): The question to answerinclude_confidence(Boolean, optional): Include confidence score (default: false)model(String, optional): Specific model to use
Examples:
article = "Ruby was created by Yukihiro Matsumoto in 1995..."
# Basic question answering
RubyLLM::Text.answer(article, "Who created Ruby?")
# => "Yukihiro Matsumoto"
# Boolean questions
RubyLLM::Text.answer(article, "Is Ruby a programming language?")
# => true
# With confidence scores
result = RubyLLM::Text.answer(article, "When was Ruby created?", include_confidence: true)
# => {"answer" => "1995", "confidence" => 0.98}
# When answer not found
RubyLLM::Text.answer(article, "What is Python?")
# => "information not available"This gem uses ruby_llm's configuration for API keys and default models:
# Configure ruby_llm (API keys, default model, etc.)
RubyLLM.configure do |config|
config.openai_api_key = ENV["OPENAI_API_KEY"]
config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
config.default_model = "gpt-4.1-mini"
endOptionally configure text-specific settings:
RubyLLM::Text.configure do |config|
# Temperature for text operations (default: 0.3)
config.temperature = 0.3
# Method-specific model overrides (falls back to RubyLLM.config.default_model)
config.summarize_model = "gpt-4.1-mini"
config.translate_model = "claude-sonnet-4-5" # Use Claude for translation
config.extract_model = "gpt-4.1" # Use GPT-4 for extraction
config.classify_model = "gpt-4.1-mini"
config.grammar_model = "gpt-4.1-mini" # Good for grammar correction
config.sentiment_model = "claude-haiku-4-5" # Fast for sentiment analysis
config.key_points_model = "gpt-4.1-mini" # Good for summarization tasks
config.rewrite_model = "gpt-4.1" # Creative rewriting tasks
config.answer_model = "claude-sonnet-4-5" # Strong reasoning for Q&A
endPer-call overrides:
# Override model for specific calls
RubyLLM::Text.summarize(text, model: "claude-sonnet-4-5")
# Pass additional options (passed through to ruby_llm)
RubyLLM::Text.translate(text, to: "es", temperature: 0.1)For a more Rails-like experience, you can enable String monkey-patching:
require 'ruby_llm/text/string_ext'
# Now you can call methods directly on strings
"Long article text...".summarize
"Bonjour".translate(to: "en")
"John is 30".extract(schema: { name: :string, age: :integer })
"Great product!".classify(categories: ["positive", "negative"])
"Their going to the stor".fix_grammar
"I love this!".sentiment
"Long meeting notes...".key_points(max_points: 3)
"hey whats up".rewrite(tone: :professional)
"Ruby was created in 1995".answer("When was Ruby created?")This gem builds on top of ruby_llm and inherits its configuration and model support:
- Models: Supports all ruby_llm models (OpenAI GPT, Anthropic Claude, etc.)
- Configuration: Uses ruby_llm's underlying configuration system
- Error handling: Inherits ruby_llm's robust error handling
The gem provides clear error messages for common issues:
# Missing required parameters
RubyLLM::Text.extract("text") # ArgumentError: schema is required for extraction
RubyLLM::Text.classify("text", categories: []) # ArgumentError: categories are required
RubyLLM::Text.answer("text", "") # ArgumentError: question is required
RubyLLM::Text.rewrite("text") # ArgumentError: Must specify at least one of: tone, style, or instruction
# API errors are wrapped with context
RubyLLM::Text.summarize("text") # RubyLLM::Text::Error: LLM call failed: [original error]
# Graceful fallbacks for parsing issues
RubyLLM::Text.sentiment("text") # Falls back to simple mode if JSON parsing failsAfter checking out the repo, run:
bin/setup # Install dependencies
rake test # Run tests
rake rubocop # Run linter
rake # Run tests and linterThe test suite uses Mocha for mocking LLM API calls, ensuring reliable and fast tests without requiring API keys:
# Run all tests
bundle exec rake test
# Run tests with linting
bundle exec rake
# Run linting only
bundle exec rubocopFor manual testing with real API calls, use the provided test script:
# Set up your API key
export OPENAI_API_KEY="your-key"
# or
export ANTHROPIC_API_KEY="your-key"
# Run manual tests
bin/manual-testThis script tests all nine methods with real LLM APIs and provides helpful output for verification.
Bug reports and pull requests are welcome on GitHub at https://github.com/patrols/ruby_llm-text.
The gem is available as open source under the terms of the MIT License.