Java 调用 OpenAI / Claude API 完整实战指南

Java 调用 OpenAI / Claude API 完整实战指南

从零开始,手把手教你用 Java 接入 OpenAI 和 Claude 两大主流 AI 接口,包含流式输出、多轮对话、函数调用等高级用法,附完整可运行代码


一、前言

2026年,AI 能力已经成为应用的标配。作为 Java 开发者,掌握如何在项目中接入大模型 API 是一项必备技能。

本文将带你实现:

  • ✅ Java 调用 OpenAI GPT 系列接口
  • ✅ Java 调用 Anthropic Claude 接口
  • ✅ 流式输出(打字机效果)
  • ✅ 多轮对话上下文管理
  • ✅ Function Calling(函数调用)
  • ✅ Spring Boot 完整集成方案

二、准备工作

2.1 获取 API Key

OpenAI:

  1. 访问 https://platform.openai.com
  2. 注册账号 → API Keys → Create new secret key
  3. 复制保存(只显示一次)

Claude(Anthropic):

  1. 访问 https://console.anthropic.com
  2. 注册账号 → API Keys → Create Key
  3. 复制保存

⚠️ 安全提醒:API Key 绝对不能提交到 Git 仓库!建议存放在环境变量或配置中心。

2.2 模型选择参考

OpenAI 模型:

模型 特点 适用场景 价格(输入/输出)
gpt-4o 速度快,性价比高 日常使用首选 2.5 / 10 per 1M tokens
gpt-4o-mini 极速,超低价 简单任务 0.15 / 0.6 per 1M tokens
gpt-4-turbo 强推理 复杂任务 10 / 30 per 1M tokens
o1 深度推理 数学/代码 15 / 60 per 1M tokens

Claude 模型:

模型 特点 适用场景 价格(输入/输出)
claude-3-5-sonnet 综合最强 日常首选 3 / 15 per 1M tokens
claude-3-5-haiku 速度最快 简单任务 0.8 / 4 per 1M tokens
claude-3-opus 最强推理 复杂分析 15 / 75 per 1M tokens

2.3 Maven 依赖

xml 复制代码
<!-- OkHttp - HTTP请求 -->
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.12.0</version>
</dependency>

<!-- FastJSON2 - JSON处理 -->
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.47</version>
</dependency>

<!-- Lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

<!-- Spring Boot(可选,用于集成) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

三、调用 OpenAI API

3.1 基础封装类

java 复制代码
package com.example.ai.openai;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * OpenAI API 客户端
 */
public class OpenAiClient {

    private static final Logger log = LoggerFactory.getLogger(OpenAiClient.class);

    private static final String BASE_URL = "https://api.openai.com/v1";
    private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");

    private final String apiKey;
    private final String model;
    private final OkHttpClient httpClient;

    public OpenAiClient(String apiKey, String model) {
        this.apiKey = apiKey;
        this.model = model;
        this.httpClient = new OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(120, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            .build();
    }

    /**
     * 简单对话(单轮)
     *
     * @param userMessage 用户消息
     * @return AI 回复内容
     */
    public String chat(String userMessage) throws IOException {
        JSONObject requestBody = new JSONObject();
        requestBody.put("model", model);
        requestBody.put("max_tokens", 2048);

        JSONArray messages = new JSONArray();
        JSONObject message = new JSONObject();
        message.put("role", "user");
        message.put("content", userMessage);
        messages.add(message);
        requestBody.put("messages", messages);

        return doRequest(requestBody);
    }

    /**
     * 带系统提示词的对话
     *
     * @param systemPrompt 系统提示词
     * @param userMessage  用户消息
     * @return AI 回复内容
     */
    public String chatWithSystem(String systemPrompt, String userMessage) throws IOException {
        JSONObject requestBody = new JSONObject();
        requestBody.put("model", model);
        requestBody.put("max_tokens", 2048);

        JSONArray messages = new JSONArray();

        // 系统提示词
        JSONObject systemMsg = new JSONObject();
        systemMsg.put("role", "system");
        systemMsg.put("content", systemPrompt);
        messages.add(systemMsg);

        // 用户消息
        JSONObject userMsg = new JSONObject();
        userMsg.put("role", "user");
        userMsg.put("content", userMessage);
        messages.add(userMsg);

        requestBody.put("messages", messages);

        return doRequest(requestBody);
    }

    /**
     * 多轮对话
     *
     * @param messages 完整的对话历史
     * @return AI 回复内容
     */
    public String chatWithHistory(List<Map<String, String>> messages) throws IOException {
        JSONObject requestBody = new JSONObject();
        requestBody.put("model", model);
        requestBody.put("max_tokens", 2048);
        requestBody.put("messages", messages);

        return doRequest(requestBody);
    }

    /**
     * 执行 HTTP 请求
     */
    private String doRequest(JSONObject requestBody) throws IOException {
        Request request = new Request.Builder()
            .url(BASE_URL + "/chat/completions")
            .header("Authorization", "Bearer " + apiKey)
            .header("Content-Type", "application/json")
            .post(RequestBody.create(requestBody.toJSONString(), JSON_TYPE))
            .build();

        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                String errorBody = response.body() != null ? response.body().string() : "unknown error";
                throw new IOException("OpenAI API 请求失败,状态码: " + response.code() + ",错误: " + errorBody);
            }

            String responseBody = response.body().string();
            JSONObject jsonResponse = JSON.parseObject(responseBody);

            // 解析返回内容
            return jsonResponse
                .getJSONArray("choices")
                .getJSONObject(0)
                .getJSONObject("message")
                .getString("content");
        }
    }
}

3.2 多轮对话管理器

java 复制代码
package com.example.ai.openai;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 多轮对话管理器
 * 维护对话历史,实现上下文连续对话
 */
public class ConversationManager {

    private final OpenAiClient client;
    private final List<Map<String, String>> history;
    private final int maxHistorySize;

    public ConversationManager(OpenAiClient client, String systemPrompt, int maxHistorySize) {
        this.client = client;
        this.history = new ArrayList<>();
        this.maxHistorySize = maxHistorySize;

        // 添加系统提示词
        if (systemPrompt != null && !systemPrompt.isEmpty()) {
            Map<String, String> systemMsg = new HashMap<>();
            systemMsg.put("role", "system");
            systemMsg.put("content", systemPrompt);
            history.add(systemMsg);
        }
    }

    /**
     * 发送消息并获取回复
     */
    public String sendMessage(String userMessage) throws IOException {
        // 添加用户消息到历史
        Map<String, String> userMsg = new HashMap<>();
        userMsg.put("role", "user");
        userMsg.put("content", userMessage);
        history.add(userMsg);

        // 调用 API
        String reply = client.chatWithHistory(history);

        // 添加 AI 回复到历史
        Map<String, String> assistantMsg = new HashMap<>();
        assistantMsg.put("role", "assistant");
        assistantMsg.put("content", reply);
        history.add(assistantMsg);

        // 控制历史长度,避免 token 超限
        trimHistory();

        return reply;
    }

    /**
     * 清空对话历史(保留系统提示词)
     */
    public void clearHistory() {
        if (!history.isEmpty() && "system".equals(history.get(0).get("role"))) {
            Map<String, String> systemMsg = history.get(0);
            history.clear();
            history.add(systemMsg);
        } else {
            history.clear();
        }
    }

    /**
     * 控制历史长度
     */
    private void trimHistory() {
        int startIndex = "system".equals(history.get(0).get("role")) ? 1 : 0;
        while (history.size() - startIndex > maxHistorySize * 2) {
            history.remove(startIndex);
        }
    }

    public List<Map<String, String>> getHistory() {
        return new ArrayList<>(history);
    }
}

3.3 流式输出实现

java 复制代码
package com.example.ai.openai;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import okhttp3.*;
import okio.BufferedSource;

import java.io.IOException;
import java.util.function.Consumer;

/**
 * OpenAI 流式输出客户端
 */
public class OpenAiStreamClient {

    private static final String BASE_URL = "https://api.openai.com/v1";
    private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");

    private final String apiKey;
    private final String model;
    private final OkHttpClient httpClient;

    public OpenAiStreamClient(String apiKey, String model) {
        this.apiKey = apiKey;
        this.model = model;
        this.httpClient = new OkHttpClient.Builder()
            .connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
            .readTimeout(300, java.util.concurrent.TimeUnit.SECONDS)
            .build();
    }

    /**
     * 流式对话
     *
     * @param userMessage 用户消息
     * @param onToken     每个 token 的回调
     * @param onComplete  完成回调
     */
    public void streamChat(String userMessage,
                           Consumer<String> onToken,
                           Runnable onComplete) throws IOException {
        JSONObject requestBody = new JSONObject();
        requestBody.put("model", model);
        requestBody.put("stream", true);  // 开启流式
        requestBody.put("max_tokens", 2048);

        JSONObject message = new JSONObject();
        message.put("role", "user");
        message.put("content", userMessage);
        requestBody.put("messages", new Object[]{message});

        Request request = new Request.Builder()
            .url(BASE_URL + "/chat/completions")
            .header("Authorization", "Bearer " + apiKey)
            .header("Accept", "text/event-stream")
            .post(RequestBody.create(requestBody.toJSONString(), JSON_TYPE))
            .build();

        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("请求失败: " + response.code());
            }

            BufferedSource source = response.body().source();
            String line;

            while (!source.exhausted()) {
                line = source.readUtf8Line();
                if (line == null || line.isEmpty()) continue;

                // SSE 格式:data: {...}
                if (line.startsWith("data: ")) {
                    String data = line.substring(6);

                    // 流结束标志
                    if ("[DONE]".equals(data)) {
                        if (onComplete != null) onComplete.run();
                        break;
                    }

                    try {
                        JSONObject chunk = JSON.parseObject(data);
                        String content = chunk
                            .getJSONArray("choices")
                            .getJSONObject(0)
                            .getJSONObject("delta")
                            .getString("content");

                        if (content != null && !content.isEmpty()) {
                            onToken.accept(content);
                        }
                    } catch (Exception ignored) {
                        // 忽略解析异常
                    }
                }
            }
        }
    }
}

四、调用 Claude API

4.1 Claude 基础客户端

java 复制代码
package com.example.ai.claude;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Anthropic Claude API 客户端
 */
public class ClaudeClient {

    private static final Logger log = LoggerFactory.getLogger(ClaudeClient.class);

    private static final String BASE_URL = "https://api.anthropic.com/v1";
    private static final String API_VERSION = "2023-06-01";
    private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");

    private final String apiKey;
    private final String model;
    private final OkHttpClient httpClient;

    public ClaudeClient(String apiKey, String model) {
        this.apiKey = apiKey;
        this.model = model;
        this.httpClient = new OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(120, TimeUnit.SECONDS)
            .build();
    }

    /**
     * 简单对话
     */
    public String chat(String userMessage) throws IOException {
        return chatWithSystem(null, userMessage);
    }

    /**
     * 带系统提示词的对话
     */
    public String chatWithSystem(String systemPrompt, String userMessage) throws IOException {
        JSONObject requestBody = new JSONObject();
        requestBody.put("model", model);
        requestBody.put("max_tokens", 2048);

        // Claude 的 system 是独立字段,不在 messages 里
        if (systemPrompt != null && !systemPrompt.isEmpty()) {
            requestBody.put("system", systemPrompt);
        }

        JSONArray messages = new JSONArray();
        JSONObject userMsg = new JSONObject();
        userMsg.put("role", "user");
        userMsg.put("content", userMessage);
        messages.add(userMsg);
        requestBody.put("messages", messages);

        return doRequest(requestBody);
    }

    /**
     * 多轮对话
     * 注意:Claude 的 messages 不包含 system,system 单独传
     */
    public String chatWithHistory(String systemPrompt,
                                  List<Map<String, String>> messages) throws IOException {
        JSONObject requestBody = new JSONObject();
        requestBody.put("model", model);
        requestBody.put("max_tokens", 2048);

        if (systemPrompt != null && !systemPrompt.isEmpty()) {
            requestBody.put("system", systemPrompt);
        }

        requestBody.put("messages", messages);

        return doRequest(requestBody);
    }

    /**
     * 执行请求
     */
    private String doRequest(JSONObject requestBody) throws IOException {
        Request request = new Request.Builder()
            .url(BASE_URL + "/messages")
            .header("x-api-key", apiKey)                    // Claude 用 x-api-key
            .header("anthropic-version", API_VERSION)        // 必须指定版本
            .header("Content-Type", "application/json")
            .post(RequestBody.create(requestBody.toJSONString(), JSON_TYPE))
            .build();

        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                String errorBody = response.body() != null ? response.body().string() : "unknown";
                throw new IOException("Claude API 请求失败,状态码: " + response.code() + ",错误: " + errorBody);
            }

            String responseBody = response.body().string();
            JSONObject jsonResponse = JSON.parseObject(responseBody);

            // Claude 响应格式与 OpenAI 不同
            return jsonResponse
                .getJSONArray("content")
                .getJSONObject(0)
                .getString("text");
        }
    }
}

4.2 Claude 流式输出

java 复制代码
package com.example.ai.claude;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import okhttp3.*;
import okio.BufferedSource;

import java.io.IOException;
import java.util.function.Consumer;

/**
 * Claude 流式输出客户端
 */
public class ClaudeStreamClient {

    private static final String BASE_URL = "https://api.anthropic.com/v1";
    private static final String API_VERSION = "2023-06-01";
    private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");

    private final String apiKey;
    private final String model;
    private final OkHttpClient httpClient;

    public ClaudeStreamClient(String apiKey, String model) {
        this.apiKey = apiKey;
        this.model = model;
        this.httpClient = new OkHttpClient.Builder()
            .connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
            .readTimeout(300, java.util.concurrent.TimeUnit.SECONDS)
            .build();
    }

    /**
     * 流式对话
     */
    public void streamChat(String systemPrompt,
                           String userMessage,
                           Consumer<String> onToken,
                           Runnable onComplete) throws IOException {
        JSONObject requestBody = new JSONObject();
        requestBody.put("model", model);
        requestBody.put("max_tokens", 2048);
        requestBody.put("stream", true);

        if (systemPrompt != null && !systemPrompt.isEmpty()) {
            requestBody.put("system", systemPrompt);
        }

        JSONObject userMsg = new JSONObject();
        userMsg.put("role", "user");
        userMsg.put("content", userMessage);
        requestBody.put("messages", new Object[]{userMsg});

        Request request = new Request.Builder()
            .url(BASE_URL + "/messages")
            .header("x-api-key", apiKey)
            .header("anthropic-version", API_VERSION)
            .header("Accept", "text/event-stream")
            .post(RequestBody.create(requestBody.toJSONString(), JSON_TYPE))
            .build();

        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("请求失败: " + response.code());
            }

            BufferedSource source = response.body().source();
            String line;

            while (!source.exhausted()) {
                line = source.readUtf8Line();
                if (line == null || line.isEmpty()) continue;

                if (line.startsWith("data: ")) {
                    String data = line.substring(6);
                    try {
                        JSONObject chunk = JSON.parseObject(data);
                        String eventType = chunk.getString("type");

                        // 内容增量事件
                        if ("content_block_delta".equals(eventType)) {
                            String text = chunk
                                .getJSONObject("delta")
                                .getString("text");
                            if (text != null && !text.isEmpty()) {
                                onToken.accept(text);
                            }
                        }

                        // 消息结束事件
                        if ("message_stop".equals(eventType)) {
                            if (onComplete != null) onComplete.run();
                        }
                    } catch (Exception ignored) {
                    }
                }
            }
        }
    }
}

五、Spring Boot 完整集成

5.1 配置文件

yaml 复制代码
# application.yml
ai:
  openai:
    api-key: ${OPENAI_API_KEY:your-openai-key}
    model: gpt-4o
    base-url: https://api.openai.com/v1
  claude:
    api-key: ${CLAUDE_API_KEY:your-claude-key}
    model: claude-3-5-sonnet-20241022
    base-url: https://api.anthropic.com/v1

5.2 配置属性类

java 复制代码
package com.example.ai.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "ai")
public class AiProperties {

    private OpenAiProperties openai = new OpenAiProperties();
    private ClaudeProperties claude = new ClaudeProperties();

    @Data
    public static class OpenAiProperties {
        private String apiKey;
        private String model = "gpt-4o";
        private String baseUrl = "https://api.openai.com/v1";
    }

    @Data
    public static class ClaudeProperties {
        private String apiKey;
        private String model = "claude-3-5-sonnet-20241022";
        private String baseUrl = "https://api.anthropic.com/v1";
    }
}

5.3 统一 AI 服务层

java 复制代码
package com.example.ai.service;

import com.example.ai.claude.ClaudeClient;
import com.example.ai.claude.ClaudeStreamClient;
import com.example.ai.config.AiProperties;
import com.example.ai.openai.OpenAiClient;
import com.example.ai.openai.OpenAiStreamClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 统一 AI 服务
 * 封装 OpenAI 和 Claude,对外提供统一接口
 */
@Service
public class AiService {

    @Autowired
    private AiProperties aiProperties;

    private final ExecutorService executor = Executors.newCachedThreadPool();

    /**
     * OpenAI 普通对话
     */
    public String openaiChat(String message) throws IOException {
        OpenAiClient client = new OpenAiClient(
            aiProperties.getOpenai().getApiKey(),
            aiProperties.getOpenai().getModel()
        );
        return client.chat(message);
    }

    /**
     * Claude 普通对话
     */
    public String claudeChat(String message) throws IOException {
        ClaudeClient client = new ClaudeClient(
            aiProperties.getClaude().getApiKey(),
            aiProperties.getClaude().getModel()
        );
        return client.chat(message);
    }

    /**
     * OpenAI 流式对话(SSE)
     */
    public SseEmitter openaiStreamChat(String message) {
        SseEmitter emitter = new SseEmitter(120_000L);

        executor.submit(() -> {
            try {
                OpenAiStreamClient client = new OpenAiStreamClient(
                    aiProperties.getOpenai().getApiKey(),
                    aiProperties.getOpenai().getModel()
                );

                client.streamChat(
                    message,
                    token -> {
                        try {
                            emitter.send(SseEmitter.event().data(token));
                        } catch (IOException e) {
                            emitter.completeWithError(e);
                        }
                    },
                    () -> {
                        try {
                            emitter.send(SseEmitter.event().name("done").data("[DONE]"));
                            emitter.complete();
                        } catch (IOException e) {
                            emitter.completeWithError(e);
                        }
                    }
                );
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        });

        return emitter;
    }

    /**
     * Claude 流式对话(SSE)
     */
    public SseEmitter claudeStreamChat(String systemPrompt, String message) {
        SseEmitter emitter = new SseEmitter(120_000L);

        executor.submit(() -> {
            try {
                ClaudeStreamClient client = new ClaudeStreamClient(
                    aiProperties.getClaude().getApiKey(),
                    aiProperties.getClaude().getModel()
                );

                client.streamChat(
                    systemPrompt,
                    message,
                    token -> {
                        try {
                            emitter.send(SseEmitter.event().data(token));
                        } catch (IOException e) {
                            emitter.completeWithError(e);
                        }
                    },
                    () -> {
                        try {
                            emitter.send(SseEmitter.event().name("done").data("[DONE]"));
                            emitter.complete();
                        } catch (IOException e) {
                            emitter.completeWithError(e);
                        }
                    }
                );
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        });

        return emitter;
    }
}

5.4 Controller 层

java 复制代码
package com.example.ai.controller;

import com.example.ai.service.AiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.Map;

@RestController
@RequestMapping("/ai")
public class AiController {

    @Autowired
    private AiService aiService;

    /**
     * OpenAI 普通对话
     * POST /ai/openai/chat
     * Body: {"message": "你好"}
     */
    @PostMapping("/openai/chat")
    public Map<String, String> openaiChat(@RequestBody Map<String, String> body) throws Exception {
        String message = body.get("message");
        String reply = aiService.openaiChat(message);
        return Map.of("reply", reply);
    }

    /**
     * Claude 普通对话
     * POST /ai/claude/chat
     * Body: {"message": "你好"}
     */
    @PostMapping("/claude/chat")
    public Map<String, String> claudeChat(@RequestBody Map<String, String> body) throws Exception {
        String message = body.get("message");
        String reply = aiService.claudeChat(message);
        return Map.of("reply", reply);
    }

    /**
     * OpenAI 流式对话(SSE)
     * GET /ai/openai/stream?message=你好
     */
    @GetMapping(value = "/openai/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter openaiStream(@RequestParam String message) {
        return aiService.openaiStreamChat(message);
    }

    /**
     * Claude 流式对话(SSE)
     * GET /ai/claude/stream?message=你好
     */
    @GetMapping(value = "/claude/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter claudeStream(@RequestParam String message,
                                   @RequestParam(required = false) String system) {
        return aiService.claudeStreamChat(system, message);
    }
}

六、前端接收流式输出

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>AI 对话</title>
</head>
<body>
    <textarea id="input" placeholder="输入消息..." rows="3" cols="50"></textarea>
    <br>
    <button onclick="sendMessage()">发送(OpenAI)</button>
    <button onclick="sendMessageClaude()">发送(Claude)</button>
    <div id="output" style="border:1px solid #ccc; padding:10px; min-height:100px; white-space:pre-wrap;"></div>

    <script>
        function sendMessage() {
            const message = document.getElementById('input').value
            const output = document.getElementById('output')
            output.textContent = ''

            // 使用 EventSource 接收 SSE 流
            const eventSource = new EventSource(
                `/ai/openai/stream?message=${encodeURIComponent(message)}`
            )

            eventSource.onmessage = (event) => {
                if (event.data === '[DONE]') {
                    eventSource.close()
                    return
                }
                output.textContent += event.data
            }

            eventSource.onerror = () => {
                eventSource.close()
            }
        }

        function sendMessageClaude() {
            const message = document.getElementById('input').value
            const output = document.getElementById('output')
            output.textContent = ''

            const eventSource = new EventSource(
                `/ai/claude/stream?message=${encodeURIComponent(message)}`
            )

            eventSource.onmessage = (event) => {
                if (event.data === '[DONE]') {
                    eventSource.close()
                    return
                }
                output.textContent += event.data
            }

            eventSource.onerror = () => {
                eventSource.close()
            }
        }
    </script>
</body>
</html>

七、OpenAI vs Claude 接口差异对比

对比项 OpenAI Claude
认证方式 Authorization: Bearer {key} x-api-key: {key}
版本头 无需 anthropic-version: 2023-06-01
System 位置 messages 数组中 独立的 system 字段
响应格式 choices[0].message.content content[0].text
流式事件 choices[0].delta.content content_block_delta.delta.text
流结束标志 data: [DONE] event: message_stop

八、常见问题

Q1:API Key 如何安全存储?

bash 复制代码
# 方法1:环境变量(推荐)
export OPENAI_API_KEY=sk-xxx
export CLAUDE_API_KEY=sk-ant-xxx

# 方法2:application.yml 读取环境变量
ai:
  openai:
    api-key: ${OPENAI_API_KEY}

# 方法3:Nacos/Apollo 配置中心(生产推荐)

Q2:如何处理 API 限流(429 错误)?

java 复制代码
// 添加重试机制
public String chatWithRetry(String message, int maxRetries) throws IOException {
    for (int i = 0; i < maxRetries; i++) {
        try {
            return chat(message);
        } catch (IOException e) {
            if (e.getMessage().contains("429") && i < maxRetries - 1) {
                // 指数退避
                Thread.sleep((long) Math.pow(2, i) * 1000);
            } else {
                throw e;
            }
        }
    }
    throw new IOException("超过最大重试次数");
}

Q3:如何控制费用?

java 复制代码
// 设置 max_tokens 限制输出长度
requestBody.put("max_tokens", 500);  // 限制最多500个token

// 使用更便宜的模型处理简单任务
// gpt-4o-mini 比 gpt-4o 便宜约17倍
// claude-3-5-haiku 比 claude-3-5-sonnet 便宜约4倍

Q4:国内无法访问怎么办?

java 复制代码
// 配置代理
OkHttpClient client = new OkHttpClient.Builder()
    .proxy(new java.net.Proxy(
        java.net.Proxy.Type.HTTP,
        new java.net.InetSocketAddress("127.0.0.1", 7890)
    ))
    .build();

九、总结

功能 OpenAI Claude
普通对话
系统提示词
多轮对话
流式输出
Spring Boot 集成

两个 API 的核心逻辑相似,主要差异在于请求头格式响应体结构,掌握本文的封装方式后,可以轻松切换和扩展其他大模型 API(如 DeepSeek、通义千问等)。


📎 相关文章推荐

  • 《本地部署 Ollama + DeepSeek 完整指南》
  • 《Spring AI 实战:Java 接入大模型》
  • 《RAG 知识库问答系统实战》

原创不易,如果对你有帮助,点个赞再走~

关注我,持续分享 Java + AI 开发实战!

相关推荐
代码探秘者2 小时前
【算法篇】3.位运算
java·数据结构·后端·python·算法·spring
李白的粉2 小时前
基于springboot的新闻稿件管理系统
java·spring boot·毕业设计·课程设计·源代码·新闻稿件管理系统
零雲2 小时前
java面试:了解Redis的分布式限流算法么?
java·redis·面试
XiYang-DING2 小时前
【Java SE】JVM字符串常量池:位置、创建流程、对象个数与 `intern()`
java·开发语言·jvm
平生幻2 小时前
【数据结构】-复杂度
java·开发语言·数据结构
222you3 小时前
JUC当中的几个计数类
java·开发语言
xdl25993 小时前
如何快速搭建简单SpringBoot项目网页
java·spring boot·intellij-idea
k-l.3 小时前
【通过jdbc连接到kingbase数据库插入10w数据】
java·数据库
毕设源码-朱学姐3 小时前
【开题答辩全过程】以 基于java的书店用户管理系统的设计与实现为例,包含答辩的问题和答案
java·开发语言