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

5.5 KiB

사고 과정 표시 기능 분석 보고서

테스트 일시: 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:111call_model 함수는 LLM 청크를 내부에서 모두 누적한 뒤 단일 AIMessage로 반환한다:

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의 조건이 이를 차단한다:

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 — 인스턴스 변수 참조 오류

- if thinking and self._think_verbose:   # 항상 False (config 기본값)
+ if thinking and _think_verbose:        # 체크박스 값 사용

이 수정은 엣지케이스(content 스트리밍 없이 최종 AIMessage만 도달하는 경우)에서 체크박스를 올바르게 반영한다.
그러나 정상 스트리밍 경로에서는 content_started=True 조건이 여전히 블록을 막는다.


제안하는 추가 수정

stream_response에서 최종 AIMessage의 thinking을 저장해두고,
스트리밍 루프 종료 후 표시하는 방식이 가장 간단하다:

# 루프 내 - 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)에 의한 자연 편차로 보인다.
체크박스는 표시 여부만 제어하며 모델 동작 자체는 바꾸지 않는다.