
「Claude로 충분하지 않아?」라는 일축을 듣고, 자체 로컬 AI 기반 「ragy」를 AI와 페어 프로그래밍하여 초고속으로 구축한 이야기
요약
기밀 유지를 위해 로컬 환경에서 작동하는 RAG 및 코딩 에이전트 통합 앱 'ragy'를 구축한 경험담입니다. Dify, LiteLLM, Watchdog 등을 활용하여 보안과 자동화를 동시에 잡은 아키텍처를 소개합니다.
핵심 포인트
- LiteLLM을 활용한 하이브리드 LLM 구성으로 모델별 역할 분담
- Watchdog을 통한 프로젝트 문서와 Dify 지식 간의 실시간 동기화
- 에러 로그를 감시하고 스스로 코드를 수정하는 자기 수복 에이전트 구현
- Squid 프록시를 이용한 Dify Sandbox의 SSRF 보안 방어
안녕하세요. 요즘도 잘 모르는 SES 기업에서 매일 Excel을 열고 있는 하급 엔지니어입니다.
오늘은 회사가 「RAG와 에이전트가 통합된 앱이 필요하다」라고 떠들어대길래 이것저것 조사해서 설계했더니, 「Claude의 엔터프라이즈 플랜이면 충분해」라고 말해서 짜증이 났습니다. 그래서 원래 설계를 대폭 다운그레이드하여 개인용으로 다시 만든 이야기를 해보려고 합니다.
계기는 회사가 새로운 프로젝트를 가져왔고, 그 개발용으로 에이전트를 사용하고 싶으면서 동시에 프로젝트 문서를 보기 위한 RAG가 필요하다고 말하기 시작했기 때문입니다.
개인적으로 RAG를 사용하고 싶다면 NotebookLM이 상당히 우수하죠. 하지만 프로젝트 문서는 기밀 정보를 포함하므로 어딘가에 업로드하는 것이 꺼려집니다.
또한, 현재의 NotebookLM은 API가 제공되지 않아 문서의 추가 업데이트나 에이전트와의 협업 같은 부분이 조금 번거롭습니다. 저 역시 NotebookLM을 활용하고는 있었지만, 검색을 실행하고 그 답변을 에이전트에 복사해서 붙여넣는... 식의 작업을 하고 있었습니다.
이번 회사의 요구사항을 계기로, 직접 RAG와 코딩 에이전트가 협업할 수 있는 앱을 만들기로 한 것이 설계의 시작이었습니다.
다만, 여기서 설계한 것은 엔터프라이즈용이라는 점 때문에 AWS를 사용한 상당히 대규모 모델이었습니다. 그것을 필요 없다고 일축당했기에, 눈물을 머금고 개인용으로 다운그레이드했습니다.
직접 만들어보니 여러 반성점과 개선점, 반대로 좋았던 점도 발견했기에 비망록으로서 남겨두고자 합니다. 괜찮으시다면 함께해 주세요.
회사용으로 생각했던 「VPC가 어떻다」라거나 「가용성이 어떻다」라는 무거운 요구사항은 전부 창밖으로 던져버렸습니다.
개인 개발에서 원하는 것은, 터미널에서 한 발짝도 나가지 않고 폴더에 Markdown을 두기만 하면 AI가 알아서 문맥을 이해하고 코드를 작성해 주는, 극한까지 게으를 수 있는 마법 같은 환경입니다.
그렇다고 해서 보안이나 모델의 운용 측면을 타협하고 싶지는 않았기에, Dify를 코어로 삼으면서 다음과 같은 구성으로 결정했습니다.
대략 이런 느낌입니다.
하이브리드 LLM (LiteLLM): 메인 코딩에는 Gemini를 사용하고, 로컬 처리나 채팅에는 Qwen2.5-Coder를, RAG 검색의 벡터화에는 임베딩 모델 (multilingual-e5-large)을 백엔드에서 나누어 할당합니다. 에디터 측에서는 단일 OpenAI 호환 API로 보이도록 위장합니다. Continue나 Aider뿐만 아니라, OpenAI 호환 API를 지원하는 임의의 도구(최근 화제인 pi나 opencode 등)에서도 엔드포인트와 API 키만 맞추면 그대로 접속 및 이용 가능합니다.
전자동 RAG 동기화 (Watchdog): 프로젝트의 docs/에 파일을 두기만 하면 Dify의 지식(Knowledge)과 실시간 동기화됩니다. 브라우저를 여는 석기 시대 방식의 조작은 박멸합니다.
자기 수복 에이전트: 에러 로그를 감시하여 예외(Exception)가 발생하면, AI 에이전트가 에러 로그와 소스 코드의 문맥으로부터 수정 내용을 자율적으로 판단하여 패치를 적용하고 PR을 보냅니다.
SSRF 방어: Dify의 Sandbox가 폭주하여 로컬 네트워크를 망가뜨리지 않도록 Squid 프록시로 막아둡니다.
이번 개인용 AI 기반 「ragy」를 구축하면서 가장 놀랐던 점은 「생각보다 간단하고, 또한 초고속으로 작동하는 것이 완성되었다」는 것입니다.
물론, 기본이 되는 아키텍처의 골격(LiteLLM 게이트웨이를 통한 분기, Dify API를 호출하는 자동 동기화, Redis를 이용한 MCP 시맨틱 캐시, Squid 프록시에 의한 SSRF 방어 등의 연계 설계)은 저 스스로 머리를 짜내어 생각했습니다.
하지만 실제 Python 코드나 쉘 스크립트를 작성하고 설정 파일을 정비하는 등의 구질구질한 구현 부분은, 거의 전부 개발 AI 어시스턴트 (Antigravity)에게 통째로 맡겼습니다.
제가 한 일이라고는,
「이런 아키텍처로 동작시키고 싶으니까, 모니터링용 Python 스크립트를 작성해 줘」
「Dify API의 이 사양에 맞춰서, 설정 정보를 JSON으로 패킹하는 구현으로 해줘」
라고 지시를 내린 것뿐입니다.
AI 에이전트의 자율적인 코딩 능력 덕분에, 인간 측은 「시스템 설계와 지시」에 100% 전념할 수 있었고, 환경 구축에 걸린 실질적인 시간은 불과 며칠이었습니다. 아키텍처(Architecture) 설계도만 올바르게 그릴 수 있다면, 구현은 AI와 페어 프로그래밍(Pair Programming)을 함으로써 개인의 개발 효율을 몇 배로 끌어올릴 수 있는 시대가 오고 있음을 절감했습니다.
그렇다고는 해도, AI에게 구현을 통째로 맡기는 과정에서 OS의 동작이나 시그널(Signal) 주변의 「AI 혼자서는 해결하기 어려운 저수준(Low-level)의 기괴한 현상」에 부딪혔고, 그 부분에 대해서는 AI와 몇 번이고 로그를 대조하며 격투해야 했습니다.
백그라운드 동기화나 Webhook 리스너를 데몬 프로세스(Daemon Process)로 상주시키려 했을 때, AI는 Python의 os.fork()를 사용한 일반적인 데몬화 코드를 작성했습니다. 하지만 이것을 macOS (Darwin) 환경에서 실행하자마자 SIGABRT로 즉시 강제 종료되는 현상이 발생했습니다.
이는 macOS의 사양상, 멀티 스레드(Multi-thread) 환경(Uvicorn 실행 시 등)에서의 안전성 관점에서 fork가 엄격하게 제한되어 있기 때문이었습니다.
이에 대해 AI와 상담하여 subprocess.Popen에 start_new_session=True를 지정함으로써, fork 제한을 완전히 우회하면서도 부모 프로세스의 강제 종료 시그널이 전파되지 않는 완전한 독립 데몬으로 동작시키는 수법에 도달했습니다.
GitHub로의 푸시(Push)를 트리거로 하여, 자동으로 최신 소스를 풀(Pull)하고 시스템을 재시작하는 deploy_listener.py를 구현했는데, 초기 구현에서는 재시작 명령(ragy restart)을 실행하는 순간, 재시작을 트리거했을 리의 리시버(Receiver) 프로세스가 함께 강제 종료되어 데플로이(Deploy) 처리 자체가 도중에 소멸해 버렸습니다.
원인은 부모 프로세스가 자식 프로세스 트리 전체를 길동무 삼아 클린업(Cleanup)하는 시그널(SIGHUP 등)의 연쇄사였습니다.
이를 해결하기 위해,
- 데플로이 재시작 시
AUTO_DEPLOY=1이라는 환경 변수를 부여하여 킬(Kill) 처리를 스킵하게 함 - Webhook 리시버 스스로가 HTTP 응답을 반환한 직후
os._exit(0)로 스스로 깔끔하게 종료하여 포트(Port)를 해제함
위와 같이 OS의 프로세스 관리와 시그널의 동작을 세밀하게 제어하는 진흙탕 싸움 같은 디버깅을 AI와 반복함으로써, 겨우 안정적인 가동에 성공했습니다.
우선 최속으로 동작하는 것을 만들어 v1.1.0으로 릴리스하며 감동도 맛보았지만, 냉정하게 운용해 보니 자신의 천박함을 저주하게 되었습니다.
Aider 등의 에이전트가 Dify의 RAG API를 직접 호출하는 완전 동기형의 밀결합(Tight Coupling) 아키텍처로 만들어 버린 것입니다.
이것이 무엇이 위험하냐면, 다음 두 가지 점으로 요약됩니다.
- Dify 컨테이너가 재시작 중이거나, Weaviate가 인덱스(Index) 생성으로 숨을 고르고 있을 때 에이전트가 요청을 보내면, 가차 없이 타임아웃(Timeout)이 발생하며 에이전트 측도 크래시(Crash)됩니다. 에이전트에게는 「RAG가 바쁘다면 일단 로컬 파일을 보고 작업을 진행해 줘」라고 말하고 싶은데, RAG와 운명을 같이하는 사양이 되어 있었습니다.
작업하면서 Continue가 내보내는 에러 팝업을 자주 보았습니다...
- API로 직접 주고받으며 완결되어 버리기 때문에, 중간에 개입하여 「에이전트가 RAG에 던진 검색 쿼리(Query), 좀 바보 같지 않아?」라거나 「RAG가 반환한 컨텍스트(Context), 이건 절대 아니잖아」라고 태클을 걸 틈이 없었습니다.
빨리 동작시키고 싶다는 편의성 때문에 만든 결과, 확장성이나 내결함성(Fault Tolerance)이 희생된 것입니다. AWS의 중후한 설계를 생각했으면서도, 로컬로 가져오자마자 이 꼴이 된 것입니다.
이 밀결합의 벽을 부수고, 나아가 RAG의 정밀도를 폭발적으로 높이기 위해, 다음으로는 Redis를 메시징 큐(Messaging Queue)로 끼워 넣는 비동기 아키텍처와, Dify의 「워크플로우(Workflow) 기능」을 결합한 Self-RAG로의 진화를 꾀하고 있습니다.
직접 Dify API와 주고받는 것을 그만두고, 모든 검색 의뢰를 비동기 이벤트로서 일단 Redis 큐에 던집니다.
[Aider / Continue] ──(MCP API)──► [ MCP Server ]
│
▼
...
RAG의 고도화된 루프(Loop) 처리(Self-RAG 등) 구현이라고 하면 LangGraph가 유명하지만, 그것을 개인 개발에서 풀 스크래치(Full Scratch)로 도입하면 청킹(Chunking)이나 벡터 DB(Vector DB) 관리 등의 인프라도 모두 코드로 유지보수해야 하는 상황이 되어 개발 비용이 폭발하게 됩니다.
그래서 인프라 측면은 Dify의 강력한 지식 관리 (Knowledge Management) 기능을 그대로 활용하고, 고도의 루프 제어(검색 쿼리 최적화 → 검색 → 검색 결과 평가 → 실패 시 쿼리를 동적으로 수정하여 재검색)는 Dify의 「워크플로우 기능 (Workflow)」으로 구현하는 하이브리드 전략을 취합니다.
중간에 Redis 큐 (Queue)를 삽입하여 이벤트 주도 (Event-driven) 방식으로 구성함으로써, Dify가 다운되더라도 요청은 큐에 쌓이게 되어 에디터 측의 에이전트 (Agent)가 충돌하여 작업이 중단되는 일도 없게 됩니다. 개인용 앱이라고는 하지만, 이 정도까지 구현하면 매우 견고하고 자율적인 로컬 AI 기반 (Local AI Infrastructure)이 됩니다.
회사에서 요구사항을 단칼에 거절당했을 때는 정말 화가 났지만, 결과적으로 「내가 가장 원하는, 터미널 완결형 AI 개발 기반」을 스스로 고안한 아키텍처와 AI 어시스턴트 (AI Assistant)의 압도적인 구현 속도, 그리고 OS의 사양 문제를 해결하며 만들어낼 수 있었던 것은 엔지니어로서 최고의 소일거리(이자 공부)가 되었습니다.
업무로서 만드는 중후한 시스템도 좋지만, 오직 자신만을 위해 아키텍처를 만지작거리고, AI에게 구현시키고, 실패하고, 다시 설계하는 프로세스는 역시 즐거운 법입니다.
만약 저와 마찬가지로, 잘 알 수 없는 SES 기업에서 엑셀 (Excel)을 열어두고 「사실은 좀 더 모던한 환경에서 코드를 짜고 싶단 말이야!」라며 속을 끓이고 있는 엔지니어가 있다면, 부디 자신의 로컬 환경만이라도 최강의 AI 기반을 구축해 보시기 바랍니다.
끝까지 읽어주셔서 감사합니다!
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기