D-SKET Canvas는 처음부터 멀티벤더였습니다. 사용자가 Claude를 쓰든, OpenAI를 쓰든, Gemini를 쓰든 똑같이 동작해야 했죠. 한 번의 라이브러리 교체로 끝나는 일이라 생각했는데, 6개월이 지난 지금 우리는 800줄짜리 자체 추상화 레이어를 운영하고 있습니다.
처음에는 단순했습니다. Vercel AI SDK가 있고, 각 벤더의 공식 SDK가 있으니, 인터페이스만 통일하면 되지 않을까. 실제로 첫 일주일은 그게 맞았습니다. generateText({ model, prompt }) 하나로 세 벤더 모두 호출이 됐죠.
문제 1: 응답 형식
다이어그램 생성을 위해 우리는 JSON 응답을 강제해야 합니다. “노드 5개, 엣지 4개 만들어줘” 같은 프롬프트에 정확한 JSON 스키마로 답해야 캔버스에 렌더링이 가능합니다.
그런데 세 벤더의 JSON 모드 동작이 다 달랐습니다:
- Claude는
tools파라미터로 강제 — 가장 엄격하고 안정적 - OpenAI는
response_format: { type: 'json_object' }+ 시스템 프롬프트 명시 필요 - Gemini는
generationConfig.responseMimeType: 'application/json'사용
여기까지는 어떻게든 통합했습니다. 진짜 문제는 스트리밍에서 터졌습니다.
문제 2: 스트리밍 토큰의 의미가 다름
SSE 기반으로 토큰을 실시간으로 받아오면 사용자 경험이 훨씬 좋습니다. 다이어그램 생성도 노드 하나가 만들어질 때마다 캔버스에 즉시 표시할 수 있죠.
그런데 각 벤더의 스트림 청크가 의미하는 바가 달랐습니다.
OpenAI의 스트림 청크 하나는 보통 1~3 토큰. Claude는 의미 단위로 묶여서 한 번에 길게 와요. Gemini는 거의 한 줄씩 던집니다. 사용자에게 “타이핑하듯 보이는 효과”를 같은 속도로 만들려면 벤더별로 다른 버퍼링 로직이 필요했습니다.
— 조부건 (AI 엔진 리드)
문제 3: 에러의 모양이 다름
가장 짜증났던 부분입니다. 같은 종류의 에러(레이트 리밋, 토큰 초과, 콘텐츠 정책 위반)인데 응답 코드와 메시지 구조가 다 달랐습니다.
// OpenAI: HTTP 429
{ "error": { "type": "rate_limit_exceeded", "code": "rate_limit", ... } }
// Claude: HTTP 429
{ "type": "error", "error": { "type": "rate_limit_error", ... } }
// Gemini: HTTP 200 (!) but with error embedded
{ "promptFeedback": { "blockReason": "SAFETY" } }
Gemini가 200을 반환하면서 에러를 응답 본문에 넣는 건 정말 의외였습니다. axios interceptor에서 한 번 더 검증하는 로직을 추가해야 했죠.
그래서 우리는 자체 레이어를 만들었습니다
3주에 걸쳐 다음을 추상화했습니다:
- 요청 어댑터 — 통일된
chat({ messages, schema, stream })인터페이스 - 응답 정규화 — 모든 벤더의 응답을 같은 모양으로 (delta token, function call, finish reason)
- 에러 분류 — 7가지 에러 타입으로 분류 (rate, token, policy, network, auth, parse, unknown)
- 버퍼링 정책 — 사용자 화면에서 “비슷한 속도로 타이핑”하도록 자동 조절
지금 얻고 있는 것
몇 개월 운영해 보니 자체 추상화의 가장 큰 효용은 **“모델 교체가 환경변수 한 줄”**이 됐다는 거예요. 사용자가 Pro 플랜에서 Free로 다운그레이드하면 자동으로 더 저렴한 모델로 폴백. 특정 벤더에 장애가 생기면 다른 벤더로 자동 라우팅.
또 하나, BYOK 사용자의 API 키 검증 로직을 한 곳에서 관리할 수 있게 된 것. 사용자가 잘못된 키를 입력했을 때 어느 벤더든 동일한 에러 메시지로 안내합니다.
다음으로 할 일
다음 분기에는 온프레미스 LLM(Llama, Qwen 등) 어댑터를 추가합니다. 보안이 까다로운 엔터프라이즈 고객들이 사내 GPU 서버를 쓰고 싶어 하는 경우가 늘고 있어서요. 같은 어댑터 인터페이스로 통일하면 사용자에겐 차이가 보이지 않게 할 수 있습니다.
덧붙이자면, 이 추상화 레이어는 D-SKET Canvas뿐 아니라 컨설팅 프로젝트에서도 그대로 재사용합니다. SI로 만든 시스템에 AI 기능을 붙일 때 같은 코드가 들어가니, 우리가 자체 SaaS를 운영하는 의의가 이런 데서도 있습니다.