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-aiOr 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")
endWithout 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']}"
endNine 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']})"
endTo 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}"
endHinow::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::Erroris still the base class, so existingrescue Hinow::Errorkeeps working. - Image, video and speech results come back in the OpenAI shape:
response["data"][0]["url"]instead ofresponse["data"]["urls"][0].
License
MIT