나도 모르게 브라우저에서 LLM 오케스트레이션 시스템을 만든 이야기

Litseller가 GPT API와 React, 프롬프트, 브라우저 쪽 오케스트레이션으로 책 카탈로그의 구조화된 콘텐츠를 만들어 낸 방식을 아키텍처의 눈으로 돌아봅니다.

·
나도 모르게 브라우저에서 LLM 오케스트레이션 시스템을 만든 이야기
React와 프롬프트, 그리고 현실적인 엔지니어링 판단으로 조립된 브라우저 위의 LLM 오케스트레이션

이 년 전에 저는 Litseller를 만들었습니다.

그때는 LLM 오케스트레이션도, 그런 시스템의 아키텍처도 생각하지 않았습니다. 그저 구체적인 문제를 풀고 있었습니다. 책 카탈로그용 구조화된 콘텐츠를 어떻게 빨리 만들어 낼 것인가.

지금 돌아보면 그것은 사실상 완결된 LLM 오케스트레이션 시스템이었습니다. 다만 백엔드가 아니라 브라우저 안에 구현되어 있었을 뿐입니다.

그것이 실제로 무엇이었는가

이 부분은 제대로 규정해 둘 필요가 있습니다.

Litseller는 LLM 서비스가 아니었습니다.

카탈로그를 갖춘 평범한 웹 애플리케이션이었고, 그 관리자 영역 안에 데이터 생성 도구로 LLM을 끼워 넣은 것뿐이었습니다.

오케스트레이션은 전부 단순한 흐름 안에서 일어났습니다. 편집 화면, GPT API, JSON, 검증, 백엔드 저장.

  • 큐 없음.
  • 워커 없음.
  • 서버 쪽 오케스트레이션 없음.
  • 복잡한 인프라 없음.

모든 것이 React와 프롬프트, 그리고 요청의 사슬 위에 세워져 있었습니다.

아키텍처

시스템은 세 개의 층으로 나뉘어 있었습니다.

  • 프런트엔드: Next.js 관리자 영역, 에디터, 그리고 LLM 로직 전부.
  • 백엔드: .NET API, 검증, 영속화.
  • 저장소: SQL Server와 S3.
LLM 로직은 백엔드에 전혀 없었습니다. 호출은 브라우저에서 곧바로 나갔습니다.

생성 파이프라인

하나의 큰 요청 대신 파이프라인을 짰습니다.

  1. 모델이 그 책을 아는지 확인한다.
  2. 필요하면 제목을 분명히 한다.
  3. 분류를 고른다.
  4. 핵심 정보를 생성한다.
  5. 줄거리, 인용, 주제 등 콘텐츠 블록을 고른다.
  6. 각 블록을 따로 생성한다.
  7. JSON을 조립한다.
  8. 내용을 다른 언어로 번역한다.
  9. 결과를 검증한다.
  10. 최종 데이터를 저장한다.

이미 진짜 오케스트레이션이었습니다. 다만 별도의 오케스트레이션 서비스가 없었을 뿐입니다.

블록 단위가 잘 통한 이유

콘텐츠를 블록별로 생성한 것은 가장 잘한 판단 가운데 하나였습니다.

책 페이지 전체를 한 번에 생성해 달라고 하지 않았습니다.

  • 줄거리는 따로 생성했습니다.
  • 등장인물은 따로 생성했습니다.
  • 인용은 따로 생성했습니다.
  • 주제는 따로 생성했습니다.

덕분에 품질을 더 잘 통제할 수 있었고, 일부만 다시 생성할 수 있었으며, JSON이 안정적이었고 오류도 줄었습니다.

실질적으로는 LLM 출력 위에 손으로 얹은 버전 관리 층이 된 셈입니다.

프롬프트 설계

프롬프트는 단순했지만 구조를 갖추고 있었습니다.

  • 엄격한 JSON 형식.
  • 명확한 지시.
  • 마법은 최소한으로.

맥락은 명시적으로 넘겼습니다. 제목, 저자, 분류, 언어, 그리고 앞선 블록들.

  • 대화 기억 없음.
  • 복잡한 상태 없음.
  • 툴 호출 없음.

가장 논쟁적인 판단

API 키를 localStorage에 두었습니다.

이유는 단순했습니다. 에디터는 공개 화면이 아니었고, 사용자는 아주 적었으며, 우선순위는 빨리 내놓는 것이었고, 백엔드 오케스트레이션은 시스템을 훨씬 복잡하게 만들었을 것이기 때문입니다.

의식적인 판단이었습니다. 위험은 이해했고 받아들였습니다.

게다가 접근은 제한되어 있었고 Cloudflare로 수동으로 보호했습니다.

약한 지점

오늘 정직하게 보면 약점은 분명합니다.

  • 클라이언트에 있는 API 키.
  • 중앙화된 요청 제한 없음.
  • 호출에 대한 중앙 통제 없음.
  • 재시도나 백오프 장치 없음.
  • 제대로 된 스키마 검증 없음.
  • 약한 오류 처리.
  • 관측 가능성 없음.
  • 브라우저에 지나치게 복잡한 로직.
탄탄한 MVP였지만, 실서비스에 걸맞은 LLM 플랫폼은 아니었습니다.

강한 지점

동시에 그 시스템은 돌아갔고 실제 성과를 냈습니다.

  • 아주 빠른 개발.
  • 최소한의 인프라.
  • 높은 유연성.
  • 화면을 통한 완전한 통제.
  • 손으로 다듬기 편함.
  • 모듈 단위 생성.
  • 실제로 돌아가는 운영 흐름.
LLM은 도구였지, 시스템의 중심이 아니었습니다.

지금이라면 다르게 할 것들

오늘 다시 만든다면 아키텍처를 바꿀 것입니다.

  • LLM 호출을 백엔드 게이트웨이로 옮긴다.
  • 클라이언트에서 API 키를 없앤다.
  • 큐와 재시도를 넣는다.
  • 엄격한 JSON 스키마 검증을 도입한다.
  • 로깅과 트레이싱을 붙인다.
  • 요청 제한을 구현한다.
  • 오케스트레이션을 화면에서 떼어 낸다.

가장 큰 배움

제게 가장 흥미로운 대목은, 제가 LLM 시스템을 설계한 것이 아니라는 점입니다.

저는 그저 문제를 풀고 있었습니다.

나중에서야 제가 지금 LLM 오케스트레이션이라고 부르는 아키텍처를 만들었다는 것을 깨달았습니다.

좋은 엔지니어링 판단은 처음에는 혼돈과 직관처럼 보이기도 합니다. 그것이 의식적으로 다듬을 수 있는 또렷한 아키텍처가 되는 것은 나중의 일입니다.

모델 안에서 일어나는 일

위의 내용은 이미 작동하는 모델을 둘러싼 오케스트레이션입니다. 인터랙티브 페이지는 반대쪽을 다룹니다. 슬라이더 하나로 모델을 천 번의 학습 단계에 통과시켜, 무작위 숫자에서 예측까지 따라갑니다.

언어 모델은 어떻게 배우는가: 인터랙티브 도해

더 읽기