Kai 2 0
게시물 메뉴

지난 글에서는 뭘 만들려고 하는지 이야기했다면, 이번에는 실제로 어떤 기술로 만들었는지 정리해봤습니다.

llms는 여러 LLM 계정을 하나로 묶어서 쓰는 오케스트레이션 허브입니다.

Claude, Codex, API Key 등을 연결해두면 클라이언트에서는 OpenAI 호환 주소 하나만 바라봅니다.

예를 들면 이런 식입니다.

https://llms.goodtek.xyz/r/my-route/v1

클라이언트는 뒤에서 Claude가 호출되는지 Codex가 호출되는지 알 필요가 없습니다.

게이트웨이가 계정 상태, 한도, 응답속도 등을 보고 적절한 업스트림을 선택하고 문제가 생기면 다른 계정으로 넘겨줍니다.

이번에 가장 오래 고민한 건 언어였습니다.

결론은 Go.

처음에는 익숙한 TypeScript로 전부 만드는 것도 생각했는데, 이 프로젝트의 핵심은 결국 HTTP 스트리밍 프록시입니다.

동시에 여러 연결을 처리하고, 스트리밍 중 상태를 관리하고, 장애가 난 업스트림을 빠르게 다른 곳으로 넘기는 일이 많습니다.

그리고 CLI까지 하나의 바이너리로 배포할 수 있다는 점도 컸습니다.

그래서 Gateway + CLI 모두 Go로 통일했습니다.

CLI는 단순히 플래그 몇 개 받는 도구로 만들 생각은 없습니다.

llms login llms plan llms upgrade

이런 식으로 쓰되, 처음 설정할 때는 따라가기만 하면 되는 위저드 형태를 목표로 하고 있습니다.

CLI UI는 Charm의 huh를 기본으로 하고 필요한 부분은 Bubble Tea를 사용합니다.

HTTP는 chi.

DB는 Cloud와 OSS를 다르게 가져갑니다.

Cloud: PostgreSQL + pgx

OSS: SQLite

오픈소스 버전까지 PostgreSQL을 강제하면 셀프호스팅 진입장벽이 너무 높아집니다.

openllms는 받아서 최대한 바로 실행할 수 있게 만드는 게 목표라 SQLite를 선택했습니다.

시크릿 관리도 분리했습니다.

Cloud에서는 이미 운영 중인 Infisical을 사용합니다.

DB에는 실제 API Key나 토큰을 넣지 않고 Infisical의 경로만 저장합니다.

반대로 OSS는 별도 Infisical 서버까지 띄우라고 하면 너무 무거워지니 로컬 파일 볼트를 사용합니다.

인증도 두 종류로 나눴습니다.

사람이 llms에 로그인할 때는 GitHub OAuth.

실제 Cursor나 다른 AI 클라이언트에서 모델을 호출할 때는

Bearer sk-gt-...

형태의 별도 키를 사용합니다.

업스트림의 Claude/Codex/API Key가 클라이언트에 직접 노출되는 구조는 피했습니다.

그리고 이번 설계에서 개인적으로 가장 중요한 결정.

공개 API는 OpenAI의

/chat/completions

하나만 노출합니다.

Codex는 내부적으로 /responses, Claude는 /v1/messages를 사용하지만

그 차이는 게이트웨이가 처리합니다.

클라이언트가 Claude인지 Codex인지에 따라 구현을 바꾸게 만들고 싶지 않았습니다.

라우팅도 단순 Round Robin으로 끝내지 않을 생각입니다.

계정별 헬스 상태, 남은 쿼터, TTFT, 실패 여부 등을 보고 선택하고

아직 응답이 클라이언트에 전달되기 전이라면 다른 업스트림으로 페일오버합니다.

대화 도중 계속 모델이나 계정이 바뀌지 않도록 스티키 라우팅도 넣습니다.

사실 이 부분이 llms의 핵심 엔진이 될 것 같습니다.

오픈소스와 Cloud의 경계도 처음부터 정했습니다.

핵심 엔진은 MIT 라이선스로 openllms에 먼저 공개합니다.

Cloud 전용 기능은 cloud/와 Go build tag로 분리합니다.

즉,

openllms = 실제로 쓸 수 있는 엔진 llms.goodtek.xyz = 그 엔진을 직접 운영하기 귀찮은 사람을 위한 Hosted 서비스

정도로 가져가려고 합니다.

오픈소스를 일부러 불편하게 만들어 Cloud로 끌고 오는 방식은 하지 않으려고 합니다.

Hosted의 과금도 최대한 단순하게 시작합니다.

Free Starter $5 Pro $9

BYOK 기반이라 모델 토큰을 재판매하는 구조가 아니라 라우팅과 운영 편의에 비용을 받는 형태입니다.

결제는 Polar를 붙이고, 암호화폐 결제는 Unifi Pay도 같이 붙여보려고 합니다.

현재 개발은 사실 거의 끝까지 왔습니다.

게이트웨이, 라우팅, CLI, Cloud 구조까지 대부분 만들어놨고 이제 결제 쪽을 붙이면 실제 서비스로 한번 열어볼 수 있는 단계입니다.

그런데 마지막 결제에서 멈췄습니다. ㅎㅎ

Polar 붙이고, Unifi Pay도 한번 연결해보고 싶은데

요즘 다른 것들을 같이 하다 보니 이걸 붙일 시간이 계속 안 나네요.

개발할 때 항상 느끼지만 0에서 90% 만드는 것보다 마지막 10%를 끝내는 게 더 어려운 것 같습니다.

기능은 거의 다 만들었는데 결제를 안 붙여서 아직 출시를 못 하는 아이러니한 상태입니다.

시간 나는 대로 이 마지막 10%를 끝내고 실제로 오픈해 보겠습니다!