반갑습니다, 수누다입니다.
지난 1부에서 거창한 프레임워크인 OpenClaw를 도입했다가, '로그 분석관'이 튀어나오는 참사를 겪고 컨테이너를 날려버렸다는 이야기 전해드렸죠.
엔지니어 생활 13년 하면서 느낀 불변의 진리가 하나 있습니다.
"튜닝의 끝은 순정이다."
내 마음대로 제어가 안 되는 블랙박스(프레임워크)를 붙들고 씨름할 바엔, 차라리 바닥부터 직접 짜는 게 훨씬 빠르고 속 편할 때가 많습니다. 오늘은 그 '순정 봇'을 만드는 과정을 공유합니다.
준비물은 딱 두 개면 됩니다. Python, 그리고 Docker.
(제 PC엔 파이썬도 안 깔려 있습니다만, 우리에겐 도커가 있으니 상관없습니다.)
1. 아키텍처 다이어트 (Refactoring)
1부의 그 복잡했던 구조도에서 미들웨어를 다 걷어냈습니다.
중간에 에이전트 프레임워크가 낄 자리는 없습니다. 뇌(Ollama)와 입(Discord)을 직통으로 연결합니다.
- Host (PC): Ollama (Llama 3.1) 구동 중
- Docker Container: Python Script (Discord API ↔ Ollama API 중계)
- Interface: Discord
(▲ 중간 유통 과정을 없애니 이렇게나 깔끔합니다. 직거래가 답이죠.)
2. 코드 구현: 딱 50줄의 미학
거창한 클래스 설계? 디자인 패턴?
토이 프로젝트에 그런 건 사치입니다. discord.py와 requests 라이브러리만 있으면 됩니다.
핵심 로직은 이게 전부입니다.
# mybot.py (핵심 요약)
import discord
import requests
# 여기가 핵심입니다.
# 도커 컨테이너에서 호스트 PC의 Ollama를 찾으려면 localhost를 쓰면 안 됩니다.
OLLAMA_URL = "http://host.docker.internal:11434/api/chat"
@client.event
async def on_message(message):
if message.author == client.user: return
# 1. 사용자 메시지 수신
user_content = message.content
# 2. Ollama에게 토스 (Request)
payload = {
"model": "llama3.1",
"messages": [
# 여기서 봇의 성격을 정의합니다.
{"role": "system", "content": "너는 친절한 AI 비서 '구름'이야."},
{"role": "user", "content": user_content}
],
"stream": False
}
# 3. 답변 받아서 디스코드에 전송 (Response)
response = requests.post(OLLAMA_URL, json=payload).json()
bot_reply = response['message']['content']
await message.channel.send(bot_reply)
복잡한 설정 파일(json) 수십 줄보다, 내가 짠 파이썬 코드 50줄이 훨씬 직관적이고 수정하기도 편하죠.
3. 도커 배포: 내 PC를 더럽히지 말자
파이썬 환경 설정하다가 의존성 꼬여보신 분들 많으시죠?
저는 제 로컬 환경이 지저분해지는 걸 병적으로 싫어합니다. 봇을 위한 깔끔한 독방(Container)을 하나 만들어 줍니다.
# Dockerfile
FROM python:3.9-slim # 가볍게 갑시다
WORKDIR /app
# 필수 라이브러리 설치
RUN pip install discord.py requests
# 코드 복사
COPY mybot.py .
# 실행
CMD ["python", "mybot.py"]
💡 삽질 방지 팁: host.docker.internal
위 코드에서 OLLAMA_URL을 보시면 localhost 대신 host.docker.internal을 썼습니다.
도커 컨테이너 입장에서 localhost는 컨테이너 자기 자신을 의미합니다. 내 PC(Host)에 떠 있는 Ollama에 접속하려면, 도커가 제공하는 이 매직 도메인을 써야 합니다.
(이거 몰라서 포트 포워딩하고 방화벽 끄고... 삽질하는 주니어들 많이 봤습니다.)
4. 트러블슈팅: 원숭이도 나무에서 떨어진다
자, 빌드하고 실행! ...했는데 에러가 떴습니다.
File "/app/mybot.py", line 10
OLLAMA_URL = "..."MODEL_NAME = "llama3.1"
^
SyntaxError: invalid syntax
...아.
블로그에 올릴 코드 정리하다가 복사/붙여넣기 실수로 변수 두 개가 한 줄에 붙어버렸네요.
13년 차 엔지니어도 이런 오타(Human Error) 냅니다. (머쓱)
엔터 한 방 쳐주고 다시 빌드했습니다.
5. 결과: 3분 컷, 그리고 평화
# 1. 빌드
docker build -t gureum-bot .
# 2. 실행 (호스트 연결 옵션 필수!)
docker run -d --name my-gureum --add-host=host.docker.internal:host-gateway gureum-bot
명령어 두 줄로 봇이 살아났습니다.
1부에서 그 고생을 했는데, 이번엔 3분도 안 걸렸네요.
떨리는 마음으로 다시 말을 걸어봅니다.

드디어!
영어 논문을 읊어대던 '분석관'은 사라지고, 제가 원하던 '친구'가 나타났습니다.
Latency(응답 속도)도 1초 미만. 아주 쾌적합니다.
6. 마치며: 하지만 뭔가 부족하다?
이렇게 해서 Python + Docker + Ollama 조합으로 깔끔하게 봇을 구축했습니다.
"역시 심플한 게 최고다"라며 만족하고 있었는데... 며칠 써보니 치명적인 단점이 보이더군요.
Me: 구름아, 오늘 삼성전자 주가 얼마야?
Gureum: 죄송해요. 저는 2023년 데이터까지만 알고 있어서 최신 주가는 몰라요. 😅
그렇습니다. 제 로컬 PC에 갇혀 있는 이 녀석은 세상과 단절된 '방구석 천재'였습니다.
오늘 날씨도, 실시간 뉴스도 모르는 녀석을 진정한 '비서'라고 부르긴 어렵겠죠.
그래서 다음 편(마지막 3부)에서는, 이 녀석에게 인터넷(Web Search)이라는 강력한 무기를 쥐여주는 과정을 다뤄보겠습니다. 돈 드는 API 말고요, 공짜로요.
[Next] 방구석 천재 AI에게 '구글링' 능력 달아주기 (Tool Use 구현)
[수누다의 한 줄 요약]
"남이 만든 거대한 프레임워크보다, 내가 짠 허접한 코드가 때론 더 강력하다."