I made my agent prove every quote against the source document
에이전트가 인용문마다 원문에서 근거를 찾도록 만들었습니다
NHTSA 차량 결함 기록을 검색하는 에이전트가 인용한 문구를 실제 출처 문서와 대조하고, 차량 적용 범위와 문서 간 충돌도 따로 확인합니다. 검색된 문서가 실제 인용의 근거인지 검증하고, 충돌을 사람의 승인 없이 확정하지 않는 설계를 소개합니다.
- 주제
AI 요약
에이전트가 실제 문서를 인용하면서도, 그 문서에 없는 문장을 출처로 잘못 연결하는 문제를 해결한 프로젝트입니다. 개발자는 NHTSA(National Highway Traffic Safety Administration) 공개 API에서 2017~2022년형 Honda CR-V의 비정상적인 자동 긴급 제동 관련 기록을 가져와 TSB Oracle을 만들었습니다. 같은 문제를 두고 제조사 공지, 개정된 공지, 연방 조사, 차량 소유자 제보가 서로 다른 내용을 담을 수 있습니다. 검색 순위가 높은 문서 하나를 고르거나 챗봇이 한쪽 설명을 단정하는 대신, 기록의 차이를 독자에게 보여주는 것이 목표입니다.
차량에 따라 달라지는 적용 범위
예를 들어 NHTSA 조사 EA24-002는 2017~2022년형을 다루지만, Honda 소프트웨어 업데이트 26-091은 2019년형까지만 포함하며 LX 트림은 제외합니다. 따라서 2021년형 소유자는 연방 조사 대상에 포함되더라도 공개된 수리 방안은 찾지 못할 수 있습니다. 프로젝트는 이런 범위 차이를 단순한 답변 속에 감추지 않고, 어떤 문서가 해당 연식·차종·트림에 적용되는지 확인합니다. 자료가 없는 1994년형 Civic del Sol 질문에는 출처가 없다고 표시합니다.
답변의 사실 주장에는 NHTSA 식별자를 붙이고 공개 기록으로 연결합니다. 실제로 검색하지 않은 인용은 황색으로 표시하며, 인용 문구는 지식 베이스의 요약문이 아니라 인용 대상 문서의 원문 사본과 대조합니다. 두 출처가 충돌하면 날짜와 설명을 나란히 보여주고, 에이전트가 해결안을 제안해도 사람의 승인을 받기 전에는 확정하지 않습니다.
지식 베이스와 구조화 질의의 역할
데이터 모델은 네 종류입니다. tsb는 기술 서비스 공지, 조사, 딜러 메시지, 소유자 제보 같은 출처 문서입니다. claim은 출처가 뒷받침하는 개별 주장, contradiction은 서로 다른 주장을 연결한 충돌 기록, decision은 충돌을 누가 어떻게 해결했는지와 승인 상태를 담습니다.
Sanity Context의 지식 베이스에는 tsb와 claim을 넣되 contradiction은 제외했습니다. 충돌 정보를 미리 주입하면 에이전트가 원문에서 차이를 찾아낸 것인지, 저장된 충돌을 되풀이한 것인지 구분하기 어렵기 때문입니다. 대신 Context MCP의 initial_context와 knowledge_base_read가 문서 표현을 제공하고, 별도 도구인 check_applicability가 GROQ 질의로 연식·제조사·차종·트림의 적용 여부와 관련 충돌, 해결 상태를 확인합니다. 지식 베이스는 문서가 무엇을 말하는지 찾고, GROQ는 그 문서가 지금 질문한 차량에 해당하는지 확인합니다.
인용 검증이 확인한 오류
개발자는 지식 베이스 항목이 인용의 근거로도 쓰일 것으로 예상했지만, 항목은 여러 문서를 합치고 바꿔 쓴 종합문이라 그대로 인용하기에 적합하지 않았습니다. 실제 문서에 없는 문장이 요약 항목에는 들어갈 수 있기 때문입니다. 그래서 원문 사본을 직접 검사하도록 바꿨고, 테스트 중에는 실제 문구를 가져오면서도 다른 버전의 공지 식별자를 붙인 사례를 잡았습니다. 검색 결과에 관련 문서가 있다는 사실만으로 인용의 정확성이 보장되지는 않는다는 점을 보여줍니다.
지식 베이스 구축 과정에서는 NHTSA 자체 요약의 오류도 네 건 발견했습니다. 공지 A18-006 요약은 진단 코드 DTC를 OTC로, 표시 화면 MID를 MIO로 적었습니다. 또 두 항목은 민원에 제기된 사고 31건과 전체 차량별 보고 47건, 민원상 부상 50건과 전체 보고상 부상 93건을 혼합했습니다. 개발자는 원문이 뒷받침하는 내용에 맞춰 이를 바로잡았고, 수정 사항은 이후 빌드에도 반영됩니다. 직접 작성한 주장끼리는 출처가 명시돼 있어 명백한 충돌이 없었습니다. 충돌 감지를 보여주려고 인위적인 사례를 넣지 않았습니다.
사람의 승인과 남은 검증 과제
record_decision 도구는 해결안을 확정하지 않고 proposed 상태로 기록합니다. 사람이 Sanity Studio에서 승인하거나 거부해야 합니다. 승인할 때 결정과 해당 충돌의 상태를 한 트랜잭션에서 함께 바꿔, 결정은 승인됐는데 충돌은 미해결로 남는 불일치를 막습니다. 승인 결과와 근거는 다음 질문에도 사용합니다. 공개 데모에서 한 번의 대화가 이후 모든 사용자에게 통용되는 답으로 굳어지는 상황을 막으려는 설계입니다.
댓글에서는 출처 문서의 식별자뿐 아니라 버전이나 콘텐츠 해시까지 인용에 연결해야 한다는 제안이 나왔습니다. 현재 데이터셋은 두 공지 버전을 서로 다른 NHTSA 식별자로 구분해 잘못된 버전 인용을 잡지만, 같은 식별자의 문서가 갱신되면 과거 원문과 연결하는 장치는 없습니다. 작성자는 문서와 주장의 retrievedAt·extractedAt 시각으로 갱신 여부를 감지할 수 있고, 콘텐츠 해시를 저장하면 더 강하게 검증할 수 있다고 답했습니다. 또 인용 문구가 원문에 실제로 있다는 검증과, 그 문구가 답변의 더 넓은 주장을 뒷받침하는지 판단하는 검증은 별개라는 지적도 나왔습니다. 현재 프로젝트는 첫 검증을 코드로 강제하고, 두 번째 판단은 충돌 양쪽의 주장과 근거를 공개한 뒤 사람에게 맡깁니다.
dev.to 반응
- @mihai_leanzero — 결정과 충돌 기록을 한 트랜잭션에서 함께 바꾸는 부분은 가져다 쓸 만합니다. 두 기록이 서로 다른 상태로 어긋나지 않게 하는 불변 조건은 마감에 쫓기면 빠뜨리기 쉽고, 나중에 조용히 데이터가 갈라지면 디버깅 비용이 큽니다. 지식 베이스는 표현을 맡고 GROQ는 구조를 맡는 두 단계 분리도 대부분의 검색 구성보다 깔끔합니다. 디버깅 중에 충돌을 지식 베이스에서 슬쩍 확인하고 싶은 유혹은 없었나요? 그 원칙을 끝까지 지켰나요?
- @chanadev — 지키긴 했지만 의지력 덕분이라기보다 그럴 필요가 없었기 때문입니다. 에이전트는 여전히 모든 불일치를 봅니다. 지식 베이스가 아니라 데이터셋을 직접 질의해 찾습니다. 충돌을 제외한 이유는 Context가 제가 넣은 충돌을 다시 읽는 대신 원문에서 직접 알아차리게 하려는 것이었습니다. 진짜 유혹은 다른 데서 왔습니다. 첫 빌드가 충돌을 하나도 찾지 못했을 때, 기능이 작동하는 모습을 보여주려고 충돌을 주입하는 빠른 해결책이 있었지만 그러지 않았습니다. 제가 작성한 주장은 모두 누가 말했는지 표시합니다. 출처가 다른 두 주장은 그 자체로 명백한 모순이 아닙니다. 대신 빌드는 NHTSA 자체 요약에서 진짜 오류 네 건을 찾았습니다. 질문을 해결한 상태인 결정 옆에 해당 질문이 여전히 미해결로 남는 실패를 막으려면 두 기록을 한 번에 쓰는 게 가장 간단했습니다.
- @micheypico — 모델 라우팅 계층을 운영하며 32개 모델을 하나의 키로 연결하는 저희 경험과도 맞습니다. 여러 모델 구성을 실용적으로 만드는 것은 LLM 주변의 결정적 제어 구조입니다. 제공업체가 작업 도중 요청을 제한하면 상태 머신이 재시도, 대체 경로, 오류 처리를 정합니다. LLM은 그 결정을 안정적으로 내리지 못합니다. 불안정한 에이전트를 디버깅하다 보면, 괜찮은 모델 주변에 상태 머신을 빠뜨린 문제가 원인일 때가 많습니다.
- @chanadev — 동의합니다. 그래서 검사는 프롬프트가 아니라 코드에 둡니다. 모델은 기록을 읽고 무엇을 찾았는지 말하는 데 유용하지만, 보장은 주변 계층에서 나옵니다. 인용이 화면에 도달하기 전에 원문과 대조하고, 해결안 기록 도구는 해당 충돌에 실제로 속한 주장만 받습니다. 테스트에서는 실제로 가져온 공지 두 버전 중 하나의 문구를 다른 버전의 출처에 붙인 사례를 잡았습니다. 모델은 어느 쪽이 차량에 적용되는지 판단하지만, 사람이 승인해야 이후 사용자에게 확정 답변으로 제공됩니다.
- @zira125 — 원문에서 인용을 확인하는 부분이 가장 강력해 보입니다. 회귀 테스트에서는 NHTSA 식별자뿐 아니라 문서 버전이나 콘텐츠 해시에도 인용을 고정하겠습니다. 공지 1판과 2판이 문장 하나만 다르게 하는 테스트 자료도 두면 좋겠습니다. 두 버전이 모두 검색된 상황에서도 다른 판의 인용을 잡을 수 있습니다. 데이터셋을 새로 가져올 때 원본 버전을 보존하나요?
- @chanadev — 이 데이터셋에서는 두 버전이 서로 다른 NHTSA 식별자, MC-11035885와 MC-11035781을 가진 별도 문서입니다. 따라서 문서 식별자 단위 검사를 이미 하고 있고, 말씀하신 경우에 해당하는 잘못된 인용을 실제 실행에서 잡았습니다. 단위 테스트에도 실제 인용을 잘못된 문서와 대조하는 사례가 있지만, 한 문장만 다른 두 판을 쓰는 테스트가 더 정교하겠습니다. 갱신 때는 문서를 제자리에서 덮어쓰고 retrievedAt만 기록합니다. Sanity에는 문서 이력이 있지만 검사는 그 이력을 확인하지 않습니다. 같은 식별자로 NHTSA 요약을 수정하면 이전 문구가 더는 일치하지 않아 황색 밑줄이 뜨겠지만, 이를 의도적으로 처리하는 장치는 없습니다. 주장에는 extractedAt, 문서에는 retrievedAt이 있으므로 해시 없이도 문서 재수집 시점을 감지할 수 있습니다. 해시는 더 강한 방법이며 실제 트래픽을 처리한다면 가져올 때 저장하겠습니다.
- @nomad-link-id — 검색 결과에 문서가 있다는 것과 인용이 충실하다는 것은 다릅니다. 관련 문서가 인용 목록에 있어도 그 문서에 없는 문장을 붙이는 실패를 저도 봤습니다. 검색 순위와 관련성은 괜찮아 보여도 인용 구절 검사는 실패할 수 있습니다. “유용한 문서를 찾았는가?”와 별도로, 인용한 모든 구절이 표시한 출처에 있는지 확인하고 없으면 통과시키지 않는 검증을 두겠습니다. 생성 결과를 한 번 더 다듬는 작업이 아니라, 검색 후 근거를 확인하는 검사입니다.
- @chanadev — 이 차이를 알아차리는 데 가장 오래 걸렸습니다. 문서를 검색했고 관련성도 있고 순위도 맞더라도, 그 문서에 인용한 문장이 없을 수 있습니다. 순위는 선반을 제대로 찾았다는 뜻이지, 책장을 제대로 찾았다는 뜻은 아닙니다. 다만 실패하면 답변을 차단하는 방식에는 다르게 접근합니다. 저는 인용을 숨기지 않고 답변에 표시합니다. 자기 차에 관한 내용을 읽는 사람에게는 검증하지 못한 주장을 의심 표시와 함께 보여주는 편이, 해당 문장을 조용히 빼버리는 것보다 낫다고 봅니다. 독자가 아니라 다른 시스템이 결과를 받는다면, 황색 밑줄을 볼 수 없으므로 말씀처럼 통과를 막겠습니다.
- @luisprimecore — 잘못된 출처를 붙이는 문제는 문장을 지어내는 것보다 더 까다롭습니다. 정확한 문서 식별자와 문구를 대조하는 방식이 적절합니다. 인용 검증 실패도 NHTSA 페이지 두 곳의 모순과 같은 방식으로 화면에 표시하나요?
- @chanadev — 일부러 완전히 다르게 표시합니다. 모순은 기록 안에 있는 내용이므로 답변 아래에 두 주장과 날짜, 불일치 이유를 놓고 한쪽을 선택하는 버튼을 둡니다. 잘못된 인용은 기록이 아니라 에이전트가 방금 작성한 문장의 문제라서 문장 안에 남깁니다. 황색 점선 밑줄을 긋고 마우스를 올리면 이유를 보여주며, 황색 인용 표시와 같은 방식입니다. 모순은 조치가 필요하고, 표시된 인용은 신뢰도를 낮춰 읽어야 합니다. 약점은 툴팁입니다. 휴대전화에서는 색만 보이고 설명은 보이지 않는데, 아직 해결하지 못했습니다.
- @glenallen — 인용 단위 검사는 중요한 실패 원인을 해결하지만 그 위에 한 단계가 더 있습니다. 인용이 원문과 정확히 일치해도 그 근거가 더 큰 주장을 뒷받침하지 못할 수 있습니다. 저희는 “이 문구가 원문에 있나?”와 “이 근거가 결론을 정당화하나?”를 두 단계로 나누면 검증이 강해진다는 점을 확인했습니다. 첫 단계는 결정적으로 검사하기 쉽지만, 두 번째 단계는 범위와 맥락, 원문이 실제로 뒷받침하는 수준을 따져야 합니다. 두 검사를 분리하면 문장 자체는 맞지만 오해를 부르는 인용으로 에이전트가 검증을 통과하는 일을 줄일 수 있습니다.
- @chanadev — 두 검사를 나누자는 데 동의하며, 현재 구현한 것은 첫 번째뿐입니다. 이 문구가 그 문서에 있는지는 사실 여부로 확인할 수 있어 검사하기 쉽습니다. 문구가 답변 문장을 뒷받침하는지는 판단이 필요하며, 이를 자동화하려면 한 모델에게 다른 모델을 평가하게 해야 합니다. 그러면 같은 문제가 한 단계 위에서 반복됩니다. 그래서 이 프로젝트에서는 두 번째 판단을 사람에게 맡깁니다. 에이전트가 충돌 해결안을 제안할 때 두 주장에 근거한 설명을 작성해야 하고, 도구는 해당 충돌에 속하지 않는 주장을 거부합니다. 사람이 Studio에서 승인하거나 거부하기 전에는 해결되지 않습니다. 충돌 화면에는 양쪽 주장과 날짜, 적용 연식 범위를 함께 보여줘 독자가 근거의 범위를 직접 확인하게 합니다.
원문: dev.to / 번역·요약: Trawling