Steadywag: 기록에 잘못된 정보를 아는 케어 에이전트
요약
Steadywag은 만성 질환을 앓는 반려동물을 위한 케어 동반자 앱입니다. 이 앱은 PDF, 이메일 등 분산된 방대한 의료 기록을 구조화된 차트(Sanity)로 통합하여 관리합니다. 사용자는 질문하기, 체크인, 예약, 병력 확인 등의 기능을 통해 일관되고 체계적인 돌봄 정보를 얻을 수 있습니다.
핵심 포인트
- 분산된 의료 정보(PDF, 이메일 등)를 구조화된 차트로 통합 관리
- 에이전트는 진단이나 처방 대신 '결정'만 제시하며 안전성을 검증
- 체크인 기능을 통해 일상적인 신체 및 행동 변화를 기록하고 추적 가능
- 반려견의 모든 케어 정보가 하나의 중앙 집중식 플랫폼에서 제공
본 글은 Sanity Challenge, Path One: 실제 콘텐츠를 질의하는 에이전트를 배포하기 제출물입니다.
처방약이 서면 목록에 있었다. 하지만 강아지는 그것을 받지 못했다.
전문가가 한 간병인에게 구두로 건너뛰라고 말했다. 아무도 종이를 업데이트하지 않았다. 그 강아지의 약물을 담당하는 두 번째 간병인은 모든 문서가 그렇게 말하고 있기 때문에, 이것이 현재 상태라고 합리적으로 생각할 것이다.
바로 이 격차가 전체 프로젝트의 핵심이다.
제가 구축한 것
Steadywag은 만성 질환을 앓는 강아지를 위한 케어 동반자이며, 실제 사례 하나를 기반으로 만들어졌습니다. 바로 테오(Theo)라는 이름의 8세 시츄 혼종견입니다. 그는 구리 저장 간병증(copper storage hepatopathy), 재발성 췌장염 및 고트리글리세라이드혈증을 앓고 있습니다. 그의 케어 정보는 약 180페이지 분량의 PDF, 이메일, 그리고 검진실에서 구두로 언급된 내용 속에 존재합니다.
저는 이 모든 정보를 Sanity라는 구조화된 차트로 가져왔고, 그 위에 앱을 구축했습니다. 이 앱은 휴대폰이나 컴퓨터에 설치되며, 열어둔 페이지에 대해서는 오프라인으로 작동하고, 라이트 모드 또는 다크 모드를 지원합니다.
- 질문하기(Ask). 작업을 선택하세요: 오늘 무엇이 필요한가요? 뭔가 변경되었나요? 이것을 먹어도 되나요? 테오를 지켜보고 있어요 (인쇄할 수 있는 즉석 돌봄 요약본). 수의사 방문 준비하기 (사용자 본인의 체크인 포함). 또는 무엇이든 질문하세요. 에이전트가 작동하는 동안, 사용자는 이를 관찰합니다:
체크인(Check-in). 수의사가 방문 시마다 묻는 사항들을 기록하는 30초 분량의 일일 로그입니다: 평소와 비교한 식사 및 음수량, 에너지 수준, 배변 상태 (사진으로 촬영), 구토 여부, 복용 약물, 활동량, 그리고 멍이나 눈, 귀 주름 또는 잇몸의 황달(yellow tint) 유무에 대한 신체 검진 기록입니다. 방문 전에는 이러한 항목들을 날짜별로 계산합니다: "3일 중 2일 이상 평소보다 많이 마심"과 같이, 질문에 답변한 날짜만을 카운트합니다.
4. 예약(Appointments). 수의사, 미용실 및 치과 방문 기록을 월간 달력으로 제공하며, 방문 하루 전 알림이 포함된 "내 캘린더에 추가하기" 파일을 제공합니다.
5. 병력(History). "테오(Theo)를 60초 만에," 하나의 차트에 ALT 수치, 약물 복용 기간 및 급성기 발작을 공유 축 위에 표시하며, 다섯 개의 장과 네 가지 패턴으로 구성되어 있습니다. 각 주장(claim)은 근거로 삼는 기록들을 명시합니다. "변화된 점" 로그와 그의 DNA 검사 보고서가 함께 제공됩니다.
6. 반려견(Your dog). 가족들이 연구원들에게 일관된 식이 구리(dietary copper) 데이터를 제공할 수 있도록 하는 사전 진단 식단 설문지입니다. 이 설문지는 빈 상태로 시작합니다. 답변은 방문자의 브라우저를 벗어나지 않습니다.
에이전트가 할 수 있는 것과 할 수 없는 것
에이전트는 절대 진단을 내리지 않으며, 투여량이나 용량을 제시하거나, 무언가를 시작하거나 중단하라고 지시하지 않습니다. 단지 읽을 뿐입니다. 작성할 수는 없습니다.
-
결정부터 제시합니다 (Verdict first). 'X를 먹을 수 있을까?'라는 질문에 대해, 먼저 하나의 결정(verdict)으로 시작합니다. 이 결정은 계획의 규칙에 부합함, 아마도 아님, 안 됨, 또는 알 수 없음 중 하나입니다. 그런 다음 자신의 계획과 비교하여 확인 사항들을 보여줍니다: 승인된 음식과의 구리 성분 비교, 나트륨, 지방, 칼로리를 간식 제한량과 비교, 플레인 조리 여부, 그리고 피해야 할 목록(avoid list) 등을 확인합니다. 절대 '안전함'이라고 말하지 않으며, 수의사가 아직 확인해주지 않은 경우라는 점을 항상 밝히고, 마지막에는 수의사에게 물어볼 질문으로 마무리됩니다.
-
차트 외부의 두 가지 제한적인 범위 (Two narrow reaches outside his chart). 계획에 포함되지 않은 음식에 대해서는 USDA FoodData Central에서 구리, 나트륨, 지방 정보를 찾아 보여주며, 이때
-
오늘 필요한 것은 무엇인가요? 목록에는 있지만 한 번도 투여되지 않은 약물부터 시작합니다.
-
테오를 돌보고 있어요. 서류상 기록이 잘못된 약물로 시작하고, 전화번호 추가 알림으로 끝나는 즉석 간병인 브리프(sitter brief)가 열립니다.
-
건조 사료 간식은 허용되나요? 그의 기록들이 의견을 달리합니다. 답변은 양쪽 입장을 모두 보여주면서 어느 한쪽을 선택하기를 거부합니다.
-
망고를 먹어도 되나요? 그의 계획에는 포함되어 있지 않기 때문에, USDA 수치를 조회하여 승인된 사료와 비교하는 것을 볼 수 있습니다.
Ask 외의 모든 페이지에서는 떠다니는 채팅 버튼이 동일한 에이전트를 창에 열어주므로, 읽으면서 질문할 수 있습니다.
Code
GitHub logo narlynars07 / steadywag
만성 질환을 앓는 개의 간병 동반자. 기록이 말해주지 않는 것을 알려주는 Sanity Context의 에이전트. DEV Sanity Challenge, Path One.
Steadywag
만성 질환을 앓는 개를 위한 간병 동반자입니다. 그의 기록을 읽고 서류상으로는 알 수 없는 정보를 알려줍니다.
실제 사례 하나인 테오(Theo)에게서 개발되었습니다. 테오는 구리 저장 간병증(copper storage hepatopathy), 재발성 췌장염(recurrent pancreatitis), 고중성지방혈증(high triglycerides)을 앓는 8세 시추믹스견입니다. 그의 관리는 약 180페이지 분량의 보고서, 이메일, 그리고 진료실에서 구두로 언급된 내용들 속에 존재했습니다. Steadywag은 이를 Sanity 내 하나의 구조화되고 출처가 명시된 기록으로 변환한 다음, 그 위에 에이전트, 일일 계획, 체크인 및 예약 기능을 추가합니다. DEV Sanity Challenge의 Path One을 위해 개발되었으며, 이는 실제 콘텐츠를 질의하는 에이전트입니다.
하나의 예시로 아이디어를 설명하자면. 약물은 서면 목록에 있었지만 개에게 투여된 적이 없습니다. 전문의가 한 간병인에게 구두로 건너뛰라고 지시했고, 아무도 기록을 업데이트하지 않았습니다. 구조는 이를 알고 있습니다 (`medication.status =
Next.js 16과 웹 측의 Vercel AI SDK를 사용합니다. Sanity Studio와 studio/에 있는 22개 타입 스키마가 있습니다. README에는 아키텍처와 실행 방법이 설명되어 있으며, evals/ 폴더에는 아래에서 설명하는 감사 질문들과 러너(runner)가 들어 있습니다.
제가 Sanity를 사용한 방법
구조 자체가 역할을 합니다. 원본 PDF에 대한 키워드 검색으로는 알 수 없는 내용들입니다:
-
medication.status = "listed-not-given"이고recordGap이 종류가verbal-instruction인 경우. PDF에는 약물이 활성 상태로 표시되어 있습니다. 하지만 실제로 투여된 적이 없다는 사실은 가족이 저에게 말해준 덕분에 존재합니다. -
기록의 모든 사실에 대한
sourceNote.confidence:confirmed,single-source, 또는conflicting. 충돌(conflict)은 문장 속에 묻힌 상태가 아니라 검색 가능한 상태입니다. Cerenia의 경우 그 예시입니다: 약물 목록에는 월요일, 수요일, 금요일이라고 되어 있고, 같은 보고서에 적힌 서면 지침으로는 필요할 때마다 24시간 간격으로라고 되어 있습니다. 두 정보 모두 표시됩니다. -
recordGap문서. 부재(Absence)는 검색할 수 없습니다. 모델링되어야 합니다. 이 차트는 전문의가 보류 중이라고 말했지만 결코 도착하지 않은 실험실 결과와 같은 9개의 미해결 격차를 담고 있습니다. -
dietHistoryEntry.origin:family-recall또는medical-record. 에이전트는 인용하고 있는 출처가 무엇인지 명시해야 합니다. -
별도의
careRoutine타입 옆에 있는medication.writtenInstruction. 수의사의 말과 가족의 루틴은 다른 출처입니다. 에이전트는 각각을 레이블링합니다: 수의사 기록, 가족 루틴, 가족 회상, 가족 관찰, 본인 체크인, 웹에서 가져옴. 수의사가 시간을 지정했다면 그것을 사용합니다. 루틴이 이를 덮어쓰지 않습니다. -
familyNote문서: 한 가족이 알아차린 것, 예를 들어 재발의 첫 징후 같은 것입니다. 그들은 수의사가 지시했는지 여부를 말하며, 에이전트는 이 내용을 조언으로 전환해서는 안 된다고 받았습니다. 약물 시간과 용량은 제외됩니다. -
historyChapter와historyPattern문서 각각은 방문 기록, 실험실 결과, 그리고 의존하는 약물에 대한 참조를 담고 있습니다. 히스토리 페이지와 에이전트는 동일한 문서를 읽습니다. -
dnaReport: 보고서에 명시된 대로 소비자의 DNA 검사 결과를 제공합니다. 이 정보를 가이드라인 옆에 배치함으로써 에이전트는 교차 출처 답변을 얻었습니다. 즉, 보고서에는 구리 관련 검사가 없으며, 그의 품종 중 어느 것도 구리 가이드라인에서 언급하는 목록에 포함되지 않는다는 것입니다. 두 사실 모두 게시되고 레이블링되었습니다. 키 번호나 개인 링크는 절대 저장되지 않습니다.
이 데이터셋은 공개적입니다: 288개의 실험실 결과, 26개의 방문 기록, 26개의 체중을 포함하여 총 22가지 유형의 516개 문서로 구성되어 있습니다. ALT 수치는 2023년 12월의 2,431에서 2026년 8월의 59로 떨어졌으며, 원본 데이터에서 이를 그래프로 확인할 수 있습니다.
두 가지 '상태 확인(Sanity Context)' 엔드포인트. 에이전트는 이 두 곳에 연결됩니다:
- 데이터셋에 대한 차트 엔드포인트 (GROQ 모드 사용). 도구:
groq_query,schema_explorer,array_field_reader. - **지식 기반(Knowledge Base)**으로 지원되는 지식 엔드포인트. 저는 이 지식 기반을 하나의 GROQ 쿼리, 즉 `*[_type ==
에이전트는 최대 12개의 도구 단계와 읽기 전용 액세스를 가진 Claude Sonnet 5.5입니다. 각 작업은 기본 규칙 위에 자체 지침을 추가합니다. 'Something changed'의 경우, 경고 징후가 먼저 제시되며 과거 발작 기록은 '과거 발작 동안, 그의 기록에는...이 표시되어 있습니다.'와 같은 형식으로 다루어지며 현재에 대한 지침으로 절대 사용되지 않습니다. 이 에이전트는 먼저 정보를 찾아보고 어떤 출처를 사용했는지 인용합니다. 만약 Context 엔드포인트에 접근할 수 없다면 동일한 공개 데이터셋을 직접 조회하는 방식으로 폴백(fallback)하지만, 절대로 조용히 처리하지는 않습니다. 답변에는 'Sanity Context가 아닌 직접 쿼리'라는 알림이 표시됩니다.
방문자 데이터는 데이터셋에 저장되지 않습니다. 체크인 및 예약 정보는 브라우저에 남아 있습니다. 이는 의도적인 설계입니다. 이들은 개인 건강 정보이기 때문에, 서버에 저장하려면 계정이 필요하며, 로그인 없이 모든 방문자의 기록이 하나의 공유된 곳에 쌓이는 것을 방지하기 위함입니다. 각 페이지에는 '이 장치에만 저장됨'이라고 표시되며, 백업 파일은 이를 다른 장치로 옮깁니다. 방문 요약을 요청할 때, 브라우저는 마지막 방문 이후의 기록을 검증되고 크기가 제한된 필드로 전송합니다. 이 데이터는 모델 내부의 구분자(delimiters) 안에 '가족 입력 관찰(family-entered observations)'로 표시되어 들어갑니다. 서버 측에 아무것도 저장되지 않으며, 일일 질문 한도는 방문자의 주소 자체를 기록하는 것이 아니라 단방향 해시(one-way hash)만 유지합니다.
어디서 문제가 발생했고, 제가 어떻게 테스트했는지. 저는 철저하게 테스트했으며, 질문들은 레포지토리 안에 있습니다.
evals/ 폴더에는 24개의 감사 질문이 들어 있으며, 각 질문은 존재 이유와 몇 가지 검증 가능한 기대치(expectations)를 가지고 있고, 실행기(node evals/run.mjs)가 있습니다. 이들은 점수가 아니라 연기 테스트(smoke checks)이기 때문에, 저는 답변도 읽었습니다. 두 차례의 라운드에서 실제 문제가 발견되었습니다:
1차 라운드: 12개 질문 중 4개가 실패했습니다.
- 블루베리(Blueberries). 에이전트는 자신의 계획에 블루베리가 언급되지 않았다고 말했습니다. 하지만 영양 상담 기록에는 승인된 간식으로 목록화되어 있습니다. 제 식단 문서에는 출처 노트가 없었기 때문에, 에이전트는 그 계획이 어디서 왔는지 알려줄 수 없었습니다.
- 진단 전 섭취 음식(What he ate before diagnosis). 에이전트는 답을 볼 수 없었습니다. 저는 이 식단을 Sanity에 저장하는 대신 페이지에 하드코딩했기 때문입니다.
- 간 구리(Liver copper). 위에서 만든 지식 베이스(Knowledge Base) 행입니다.
- 아토피카(Atopica). 한 답변은 2024년 이후에는 기록된 것이 없다고 주장했습니다. 하지만 데이터는 그곳에 있었습니다. 제 질의에서는 확인 날짜를 요청한 적이 없습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기