Ir para o conteúdo

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.

Instalação

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.

pom.xmlxml
<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>

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.

terminalbash
export HINOW_API_KEY="hi_sua_chave_aqui"

A primeira chamada

PrimeiraChamada.javajava
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.

Mostrando a resposta enquanto ela chega

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.

Streaming.javajava
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ê.

Busca na web

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.

BuscaWeb.javajava
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());
    }
}
TipoO que vem preenchido em cada resultado
ToolsService.SEARCH, SCHOLAR, PATENTSposition, title, url, snippet
ToolsService.NEWSos acima mais source, date, imageUrl
ToolsService.IMAGESlink, imageUrl, thumbnailUrl, width, height
ToolsService.VIDEOSchannel, duration, date, thumbnailUrl
ToolsService.PLACESaddress, category, phone, website, rating, coordenadas
ToolsService.SHOPPINGprice, delivery, rating, source
ToolsService.AUTOCOMPLETEsuggestions; results fica vazio

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.

ContatosSite.javajava
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.

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

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

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 HinowException, e as que vieram da API carregam getStatusCode(), getErrorType() e o corpo da resposta.

Erros.javajava
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());
        }
    }
}
ClasseQuando acontece
AuthenticationException401 — chave ausente, inválida ou revogada
PermissionException403 — a chave não tem acesso a esse recurso
NotFoundException404 — modelo, arquivo ou assistente inexistente
InvalidRequestException400 ou 422 — falta um campo ou um valor está fora da faixa
RateLimitException429 — limite atingido
InsufficientBalanceException402 — saldo esgotado
ServerException5xx — a falha é do lado da API
ConnectionExceptiona requisição não chegou; nada foi cobrado
Modelos.javajava
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());
    }
}

Tudo que o cliente expõe

MétodoPara 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

Configuração

Cliente.javajava
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 5xx

Vindo da versão 1.x

Até 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() devolvia null sempre que o conteúdo não era uma String pura. Continua funcionando, e getText() é o nome curto que também junta conteúdo em partes.
  • Os erros passaram a ser tipados, mantendo HinowException como classe base para que os catch existentes continuem valendo.
  • responseFormat recebe um objeto — Map.of("type", "json_object") — que é o que a API espera.
  • ModelsService passou a trazer nome, categoria e preço, e ganhou retrieve().

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?