Ollama용 Go 클라이언트: SDK 비교 및 Qwen3/GPT-OSS 예제
Ollama를 Go와 통합하기: SDK 가이드, 예제 및 프로덕션 최고 실천 방법.
이 가이드는 사용 가능한 Go SDKs for Ollama에 대한 종합적인 개요를 제공하고, 그 기능 세트를 비교합니다.
우리는 Ollama에서 호스팅된 Qwen3 및 GPT-OSS 모델을 호출하는 실용적인 Go 예제를 탐구할 것입니다. 이는 raw REST API 호출 및 공식 Go 클라이언트를 통해 이루어지며, Qwen3에서 thinking 및 non-thinking 모드의 상세한 처리를 포함합니다.
왜 Ollama + Go인가?
Ollama는 generate 및 chat 워크로드를 위한 작은, 실용적인 HTTP API(보통 http://localhost:11434
에서 실행됨)를 노출합니다. 이 API는 스트리밍 지원 및 모델 관리 기능이 내장되어 있습니다. 공식 문서는 /api/generate
및 /api/chat
요청/응답 구조와 스트리밍 의미론을 철저히 다룹니다.
Go는 HTTP에 대한 강력한 표준 라이브러리 지원, 우수한 JSON 처리, 네이티브 동시성 원시체, 컴파일 시 오류를 포착하는 정적 타입 인터페이스 덕분에 Ollama 클라이언트를 구축에 대한 최고의 선택입니다.
2025년 10월 현재, 고려할 수 있는 Go SDK 옵션은 다음과 같습니다.
Ollama용 Go SDK — 사용 가능한 항목
SDK / 패키지 | 상태 및 “소유자” | 범위 (Generate/Chat/Streaming) | 모델 관리 (pull/list/etc.) | 추가 사항 |
---|---|---|---|---|
github.com/ollama/ollama/api |
Ollama 레포 내부의 공식 패키지; ollama CLI 자체에서 사용됨 |
REST에 매핑된 전체 범위; 스트리밍 지원 | 예 | 공식 Go 클라이언트로 간주됨; API는 문서와 밀접하게 대응합니다. |
LangChainGo (github.com/tmc/langchaingo/llms/ollama ) |
Ollama LLM 모듈이 포함된 커뮤니티 프레임워크 (LangChainGo) | 챗/완료 + 프레임워크 추상화를 통한 스트리밍 | 제한적 (모델 관리가 주 목적이 아님) | 체인, 도구, 벡터 저장소를 사용하려면 훌륭한 선택; 원시 SDK는 아님. |
github.com/swdunlop/ollama-client |
커뮤니티 클라이언트 | 챗에 초점; 도구 호출 실험에 좋음 | 부분적 | 도구 호출 실험을 위해 작성됨; 표면이 1:1이 아님. |
기타 커뮤니티 SDK (예: ollamaclient , 제3자 “go-ollama-sdk”) |
커뮤니티 | 다양함 | 다양함 | 품질 및 범위가 다양함; 레포별로 평가해야 함. |
추천: 프로덕션에서는 **github.com/ollama/ollama/api
**를 선호하세요—이 패키지는 핵심 프로젝트와 함께 유지되며 REST API를 정확히 반영합니다.
Ollama에서의 Qwen3 및 GPT-OSS: thinking vs non-thinking (알아야 할 사항)
- Ollama의 thinking 모드는 활성화된 경우 모델의 “논리"를 최종 출력에서 분리합니다. Ollama 문서는 지원되는 모델에 대해 enable/disable thinking 행동을 첫 번째 등급으로 문서화합니다.
- **(https://www.glukhov.org/ko/post/2025/10/qwen3-30b-vs-gpt-oss-20b/ “Qwen3:30b vs GPT-OSS:20b: 기술 세부 사항, 성능 및 속도 비교”)**는 동적 전환을 지원합니다: 시스템/사용자 메시지에
/think
또는/no_think
를 추가하여 모드를 턴-by-턴 전환; 최신 지시가 우선 적용됩니다. - GPT-OSS: 사용자들은 thinking 비활성화 (예:
/set nothink
또는--think=false
)가gpt-oss:20b
에서 신뢰할 수 없을 수 있다고 보고합니다; UI에서 표시하지 않아야 하는 경우 필터링/숨기기를 계획하세요.
1부 — raw REST를 통해 Ollama 호출 (Go, net/http)
공유 타입
먼저 예제에서 사용할 공통 타입과 도우미 함수를 정의해 보겠습니다:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
// ---- 챗 API 타입 ----
type ChatMessage struct {
Role string `json:"role"`
Content string `json:"content"`
}
type ChatRequest struct {
Model string `json:"model"`
Messages []ChatMessage `json:"messages"`
// 일부 서버는 thinking 제어를 boolean 플래그로 노출합니다.
// 생략하더라도 Qwen3을 /think 또는 /no_think 태그를 통해 제어할 수 있습니다.
Think *bool `json:"think,omitempty"`
Stream *bool `json:"stream,omitempty"`
Options map[string]any `json:"options,omitempty"`
}
type ChatResponse struct {
Model string `json:"model"`
CreatedAt string `json:"created_at"`
Message struct {
Role string `json:"role"`
Content string `json:"content"`
Thinking string `json:"thinking,omitempty"` // thinking이 활성화된 경우 존재
} `json:"message"`
Done bool `json:"done"`
}
// ---- 생성 API 타입 ----
type GenerateRequest struct {
Model string `json:"model"`
Prompt string `json:"prompt"`
Think *bool `json:"think,omitempty"`
Stream *bool `json:"stream,omitempty"`
Options map[string]any `json:"options,omitempty"`
}
type GenerateResponse struct {
Model string `json:"model"`
CreatedAt string `json:"created_at"`
Response string `json:"response"` // 비스트리밍의 경우 최종 텍스트
Thinking string `json:"thinking,omitempty"` // thinking이 활성화된 경우 존재
Done bool `json:"done"`
}
// ---- 도우미 함수 ----
func httpPostJSON(url string, payload any) ([]byte, error) {
body, err := json.Marshal(payload)
if err != nil {
return nil, err
}
c := &http.Client{Timeout: 60 * time.Second}
resp, err := c.Post(url, "application/json", bytes.NewReader(body))
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
// bptr은 boolean 값의 포인터를 반환합니다
func bptr(b bool) *bool { return &b }
챗 — Qwen3과 thinking ON (및 어떻게 OFF로 전환하는지)
func chatQwen3Thinking() error {
endpoint := "http://localhost:11434/api/chat"
req := ChatRequest{
Model: "qwen3:8b-thinking", // 풀어낸 :*-thinking 태그를 사용
Think: bptr(true),
Stream: bptr(false),
Messages: []ChatMessage{
{Role: "system", Content: "당신은 정확한 보조자입니다."},
{Role: "user", Content: "재귀를 짧은 Go 예제로 설명해 주세요."},
},
}
raw, err := httpPostJSON(endpoint, req)
if err != nil {
return err
}
var out ChatResponse
if err := json.Unmarshal(raw, &out); err != nil {
return err
}
fmt.Println("🧠 thinking:\n", out.Message.Thinking)
fmt.Println("\n💬 answer:\n", out.Message.Content)
return nil
}
// 다음 턴에서 thinking을 OFF로 전환하려면:
// (a) Think=false로 설정하거나,
// (b) 가장 최근의 시스템/사용자 메시지에 "/no_think"를 추가합니다 (Qwen3의 소프트 스위치).
// Qwen3은 다중 턴 챗에서 가장 최근의 /think 또는 /no_think 지시를 따릅니다.
func chatQwen3NoThinking() error {
endpoint := "http://localhost:11434/api/chat"
req := ChatRequest{
Model: "qwen3:8b-thinking",
Think: bptr(false),
Stream: bptr(false),
Messages: []ChatMessage{
{Role: "system", Content: "당신은 간결합니다. /no_think"},
{Role: "user", Content: "재귀를 한 문장으로 설명해 주세요."},
},
}
raw, err := httpPostJSON(endpoint, req)
if err != nil {
return err
}
var out ChatResponse
if err := json.Unmarshal(raw, &out); err != nil {
return err
}
// thinking이 비어 있을 것으로 예상하지만, 방어적으로 처리합니다.
if out.Message.Thinking != "" {
fmt.Println("🧠 thinking (예상치 못함):\n", out.Message.Thinking)
}
fmt.Println("\n💬 answer:\n", out.Message.Content)
return nil
}
(Qwen3의 /think
및 /no_think
소프트 스위치는 Qwen 팀이 문서화했으며, 다중 턴 챗에서 마지막 지시가 우선 적용됩니다.)
챗 — GPT-OSS와 thinking (및 주의사항)
func chatGptOss() error {
endpoint := "http://localhost:11434/api/chat"
req := ChatRequest{
Model: "gpt-oss:20b",
Think: bptr(true), // 지원되는 경우 분리된 추론 요청
Stream: bptr(false),
Messages: []ChatMessage{
{Role: "user", Content: "동적 프로그래밍이란 무엇인가? 핵심 아이디어를 설명해 주세요."},
},
}
raw, err := httpPostJSON(endpoint, req)
if err != nil {
return err
}
var out ChatResponse
if err := json.Unmarshal(raw, &out); err != nil {
return err
}
// 알려진 특이점: thinking 비활성화가 gpt-oss:20b에서 완전히 억제되지 않을 수 있습니다.
// UI에서 표시하지 않으려면 항상 thinking을 필터링/숨기세요.
fmt.Println("🧠 thinking:\n", out.Message.Thinking)
fmt.Println("\n💬 answer:\n", out.Message.Content)
return nil
}
사용자들은 gpt-oss:20b에서 thinking 비활성화 (예: /set nothink
또는 --think=false
)가 무시될 수 있다고 보고합니다—필요한 경우 클라이언트 측 필터링을 계획하세요.
생성 — Qwen3 및 GPT-OSS
func generateQwen3() error {
endpoint := "http://localhost:11434/api/generate"
req := GenerateRequest{
Model: "qwen3:4b-thinking",
Prompt: "데이터베이스에서 B-Tree는 무엇에 사용되나요? 2–3 문장으로 설명해 주세요.",
Think: bptr(true),
}
raw, err := httpPostJSON(endpoint, req)
if err != nil {
return err
}
var out GenerateResponse
if err := json.Unmarshal(raw, &out); err != nil {
return err
}
if out.Thinking != "" {
fmt.Println("🧠 thinking:\n", out.Thinking)
}
fmt.Println("\n💬 answer:\n", out.Response)
return nil
}
func generateGptOss() error {
endpoint := "http://localhost:11434/api/generate"
req := GenerateRequest{
Model: "gpt-oss:20b",
Prompt: "신경망에서 역전파를 간단히 설명해 주세요.",
Think: bptr(true),
}
raw, err := httpPostJSON(endpoint, req)
if err != nil {
return err
}
var out GenerateResponse
if err := json.Unmarshal(raw, &out); err != nil {
return err
}
if out.Thinking != "" {
fmt.Println("🧠 thinking:\n", out.Thinking)
}
fmt.Println("\n💬 answer:\n", out.Response)
return nil
}
REST 형상 및 스트리밍 행동은 Ollama API 참조에서 직접 가져옵니다.
2부 — 공식 Go SDK (github.com/ollama/ollama/api
)를 통해 Ollama 호출
공식 패키지는 REST API에 해당하는 Client
의 메서드를 노출합니다. Ollama CLI 자체는 이 패키지를 사용하여 서비스와 통신하므로, 호환성 측면에서 가장 안전한 선택입니다.
설치
go get github.com/ollama/ollama/api
챗 — Qwen3 (thinking ON / OFF)
package main
import (
"context"
"fmt"
"log"
"github.com/ollama/ollama/api"
)
func chatWithQwen3Thinking(ctx context.Context, thinking bool) error {
client, err := api.ClientFromEnvironment() // OLLAMA_HOST가 설정된 경우 존중
if err != nil {
return err
}
req := &api.ChatRequest{
Model: "qwen3:8b-thinking",
// 많은 서버 빌드는 thinking을 최상위 플래그로 노출합니다;
// 또한, Qwen3을 메시지 내의 /think 또는 /no_think로 제어할 수 있습니다.
Think: api.Ptr(thinking),
Messages: []api.Message{
{Role: "system", Content: "당신은 정확한 보조자입니다."},
{Role: "user", Content: "병합 정렬을 짧은 Go 스니펫으로 설명해 주세요."},
},
}
var resp api.ChatResponse
if err := client.Chat(ctx, req, &resp); err != nil {
return err
}
if resp.Message.Thinking != "" {
fmt.Println("🧠 thinking:\n", resp.Message.Thinking)
}
fmt.Println("\n💬 answer:\n", resp.Message.Content)
return nil
}
func main() {
ctx := context.Background()
if err := chatWithQwen3Thinking(ctx, true); err != nil {
log.Fatal(err)
}
// 예: non-thinking
if err := chatWithQwen3Thinking(ctx, false); err != nil {
log.Fatal(err)
}
}
챗 — GPT-OSS (논리 처리에 주의)
func chatWithGptOss(ctx context.Context) error {
client, err := api.ClientFromEnvironment()
if err != nil {
return err
}
req := &api.ChatRequest{
Model: "gpt-oss:20b",
Think: api.Ptr(true),
Messages: []api.Message{
{Role: "user", Content: "메모화란 무엇이며 언제 유용한가요?"},
},
}
var resp api.ChatResponse
if err := client.Chat(ctx, req, &resp); err != nil {
return err
}
// 표시하지 않으려면 여기서 무조건 처리하세요.
if resp.Message.Thinking != "" {
fmt.Println("🧠 thinking:\n", resp.Message.Thinking)
}
fmt.Println("\n💬 answer:\n", resp.Message.Content)
return nil
}
생성 — Qwen3 & GPT-OSS
func generateWithQwen3(ctx context.Context) error {
client, err := api.ClientFromEnvironment()
if err != nil {
return err
}
req := &api.GenerateRequest{
Model: "qwen3:4b-thinking",
Prompt: "B-Tree가 인덱싱에서 어떤 역할을 하는지 요약해 주세요.",
Think: api.Ptr(true),
}
var resp api.GenerateResponse
if err := client.Generate(ctx, req, &resp); err != nil {
return err
}
if resp.Thinking != "" {
fmt.Println("🧠 thinking:\n", resp.Thinking)
}
fmt.Println("\n💬 answer:\n", resp.Response)
return nil
}
func generateWithGptOss(ctx context.Context) error {
client, err := api.ClientFromEnvironment()
if err != nil {
return err
}
req := &api.GenerateRequest{
Model: "gpt-oss:20b",
Prompt: "경사 하강법을 간단히 설명해 주세요.",
Think: api.Ptr(true),
}
var resp api.GenerateResponse
if err := client.Generate(ctx, req, &resp); err != nil {
return err
}
if resp.Thinking != "" {
fmt.Println("🧠 thinking:\n", resp.Thinking)
}
fmt.Println("\n💬 answer:\n", resp.Response)
return nil
}
공식 패키지의 표면은 REST 문서와 동일하며, 핵심 프로젝트와 함께 업데이트됩니다.
스트리밍 응답
실시간 스트리밍을 위해 요청에서 Stream: bptr(true)
를 설정하세요. 응답은 줄 단위로 구분된 JSON 청크로 전달됩니다:
func streamChatExample() error {
endpoint := "http://localhost:11434/api/chat"
req := ChatRequest{
Model: "qwen3:8b-thinking",
Think: bptr(true),
Stream: bptr(true), // 스트리밍 활성화
Messages: []ChatMessage{
{Role: "user", Content: "퀵소트 알고리즘을 단계별로 설명해 주세요."},
},
}
body, _ := json.Marshal(req)
resp, err := http.Post(endpoint, "application/json", bytes.NewReader(body))
if err != nil {
return err
}
defer resp.Body.Close()
decoder := json.NewDecoder(resp.Body)
for {
var chunk ChatResponse
if err := decoder.Decode(&chunk); err == io.EOF {
break
} else if err != nil {
return err
}
// thinking 및 내용이 도착할 때마다 처리
if chunk.Message.Thinking != "" {
fmt.Print(chunk.Message.Thinking)
}
fmt.Print(chunk.Message.Content)
if chunk.Done {
break
}
}
return nil
}
공식 SDK를 사용하는 경우 스트리밍 청크를 처리하기 위해 콜백 함수를 사용하세요:
func streamWithOfficialSDK(ctx context.Context) error {
client, _ := api.ClientFromEnvironment()
req := &api.ChatRequest{
Model: "qwen3:8b-thinking",
Think: api.Ptr(true),
Messages: []api.Message{
{Role: "user", Content: "이진 검색 트리를 설명해 주세요."},
},
}
err := client.Chat(ctx, req, func(resp api.ChatResponse) error {
if resp.Message.Thinking != "" {
fmt.Print(resp.Message.Thinking)
}
fmt.Print(resp.Message.Content)
return nil
})
return err
}
Qwen3 thinking vs non-thinking 작업 (실용적인 지침)
-
두 가지 레버:
- Ollama의 thinking 기능을 지원하는 boolean
thinking
플래그; 그리고 - Qwen3의 소프트 스위치 명령
/think
및/no_think
을 최신 시스템/사용자 메시지에 포함합니다. 가장 최근 지시가 다음 턴(s)을 결정합니다.
- Ollama의 thinking 기능을 지원하는 boolean
-
기본 자세: non-thinking으로 빠른 응답; 단계별 추론이 필요한 작업(수학, 계획, 디버깅, 복잡한 코드 분석)에는 thinking으로 승격하세요.
-
스트리밍 UI: thinking이 활성화된 경우 스트리밍 프레임에서 추론/내용이 교차될 수 있으므로, 버퍼링하거나 별도로 렌더링하고 사용자에게 “추론 보기” 토글을 제공하세요. (API 문서에서 스트리밍 형식을 참조하세요.)
-
다중 턴 대화: Qwen3은 이전 턴의 thinking 모드를 기억합니다. 대화 중간에 토글하려면 플래그와 소프트 스위치 명령을 모두 사용하여 신뢰성을 확보하세요.
GPT-OSS에 대한 참고 사항
- thinking을 비활성화하려 해도 추론이 존재할 수 있으므로, UX가 표시하지 않아야 한다면 클라이언트에서 필터링하세요.
- GPT-OSS를 사용하는 프로덕션 애플리케이션에서는 필요 시 추론 패턴을 감지하고 제거할 수 있는 클라이언트 측 필터링 로직을 구현하세요.
- 특정 GPT-OSS 모델 변종을 철저히 테스트하세요. 동작은 다른 양자화 및 버전 간에 달라질 수 있습니다.
최선의 실천 및 프로덕션 팁
오류 처리 및 타임아웃
항상 적절한 타임아웃 처리 및 오류 복구를 구현하세요:
func robustChatRequest(ctx context.Context, model string, messages []api.Message) (*api.ChatResponse, error) {
// 합리적인 타임아웃 설정
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
client, err := api.ClientFromEnvironment()
if err != nil {
return nil, fmt.Errorf("클라이언트 생성: %w", err)
}
req := &api.ChatRequest{
Model: model,
Messages: messages,
Options: map[string]interface{}{
"temperature": 0.7,
"num_ctx": 4096, // 컨텍스트 창 크기
},
}
var resp api.ChatResponse
if err := client.Chat(ctx, req, &resp); err != nil {
return nil, fmt.Errorf("챗 요청 실패: %w", err)
}
return &resp, nil
}
연결 풀링 및 재사용
요청마다 새로운 클라이언트를 생성하는 대신 Ollama 클라이언트를 재사용하세요:
type OllamaService struct {
client *api.Client
}
func NewOllamaService() (*OllamaService, error) {
client, err := api.ClientFromEnvironment()
if err != nil {
return nil, err
}
return &OllamaService{client: client}, nil
}
func (s *OllamaService) Chat(ctx context.Context, req *api.ChatRequest) (*api.ChatResponse, error) {
var resp api.ChatResponse
if err := s.client.Chat(ctx, req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
환경 구성
유연한 배포를 위해 환경 변수를 사용하세요:
export OLLAMA_HOST=http://localhost:11434
export OLLAMA_NUM_PARALLEL=2
export OLLAMA_MAX_LOADED_MODELS=2
공식 SDK는 api.ClientFromEnvironment()
를 통해 OLLAMA_HOST
를 자동으로 존중합니다.
모니터링 및 로깅
프로덕션 시스템에 구조화된 로깅을 구현하세요:
func loggedChat(ctx context.Context, logger *log.Logger, req *api.ChatRequest) error {
start := time.Now()
client, _ := api.ClientFromEnvironment()
var resp api.ChatResponse
err := client.Chat(ctx, req, &resp)
duration := time.Since(start)
logger.Printf("model=%s duration=%v error=%v tokens=%d",
req.Model, duration, err, len(resp.Message.Content))
return err
}
결론
-
Go 프로젝트에 대해, **
github.com/ollama/ollama/api
**는 가장 완전하고 프로덕션 준비가 되어 있는 선택입니다. 이 패키지는 Ollama 핵심 프로젝트와 함께 유지되며, 공식 CLI에서 사용되며, 보장된 호환성을 가진 포괄적인 API 커버리지를 제공합니다. -
고수준 추상화가 필요한 경우, LangChainGo를 고려하세요. 체인, 도구, 벡터 저장소, RAG 파이프라인을 필요로 할 때, 이는 일부 저수준 제어를 포기하지만 편리함을 제공합니다.
-
Qwen3은 플래그와 메시지 수준 토글 (
/think
,/no_think
)을 통해 thinking 모드에 대한 깨끗하고 신뢰할 수 있는 제어를 제공하므로, 빠른 응답과 깊은 추론이 필요한 애플리케이션에 이상적입니다. -
GPT-OSS에 대해, 필요 시 추론 출력을 클라이언트 측에서 항상 정화해야 합니다. thinking 비활성화 플래그가 일관되게 존중되지 않을 수 있습니다.
-
프로덕션에서는 적절한 오류 처리, 연결 풀링, 타임아웃, 모니터링을 구현하여 강력한 Ollama 기반 애플리케이션을 구축하세요.
Go의 강력한 타이핑, 우수한 동시성 지원, Ollama의 직관적인 API는 AI 기반 애플리케이션을 구축하기에 이상적인 스택을 제공합니다—from 간단한 챗봇에서 복잡한 RAG 시스템까지.
주요 요약
선택 방법에 대한 빠른 참조입니다:
사용 사례 | 권장 접근 방식 | 이유 |
---|---|---|
프로덕션 애플리케이션 | github.com/ollama/ollama/api |
공식 지원, 포괄적인 API 커버리지, 핵심 프로젝트와 함께 유지됨 |
RAG/체인/도구 파이프라인 | LangChainGo | 고수준 추상화, 벡터 저장소와의 통합 |
학습/실험 | net/http를 사용한 raw REST | 완전한 투명성, 의존성 없음, 교육용 |
빠른 프로토타이핑 | 공식 SDK | 간단함과 강력함의 균형 |
스트리밍 챗 UI | 콜백을 사용한 공식 SDK | 내장 스트리밍 지원, 깔끔한 API |
모델 선택 지침:
- Qwen3: 제어 가능한 thinking 모드와 신뢰할 수 있는 다중 턴 대화가 필요한 애플리케이션에 최적
- GPT-OSS: 강력한 성능을 제공하지만, thinking/추론 출력을 방어적으로 처리해야 함
- 기타 모델: 철저히 테스트해야 함; thinking 행동은 모델 가족에 따라 달라질 수 있음
참고 자료 및 추가 읽을거리
공식 문서
- Ollama API 참조 — 완전한 REST API 문서
- 공식 Ollama Go 패키지 — Go SDK 문서
- Ollama GitHub 저장소 — 소스 코드 및 이슈
Go SDK 대안
- LangChainGo Ollama 통합 — 체인 기반 애플리케이션용
- swdunlop/ollama-client — 도구 호출 기능이 있는 커뮤니티 클라이언트
- xyproto/ollamaclient — 또 다른 커뮤니티 옵션
모델별 자료
- Qwen 문서 — 공식 Qwen 모델 정보
- GPT-OSS 정보 — GPT-OSS 모델 세부 정보
관련 주제
- Go로 RAG 애플리케이션 구축 — LangChainGo 예제
- Go context 패키지 — 타임아웃 및 취소에 필수적
- Go HTTP 클라이언트 최고 실천법 — 표준 라이브러리 문서