안녕하세요! 퇴근 후 "되묻고 깊어지는 상담 대화"를 목표로 주역 상담 AI를 개발 중인 직장인 바이브코더입니다.
최근 저작권 이슈로 DB를 교토대 오픈 소스(Kanripo)로 전면 피벗했던 4일 차에 이어, 어제와 오늘(8/15 ~ 8/16)은 프로젝트의 뼈대와 인프라의 차원을 한 단계 끌어올리는 거대한 전환이 있었습니다.
🛠️ My Vibe Coding Stack
- Local Dev: Mac Mini M4 Pro + Ollama + Docker Compose
- Database: PostgreSQL 16 + pgvector (iching-db, 총 2,536건 RAG 인덱스)
- Model (이원화): gemma4:latest (로컬 개발용) vs Gemini 2.5 Flash (실제 상용 배포용)
- Architecture Framework: Google ADK (google/adk-python)
- Deployment Target: Google Cloud Run (Dockerfile 패키징 완료)
2가지 핵심 작업방향 변화
1. "로컬 AI를 클라우드에 올린다?" 배포를 해본 자와 안 해본 자의 차이
처음에는 개발용으로 쓰던 로컬 모델(Gemma4 26B)을 그대로 클라우드에 올릴까 고민했습니다. 하지만 실제로 클라우드 환경에서 서비스를 운영하려고 가상 서버 및 무상태 컨테이너의 하드웨어 할당 비용을 시뮬레이션해 보니, 로컬 구동용 무거운 가중치를 클라우드에 직접 올려 서빙하는 것은 상업적으로 극히 비효율
적이었습니다. 이 뼈아픈 현실을 뒤늦게 깨닫고 운영 전략을 확실하게 이원화했습니다.
- 개발/테스트 : 로컬 `gemma4:latest` (Ollama 구동, 호출 비용 0원)
- 실제 배포/운영 :`Gemini 2.5 Flash` (Vertex AI 기반 상용 API 호출)
이원화 후 실측 결과, 제미나이 2.5 플래시 기준 1턴 응답 약 11초가 소요되며, 턴당 사고(Thinking) 토큰은 약 690토큰 내외로 안정적인 비용 기준선을 확보했습니다.
2. 구글 클라우드런(Cloud Run) 배포를 위한 Google ADK 표준 리팩토링
멀티에이전트 파이프라인을 구축해 무상태(Stateless) 컨테이너인 Cloud Run에 그대로 올릴 경우, 에이전트 간의 컨텍스트 유지 및 동시성 락 처리 과정에서 충돌 가능성이 대두되었습니다.
이를 완벽하게 방지하기 위해 구글의 에이전트 개발 표준 라이브러리인 Google ADK (google/adk-python)를 전격 도입하여 전면 리팩토링을 수행했습니다.
- `agents/tools.py`에 괘 산출, DB 조회, RAG 검색, 세션 이력 조회를 ADK 표준 도구(Tool)로 캡슐화 완료.
- 5개 에이전트와 오케스트레이터를 `IChingADKWorkflow` 표준 구조로 구현하여 컨테이너 환경에서의 안전성을 완전 확보했습니다.
데이터 레이어 및 RAG 인덱스 고도화
1. 전의(傳義: 정전 + 본의) RAG 체제 완성
조선 시대 이래 주역의 표준 텍스트 체제인 **정전(程傳)과 본의(本義)**를 모두 담기 위해, 기존 1,752건의 정전 RAG 데이터에 주자 『본의』 784건을 정밀 파싱/번역하여 추가 적재했습니다. (총 2,536건 RAG 인덱스 확정)
2. search_balanced (정전·본의 균형 검색 알고리즘) 도입
한 풀에 RAG를 그냥 던지면 특정 주석의 가중치가 왜곡되어 정전과 본의 중 한쪽 해설만 검색되는 문제가 발생합니다. 이를 위해 출처별로 괘 단위 고정 쿼터(정전 3 + 본의 2, 초점 효: 2 + 1)를 할당해 균형 있게 검색하고 대조하도록 알고리즘을 개선했습니다.
5단계 멀티에이전트 파이프라인 세부 구현 현황
- 안전 스크리닝 에이전트 : 자해/위기 신호 감지 시 즉시 핫라인 안내 분기. 세션을 재개하더라도 위기가 차단되도록 사용자 ID 기준 24시간 위기 래치(Latch)
로직을 도입했습니다.
- 정리 에이전트 : 고민을 명확화하고 주역의 '재삼독(동일 질문 반복 방지)' 몽괘 원칙을 감지 시 괘를 다시 뽑지 않고 이전 상담 내용을 회고하는 전용 분기로 라우팅합니다.
- 괘 도출 및 해석 에이전트 : 변효 규칙 엔진 + DB 1:1 확정 원문 조회 + RAG 균형 주석을 조합해 매핑 초안을 냅니다.
- 상담 에이전트 : 한문 원문을 원천 차단하고 순수 한글 번역만 전달하며, 1턴 1질문의 되묻기(Reflective Inquiry) 루프와 의료/진단성 어휘("우울증" 등) 코드 레벨 철저 차단 처리를 완료했습니다.
- 저널 에이전트 : 상담 종료 시 전체 대화를 요약해 실천 과제(Action Items)를 DB에 적재합니다.
잡아낸 숨겨진 버그와 인사이트
1. 부록을 마지막 괘로 집어삼키던 파서 오류 발견
원본 파일 재파싱 과정에서 파서가 부록(계사전 등) 데이터를 마지막 64번째 괘의 1효 효사 데이터(9,688자) 뒤에 붙여 한꺼번에 삼켜버리는 오염을 발견했습니다. 조기에 인지하고 파서를 수정하여 바이트 단위로 나머지 446건 데이터의 동일성을 정교하게 확인했습니다.
2. max_tokens=2048의 덫 (Gemini를 터뜨린 원인)
테스트를 가볍게 1건만 돌릴 때는 몰랐는데, 대량 배치를 돌리자 4건 중 3건꼴로 실패가 떴습니다. 원인은 추론 전용 모델의 사고 과정(Reasoning Tokens)이 길어지면서 정작 아웃풋이 나오기 전에 '토큰 예산 부족'으로 잘려버린 문제였습니다. 토큰 리밋을 확장하여 정상 해결했습니다.
3. 병렬 모드에서의 데드락 (Deadlock)
멀티에이전트 병렬 처리 과정에서 락을 쥔 채 내부에서 동일한 락을 다시 획득하려고 대기하는 교착 상태가 일어났습니다. 재진입이 가능한 `RLock` 구조로 신속히 교체하여 병목을 뚫어냈습니다.
4. 모델이 "모르겠다"고 자기 신고를 안 하는 한계 돌파
번역 검수 단계든 안전 필터링 단계든, AI 모델들에게 자발적인 "모호함(unclear/flag)" 리포팅을 기대하면 무조건 자기 꼼수로 회피하거나 100% 완벽한 척 속인다는 점을 뼈아프게 깨달았습니다. 자동화된 시그널을 모델의 정직성에 기대지 않고, 두 모델의 불일치(Diff)를 강제로 추출해 블라인드 검수 목록을 만드는 방식으로 안전망을 짰습니다.
크루분들께 드리는 질문!
이제 아키텍처의 척추인 해석 엔진까지 무사히 연동을 마쳤습니다.(일단 돌려보면서 어떻게 나오는지는 파악해봐야 할듯 합니다.) 본격적인 법적 고지문 작업과 다음 마일스톤으로 넘어가기 전, 소중한 훈수를 구합니다.
1. 로컬 환경(개발 비용 0원)과 클라우드 배포용 API를 저처럼 이원화하여 사용 중이신 분들이 계신다면, 로컬과 클라우드 간의 미세한 프롬프트 반응 차이나 출력 품질 편차를 극복하는 좋은 팁이 있으신가요?
2. Google ADK 프레임워크나 Cloud Run 환경에 가볍게 상태(State) 및 메모리 캐싱 레이어를 얹어 쓸 때 선호하시는 인프라 구성 방식(예: Redis vs Cloud SQL 밀어넣기)이 있다면 아낌없이 조언 부탁드립니다! 👇
와 이번 글은 진짜 삽질에서 얻은 내용이 많네요 ㅎㅎ
특히 로컬 Gemma로 개발하고 실제 운영은 Gemini API로 이원화한 부분이 현실적으로 와닿았습니다. 로컬 모델을 그대로 클라우드에 올리면 결국 GPU 비용 때문에 배보다 배꼽이 커질 수 있으니까요.
저라면 로컬/운영 모델의 출력 차이를 줄이려고 프롬프트를 완전히 같게 만드는 것보다는, 실제 상담 케이스를 테스트셋으로 만들어 두고 두 모델 결과를 계속 비교하는 쪽으로 갈 것 같습니다. 이미 번역 검수에서 모델 간 Diff를 활용하셨던 것처럼요.
그리고 Redis vs Cloud SQL은 지금 규모라면 일단 Cloud SQL 쪽에 최대한 붙여서 단순하게 시작할 것 같아요. 정말 Redis가 필요한 병목이 확인됐을 때 추가해도 늦지 않을 듯합니다.
파서가 부록 9,688자를 마지막 괘에 먹어버린 건... 배포 전에 발견해서 정말 다행입니다 ㅋㅋ 이런 게 빌드로그 보는 재미네요.