브로슈어는 18층이라고 했고, 등록 서류는 12층이라고 했다.
요약
본 글은 부동산 브로슈어와 실제 등록 서류 간의 정보 불일치를 찾아내는 'Home Truth'라는 에이전트를 소개합니다. 이 도구는 여러 문서를 동시에 분석하여, 모델이 가장 크고 자신감 있게 답변하는 내용(브로슈어)과 법적으로 우선해야 하는 사실(등록 서류) 사이의 모순을 사용자에게 명확히 보여줍니다.
핵심 포인트
- 여러 문서 간의 정보 불일치 탐지 가능
- 법적/공식 문서를 기준으로 진실을 파악하는 에이전트 구현
- 단순 검색 인덱스 방식의 한계를 극복함
이것은 Sanity Challenge, Path One: 실시간 콘텐츠를 조회하는 에이전트를 배포하기에 제출하는 내용입니다.
제가 만든 것
아난야(Ananya)를 소개합니다. 가상의 인물이지만, 아마 당신도 그녀와 똑같은 사람을 알고 있을 것입니다.
그녀는 31세로 푸네(Pune)에서 일하며 첫 아파트를 위해 8년 동안 돈을 모았습니다. 어느 일요일, 그녀는 Nimbus Greens라는 프로젝트의 판매 사무실에 들어갑니다. 브로슈어는 정말 멋집니다. 18층까지 올라가는 네 개의 타워, 루프탑 수영장, 2027년 3월 입주 가능, '모든 것 포함'으로 1,050 제곱피트의 2 BHK가 ₹72 lakh에 제공되며 주차장은 무료입니다. 중개인은 친절하고 서두르는 분위기입니다. 오늘 20%만 지불하면 가격이 고정됩니다.
하지만 아무도 그녀에게 말해주지 않는 부분이 있습니다. 이 결정을 내리는 데 필요한 모든 것은 이미 공개되어 있다는 것입니다. 인도에서는 모든 프로젝트가 주(州)의 부동산 규제 기관(Real Estate Regulatory Authority, RERA)에 등록해야 하며, 빌더는 그 등록 내용에 법적으로 구속됩니다. 그리고 판매 계약서 초안과 RERA 법 자체도 있습니다. 아무도 브로슈어 옆에 있는 이 세 가지 문서를 읽지 않습니다. 그래서 아무도 이것들이 다른 이야기를 한다는 것을 눈치채지 못합니다:
| 브로슈어가 말하는 것 | 기록이 말하는 것 |
|---|---|
| 4개의 타워, G+18 | 건물 3개, 지상층(Ground) + 12층으로 승인됨 |
| ... | |
| Nimbus Greens는 가상의 프로젝트입니다. 하지만 표의 모든 줄은 그렇지 않습니다. 이것들은 인도 구매자들이 돈을 다 쓰고 건물이 12층에서 멈추었을 때 몇 년 후에 알게 되는 속임수들입니다. |
그래서 저는 Home Truth를 만들었습니다. 이 도구에 프로젝트 문서를 입력하면, 그것들이 나란히 배치되고, 의견이 다른 모든 곳을 찾아내며, 각 문서의 정확한 문장을 보여주어 당신이 그 말을 맹신하지 않도록 합니다. 이것은 정보의 공백을 빌더에게 물어볼 질문 체크리스트로 바꿔줍니다. 그리고 전화 통화에서 중개인이 던지는 종류의 질문(
가장 당연한 버전부터 머릿속으로 시도해 봤습니다. 문서를 검색 인덱스에 덤프(dump)하고 모델에게 답변을 맡기는 방식입니다. 'Nimbus Greens는 몇 층인가요?'라고 물어보면, 쌓여 있는 정보 중 가장 크고 자신감 넘치는 답변은 브로슈어의 'G+18'입니다. 반복되고, 큰 글씨로 되어 있고, 틀린 정보죠.
이것을 정확하게 파악하려면 어떤 단일 문서에도 쓰여 있지 않은 지식이 필요합니다. 등록 서류가 브로슈어보다 우선한다는 것을 알아야 합니다. 예약 금액은 법적으로 10%로 제한되므로, 20%를 요청하는 것은 세부 사항이 아니라 위반이라는 것을 알아야 합니다. 그리고 '1,050 sq ft'와 '690 sq ft carpet'가 두 가지 방식으로 측정된 같은 평면이며, 브로슈어는 자신에게 유리한 쪽을 골랐다는 것을 알아야 합니다. 이러한 지식은 콘텐츠를 어떻게 모델링하느냐에 따라 존재합니다. 이것이 제가 Sanity에 구현한 부분입니다.
데모(Demo)
실시간: https://home-truth-theta.vercel.app. 로그인 불필요. _Nimbus Greens_를 열고 채팅을 시도해 보세요.
직접 둘러보고 싶다면, 2분짜리 투어를 참고하세요.
홈페이지에서 건물을 드래그해 보세요. 이것은 데이터에서 가져온 것입니다. 승인된 계획에 따르면 12개의 견고한 층이 존재하며, 그 위에 있는 여섯 개의 반투명한 빨간색 층은 오직 브로슈어에만 존재했습니다. 아래로 스크롤하면 브로슈어 자체를 3D로 넘겨볼 수 있고, 20% 항목이 강조 표시되고 법률의 제13조 (1)항이 바로 옆에 인용되어 있습니다.
전체 보고서를 열어보세요. 12가지 사실 중 10가지는 근거가 부족하며, 그중 5가지는 매우 중요합니다. 모든 발견 사항은 구매자에게 왜 중요한지 설명하고 있으며, 각각의 항목에는 각 문서에서 인용한 내용을 보여주는 정확한 단어 보기 드로어가 있습니다. _건설사에게 물어볼 질문들_을 한 번 클릭하면, 요청해야 할 문서를 명시하는 인쇄 가능한 체크리스트 전체가 됩니다.
그런 다음 채팅에 평면 B-1502에 대해 물어보세요. Sanity로 이동하여 등록 서류를 확인하고, 건물 B는 지상 + 12층으로 승인되었음을 파악한 후, 15층 평면은 승인된 계획이 없다고 알려줍니다. 답변 아래의 _제가 확인한 방법 보기_를 열면 실행된 모든 질의(query)를 볼 수 있습니다.
코드(Code)
GitHub logo Reet24-del / home-truth
인용된 조사 결과와 인터랙티브 3D 건물을 통해 건설업자의 약속과 등록 서류 비교하기.
Home Truth
아파트를 구매하기 전에, 건설업자가 제시하는 약속을 확인하세요.
Home Truth는 건설업자가 광고하는 내용(브로슈어, 웹사이트, 중개사 메시지)을 주 정부 부동산 규제 당국(RERA: Real Estate Regulatory Authority)에 등록된 내용, 매매 계약 초안의 내용, 그리고 2016년 부동산(규제 및 개발)법(Real Estate (Regulation and Development) Act, 2016)이 요구하는 사항과 비교합니다. 모든 조사 결과는 해당 정보가 나온 정확한 문구를 보여줍니다.
DEV x Sanity Challenge를 위해 제작되었으며, Path One은 Sanity Context를 통해 실제 콘텐츠를 질의하는 에이전트입니다.
구조화된 콘텐츠가 필요한 이유
키워드 검색으로
속성(attribute)이란 건 구매자가 신경 쓰는 것들, 예를 들어 건물 층수, 카펫 면적 또는 소유 날짜 같은 것입니다. 또한 그 사실을 어떻게 비교해야 하는지—단위, 허용 오차 범위, 통제된 어휘 목록, 그리고 그것이 등가성을 확인하는지 아니면 법적 한계에 대비하여 확인하는지—도 알려줍니다. '층수(Floors)'와 '예약 금액(booking amount)'은 매우 다르게 작동하며, 이 차이가 바로 속성에서 결정됩니다.
클레임(claim)이란 특정 문서가 가진 하나의 속성에 대한 버전이며, 그것이 어디서 왔는지의 정확한 단어들과 그 위치를 포함합니다. 데모 프로젝트에는 28개의 클레임이 있습니다. 파인딩(finding)은 한 속성에 대한 모든 클레임을 비교하여 나온 판결문입니다: 일치(match), 불일치(mismatch), 위반(violation), 또는 단 하나의 출처만 있는 경우입니다. 그리고 프로젝트(project)가 이 모든 것을 하나로 묶어줍니다.
제가 가장 중요하게 생각했던 규칙은 아무것도 의역되어서는 안 된다는 것입니다. 모든 클레임에는 인용문이 첨부되며, npm test는 각 데모 인용문의 단어를 출처 문서와 일대일로 확인합니다. Claude가 새 문서를 통해 클레임을 추출할 때도 동일한 검사를 거치며, 사람이 승인하기 전까지는 '미확인(unverified)'으로 표시되어 Studio의 '클레임 검증(Claims to verify)' 뷰에 도착합니다.
코드가 누가 틀렸는지 결정한다, 모델이 아니다
저는 LLM이 건설업자가 법을 위반했는지 여부를 결정하도록 하고 싶지 않았습니다. 그래서 파인딩은 작은 비교 엔진인 studio/lib/compare.ts에서 나옵니다. 이 엔진은 출처들을 순위화하고(법규, 그다음 등록 서류, 그다음 계약서, 그다음 마케팅 자료), 허용 오차 범위와 어휘 목록을 적용하며 법적 한계를 확인합니다. 동일한 클레임은 항상 동일한 파인딩을 생성하므로, 보고서의 모든 줄은 인용문으로 추적될 수 있습니다. 모델의 역할은 나중에 오며, 그 역할은 더 좁습니다: 설명하는 것입니다.
산성 맥락(Sanity Context)을 가리키기
여기서 흥미로워졌습니다.
저는 Context 앱에 지식 기반(Knowledge Base)을 만들고, GROQ 소스 쿼리를 사용하여 본문이 있는 모든 문서를 가져오도록 제 자체 데이터셋을 연결했습니다:
*[_type ==
지식 기반(Knowledge Base)의 목적을 위해 저는 사람에게 브리핑하듯이 작성했습니다. 누가 질문할 것인지(인도의 주택 구매자), 무엇이 핵심적으로 다뤄져야 하는지(구매자 권리, 등록된 사실, 계약 조항, 위험 요소), 그리고 범위를 벗어나는 것은 무엇인지 등을 말입니다.
그러고 나서 '엔트리 생성(Build entries)'을 눌렀더니 Context가 6가지 이슈를 가지고 돌아왔습니다. 이 모든 것이 브로슈어와 등록 서류 간의 충돌이었습니다. 층수, 타워 개수, 입주 날짜, 두 가지 평형대의 크기, 그리고 1km로 광고된 조깅 트랙이 실제로는 600m로 등록되어 있는 경우였습니다. 이것들을 읽는 것은 마치 제품이 스스로 작동하는 것을 지켜보는 것 같았습니다. 저는 각각의 충돌에 대해 등록 서류를 우선시하도록 해결했고, Context는 이 모든 결정을 미래 빌드(future builds)가 따르도록 지침으로 저장했습니다.
하지만 단순히 '등록 서류가 승리한다'고만 하면 유용한 무언가를 놓치게 됩니다. 구매자는 무엇이 사실인지뿐만 아니라 자신에게 *약속받은* 내용도 알아야 합니다. 왜냐하면 그 간극이야말로 구매자가 건설사 측에 제기할 핵심 질문이기 때문입니다. 그래서 저는 수동으로 하나의 지침을 추가했습니다:
> 마케팅 문서가 등록 문서를 모순하는 경우, 등록 서류를 정확한 것으로 취급하되, 해당 마케팅 주장을 출처와 함께 '광고된(Advertised)' 항목에 유지합니다.
이제 에이전트는
`home-truth-data`는 GROQ 모드에서 실행되며 정확한 질문에 답합니다. 각 채팅은 엔드포인트 URL에 `groqFilter`를 추가하여 현재 프로젝트로 범위를 좁히고, 에이전트는 해당 프로젝트, 속성 정의 및 법률만 볼 수 있습니다. 컨텍스트는 이 필터를 엔드포인트 자체의 것과 결합하므로, 접근 권한을 넓힐 수는 없고 오직 좁힐 수만 있습니다. 또한 도구 목록도 `groq_query`, `schema_explorer`, `array_field_reader`로 줄였습니다. 시스템 프롬프트가 이미 스키마를 설명하고 있기 때문에, `initial_context`는 단순히 토큰을 소모하는 역할이었습니다.
`home-truth-kb`는 Knowledge Base 모드에서 실행됩니다. 에이전트가 "이 조항은 무슨 의미인가" 또는 "점유가 지연될 경우 내 권리는 무엇인가"와 같은 질문을 할 때 이곳을 이용합니다. 이 기능은 개요를 얻기 위해 `initial_context`를 한 번 호출한 다음, 필요한 항목에 대해 `knowledge_base_read`를 사용합니다.
### 에이전트의 실제 작동 방식
에이전트는 Groq에서 `openai/gpt-oss-120b` 모델을 사용하여 Groq의 Responses API로 실행됩니다. Groq는 두 엔드포인트 모두에 원격 MCP 서버로 연결하고 도구 호출 자체를 수행하므로, 제 서버는 대화 내용만 전송하고 각 단계를 페이지로 다시 전달하는 역할을 합니다.
제가 이 타워들이 몇 층으로 등록되어 있는지와 브로셔가 주장하는 내용은 무엇인지 물었을 때, 에이전트는 먼저 다음 코드를 실행했습니다:
*[_type == "finding" && attribute->label match "floor"]{status, severity, summary, "fact": attribute->label, entries}
그 후 이 발견(finding)에 관련된 두 가지 주장을 가져오고, Knowledge Base 개요를 열어 _possession and delays_ 및 _buyer rights_ 항목을 읽었습니다. 답변은 승인된 계획("Ground + 12")과 브로셔("G+18")를 대조하여 인용하고, 이를 심각한 불일치(critical mismatch)라고 지적했으며, 지연 점유에 관한 섹션 18을 요약하고, 마지막으로 건설사에게 무엇을 요청해야 하고 아직 무엇을 지불해서는 안 되는지에 대해 설명했습니다. 이 다섯 단계 모두 _How I checked_ 아래에 배치되어 있습니다.
시스템 프롬프트는 개성이 부족하고 규칙에 엄격합니다. 법(Act)을 신뢰하고, 그다음 등록 서류를, 그다음 계약서를, 그리고 마케팅 자료를 신뢰하세요. 인용문, 섹션 번호, 날짜 또는 금액을 절대 지어내지 마세요. 만약 출처 어느 곳에서도 알 수 없다면, 그렇게 말하고 구매자를 주(State) RERA 포털이나 변호사에게 안내하세요.
### 나를 괴롭힌 부분들
프로젝트 토큰은 Context에는 작동하지 않았습니다. Context Viewer를 사용하여 ᵟorganizationᵬ 토큰을 만들 때까지 403 에러가 발생했습니다.
스키마 배포만으로는 충분하지도 않았습니다. GROQ 엔드포인트는 제가 `sanity deploy`를 실행하고 스튜디오가 라이브 상태가 될 때까지 계속해서 “No Studio application found”라고 말했습니다. 그러자 모든 것이 한 번에 녹색으로 바뀌었습니다.
모델은 습관이 있습니다. gpt-oss는 산문 속에 `【result[0].quote】` 같은 마커를 남기고, 표 셀에는 `<br>`을 떨어뜨려서, 답변이 페이지에 도달하기 전에 둘 다 제거됩니다.
그리고 채팅은 Groq에서 시작되지 않았습니다. 저는 처음에 Claude의 MCP 커넥터로 구축한 다음 옮겼습니다. 두 곳 모두 자체적으로 MCP를 실행하기 때문에, 전환 과정에서 약 90줄 분량의 파일 하나가 건드려졌습니다. Claude는 여전히 새 문서에 대한 주장 추출(claim extraction)을 수행합니다.
## Sanity 프로젝트 상세 정보
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기