AI Lite

AI Lite is a pure Ruby, dependency-light client for simple OpenAI API calls.

It is built for Rails apps and plain Ruby projects where installing a full OpenAI SDK is too heavy, incompatible, or unnecessary. It is especially useful in legacy Rails apps where Rails, ActiveSupport, Ruby, or HTTP-client dependency constraints make larger client libraries difficult to add.

Use AI Lite when you want a small OpenAI client that works without tying your app to a specific Rails version or a larger dependency stack.

This gem is intentionally small:

  • No Rails dependency
  • No official OpenAI gem dependency
  • No Faraday, HTTParty, ActiveSupport, or connection pool dependency
  • Uses only Ruby stdlib: Net::HTTP, URI, JSON, and Base64

It is not meant to replace the official OpenAI SDK. It is a small wrapper for projects that only need a few clean interfaces:

ai.chat("Say hello")
ai.moderate("User submitted text")
ai.embed("Text to vectorize")
ai.image("A simple app icon")

Usage

require "ai_lite"

ai = AiLite.new
result = ai.chat("Say hello")

puts result["content"]

By default, the client looks for an API key in OPENAI_API_KEY, then falls back to OPEN_AI_TOKEN.

You can also pass the key directly:

ai = AiLite.new(api_key: "sk-...")

Configuration

In Rails, configure the default client from an initializer:

# config/initializers/ai_lite.rb
AiLite.configure do |config|
  config.api_key = ENV["OPENAI_API_KEY"]
  config.model = "gpt-5.5"
  config.moderation_model = "omni-moderation-latest"
  config.embedding_model = "text-embedding-3-small"
  config.image_model = "gpt-image-2"
  config.timeout = 120
  config.max_output_tokens = 2000
end

Then use the configured singleton-style client:

result = AiLite.chat("Say hello")

You can still instantiate a separate client for another token:

client = AiLite.new(api_key: "sk-other-token")
result = client.chat("Say hello")

Chat

result = ai.chat(
  "Return JSON confirming whether this is valid.",
  instructions: "Return only minified JSON.",
  max_output_tokens: 500,
  options: {
    text: { verbosity: "low" }
  }
)

chat sends a POST request to /v1/responses with:

  • model
  • input
  • max_output_tokens
  • optional instructions
  • optional debug
  • optional extra options

The default model is gpt-5.5.

The OpenAI API URL is fixed to https://api.openai.com/v1/responses.

Moderation

Use moderate to classify user-submitted text or images for potentially harmful content before saving, publishing, or sending it into another AI call.

A common use case is checking a comment before it is shown publicly:

comment = "User submitted comment"
result = ai.moderate(comment)

if result["content"]["flagged"]
  # hide, block, or route the comment to review
else
  # publish the comment
end

Moderation responses include an overall flagged value plus category booleans and scores:

{
  "content" => {
    "flagged" => false,
    "categories" => {
      "hate" => false,
      "violence" => false
    },
    "category_scores" => {
      "hate" => 0.01,
      "violence" => 0.02
    }
  },
  "response_id" => "modr_...",
  "status" => 200,
  "error" => nil,
  "raw" => nil
}

moderate sends a POST request to /v1/moderations with:

  • model
  • input
  • optional extra options

The default moderation model is omni-moderation-latest.

You can pass an image URL:

result = ai.moderate(
  text: "Profile caption",
  image_url: "https://example.com/image.png"
)

Or a local image path:

result = ai.moderate(image_path: "tmp/upload.png")

Local images are read, base64 encoded, and sent as JSON data URLs. They do not use multipart uploads. Supported local image extensions are .gif, .jpeg, .jpg, .png, and .webp.

You can also pass OpenAI's raw moderation input shape directly:

result = ai.moderate([
  { type: "text", text: "Caption" },
  {
    type: "image_url",
    image_url: {
      url: "https://example.com/image.png"
    }
  }
])

Embeddings

Use embed to turn text into vectors that can be stored and compared for semantic search, recommendations, duplicate detection, or retrieval-augmented generation.

result = ai.embed("How do I reset my password?")
vector = result["content"]

For one string input, content is the embedding vector:

{
  "content" => [0.012, -0.44, 0.203],
  "response_id" => nil,
  "status" => 200,
  "error" => nil,
  "raw" => nil
}

For multiple inputs, content is an array of vectors in the same order:

result = ai.embed([
  "How do I reset my password?",
  "How do I update my billing card?"
])

vectors = result["content"]
first_vector = vectors[0]
{
  "content" => [
    [0.012, -0.44, 0.203],
    [0.332, 0.021, -0.118]
  ],
  "response_id" => nil,
  "status" => 200,
  "error" => nil,
  "raw" => nil
}

embed sends a POST request to /v1/embeddings with:

  • model
  • input
  • optional dimensions
  • optional encoding_format
  • optional debug
  • optional extra options

The default embedding model is text-embedding-3-small.

Pass debug: true to include the raw OpenAI response, including token usage:

result = ai.embed("Hello", debug: true)

result["content"]       # embedding vector
result["raw"]["usage"]  # token usage

Images

Use image to generate an image from a prompt.

result = ai.image("A clean Ruby gem logo on a white background")
image_data = result["content"]

By default, content is the base64-encoded generated image:

{
  "content" => "iVBORw0KGgo...",
  "response_id" => nil,
  "status" => 200,
  "error" => nil,
  "raw" => nil
}

Write the generated image bytes directly to a file with output_path:

result = ai.image(
  "A clean Ruby gem logo on a white background",
  output_path: "tmp/logo.png"
)

image sends a POST request to /v1/images/generations with:

  • model
  • prompt
  • optional size
  • optional quality
  • optional background
  • optional output_format
  • optional debug
  • optional extra options

The default image model is gpt-image-2.

Use output_format to request png, webp, or jpeg output:

result = ai.image(
  "A transparent app icon",
  background: "transparent",
  output_format: "webp",
  output_path: "tmp/icon.webp"
)

Pass debug: true to include the raw OpenAI response, including usage when returned:

result = ai.image("A tiny robot sticker", debug: true)

result["content"]       # base64 image data
result["raw"]["usage"]  # token usage, when returned

Multi-Turn Chat

Responses include a response_id that can be passed back through options as previous_response_id:

first = ai.chat("Tell me a short joke.")

follow_up = ai.chat(
  "Explain why that is funny.",
  options: {
    previous_response_id: first["response_id"]
  }
)

puts follow_up["content"]

Return Shape

Methods return a hash envelope.

Text output:

{
  "content" => "Hello!",
  "response_id" => "resp_...",
  "status" => 200,
  "error" => nil,
  "raw" => nil
}

JSON-looking model output:

{
  "content" => { "valid" => true },
  "response_id" => "resp_...",
  "status" => 200,
  "error" => nil,
  "raw" => nil
}

Failure:

{
  "content" => nil,
  "response_id" => nil,
  "status" => 401,
  "error" => "Invalid API key",
  "raw" => nil
}

Pass debug: true to include the raw OpenAI response:

result = ai.chat("Say hello", debug: true)

{
  "content" => "Hello!",
  "response_id" => "resp_...",
  "status" => 200,
  "error" => nil,
  "raw" => { ... }
}

Development

Run the test suite:

ruby -Ilib:test test/ai_lite_test.rb