Quickstart: Ruby
This quickstart uses Net::HTTP and JSON from the Ruby standard library. It needs Ruby 3.1 or later.
Write a small client
# syntic.rb
require "json"
require "net/http"
require "uri"
module Syntic
URL = URI("https://app.syntic.ai/v1/chat/completions")
class Error < StandardError
attr_reader :status, :code, :request_id
def initialize(status, code, request_id)
@status, @code, @request_id = status, code, request_id
super("#{status} #{code} (request #{request_id})")
end
end
def self.request(body)
req = Net::HTTP::Post.new(URL)
req["Authorization"] = "Bearer #{ENV.fetch('SYNTIC_API_KEY')}"
req["Content-Type"] = "application/json"
req.body = JSON.generate(body)
req
end
def self.complete(body)
Net::HTTP.start(URL.host, URL.port, use_ssl: true, read_timeout: 300) do |http|
res = http.request(request({ stream: false }.merge(body)))
data = JSON.parse(res.body)
raise Error.new(res.code.to_i, data["error"], res["x-syntic-request-id"]) unless res.is_a?(Net::HTTPSuccess)
data
end
end
endSend a request
# hello.rb
require_relative "syntic"
reply = Syntic.complete(
model: "Amara S1.2",
max_tokens: 300,
messages: [
{ role: "system", content: "Answer in two sentences." },
{ role: "user", content: "Why do bicycles stay upright when moving?" }
]
)
puts reply.dig("choices", 0, "message", "content")
puts "tokens: #{reply.dig('usage', 'total_tokens')}"Run it
ruby hello.rbStream the reply
read_body with a block yields the body in pieces as they arrive:
module Syntic
def self.stream(body)
Net::HTTP.start(URL.host, URL.port, use_ssl: true, read_timeout: 300) do |http|
http.request(request(body.merge(stream: true))) do |res|
unless res.is_a?(Net::HTTPSuccess)
data = JSON.parse(res.read_body)
raise Error.new(res.code.to_i, data["error"], res["x-syntic-request-id"])
end
buffer = +""
res.read_body do |piece|
buffer << piece
while (newline = buffer.index("\n"))
line = buffer.slice!(0..newline).strip
next unless line.start_with?("data: ")
payload = line.delete_prefix("data: ")
return if payload == "[DONE]"
event = JSON.parse(payload)
raise "stream failed: #{event['detail']}" if event["error"]
text = event.dig("choices", 0, "delta", "content")
yield text if text && !text.empty?
end
end
end
end
end
end
Syntic.stream(model: "Amara Quick", messages: [{ role: "user", content: "Name four famous lighthouses." }]) do |text|
print text
$stdout.flush
end
putsBreaking out of the block (or returning from it) closes the connection and stops generation.
Call a function
tools = [{
type: "function",
function: {
name: "pool_hours",
description: "Opening hours of the public swimming pool on a given date (YYYY-MM-DD).",
parameters: {
type: "object",
properties: { date: { type: "string" } },
required: ["date"]
}
}
}]
messages = [{ role: "user", content: "Is the pool open on 2026-12-26?" }]
reply = Syntic.complete(model: "Amara S1.2", tools: tools, messages: messages)
msg = reply.dig("choices", 0, "message")
if msg["tool_calls"]&.any?
messages << msg # the assistant turn, unchanged
msg["tool_calls"].each do |call|
args = JSON.parse(call.dig("function", "arguments")) rescue {}
result = { open: "10:00", close: "14:00" } # replace with a real lookup
messages << { role: "tool", tool_call_id: call["id"], content: JSON.generate(result) }
end
reply = Syntic.complete(model: "Amara S1.2", tools: tools, messages: messages)
end
puts reply.dig("choices", 0, "message", "content")If you use an OpenAI-compatible Ruby gem, point its base URI at https://app.syntic.ai/ (with the v1 API version) and pass stream: false in the parameters of non-streaming calls.
Handle errors
begin
Syntic.complete(model: "Amara S1.2", messages: [])
rescue Syntic::Error => e
case e.status
when 429, 500..599 then # retry with back-off
when 402 then # quota_exhausted: see the Handle 402 recipe
else raise
end
endSee Retries with back-off and Error codes.
Next steps
- Guides
- Chat completions reference
- Other languages: the planned official Ruby client.
Was this page helpful?