Ir para o conteúdo

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.

Instalação

terminalbash
gem install hinow-ai

A 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.

terminalbash
export HINOW_API_KEY="hi_sua_chave_aqui"

A primeira chamada

primeira_chamada.rbruby
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").

Num app Rails

Para não construir um cliente a cada requisição, configure uma vez num inicializador e use Hinow.client onde precisar.

config/initializers/hinow.rbruby
require "hinow"

Hinow.configure do |config|
  config.api_key = ENV.fetch("HINOW_API_KEY")
  config.timeout = 120
end

Mostrando a resposta enquanto ela chega

create_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.

streaming.rbruby
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ê.

Busca na web

Responde na hora, sem job para acompanhar, a US$ 0,005 por chamada. country: e lang: orientam os resultados.

busca_web.rbruby
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, patentsposition, title, url, snippet
newsos acima mais source, date, image_url
imageslink, image_url, thumbnail_url, width, height
videoschannel, duration, date, thumbnail_url
placesaddress, category, phone, website, rating, coordenadas
shoppingprice, delivery, rating, source
autocompletesuggestions, um array de strings — não tem results

Contatos de um site

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.

contatos_site.rbruby
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
end

Repetir 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.

Agentes

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.

agente.rbruby
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.

Documentos e busca semântica

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.

conhecimento.rbruby
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]}…"
end

O 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.

Erros

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.

erros.rbruby
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
ClasseQuando acontece
Hinow::AuthenticationError401 — chave ausente, inválida ou revogada
Hinow::PermissionError403 — a chave não tem acesso a esse recurso
Hinow::NotFoundError404 — modelo, arquivo ou assistente inexistente
Hinow::InvalidRequestError400 ou 422 — falta um campo ou um valor está fora da faixa
Hinow::RateLimitError429 — limite atingido ou saldo esgotado
Hinow::ServerError5xx — a falha é do lado da API
Hinow::ConnectionErrora requisição não chegou; nada foi cobrado
Hinow::TimeoutErrorum job ou run não terminou no tempo dado
modelos.rbruby
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(', ')}"

Tudo que o cliente expõe

MétodoPara quê
chat.completionsConversa, streaming, chamada de funções, modo JSON
embeddingsVetores para busca semântica
images · audio · videoGeração
modelsCatálogo e recursos de cada modelo
toolsBusca na web e contatos de site
filesUpload de documentos
vector_storesBases de conhecimento pesquisáveis
ragBusca semântica nos seus documentos
beta.assistants · beta.threadsAgentes executados no servidor
get_balanceSaldo da conta

Configuração

cliente.rbruby
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
)

Vindo da versão 1.x

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.

Esta página foi útil?