Spring AIとOpenAIを使用したチャットアプリケーション実装

# Spring AIとOpenAIを使用したチャットアプリケーション実装

## はじめに

この記事では、Spring AIとOpenAIのAPIを使用して、シンプルなチャットアプリケーションを実装する方法を説明します。Spring AIを使用することで、OpenAIのAPIを簡単に統合し、効率的なチャット機能を実装することができます。

## 1. プロジェクトのセットアップ

### 依存関係の追加

まず、`build.gradle`ファイルに必要な依存関係を追加します：

```groovy
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter'
    implementation 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
}
```

### アプリケーションの設定

`application.properties`ファイルにOpenAIのAPIキーを設定します：

```properties
spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.model=gpt-3.5-turbo
spring.ai.openai.temperature=0.7
```

## 2. チャットアプリケーションの実装

### チャットメッセージのモデル

```java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ChatMessage {
    private String role;
    private String content;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ChatRequest {
    private String message;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ChatResponse {
    private String response;
}
```

### チャットサービスの実装

```java
@Service
@Slf4j
public class ChatService {
    private final ChatClient chatClient;

    public ChatService(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    public ChatResponse chat(ChatRequest request) {
        try {
            String response = chatClient.prompt(request.getMessage())
                .call()
                .content();
            return new ChatResponse(response);
        } catch (Exception e) {
            log.error("チャット処理中にエラーが発生しました", e);
            throw new RuntimeException("チャット処理に失敗しました", e);
        }
    }
}
```

### コントローラーの実装

```java
@RestController
@RequestMapping("/api/chat")
@Slf4j
public class ChatController {
    private final ChatService chatService;

    public ChatController(ChatService chatService) {
        this.chatService = chatService;
    }

    @PostMapping
    public ResponseEntity<ChatResponse> chat(@RequestBody ChatRequest request) {
        log.info("チャットリクエスト受信: {}", request.getMessage());
        ChatResponse response = chatService.chat(request);
        return ResponseEntity.ok(response);
    }
}
```

## 3. 高度な機能の実装

### 会話履歴の保持

```java
@Service
@Slf4j
public class ChatHistoryService {
    private final Map<String, List<ChatMessage>> conversationHistory = new ConcurrentHashMap<>();

    public void addMessage(String sessionId, ChatMessage message) {
        conversationHistory.computeIfAbsent(sessionId, k -> new ArrayList<>())
            .add(message);
    }

    public List<ChatMessage> getHistory(String sessionId) {
        return conversationHistory.getOrDefault(sessionId, new ArrayList<>());
    }
}
```

### ストリーミングレスポンスの実装

```java
@Service
@Slf4j
public class StreamingChatService {
    private final ChatClient chatClient;

    public StreamingChatService(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    public Flux<String> streamChat(String message) {
        return chatClient.prompt(message)
            .stream()
            .map(response -> response.getResult().getOutput().getContent());
    }
}

@RestController
@RequestMapping("/api/chat")
public class StreamingChatController {
    private final StreamingChatService streamingChatService;

    @PostMapping("/stream")
    public Flux<String> streamChat(@RequestBody ChatRequest request) {
        return streamingChatService.streamChat(request.getMessage());
    }
}
```

## 4. エラーハンドリング

```java
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception e) {
        log.error("予期せぬエラーが発生しました", e);
        return ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(new ErrorResponse("エラーが発生しました", e.getMessage()));
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ErrorResponse {
    private String message;
    private String details;
}
```

## 5. 使用例

### cURLを使用したAPI呼び出し

```bash
curl -X POST http://localhost:8080/api/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "こんにちは、自己紹介をしてください"}'
```

### ストリーミングAPIの呼び出し

```bash
curl -X POST http://localhost:8080/api/chat/stream \
  -H "Content-Type: application/json" \
  -d '{"message": "長い文章を生成してください"}'
```

## 6. セキュリティ考慮事項

1. **APIキーの保護**
   - 環境変数での管理
   - 本番環境での適切な認証情報管理

2. **レート制限の実装**
   ```java
   @Service
   public class RateLimiter {
       private final Map<String, AtomicInteger> requestCounts = new ConcurrentHashMap<>();
       private static final int MAX_REQUESTS_PER_MINUTE = 60;

       public boolean isAllowed(String userId) {
           AtomicInteger count = requestCounts.computeIfAbsent(userId, k -> new AtomicInteger(0));
           return count.incrementAndGet() <= MAX_REQUESTS_PER_MINUTE;
       }
   }
   ```

## まとめ

この記事では、Spring AIとOpenAIを使用したチャットアプリケーションの基本的な実装方法を説明しました。実装した機能は以下の通りです：

- 基本的なチャット機能
- 会話履歴の保持
- ストリーミングレスポンス
- エラーハンドリング
- レート制限

これらの機能を組み合わせることで、実用的なチャットアプリケーションを構築することができます。

## 参考資料

- [Spring AI公式ドキュメント](https://spring.io/projects/spring-ai)
- [OpenAI APIドキュメント](https://platform.openai.com/docs/api-reference)
- [Spring Boot公式ドキュメント](https://spring.io/projects/spring-boot)