[번역] Gen AI 플랫폼 만들기

Tommy Park
43 min readJan 10, 2025

--

이 글은 Chip Huyen이 작성한 “Building A Generative AI Platform”의 한글 번역입니다.

여러 회사들의 Gen AI 애플리케이션 배포 방식을 살펴보면 많은 공통점들이 있습니다. 이 글에서 Gen AI 플랫폼의 전반적인 공통 구성 요소와 각각의 역할, 그리고 어떻게 구현 되는지 설명하려고 합니다. 설계를 최대한 일반화하려고 노력했지만 애플리케이션에 따라 다를 수 있습니다. 전체적인 그림은 다음과 같습니다.

Gen AI 플랫폼의 전체 설계

꽤 복잡해 보이는 시스템입니다. 이 글에서는 가장 단순한 아키텍처에서 시작해서 점차 더 많은 구성 요소를 추가해 나가려고 합니다. 가장 단순한 구조에서는 애플리케이션이 요청을 받고 이를 모델로 보냅니다. 모델은 응답을 생성하고 그 결과를 사용자에게 반환합니다. 아직 가드레일도, 컨텍스트 보강도, 최적화도 없습니다. 모델 API 상자는 OpenAI, Google, Anthropic과 같은 서드파티 API와 자체 호스팅 API가 모두 포함될 수 있습니다.

가장 단순한 형태

여기서 부터 필요에 따라 더 많은 구성 요소들을 추가해 나갈 수 있습니다. 이 글에서는 일반적인 순서에 따라 설명하겠지만 꼭 이 순서를 따를 필요는 없습니다. 시스템이 문제 없이 작동한다면 특정 구성 요소를 생략할 수도 있습니다. 잘 작동하는지 평가하는 것은 개발의 각 단계마다 필수적입니다.

  1. 모델이 외부 데이터 소스와 정보 수집 도구에 접근할 수 있도록 해서 모델에 주어지는 컨텍스트를 강화한다.
  2. 가드레일을 추가해서 시스템과 사용자를 보호한다.
  3. 모델 라우터와 게이트웨이를 추가해서 복잡한 파이프라인을 지원하고 보안을 강화한다.
  4. 캐시를 적용해서 지연 시간과 비용을 최적화한다.
  5. 복잡한 로직과 액션을 추가해서 시스템의 능력을 극대화한다.

시스템의 실제 동작을 들여다보고 모니터링과 디버깅을 가능하도록하는 가시성 확보(Observability), 그리고 시스템의 모든 구성요소가 함께 연결되어 동작하게 하는 오케스트레이션(Orchestration)은 플랫폼의 두 핵심 요소입니다. 이에 대해서는 글의 마지막에 다룰 예정입니다.

Step 1. 컨텍스트 강화

플랫폼의 첫 번째 확장은 일반적으로 답변 생성을 위해 필요한 정보를 추가할 수 있는 메커니즘 도입입니다. 관련된 정보를 수집하는 과정을 컨텍스트 구축(Context construction)이라고 합니다.

많은 경우 답변을 생성하기 위해 컨텍스트가 필요합니다. 컨텍스트에 관련된 정보가 많을수록 모델이 자신의 내부 지식에 의존해야 하는 정도가 줄어듭니다. 내부 지식은 훈련 데이터와 방법론의 한계 때문에 신뢰성이 떨어질 수 있습니다. 연구에 따르면 컨텍스트에 관련 정보가 포함되면 모델이 더 상세한 응답을 생성하는 데 도움을 주고 환각(hallucination)을 줄일 수 있다고 합니다 (Lewis et al., 2020).

예를 들어 “Acme의 fancy-printer-A300이 100pps로 출력할 수 있나요?”라는 질문에 대해, 모델이 fancy-printer-A300의 사양을 제공받는다면 더 나은 응답을 할 수 있을겁니다.

파운데이션 모델에게 컨텍스트 구축은 기존 머신러닝 모델의 피쳐 엔지니어링과 같은 역할을 합니다. 둘 다 모델이 입력 데이터를 처리하는 데 필요한 정보를 제공한다는 점에서 유사합니다.

인-컨텍스트 학습(In-context learning), 즉 컨텍스트로부터 배우는 것은 일종의 지속 학습(continual learning)입니다. 이는 모델이 학습 완료된 시점에 머무르지 않고 새로운 정보를 지속적으로 통합하여 결정을 내릴 수 있도록 해줍니다. 예를 들어 지난주 데이터를 기반으로 훈련된 모델은 최신 정보가 컨텍스트에 포함되지 않는 한 이번 주에 관한 질문에 답할 수 없지만 컨텍스트에 최신 정보(예: fancy-printer-A300의 최신 사양)를 제공함으로써 모델은 최신 상태를 유지하며 컷오프 날짜 이후의 질문에도 대응할 수 있게 됩니다.

RAGs

컨텍스트를 구축하는 가장 널리 알려진 방식은 RAG(Retrieval-Augmented Generation)입니다. RAG는 두 가지 구성 요소로 이루어져 있습니다. 하나는 텍스트를 생성하는 생성기(언어 모델)이고, 다른 하나는 외부 소스에서 필요한 정보를 찾아오는 검색기(Retriever)입니다.

RAG

정보 검색(Retrieval)은 RAG에만 사용하는 특별한 기술이 아닙니다. 검색 엔진, 추천 시스템, 로그 분석 등 다양한 시스템의 핵심 역할입니다. 전통적인 검색 시스템을 위해 개발된 많은 검색 알고리즘이 RAG에서도 활용될 수 있습니다.

많은 경우 외부 데이터는 비정형 데이터인 문서입니다. 하나의 문서는 10개의 토큰일 수도 있고 100만 개의 토큰일 수도 있습니다. 하지만 문서를 그대로 검색하면 컨텍스트가 지나치게 길어질 위험이 있습니다. RAG는 일반적으로 문서를 모델의 최대 컨텍스트 길이와 애플리케이션의 지연 요구 사항에 맞게 관리 가능한 크기로 잘라 나누는 과정을 필요로 합니다. 문서 조각화(chunking)와 최적의 조각 크기에 대해 더 알고 싶다면 Pinecone, Langchain, Llamaindex, 그리고 Greg Kamradt의 튜토리얼을 참고하면 도움이 됩니다.

외부 메모리 소스에서 데이터를 불러오고 이를 Chunking한 이후, 검색은 두 가지 주요 방식으로 수행됩니다.

  1. 단어 기반 검색 (Term-based retrieval)

이 방식은 간단한 키워드 검색으로 시작할 수 있습니다. 예를 들어 “transformer”라는 쿼리를 받으면 이 키워드를 포함한 모든 문서를 가져오는 방식입니다. TF-IDF를 활용한 BM25, 역색인을 활용하는 Elasticsearch 등이 여기에 해당됩니다.

단어 기반 검색은 주로 텍스트 데이터에 사용되지만, 제목, 태그, 캡션, 댓글 등 텍스트 메타데이터가 있는 이미지나 동영상에도 활용할 수 있습니다.

2. 임베딩 기반 검색 (Embedding-based retrieval a.k.a Vector Search)

이 방식은 데이터를 임베딩 모델(BERT, sentence-transformers, OpenAI나 Google의 임베딩 모델 등)을 사용해 벡터 형태로 변환 후 주어진 쿼리 벡터와 가장 가까운 벡터를 가진 데이터를 벡터 검색 알고리즘으로 찾아냅니다.

벡터 검색은 일반적으로 최근접 이웃(nearest neighbor) 검색으로 표현되며, FAISS(Facebook AI Similarity Search), Google의 ScaNN, Spotify의 ANNOY, hnswlib(Hierarchical Navigable Small World) 같은 근사 최근접 이웃(ANN: approximate nearest neighbor) 알고리즘이 사용됩니다.

ANN-benchmarks 웹사이트에서는 다양한 데이터셋에서 여러 ANN 알고리즘을 다음의 네 가지 주요 기준으로 비교합니다. 이 기준은 인덱싱과 검색 간의 트레이드오프를 평가하는 데 유용합니다.

  • 리콜(Recall): 알고리즘이 찾아낸 최근접 이웃의 비율. 검색 정확도를 평가합니다.
  • 초당 검색 수(QPS): 알고리즘이 초당 처리할 수 있는 검색 횟수. 트래픽이 많은 애플리케이션에서 매우 중요합니다.
  • 인덱스 생성 시간(Build time): 인덱스를 구축하는 데 걸리는 시간. 데이터가 자주 변경되어 인덱스를 업데이트해야 하는 경우 특히 중요합니다.
  • 인덱스 크기(Index size): 생성된 인덱스의 크기. 알고리즘의 확장성과 저장 요구 사항을 평가하는 데 중요합니다.

이 방법은 텍스트 문서 뿐만 아니라 이미지, 동영상, 오디오, 코드에도 적용할 수 있습니다. 많은 회사들이 SQL 테이블이나 데이터프레임을 요약한 후, 이 요약을 활용해 임베딩을 생성하고 검색에 사용하는 시도를 하고 있습니다.

단어 기반 검색은 임베딩 기반 검색보다 훨씬 빠르고 비용이 저렴합니다. 설정이 간단해서 바로 사용하기에 적합하며 첫 단계에서 매력적인 선택지입니다. BM25와 Elasticsearch는 업계에서 널리 사용되며 복잡한 검색 시스템을 구축하기 전에 훌륭한 기준점이 됩니다. 임베딩 기반 검색은 계산 비용이 높지만 시간이 지나면서 성능이 개선되어 단어 기반 검색을 능가할 수 있습니다.

실제 프로덕션의 검색 시스템은 여러 방식을 조합해서 사용하는 경우가 많습니다. 단어 기반 검색과 임베딩 기반 검색을 결합한 방식을 하이브리드 검색이라고 합니다.

일반적인 패턴 중 하나는 순차 검색입니다. 먼저 단어기반 검색처럼 저렴하고 덜 정교한 방식으로 후보 문서를 가져옵니다. 이후 kNN이나 랭킹 모델 처럼 정밀하지만 비용이 많이 드는 방식을 통해 이 후보 중 최적의 결과를 찾아냅니다. 이 두 번째 단계를 리랭킹이라고 부릅니다.

예를 들어 “transformer”라는 단어에 대해서 우선 전기 장치, 신경망 구조, 영화 등 모든 관련 문서를 가져옵니다. 그런 다음 벡터 검색을 사용해 이들 중 실제로 질의와 관련 있는 문서를 선별하는 식입니다.

컨텍스트 리랭킹이 전통적인 검색 리랭킹과 다른 부분도 있습니다. 검색 리랭킹에서는 결과의 순서가 매우 중요합니다. 컨텍스트 리랭킹에서 문서의 순서는 그만큼 치명적이지는 않습니다. 하지만 여전히 모델의 답변에 어느정도 영향을 미칠 수 있습니다. Lost in the middle(Liu et al., 2023)이라는 연구에 따르면 문서가 컨텍스트의 시작과 끝에 위치할 때 모델이 더 잘 이해할 가능성이 높습니다. 그렇지만 결국 문서가 컨텍스트에 포함되기만 한다면 순서의 중요성은 검색 리랭킹에 비해 덜합니다.

또 다른 패턴은 앙상블 방식입니다. 여러 검색기를 동시에 사용해 후보 문서를 가져온 후, 각 검색기의 순위를 결합해 최종 순위를 만듭니다.

RAGs with tabular data

데이터프레임이나 SQL 테이블과 같은 구조화된 데이터도 RAG에 사용할 수 있습니다. SQL 테이블에서 데이터를 검색하는 방식은 비정형 문서에서 데이터를 검색하는 방식과는 꽤 다릅니다. 쿼리가 주어지면 시스템은 다음과 같이 처리합니다.

  1. Text-to-SQL: 사용자 쿼리와 테이블 스키마를 기반으로 필요한 SQL 쿼리를 결정합니다.
  2. SQL 실행: 생성된 SQL 쿼리를 실행합니다.

3. 응답 생성: SQL 결과와 원래 사용자 쿼리를 기반으로 최종 응답을 생성합니다.

Text-to-SQL 단계에서 테이블이 많아서 모든 테이블의 스키마를 모델의 컨텍스트에 포함할 수 없는 경우에는, 각 질의에 적합한 테이블을 예측하는 중간 단계를 거쳐야 할 수 있습니다. Text-to-SQL 처리는 최종 응답을 생성하는 모델과 같은 모델로 수행할 수도 있고, Text-to-SQL에 특화된 모델을 활용할 수도 있습니다.

Agentic RAGs

인터넷은 중요한 데이터 소스 중 하나입니다. Google이나 Bing API 같은 웹 검색 도구를 사용하면 모델이 각 쿼리에 대한 관련 정보를 수집하기 위해 풍부한 최신 정보를 활용할 수 있습니다. 예를 들어 “올해 오스카를 누가 수상했나요?“라는 쿼리에 대해 시스템은 최신 오스카 관련 정보를 인터넷에서 검색하고 이를 바탕으로 사용자에게 최종 응답을 생성합니다.

단어 기반 검색, 임베딩 기반 검색, SQL 실행, 웹 검색은 모델이 컨텍스트를 보강하기 위해 수행할 수 있는 작업들입니다. 이러한 각 작업을 모델이 호출할 수 있는 하나의 함수로 간주할 수 있습니다. 이렇게 외부 액션을 실행할 수 있도록 통합된 워크플로우를 에이전트라고도 합니다. 이제 전체 설계는 다음과 같습니다.

도구 vs. 액션

하나의 도구는 여러 액션을 수행할 수 있습니다. 예를 들어 인물 검색 도구는 이름으로 검색하거나 이메일로 검색하는 두 가지 액션을 제공할 수 있습니다. 그러나 액션과 도구의 차이는 미미하기 때문에 두 용어를 종종 구분 없이 사용하기도 합니다.

읽기 전용 액션 vs. 쓰기 액션

외부 소스에서 정보를 검색하지만 상태를 변경하지 않는 액션은 읽기 전용 액션(read-only actions)이라고 합니다. 반면 테이블의 값을 업데이트하는 것처럼 상태를 변경하는 쓰기 액션(write actions)을 부여하면 모델이 더 다양한 작업을 수행할 수 있습니다. 하지만 쓰기 액션에는 추가적인 리스크들이 있고 이에 대해서는 후에 다시 설명합니다.

Query rewriting

사용자 쿼리를 적절히 재작성하면 관련 정보를 검색할 가능성이 더 높아질 수 있습니다. 다음 대화를 예로 들어보겠습니다.

사용자: John Doe가 마지막으로 우리에게서 물건을 산 게 언제였나요?

AI: John은 2030년 1월 3일, 2주 전에 Fruity Fedora 모자를 마지막으로 구입했습니다.

사용자: Emily Doe는요?

마지막 질문인 “Emily Doe는요?”는 모호합니다. 이 쿼리를 그대로 사용해 문서를 검색하면 관련 없는 결과가 나올 가능성이 아주 높습니다. 이런 경우에 사용자가 실제로 묻고자 하는 내용을 반영하도록 질의를 재작성해야 합니다. “Emily Doe는요?”를 “Emily Doe가 마지막으로 우리에게서 물건을 산 게 언제였나요?”로 재작성하면 의미가 명확해집니다.

쿼리 재작성은 주로 AI 모델에게 “다음 대화를 참고하여, 사용자가 실제로 묻고자 하는 내용을 반영하도록 마지막 사용자 입력을 재작성하세요.” 와 같은 프롬프트를 주고 생성합니다.

쿼리 재작성은 사용자가 언급한 대상을 정확하게 식별해야 하거나 추가적인 정보를 얻어와야 하는 경우 복잡해질 수 있습니다. 예를 들어 사용자가 “그의 아내는요?”라고 묻는다면 먼저 데이터베이스를 조회해 그의 아내가 누구인지 확인해야 합니다. 만약 이런 정보가 없다면 재작성 모델은 해결할 수 없는 쿼리임을 판단할 수 있어야 하며, 임의로 이름을 만들어내서 재작성하는 등의 환각 현상이 일어나지 않도록 주의해야 합니다.

Step 2. 가드레일 추가

가드레일은 AI의 위험을 줄이고 사용자와 개발자를 보호합니다. 이 글에서는 입력 가드레일과 출력 가드레일, 두 가지 유형에 대해 설명합니다.

입력 가드레일

입력 가드레일은 주로 두 가지 위험을 방지하기 위한 보호 장치입니다. 외부 API로 민감한 정보가 유출되는 것, 그리고 시스템을 위협하는 악성 프롬프트 실행(model jailbreaking)입니다.

외부 API로 민감한 정보 유출

이 위험은 외부 모델 API를 사용하여 데이터를 조직 외부로 전송해야 하는 경우에 발생합니다. 회사의 기밀 정보나 사용자의 개인 정보가 프롬프트에 입력되어 모델 서버로 전송될 수 있습니다. 주목할 만한 초기 사례 중 하나는 삼성 직원들이 삼성의 기밀 정보를 ChatGPT에 입력하면서 회사 비밀이 유출된 사건입니다. 삼성이 이 유출을 어떻게 발견했는지, 유출된 정보가 삼성에 어떤 영향을 미쳤는지는 명확하지 않지만, 2023년 5월에 삼성이 ChatGPT 사용을 금지하는 결과로 이어졌습니다.

서드파티 API를 사용할 때 잠재적 유출을 완전히 차단할 방법은 없습니다. 그러나 가드레일을 사용해 이를 완화할 수 있습니다. 민감한 데이터를 자동으로 감지하는 다양한 도구를 활용할 수 있으며, 어떤 민감한 데이터를 탐지할지 지정합니다. 일반적인 민감 데이터의 유형은 다음과 같습니다.

  • 개인 정보(ID 번호, 전화번호, 은행 계좌 등)
  • 얼굴 사진
  • 회사의 지적 재산권이나 기밀 정보와 관련된 특정 키워드 및 문구

많은 민감 데이터 감지 도구는 AI를 활용해 잠재적으로 민감한 정보를 식별합니다. 예를 들어 주어진 문자열이 주소 패턴과 유사한지 판단할 수 있습니다. 민감한 정보가 발견되면 쿼리를 전체를 차단할 수도 있고, 사용자의 전화번호를 [PHONE NUMBER]로 마스킹하는 것처럼 민감한 정보만 제거할 수도 있습니다. 이후 생성된 응답에 마스킹했던 부분을 PII(개인 식별 정보) 복원 사전을 사용해 원래 정보로 복원할 수 있습니다. 아래 예시처럼 동작합니다.

민감 정보 마스킹과 복원

모델 탈옥 (Model Jailbreaking)

AI 모델을 탈옥(jailbreak)하려는 시도는 온라인에서 일종의 놀이처럼 시도되고 있습니다. ChatGPT 같은 모델이 논란이 될 만한 발언을 하도록 유도하는 것을 재미있어하는 사람도 있지만, 고객 지원 챗봇이 당신의 이름과 로고를 달고 그렇게 한다면 절대 웃을 일이 아닐 것입니다. 특히 AI 시스템이 도구에 접근할 수 있는 경우 이런 위험은 더욱 커집니다. 사용자가 시스템을 조작해 데이터를 손상시키는 SQL 쿼리를 실행하도록 유도할 수 있다고 생각해 보세요.

이를 방지하려면 시스템에 가드레일을 설치해 유해한 작업이 자동으로 실행되지 않도록 해야 합니다. 데이터 삽입, 삭제, 업데이트와 같은 SQL 쿼리는 반드시 사람의 승인을 거쳐야만 실행되도록 설정할 수 있습니다. 다만 이런 보안 조치는 시스템의 속도를 느리게 할 수 있다는 단점이 있습니다.

애플리케이션이 부적절하거나 논란이 될 만한 발언을 하지 않도록 하려면 애플리케이션의 주제 스코프를 정의하는 방법이 있습니다. 예를 들면 고객 지원 챗봇은 정치나 사회적 질문에 답하지 않도록 설정할 수 있습니다. 간단히 구현하려면 “이민”이나 “백신 반대” 같은 논란의 여지가 있는 주제와 관련된 특정 문구를 포함한 입력을 필터링하면 됩니다. AI를 활용해 입력이 사전에 정의된 제한된 주제와 관련이 있는지 분류할 수도 있습니다.

유해한 프롬프트가 시스템에서 매우 드물게 발생한다면 이상 감지 알고리즘을 사용해서 이를 식별하는 방법도 가능합니다.

출력 가드레일

AI 모델은 확률적이기 때문에 출력을 그대로 믿고 사용하기 어렵지만, 가드레일을 적용하면 애플리케이션의 신뢰성을 크게 향상시킬 수 있습니다. 출력 가드레일은 두 가지 주요 기능을 수행합니다:

1. 각 출력의 품질을 평가합니다.

2. 다양한 실패 케이스에 대한 처리 방침을 명시합니다.

출력 평가

출력이 기대 수준에 미치지 못하는 경우를 감지하려면, 실패 케이스가 어떤 형태인지 이해하는 것이 중요합니다. 다음은 실패 케이스와 이를 감지하는 방법입니다.

1. 빈 응답: 모델이 아무런 응답을 생성하지 않는 경우입니다.

2. 잘못된 형식: 기대하는 출력 형식과 맞지 않는 응답입니다. 예를 들어, 애플리케이션이 JSON 형식을 기대하는데 생성된 응답에서 닫는 괄호가 누락된 경우입니다. 이를 감지하기 위해 정규식, JSON, Python 코드 검증기와 같은 형식 검증 도구를 사용할 수 있습니다. 또는 guidance, outlines, instructor와 같은 도구를 활용해 제한된 샘플링 방식을 적용할 수도 있습니다.

3. 유해한 응답: 인종차별적이거나 성차별적인 유해한 내용을 포함한 응답입니다. 이러한 응답은 다양한 독성 탐지(toxicity detection) 도구를 사용해 감지할 수 있습니다.

4. 사실과 일치하지 않는 응답: 모델이 환각(hallucination)으로 생성한 정보가 포함된 응답입니다. 이를 감지하기 위한 연구는 활발히 진행 중이며, 예로 SelfCheckGPT (Manakul et al., 2023)와 SAFE(Search Engine Factuality Evaluator, Wei et al., 2024) 같은 솔루션이 있습니다. 환각을 완화하기 위해 컨텍스트를 충분히 제공하거나 chain-of-thought과 같은 프롬프트 기법을 활용할 수 있습니다.

5. 민감한 정보를 포함한 응답: 다음 두 가지 시나리오에서 발생할 수 있습니다.

• 모델이 민감한 데이터로 학습되어 이를 그대로 출력하는 경우.

• 시스템이 내부 데이터베이스에서 민감한 정보를 검색해 컨텍스트로 사용한 뒤 이를 응답에 포함하는 경우.

이를 방지하려면 민감한 데이터를 학습에 사용하지 않거나, 모델이 민감한 데이터를 검색하지 못하도록 해야 합니다. 출력에 포함된 민감한 정보는 입력 가드레일에서 사용하는 도구와 동일한 도구로 탐지할 수 있습니다.

6. 브랜드에 해를 끼칠 수 있는 응답: 회사나 경쟁사를 잘못 묘사하는 응답입니다. 예를 들어, X사가 학습한 Grok 모델이 “Grok은 OpenAI가 학습시킨 모델이다”라는 응답을 생성해, 인터넷에서 X사가 OpenAI의 데이터를 훔쳤다는 의혹이 제기된 사례가 있습니다. 이를 방지하려면 키워드 모니터링을 통해 브랜드 및 경쟁사와 관련된 출력을 식별해야 합니다. 이후 해당 출력을 차단하거나, 인간 검토자에게 전달하거나, 다른 모델을 사용해 출력의 감정을 분석하여 적절한 감정만 반환되도록 할 수 있습니다.

7. 전반적으로 품질이 낮은 응답: 모델이 작성한 에세이가 매우 형편없거나, 저칼로리 케이크 레시피를 요청했는데 생성된 레시피에 설탕이 과도하게 포함된 경우입니다. 최근에는 AI Judge를 사용해 모델 응답의 품질을 평가하는 방식이 인기를 끌고 있습니다. 이러한 AI Judge는 ChatGPT나 Claude 같은 범용 모델을 사용해도 되고 주어진 쿼리에 대해 구체적인 점수를 출력하도록 학습시킨 스코어러 모델이 될 수도 있습니다.

실패 케이스 처리

AI 모델은 확률적으로 동작하므로 같은 입력을 다시 요청해도 다른 응답이 나올 수 있습니다. 간단한 재시도 로직을 사용해 해결할 수 있는 실패 케이스들도 많습니다. 예를 들어 응답이 비어 있을 경우, X번 또는 비어 있지 않은 응답이 나올 때까지 다시 시도할 수 있습니다. 응답 형식이 잘못된 경우에도 올바른 형식의 응답이 생성될 때까지 다시 시도할 수 있습니다.

하지만 이러한 재시도 정책은 추가적인 지연과 비용을 발생시킵니다. 한 번의 재시도는 API 호출 횟수를 두 배로 늘립니다. 실패 후 재시도가 이루어진다면 사용자가 경험하는 지연 시간도 두 배가 됩니다. 이를 줄이기 위해 병렬 호출을 사용할 수 있습니다. 예를 들어 쿼리 하나에 대해 첫 번째 시도가 실패하기를 기다리지 않고 같은 쿼리를 두 번 동시에 요청하고 두 개의 응답 중 더 나은 것을 선택할 수 있습니다. 이 방법은 불필요한 API 호출이 늘어나지만 지연 시간을 효과적으로 관리할 수 있습니다.

복잡한 쿼리를 사람에게 처리를 넘기는 방식도 흔히 사용됩니다. 특정 키워드를 포함하는 경우 인간 상담사에게 넘기는 방식입니다. 대화를 인간에게 넘길 시점을 결정하기 위해 내부적으로 학습된 모델을 사용하기도 합니다. 감정 분석 모델을 활용해 사용자가 화를 내고 있는 것으로 감지되면 대화를 인간 상담사에게 넘기는 사례도 있습니다. 또 사용자가 무한 루프에 빠지지 않도록 일정 횟수 이상의 대화 이후에는 대화를 인간 상담사에게 넘기도 합니다.

가드레일 트레이드오프들

신뢰성 vs. 지연 시간: 가드레일의 중요성을 인정하면서도 지연 시간이 증가될 수 있기 때문에 가드레일을 구현하지 않기로 결정하는 경우도 있습니다. 하지만 대부분의 가드레일로 추가되는 지연 시간보다 가드레일이 없을 때 부담해야 하는 위험요소가 더 비용이 많이 든다고 판단합니다.

스트림 모드: 스트림 모드에서 출력 가드레일은 잘 작동하지 않을 수 있습니다. 스트림 모드를 사용하지 않는 경우 전체 응답 생성이 완료된 이후 사용자에게 보여지므로 사용자가 응답을 보기 까지 시간이 오래 걸립니다. 스트림 모드에서는 응답 토큰이 생성되는 대로 사용자에게 스트리밍되므로 사용자가 기다리지 않고 응답을 보는 이점이 있습니다. 그러나 부분적으로 생성된 응답을 평가하는 것이 어렵기 때문에 가드레일이 차단해야 한다고 판단하기 전에 안전하지 않은 응답이 이미 사용자에게 스트리밍될 수 있습니다.

셀프 호스팅 vs. 외부 API: 모델을 셀프 호스팅하면 데이터를 타사에 전송할 필요가 없으므로 입력 가드레일의 필요성이 줄어듭니다. 그러나 반대로 외부 서비스가 제공하는 가드레일 기능을 활용할 수 없고 필요한 모든 가드레일을 직접 구현해야 한다는 뜻이기도 합니다.

우리 플랫폼은 이제 다음과 같은 구조를 가집니다. 가드레일은 독립적인 도구일 수도 있고 모델 게이트웨이의 일부일 수도 있습니다. 스코어러가 사용되는 경우 모델 API에 함께 묶이는데, 스코어러 역시 일반적으로 AI 모델이기 때문입니다. 스코어링에 사용되는 모델은 생성에 사용되는 모델보다 일반적으로 작고 빠릅니다.

Step 2 까지 추가된 설계

Step 3. 모델 라우터와 게이트웨이 추가

애플리케이션이 점점 복잡해지고 다양한 모델을 사용하게 되면 여러 모델을 효율적으로 관리하기 위한 도구로 라우터와 게이트웨이가 필요합니다.

라우터

애플리케이션은 다양한 종류의 요청에 따라 서로 다른 모델을 사용할 수 있습니다. 요청별로 다른 솔루션을 적용하는 것은 여러 가지 장점이 있습니다. 우선, 특정 작업에 특화된 솔루션을 사용할 수 있습니다. 기술 문제 해결에 특화된 모델이나 구독 관련 처리를 전문으로 하는 모델처럼, 특화된 모델은 범용 모델보다 더 나은 성능을 발휘할 가능성이 있습니다. 그리고 모든 요청을 고비용 모델로 처리하는 대신 간단한 요청은 저렴한 모델로 처리해서 비용을 절감할 수 있습니다.

라우터에는 일반적으로 사용자의 의도를 예측하는 의도 분류기가 들어갑니다. 예측된 의도에 따라 요청을 적합한 솔루션으로 라우팅합니다. 예를 들어 고객 지원 챗봇의 경우 의도에 따라 다음과 같이 라우팅할 수 있습니다.

• 비밀번호를 재설정하려는 경우 → 비밀번호 재설정 페이지로 사용자 안내.

• 청구 오류를 수정하려는 경우 → 사용자 요청을 인간 상담사에게 연결.

• 기술 문제를 해결하려는 경우 → 기술 문제 해결에 맞게 미세 조정된 모델로 요청 전달.

의도 분류기는 시스템이 범위를 벗어난 대화를 피하는 데도 도움을 줄 수 있습니다. 요청이 부적절하다고 판단되면(예: “다음 선거에서 누구에게 투표할 건가요?”) 챗봇은 불필요한 API 호출 없이 미리 준비된 응답으로 예의 있게 대화를 거절할 수 있습니다. (“저는 챗봇이기 때문에 투표할 수 없습니다. 제품에 대해 궁금한 점이 있으시면 도와드리겠습니다.”)

시스템이 여러가지 액션을 할 수 있는 경우, 라우터는 다음 액션 예측 모델을 사용해 어떤 작업을 수행할지 결정할 수 있습니다. 예를 들어 사용자의 요청이 모호한 경우 추가 설명을 요청하는 액션을 실행할 수 있습니다. 사용자가 “Freezing”이라고만 입력한 경우 시스템은 “계정을 동결하려는 건가요, 아니면 날씨에 대해 말하는 건가요?”라고 묻거나 간단히 “죄송합니다. 조금 더 자세히 설명해 주실 수 있나요?”라고 답변할 수 있습니다.

의도 분류기와 다음 액션 예측 모델은 범용 모델을 사용할 수도 있고 특정 작업에 맞게 학습시킨 분류 모델을 사용할 수도 있습니다. 특화된 분류 모델은 일반적으로 범용 모델보다 훨씬 작고 빠르기 때문에 여러 개를 사용해도 지연 시간이나 비용이 크게 증가하지 않는 장점이 있습니다.

컨텍스트 길이 제한을 가진 모델로 요청을 라우팅할 때는 요청 컨텍스트를 조정해야 할 수 있습니다. 예를 들어 1,000토큰의 요청이 4,000 토큰 컨텍스트 제한을 가진 모델로 보내질 예정인데 시스템이 웹 검색을 수행해 8,000토큰의 문맥을 반환했다고 하면, 요청의 문맥을 잘라 원래 의도된 모델에 맞추거나, 더 큰 문맥 한도를 가진 모델로 요청을 라우팅해야 할 수 있습니다.

게이트웨이

모델 게이트웨이는 조직이 다양한 모델과 안전하고 통합된 방식으로 상호작용할 수 있도록 해주는 중간 레이어입니다. 모델 게이트웨이의 가장 기본적인 기능은 개발자가 셀프 호스팅 모델이든 OpenAI나 Google 같은 상업적 API 모델이든 모두 동일한 방식으로 접근할 수 있도록 하는 것입니다. 모델 게이트웨이를 사용하면 코드를 유지 관리하기가 더 쉬워집니다. 특정 모델 API가 변경되더라도 모델 게이트웨이만 업데이트하면 되고 이 모델 API를 사용하는 모든 애플리케이션을 업데이트할 필요가 없습니다.

Model Gateway

가장 단순한 형태의 모델 게이트웨이는 다음 코드 예제와 같은 통합 래퍼입니다. (이 예제는 모델 게이트웨이가 어떻게 구현될 수 있는지에 대한 아이디어를 제공하기 위한 것입니다. 에러 검사나 최적화가 포함되어 있지 않으므로 실제로 작동하는 코드는 아닙니다.)

import google.generativeai as genai
import openai

def openai_model(input_data, model_name, max_tokens):
openai.api_key = os.environ["OPENAI_API_KEY"]
response = openai.Completion.create(
engine=model_name,
prompt=input_data,
max_tokens=max_tokens
)
return {"response": response.choices[0].text.strip()}

def gemini_model(input_data, model_name, max_tokens):
genai.configure(api_key=os.environ["GOOGLE_API_KEY"])
model = genai.GenerativeModel(model_name=model_name)
response = model.generate_content(input_data, max_tokens=max_tokens)
return {"response": response["choices"][0]["message"]["content"]}

@app.route('/model', methods=['POST'])
def model_gateway():
data = request.get_json()
model_type = data.get("model_type")
model_name = data.get("model_name")
input_data = data.get("input_data")
max_tokens = data.get("max_tokens")

if model_type == "openai":
result = openai_model(input_data, model_name, max_tokens)
elif model_type == "gemini":
result = gemini_model(input_data, model_name, max_tokens)
return jsonify(result)

모델 게이트웨이는 접근 제어와 비용 관리 역할을 맡게 됩니다. OpenAI API에 접근하려는 모든 사람에게 조직의 토큰을 직접 제공하는 대신, 모델 게이트웨이를 통해 접근하도록 함으로써 중앙에서 제어된 접근점을 만들 수 있습니다. 이를 통해 토큰 유출을 방지하고 세분화된 접근 제어를 구현하여 특정 사용자나 애플리케이션이 어떤 모델에 접근할 수 있는지를 지정할 수 있습니다. 더불어 API 호출 사용량을 모니터링하고 제한함으로써 남용을 방지하고 비용을 효과적으로 관리할 수 있습니다.

모델 게이트웨이는 rate limit이나 API 장애를 극복하기 위한 폴백 정책을 구현하는 데도 사용할 수 있습니다. 기본 API가 사용할 수 없는 상황일 때, 요청을 다른 폴백 모델로 라우팅하거나, 짧은 대기 후 다시 시도하거나, 다른 우아한 방식으로 장애를 처리할 수 있습니다. 이를 통해 애플리케이션이 중단 없이 원활히 작동하도록 보장합니다.

모델 게이트웨이를 통해 모든 요청과 응답이 이미 흐르고 있으므로, 부하 분산, 로깅, 분석과 같은 추가 기능을 구현하기에도 좋습니다. 일부 게이트웨이 서비스는 캐싱과 가드레일 기능까지 제공하기도 합니다.

게이트웨이는 비교적 구현이 간단하기 때문에, 이미 다양한 기성 게이트웨이가 존재합니다. 예로는 Portkey의 게이트웨이, MLflow AI Gateway, WealthSimple의 llm-gateway, TrueFoundry, Kong, 그리고 Cloudflare 등이 있습니다.

게이트웨이와 라우터가 추가되면서 우리의 플랫폼은 점점 더 흥미로워지고 있네요. 스코어링과 마찬가지로 라우팅도 모델 게이트웨이 안에서 이루어집니다. 스코어링에 사용되는 모델과 마찬가지로 라우팅에 사용되는 모델은 생성에 사용되는 모델보다 일반적으로 더 작습니다.

Step 3 까지 추가된 설계

Step 4. 캐시(Cache)

캐시는 애플리케이션의 지연 시간과 비용을 크게 줄일 수 있습니다.

Prompt Cache

애플리케이션에서 사용되는 많은 프롬프트에는 중복된 텍스트 세그먼트가 포함되어 있습니다. 예를 들어 모든 요청은 동일한 시스템 프롬프트를 공유할 수 있습니다. 프롬프트 캐시는 이러한 중복을 저장하여 재사용할 수 있도록 합니다. 프롬프트 캐시가 없다면 모델은 모든 요청마다 시스템 프롬프트를 다시 처리해야 합니다. 그러나 프롬프트 캐시를 사용하면 첫 번째 요청에서만 시스템 프롬프트를 처리하게 됩니다.

특히 긴 시스템 프롬프트를 사용하는 애플리케이션에서는 프롬프트 캐시를 통해 지연 시간과 비용을 크게 줄일 수 있습니다. 만약 시스템 프롬프트가 1000토큰이고 애플리케이션이 하루에 100만 번의 모델 API 호출을 생성한다면, 프롬프트 캐시는 하루에 약 10억 개의 중복 입력 토큰 처리를 줄여줍니다. 프롬프트 캐시는 긴 문서를 포함하는 요청에서도 유용합니다. 사용자의 요청 중 많은 수가 하나의 긴 문서(예: 책이나 코드베이스)와 관련이 있다면 이 긴 문서를 캐시하여 여러 요청에서 재사용할 수 있습니다.

프롬프트 캐시는 2023년 11월에 Gim et al. 연구진에 의해 도입된 이후 다양한 모델 API에 통합되었습니다. Google은 2024년 6월 Gemini API에 “컨텍스트 캐시(context cache)“라는 이름으로 이 기능을 제공한다고 발표했습니다. 캐시된 입력 토큰은 일반 입력 토큰보다 75% 할인된 비용으로 처리되지만, 캐시 스토리지 비용(작성 시점 기준, 시간당 100만 토큰당 $1.00)이 추가됩니다. (역자 주: OpenAI도 2024년 10월부터 1,024 토큰보다 긴 중복 프롬프트에 대해 캐시를 자동으로 적용하며 50% 할인된 비용으로 처리됩니다.)

Exact Cache

Exact Cache 일반적이고 간단한 캐시 방식입니다. 시스템은 처리된 항목을 저장해 두었다가 동일한 항목이 요청될 때 이를 재사용합니다. 예를 들어 사용자가 특정 제품의 요약을 요청하면 시스템은 해당 제품 요약이 캐시에 있는지 확인해서 있으면 요약을 가져오고 없으면 요약을 생성한 뒤 이를 캐시에 저장합니다.

Exact cache는 벡터 검색 기반 검색에서 중복된 벡터 검색을 피하기 위해서 사용되기도 합니다. 사용자 쿼리가 이미 벡터 검색 캐시에 있다면 새롭게 백터 검색을 수행하지 않고 캐시된 검색 결과를 반환합니다.

캐시는 여러 단계가 필요하거나 시간이 오래 걸리는 작업(예: 검색, SQL 실행, 웹 검색 등)에 특히 유용합니다.

Exact cache는 빠른 검색을 위해 인메모리 스토리지를 사용해 구현할 수 있지만 용량이 제한적이므로 PostgreSQL이나 Redis 같은 데이터베이스 또는 계층형 스토리지를 사용하여 속도와 저장 용량의 균형을 맞출 수 있습니다. 캐시 크기를 관리하고 성능을 유지하기 위해 eviction policy를 도입하는 것이 중요합니다. 일반적으로는 가장 최근에 사용되지 않은 항목 제거(LRU), 사용 빈도가 낮은 항목 제거(LFU), 삽입된 순서대로 제거(FIFO) 등의 정책을 사용합니다.

얼마나 오래 캐시할지는 해당 쿼리가 다시 호출될 가능성에 따라 결정합니다. “내 최근 주문 상태는?” 같은 사용자별 쿼리는 다른 사용자들이 재사용할 가능성이 낮으므로 캐시하지 않는 것이 좋고 “오늘 날씨 어때?” 같은 시간 민감도가 높은 쿼리를 캐시하는 것도 적절하지 않습니다. 질의를 캐시할지 여부를 예측하기 위해 작은 분류 모델을 훈련시키기도 합니다.

Semantic cache

Semantic Cache는 Exact Cache와 달리 들어오는 쿼리가 기존 캐시된 쿼리와 완전히 동일할 필요가 없습니다. Semantic Cache는 비슷한 쿼리를 재사용할 수 있도록 합니다. 예를 들어 한 사용자가 “베트남의 수도는?“이라고 묻고 모델이 “하노이”라는 답을 생성했다면 이후 다른 사용자가 “베트남의 수도는 어디인가요?“이라고 물을 때 “어디인가요" 가 추가되었지만 사실상 동일한 질문이기 때문에 Semantic Cache는 시스템이 “하노이”라는 기존 답변을 재사용하도록 해줍니다.

Semantic Cache가 작동하려면 두 쿼리가 의미적으로 유사한지 신뢰할 수 있는 방법으로 판단해야 합니다. 일반적인 접근법은 임베딩 기반 유사성을 활용하는 것입니다.

1. 각 쿼리에 대해 임베딩 모델을 사용해 임베딩을 생성합니다.

2. 벡터 검색을 사용해 현재 쿼리 임베딩과 가장 가까운 캐시된 임베딩을 찾습니다.

3. 유사도가 설정한 임계값보다 크면 캐시된 쿼리가 현재 쿼리와 동일한 것으로 간주하고 캐시된 결과를 반환합니다. 임계값보다 낮으면 현재 질의를 처리하고 이를 임베딩과 결과와 함께 캐시에 저장합니다.

이 접근법에는 캐시된 질의의 임베딩을 저장하기 위한 벡터 데이터베이스가 필요합니다.

Semantic Cache는 다른 캐싱 기법과 비교했을 때 여러 구성 요소가 실패하기 쉬운 경향이 있기 때문 유용성이 다소 모호할 수 있습니다. 성공 여부는 임베딩의 품질, 제대로 작동하는 벡터 검색, 신뢰할 수 있는 유사도 측정에 달려 있습니다. 적절한 유사도 임계값을 설정하는 것도 까다로우며 많은 시행착오가 필요합니다. 사용자의 쿼리를 시스템이 잘못 판단해 다른 쿼리와 유사하다고 판단하면 캐시에서 잘못된 응답을 가져와 사용자에게 전달될 수 있습니다. Semantic Cache는 벡터 검색이 필요하기 때문에 시간이 걸리고 계산 비용이 높아질 수 있습니다. 벡터 검색의 속도와 비용은 캐시된 임베딩 데이터베이스의 크기에 따라 달라집니다. 그럼에도 불구하고 캐시 히트율이 높다면 Semantic Cache가 유용할 수 있습니다. Semantic Cache를 도입하기 전에 효율성, 비용, 성능 위험을 충분히 평가하는 것이 중요합니다.

이제 캐시 시스템이 추가되면서 플랫폼은 다음과 같은 구조를 갖게 되었습니다. KV Cache와 Prompt Cache는 일반적으로 모델 API 제공자에 의해 구현되므로 이 이미지에는 표시하지 않았습니다.

Step 4 까지 추가된 설계

Step 5. 복잡한 로직과 쓰기 액션 추가

지금까지 논의한 애플리케이션들은 비교적 단순한 흐름을 가지고 있습니다. 파운데이션 모델이 생성한 출력은 대부분 사용자에게 반환됩니다. 만약 애플리케이션의 흐름에 반복(loop)과 조건 분기(conditional branching)가 포함되면 더 복잡해질 수 있습니다.

복잡한 로직

모델의 출력을 조건에 따라 다른 모델로 전달하거나 다음 단계의 입력으로 모델에 다시 입력할 수 있습니다. 작업이 완료되었다고 판단하고 최종 응답을 사용자에게 반환할 때까지 이러한 과정을 계속할 수 있습니다.

시스템에 계획을 세우고 다음에 무엇을 해야 할지 결정할 수 있는 능력을 부여하려면 이러한 구조가 꼭 필요할 수 있습니다. “파리에서 보낼 주말 일정 계획해줘”라는 요청을 생각해봅시다. 모델은 먼저 에펠탑 방문, 카페에서 점심 식사, 루브르 투어 등 잠재적인 활동 목록을 생성할 수 있습니다. 그런 다음 각 활동은 모델에 다시 입력되어 더 구체적인 계획을 생성하게 됩니다. “에펠탑 방문”은 운영 시간 확인, 티켓 구매, 근처 레스토랑 찾기와 같은 하위 작업을 생성하도록 모델을 유도할 수 있습니다. 이런 반복적인 과정을 통해 종합적이고 상세한 일정을 완성할 수 있습니다.

이제 우리 인프라에는 생성된 응답을 컨텍스트 구축(context construction) 단계로 다시 전달하는 화살표가 추가되었고 이 컨텍스트는 다시 모델 게이트웨이의 모델로 전달됩니다.

쓰기 액션

컨텍스트 구축은 읽기 전용 액션입니다. 읽기 전용 액션은 모델이 데이터 소스를 읽어 컨텍스트를 수집할 수 있도록 합니다. 시스템은 쓰기 액션을 통해 데이터를 변경하거나 현실 세계에 영향을 줄 수 있습니다. 모델이 “X에게 Y 메시지를 포함한 이메일을 보내라”는 출력을 생성하면 시스템은 send_email(recipient=X, message=Y)라는 액션을 실행해서 실제로 이메일을 보내는 식입니다.

쓰기 액션은 시스템의 능력을 크게 확장시키고 워크플로우 전체를 자동화할 수 있습니다. 예를 들어 잠재 고객 조사, 연락처 찾기, 이메일 초안 작성, 첫 이메일 발송, 응답 읽기, 후속 조치, 주문 추출, 새로운 주문을 데이터베이스에 업데이트하는 등의 고객 발굴 과정 전체를 자동화할 수 있습니다.

AI가 우리의 삶에 자동으로 영향을 주는 능력을 갖는 것은 두려운 일이기도 합니다. 인턴에게 실수로 프로덕션 데이터베이스를 삭제할 권한을 주지 않는 것처럼, 신뢰할 수 없는 상태의 AI에게 은행 송금을 마음대로 실행할 권한을 주어서는 안 됩니다. 시스템의 능력에 대한 신뢰 확보와 보안 조치는 필수입니다. 시스템이 악의적인 행위자들로부터 보호받아 유해한 작업을 수행하지 않도록 해야 합니다.

AI 시스템은 다른 소프트웨어 시스템과 마찬가지로 사이버 공격에 취약하지만, 프롬프트 인젝션(Prompt injection) 이라는 또 다른 약점도 있습니다. 프롬프트 인젝션은 모델의 입력 프롬프트를 조작해 바람직하지 않은 행동을 유도하는 공격입니다.

많은 기업이 두려워하는 시나리오 중 하나는 AI 시스템에 내부 데이터베이스 접근 권한을 부여했을 때 공격자가 이 시스템을 속여 데이터베이스에서 민감한 정보를 유출시키는 것입니다. 시스템이 데이터베이스에 대한 쓰기 권한까지 가지고 있다면 공격자는 시스템을 속여 데이터를 손상시키거나 변경할 수도 있습니다.

AI를 활용하려는 모든 조직은 안전성과 보안을 진지하게 생각해야 합니다. 하지만 AI 시스템이 현실 세계에서 행동할 권한을 절대 가져서는 안 된다는 뜻은 아닙니다. AI 시스템은 실패할 수 있지만, 인간도 실패할 수 있습니다. 언젠가 충분한 보안 조치가 이루어진다면 자율적인 AI 시스템을 완전히 신뢰할 수 있는 날이 오리라 기대합니다.

가시성 (Observability)

가시성(Observability)을 별도의 섹션으로 다루고 있지만, 가시성은 나중에 추가하는 것이 아니라 플랫폼 설계 초기부터 통합되어 고려해야 합니다. 가시성은 프로젝트의 규모와 관계없이 중요하며 시스템이 복잡해질수록 그 중요성은 더욱 커집니다. 가시성의 모든 세부사항을 블로그 글에서 다루는 것은 불가능하기 때문에 모니터링의 세 가지 주요 축인 메트릭(Metrics), 로그(Logs), 트레이스(Traces)에 대한 간략한 설명만 하려고 합니다. 사용자 피드백, 드리프트 탐지, 디버깅과 같은 주제는 여기서 구체적으로 다루지 않습니다.

Metrics

모니터링에 대해 이야기하면 대부분의 사람들은 메트릭(Metrics)을 먼저 떠올립니다. 어떤 메트릭을 추적할지는 시스템에서 무엇을 추적하고 싶은지, 즉 애플리케이션의 특성에 따라 달라집니다. 일반적으로는 두 가지 메트릭을 추적하게 됩니다. 시스템 메트릭(System Metrics)과 모델 메트릭(Model Metrics)입니다.

시스템 메트릭은 전체 시스템의 상태를 보여줍니다. 일반적인 시스템 메트릭으로는 처리량(Throughput), 메모리 사용량, 하드웨어 usage, 서비스 가용성/업타임 등이 있습니다. 이것들은 모든 소프트웨어 엔지니어링 애플리케이션에 공통적인 메트릭입니다.

모델 메트릭은 모델의 성능을 평가합니다. 정확도(Accuracy), 유해성(Toxicity), 환각률(Hallucination Rate) 등 입니다. 애플리케이션 파이프라인의 각 단계도 고유 메트릭을 가질 수 있습니다. RAG 애플리케이션에서는 문맥 관련성(Context Relevance)과 문맥 정밀도(Context Precision)를 사용해 검색 품질을 평가할 수 있습니다. 벡터 데이터베이스는 데이터 인덱싱에 필요한 스토리지 크기와 쿼리에 걸리는 시간을 기준으로 평가할 수 있습니다.

모델의 출력이 실패할 수 있는 방식에는 여러 가지가 있습니다. 이러한 문제를 식별하고 모니터링할 수 있는 메트릭을 개발하는 것이 매우 중요합니다. 모델이 얼마나 자주 타임아웃이 발생하는지, 빈 응답을 반환하는지, 잘못된 형식의 응답을 생성하는지 등을 추적할 수 있습니다. 모델이 민감한 정보를 노출할 가능성이 우려된다면 이를 추적할 방법도 마련해야 합니다.

질문 길이, 문맥 길이, 응답 길이와 같은 길이 관련 메트릭은 모델의 동작을 이해하는 데 유용합니다. 한 모델이 다른 모델보다 더 장황한지, 특정 유형의 쿼리가 더 긴 응답을 생성할 가능성이 있는지 등의 메트릭은 특히 애플리케이션의 변화를 감지하는 데 도움이 됩니다. 예를 들어 평균 쿼리 길이가 갑자기 줄어든다면 조사해야 할 근본적인 문제가 있다고 예상할 수 있습니다. 길이 관련 메트릭은 지연 시간(latency)과 비용(cost)을 추적하는 데도 중요합니다. 컨텍스트와 응답이 길어지면 일반적으로 지연 시간과 비용이 증가합니다.

지연 시간을 추적하는 것은 사용자 경험을 이해하는 데 필수적입니다. 일반적인 지연 시간 메트릭에는 다음이 포함됩니다:

Time to First Token (TTFT): 첫 번째 토큰이 생성되는 데 걸리는 시간.

Time Between Tokens (TBT): 각 토큰 생성 간의 시간 간격.

Tokens Per Second (TPS): 토큰이 생성되는 속도.

Time Per Output Token (TPOT): 각 출력 토큰을 생성하는 데 걸리는 시간.

Total Latency: 응답을 완료하는 데 필요한 총 시간.

비용 추적도 필요합니다. 비용 관련 메트릭으로는 쿼리 수와 입력 및 출력 토큰의 양이 포함됩니다. Rate limit이 있는 API를 사용하는 경우 초당 요청 수를 추적하여 할당된 제한을 초과하지 않도록 하고 서비스 중단을 방지하는 것이 중요합니다.

메트릭을 계산할 때는 스팟 체크(spot checks)와 전수 검사(exhaustive checks) 중 선택할 수 있습니다. 스팟 체크는 데이터의 일부를 샘플링하여 문제를 빠르게 식별하는 방식이고 전수 검사는 모든 요청을 평가하여 포괄적인 성능 관점을 제공합니다. 시스템 요구사항과 사용 가능한 자원에 따라 선택할 수 있고 두 방식을 조합해서 균형 잡힌 모니터링 전략을 세울 수도 있습니다.

메트릭을 계산할 때 사용자, 릴리스, 프롬프트 및 체인 버전과 타입, 시간 등 관련 축(axis)으로 세분화할 수 있어야 합니다. 이러한 세분화는 성능 변동을 이해하고 특정 문제를 식별하는 데 도움이 됩니다.

Logs

로그의 철학은 간단합니다 — 모든 것을 기록하라. 시스템 설정, 쿼리, 출력, 중간 출력 등을 모두 기록하세요. 컴포넌트가 시작할 때, 끝날 때, 충돌이 발생할 때 등을 기록하십시오. 로그를 기록할 때는 해당 로그가 시스템의 어디에서 생성되었는지 알 수 있도록 태그와 ID를 부여하세요. 모든 것을 기록한다는 것은 로그의 양이 매우 빠르게 증가할 수 있음을 의미합니다. 자동 로그 분석 및 로그 이상 탐지를 위한 많은 도구가 AI에 의해 구동됩니다.

로그를 수작업으로 처리하는 것은 불가능하지만, 프로덕션 데이터를 매일 직접 확인해보는 것은 유용합니다. 이를 통해 사용자가 애플리케이션을 어떻게 사용하는지 파악할 수 있습니다. Shankar et al.(2024) 연구에 따르면 개발자는 더 많은 데이터와 상호작용하면서 좋은 출력과 나쁜 출력에 대한 인식이 바뀌고 이를 통해 더 나은 응답을 위해 프롬프트를 고치거나 나쁜 응답을 포착하기 위해 평가 파이프라인을 개선 할 수 있다고 합니다.

Traces

트레이스는 요청이 시스템의 다양한 컴포넌트와 서비스를 거쳐 실행된 경로를 자세히 기록하는 것을 의미합니다. AI 애플리케이션에서의 트레이스는 사용자가 쿼리를 보낸 시점부터 최종 응답이 반환되기까지의 전체 과정을 보여줍니다. 시스템이 수행한 작업, 검색된 문서, 모델에 전달된 최종 프롬프트 등이 포함됩니다. 각 단계가 소요한 시간과 측정 가능한 경우 관련 비용도 나타내야 합니다. 아래는 Langsmith의 trace 예시입니다.

이상적으로는 각 쿼리가 시스템을 거치며 변환되는 과정을 단계별로 추적할 수 있어야 합니다. 쿼리가 실패한 경우 잘못 처리된 단계, 검색된 문맥이 적절하지 않았던 부분, 또는 모델이 잘못된 응답을 생성한 부분 등 어떤 단계에서 문제가 발생했는지 정확히 찾아낼 수 있어야 합니다.

AI Pipeline Orchestration

AI 애플리케이션은 여러 모델, 데이터베이스, 그리고 다양한 도구에 대한 접근 권한을 포함하게 되면서 상당히 복잡해질 수 있습니다. 오케스트레이터는 다양한 컴포넌트가 어떻게 결합(체인)되어 전체 애플리케이션 흐름을 만드는지 설정할 수 있게 합니다. 크게 보면 오케스트레이터는 두 단계로 작동합니다.

  1. 컴포넌트 정의

오케스트레이터에 시스템이 사용할 컴포넌트들을 정의합니다. 컴포넌트에는 텍스트 생성, 라우팅, 스코어링 모델, 데이터베이스 및 검색 소스, 그리고 시스템이 수행할 수 있는 액션들이 포함됩니다. 모델 게이트웨이와 직접 통합할 수도 있고 일부 오케스트레이터 도구는 게이트웨이 역할도 함께 지원합니다. 많은 오케스트레이터는 평가와 모니터링 도구와의 통합도 지원합니다.

2. 체이닝(또는 파이프라이닝)

체이닝은 간단히 말해 함수의 조합(composition)입니다. 오케스트레이터에 사용자 쿼리를 받은 후 작업을 완료하기까지 시스템이 수행해야 하는 단계의 순서를 알려주는 것입니다. 아래는 파이프라인이 어떻게 생겼는지에 대한 예시입니다.

• 원본 쿼리를 전처리합니다.

• 처리된 쿼리를 기반으로 관련 데이터를 검색합니다.

• 검색된 데이터와 원본 쿼리를 결합하여 프롬프트를 생성합니다.

• 프롬프트를 모델에 보내 응답을 생성합니다.

• 응답을 평가합니다.

• 평가 후 응답이 양호하다고 판단되면 사용자에게 반환합니다. 그렇지 않으면 쿼리를 인간 운영자에게 라우팅합니다.

오케스트레이터는 각 단계 간 데이터를 전달하는 역할을 하며 현재 단계의 출력이 다음 단계에서 예상하는 형식과 일치하도록 돕는 도구를 제공합니다.

애플리케이션의 파이프라인을 설계할 때 지연 시간 요구사항이 엄격하다면, 가능한 한 많은 작업을 병렬로 처리하도록 설계합니다. 라우팅 컴포넌트(쿼리를 어디로 보낼지 결정하는 역할)와 PII(개인 식별 정보) 제거 컴포넌트가 있다면 두 작업을 동시에 수행할 수 있을 것입니다.

LangChain, LlamaIndex, Flowise, Langflow, Haystack 등 많은 AI 오케스트레이션 도구들이 있습니다. 각 도구는 자체 API를 제공하므로 여기서는 실제 코드를 보여주지 않겠습니다.

프로젝트를 시작할 때 곧바로 오케스트레이션 도구를 사용하고 싶을 수 있지만, 처음에는 이러한 도구 없이 애플리케이션을 구축하는 것이 오히려 좋습니다. 외부 도구는 복잡성을 추가할 수 있고 오케스트레이터는 시스템 작동 방식의 중요한 디테일을 추상화하기 때문에 시스템을 이해하거나 디버깅하기 어렵게 합니다.

애플리케이션 개발 과정이 더 진전되면 오케스트레이터를 사용하는 것이 작업을 더 쉽게 만들어 줄 수 있다고 판단할 수 있습니다. 오케스트레이터를 평가할 때 고려해야 할 세 가지 측면은 다음과 같습니다.

  1. 통합성과 확장 가능성

오케스트레이터가 현재 사용 중이거나 앞으로 채택할 가능성이 있는 컴포넌트를 지원하는지 확인하세요. 예를 들어 Llama 모델을 사용하려 한다면 오케스트레이터가 이를 지원하는지 확인해야 합니다. 모델, 데이터베이스, 프레임워크가 매우 다양하므로, 모든 것을 지원하는 오케스트레이터는 없다는 점을 명심하세요. 따라서 오케스트레이터의 확장 가능성도 고려해야 합니다. 특정 컴포넌트를 지원하지 않을 경우 이를 변경해서 직접 추가하는 것이 얼마나 쉽게 가능한지 평가하세요.

2. 복잡한 파이프라인 지원

애플리케이션이 복잡해지면서 여러 단계와 조건 논리가 포함된 정교한 파이프라인을 관리해야 할 수도 있습니다. 분기(branching), 병렬 처리, 오류 처리와 같은 고급 기능을 지원하는 오케스트레이터는 이러한 복잡성을 효율적으로 관리하는 데 도움이 됩니다.

3. 사용 편의성, 성능, 스케일 확장성

오케스트레이터의 사용 편의성을 고려하세요. 직관적인 API, 포괄적인 문서, 강력한 커뮤니티 지원을 제공하는 도구를 찾으세요. 이는 학습 곡선을 크게 줄여줍니다. 숨겨진 API 호출이 일어나거나 지연 시간이 추가되는 오케스트레이터는 피하세요. 또한 애플리케이션, 개발자, 트래픽이 증가함에 따라 오케스트레이터가 효과적으로 스케일을 확장할 수 있는지 확인하세요.

결론

지금까지 가장 단순한 아키텍처에서 시작해서 애플리케이션의 복잡성이 증가함에 따라 컴포넌트를 점차 추가해 나가는 과정을 다뤘습니다. 각 추가 단계는 고유의 이점과 어려움이 있기 때문에 신중하게 고려하고 구현해야 합니다.

시스템을 모듈화하고 유지 관리를 쉽게 하기 위해서 컴포넌트를 분리하는 것이 중요합니다. 하지만 꼭 모든 컴포넌트를 다 나누어야 하는 것은 아닙니다. 예를 들면 모델 게이트웨이는 가드레일과 기능을 공유할 수 있습니다. 캐시는 벡터 검색이나 추론 서비스와 같은 다양한 컴포넌트 안에 구현될 수 있습니다.

글이 생각보다 훨씬 길어졌지만 가시성(observability), 컨텍스트 구축(context construction), 복잡한 로직, 캐시, 그리고 가드레일과 관련된 많은 세부 사항을 충분히 다루지 못했습니다. 이러한 컴포넌트에 대해서는 다가올 책 AI Engineering에서 더 깊이 다룰 예정입니다. 또한, 이 글에서는 모델 서빙 방식에 대해 다루지 않았습니다. 대부분의 사람들이 외부 API에서 제공하는 모델을 사용할 것이라는 가정 때문입니다. AI Engineering에서는 추론(inference)과 모델 최적화에 대한 전용 챕터도 포함될 예정입니다.

--

--

Tommy Park
Tommy Park

Written by Tommy Park

AI/ML Software Engineer @Karrot

No responses yet