Field guide · 크로스플랫폼 동기화

같은 Tavern 카드를 Chub.ai, RisuAI, SillyTavern에 넣어도 동일하게 동작하지 않는다. 인사말 순서가 바뀌고, lorebook 트리거가 조용히 죽고, 커스텀 프롬프트가 사라진다. 그 이유를 필드 단위로 풀어내고, 실전에서 쓰는 대응 동작까지 정리한다.

fol. i.r

같은 카드의 세 가지 삶

어제 만든 카드를 SillyTavern에서 V2 PNG로 내보내고, Chub.ai에 업로드한다. Chub이 내려주는 파일을 다시 받아서 RisuAI에 올린다. 세 곳 모두 겉으로는 거의 똑같다 ── 같은 초상, 같은 이름, 같은 description ── 그런데 실제 대화 동작은 슬그머니 달라져 있고, 원인을 찾는 데 며칠이 걸린다.

첫 메시지가 원래의 first_mes가 아니라 세 번째 alternate_greeting으로 나온다. SillyTavern에서 안정적으로 발화하던 lorebook 항목이 RisuAI에서는 조용히 발화에 실패한다. Tavern에서 분명히 설정한 extensions.depth_prompt가 세 번째 호스트에는 그냥 존재하지 않는다. 이들은 흔히 말하는 버그가 아니라, 같은 포맷만 공유할 뿐인 세 프로젝트 사이의 구현 드리프트(implementation drift)다.

fol. ii.r

세 플랫폼, 세 가지 철학

어떤 필드가 어디서 살아남을지 가늠하려면, 각 호스트가 본질적으로 무엇인지부터 기억하는 게 빠르다.

  • SillyTavern은 셀프호스트 클라이언트다. 카드 스펙을 모국어처럼 다루며 V1, V2, 그리고 V3의 상당 부분이 채팅 루프에 직접 읽힌다. 확장 기능 대부분도 스키마를 건드리는 대신 extensions 아래에 자기 키를 더하는 방식을 택한다. Tavern 카드의 “정규 동작”이 어떻게 생겼는지 보고 싶다면, 대체로 SillyTavern이 사실상의 레퍼런스다.
  • Chub.ai는 커뮤니티 호스트다. 일은 저장과 노출이지 추론이 아니다. 카드를 파싱하는 건 갤러리, 버전 관리, 검색 UI를 돌리기 위해서고, PNG 자체는 사용자의 클라이언트에 그대로 돌려준다. Chub은 자체 확장 네임스페이스( extensions 아래 chub_ 접두어 키)를 가지고 있고, 업로드 시 페이로드를 비교적 보수적으로 보존하지만, 갤러리에서 강조하는 필드는 뚜렷한 선호가 있다.
  • RisuAI는 크로스플랫폼 클라이언트(웹 · 데스크톱 · 모바일)로, 역사적으로 Tavern 포맷을 베이스 레이어로 두고 그 위에 자체 모듈 시스템을 얹어 왔다. lorebook, 트리거, 모듈 바인딩에는 Risu 고유의 작법이 있고, 그것들은 extensions 를 통해 오간다. 카드는 다른 호스트에서도 열리지만, Risu 고유 동작은 함께 옮겨가지 않는다.

어느 설계가 틀린 것은 아니다. 일을 클라이언트에 둘지, 호스트에 둘지, 휴대 가능한 런타임에 둘지 ── 세 가지 다른 내기의 결과이고, 창작자가 마주하는 드리프트는 그 내기들이 같은 파일 포맷 위에 드리운 그림자다.

fol. iii.r

필드 드리프트는 어디에 모이는가

드리프트는 무작위가 아니다. 스펙이 느슨하게 정의된 필드와, 각 플랫폼이 확장해 둔 필드에 몰린다. 아래 표는 작업용 대조표이지 계약서가 아니다 ── 버전마다 동작이 바뀌어 왔고 앞으로도 계속 바뀔 것이다.

FieldSillyTavernChub.aiRisuAI
character_book네이티브, 완전한 V2 의미론보존, chub_lorebook 확장으로 노출읽지만 Risu 자체 lore 모듈로 재투영되는 경우가 많음
alternate_greetings순서 보존, 사용자 선택 가능순서 보존, 갤러리는 표시용으로 재정렬할 수 있음대체로 순서 보존, UI 선택기는 빌드별로 다름
extensions.*대부분 보존, 매칭되는 확장이 소비chub_ 키 포함 라운드트립 가능Risu 키는 존중, 그 외 키는 재내보내기 시 누락 가능
mes_example「START」 마커로 파싱, few-shot으로 투입그대로 보관, 호스트가 해석하지 않음해석함. 공백과 마커가 정규화될 수 있음
system_prompt존중, 전역 preset과 병합될 수 있음그대로 보관존중, Risu 모듈에 의해 재포장될 수 있음
assets (V3)지원 확장 중, 경로 해석 규칙은 아직 수렴 중보관하지만 일부 종류만 표시됨부분 지원, 표정 에셋은 별도 경로
creator_notes_multilingual (V3)있으면 읽음creator_notes와 함께 표시되기도 함표시가 일관되지 않음

두 패턴이 반복된다. 첫째, extensions는 가장 정직한 네임스페이스다. 호스트들이 더하고 싶은 건 전부 이곳에 모이고, 라운드트립의 성공은 다음 호스트가 그 키를 아는지에 전적으로 달려 있다. 둘째, 해석되는 필드일수록 더 많이 드리프트한다. JSON을 그저 보관하는 호스트 (Chub)는 잃는 게 적고, 파싱해 재렌더링하는 호스트 (SillyTavern, RisuAI)는 잃는 게 많다.

fol. iv.r

PNG tEXt: 보이지 않는 무대

필드 수준의 해석 차이 이전에, PNG 컨테이너 자체가 드리프트의 원천이다. Tavern 카드 본문은 PNG의 tEXt 청크(큰 경우엔 zTXt) 안에 살며, 키는 chara, 값은 Base64로 인코딩된 JSON이다. 세 호스트가 이 청크를 건드리는 정도는 다르다:

  • SillyTavern은 임포트 시 읽고 익스포트 시 다시 쓴다. 알고 있는 필드에 대해서는 무손실이지만, 인식하지 못하는 확장 데이터는 왕복 과정에서 순서가 재배치되거나 정규화 되었다는 보고가 있어 왔다.
  • Chub.ai는 업로드 시 원본 청크를 대체로 보존하지만, 썸네일이나 최적화 변형을 만들 때 PNG 자체를 다시 인코딩할 수 있다. 정식 카드 다운로드 경로가 가장 안전한 페치 경로다.
  • RisuAI의 임포트 경로는 페이로드를 자체 모델에 통과시킨 뒤 다시 내보낸다. Risu를 한 바퀴 돈 카드는 구조적으로 동일하면서 바이트 단위로는 다르게 나오는 일이 흔하다.

카드가 업로드 직후 조용히 필드를 잃는데 원인이 안 보인다면, 답은 보통 이 층에 있다. 인코딩의 디테일은 별도 글 PNG tEXt 청크 메타데이터와 Tavern 카드에서 더 깊게 다룬다.

fol. vi.r

라이브러리를 동기화 상태로 유지하는 법

세 호스트를 완벽히 일치시킬 수는 없다. 라이브러리가 여행을 견딜 정도로는 가깝게 둘 수 있다. 가장 도움이 되는 작업 습관은 셋이다:

  1. 최대공약수 필드 집합을 주역으로 삼는다. V2의 정규 키 ── name, description, personality, scenario, first_mes, alternate_greetings, mes_example, system_prompt, character_book ── 을 하중을 받는 구조재로 다루고, extensions 안에 든 모든 것은 베스트에포트로 다룬다. 동작이 중요하다면 정규 키로 표현할 수 있어야 한다.
  2. 릴리스마다 라운드트립 테스트. 진실의 원본에서 내보내, 각 호스트에 들이고, 다시 내보내고, JSON을 diff한다. 그 diff가 드리프트다. 대부분의 작자는 이것을 처음 한 번만 수작업으로 하고 만다. 오래 살아남는 카드는 이것을 자동화한 사람이 키우는 카드다.
  3. diff를 리포트로 남긴다. 드리프트는 누적된다. 어제 라운드트립 세 번을 견뎌낸 필드가 다음 주 호스트 업데이트에 끊어질 수 있다. diff를 감이 아니라 데이터로 추적하는 것이, 독자보다 먼저 조용한 퇴화를 잡아내는 유일한 방법이다.
fol. vii.r

원본은 하나, 방언은 셋

tavernai.cards는 바로 이 문제를 위한 코덱스로 만들고 있다: 정규 카드는 하나, 살아 있는 방언은 셋, 그리고 읽을 수 있는 diff.

  • 양방향 동기화 ── 같은 카드를 Chub.ai, RisuAI, SillyTavern으로 밀어 넣고, 호스트 쪽 편집을 진실의 원본으로 끌어온다.
  • 자동 diff 리포트 ── 라운드트립이 끝날 때마다 어떤 필드가 보존, 정규화, 혹은 누락되었는지 호스트별로 보여준다.
  • 포맷 인식 변환 ── V1, V2, V3와 각 호스트가 extensions 아래 쌓아 온 방언을 블랙박스가 아닌 눈에 보이는 매핑으로 변환한다.
  • 공개 전 lint ── 호스트 비호환 필드, 깨진 에셋 참조, 무음의 인코딩 버그를 카드가 떠나기 전에 잡아낸다.

작업대는 차례로 열리고 있다. Chub, Risu, Tavern 위에 동시에 살아 있어야 하는 카드 라이브러리를 운영하고 있다면, 두루마리에 먼저 이름을 올려두자 ── 크로스플랫폼 동기화가 가장 먼저 나가는 기능이다.

다른 언어