GitHub - chessire/shelter-puppy

Contribute to chessire/shelter-puppy development by creating an account on GitHub.

github.com

임베딩은 진위를 가리지 않고,
유사도를 잽니다.
검수는 검색 뒤가 아니라,
저장 앞에 세웠습니다.

 

영상을 만드는 이야기는 지난 편에서 일단락 됐습니다. raw 영상 아홉 개가 4분 만에 내레이션 붙은 숏폼이 되는 파이프라인이었습니다. 이제 상담 봇을 제작할 차례입니다.

 

"다른 강아지랑 잘 지내나요?"

 

입양 문의입니다. 영상은 관심을 만들고, 관심은 질문이 됩니다. 질문에 답하려면 그 강아지에 대한 기록이 필요하고, 기록을 담을 저장소가 필요합니다. 그래서 2부의 첫 작업은 봇이 아니라 DB입니다. 이번 편은 Postgres + pgvector + bge-m3로 로컬 RAG 저장소를 설정하고, 영상 아홉 개를 인제스트 해서 위 질문에 타임스탬프 붙은 관찰 기록이 검색되기까지의 기록입니다.

python -m rag.cli search tori "다른 강아지랑 잘 지내나요?" --card

 

위 명령의 실제 출력

RAG를 붙이면 환각이 해결되지 않을까?

 

이번 함정은 그 기대 자체에 있습니다. RAG의 약속은 "문서에 근거하여 답한다"입니다. 그런데 이 약속에는 전제가 하나 숨어 있습니다. 문서가 사실이어야 한다. 이 시스템의 문서는 사람이 쓴 글이 아니라 기계가 영상을 보고 적은 텍스트입니다. 캡션, 행동 서술, 그리고 그것들을 묶은 요약이죠. LLM이 지어낸 문장이 DB에 한 번 들어가면 검색은 그 문장을 성실하게 찾아주고, 봇은 자신 있게 인용합니다. 임베딩 점수가 아무리 높아도 그건 질문과 비슷하다는 뜻이지, 사실이라는 뜻은 아닙니다.

 

그래서 이번 편의 규칙은 검색 튜닝이 아니라 저장 규칙입니다.

문장의 출처로 저장 위치를 정한다.
LLM이 문장을 만드는 곳은 한 곳이고,
그 문장은 검수를 통과해야 저장된다.

 

3계층 저장 구조


20. 출처로 저장 위치를 정한다 - 스키마 설계

구축 자체는 별 것 없었습니다. Postgres는 이미 설치해두었고, pgvector를 얹어 CREATE EXTENSION vector 한 줄을 호출했습니다. 임베딩은 Ollama의 bge-m3를 사용했습니다. "잘 지내나요?"라는 한국어 질문과 한국어 관찰 기록을 같은 벡터 공간에 놓아야 하다보니 다국어 임베딩이 필요해서 선택했습니다. 클라이언트는 기존 venv의 psycopg2와 ollama 그대로라 추가 파이썬 의존성 0으로 로컬 스택은 1부에서 세운 그대로입니다. 구성은 schema.sql, db.py, embed.py, ingest.py, retrieve.py, cli.py(init / card / ingest / search)입니다. 단위 테스트 11개도 추가되었습니다.

 

추가적인 설계가 들어간 곳은 스키마입니다. 발단은 사소한 질문이었습니다.

"할루시네이션이 걱정되니 텍스트에 LLM 저작이 얼마나 섞였는지, 비율을 메타데이터로 넣을까?"

 

출처를 연속값 컬럼으로 기록하자는 발상인데, 구상 끝에 결정을 바꿨습니다. 인제스트하는 시점에 코드는 이 텍스트가 어디서 왔는지 이미 알고 있습니다. 그 사실을 컬럼에 다시 적는 건 군더더기고, 비율이라는 연속값은 "이 문장을 믿어도 되는가"라는 이진 질문에 답하지 못합니다. 대신 출처로 저장 위치를 구분했습니다.

자리 저자 규칙
카드 사람 보호자가 입력한 정형 정보. 백신·구조 이력 같은 사실.
L2 청크 기계 관찰 원문 영상 분석이 적은 그대로. LLM 수정 금지 — 원문이 청크의 부분문자열로 보존되는 것을 테스트로 고정.
L1 청크 LLM 파이프라인 전체에서 LLM이 새 문장을 만드는 유일한 곳. 검수 필수(다음 절).
저작 자막 — DB에 넣지 않음. 연출용 문장은 사실 저장소에 섞이지 않습니다.

 

L2는 코드가 조립합니다. 행동 서술에는 인용 타임스탬프를 붙이고(신뢰도 0.7 이상, 비uncertain 구간 중 최고 신뢰도), 확정된 장면 태그를 합칩니다. 6편에서는 필드의 소유권으로 갈랐다면, 이번에는 문장의 저자로 가른 셈입니다.

 

노출도 통제했습니다. 카드 필드는 3단 가시성이라 구조 위치 같은 스태프 정보는 검색에 나오지 않고, 민감한 필드는 순화본만 노출됩니다. e2e에서 게이팅이 그대로 작동하는 것까지 확인했습니다.


21. LLM의 문장은 검수를 통과해야 저장된다 - L1 요약

L1은 강아지 한 마리의 관찰 전체를 묶은 요약입니다. "활발하고 다른 개들과 잘 어울립니다" 같은. 상담 봇 입장에서 가장 쓰기 좋은 텍스트지만, 동시에 이 저장소에서 유일하게 LLM이 지어낼 수 있는 텍스트입니다. 그래서 저장 전에 관문을 세웠습니다.

 

절차는 이렇습니다. Gemma가 요약을 쓰고(temperature 0.2, 프롬프트는 지시만 — 6편의 규칙 그대로) → 요약을 주장 단위로 분해 > 주장마다 관찰 기록과 대조 > 근거 없는 주장은 증거 범위로 제한해 재작성 > 그래도 근거 미달이면 드롭. 요약이 통째로 비어도 시스템은 무너지지 않습니다. L2와 카드가 남으니까요. 요약은 편의성을 위한 것이지 진실의 원천이 아닙니다. 위층이 무너져도 아래층이 받치는 안전한 저하 구조입니다.

 

검수 기능은 새로 만들지 않았습니다. 자막 사실 검수에 쓰던 기능(주장 분해, 근거 대조)을 파이프라인에서 그대로 import 했습니다.

판정 단위는 한 번 나눴습니다. 자막 검수는 all-fit입니다. 블록에 들어간 모든 소스가 주장을 받쳐야 통과합니다. 하지만 L1은 강아지 전체의 요약이라 **기록이 하나라도 맞다면 근거(any-fit)**로 정했습니다. "다른 강아지와 잘 논다"는 주장은 아홉 영상 중에서 한 영상에서만 특정 되어도 성립하니까요.

 

그리고 e2e에서 이 관문이 실제로 발동했습니다.

  • "고양이와 서로 마주 보고 앉아 있음", 사교성의 근거로 미달 판정.
  • "움직임 없이 정지해 있는 상태", 안정성의 근거로 미달 판정.

둘 다 그럴듯합니다. 마주 보고 앉아 있으면 사교적인 것 같고, 정지해 있으면 안정적인 것 같죠. 검수 기능은 그 동의어 경계에서 보수 쪽으로 판정했고, 두 문장은 증거 범위로 재작성된 뒤에야 통과했습니다. 정밀도를 우선하였기에 설계 의도는 그대로였습니다. 입양 문의에 답하는 시스템에서 "그럴듯한데 근거 없는 말"은 가장 비싼 오류입니다.


22. 신뢰 경계는 DB 앞까지 - 게이트와 멱등

저장 규칙만큼 중요한 것이 게이트입니다.

 

첫째, re-ID 게이트. 잡에 재식별 미확정(foster_uncertain)이 남아 있으면 인제스트 자체를 거부합니다. 1부에서 재식별을 카드 한 장, 탭 한 번으로 확정하게 만들었는데, 그 신뢰 경계가 DB 입구까지 연장된 겁니다. 누구의 것인지 확정되지 않은 관찰은 그 강아지의 기록이 될 수 없습니다. "일단 넣고 나중에 지우자"는 없습니다. 지우는 걸 잊는 날이 오니까요.

 

둘째, 재인제스트 멱등. video_id는 소스 파일명입니다. 같은 영상을 다른 잡에서 다시 인제스트하면 중복이 아니라 갱신입니다. 영상 분석은 업서트, L2는 그 영상의 청크를 지우고 재삽입, L1은 그 강아지 전체를 재생성합니다. 실측으로 확인했습니다. 같은 잡을 두 번 인제스트한 전후 행 수가 동일합니다. 유저 요청 원문은 저장하지 않아서 요청에서 유래하는 데이터는 자동 장면 태그가 유일하고, 그것도 set 병합이라 누적되지 않습니다.

 

한계도 적어두겠습니다. 파일명이 곧 정체성이라, 다른 푸티지가 같은 파일명으로 오면 중복이 아니라 덮어쓰기가 됩니다. 영상이 본격적으로 누적되는 운영 전에 콘텐츠 해시 같은 정체성 강화가 필요합니다. 나중에 구현할 수 있도록 남겨뒀습니다.

 

셋째, 재집계는 DB 기반. L1 재생성은 잡 디렉토리가 아니라 DB에 쌓인 영상 분석을 읽습니다. 잡 폴더를 정리해도 요약은 다시 만들 수 있고, 영상이 쌓일수록 요약이 두꺼워집니다. 임시 산출물과 축적 데이터의 경계를 여기서 그었습니다.


23. "모른다"는 분포에서 나온다 - 검색 스모크

인제스트 결과는 영상 9개 → L2 18청크, L1 4청크. 여기에 질문 네 종류를 던졌습니다.

  • 사교성 질문("다른 강아지랑 잘 지내나요?") > 놀이 장면 관찰 2건이 타임스탬프와 함께 상위(0.56~0.57), 활동성 요약이 뒤따름.
  • 고양이 질문 > 고양이 관찰 기록이 1위.
  • 산책 질문 > 장면 태그가 합류된 산책 영상들.
  • 그리고 개인기 질문, 기록에 없는 것. 최고 점수 0.44로 낮게 깔렸습니다.

네 번째가 이번 스모크의 핵심입니다. 있는 것을 찾는 것은 임베딩의 기본기지만, 없는 것을 물었을 때 점수가 낮게 깔리는 분포여야 상담 봇이 임계값 하나로 관찰 기록이 없다고 답할 수 있습니다. 봇의 정직함은 화법에서 나오는 것이 아니라 이러한 분포에서 나옵니다. 아는 것과 모르는 것 사이에 점수 간격이 있어야 모른다는 것을 구현할 수 있게 됩니다.

 

테스트는 11개가 추가됐습니다. RAG는 파이프라인의 산출물을 읽기만 하는 별도 컴포넌트로 구축하였습니다.

 

다만 시리즈의 기준으로 보면 스모크는 스모크입니다. 검색 품질도 골든셋으로 채점하는 일이고, 질문과 정답 쌍을 손으로 만들어 bge-m3의 검색을 점수화하는 일은 아직 하지 않았습니다. 관찰 서술이 장면 단위라서 다견 영상의 무리 행동("여러 마리가 뒤엉켜 놀음")이 개체 귀속 없이 남는 것도 한계입니다.


맺음말

DB 하나를 설정하는 데 하루가 걸렸고, 규칙은 네 개였습니다.

  • 출처를 고정합니다. 카드는 사람, L2는 관찰 원문, L1만 LLM — 저작 텍스트는 DB 밖.
  • LLM의 문장은 검수를 통과해야 저장됩니다. 주장 분해 > 기록 대조 > 재작성 1회 > 드롭. e2e에서 실제로 두 건을 잡았습니다.
  • 신뢰 경계는 DB 앞까지입니다. 재식별 미확정이면 인제스트 거부, 재인제스트는 멱등.
  • "모른다"는 분포로 준비합니다. 무관 질문이 0.44로 깔리는 간격이 상담 봇 정직함의 재료입니다.

시리즈의 문장에 하나를 보탭니다. 골든셋으로 채점하고, 측정할 수 있는 것은 측정에게 맡기고, 구조의 주인을 하나로 세우고, 빠르게 만들 때조차 자부터 검증한다. 그리고 DB에 넣기 전에, 출처부터 고정한다.

 

다음 편은 이 저장소 위에 서는 상담 봇입니다. 검색된 기록을 어떤 화법으로 인용할지, 카드·요약·관찰 원문에 각각 다른 말투를 줄지, 그리고 점수가 임계 아래일 때 "모른다"를 어떻게 말하게 할지. 저장이 정직해졌으니, 이제 다음을 구현하면 됩니다.


이 글에서 다룬 고정 함수 분류층은 ProjectDavid의 일부입니다.

전체를 바닥부터 만드는 과정은 강의 3부작으로 정리하고 있고, 10월에 오픈 예정입니다.

→ 오픈 알림 받기


이전글

 

스레드보다 자원: Mac GPU(MPS)로 3.5배 빠르게 - Resource First AI

GitHub - chessire/shelter-puppyContribute to chessire/shelter-puppy development by creating an account on GitHub.github.com파이프라인을 빠르게 만드는 첫 질문은"스레드를 몇 개 띄울까?"가 아니라"어떤 자원이 어디서 놀고

chessire.tistory.com

관련글

 

오픈소스 LLM vs 빅테크 LLM API: AI 사업의 비용과 리스크

모델은 커모디티가 된다.마진은 판단에 남는다. 2025년 2월, OpenAI는 당시 가장 비싼 모델 GPT-4.5를 출시했습니다. 백만 토큰당 입력 75 달러, 출력 150 달러. 그리고 다섯 달이 지나기 전, API에서 제거

chessire.tistory.com

 

 

GitHub - chessire/shelter-puppy

Contribute to chessire/shelter-puppy development by creating an account on GitHub.

github.com

raw 폰 영상 아홉 개와 요청 한 문단.
약 4분 뒤, 내레이션이 붙은 세로 숏폼 하나.
그 사이의 모든 판단을 맥 한 대 안에서, 이 결과물이 나오게 됐습니다.

 

 

이 글은 그 파이프라인을 만든 7편의 기록에 대한 요약입니다.


"유기견 입양에 AI를 붙였다"가 가리는 것

보통은 이렇게 떠올립니다. 모델에 영상을 던지면, 알아서 편집되고, 그럴듯한 게 나온다. 데모 하나는 그럭저럭 나옵니다. 틀린 말은 아닙니다.

하지만 그 출발점은 정작 중요한 두 질문을 가립니다.

  • 내일 똑같이 다시 뽑을 수 있는가? (재현)
  • AI가 지어낸 것과 사람이 정한 것이 섞이지 않는가? (신뢰)

단순한 LLM 데모는 이 둘에 답하지 않습니다. 이 시리즈는 이 두 질문에 답하려고 내린 결정들의 기록입니다.


한 문단으로

  • LLM은 레시피만 씁니다. 실행은 코드가 합니다. 그래서 LLM이 환각을 뱉어도 실행 단계에서 격리됩니다. (temperature 0 + 스키마 강제 + 허용셋 밖 라벨은 거부)
  • 모델은 갈아끼우는 부품, 골든셋은 그 부품을 채점하는 기준입니다. 눈으로 하는 데모 비교 대신, 정답을 손으로 계산해 대조하는 단위테스트 130여 개 위에서 결정을 내렸습니다. 실제로 당연히 먹힐 것이라고 여겼던 개선안들이 골든셋 채점에서 두 번이나 기각 당했습니다.
  • 가능한 많은 것을 외부 API가 아니라 맥 한 대 안에 둡니다. 데이터가 매 호출마다 밖으로 나가지 않고, 호출 비용이 없고, 통제권이 제 손에 남습니다.
  • 자동화가 못 믿는 딱 그 지점에만 사람을 남깁니다. 재식별은 90%를 자동으로 하고, 애매한 나머지는 카드 한 장·탭 한 번(영상당 1.75탭 실측)으로 끝냅니다. "AI가 다 한다"보다 정직하고, 실제로 더 잘 작동했습니다.

이건 강아지 영상 이야기가 아닙니다

도메인은 강아지였지만, 만든 것은 신뢰할 수 있는 로컬 AI 파이프라인을 세우는 방법론입니다. 골든셋으로 채점하는 법, LLM과 코드의 권한을 가르는 법, 환각을 격리하는 법, 빠르게 만들 때조차 결과 동일성부터 지키는 법. 이 규칙들은 영상 도메인에 묶여 있지 않습니다. 입력이 픽셀이든 문서든 로그든, 옮겨 붙습니다.

그래서 이 글은 두 부류를 위한 것입니다. 데모가 아니라 내일도 똑같이 도는 시스템을 만들어야 하는 사람. 그리고 no-code 위에서 뭔가 만들었는데 왜 두 번째 입력에서는 안 되는지 답답한 사람.


7편 링크

  1. 맥 로컬 LLM 구축, 무엇을 내 손 안에 두고, 무엇만 밖에 맡길 것인가. 노트북 한 대에 세운 환경.
  2. 로컬 LLM 아키텍처, 왜 로컬인지, 왜 이 구성인지. 선택의 이유.
  3. YOLO11 해상도 함정, 정답지부터 만들고 모델을 붙인다. 해상도를 올렸더니 오히려 나빠진 이야기.
  4. 결정론적 AI 파이프라인 설계, 같은 입력이면 같은 출력. 창작만 예외로 두되, 그 창작에도 가드를 건다.
  5. 로컬 TTS로 타임라인 컨트롤, 대본을 음성으로, 음성을 영상 타임라인에 맞추기.
  6. LLM은 지시만, 프롬프트에 내용을 넣으면 출력으로 샌다. 같은 문제를 네 번 고치고 얻은 규칙.
  7. 스레드보다 자원, 8분을 4분으로. 가장 큰 배수는 멀티스레딩이 아니라, 놀고 있던 GPU를 깨운 한 줄에서 나왔습니다.

정직하게 남겨둘 것

이 검증들은 서로 다른 다섯 개의 영상으로 만든 골든셋에서 이뤄졌습니다. 도메인은 하나, 혼자서 진행했습니다. 그래서 이 기록은 이 방법이 모든 곳에서 최고라는 것이 아닙니다. 이 방법이면 만든 것을 믿을 수 있고, 부품을 갈아끼워도 기준이 흔들리지 않는다는 것입니다. 방법론의 엄밀함이지, 규모의 검증은 아닙니다. 그 경계를 넘는 건 다음 도메인의 몫입니다.


이 글에서 다룬 고정 함수 분류층은 ProjectDavid의 일부입니다.

전체를 바닥부터 만드는 과정은 강의 3부작으로 정리하고 있고, 10월에 오픈 예정입니다.

→ 오픈 알림 받기


이전글

 

스레드보다 자원: Mac GPU(MPS)로 3.5배 빠르게 - Resource First AI

GitHub - chessire/shelter-puppyContribute to chessire/shelter-puppy development by creating an account on GitHub.github.com파이프라인을 빠르게 만드는 첫 질문은"스레드를 몇 개 띄울까?"가 아니라"어떤 자원이 어디서 놀고

chessire.tistory.com

 

관련글

 

오픈소스 LLM vs 빅테크 LLM API: AI 사업의 비용과 리스크

모델은 커모디티가 된다.마진은 판단에 남는다. 2025년 2월, OpenAI는 당시 가장 비싼 모델 GPT-4.5를 출시했습니다. 백만 토큰당 입력 75 달러, 출력 150 달러. 그리고 다섯 달이 지나기 전, API에서 제거

chessire.tistory.com

 

 

GitHub - chessire/shelter-puppy

Contribute to chessire/shelter-puppy development by creating an account on GitHub.

github.com

1. 강아지를 입양했다

기술을 먼저 정하고 문제를 찾은 게 아닙니다.
한 마리를 데려오고 나서야
풀어야 할 문제가 보였습니다.

 AI로 무언가를 만들어보려던 참이었습니다. 도구는 손에 있는데 무엇을 풀지가 없던 흔한 상태였습니다.

 

 최근에 강아지를 입양해왔습니다. 5월 초부터 마음을 정해두고, 아내가 매일같이 보호 글을 들여다보며 인연을 찾았습니다. 그렇게 5월 24일, 강릉의 한 보호소와 연계된 임시보호자님에게 있던 아기 강아지를 데려왔습니다.

 

 이 아이의 사정은 이랬습니다. 떠돌이 어미가 낳은 새끼들 중 하나였고, 새끼들만 구조되었고 어미는 끝내 구조되지 못했다고 들었습니다. 세 마리 중 둘은 먼저 가족을 찾았고, 가장 오래 떠나지 못하고 남아 있던 아이가 우리에게 왔습니다.

 

 데려오는 과정에서 예상하지 못한 걸 봤습니다. 임시보호자와 입양 희망자 사이에서 오가는 소통, 정보, 그 사이의 불편함이 분명히 존재했습니다. 사람 손만으로는 메우기 버거운 자리였습니다.

 

 그 순간 두 가지가 겹쳤습니다. 풀 문제를 찾던 나, 그리고 눈앞에 보인 진짜 불편함. 마침 AI를 만들고 싶었던 저는 그 틈을 AI로 메울 수 있겠다 싶었습니다. 그래서 결심했습니다. 이 틈을 내 능력으로 메워보자고요.

 

 이 글은 그 결심에서 출발한 기록입니다. 그리고 다음 이야기들은 "왜 로컬인지", "왜 이렇게 도구를 쪼갰는지"를 다뤄보고자 합니다.

 

귀여운 토리


2. 영상 - 왜 전부 LLM에 맡기지 않을까?

"AI한테 영상 던지고 '이 강아지 뭐 해?' 물어보면 되잖아."
이 방법은 가장 비싸고 가장 느리게 도착하는 방법입니다.

 

 보호소 강아지의 영상들을 가지고 입양 공고 콘텐츠로 만든다고 해봅시다. 멀티모달 LLM에 영상을 통째로 밀어 넣으면 끝날 것 같습니다. 하지만 이 출발점은 비용도 속도도 가장 안 좋은 형태로 시작됩니다.

 

 여기서 짚어야 할 게 있습니다. "AI"는 한 덩어리가 아니라 파이프라인입니다. 영상 한 편을 처리하는 데에도 성격이 다른 셋이 섞입니다.

  1. 고전 알고리즘 (OpenCV) - 학습이 없습니다. 수학과 규칙으로 돕니다. 빠르고, 싸고, 결과가 일정합니다.
  2. 학습된 전용 신경망 (YOLO) - AI지만 "검출" 한 가지에 특화돼 있습니다. 프레임마다 강아지의 박스와 위치를 찾습니다.
  3. 생성형 LLM (gemma) - 의미를 판단합니다. "이 강아지는 지금 사람을 반기는 중"이라는 해석. 강력하지만 느리고, 비싸고, 가끔 틀립니다.

 만약 강아지가 화면 어디 있는지 박스 좌표를 찾는 일까지 LLM에 시키면 어떻게 될까요? 느리고, 부정확하고, 비용만 듭니다. 그건 YOLO가 훨씬 잘하고 싸게 하는 일입니다.

 

 그래서 설계의 핵심은 이렇게 정리됩니다.

 

 잘 만든 설계는 무거운 생성형 LLM을 '꼭 필요한 한 스텝'에만 씁니다. 나머지는 싸고 빠른 도구로 분리합니다.

 

 실제 영상 한 편은 이렇게 흘러갑니다.

  • 프레임 자르기·인코딩·자막 입히기 -> ffmpeg (그냥 도구)
  • 강아지가 어디 있나(박스) -> YOLO (검출)
  • 그 강아지를 영상 내내 같은 ID로 유지 -> ByteTrack (추적)
  • 마지막에 딱 한 번, "이 장면을 입양 공고용으로 어떻게 설명할까"를 저작 -> gemma (의미·생성)
  • 대본을 나레이션 음성으로 입힌다 -> mlx-audio (TTS)

 작은 비영리에서 이건 취향이 아닌 비용 절감입니다. LLM 호출 한 번이 곧 비용이고 솔로 운영에서 느린 파이프라인은 곧 굴리지 못하는 파이프라인이 됩니다. 무거운 도구를 아껴 쓰는 설계가 비영리 사업을 지속 가능하게 만드는 조건이 될 수 있습니다.

 

 이 "계층 분리"는 영상에만 해당하는 이야기가 아닙니다. 사실 이 프로젝트 전체를 관통하는 질문이고 다음 DB 이야기도 똑같은 자리에서 출발합니다.


3. DB - 왜 Postgres와 벡터DB를 같이 둘까?

하나는 "정확히 일치하는 것"을 찾고
다른 하나는 "비슷한 의미"를 찾습니다.

 

 DB를 두 개나 쓴다고 하면 과해 보입니다. "그냥 데이터베이스 하나면 되지 않나?" 하지만 이건 중복이 아니라 2번에서 본 것과 똑같은 역할 분리입니다. 두 DB는 애초에 답하는 질문이 다릅니다.

  1. PostgreSQL
    "정확히 일치하는 것"을 찾는다. 회원, 강아지 공고, 입양 신청처럼 행과 열로 떨어지는 데이터입니다. "보호 중이고, 중성화 완료이고, 3살 이하인 강아지" — 조건이 명확하고, 답도 딱 떨어집니다. 이러한 과정은 관계형 DB가 최적입니다.
  2. 벡터DB(chromadb)
    "비슷한 의미"를 찾는다. "사람을 잘 따르고 아파트에서도 키울 만한 순한 아이" 같은 질문은 다릅니다. 어느 칸에도 그렇게 적혀 있지 않습니다. 이건 의미가 가까운 데이터를 찾는 일이고 텍스트를 숫자 벡터로 바꿔(임베딩) 거리로 비교해야 됩니다. 관계형 DB의 WHERE 조건으로는 풀기 어려운 문제입니다.

 그래서 둘은 대체재가 아닙니다. 하나는 사실을 정확히 찾고, 하나는 의미를 비슷하게 찾습니다. 응대봇이 "이 조건에 맞는 강아지"(Postgres)와 "이 느낌의 강아지"(벡터DB)를 동시에 답하려면 둘 다 필요합니다.

 

 이게 2번의 영상 이야기와 똑같은 자리인 이유가 여기 있습니다. "만능 도구 하나로 다 해결한다"가 아니라 "질문의 성격에 맞는 도구를 선택한다". LLM에 전부 안 시키듯, DB도 한 종류에 전부 안 맡깁니다.

 

 다만 솔직히 둘 다 끌고 가는 건 비용입니다. 운영할 DB가 둘이 되니까요. 그래서 지금은 명확히 분리해 두되, 규모가 커지면 한쪽으로 합치는 길도 열어둡니다. PostgreSQL에 벡터 검색 확장(pgvector)을 얹어 한 DB에서 사실과 의미를 같이 다루는 선택지입니다. 초기에는 역할을 선명하게 분리하고, 규모가 커지면 운영 부담을 줄이는 쪽으로 통합하려 합니다.


4. 응대봇 - 왜 로컬이며, 왜 이 모델일까?

똑똑한 모델 하나를 고르는 문제가 아닙니다.
"무엇을 사용해야 하는가"와
"어떤 일에 어떤 모델을 쓰는가"입니다.

 

 입양 문의에 답하는 봇을 만든다고 해봅시다. 가장 쉬운 길은 외부 API(예: GPT)를 부르는 겁니다. 그런데 그 길을 택하는 순간, 1편 첫머리에서 던졌던 질문이 그대로 돌아옵니다. 무엇을 로컬에 두고 무엇을 밖에 맡길 것인가.

 

 왜 로컬(Ollama)인가. 세 가지 이유입니다.

  • 데이터. 강아지 정보, 보호 기록, 입양 신청자 문의가 매 호출마다 외부 서버로 나갑니다. 명분 사업에서 보호 동물과 신청자의 데이터를 남의 서버에 흘리는 것은 가볍지 않습니다. 로컬은 그 정보들이 내 기기 밖으로 안 흘러나갑니다.
  • 비용. 외부 API는 호출당 과금입니다. 응대봇은 많이 부를수록 가치가 큰데 많이 부를수록 그대로 토큰 값이 됩니다. 로컬 모델은 전기값 외에 호출당 비용이 0입니다.
  • 통제. 모델이 갑자기 바뀌거나 가격이 오르거나 정책으로 막히는 일에서 자유롭습니다. 예측 가능함이 곧 안정성입니다.

왜 이 모델 조합인가. 여기서도 2번의 "계층 분리"가 그대로 반복됩니다. 응대봇은 모델 하나가 아니라 역할이 다른 둘로 작동합니다.

  • bge-m3 (임베딩) — 대화하지 않습니다. 텍스트를 숫자 벡터로 바꾸는 일만 합니다. "이 강아지 설명"과 "사용자 질문"을 같은 좌표 공간에 올려 의미가 가까운지를 잴 수 있게 만듭니다. 한국어 성능이 좋아 강아지 데이터에 적합합니다.
  • gemma (생성) — 답을 만듭니다. 검색해 온 데이터를 읽고, 영상을 해석하며, 사람에게 건넬 문장으로 풀어냅니다.

이 둘이 3번의 두 DB와 맞물려 하나의 흐름이 됩니다. 이게 RAG입니다.

  1. 사용자가 묻습니다. "사람 잘 따르고 아파트에서도 키울 만한 아이 있어요?"
  2. bge-m3가 질문을 벡터로 바꾸고 벡터DB에서 의미가 가까운 강아지를 꺼냅니다. (동시에 Postgres로 "보호중, 입양가능" 같은 사실 조건도 거릅니다.)
  3. 그렇게 추려진 진짜 데이터를 gemma에게 건네며 답을 만들게 합니다.

 RAG의 핵심은 gemma가 아는 척 지어내지 않게 만드는 겁니다. 모델의 메모리가 아니라 우리 DB의 실제 강아지 데이터를 근거로 답하니까요. 있지도 않은 강아지를 추천하는 환각은 단순 오류가 아니라 신뢰 문제입니다. RAG는 그 위험을 구조로 막습니다.

 

모델 사양의 선택. gemma는 26B 규모지만 MoE 구조라 토큰당 약 4B만 활성화됩니다. 전체는 메모리에 올라가되 속도는 작은 모델처럼 나오는 절충입니다. 거기에 4비트 양자화(q4_K_M)를 더해 메모리를 줄여 워크스테이션이 아닌 한 대의 맥 안에 들어오게 맞췄습니다. "가장 큰 모델"이 아니라 "이 기기에서 굴러가는 충분히 똑똑한 모델"을 고른 겁니다.

 

 결국 이 섹션도 같은 자리로 돌아옵니다. 강력한 모델 하나로 다 하는 게 아니라 일을 쪼개 각자에게 맞는 도구를 붙이고, 그 전부를 로컬에 두는 것입니다.


5. 프론트엔드와 백엔드 - 왜 Django며 왜 Vite에서 Remix일까?

지금 필요한 것과 나중에 필요할 것은 다릅니다.
좋은 선택은 지금을 빠르게 해결하고
나중의 선택지도 닫지 않습니다.

 

 플랫폼엔 두 축이 있습니다. 데이터를 다루는 백엔드와 화면을 그리는 프론트엔드입니다. 둘 다 선택지가 넘치는데 기준은 하나였습니다. 지금 단계에서 가장 적은 공수로 가장 멀리 가는 길.

왜 FastAPI가 아니라 Django인가.

 파이썬 백엔드라면 가벼운 FastAPI도 강력한 후보입니다. 그런데 이 프로젝트는 CRUD가 많은 플랫폼으로 예상됩니다. 강아지 공고 등록과 수정, 임보자 관리, 입양 신청 처리에 데이터를 넣고 빼는 화면이 끝없이 필요합니다.

 

 Django는 여기서 결정적인 걸 공짜로 줍니다.

  • Admin 패널 내장. 공고·신청·회원 관리 화면을 자동으로 생성해줍니다. 프론트를 한 줄도 안 짜도 데이터를 넣고 바로 운영·테스트할 수 있습니다. 솔로 운영에서 이건 몇 주를 아끼는 차이입니다.
  • ORM과 마이그레이션, 인증 기본 제공. FastAPI라면 직접 골라 붙여야 할 조각들이 처음부터 한 묶음으로 옵니다.

 무엇보다 4번까지 만든 AI 코드(Ollama, RAG, YOLO)가 전부 파이썬입니다. Django도 파이썬이라 같은 백엔드 안에서 AI와 플랫폼 로직이 함께 돕니다. 언어가 갈리지 않는다는 것은 곧 개발 속도를 빠르게 합니다.

 

 FastAPI의 가벼움이 빛나는 자리도 분명 있습니다. 다만 CRUD가 많은 플랫폼, 1인 운영이라는 이 조건에선 덜어주는 일의 양이 Django 쪽이 압도적이었습니다.

왜 한 번에 안 가고 단계적으로 Vite에서 Remix인가.

프론트는 처음부터 정답을 고르지 않았습니다. 단계마다 필요가 다르기 때문입니다.

  • MVP 단계 = Vite + React. 지금 필요한 건 빠른 출시입니다. Vite는 가볍고 빠른 개발 환경으로 React 화면을 즉시 띄웁니다. 단점도 분명합니다. 순수 SPA라 SSR이 없어 검색 노출(SEO)과 링크 미리보기가 약합니다. MVP에선 감수할 수 있는 약점입니다. 아직 공개 공고를 검색에 태울 단계가 아니니까요.
  • 프로덕트 단계 = Remix(SSR). 공개 입양 공고가 검색에 잡히고, 카톡·인스타에 링크를 붙였을 때 미리보기가 떠야 하는 순간이 옵니다. 그때 SSR이 필요해지고, Remix로 넘어갑니다. Django API 백엔드와 역할이 겹치지 않아 조합도 깔끔합니다.

 그래서 핵심은 단계 전략입니다. 지금은 발견성(SEO)을 포기하고 출시 속도를 빠르게 합니다. 나중에 발견성이 필요해지면 그때 SSR로 갈아탑니다. 처음부터 Remix로 시작해 무겁게 가는 대신 지금의 문제를 가장 빨리 풀면서 나중의 선택지도 닫지 않습니다.


맺음말

 다섯 개의 질문은 사실 하나인 듯합니다. 영상도, DB도, 응대봇도, 프론트엔드도, 백엔드도 전부 같은 자리로 수렴했습니다. 강력한 도구 하나로 다 풀지 않는다. 일을 쪼개 각자에게 맞는 도구를 붙인다. 그리고 가능한 많은 것을 로컬에 둔다는 것입니다.

 

 이건 취향이 아니었습니다. 개인이 굴리는 작은 비영리에서 무거운 것을 아껴 쓰는 설계만이 지속 가능하니까요. 가장 비싼 도구를 가장 적게 쓰는 법을 아는 것, 그것이 노하우가 됩니다.

 

도구는 이렇게 깔았고(1편), 왜 그렇게 골랐는지도 풀었습니다(2편). 다음은 이걸로 실제로 무엇을 만드는가입니다.


이 글에서 다룬 고정 함수 분류층은 ProjectDavid의 일부입니다.

전체를 바닥부터 만드는 과정은 강의 3부작으로 정리하고 있고, 10월에 오픈 예정입니다.

→ 오픈 알림 받기


다음글

 

YOLO11 해상도 함정: 골든셋으로 검증 - Measurement First AI

좋은 모델이 좋은 파이프라인을 만드는 게 아닙니다.좋은 채점 기준이,모델을 갈아끼울 수 있게 해줍니다. "AI 영상 분석이면 일단 모델부터 돌려보는 거 아냐?" 틀린 말은 아닙니다. YOLO를 붙이

chessire.tistory.com

이전글

 

맥 로컬 LLM 구축 - Local First AI

AI는 한 덩어리가 아닙니다.역할이 다른 도구들을,한 대의 맥 위에 포개는 일입니다."유기견 입양에 AI를 붙인다"고 하면, 보통 이렇게 떠올립니다."그냥 ChatGPT API 부르면 되는 거 아냐?" 틀린 말은

chessire.tistory.com

관련글

 

오픈소스 LLM vs 빅테크 LLM API: AI 사업의 비용과 리스크

모델은 커모디티가 된다.마진은 판단에 남는다. 2025년 2월, OpenAI는 당시 가장 비싼 모델 GPT-4.5를 출시했습니다. 백만 토큰당 입력 75 달러, 출력 150 달러. 그리고 다섯 달이 지나기 전, API에서 제거

chessire.tistory.com

 

 

GitHub - chessire/shelter-puppy

Contribute to chessire/shelter-puppy development by creating an account on GitHub.

github.com

AI는 한 덩어리가 아닙니다.
역할이 다른 도구들을,
한 대의 맥 위에 포개는 일입니다.

"유기견 입양에 AI를 붙인다"고 하면, 보통 이렇게 떠올립니다.
"그냥 ChatGPT API 부르면 되는 거 아냐?"

 

틀린 말은 아닙니다. 하지만 이 출발점은 정작 중요한 걸 가립니다. 강아지 정보와 영상 같은 데이터가 매 호출마다 외부로 나가고, 호출량만큼 비용이 붙고, 모델·데이터의 통제권은 내 손을 떠납니다. 작은 비영리 명분 사업에서 이 셋은 가볍지 않은 비용입니다.

그래서 질문을 바꿨습니다.

 

"무엇을 내 손 안(로컬)에 두고, 무엇만 밖에 맡길 것인가?"

 

이 글은 그 경계를, 노트북 한 대 위에 세우는 과정입니다. 선택의 이유는 다음 편으로 미루고, 이번 편은 "무엇을 어떻게 깔았는가"만 다룹니다.


1. AI 플랫폼을 설계하다 - 한 대의 맥에 무엇이 올라가나

세울 것은 세 개의 계층입니다.

  1. Python 계층 — AI 코드(LLM·RAG·영상)와 백엔드(Django)가 같은 언어로 도는 곳.
  2. 데이터 계층 — 표 형태 데이터를 담는 PostgreSQL.
  3. Node 계층 — 화면(React)을 띄우는 프론트엔드 런타임.

핵심은, 이질적으로 보이는 이 조각들이 대부분 파이썬 하나로 묶인다는 점입니다. 강아지 공고 관리도, 응대봇도, 영상 분석도 전부 파이썬 위에서 돕니다. 그래서 환경설정의 무게중심도 자연히 파이썬에 실립니다.

AI Pipeline


2. Python 계층 - AI와 백엔드가 같은 언어로 돈다

맥 기본 파이썬은 버전이 낡아 개발용으로는 권장되지 않습니다. brew로 최신을 깔고, 프로젝트 전용 가상환경(venv)을 만듭니다.

brew install python
python3 --version          # 3.12 / 3.13 나오면 OK
which python3              # /opt/homebrew/bin/python3 이면 brew 거 잡힌 것

# 프로젝트 전용 가상환경
python3 -m venv ~/ai/venv
source ~/ai/venv/bin/activate   # 프롬프트에 (venv) 뜨면 활성
pip install -U pip

그 위에 세 묶음을 올립니다. LLM·RAG 스택, 영상 파이프라인, 그리고 백엔드(Django).

# LLM·RAG — 로컬 모델 클라이언트 + 벡터DB
pip install ollama chromadb

# 영상 파이프라인
brew install ffmpeg                            # 영상 자르기·인코딩·자막 입히기
pip install opencv-python                      # 프레임 추출
pip install ultralytics                        # YOLO(검출) + ByteTrack(추적)
pip install mlx-audio soundfile sounddevice    # TTS — 대본을 나레이션 음성으로 (애플 실리콘 최적)

# 백엔드 — Django + REST + DB 드라이버
pip install django djangorestframework django-cors-headers psycopg2-binary
django-admin startproject config .        # 현재 폴더에 프로젝트 생성
python manage.py runserver                # http://127.0.0.1:8000 뜨면 OK

로컬 LLM 본체인 Ollama는 코드 라이브러리가 아니라 독립 서버라 brew로 깝니다. 위에서 깐 ollama(pip)는 그 서버를 파이썬에서 부르는 리모컨이고, 지금 까는 본체가 TV입니다.

brew install ollama
brew services start ollama        # 재부팅해도 자동으로 뜨는 백그라운드 서비스

ollama pull bge-m3                 # 임베딩 모델 (작고 빠름, 첫 성공)
ollama pull gemma4:26b-a4b-it-q4_K_M   # 멀티모달 본체 (비전+텍스트)

ollama run gemma4:26b-a4b-it-q4_K_M "안녕, 한국어 돼?"   # 응답 나오면 /bye

이 한 덩어리만으로 "텍스트를 벡터로 바꾸고(bge-m3), 의미가 비슷한 데이터를 꺼내(chromadb), 그걸 보고 답하는(gemma)" 응대봇의 뼈대가 한 대의 맥 안에 들어옵니다.


3. PostgreSQL - 구조화 데이터의 자리

회원, 강아지 공고, 입양 신청처럼 행과 열로 떨어지는 데이터는 관계형 DB에 둡니다.

brew install postgresql
brew services start postgresql    # ollama처럼 백그라운드 서비스로

조금 전 깐 chromadb와는 역할이 다릅니다. postgres는 구조화 데이터, chromadb는 의미 검색용 벡터입니다. 대체재가 아니라 함께 씁니다. (왜 둘 다 필요한지는 다음 편에서.)


4. Node 계층 - 화면을 띄우다

프론트엔드(React)를 돌릴 런타임입니다.

brew install node          # node + npm 같이 들어옴
node --version             # 20 이상

npm create vite@latest     # React 선택 (MVP용 SPA)

MVP는 가볍고 빠른 Vite + React로 갑니다. 검색 노출(SEO)이 필요한 프로덕트 단계에서 SSR 프레임워크로 넘어갈 여지를 남겨두지만, 그 갈림길의 이야기도 다음 편 몫입니다. (참고로 create-react-app은 폐기되어 쓰지 않습니다.)


맺음말

여기까지가 골격입니다.

  • Python 하나에 AI와 백엔드를 포개고,
  • Postgres로 구조화 데이터를 받치고,
  • Node로 화면을 띄운다.

겉으로는 그저 brew install과 pip install의 나열처럼 보입니다. 하지만 이 배치에는 일관된 선택이 하나 깔려 있습니다. 가능한 많은 것을 외부 API가 아니라 내 손 안에 두는 것. 왜 굳이 로컬인지, 왜 FastAPI가 아니라 Django인지, 그리고 영상 분석을 왜 전부 LLM에 맡기지 않는지 — 이 환경을 이렇게 세운 이유는 다음 편에서 풀어가겠습니다.


이 글에서 다룬 고정 함수 분류층은 ProjectDavid의 일부입니다.

전체를 바닥부터 만드는 과정은 강의 3부작으로 정리하고 있고, 10월에 오픈 예정입니다.

→ 오픈 알림 받기


다음글

 

로컬 LLM 아키텍처 - Local First AI

1. 강아지를 입양했다기술을 먼저 정하고 문제를 찾은 게 아닙니다.한 마리를 데려오고 나서야풀어야 할 문제가 보였습니다. AI로 무언가를 만들어보려던 참이었습니다. 도구는 손에 있는데 무

chessire.tistory.com

관련글

 

오픈소스 LLM vs 빅테크 LLM API: AI 사업의 비용과 리스크

모델은 커모디티가 된다.마진은 판단에 남는다. 2025년 2월, OpenAI는 당시 가장 비싼 모델 GPT-4.5를 출시했습니다. 백만 토큰당 입력 75 달러, 출력 150 달러. 그리고 다섯 달이 지나기 전, API에서 제거

chessire.tistory.com