背景
- 社内で ChatGPT のようなサービスを展開したい
- OpenAI の API と互換性がある Azure OpenAI Service を使いたい
- 以下 AOAI
- フロントの UI がオープンソースで複数公開されているが、API キーを入力するものが多い
- 社内で展開する場合、API キーは隠蔽したい
- フロント>実装するAPIサーバ>AOAI API とう構成でバックエンドに API キーを隠蔽
- 中継 API サーバを立てるイメージ
- APIサーバを実装する際、ストリーム(SSE)対応したい
- それにより、レスポンスが徐々に生成される UX を実現できる
手順
Spring Boot プロジェクトの新規作成
-
https://start.spring.io/ を開き、Dependencies に「Spring Reactive Web」を指定
-
または build.gradle などに
spring-boot-starter-webflux
を指定dependencies { implementation 'org.springframework.boot:spring-boot-starter-webflux' }
ストリーム対応する AOAI の API を実行するためのクライアント準備する Config を作成
Config.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
public class AppConfig {
@Bean
public WebClient webClient() {
return WebClient.builder().build();
}
}
フロントからのリクエストを受け取り、AOAI の API を実行するコントローラを作成
Controller.java
package jp.co.saison.chatbackend.controller;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
@RestController
@CrossOrigin(origins = "${app.allowedOrigins}")
public class CompletionsController {
private final WebClient webClient;
public CompletionsController(WebClient webClient) {
this.webClient = webClient;
}
@PostMapping(value = "/api/completions", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> completionsForStream(@RequestBody String body) {
Flux<String> responseFlux = webClient.post()
.uri("Azure OpeanAI サービス API のエンドポイント")
.contentType(MediaType.APPLICATION_JSON)
.header("api-key", "APIキー")
.bodyValue(body)
.retrieve()
.bodyToFlux(String.class);
return responseFlux;
}
}
- フロントからのリクエストボディは stream が true であることが前提
- 戻り値の型は
Flux<String>
とする -
produces = MediaType.TEXT_EVENT_STREAM_VALUE
も指定