Ollama 고급 활용

Modelfile 작성, 커스텀 모델 생성, RAG 파이프라인, 에이전트 구현, 프로덕션 배포

업데이트 안내: 모델/요금/버전/정책 등 시점에 민감한 정보는 변동될 수 있습니다. 최신 내용은 공식 문서를 확인하세요.
🎯 이 문서에서 배울 내용
  1. Modelfile - 모델 동작을 완전히 커스터마이징
  2. 커스텀 모델 - 특정 도메인에 특화된 모델 생성
  3. RAG 파이프라인 - 문서 기반 질의응답 시스템
  4. 에이전트 - 도구를 사용하는 자율 AI
  5. 성능 최적화 - 속도와 품질 개선
  6. 프로덕션 배포 - 실제 서비스 운영

Modelfile 작성

Modelfile은 Dockerfile과 유사하게, Ollama 모델의 동작을 정의하는 설정 파일입니다. 시스템 프롬프트, 파라미터, 템플릿 등을 커스터마이징할 수 있습니다.

기본 구조

# Modelfile 예시
FROM llama3.2

# 시스템 프롬프트
SYSTEM """당신은 파이썬 전문가입니다.
모든 답변은 PEP 8 스타일 가이드를 따르고,
타입 힌트와 docstring을 포함해야 합니다."""

# 파라미터 설정
PARAMETER temperature 0.7
PARAMETER top_p 0.9
PARAMETER top_k 40

# 메시지 템플릿
TEMPLATE """{{ .System }}

User: {{ .Prompt }}
Assistant:"""

Modelfile 명령어

명령어 설명 예시
FROM 베이스 모델 지정 FROM llama3.2
SYSTEM 시스템 프롬프트 SYSTEM "당신은 전문가입니다"
PARAMETER 모델 파라미터 PARAMETER temperature 0.8
TEMPLATE 메시지 템플릿 TEMPLATE "{{ .Prompt }}"
MESSAGE 대화 히스토리 MESSAGE user "안녕"
ADAPTER LoRA 어댑터 ADAPTER ./adapter.bin
LICENSE 라이선스 정보 LICENSE "MIT"

파라미터 설정

파라미터 기본값 범위 효과
temperature 0.8 0.0 ~ 2.0 높을수록 창의적, 낮을수록 일관적
top_p 0.9 0.0 ~ 1.0 누적 확률 임계값 (nucleus sampling)
top_k 40 1 ~ 100 상위 K개 토큰만 선택
num_ctx 2048 512 ~ 8192 컨텍스트 윈도우 크기 (토큰)
num_predict 128 -1 ~ 2048 생성할 최대 토큰 수 (-1 = 무제한)
repeat_penalty 1.1 1.0 ~ 2.0 반복 억제 (높을수록 덜 반복)
stop - 문자열 생성 중단 시퀀스
seed -1 정수 난수 시드 (재현성)

실전 예제

1. 코드 리뷰 모델

# Modelfile.codereview
FROM codellama:13b

SYSTEM """당신은 시니어 소프트웨어 엔지니어입니다.
코드를 리뷰할 때 다음 관점에서 분석하세요:

1. 버그 및 논리 오류
2. 성능 최적화 기회
3. 보안 취약점
4. 코드 가독성 및 유지보수성
5. 테스트 커버리지

리뷰는 건설적이고 구체적인 개선 제안을 포함하세요."""

PARAMETER temperature 0.3
PARAMETER top_p 0.95
PARAMETER num_ctx 4096

TEMPLATE """{{ .System }}

코드:
```
{{ .Prompt }}
```

리뷰:"""
# 모델 생성
ollama create codereview -f Modelfile.codereview

# 사용
ollama run codereview "def calc(a,b): return a/b"

2. JSON 출력 모델

# Modelfile.json
FROM llama3.2

SYSTEM """당신은 JSON 생성 전문가입니다.
모든 응답은 유효한 JSON 형식이어야 합니다.
추가 설명이나 텍스트 없이 JSON만 출력하세요."""

PARAMETER temperature 0.1
PARAMETER stop "```"

TEMPLATE """{{ .System }}

요청: {{ .Prompt }}

JSON:
```json"""
ollama create json-generator -f Modelfile.json

ollama run json-generator "사용자 프로필 스키마를 만들어줘 (name, email, age)"
# 출력:
{
  "name": "string",
  "email": "string",
  "age": "number"
}

3. 한국어 특화 모델

# Modelfile.korean
FROM llama3.2

SYSTEM """당신은 한국어 전문 AI 어시스턴트입니다.
모든 답변은 존댓말을 사용하고, 자연스러운 한국어로 작성하세요.
외래어나 영어 표현은 최소화하고, 한글 우선으로 설명하세요."""

PARAMETER temperature 0.7
PARAMETER repeat_penalty 1.2

MESSAGE user "안녕하세요"
MESSAGE assistant "안녕하세요! 무엇을 도와드릴까요?"

4. SQL 생성 모델

# Modelfile.sql
FROM codellama:7b

SYSTEM """당신은 SQL 전문가입니다.
자연어 질문을 PostgreSQL 쿼리로 변환하세요.

규칙:
- 표준 SQL 문법 사용
- 주석으로 쿼리 설명 추가
- 인덱스 활용 고려
- SQL 인젝션 방지"""

PARAMETER temperature 0.2
PARAMETER top_p 0.95

TEMPLATE """{{ .System }}

스키마:
- users (id, name, email, created_at)
- orders (id, user_id, amount, status, created_at)
- products (id, name, price, stock)

질문: {{ .Prompt }}

SQL:
```sql"""

커스텀 모델 생성

GGUF 모델 가져오기

Hugging Face에서 GGUF 포맷 모델을 다운로드하여 Ollama에서 사용할 수 있습니다.

# 1. Hugging Face에서 GGUF 파일 다운로드
# 예: TheBloke의 모델들
wget https://huggingface.co/TheBloke/Llama-2-13B-GGUF/resolve/main/llama-2-13b.Q4_K_M.gguf

# 2. Modelfile 작성
cat > Modelfile.custom <<EOF
FROM ./llama-2-13b.Q4_K_M.gguf

SYSTEM "당신은 도움이 되는 AI 어시스턴트입니다."

PARAMETER temperature 0.8
EOF

# 3. Ollama 모델 생성
ollama create my-llama2-13b -f Modelfile.custom

# 4. 사용
ollama run my-llama2-13b

LoRA 어댑터 적용

LoRA (Low-Rank Adaptation)는 모델의 일부만 파인튜닝하여 특정 작업에 특화시키는 효율적인 방법입니다.

# 1. LoRA 어댑터 학습 (외부 도구 사용)
# 예: llama.cpp의 finetune 기능

# 2. Modelfile에 어댑터 추가
cat > Modelfile.lora <<EOF
FROM llama3.2

ADAPTER ./my-lora-adapter.bin

SYSTEM "당신은 의료 전문 AI입니다."

PARAMETER temperature 0.5
EOF

# 3. 모델 생성
ollama create medical-llama -f Modelfile.lora

Few-Shot 학습 모델

# Modelfile.fewshot
FROM llama3.2

SYSTEM "당신은 감성 분석 전문가입니다. 텍스트의 감정을 positive/negative/neutral로 분류하세요."

# Few-shot 예제 추가
MESSAGE user "이 영화 정말 최고예요!"
MESSAGE assistant "positive"

MESSAGE user "돈 아까워요. 시간 낭비였어요."
MESSAGE assistant "negative"

MESSAGE user "그냥 평범한 영화였어요."
MESSAGE assistant "neutral"

PARAMETER temperature 0.3
ollama create sentiment-analyzer -f Modelfile.fewshot

ollama run sentiment-analyzer "기대 이상이었어요. 강추합니다!"
# positive

RAG 파이프라인 구축

RAG (Retrieval-Augmented Generation)는 외부 문서를 검색하여 LLM의 응답을 보강하는 기법입니다. 환각(hallucination)을 줄이고 최신 정보를 제공합니다.

RAG 아키텍처

사용자 질문 임베딩 생성 벡터 DB 검색 (Chroma, FAISS) 관련 문서 Top K 문서 LLM (Ollama) 질문 + 컨텍스트 답변 생성 응답 문서 저장소 PDF, DOCX, TXT

간단한 RAG 구현 (Python)

import requests
from typing import List
import numpy as np

class SimpleRAG:
    def __init__(self, documents: List[str]):
        self.documents = documents
        self.embeddings = [self._embed(doc) for doc in documents]

    def _embed(self, text: str) -> List[float]:
        """Ollama 임베딩 API 사용"""
        response = requests.post(
            'http://localhost:11434/api/embeddings',
            json={'model': 'nomic-embed-text', 'prompt': text}
        )
        return response.json()['embedding']

    def _cosine_similarity(self, a: List[float], b: List[float]) -> float:
        """코사인 유사도 계산"""
        a = np.array(a)
        b = np.array(b)
        return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

    def retrieve(self, query: str, k: int = 3) -> List[str]:
        """가장 관련 있는 문서 K개 검색"""
        query_emb = self._embed(query)

        # 유사도 계산
        scores = [
            self._cosine_similarity(query_emb, doc_emb)
            for doc_emb in self.embeddings
        ]

        # 상위 K개 인덱스
        top_indices = np.argsort(scores)[-k:][::-1]

        return [self.documents[i] for i in top_indices]

    def generate(self, query: str) -> str:
        """RAG 응답 생성"""
        # 1. 관련 문서 검색
        context_docs = self.retrieve(query, k=3)
        context = "\n\n".join(context_docs)

        # 2. 프롬프트 구성
        prompt = f"""다음 컨텍스트를 바탕으로 질문에 답변하세요.

컨텍스트:
{context}

질문: {query}

답변:"""

        # 3. LLM 호출
        response = requests.post(
            'http://localhost:11434/api/generate',
            json={
                'model': 'llama3.2',
                'prompt': prompt,
                'stream': False
            }
        )

        return response.json()['response']


# 사용 예시
documents = [
    "FastAPI는 Python 웹 프레임워크입니다. 빠르고 현대적입니다.",
    "Flask는 마이크로 웹 프레임워크로 간단하고 유연합니다.",
    "Django는 풀스택 프레임워크로 admin 패널을 제공합니다.",
]

rag = SimpleRAG(documents)
answer = rag.generate("FastAPI의 특징은?")
print(answer)

ChromaDB를 사용한 프로덕션 RAG

from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import OllamaEmbeddings
from langchain_community.llms import Ollama
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQA
from langchain_community.document_loaders import DirectoryLoader, PyPDFLoader

class ProductionRAG:
    def __init__(self, docs_dir: str, persist_dir: str = "./chroma_db"):
        self.embeddings = OllamaEmbeddings(model="nomic-embed-text")
        self.llm = Ollama(model="llama3.2", temperature=0.3)
        self.persist_dir = persist_dir

        # 문서 로드 및 벡터 DB 생성
        self.vectorstore = self._build_vectorstore(docs_dir)
        self.qa_chain = self._create_qa_chain()

    def _build_vectorstore(self, docs_dir: str):
        """문서 로드 및 벡터 DB 생성"""
        # PDF 로더
        loader = DirectoryLoader(
            docs_dir,
            glob="**/*.pdf",
            loader_cls=PyPDFLoader
        )
        documents = loader.load()

        # 텍스트 분할
        text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=1000,
            chunk_overlap=200,
            length_function=len
        )
        chunks = text_splitter.split_documents(documents)

        # 벡터 DB 생성
        vectorstore = Chroma.from_documents(
            documents=chunks,
            embedding=self.embeddings,
            persist_directory=self.persist_dir
        )

        return vectorstore

    def _create_qa_chain(self):
        """QA 체인 생성"""
        return RetrievalQA.from_chain_type(
            llm=self.llm,
            chain_type="stuff",
            retriever=self.vectorstore.as_retriever(
                search_type="similarity",
                search_kwargs={"k": 5}
            ),
            return_source_documents=True
        )

    def query(self, question: str):
        """질문하고 답변 받기"""
        result = self.qa_chain.invoke({"query": question})

        print(f"질문: {question}")
        print(f"답변: {result['result']}")
        print(f"\n출처 문서 {len(result['source_documents'])}개:")

        for i, doc in enumerate(result['source_documents'], 1):
            print(f"  {i}. {doc.metadata.get('source', 'Unknown')}")

        return result


# 사용
rag = ProductionRAG("./company_docs")
rag.query("회사의 휴가 정책은 무엇인가요?")

RAG 최적화 기법

1. 하이브리드 검색 (키워드 + 벡터)

from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever

# BM25 (키워드 검색)
bm25_retriever = BM25Retriever.from_documents(documents)
bm25_retriever.k = 3

# 벡터 검색
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 3})

# 앙상블 (하이브리드)
ensemble_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, vector_retriever],
    weights=[0.4, 0.6]  # 벡터 검색 60% 가중치
)

2. Reranking (재순위화)

from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor

# LLM으로 관련성 재평가
compressor = LLMChainExtractor.from_llm(llm)

compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=vector_retriever
)

# 더 정확한 문서 검색
compressed_docs = compression_retriever.get_relevant_documents("질문")

3. Parent Document Retriever

from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore

# 작은 청크로 검색, 큰 청크로 컨텍스트 제공
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)

store = InMemoryStore()

retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=store,
    child_splitter=child_splitter,
    parent_splitter=parent_splitter
)

에이전트 구현

에이전트는 도구(Tools)를 사용하여 복잡한 작업을 자율적으로 수행하는 AI 시스템입니다.

간단한 에이전트

from langchain.agents import initialize_agent, Tool, AgentType
from langchain_community.llms import Ollama
import requests

# 도구 정의
def search_wikipedia(query: str) -> str:
    """위키피디아 검색"""
    url = f"https://en.wikipedia.org/api/rest_v1/page/summary/{query}"
    response = requests.get(url)
    if response.status_code == 200:
        return response.json().get("extract", "No results")
    return "Error"

def calculate(expression: str) -> str:
    """수학 계산"""
    try:
        return str(eval(expression))
    except Exception as e:
        return f"Error: {e}"

def get_weather(city: str) -> str:
    """날씨 정보 (가짜 데이터)"""
    import random
    temp = random.randint(15, 30)
    return f"{city}의 현재 온도는 {temp}°C입니다."

# 도구 리스트
tools = [
    Tool(
        name="Wikipedia",
        func=search_wikipedia,
        description="위키피디아에서 정보를 검색할 때 사용. 입력: 검색어"
    ),
    Tool(
        name="Calculator",
        func=calculate,
        description="수학 계산. 입력: 파이썬 표현식 (예: '2+2', '10*5')"
    ),
    Tool(
        name="Weather",
        func=get_weather,
        description="도시의 날씨 정보. 입력: 도시 이름"
    )
]

# LLM 및 에이전트 초기화
llm = Ollama(model="llama3.2", temperature=0)
agent = initialize_agent(
    tools,
    llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True,
    max_iterations=5
)

# 실행
result = agent.run("서울의 날씨를 알려주고, 화씨로 변환해줘")
print(result)

커스텀 도구 에이전트

from langchain.tools import BaseTool
from pydantic import Field

class FileReadTool(BaseTool):
    name = "file_reader"
    description = "파일 내용을 읽는 도구. 입력: 파일 경로"

    def _run(self, file_path: str) -> str:
        try:
            with open(file_path, 'r') as f:
                return f.read()
        except Exception as e:
            return f"Error: {e}"

    async def _arun(self, file_path: str) -> str:
        raise NotImplementedError("Async not supported")


class DatabaseQueryTool(BaseTool):
    name = "database_query"
    description = "SQLite DB 쿼리 실행. 입력: SQL 쿼리"
    db_path: str = Field(default="./data.db")

    def _run(self, query: str) -> str:
        import sqlite3
        try:
            conn = sqlite3.connect(self.db_path)
            cursor = conn.cursor()
            cursor.execute(query)
            results = cursor.fetchall()
            conn.close()
            return str(results)
        except Exception as e:
            return f"Error: {e}"


# 사용
tools = [FileReadTool(), DatabaseQueryTool(db_path="./users.db")]
agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION)

대화형 에이전트

from langchain.agents import AgentType
from langchain.memory import ConversationBufferMemory

# 대화 메모리
memory = ConversationBufferMemory(
    memory_key="chat_history",
    return_messages=True
)

# 대화형 에이전트
agent = initialize_agent(
    tools,
    llm,
    agent=AgentType.CONVERSATIONAL_REACT_DESCRIPTION,
    memory=memory,
    verbose=True
)

# 대화
agent.run("서울의 날씨를 알려줘")
agent.run("거기 인구는 얼마야?")  # "거기" = 서울 (메모리 유지)

성능 최적화

모델 선택 최적화

작업 추천 모델 이유
코드 생성 CodeLlama 13B, Qwen2.5-Coder 14B 코드 특화, 높은 정확도
일반 대화 Llama 3.2 7B, Mistral 7B 균형잡힌 성능/속도
빠른 자동완성 Starcoder2 3B, Phi-3 Mini 작고 빠름
임베딩 nomic-embed-text 빠르고 정확
요약 Llama 3.2 3B 빠르고 효율적
복잡한 추론 Llama 3.1 70B, Mixtral 8x22B 최고 성능 (GPU 필수)

컨텍스트 윈도우 최적화

# Modelfile에서 설정
PARAMETER num_ctx 8192    # 기본 2048 → 8192로 증가
PARAMETER num_predict 512  # 응답 길이 제한

# API 호출 시
requests.post('http://localhost:11434/api/generate', json={
    'model': 'llama3.2',
    'prompt': prompt,
    'options': {
        'num_ctx': 8192,
        'num_predict': 512
    }
})

배치 처리

import asyncio
import aiohttp

async def generate_async(session, prompt):
    async with session.post(
        'http://localhost:11434/api/generate',
        json={'model': 'llama3.2', 'prompt': prompt, 'stream': False}
    ) as response:
        data = await response.json()
        return data['response']

async def batch_generate(prompts):
    async with aiohttp.ClientSession() as session:
        tasks = [generate_async(session, p) for p in prompts]
        return await asyncio.gather(*tasks)

# 사용
prompts = [f"숫자 {i}를 설명해줘" for i in range(10)]
results = asyncio.run(batch_generate(prompts))

응답 캐싱

from functools import lru_cache
import hashlib

class CachedOllama:
    def __init__(self, model: str):
        self.model = model
        self.cache = {}

    def _hash_prompt(self, prompt: str) -> str:
        return hashlib.md5(prompt.encode()).hexdigest()

    def generate(self, prompt: str) -> str:
        cache_key = self._hash_prompt(prompt)

        # 캐시 확인
        if cache_key in self.cache:
            print("Cache hit!")
            return self.cache[cache_key]

        # 생성
        response = requests.post(
            'http://localhost:11434/api/generate',
            json={'model': self.model, 'prompt': prompt, 'stream': False}
        )
        result = response.json()['response']

        # 캐시 저장
        self.cache[cache_key] = result
        return result


ollama = CachedOllama("llama3.2")
ollama.generate("안녕")  # LLM 호출
ollama.generate("안녕")  # 캐시 사용 (즉시 반환)

GPU 활용 최적화

# 환경 변수로 GPU 레이어 수 조정
export OLLAMA_NUM_GPU=99  # 모든 레이어를 GPU에
export OLLAMA_NUM_GPU=20  # 일부만 GPU (하이브리드)

# 다중 GPU 사용
export CUDA_VISIBLE_DEVICES=0,1  # GPU 0, 1 사용

# VRAM 사용량 모니터링
watch -n 1 nvidia-smi

프로덕션 배포

Docker 프로덕션 배포

# Dockerfile
FROM ollama/ollama:latest

# 모델 미리 다운로드
RUN ollama serve & sleep 5 && \
    ollama pull llama3.2 && \
    ollama pull nomic-embed-text && \
    pkill ollama

# 환경 변수
ENV OLLAMA_HOST=0.0.0.0:11434
ENV OLLAMA_KEEP_ALIVE=24h
ENV OLLAMA_NUM_PARALLEL=4

EXPOSE 11434

CMD ["ollama", "serve"]
# 빌드 및 실행
docker build -t my-ollama .
docker run -d -p 11434:11434 --gpus all --name ollama-prod my-ollama

Kubernetes 배포

# ollama-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ollama
spec:
  replicas: 2
  selector:
    matchLabels:
      app: ollama
  template:
    metadata:
      labels:
        app: ollama
    spec:
      containers:
      - name: ollama
        image: ollama/ollama:latest
        ports:
        - containerPort: 11434
        env:
        - name: OLLAMA_HOST
          value: "0.0.0.0:11434"
        resources:
          limits:
            nvidia.com/gpu: 1
            memory: "16Gi"
          requests:
            memory: "8Gi"
        volumeMounts:
        - name: ollama-data
          mountPath: /root/.ollama
      volumes:
      - name: ollama-data
        persistentVolumeClaim:
          claimName: ollama-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: ollama
spec:
  selector:
    app: ollama
  ports:
  - protocol: TCP
    port: 11434
    targetPort: 11434
  type: LoadBalancer

NGINX 리버스 프록시

# nginx.conf
upstream ollama_backend {
    server 127.0.0.1:11434;
    keepalive 32;
}

server {
    listen 80;
    server_name ollama.example.com;

    # HTTPS 리다이렉트
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name ollama.example.com;

    ssl_certificate /etc/ssl/certs/ollama.crt;
    ssl_certificate_key /etc/ssl/private/ollama.key;

    # 타임아웃 설정 (긴 응답 대비)
    proxy_read_timeout 300s;
    proxy_connect_timeout 75s;

    location / {
        proxy_pass http://ollama_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Rate limiting
        limit_req zone=ollama_limit burst=10 nodelay;
    }
}

# Rate limit zone
limit_req_zone $binary_remote_addr zone=ollama_limit:10m rate=10r/s;

모니터링

# Prometheus 메트릭 수집 (Python 예시)
from prometheus_client import Counter, Histogram, start_http_server
import time

# 메트릭 정의
request_count = Counter('ollama_requests_total', 'Total requests')
response_time = Histogram('ollama_response_seconds', 'Response time')

def generate_with_metrics(prompt):
    request_count.inc()
    start = time.time()

    # Ollama 호출
    response = requests.post('http://localhost:11434/api/generate', json={
        'model': 'llama3.2',
        'prompt': prompt,
        'stream': False
    })

    duration = time.time() - start
    response_time.observe(duration)

    return response.json()['response']

# Prometheus 서버 시작 (포트 8000)
start_http_server(8000)

로드 밸런싱

# Python 라운드 로빈 로드 밸런서
import requests
from itertools import cycle

class OllamaLoadBalancer:
    def __init__(self, servers: list):
        self.servers = cycle(servers)

    def generate(self, prompt: str) -> str:
        server = next(self.servers)
        response = requests.post(
            f'{server}/api/generate',
            json={'model': 'llama3.2', 'prompt': prompt, 'stream': False}
        )
        return response.json()['response']


lb = OllamaLoadBalancer([
    'http://ollama1:11434',
    'http://ollama2:11434',
    'http://ollama3:11434'
])

result = lb.generate("안녕")  # 자동으로 서버 분산

베스트 프랙티스

보안

  • 인증 추가: NGINX에서 Basic Auth 또는 JWT 검증
  • Rate Limiting: 남용 방지 (NGINX limit_req)
  • HTTPS: 모든 통신 암호화
  • 방화벽: 포트 11434를 외부에 직접 노출하지 말 것
  • 입력 검증: 프롬프트 인젝션 방지

프롬프트 엔지니어링

  • 구체적으로: 모호한 지시 대신 명확한 요구사항
  • 예시 제공: Few-shot learning (2-3개 예제)
  • 포맷 지정: JSON, Markdown 등 출력 형식 명시
  • 단계별 지시: "먼저 A를 하고, 그 다음 B를 해"
  • 역할 부여: "당신은 X 전문가입니다"

에러 처리

import requests
from tenacity import retry, stop_after_attempt, wait_exponential

@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=2, max=10)
)
def generate_with_retry(prompt: str) -> str:
    try:
        response = requests.post(
            'http://localhost:11434/api/generate',
            json={'model': 'llama3.2', 'prompt': prompt, 'stream': False},
            timeout=30
        )
        response.raise_for_status()
        return response.json()['response']
    except requests.exceptions.Timeout:
        print("Timeout, retrying...")
        raise
    except requests.exceptions.HTTPError as e:
        print(f"HTTP error: {e}")
        raise

테스트

import pytest

def test_ollama_connection():
    """Ollama 서버 연결 테스트"""
    response = requests.get('http://localhost:11434/api/tags')
    assert response.status_code == 200

def test_model_generation():
    """모델 생성 테스트"""
    result = generate_with_retry("2+2는?")
    assert "4" in result

def test_rag_retrieval():
    """RAG 검색 테스트"""
    rag = SimpleRAG(["Python is a programming language"])
    docs = rag.retrieve("What is Python?")
    assert len(docs) > 0

다음 단계

고급 활용법을 마스터했습니다! 이제 문제 해결 방법을 배워봅시다.

📚 계속 학습하기
  1. 트러블슈팅 - 일반적인 문제 해결
  2. 도구 연동 - Continue, Aider 설정
  3. 모델 선택 - 용도별 최적 모델
🚀 실전 프로젝트
  • 회사 문서 RAG: 내부 문서 기반 Q&A 챗봇 (프라이버시 보장)
  • 코드 리뷰 봇: Git 커밋 자동 리뷰 (Modelfile 커스터마이징)
  • 다국어 번역 API: Ollama + FastAPI 프로덕션 배포
  • AI 에이전트: 자동화 워크플로우 (Slack, Jira 통합)

핵심 정리

  • Ollama 고급 활용의 핵심 개념과 흐름을 정리합니다.
  • Modelfile 작성를 단계별로 이해합니다.
  • 실전 적용 시 기준과 주의점을 확인합니다.

실무 팁

  • 입력/출력 예시를 고정해 재현성을 확보하세요.
  • Ollama 고급 활용 범위를 작게 잡고 단계적으로 확장하세요.
  • Modelfile 작성 조건을 문서화해 대응 시간을 줄이세요.