Scarab Diagnostic Suite 현장 테스트 #002: Open WebUI에서의 Retrieval Truth Boundary
요약
Scarab Diagnostic Suite(SDS)를 활용하여 Open WebUI의 검색 결과 주입 실패 문제를 진단하는 현장 테스트 결과입니다. SDS가 구체적인 버그 정보 없이도 시스템의 불안정성이 발생하는 '검색 진실 경계(retrieval truth boundary)'를 정확히 식별할 수 있음을 증명했습니다.
핵심 포인트
- SDS는 정답 없이도 의심스러운 서브시스템을 식별 가능
- Open WebUI의 검색 결과 주입(context injection) 실패 지점 포착
- 데이터 형태(response-shape) 불일치로 인한 오류 진단
- AI 수리 에이전트 전 단계로서의 진단 능력 검증
이것은 저의 Scarab Diagnostic Suite 시리즈 중 두 번째 현장 테스트입니다.
첫 번째 현장 테스트는 가이드된 복구 패스(guided repair pass)였습니다. 이를 통해 SDS가 알려진 문제를 가져와, 계약 경계(contract boundary)를 식별하고, AI 복구 차선(AI repair lane)을 좁게 유지하며, 대상 진단 결과가 복구 후에 해결되었는지 검증할 수 있음을 증명했습니다.
이 두 번째 현장 테스트는 더 중요합니다.
이번 테스트는 제가 실제로 증명하고 싶었던 것에 더 가까워졌습니다:
Scarab Diagnostic Suite가 정확한 버그를 듣기 전에 올바른 실패 표면(failure surface)을 식별할 수 있는가?
대상은 Open WebUI 이슈 #25038이었습니다.
사용자에게 나타나는 증상은 웹 검색 문제처럼 보였습니다:
Open WebUI가 SearXNG에 성공적으로 쿼리했습니다.
UI는 유효한 검색 결과를 보여주었습니다.
검색된 스니펫(snippets)에는 이미 유용한 정보가 포함되어 있었습니다.
그 후 앱이 “Querying(쿼리 중)” 단계에 진입했습니다.
그다음 “No sources found(소스를 찾을 수 없음)”라고 표시되었습니다.
그 후 모델은 마치 실시간 웹 검색을 사용할 수 없는 것처럼 답변했습니다.
이러한 종류의 실패는 시스템이 작동하는 동시에 실패하는 것처럼 보이기 때문에 매우 좌절감을 줍니다.
검색은 작동했습니다.
검색(Retrieval)은 작동했습니다.
UI는 결과가 존재한다는 것을 알고 있었습니다.
하지만 모델이 검색된 정보를 사용하기 전에 소스/컨텍스트 주입(source/context injection)이 실패했습니다.
해당 이슈에는 다음과 같은 트레이스백(traceback)도 포함되어 있었습니다:
TypeError: 'JSONResponse' object is not subscriptable
chat_web_search_handler 내부에서 발생했습니다.
이는 응답 형태(response-shape) 문제임을 가리켰지만, 저는 초기 진단 패스(diagnostic pass)를 위해 해당 이슈 텍스트를 SDS에 입력하지 않았습니다.
현장 테스트 설정
저는 최신 Open WebUI v0.9.5 대상을 상대로 SDS를 실행했습니다.
중요한 제약 조건은 다음과 같습니다:
SDS는 GitHub 이슈 텍스트를 받지 않았습니다.
알려진 실패를 겨냥한 커스텀 진단 스크립트도 없었습니다.
이 이슈를 위해 수기로 작성된 탐지기(detector)도 삽입되지 않았습니다.
목표는 SDS가 정답을 전달받지 않고도 저장소(repo) 내의 의심스러운 서브시스템을 식별할 수 있는지 확인하는 것이었습니다.
그 차이가 중요합니다.
AI 도구가 정확한 버그를 떠먹여 받는다면, 실제보다 더 똑똑해 보일 수 있습니다. 저는 수리 에이전트(repair agent)가 변경 사항을 만들기 전에 SDS가 불안정성의 올바른 범주(class)를 표면화할 수 있는지 알고 싶었습니다.
SDS가 발견한 것
SDS는 Memory/RAG 표면(surface)을 선택하고 retrieval truth boundary(검색 진실 경계) 문제를 식별했습니다.
발견 사항은 다음과 같습니다:
retrieval_truth_boundary
계약 클래스(contract class):
source_context_contract_consistency
플래그가 지정된 경로는 다음과 같은 변수 주변이었습니다:
res
results
queries_response
그것이 첫 번째 중요한 신호였습니다.
SDS는 다음과 같이 말하지 않았습니다:
“이것은 Open WebUI 이슈 #25038입니다.”
그렇게 말했다면 오히려 의심스러웠을 것입니다.
대신, SDS는 훨씬 더 유용한 내용을 말했습니다:
“이 retrieval/source-context 경계는 노이즈가 심합니다. 이 경계를 가로지르는 response 및 result의 형태(shape)가 눈에 띄게 안정적이지 않습니다.”
이는 실제 실패 범주와 일치했습니다.
겉으로 드러난 문제는 “검색된 소스 없음(No sources found)”이었지만, 더 깊은 문제는 검색된 진실(retrieved truth)이 모델 컨텍스트(model context)로 전달되는 과정에서 안전하게 살아남지 못했다는 점이었습니다.
그것이 바로 Scarab Diagnostic Suite가 탐지하도록 설계된 종류의 경계입니다.
이것을 왜 retrieval truth boundary라고 부르는가
AI 애플리케이션에서 검색(retrieval)만으로는 충분하지 않습니다.
검색 제공자(search provider)가 좋은 결과를 반환할 수 있습니다.
UI가 해당 결과를 표시할 수 있습니다.
문서 로더(document loader)가 이를 처리하기 시작할 수 있습니다.
벡터/소스 파이프라인(vector/source pipeline)이 작업을 시작할 수 있습니다.
하지만 최종 모델 컨텍스트가 검색된 진실을 사용 가능한 형태(shape)로 전달받지 못한다면, 시스템은 어쨌든 실패합니다.
이것이 이 종류의 버그를 매우 기만적으로 만드는 요인입니다.
사용자는 다음과 같은 과정을 봅니다:
“웹 검색 중.”
“5개 사이트 검색 완료.”
유효한 URL.
유효한 스니펫(snippets).
그다음:
“검색된 소스 없음.”
그러면 모델은 마치 검색에 접근할 수 없었던 것처럼 응답합니다.
따라서 실패는 단순히 “웹 검색이 고장 났다”는 것이 아닙니다.
실패의 본질은 다음과 같습니다:
검색된 진실은 존재했으나,
그 진실을 앞으로 전달해야 할 내부 경계(internal boundary)가 붕괴된 것입니다.
이것이 제가 이 표면을 retrieval truth boundary라고 부르는 이유입니다.
수리 레인 (The repair lane)
SDS가 핫스팟(hotspot)을 선택한 후, Codex는 한 번의 제한된 안정화 패스(bounded stabilization pass)를 수행했습니다.
이것은 Scarab 워크플로의 중요한 부분입니다:
SDS가 수리 도구(repair tool)가 된 것이 아닙니다.
SDS는 진단 경계(diagnostic boundary)를 식별했습니다.
Codex가 수리를 수행했습니다.
그 후 SDS는 대상 표면(targeted surface)이 조용해졌는지(quieted) 확인하기 위해 다시 실행되었습니다.
Memory/RAG retrieval 경계에 대한 초기 SDS 탐지(finding) 횟수는 3회였습니다.
제한된 수리(bounded repair) 이후, 대상 탐지 건수는 0으로 감소했습니다.
해당 선택된 표면에 대해 Stepwise 결과는 노이즈가 많고 모순적인(noisy/contradictory) 상태에서 조용하고 정리된(quiet/organized) 상태로 이동했습니다.
이것은 내부적으로 한 가지를 증명했습니다:
수리가 SDS가 선택했던 바로 그 표면을 다루었다는 사실입니다.
하지만 그것만으로는 여전히 충분하지 않았습니다.
진단 탐지(diagnostic finding)가 사라지는 것은 유용한 내부 증거이지만, 상위 유지 관리자(upstream maintainers)들은 "SDS가 조용해졌다"는 사실에는 관심이 없습니다.
그들은 실제 버그가 수정되었는지에 관심을 가집니다.
따라서 다음 단계는 기능적 레드/그린 증명(functional red/green proof)이었습니다.
기능적 증명 (Functional proof)
SDS가 핫스팟을 선택한 후에야 저는 GitHub 이슈를 기능적 증명으로 사용했습니다.
이를 통해 역할(roles)을 명확히 분리했습니다:
SDS는 이슈 텍스트 없이 표면을 찾아냈습니다.
이슈 텍스트는 구체적인 실패 형태(failure shape)를 정의했습니다.
수리는 사용자에게 보이는 동작(user-visible behavior)을 증명해야 했습니다.
집중된 재현(focused repro)은 다음을 재현했습니다:
TypeError: 'JSONResponse' object is not subscriptable
이 실패는 chat_web_search_handler가 JSONResponse 객체를 받을 수는 있지만, 이를 choices 페이로드를 가진 OpenAI 스타일의 completion 딕셔너리인 것처럼 소비하려고 했기 때문에 발생했습니다.
이는 전형적인 응답 형태 계약 실패(response-shape contract failure)입니다.
수리는 응답 형태 경계(response-shape boundary)를 추가했습니다.
이제 코드는 응답에 직접 하위 인덱싱(subscripting)을 하는 대신, 생성된 쿼리(generated-query) 응답이 예상되는 completion 형태를 갖추고 있는지 확인합니다. 만약 그렇지 않다면, 웹 검색 경로는 소스/컨텍스트 처리 중에 충돌(crash)하는 대신 원래의 사용자 쿼리로 안전하게 폴백(fallback)합니다.
수리 후 집중된 회귀 테스트(focused regression)를 통과했습니다.
기능적 증명 이후에도 SDS retrieval 경계는 조용한 상태를 유지했습니다.
이로써 전체 루프(full loop)가 완성되었습니다:
SDS가 표면을 감지했습니다.
Stepwise가 핫스팟(hotspot)을 선택했습니다.
Codex가 경계가 지정된 수리(bounded repair)를 수행했습니다.
실제 실패 사례가 빨간색에서 초록색으로 변했습니다.
SDS가 선택된 경계가 해결되었음을 검증했습니다.
이것이 바로 제가 중요하게 생각하는 현장 테스트 패턴입니다.
Field Test #001과 무엇이 달랐는가
Field Test #001은 가치 있었지만, 가이드가 제공된 상태였습니다. 문제는 이미 알려져 있었고, SDS는 이를 제한된 수리 경로(constrained repair lane)로 전환하는 데 도움을 주었습니다.
Field Test #002가 더 강력했던 이유는 SDS가 문제 텍스트를 전달받기 전에 관련 서브시스템(subsystem)을 찾아냈기 때문입니다.
정확한 GitHub 이슈 번호를 찾아낸 것은 아닙니다.
버그 전체를 마법처럼 알아낸 것도 아닙니다.
하지만 다음과 같은 적절한 불안정성 범주(class of instability)를 식별해냈습니다:
retrieval/source-context response-shape 불일치 (inconsistency).
이것이 단순히 지시를 따르는 도구와, 실제 실패 경계(failure boundary)를 드러낼 수 있는 진단 스위트(diagnostic suite) 사이의 차이입니다.
업스트림 형태 (Upstream shape)
업스트림(upstream) 기여는 의도적으로 좁게 설정되었습니다.
공개된 PR(Pull Request)에는 Scarab, SDS, Stepwise 또는 내부 증명 내보내기(internal proof exports)에 대한 언급이 없었습니다.
이 점이 중요합니다.
메인테이너(Maintainer)들이 패치를 검토하기 위해 저의 진단 스위트를 이해할 필요는 없습니다.
업스트림을 향한 수리는 일반적인 엔지니어링 언어로 구성되었습니다:
chat_web_search_handler가 OpenAI 스타일의 completion dict를 예상하는 곳에서 JSONResponse를 받을 수 있습니다.
이로 인해 다음과 같은 오류가 발생합니다:
TypeError: 'JSONResponse' object is not subscriptable
수리 사항은 응답 형태(response-shape) 처리와 회귀 테스트 커버리지(regression coverage)를 추가합니다.
이것이 올바른 업스트림 경계입니다.
Scarab는 내부적으로 수리를 가이드할 수 있지만, PR 자체는 일반적인 리포지토리 네이티브(repo-native) 증거만으로 독립적으로 존재해야 합니다.
내부 분류 (Internal classification)
현장 테스트 (Field Test) #002
프로젝트 (Project): Open WebUI
이슈 (Issue): #25038
표면 (Surface): 메모리/RAG 검색 진실 경계 (Memory/RAG retrieval truth boundary)
계약 클래스 (Contract class): 소스/컨텍스트 응답 형태 일관성 (source/context response-shape consistency)
모드 (Mode): 반-맹목적 진단 표면 탐지 후 기능적 증명 (blind-ish diagnostic surface detection followed by functional proof)
결과 (Outcome): 로컬 수정 성공, 타겟팅된 SDS 결과 확인 완료, 집중 회귀 테스트 통과, 업스트림 PR 제출
주요 교훈 (Primary lesson): SDS는 이슈 텍스트가 기능적 증명으로 사용되기 전에 올바른 실패 표면을 식별할 수 있다.
이것이 중요한 이유
많은 AI 애플리케이션 버그는 시스템 사이의 공간에 존재합니다.
검색은 작동하지만, 컨텍스트 주입 (context injection)이 실패합니다.
도구는 데이터를 반환하지만, 모델은 이를 전혀 받지 못합니다.
프로바이더 (provider)의 응답은 존재하지만, 핸들러 (handler)는 다른 형태 (shape)를 기대합니다.
UI는 한 가지를 말하지만, 런타임 (runtime)은 다른 것을 수행합니다.
이것들은 항상 명백한 구문 오류 (syntax errors)인 것은 아닙니다.
이것들은 진실 경계 (truth-boundary)의 실패입니다.
Scarab Diagnostic Suite는 이러한 경계를 찾아내어 AI 코딩 에이전트가 광범위하고 노이즈가 많은 수정을 하지 않도록 하기 위해 구축되고 있습니다.
이번 현장 테스트는 그 패턴이 작동함을 보여주었습니다.
완벽하지는 않았습니다.
마법 같지도 않았습니다.
하지만 유용했습니다.
스위트 (suite)는 불안정한 표면을 찾아냈습니다.
에이전트는 한 조각을 수정했습니다.
테스트는 실제 실패를 증명했습니다.
진단 재실행은 경계가 안정되었음을 확인했습니다.
이것이 제가 원하는 수정 루프 (repair loop)입니다:
더 큰 AI가 아니라,
더 많은 자율적 방황이 아니라,
경계가 지정된 진단적 수정 (bounded diagnostic repair)입니다.
노이즈가 많은 리포지토리 (repo)를 보고 다음과 같이 말할 수 있는 시스템 말입니다:
“문제는 아마 여기일 것입니다.
이곳이 경계입니다.
이 차선(lane)만 수정하십시오.
이제 그것을 증명하십시오.”
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기