Files
youlbot/docs/thinking-feature-analysis.md
shinalok 145b0cc96f Implement Phase 12 feedback, Phase 13 Semantic Chunker, Phase 13-B Reranker, Bug 5 thinking fix
- Phase 12: FeedbackRepository + td_feedback 테이블, Gradio 👍/👎 이벤트, run_id 추적, LangSmith create_feedback() 연동
- Phase 13: 커스텀 _SemanticSplitter 제거 → langchain_experimental.SemanticChunker 교체, buffer_size/threshold_type 환경변수 적용
- Phase 13-B: RerankService (Cross-Encoder), RetrieverService.search()에 reranker 통합, tools.py as_retriever() → search() 전환
- Bug 5: mlx_chat_model enable_thinking 런타임 오버라이드, agent_service stream_mode=["messages","custom"] 이중 스트림, thinking 토큰 custom 이벤트로 emit
- ROADMAP: LLM 모델명 8B 반영, RAG에 Reranker 추가, 추천 진행 순서 갱신

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 17:41:36 +09:00

151 lines
5.5 KiB
Markdown

# 사고 과정 표시 기능 분석 보고서
**테스트 일시**: 2026-05-28
**테스트 질문**: "논문 결과가 어떻게 돼?"
**앱 버전**: http://localhost:7860
---
## 테스트 결과 요약
| 항목 | 사고 과정 OFF | 사고 과정 ON |
|------|-------------|------------|
| 총 소요 시간 | 200.5s | 233.7s |
| 1단계 (질문 분석) | 59.8s | 77.4s |
| 사고 과정 블록 표시 | 없음 | **없음 (버그)** |
| 최종 답변 내용 | 6개 섹션, 동일 | 6개 섹션, 동일 |
| 답변 차이 | **없음** | **없음** |
**결론: ON/OFF 체크박스가 현재 아무런 시각적 차이를 만들지 않는다.**
---
## 실제 응답 (두 경우 모두 동일)
```
[LangGraph → agent: 질문 분석 중] (59.84s)
문서 검색 중... ("어머니의 반응성 상호작용이 아동의 중심축 행동과 지능 및 다중지능 발달에 미치는 영향")
[LangGraph → tools: 도구 실행 중] (71.18s)
[결과: 3개 문서 반환 → agent 복귀]
[문서 검색: "어머니의 반응성 상호작용이 아동의 중심축 행동과 지능 및 다중지능 발달에 미치는 영향"]
→ [문서 1] 1, 81-99 어머니의 반응성 상호작용이 아동의 중심축 행동...
→ [문서 2] 김정미․정은주/ 어머니의반응성상호작용이...
→ [문서 3] 김정미․정은주/ 어머니의반응성상호작용이...
[LangGraph → agent: 검색 결과 반영 중] (132.91s)
[LangGraph → agent: 최종 답변 생성]
본 연구의 결과는 다음과 같이 요약할 수 있습니다:
1. 어머니의 반응성 상호작용과 아동의 중심축 행동 간의 관계
...
```
사고 과정 ON을 선택했을 때 기대되는 `[사고 과정]...[/사고 과정]` 블록이 나타나지 않음.
---
## 원인 분석
### 구조적 문제
```
LLM 생성 흐름:
<think>사고 내용...</think> → 최종 답변 텍스트
↓ ↓
AIMessageChunk AIMessageChunk
content="" content="본 연구의..."
additional_kwargs= additional_kwargs={}
{"thinking": "..."}
```
#### 핵심 병목: `call_model` 내부 누적 방식
`agent_service.py:111``call_model` 함수는 LLM 청크를 내부에서 모두 누적한 뒤 **단일 `AIMessage`로 반환**한다:
```python
async for chunk in llm_with_tools.astream(msgs, config):
thinking_acc += chunk.additional_kwargs.get("thinking", "")
content_acc += chunk.content or ""
...
return {"messages": [AIMessage(content=content_acc, additional_kwargs={"thinking": thinking_acc})]}
```
LangGraph `stream_mode="messages"`는 내부 LLM 청크를 외부로 통과시키지만,
사고 청크(`content=""`, `additional_kwargs={"thinking":"..."}`)는
빈 content로 인해 **LangGraph 스트림에서 필터링**되거나 전달되지 않는 것으로 보인다.
결과적으로 `stream_response`가 수신하는 청크:
| 수신되는 것 | 수신 안 되는 것 |
|-----------|--------------|
| content가 있는 `AIMessageChunk` | **thinking이 있는 `AIMessageChunk`** |
| 최종 `AIMessage` (thinking 포함) | |
#### 왜 최종 `AIMessage`의 thinking도 표시 안 되는가
`stream_response:221`의 조건이 이를 차단한다:
```python
elif node == "agent" and isinstance(chunk, AIMessage):
if not content_started and not thinking_open: # ← content_started=True면 전체 스킵
thinking = chunk.additional_kwargs.get("thinking", "")
if thinking and _think_verbose:
yield "\n[사고 과정]\n"
...
```
content `AIMessageChunk`들이 먼저 처리되면서 `content_started = True`가 세팅됨.
최종 `AIMessage`가 도착할 때는 이미 `content_started=True`라 전체 블록이 실행되지 않는다.
---
## 적용된 버그 수정 (2026-05-28)
### 수정 1: `agent_service.py:223` — 인스턴스 변수 참조 오류
```diff
- if thinking and self._think_verbose: # 항상 False (config 기본값)
+ if thinking and _think_verbose: # 체크박스 값 사용
```
이 수정은 엣지케이스(content 스트리밍 없이 최종 AIMessage만 도달하는 경우)에서 체크박스를 올바르게 반영한다.
그러나 정상 스트리밍 경로에서는 `content_started=True` 조건이 여전히 블록을 막는다.
---
## 제안하는 추가 수정
`stream_response`에서 최종 `AIMessage`의 thinking을 저장해두고,
스트리밍 루프 종료 후 표시하는 방식이 가장 간단하다:
```python
# 루프 내 - AIMessage 처리 시 thinking 저장
elif node == "agent" and isinstance(chunk, AIMessage):
if not thinking_open:
deferred_thinking = chunk.additional_kwargs.get("thinking", "")
if chunk.content and not content_started:
...
# 루프 종료 후
if deferred_thinking and _think_verbose:
yield "\n\n---\n**[사고 과정]**\n\n"
yield deferred_thinking
yield "\n\n**[/사고 과정]**\n"
```
> 단, thinking이 답변 뒤에 표시되는 UX 트레이드오프가 있다.
> 답변 전에 표시하려면 `call_model`을 리팩토링해 thinking을 먼저 스트리밍해야 한다.
---
## 소요 시간 비교 참고
ON이 OFF보다 약 33초 더 걸린 점은 주목할 만하다.
`enable_thinking=True`(config 설정)로 모델이 항상 thinking을 생성하므로,
ON/OFF 간 소요 시간 차이는 모델 비결정성(temperature)에 의한 자연 편차로 보인다.
체크박스는 표시 여부만 제어하며 모델 동작 자체는 바꾸지 않는다.