diff --git a/journey/plans/260319_rb8001_응답품질_톤과장_문맥유실_수정계획.md b/journey/plans/260319_rb8001_응답품질_톤과장_문맥유실_수정계획.md index 79d0799..8574ebb 100644 --- a/journey/plans/260319_rb8001_응답품질_톤과장_문맥유실_수정계획.md +++ b/journey/plans/260319_rb8001_응답품질_톤과장_문맥유실_수정계획.md @@ -6,9 +6,14 @@ tags: [rb8001, prompt, tone, context, quality, plan] ## 관련 문서 - [트러블슈팅: rb8001 프롬프트·의도분석·문맥응답 품질 문제](../troubleshooting/260317_rb8001_prompt_intent_context_response_quality_문제오픈.md) -- [리서치: 톤 과장·문맥 유실 전수 조사](../research/260319_rb8001_응답품질_톤과장_문맥유실_전수조사_리서치.md) +- [리서치: 톤 과장·문맥 유실 전수 조사](../research/260319_rb8001_응답품질_톤과장_문맥유실_전수조사_리서치.md) (§7 OpenClaw 레퍼런스 포함) - [프롬프트 DB 폐루프 P1 구현 완료](../worklog/260319_프롬프트DB_폐루프_P1_구현_및_검증완료.md) +### 레퍼런스 +- OpenClaw 소스: `/home/admin/robeing/reference/openclaw` — Context Engine 패턴, compaction 알고리즘 참고 +- 이번 계획에서 가져오는 것: OpenClaw `assemble()` 중 **provider별 형식 변환** 패턴만 +- 향후 별도 추적: compaction, session-memory, MEMORY.md → [OpenClaw compaction 아이디어](../ideas/260317_OpenClaw_스타일_컨텍스트_compaction_아이디어.md) + ### 상위 원칙 - [0_VALUE Coding Principles](https://github.com/happybell80/0_VALUE/blob/main/02_Governance/coding-principles.md) — #1 원인 직접 수정, #4 폴백 절제, #9 질문별 특례 금지 @@ -34,6 +39,9 @@ tags: [rb8001, prompt, tone, context, quality, plan] - Company X RAG 실패 응답 개선 (23에서 작업 중) - 의도 분류기 구조 변경 (별도 이슈) - Pydantic JSON 강제 (1~3단계 효과 확인 후 필요 시 추가) +- Context Engine 중간 계층 도입 (리서치 §7-5, 향후 과제) +- Compaction 도입 ([아이디어 문서](../ideas/260317_OpenClaw_스타일_컨텍스트_compaction_아이디어.md)에서 별도 추적) +- Session Memory / MEMORY.md 도입 (향후 과제) ## 구현 단계 diff --git a/journey/research/260319_rb8001_응답품질_톤과장_문맥유실_전수조사_리서치.md b/journey/research/260319_rb8001_응답품질_톤과장_문맥유실_전수조사_리서치.md index 70cd5a4..8e53828 100644 --- a/journey/research/260319_rb8001_응답품질_톤과장_문맥유실_전수조사_리서치.md +++ b/journey/research/260319_rb8001_응답품질_톤과장_문맥유실_전수조사_리서치.md @@ -197,7 +197,69 @@ class ChatResponse(BaseModel): - `length_instruction` 기본값을 "중간" → "짧게"로 변경 - 호칭 지시를 조건부로 (첫 대화에만 사용, 이후 생략) -## 7. 리스크 +## 7. OpenClaw 레퍼런스 분석 + +**소스:** `/home/admin/robeing/reference/openclaw` (commit `7f86be1037`) + +### 7-1. OpenClaw의 컨텍스트 처리 구조 + +``` +User Message + → SessionManager.open() — JSONL에서 전체 대화 이력 로드 + → sanitizeSessionHistory() — provider별 메시지 형식 정규화 + → contextEngine.assemble(messages, tokenBudget) — 토큰 예산 내로 조립 + → LLM 호출 + → afterTurn() — 배경에서 auto-compaction +``` + +핵심 파일: +- 컨텍스트 엔진 인터페이스: `src/context-engine/types.ts` +- Compaction 알고리즘: `src/agents/compaction.ts` +- 세션 이력 정규화: `src/agents/pi-embedded-runner/google.ts:520+` +- 컨텍스트 조립: `src/agents/pi-embedded-runner/run/attempt.ts:2130-2189` + +### 7-2. OpenClaw Context Engine의 핵심 메서드 + +| 메서드 | 역할 | +|--------|------| +| `assemble(messages, tokenBudget)` | 메시지를 토큰 예산 내로 조립, **provider별 형식 변환 포함** | +| `compact(sessionFile, tokenBudget)` | 오래된 메시지를 요약 압축 | +| `ingest(message)` | 새 메시지 추가 | +| `afterTurn()` | 턴 완료 후 배경 compaction | + +**플러그인 방식:** `registerContextEngine()`으로 엔진 교체 가능. + +### 7-3. OpenClaw의 provider 정규화 (`sanitizeSessionHistory`) + +OpenClaw는 OpenAI/Gemini/Anthropic 각각 다른 메시지 형식을 **하나의 정규화 단계**에서 통일한 후 handler에 전달. 구체적으로: +- 이미지 해상도/형식 정규화 +- thinking block 제거 (정책에 따라) +- tool_use/tool_result 페어링 수리 +- OpenAI용 function call 다운그레이드 +- 세션 간 메시지 경계 주석 + +**로빙과의 차이:** 로빙은 이 정규화 계층이 없음. OpenAI handler는 `previous_messages`, Gemini handler는 `recent_conversations`를 각자 읽어서 provider별 키 불일치가 발생. + +### 7-4. OpenClaw의 Compaction 알고리즘 + +1. 메시지를 토큰 기준으로 청크 분할 (SAFETY_MARGIN 1.2, 적응적 청크 비율) +2. 청크별 LLM 요약 생성 +3. 재귀적 병합 (부분 요약 → 통합 요약) +4. 식별자(UUID, URL, 파일명) 보존 강제 +5. 실패 시 점진적 fallback: 전체 요약 → 부분 요약 → 개수만 기록 + +### 7-5. 로빙에 적용할 것 (범위 분리) + +| 적용 대상 | 이번 계획 범위 | 향후 (별도 문서) | +|----------|-------------|---------------| +| provider별 형식 변환 | ✅ `recent_conversations` → `previous_messages` 변환 | Context Engine 중간 계층 도입 | +| 토큰 예산 관리 | ❌ | `assemble()` 패턴 도입 | +| Compaction | ❌ | [OpenClaw compaction 아이디어](../ideas/260317_OpenClaw_스타일_컨텍스트_compaction_아이디어.md)에서 추적 | +| 세션 메모리 | ❌ | daily log + MEMORY.md 도입 | + +**이번 수정의 위치:** OpenClaw의 `assemble()` 중 "provider별 형식 변환" 부분만 가져옴. `llm_service.py`의 `process_request()`에서 `recent_conversations`를 OpenAI `messages[]` 형식으로 변환. + +## 8. 리스크 | 리스크 | 대응 | |--------|------| @@ -205,8 +267,9 @@ class ChatResponse(BaseModel): | JSON 강제 시 자연스러움 저하 | 1~2단계 선적용 후 필요 시에만 3단계 | | 문맥 이력 5개 추가 시 토큰 증가 | 이미 Gemini에서 3개 사용 중, OpenAI 5개는 허용 범위 | | 감정 constraints 제거 시 감정 대응 약화 | neutral 외 감정에서는 유지 | +| Context Engine 없이 키 변환만 하면 또 다른 handler 추가 시 같은 문제 재발 | 향후 Context Engine 도입 시 해소. 이번은 최소 수정 원칙 (SSOT #2 Minimal Change) | -## 8. 구현 순서 +## 9. 구현 순서 | 순서 | 내용 | 종결 효과 | |------|------|----------| @@ -218,7 +281,7 @@ class ChatResponse(BaseModel): 1~4 완료 시 트러블슈팅 종결 가능. -## 9. 결론 +## 10. 결론 - 톤 과장의 원인: 시스템 프롬프트의 "도움이 되는 방향으로 조언", "액션 아이템 제시" 문구 + 감정 constraints의 무조건 주입 - 문맥 유실의 원인: OpenAI handler가 `previous_messages` 키를 읽는데, 이 키를 아무도 채우지 않음 diff --git a/journey/troubleshooting/260317_rb8001_prompt_intent_context_response_quality_문제오픈.md b/journey/troubleshooting/260317_rb8001_prompt_intent_context_response_quality_문제오픈.md index 78c9b41..140d94c 100644 --- a/journey/troubleshooting/260317_rb8001_prompt_intent_context_response_quality_문제오픈.md +++ b/journey/troubleshooting/260317_rb8001_prompt_intent_context_response_quality_문제오픈.md @@ -39,13 +39,15 @@ tags: [robeing, rb8001, prompt, intent, context, rag, troubleshooting] 6. 실제 응답 톤은 기본 시스템 프롬프트보다 감정 분석, 호칭(`이사님`), task별 `system_instruction` 누적의 영향을 크게 받습니다. 7. Company X 질문은 `try_companyx_grounding()`이 먼저 가로채며, 근거 부족 시 실패 응답으로 조기 종료될 수 있습니다. -## 원인 가설 +## 원인 (2026-03-19 리서치에서 확정) -1. 일반 대화와 RAG 질문이 지나치게 보수적인 실패 경로로 수렴합니다. -2. 짧은 후속 질문 컨텍스트 유지 로직이 실제 Slack 실사용 문장(`난 지금 서울이야` → `내가 지금 어디라고?`)에 안정적으로 작동하지 않습니다. -3. 호칭/감정 주입 프롬프트가 기본 답변 스타일보다 앞서 작동해 과한 의전형 문체를 만듭니다. -4. `weather`, `location`, `followup` 같은 생활형 질의를 별도 정책 없이 `general_chat` 또는 fallback 대화로 흘려보냅니다. -5. `companyx_rag` 경로는 근거 부족 시 실패 응답 보호는 되지만, 질문 적합도 재시도나 대체 응답 전략이 약합니다. +상세는 [리서치 §2](../research/260319_rb8001_응답품질_톤과장_문맥유실_전수조사_리서치.md) 참조. + +1. **톤 과장**: 시스템 프롬프트의 "항상 도움이 되는 방향으로 조언", "액션 아이템 제시" 문구가 매 응답마다 과잉 서비스를 유도. (`openai_handler.py:206~226`) +2. **문맥 유실**: OpenAI handler가 `previous_messages` 키를 읽는데, 이 키를 아무도 채우지 않음. `recent_conversations`는 로드되지만 OpenAI 형식으로 변환 안 됨. (`llm_service.py`, `openai_handler.py:64`) +3. **과잉 제안**: "묻지 않은 것은 제안하지 마라" 지시 부재 + 응답 길이 제한 없음. +4. **감정 constraints 무조건 주입**: neutral 감정에서도 "자연스럽게 중간 응답하세요"가 붙어 응답을 늘림. (`llm_service.py:262~268`) +5. `companyx_rag` 경로의 실패 응답 전략은 23에서 별도 작업 중 (이번 범위 제외). ## 코드 기준 구조 요약