Chat and Language Models
Chat and Language Models (채팅·언어 모델)
LangChain4j 로 LLM 을 다루는 가장 기본적인 API 를 살펴볼게요. 여기 나오는 ChatModel 은 저수준 API 로 가장 강력하고 유연해요. 상위 레벨의 AI Services 는 이 위에 쌓여 있어요.
출처: 공식문서
두 가지 LLM API 유형
LanguageModel—String을 입력받아String을 반환하는 아주 단순한 API. 이제 점차 채팅 API 로 대체되고 있어요.ChatModel— 여러ChatMessage를 입력받아 하나의AiMessage를 반환.
LangChain4j 는 앞으로 LanguageModel 에 대한 지원을 확장하지 않을 예정이에요. 새 기능은 전부 ChatModel API 로 만들어지죠. ChatModel 외에도 EmbeddingModel(텍스트→Embedding), ImageModel(이미지 생성·편집), ModerationModel(유해 콘텐츠 검사), ScoringModel(쿼리 대비 텍스트 관련도 점수·랭킹, RAG 에 유용) 모델 타입이 있어요.
ChatModel API
ChatModel 에는 편의용 chat(String) 메서드가 있는데, String 을 UserMessage 로 감쌀 필요 없이 빠르게 시험해볼 수 있어요.
public interface ChatModel {
String chat(String userMessage);
...
}
여러 메시지를 다루는 오버로드와 요청을 세밀하게 커스터마이즈하는 chat(ChatRequest) 도 있어요:
ChatResponse chat(ChatMessage... messages);
ChatResponse chat(List<ChatMessage> messages);
ChatResponse chat(ChatRequest chatRequest);
ChatRequest 는 빌더로 model name, temperature, topP, topK, frequencyPenalty, presencePenalty, maxOutputTokens, stopSequences, toolSpecifications, toolChoice, responseFormat 등을 지정할 수 있어요. parameters(...) 로 공통·프로바이더별 파라미터를 한 번에 넣을 수도 있어요.
ChatMessage 유형
UserMessage— 사용자의 메시지.contents()(텍스트 또는 멀티모달),name(),attributes()(모델에 전송되지 않고ChatMemory에 저장되는 속성)를 포함.AiMessage— AI 가 생성한 메시지.text(),thinking()(추론 내용),toolExecutionRequests()(도구 실행 요청),attributes()포함.ToolExecutionResultMessage—ToolExecutionRequest의 실행 결과.SystemMessage— 시스템 메시지. 개발자가 역할·행동·답변 스타일 지시를 넣어요. LLM 은 다른 메시지보다SystemMessage에 더 주의를 기울이도록 훈련돼 있어, 엔드 유저가 자유롭게 주입하지 못하게 하는 게 좋아요. 보통 대화의 시작에 위치.CustomMessage— 임의 속성을 담는 커스텀 메시지. 이를 지원하는ChatModel구현(현재는 Ollama 만)에서만 사용 가능.
다중 턴 대화
LLM 은 본질적으로 무상태(stateless)라 대화 상태를 직접 관리해야 해요. 다중 턴 대화를 만들려면 이전 메시지를 계속 함께 넘겨야 해요:
UserMessage firstUserMessage = UserMessage.from("Hello, my name is Klaus");
AiMessage firstAiMessage = model.chat(firstUserMessage).aiMessage(); // Hi Klaus, how can I help you?
UserMessage secondUserMessage = UserMessage.from("What is my name?");
AiMessage secondAiMessage = model.chat(firstUserMessage, firstAiMessage, secondUserMessage).aiMessage(); // Klaus
이런 메시지 관리를 손으로 하는 건 번거로워요. 그래서 ChatMemory 개념이 있어요.
ChatResponse 는 AiMessage 외에도 ChatResponseMetadata 를 담는데, 여기에 TokenUsage(입력 토큰·출력 토큰·합계 — 호출 당 비용 계산에 필요)와 FinishReason(생성 중단 사유, 보통 FinishReason.STOP)가 포함돼요.
멀티모달
UserMessage 는 List<Content> 를 가질 수 있고 Content 구현으로 TextContent, ImageContent, AudioContent, VideoContent, PdfFileContent 가 있어요. 이미지 URL 을 원격으로 넘기거나:
UserMessage userMessage = UserMessage.from(
TextContent.from("Describe the following image"),
ImageContent.from("https://example.com/cat.jpg")
);
ChatResponse response = model.chat(userMessage);
Base64 바이너리로도 넘길 수 있어요. ImageContent.from(base64Data, "image/jpg"). DetailLevel enum(LOW/HIGH/AUTO)으로 모델이 이미지를 처리하는 방식을 제어할 수도 있어요.
논블로킹 호출 (실험적)
ChatModel 은 호출 스레드를 블로킹하지 않고도 답할 수 있어요:
CompletableFuture<ChatResponse> future = model.chatAsync(chatRequest);
future 를 취소하면 호출자를 풀어주고 진행 중인 HTTP 호출을 중단하려 시도해요. future 는 모델의 transport 스레드에서 완성되므로, 명시적 executor 없이 붙인 연속 작업은 거기서 돌아요 — 논블로킹으로 유지하거나 thenApplyAsync(fn, executor) 를 쓰세요. 논블로킹 경로를 구현하지 않은 프로바이더는 조용히 블로킹하는 대신 AsyncNotSupportedException 으로 뚜렷하게 실패해요. 전체 그림은 Non-blocking and Reactive 참고.
Kotlin 확장
ChatModel Kotlin 확장은 코루틴 기반 비동기 처리를 제공해요. chatAsync(request: ChatRequest) 는 Dispatchers.IO 로 chat 을 감싸는 suspend fun 이고, chat(block: ChatRequestBuilder.() -> Unit) 은 타입 안전 빌더 DSL 로 비동기 실행을 제공해요.
Kotlin 사용자 주의(파괴적 변경): 이제
ChatModel은 Java 멤버로chatAsync(ChatRequest): CompletableFuture<ChatResponse>를 선언해요. Kotlin 에서 같은 시그니처의 멤버가 확장보다 우선하므로, 이전에suspend확장(반환ChatResponse)으로 해석되던 단일 인자 호출이 이제 멤버(반환CompletableFuture<ChatResponse>)로 해석돼요. 마이그레이션:model.chatAsync(request).await()을 쓰거나,Dispatchers.IO를 넘겨 여전히suspend확장을 선택하게 하면 돼요.
더 알아보기
- AI Services — 상위 레벨 LLM API
- 채팅 메모리 — 대화 상태 관리
- 도구 호출 —
toolExecutionRequests활용 - 지원되는 모든 LLM: integrations/language-models