
AI의 할루시네이션(Hallucination)을 간파하는 『세컨드 오피니언』 조사 기술: Claude Code와 GitHub Copilot의
요약
Claude Code와 GitHub Copilot을 '세컨드 오피니언'으로 활용하여 AI의 할루시네이션을 검출하고 해결하는 실천적인 방법을 제시합니다. 두 도구의 접근 방식 차이를 이용해 서로의 오류를 상호 검증하는 워크플로우를 설명합니다.
핵심 포인트
- AI는 코드의 구조는 읽지만 코드의 역사와 의도는 오해하기 쉬움
- Claude Code는 로컬 파일 시스템 탐색에 강점이 있음
- GitHub Copilot은 리포지토리 이력 및 브랜치 연계에 능숙함
- 두 도구의 비대칭성을 활용해 할루시네이션을 상호 검증 가능
「이 코드, 어디가 문제인가요?」라고 AI에게 물으면, 근거와 함께 상세한 수정안이 돌아옵니다. 그런 경험이 이제 당연해졌습니다.
하지만 얼마 전, 이런 일이 있었습니다. 어떤 메뉴 화면의 4가지 버그를 Claude Code에게 횡단 조사하도록 요청했고, 코드의 근거를 제시한 수정안까지 받았습니다. 「완벽한 분석이다」라고 느낀 직후, 만약을 위해 GitHub Copilot에게도 동일한 조사를 의뢰했더니, 수정 방침 중 2곳이 잘못되었다고 지적되었습니다.
반대의 경우도 있었습니다. 외부 API와의 접속 URL을 Copilot으로 조사하고, Claude Code에게 검증을 의뢰했더니, Copilot이 오래된 설정 파일을 정상 설정으로 오인하고 있었다는 사실이 판명되었습니다.
흥미로운 점은, 두 AI 모두 「확신을 가지고」 틀렸다는 것입니다. 근거 파일의 경로까지 제시하며 자신만만하게 잘못된 정보를 반환했습니다.
이 기사에서는 두 가지 AI 도구를 세컨드 오피니언(Second Opinion)으로 활용하여, 할루시네이션 (Hallucination)을 검출하고 해소하는 실천적인 플로우를 해설합니다.
「Java에서 문자열을 리스트로 변환하려면?」과 같은 일반적인 질문이라면, LLM은 거의 정확하게 대답합니다. 문제가 되는 것은, 프로젝트 고유의 역사적 경위나 암묵적인 규칙이 얽혀 있는 경우입니다.
구체적으로는 다음과 같은 상황에서 AI는 잘못된 정보를 내놓기 쉽습니다.
「그럴듯한 파일」이 여러 개 있음: 운영 설정·구 설정·소스 트리 내의 개발 시 설정이 혼재되어, AI가 「더 가까이 있는」 파일을 우선시함 -
암묵적인 연계가 있음: 「이 HTML 요소의 ID는 다른 JS 파일이 이미 관리하고 있다」라는 문맥을 읽지 못함 -
이전된 설정이 남아 있음: 「이 파일은 리포지토리 정리 후에 사용하지 않게 되었다」라는 컨텍스트를 AI는 가질 수 없음 -
「삭제 vs 주석 처리」와 같은 의도의 문제: 기술적으로는 둘 다 동작하지만, 팀의 관습으로서 올바른 쪽이 있음
요컨대, LLM은 코드의 구조는 읽을 수 있어도, 코드의 역사와 의도는 잘못 읽기 쉽습니다. 이것이 레거시 시스템이나 대규모 리포지토리에서 할루시네이션 (Hallucination) 빈도가 높아지는 이유입니다.
두 도구에는 「코드로 들어가는 입구」의 차이가 있습니다.
| Claude Code | GitHub Copilot (Agent) | |
|---|---|---|
| 조사의 기점 | 로컬 파일 시스템을 직접 탐색 | GitHub 리포지토리 (Remote)의 코드를 검색 |
| 강점 | 배포 설정·로컬 한정 파일에도 액세스 가능. grep으로 전 환경의 설정값을 횡단 비교할 수 있음 | PR·브랜치·커밋 이력과의 연계가 자연스러움. 「이 브랜치에 이미 수정 사항이 있다」는 검출에 능숙함 |
| 약점 | 파일 수가 많으면 「그럴듯한 파일」을 선택하기 쉬워짐 | 리포지토리 외부 (예: 별도로 관리되는 설정 리포지토리)에는 액세스하기 어려움 |
이러한 특성의 차이가 후술할 「어느 쪽이 틀리는가」의 방향성을 결정합니다.
중요한 것은 「어느 쪽이 우월한가」가 아닙니다. 각자가 서로 다른 입구에서 코드를 보고 있기 때문에, 한쪽이 놓치는 것을 다른 한쪽이 잡아내기 쉽다는 비대칭성이야말로 가치가 됩니다.
서비스 추가 신청 화면에서, 과거 수정 티켓의 영향으로 인한 디그레이드 (Degrade)로 의심되는 4건의 버그가 발견되었습니다.
- Bug 1: 특정 상품의 중복 신청 확인 다이얼로그가 표시되지 않음
- Bug 2: 특정 섹션에 일괄 신청 버튼이 표시되지 않음
- Bug 3: 일괄 신청 버튼의 마우스 오버 이미지가 깨짐
- Bug 4: 특정 상품이 메뉴에 표시되지 않음
4건 모두 동일한 티켓의 수정 코드 영향 범위에 포함되어 있었으며, 여러 JSP·JS 파일에 걸친 횡단적인 조사가 필요했습니다.
Claude Code에게 4건의 버그를 의뢰하여, 각 버그의 원인 코드 위치·수정안·근거 파일을 세트로 제시받았습니다.
분석 결과는 상세하고 논리적이었지만, 그중에서 주목해야 할 두 가지 수정안이 있었습니다.
Bug 2의 수정안 (Claude Code) — 신규 ID를 생성하여 추가함
<!-- Claude Code의 제안 -->
<span id="bulk_apply_btn_4_active" class="js_hide">
<a href="..."><img src=".../btn_bulk_apply.png"></a>
...
Bug 4의 수정안 (Claude Code) — <c:if> 태그를 삭제함
- <c:if test="${status.index >= 3}">
...
...表示 제어 로직...
- </c:if>
둘 다 근거가 있으며, 읽어본 바로는 문제가 없어 보입니다. 하지만 여기에 함정이 있었습니다.
Claude Code가 놓친 문맥:
- Bug 2: 기존의
form-config.js가 이미bulk_apply_btn_2_active/inactive(id2)를 제어하고 있다. id4라는 새로운 ID를 만들어도 JS 측에서 반응하지 않기 때문에 버튼이 제어되지 않는다. - Bug 4: 이 팀의 관습으로서, 로직의 '삭제'가 아니라 '주석 처리를 통한 무효화'가 표준이다. 향후 참조나 롤백 (rollback)을 용이하게 하기 위한 판단이다.
Claude Code는 파일을 단독으로 읽어 최적의 답변을 내놓았지만, '다른 파일과의 연계'와 '팀의 역사적인 관습'이라는 두 가지 컨텍스트 (context)가 누락되어 있었습니다.
Claude Code의 조사 결과를 GitHub Copilot에 전사하여 비교를 요청했습니다.
"Claude Code의 조사 결과를 전사합니다. 지금까지의 조사 결과와 비교하여 차이가 있다면 어느 쪽이 옳은지 조사해 주세요."
Copilot은 브랜치 (branch) 및 커밋 (commit) 이력을 참조하며 다음과 같이 지적했습니다.
Bug 2 (Copilot) — 기존 ID를 재사용한다
<!-- Copilot의 지적: id2(기존)를 사용해야 함 -->
<span id="bulk_apply_btn_2_active" class="js_hide">
<a href="..."><img src=".../btn_bulk_apply.png"></a>
...
form-config.js는 이미 bulk_apply_btn_2_active/inactive를 제어하고 있다. id2를 재사용하면 JS의 변경은 불필요하다.
Bug 4 (Copilot) — 태그는 주석 처리로 남겨둔다
- <c:if test="${status.index >= 3}">
+ <!-- <c:if test="${status.index >= 3}"> -->
...表示 제어 로직...
...
커밋 이력을 참조하면, 과거의 수정에서도 동일한 조건 제어는 주석 처리로 무효화되어 있었다.
게다가 Copilot은 "Bug 2와 Bug 4는 과거의 수정 브랜치에 이미 수정 사항(fix)이 포함되어 있는 것 아닌가?"라는 추가 가설도 제시했습니다.
두 AI의 주장이 엇갈렸기 때문에, 실제 git diff로 확인합니다.
git diff HEAD remotes/origin/feature/[과거의 수정 브랜치]
차이점을 확인한 결과, Copilot의 지적은 둘 다 옳다는 것이 확정되었습니다. Claude Code도 재확인 후 "2가지 중대한 차이가 있습니다. Copilot의 지적이 옳습니다"라고 인정했습니다.
나아가 Copilot의 "과거 브랜치에 수정 사항이 포함되어 있다"라는 가설도 git diff로 확인할 수 있었으나, 여기서 더 깊이 파고들자 의외의 사실이 판명되었습니다. '수정 완료된 브랜치'의 머지 (merge)가 오히려 이번 4건의 버그를 모두 도입한 원인이었던 것입니다. 이 발견 또한 Copilot의 '브랜치와의 차이점'이라는 관점이 없었다면 알아차릴 수 없었을 것입니다.
최종적으로는 새로운 hotfix 브랜치를 생성하여, 3개 파일의 수정으로 커밋을 완료했습니다.
Claude Code가 제안한 '신규 ID'는 기술적으로 틀린 것은 아닙니다. 하지만 기존 JS 파일과의 연계라는 암묵적인 문맥을 다 읽어내지 못했던 것입니다.
Copilot은 커밋 이력을 기점으로 조사를 진행하기 때문에, '이 패턴은 과거에 어떻게 다뤄져 왔는가'라는 역사적 문맥을 파악하기 쉽습니다. 이것이 이번에 Copilot이 정답을 맞힌 이유였습니다.
외부 시스템과의 연계 API 명세서를 작성함에 있어, 접속 대상 URL이나 포트 번호를 코드베이스 (codebase)에서 확인할 필요가 있었습니다. 이 프로젝트는 운영, 검증, 테스트의 여러 환경이 있으며, 게다가 과거의 설정 관리 방식 변경으로 인해 동일한 이름의 설정 파일이 여러 경로에 존재하는 상태였습니다.
Claude Code가 로컬의 배포 설정 파일을 참조한 결과:
# Claude Code의 조사 결과
external_api.url.base=https://[외부 API 서버]/cc
포트 번호 지정 없음.
반면, Copilot에게 동일한 조사를 요청하면:
# Copilot의 조사 결과
external_api.url.base=https://[외부 API 서버]:[포트 번호]/cc
포트 :[포트 번호]
가 붙어 있다.
사양서에 기재할 값이므로, 어느 쪽이 옳은지는 중요한 문제입니다.
설정 파일을 리포지토리(Repository) 전체에서 망라적으로 조사했습니다.
grep -r "external_api.url.base" [배포 설정 디렉토리]/
모든 환경의 설정값을 확인한 결과:
| 환경 | 설정값 |
|---|---|
| 운영·검증·테스트 (모든 실환경) | https://[외부 API 서버]/cc (포트 없음) |
| 검증·테스트 (일부 모크(Mock) 환경) | https://[모크 서버]/api/cc (실제 API에는 미연결) |
Claude Code의 답변이 옳다는 것이 확정되었습니다. 그렇다면, Copilot은 어떤 파일을 참조하고 있었던 것일까요.
조사를 계속하자, Copilot은 소스 트리(Source Tree) 내의 다른 경로에 있는 설정 파일을 참조하고 있었음이 밝혀졌습니다.
[소스 트리 내의 구 설정 파일 경로]
→ external_api.url.base=https://[외부 API 서버]:[포트 번호]/cc
이 파일의 정체를 확인합니다.
빌드 설정에 포함되지 않음: JAR/WAR 결과물에 포함되지 않으므로, 실제로는 배포되지 않음 -
이전 완료 메모가 존재함: 설정 파일을 전용 설정 리포지토리로 이전했을 때 남겨진 것 -
운영 환경용 설정이 동일 디렉토리에 존재하지 않음: 이전 과정 중의 흔적
결론: Copilot이 참조하고 있었던 것은 설정 관리 정리 전에 사용되었던 구 설정 파일이었습니다. 소스 트리의 "그럴듯한 경로에 있는 설정 파일"을 정규 설정으로 오인한 것입니다.
Copilot은 리포지토리의 소스 트리를 검색하기 때문에, "경로명과 내용이 일치하는 파일"을 우선시하기 쉬운 특성이 있습니다. "설정 파일의 관리 위치가 이동했다"라는 문맥을 파악하지 못하면, 이번과 같은 오인이 발생합니다. 반면 Claude Code는 로컬의 배포 설정을 직접 참조할 수 있었기 때문에, 모든 환경의 설정값을 정확하게 비교할 수 있었습니다.
두 가지 실전 사례에서 얻은, 재현 가능한 4단계 플로우입니다.
Step 1 — AI-A로 초기 조사
Claude Code 또는 Copilot 중 사용하기 편한 쪽으로 조사를 수행합니다. 조사 결과는 근거 파일 및 행 번호와 함께 출력하도록 하면 후속 공정에서 사용하기 용이합니다.
Step 2 — AI-B에 세컨드 오피니언(Second Opinion) 요청
Step 1의 결과를 다른 AI에 전기하고, 다음 프롬프트로 비교하게 합니다.
"[AI-A]의 조사 결과를 전기합니다.
지금까지의 조사 결과와 비교하여, 차이가 있는 경우에는
어느 쪽이 옳은지 조사해 주세요."
"차이가 있는 경우에는 조사하라"가 포인트입니다. 단순히 "확인해 줘"라고 전달하면 일치하는 부분만 돌아옵니다. 능동적으로 차이를 찾게 함으로써, AI가 놓친 부분을 지적하기 쉽게 만듭니다.
Step 3 — 차이가 있다면 근거 파일까지 파고들기
두 AI의 의견이 엇갈릴 때, "어느 쪽이 옳은가"뿐만 아니라 "왜 엇갈렸는지, 근거 파일을 제시하라"고 심층 조사를 요구합니다. 이번 Copilot의 오류는 "어떤 파일을 보고 있었는지"를 파고듦으로써 구 설정 파일이 혼재되어 있다는 근본 원인이 밝혀졌습니다.
Step 4 — grep / 파일 직접 읽기 / git diff로 최종 확인
어느 한쪽의 주장에 기울었다면, 코드(사실)로 최종 확인합니다. AI의 "설명의 논리적 타당성"과 "내용의 사실적 정확성"은 별개의 문제입니다.
# 설정값을 모든 환경에 걸쳐 비교
grep -r "설정 키 이름" [설정 디렉토리]/
# 변경의 기점이 된 커밋을 특정
...
"두 AI가 같은 말을 하고 있다 → 옳다"는 성립하지 않습니다. 동일한 코드베이스를 참조하고 있는 이상, 같은 편향(Bias)을 가질 가능성이 있습니다. 중요한 판단에 대해서는, 일치하더라도 최종적으로 코드로 확인하는 습관을 갖는 것이 중요합니다.
AI는 "그럴듯한 답"을 확신을 가지고 내놓습니다. 코드는 거짓말을 하지 않습니다.
| AI의 답변 | 코드·git |
|---|---|
| 정확성 | 높은 확률로 정확하지만, 틀려도 자신만만함 |
| ... | git log / git blame으로 추적 가능 |
| 속도 | 초 단위 |
AI를 사용하는 의의는 「속도와 가설 생성」에 있습니다. 그리고 grep이나 git은 「검증」에 사용합니다. AI의 답변을 「가설」로 받아들이고, 코드로 「검증」한다는 역할 분담이 AI 활용 조사의 기본 스탠스입니다.
두 가지 실천 사례를 통해 드러난, AI를 이용한 코드 조사의 핵심을 정리합니다.
어느 AI가 더 우월한가의 문제가 아니다: Claude Code와 Copilot은 각각 서로 다른 관점에서 코드를 읽는다. 이번 두 사례에서는 「틀리는 방향」이 반대였다——이것이 가장 시사하는 바가 큰 사실이다 -
세컨드 오피니언(Second Opinion)은 가장 저렴한 검증 수단: 하나의 AI에게 계속 깊이 파고들게 하는 것보다, 다른 AI에게 내용을 옮겨 적어 비교하게 하는 것이 사각지대를 발견하기 쉽고 시간 비용도 낮다 -
차이점이 발견된 순간이 가장 가치 있다: 두 AI의 의견이 엇갈릴 때, 그곳에는 반드시 「어느 한쪽이 놓치고 있는 컨텍스트 (Context)」가 있다. 근거까지 파고들 가치가 있다 -
최종 확인은 코드로: 아무리 논리적인 설명이라도, grep과 파일 직접 읽기를 통해 확인하기 전까지는 「가설」로 남겨둔다
AI에 의한 코드 분석은 단독으로 사용할 경우 「AI가 말했으니까 괜찮다」라는 과신을 낳기 쉬운 함정이 있습니다. 복수의 AI 비교와 코드를 통한 최종 확인을 조합함으로써, 비로소 「확신할 수 있는 조사」가 됩니다.
AI 없는 조사에 비해 생산성 향상은 압도적입니다. 그 혜택을 극대화하려면, AI를 능숙하게 다루면서도 AI를 의심하는 습관——이 언뜻 모순되어 보이는 자세야말로 AI 시대 엔지니어링의 요체라고 느낍니다.
본 기사의 사례는 실제 업무 조사를 바탕으로 하고 있으나, 고유명사·IP 주소·사내 정보는 일반화 또는 마스킹하여 기재하였습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기