Skip to main content

REST API와 OpenAI 호환 API 시작하기

REST API와 OpenAI 호환 API 시작하기

터미널 다음 단계는 왜 API인가

앞선 장들에서 우리는 ollama run으로 모델과 대화하고, pull로 모델을 내려받고, CLI에서 꽤 많은 일을 해냈습니다. 그런데 실제 프로그램은 터미널에 손으로 질문을 치지 않습니다. 웹 앱, 백엔드, 자동화 스크립트, 사내 도구는 모두 HTTP 요청으로 모델을 부릅니다.

여기서 API를 이해하면 Ollama가 갑자기 "재밌는 로컬 장난감"에서 "앱에 붙일 수 있는 엔진"으로 바뀝니다. 터미널이 운전석이라면, API는 엔진룸입니다.

먼저 그림부터: Ollama는 로컬 HTTP 서버다

Ollama가 실행 중이면 기본적으로 로컬 HTTP API를 제공합니다. 네이티브 API의 기본 주소는 http://localhost:11434/api이고, OpenAI 호환 엔드포인트는 같은 서버 아래의 /v1/* 경로에 열립니다.15

처음에는 규칙이 많아 보여도 핵심은 단순합니다.

  • 네이티브 API 기본 주소는 http://localhost:11434/api

  • 요청 본문은 보통 JSON

  • 응답도 JSON

  • 스트리밍 엔드포인트는 응답이 여러 조각으로 나뉘어 옴

  • 모델 이름은 로컬에 실제로 존재하는 이름을 써야 함

즉, 앱이 Ollama에게 "이 모델로, 이 입력을 처리해줘"라고 JSON으로 요청하는 구조라고 생각하면 됩니다.

첫 확인: /api/tags로 로컬 모델 목록 보기

API 입문에서 가장 먼저 해볼 요청은 GET /api/tags입니다. 이 엔드포인트는 현재 로컬에 설치된 모델 목록을 JSON으로 보여 줍니다.2

curl http://localhost:11434/api/tags

응답에는 모델 이름, 수정 시각, 크기, 세부 정보 같은 값이 들어 있습니다. 여기서 가장 중요한 건 name입니다. 이후 /api/generate나 /api/chat에서 model 필드에 넣을 값이 바로 이것입니다.

CLI의 ollama ls를 API 버전으로 바꿔 놓은 느낌이라고 생각하면 이해가 쉽습니다.

한 번 묻고 한 번 답받기: /api/generate

POST /api/generate는 가장 단순한 생성 엔드포인트입니다. 하나의 프롬프트를 보내고, 그에 대한 생성 결과를 받는 방식입니다.3

요약, 변환, 짧은 글 생성, 코드 한 조각 만들기처럼 "일회성 요청"에 잘 맞습니다.

curl http://localhost:11434/api/generate -d '{
  "model": "gemma3",
  "prompt": "REST API를 한 문장으로 설명해줘.",
  "stream": false
}'

"stream": false를 넣었기 때문에 응답은 한 번에 도착합니다.

{
  "model": "gemma3",
  "created_at": "2026-04-21T10:00:00Z",
  "response": "REST API는 HTTP를 통해 기능과 데이터를 주고받도록 만든 인터페이스입니다.",
  "done": true,
  "done_reason": "stop",
  "total_duration": 1234567890,
  "load_duration": 12345678,
  "prompt_eval_count": 18,
  "prompt_eval_duration": 123456,
  "eval_count": 24,
  "eval_duration": 456789000
}

여기서 처음엔 아래 필드만 읽어도 충분합니다.

  • response: 우리가 읽고 싶은 본문

  • done: 생성이 끝났는지 여부

  • total_duration / load_duration: 전체 시간과 로딩 시간

  • prompt_eval_count / eval_count: 입력 토큰 수와 출력 토큰 수

이 메타데이터는 꽤 유용합니다. 예를 들어 load_duration이 크면 모델이 메모리에 안 올라와 있었다는 뜻이고, eval_count와 eval_duration을 보면 생성 속도 감각도 잡을 수 있습니다.6

스트리밍이 기본이라는 점을 꼭 기억하자

/api/generate는 기본적으로 스트리밍 응답을 사용합니다. 즉, 응답이 한 덩어리로 오지 않고 여러 JSON 조각으로 나뉘어 도착합니다.34

curl http://localhost:11434/api/generate -d '{
  "model": "gemma3",
  "prompt": "하늘이 왜 파란지 짧게 설명해줘."
}'

이 방식은 화면에 타이핑되듯 보여주고 싶을 때 아주 좋습니다. 반대로 백엔드에서 한 번에 받아 파싱하고 싶다면 stream: false를 주는 편이 훨씬 편합니다.4

JSON 모드도 바로 써볼 수 있다

Ollama의 네이티브 API는 format: "json"을 주면 JSON 형식 응답을 강제할 수 있습니다. 더 나아가 JSON Schema 객체를 넣어 응답 구조를 더 엄격하게 제한할 수도 있습니다.3

curl http://localhost:11434/api/generate -d '{
  "model": "gemma3",
  "prompt": "봄, 여름, 가을, 겨울의 특징을 JSON으로 답해줘.",
  "format": "json",
  "stream": false
}'

다만 여기서 중요한 점이 하나 있습니다. format: "json"만 믿기보다, 프롬프트 안에서도 JSON으로 답하라고 분명하게 요구하는 편이 더 안정적입니다. 구조화 출력은 다음 장들에서 다시 더 자세히 다루겠습니다.

대화를 이어 가기: /api/chat

POST /api/chat은 채팅용 엔드포인트입니다. generate와 달리 prompt 문자열 하나를 보내는 대신, messages 배열을 보냅니다. 각 메시지는 role과 content를 가집니다.7

curl http://localhost:11434/api/chat -d '{
  "model": "gemma3",
  "messages": [
    {
      "role": "system",
      "content": "너는 초보 개발자에게 쉽게 설명하는 튜터야."
    },
    {
      "role": "user",
      "content": "REST API가 뭐야?"
    }
  ],
  "stream": false
}'

응답 구조는 generate와 조금 다릅니다.

{
  "model": "gemma3",
  "created_at": "2026-04-21T10:05:00Z",
  "message": {
    "role": "assistant",
    "content": "REST API는 프로그램끼리 약속된 방식으로 대화하게 해주는 창구입니다."
  },
  "done": true,
  "done_reason": "stop",
  "total_duration": 987654321,
  "eval_count": 21
}

여기서는 본문이 response가 아니라 message.content 안에 들어 있습니다.7 generate를 쓰다가 chat으로 넘어오면 많은 사람이 여기서 잠깐 멈춥니다. 자연스러운 일입니다.

채팅 기억은 서버가 아니라 요청에 담아 보낸다

/api/chat의 핵심은 대화 이력을 messages에 넣어 함께 보낸다는 점입니다. 서버가 마법처럼 모든 대화를 영구히 기억해 주는 방식이 아닙니다. 애플리케이션이 이전 대화를 관리하고, 다음 요청 때 함께 보내는 구조입니다.

예를 들어 두 번째 턴에서는 이렇게 됩니다.

{
  "model": "gemma3",
  "messages": [
    {"role": "system", "content": "너는 초보 개발자에게 쉽게 설명하는 튜터야."},
    {"role": "user", "content": "REST API가 뭐야?"},
    {"role": "assistant", "content": "REST API는 프로그램끼리 약속된 방식으로 대화하게 해주는 창구입니다."},
    {"role": "user", "content": "그럼 웹훅과는 뭐가 달라?"}
  ],
  "stream": false
}

즉, "기억해 둬"라고 막연히 기대하기보다, 기억할 내용을 내가 같이 보내 준다고 생각하면 됩니다.

/api/generate와 /api/chat, 무엇을 고를까

둘 다 텍스트를 생성하지만, 쓰임새는 다릅니다.

  • /api/generate는 단발성 작업에 잘 맞습니다.

  • /api/chat은 역할 구분과 대화 맥락이 필요할 때 자연스럽습니다.

  • 채팅 UI, 비서, 상담봇, 튜터형 인터페이스는 보통 /api/chat이 더 편합니다.

아주 단순하게 외우면 됩니다. 문장 하나 던져서 답만 받기는 generate, 대화처럼 이어 가기는 chat입니다.

OpenAI 호환 API: /v1로 기존 도구에 바로 붙이기

여기서부터가 많은 개발자에게 진짜 매력 포인트입니다. Ollama는 네이티브 API인 /api/* 외에도, OpenAI 스타일 인터페이스인 /v1/** 제공합니다.5

대표적으로 다음 엔드포인트를 쓸 수 있습니다.

  • /v1/chat/completions

  • /v1/completions

  • /v1/models

  • /v1/embeddings

  • 최근 버전에서는 /v1/responses도 지원5

이게 왜 좋을까요? 이미 세상에는 OpenAI 형식에 맞춰진 라이브러리와 도구가 아주 많기 때문입니다. 어떤 도구가 "OpenAI 호환 base URL만 넣어 주세요"라고 말한다면, 거기에 Ollama를 연결할 수 있는 경우가 많습니다.

대응 관계를 보면 감이 더 빨리 옵니다.

용도네이티브 APIOpenAI 호환 API
채팅/api/chat/v1/chat/completions
단발 생성/api/generate/v1/completions
모델 목록/api/tags/v1/models
임베딩/api/embed/v1/embeddings

즉, Ollama는 자기 방식의 API도 갖고 있고, 동시에 OpenAI처럼 보이는 문도 하나 더 달아 둔 셈입니다.

/v1/chat/completions를 curl로 호출하기

curl http://localhost:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gemma3",
    "messages": [
      {"role": "user", "content": "로컬 LLM의 장점을 세 가지 알려줘."}
    ]
  }'

응답은 OpenAI 스타일의 choices[0].message.content 구조를 따릅니다. 그래서 OpenAI용 예제 코드 상당수가 base_url만 바꾸면 그대로 동작하는 경우가 많습니다.5

OpenAI Python SDK로 바로 붙이기

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama",
)

response = client.chat.completions.create(
    model="gemma3",
    messages=[
        {"role": "user", "content": "Ollama를 한 문단으로 소개해줘."}
    ]
)

print(response.choices[0].message.content)

여기서 api_key는 SDK 형식상 넣지만, 로컬 Ollama에서는 실제 인증 검증에 쓰이지 않습니다.5

모델 이름은 내 컴퓨터에 있는 이름이어야 한다

OpenAI 호환 API를 쓴다고 해서 모델 이름까지 OpenAI 이름이 되는 것은 아닙니다. model에는 내 로컬에 실제로 존재하는 Ollama 모델 이름을 넣어야 합니다.5

그래서 이런 요청은 됩니다.

{"model": "gemma3"}

하지만 로컬에 없는 이름을 넣으면 당연히 실패합니다.

{"model": "gpt-4o"}

다만 어떤 도구가 기본 모델명을 gpt-3.5-turbo처럼 고정해 두는 경우가 있습니다. 이런 때는 ollama cp로 별칭 이름을 만들어 우회할 수 있습니다.5

ollama cp gemma3 gpt-3.5-turbo

네이티브 API와 /v1, 둘 중 무엇을 써야 할까

정답은 상황에 따라 다르지만, 시작할 때는 아래 기준이면 충분합니다.

  • Ollama 고유 기능과 메타데이터가 중요하다면 /api/*

  • 기존 OpenAI SDK, 프레임워크, 외부 도구를 재사용하고 싶다면 /v1/*

  • 새 프로젝트를 만들며 구조를 이해하고 싶다면 네이티브 API부터 배우는 편이 개념이 잘 잡힘

한마디로 정리하면 이렇습니다.

직접 제어는 /api/*, 호환성은 /v1/*.*

처음 붙일 때 자주 만나는 오류

처음 API를 다룰 때는 대개 같은 곳에서 한 번씩 걸립니다.

  • 연결 실패: connection refused가 나오면 Ollama가 실행 중인지 먼저 확인합니다.

  • 모델 없음: model not found가 나오면 /api/tags나 ollama ls로 정확한 모델명을 다시 봅니다.2

  • 응답이 조각조각 옴: 기본이 스트리밍이라 그렇습니다. 한 번에 받고 싶으면 stream: false를 넣습니다.4

  • JSON 파싱 실패: JSON 모드나 스키마를 쓰더라도 프롬프트를 너무 모호하게 쓰면 결과가 흔들릴 수 있습니다.3

이 네 가지만 기억해도 초반 시행착오의 절반은 줄어듭니다.

마무리

이번 장에서 꼭 가져가야 할 감각은 세 가지입니다.

첫째, Ollama는 로컬에서 돌아가는 HTTP 서버입니다. 둘째, 단발 생성은 /api/generate, 대화는 /api/chat이 맡습니다. 셋째, 기존 OpenAI 생태계와 연결할 때는 /v1/*가 아주 강력한 지름길이 됩니다.

이제부터 Ollama는 더 이상 터미널 속 도우미가 아닙니다. 웹 앱, 백엔드, 자동화 스크립트 어디에든 호출할 수 있는 로컬 AI 엔진입니다. 다음 장에서는 이 API를 실제 코드, 즉 Python과 JavaScript 안으로 데려가 보겠습니다.

참고

1Introduction - Ollama API

2List models - Ollama

3Generate a response - Ollama

4Streaming - Ollama

5OpenAI compatibility - Ollama

6Usage - Ollama API

7Generate a chat message - Ollama

책 목록

Ollama 사용법: 내 PC에서 시작하는 로컬 LLM 실전 가이드

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기