gigachat-ruby
Ruby client for Sber's GigaChat REST API. It covers chat completions (v2 and v1) with streaming, embeddings, files, batches, token counting, AI-text detection and function validation.
- Plain Ruby (4.0+). No Rails required.
- Automatic OAuth token management, retries with backoff, and typed errors.
- Works out of the box with GigaChat's TLS certificates (see TLS).
Installation
# Gemfile
gem "gigachat-ruby"require "gigachat"Quick start
client = GigaChat::Client.new(credentials: ENV["GIGACHAT_CREDENTIALS"], model: "GigaChat-2-Max")
completion = client.chat.create(messages: [{ role: "user", content: "Привет! Расскажи о себе." }])
puts completion.textcredentials is the base64 Authorization key from your GigaChat Studio project. The client exchanges it for
a 30-minute access token, caches the token, and refreshes it before it expires.
Configuration
Options are resolved in this order: keyword arguments, then GigaChat.configure, then GIGACHAT_* environment
variables, then defaults.
GigaChat.configure do |c|
c.scope = "GIGACHAT_API_CORP"
c.model = "GigaChat-2-Max"
end
client = GigaChat::Client.new # picks up the global config and GIGACHAT_* variables| Option | Env var | Default |
|---|---|---|
credentials |
GIGACHAT_CREDENTIALS |
— |
scope |
GIGACHAT_SCOPE |
GIGACHAT_API_PERS (also GIGACHAT_API_B2B, GIGACHAT_API_CORP) |
access_token |
GIGACHAT_ACCESS_TOKEN |
— (a pre-obtained token) |
user, password
|
GIGACHAT_USER, GIGACHAT_PASSWORD
|
— (the /token flow; needs a base_url that serves it) |
base_url |
GIGACHAT_BASE_URL |
https://api.giga.chat/v1 |
auth_url |
GIGACHAT_AUTH_URL |
https://ngw.devices.sberbank.ru:9443/api/v2/oauth |
model |
GIGACHAT_MODEL |
— (required for chat and token counting) |
timeout / open_timeout
|
GIGACHAT_TIMEOUT / — |
60 / 10 seconds |
max_retries |
GIGACHAT_MAX_RETRIES |
2 |
verify_ssl_certs |
GIGACHAT_VERIFY_SSL_CERTS |
true |
bundled_ca |
— | true |
ca_bundle_file |
GIGACHAT_CA_BUNDLE_FILE |
— |
cert_file, key_file, key_file_password
|
same names, GIGACHAT_ prefix |
— (mTLS) |
client_id, session_id
|
— | — (sent as X-Client-ID / X-Session-ID) |
logger |
— | — |
The environment variable names match the official Python SDK, so one .env file works for both.
TLS
GigaChat's certificates chain to the Russian Trusted Root CA (Ministry of Digital Development). Ruby's default
OpenSSL store doesn't include it, so plain HTTPS calls fail with certificate verify failed.
This gem ships that root certificate (valid until 2032-02-27; its SHA-256 fingerprint is pinned in the test suite). It adds the certificate to a per-client certificate store, next to your system roots, so:
- verification stays on;
- nothing changes for other HTTPS connections in your process.
Options:
-
bundled_ca: falseuses only the system store, for example when your OS image already trusts the root. -
ca_bundle_file: "/path/to/ca.pem"adds more trusted CAs; they are appended, never replacing the defaults. -
verify_ssl_certs: falsedisables verification. Use it only for debugging; the client logs a warning.
Chat (v2)
completion = client.chat.create(
messages: [
{ role: "system", content: "Ты — профессиональный переводчик на английский язык." },
{ role: "user", content: "GigaChat — это сервис для диалога с пользователем." }
],
model_options: { temperature: 0.3 }
)
completion.text # assistant text
completion.usage.total_tokens
completion.finish_reason # "stop", "length", "function_call", ...
completion.request_id # x-request-id, useful for support ticketscontent: "text" is shorthand for v2 content parts ([{ text: "text" }]). Any other keyword (tools,
tool_config, model_options, user_info, ranker_options, …) is sent to the API as is.
Keep the history by passing previous messages back, including response objects:
history = [{ role: "user", content: "Привет!" }]
reply = client.chat.create(messages: history)
history += [reply.message, { role: "user", content: "Расскажи анекдот" }]
client.chat.create(messages: history)Streaming
# A block streams events and returns the accumulated completion.
final = client.chat.stream(messages: [{ role: "user", content: "Напиши стих" }]) do |event|
print event.text if event.delta?
end
final.usage
# Without a block you get a lazy, single-use stream.
stream = client.chat.stream(messages: [{ role: "user", content: "Напиши стих" }])
stream.text.each { print it } # text deltas only
stream.response # accumulated completionStreams run over HTTP/2 (through httpx): GigaChat delivers events as they are generated only over HTTP/2, while over HTTP/1.1 the whole answer arrives at once. Breaking out of the block (or raising from it) aborts the request and closes the connection, so GigaChat stops generating. Proxies from HTTP(S)_PROXY are not applied to streams.
Function calling
weather = {
name: "weather_forecast",
description: "Weather forecast for a city",
parameters: { type: "object", properties: { location: { type: "string" } }, required: ["location"] }
}
tools = [{ functions: { specifications: [weather] } }]
messages = [{ role: "user", content: "Какая погода в Туле?" }]
first = client.chat.create(messages:, tools:)
if (call = first.function_call)
result = { temperature: 12 } # call your code with call.arguments (a Hash)
messages += [
first.message, # carries tool_state_id, which ties the result to this call
{ role: "tool", content: [{ function_result: { name: call.name, result: JSON.generate(result) } }] }
]
puts client.chat.create(messages:, tools:).text
endPass the assistant message back unchanged: it carries the tool_state_id the API returned (also readable as
first.message.tools_state_id).
Structured output
client.chat.create(
messages: [{ role: "user", content: "27 октября 2023 года у меня родился сын" }],
model_options: {
response_format: {
type: "json_schema",
schema: { type: "object", properties: { date: { type: "string" } }, required: ["date"] },
strict: true
}
}
)Chat (v1)
The original /chat/completions API is fully supported:
client.chat.v1.create(messages: [{ role: "user", content: "Привет!" }]).text
client.chat.v1.stream(messages: [{ role: "user", content: "Привет!" }]) { |chunk| print chunk.text }Embeddings
client.embeddings.create(input: ["Первый текст", "Второй текст"]).vectors # model defaults to "Embeddings"
client.embeddings.create(input: "Текст", model: "EmbeddingsGigaR")Models
client.models.list.map(&:id)
client.models.retrieve("GigaChat-2-Max")Files
file = client.files.upload("report.pdf") # a path or Pathname
file = client.files.upload(io, filename: "notes.txt") # any IO; filename needed when the IO has no path
client.chat.create(messages: [{ role: "user", content: [{ text: "Summarize", files: [{ id: file.id }] }] }])
client.files.list.map(&:filename)
client.files.retrieve(file.id)
client.files.content(image_id) # binary String, e.g. a generated image
client.files.delete(file.id).deleted?Token counting and balance
client.tokens_count(input: ["Я к вам пишу — чего же боле?"]).map(&:tokens)
client.balance.map { [it.usage, it.value] } # prepaid packages only (403 on pay-as-you-go)AI-text detection
client.ai_check(input: text, model: "GigaCheckClassification").category # "ai", "human" or "mixed"Function validation
result = client.functions.validate(weather)
result.valid?
result.warnings.map(&:description)Batches
Batches are available on pay-as-you-go (GIGACHAT_API_CORP).
requests = [
{ id: "1", request: { model: "GigaChat-2", messages: [{ role: "user", content: "Привет" }] } }
]
batch = client.batches.create(requests, method: :chat_completions) # or method: :embedder
batch = client.batches.retrieve(batch.id)
batch.batch_method # "chat_completions"
client.batches.results(batch) if batch.completed? # parsed JSONL linesThe API's method field is exposed as batch_method, because a method reader would shadow Ruby's
Object#method. The raw value is also available as batch[:method].
Errors
| Status | Error |
|---|---|
| 400 | GigaChat::BadRequestError |
| 401 | GigaChat::AuthenticationError |
| 402 |
GigaChat::PaymentRequiredError (token balance exhausted) |
| 403 | GigaChat::PermissionDeniedError |
| 404 | GigaChat::NotFoundError |
| 413 | GigaChat::RequestEntityTooLargeError |
| 422 | GigaChat::UnprocessableEntityError |
| 429 |
GigaChat::RateLimitError (#retry_after) |
| 5xx | GigaChat::ServerError |
| network |
GigaChat::APIConnectionError, GigaChat::APITimeoutError
|
Every GigaChat::APIError carries #status, #body, #headers and #request_id. All errors inherit from
GigaChat::Error.
Retries and timeouts
Retries apply to statuses 429, 500, 502, 503 and 504, to connection failures, and to timeouts on GET requests. There are 2 retries by default:
- the delay is exponential with jitter, capped at 8 seconds;
-
Retry-Afterwins over the default delay, capped at 60 seconds; - streams are retried only before the first event arrives.
You can override this per call:
client.chat.create(messages:, request_options: { timeout: 300, max_retries: 0, headers: { "X-Client-ID" => "u-1" } })
client.with_options(session_id: "chat-42").chat.create(messages:) # a copy that reuses the access tokenLogging
client = GigaChat::Client.new(logger: Logger.new($stdout))
# GigaChat: POST https://api.giga.chat/v2/chat/completions 200 812ms req=…The log never contains request or response bodies, tokens or credentials.
Low-level requests
client.request(method: :get, path: "models") # auth, retries and error mapping includedDevelopment
bundle install
bundle exec rake # unit tests + RuboCop
GIGACHAT_CREDENTIALS=… GIGACHAT_SCOPE=GIGACHAT_API_PERS bundle exec rake test:live # real API, opt-inLicense
MIT. See LICENSE.txt.