Ollama 사용법

Ollama의 CLI 명령어, REST API, 파라미터 설정 등 실전 사용 방법을 상세히 다룹니다.

⚡ 빠른 시작
  1. ollama pull qwen3:8b - 모델 다운로드 (또는 llama3.2, gemma3, deepseek-r1:8b)
  2. ollama run qwen3:8b - 대화형 실행
  3. ollama run deepseek-r1:8b --think - Thinking 모드로 추론 과정 확인
  4. ollama list - 설치된 모델 확인
  5. API로 프로그래밍 방식 접근 (구조화된 출력, 도구 호출 지원)
  6. 파라미터로 출력 품질 조정

CLI 기본 명령어

Ollama CLI는 모델 관리와 실행을 위한 간단하고 직관적인 인터페이스를 제공합니다.

모델 다운로드 (pull)

# 기본 사용법
ollama pull <모델명>

# 예제
ollama pull llama3.2          # 최신 버전 (latest 태그)
ollama pull qwen3:8b          # Qwen 3 8B 모델
ollama pull deepseek-r1:8b    # DeepSeek R1 추론 모델
ollama pull gemma3:12b        # Google Gemma 3
ollama pull llama3.2:7b-instruct-q4_0  # 특정 양자화

# 다운로드 진행 상황
pulling manifest
pulling 8eeb52dfb3bb... 100% ▕████████████████▏ 4.7 GB
pulling 73b313b5552d... 100% ▕████████████████▏  11 KB
pulling 0ba8f0e314b4... 100% ▕████████████████▏  12 KB
pulling 56bb8bd477a5... 100% ▕████████████████▏  96 B
pulling 1a4c3c319823... 100% ▕████████████████▏ 487 B
verifying sha256 digest
writing manifest
success

모델 실행 (run)

run 명령어는 모델을 실행하고 대화형 세션을 시작합니다.

# 대화형 모드
ollama run llama3.2

# 출력 예시
>>> 안녕하세요?
안녕하세요! 무엇을 도와드릴까요?

>>> 파이썬으로 피보나치 수열 함수를 작성해줘
물론입니다. 재귀와 메모이제이션을 사용한 효율적인 구현입니다:

def fibonacci(n, memo={}):
    if n in memo:
        return memo[n]
    if n <= 1:
        return n
    memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo)
    return memo[n]

>>> /bye
# 대화 종료

단일 프롬프트 실행

대화형 모드 없이 즉시 응답을 받고 종료합니다.

# 인라인 프롬프트
ollama run llama3.2 "Explain quantum computing in one sentence"

# 파일에서 프롬프트 읽기
ollama run llama3.2 < prompt.txt

# 출력을 파일로 저장
ollama run llama3.2 "Write a haiku about coding" > haiku.txt

# 파이프라인에서 사용
cat document.txt | ollama run llama3.2 "Summarize this text"

설치된 모델 확인 (list)

# 모든 로컬 모델 나열
ollama list

# 출력 예시
NAME                          ID              SIZE      MODIFIED
qwen3:8b                      f2170b1ea08a    4.9 GB    2 minutes ago
deepseek-r1:8b                28f8fd6cdc67    4.9 GB    1 hour ago
gemma3:12b                    6fd036cdfb18    8.1 GB    3 hours ago
llama3.2:latest               a80c4f17acd5    4.7 GB    2 days ago
mistral:7b-instruct           2ae6f6dd7a3d    4.1 GB    1 week ago

모델 정보 확인 (show)

# 모델 상세 정보
ollama show llama3.2

# 출력 예시
  Model
    architecture        llama
    parameters          7.2B
    context length      131072
    embedding length    4096
    quantization        Q4_0

  Parameters
    stop    "<|start_header_id|>"
    stop    "<|end_header_id|>"
    stop    "<|eot_id|>"

  License
    LLAMA 3.2 COMMUNITY LICENSE AGREEMENT

  System
    You are a helpful assistant.

# Modelfile 출력
ollama show --modelfile llama3.2

FROM /path/to/model/weights
TEMPLATE """{{ .System }}
{{ .Prompt }}"""
PARAMETER stop "<|start_header_id|>"
SYSTEM "You are a helpful assistant."

모델 삭제 (rm)

# 모델 제거
ollama rm llama3.2:7b-instruct-q4_0

# 여러 모델 한 번에 제거
ollama rm model1 model2 model3

# 확인 메시지
deleted 'llama3.2:7b-instruct-q4_0'

실행 중인 모델 확인 (ps)

# 현재 실행 중인 모델
ollama ps

# 출력 예시
NAME              ID              SIZE      PROCESSOR    UNTIL
llama3.2:latest   a80c4f17acd5    7.4 GB    100% GPU     4 minutes from now

# 설명
# - SIZE: 메모리에 로드된 크기
# - PROCESSOR: GPU 또는 CPU 사용률
# - UNTIL: 언로드 예정 시간 (idle timeout)

모델 복사 (cp)

# 모델을 새 이름으로 복사
ollama cp llama3.2:latest my-custom-llama

# 사용 사례: 커스텀 버전 관리
ollama cp llama3.2 llama3.2-backup
# 이후 llama3.2를 커스터마이징하고 원본은 백업으로 유지

커스텀 모델 생성 (create)

# Modelfile을 사용해 새 모델 생성
ollama create my-model -f Modelfile

# Modelfile 예시
FROM llama3.2:7b

# 시스템 프롬프트 커스터마이징
SYSTEM """당신은 한국어 전문 AI 어시스턴트입니다.
항상 정중하고 상세하게 답변합니다."""

# 파라미터 조정
PARAMETER temperature 0.8
PARAMETER top_p 0.9

서버 모드 (serve)

# Ollama API 서버 시작 (보통 자동 시작됨)
ollama serve

# 기본 포트: 11434
# 환경 변수로 포트 변경
OLLAMA_HOST=0.0.0.0:8080 ollama serve

# 로그 확인
time=2024-01-15T10:30:45.123+09:00 level=INFO source=server.go:123 msg="Listening on 127.0.0.1:11434"
💡 CLI 팁
  • 대화 중 /bye 입력으로 종료
  • /set parameter value로 실행 중 파라미터 변경
  • /show로 현재 설정 확인
  • /load <filepath>로 파일 내용을 컨텍스트에 추가
  • Ctrl+D로 대화 종료

일반적인 CLI 워크플로우

1. ollama pull 모델 다운로드 2. ollama list 설치 확인 3. ollama run 실행 4. 대화/작업 프롬프트 입력 만족스럽지 않으면 다른 모델 시도 ollama pull 5. 최적화 파라미터 조정 6. 통합 API로 앱 연동 ollama rm 불필요 모델 삭제

Ollama CLI 워크플로우: 다운로드부터 API 연동까지의 기본 흐름

REST API 사용법

Ollama는 HTTP REST API를 제공하여 프로그래밍 방식으로 모델과 상호작용할 수 있습니다.

주요 API 엔드포인트

엔드포인트 메서드 설명
/api/generate POST 텍스트 생성 (단일 프롬프트)
/api/chat POST 채팅 대화 (멀티턴)
/api/embed POST 텍스트 임베딩 생성
/api/tags GET 로컬 모델 목록
/api/show POST 모델 정보 조회
/api/pull POST 모델 다운로드
/api/push POST 모델 업로드
/api/delete DELETE 모델 삭제

텍스트 생성 API (/api/generate)

기본 사용

# curl 사용
curl http://localhost:11434/api/generate -d '{
  "model": "llama3.2",
  "prompt": "Why is the sky blue?"
}'

# 응답 (스트리밍)
{"model":"llama3.2","created_at":"2024-01-15T10:30:45Z","response":"The","done":false}
{"model":"llama3.2","created_at":"2024-01-15T10:30:45Z","response":" sky","done":false}
{"model":"llama3.2","created_at":"2024-01-15T10:30:45Z","response":" appears","done":false}
...
{"model":"llama3.2","done":true,"total_duration":2500000000,"prompt_eval_count":10}

스트리밍 비활성화

curl http://localhost:11434/api/generate -d '{
  "model": "llama3.2",
  "prompt": "Why is the sky blue?",
  "stream": false
}'

# 응답 (전체 텍스트 한 번에)
{
  "model": "llama3.2",
  "created_at": "2024-01-15T10:30:45Z",
  "response": "The sky appears blue because of a phenomenon...",
  "done": true,
  "total_duration": 2500000000,
  "load_duration": 500000000,
  "prompt_eval_count": 10,
  "prompt_eval_duration": 800000000,
  "eval_count": 50,
  "eval_duration": 1200000000
}

채팅 API (/api/chat)

멀티턴 대화를 위한 API입니다. 대화 히스토리를 유지하려면 이전 메시지를 포함해야 합니다.

curl http://localhost:11434/api/chat -d '{
  "model": "llama3.2",
  "messages": [
    {
      "role": "system",
      "content": "You are a helpful assistant specializing in Python."
    },
    {
      "role": "user",
      "content": "How do I read a CSV file in Python?"
    }
  ]
}'

# 두 번째 메시지 (컨텍스트 유지)
curl http://localhost:11434/api/chat -d '{
  "model": "llama3.2",
  "messages": [
    {
      "role": "system",
      "content": "You are a helpful assistant specializing in Python."
    },
    {
      "role": "user",
      "content": "How do I read a CSV file in Python?"
    },
    {
      "role": "assistant",
      "content": "You can use the csv module or pandas..."
    },
    {
      "role": "user",
      "content": "Can you show me a pandas example?"
    }
  ]
}'

Python API 예제

requests 라이브러리 사용

import requests
import json

def generate_text(prompt, model="llama3.2", stream=True):
    url = "http://localhost:11434/api/generate"
    payload = {
        "model": model,
        "prompt": prompt,
        "stream": stream
    }

    response = requests.post(url, json=payload, stream=stream)

    if stream:
        # 스트리밍 응답 처리
        full_response = ""
        for line in response.iter_lines():
            if line:
                data = json.loads(line)
                if not data.get("done"):
                    chunk = data.get("response", "")
                    full_response += chunk
                    print(chunk, end="", flush=True)
        print()  # 줄바꿈
        return full_response
    else:
        # 비스트리밍 응답
        data = response.json()
        return data.get("response", "")

# 사용 예시
result = generate_text("Explain machine learning in simple terms")
print(result)

채팅 기능 구현

class OllamaChat:
    def __init__(self, model="llama3.2", system_prompt=None):
        self.model = model
        self.messages = []
        self.url = "http://localhost:11434/api/chat"

        if system_prompt:
            self.messages.append({
                "role": "system",
                "content": system_prompt
            })

    def send(self, user_message):
        # 사용자 메시지 추가
        self.messages.append({
            "role": "user",
            "content": user_message
        })

        # API 호출
        payload = {
            "model": self.model,
            "messages": self.messages,
            "stream": False
        }

        response = requests.post(self.url, json=payload)
        data = response.json()

        # 어시스턴트 응답 저장
        assistant_message = data["message"]["content"]
        self.messages.append({
            "role": "assistant",
            "content": assistant_message
        })

        return assistant_message

    def reset(self):
        # 대화 히스토리 초기화
        system_msg = [m for m in self.messages if m["role"] == "system"]
        self.messages = system_msg

# 사용 예시
chat = OllamaChat(
    model="llama3.2",
    system_prompt="You are a helpful coding assistant."
)

print(chat.send("How do I sort a list in Python?"))
print(chat.send("What about in reverse order?"))
print(chat.send("Can I sort by a custom key?"))

JavaScript/Node.js API 예제

// fetch API 사용
async function generateText(prompt, model = 'llama3.2') {
  const response = await fetch('http://localhost:11434/api/generate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      model,
      prompt,
      stream: false
    })
  });

  const data = await response.json();
  return data.response;
}

// 스트리밍 처리
async function streamText(prompt, model = 'llama3.2') {
  const response = await fetch('http://localhost:11434/api/generate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ model, prompt, stream: true })
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const text = decoder.decode(value);
    const lines = text.split('\n').filter(line => line.trim());

    for (const line of lines) {
      const data = JSON.parse(line);
      if (!data.done) {
        process.stdout.write(data.response);
      }
    }
  }
  console.log();
}

// 사용
await streamText('Write a haiku about programming');

임베딩 API

# 임베딩 생성
curl http://localhost:11434/api/embed -d '{
  "model": "nomic-embed-text",
  "input": "The quick brown fox jumps over the lazy dog"
}'

# 응답
{
  "embeddings": [[0.123, -0.456, 0.789, ...]]  // 768차원
}

# Python 예제
def get_embedding(text, model="nomic-embed-text"):
    response = requests.post(
        "http://localhost:11434/api/embed",
        json={"model": model, "input": text}
    )
    return response.json()["embeddings"][0]

# 코사인 유사도 계산
from numpy import dot
from numpy.linalg import norm

def cosine_similarity(a, b):
    return dot(a, b) / (norm(a) * norm(b))

emb1 = get_embedding("I love programming")
emb2 = get_embedding("I enjoy coding")
emb3 = get_embedding("The weather is nice")

print(cosine_similarity(emb1, emb2))  # 높은 유사도
print(cosine_similarity(emb1, emb3))  # 낮은 유사도

파라미터 설정

Ollama는 다양한 파라미터를 통해 모델의 출력을 세밀하게 조정할 수 있습니다.

주요 파라미터

파라미터 기본값 범위 설명
temperature 0.8 0.0 - 2.0 창의성 vs 일관성
top_p 0.9 0.0 - 1.0 누적 확률 샘플링
top_k 40 1 - 100 상위 k개 토큰만 고려
num_ctx 2048 128 - 128000 컨텍스트 윈도우 크기
num_predict -1 -1 or 1+ 최대 생성 토큰 수
repeat_penalty 1.1 0.0 - 2.0 반복 억제 강도
seed 0 - 재현 가능한 출력
stop [] - 생성 중단 문자열

Temperature (온도)

가장 중요한 파라미터로, 출력의 무작위성을 조절합니다.

# 낮은 temperature (0.1-0.3): 결정론적, 일관적
curl http://localhost:11434/api/generate -d '{
  "model": "llama3.2",
  "prompt": "Complete the phrase: To be or not to be",
  "options": {
    "temperature": 0.1
  }
}'
# 출력: "that is the question" (매우 일관적)

# 높은 temperature (1.5-2.0): 창의적, 다양함
curl http://localhost:11434/api/generate -d '{
  "model": "llama3.2",
  "prompt": "Complete the phrase: To be or not to be",
  "options": {
    "temperature": 1.8
  }
}'
# 출력: "a butterfly in the moonlight" (창의적이지만 예측 불가)
0.0 0.8 (기본) 2.0 결정론적 • 코드 생성 • 정확한 답변 • 번역 균형 • 일반 대화 • 질의응답 창의적 • 브레인스토밍 • 스토리 생성 • 시/음악

Temperature 값에 따른 출력 특성 변화

Top-p와 Top-k

두 파라미터 모두 샘플링을 제한하여 출력 품질을 조절합니다.

# Top-p (nucleus sampling)
# 누적 확률이 p에 도달할 때까지의 토큰만 고려
{
  "options": {
    "top_p": 0.9  // 상위 90% 확률 토큰만
  }
}

# Top-k
# 확률 상위 k개 토큰만 고려
{
  "options": {
    "top_k": 40  // 상위 40개 토큰만
  }
}

# 조합 사용 (권장)
{
  "options": {
    "temperature": 0.7,
    "top_p": 0.9,
    "top_k": 40
  }
}

컨텍스트 길이 (num_ctx)

모델이 한 번에 처리할 수 있는 토큰 수를 설정합니다.

# 긴 문서 처리
{
  "model": "llama3.2",
  "prompt": "Summarize this long document: ...",
  "options": {
    "num_ctx": 8192  // 기본 2048보다 큼
  }
}

# 주의: 더 큰 컨텍스트는 더 많은 메모리와 시간 필요
# Llama 3.2는 최대 128K 토큰 지원

시드 (재현성)

# 같은 시드 = 같은 출력
{
  "model": "llama3.2",
  "prompt": "Generate a random story",
  "options": {
    "seed": 42,
    "temperature": 0.8
  }
}

# 테스트, 디버깅, 벤치마크에 유용

사용 사례별 파라미터 프리셋

# 1. 코드 생성 (정확성 우선)
CODE_GENERATION = {
    "temperature": 0.2,
    "top_p": 0.95,
    "top_k": 40,
    "repeat_penalty": 1.1
}

# 2. 창의적 글쓰기
CREATIVE_WRITING = {
    "temperature": 1.2,
    "top_p": 0.95,
    "top_k": 50,
    "repeat_penalty": 1.2
}

# 3. 정확한 답변 (QA)
FACTUAL_QA = {
    "temperature": 0.1,
    "top_p": 0.9,
    "top_k": 30,
    "num_predict": 256
}

# 4. 일반 대화
CONVERSATIONAL = {
    "temperature": 0.8,
    "top_p": 0.9,
    "top_k": 40
}

# 5. 번역
TRANSLATION = {
    "temperature": 0.3,
    "top_p": 0.9,
    "repeat_penalty": 1.0
}
⚠️ 파라미터 주의사항
  • temperature=0은 완전 결정론적이지만 반복적일 수 있음
  • top_ptop_k를 함께 사용하면 더 제한적
  • num_ctx 증가는 메모리 사용량을 크게 늘림
  • repeat_penalty가 너무 높으면 비문법적 출력 가능
  • 각 모델마다 최적 파라미터가 다를 수 있음

스트리밍 응답 처리

스트리밍은 토큰이 생성되는 즉시 받아볼 수 있어 사용자 경험이 향상됩니다.

Python 스트리밍

import requests
import json

def stream_response(prompt, model="llama3.2"):
    url = "http://localhost:11434/api/generate"
    payload = {
        "model": model,
        "prompt": prompt,
        "stream": True
    }

    with requests.post(url, json=payload, stream=True) as response:
        for line in response.iter_lines():
            if line:
                data = json.loads(line)

                # 생성된 텍스트 출력
                if not data.get("done", False):
                    print(data.get("response", ""), end="", flush=True)
                else:
                    # 완료 정보
                    print("\n\n--- Stats ---")
                    print(f"Total duration: {data.get('total_duration', 0) / 1e9:.2f}s")
                    print(f"Tokens generated: {data.get('eval_count', 0)}")
                    print(f"Speed: {data.get('eval_count', 0) / (data.get('eval_duration', 1) / 1e9):.1f} tokens/s")

# 사용
stream_response("Write a short story about a robot")

JavaScript 스트리밍 (브라우저)

async function streamToDOM(prompt, elementId) {
  const element = document.getElementById(elementId);
  element.textContent = '';

  const response = await fetch('http://localhost:11434/api/generate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      model: 'llama3.2',
      prompt,
      stream: true
    })
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const text = decoder.decode(value);
    const lines = text.split('\n').filter(l => l.trim());

    for (const line of lines) {
      const data = JSON.parse(line);
      if (!data.done) {
        element.textContent += data.response;
        // 자동 스크롤
        element.scrollIntoView({ behavior: 'smooth', block: 'end' });
      }
    }
  }
}

// 사용
streamToDOM('Explain async/await', 'output');

Server-Sent Events (SSE) 패턴

# Express.js 서버 예제
const express = require('express');
const fetch = require('node-fetch');
const app = express();

app.get('/stream', async (req, res) => {
  const prompt = req.query.prompt;

  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  const ollamaRes = await fetch('http://localhost:11434/api/generate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      model: 'llama3.2',
      prompt,
      stream: true
    })
  });

  const reader = ollamaRes.body.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) {
      res.write('data: [DONE]\n\n');
      res.end();
      break;
    }

    const text = decoder.decode(value);
    const lines = text.split('\n').filter(l => l.trim());

    for (const line of lines) {
      res.write(`data: ${line}\n\n`);
    }
  }
});

app.listen(3000);

멀티모달 (이미지) 처리

Ollama는 LLaVA, Bakllava, Llama 3.2 Vision 등 멀티모달 모델을 지원합니다.

CLI에서 이미지 사용

# 이미지 파일과 함께 프롬프트
ollama run llava "What's in this image?" /path/to/image.jpg

# 여러 이미지
ollama run llava "Compare these images" image1.jpg image2.jpg

# 대화형 모드에서
ollama run llava
>>> 이 이미지를 설명해줘 /path/to/photo.png
이 이미지는 아름다운 해질녘 풍경입니다. 산 너머로 노을이 지고 있습니다...

>>> 이 이미지는 어떤 색상이 있어? /path/to/photo.png
이 이미지의 주요 색상은 주황색, 분홍색, 보라색입니다...

API로 이미지 전송

# Base64 인코딩 필요
import base64
import requests

def analyze_image(image_path, prompt):
    # 이미지를 base64로 인코딩
    with open(image_path, "rb") as img_file:
        image_data = base64.b64encode(img_file.read()).decode('utf-8')

    # API 호출
    response = requests.post(
        "http://localhost:11434/api/generate",
        json={
            "model": "llava",
            "prompt": prompt,
            "images": [image_data],
            "stream": False
        }
    )

    return response.json()["response"]

# 사용 예시
result = analyze_image(
    "screenshot.png",
    "What UI elements are visible in this screenshot?"
)
print(result)

비전 모델 활용 사례

# 1. OCR (텍스트 추출)
analyze_image("document.jpg", "Extract all text from this image")

# 2. 이미지 설명
analyze_image("photo.jpg", "이 이미지를 상세히 설명해줘")

# 3. 객체 감지
analyze_image("scene.jpg", "List all objects you can see")

# 4. 다이어그램 분석
analyze_image("flowchart.png", "Explain this flowchart step by step")

# 5. 코드 스크린샷
analyze_image("code.png", "What does this code do? Find any bugs.")

# 6. UI/UX 분석
analyze_image("website.png", "Critique this web design")

# 7. 차트/그래프 해석
analyze_image("chart.png", "Summarize the trends in this chart")
💡 비전 모델 팁
  • 고해상도 이미지는 자동으로 리사이즈됨
  • 복잡한 이미지는 더 큰 모델(13B+) 사용 권장
  • 명확하고 구체적인 프롬프트 사용
  • OCR은 인쇄된 텍스트에서 가장 잘 작동
  • 여러 각도/버전의 이미지로 테스트

멀티턴 대화 구현

대화 히스토리를 유지하면서 연속적인 대화를 구현하는 방법입니다.

대화 관리 클래스

import requests
from typing import List, Dict

class Conversation:
    def __init__(
        self,
        model: str = "llama3.2",
        system_prompt: str = None,
        api_url: str = "http://localhost:11434/api/chat"
    ):
        self.model = model
        self.api_url = api_url
        self.messages: List[Dict] = []

        if system_prompt:
            self.messages.append({
                "role": "system",
                "content": system_prompt
            })

    def send(self, message: str, stream: bool = False) -> str:
        """메시지 전송 및 응답 받기"""
        self.messages.append({
            "role": "user",
            "content": message
        })

        response = requests.post(
            self.api_url,
            json={
                "model": self.model,
                "messages": self.messages,
                "stream": stream
            },
            stream=stream
        )

        if stream:
            return self._handle_stream(response)
        else:
            data = response.json()
            assistant_msg = data["message"]["content"]
            self.messages.append({
                "role": "assistant",
                "content": assistant_msg
            })
            return assistant_msg

    def _handle_stream(self, response):
        """스트리밍 응답 처리"""
        full_response = ""
        for line in response.iter_lines():
            if line:
                import json
                data = json.loads(line)
                if not data.get("done"):
                    chunk = data.get("message", {}).get("content", "")
                    full_response += chunk
                    print(chunk, end="", flush=True)
        print()
        self.messages.append({
            "role": "assistant",
            "content": full_response
        })
        return full_response

    def get_history(self) -> List[Dict]:
        """대화 히스토리 반환"""
        return self.messages.copy()

    def clear(self):
        """대화 초기화 (시스템 메시지 유지)"""
        system_msgs = [m for m in self.messages if m["role"] == "system"]
        self.messages = system_msgs

    def save(self, filepath: str):
        """대화 저장"""
        import json
        with open(filepath, "w") as f:
            json.dump({
                "model": self.model,
                "messages": self.messages
            }, f, indent=2)

    @classmethod
    def load(cls, filepath: str):
        """저장된 대화 불러오기"""
        import json
        with open(filepath) as f:
            data = json.load(f)
        conv = cls(model=data["model"])
        conv.messages = data["messages"]
        return conv

# 사용 예시
conv = Conversation(
    model="llama3.2",
    system_prompt="You are a helpful Python programming tutor."
)

print(conv.send("How do I create a class in Python?"))
print(conv.send("Can you show me an example?"))
print(conv.send("What about inheritance?"))

# 대화 저장
conv.save("chat_history.json")

# 나중에 불러오기
conv2 = Conversation.load("chat_history.json")
print(conv2.send("Can you explain polymorphism?"))

간단한 REPL 구현

def chat_repl(model="llama3.2"):
    """간단한 대화형 인터페이스"""
    conv = Conversation(model)

    print(f"Chatting with {model}. Type 'exit' to quit, 'clear' to reset.")
    print("=" * 60)

    while True:
        try:
            user_input = input("\n👤 You: ").strip()

            if not user_input:
                continue

            if user_input.lower() == "exit":
                print("Goodbye!")
                break

            if user_input.lower() == "clear":
                conv.clear()
                print("✓ Conversation cleared")
                continue

            print("\n🤖 Assistant: ", end="")
            conv.send(user_input, stream=True)

        except KeyboardInterrupt:
            print("\n\nInterrupted. Goodbye!")
            break
        except Exception as e:
            print(f"\n❌ Error: {e}")

if __name__ == "__main__":
    chat_repl()

고급 CLI 기능

모델 언로드 제어 (keep_alive)

# 기본적으로 5분 후 자동 언로드
# 유지 시간 변경
curl http://localhost:11434/api/generate -d '{
  "model": "llama3.2",
  "prompt": "Hello",
  "keep_alive": "10m"
}'

# 영구 유지 (서버 재시작 전까지)
curl http://localhost:11434/api/generate -d '{
  "model": "llama3.2",
  "prompt": "Hello",
  "keep_alive": -1
}'

# 즉시 언로드
curl http://localhost:11434/api/generate -d '{
  "model": "llama3.2",
  "prompt": "Hello",
  "keep_alive": 0
}'

출력 형식 지정 (format)

# JSON 형식 강제
curl http://localhost:11434/api/generate -d '{
  "model": "llama3.2",
  "prompt": "List 3 programming languages with their year of creation",
  "format": "json"
}'

# 출력 예시:
{
  "languages": [
    {"name": "Python", "year": 1991},
    {"name": "JavaScript", "year": 1995},
    {"name": "Rust", "year": 2010}
  ]
}

Raw 모드

# 템플릿 처리 없이 직접 모델에 전달
curl http://localhost:11434/api/generate -d '{
  "model": "llama3.2",
  "prompt": "<|start_header_id|>user<|end_header_id|>\n\nHello<|eot_id|>",
  "raw": true
}'
💡 고급 기능 활용
  • keep_alive: 빈번한 요청 시 메모리에 유지하여 속도 향상
  • format: "json": 구조화된 데이터 추출 시 유용
  • raw: true: 특수한 프롬프트 형식 테스트 시 사용

모범 사례

에러 처리

import requests
from requests.exceptions import RequestException, Timeout

def safe_generate(prompt, model="llama3.2", max_retries=3):
    for attempt in range(max_retries):
        try:
            response = requests.post(
                "http://localhost:11434/api/generate",
                json={"model": model, "prompt": prompt, "stream": False},
                timeout=60  # 60초 타임아웃
            )
            response.raise_for_status()
            return response.json()["response"]

        except Timeout:
            print(f"Timeout on attempt {attempt + 1}")
            if attempt == max_retries - 1:
                raise

        except RequestException as e:
            print(f"Error: {e}")
            if attempt == max_retries - 1:
                raise

        except KeyError:
            print("Invalid response format")
            raise

성능 최적화

  • 배치 처리: 여러 요청을 병렬로 처리
  • 캐싱: 동일 프롬프트 결과 캐시
  • 연결 재사용: requests.Session() 사용
  • 적절한 타임아웃: 긴 응답 예상 시 타임아웃 증가
from concurrent.futures import ThreadPoolExecutor
import requests

# 연결 재사용
session = requests.Session()

def generate_parallel(prompts, model="llama3.2"):
    def _generate(prompt):
        response = session.post(
            "http://localhost:11434/api/generate",
            json={"model": model, "prompt": prompt, "stream": False}
        )
        return response.json()["response"]

    with ThreadPoolExecutor(max_workers=4) as executor:
        results = list(executor.map(_generate, prompts))

    return results

# 사용
prompts = [
    "Summarize quantum computing",
    "Explain blockchain",
    "What is AI?"
]
results = generate_parallel(prompts)

Thinking 모드 (--think)

Ollama는 추론(reasoning) 모델에서 Thinking 모드를 지원합니다. 모델의 내부 사고 과정과 최종 답변을 분리하여 확인할 수 있습니다.

CLI에서 Thinking 모드

# Thinking 모드 활성화
ollama run deepseek-r1:8b --think

# 대화 예시
>>> 9.11과 9.8 중 어느 것이 더 큰가?

<think>
사용자가 9.11과 9.8의 크기를 비교하고 있습니다.
소수점 이하를 비교하면:
9.11의 소수 부분은 0.11
9.8의 소수 부분은 0.80
0.80 > 0.11이므로 9.8이 더 큽니다.
</think>

9.8이 9.11보다 더 큽니다.

# Thinking 모드 비활성화
ollama run deepseek-r1:8b --think=false

API에서 Thinking 모드

# think 필드로 추론 과정 확인
curl http://localhost:11434/api/chat -d '{
  "model": "deepseek-r1:8b",
  "messages": [
    {"role": "user", "content": "How many Rs are in strawberry?"}
  ],
  "think": true,
  "stream": false
}'

# 응답에 thinking 필드가 포함됨
{
  "message": {
    "role": "assistant",
    "content": "There are 3 Rs in 'strawberry'.",
    "thinking": "Let me count the letters... s-t-r-a-w-b-e-r-r-y. R appears at positions 3, 8, 9."
  }
}
Thinking 모드 지원 모델
  • DeepSeek R1 - deepseek-r1:8b, deepseek-r1:14b, deepseek-r1:32b
  • Qwen 3 - qwen3:8b, qwen3:32b 등 (기본 Thinking 모드 활성화)
  • Gemma 4 - gemma4:27b

구조화된 출력 (Structured Output)

Ollama는 JSON Schema를 사용하여 모델의 출력 형식을 정확하게 제어할 수 있습니다. 기존의 "format": "json"보다 훨씬 세밀한 스키마 정의가 가능합니다.

기본 사용법

# JSON Schema로 출력 형식 제어
curl http://localhost:11434/api/generate -d '{
  "model": "qwen3:8b",
  "prompt": "List 3 programming languages",
  "format": {
    "type": "object",
    "properties": {
      "languages": {
        "type": "array",
        "items": {"type": "string"}
      }
    }
  }
}'

# 응답 예시
{
  "languages": ["Python", "JavaScript", "Rust"]
}

복잡한 스키마 예제

# 상세 정보가 포함된 구조화된 출력
curl http://localhost:11434/api/chat -d '{
  "model": "qwen3:8b",
  "messages": [
    {"role": "user", "content": "서울의 날씨와 관광 정보를 알려줘"}
  ],
  "format": {
    "type": "object",
    "properties": {
      "city": {"type": "string"},
      "weather": {
        "type": "object",
        "properties": {
          "temperature": {"type": "number"},
          "condition": {"type": "string"}
        }
      },
      "attractions": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "name": {"type": "string"},
            "category": {"type": "string"}
          }
        }
      }
    }
  },
  "stream": false
}'

Python에서 구조화된 출력

import requests
import json

def structured_generate(prompt, schema, model="qwen3:8b"):
    response = requests.post(
        "http://localhost:11434/api/generate",
        json={
            "model": model,
            "prompt": prompt,
            "format": schema,
            "stream": False
        }
    )
    return json.loads(response.json()["response"])

# 스키마 정의
book_schema = {
    "type": "object",
    "properties": {
        "title": {"type": "string"},
        "author": {"type": "string"},
        "year": {"type": "number"},
        "genres": {
            "type": "array",
            "items": {"type": "string"}
        }
    }
}

result = structured_generate(
    "Tell me about the book '1984'",
    book_schema
)
print(result["title"])    # "1984"
print(result["author"])   # "George Orwell"
print(result["genres"])   # ["Dystopian", "Science Fiction"]
구조화된 출력 팁
  • JSON Schema를 format 파라미터에 직접 전달하면 모델이 해당 스키마를 준수하는 JSON을 생성합니다
  • 기존 "format": "json"도 여전히 지원되지만, 스키마를 사용하면 출력 구조를 보장할 수 있습니다
  • 복잡한 스키마일수록 더 큰 모델(8B+)을 사용하는 것이 정확도가 높습니다

도구 호출 (Tool Calling)

Ollama는 모델이 외부 함수(도구)를 호출할 수 있는 기능을 지원합니다. 모델이 직접 계산하거나 외부 데이터를 가져오는 대신, 정의된 도구를 호출하도록 요청할 수 있습니다.

API에서 도구 호출

# 도구 정의와 함께 채팅 요청
curl http://localhost:11434/api/chat -d '{
  "model": "qwen3:8b",
  "messages": [
    {"role": "user", "content": "서울의 현재 날씨는?"}
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "지정된 도시의 현재 날씨를 가져옵니다",
        "parameters": {
          "type": "object",
          "properties": {
            "city": {
              "type": "string",
              "description": "도시 이름"
            },
            "unit": {
              "type": "string",
              "enum": ["celsius", "fahrenheit"],
              "description": "온도 단위"
            }
          },
          "required": ["city"]
        }
      }
    }
  ],
  "stream": false
}'

# 모델이 도구 호출을 요청하는 응답
{
  "message": {
    "role": "assistant",
    "content": "",
    "tool_calls": [
      {
        "function": {
          "name": "get_weather",
          "arguments": {
            "city": "Seoul",
            "unit": "celsius"
          }
        }
      }
    ]
  }
}

Python에서 도구 호출 처리

import requests
import json

def get_weather(city, unit="celsius"):
    # 실제로는 날씨 API를 호출
    return {"city": city, "temp": 22, "condition": "맑음"}

# 도구 매핑
tools_map = {"get_weather": get_weather}

def chat_with_tools(messages, tools):
    response = requests.post(
        "http://localhost:11434/api/chat",
        json={
            "model": "qwen3:8b",
            "messages": messages,
            "tools": tools,
            "stream": False
        }
    ).json()

    msg = response["message"]

    # 도구 호출이 있는 경우 처리
    if msg.get("tool_calls"):
        messages.append(msg)
        for tool_call in msg["tool_calls"]:
            fn_name = tool_call["function"]["name"]
            fn_args = tool_call["function"]["arguments"]
            result = tools_map[fn_name](**fn_args)
            messages.append({
                "role": "tool",
                "content": json.dumps(result)
            })
        # 도구 결과를 포함하여 다시 요청
        return chat_with_tools(messages, tools)

    return msg["content"]
도구 호출 지원 모델
  • Llama 4 - llama4 시리즈
  • Qwen 3 - qwen3:8b, qwen3:32b
  • Gemma 4 - gemma4:27b
  • Mistral - mistral, mistral-nemo

ollama launch 명령

Ollama v0.15+ (2026년 1월)에 추가된 launch 명령은 코딩 도구를 로컬 또는 클라우드 모델과 바로 연결하는 기능입니다. 환경 변수를 직접 설정할 필요 없이, 한 줄 명령으로 즉시 사용할 수 있습니다.

기본 사용법

# Claude Code를 Ollama 모델과 연결
ollama launch claude-code

# OpenCode를 Ollama 모델과 연결
ollama launch opencode

# Codex를 Ollama 모델과 연결
ollama launch codex

# 특정 모델 지정
ollama launch claude-code --model qwen3:32b

동작 원리

ollama launch는 내부적으로 다음을 자동 처리합니다:

  • 해당 코딩 도구에 필요한 환경 변수 (OLLAMA_HOST, API 키 등)를 자동 설정
  • OpenAI 호환 API 엔드포인트를 코딩 도구에 연결
  • 로컬 모델 또는 클라우드 모델 자동 선택
# 이전 방식 (환경 변수 수동 설정)
export OPENAI_API_BASE=http://localhost:11434/v1
export OPENAI_API_KEY=ollama
claude-code

# 새로운 방식 (한 줄로 해결)
ollama launch claude-code
ollama launch 팁
  • 지원 도구는 지속적으로 추가되고 있습니다 (ollama launch --list로 확인)
  • 클라우드 모델과 함께 사용하려면 먼저 ollama signin이 필요합니다
  • 코딩 도구가 시스템에 설치되어 있어야 합니다

클라우드 모델

Ollama는 로컬 모델 실행 외에도 클라우드 모델을 동일한 인터페이스로 사용할 수 있습니다. 로컬 하드웨어 제약 없이 대형 모델을 활용할 수 있습니다.

계정 연결

# Ollama 계정으로 로그인
ollama signin

# 브라우저가 열리며 인증 진행
# 인증 완료 후 클라우드 모델 사용 가능

클라우드 모델 사용

# 클라우드 모델은 -cloud 접미사로 구분
ollama run qwen3-coder:480b-cloud

# 로컬과 동일한 명령어 사용
ollama run qwen3-coder:480b-cloud "파이썬으로 웹 크롤러를 만들어줘"

# 다른 클라우드 모델 예시
ollama pull deepseek-r1:671b-cloud
ollama run deepseek-r1:671b-cloud --think

API에서 클라우드 모델

# 로컬 모델과 동일한 API 사용
curl http://localhost:11434/api/generate -d '{
  "model": "qwen3-coder:480b-cloud",
  "prompt": "Implement a binary search tree in Python",
  "stream": false
}'
클라우드 모델 주의사항
  • 클라우드 모델 사용 시 데이터가 외부 서버로 전송됩니다
  • 인터넷 연결이 필요하며, 네트워크 지연이 발생할 수 있습니다
  • 사용량에 따라 요금이 발생할 수 있습니다 (계정 플랜 확인)
  • 민감한 데이터는 로컬 모델 사용을 권장합니다

핵심 정리

  • Ollama 사용법의 핵심 개념과 흐름을 정리합니다.
  • CLI 기본 명령어를 단계별로 이해합니다.
  • 실전 적용 시 기준과 주의점을 확인합니다.

실무 팁

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