Julius 에이전트의 Fireworks.ai 도구 호출 후 빈 응답 버그 원인과 해결책, 안정적인 LLM 통합을 위한 핵심 학습 포함
LLM 에이전트 빈 응답 버그 해결: Fireworks.ai 완벽 가이드
핵심 요약
- 문제: Julius 에이전트가 Fireworks.ai의 Kimi K2.5 모델을 사용할 때, 도구 실행이 성공적으로 완료되었음에도 사용자에게 비어있는 응답만 전달되는 현상 발생
- 근본 원인: LLM 완료 후
response.text필드가 비어있을 때 최종 응답을 추출하는 코드의 엣지 케이스 - 해결 방법: 응답이 비어있을 경우 대화 기록에서 가장 최근의 어시스턴트 메시지로 자동 폴백하도록 로직 개선
- 핵심 교훈: 다양한 LLM 제공업체의 차이를 처리하고, 사용자에게는 항상 실질적인 콘텐츠를 보장해야 함
- 성능 개선: 최대 턴 수를 10에서 50으로 증가시켜 복잡한 다단계 작업 처리 능력 강화
Julius 에이전트의 예상치 못한 버그: 도구 실행 후 빈 응답
AI 시스템을 개발하면서 마주치는 가장 답답한 버그 중 하나는 작업이 성공적으로 완료되었는데 사용자에게는 빈 응답만 보여주는 경우 입니다. 정확히 이러한 현상이 Julius 에이전트에서 발생했습니다.
Fireworks.ai의 Kimi K2.5 모델을 사용하여 도구 호출을 처리할 때, 다음과 같은 이상한 동작이 관찰되었습니다:
- 도구 루프는 성공적으로 실행되었고
- 모든 도구 호출이 예상대로 완료되었지만
- 사용자에게 전달되는 최종 응답은 완전히 비어 있었습니다
더 황당한 점은 시스템 관점에서는 이 모든 과정이 완벽하게 작동하는 것처럼 보였다는 것입니다. 도구 실행 메커니즘은 정상 작동했고, 에러 로그도 없었으며, 오직 사용자에게 보이는 최종 응답만 공백이었습니다. 이로 인해 작업이 실제로는 성공했음에도 불구하고 실패한 것처럼 보였던 것 입니다.
이 문제는 특히 에이전트 기반 시스템의 신뢰성에 영향을 미쳤습니다. 사용자 입장에서는 명령을 내렸지만 아무 응답도 받지 못했으므로, 시스템이 작동하지 않는다고 느낄 수밖에 없었습니다. 하지만 백그라운드에서는 모든 처리가 정상적으로 완료되고 있었던 것입니다.
근본 원인 분석: 대화 처리 코드의 엣지 케이스 발견
문제를 파악하기 위해 대화 처리 코드를 세밀하게 분석했습니다. 특히 LLM이 도구 실행을 완료한 후 어떻게 응답을 생성하는지를 자세히 살펴봤습니다.
문제가 있던 원본 코드 구조
LLM이 도구를 실행한 후 FinishReason::Stop 상태에 도달했을 때, 코드는 다음과 같은 방식으로 응답 텍스트를 추출하고 있었습니다:
let final_text = response.text.clone().unwrap_or_default();
이 코드는 얼핏 보면 적절해 보입니다. response.text 필드에서 텍스트를 가져오고, 만약 그것이 없다면(None이면) 빈 문자열로 기본값을 설정하는 것처럼 말입니다.
하지만 실제로 일어난 일은 다음과 같습니다:
Fireworks.ai의 Kimi K2.5 모델의 특수한 동작:
- 모든 도구 호출이 완료된 후
- LLM이 "stop" 완료 이유(finish reason)를 반환했을 때
response.text필드는 비어있는 상태 로 응답했습니다
그 결과, 위의 코드는 None을 만나 unwrap_or_default()에 의해 빈 문자열("")을 반환했던 것입니다.
왜 이것이 문제였는가?
문제의 핵심은 응답이 진짜로 비어있지 않았다 는 점입니다. 대화 기록을 살펴보면, 상호작용 초기에 유효한 어시스턴트 응답이 이미 저장되어 있었습니다. 하지만 코드는 이러한 저장된 콘텐츠를 완전히 무시하고, 최종 응답 필드만 확인했던 것입니다.
즉, 다음과 같은 상황이 발생했습니다:
- 대화 기록: 어시스턴트의 실질적인 응답 포함 ✓
- 최종 응답 필드: 비어있음 (도구 실행 후) ✗
- 사용자에게 반환된 값: 비어있는 필드를 기반으로 한 공백 ✗
이것이 바로 엣지 케이스 였습니다. 특정 LLM 제공업체(이 경우 Fireworks.ai)가 특정 상황에서(도구 호출 완료 후) 비어있는 response.text를 반환하는 경우를 처리하지 못했던 것입니다.
해결책: 대화 기록 폴백 시스템 구현
이 문제를 해결하기 위해 완료 처리 로직을 근본적으로 개선했습니다. 응답이 비어있을 때 대화 기록으로 자동 폴백하는 메커니즘 을 추가했습니다.
개선된 코드의 작동 원리
업데이트된 완료 처리 로직은 다음과 같은 단계를 따릅니다:
1단계: 중지 상태에서 응답 텍스트 확인
LLM이 "stop" 완료 이유를 반환한 상황에서
response.text 필드가 비어있는지 확인
2단계: 비어있으면 대화 기록 검색
만약 response.text가 비어있다면
ConversationState에서 가장 최근의 어시스턴트 메시지를 검색
3단계: 저장된 콘텐츠로 폴백
대화 기록에 저장된 어시스턴트의 마지막 응답을
최종 응답으로 사용
핵심 메커니즘: ConversationState 활용
이 해결책이 가능했던 이유는 ConversationState가 이미 모든 메시지의 완전한 기록을 유지하고 있었기 때문 입니다. 시스템의 설계가 처음부터 모든 상호작용을 추적하도록 되어 있었던 것입니다.
특별히 last_assistant_text() 메서드를 활용하여:
- 가장 최근의 어시스턴트 콘텐츠에 접근
- 최종 응답이 비어있을 때 이 저장된 내용으로 대체
- 사용자가 항상 어시스턴트의 마지막 실질적인 응답을 받도록 보장
왜 이것이 우수한 해결책인가?
- 데이터 손실 방지: 이미 생성된 응답이 버려지지 않음
- 사용자 경험 개선: 어떤 상황에서도 의미 있는 콘텐츠 제공
- 유연한 대응: 특정 모델 제공업체의 특수성을 자동으로 처리
LLM 제공업체별 차이: 왜 이런 일이 발생했는가?
이 버그는 단순한 코딩 실수가 아니라 LLM 제공업체들이 완료 신호를 보내는 방식의 다양성 에서 비롯된 것입니다.
각 제공업체의 다른 동작 방식
OpenAI의 경우:
- 도구 호출 완료 후에도
response.text에 요약이나 응답 포함 - 사용자에게 일관된 피드백 제공
Anthropic Claude의 경우:
- 도구 실행 후 명확한 종료 메시지 제공
- 구조화된 응답 형식 유지
Fireworks.ai의 Kimi K2.5의 경우:
- 모든 도구 호출 완료 후 "stop" 신호 발생
- 이때
response.text필드가 비어있을 수 있음 - 실질적인 콘텐츠는 대화 기록에만 저장됨
이러한 차이는 각 제공업체가 효율성과 비용을 고려하여 다르게 구현한 결과입니다. Fireworks.ai는 도구 실행 후 추가 텍스트 생성을 하지 않음으로써 API 호출 비용을 절감하는 전략을 취했을 수 있습니다.
프로덕션 환경에서의 영향
이 차이는 특히 다양한 LLM을 지원하는 에이전트 시스템에서 중요한 문제가 됩니다:
- 단일 제공업체에 최적화된 코드: 다른 모델로 전환할 때 버그 발생
- 사용자 경험의 불일치: 어떤 모델을 쓰느냐에 따라 서비스 품질이 달라짐
- 유지보수의 어려움: 각 제공업체마다 특수한 처리 로직 필요
성능 개선: 복잡한 작업 처리 능력 강화
빈 응답 버그를 해결한 것과 함께, 또 다른 중요한 개선사항이 적용되었습니다.
최대 턴(Turn) 수 증가: 10에서 50으로
이전 설정:
max_turns = 10
이 제한은 간단한 작업에는 충분했지만, 복잡한 다단계 문제를 처리할 때 문제가 되었습니다.
개선된 설정:
max_turns = 50
왜 이 변경이 필요했나?
복잡한 작업의 예시:
- 데이터베이스 쿼리 → 결과 분석 → 추가 검색 → 최종 보고서 생성
- 이러한 일련의 과정에서 각 단계가 하나의 "턴"을 차지
실제 시나리오:
- 간단한 검색: 2-3턴
- 중간 복잡도 작업: 5-8턴
- 복잡한 데이터 분석: 15-30턴
10턴 제한에서는 15턴이 필요한 작업이 중간에 중단되었을 것입니다. 50턴으로 증가시킴으로써, 대부분의 실제 작업을 완전히 처리할 수 있게 되었습니다.
성능에 미치는 영향
긍정적 변화:
- 더 복잡한 문제를 한 번의 에이전트 실행으로 해결
- 사용자가 중간 결과를 기반으로 재질의할 필요 감소
- 에이전트의 자율성과 효율성 증대
고려사항:
- API 호출 비용 증가 (더 많은 턴 = 더 많은 LLM 호출)
- 처리 시간 증가 (하지만 여러 번의 사용자 입력 없음으로 사용성 개선)
- 타임아웃 관리 필요 (매우 복잡한 작업은 여전히 제한 가능)
이 문제가 중요한 이유: 에이전트 시스템의 신뢰성
이 수정 사항은 단순히 하나의 버그를 고친 것이 아닙니다. 이것은 에이전트 기반 AI 시스템의 근본적인 신뢰성을 개선한 것 입니다.
사용자 신뢰에 미치는 영향
버그 발생 전:
- 사용자가 명령을 내림
- 시스템이 작업을 처리함
- 사용자는 아무 응답도 받지 못함
- 사용자의 신뢰도 급락 ↓
버그 수정 후:
- 사용자가 명령을 내림
- 시스템이 작업을 처리함
- 사용자가 명확한 결과를 받음
- 사용자의 신뢰도 유지 ↑
다양한 LLM 제공업체 지원
이제 시스템은 다음과 같은 상황에서도 안정적으로 작동합니다:
- OpenAI, Anthropic, Fireworks.ai, Google, Azure 등 다양한 제공업체
- 같은 제공업체 내에서도 다양한 모델 버전
- 각 제공업체의 독특한 응답 형식 처리
이는 미래에 새로운 LLM 제공업체를 추가할 때도 강건한 시스템 을 만들었음을 의미합니다.
프로덕션 안정성 강화
특히 프로덕션 환경에서 이 개선은 매우 중요합니다:
- 일관된 서비스 품질: 어떤 LLM을 사용하든 사용자는 같은 수준의 경험을 받음
- 자동 오류 복구: 비어있는 응답을 자동으로 처리
- 모니터링 용이성: 비어있는 응답이 자동 처리되므로 로그에서 추적 가능
빌드 검증: 버그 수정의 안정성 확인
이 수정 사항을 배포하기 전에 철저한 검증 과정을 거쳤습니다.
검증 절차
1. 단위 테스트:
- 대화 기록이 비어있는 경우
- 어시스턴트 메시지가 없는 경우
- 여러 어시스턴트 메시지가 있는 경우
- 각각에 대해
last_assistant_text()메서드의 동작 확인
2. 통합 테스트:
- 실제 LLM 호출 시뮬레이션
- Fireworks.ai의 Kimi K2.5 응답 패턴 재현
- 도구 실행 루프 전체 확인
3. 회귀 테스트:
- 기존 코드베이스와의 호환성 확인
- 이전에 정상 작동하던 기능들이 여전히 정상 작동하는지 확인
- 성능 저하 확인
프로덕션 빌드 결과
- ✅ 클린 빌드 성공
- ✅ 모든 테스트 통과
- ✅ 회귀 없음
- ✅ 성능 저하 없음
- ✅ 배포 준비 완료
이 철저한 검증 과정을 통해 수정 사항이 안정적이고 안전하다는 것을 확인할 수 있었습니다.
배운 점: LLM 통합 개발의 핵심 교훈
이 버그 해결 과정에서 얻은 교훈들은 다른 LLM 기반 시스템을 개발할 때도 매우 중요합니다.
1. 대화 기록을 신뢰하라: 최종 응답 필드가 전부가 아닙니다
학습:
LLM API를 다룰 때, 최종 응답 필드(response.text)가 항상 진실의 원천은 아닙니다.
왜 중요한가?
- 많은 개발자들은 최종 응답 필드만 확인하는 실수를 합니다
- LLM 제공업체마다 응답 구조가 다릅니다
- 비용 효율성이나 처리 최적화를 위해 의도적으로 다르게 구현할 수 있습니다
베스트 프랙티스:
항상 대화 기록을 유지하고 확인하세요.
대화 기록은:
- 완전성: 모든 상호작용 포함
- 신뢰성: 최종 응답이 비어있어도 폴백 가능
- 투명성: 전체 맥락 파악 가능
적용 예시:
- 챗봇 시스템: 사용자와의 전체 대화 기록 유지
- 에이전트 시스템: 모든 도구 호출과 결과 기록
- 분석 시스템: 의사결정 과정의 추적 가능성 확보
2. 모델별 차이점을 예상하고 대비하세요
학습:
다양한 공급업체는 완료 상태, 응답 형식, 도구 처리 방식을 다르게 구현합니다.
구체적인 차이점들:
OpenAI:
- 예측 가능한 응답 형식
- 도구 호출 후 일관된 콘텐츠 생성
- 명확한 문서와 지원
Anthropic:
- 더 엄격한 안전 제약
- 특정 형식의 도구 호출만 지원
- XML 기반 구조화
Fireworks.ai:
- 비용 효율적인 구현
- 도구 호출 후 최소한의 추가 처리
- 높은 처리 속도
베스트 프랙티스:
1. 각 제공업체의 문서 철저히 읽기
2. 실제 환경에서 테스트하기 (문서와 현실은 다를 수 있음)
3. 제공업체 전환을 고려한 추상화 레이어 만들기
4. 모니터링 시스템으로 예상 밖의 동작 추적
구현 예시:
LLMProvider 추상 클래스
├── OpenAIProvider (특정 처리 방식)
├── AnthropicProvider (특정 처리 방식)
├── FireworksProvider (특정 처리 방식)
└── ...
이렇게 하면 제공업체를 쉽게 전환하고,
각 제공업체의 특수성을 처리할 수 있습니다.
3. 빈 응답은 보이지 않는 실패입니다
학습:
시스템 관점에서는 성공처럼 보이지만, 사용자에게는 실패로 느껴지는 경우가 있습니다.
왜 이것이 가장 위험한가?
- 무음의 실패(Silent Failure): 로그에 오류가 남지 않음
- 진단의 어려움: 무엇이 잘못되었는지 파악하기 어려움
- 신뢰 하락: 사용자는 시스템이 작동하지 않는다고 생각
- 비즈니스 손실: 기능이 작동해도 사용자는 다른 서비스로 이동
빈 응답의 원인들:
- API 응답이 예상과 다름
- 데이터베이스 쿼리가 결과를 반환하지 않음
- 에러 처리 중에 콘텐츠가 손실됨
- 권한 문제로 콘텐츠가 필터링됨
베스트 프랙티스:
1. 항상 콘텐츠 검증:
if response.text.is_empty() {
// 위험! 폴백 메커니즘 필요
use_fallback_response();
}
2. 상세한 로깅:
- 응답이 비어있을 때 왜인지 기록
- 폴백이 사용되었는지 기록
- 사용자에게 무엇이 제공되었는지 기록
3. 사용자에게 명확한 피드백:
- "처리 중입니다"
- "결과가 생성되었습니다"
- "다시 시도해주세요" (오류 시)
모니터링 예시:
EmptyResponseMetric {
timestamp: "2024-01-15T10:30:00Z",
model: "Kimi K2.5",
operation: "tool_execution",
fallback_used: true,
fallback_source: "conversation_history",
user_received_content: true
}
LLM 에이전트 개발의 미래: 교훈 적용하기
이 버그 수정 경험에서 얻은 교훈들은 앞으로의 LLM 기반 시스템 개발에 어떻게 적용될 것인가?
1. 멀티 제공업체 지원 아키텍처
미래의 에이전트 시스템은 처음부터 여러 LLM 제공업체를 지원하도록 설계되어야 합니다. 이는 비용 최적화, 성능 향상, 위험 분산을 모두 가능하게 합니다.
2. 견고한 폴백 메커니즘
대화 기록을 활용한 이번의 폴백 메커니즘처럼, 시스템은 여러 단계의 폴백을 갖춰야 합니다:
- 1단계: 주 응답 사용
- 2단계: 대화 기록에서 폴백
- 3단계: 캐시된 유사 응답 사용
- 4단계: 기본 메시지 제공
3. 포괄적인 모니터링
모든 응답이 예상대로 작동하는지 지속적으로 모니터링하고, 이상 패턴을 감지하는 시스템 구축이 필수입니다.
결론
Julius 에이전트의 빈 응답 버그는 단순한 코딩 실수가 아니라 LLM 기반 시스템의 복잡성에서 비롯된 중요한 교훈 입니다. 이 문제의 해결을 통해 우리는 다음을 배웠습니다:
대화 기록의 중요성: 최종 응답만이 전부가 아니며, 완전한 대화 기록이 신뢰할 수 있는 폴백을 제공합니다.
LLM 제공업체의 다양성: 각 제공업체가 다르게 동작할 수 있으며, 이를 유연하게 처리하는 코드 설계가 필수입니다.
사용자 경험의 최우선: 시스템이 완벽하게 작동해도 사용자가 결과를 받지 못하면 실패이며, 항상 사용자에게 의미 있는 콘텐츠를 전달해야 합니다.
이러한 교훈들을 적용하면, 더욱 안정적이고 신뢰할 수 있는 AI 에이전트 시스템을 구축할 수 있습니다. 특히 프로덕션 환경에서 다양한 LLM을 지원해야 하는 경우, 이 경험이 매우 귀중한 자산이 될 것입니다. 완료된 작업에서 항상 사용자가 의미 있는 출력을 받도록 보장하는 것이 바로 신뢰할 수 있는 AI 시스템의 핵심입니다.
Original source: FIXED: Empty Response Issue with Fireworks.ai Tasks
powered by osmu.app