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_info`도 `chat`으로 바꾼다.
- `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`의 질의 해석 구조 안에서 재현 가능한 문제다.

## 관련 문서
- [260307_23서버_인프라점검_질의에대한_로빙응답실패_분석.md](../troubleshooting/260307_23서버_인프라점검_질의에대한_로빙응답실패_분석.md)
- [context_followup_question_solutions.md](./context_followup_question_solutions.md)
- [260205_openclaw_official_docs_summary.md](./orchestration_tools/260205_openclaw_official_docs_summary.md)
- [writing-principles.md](../../book/300_architecture/312_writing-principles.md)
