Article
TypeSafe AI Ruby SDK: Typed AI Answers for Ruby and Rails
A practical introduction to typesafe-sdk-ruby, an unofficial Ruby SDK for the TypeSafe AI API with typed questions, retries, timeouts, and predictable error handling.
I have been looking for practical ways to add useful AI capabilities to Ruby applications without making the codebase depend on a collection of raw HTTP calls and custom response parsers. Support-ticket routing, document classification, moderation, and workflow automation all need the same thing: predictable results that fit naturally into application code.
That is the problem I wanted to solve with typesafe-sdk-ruby, an unofficial, community-maintained Ruby SDK for the TypeSafe AI API. TypeSafe already had an official JavaScript/TypeScript SDK, but there was no Ruby SDK for Ruby developers. I created this gem to bring the same core API experience to Ruby and Rails projects, including typed questions, typed response objects, retries, timeouts, structured logging, and a clear error hierarchy.
This is the story behind the project, how it relates to the original JavaScript SDK, and how to use it in a Ruby application.
What is typesafe-sdk-ruby?
typesafe-sdk-ruby is a Ruby client for TypeSafe AI’s Jev model. Instead of treating every AI response as an untyped string, you describe the question you want to ask and receive an answer object that matches that question.
The gem supports three question types:
- Noul questions for yes-or-no decisions
- Choice questions for selecting one option from a defined set
- Score questions for returning a scored result with confidence and probabilities
Why I created a Ruby version
The starting point was the official TypeSafe JavaScript SDK. It provides the reference client experience for JavaScript and TypeScript, including the same question-driven approach to typed answers. Since there was no Ruby implementation available, I created typesafe-sdk-ruby as a community port for Ruby developers who wanted to use TypeSafe AI without switching languages or maintaining raw HTTP integration code themselves.
The Ruby gem follows the same core concepts while using Ruby-native naming, objects, configuration, and error handling. It is not affiliated with, endorsed by, or supported by TypeSafe AI. Gem versions intentionally mirror the upstream JavaScript SDK versions, making it easier to see which release the Ruby port follows.
Why use a typed Ruby AI SDK?
Calling an AI API directly can be enough for a prototype. In a production Ruby application, an SDK should also make the boundaries around that API explicit.
Typed question builders help keep the prompt and expected output together. Typed response classes make the result easier to inspect in Ruby code. A dedicated error hierarchy lets the application handle rate limits, authentication problems, invalid requests, and network failures differently.
This matters especially in Rails applications, where an AI result often becomes part of a background job, a customer-support workflow, an admin dashboard, or a database write. The closer the external response is to a normal Ruby object, the less parsing and defensive code the application needs to maintain.
Install the Ruby SDK
The gem supports Ruby 3.1 and newer. Install it directly:
gem install typesafe-sdk-ruby
Or add it to a Rails or Ruby application’s Gemfile:
gem "typesafe-sdk-ruby"
Then run:
bundle install
The client reads the API key from TYPESAFE_API_KEY by default. Keeping the key in the environment is the recommended approach for local development, CI, and production deployments.
Quickstart: classify text with typed answers
Here is a small example that classifies a support ticket:
require "typesafe-sdk-ruby"
client = Typesafe::SDK::Client.new
response = client.system_one(
state: { document: "I was charged twice. Please fix this ASAP." },
questions: {
category: Typesafe::SDK.choice("What is this ticket about?", {
billing: nil,
technical: nil,
other: nil,
}),
},
)
category = response.answers["category"].choice
puts category
The answer for the choice question is a Typesafe::SDK::ChoiceResponse. That response exposes the selected choice, along with confidence and probabilities when available.
The same pattern works for other decision types:
question = Typesafe::SDK.score(
"How urgent is this request?",
{ low: nil, medium: nil, high: nil },
)
The important idea is that the question type communicates the shape of the answer before the request is sent. That makes the integration easier to read and easier to test.
Configuration for Ruby applications
The SDK supports explicit client options and environment variables. Explicit options take precedence over environment variables.
client = Typesafe::SDK::Client.new(
api_key: ENV.fetch("TYPESAFE_API_KEY"),
base_url: "https://api.typesafe.ai",
default_model: "jev-latest",
log_level: :info,
timeout: 10,
retry_policy: {
max_retries: 2,
backoff_initial_ms: 500,
},
)
The main environment variables are:
| Option | Environment variable | Default |
| --- | --- | --- |
| api_key: | TYPESAFE_API_KEY | Required |
| base_url: | TYPESAFE_BASE_URL | https://api.typesafe.ai |
| default_model: | TYPESAFE_DEFAULT_MODEL | jev-latest |
| log_level: | TYPESAFE_LOG_LEVEL | warn |
You can also override settings for an individual request. For example, a particularly slow classification can use a longer timeout without changing the client’s default behavior.
Retries, timeouts, and rate limits
AI API calls can fail for temporary reasons. The Ruby SDK retries HTTP 408, 429, and 5xx responses, as well as connection failures and timeouts. It uses capped exponential backoff with jitter and honors Retry-After and retry-after-ms headers up to a cap.
Retries can be configured when creating the client or disabled for a specific call:
client.system_one(
state: "...",
questions: { ... },
timeout: 30,
retry_policy: { max_retries: 0 },
)
This is useful when the surrounding job system already owns retry behavior. For example, a Rails background job might disable SDK-level retries and let Sidekiq or another job runner decide when to retry the complete operation.
Predictable error handling
The SDK exposes typed errors instead of forcing every caller to interpret a generic exception:
begin
client.system_one(state: "...", questions: { ... })
rescue Typesafe::SDK::RateLimitError => e
retry_after(e.retry_after_ms)
rescue Typesafe::SDK::APIError => e
warn "API error #{e.status} for request #{e.request_id}"
end
The hierarchy includes errors for bad requests, authentication, permission errors, missing resources, validation failures, rate limits, and server errors. It also distinguishes API connection failures, timeouts, and caller cancellation.
That distinction gives a Ruby application a better chance to recover correctly. A rate limit may call for delayed retrying. An authentication error should usually page an operator or fail fast. A timeout may be safe to retry, while a malformed request needs a code change.
Using the SDK with Rails
The gem is framework-agnostic, but it works naturally with Rails. A common setup is to create one configured client in an initializer:
# config/initializers/typesafe.rb
TYPESAFE = Typesafe::SDK::Client.new(log_level: :info)
Then an application service or model concern can use that client for a focused task:
module TypesafeClassifiable
def classify(text)
TYPESAFE.system_one(
state: text,
questions: {
category: Typesafe::SDK.choice("Category?", {
billing: nil,
technical: nil,
other: nil,
}),
},
).answers["category"].choice
end
end
For production systems, I would normally put this call behind a service object or background job, add application-level observability, and persist the original input and model response when the result affects a business decision.
Logging and model discovery
The default logger writes to $stderr with a [typesafe-sdk-ruby] prefix. info logs request summaries, while debug includes headers with credentials redacted and request bodies.
Rails applications can pass an existing logger:
client = Typesafe::SDK::Client.new(
logger: Rails.logger,
log_level: :info,
)
The client also exposes model listing, which can be useful when building an admin screen or checking which models are available to an environment:
client.models.list.each do |model|
puts "#{model.name}: #{model.description}"
end
When should you use this SDK?
This Ruby SDK is a good fit when you want to add structured AI decisions to a Ruby or Rails application without scattering raw HTTP calls and response parsing throughout the codebase.
Typical use cases include:
- Routing customer-support tickets
- Categorizing documents or inbound messages
- Scoring urgency, quality, or risk
- Building AI-assisted workflows with explicit answer types
- Experimenting with TypeSafe AI from a Ruby codebase
It is still an unofficial community gem, so teams should review the repository, run the test suite, pin versions, and verify behavior against their own production requirements before adopting it for critical workloads.
Contributing to the project
The project is open source under the MIT License. Contributions, bug reports, and pull requests are welcome on GitHub.
If you work primarily in Ruby and want to use TypeSafe AI without giving up Ruby’s readable object-oriented interfaces, typesafe-sdk-ruby is a small but useful bridge between the two ecosystems.
Final thoughts
The goal of typesafe-sdk-ruby is straightforward: make TypeSafe AI feel like a natural dependency in a Ruby application. Typed questions make intent visible. Typed answers reduce parsing. Retries and timeouts make network behavior configurable. Typed errors give the rest of the application something meaningful to handle.
If you are building AI features in Rails or Ruby, you can explore the gem on GitHub, try the quickstart, and adapt the question types to your own workflow. I hope it gives Ruby developers a useful starting point while the ecosystem around TypeSafe AI continues to grow.
Related Reading
- Built-in Authentication in Rails
- Vibe Coding and AI-Driven Development
- The Ultimate Guide to AI Integration for Modern Applications
Sources
Related posts
Keep reading in the same neighborhood.
Ukrainian Localization of the Ruby Website
How I helped ship a Ukrainian version of ruby-lang.org, what the contribution process looked like, and why the locale details mattered.
Built-In Authentication in Rails 8: Deep Dive and Comparison
A technical look at Rails 8 authentication, how it compares with Devise, and where the native approach fits well or falls short.
Building Your Personal Brand on GitHub: A Practical Guide
Your GitHub profile is more than a code repository—it's your professional portfolio. Learn how to leverage GitHub's features to build a personal brand that attracts opportunities.