Chat Conversation
Message 1 from User
datadog 의 trace 에서 최근 발생한 에러 하나 찾아서, https://github.com/chequer-io/duplo 코드에서 문제되는 부분 찾아주세요.
Message 2 from QueryPie
Datadog의 trace에서 최근 에러를 찾고, 해당 에러와 관련된 코드 부분을 GitHub 저장소에서 찾아드리겠습니다.
먼저 Datadog에서 최근 trace 정보를 확인해보겠습니다.
첫 번째 trace 목록을 확인했습니다. 모든 trace들이 "status": "ok"로 표시되어 에러가 없는 것 같습니다. 에러가 있는 trace를 찾기 위해 에러가 포함된 trace를 검색해보겠습니다.
Datadog 로그에서 명확한 에러를 발견했습니다. duplo-chat 서비스에서 QpError (Abort) 에러가 발생했습니다. 이제 해당 GitHub 리포지토리에서 관련 코드를 살펴보겠습니다.
Datadog에서 최근 발생한 에러를 분석한 결과, duplo-chat 서비스에서 "Abort" 에러가 발생한 것을 확인했습니다. 이는 LangChain의 LangGraph 실행 중에 발생한 에러로, 사용자가 채팅을 중단했을 때 발생하는 정상적인 중단 처리와 관련이 있습니다.
에러 분석
에러 정보:
- 서비스: duplo-chat
- 에러 메시지: "QpError: Abort"
- 발생 위치: ChatInternalService의 chatInternal 메서드
- 에러 스택: LangChain의 PregelRunner._executeTasksWithRetry에서 발생한 "Error: Abort"
문제되는 코드 부분
에러가 발생하는 핵심 코드는 chat/packages/chat/src/modules/chat/internal/chat.internal.service.ts 파일의 다음 부분입니다:
private async *chatInternal(
context: ChatContext,
messages: BaseMessage[],
graphConfig: GraphConfig,
): AsyncGenerator<StreamEvent> {
let error: QpError | null = null;
try {
const stream = this.langchainGraphService.runStream(
messages,
graphConfig,
);
for await (const streamEvent of stream) {
// ... stream event 처리
}
} catch (e: unknown) {
error = makeQpError(e); // 여기서 "Abort" 에러가 QpError로 래핑됨
context.setError(error);
this.logger.error('[chatInternal] Error', error);
}
// ... 에러 처리
}
현재 에러 처리의 문제점
-
중복 처리: chat 메서드에서는 AbortError를 정상적으로 처리하지만, chatInternal에서는 같은 Abort 에러를 다시 에러로 래핑하여 처리합니다.
-
불필요한 에러 로깅: 사용자 중단은 정상적인 동작이므로 에러로 로깅할 필요가 없습니다.
해결 방법
chat.internal.service.ts의 chatInternal 메서드에서 AbortError를 별도로 처리해야 합니다:
private async *chatInternal(
context: ChatContext,
messages: BaseMessage[],
graphConfig: GraphConfig,
): AsyncGenerator<StreamEvent> {
let error: QpError | null = null;
try {
const stream = this.langchainGraphService.runStream(
messages,
graphConfig,
);
for await (const streamEvent of stream) {
// ... stream event 처리
}
} catch (e: unknown) {
// AbortError는 정상적인 중단이므로 에러로 처리하지 않음
if (e instanceof Error && e.name === 'AbortError') {
return; // 정상 종료
}
error = makeQpError(e);
context.setError(error);
this.logger.error('[chatInternal] Error', error);
}
// ... 에러 처리
}
이렇게 수정하면 사용자가 채팅을 중단했을 때 불필요한 에러 로깅과 처리를 방지할 수 있습니다. 현재는 상위 레벨의 chat 메서드에서만 AbortError를 처리하고 있지만, 하위 레벨에서도 동일한 처리가 필요한 상황입니다.
Message 3 from User
코드에서 해당 에러가 발생하기까지의 시퀀스 다이어그램을 간단히 그려줘
Message 4 from QueryPie
에러 발생까지의 시퀀스를 분석해서 다이어그램으로 그려드리겠습니다.
에러 발생 시퀀스 분석
이 다이어그램은 사용자가 채팅을 중단했을 때 "QpError: Abort" 에러가 발생하는 전체 흐름을 보여줍니다.
핵심 문제점
-
이중 에러 래핑: LangGraph에서 발생한
Error("Abort")가LangchainGraphInternalService.makeQpError()에서 한 번 래핑되고, 다시ChatInternalService.chatInternal()에서 또 다른QpError로 래핑됩니다. -
에러 타입 변경: 원래
AbortError였던 것이QpError로 변환되어, 상위 레벨의 AbortError 처리 로직(e.name !== 'AbortError')을 우회하게 됩니다. -
불필요한 로깅: 정상적인 사용자 중단 동작임에도 불구하고 에러로 로깅되어 Datadog에 에러로 기록됩니다.
해결 방안
Option 1: chatInternal에서 AbortError 직접 처리
catch (e: unknown) {
if (e instanceof Error && e.name === 'AbortError') {
return; // 정상 종료
}
// QpError인 경우 원본 에러 확인
if (e instanceof QpError && e.cause instanceof Error && e.cause.name === 'AbortError') {
return; // 정상 종료
}
error = makeQpError(e);
// ...
}
Option 2: LangchainGraphInternalService에서 AbortError 전파
// LangchainGraphInternalService에서
catch (e: unknown) {
if (e instanceof Error && e.name === 'AbortError') {
throw e; // QpError로 래핑하지 않고 원본 유지
}
const error = this.makeQpError(e);
throw error;
}
이렇게 수정하면 사용자 중단 시 불필요한 에러 로깅을 방지하고 정상적인 중단 처리가 가능합니다.