본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 06. 22. 21:12

로컬 AI Gateway에 Provider Adapter를 구현했다

요약

로컬 AI Gateway의 복잡도를 낮추기 위해 다양한 AI 프로바이더의 API 형식을 통일하는 Provider Adapter 구현 사례를 소개합니다. OpenAI와 Anthropic 등 각기 다른 API 구조를 인터페이스로 추상화하여 확장성을 확보했습니다.

핵심 포인트

  • 프로바이더별 API 차이(요청/응답 구조)를 흡수하는 어댑터 패턴 적용
  • Provider 인터페이스를 통해 Gateway 로직과 프로바이더 로직 분리
  • Provider Registry를 활용한 동적인 프로바이더 관리 체계 구축
  • Anthropic의 프롬프트 캐싱 등 프로바이더별 특화 기능 대응 가능

지난 기사에서는 로컬 AI Gateway에 Policy Engine을 구현했습니다.

이번에는 해당 게이트웨이에

Provider Adapter

를 추가로 구현한 내용을 소개합니다.

왜 Provider Adapter가 필요한가

AI 프로바이더는 하나가 아닐 수 있습니다.

예를 들어, OpenAI와 Anthropic은 API 형식이 다릅니다.

  • 요청(Request) 구조가 다름
  • 시스템 프롬프트(System Prompt)를 전달하는 방식이 다름
  • 응답(Response) 필드명이 다름

이러한 차이점을 Gateway 내에서 그대로 다루면,

프로바이더를 추가할 때마다 Gateway의 로직이 복잡해집니다.

프로바이더별 차이를 흡수하여,

Gateway 관점에서의 API를 통일하는 것

이 이번의 목적입니다.

문제는 「프로바이더 고유의 처리가 Gateway에 혼재하는 것」

이전까지의 구현에서는 Gateway가 OpenAI API로 직접 요청을 전달했습니다.

Anthropic을 추가하려고 하면,

게이트웨이 안에 OpenAI용·Anthropic용 분기(Branch)가 들어가게 됩니다.

프로바이더가 늘어날수록 분기가 늘어나며,

변경할 때마다 Gateway의 핵심부를 수정해야 하는

문제가 발생합니다.

구현한 내용

프로바이더별 차이를 흡수하는 Provider Adapter를 구현했습니다.

구현한 내용은 다음 세 가지입니다.

  • Provider 인터페이스와 두 개의 어댑터 (OpenAI·Anthropic)
  • Provider Registry를 통한 동적인 프로바이더 관리
  • 정책(Policy)에 프로바이더·모델 검증 추가

아키텍처

Gateway는 Provider Registry에 프로바이더 이름만 전달하면 되며,

어느 API로 보낼지를 의식할 필요가 없습니다.

Provider 인터페이스

모든 어댑터가 구현하는 인터페이스입니다.

type Provider interface {
Name() string
Chat(ctx context.Context, req ChatRequest) (*ChatResponse, error)
...

Gateway는 이 인터페이스만을 통해 프로바이더와 통신합니다.

요청(Request)과 응답(Response)은 게이트웨이 내의 통일된 타입으로 처리합니다.

type ChatRequest struct {
Provider string
Model string
...

클라이언트는 provider 필드로 송신처를 지정합니다.

{
"provider": "anthropic",
"model": "claude-sonnet-4-6",
...

어댑터 구현

각 어댑터는 Gateway의 ChatRequest를 프로바이더 고유의 API 형식으로 변환하고, 응답을 통일된 형식으로 변환하여 반환합니다.

시스템 프롬프트와 메시지 모두 변환 대상입니다.

OpenAI Adapter

Chat Completions API (/v1/chat/completions)를 사용합니다.

시스템 프롬프트를 role="system" 메시지로 맨 앞에 추가합니다.

if req.System != "" {
messages = append([]openAIMessage{{Role: "system", Content: req.System}}, messages...)
}

메시지는 Gateway의 통일된 형식에서 OpenAI 형식으로 변환하여 전송합니다.

Anthropic Adapter

Anthropic에서는 시스템 프롬프트가 독립된 필드입니다.

또한, **프롬프트 캐싱 (Prompt Caching)**을 활성화하기 위해 cache_control을 부여합니다.

if req.System != "" {
system = []antSystemBlock{{
Type: "text",
...

Anthropic의 프롬프트 캐싱 기능을 이용하기 위해, 시스템 프롬프트에 cache_control을 부여하고 있습니다.

동일한 시스템 프롬프트를 반복해서 사용할 경우, 캐시 히트(Cache Hit)를 통해 토큰 비용을 절감할 수 있습니다.

메시지는 Gateway의 통일된 형식에서 Anthropic의 형식으로 변환하여 전송합니다.

Provider Registry를 통한 관리

API 키가 설정되어 있는 프로바이더(Provider)만 기동 시에 등록합니다.

registry := provider.NewRegistry()
if cfg.OpenAIAPIKey != "" {
registry.Register(adapter.NewOpenAI(cfg.OpenAIAPIKey))
...

등록되지 않은 프로바이더로의 요청은 에러가 발생합니다.

또한, Policy Engine에서도 허가되지 않은 프로바이더를 차단합니다.

정책(Policy)으로의 통합

policy.yaml에 프로바이더별 설정을 추가했습니다.

providers:
openai:
enabled: true
...

이전의 models.allowed는 프로바이더를 가로지르는 허가 리스트였으나,

프로바이더별로 모델을 관리할 수 있는 형태로 변경했습니다.

요청이 도착하면, Policy Engine은 다음 순서로 평가합니다.

  • 프로바이더가 유효한가
  • 해당 프로바이더에서 해당 모델이 허가되었는가
  • 탐지 결과에 문제가 없는가

이 중 하나라도 block이 되면 요청을 거부합니다.

구현 시 의식한 점

어댑터 패턴 (Adapter Pattern)

Gateway는 Provider 인터페이스만을 알고 있습니다.

각 어댑터가 API의 차이를 흡수하기 때문에,

프로바이더를 추가해도 Gateway의 코드는 변경되지 않습니다.

페일세이프 (Fail-safe)

정책에 정의되지 않은 프로바이더나 모델은 기본적으로 차단합니다.

이는 이전 Policy Engine에서 구현한 페일세이프와 동일한 개념입니다.

외부 전송 관리

허가된 프로바이더에게만 전송되기 때문에,

어떤 API로 데이터가 전송되고 있는지 파악하고 관리할 수 있습니다.

또한, 감사 로그(Audit Log)에도 provider 필드가 기록됩니다.

{
"request_id": "dd236fa325819cbf",
"provider": "anthropic",
...

어떤 프로바이더로 요청이 전송되었는지 나중에 추적할 수 있습니다.

SSK의 설계 패턴과의 대응

이번 구현은 SSK에서의 AI Gateway의 "외부 전송 관리" 구현 사례입니다.

사용하는 모델 API와 전송하는 데이터를 파악하고 관리할 수 있도록 함으로써,

의도하지 않은 외부로의 전송을 방지하는

설계를 실현하고 있습니다.

요약

  • 프로바이더별 API 차이를 Gateway 내에 혼재시키면, 변경될 때마다 영향이 확산됨
  • Provider Adapter 패턴으로 차이를 흡수하고, Gateway의 API를 통일함
  • Provider Registry를 통해 기동 시 이용 가능한 프로바이더를 관리함
  • 정책에 프로바이더·모델 허가 설정을 추가하여 외부 전송을 관리할 수 있도록 함
  • 등록·허가되지 않은 프로바이더로의 전송은 페일세이프로서 차단함
  • 감사 로그에 provider를 기록하여 어떤 API로 전송되었는지 추적 가능하게 함

Discussion

AI 자동 생성 콘텐츠

본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0