nestjs에서 open ai의 gpt-3.5-turbo를 사용하여 streaming 통신 구현 과정을 기록하였습니다.
참고 :
https://github.com/openai/openai-node#streaming-responses
GitHub - openai/openai-node: The official Node.js / Typescript library for the OpenAI API
The official Node.js / Typescript library for the OpenAI API - GitHub - openai/openai-node: The official Node.js / Typescript library for the OpenAI API
github.com
1. nestjs에서 api를 구현하여 우선 console.log 찍어보기
@Controller('open-ai')
export class OpenAiController {
private readonly openAiLogger = new Logger('open-ai');
private openAi: OpenAI;
private readonly model = 'gpt-3.5-turbo';
constructor() {
this.openAi = new OpenAI({
apiKey: process.env.OPEN_AI_KEY,
});
}
@Post('/stream/message')
@UseGuards(PlatformGuard)
async chatGptStreamRequest(@Body() requestMessage: GptRequestMessageDto) {
const stream = await this.openAi.chat.completions.create({
model: this.model,
messages: requestMessage.messages,
stream: true,
});
for await (const part of stream) {
const content = part.choices[0]?.delta?.content || '';
console.log(content)
}
console.log('end');
}
}
위와 같이 controller를 구현하여 stream으로 데이터를 받는 인터페이스를 구현하였습니다.
아래와 같은 데이터로 요청을 보내보면,
{
"messages": [
{
"role": "user",
"content": "Say this is a test"
}
]
}
'tihs is a test'가 한 글자씩 전송되어 콘솔이 찍히는 것을 확인할 수 있습니다.
2. client에 보낼 때는 WebSocket을 통해 통신 구현
우선 Gateway를 구현합니다.
namespace로 client를 구분하기 위해 정규표현식으로 namespace를 정의했습니다.
@WebSocketGateway({ cors: '*', namespace: /\/open-ai-.+/ })
export class OpenAiGateway {
constructor() {}
@WebSocketServer()
server: Server;
massageEmit(content: string) {
this.server.emit('massage', content);
}
}
그 후 컨트롤러에서 post요청을 받으면 massageEmit을 호출하여 데이터를 client에 전송하였습니다.
@Controller('open-ai')
export class OpenAiController {
private readonly openAiLogger = new Logger('open-ai');
private openAi: OpenAI;
private readonly model = 'gpt-3.5-turbo';
constructor(private readonly openAiGateway: OpenAiGateway) { // 추가된 부분
this.openAi = new OpenAI({
apiKey: process.env.OPEN_AI_KEY,
});
}
@Post('/stream/message')
@UseGuards(PlatformGuard)
async chatGptStreamRequest(@Body() requestMessage: GptRequestMessageDto) {
const stream = await this.openAi.chat.completions.create({
model: this.model,
messages: requestMessage.messages,
stream: true,
});
for await (const part of stream) {
const content = part.choices[0]?.delta?.content || '';
this.openAiGateway.massageEmit(content); 추가된 부분
}
console.log('end');
}
}
3. Client는 React에서 socket.io-client 라이브러리를 사용하여 구현하였습니다
아래와 같이 Class를 구현하여,
import { io } from 'socket.io-client';
export default class ClientSocket {
private readonly socket;
constructor() {
this.socket = io('http://localhost:4000/open-ai-1');
}
close() {
this.socket.close();
}
getMessage(fn: React.Dispatch<React.SetStateAction<string>>) {
this.socket.on('massage', (arg) => {
console.log('massage', arg);
fn((prev) => prev + arg);
});
}
removeAllListeners() {
this.socket.removeAllListeners();
}
}
초기화 후 setState함수를 넘겨주어서 데이터를 업데이트하였습니다.
const socket = new ClientSocket(); // 1.소켓 초기화
export default function useSurveyForm() {
...
const [answer, setAnswer] = useState('');
...
const onClickButton = async () => {
socket.getMessage(setAnswer); // listener 활성화
await openAiController.chatGptStreamRequestStream([
{ role: 'user', content: 'say this is a test' },
]);
socket.removeAllListeners(); // listener 제거
};
...
}
... 은 생략된 코드들입니다.
해당 방식은 정답은 아니라고 생각합니다.
SSE 방식으로 클라이언트에 송신하고 EventSource로 데이터를 핸들링하는 방법도 있습니다.
그리고 저는 해당 방식으로 구현하는 과정에서 잘 되지 않아( 원인도 아직 정확히 모르는 중)
서버에서 SSE 방식으로 데이터를 받아서 WebSocket으로 이벤트로 넘겨서 Streaming 통신을 구현하였습니다.
위와 같이 구현하여 Streaming 통신 구현에 대응하였고, 이런 경우도 있다는 것을 공유하고자 글로 남기게 되었습니다.
연관 글
Server-Sent Events(SSE) in nestjs
Server-Sent Events(SSE) in nestjs
1. SSE란 무엇인가? SSE는 서버에서 클라이언트로 실시간으로 데이터를 전송하는 단방향 기술입니다. Websocket은 양방향으로 통신한다면 SSE는 서버에서 일방적으로 데이터를 전송할때 사용합니다
kimjunho97.tistory.com
'nestjs' 카테고리의 다른 글
Server-Sent Events(SSE) in nestjs (0) | 2023.10.15 |
---|---|
nestjs socket server https 접속 (0) | 2023.09.05 |
Nestjs 테스트코드 + Github action으로 EC2 자동화 배포 (0) | 2023.08.29 |
댓글