SDK Ruby
Instale a gem oficial do HiNow e use chat, streaming, busca na web, agentes e busca semântica em Ruby e Rails.
Atualizado em 09 de ago. de 2026
O SDK oficial em Ruby para a API do HiNow. Roda em Ruby 3.0 ou mais novo e usa Faraday, então se encaixa em qualquer app Rails sem trazer um stack HTTP diferente do seu.
A API fala o protocolo da OpenAI, e o SDK segue o mesmo formato. Se você já integrou com a OpenAI, o desenho das chamadas é o que você conhece.
gem install hinow-aigem "hinow-ai"bundle add hinow-aiA gem é hinow-ai, o require é hinow
São nomes diferentes de propósito: gem install hinow-ai instala, e require "hinow" carrega. Pedir gem install hinow instala outra coisa.
Guarde a chave em HINOW_API_KEY e o SDK a encontra sozinho. Passar api_key: no construtor também funciona, mas evite deixar o valor no código.
export HINOW_API_KEY="hi_sua_chave_aqui"require "hinow"
# With no arguments, the client reads the key from HINOW_API_KEY.
client = Hinow::Client.new
answer = client.chat.completions.create(
model: "hinow/himax",
messages: [
{ role: "system", content: "You answer in English." },
{ role: "user", content: "What is an API? Answer in one paragraph." }
]
)
puts answer["choices"][0]["message"]["content"]
# What it cost, in tokens.
uso = answer["usage"]
puts "\ninput: #{uso['prompt_tokens']} · output: #{uso['completion_tokens']}"O prefixo hinow/ faz parte do nome do modelo
Mandar himax em vez de hinow/himax devolve 404 model_not_found, e a mensagem não deixa claro que faltou o prefixo. Vale para todos: hinow/himax, hinow/hinova, hinow/higenesis.
As respostas são hashes com chaves em texto, então dig funciona do começo ao fim: resposta.dig("choices", 0, "message", "content").
Para não construir um cliente a cada requisição, configure uma vez num inicializador e use Hinow.client onde precisar.
require "hinow"
Hinow.configure do |config|
config.api_key = ENV.fetch("HINOW_API_KEY")
config.timeout = 120
endclass Atendimento
def responder(pergunta)
resposta = Hinow.client.chat.completions.create(
model: "hinow/himax",
messages: [{ role: "user", content: pergunta }]
)
resposta.dig("choices", 0, "message", "content")
end
endcreate_stream entrega cada pedaço ao bloco assim que ele chega. Numa tela de conversa isso muda a percepção de velocidade mais do que trocar de modelo. Sem bloco, devolve um Enumerator.
require "hinow"
client = Hinow::Client.new
client.chat.completions.create_stream(
model: "hinow/hinova",
messages: [{ role: "user", content: "Write three lines about the sea at dawn." }]
) do |chunk|
# Note the "delta": each chunk carries the new fragment, not the whole answer.
print chunk.dig("choices", 0, "delta", "content")
$stdout.flush
end
putsÉ delta, não message
Na resposta completa o texto vem em message.content. No streaming, cada pedaço traz apenas o trecho novo, em delta.content — juntar tudo é com você.
Responde na hora, sem job para acompanhar, a US$ 0,005 por chamada. country: e lang: orientam os resultados.
require "hinow"
client = Hinow::Client.new
# Answers on the spot: there is no job to follow.
search = client.tools.search("best beaches in northeast Brazil", country: "br", lang: "pt-br")
search["results"].each do |item|
puts "#{item['position']}. #{item['title']}"
puts " #{item['url']}"
end
puts "\n#{search['results'].size} results · US$ #{search['cost']}"type: | O que vem em cada resultado |
|---|---|
search, scholar, patents | position, title, url, snippet |
news | os acima mais source, date, image_url |
images | link, image_url, thumbnail_url, width, height |
videos | channel, duration, date, thumbnail_url |
places | address, category, phone, website, rating, coordenadas |
shopping | price, delivery, rating, source |
autocomplete | suggestions, um array de strings — não tem results |
Varre um ou mais sites atrás de e-mails, telefones e perfis sociais, e cada achado vem com a página exata onde apareceu. Como a varredura leva tempo, esta roda como job.
require "hinow"
client = Hinow::Client.new
# The crawl takes a while, so it runs as a job. This method waits for you.
job = client.tools.website_contacts_and_wait(
["https://www.teclia.com"],
max_depth: 2,
max_links_per_page: 10
)
puts "status: #{job['status']} · US$ #{job['cost']}#{job['cached'] ? ' (from cache)' : ''}"
puts
# Each item carries the type, the value and the exact page it was found on.
job["result"]["items"].each do |item|
case item["type"]
when "email", "phone"
puts "#{item['type'].ljust(9)}#{item['value']}"
puts "#{' '.ljust(9)}em #{item['sourceUrl']}"
else
puts "#{item['platform'].to_s.ljust(9)}#{item['url']}"
end
endRepetir uma execução idêntica não cobra de novo
O job volta com cached verdadeiro quando o resultado veio do cache. Se quiser controlar o ciclo você mesmo, use website_contacts e acompanhe com tools.jobs.retrieve(job["job_id"]) — repare que o campo é job_id, não id.
Valide o que vier antes de usar
A varredura devolve o que encontrou no HTML da página, sem julgar. Endereços truncados ou repetidos aparecem — trate a lista como matéria-prima e valide antes de gravar no seu banco.
Assistentes, threads e runs no mesmo formato da API da OpenAI. Você declara as funções, o modelo decide quando chamá-las: o run para, você executa e devolve o resultado.
require "hinow"
require "json"
client = Hinow::Client.new
# Your real function: a hash here, just for the example.
ORDERS = {
"A-1001" => { status: "delivered", delivered: "2026-08-02" },
"A-1002" => { status: "in transit", estimated: "2026-08-12" }
}.freeze
def get_order(code)
ORDERS[code] || { error: "order not found" }
end
# 1. The assistant: model, instructions and the functions it may call.
assistant = client.beta.assistants.create(
model: "hinow/himax",
name: "Support",
instructions: "You answer questions about orders. Check the tool before stating any status.",
tools: [{
type: "function",
function: {
name: "get_order",
description: "Look up an order by its code.",
parameters: {
type: "object",
properties: {
code: { type: "string", description: "Order code, e.g. A-1001" }
},
required: ["code"]
}
}
}]
)
# 2. The conversation and the question.
thread = client.beta.threads.create
client.beta.threads.messages.create(thread["id"], "Has order A-1002 arrived?")
# 3. Run it and wait.
run = client.beta.threads.runs.create_and_poll(thread["id"], assistant_id: assistant["id"])
# 4. While the model asks for functions, run them and hand the result back.
while run["status"] == "requires_action"
outputs = run["required_action"]["submit_tool_outputs"]["tool_calls"].map do |call|
args = JSON.parse(call["function"]["arguments"])
puts "-> the model called #{call['function']['name']}(#{args['code']})"
{
tool_call_id: call["id"],
output: get_order(args["code"]).to_json
}
end
client.beta.threads.runs.submit_tool_outputs(thread["id"], run["id"], outputs)
run = client.beta.threads.runs.poll(thread["id"], run["id"])
end
# 5. The final answer is the last message on the thread.
messages = client.beta.threads.messages.list(thread["id"], limit: 1, order: "desc")
puts "\n#{messages['data'][0]['content'][0]['text']['value']}"
client.beta.assistants.delete(assistant["id"])requires_action não é erro
É o run devolvendo o controle para você executar uma função. Por isso o poll retorna nesse estado em vez de continuar girando: leia o required_action, chame submit_tool_outputs e volte a acompanhar.
Suba arquivos, junte numa base e busque por significado. A indexação é assíncrona — buscar antes de terminar devolve zero resultado, sem erro, e é para isso que existe o poll.
require "hinow"
client = Hinow::Client.new
# 1. Upload the document.
file = client.files.create("returns-policy.txt")
puts "file: #{file['id']} (#{file['bytes']} bytes)"
# 2. Create the store and attach the file to it.
base = client.vector_stores.create(name: "Support base")
client.vector_stores.files.create(base["id"], file["id"])
puts "store: #{base['id']}"
# 3. Indexing is asynchronous. Searching before it finishes returns nothing
# at all, with no error, so poll waits for it.
state = client.vector_stores.files.poll(base["id"], file["id"])
puts "indexing: #{state['status']}"
# 4. Search by meaning, not by exact word.
hits = client.rag.search(
"How many days do I have to return an item?",
rag_id: base["id"],
top_k: 3
)
puts
hits["results"].each do |achado|
puts "#{achado['score'].round(2)} #{achado['source']}"
puts " #{achado['text'].tr("\n", ' ')[0, 110]}…"
endO filtro por base chama-se rag_id
Passar vector_store_id não gera erro: a busca simplesmente varre todos os documentos da conta em vez da base que você queria. É o tipo de detalhe que faz parecer que a busca está devolvendo lixo.
Cada falha vira uma classe própria, então dá para tratar por tipo em vez de comparar string de mensagem. Todas herdam de Hinow::Error, e as que vieram da API carregam status_code, error_type e o corpo da resposta.
require "hinow"
client = Hinow::Client.new
begin
client.chat.completions.create(
model: "himax", # the hinow/ prefix is missing
messages: [{ role: "user", content: "Olá" }]
)
rescue Hinow::AuthenticationError
puts "Invalid or expired key. Check HINOW_API_KEY."
rescue Hinow::NotFoundError => e
puts "Not found: #{e.message}"
rescue Hinow::InvalidRequestError => e
puts "Invalid request: #{e.message}"
rescue Hinow::RateLimitError
puts "Rate limited. The SDK already retried; wait a moment."
rescue Hinow::ConnectionError => e
# Nothing reached the API, so nothing was charged.
puts "Sem answer da API: #{e.message}"
rescue Hinow::Error => e
# Safety net: any other error the API reported.
puts "Error #{e.status_code}: #{e.message}"
end| Classe | Quando acontece |
|---|---|
Hinow::AuthenticationError | 401 — chave ausente, inválida ou revogada |
Hinow::PermissionError | 403 — a chave não tem acesso a esse recurso |
Hinow::NotFoundError | 404 — modelo, arquivo ou assistente inexistente |
Hinow::InvalidRequestError | 400 ou 422 — falta um campo ou um valor está fora da faixa |
Hinow::RateLimitError | 429 — limite atingido ou saldo esgotado |
Hinow::ServerError | 5xx — a falha é do lado da API |
Hinow::ConnectionError | a requisição não chegou; nada foi cobrado |
Hinow::TimeoutError | um job ou run não terminou no tempo dado |
require "hinow"
client = Hinow::Client.new
# Account credit, in US dollars.
balance = client.get_balance
puts "balance: US$ #{format('%.2f', balance['balance'])}"
puts
# One specific model. The id is namespaced: hinow/himax, not himax.
model = client.models.retrieve("hinow/himax")
puts "#{model['name']} (#{model['id']})"
puts "categories: #{model['category'].join(', ')}"| Método | Para quê |
|---|---|
chat.completions | Conversa, streaming, chamada de funções, modo JSON |
embeddings | Vetores para busca semântica |
images · audio · video | Geração |
models | Catálogo e recursos de cada modelo |
tools | Busca na web e contatos de site |
files | Upload de documentos |
vector_stores | Bases de conhecimento pesquisáveis |
rag | Busca semântica nos seus documentos |
beta.assistants · beta.threads | Agentes executados no servidor |
get_balance | Saldo da conta |
client = Hinow::Client.new(
api_key: ENV["HINOW_API_KEY"], # ou deixe em branco e use a variável
base_url: "https://api.hinow.ai", # ou HINOW_BASE_URL
timeout: 120, # segundos
max_retries: 2 # repete 429 e 5xx
)Até a 1.0.1, o SDK empacotava temperature, max_tokens, top_p e repetition_penalty dentro de um objeto parameters antes de enviar, com os números convertidos em texto. A API aceita esse formato e ignora, então essas opções nunca surtiam efeito: pedir max_tokens: 10 devolvia a resposta inteira. A partir da 2.0 tudo vai no nível raiz, como a API espera.
Outras duas mudanças: os erros passaram a ser tipados, mantendo Hinow::Error como classe base para que os rescue existentes continuem valendo; e imagem, vídeo e áudio devolvem no formato da OpenAI, resposta["data"][0]["url"] no lugar de resposta["data"]["urls"][0].
Escolhendo entre HiMax, HiNova e HiGenesis
O que cada modelo faz bem, quanto custa e como escrever o prompt para cada um.

