🌊 Mistral · 실전 예제

Mistral 실전 예제 모음

Mistral의 강점인 Function Calling과 Tool Use를 활용한 에이전트 예제부터 RAG, 분류, 멀티턴 API까지. 모든 예제는 Ollama 기반 무료입니다.

✅ 100% 무료 ✅ API 키 불필요 OllamaFunction Calling Tool UseFastAPI

0환경 설정

1
Ollama + Mistralollama pull mistral (7B, ~4.1GB)
2
패키지pip install fastapi uvicorn ollama langchain-community chromadb sentence-transformers

1Function Calling 에이전트

Mistral의 네이티브 Function Calling을 사용해 외부 API, DB, 파일 시스템과 연동하는 에이전트를 만듭니다.

function_agent.py — 날씨/계산기/파일 에이전트
Function CallingOllama
자연어 명령을 받아 적절한 도구를 선택·실행하는 완전한 에이전트. Ollama의 tool_calls 기능 활용.
import json
from ollama import Client

ollama = Client()

# ── 도구 정의
TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "특정 도시의 현재 날씨 정보를 가져옵니다.",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "도시 이름 (예: 서울, 부산)"},
                    "unit": {"type": "string", "enum": ["celsius", "fahrenheit"], "default": "celsius"},
                },
                "required": ["city"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "수학 계산을 수행합니다.",
            "parameters": {
                "type": "object",
                "properties": {
                    "expression": {"type": "string", "description": "계산할 수식 (예: 2 + 3 * 4)"},
                },
                "required": ["expression"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "search_file",
            "description": "로컬 파일에서 텍스트를 검색합니다.",
            "parameters": {
                "type": "object",
                "properties": {
                    "filename": {"type": "string"},
                    "query": {"type": "string"},
                },
                "required": ["filename", "query"],
            },
        },
    },
]

# ── 도구 실행 함수들
def get_weather(city: str, unit: str = "celsius") -> dict:
    # 실제 날씨 API 연동 시 여기에 구현
    return {"city": city, "temp": 22, "condition": "맑음", "unit": unit}

def calculate(expression: str) -> dict:
    try:
        result = eval(expression, {"__builtins__": {}})  # 안전한 eval
        return {"expression": expression, "result": result}
    except Exception as e:
        return {"error": str(e)}

def search_file(filename: str, query: str) -> dict:
    try:
        with open(filename) as f:
            lines = [l for l in f.readlines() if query.lower() in l.lower()]
        return {"matches": lines[:5], "total": len(lines)}
    except FileNotFoundError:
        return {"error": f"{filename} 파일을 찾을 수 없습니다."}

TOOL_FUNCTIONS = {
    "get_weather": get_weather,
    "calculate": calculate,
    "search_file": search_file,
}

# ── 에이전트 실행
def run_agent(user_message: str) -> str:
    messages = [{"role": "user", "content": user_message}]

    while True:
        resp = ollama.chat(
            model="mistral",
            messages=messages,
            tools=TOOLS,
        )
        msg = resp["message"]
        messages.append(msg)

        # 도구 호출 없으면 최종 응답
        if not msg.get("tool_calls"):
            return msg["content"]

        # 도구 실행
        for tool_call in msg["tool_calls"]:
            fn_name = tool_call["function"]["name"]
            fn_args = json.loads(tool_call["function"]["arguments"])

            result = TOOL_FUNCTIONS[fn_name](**fn_args)

            messages.append({
                "role": "tool",
                "content": json.dumps(result, ensure_ascii=False),
            })

# 테스트
queries = [
    "서울 날씨는 어때?",
    "123 * 456 + 789 계산해줘",
    "오늘 밥 먹었어?",  # 도구 불필요한 일반 대화
]
for q in queries:
    print(f"Q: {q}")
    print(f"A: {run_agent(q)}\n")python

2다중 도구 ReAct 에이전트

여러 도구를 순서대로 사용하며 복잡한 작업을 단계적으로 해결하는 에이전트입니다.

react_agent.py — 연구 보조 에이전트
ReAct 패턴LangChain
from langchain_community.llms import Ollama
from langchain.agents import AgentExecutor, create_react_agent
from langchain.tools import tool
from langchain import hub
from langchain.prompts import PromptTemplate

llm = Ollama(model="mistral", temperature=0)

@tool
def web_search(query: str) -> str:
    """인터넷에서 정보를 검색합니다. 최신 정보나 모르는 사실을 찾을 때 사용합니다."""
    # 실제로는 DuckDuckGo, SerpAPI 등 연동
    return f"'{query}'에 대한 검색 결과: [데모 결과] Python 3.12가 2023년 10월 출시되었습니다."

@tool
def read_file(filepath: str) -> str:
    """로컬 파일의 내용을 읽습니다. filepath는 파일 경로입니다."""
    try:
        with open(filepath) as f:
            return f.read()[:2000]  # 처음 2000자
    except Exception as e:
        return f"오류: {e}"

@tool
def write_summary(content: str, filename: str = "summary.txt") -> str:
    """내용을 파일로 저장합니다."""
    with open(filename, "w", encoding="utf-8") as f:
        f.write(content)
    return f"✅ {filename}에 저장되었습니다."

@tool
def math_calc(expression: str) -> str:
    """수학 계산을 수행합니다. 예: '2**10' → 1024"""
    try:
        return str(eval(expression, {"__builtins__": {}, "abs": abs, "round": round}))
    except Exception as e:
        return f"계산 오류: {e}"

tools = [web_search, read_file, write_summary, math_calc]

# ReAct 프롬프트 (한국어 커스텀)
react_prompt = PromptTemplate.from_template("""
당신은 주어진 도구를 활용해 문제를 해결하는 AI 에이전트입니다.

사용 가능한 도구:
{tools}

형식:
생각: [현재 상황 분석]
행동: [도구 이름]
행동 입력: [도구에 전달할 입력]
관찰: [도구 실행 결과]
... (반복)
최종 답변: [사용자에게 전달할 최종 답변]

질문: {input}
{agent_scratchpad}
""")

agent = create_react_agent(llm, tools, react_prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, max_iterations=5)

result = agent_executor.invoke({
    "input": "Python 최신 버전을 검색하고, 2의 10승을 계산해서 정리해줘"
})
print(result["output"])python

3RAG + 로컬 임베딩

mistral_rag.py — 사내 문서 Q&A
ChromaDBsentence-transformers
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.llms import Ollama
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import ConversationalRetrievalChain
from langchain.memory import ConversationBufferMemory

# 무료 다국어 임베딩 모델
embeddings = HuggingFaceEmbeddings(
    model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
)

# 문서 인덱싱
def index_documents(file_paths: list[str]) -> Chroma:
    all_docs = []
    splitter = RecursiveCharacterTextSplitter(chunk_size=400, chunk_overlap=40)

    for path in file_paths:
        with open(path, encoding="utf-8") as f:
            text = f.read()
        from langchain.schema import Document
        docs = splitter.split_documents([Document(page_content=text, metadata={"source": path})])
        all_docs.extend(docs)

    return Chroma.from_documents(all_docs, embeddings, persist_directory="./mistral_rag_db")

# 대화형 QA (대화 히스토리 유지)
def create_conversational_qa(vectorstore: Chroma):
    llm = Ollama(model="mistral", temperature=0.3)
    memory = ConversationBufferMemory(
        memory_key="chat_history",
        return_messages=True,
        output_key="answer",
    )
    return ConversationalRetrievalChain.from_llm(
        llm=llm,
        retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
        memory=memory,
        return_source_documents=True,
    )

# 사용 예시
if __name__ == "__main__":
    # 샘플 문서 생성
    with open("company_policy.txt", "w") as f:
        f.write("우리 회사 휴가 정책: 연간 15일의 유급 휴가를 제공합니다.\n")
        f.write("재택근무: 주 3일까지 재택 근무 가능합니다.\n")
        f.write("점심 시간: 12시~13시입니다.\n")

    vs = index_documents(["company_policy.txt"])
    qa = create_conversational_qa(vs)

    questions = ["휴가는 몇 일이야?", "재택 근무는 얼마나 할 수 있어?"]
    for q in questions:
        result = qa.invoke({"question": q})
        print(f"Q: {q}")
        print(f"A: {result['answer']}\n")python

4텍스트 분류 API

classifier_api.py — 다중 레이블 분류
FastAPIJSON Mode
from fastapi import FastAPI
from pydantic import BaseModel
from ollama import Client
import json

app = FastAPI(title="Text Classifier API")
ollama = Client()

class ClassifyRequest(BaseModel):
    text: str
    categories: list[str]
    multi_label: bool = False  # True면 여러 카테고리 허용

class ClassifyResponse(BaseModel):
    text: str
    category: str | list[str]
    confidence: float
    reason: str

@app.post("/classify", response_model=ClassifyResponse)
async def classify(req: ClassifyRequest):
    cats = ", ".join(req.categories)
    mode = "하나 이상" if req.multi_label else "정확히 하나"

    resp = ollama.chat(
        model="mistral",
        messages=[{
            "role": "system",
            "content": f"""텍스트를 분류해 JSON으로 반환하세요.
카테고리: {cats}
{mode}의 카테고리를 선택하세요.

JSON 형식:
{{"category": {"[배열]" if req.multi_label else '"단일 문자열"'},
 "confidence": 0.0~1.0,
 "reason": "선택 이유 한 문장"}}"""
        }, {
            "role": "user",
            "content": req.text,
        }],
        format="json",
    )

    data = json.loads(resp["message"]["content"])
    return ClassifyResponse(text=req.text, **data)

# 테스트: uvicorn classifier_api:app --reload
# curl -X POST http://localhost:8000/classify \
#   -H "Content-Type: application/json" \
#   -d '{"text":"내일 비가 온대", "categories":["날씨","음식","스포츠","기술"]}'python

5번역 서비스

translation_api.py — 다국어 번역 API
FastAPI비동기
from fastapi import FastAPI
from pydantic import BaseModel
from ollama import AsyncClient
import asyncio

app = FastAPI(title="Translation API")
ollama = AsyncClient()

LANGS = {"ko": "한국어", "en": "영어", "ja": "일본어", "zh": "중국어", "fr": "프랑스어", "de": "독일어"}

class TranslateRequest(BaseModel):
    text: str
    source: str = "auto"  # 자동 감지
    target: str = "en"

class TranslateResponse(BaseModel):
    original: str
    translated: str
    source_lang: str
    target_lang: str

@app.post("/translate", response_model=TranslateResponse)
async def translate(req: TranslateRequest):
    target_name = LANGS.get(req.target, req.target)

    if req.source == "auto":
        system = f"다음 텍스트를 {target_name}로 번역하세요. 번역문만 출력하고 설명은 하지 마세요."
        source_name = "자동 감지"
    else:
        source_name = LANGS.get(req.source, req.source)
        system = f"{source_name}를 {target_name}로 번역하세요. 번역문만 출력하세요."

    resp = await ollama.chat(
        model="mistral",
        messages=[
            {"role": "system", "content": system},
            {"role": "user", "content": req.text},
        ],
    )

    return TranslateResponse(
        original=req.text,
        translated=resp["message"]["content"].strip(),
        source_lang=source_name,
        target_lang=target_name,
    )

# 병렬 번역 (여러 언어 동시)
@app.post("/translate/batch")
async def batch_translate(text: str, targets: list[str] = ["en", "ja", "zh"]):
    tasks = [translate(TranslateRequest(text=text, target=t)) for t in targets]
    results = await asyncio.gather(*tasks)
    return {r.target_lang: r.translated for r in results}python
🚀
다음 단계: Mistral 완전 가이드 또는 다른 모델 예제: Llama · Gemma · DeepSeek