무료로 데이터베이스와 채팅: 파이썬으로 프라이빗 AI 에이전트 만들기
Source: Dev.to

당신은 그 상황을 잘 알고 있습니다. 복잡한 백엔드 문제를 해결하느라 몰입하고 있을 때, 프로덕트 매니저가 슬랙으로 알림을 보냅니다:
“안녕, 지난 주에 독일에서 가입한 모든 사용자를 빨리 리스트로 뽑아줄 수 있어? 10분 안에 회의에 써야 해.”
당신은 SQL을 압니다. 잠자는 동안에도 그 쿼리를 작성할 수 있죠. 하지만 그렇게 해야 할 필요는 없습니다.
즉석 보고용 쿼리를 작성하는 데 쓰는 1분은 실제 제품 기능을 만드는 데서 빼앗기는 시간입니다.
해결책은 또 다른 복잡한 관리자 대시보드를 만드는 것이 아닙니다.
해결책은 팀에게 그들의 언어로 대화할 수 있는 도구를 제공하는 것입니다.
이번 튜토리얼에서는 “데이터베이스와 채팅하기” 애플리케이션을 만들겠습니다. 비기술 사용자도 자연어로 질문하면 AI가 안전하고 읽기 전용인 SQL을 작성·실행해 줍니다.
가장 좋은 점은? 100 % 로컬에서 실행됩니다.
- API 키 없음: 비용 $0.00.
- 데이터 프라이버시 위험 없음: 프로덕션 스키마가 절대 머신을 떠나지 않습니다.
- ‘글루 코드’ 악몽 없음: 새로운 Model Context Protocol (MCP) 을 사용해 아키텍처를 깔끔하게 유지합니다.
이 가이드를 끝까지 따라오면 “이 데이터 좀 뽑아줄래?” 라는 요청을 “물론이죠, 봇에게 물어보세요.” 로 바꾸는 배포 가능한 Docker 컨테이너를 얻게 됩니다.
시간을 되찾아 보세요.

청사진
이 튜토리얼을 이해하기 쉽게 만들기 위해 AI 통합의 핵심 로직에만 집중하겠습니다. 50줄에 달하는 파이썬 import 문으로 지루하게 만들지는 않을게요.
전체 코드(도커 설정 및 시드 데이터 생성기 포함)는 저장소에서 받을 수 있습니다. 저장소를 클론하고 docker-compose up을 실행한 뒤 따라오세요.
The Architecture: “The USB‑C for AI”
코드를 한 줄이라도 작성하기 전에, Model Context Protocol (MCP) 를 왜 사용하는지 이해해야 합니다.
LLM 초기 시절에는 모델을 데이터베이스에 연결하려면 지저분한 “접착 코드”를 작성해야 했습니다. 프롬프트에 테이블 정의를 수동으로 삽입하고, 정규식을 파싱해 SQL을 찾아내며, 모든 오류 상황을 직접 처리해야 했죠. 이는 매우 깨지기 쉽고 유지보수가 어려웠습니다.
MCP는 연결 방식을 표준화함으로써 이를 해결합니다. 마치 AI용 USB‑C 포트와 같습니다:
- Resources (Context): AI가 “내가 어떤 데이터를 가지고 있지?” 라고 물으면, 서버가 데이터베이스 스키마를 반환합니다.
- Tools (Action): AI가 “쿼리를 실행해야겠다” 라고 결정하면, 서버가 안전하게 함수를 실행합니다.
우리는 Brain (LLM) 과 Hands (데이터베이스 실행)를 분리하고 있습니다.
Stack we will build
| Component | Technology |
|---|---|
| Brain | Mistral 7B running on Ollama (chosen for its reliable JSON output) |
| Interface | Streamlit app where the user chats |
| Server | Python script using FastMCP that holds the SQLite connection |

Step 1: 환경 (Docker는 당신의 친구)
Docker를 사용하여 “내 컴퓨터에서는 작동한다”는 증후군을 피하겠습니다. 컨테이너는 UI와 데이터베이스를 포함하지만, 호스트에서 실행 중인 Ollama 인스턴스와도 통신할 수 있습니다.
docker-compose.yml 파일을 생성합니다. 이것이 원클릭 설정입니다:
services:
mcp-reporter:
build: .
container_name: pocket_analyst
ports:
- "8501:8501"
volumes:
- ./data:/app/data
environment:
# Crucial: lets the container talk to Ollama on your laptop
- OLLAMA_HOST=http://host.docker.internal:11434
extra_hosts:
- "host.docker.internal:host-gateway"
Step 2: The Server (Giving the AI “Hands”)
우리는 FastMCP 라이브러리를 사용해 서버를 만듭니다. 이 스크립트는 두 가지 일을 수행합니다:
- 데이터베이스 스키마를 노출합니다 (Context).
- 데이터베이스를 질의하는 함수를 제공합니다 (Tool).
Note: Boiler‑plate code is omitted for brevity. See src/server.py in the repo for the full implementation.
# ... standard imports and DB setup (see repo) ...
@mcp.resource("sqlite://schema")
def get_schema() -> str:
"""Read the database schema and return it as context."""
# In a real app you would query sqlite_master dynamically
return "CREATE TABLE transactions (...);"
@mcp.tool()
def query_database(sql: str) -> str:
"""
Execute a read‑only SQL query against the database.
Args:
sql: The SELECT statement to execute.
Returns:
The query results as a JSON‑serialisable string.
"""
# Connect to the database, execute the query, and return results
# (implementation omitted for brevity)
pass
Step 3: AI 브레인 (Mistral 7B via Ollama)
Run Ollama locally and pull the Mistral 7B model:
ollama serve # starts the Ollama server (default port 11434)
ollama pull mistral # downloads the model
FastMCP 서버에서는 이 엔드포인트와 통신하고 모델에게 다음을 수행하도록 지시하는 클라이언트를 구성합니다.
get_schema리소스를 통해 스키마를 요청합니다.- JSON‑only 형식의 응답으로 SQL 쿼리를 생성합니다.
- 생성된 SQL을 사용해
query_database를 호출합니다.
모델의 응답은 이후 Streamlit UI로 다시 전달됩니다.
Step 4: 프론트‑엔드 (Streamlit 채팅)
간단한 Streamlit 앱(app.py)이 채팅 UI를 처리합니다:
import streamlit as st
import httpx
st.title("🗂️ Chat with your Database")
if "messages" not in st.session_state:
st.session_state.messages = []
def send_message(user_msg: str):
st.session_state.messages.append({"role": "user", "content": user_msg})
with httpx.Client(base_url="http://localhost:8000") as client:
resp = client.post("/chat", json={"message": user_msg})
assistant_msg = resp.json()["reply"]
st.session_state.messages.append({"role": "assistant", "content": assistant_msg})
for msg in st.session_state.messages:
st.chat_message(msg["role"]).write(msg["content"])
if prompt := st.chat_input("Ask a question about the data…"):
send_message(prompt)
UI를 실행하려면:
streamlit run app.py
http://localhost:8501에 접속하여 채팅을 시작하세요.
Step 5: 전체를 한데 모으기
# 1️⃣ Build and start the containers
docker-compose up --build
# 2️⃣ In another terminal, start Ollama (if not already running)
ollama serve
# 3️⃣ Open the Streamlit UI in your browser
# → http://localhost:8501
이제 다음과 같은 자연어 질문을 할 수 있습니다:
- “지난 주에 독일에서 몇 명의 사용자가 가입했나요?”
- “지난 한 달 동안 국가별 총 거래 금액을 보여 주세요.”
AI는 다음과 같이 동작합니다:
- 스키마를 가져옵니다.
- 안전하고 읽기 전용인 SQL을 생성합니다.
- SQLite DB에 대해 쿼리를 실행합니다.
- 결과를 채팅에 반환합니다.
Recap
- Zero API keys – 모든 것이 로컬에서 실행됩니다.
- Zero data leakage – 스키마가 절대로 머신을 떠나지 않습니다.
- Clean architecture – MCP는 컨텍스트, 도구, LLM 뇌를 깔끔하게 분리합니다.
- Dockerised – 한 명령으로 전체 스택을 실행할 수 있습니다.
한 번 실행해 보고, 프롬프트를 조정하고, 패턴을 여러분의 데이터베이스(Postgres, MySQL 등)에 맞게 적용해 보세요. 즐거운 해킹 되세요!
Step 3: The Brain (Why Mistral Wins)
이제 Host—대화를 관리하는 Streamlit 앱이 필요합니다.
우리는 모델 실행을 위해 Ollama를 사용하고 있습니다. 테스트를 통해 얻은 중요한 인사이트: Mistral 7B를 사용하고, Llama 3는 사용하지 말 것.
Llama 3는 강력하지만, 작은 모델은 엄격한 JSON 형식을 맞추는 데 어려움을 겪는 경우가 많습니다. 여러분이
{ "sql": "…" }
와 같이 요청하면 Llama는 *“Sure! Here is your JSON: { … }”*와 같이 추가적인 대화 텍스트를 포함해 답변할 수 있습니다. 이 여분의 텍스트가 파서를 깨뜨립니다.
Mistral 7B는 놀라울 정도로 규칙을 잘 지킵니다. 불필요한 말을 하지 않고 우리가 필요한 JSON만 출력해 도구를 트리거합니다.
src/app.py (excerpt)
# ... imports and Streamlit page config omitted ...
# The System Prompt is where we "program" the behavior
# We explicitly tell the model about the schema and the output format
SYSTEM_PROMPT = """
You are a Data Analyst.
1. Output ONLY a JSON object with the tool call.
2. The format must be: { "tool": "query_database", "sql": "SELECT..." }
3. Always convert cents to main currency units in the final answer.
"""
# ... inside the main chat loop ...
if prompt := st.chat_input("Ask a question..."):
# 1. Send the user's request to Ollama
# Note: We use format='json' to force structured output
response = client.chat(
model="mistral",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": prompt}
],
format="json"
)
# 2. Parse the tool call
# (Full error handling and JSON parsing logic is in the repo)
tool_call = json.loads(response['message']['content'])
if tool_call['tool'] == 'query_database':
# 3. Execute the SQL via our MCP Server
result = call_mcp_tool(tool_call['sql'])
# 4. Feed the data back to Mistral to summarize
final_answer = client.chat(
model="mistral",
messages=[
# … previous messages …
{"role": "user", "content": f"Data: {result}"}
]
)
st.write(final_answer['message']['content'])

Step 4: 데이터와 대화하기
docker-compose up을 실행하면 컨테이너가 자동으로 seed_data.py 스크립트(레포에 포함)를 실행하여 2,000개의 현실적인 “Mock Stripe” 거래를 SQLite 데이터베이스에 채웁니다.
Test 1 – “복합 필터”
Prompt
어제 독일에서 받은 실패한 신용카드 결제 건수가 몇 건인가요?
Generated SQL
SELECT count(*)
FROM transactions
WHERE country_code = 'DE'
AND status = 'failed'
AND payment_method = 'card'
AND created_at > date('now', '-1 day');
Test 2 – “즉석 변환”
Prompt
지난 달 국가별 총 EUR 결제 금액을 계산하고 CSV 형식으로 출력하세요.
Result (CSV)
"Country Code","Total EUR Payments"
"US",2873932
"DE",1093878
"GB",533471
"BR",492770
"FR",454523
"JP",348649
전통적인 대시보드의 치명적인 결함은 사용자가 물어볼 것이라고 예측한 질문에만 답한다는 점입니다. 이 아키텍처는 예상하지 못한 문제들을 해결하며, 제한된 하드코딩 버튼 세트를 무한한 언어 유연성으로 대체합니다.
계속 성장합시다.
자신 있게 엔지니어링 경력을 성장시키려면 구독하세요.
