본문 바로가기
끼리KKIRI함께 결정하는 방법을 설계합니다
문서 둘러보기
목업 체험
기획 자료순서대로 읽기

CHAPTER 08 / 13

AI 설계서 (기술 아키텍처)

AI는 어디에 쓰이고, 왜 결정은 규칙이 하나?

2026-09-06 확장 안내

아래 본문은 기존 워크숍 목업 기준입니다. 새로 추가한 여행·회식·모임·선물의 질문과 후보는 다섯 가지 결정, 질문 생성과 설정은 AI 고도화에서 확인하세요. 새 체험은 규칙 기반이며 실제 LLM·다기기 응답 수집은 미연결입니다.

읽는 순서 8 / 13 · 요약

AI는 어디에 쓰이고, 왜 결정은 규칙이 하나?

누가 읽나: "AI가 아니라 규칙 아닌가"를 묻는 심사위원 · 개발자

← 이전: 7. 케이스 스터디다음: 9. 요구사항·정책 →

원본: KKIRI_AI_DESIGN.md

P0 ② · 결정은 규칙, 이해와 설명은 LLM. 같이 보기: ① 문제 정의 · ③ 심사 대응 · ④ 요구사항·정책 · ⑤ 지표 · 안내

작성: 페이블 · 2026-09-06 · P0 산출물 ②
목적: 「AI가 아니라 규칙 아닌가」에 답한다. LLM이 하는 일, 규칙이 하는 일, 그 경계를 왜 그렇게 그었는지, 비용·지연·실패 시 동작까지 한 문서에 둔다.
기준 코드: mockup/engine.js (207줄) · mockup/app.js (419줄), 2026-09-06 실행 확인 (node check.mjs 3~8인 assertion 통과, node --test 8건 통과).


0. 한 장 요약

질문답
LLM이 하는 일① 자유서술 → 조건 구조화, ② 집계 사실 → 설명 문장 생성. (NEXT) ③ 후보안 생성, ④ 대리인 라운드
규칙이 하는 일거부권 필터, 만족도 계산, 최저 만족도 우선 정렬, 공개/비공개 경계, 개인 리포트
왜 결정은 규칙인가같은 입력이면 같은 결과(재현성), 모든 숫자를 화면에서 검증 가능(설명 가능성), LLM 문장이 개인 답변을 새게 할 통로가 없음(비공개)
「대리인」의 실체사람마다 하나씩 만들어지는 구조화된 조건 객체 {선호, 상한, 금지}와, 그 객체를 대변해 후보를 평가하는 함수. 자유 대화하는 챗봇이 아니다
현재 목업LLM 호출 0회. 구조화는 키워드 규칙 7종, 설명은 템플릿. 30초 폴백 UI만 있음
MVP에서 붙일 것LLM 호출 2종(①②), 서버·저장소·개인 토큰. 결정 규칙은 그대로
방 하나당 LLM 호출최대 참가자 수 + 1회 (자유서술 쓴 사람 수 + 설명 1회). 8명 방 ≤ 9회

1. 설계 원칙

  1. LLM은 이해하고 설명한다. 결정하지 않는다. 순위·탈락·우승은 engine.js의 결정적 규칙이 낸다. 이유: 발표 Q&A에서 「왜 양평이냐」에 숫자로 답할 수 있어야 하고, 같은 방을 다시 돌려도 같은 답이 나와야 한다.
  2. 원문은 LLM 구조화 호출 한 번만 본다. 구조화 결과(닫힌 어휘의 태그)만 저장·계산·표시한다. 설명 생성 LLM은 원문을 받지 않는다.
  3. LLM 출력은 닫힌 스키마로만 받는다. 자유 텍스트가 공개 화면에 그대로 가는 경로는 없다. 설명 문장도 「입력 사실에 있는 숫자·명사만 쓴다」 검사를 통과해야 표시된다.
  4. LLM이 죽어도 방은 끝난다. 구조화 실패 → 사용자가 목록에서 직접 고름. 설명 실패 → 템플릿 문장. (둘 다 목업에 이미 구현)

2. 현재 목업 아키텍처 (AS-IS)

[브라우저 1개]  index.html ── app.js (화면·시계·상태) ── engine.js (규칙)
                 └── localStorage (방 상태 전체)
LLM 호출: 없음
  · 자유서술 구조화  = AVOID_RULES 정규식 7종 (운전·술·숙박·격한 야외·장거리·날음식·이른 출발)
  · 설명 생성        = reasons()/concessions()/kept() 템플릿
  · LLM 타임아웃     = 패널 토글로 연출 (LLM_TIMEOUT_MS = 30000)
다기기 동기화·인증: 없음 (참가자 전환은 시뮬레이션 패널)

한계: 키워드 7종 밖의 문장(「사람 많은 데 싫어요」)은 구조화 실패로 떨어진다. 설명은 항상 같은 문형이다. 이 두 자리가 정확히 LLM이 들어갈 자리다.


3. 목표 아키텍처 (MVP)

 방장·참가자 브라우저 (기존 app.js 화면 재사용)
      │ HTTPS · 개인 토큰(/p/{token}) 또는 공용 링크+6자리 코드
      ▼
 API (Cloudflare Workers)
   ├─ /rooms            방 생성·상태·마감·리마인드 스케줄
   ├─ /answers          답변 저장 (구조화 태그만, 원문은 별도 테이블·만료 시각 포함)
   ├─ /structure  ──►  LLM ①  구조화 (원문 → 태그 JSON)      p95 3s · 타임아웃 8s
   ├─ /negotiate        engine.js 그대로 서버 실행 (결정적)
   ├─ /explain    ──►  LLM ②  설명 생성 (집계 사실 → 문장)   p95 10s · 타임아웃 30s → 템플릿
   └─ /ratify           재확인·재조율(1회)·확정
 저장소: D1(방·답변 태그·결과) + KV(토큰·코드·TTL)  ※ 원문은 결정 완료 후 삭제 (보관 기간 정책 미정)

컴포넌트 책임:

컴포넌트책임목업 대응
화면S0~S9, 집계 진행 4단계, 개인 리포트app.js 그대로
규칙 엔진vetoes → satisfaction → compareRowsengine.js 그대로, 서버에서 실행
LLM ① 구조화자유서술 1문항 → {tags[], unknown[]}structureAvoid() 대체
LLM ② 설명결과 집계 → 공개 이유 3문장 + 탈락 사유 1문장/후보reasons() 보강 (템플릿은 폴백으로 유지)
저장소방 상태, 태그, 결과, 토큰localStorage 대체

4. LLM 호출 ① — 자유서술 구조화

입력: 참가자 1명의 「이것만은 피하고 싶어요」 한 문장 (질문 6). 다른 참가자 답변은 절대 함께 넣지 않는다.
출력: 닫힌 어휘 태그. 어휘 밖이면 unknown으로 돌려주고 사용자에게 목록 선택을 받는다.

{ "tags": [ { "tag": "drinking", "topic": "식사 분위기", "confidence": 0.92, "span": "술자리 중심은" } ],
  "unknown": [ "사람 많은 데" ] }

허용 태그(현재 7종, AVOID_RULES와 동일): driving 운전 · drinking 술자리 중심 · overnight 숙박 · outdoor-heavy 격한 야외 · long-travel 장거리 이동 · raw 날음식 · early 이른 출발. 태그를 늘리려면 후보 데이터의 tags에도 같은 값이 있어야 한다(둘은 같은 어휘).

프롬프트 골격(시스템):

너는 모임 조건 분류기다. 사용자가 피하고 싶은 것을 아래 태그 중에서만 고른다.
태그: driving, drinking, overnight, outdoor-heavy, long-travel, raw, early
규칙: (1) 목록에 없는 조건은 unknown 배열에 원문 조각으로 넣는다. (2) 추측하지 않는다. confidence < 0.6이면 태그 대신 unknown. (3) JSON만 출력한다.

후처리 검증(코드): 태그가 허용 목록 밖이면 버림. confidence < 0.6은 unknown으로 강등. unknown이 비어 있지 않으면 화면에서 「이 조건은 이해하지 못했어요 — 목록에서 골라주세요」(현재 목업의 구조화 실패 UI 재사용). 사용자는 구조화 결과를 제출 전에 반드시 확인한다(현재 목업 Q6 흐름과 동일). 즉 LLM 오분류는 사용자 확인 단계에서 걸러지고, 걸러지지 못한 오분류는 개인 리포트의 「지켜진 조건」에 드러난다.

품질 목표(발표 전 측정): 문장 70개(태그당 10개) 테스트 세트에서 태그 정밀도 ≥ 0.95, 재현율 ≥ 0.85. 재현율보다 정밀도를 높게 잡는 이유: 없는 거부권을 만들어 후보를 잘못 탈락시키는 쪽이 더 나쁘다.


5. LLM 호출 ② — 설명 생성

입력: 집계 사실만. 우승 후보 스펙, reasons()가 낸 사실 목록(「팀워크 1순위 2명」), concessions()의 2명 이상 집계, kept()의 조건 건수, 탈락 후보별 topics. 개인 답변, 개인 만족도, 단독 조건은 넣지 않는다 — 넣을 수 없게 입력 스키마에서 필드 자체를 없앤다.
출력: 공개 이유 3문장 이내 + 탈락 후보마다 사유 1문장 + 「근거 신뢰도」 라벨(계산 / 집계 / 추정).

{ "reasons": ["재충전을 1순위로 낸 분이 2명이라 몰입형 하루 일정이 맞았어요.", "..."],
  "eliminated": { "c3": "예산 상한 1건과 술자리 제외 조건 1건에 걸렸어요." },
  "labels": { "reasons": "집계", "eliminated": "계산" } }

후처리 검증(코드): 출력에 나온 모든 숫자와 후보명·조건명이 입력 사실 안에 있는지 대조한다. 하나라도 없으면 그 문장을 버리고 템플릿 문장으로 대체한다. 30초 안에 끝나지 않으면 전체를 템플릿으로 대체하고 결과 배지에 「설명은 간략하게 제공돼요」(목업 구현 그대로).

이 검증이 있어서 「LLM이 없는 이유를 지어냈다」는 지적에 「지어낼 수 없다」고 답할 수 있다.


6. 결정 규칙 (LLM 아님, engine.js)

  1. 거부권: 시간 상한, 예산 상한, 금지 태그 중 하나라도 걸리면 탈락. 한 명만 걸려도 탈락.
  2. 만족도(0~100): 목적 40 (1순위 일치 1.0 / 2순위 0.5) + 방식 25 + 지역 20 + 시간 15 − 예산 선호 초과 페널티(1.5배 이내 5, 초과 10).
  3. 정렬: 최저 개인 만족도 ↓ → 평균 ↓ → 비용 ↑ → 일정 길이 ↑ → 후보 ID.
  4. 공개 경계: 2명 이상 공통인 것만 공개 이유·차이에 구체화. 단독은 「그 외 단독 선호 차이 N건」. 개인 만족도와 정확한 상한은 본인 리포트만.

「최저 만족도 우선」을 고른 이유: 다수결·평균은 소수를 버린다. 최저를 올리는 규칙(맥시민)은 「가장 덜 만족한 사람의 만족을 최대화」하므로 끼리의 약속(소수 의견 보호)과 수식이 같다. 쏠 검토에서 0.5·min+0.5·mean이 이 약속을 어기는 반례(양평 최저 52.5 < 가평 최저 60인데 양평 우승)가 재현돼 규칙을 바꿨다(docs/corrections).


7. 「대리인 협상」은 어디에 있나 — 정직한 답과 NEXT

지금 설계에 대리인끼리 메시지를 주고받는 라운드는 없다. 「대리인」은 개인 조건 객체 + 그 조건으로 후보를 채점하는 함수다. 협상에 해당하는 것은 「모든 대리인의 거부권을 통과한 후보 중 가장 덜 만족한 대리인의 점수를 최대화」하는 정렬이다. 제출 원문의 「대리인 간 협상」 표현보다 좁다. 발표에서는 이 좁은 정의를 먼저 말한다.

NEXT ④ 대리인 라운드(LLM, 범위 한정): 후보가 0개일 때만 작동. 각 대리인 LLM이 자기 조건 객체를 받아 「상한을 어디까지 완화할 수 있는가」를 JSON으로만 제안(예: {"budgetCap": "50→60", "reason_tag": "one-time"}). 규칙 엔진이 완화안을 적용해 재계산. 대리인 발언 텍스트는 만들지 않고 공개하지 않는다(쏠 검토의 재식별 문제 회피). 본인에게는 「내 대리인이 예산 상한 완화를 제안했어요 — 동의?」로 묻고, 동의한 완화만 반영. 이 설계면 「AI 대리인이 양보 지점을 협상」이라는 말을 과장 없이 쓸 수 있다.

NEXT ③ 후보 생성(LLM + 장소 데이터): 현재 후보 5개는 고정. 제품에서는 주제·지역·인원으로 후보 5~8개를 생성하고, 각 후보에 tags·cost·time을 구조화해 붙인다. 후보 태그의 정확도가 거부권 정확도를 좌우하므로 사람 검수 단계를 둔다.


8. 데이터 흐름과 비공개 경계

데이터기기서버 저장LLM ①LLM ②공개 화면본인 리포트방장
자유서술 원문O임시(만료 시각 포함)O (본인 것만)XXXX
구조화 태그·상한·선호OOXX2명 이상 집계만OX
개인 만족도계산OXXXOX
응답 여부OOXX인원수—이름별 (개인 링크) / 닉네임 (공용)
집계 사실계산OXOOOO

원문 보관 기간·삭제 시점은 정책 미정(요구사항·정책 정의서 §6). 이 표에서 「X」인 칸은 코드 경로가 없어야 하며, 테스트로 고정한다(설명 입력 스키마에 원문 필드 없음).


9. 실패 모드와 폴백

실패감지동작사용자에게 보이는 것목업
구조화 어휘 밖unknown 비어 있지 않음목록 선택 요청「이해하지 못했어요 — 골라주세요」구현 (규칙 실패와 동일 UI)
구조화 타임아웃 8s서버목록 선택 요청동일미구현
구조화 오분류사용자 확인 단계사용자가 수정제출 전 확인 카드구현
설명 타임아웃 30s서버템플릿 문장배지 「설명은 간략하게」구현 (연출)
설명 검증 실패(없는 숫자)후처리해당 문장 템플릿 대체표시 안 함미구현
후보 0개엔진탈락 사유 집계 + 재조율 안내「후보가 없어요」 화면구현
응답 3명 미만마감 시연장(24h, 최대 2회) 또는 방 닫기방장 선택구현

10. 비용·지연 (추정, 측정 후 갱신)

호출회수/방입력 토큰출력 토큰지연 목표
① 구조화자유서술 쓴 사람 수 (≤ 8)약 400 (시스템 300 + 문장 ≤ 100)약 80p95 3s, 타임아웃 8s
② 설명1약 700 (집계 사실)약 250p95 10s, 타임아웃 30s
합계 (8명 방)≤ 9≤ 3,900≤ 890—

단가는 사용하는 모델의 공식 가격표를 곱해 기입한다(이 문서에 숫자를 적지 않는 이유: 확인하지 않은 단가를 쓰지 않기 위해). 모델 추천: ①은 지연이 중요하고 출력이 작으므로 소형 모델(예: Claude Haiku 4.5), ②는 문장 품질이 중요하므로 중형 모델(예: Claude Sonnet 5). 토큰 추정은 §4·§5 프롬프트 골격 기준이며, 실제 프롬프트 확정 후 count_tokens로 다시 잰다.

방 하나가 LLM 호출 9회 이하이므로, 비용은 방 수에 선형이고 참가자 대화 길이에 무관하다 — 챗봇형 협상과 가장 다른 지점이다.


11. 검증 계획 (발표 전)

항목방법통과 기준
구조화 정확도문장 70개 라벨 세트, 모델 2종 비교정밀도 ≥ 0.95 · 재현율 ≥ 0.85
설명 충실도결과 30건 × 후처리 검증기없는 숫자·명사 0건 통과 (검증기가 잡은 건수 보고)
결정 재현성같은 입력 100회결과 동일 100/100 (규칙이므로 당연히 통과, 문서화 목적)
비공개 경계설명 입력 스키마 테스트원문 필드 포함 시 테스트 실패
지연8명 방 20회구조화 p95 ≤ 3s, 설명 p95 ≤ 10s
폴백LLM 차단 상태로 방 1개 완주결과·리포트·재확인 모두 도달

12. 발표 Q&A 답변 스크립트

Q. 이거 그냥 규칙 기반 아닌가요?
A. 결정은 규칙입니다. 일부러 그렇게 했습니다. 같은 답을 다시 넣으면 같은 결과가 나와야 하고, 왜 양평인지 숫자로 보여줘야 하니까요. AI는 두 군데에서 씁니다. 사람이 자기 말로 쓴 조건을 이해하는 것과, 결과를 사람 말로 설명하는 것. 이 둘은 규칙으로 못 합니다.

Q. 대리인이 협상한다면서요?
A. 지금 버전의 대리인은 「내 조건을 들고 후보를 채점하는 나의 대변자」입니다. 서로 말을 주고받지는 않습니다. 후보가 하나도 안 남을 때 각 대리인이 「내 상한을 어디까지 풀 수 있는지」를 제안하고 본인 동의를 받는 라운드가 다음 단계입니다. 그때도 발언은 공개하지 않습니다 — 소규모 방에서는 발언이 곧 신원이라서요.

Q. LLM이 없는 이유를 지어내면요?
A. 설명 생성기는 개인 답변을 못 받고 집계 사실만 받습니다. 출력의 모든 숫자와 이름을 입력과 대조해서, 입력에 없는 것이 하나라도 있으면 그 문장은 버리고 템플릿으로 바꿉니다.