Project

hinow-ai

0.0
The project is in a healthy, maintained state
Ruby client library for HINOW AI - Access LLMs, image generation, TTS, STT, video generation, and embeddings.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

 Project Readme

HINOW Ruby SDK

Official Ruby SDK for the HINOW AI API.

The API speaks the OpenAI protocol, so the shape of these calls is the one you already know.

Requirements

  • Ruby 3.0 or newer

Installation

gem install hinow-ai

Or in a Gemfile:

gem "hinow-ai"

The gem is hinow-ai, and what you require is hinow:

require "hinow"

Keep the key in the environment and the client finds it on its own:

export HINOW_API_KEY="hi_your_key_here"

First call

require "hinow"

client = Hinow::Client.new

response = client.chat.completions.create(
  model: "hinow/himax",
  messages: [{ role: "user", content: "Explain what an API is in one paragraph." }]
)

puts response["choices"][0]["message"]["content"]

Responses are plain hashes with string keys, so dig works throughout.

The hinow/ prefix is part of the model name. Sending himax instead of hinow/himax answers 404 model_not_found.

For scripts, there is also a module-level client configured once:

Hinow.configure { |c| c.api_key = ENV["HINOW_API_KEY"] }
Hinow.client.chat.completions.create(model: "hinow/himax", messages: [...])

Streaming

create_stream yields each chunk as it arrives. Each one carries delta — the new fragment — not the answer so far.

client.chat.completions.create_stream(
  model: "hinow/hinova",
  messages: [{ role: "user", content: "Write a haiku about the sea." }]
) do |chunk|
  print chunk.dig("choices", 0, "delta", "content")
end

Without a block it returns an Enumerator.

Web search

Answers on the spot, US$ 0.005 per call.

result = client.tools.search("best beaches in northeast Brazil", country: "br", lang: "pt-br")

result["results"].each do |hit|
  puts "#{hit['position']}. #{hit['title']}"
  puts "   #{hit['url']}"
end

Nine types are available. What each result carries depends on the type:

type: Each result carries
search, scholar, patents position, title, url, snippet
news the above plus source, date, image_url
images link, image_url, thumbnail_url, width, height
videos channel, duration, date, thumbnail_url
places address, category, phone, website, rating, coordinates
shopping price, delivery, rating, source
autocomplete suggestions, a list of strings — there is no results

Website contacts

This one crawls, so it runs as a job.

job = client.tools.website_contacts_and_wait(
  ["https://example.com"],
  max_depth: 2,
  max_links_per_page: 10
)

job["result"]["items"].each do |contact|
  puts "#{contact['type']}: #{contact['value']} (#{contact['sourceUrl']})"
end

To drive the loop yourself, use website_contacts and follow it with tools.jobs.retrieve(job["job_id"]). Mind the field name: it is job_id, not id.

Agents

Assistants, threads and runs, in the OpenAI shape. The model decides when to call your functions; the run stops, you execute, you hand the result back.

assistant = client.beta.assistants.create(
  model: "hinow/himax",
  name: "Support",
  instructions: "Check the order before stating any status.",
  tools: [{
    type: "function",
    function: {
      name: "get_order",
      description: "Look up an order by code.",
      parameters: {
        type: "object",
        properties: { code: { type: "string" } },
        required: ["code"]
      }
    }
  }]
)

thread = client.beta.threads.create
client.beta.threads.messages.create(thread["id"], "Has order A-1002 arrived?")

run = client.beta.threads.runs.create_and_poll(thread["id"], assistant_id: assistant["id"])

while run["status"] == "requires_action"
  outputs = run["required_action"]["submit_tool_outputs"]["tool_calls"].map do |call|
    args = JSON.parse(call["function"]["arguments"])
    { 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

messages = client.beta.threads.messages.list(thread["id"], limit: 1, order: "desc")
puts messages["data"][0]["content"][0]["text"]["value"]

requires_action is not an error — it is the run handing control back to you. That is why poll returns in that state instead of spinning.

Documents and semantic search

file = client.files.create("returns-policy.txt")
store = client.vector_stores.create(name: "Support base")
client.vector_stores.files.create(store["id"], file["id"])

# Indexing is asynchronous. Searching too early returns nothing, with no error.
client.vector_stores.files.poll(store["id"], file["id"])

hits = client.rag.search("how many days do I have to return an item?",
                         rag_id: store["id"], top_k: 3)

hits["results"].each { |hit| puts "#{hit['score'].round(2)}  #{hit['source']}" }

The filter is called rag_id. Passing vector_store_id raises no error — the search just runs across every document on the account instead of the base you meant.

Errors

Each failure has its own class, so you can rescue by type instead of matching on message text.

begin
  client.chat.completions.create(model: "hinow/himax", messages: messages)
rescue Hinow::AuthenticationError
  # 401 — check HINOW_API_KEY
rescue Hinow::NotFoundError
  # 404 — usually a model name missing its `hinow/` prefix
rescue Hinow::RateLimitError
  # 429 — already retried; back off further
rescue Hinow::Error => e
  puts "#{e.status_code} #{e.message}"
end

Hinow::ConnectionError covers the case where the request never reached the API, so nothing was charged.

What the client exposes

Method For
chat.completions Conversation, streaming, function calling, JSON mode
embeddings Vectors for semantic search
images · audio · video Generation
models Catalogue and capabilities
tools Web search and website contacts
files Document upload
vector_stores Searchable knowledge bases
rag Semantic search over your documents
beta.assistants · beta.threads Server-side agents
get_balance Account credit

Configuration

client = Hinow::Client.new(
  api_key: ENV["HINOW_API_KEY"],   # or leave it out and use the variable
  base_url: "https://api.hinow.ai", # or HINOW_BASE_URL
  timeout: 120,                     # seconds
  max_retries: 2                    # rate limits and 5xx
)

Upgrading from 1.x

Up to 1.0.1 the SDK packed temperature, max_tokens, top_p and repetition_penalty into a parameters object before sending, with the numbers converted to strings. The API accepts that object and ignores it, so those settings never took effect — max_tokens: 10 still returned the whole answer. From 2.0 they go at the root, where the API reads them.

Two more things changed:

  • Errors are typed. Hinow::Error is still the base class, so existing rescue Hinow::Error keeps working.
  • Image, video and speech results come back in the OpenAI shape: response["data"][0]["url"] instead of response["data"]["urls"][0].

License

MIT