DOCS/journey/research/260307_23서버_인프라점검_응답실패_원인확정_리서치.md

14 KiB

tags: [robeing, infra, operations, response-failure, research, root-cause]

260307 23서버 인프라점검 응답실패 원인확정 리서치

1) 목적

  • 대상 문제 1개만 원인 확정:
  1. 사용자가 23 서버 잘 돌아가는지 체크해줘, 아이피는 뭐야?라고 물었을 때 로빙이 실행형 답변 대신 재질문/무근거 단정을 반환한 문제
  • 목표: 이 실패가 어디서 만들어졌는지 코드, 실행 로그, 문서, 커밋 이력, 외부 1차 자료를 기준으로 단일 원인 사슬로 좁힌다.

2) 범위 / 비범위

  • 범위: rb8001의 메시지 처리 경로, 의도 분류, 질문 확장, 내부 LLM 호출, 시스템 프롬프트, 관련 리서치/트러블슈팅 문서
  • 비범위: 23 서버 실제 장애 여부, nginx/docker/postgresql 상태 자체, 51124 배포 문제, 다른 도메인 프로젝트

3) 사실(Facts)

3-1. 실제 실패 대화 사실

  • 사용자 발화 순서:
  1. 23 서버
  2. 잘 돌아가는 지 체크해줘
  3. 어떻게 돌아가는데? 아이피는 뭐야?
  • 로빙 응답 순서:
  1. 이사님, 23번 서버에 대해 어떤 정보가 필요하신가요?
  2. 이사님, 현재 인프라가 잘 작동하고 있다는 점을 확인했습니다. 혹시 더 구체적으로 확인하고 싶으신 부분이 있으신가요?
  3. 이사님, 현재 인프라가 잘 작동하고 있다는 점을 확인했습니다. 혹시 특정 서버의 IP 주소를 확인하고 싶으신가요?

3-2. 실행 환경 사실

  • rb8001 컨테이너 환경값:
    • DEFAULT_LLM_MODEL=gemini-2.5-flash-lite
    • INTENT_ENGINE=v1
    • INTENT_USE_COT=false
    • INTENT_USE_LANGGRAPH=false
    • ROBEING_ID=rb8001
    • STATE_SERVICE_URL=http://localhost:9024
  • 의미:
    • 이 시점의 질의 처리는 LangGraph 워크플로우가 아니라 message_service.py 직행 경로에서 수행됐다.

3-3. 실행 로그 사실 (2026-03-07 15:43-15:44 KST)

  • 23 서버
    • app.services.brain.decision_engine: 의도 매칭 실패 - UNKNOWN으로 처리: '23 서버'
    • app.services.message_service: [Question Expansion] '23 서버' → '23번 서버 상태를 확인해 드릴까요?'
    • Router result: intent.type='unknown', skills=[{'skill': 'llm', 'action': 'understand'}]
    • 최종 응답: 이사님, 23번 서버에 대해 어떤 정보가 필요하신가요?
  • 잘 돌아가는 지 체크해줘
    • app.services.brain.decision_engine: 두 차례 모두 UNKNOWN
    • Router result: intent.type='unknown', requires_clarification=True, action='understand'
    • 최종 응답: 현재 인프라가 잘 작동하고 있다는 점을 확인했습니다...
  • 어떻게 돌아가는데? 아이피는 뭐야?
    • app.services.brain.decision_engine: 두 차례 모두 UNKNOWN
    • Router result: intent.type='unknown', requires_clarification=True, action='understand'
    • 최종 응답: 현재 인프라가 잘 작동하고 있다는 점을 확인했습니다. 혹시 특정 서버의 IP 주소를 확인하고 싶으신가요?
  • 같은 로그 구간 어디에도 실제 서버 점검 실행, IP 조회, 서비스 상태 수집, 도구 호출 결과는 없다.

3-4. 질문 확장 코드 사실

  • rb8001/app/services/message_service.py:194-214
    • INTENT_USE_LANGGRAPH=false일 때
    • len(resolved_message.strip()) <= 10이면 LLMService.expand_question()을 호출한다.
  • rb8001/app/services/llm/llm_service.py:54-130
    • expand_question()는 최근 대화와 슬롯을 넣고 LLM에게 완전한 질문으로 확장을 요청한다.
    • 예시는 "어디서?" → "회의는 어디서 하나요?", "그거 어떻게 됐어?" → "이메일 발송은 어떻게 됐어요?"처럼 후속질문 중심이다.
  • 실제 로그에서 23 서버는 이 경로를 통해 23번 서버 상태를 확인해 드릴까요?로 바뀌었다.
  • 이 확장 결과는 사용자 요구를 실행 명령으로 구체화하지 않고, 로빙의 확인 질문 형태로 뒤집었다.

3-5. 의도 분류 코드 사실

  • rb8001/app/services/brain/decision_engine.py:29-74
    • 정의된 의도는 이메일, 뉴스, 슬랙, 캘린더, 웹검색, 문서분석, 스탯, 일반대화, 컨텍스트 후속질문 등이다.
  • rb8001/app/services/brain/decision_engine.py:116-214
    • intent_patterns서버 상태, 인프라 점검, IP 조회, 서비스 헬스체크에 해당하는 의도 패턴이 없다.
  • rb8001/app/services/brain/decision_engine.py:500-501
    • 매칭 실패 시 IntentType.UNKNOWN, 0.3을 반환한다.
  • rb8001/app/services/brain/decision_engine.py:613-614
    • IntentType.UNKNOWN의 스킬 시퀀스는 {'skill': SkillType.LLM, 'action': 'understand'} 하나다.

3-6. UNKNOWN 이후 LLM 일반채팅화 사실

  • rb8001/app/services/llm/internal_llm_service.py:35-43
    • task_type == "understand"chat으로 바꾼다.
    • show_stats, provide_infochat으로 바꾼다.
  • rb8001/app/router/router.py:176-180
    • action in ("understand", "chat", None, "")이면 내부 LLM chat 경로로 보낸다.
  • 결과적으로 UNKNOWN은 "추가 사실 확인을 위한 운영 프로토콜"이 아니라 "일반 대화 생성"으로 흘러간다.

3-7. 시스템 프롬프트 사실

  • rb8001/app/services/llm/gemini_handler.py:604-646
    • 금지 규칙: 앵무새 응답 금지, 무의미한 질문 반복 금지, 모르는 것을 아는 척하지 말 것, 절대 거짓말 금지
    • 직전 대화 맥락 최우선, 존댓말, 간결/실용 응답 규칙 포함
  • 그러나 아래 규칙은 없다:
    • 운영 점검 질의 시 가능한 범위의 사실 확인을 먼저 수행하라
    • 확인하지 않은 상태를 확인했습니다라고 단정하지 말라
    • IP, 상태, 헬스체크 같은 운영 질의는 고정 런북 순서로 답하라

3-8. 기존 내부 문서 사실

  • robeing/DOCS/journey/research/context_followup_question_solutions.md
    • 짧은 후속 질문 실패를 해결하기 위해 질문 확장을 도입해야 한다고 정리한다.
    • 범위는 어디서?, 언제?, 그거 어떻게 됐어? 같은 맥락 의존 후속질문이다.
  • 현재 문제의 23 서버, 잘 돌아가는 지 체크해줘, 아이피는 뭐야?는 "후속질문 복원"과 일부 겹치지만, 본질은 운영 점검 실행 요청이다.
  • 따라서 기존 문서는 현재 실패의 일부 배경은 설명하지만, 이 문제의 주 원인을 단독으로 설명하지는 못한다.

3-9. 커밋 이력 사실

  • 9c89e81 feat: Phase 1 LLM 질문 확장 기능 구현 (TDD)
    • message_service.py, llm_service.py에 질문 확장 로직이 추가됐다.
  • cf36baf feat: Phase 2 LLM 의도 분류 강화 구현 (TDD) - 맥락 포함 재분류
    • 메시지 처리 경로의 맥락 보강이 추가됐다.
  • 3b96ff0 fix: Phase 1 중복 실행 방지 - LangGraph 워크플로우 활성화 시 message_service의 질문 확장 건너뛰기
    • 질문 확장 기능이 현재 아키텍처에서 여전히 핵심 경로임을 보여준다.
  • 69e201c fix(llm): normalize 'show_stats'/'provide_info' to 'chat' in internal LLM task mapping to prevent validation errors
    • 정보성 액션을 일반 chat으로 흡수하는 방향이 강화됐다.
  • 확인된 커밋 이력 어디에도 인프라 점검, 서버 상태, IP 조회, 운영 런북 응답 의도 추가는 없다.

3-10. 외부 1차 자료 사실

  • OpenAI Model Spec (2025-09-12):
    • 모델은 불확실성을 숨기지 말고, 필요할 때만 질문해야 하며, 가능한 경우 사용 가능한 맥락과 도구를 활용해 직접 도움을 주는 방향을 권장한다.
  • OpenAI GPT-5.2 Prompting Guide:
    • 모호성이 치명적이지 않으면 합리적 가정을 두고 진행하고, 불필요한 재질문을 줄이는 방향을 권장한다.
  • 이 외부 원칙은 이번 문제의 직접 원인은 아니지만, 로빙의 현재 동작이 일반적인 에이전트 운영 원칙에도 어긋난다는 비교 기준이 된다.

3-11. 외부 에이전트 처리 흐름 및 OpenClaw 비교 사실

  • 일반적인 에이전트 운영 원칙은 아래 순서에 가깝다.
  1. 직전 대화 맥락을 유지한다.
  2. 가능한 범위에서 먼저 관측/도구 실행을 시도한다.
  3. 확인된 사실만 보고한다.
  4. 정말 필요한 경우에만 짧게 명확화 질문을 한다.
  • OpenClaw 공식 문서 요약 기준:
    • Gateway가 세션·라우팅·이벤트의 단일 진실 원천이다.
    • exec, browser, web_search, sessions_* 등 도구를 통해 실제 관측/실행이 가능하다.
    • 반복 무진전 루프는 tool-loop detection으로 제한한다.
  • 비교 결과:
    • OpenClaw 계열 흐름이라면 이 질의는 같은 세션의 운영 태스크로 유지하고, 가능한 status/IP 관측을 먼저 수행하거나, 도구가 없으면 그 부재를 명시하는 쪽이 자연스럽다.
    • 이번 로빙 응답처럼 재질문과 가짜 안정 문구로 빈칸을 메우는 방식은 외부 에이전트 일반 흐름과도 어긋난다.

4) 해석(Interpretation)

4-1. 1차 원인: 문제 유형을 잘못 분류했다

  • 현재 구조는 짧은 표현을 보면 먼저 후속질문 복원 문제로 다룬다.
  • 그러나 23 서버와 뒤이은 발화는 실제로는 운영 점검 실행 요청이다.
  • 즉, 이 실패의 시작점은 사용자의 문제를 인프라 운영 태스크로 잡지 못하고 짧은 애매한 질문으로 취급한 것이다.

4-2. 2차 원인: 질문 확장이 사용자 의도를 실행형으로 보강하지 못하고 확인질문형으로 변형했다

  • expand_question()의 설계 목적은 짧은 후속질문 복원이다.
  • 이 설계를 23 서버 같은 운영 명령에 그대로 적용하면서, 사용자 명령이 23번 서버 상태를 확인해 드릴까요?라는 로빙 발화형 문장으로 바뀌었다.
  • 이 변형은 의도 분류 정확도를 높이지 못했고, 오히려 실행 요청을 또 다른 확인 질문으로 약화시켰다.

4-3. 3차 원인: 인프라 점검 전용 의도와 실행 경로가 없다

  • DecisionEngine에는 웹검색, 이메일, 캘린더 등은 있어도 server_status_check, infra_health_check, server_ip_lookup 같은 의도가 없다.
  • 그래서 23 서버, 잘 돌아가는 지 체크해줘, 아이피는 뭐야?는 모두 UNKNOWN으로 떨어진다.
  • 이 단계에서 이미 로빙은 서버 점검을 수행할 구조적 수단을 잃는다.

4-4. 4차 원인: UNKNOWN이 일반채팅으로 흘러가면서 무근거 응답을 생성했다

  • UNKNOWN -> understand -> chat 경로는 실행보다 대화 생성에 최적화되어 있다.
  • 이 경로는 "도구를 써서 점검"이 아니라 "맥락상 그럴듯한 답을 생성"하는 쪽으로 작동한다.
  • 그래서 실제 확인이 없는데도 확인했습니다 같은 표현이 생성됐다.

4-5. 5차 원인: 시스템 프롬프트에 운영형 질의 프로토콜이 없다

  • 현재 프롬프트는 거짓말 금지, 무의미한 재질문 금지는 말하지만, 운영 질의에서 무엇을 먼저 해야 하는지까지는 고정하지 않는다.
  • 따라서 UNKNOWN -> chat 상황에서 모델이 보수적으로 확인 질문을 하거나, 반대로 근거 없는 안정 문구를 생성할 여지가 남아 있다.

4-6. 이번 문제의 본질

  • 이번 실패는 단일 모델 품질 문제가 아니다.
  • 원인 사슬은 운영 점검 의도 부재 -> 짧은 질문 확장 오적용 -> UNKNOWN -> 일반 chat 폴백 -> 운영 응답 프로토콜 부재다.
  • 따라서 증상은 대화 품질 저하처럼 보이지만, 본질은 라우팅/의도 설계/프롬프트 가드레일의 구조적 결손이다.

4-7. 외부 기준과의 차이

  • 외부 기준에서는 모르면 재질문이 기본이 아니라 할 수 있는 관측을 먼저 하고, 그 뒤에도 부족할 때만 질문이 기본에 가깝다.
  • 이번 로빙은 UNKNOWN 이후 관측 우선 흐름으로 가지 않고 일반 대화 생성으로 이동했다.
  • 그래서 이번 문제는 단순한 의도 분류 실패가 아니라, 에이전트 기본 동작 순서 자체가 뒤집힌 사례로 볼 수 있다.

5) 결론(Conclusion)

  • 확정 원인:
  1. 23 서버 계열 요청을 처리할 인프라 운영 의도와 실행 경로가 없다.
  2. 짧은 질문 확장 기능이 이 요청을 후속질문 복원 문제로 잘못 취급했고, 실제로는 확인질문형 문장으로 왜곡했다.
  3. UNKNOWN이 내부 LLM 일반채팅으로 정규화되면서, 점검 없는 상태 단정 응답이 생성됐다.
  4. 시스템 프롬프트와 운영 규칙에 "운영 점검 질의는 사실 확인을 먼저 수행한다"는 강한 가드레일이 없다.
  • 따라서 이 문제는 질문을 한 번 잘못 이해했다가 아니라, 현재 robeing 아키텍처가 운영 점검 요청을 독립된 작업 타입으로 모델링하지 않은 데서 생긴 구조적 실패다.

6) 미확정 항목(Unresolved)

  • 본 문서는 원인 확정 문서이며, 수정안 우선순위와 구현안 비교는 별도 계획 문서에서 다루는 것이 맞다.
  • 다만 아래는 아직 이 문서에서 확정하지 않았다:
  1. 인프라 점검 의도를 FastPath 정규식으로 먼저 추가할지
  2. 별도 tool-calling 경로로 설계할지
  3. 프롬프트 가드레일만으로 일부 증상을 먼저 막을지

7) 비영향 범위

  • 이번 실패는 23 서버 자체 장애의 증거가 아니다.
  • 이번 실패는 INTENT_USE_LANGGRAPH=false 환경에서 발생했으므로 LangGraph 재진입 문제의 재발이 아니다.
  • 이번 실패는 gateway/nginx/postgresql의 실제 상태와 무관하게, rb8001의 질의 해석 구조 안에서 재현 가능한 문제다.

관련 문서