테마 전환

Ollama API 호출 방법: curl부터 OpenAI SDK 호환 인터페이스까지

Easton editorial illustration: modular AI application workbench

터미널에서 실행한 curl 명령의 JSON 응답에 단어가 절반만 들어 있었습니다. 두 시간이나 씨름한 뒤에야 스트리밍 응답 때문이라는 사실을 알았습니다. Ollama는 기본적으로 물이 흐르듯 내용을 조금씩 내보내며, 각 JSON 객체에는 몇 글자만 담깁니다.

로컬 LLM 배포에서 Ollama는 진입 장벽을 크게 낮췄습니다. 다운로드, 설치, 실행이면 세 단계로 끝납니다. 하지만 API 호출에서는 헷갈리기 쉽습니다. 네이티브 REST API와 OpenAI SDK 호환 인터페이스는 무엇이 다를까요? 스트리밍 응답은 어떻게 처리해야 할까요?

이 글에서는 직접 겪은 문제를 바탕으로 curl 명령부터 OpenAI SDK의 코드 변경 없는 마이그레이션, 문서에서 명확히 설명하지 않은 세부 사항까지 정리합니다.


Ollama API의 두 가지 인터페이스

이 부분은 한동안 정말 헷갈렸습니다. Ollama는 실제로 완전히 다른 두 종류의 API 인터페이스를 제공합니다.

네이티브 REST API: http://localhost:11434/api/*

  • 엔드포인트: /api/generate(텍스트 생성), /api/chat(대화), /api/tags(모델 목록)
  • 기본적으로 스트리밍 응답 사용(새벽 3시에 겪었던 문제의 원인입니다)
  • 별도 SDK 없이 직접 HTTP로 호출

OpenAI 호환 인터페이스: http://localhost:11434/v1/*

  • 엔드포인트: /v1/chat/completions, /v1/completions, /v1/models
  • OpenAI SDK와 완전히 호환(Python과 JavaScript에서 바로 사용 가능)
  • 기존 OpenAI 도구 생태계 지원

왜 두 종류가 필요한지 궁금할 수 있습니다. 각각 쓰임새가 다릅니다. 네이티브 API는 더 가볍고 직접적이어서 직접 HTTP 클라이언트를 작성할 때 적합합니다. OpenAI 호환 인터페이스는 기존 OpenAI SDK 코드를 그대로 사용할 수 있게 해 줍니다. base_url만 바꾸면 됩니다.

솔직히 꽤 영리한 설계입니다. 간단한 호출을 원하는 개발자와 이미 OpenAI 생태계용 코드를 보유한 팀을 모두 고려했습니다.


네이티브 REST API: curl로 시작하기

먼저 네이티브 API를 살펴보겠습니다. 표준 REST 인터페이스라서 사용법은 비교적 단순합니다.

기본 curl 호출

가장 간단한 텍스트 생성 예제입니다.

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

여기서 stream: false에 주의해야 합니다. Ollama는 기본적으로 콘텐츠를 스트리밍 방식으로 반환합니다. 완전한 JSON 응답을 원한다면 스트리밍을 명시적으로 비활성화해야 합니다. 그렇지 않으면 다음과 같은 출력이 이어집니다.

{"model":"llama3.2","response":"That","done":false}
{"model":"llama3.2","response":"'","done":false}
{"model":"llama3.2","response":"s","done":false}
{"model":"llama3.2","response":" a","done":false}
...
{"model":"llama3.2","response":"!","done":true}

각 JSON 객체에는 몇 글자만 들어 있고 token 단위로 출력됩니다. 이것이 NDJSON(Newline-Delimited JSON) 형식입니다. 한 줄마다 JSON 객체 하나가 들어갑니다. 새벽 3시에 문제를 겪었을 때는 이 점을 놓치고 일반 JSON처럼 파싱해서 첫 번째 객체의 “That”만 받았습니다.

대화 모드가 더 실용적입니다

일회성 생성은 간단한 작업에 적합하지만, 실제로는 대화 모드를 더 자주 사용합니다.

curl http://localhost:11434/api/chat -d '{
  "model": "llama3.2",
  "messages": [
    { "role": "user", "content": "Hello!" }
  ],
  "stream": false
}'

messages 배열을 유지하면서 이전 대화를 함께 전달하면 모델이 문맥을 기억할 수 있습니다. 채팅 애플리케이션을 구축할 때 특히 중요합니다.

설치된 모델 확인

로컬에 어떤 모델이 있는지 확인하고 싶을 때는 다음 명령을 사용합니다.

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

반환된 JSON에는 다운로드한 모든 모델과 각 모델의 크기, 수정 시각, 양자화 수준 등이 표시됩니다. 꽤 편리합니다.


스트리밍 응답 처리

이 부분은 따로 짚고 넘어갈 필요가 있습니다. Ollama의 스트리밍 응답은 전체 콘텐츠를 한 번에 반환하지 않고 token 단위로 출력합니다.

Python에서 스트리밍 처리

Python의 requests 라이브러리로 스트리밍 응답을 처리하는 예제입니다.

import requests
import json

url = "http://localhost:11434/api/chat"
payload = {
    "model": "llama3.2",
    "messages": [{"role": "user", "content": "Write a short poem"}],
    "stream": True
}

response = requests.post(url, json=payload, stream=True)
for line in response.iter_lines():
    if line:
        chunk = json.loads(line)
        print(chunk.get("message", {}).get("content", ""), end="", flush=True)

핵심은 response.iter_lines()입니다. 이 메서드를 사용하면 NDJSON 스트림을 한 줄씩 읽을 수 있습니다. 각 chunk에는 몇 글자만 들어 있을 수 있으므로 내용을 누적해야 완전한 응답을 얻을 수 있습니다.

JavaScript에서 스트리밍 처리

프론트엔드에서 fetch API를 사용할 때도 방식은 비슷합니다.

const response = await fetch('http://localhost:11434/api/chat', {
  method: 'POST',
  body: JSON.stringify({
    model: 'llama3.2',
    messages: [{ role: 'user', content: 'Hello!' }],
    stream: true
  })
});

const reader = response.body.getReader();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  const chunk = new TextDecoder().decode(value);
  // 각 chunk 처리...
}

스트리밍 처리는 비스트리밍 방식보다 조금 번거롭지만 사용자 경험은 훨씬 좋습니다. 한참 기다린 뒤 긴 텍스트가 갑자기 나타나는 대신, 모델이 “생각”하고 출력하는 과정을 실시간으로 볼 수 있습니다.


OpenAI SDK 호환 인터페이스: 코드 변경 없이 마이그레이션

가장 마음에 드는 부분입니다. Ollama는 완전한 OpenAI API 호환 인터페이스를 제공하므로 기존 코드를 거의 그대로 마이그레이션할 수 있습니다.

Python OpenAI SDK 예제

OpenAI 공식 SDK를 그대로 사용합니다.

from openai import OpenAI

client = OpenAI(
    base_url='http://localhost:11434/v1/',
    api_key='ollama'  # 로컬에서는 검증하지 않으므로 아무 값이나 입력 가능
)

response = client.chat.completions.create(
    model="llama3.2",
    messages=[{"role": "user", "content": "Hello!"}]
)

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

보이는 것처럼 달라지는 부분은 base_url 설정과 임의의 api_key 입력뿐입니다. 나머지 코드는 완전히 동일합니다.

개발 환경과 프로덕션 환경 전환

이 기능은 특히 유용합니다. 개발 환경에서는 로컬 Ollama를 사용하고 프로덕션 환경에서는 OpenAI를 사용할 수 있습니다.

# 개발 환경 .env
OPENAI_API_KEY=anyrandomtext
LLM_ENDPOINT="http://localhost:11434/v1"
MODEL=llama3.2

# 프로덕션 환경 .env
OPENAI_API_KEY=sk-XXXXXXXXXXXXXXXXXXXXXXXX
LLM_ENDPOINT="https://api.openai.com/v1"
MODEL=gpt-3.5-turbo

코드에서는 환경 변수에서 값을 읽기만 하면 됩니다.

import os
from openai import OpenAI

client = OpenAI(
    base_url=os.getenv('LLM_ENDPOINT'),
    api_key=os.getenv('OPENAI_API_KEY')
)

이렇게 하면 개발 중에는 OpenAI API 호출 비용 없이 로컬에서 충분히 테스트할 수 있습니다. 배포할 때는 환경 변수만 바꾸면 실제 OpenAI로 전환됩니다.

지원하는 엔드포인트

Ollama의 OpenAI 호환 인터페이스는 다음 엔드포인트를 지원합니다.

엔드포인트기능지원 수준
/v1/chat/completions대화 생성완전 지원
/v1/completions텍스트 완성완전 지원
/v1/models모델 목록완전 지원
/v1/embeddings텍스트 임베딩완전 지원
/v1/responses새로운 응답 API완전 지원

실험적인 /v1/images/generations 엔드포인트도 있지만 아직 안정성이 충분하지 않습니다.

모델 별칭

작은 팁이 하나 있습니다. 모델에 별칭을 지정할 수 있습니다. 예를 들어 코드상으로 GPT-3.5를 호출하는 것처럼 보이게 하려면 다음 명령을 사용합니다.

ollama cp llama3.2 gpt-3.5-turbo

이제 코드에 model="gpt-3.5-turbo"라고 작성해도 실제로는 로컬 llama3.2를 사용합니다. 코드를 마이그레이션할 때 유용합니다.


두 방식 중 무엇을 선택해야 할까요?

여기까지 읽으면 어떤 방식을 사용해야 할지 고민될 수 있습니다.

네이티브 REST API가 적합한 경우

다음과 같은 상황에 적합합니다.

  • 가장 가벼운 호출 방식을 원하는 경우
  • OpenAI SDK 생태계가 필요하지 않은 경우
  • 직접 HTTP 클라이언트를 작성하는 경우(임베디드 장치나 특수 환경 등)
  • 스트리밍 응답의 세부 사항을 정밀하게 제어해야 하는 경우

네이티브 API는 더 직접적이고 저수준에 가깝습니다. HTTP 프로토콜에 익숙하다면 편하게 사용할 수 있습니다.

OpenAI SDK 호환 인터페이스가 적합한 경우

다음과 같은 상황에 적합합니다.

  • 이미 OpenAI SDK 기반 코드가 있는 경우
  • 로컬 배포로 빠르게 마이그레이션해야 하는 경우
  • OpenAI 도구 체인(LangChain, LlamaIndex 등)을 사용하는 경우
  • 개발 환경과 프로덕션 환경 사이를 전환해야 하는 경우

간단히 말해 기존 도구를 그대로 활용하면서 코드를 바꾸고 싶지 않다면 OpenAI 호환 인터페이스를 선택하면 됩니다.

제안

솔직히 개발할 때는 OpenAI SDK 호환 인터페이스를 더 선호합니다. 코드 변경이 적고 도구 체인을 사용할 수 있으며 디버깅도 편합니다. 하지만 아주 단순한 CLI 도구를 만들거나 OpenAI SDK를 지원하지 않는 환경에서 호출해야 할 때는 네이티브 API가 더 적합합니다.


실전 코드 조각

마지막으로 자주 사용하는 코드 조각 몇 가지를 공유합니다.

Python 스트리밍 채팅(OpenAI SDK)

from openai import OpenAI

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

stream = client.chat.completions.create(
    model="llama3.2",
    messages=[{"role": "user", "content": "Write a poem"}],
    stream=True
)

for chunk in stream:
    if chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)

JavaScript 전체 대화(네이티브 API)

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

// 대화 기록 유지
let conversation = [
  { role: 'user', content: 'Hello!' }
];

const result = await chat(conversation);
conversation.push({
  role: 'assistant',
  content: result.message.content
});

console.log(result.message.content);

도구 호출 예제

Ollama는 Function Calling(도구 호출)도 지원합니다.

from openai import OpenAI

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

tools = [
  {
    "type": "function",
    "function": {
      "name": "get_weather",
      "description": "Get current weather",
      "parameters": {
        "type": "object",
        "properties": {
          "location": {"type": "string"}
        },
        "required": ["location"]
      }
    }
  }
]

response = client.chat.completions.create(
  model="llama3.2",
  messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
  tools=tools
)

if response.choices[0].message.tool_calls:
  print("Model wants to call:", response.choices[0].message.tool_calls[0].function.name)

마무리

Ollama의 API 설계는 꽤 균형이 잘 잡혀 있습니다. 네이티브 REST API의 직접성과 가벼움, OpenAI SDK의 편리함과 생태계 호환성을 모두 제공합니다. 두 방식은 각각 맞는 상황이 있으므로 요구 사항에 따라 선택하면 됩니다.

Ollama를 처음 사용한다면 OpenAI SDK 호환 인터페이스부터 시도해 보기를 권합니다. 시작하기 쉽고 코드 변경도 적습니다. 익숙해진 뒤 구체적인 요구에 따라 네이티브 API 사용 여부를 결정하면 됩니다.

그리고 스트리밍 응답 처리는 정말 놓치기 쉬운 부분입니다. 기본값이 스트리밍이라는 점을 기억하세요. 스트리밍이 필요하지 않다면 stream: false를 명시적으로 설정해야 합니다. 그렇지 않으면 저처럼 새벽 3시에 단어 절반을 바라보며 고민하게 될 수 있습니다.


참고 자료

Ollama API를 호출하는 두 가지 방법

curl 기반 네이티브 API부터 OpenAI SDK 호환 인터페이스까지 전체 호출 과정

⏱️ Estimated time: 10 min

  1. 1

    Step 1: Ollama 설치 및 실행 상태 확인

    먼저 Ollama가 정상적으로 실행 중인지 확인합니다.

    • 터미널에서 ollama list 실행(다운로드한 모델 확인)
    • 또는 http://localhost:11434 접속(Ollama is running이 표시되어야 함)
    • 기본 포트: 11434
  2. 2

    Step 2: 호출 방식 선택

    상황에 맞게 선택합니다.

    • 네이티브 REST API: 가벼운 호출이나 사용자 지정 클라이언트에 적합
    • OpenAI SDK 호환: 기존 OpenAI 코드나 빠른 마이그레이션에 적합
  3. 3

    Step 3: 네이티브 REST API 사용(curl 방식)

    가장 기본적인 curl 호출입니다.

    • 텍스트 생성: curl http://localhost:11434/api/generate -d '{"model": "llama3.2", "prompt": "...", "stream": false}'
    • 대화 모드: curl http://localhost:11434/api/chat -d '{"model": "llama3.2", "messages": [...], "stream": false}'
    • 주의: 기본값은 스트리밍 응답이므로 비활성화하려면 stream: false를 설정해야 합니다.
  4. 4

    Step 4: OpenAI SDK 호환 인터페이스 사용

    Python OpenAI SDK 호출 방법입니다.

    • base_url='http://localhost:11434/v1/' 설정
    • api_key는 아무 값이나 입력 가능(로컬에서 검증하지 않음)
    • 나머지 코드는 OpenAI와 완전히 동일
    • 환경 전환: base_url만 변경(개발 환경은 로컬, 프로덕션 환경은 OpenAI)
  5. 5

    Step 5: 스트리밍 응답 처리

    스트리밍 응답 처리의 핵심입니다.

    • Python: response.iter_lines()로 NDJSON을 한 줄씩 읽기
    • JavaScript: response.body.getReader()로 스트림 읽기
    • 각 chunk에는 몇 글자만 들어 있으므로 전체 응답을 누적해야 함
    • 비스트리밍: stream: false를 설정해 완전한 JSON 받기

FAQ

Ollama의 네이티브 API와 OpenAI SDK 호환 인터페이스는 무엇이 다른가요?
네이티브 API는 더 가볍고 직접적이며, 기본적으로 NDJSON 형식의 스트리밍 응답을 사용해 사용자 지정 HTTP 클라이언트에 적합합니다. OpenAI SDK 호환 인터페이스는 OpenAI SDK와 완전히 호환되며 base_url만 바꾸면 되므로 기존 OpenAI 코드를 빠르게 마이그레이션할 때 적합합니다.
Ollama API를 호출했는데 왜 단어의 절반만 반환되나요?
기본 스트리밍 응답 동작 때문입니다. Ollama는 기본적으로 NDJSON 형식으로 token을 하나씩 출력하며, 각 JSON 객체에는 몇 글자만 들어 있습니다. 해결 방법은 다음과 같습니다.

• stream: false를 설정해 스트리밍을 끄고 완전한 JSON 받기
• 또는 NDJSON 스트림을 올바르게 처리해 한 줄씩 읽고 내용을 누적하기
개발 환경에서는 로컬 Ollama를, 프로덕션 환경에서는 OpenAI를 사용하려면 어떻게 하나요?
환경 변수로 전환합니다. 개발 환경에서는 LLM_ENDPOINT="http://localhost:11434/v1"로, 프로덕션 환경에서는 "https://api.openai.com/v1"로 설정합니다. 코드에서는 환경 변수에서 base_url만 읽으면 되고 나머지 코드는 그대로 사용할 수 있습니다.
Ollama는 어떤 OpenAI 엔드포인트를 지원하나요?
/v1/chat/completions, /v1/completions, /v1/models, /v1/embeddings, /v1/responses를 완전히 지원합니다. /v1/images/generations는 실험적으로 지원하지만 아직 안정성이 충분하지 않습니다.
Ollama 모델에 별칭을 지정할 수 있나요?
가능합니다. ollama cp llama3.2 gpt-3.5-turbo 명령을 사용하면 코드에서 model="gpt-3.5-turbo"라고 작성해도 실제로는 로컬 llama3.2를 사용합니다. 코드를 마이그레이션할 때 유용한 방법입니다.
Ollama는 도구 호출(Function Calling)을 지원하나요?
지원합니다. OpenAI SDK 호환 인터페이스에서 tools 매개변수로 함수 schema를 정의할 수 있으며, 모델은 호출할 함수를 가리키는 tool_calls 필드를 반환합니다.

3분 읽기 · 게시일: 2026년 4월 3일 · 수정일: 2026년 9월 4일

댓글

GitHub로 로그인하여 댓글을 남기세요

Easton BlogEaston Blog