LangChain4j의 핵심 언어 모델 인터페이스
LangChain4j 프레임워크에서 대형 언어 모델(LLM)과 통신할 때 주로 사용되는 두 가지 핵심 인터페이스는 ChatLanguageModel과 StreamingChatLanguageModel입니다. 이 두 인터페이스는 응답을 수신하는 메커니즘과 적합한 애플리케이션 아키텍처에서 명확한 차이를 보입니다.
아키텍처 및 동작 방식 비교
| 항목 | ChatLanguageModel (동기) | StreamingChatLanguageModel (비동기 스트리밍) |
|---|---|---|
| 실행 흐름 | 블로킹 방식의 동기 실행 | 논블로킹 방식의 비동기 실행 |
| 응답 수신 | 전체 텍스트 생성 완료 후 한 번에 반환 | 토큰 단위로 실시간 스트리밍 수신 |
| 콜백 지원 | 미지원 | 토큰 수신 및 완료 시점 콜백 핸들러 지원 |
| 주요 사용처 | 백그라운드 배치 작업, 단순 API 응답, 데이터 파이프라인 | 실시간 챗봇, UI 타자기 효과, 음성 비서 |
| 사용자 경험(UX) | 전체 생성 시간 동안 대기 필요 | 첫 토큰 생성 즉시 화면 표시 가능 |
구현 및 코드 예제
아래 예제는 Spring Boot 환경에서 OpenAI 모델을 연동하는 상황을 가정합니다.
Maven 의존성 및 환경 설정
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>1.0.0-beta3</version>
</dependency>
application.yml에 인증 정보를 구성하여 환경 변수로 API 키를 주입합니다.
langchain4j:
open-ai:
chat-model:
api-key: ${OPENAI_API_KEY}
1. 동기식 응답 처리 (ChatLanguageModel)
전체 응답이 생성될 때까지 스레드를 블로킹하며, 배치 처리나 내부 로직 수행에 적합합니다.
import dev.langchain4j.model.chat.ChatLanguageModel;
import org.springframework.stereotype.Component;
@Component
public class SynchronousChatService {
private final ChatLanguageModel languageModel;
public SynchronousChatService(ChatLanguageModel languageModel) {
this.languageModel = languageModel;
}
public String fetchCompleteAnswer(String prompt) {
// 모델이 전체 응답을 생성할 때까지 현재 스레드가 대기함
return languageModel.generate(prompt);
}
}
2. 비동기 스트리밍 응답 처리 (StreamingChatLanguageModel)
토큰이 생성되는 즉시 콜백을 통해 전달받으며, 실시간 인터랙션이 필요한 서비스에 최적화되어 있습니다.
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.StreamingResponseHandler;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.output.Response;
import org.springframework.stereotype.Component;
import java.util.StringJoiner;
@Component
public class AsynchronousStreamService {
private final StreamingChatLanguageModel streamModel;
public AsynchronousStreamService(StreamingChatLanguageModel streamModel) {
this.streamModel = streamModel;
}
public void processStreamedTokens(String query, StreamingResponseHandler<AiMessage> handler) {
// 외부에서 주입된 핸들러를 통해 스트리밍 로직 위임
streamModel.generate(query, handler);
}
// 내부 로직용 커스텀 핸들러 실행 예시
public void executeWithCustomHandler(String query) {
StringJoiner tokenJoiner = new StringJoiner("");
streamModel.generate(query, new StreamingResponseHandler<AiMessage>() {
@Override
public void onNext(String token) {
// 각 토큰이 도착할 때마다 실행 (예: WebSocket으로 클라이언트에 전송)
System.out.print(token);
tokenJoiner.add(token);
}
@Override
public void onComplete(Response<AiMessage> response) {
System.out.println("\n[최종 완료] 누적 응답: " + tokenJoiner);
}
@Override
public void onError(Throwable error) {
System.err.println("스트리밍 중 오류 발생: " + error.getMessage());
}
});
}
}
아키텍처 설계 가이드라인
- 단순 API 및 데이터 파이프라인:
ChatLanguageModel을 사용하여 코드 흐름을 단순하게 유지하고, 응답을 파싱하거나 데이터베이스에 저장하는 후속 작업을 동기적으로 수행합니다. - 웹 및 모바일 클라이언트 인터페이스:
StreamingChatLanguageModel을 선택하고, Server-Sent Events(SSE) 또는 WebSocket과 결합하여 클라이언트에게 타자기 효과를 제공하는 것이 현대적인 LLM 애플리케이션의 표준입니다. - 비동기 통합: 스트리밍 모델의 비동기 특성을 활용하려면 Reactor(WebFlux)나
CompletableFuture와 같은 반응형 프로그래밍 패러다임과 함께 설계하여 시스템의 처리량(Throughput)을 극대화해야 합니다.