SDK Java
Instale o SDK oficial em Java 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 Java para a API do HiNow. Roda em Java 11 ou mais novo e usa OkHttp e Gson, que já estão na maioria dos projetos.
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.
A biblioteca é publicada pelo JitPack, então o repositório precisa ser declarado junto com a dependência. Só a dependência não resolve.
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependency>
<groupId>com.github.hinow-ai</groupId>
<artifactId>sdk-java</artifactId>
<version>v2.0.0</version>
</dependency>repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
dependencies {
implementation 'com.github.hinow-ai:sdk-java:v2.0.0'
}repositories {
mavenCentral()
maven("https://jitpack.io")
}
dependencies {
implementation("com.github.hinow-ai:sdk-java:v2.0.0")
}Guarde a chave em HINOW_API_KEY e passe null para o construtor. Passar a chave direto também funciona, mas evite deixar o valor no código.
export HINOW_API_KEY="hi_sua_chave_aqui"import ai.hinow.ChatCompletion;
import ai.hinow.ChatCompletionRequest;
import ai.hinow.Hinow;
import ai.hinow.HinowException;
import ai.hinow.Message;
public class FirstCall {
public static void main(String[] args) throws HinowException {
// Passing null makes the client read the key from HINOW_API_KEY.
Hinow client = new Hinow(null);
ChatCompletion answer = client.chat().completions().create(
ChatCompletionRequest.builder()
.model("hinow/himax")
.addMessage(new Message("system", "You answer in English."))
.addMessage(new Message("user", "What is an API? Answer in one paragraph."))
.build());
// getText() gives you the string; getContent() holds the raw value.
System.out.println(answer.getChoices().get(0).getMessage().getText());
ChatCompletion.Usage uso = answer.getUsage();
System.out.printf("%ninput: %d · output: %d%n",
uso.getPromptTokens(), uso.getCompletionTokens());
}
}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 getText()
getContent() devolve Object, porque uma mensagem pode carregar texto ou uma lista de partes. getText() resolve os dois casos e devolve String.
createStream entrega cada pedaço ao seu callback assim que ele chega e retorna quando o modelo termina. Numa tela de conversa isso muda a percepção de velocidade mais do que trocar de modelo.
import ai.hinow.ChatCompletionRequest;
import ai.hinow.Hinow;
import ai.hinow.HinowException;
import ai.hinow.Message;
public class Streaming {
public static void main(String[] args) throws HinowException {
Hinow client = new Hinow(null);
client.chat().completions().createStream(
ChatCompletionRequest.builder()
.model("hinow/hinova")
.addMessage(new Message("user", "Write three lines about the sea at dawn."))
.build(),
chunk -> {
// Note the delta: each chunk carries the new fragment, not the whole answer.
if (!chunk.getChoices().isEmpty()) {
String excerpt = chunk.getChoices().get(0).getDelta().getContent();
if (excerpt != null) {
System.out.print(excerpt);
}
}
});
System.out.println();
}
}É getDelta(), não getMessage()
Na resposta completa o texto vem em getMessage().getText(). No streaming, cada pedaço traz apenas o trecho novo, em getDelta().getContent() — juntar tudo é com você.
Responde na hora, sem job para acompanhar, a US$ 0,005 por chamada. Os nove tipos são constantes em ToolsService, e um tipo inválido é recusado antes de a requisição sair.
import ai.hinow.Hinow;
import ai.hinow.HinowException;
import ai.hinow.ToolsService;
public class SearchWeb {
public static void main(String[] args) throws HinowException {
Hinow client = new Hinow(null);
// Answers on the spot: there is no job to follow.
ToolsService.SearchResponse search = client.tools().search(
"best beaches in northeast Brazil", ToolsService.SEARCH, "br", "pt-br");
for (ToolsService.SearchResult item : search.getResults()) {
System.out.println(item.getPosition() + ". " + item.getTitle());
System.out.println(" " + item.getUrl());
}
System.out.printf("%n%d results · US$ %s%n", search.getResults().size(), search.getCost());
}
}| Tipo | O que vem preenchido em cada resultado |
|---|---|
ToolsService.SEARCH, SCHOLAR, PATENTS | position, title, url, snippet |
ToolsService.NEWS | os acima mais source, date, imageUrl |
ToolsService.IMAGES | link, imageUrl, thumbnailUrl, width, height |
ToolsService.VIDEOS | channel, duration, date, thumbnailUrl |
ToolsService.PLACES | address, category, phone, website, rating, coordenadas |
ToolsService.SHOPPING | price, delivery, rating, source |
ToolsService.AUTOCOMPLETE | 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.
import ai.hinow.Hinow;
import ai.hinow.HinowException;
import ai.hinow.ToolsService;
import java.util.Arrays;
public class SiteContacts {
public static void main(String[] args) throws HinowException {
Hinow client = new Hinow(null);
// The crawl takes a while, so it runs as a job. This method waits for you.
ToolsService.ToolJob job = client.tools().websiteContactsAndWait(
Arrays.asList("https://www.teclia.com"), 2, 10);
String cache = Boolean.TRUE.equals(job.getCached()) ? " (from cache)" : "";
System.out.printf("status: %s · US$ %s%s%n%n", job.getStatus(), job.getCost(), cache);
// Each item carries the type, the value and the exact page it was found on.
for (ToolsService.ContactItem item : job.getResult().getItems()) {
if ("email".equals(item.getType()) || "phone".equals(item.getType())) {
System.out.printf("%-9s%s%n", item.getType(), item.getValue());
System.out.printf("%-9sem %s%n", "", item.getSourceUrl());
} else {
System.out.printf("%-9s%s%n", item.getPlatform(), item.getUrl());
}
}
}
}Repetir uma execução idêntica não cobra de novo
O job volta com getCached() verdadeiro quando o resultado veio do cache. Se quiser controlar o ciclo você mesmo, use websiteContacts e acompanhe com tools().jobs().retrieve(job.getJobId()) — 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.
import ai.hinow.BetaService;
import ai.hinow.ChatCompletionRequest;
import ai.hinow.Hinow;
import ai.hinow.HinowException;
import ai.hinow.Tool;
import ai.hinow.ToolCall;
import com.google.gson.Gson;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class Agent {
private static final Gson GSON = new Gson();
// Your real function: a map here, just for the example.
static String getOrder(String code) {
Map<String, String> order = new LinkedHashMap<>();
if ("A-1001".equals(code)) {
order.put("status", "delivered");
order.put("delivered", "2026-08-02");
} else if ("A-1002".equals(code)) {
order.put("status", "in transit");
order.put("estimated", "2026-08-12");
} else {
order.put("error", "order not found");
}
return GSON.toJson(order);
}
public static void main(String[] args) throws HinowException {
Hinow client = new Hinow(null);
// The argument schema, in JSON Schema.
Map<String, Object> codeField = new LinkedHashMap<>();
codeField.put("type", "string");
codeField.put("description", "Order code, e.g. A-1001");
Map<String, Object> properties = new LinkedHashMap<>();
properties.put("code", codeField);
Map<String, Object> schema = new LinkedHashMap<>();
schema.put("type", "object");
schema.put("properties", properties);
schema.put("required", Arrays.asList("code"));
// 1. The assistant: model, instructions and the functions it may call.
BetaService.Assistant assistant = client.beta().assistants().create(
BetaService.AssistantRequest.builder()
.model("hinow/himax")
.name("Support")
.instructions("You answer questions about orders. "
+ "Check the tool before stating any status.")
.tools(Arrays.asList(Tool.function(
"get_order",
"Look up an order by its code.",
schema)))
.build());
// 2. The conversation and the question.
BetaService.Thread thread = client.beta().threads().create();
client.beta().threads().messages().create(thread.getId(), "Has order A-1002 arrived?", "user");
// 3. Run it and wait.
BetaService.Run run = client.beta().threads().runs()
.createAndPoll(thread.getId(), assistant.getId());
// 4. While the model asks for functions, run them and hand the result back.
while ("requires_action".equals(run.getStatus())) {
List<BetaService.ToolOutput> outputs = new ArrayList<>();
for (ToolCall call : run.getRequiredAction().getSubmitToolOutputs().getToolCalls()) {
Map<?, ?> callArgs = GSON.fromJson(call.getFunction().getArguments(), Map.class);
String code = String.valueOf(callArgs.get("code"));
System.out.println("-> the model called " + call.getFunction().getName() + "(" + code + ")");
outputs.add(new BetaService.ToolOutput(call.getId(), getOrder(code)));
}
client.beta().threads().runs().submitToolOutputs(thread.getId(), run.getId(), outputs);
run = client.beta().threads().runs().poll(thread.getId(), run.getId());
}
// 5. The final answer is the last message on the thread.
BetaService.ThreadMessageList messages =
client.beta().threads().messages().list(thread.getId(), 1, "desc");
System.out.println("\n" + messages.getData().get(0).getText());
client.beta().assistants().delete(assistant.getId());
}
}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 getRequiredAction(), 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.
import ai.hinow.FilesService;
import ai.hinow.Hinow;
import ai.hinow.HinowException;
import ai.hinow.RagService;
import ai.hinow.VectorStoresService;
public class Knowledge {
public static void main(String[] args) throws HinowException {
Hinow client = new Hinow(null);
// 1. Upload the document.
FilesService.FileObject file =
client.files().create("returns-policy.txt", "assistants");
System.out.println("file: " + file.getId() + " (" + file.getBytes() + " bytes)");
// 2. Create the store and attach the file to it.
VectorStoresService.VectorStore base = client.vectorStores().create("Support base");
client.vectorStores().files().create(base.getId(), file.getId());
System.out.println("store: " + base.getId());
// 3. Indexing is asynchronous. Searching before it finishes returns nothing
// at all, with no error, so poll waits for it.
VectorStoresService.VectorStoreFile state =
client.vectorStores().files().poll(base.getId(), file.getId());
System.out.println("indexing: " + state.getStatus() + "\n");
// 4. Search by meaning, not by exact word.
RagService.RagSearchResponse hits =
client.rag().search("How many days do I have to return an item?", base.getId(), 3);
for (RagService.RagHit achado : hits.getResults()) {
String excerpt = achado.getText().replace("\n", " ");
excerpt = excerpt.substring(0, Math.min(110, excerpt.length()));
System.out.printf("%.2f %s%n %s…%n", achado.getScore(), achado.getSource(), excerpt);
}
}
}O filtro por base é o argumento 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 vira uma classe própria, então dá para tratar por tipo em vez de comparar string de mensagem. Todas herdam de HinowException, e as que vieram da API carregam getStatusCode(), getErrorType() e o corpo da resposta.
import ai.hinow.AuthenticationException;
import ai.hinow.ChatCompletionRequest;
import ai.hinow.ConnectionException;
import ai.hinow.Hinow;
import ai.hinow.HinowException;
import ai.hinow.InvalidRequestException;
import ai.hinow.Message;
import ai.hinow.NotFoundException;
import ai.hinow.RateLimitException;
public class Errors {
public static void main(String[] args) {
try {
Hinow client = new Hinow(null);
client.chat().completions().create(
ChatCompletionRequest.builder()
.model("himax") // the hinow/ prefix is missing
.addMessage(new Message("user", "Olá"))
.build());
} catch (AuthenticationException e) {
System.out.println("Invalid or expired key. Check HINOW_API_KEY.");
} catch (NotFoundException e) {
System.out.println("Not found: " + e.getMessage());
} catch (InvalidRequestException e) {
System.out.println("Invalid request: " + e.getMessage());
} catch (RateLimitException e) {
System.out.println("Rate limited. The SDK already retried; wait a moment.");
} catch (ConnectionException e) {
// Nothing reached the API, so nothing was charged.
System.out.println("Sem answer da API: " + e.getMessage());
} catch (HinowException e) {
// Safety net: any other error the API reported.
System.out.println("Error " + e.getStatusCode() + ": " + e.getMessage());
}
}
}| Classe | Quando acontece |
|---|---|
AuthenticationException | 401 — chave ausente, inválida ou revogada |
PermissionException | 403 — a chave não tem acesso a esse recurso |
NotFoundException | 404 — modelo, arquivo ou assistente inexistente |
InvalidRequestException | 400 ou 422 — falta um campo ou um valor está fora da faixa |
RateLimitException | 429 — limite atingido |
InsufficientBalanceException | 402 — saldo esgotado |
ServerException | 5xx — a falha é do lado da API |
ConnectionException | a requisição não chegou; nada foi cobrado |
import ai.hinow.BalanceResponse;
import ai.hinow.Hinow;
import ai.hinow.HinowException;
import ai.hinow.ModelsService;
public class Models {
public static void main(String[] args) throws HinowException {
Hinow client = new Hinow(null);
// Account credit, in US dollars. The response is wrapped in "data".
BalanceResponse balance = client.getBalance();
System.out.printf("balance: US$ %.2f%n%n", balance.getData().getBalance());
// One specific model. The id is namespaced: hinow/himax, not himax.
ModelsService.ModelInfo model = client.models().retrieve("hinow/himax");
System.out.println(model.getName() + " (" + model.getId() + ")");
System.out.println("categories: " + String.join(", ", model.getCategory()));
System.out.println("price per million tokens: input US$ " + model.getCost().getInput()
+ " · output US$ " + model.getCost().getOutput());
}
}| 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, 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 |
Hinow client = new Hinow(
System.getenv("HINOW_API_KEY"), // ou null e use a variável
"https://api.hinow.ai", // ou HINOW_BASE_URL
Duration.ofSeconds(120),
2); // repete 429 e 5xxAté a 1.0.0, o SDK empacotava temperature, maxTokens, topP e repetitionPenalty 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 maxTokens(10) devolvia a resposta inteira. A partir da 2.0 tudo vai no nível raiz, como a API espera.
Outras mudanças que afetam código existente:
Message.getContentAsString()devolvianullsempre que o conteúdo não era umaStringpura. Continua funcionando, egetText()é o nome curto que também junta conteúdo em partes.- Os erros passaram a ser tipados, mantendo
HinowExceptioncomo classe base para que oscatchexistentes continuem valendo. responseFormatrecebe um objeto —Map.of("type", "json_object")— que é o que a API espera.ModelsServicepassou a trazer nome, categoria e preço, e ganhouretrieve().
Escolhendo entre HiMax, HiNova e HiGenesis
O que cada modelo faz bem, quanto custa e como escrever o prompt para cada um.

