SDK Go
Instale o SDK oficial em Go e use chat, streaming, busca na web, agentes e busca semântica no HiNow.
Atualizado em 09 de ago. de 2026
O SDK oficial em Go para a API do HiNow. Só usa a biblioteca padrão — nenhuma dependência externa entra no seu go.sum por causa dele.
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.
go get github.com/hinow-ai/sdk-go/v2O caminho tem /v2 e termina em /hinow_ai
O /v2 faz parte do caminho do módulo, não é engano: o Go exige o sufixo a partir da versão 2, e go get recusa a tag sem ele. O pacote ainda fica em hinow_ai, então importar só o módulo não compila. O apelido deixa o resto do código mais curto:
import hinow "github.com/hinow-ai/sdk-go/v2/hinow_ai"
Guarde a chave em HINOW_API_KEY e passe uma string vazia para NewClient. Passar a chave direto também funciona, mas evite deixar o valor no código.
export HINOW_API_KEY="hi_sua_chave_aqui"package main
import (
"context"
"fmt"
"log"
hinow "github.com/hinow-ai/sdk-go/v2/hinow_ai"
)
func main() {
ctx := context.Background()
// An empty key makes the client read HINOW_API_KEY.
client := hinow.NewClient("")
answer, err := client.Chat.Completions().Create(ctx, &hinow.ChatCompletionRequest{
Model: "hinow/himax",
Messages: []hinow.Message{
{Role: "system", Content: "You answer in English."},
{Role: "user", Content: "What is an API? Answer in one paragraph."},
},
})
if err != nil {
log.Fatal(err)
}
// Text() gives you the string; Content holds the raw value.
fmt.Println(answer.Choices[0].Message.Text())
uso := answer.Usage
fmt.Printf("\ninput: %d · output: %d\n", uso.PromptTokens, uso.CompletionTokens)
}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.
Leia a resposta por Message.Text()
Content é um interface{}, porque uma mensagem pode carregar texto ou uma lista de partes. Text() resolve os dois casos; um type assertion para string só funciona às vezes.
Assim "não informado" e "zero" não se confundem: Temperature: 0 é um pedido legítimo de resposta determinística, e sem ponteiro o SDK não teria como distinguir isso de deixar em branco. hinow.Int e hinow.Float encurtam a escrita.
resposta, err := client.Chat.Completions().Create(ctx, &hinow.ChatCompletionRequest{
Model: "hinow/himax",
Messages: mensagens,
MaxTokens: hinow.Int(500),
Temperature: hinow.Float(0.2),
// Modo JSON: a resposta vem como um objeto válido, sem texto em volta.
ResponseFormat: &hinow.ResponseFormat{Type: "json_object"},
})CreateStream devolve dois canais: um de pedaços e um de erro. Numa tela de conversa isso muda a percepção de velocidade mais do que trocar de modelo.
package main
import (
"context"
"fmt"
"log"
hinow "github.com/hinow-ai/sdk-go/v2/hinow_ai"
)
func main() {
ctx := context.Background()
client := hinow.NewClient("")
chunks, errors := client.Chat.Completions().CreateStream(ctx, &hinow.ChatCompletionRequest{
Model: "hinow/hinova",
Messages: []hinow.Message{{Role: "user", Content: "Write three lines about the sea at dawn."}},
})
for chunk := range chunks {
// Note the Delta: each chunk carries the new fragment, not the whole answer.
if len(chunk.Choices) > 0 {
fmt.Print(chunk.Choices[0].Delta.Content)
}
}
// Always read the error channel after the loop: a failure mid-stream
// shows up here and nowhere else.
if err := <-errors; err != nil {
log.Fatal(err)
}
fmt.Println()
}Leia o canal de erro depois do laço
Se a conexão cair no meio, o laço sobre os pedaços simplesmente termina — sem aviso. O erro fica no segundo canal, e ignorá-lo transforma uma falha de rede numa resposta cortada pela metade.
Responde na hora, sem job para acompanhar, a US$ 0,005 por chamada. Os nove tipos são constantes, então o compilador não deixa você errar o nome.
package main
import (
"context"
"fmt"
"log"
hinow "github.com/hinow-ai/sdk-go/v2/hinow_ai"
)
func main() {
ctx := context.Background()
client := hinow.NewClient("")
// Answers on the spot: there is no job to follow.
search, err := client.Tools.Search(ctx, &hinow.SearchRequest{
Query: "best beaches in northeast Brazil",
Type: hinow.SearchTypeSearch,
Country: "br",
Lang: "pt-br",
})
if err != nil {
log.Fatal(err)
}
for _, item := range search.Results {
fmt.Printf("%d. %s\n %s\n", item.Position, item.Title, item.URL)
}
fmt.Printf("\n%d results · US$ %v\n", len(search.Results), search.Cost)
}| Tipo | O que vem preenchido em cada resultado |
|---|---|
SearchTypeSearch, Scholar, Patents | Position, Title, URL, Snippet |
SearchTypeNews | os acima mais Source, Date, ImageURL |
SearchTypeImages | Link, ImageURL, ThumbnailURL, Width, Height |
SearchTypeVideos | Channel, Duration, Date, ThumbnailURL |
SearchTypePlaces | Address, Category, Phone, Website, Rating, coordenadas |
SearchTypeShopping | Price, Delivery, Rating, Source |
SearchTypeAutocomplete | Suggestions; Results fica vazio |
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.
package main
import (
"context"
"fmt"
"log"
hinow "github.com/hinow-ai/sdk-go/v2/hinow_ai"
)
func main() {
ctx := context.Background()
client := hinow.NewClient("")
// The crawl takes a while, so it runs as a job. This method waits for you.
job, err := client.Tools.WebsiteContactsAndWait(ctx, &hinow.WebsiteContactsRequest{
Websites: []string{"https://www.teclia.com"},
MaxDepth: 2,
MaxLinksPerPage: 10,
}, nil)
if err != nil {
log.Fatal(err)
}
origem := ""
if job.Cached {
origem = " (from cache)"
}
fmt.Printf("status: %s · US$ %v%s\n\n", job.Status, job.Cost, origem)
// Each item carries the type, the value and the exact page it was found on.
for _, item := range job.Result.Items {
switch item.Type {
case "email", "phone":
fmt.Printf("%-9s%s\n%-9sem %s\n", item.Type, item.Value, "", item.SourceURL)
default:
fmt.Printf("%-9s%s\n", item.Platform, item.URL)
}
}
}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 WebsiteContacts e acompanhe com Tools.Jobs.Retrieve(ctx, job.JobID) — 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.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
hinow "github.com/hinow-ai/sdk-go/v2/hinow_ai"
)
// Your real function: a map here, just for the example.
func getOrder(code string) map[string]string {
orders := map[string]map[string]string{
"A-1001": {"status": "delivered", "delivered": "2026-08-02"},
"A-1002": {"status": "in transit", "estimated": "2026-08-12"},
}
if order, ok := orders[code]; ok {
return order
}
return map[string]string{"error": "order not found"}
}
func main() {
ctx := context.Background()
client := hinow.NewClient("")
// 1. The assistant: model, instructions and the functions it may call.
assistant, err := client.Beta.Assistants.Create(ctx, &hinow.AssistantRequest{
Model: "hinow/himax",
Name: "Support",
Instructions: "You answer questions about orders. Check the tool before stating any status.",
Tools: []hinow.Tool{
hinow.NewTool("get_order", "Look up an order by its code.",
map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"code": map[string]interface{}{
"type": "string",
"description": "Order code, e.g. A-1001",
},
},
"required": []string{"code"},
}),
},
})
if err != nil {
log.Fatal(err)
}
defer client.Beta.Assistants.Delete(ctx, assistant.ID)
// 2. The conversation and the question.
thread, err := client.Beta.Threads.Create(ctx)
if err != nil {
log.Fatal(err)
}
if _, err := client.Beta.Threads.Messages.Create(ctx, thread.ID, "Has order A-1002 arrived?", "user"); err != nil {
log.Fatal(err)
}
// 3. Run it and wait.
run, err := client.Beta.Threads.Runs.CreateAndPoll(ctx, thread.ID, assistant.ID)
if err != nil {
log.Fatal(err)
}
// 4. While the model asks for functions, run them and hand the result back.
for run.Status == "requires_action" {
var outputs []hinow.ToolOutput
for _, call := range run.RequiredAction.SubmitToolOutputs.ToolCalls {
var args struct {
Code string `json:"code"`
}
if err := json.Unmarshal([]byte(call.Function.Arguments), &args); err != nil {
log.Fatal(err)
}
fmt.Printf("-> the model called %s(%s)\n", call.Function.Name, args.Code)
resultado, _ := json.Marshal(getOrder(args.Code))
outputs = append(outputs, hinow.ToolOutput{ToolCallID: call.ID, Output: string(resultado)})
}
if _, err := client.Beta.Threads.Runs.SubmitToolOutputs(ctx, thread.ID, run.ID, outputs); err != nil {
log.Fatal(err)
}
if run, err = client.Beta.Threads.Runs.Poll(ctx, thread.ID, run.ID); err != nil {
log.Fatal(err)
}
}
// 5. The final answer is the last message on the thread.
messages, err := client.Beta.Threads.Messages.List(ctx, thread.ID, 1, "desc")
if err != nil {
log.Fatal(err)
}
fmt.Printf("\n%s\n", messages.Data[0].Text())
}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 RequiredAction, chame SubmitToolOutputs 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.
package main
import (
"context"
"fmt"
"log"
"strings"
hinow "github.com/hinow-ai/sdk-go/v2/hinow_ai"
)
func main() {
ctx := context.Background()
client := hinow.NewClient("")
// 1. Upload the document.
file, err := client.Files.CreateFromPath(ctx, "returns-policy.txt", "assistants")
if err != nil {
log.Fatal(err)
}
fmt.Printf("file: %s (%d bytes)\n", file.ID, file.Bytes)
// 2. Create the store and attach the file to it.
base, err := client.VectorStores.Create(ctx, &hinow.VectorStoreRequest{Name: "Support base"})
if err != nil {
log.Fatal(err)
}
if _, err := client.VectorStores.Files.Create(ctx, base.ID, file.ID); err != nil {
log.Fatal(err)
}
fmt.Printf("store: %s\n", base.ID)
// 3. Indexing is asynchronous. Searching before it finishes returns nothing
// at all, with no error, so Poll waits for it.
state, err := client.VectorStores.Files.Poll(ctx, base.ID, file.ID)
if err != nil {
log.Fatal(err)
}
fmt.Printf("indexing: %s\n\n", state.Status)
// 4. Search by meaning, not by exact word.
hits, err := client.Rag.Search(ctx, &hinow.RagSearchRequest{
Query: "How many days do I have to return an item?",
RagID: base.ID,
TopK: 3,
})
if err != nil {
log.Fatal(err)
}
for _, achado := range hits.Results {
excerpt := strings.ReplaceAll(achado.Text, "\n", " ")
if len(excerpt) > 110 {
excerpt = excerpt[:110]
}
fmt.Printf("%.2f %s\n %s…\n", achado.Score, achado.Source, excerpt)
}
}O filtro por base chama-se RagID
A API ignora vector_store_id nesse endpoint: 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 tem um tipo próprio, com uma função Is… construída sobre errors.As. Dá para decidir por tipo em vez de comparar texto de mensagem.
package main
import (
"context"
"errors"
"fmt"
hinow "github.com/hinow-ai/sdk-go/v2/hinow_ai"
)
func main() {
ctx := context.Background()
client := hinow.NewClient("")
_, err := client.Chat.Completions().Create(ctx, &hinow.ChatCompletionRequest{
Model: "himax", // the hinow/ prefix is missing
Messages: []hinow.Message{{Role: "user", Content: "Olá"}},
})
switch {
case err == nil:
fmt.Println("deu certo")
case hinow.IsAuthenticationError(err):
fmt.Println("Invalid or expired key. Check HINOW_API_KEY.")
case hinow.IsNotFoundError(err):
fmt.Println("Not found:", err)
case hinow.IsInvalidRequestError(err):
fmt.Println("Invalid request:", err)
case hinow.IsRateLimitError(err):
fmt.Println("Rate limited. The SDK already retried; wait a moment.")
case hinow.IsConnectionError(err):
// Nothing reached the API, so nothing was charged.
fmt.Println("Sem answer da API:", err)
default:
// Safety net: any other error the API reported.
var apiErr *hinow.APIError
if errors.As(err, &apiErr) {
fmt.Printf("Error %d: %s\n", apiErr.StatusCode, apiErr.Message)
}
}
}| Verificação | Quando é verdadeira |
|---|---|
IsAuthenticationError | 401 — chave ausente, inválida ou revogada |
IsPermissionError | 403 — a chave não tem acesso a esse recurso |
IsNotFoundError | 404 — modelo, arquivo ou assistente inexistente |
IsInvalidRequestError | 400 ou 422 — falta um campo ou um valor está fora da faixa |
IsRateLimitError | 429 — limite atingido |
IsInsufficientBalanceError | 402 — saldo esgotado |
IsServerError | 5xx — a falha é do lado da API |
IsConnectionError | a requisição não chegou; nada foi cobrado |
package main
import (
"context"
"fmt"
"log"
hinow "github.com/hinow-ai/sdk-go/v2/hinow_ai"
)
func main() {
ctx := context.Background()
client := hinow.NewClient("")
// Account credit, in US dollars.
balance, err := client.GetBalance(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("balance: US$ %.2f\n\n", balance.Balance)
// One specific model. The id is namespaced: hinow/himax, not himax.
model, err := client.Models.Retrieve(ctx, "hinow/himax")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s (%s)\n", model.Name, model.ID)
fmt.Printf("categories: %v\n", model.Category)
fmt.Printf("price per million tokens: input US$ %v · output US$ %v\n",
model.Cost.Input, model.Cost.Output)
}| Campo | 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, preço e recursos de cada modelo |
Tools | Busca na web e contatos de site |
Files | Upload de documentos |
VectorStores | Bases de conhecimento pesquisáveis |
Rag | Busca semântica nos seus documentos |
Beta.Assistants · Beta.Threads | Agentes executados no servidor |
GetBalance | Saldo da conta |
client := hinow.NewClient(os.Getenv("HINOW_API_KEY"),
hinow.WithBaseURL("https://api.hinow.ai"), // ou HINOW_BASE_URL
hinow.WithTimeout(120*time.Second),
hinow.WithMaxRetries(2), // repete 429 e 5xx
)Três partes do SDK devolviam algo diferente do que o código dizia:
ChatempacotavaTemperature,MaxTokenseTopPnum objetoparameters, com os números convertidos em texto. A API aceita esse formato e ignora, então pedirMaxTokens: 10devolvia a resposta inteira. Agora tudo vai no nível raiz.GetBalancelia a resposta como um objeto plano, mas o endpoint devolve{"data": {...}}. Toda chamada informava saldo zero.Models.Listprocurava campos chamadosinputNameecategorys, que a API não envia, então todo modelo voltava comIDvazio. EModels.Retrieveescapava o id inteiro, transformandohinow/himaxemhinow%2Fhimaxe num 404.
Além disso, ResponseFormat virou objeto — use &hinow.ResponseFormat{Type: "json_object"} — e BalanceResponse passou a ser um alias de Balance, então o código antigo continua compilando.
Escolhendo entre HiMax, HiNova e HiGenesis
O que cada modelo faz bem, quanto custa e como escrever o prompt para cada um.

