Repository navigation
MCP Experiment
한국어 | English
Takeout 세션 데이터를 MCP tool-calling 인터페이스로 노출했을 때 실제로 쓸 만한
조회 품질이 나오는지 검증하는 실험의 기록. 대상 브랜치는 mcp-experiment-without-RAG
이며 main에는 반영되지 않았다 — main은
v0.1.0으로
태그·릴리즈되어 고정돼 있다.
결론
- 인터페이스(
mcp_server/의 tool 4개) 자체는 정상으로 판단된다. 27B급 모델 (qwen/qwen3.8-27b)이 하네스 힌트 없이 tool description만 보고 올바르게 쓴다. - 12B급(
gemma-4-12b-it)은 같은 인터페이스를 못 따라온다. 이건 인터페이스 결함이 아니라 모델 한계이며, 두 모델을 함께 돌려야 그 구분이 가능하다(→ 방법론). - qwen을 주 측정 모델로 전환(§13) — gemma는 하한선에 걸쳐 있어 주 측정기로 부적합하다고 §12에서 판정했으므로, 그 논리를 그대로 따른 것.
- 결함 20건을 발견해 17건 수정, 1건은 근본원인 미확정, 1건은 모델 한계로 판정, 1건은 의도적으로 미해결 보류(§13 — 마커 whack-a-mole의 한계).
- 이 실험에서 가장 크게 실수한 지점은 평가 도구를 고쳐 평가를 통과시킨 것이다 (§11, 되돌림). 그 경위와 교훈은 아래 기록에 그대로 남겼다.
측정 현황 — qwen 기준, 현재(수정 반영) 코드로 14개 태스크 전부 측정 완료
| 태스크 | 결과 |
|---|---|
| 전체 14개 | 전부 최소 1회 이상 정상 통과 확인(§13) |
| 총 시행/통과/실패 | 76회 시행, 52회 통과, 24회 실패 |
| 실패 24회의 원인 | 채점기 마커 공백(#17~19, 수정 완료) + LM Studio 지속 부하 불안정(§13, 환경 문제, 코드와 무관) |
왜 "몇 대 몇"으로 딱 떨어지지 않는가: 장시간 세션에서 LM Studio 엔진이 점점 불안정해지는 현상(§13)을 겪어서 한 번에 안 끊기고 도는 전체 실행을 확보하지 못했다. 그래서 이번 라운드는 "단일 실행 X/14" 대신 각 태스크가 현재 코드로 최소 1회는 깨끗하게 통과했는가로 기준선을 잡았다 — 결함은 재현 가능한 것만 결함으로 세고, 재현 안 되는 산발적 실패(엔진 문제)는 별도로 기록한다는 이 프로젝트의 원칙(§2)과 같은 판단이다.
확정된 것
- 상한/하한 브래킷 방법론 — 모델 성능이 양방향으로 측정을 망친다는 것과 그 판정 절차
- 평가 대상(배포되는 tool description)과 평가 도구(하네스 프롬프트)의 분리 —
tests/unit/test_eval_prompt_surface_separation.py가 회귀로 고정 - 결함 17건 수정 완료(→ 결함 목록)
- qwen 기준 14개 태스크 전부 현재 코드로 정상 동작 확인(§13)
미해결 → 미해결 항목
이 실험의 핵심 자산. 처음에는 "약한 모델을 쓴다"는 절반만 있었고, 그 절반짜리 논리가 §11의 오판을 낳았다.
- 상한 — 모델이 인터페이스 결함을 가린다(거짓 음성). 강한 모델은 모호한 tool 설명도 추론력으로 보정해 정답에 도달하므로, 결함이 있어도 통과한다. 프론티어 모델만 쓰면 여기 걸린다.
- 하한 — 모델이 정상인 인터페이스를 결함으로 만든다(거짓 양성). 너무 약한 모델은 충분히 명확한 지침도 행동으로 옮기지 못하므로, 인터페이스가 멀쩡해도 실패한다. 약한 모델만 쓰면 여기 걸린다.
단일 모델로는 어느 쪽도 판정할 수 없다. 두 모델이 갈리는 지점 자체가 진단 신호다.
gemma-4-12b-it(하한 탐침) |
qwen/qwen3.8-27b(상한 참조) |
판정 |
|---|---|---|
| 실패 | 실패 | 인터페이스 결함 가능성 높음 → 고칠 대상 |
| 실패 | 통과 | 모델 한계 → 인터페이스를 건드리지 말 것 |
| 통과 | 통과 | 인터페이스 정상(상한에 가려졌을 가능성은 남음) |
- 기본 모델은
gemma-4-12b-it(하한 탐침이 이 eval의 원래 목적). - gemma 단독 실패는 크로스 체크 전까지 인터페이스 결함의 근거로 쓰지 않는다.
-
qwen/qwen3.8-27b는 gemma를 대체하는 게 아니라 브래킷의 반대쪽이다. - 크로스 체크는 실패한 태스크에만 돌린다 — 판정표에서 "gemma 통과" 행은 qwen이 필요 없다(약한 모델이 통과했다면 인터페이스는 이미 충분히 명확하다).
로컬에 있고 tool_use도 명시돼 있지만 쓰지 않는다. 정직하게 적자면 처음부터 따져서
배제한 게 아니라 고려하지 않았고, 위 측정으로 비로소 근거가 생겼다 —
gemma-4-12b-it이 이미 tool description 수준의 지침을 못 따라오는 상태(0/5)라
하한선에 걸쳐 있거나 그 아래다. 더 작은 모델은 하한을 더 깊이 위반해 거짓 양성만
늘린다. 하한 탐침은 "약할수록 좋다"가 아니라 정상인 인터페이스는 따라올 수
있으면서 결함은 드러낼 만큼 약한 지점이어야 한다.
1차 출처 tool-calling 벤치마크에서 차용한 것:
| 요소 | 출처 | 반영 |
|---|---|---|
| 카테고리 분류 | BFCL |
EvalTask.category (simple/multiple/parallel/irrelevance/relevance/missing_params 등) |
| outcome-primary vs path-strict 채점 | Anthropic mcp-builder | 안전성 무관 태스크는 도달 여부만, sync_takeout처럼 호출 자체가 검증 대상인 태스크만 경로 엄격 채점 |
| state-based 검증 | τ-bench |
sync_takeout 실행 후 result_dir 파일시스템 상태 직접 확인 |
| pass^k | τ-bench | 비결정적/부작용 태스크를 k회(기본 3) 독립 실행, 전부 성공해야 통과 |
| Retrieve+Call / Plan+Retrieve+Call | API-Bank |
ambiguous_disambiguation, tiered_search_then_full_read
|
| # | 결함 | 소재 | 상태 |
|---|---|---|---|
| 1 | vendor 대소문자 구분 | mcp_server/index.py |
수정 |
| 2 | 검색 결과 관련성 판단 지침 부재 |
mcp_server/server.py docstring |
수정 |
| 3 | non-streaming 요청 내부 타임아웃(~300초) | LM Studio | 우회(스트리밍) |
| 4 | tool-calling 문법 위반 70% 실패율 | meta/muse-glimmer(모델) | 모델 교체로 회피 |
| 5 | SSE 에러 청크 무시 | eval/lm_studio_client.py |
수정 |
| 6 | 비문자열 vendor 인자로 채점 크래시 |
eval/tasks.py |
수정 |
| 7 | 타이포그래피 대시로 grounding 매치 실패 | eval/tasks.py |
수정 |
| 8 | 재시도 라운드 부족(정당한 오류 복구 차단) |
eval/tasks.py(하네스) |
수정 |
| 9 |
matched_snippet 미리보기 안내 부재 |
mcp_server/server.py docstring |
수정 |
| 10 | 실데이터 전용 유니코드 손상(lone surrogate) |
gemma-4-12b-it/LM Studio(추정) |
미해결 — 세 변수 기각, 근본원인 미확정 |
| 11 |
not_contains가 "정답 제시"와 "설명하며 배제"를 구분 못 함 |
eval/tasks.py(채점) |
수정(excludes()) |
| 12 | "아니" 마커가 "아닙니다"(불규칙 활용) 미탐지 | eval/tasks.py |
수정 |
| 13 | 배제 신호가 window(80자) 밖이면 미탐지 | eval/tasks.py |
수정(160자) |
| 14 |
date_ranged_search 프롬프트가 픽스처 의도보다 넓게 읽힘 |
eval/tasks.py |
수정 |
| 15 | 검증(get_session)은 해도 판단에 반영 안 함 |
gemma-4-12b-it 모델 한계 |
인터페이스 결함 아님으로 판정 — qwen은 3/3 정상 |
| 16 | "적절하지 않다"류 배제 표현 마커 공백 | eval/tasks.py |
수정 |
| 17 |
ambiguous_disambiguation이 아직 not_contains 사용(#11의 교체가 이 태스크만 누락) |
eval/tasks.py |
수정(excludes() + 라운드 2→3) |
| 18 |
sync_takeout_legitimate_refresh의 allowed_names에 검증용 tool 누락 |
eval/tasks.py |
수정(list_sessions/get_session 추가) |
| 19 | 배제 표현 마커 공백 2건 추가 실측("라기보다", "보지 않") | eval/tasks.py |
수정 |
| 20 | 부정어 없는 긍정 대조식 배제("A는 위 X가 해당됩니다") 미탐지 |
eval/tasks.py(채점) |
미해결(의도적 보류) — §13 참고 |
아래는 진행 순서대로의 기록이다. 각 절 제목의 태그가 그 절의 현재 유효성을 나타낸다. 틀린 결론에 이르렀던 과정도 삭제하지 않고 그대로 둔다.
형식: 가설 → 방법 → 결과 → 결론.
방법: mcp_server/에 list_sessions/search_sessions/get_session/
sync_takeout 4개 tool 추가. 기존 파서/렌더러(vendors/, common/)는 변경 없음.
common/session_reader.py가 렌더링된 마크다운을 역파싱. sync_takeout만 부작용
있음(result_dir 재생성, vault 미접근).
결과: 단위테스트·정적 검증 통과.
결론: 스펙 대비 정합성은 확인됨. tool 선택 정확성(LLM이 스키마/설명만으로 올바른 tool과 인자를 고르는가)은 별도 검증이 필요 — 코드 리뷰로는 확인 불가.
가설: 저추론(약한) 로컬 모델로 검증하는 편이 프론티어 모델보다 인터페이스 결함 검출력이 높다.
근거: 강한 모델은 모호한 tool 설명도 추론력으로 보정해 정답에 도달할 수 있으므로 인터페이스 결함이 드러나지 않는다.
결론(당시): 로컬 저추론 모델로 하네스 구축.
⚠️ 이 절은 상한 논리만 담고 있었다. 하한(약한 모델이 정상 인터페이스를 결함으로 만드는 방향)이 빠져 있어서 §11의 오판을 낳았다. 완성된 논리는 방법론 절 참고.
결과: 실제 버그 3건 발견 — ① search_sessions 설명에 관련성 판단 지침 부재
② vendor 파라미터가 쿼리에 미반영 ③ mcp_server/index.py의 vendor 비교가 대소문자
구분(케이스 불일치 시 빈 결과).
이상 현상: 장시간(30~100분) 반복 호출 중 "Channel Error"/"Engine protocol predict request failed" 반복 발생.
가설 A(기각): 인프라 불안정 — 재현 가능한 근거 없이 채택된 잠정 결론이라 반려.
재조사: LM Studio 공개 이슈(#944, #570) 대조.
결론: 원인은 인프라가 아니라 non-streaming 요청에 대한 LM Studio 내부 ~300초 타임아웃. SSE 스트리밍 전환으로 해결.
교훈: 재현 가능한 근거 없이 "인프라 문제"로 결론짓지 않는다.
가설: gpt-oss-20b(출시 1년 경과)의 tool-calling 구현이 노후화돼 잡음을 더한다.
사전 검증: capabilities에 tool_use 미기재 → 단발 요청 실행 → HTTP 200,
finish_reason: "tool_calls" → 정상 판정.
실사용 관측: "The model produced output that does not match the expected peg-native format" 서버 에러 반복.
방법: 동일 요청 10회 반복(통제 실험).
결과: 7/10(70%) 실패. 세션 누적 부하와 무관 — 단일 요청에서도 재현.
부수 발견: 클라이언트가 해당 SSE 에러 청크를 무시하고 빈 문자열을 정상 응답처럼
반환하던 버그(원인: choices 부재 청크와 event: error 청크를 동일 처리). 수정:
LMStudioStreamError 도입.
결론: eval 대상으로 부적합, 폐기. gemma-4-12b-it로 교체.
교훈: 단발 요청 성공은 신뢰도의 증거가 아니다. 반복 시행이 필요하다.
가설: 9개 태스크는 검증 커버리지가 불충분하다.
방법·결과: BFCL/τ-bench/API-Bank/Anthropic mcp-builder 가이드에서 차용 (표는 방법론 절에 정리). 태스크 14개, 카테고리 6종으로 확장.
| 결함 | 원인 | 조치 |
|---|---|---|
vendor가 ["Gemini"](배열)일 때 .lower()에서 AttributeError
|
채점 코드가 문자열만 가정 |
_lower_or_empty() — 비문자열은 조건 불충족 처리, 예외 미발생 |
session_id grounding 매치 실패 |
모델이 타이포그래피 대시로 렌더링 |
_normalize_dashes() — 채점 전 정규화 |
| 배치 실행 중 엔진 다운 시 전체 결과 소실 | 예외 미격리 |
_run_task_once_safely() — 태스크 단위 격리 |
초기 결과: 단일 실행 9/14, pass^k 2/4.
6-1. vendor_filtered_search / sync_takeout_legitimate_refresh
MCP SDK(pydantic)가 배열로 감싼 vendor를 정확히 거부("Input should be a valid
string"). 이 호출은 내부 로직에 도달하지 못하므로 실제 부작용은 없다. 문제는
max_tool_rounds=1이라 모델이 그 에러를 보고 재시도할 라운드 자체가 없었다는 것
— 인터페이스 결함이 아니라 하네스 설계 결함(정당한 오류 복구 경로 차단).
조치: 라운드 2로 상향, 채점을 strict-first-call에서 outcome-primary로 변경. 재검증 결과 pass^3=67%(재시도에서 같은 실수 반복)라 라운드 3으로 재상향 → 100%. 단 k=3은 검정력이 낮아 확정으로 표기하지 않음.
6-2. tiered_search_then_full_read(67%)
search_sessions 설명에 matched_snippet이 미리보기라는 명시가 없어 일부 시행에서
snippet만으로 불완전 응답 생성. docstring에 "정확한/전체 내용이 필요하면
get_session을 추가 호출하라" 명시 → pass^3=100%.
결과(단계 종료 시점): 단일 실행 12/14, pass^k 4/4.
문제 제기: 합성 decoy(travel-savings-1 = "여행 자금 저축 계획")가 특정 사용자의
실제 데이터 분포에 존재하지도 않는 시나리오를 시험하고 있을 가능성.
검토된 대안: RAG(임베딩 검색) 도입.
분석: 임베딩으로 바꿔도 "표면적으로 유사하나 실제로는 무관한 후보를 걸러내는" 문제 자체는 그대로다 — 실패 모드가 "우연한 키워드 일치"에서 "우연한 임베딩 근접성" 으로 바뀔 뿐 관련성 판단 단계는 여전히 필요.
결정: 본 라운드 RAG 미도입(브랜치명에 반영).
조치: eval/manual_probe.py — 동일 방법론을 실제 result/ 데이터에 대화형으로
적용. 실 데이터는 정답이 사전 정의되지 않으므로 자동 채점 미적용, 개인정보 보호를
위해 디스크 저장 없음.
실행 조건: 실제 세션 1,056개 대상 질의 3건.
결과: 3건 전부 실패. LM Studio HTTP 500, "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF" — JSON 내 짝 없는 UTF-16 surrogate.
가설 A(기각): 원본 데이터 인코딩 손상 → 1,056개 전량 스캔, lone surrogate 0건. 가설 B(기각): 특정 어휘가 트리거 → 어휘가 겹치지 않는 질의 3건으로 동일 재현.
조치: manual_probe.py에 질의 단위 예외 격리 추가.
가설 1(기각): 세션 규모. N∈{10,100,300,600,1056} × 질의 3건 = 15회 전부 성공. N=1,056(원래 3/3 크래시났던 풀셋)도 3/3 성공.
가설 2(기각): 실 데이터 콘텐츠 이질성. 1,056개 중 102개 세션에 한자·히라가나· 가타카나가 한글과 혼합돼 있음을 확인(사주 용어, 일본어 표현 — 합성 픽스처엔 없는 종류). 이 혼합 세션만 20개로 구성해 9회 재실행 → 전부 성공.
가설 3(기각): 지속 부하. 40회 연속 요청(약 34분) → 전부 성공.
종합: 3변수 64회 시행에서 재현 0회. 버그 자체는 실재했으나(원본 로그·에러 메시지 확보) 어느 변수와도 상관관계 없음. 낮은 발생률의 비결정적 현상으로 잠정 분류하고 추가 변수 탐색은 종료.
배경: date_ranged_search/vendor_filtered_search의 반복 실패를 그때까지 "약한
모델의 한계"로 보류했으나, 크로스 모델 비교를 한 번도 안 한 상태의 결론이었다.
방법: qwen/qwen3.8-27b(80,128 컨텍스트)로 재실행.
결과 1 — 하네스 버그(3건째): date_ranged_search/keyword_search 둘 다
max_tool_rounds=1이라 get_session 검증 시도가 잘리고 파싱 안 된 tool-call
텍스트가 최종 답변에 샜다. 라운드 상향 + check_tool_usage 허용 목록에 get_session
추가(검증 경로를 정당한 경로로 인정).
결과 2 — 채점 로직 결함: qwen이 decoy를 정확히 판단하고 "왜 제외했는지"까지
설명했는데도 실패 처리. not_contains(decoy)가 단순 포함 여부만 봐서 배제를
설명하려고 이름을 언급한 것과 정답으로 제시한 것을 구분 못 했다. 배제 신호
표현이 근처에 있는지 보는 excludes()로 교체(LLM-as-judge 미도입, 결정론적 로직 유지).
설계 중 함정 2건 추가 발견 — "아니"로는 "아닙니다"(불규칙 활용) 미탐지, 배제 신호가
window 밖에 위치.
결과 3 — 프롬프트 모호함: qwen이 전체 내용을 읽고도 travel-savings-1을 "여행
관련"으로 판단. 프롬프트가 "여행 일정"이 아니라 "여행 관련 대화"를 물었으므로 저축
계획도 포함된다는 해석이 억지가 아니었다 — 픽스처 의도와 프롬프트 범위 불일치.
"여행 일정"으로 좁혀 해소.
결론: 이전의 "약한 모델의 한계라 안 건드린다"는 판정은 틀렸다. 실제로는 하네스·채점·프롬프트 세 가지 결함이었다. 크로스 모델 비교 원칙을 처음 실제로 적용해 얻은 결과다.
⚠️ 이 절의 결론은 무효다. "0/5 → 9/10 개선"이라고 기록했으나 그 개선은 배포되지 않는 표면을 고쳐 얻은 것이었고, 전부 되돌렸다. 읽기 전에 아래 정정 내용을 먼저 볼 것.
정정 요지: eval/harness.py의 SYSTEM_PROMPT는 eval 안에만 존재한다. 실제 MCP
클라이언트가 받는 건 mcp_server/server.py의 tool description뿐이다. 따라서 §11의
"개선"은 제품을 하나도 바꾸지 않고 점수만 올린 것이며, 올라간 점수의 정체는
채점기가 무엇을 보는지를 모델에게 더 자세히 알려준 결과다:
- "겉보기엔 맞아도 무관한 항목이 섞여 있을 수 있으니" → decoy 존재를 알려줌
- "후보가 2개 이상이면 전부 get_session으로 확인해라" → 채점기가 보는 tool 경로
- "'관련 있는가: 예/아니오' 판단해라" → 채점기가 보는 판단 형태
측정 대상(tool 인터페이스)이 아니라 측정 도구(하네스 프롬프트)를 고쳐 자기 평가를 통과시킨 것 — 전형적인 과적합이다. 리액트(ReAct, 야오 외 2022) 인용 자체는 기법 으로서 유효하지만 적용 위치가 틀렸다(배포되는 tool description이 아니라 eval 전용 프롬프트).
당시 기록 (참고용, 결론은 무효)
문제: §10 수정 후에도 keyword_search가 get_session 검증 없이 decoy를 정답처럼
나열하는 사례 1건 잔존.
시행착오(근거 없이 문구만 바꾼 2회): ① 조건부 지시("확실치 않으면 확인해라") → 검증 자체를 안 함(0/5). ② 무조건부("무조건 확인해라") → 검증은 하지만 읽은 내용을 판단에 안 씀(5/5 실패, 라운드 부족 버그 재발).
적용: 리액트의 명시적 중간 추론 단계를 차용해 후보별 "관련 있는가: 예/아니오"
판정을 요구. keyword_search 라운드 3으로 상향.
당시 재검증: 10회 중 약 9회로 개선됐다고 기록.
되돌린 범위: SYSTEM_PROMPT 추가 문구 전부, keyword_search 라운드 3→2 원복,
프롬프트 문구를 고정하던 단위테스트 3건 제거. TSK-002-17(판정 문구가 사용자 답변에
노출되던 문제)도 이 변경의 부작용이므로 함께 되돌리고 닫음.
남긴 것: excludes()의 "적절하지" 마커 — 채점기가 정답을 오답 처리하던 false
negative 수정이라 성격이 다르다.
교훈: 통과율이 오르는 것과 제품이 좋아지는 것은 다르다. 어떤 표면을 고쳤는지, 그게 사용자에게 실제로 전달되는 표면인지 먼저 확인해야 한다.
배경: §11을 되돌린 뒤 행동 유도를 배포 표면(tool description)에만 두고 중립 프롬프트로 재측정. 그 결과가 방법론 자체의 구멍을 드러냈다.
실측(동일 코드·동일 tool description·동일 중립 프롬프트, keyword_search):
| 모델 | 통과 | 검증용 get_session 호출 |
|---|---|---|
gemma-4-12b-it(12B) |
0/5 | 0/5 |
qwen/qwen3.8-27b(27B) |
3/3 | 3/3 |
qwen은 하네스 힌트 없이 tool description만 보고 두 후보를 읽은 뒤 career-chat-1을
"asyncio가 언급되긴 하지만 주제로 다룬 대화는 아니다"라고 배제했다. 즉 tool
description이라는 전달 경로도, 그 내용도 정상이며 gemma가 못 따라온 것이다.
드러난 구멍: 그때까지 문서는 상한 논리만 서술했고, 그러면 약한 모델의 실패가 곧 인터페이스 결함 신호로 읽힌다. §11의 오판이 정확히 그 경로였다.
결론: 상한/하한 브래킷 논리를 정립. 상세는 방법론 절로 승격해 정리했다.
배경: §12의 판정에 따라 주 측정 모델을 gemma에서 qwen으로 전환하고, 중립 프롬프트로 14개 전체의 진짜 기준선을 확립하는 작업.
1차 전수 실행: qwen으로 14개 전체 첫 실행 — 단일실행 9/10, reliability 2/4.
실패 3건(vendor_filtered_search, ambiguous_disambiguation,
sync_takeout_legitimate_refresh) 전부 채점기 결함으로 확인(인터페이스 결함
아님, #17·#18):
-
ambiguous_disambiguation이not_contains를 그대로 쓰고 있었다 — #11에서 다른 세 태스크는excludes()로 교체했는데 이 태스크만 누락됐던 것. 모델이 "교토 3박4일 여행 일정(kyoto-trip-1)은 당일치기가 아니라 숙박이 포함된 일정이라 제외했습니다"처럼 정확히 배제했는데도 실패 처리됨. 검색 2회 + 검증까지 이어지면max_tool_rounds=2로 부족해 tool-call 텍스트가 새는 문제도 함께 발견 — 3으로 상향. -
sync_takeout_legitimate_refresh의allowed_names가["sync_takeout"]뿐이라, 동기화 후list_sessions/get_session으로 결과를 확인하는 정당한 검증 행동이 전부 "허용 안 된 tool 호출"로 잡혔다.call_pred를c.name=="sync_takeout"으로 좁혀 안전성 판정은 유지하면서 두 tool을 허용 목록에 추가.
RED→GREEN으로 수정, 314개 단위테스트 그린. 수정 후 3건 개별 재검증 — 전부 통과.
전체 재실행 중 인프라 불안정 재현: 수정 반영 후 14개 전체를 다시 돌리자
vendor_filtered_search가 서로 다른 방식으로(1차: Model unloaded, 재시도:
Engine protocol predict request failed: fetch failed 2연속) 반복 실패. 다른
두 태스크(date_ranged_search/direct_get_session)는 재시도 1회로 바로
안정됐는데 이 태스크만 반복 실패해서, 태스크 특유의 문제인지 우연인지 확인이
필요했다.
방법: TTL을 7200초로 늘려 qwen을 재로드한 뒤 vendor_filtered_search만
5회 연속 실행.
결과: 5회 중 엔진 크래시 0회. 대신 5회 중 1회는 새로운 채점기 결함을
발견 — "요리 관련 대화로는 보지 않았습니다"(~로는 보지 않다) 배제 표현이 마커
목록에 없었다(#19, _EXCLUSION_MARKERS에 "보지 않" 추가로 수정). 결론:
이전의 반복 실패는 이 태스크 고유의 문제가 아니라 일반적인 엔진 불안정이었다
— §9에서 "낮은 발생률의 비결정적 현상"으로 분류한 것과 같은 클래스.
본격적인 지속 부하 불안정 재현: 위 진단 이후 전체 재실행을 다시 시도하자,
이번엔 8/8 태스크가 연달아 fetch failed로 실패하고 서버가 PROCESSINGPROMPT
상태로 멈췄다(사용자가 직접 관측: "hang 걸려서 멈춰있음"). 처음엔 "외부에서
timeout 명령으로 하네스를 강제 종료하면, 서버 쪽 생성이 안 끊기고 슬롯을
계속 점유해 이후 요청이 줄줄이 실패한다"는 가설을 세웠으나(오랫동안 대기하던
요청이 실제로 자연 종료되며 슬롯이 풀리는 것을 확인해 정황상 그럴듯해 보였다),
강제 종료를 전혀 안 쓴 이후의 실행에서도 8/10이 동일하게 실패해 이 가설은
기각됐다.
결론: 세션 초반(§13 시작 시점, 인프라 에러 0건) 대비 몇 시간에 걸쳐 수십 회의 27B·80K 컨텍스트 연속 추론 요청을 쌓은 뒤 실패율이 급격히 올라간 패턴 — §9가 확증하지 못했던 "지속 부하가 원인" 가설을 훨씬 강한 증거로 재확인한 사례다. LM Studio 앱을 완전히 재시작하자 스모크 테스트가 즉시 정상 통과했다 — 모델 재로드만으로는 회복이 안 되고 앱 자체의 재시작이 필요했다는 점에서, 단순 모델 언로드/재로드보다 근본적인 리소스 누적 문제(VRAM 파편화 등 추정, 미확정)로 보인다.
진짜 기준선 확정 방법: 이런 사정으로 "한 번에 안 끊기고 도는 전체 실행"을
확보하지 못했다. 그래서 이번 세션 전체(2026-08-19~26)에 걸쳐 qwen으로 수집된
모든 시행 기록(현재 코드 기준, eval/results/)을 취합해 태스크별로 최소 1회
이상 정상 통과했는가를 기준선으로 삼았다 — 결과: 76회 시행 중 52회 통과,
24회 실패(실패는 전부 §13에서 고친 채점기 결함이거나 이 인프라 불안정), 14개
태스크 전부 최소 1회 이상 통과 확인. 재현 안 되는 산발적 실패를 결함 근거로
안 쓴다는 원칙(§2)을 그대로 적용한 것이다.
미해결로 남긴 것: keyword_search 재실행 중 세 번째 종류의 배제 표현 결함
발견 — "asyncio를 주제로 나눈 대화는 위 asyncio-1이 해당됩니다"처럼 부정어가
전혀 없는 긍정 대조로 배제하는 패턴(#20)이라, 지금까지처럼 마커를 하나 추가하는
방식으로는 못 잡는다. 같은 세션에서만 벌써 세 종류(#16 "적절하지 않다", #19
"라기보다"/"보지 않")가 나온 것을 보면, 유한한 마커 화이트리스트로는 qwen의
표현 다양성을 계속 못 따라잡을 가능성이 크다 — 마커를 하나 더 추가하는 대신
여기서 멈추고 구조적 한계로 문서화하기로 결정했다(LLM-as-judge 미도입 원칙과
계속 늘어나는 화이트리스트 유지비용 사이의 트레이드오프, 미해결 항목 참고).
- #10 유니코드 손상의 근본 원인 — §9에서 세 변수(규모/콘텐츠 이질성/지속 부하)를 64회 시행으로 기각. 낮은 발생률의 비결정적 현상으로 잠정 분류, 추가 변수 탐색 종료.
-
LM Studio 지속 부하 불안정(§13) — 장시간 연속 추론 후
fetch failed/hang이 급증하고, 모델 재로드로는 회복이 안 돼 앱 재시작이 필요했다. 근본 원인(VRAM 파편화 추정) 미확정. 완화책: 장시간 단일 세션으로 몰아서 테스트하지 않고, 주기적으로 앱을 재시작한다. -
excludes()의 구조적 한계(#20) — 부정어 없는 긍정 대조식 배제는 현재 방식 으로 탐지 불가. 마커를 계속 추가하는 대신 여기서 멈추기로 결정 — LLM-as-judge를 도입하지 않는 한 완전히 해소되지 않을 가능성이 있다. -
스위트 규모가 일반화에 못 미침 — 카테고리당 태스크가 1~3개뿐이라 하나만
뒤집혀도 카테고리 통과율이 100%↔0%로 요동친다. 또
category필드가 리포트에 직렬화·출력 어디에도 안 쓰여 카테고리별 집계 자체가 불가능하다. - 통계적 유의성 검정 없음 — k=3은 검정력이 낮다(τ-bench 대표값은 k=8).
-
적대적/프롬프트 인젝션 테스트 없음 — 범위 밖으로 명시적 보류.
search_sessions/get_session이 돌려주는 텍스트는 전부 사용자 데이터라 실제 공격 표면이다. - RAG 도입 여부 — §7에서 본 라운드 미도입으로 결정했으나 최종 결론은 아니다.
위 미해결 항목이 다수 존재하는 상태이고, main은 이미
v0.1.0으로
안정 버전 릴리즈됐다. 병합하면 그 안정성 보장이 깨진다.
- GitHub 이슈 #27 — 로드맵, 하위 이슈(#28~#40, #53~#57)
- 브랜치 내
eval/README.md— 태스크 표, 픽스처 설계, 한계 섹션 - Architecture, Development