# java-openai
**Repository Path**: ppnt/java-openai
## Basic Information
- **Project Name**: java-openai
- **Description**: 是一个强大的Ai客户端库,用于将 OpenAI 服务集成到 Java 应用程序中。该库构建于 OkHttp 和 Fastjson2 之上,为与 OpenAI 的 API 交互提供了一种无缝高效的方式——支持聊天补全、图像处理、嵌入等功能。并通知调用 gemini,jina,textin,deepseek等服务端.
- **Primary Language**: Unknown
- **License**: MIT
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 9
- **Forks**: 4
- **Created**: 2025-02-08
- **Last Updated**: 2026-04-30
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# java-openai
A unified Java client for working with large language model providers — OpenAI, Google (Gemini & Vertex AI), Anthropic Claude, OpenRouter, and many more — through a single, stable abstraction.
The core idea: **business code should describe intent, not protocol details**. The library translates a unified request/response model to and from each provider's native protocol, so adding or switching platforms doesn't ripple through your code.
- Unified entry point: `UniChatClient`
- Unified request: `UniChatRequest`
- Unified response: `UniChatResponse`
- Unified message: `UniChatMessage`
- Built on top of [OkHttp](https://square.github.io/okhttp/) and [FastJSON2](https://github.com/alibaba/fastjson)
## Table of Contents
- [Installation](#installation)
- [Configuration](#configuration)
- [Quick Start](#quick-start)
- [Supported Platforms](#supported-platforms)
- [UniChatRequest](#unichatrequest)
- [UniChatResponse](#unichatresponse)
- [Streaming Generation](#streaming-generation)
- [Image / Multimodal Input](#image--multimodal-input)
- [Function / Tool Calling](#function--tool-calling)
- [Google Gemini](#google-gemini)
- [Google Vertex AI](#google-vertex-ai)
- [Anthropic Claude](#anthropic-claude)
- [Error Handling](#error-handling)
- [Project Layout](#project-layout)
- [License](#license)
## Installation
```xml
nexus.io
java-openai
1.3.1
```
## Configuration
`UniChatClient` resolves API keys and base URLs from environment variables (or `app.properties` loaded via `EnvUtils.load()`). Set only the ones you need:
```properties
# OpenAI / OpenAI-compatible
OPENAI_API_KEY=...
OPENAI_API_URL=https://api.openai.com/v1
# Google Gemini
GEMINI_API_KEY=...
# Google Vertex AI (use API key, not Service Account — see Vertex AI section)
VERTEX_AI_API_KEY=...
VERTEX_AI_API_URL=https://aiplatform.googleapis.com/v1/publishers/google/models
# Anthropic Claude
CLAUDE_API_KEY=...
# Aggregators / regional platforms
OPENROUTER_API_KEY=...
ZENMUX_API_KEY=...
VOLCENGINE_API_KEY=...
BAILIAN_API_KEY=...
TENCENT_API_KEY=...
MOONSHOT_API_KEY=...
MINIMAX_API_KEY=...
CEREBRAS_API_KEY=...
GITEE_API_KEY=...
# Self-hosted inference engines (only the URL is usually required)
OLLAMA_API_URL=...
LLAMACPP_API_URL=...
VLLM_API_URL=...
SWIFT_API_URL=...
# Exchange-token relay channels
EXCHANGE_TOKEN_API_KEY=...
```
Never commit API keys. Use a Secret Manager, Kubernetes Secret, or CI variables in production.
## Quick Start
```java
import java.util.ArrayList;
import java.util.List;
import nexus.io.chat.PlatformInput;
import nexus.io.chat.UniChatClient;
import nexus.io.chat.UniChatMessage;
import nexus.io.chat.UniChatRequest;
import nexus.io.chat.UniChatResponse;
import nexus.io.consts.ModelPlatformName;
import nexus.io.openrouter.OpenRouterModels;
import nexus.io.tio.utils.environment.EnvUtils;
public class QuickStart {
public static void main(String[] args) {
EnvUtils.load();
PlatformInput platform = new PlatformInput(
ModelPlatformName.OPENROUTER,
OpenRouterModels.XIAOMI_MIMO_V2_FLASH_FREE
);
List messages = new ArrayList<>();
messages.add(UniChatMessage.buildUser("just say hi"));
UniChatRequest request = new UniChatRequest(platform);
request.setMessages(messages);
UniChatResponse response = UniChatClient.generate(request);
System.out.println(response.getMessage().getContent());
}
}
```
For single-shot user prompts there's a shortcut:
```java
UniChatRequest request = new UniChatRequest(platform);
request.setUserPrompts("how are you?");
UniChatResponse response = UniChatClient.generate(request);
```
## Supported Platforms
`ModelPlatformName` enumerates every platform the unified client can route to. Switching providers usually means changing only the `PlatformInput`.
| Category | Constants |
| --- | --- |
| Native protocols | `OPENAI`, `GOOGLE`, `ANTHROPIC` |
| Google Cloud | `VERTEX_AI` |
| Aggregators (overseas) | `OPENROUTER`, `ZENMUX`, `CEREBRAS` |
| Aggregators / enterprise (regional) | `VOLC_ENGINE`, `BAILIAN`, `TENCENT`, `MOONSHOT`, `MINIMAX`, `SILICONFLOW`, `CHAT_GLM`, `GITEE` |
| Self-hosted | `OLLAMA`, `LLAMACPP`, `VLLM`, `SWIFT`, `TITANIUM` |
| Relay / exchange-token | `EXCHANGE_TOKEN`, `EXCHANGE_TOKEN_GOOGLE`, `EXCHANGE_TOKEN_ANTHROPIC`, `EXCHANGE_TOKEN_US`, `EXCHANGE_TOKEN_US_GOOGLE`, `EXCHANGE_TOKEN_US_ANTHROPIC` |
| Other | `AIAPI`, `ELEVEN_LABS`, `AUTO` |
Each platform module ships its own model constant class (`OpenAiModels`, `GoogleModels`, `ClaudeModels`, `OpenRouterModels`, `DeepSeekModels`, `MoonshotModels`, …) so model names are typed, not stringly.
## UniChatRequest
```java
public class UniChatRequest {
private String domain;
private String apiPrefixUrl;
private Long groupId;
private String groupName;
private Long taskId;
private String taskName;
private boolean useSystemPrompt = true;
private String apiKey;
private String platform;
private String model;
private String systemPrompt;
private boolean cacheSystemPrompt;
private Boolean stream;
private List messages;
private Float temperature;
private String cachedId;
private Integer max_tokens;
private Boolean enable_thinking;
private UniThinkingConfig thinkingConfig;
private String responseFormat;
private UniResponseSchema responseSchema;
private ChatProvider provider;
private List responseModalities;
private Boolean enable_search;
private List tools;
}
```
Field groups:
- **Business identifiers** (never sent to the model): `domain`, `groupId`, `groupName`, `taskId`, `taskName` — used for routing, logging, billing, and audit.
- **Platform & auth**: `platform`, `model`, `apiKey`, `apiPrefixUrl`, `provider`.
- **Prompt & messages**: `useSystemPrompt`, `systemPrompt`, `cacheSystemPrompt`, `messages`.
- **Generation**: `temperature`, `max_tokens`, `responseFormat`, `responseSchema`.
- **Reasoning**: `enable_thinking`, `thinkingConfig`.
- **Streaming / multimodal / search**: `stream`, `responseModalities`, `enable_search`.
- **Cache reuse**: `cachedId`.
- **Tools**: `tools` (function/tool calling — see below).
## UniChatResponse
```java
public class UniChatResponse {
private transient String rawData; // original platform JSON, useful for debugging
private String model; // actual model that served the request
private ChatResponseMessage message; // full message — use for non-streaming
private ChatResponseDelta delta; // incremental chunk — use for streaming
private ChatResponseUsage usage; // unified token usage
private List citations; // RAG / search citations when available
}
```
- For `UniChatClient.generate(...)`, read `response.getMessage().getContent()`.
- For `UniChatClient.stream(...)`, read `response.getDelta().getContent()` inside `onData`.
## Streaming Generation
Streaming uses the same `UniChatRequest` plus a `UniChatEventListener`. Below is a Tio-Boot SSE handler that pipes deltas straight to the browser:
```java
import nexus.io.chat.PlatformInput;
import nexus.io.chat.UniChatClient;
import nexus.io.chat.UniChatEventListener;
import nexus.io.chat.UniChatMessage;
import nexus.io.chat.UniChatRequest;
import nexus.io.chat.UniChatResponse;
import nexus.io.consts.ModelPlatformName;
import nexus.io.openrouter.OpenRouterModels;
import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.core.ChannelContext;
import nexus.io.tio.core.Tio;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.http.common.sse.SsePacket;
import nexus.io.tio.http.server.handler.HttpRequestHandler;
import nexus.io.tio.http.server.util.SseEmitter;
import okhttp3.Response;
import okhttp3.sse.EventSource;
public class PredictStreamHandler implements HttpRequestHandler {
@Override
public HttpResponse handle(HttpRequest httpRequest) throws Exception {
PlatformInput platform = new PlatformInput(
ModelPlatformName.OPENROUTER,
OpenRouterModels.XIAOMI_MIMO_V2_FLASH_FREE
);
UniChatRequest request = new UniChatRequest(platform);
request.setUserPrompts("just say hi");
HttpResponse httpResponse = TioRequestContext.getResponse();
httpResponse.setSend(false);
ChannelContext ctx = httpRequest.channelContext;
UniChatEventListener listener = new UniChatEventListener() {
@Override
public void onOpen(EventSource eventSource, Response response) {
httpResponse.addServerSentEventsHeader();
Tio.send(ctx, httpResponse);
}
@Override
public void onData(UniChatResponse chatResponse) {
// streaming output lives on delta, not message
String content = chatResponse.getDelta().getContent();
Tio.bSend(ctx, new SsePacket(content));
}
@Override
public void onClosed(EventSource eventSource) {
SseEmitter.closeSeeConnection(ctx);
}
};
UniChatClient.stream(request, listener);
return httpResponse;
}
}
```
> **Important.** In streaming mode each `onData` callback only carries the new chunk. Always read `chatResponse.getDelta()`, not `getMessage()`.
## Image / Multimodal Input
Images are attached to a `UniChatMessage` via `ChatImageFile`. The unified client maps the image to whatever each platform expects (`image_url`, `inline_data`, vision blocks, etc.).
```java
import java.util.ArrayList;
import java.util.List;
import nexus.io.chat.ChatImageFile;
import nexus.io.chat.PlatformInput;
import nexus.io.chat.UniChatClient;
import nexus.io.chat.UniChatMessage;
import nexus.io.chat.UniChatRequest;
import nexus.io.chat.UniChatResponse;
import nexus.io.consts.ModelPlatformName;
import nexus.io.openrouter.OpenRouterModels;
import nexus.io.tio.utils.base64.Base64Utils;
import nexus.io.tio.utils.http.ContentTypeUtils;
import nexus.io.tio.utils.hutool.FilenameUtils;
public class ImageOcrExample {
public String recognize(byte[] data, String filename) {
String suffix = FilenameUtils.getSuffix(filename);
String mimeType = ContentTypeUtils.getContentType(suffix);
String encoded = Base64Utils.encodeImage(data, mimeType);
List files = new ArrayList<>();
files.add(ChatImageFile.base64(mimeType, encoded));
UniChatMessage message = new UniChatMessage();
message.setContent("image");
message.setFiles(files);
PlatformInput platform = new PlatformInput(
ModelPlatformName.OPENROUTER,
OpenRouterModels.QWEN_QWEN2_5_VL_7B_INSTRUCT_FREE
);
UniChatRequest request = new UniChatRequest(platform);
request.setSystemPrompt("Extract the text from the image.");
List messages = new ArrayList<>();
messages.add(message);
request.setMessages(messages);
UniChatResponse response = UniChatClient.generate(request);
return response.getMessage().getContent();
}
}
```
You can also pass a remote image with `ChatImageFile.url("https://...")`.
## Function / Tool Calling
Declare tools via `ChatRequestTool` and assemble assistant/tool messages with `UniChatMessage.buildAssistantWithToolCalls(...)` and `UniChatMessage.buildTool(...)`.
```java
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import nexus.io.chat.PlatformInput;
import nexus.io.chat.UniChatClient;
import nexus.io.chat.UniChatMessage;
import nexus.io.chat.UniChatRequest;
import nexus.io.chat.UniChatResponse;
import nexus.io.consts.ModelPlatformName;
import nexus.io.openai.chat.ChatRequestFunctionParameter;
import nexus.io.openai.chat.ChatRequestFunctionProperty;
import nexus.io.openai.chat.ChatRequestTool;
import nexus.io.openai.chat.ChatRequestToolCallFunction;
import nexus.io.openai.constants.OpenAiModels;
public class ToolCallExample {
public static void main(String[] args) {
ChatRequestFunctionParameter parameter = new ChatRequestFunctionParameter();
parameter.setType("object");
Map properties = new HashMap<>();
properties.put("sql", new ChatRequestFunctionProperty("string", "The SQL statement to execute."));
parameter.setProperties(properties);
parameter.setRequired(List.of("sql"));
ChatRequestToolCallFunction function = new ChatRequestToolCallFunction();
function.setName("find");
function.setDescription("Execute SQL query on the database.");
function.setParameters(parameter);
ChatRequestTool tool = new ChatRequestTool("function", function);
List messages = new ArrayList<>();
messages.add(UniChatMessage.buildSystem("You are a database assistant. The database is PostgreSQL."));
messages.add(UniChatMessage.buildUser("How many tables are in my database?"));
UniChatRequest request = new UniChatRequest(
new PlatformInput(ModelPlatformName.OPENAI, OpenAiModels.GPT_4O_MINI));
request.setMessages(messages);
request.setTools(List.of(tool));
UniChatResponse response = UniChatClient.generate(request);
System.out.println(response.getMessage().getToolCalls());
}
}
```
## Google Gemini
```java
PlatformInput platform = new PlatformInput(ModelPlatformName.GOOGLE, GoogleModels.GEMINI_2_5_FLASH);
UniChatRequest request = new UniChatRequest(platform);
request.setSystemPrompt("You summarize Chinese math concepts.");
request.setUserPrompts("Input: 什么是线性齐次递推\nSummary:");
UniChatResponse response = UniChatClient.generate(request);
System.out.println(response.getMessage().getContent());
```
The unified request is automatically translated into Gemini's `system_instruction` + `contents` / `parts` shape, and the response's nested `candidates[0].content.parts[0].text` is mapped back into `response.getMessage().getContent()`. Token usage from `usageMetadata` is unified into `response.getUsage()`.
Enable Google Search grounding with `request.setEnable_search(true)` — citations land in `response.getMessage()` via `UniSources` and in `response.getCitations()` when available.
## Google Vertex AI
`java-openai` does not currently sign Service Account credentials. To use Vertex AI, create an API Key bound to the target Service Account in the Google Cloud console and pass it via `VERTEX_AI_API_KEY`. The protocol family stays Gemini — only the platform entry changes.
```java
import nexus.io.chat.PlatformInput;
import nexus.io.chat.UniChatClient;
import nexus.io.chat.UniChatRequest;
import nexus.io.chat.UniChatResponse;
import nexus.io.consts.ModelPlatformName;
import nexus.io.exception.GenerateException;
import nexus.io.gemini.GoogleModels;
import nexus.io.tio.utils.environment.EnvUtils;
public class VertexAiExample {
public static void main(String[] args) {
EnvUtils.load();
PlatformInput platform = new PlatformInput(
ModelPlatformName.VERTEX_AI,
GoogleModels.GEMINI_2_5_FLASH);
UniChatRequest request = new UniChatRequest(platform);
request.setUserPrompts("how are you?");
try {
UniChatResponse response = UniChatClient.generate(request);
System.out.println(response.getMessage().getContent());
} catch (GenerateException e) {
System.out.println(e.getResponseBody());
}
}
}
```
## Anthropic Claude
```java
PlatformInput platform = new PlatformInput(
ModelPlatformName.ANTHROPIC, ClaudeModels.CLAUDE_3_7_SONNET_20250219);
UniChatRequest request = new UniChatRequest(platform);
request.setSystemPrompt(systemPrompt);
request.setCacheSystemPrompt(true); // adds Anthropic prompt-cache breakpoint
request.setMessages(messages);
request.setMax_tokens(64000);
UniChatResponse response = UniChatClient.generate(request);
```
`cacheSystemPrompt = true` automatically attaches Anthropic's `cache_control` block to the system message. To send images, attach `ChatImageFile` files exactly as in the multimodal section — they are translated into Claude's `image` content blocks.
## Error Handling
When a provider rejects the request, `UniChatClient` throws `GenerateException`. The exception preserves the **already-translated** request body and the raw response — invaluable for diagnosing provider-side failures.
```java
import nexus.io.chat.UniChatClient;
import nexus.io.chat.UniChatRequest;
import nexus.io.chat.UniChatResponse;
import nexus.io.exception.GenerateException;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class UniPredictService {
public UniChatResponse generate(UniChatRequest request) {
for (int i = 0; i < 10; i++) {
try {
return UniChatClient.generate(request);
} catch (GenerateException e) {
log.error("provider call failed: status={}, body={}, request={}",
e.getStatusCode(), e.getResponseBody(), e.getRequestBody(), e);
}
}
return null;
}
}
```
For successful calls the original platform JSON is still available on `UniChatResponse.getRawData()`.
## Project Layout
Code is organised by capability domain rather than by client class. Selected modules:
| Module | Purpose |
| --- | --- |
| `chat` | Unified abstractions: `UniChatClient`, `UniChatRequest`, `UniChatResponse`, `UniChatMessage`, `UniChatEventListener`, `ChatImageFile`, `PlatformInput`. |
| `openai` / `claude` / `gemini` / `google` | Native protocol adapters used by the unified client. |
| `vertexai` | Vertex AI platform constants. |
| `openrouter` / `groq` / `deepseek` / `moonshot` / `minimax` / `tencent` / `volcengine` / `byteplus` / `siliconflow` / `cerebras` / `bailian` / `gitee` / `zenmux` / `aiapi` | Aggregator / regional / self-hosted adapters and model constants. |
| `consts` | `ModelPlatformName`, model name catalogs, table names. |
| `exception` | `GenerateException` and friends. |
| `image` / `tts` / `fishaudio` / `textin` | Multimodal extensions: image generation, TTS (including Gemini TTS via `GeminiTTSClient`), OCR. |
| `search` / `searchapi` / `searxng` / `tavily` / `supadata` | Search-augmented generation backends. |
| `prompt` | Prompt template engine. |
| `mcp` | Tool-protocol integrations. |
| `proxy` / `linux` / `libreoffice` | Operational glue (proxy, OS utilities, document conversion). |
## License
Apache License 2.0. See [LICENSE](LICENSE).