5分钟用Spring-AI接入OpenAI:手把手教你搞定

最近Spring-AI挺火的,我也试了一下,发现接入OpenAI确实简单。今天就来分享一下怎么用Spring-AI快速接入OpenAI,5分钟搞定不是吹的。

在这里插入图片描述

为什么选Spring-AI?

刚开始我也在想,直接调用OpenAI的API不就行了吗,为什么要用Spring-AI?用了之后发现,确实省事:

  1. 配置简单:不用自己封装HTTP调用
  2. 统一抽象:换模型改配置就行,代码不用改
  3. Spring集成:依赖注入、自动配置,用起来顺手
  4. 功能丰富:RAG、向量存储、文档处理都有

第一步:创建项目

用Spring Initializr创建个项目,或者直接用命令行:

curl https://start.spring.io/starter.zip \
  -d dependencies=web \
  -d javaVersion=17 \
  -d bootVersion=3.2.0 \
  -d groupId=com.moyulao \
  -d artifactId=spring-ai-demo \
  -o demo.zip

解压后,在pom.xml里加上Spring-AI的依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Spring AI OpenAI -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
        <version>1.0.0</version>
    </dependency>
</dependencies>

注意版本号,Spring-AI目前最新是1.0.0,要写清楚。

第二步:配置API Key

application.yml里配置OpenAI的API Key:

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}  # 从环境变量读取
      chat:
        options:
          model: gpt-3.5-turbo
          temperature: 0.7
          max-tokens: 1000

或者用application.properties

spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.chat.options.model=gpt-3.5-turbo
spring.ai.openai.chat.options.temperature=0.7
spring.ai.openai.chat.options.max-tokens=1000

重要提示: API Key不要写死在配置文件里,用环境变量。Windows设置环境变量:

set OPENAI_API_KEY=sk-xxxxx

Linux/Mac:

export OPENAI_API_KEY=sk-xxxxx

或者在IDE的运行配置里设置环境变量。

第三步:写个简单的Controller

配置好了,就可以直接用了:

@RestController
@RequestMapping("/api/chat")
public class ChatController {
    
    @Autowired
    private ChatClient chatClient;
    
    @PostMapping
    public ResponseEntity<String> chat(@RequestBody String message) {
        String response = chatClient.call(message);
        return ResponseEntity.ok(response);
    }
}

就这么简单!启动项目,测试一下:

curl -X POST http://localhost:8080/api/chat \
  -H "Content-Type: application/json" \
  -d "介绍一下Java"

应该就能收到AI的回复了。

更实用的例子:带上下文的聊天

实际应用中,通常需要保持对话上下文。可以这样写:

@Service
public class ChatService {
    
    @Autowired
    private ChatClient chatClient;
    
    private final Map<String, List<Message>> conversationHistory = new ConcurrentHashMap<>();
    
    public String chat(String sessionId, String userMessage) {
        // 获取历史消息
        List<Message> history = conversationHistory.getOrDefault(
            sessionId, 
            new ArrayList<>()
        );
        
        // 添加用户消息
        history.add(new Message("user", userMessage));
        
        // 构建上下文
        String context = buildContext(history);
        
        // 调用AI
        String response = chatClient.call(context);
        
        // 添加AI回复
        history.add(new Message("assistant", response));
        
        // 限制历史长度,避免Token太多
        if (history.size() > 20) {
            history = history.subList(history.size() - 20, history.size());
        }
        conversationHistory.put(sessionId, history);
        
        return response;
    }
    
    private String buildContext(List<Message> history) {
        StringBuilder sb = new StringBuilder();
        sb.append("你是一个友好的AI助手。以下是对话历史:\n\n");
        
        for (Message msg : history) {
            sb.append(msg.getRole())
              .append(": ")
              .append(msg.getContent())
              .append("\n");
        }
        
        return sb.toString();
    }
}

这里用了简单的内存存储,实际项目建议用Redis。

流式输出:提升用户体验

如果想让AI一个字一个字地回复,可以用流式输出:

@PostMapping("/stream")
public SseEmitter streamChat(@RequestBody String message) {
    SseEmitter emitter = new SseEmitter(60000L);
    
    CompletableFuture.runAsync(() -> {
        try {
            chatClient.stream(message)
                .doOnNext(chunk -> {
                    try {
                        String content = chunk.getResult().getOutput().getContent();
                        emitter.send(SseEmitter.event()
                            .data(content)
                            .name("message"));
                    } catch (IOException e) {
                        emitter.completeWithError(e);
                    }
                })
                .doOnComplete(emitter::complete)
                .doOnError(emitter::completeWithError)
                .subscribe();
                
        } catch (Exception e) {
            emitter.completeWithError(e);
        }
    });
    
    return emitter;
}

前端用EventSource接收:

const eventSource = new EventSource('/api/chat/stream?message=你好');

eventSource.onmessage = function(event) {
    const data = event.data;
    // 追加到聊天窗口
    appendToChat(data);
};

错误处理:必须要有

API调用可能失败,必须加上错误处理:

@RestController
@RequestMapping("/api/chat")
@Slf4j
public class ChatController {
    
    @Autowired
    private ChatClient chatClient;
    
    @PostMapping
    public ResponseEntity<?> chat(@RequestBody String message) {
        try {
            String response = chatClient.call(message);
            return ResponseEntity.ok(response);
        } catch (Exception e) {
            log.error("Chat failed", e);
            
            // 根据错误类型返回不同消息
            if (e.getMessage().contains("rate limit")) {
                return ResponseEntity.status(429)
                    .body("请求过于频繁,请稍后再试");
            } else if (e.getMessage().contains("invalid key")) {
                return ResponseEntity.status(401)
                    .body("API Key无效");
            } else {
                return ResponseEntity.status(500)
                    .body("服务暂时不可用,请稍后再试");
            }
        }
    }
}

更高级的用法:使用Prompt

Spring-AI支持更复杂的Prompt:

@Service
public class AdvancedChatService {
    
    @Autowired
    private ChatClient chatClient;
    
    public String chatWithPrompt(String userMessage) {
        Prompt prompt = Prompt.builder()
            .withSystemMessage("你是一个专业的Java开发专家,回答问题要简洁明了。")
            .withUserMessage(userMessage)
            .withOptions(ChatOptions.builder()
                .withTemperature(0.7)
                .withMaxTokens(1000)
                .build())
            .build();
        
        return chatClient.call(prompt).getResult().getOutput().getContent();
    }
}

完整示例:带参数的API

@RestController
@RequestMapping("/api/chat")
@Slf4j
public class ChatController {
    
    @Autowired
    private ChatClient chatClient;
    
    @PostMapping
    public ResponseEntity<ChatResponse> chat(@RequestBody ChatRequest request) {
        try {
            String response = chatClient.call(request.getMessage());
            
            ChatResponse chatResponse = new ChatResponse(
                response,
                System.currentTimeMillis()
            );
            
            return ResponseEntity.ok(chatResponse);
        } catch (Exception e) {
            log.error("Chat failed", e);
            return ResponseEntity.status(500)
                .body(new ChatResponse("服务暂时不可用", System.currentTimeMillis()));
        }
    }
    
    // 请求和响应类
    public record ChatRequest(String message) {}
    
    public record ChatResponse(String response, long timestamp) {}
}

常见问题

1. 依赖下载失败

如果Maven下载依赖失败,可能是网络问题,可以配置镜像:

<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
    </repository>
</repositories>

2. API Key无效

检查环境变量是否设置正确,或者直接在配置文件中测试(记得不要提交到Git)。

3. 超时问题

如果请求很慢,可能是网络问题,或者模型选择不对。GPT-4比GPT-3.5慢很多。

4. Token限制

注意Token限制,输入输出都要控制。可以在配置里设置max-tokens

总结

用Spring-AI接入OpenAI确实很简单,基本上就是:

  1. 加依赖
  2. 配API Key
  3. 注入ChatClient
  4. 调用

5分钟搞定不是吹的。当然,要做得完善,还需要加上错误处理、流式输出、上下文管理等,但基础接入就是这么简单。

如果你也在做AI应用,可以试试Spring-AI,确实省事。完整代码我放在GitHub上了,需要的同学可以看看。记得给个Star哈哈。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐