UltrasafeAI Ruby SDK
The official Ruby client library for the UltrasafeAI API. Provides access to chat completions, vision, embeddings, reranking, image generation, speech-to-text, real-time audio streaming, vector stores, assistants, threads, and more.
Requires Ruby >= 3.0.
Base URL: https://api.us.tech/v1
Installation
gem install ultrasafeai
Or in your Gemfile:
gem "ultrasafeai"
Client Setup
The client reads ULTRASAFEAI_API_KEY from the environment automatically if api_key: is not passed.
require "ultrasafeai"
client = Ultrasafeai::Client.new(api_key: ENV["ULTRASAFEAI_API_KEY"])
Options:
| Keyword | Description |
|---|---|
api_key: |
Your UltrasafeAI API key |
base_url: |
Override the base URL |
timeout: |
Request timeout in seconds (default: 60) |
max_retries: |
Max retry attempts (default: 2) |
Chat Completions
Non-Streaming
Method: client.chat.completions.create(**params)
Endpoint: POST /chat/completions
require "ultrasafeai"
client = Ultrasafeai::Client.new(api_key: ENV["ULTRASAFEAI_API_KEY"])
completion = client.chat.completions.create(
model: "usf-mini",
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Hello!" }
]
)
puts completion.choices.first..content
Parameters:
| Key | Type | Required | Description | |
|---|---|---|---|---|
model: |
String |
Yes | Model ID (e.g. "usf-mini") |
|
messages: |
Array<Hash> |
Yes | Conversation history. Roles: "system", "user", "assistant", "tool" |
|
tools: |
Array<Hash> |
No | Function or custom tools the model may call | |
tool_choice: |
`String \ | Hash` | No | "none", "auto", "required", or a specific tool |
parallel_tool_calls: |
Boolean |
No | Allow parallel tool calls (default: true) |
|
reasoning_effort: |
String |
No | Controls reasoning depth: "none", "low", "medium", "high" (default: "none") |
|
web_search: |
Boolean |
No | Enable web search (default: false) |
|
response_format: |
Hash |
No | {type: "text"}, {type: "json_object"}, or {type: "json_schema", json_schema: {...}} |
|
max_tokens: |
Integer |
No | Max tokens to generate | |
temperature: |
Float |
No | Sampling temperature 0–2 | |
top_p: |
Float |
No | Nucleus sampling probability mass | |
n: |
Integer |
No | Number of completions to generate | |
stop: |
`String \ | Array |
No | Stop sequences (up to 4) |
presence_penalty: |
Float |
No | Penalty for repeated tokens (-2.0 to 2.0) | |
frequency_penalty: |
Float |
No | Frequency-based penalty (-2.0 to 2.0) | |
seed: |
Integer |
No | Seed for deterministic sampling | |
store: |
Boolean |
No | Store conversation for retrieval | |
conversation_id: |
String |
No | Continue an existing stored conversation | |
user: |
String |
No | Stable end-user identifier |
Response: Ultrasafeai::ChatCompletion
completion.id # "chatcmpl-abc123"
completion.object # "chat.completion"
completion.created # Integer unix timestamp
completion.model # "usf-mini"
completion.conversation_id # present when store: true
completion.choices.first..role # "assistant"
completion.choices.first..content # "Hello! How can I help you?"
completion.choices.first..tool_calls # non-nil when finish_reason="tool_calls"
completion.choices.first.finish_reason # "stop" | "length" | "tool_calls" | "content_filter"
completion.usage.prompt_tokens # 12
completion.usage.completion_tokens # 10
completion.usage.total_tokens # 22
Streaming
Method: client.chat.completions.create(stream: true, ...) { |chunk| ... }
Endpoint: POST /chat/completions (with stream: true)
client.chat.completions.create(
model: "usf-mini",
stream: true,
messages: [{ role: "user", content: "Tell me a joke" }]
) do |chunk|
content = chunk.choices&.first&.delta&.content
print content if content
end
puts
Response: Yields Ultrasafeai::ChatCompletionChunk per iteration.
chunk.id # "chatcmpl-abc123"
chunk.object # "chat.completion.chunk"
chunk.choices.first.delta.role # "assistant" — only on first chunk
chunk.choices.first.delta.content # incremental text; concatenate across chunks
chunk.choices.first.delta.reasoning_content # chain-of-thought when available
chunk.choices.first.delta.tool_calls # incremental tool call data
chunk.choices.first.finish_reason # non-nil only on the final chunk
Vision
Vision uses the same create method. Pass an array of content part hashes instead of a plain string.
Non-Streaming
require "base64"
image_data = File.binread("photo.jpg")
b64 = Base64.strict_encode64(image_data)
completion = client.chat.completions.create(
model: "usf-mini-vision",
messages: [{
role: "user",
content: [
{ type: "text", text: "What is in this image?" },
{ type: "image_url", image_url: { url: "data:image/jpeg;base64,#{b64}" } }
]
}]
)
puts completion.choices.first..content
URL image:
completion = client.chat.completions.create(
model: "usf-mini-vision",
messages: [{
role: "user",
content: [
{ type: "text", text: "What is in this image?" },
{ type: "image_url", image_url: { url: "https://example.com/image.jpg" } }
]
}]
)
Streaming
client.chat.completions.create(
model: "usf-mini-vision",
stream: true,
messages: [{
role: "user",
content: [
{ type: "text", text: "What's in this image?" },
{ type: "image_url", image_url: { url: "https://example.com/image.jpg" } }
]
}]
) do |chunk|
content = chunk.choices&.first&.delta&.content
print content if content
end
Content part types:
type: value |
Additional keys | Description |
|---|---|---|
"text" |
text: String |
Plain text content |
"image_url" |
image_url: { url: String } |
URL or data:image/...;base64,... string |
Response: Same ChatCompletion / yielded ChatCompletionChunk as standard chat completions.
Embeddings
Method: client.embeddings.create(**params)
Endpoint: POST /embeddings
# Single string
response = client..create(
model: "usf-embed",
input: "The quick brown fox"
)
puts response.data.first..inspect # Array<Float>
# Multiple strings
response = client..create(
model: "usf-embed",
input: ["First sentence", "Second sentence"],
dimensions: 512
)
Parameters:
| Key | Type | Required | Description | |
|---|---|---|---|---|
model: |
String |
Yes | Embedding model ID (e.g. "usf-embed") |
|
input: |
`String \ | Array |
Yes | Text or token arrays to embed. Max 8192 tokens per input, 300k total |
dimensions: |
Integer |
No | Output embedding dimensions | |
encoding_format: |
String |
No | "float" (default) or "base64" |
|
user: |
String |
No | End-user identifier |
Response: Ultrasafeai::EmbeddingResponse
response.object # "list"
response.data.first.object # "embedding"
response.data.first.index # 0
response.data.first. # Array<Float>
response.model # "usf-embed"
response.usage.prompt_tokens # 8
response.usage.total_tokens # 8
Reranker
Method: client.rerank.create(**params)
Endpoint: POST /rerank
response = client.rerank.create(
model: "usf-rerank",
query: "What is machine learning?",
texts: [
"Machine learning is a subset of AI.",
"The weather is sunny today.",
"Deep learning uses neural networks."
],
top_n: 2
)
response.results.each do |result|
puts "#{result.index} #{result.relevance_score} #{result.text}"
end
Parameters:
| Key | Type | Required | Description |
|---|---|---|---|
model: |
String |
Yes | Rerank model ID (e.g. "usf-rerank") |
query: |
String |
Yes | Search query to rank documents against |
texts: |
Array<String> |
Yes | Documents to rerank |
top_n: |
Integer |
No | Number of top results to return |
Response: Ultrasafeai::RerankResponse
response.results.first.index # 0
response.results.first.relevance_score # 0.97
response.results.first.text # "Machine learning is a subset of AI."
Image Generation
Method: client.images.generate(**params)
Endpoint: POST /images/generations
response = client.images.generate(
model: "usf-mini-image",
prompt: "A futuristic city at sunset",
size: "1024x1024",
n: 1,
response_format: "url"
)
puts response.data.first.url
Parameters:
| Key | Type | Required | Description |
|---|---|---|---|
model: |
String |
Yes | Image model ID (e.g. "usf-mini-image") |
prompt: |
String |
Yes | Text description of the image to generate |
size: |
String |
No | "256x256", "512x512", "1024x1024" |
n: |
Integer |
No | Number of images to generate |
response_format: |
String |
No | "url" (default) or "b64_json" |
Response: Ultrasafeai::ImageResponse
response.created # Integer unix timestamp
response.images.first.url # "https://..." (when response_format: "url")
response.images.first.b64_json # base64 string (when response_format: "b64_json")
response.data.first.url # OpenAI-compat alias; same contents as images
Speech to Text
Method: client.audio.transcriptions.create(**params)
Endpoint: POST /audio/transcribe
File.open("audio.mp3", "rb") do |f|
response = client.audio.transcriptions.create(
file: f,
model: "usf-mini-asr",
language: "en",
response_format: "json"
)
puts response.text
end
Parameters:
| Key | Type | Required | Description |
|---|---|---|---|
file: |
IO |
Yes | Audio file (mp3, mp4, wav, flac, ogg, webm, etc.) |
model: |
String |
Yes | ASR model ID (e.g. "usf-mini-asr") |
language: |
String |
No | ISO 639-1 language code (e.g. "en", "es") |
response_format: |
String |
No | "json" (default), "text", "srt", "verbose_json", "vtt" |
Response: Ultrasafeai::TranscriptionResponse
response.text # "Hello, this is a transcription."
response.language # "en"
response.duration # Float seconds
Live ASR (WebSocket)
Live ASR uses a WebSocket-based client. Access it via client.audio.stream.
Class: Ultrasafeai::Audio::Stream::StreamClient
Endpoint: wss://api.us.tech/v1/audio/stream
Dependency: Add
gem "websocket-client-simple"to yourGemfile.
connect is synchronous — it blocks until the WebSocket handshake completes, then returns a live AudioStreamSession. Event handlers fire on a background thread.
require "ultrasafeai"
require "ultrasafeai/audio/stream/stream_client"
client = Ultrasafeai::Client.new(api_key: ENV["ULTRASAFEAI_API_KEY"])
session = client.audio.stream.connect(
Ultrasafeai::Audio::Stream::ConnectOptions.new(
model: "usf-mini-asr",
sample_rate: 16_000,
audio_format: "pcm_s16le",
enable_vad: false,
partial_results: true,
interim_min_duration_ms: 500,
full_context_retranscription: true
)
)
session.on("ready") { |event| puts "Connected — streaming audio" }
session.on("transcript") { |event| puts event["full_text"] }
session.on("close") { |code, reason| puts "closed (#{code}) #{reason}" }
session.on("ws_error") { |exc| warn "WebSocket error: #{exc}" }
session.on("parse_error") { |exc, raw| warn "bad frame: #{exc}" }
# Send PCM audio (binary String)
session.send(pcm_chunk)
# Keep the main thread alive until the server closes the connection
close_event = Queue.new
session.on("close") { close_event.push(:done) }
close_event.pop
session.close
ConnectOptions keyword arguments:
| Keyword | Type | Default | Description |
|---|---|---|---|
model: |
String |
"usf-mini-asr" |
ASR model ID |
sample_rate: |
Integer |
16000 |
Audio sample rate in Hz |
audio_format: |
String |
"pcm_s16le" |
"pcm_s16le" or "pcm_f32le" |
enable_vad: |
Boolean |
false |
Enable voice activity detection |
partial_results: |
Boolean |
true |
Emit partial results before segment is final |
interim_min_duration_ms: |
Integer |
500 |
Min audio duration (ms) before emitting interim |
full_context_retranscription: |
Boolean |
true |
Re-transcribe with full audio context for accuracy |
max_retries: |
Integer |
3 |
Max reconnect attempts on initial connection |
backoff_ms: |
Float |
500.0 |
Base backoff (ms) between retries; doubles each attempt |
Session methods:
| Method | Description | ||
|---|---|---|---|
| `on(event) { \ | *args\ | ... }` | Subscribe to a named event |
| `off(event) { \ | *args\ | ... }` | Unsubscribe a handler (by block object identity) |
send(audio) |
Send a PCM audio frame (binary String) | ||
close |
Close the session gracefully |
Session events:
| Event | Block arguments | Description | ||
|---|---|---|---|---|
"ready" |
`\ | event\ | —Hash` |
Server ready to receive audio |
"transcript" |
`\ | event\ | —Hash` |
Transcription result (partial or final) |
"speech_activity" |
`\ | event\ | —Hash` |
VAD speech start/end |
"control" |
`\ | event\ | —Hash` |
Lifecycle signal (action: "stop") |
"error" |
`\ | event\ | —Hash` |
Server-side error |
"close" |
`\ | code, reason\ | —Integer, String` |
Connection closed |
"ws_error" |
`\ | exception\ | —StandardError` |
WebSocket transport error |
"parse_error" |
`\ | exception, raw\ | —StandardError, String` |
Frame could not be decoded |
Transcript event hash:
event = ... # received in the "transcript" handler
event["type"] # "transcript" | "ready" | "speech_activity" | "control" | "error"
event["request_id"] # "req_abc"
event["is_final"] # true | false
event["full_text"] # "Hello world this is a test"
event["committed_text"] # "Hello world"
segment = event["segment"]
segment["id"] # Integer
segment["text"] # "this is a test"
segment["is_final"] # true | false
segment["start"] # Float seconds
segment["end"] # Float seconds
segment["confidence"] # Float
Vector Stores
Access: client.vector_stores
Create Vector Store
Method: client.vector_stores.create(**params)
Endpoint: POST /vector_stores
File.open("document.pdf", "rb") do |f|
store = client.vector_stores.create(
name: "My Knowledge Base",
files: [f]
)
puts store.id # "vs_abc123"
puts store.status # poll until "ready"
end
Parameters:
| Key | Type | Required | Description |
|---|---|---|---|
name: |
String |
Yes | Display name for the vector store |
files: |
Array<IO> |
No | Files to upload and index immediately |
Response: Ultrasafeai::VectorStore
store.id # "vs_abc123"
store.object # "vector_store"
store.created_at # Integer
store.name # "My Knowledge Base"
store.status # "ready"
store.file_counts.in_progress # 0
store.file_counts.completed # 1
store.file_counts.total # 1
List Vector Stores
response = client.vector_stores.list(limit: 20)
response.data.each do |store|
puts "#{store.id} #{store.name} #{store.status}"
end
Retrieve Vector Store
store = client.vector_stores.retrieve("vs_abc123")
puts store.status
Delete Vector Store
result = client.vector_stores.delete("vs_abc123")
puts result.deleted # true
Search Vector Store
Method: client.vector_stores.search(vector_store_id, **params)
Endpoint: POST /vector_stores/{vector_store_id}/search
results = client.vector_stores.search("vs_abc123", query: "What is the refund policy?")
results.data.each { |item| puts item }
File Management
Upload File to Vector Store
File.open("doc.pdf", "rb") do |f|
file = client.vector_stores.upload_file("vs_abc123", file: f)
end
List Vector Store Files
files = client.vector_stores.list_files("vs_abc123", limit: 20)
files.data.each { |f| puts f.id }
Retrieve / Delete Vector Store File
file = client.vector_stores.retrieve_file("vs_abc123", "file_xyz")
result = client.vector_stores.delete_file("vs_abc123", "file_xyz")
puts result.deleted # true
File Batches
# Create a batch of files by ID
batch = client.vector_stores.create_file_batch(
"vs_abc123",
file_ids: ["file_abc", "file_def"]
)
# Retrieve batch status
status = client.vector_stores.retrieve_file_batch("vs_abc123", batch.id)
# Cancel a running batch
client.vector_stores.cancel_file_batch("vs_abc123", batch.id)
# List files in a batch
batch_files = client.vector_stores.list_batch_files("vs_abc123", batch.id)
Assistants
Access: client.assistants
Create Assistant
assistant = client.assistants.create(
model: "usf-mini",
name: "My Assistant",
description: "A helpful customer support bot",
instructions: "You are a customer support agent. Be concise and friendly.",
tools: [{ type: "code_interpreter" }],
temperature: 0.5
)
puts assistant.id
Parameters:
| Key | Type | Required | Description |
|---|---|---|---|
model: |
String |
Yes | Model ID |
name: |
String |
No | Assistant name |
description: |
String |
No | Short description |
instructions: |
String |
No | System prompt / instructions |
tools: |
Array<Hash> |
No | Tool definitions (e.g. [{ type: "code_interpreter" }]) |
tool_resources: |
Hash |
No | Resources for tools |
metadata: |
Hash |
No | Arbitrary key-value metadata |
temperature: |
Float |
No | Sampling temperature |
top_p: |
Float |
No | Nucleus sampling |
response_format: |
String |
No | Response format |
Response: Ultrasafeai::Assistant
assistant.id # "asst_abc123"
assistant.object # "assistant"
assistant.created_at # Integer
assistant.name # "My Assistant"
assistant.model # "usf-mini"
assistant.instructions # "You are a customer support agent."
List Assistants
response = client.assistants.list(limit: 20)
response.data.each { |a| puts "#{a.id} #{a.name}" }
Retrieve Assistant
assistant = client.assistants.retrieve("asst_abc123")
puts assistant.name
Delete Assistant
result = client.assistants.delete("asst_abc123")
puts result.deleted # true
Threads
Access: client.threads
Create Thread
Method: client.threads.create(**params)
Endpoint: POST /threads
thread = client.threads.create(
messages: [{ role: "user", content: "Hello, I need help with my account." }]
)
puts thread.id # "thread_abc123"
Parameters:
| Key | Type | Required | Description |
|---|---|---|---|
messages: |
Array<Hash> |
No | Initial messages to seed the thread |
metadata: |
Hash |
No | Arbitrary key-value metadata |
Response: Ultrasafeai::Thread
thread.id # "thread_abc123"
thread.object # "thread"
thread.created_at # Integer
List Threads
response = client.threads.list(limit: 20)
response.data.each { |t| puts "#{t.id} #{t.created_at}" }
Retrieve Thread
thread = client.threads.retrieve("thread_abc123")
puts thread.id
Thread Messages
Add Message to Thread
Method: client.threads.add_message(thread_id, **params)
Endpoint: POST /threads/{thread_id}/messages
= client.threads.(
"thread_abc123",
role: "user",
content: "Can you summarize my previous question?"
)
puts .id # "msg_xyz"
puts .role # "user"
Parameters:
| Key | Type | Required | Description |
|---|---|---|---|
role: |
String |
Yes | Message role: "user" or "assistant" |
content: |
String |
Yes | Text content of the message |
attachments: |
Array<Hash> |
No | File attachments |
metadata: |
Hash |
No | Arbitrary key-value metadata |
List Messages in Thread
Method: client.threads.list_messages(thread_id, **params)
Endpoint: GET /threads/{thread_id}/messages
= client.threads.("thread_abc123", limit: 20)
.data.each { |msg| puts "#{msg.role}: #{msg.content}" }
Run Thread with Assistant
Method: client.threads.run(thread_id, **params)
Endpoint: POST /threads/{thread_id}/runs
run = client.threads.run(
"thread_abc123",
assistant_id: "asst_abc123",
model: "usf-mini",
instructions: "Be concise."
)
puts run.id # "run_abc"
puts run.status # "queued" | "in_progress" | "completed" | "failed"
Parameters:
| Key | Type | Required | Description |
|---|---|---|---|
assistant_id: |
String |
Yes | Assistant to use for this run |
model: |
String |
No | Override the assistant's model |
instructions: |
String |
No | Override the assistant's instructions |
tools: |
Array<Hash> |
No | Override the assistant's tools |
metadata: |
Hash |
No | Arbitrary key-value metadata |
Models
Access: client.models
List Models
Method: client.models.list
Endpoint: GET /models
response = client.models.list
response.data.each do |model|
puts "#{model.id} #{model.type} #{model.description}"
end
Response: Ultrasafeai::ListModelsResponse
response.object # "list"
response.data.first.id # "usf-mini"
response.data.first.object # "model"
response.data.first.name # "USF Mini"
response.data.first.type # "chat"
response.data.first.description # "Fast and efficient chat model"
response.data.first.is_active # true
response.data.first.created # Integer
response.data.first.owned_by # "ultrasafeai"
Retrieve Model
Method: client.models.retrieve(model_id)
Endpoint: GET /models/{model}
model = client.models.retrieve("usf-mini")
puts "#{model.id} #{model.is_active}"
Error Handling
require "ultrasafeai/errors"
begin
completion = client.chat.completions.create(
model: "usf-mini",
messages: [{ role: "user", content: "Hello" }]
)
rescue Ultrasafeai::UnauthorizedError => e
puts "Invalid API key: #{e.}"
rescue Ultrasafeai::BadRequestError => e
puts "Bad request: #{e.}"
rescue Ultrasafeai::PaymentRequiredError => e
puts "Insufficient credits: #{e.}"
rescue Ultrasafeai::APIError => e
puts "API error #{e.status_code}: #{e.}"
end
| Exception | HTTP Status | Description |
|---|---|---|
Ultrasafeai::UnauthorizedError |
401 | Invalid or missing API key |
Ultrasafeai::BadRequestError |
400 | Invalid request parameters |
Ultrasafeai::PaymentRequiredError |
402 | Insufficient account credits |
Ultrasafeai::RateLimitError |
429 | Rate limit exceeded |
Ultrasafeai::APIError |
any | Base class with status_code and message |
Retries
The client automatically retries on connection errors, timeouts, and 429/5xx responses with exponential backoff. Default is 2 retries.
# Disable retries
client = Ultrasafeai::Client.new(api_key: "...", max_retries: 0)
# Increase retries
client = Ultrasafeai::Client.new(api_key: "...", max_retries: 5)
# Override per request
client.chat.completions.create(
model: "usf-mini",
messages: [{ role: "user", content: "Hello" }],
request_options: { max_retries: 0 }
)
Timeouts
Requests time out after 60 seconds by default.
# Set globally
client = Ultrasafeai::Client.new(api_key: "...", timeout: 30)
# Override per request
client.chat.completions.create(
model: "usf-mini",
messages: [{ role: "user", content: "Hello" }],
request_options: { timeout: 10 }
)
License
MIT