Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HINOW Java SDK

Official Java 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

  • Java 11 or newer

Installation

The library is published through JitPack, so the repository has to be declared alongside the dependency.

Maven

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

Gradle

repositories {
    mavenCentral()
    maven { url 'https://jitpack.io' }
}

dependencies {
    implementation 'com.github.hinow-ai:sdk-java:v2.0.0'
}

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

export HINOW_API_KEY="hi_your_key_here"

First call

import ai.hinow.*;

Hinow client = new Hinow(null);  // null reads HINOW_API_KEY

ChatCompletion answer = client.chat().completions().create(
    ChatCompletionRequest.builder()
        .model("hinow/himax")
        .addMessage(new Message("user", "Explain what an API is in one paragraph."))
        .build());

System.out.println(answer.getChoices().get(0).getMessage().getText());

Read the answer through getText(). getContent() returns an Object, because a message may carry text or a list of parts.

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

Streaming

createStream hands each chunk to your callback as it arrives, and returns when the model is done. Each chunk carries getDelta() — the new fragment — not the answer so far.

client.chat().completions().createStream(request, chunk -> {
    if (!chunk.getChoices().isEmpty()) {
        String piece = chunk.getChoices().get(0).getDelta().getContent();
        if (piece != null) {
            System.out.print(piece);
        }
    }
});

Web search

Answers on the spot, US$ 0.005 per call.

ToolsService.SearchResponse search = client.tools().search(
    "best beaches in northeast Brazil", ToolsService.SEARCH, "br", "pt-br");

for (ToolsService.SearchResult hit : search.getResults()) {
    System.out.println(hit.getPosition() + ". " + hit.getTitle());
    System.out.println("   " + hit.getUrl());
}

Nine types are constants on ToolsService. Which fields are filled depends on the type:

Type Each result carries
SEARCH, SCHOLAR, PATENTS position, title, url, snippet
NEWS the above plus source, date, imageUrl
IMAGES link, imageUrl, thumbnailUrl, width, height
VIDEOS channel, duration, date, thumbnailUrl
PLACES address, category, phone, website, rating
SHOPPING price, delivery, rating, source
AUTOCOMPLETE suggestions; results stays empty

Website contacts

This one crawls, so it runs as a job.

ToolsService.ToolJob job = client.tools()
    .websiteContactsAndWait(Arrays.asList("https://example.com"), 2, 10);

for (ToolsService.ContactItem contact : job.getResult().getItems()) {
    System.out.println(contact.getType() + ": " + contact.getValue()
        + " (" + contact.getSourceUrl() + ")");
}

To drive the loop yourself, use websiteContacts and follow it with tools().jobs().retrieve(job.getJobId()). 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.

BetaService.Assistant assistant = client.beta().assistants().create(
    BetaService.AssistantRequest.builder()
        .model("hinow/himax")
        .name("Support")
        .instructions("Check the order before stating any status.")
        .tools(Arrays.asList(Tool.function("get_order", "Look up an order by code.", schema)))
        .build());

BetaService.Thread thread = client.beta().threads().create();
client.beta().threads().messages().create(thread.getId(), "Has order A-1002 arrived?", "user");

BetaService.Run run = client.beta().threads().runs()
    .createAndPoll(thread.getId(), assistant.getId());

while ("requires_action".equals(run.getStatus())) {
    List<BetaService.ToolOutput> outputs = new ArrayList<>();

    for (ToolCall call : run.getRequiredAction().getSubmitToolOutputs().getToolCalls()) {
        outputs.add(new BetaService.ToolOutput(call.getId(), getOrder(call)));
    }

    client.beta().threads().runs().submitToolOutputs(thread.getId(), run.getId(), outputs);
    run = client.beta().threads().runs().poll(thread.getId(), run.getId());
}

BetaService.ThreadMessageList messages =
    client.beta().threads().messages().list(thread.getId(), 1, "desc");
System.out.println(messages.getData().get(0).getText());

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

FilesService.FileObject file = client.files().create("returns-policy.txt", "assistants");
VectorStoresService.VectorStore store = client.vectorStores().create("Support base");
client.vectorStores().files().create(store.getId(), file.getId());

// Indexing is asynchronous. Searching too early returns nothing, with no error.
client.vectorStores().files().poll(store.getId(), file.getId());

RagService.RagSearchResponse hits = client.rag()
    .search("how many days do I have to return an item?", store.getId(), 3);

for (RagService.RagHit hit : hits.getResults()) {
    System.out.printf("%.2f  %s%n", hit.getScore(), hit.getSource());
}

The filter is the ragId argument. The API ignores vector_store_id here, so the search would quietly run across every document on the account instead of the base you meant.

Errors

Each failure has its own class, so you can catch by type instead of reading the message.

try {
    client.chat().completions().create(request);
} catch (AuthenticationException e) {
    // 401 — check HINOW_API_KEY
} catch (NotFoundException e) {
    // 404 — usually a model name missing its `hinow/` prefix
} catch (RateLimitException e) {
    // 429 — already retried; back off further
} catch (HinowException e) {
    System.out.println(e.getStatusCode() + " " + e.getMessage());
}

ConnectionException 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, price and capabilities
tools() Web search and website contacts
files() Document upload
vectorStores() Searchable knowledge bases
rag() Semantic search over your documents
beta().assistants() · beta().threads() Server-side agents
getBalance() Account credit

Configuration

Hinow client = new Hinow(
    System.getenv("HINOW_API_KEY"),  // or null and use the variable
    "https://api.hinow.ai",          // or HINOW_BASE_URL
    Duration.ofSeconds(120),
    2);                              // retries on rate limits and 5xx

Upgrading from 1.x

Up to 1.0.0 the SDK packed temperature, maxTokens, topP and repetitionPenalty into a parameters object before sending, with the numbers turned into strings. The API accepts that object and ignores it, so those settings never took effect — maxTokens(10) still returned the whole answer. From 2.0 they go at the root, where the API reads them.

Three more things changed:

  • Errors are typed. HinowException is still the base class, so existing catch (HinowException e) keeps working.
  • Message.getContentAsString() still works, and getText() is the shorter name that also handles multipart content.
  • responseFormat takes an object now — Map.of("type", "json_object") — since that is what the API expects.

License

MIT

About

SDK Java

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages