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.
- Java 11 or newer
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"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.
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);
}
}
});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 |
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.
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.
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.
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.
| 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 |
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 5xxUp 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.
HinowExceptionis still the base class, so existingcatch (HinowException e)keeps working. Message.getContentAsString()still works, andgetText()is the shorter name that also handles multipart content.responseFormattakes an object now —Map.of("type", "json_object")— since that is what the API expects.
MIT