
생성형 AI × Go: LLM을 프로덕트에 통합하는 설계 패턴
요약
Go 언어를 사용하여 LLM API를 실제 프로덕션 환경에 안정적으로 통합하기 위한 4가지 설계 패턴을 소개합니다. 스트리밍, 프롬프트 관리, 에러 처리, 대화 관리 측면에서 운영 가능한 아키텍처를 구축하는 방법을 다룹니다.
핵심 포인트
- Goroutine과 Channel을 활용한 SSE 스트리밍 구현으로 사용자 UX 개선
- text/template을 이용한 프롬프트의 중앙 집중식 관리 및 테스트 가능성 확보
- Context를 활용한 리소스 누수 방지 및 안정적인 에러 처리
- 단순 API 호출을 넘어 운영 환경에 적합한 설계 패턴 제시
Go로 LLM의 API를 호출해 본 적이 있나요? 수십 줄의 코드로 작동하는 프로토타입을 만들면 처음에는 감동하게 됩니다. 하지만 그것을 실제 프로덕트에 통합하려고 하면 또 다른 문제들이 차례차례 나타납니다.
응답이 너무 느려서 사용자가 이탈하거나, 프롬프트(Prompt)가 코드베이스에 흩어져 관리할 수 없게 되거나, 어느 날 갑자기 타임아웃이 발생하여 에러 로그가 흐르거나, 대화 이력이 너무 길어져 토큰 상한에 걸리는 등——. "API를 호출하는 것"과 "프로덕트에 통합하는 것"은 별개의 기술적 과제입니다.
이 기사는 그러한 "작동은 하지만 불안한 상태"에서 벗어나고 싶은 Go 엔지니어를 위해 작성되었습니다. J.B.Goode에서 실제 프로덕트 개발을 통해 발견한 4가지 설계 패턴과, 각각의 "왜 그렇게 설계하는가"에 대한 의도를 소개합니다.
Go와 HTTP API의 기초적인 읽기/쓰기가 가능하다면 읽을 수 있습니다. LLM에 대한 깊은 지식은 필요하지 않습니다. 이 기사를 다 읽고 나면 스트리밍(Streaming), 프롬프트 관리, 에러 처리, 대화 관리의 4가지 측면에서 "우선 작동하는 구현"에서 "운영 환경에서 계속 작동하는 설계"로 나아가는 구체적인 경로가 보일 것입니다.
해결하는 문제
LLM의 응답 생성에는 수 초에서 수십 초가 걸립니다. 응답이 완전히 생성될 때까지 아무것도 표시하지 않는 구현이라면, 사용자는 "고장 난 건가"라고 생각하며 이탈합니다. 체감 속도의 문제입니다.
설계의 의도
UX 문제를 아키텍처로 해결합니다. ChatGPT나 Claude에서 글자가 조금씩 흘러나오는 경험은, 사실 대기 시간을 "기다림이 아닌 진행 중"으로 바꾸는 UI 설계입니다. 이를 Go로 구현할 때, goroutine과 채널(Channel)이 자연스럽게 맞아떨어집니다.
Anthropic API는 Server-Sent Events (SSE) 형식의 스트리밍을 지원하며, 생성이 진행될 때마다 텍스트 조각(Chunk)을 수신할 수 있습니다. bufio.Scanner로 응답 바디(Response Body)를 행 단위로 읽고, goroutine과 채널로 결과를 전달하는 구성이 자연스럽습니다.
// llm/client.go
package llm
import (
...
이 채널을 HTTP 핸들러(Handler)에서 받아 클라이언트에게 SSE로 흘려보냅니다.
// handler/stream.go
package handler
import (
...
http.Flusher 체크를 잊으면 nginx나 ALB를 경유할 때 버퍼링되어 스트리밍의 의미가 없어집니다. 사용자가 탭을 닫는 등 context가 취소된 경우에도, goroutine 측의 ctx.Done()이 제대로 반응하여 깔끔하게 종료됩니다.
해결하는 문제
기능이 늘어남에 따라 fmt.Sprintf("당신은 %s입니다. 질문: %s", role, query)와 같은 인라인 문자열이 코드베이스에 흩어집니다. 어디서 사용되는지 추적할 수 없고, 변경 영향 범위를 알 수 없으며, 테스트를 작성할 수 없습니다——. 코드로 말하자면 매직 넘버(Magic Number)가 산재해 있는 상태입니다.
설계의 의도
프롬프트는 LLM의 동작을 결정하는, 코드와 동등하게 중요한 산출물입니다. Go의 text/template을 사용함으로써 프롬프트를 "검증 가능하고, 테스트 가능하며, 한 곳에서 관리할 수 있는" 것으로 만듭니다.
// prompt/builder.go
package prompt
import (
...
이 접근 방식에는 3가지 장점이 있습니다. **검증(Validation)**을 프롬프트 생성 전 단계에 둘 수 있다는 것, 테스트를 Build() 호출만으로 작성할 수 있다는 것, 그리고 템플릿 변경이 한 곳으로 모인다는 점입니다.
테스트도 다음과 같이 솔직하게 작성할 수 있습니다.
// prompt/builder_test.go
func TestBuilder_Build(t *testing.T) {
b, err := prompt.NewBuilder()
...
해결하는 문제
LLM은 네트워크 너머의 외부 서비스이며, 게다가 확률적인 시스템입니다. 속도 제한(Rate Limit, HTTP 429), 타임아웃, 서버 에러(5xx)는 언제든 발생합니다. 여기서 "일단 3번 재시도"라고 작성하면, 재시도해도 무의미한 에러(인증 실패, 잘못된 요청)에 대해서까지 재시도하게 되어 비용과 시간을 낭비하게 됩니다.
설계의 의도
Go의 명시적인 에러 타입을 활용하여, "재시도해야 할 실패"와 "재시도해서는 안 될 실패"를 타입 레벨 (Type level)에서 구분합니다. 판단 로직을 에러 타입에 캡슐화함으로써, 호출 측에서 실패의 종류를 알지 못하더라도 올바르게 동작합니다.
// resilience/retry.go
package resilience
import (
...
사용법은 다음과 같습니다.
cfg := resilience.RetryConfig{
MaxAttempts: 3,
BaseDelay: time.Second,
...
"재시도 여부"에 대한 판단을 에러 타입의 지식으로 갖게 함으로써, 호출 측에 로직이 흩어지는 것을 방지할 수 있습니다.
해결하는 문제
멀티 턴 (Multi-turn) 대화에서는 API에 전달하는 이력이 대화가 거듭될수록 계속해서 늘어납니다. 이를 그대로 계속 전달하면 토큰 비용 (Token cost)이 선형적으로 증가하며, 최종적으로 모델의 컨텍스트 윈도우 (Context window) 상한(claude-haiku-4-5의 경우 200,000 토큰)에 도달하여 에러가 발생합니다. "잘 작동하다가 갑자기 멈춘" 패턴의 전형적인 사례입니다.
설계의 의도
컨텍스트 윈도우를 "무제한 메모리"가 아닌 "유한한 예산"으로 의식적으로 다룹니다. TokenEstimator를 인터페이스 (Interface)로 만들어 둠으로써, 처음에는 글자 수 기반의 간이 추정으로 시작하고, 정밀도가 필요해지는 시점에 정확한 토크나이저 (Tokenizer)로 교체할 수 있습니다.
// conversation/manager.go
package conversation
type Role string
...
사용법은 다음과 같습니다.
manager := conversation.NewManager(
4000, // 예산: 4,000 토큰 상당
conversation.SimpleEstimator{},
...
4가지 패턴에 공통적으로 적용되는 것은 LLM을 "특별한 것"으로 취급하지 않는다는 관점입니다.
스트리밍 (Streaming)은 io.Reader의 문제, 프롬프트 (Prompt)는 템플릿의 문제, 실패는 에러 핸들링 (Error handling)의 문제, 대화 이력은 버젯 관리 (Budget management)의 문제—LLM을 외부의 비동기 서비스로 파악하고, Go에 존재하는 기존의 도구들로 대응하는 것입니다. 그 단순한 관점이 프로덕션 환경에서 계속해서 동작하는 시스템으로 이어진다고 생각합니다.
소프트웨어로 할 수 있는 모든 것. 생성형 AI는 그 "모든 것"의 범위를 확실히 넓혀주었지만, 그것을 신뢰할 수 있는 형태로 프로덕트에 구현하는 책임은 여전히 엔지니어에게 있습니다.
샘플 코드는 설명을 위해 간략화되었습니다. 실제 운영 환경에 적용할 때는 로깅 (Logging), 메트릭 수집 (Metrics collection), 적절한 시크릿 관리 (Secret management)를 추가해 주세요.
J.B.Goode Inc.에 소속되어 있습니다. 괜찮으시다면 팔로우 부탁드립니다!
J.B.Goode Inc.의 웹사이트에서는 기술 기사 외에도 기술 지식 및 일상의 깨달음 등을 전달하고 있습니다.
캐주얼 면담도 진행 중입니다. 편하게 문의해 주세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기