표준 라이브러리만으로 만든 데이터 카탈로그 AI 에이전트 — 3가지 실행 모드 아키텍처
외부 의존성 0으로 만든 Argus Catalog AI 에이전트의 아키텍처를 해부합니다. 배치·폴링 데몬·어시스턴트 서버 세 가지 실행 모드, 에이전트를 분리한 이유, 그리고 의존성 없는 설계 철학을 정리합니다.
데이터 카탈로그에 AI를 붙이는 가장 손쉬운 방법은 API 서버 안에 LLM 호출 코드를 끼워 넣는 것입니다. 하지만 운영을 길게 보면 이 방식은 GPU 자원, 데이터 주권, 스케일 단위가 모두 API 서버에 묶여버리는 부채가 됩니다. Argus Catalog Agent는 AI 메타데이터 기능을 독립 에이전트로 떼어내고, 외부 패키지 의존성을 0개로 유지하는 길을 택했습니다.
이 글은 그 에이전트의 아키텍처를 해부하는 5부작 시리즈의 첫 편입니다. 시리즈는 아키텍처 → 모델/LLM → 도구(Function Calling) → 통신·표시(SSE) → 거버넌스 순으로 이어집니다.
1. 한 패키지, 세 가지 실행 모드
argus-agent는 하나의 Python 패키지지만 세 가지 얼굴을 가집니다.
┌──────────────────────────────────────────────────────────────────┐
│ argus-agent (Docker 또는 native — 표준 라이브러리만 사용) │
│ │
│ [배치] describe/.../generate-all [데몬] worker (주기 폴링) │
│ │ ① 컨텍스트 읽기 (스키마·샘플·용어집) │
│ ├──── REST (admin 계정) ────▶ Argus Catalog API │
│ │ ③ 결과 반입 (제안 → UI 에서 사람 승인) │
│ │ ② 생성 │
│ └──── OpenAI 호환 API ────▶ ollama (qwen2.5:7b) / vLLM / ... │
│ │
│ [서버] serve (:8930) — AI 어시스턴트 tool-use 채팅 │
│ ▲ SSE (사용자 토큰 위임) │
│ │ ┌─ 도구 실행 (사용자 권한) ─▶ 카탈로그 API │
│ 백엔드 /ai/assistant/chat └─ tool-use 루프 ─▶ LLM │
│ (assistant.agent.url 설정 시 프록시) │
└──────────────────────────────────────────────────────────────────┘
| 모드 | 명령 | 성격 |
|---|---|---|
| 배치(one-shot) | describe / summarize / columns / tags / pii / generate-all | 지정한 데이터셋에 메타데이터를 한 번 생성하고 종료 |
| 폴링 데몬(worker) | worker --datasource-id ... | 설명이 비어 있는 데이터셋을 주기적으로 찾아 자동 생성 |
| 어시스턴트 서버(serve) | serve --port 8930 | 사용자와 tool-use 채팅을 주고받는 SSE HTTP 서버 |
배치와 워커는 관리자 계정으로 카탈로그를 읽고 결과를 되돌리는 데이터 파이프라인이고, serve는 사용자 토큰을 위임받아 그 사람 권한으로만 동작하는 대화형 서버입니다. 같은 도구·같은 LLM 레이어를 공유하되 진입점과 인증 모델이 다른 셈입니다.
2. 모듈 구성
기능이 셋이라고 코드가 흩어지지는 않습니다. 9개 모듈이 각자 한 가지 역할만 맡습니다.
| 모듈 | 역할 |
|---|---|
config.py | 설정 — CLI 인자 > 환경변수 > 기본값 우선순위 |
catalog.py | 카탈로그 REST 클라이언트 (배치용 — admin 계정) |
llm.py | OpenAI 호환 LLM — generate(단발)·chat(tool-calling)·chat_stream(스트리밍) |
prompts.py | 메타데이터 생성용 한국어 프롬프트 빌더 |
tasks.py | 배치 작업 5종 (describe/summarize/columns/tags/pii) |
tools.py | 어시스턴트 도구 레지스트리 (OpenAI 스키마 + 실행 함수) |
assistant.py | tool-use 루프 (LLM ↔ 도구 반복, SSE 이벤트 생성) |
server.py | serve 모드 HTTP 서버 (/chat SSE, /health) |
telemetry.py | 셀프-텔레메트리 — 레지스트리 자기등록 + 호출 지표 push |
llm.py와 tools.py는 세 모드가 모두 공유하는 코어이고, tasks.py·prompts.py는 배치/워커 전용, assistant.py·server.py·telemetry.py는 serve 전용입니다.
3. 왜 에이전트로 분리했나
AI 코드를 API 서버 밖으로 꺼내면서 얻는 효과는 네 가지로 정리됩니다.
| 관점 | 효과 |
|---|---|
| 자원 분리 | LLM(GPU)을 API 서버와 다른 머신에서 — 서버는 가볍게 유지 |
| 데이터 주권 | 로컬 모델 사용 시 스키마·샘플이 외부로 나가지 않음 (PII 감지에 특히 중요) |
| 독립 스케일 | 대량 일괄 생성 시 에이전트만 따로 확장 |
| 운영 일관성 | 기존 quality/ 배치와 동일한 외장 패턴 (API 읽기 → 처리 → 반입) |
특히 데이터 주권은 단순한 명분이 아닙니다. PII(개인정보) 컬럼을 감지하려면 실제 샘플 데이터를 모델에 보여줘야 하는데, 이걸 외부 SaaS LLM에 보내는 순간 컴플라이언스 문제가 생깁니다. 로컬 ollama 모델을 기본으로 둔 이유가 여기에 있습니다.
4. 의존성 0 — dependencies = []
이 에이전트의 가장 눈에 띄는 결정은 pyproject.toml의 이 한 줄입니다.
[project]
name = "argus-agent"
requires-python = ">=3.10"
dependencies = []HTTP 통신은 requests가 아니라 표준 라이브러리 urllib로, 서버는 flask가 아니라 http.server로, JSON은 json으로 처리합니다. 외부 패키지가 하나도 없습니다. 이렇게 한 이유는:
- 설치·배포 마찰 제거 —
pip install이 필요 없고, 폐쇄망·에어갭 환경에서도 파이썬만 있으면 돈다 - 보안 감사 표면 최소화 — 의존성 트리가 없으니 CVE 추적 대상도 없다
- 버전 충돌 제로 — 다른 서비스와 같은 호스트에 올려도 패키지가 부딪히지 않는다
requirements.txt에 -e . 한 줄만 있는 건 패키지 자신을 설치해 argus-agent CLI를 PATH에 등록하기 위한 것일 뿐, 런타임 의존성을 끌어오지 않습니다.
5. 설정 우선순위 — Docker와 native를 동시에
배치는 보통 CLI 인자로, Docker는 환경변수(.env)로 설정을 주는 게 자연스럽습니다. config.py는 둘 다 만족시키기 위해 명확한 우선순위를 둡니다 — CLI 인자 > 환경변수 > 기본값.
def pick(cli_value, env_key: str, default: str) -> str:
# CLI 인자가 명시됐으면 최우선, 없으면 환경변수, 그래도 없으면 기본값
if cli_value is not None:
return str(cli_value)
return os.environ.get(env_key, default)덕분에 docker compose에서는 .env만 채우면 되고, 로컬에서 빠르게 테스트할 때는 --model·--api-url 같은 인자로 즉석에서 덮어쓸 수 있습니다.
6. Docker Compose 한 장으로 기동
운영 묶음은 ollama(모델 서버) + agent(배치) + assistant(serve) + worker(데몬)로 구성됩니다.
services:
ollama: # qwen2.5:7b 모델 서버
ollama-pull: # 최초 1회 모델 pull
agent: # 배치 — describe/generate-all 실행
assistant: # serve :8930 — 채팅 서버
worker: # 폴링 데몬 — 설명 없는 데이터셋 자동 생성docker compose up -d ollama # 모델 자동 pull (최초 1회 수 분)
docker compose run --rm agent describe --urn <URN>
docker compose up -d assistant worker카탈로그 API가 호스트에서 돌고 있으면 host.docker.internal로 접근하도록 기본값이 잡혀 있어, .env 한 줄로 환경을 바꿀 수 있습니다.
마치며
핵심은 분리와 무의존입니다. AI 기능을 독립 에이전트로 떼어내 자원·데이터·스케일을 분리했고, 표준 라이브러리만으로 구현해 어디서든 마찰 없이 돌게 만들었습니다. 같은 코어(LLM 레이어·도구)를 세 가지 실행 모드가 공유하는 구조 덕분에, 배치 파이프라인과 대화형 어시스턴트가 한 패키지 안에 자연스럽게 공존합니다.
다음 편에서는 이 아키텍처의 심장인 LLM 레이어를 다룹니다 — provider 분기 코드 없이 ollama·vLLM·OpenAI를 하나의 클라이언트로 통합한 방법과, 7B 소형 모델을 안정적으로 다루는 기법을 살펴봅니다.