지식 베이스 Q&A 데모는 보통 문서 분할, 검색, 모델 호출의 세 단계만 연결하면 됩니다. 하지만 실제로 배포하면 문제가 훨씬 구체적이 됩니다. 답이 문서에 분명히 있는했는데도 모델이 틀리게 답하는 이유는 무엇일까요? 사용자가 페이지를 새로 고쳤는데 작업이 사라진 이유는 무엇일까요? 도구 요청이 타임아웃된 뒤 바로 다시 시도해도 될까요? 온라인 사용자가 늘어나면 연결, 데이터베이스, 모델 할당량 중 어느 것이 먼저 한계에 도달할까요?
이 문제들은 정보 검색, 분산 시스템, 모델 애플리케이션을 아우르는 영역입니다. RAG, Rerank, Checkpoint 같은 용어를 각각 외워서는 이들이 어떻게 연결되는지 설명할 수 없습니다.
이 글에서는 기업 지식 베이스 어시스턴트를 예로 들겠습니다. 사용자가 문서를 업로드하고, 문서에 대해 여러 차례 질문하며, 필요할 때 시스템이 데이터를 조회하고 도구를 호출해 보고서를 생성합니다. 이 하나의 작업을 따라가며 검색이 어떻게 증거를 제공하고, 컨텍스트가 어떻게 증거를 구성하며, Agent가 실행을 어떻게 진행하고, 시스템이 실패 후 어떻게 작업을 계속하는지 설명하겠습니다.
문 중 제시하는 후보 수, 예산, 용량 숫자는 모두 설계를 설명하기 위한 예시입니다. 실제 데이터로 평가하고 부하 테스트를 통해 검증해야 하며, 그대로 프로덕션 설정으로 사용할 수는 없습니다.
1. 먼저 지식 흐름, 실행 흐름, 상태를 구분하자
먼저 시스템의 역할을 다음과 같이 나눌 수 있습니다. 그림의 모듈은 논리적 경계를 나타내며, 작은 프로젝트라면 모두 하나의 서비스에 배포해도 됩니다.
여기서 혼동하기 쉬운 두 가지 사실이 있습니다.
첫째, RAG와 Agent를 결합할 수는 있지만 서로를 필요로 하는 것은 아닙니다. 고정된 “검색 후 답변” 흐름은 하나의 Workflow가 될 수 있습니다. 개방형 조사 작업에서는 Agent가 다음에 무엇을 조회할지 결정할 수 있고, 검색은 그중 도구 하나일 뿐입니다.
둘째, 모델 호출 한 번이 넘겨받은 메시지에만 의존하더라도 전체 애플리케이션에는 상태가 있습니다. 누가 사용자이고, 현재 작업이 어디까지 진행됐으며, 어떤 도구가 이미 성공했고, 예산이 얼마 남았는지는 모두 시스템이 관리해야 합니다. 모델 컨텍스트는 그중 일부일 뿐입니다.
따라서 설계할 때는 먼저 세 가지 질문에 답해야 합니다. 지식은 어디에서 오고, 작업은 어떻게 진행되며, 실행에 관한 사실은 어디에 저장해야 하는가? 컴포넌트 선택은 이 질문들 뒤에 합니다.
2. 지식 적재: 파싱 품질이 검색의 상한을 결정한다
문서를 지식 베이스에 넣는 첫 단계는 파싱입니다. PDF를 그대로 거대한 문자열 하나로 변환하면 제목 계층, 표 구조, 그림 캡션, 페이지 번호가 손실될 수 있습니다. 이후 Embedding이 아무리 강해도 이미 사라진 사실을 복원할 수는 없습니다.
더 적절한 중간 표현은 “문서—섹션—콘텐츠 블록”입니다. 콘텐츠 블록은 문단, 표, 이미지 또는 코드일 수 있으며, 출처 위치도 함께 보존해야 합니다. 분할된 조각에는 다음 정보를 담을 수 있습니다.
본문은 검색과 답변에 사용하고, 문서 버전은 최신본과 이전본의 충돌을 처리하며, 페이지와 위치는 인용에 씁니다. 테넌트와 권한 정보는 접근 제어에 사용합니다. 이 정보들은 입고 시점에 서로 연결되어 있어야 합니다.
정제 과정도 지나치게 무분별해서는 안 됩니다. 반복되는 머리글과 내비게이션 바는 보통 노이즈지만, 표의 단위, 계약의 부정 표현, 페이지 하단 주석의 예외 조건은 정답에 직접 영향을 줄 수 있습니다. 정제의 목표는 방해 요소를 없애면서 의미를 보존하는 것입니다.
같은 지식에 전체 텍스트 인덱스와 벡터 인덱스를 모두 구축할 수 있습니다. 문서를 업데이트할 때는 두 인덱스가 언제부터 반영되는지도 고려해야 합니다. 전체 텍스트 인덱스는 이미 v3이고 벡터 인덱스는 여전히 v2라면, 한 번의 질의에 서로 모순되는 버전이 함께 섞일 수 있습니다.
한 가지可行的 설계는 인덱스 구축에 버전 번호를 부여하고, 구축이 완료된 뒤 검색 가능한 버전을 전환하며, 각 후보에 문서 버전을 유지하는 방식입니다. 삭제와 권한 철회도 인덱스와 캐시에 동시에 반영해야 하며, 원본 파일만 수정해서는 안 됩니다.
3. Chunk의 핵심은 검색 단위와 의미적 완전성
고정 길이 분할은 구현하기 쉽지만, 문장이 끝났는지 또는 규칙과 예외의 관계를 이해하지 못합니다.
예를 들어 환불 규칙에는 두 가지 조건이 있습니다. 구매 시점이 기한 이내이고, 제품이 아직 활성화되지 않은 경우입니다. 분할 경계가 둘 사이에 정확히 걸리면 한쪽 블록만 리콜되어 잘못된 결론을 내릴 수 있습니다.
분할 방식마다 적합한 용도가 다릅니다.
| 방식 | 적합한 콘텐츠 | 주의할 문제 |
|---|---|---|
| 고정 길이 | 구조가 약하고 빠른 기준선 구축이 필요한 텍스트 | 문장과 조건이 잘리는 문제 |
| 재귀 분할 | 일반적인 장문을 문단과 문장 단위로 단계별 분할 | 구분자가 반드시 완전한 의미 경계를 뜻하지는 않음 |
| 구조 인식 | 기술 문서, 계약, 제목이 있는 Markdown | 파싱 품질에 의존하며 너무 긴 섹션을 처리해야 함 |
| 의미 기반 | 주제 전환이 뚜렷하고 구조가 일정하지 않은 콘텐츠 | 계산 단계가 추가되며 효과는 여전히 평가해야 함 |
오버랩은 경계 문제를 완화할 수 있지만 저장 공간, 중복 리콜, 컨텍스트 오버헤드를 늘립니다. 서로 멀리 떨어진 조항을 함께 리콜할 수 있다는 것도 보장하지 않습니다.
구조가 명확한 장문에는 Parent–Child Retrieval을 사용할 수 있습니다. 작은 조각으로 검색하고, 적중하면 소속 섹션 또는 필요한 인접 블록을 함께 보충합니다. 이렇게 하면 검색의 정밀도를 유지하면서 답변에 필요한 컨텍스트도 보존할 수 있습니다. 보충 범위는 여전히 token 예산의 통제를 받아야 하며, 한 번이라도 적중하면 장 전체를 모델에 넣어서는 안 됩니다.
문서가 수십만 자에 이르면 문서 요약, 섹션 요약, 원본 조각으로 이루어진 계층형 인덱스도 구축할 수 있습니다. 계층적 라우팅은 검색 범위를 줄이지만, 첫 단계에서 잘못된 섹션을 선택하면 이후에는 정답을 찾을 기회를 잃게 됩니다. 따라서 교차 섹션 검색이나 전역 검색으로 대체할 수 있는 경로를 남겨 두는 편이 좋습니다.
검색에 사용하는 단위는 정답을 생성할 때 제공하는 단위보다 작아도 됩니다. 이 구분은 하나의 청크 크기를 고정하는 것보다 유용합니다.
4. 하이브리드 검색: 정확한 매칭과 의미 매칭을 보완하다
사용자가 “컴퓨터가 멈추면 어떻게 하죠?”라고 물으면, 문서에는 “컴퓨터 응답 불가 처리 방법”이라고 되어 있을 수 있습니다. 벡터 검색은 두 표현의 의미적 연관성을 알아챌 수 있습니다. 반대로 사용자가 ORA-00942, 주문 번호 또는 API 이름을 입력하면 키워드 매칭이 더 직접적입니다.
BM25의 주요 요소에는 단어 빈도, 역문서 빈도, 문서 길이 정규화가 포함됩니다. 단어 빈도의 기여도는 점차 포화됩니다. 드문 단어일수록 일반적으로 구분력이 높고, 길이 정규화는 긴 문서가 단순히 단어 빈도만으로 유리해지는 문제를 완화합니다. Lucene BM25Similarity 문서의 실제 효과도 토크나이저, 필드 설정, 인덱스 단위에 따라 달라지므로, “BM25를 사용했다”는 이유만으로 정확한 매칭이 반드시 성공한다고 보아서는 안 됩니다.
벡터 검색은 쿼리와 내용을 벡터로 매핑해, 선택한 거리 또는 유사도를 기준으로 후보를 찾습니다. 대규모 검색은 보통 근사 최근접 이웃 알고리즘을 사용하므로, 재현율, 지연 시간, 인덱스 비용 사이에서 절충해야 합니다. 쿼리 벡터와 문서 벡터는 동일한 검색 모델의 인코딩 규약을 따라야 하며, 모델을 업그레이드할 때는 재임베딩과 인덱스 전환 계획도 세워야 합니다.
두 경로의 결과를 합칠 때 원래 점수를 단순히 더하는 것은 권장되지 않습니다. BM25의 12.8과 벡터 유사도 0.86은 서로 같은 척도가 아닙니다. 평가 데이터에서 점수를 보정할 수도 있고, 순위 기반 Reciprocal Rank Fusion을 먼저 사용할 수도 있습니다.
RRF(d) = Σ 1 / (k + rank_i(d))
여기서 합산되는 대상은 문서 d가 등장한 결과 목록뿐이며, 순위는 1부터 시작하고 k는 스무딩 상수입니다. 예를 들어 k = 60일 때 어떤 결과가 두 경로에서 각각 1위와 2위라면, 그 점수는 1/61 + 1/62입니다. RRF는 서로 다른 원시 점수의 척도를 직접 맞추지 않아도 된다는 장점이 있지만, 점수 차이 정보는 버리며 모든 데이터셋에서 최적이라고 보장하지도 않습니다. Elasticsearch RRF 문서
전체 흐름은 다음과 같을 수 있습니다.
쿼리 재작성이 매번 필요한 것은 아닙니다. 사용자가 “그건 왜 느려졌죠?”라고 물으면 어떤 대상을 말하는지 대화 기록에서 보완해야 합니다. 하지만 사용자가 이미 전체 오류 코드를 제시했다면, 추가 재작성은 지연 시간을 늘리거나 핵심 용어를 바꿀 수 있습니다. 원래 질문을 보존하고 재작성 전후의 내용을 기록해야 합니다.
권한 필터는 신뢰할 수 있는 신원을 바탕으로 서버 측에서 생성하고, 가능한 한 두 검색 경로 모두에 내려보내야 합니다. 외부 재순위화 서비스나 모델에 콘텐츠를 넘기기 전에도 접근 권한을 다시 확인해야 합니다. 권한이 없는 콘텐츠를 먼저 모델에 전달한 뒤 마지막에 인용만 숨기는 방식은 너무 늦습니다.
5. Rerank은 순위 정확도를 높이는 동시에 지연 시간의 원인이 되기도 합니다
일반적인 Bi-Encoder는 쿼리와 문서를 각각 인코딩하므로 문서 벡터를 미리 계산할 수 있습니다. Cross-Encoder는 쿼리와 후보 내용을 함께 읽고 둘의 상호작용을 통해 관련성 점수를 매기므로, 소수의 후보를 다시 정렬하는 데 적합합니다. 질의와 후보를 한 쌍씩 처리해야 하므로 대규모 말뭉치에 직접 적용하기는 부적합합니다. Sentence Transformers: Retrieve & Re-Rank
그래서 “먼저 리콜한 뒤 재순위화”하는 것입니다. 재순위화기는 이미 리콜된 후보만 개선할 수 있고, 앞 단계에서 놓친 증거를 되찾을 수는 없습니다.
Rerank이 너무 느리다면 먼저 후보 수, 후보 길이, 대기열 대기 시간을 확인해야 합니다. 중복 후보를 줄이고, 질문의 난이도에 따라 후보 수를 조절하고, 명백히 무관한 긴 텍스트를 제한한 다음, 배치 처리, 더 작은 모델, 양자화 또는 추론 백엔드 최적화를 평가할 수 있습니다.
후보가 적다고 항상 좋은 것은 아닙니다. 너무 일찍 가지치기하면 증거를 잃을 수 있고, 배치가 너무 크면 처리량이 올라가더라도 트래픽이 적은 요청이 더 오래 기다릴 수 있습니다. 최적화는 검색 품질과 꼬리 지연 시간을 함께 살펴야 합니다.
캐시 키는 최소한 쿼리, 문서 콘텐츠 버전, 재순위화 모델 버전을 구분해야 합니다. 요청이 캐시된 결과를 받더라도 현재 권한 검사는 계속해야 합니다.
재순위화 시간이 초과되면 혼합 검색 순위로 대체할 수 있지만, 이는 품질 저하입니다. 일반적인 자료 조회라면 허용할 수 있지만, 증거 요구가 엄격한 프로세스에서는 명확히 실패하거나 추가 정보를 요청해야 할 수도 있습니다. 이러한 폴백 전략은 이후 품질 변화를 설명할 수 있도록 Trace에 기록해야 합니다.
6. 검색이 적중한 뒤에는 증거가 실제로 답변에 들어갔는지도 보장해야 합니다
“제대로 검색했는데 답이 틀렸다”고 해도 모순이 아닙니다. 올바른 조각이 Rerank 이후 후보에서 밀려났거나, Prompt를 구성할 때 잘렸거나, 오래된 문서와 함께 컨텍스트에 들어가 모델이 잘못 해석했을 수 있습니다.
문제를排查할 때는 다음 집합들을 확인할 수 있어야 합니다.
检索得到的候选
↓
重排后的候选
↓
实际选入上下文的证据
↓
最终发送的模型输入
↓
回答中的结论与引用
Context Builder의 역할은 바로 이 중간 단계들을 완성하는 것입니다. 중복 제거, 필요한 컨텍스트 보완, 문서 버전 선택, 충돌 처리, token 제어, 그리고 조각에 추적 가능한 인용 식별자를 연결하는 것입니다.
v2에 “7일 환불”, v3에 “14일 환불”이라고 되어 있다면 유사도만으로 선택해서는 안 됩니다. 시스템은生效 시간, 사용자가 현재 규칙과 과거 규칙 중 무엇을 물었는지, 출처의 권위성을 함께 고려해 버전을 결정해야 합니다. 충돌을 해소할 수 없다면 이를 명확히 드러내야 합니다.
모델 용량을 고려한 예산은 다음과 같이 표현할 수 있습니다.
系统规则 + 工具定义 + 历史摘要 + 近期消息 + 检索证据
+ 为输出及模型所需额外 token 预留的空间
≤ 对应模型的有效上下文限制
정확한 token 수와 입력·출력 제한은 모델 인터페이스에 맞춰 확인해야 합니다. 제한을 넘으면 일부 인터페이스는 오류를 반환하고, 일부 시스템은 전송 전에 잘라냅니다. 모델이 항상 자동으로 처리한다고 가정해서는 안 됩니다.
《Lost in the Middle》은 테스트한 모델과 과제에서 관련 정보의 위치가 긴 컨텍스트 성능에 영향을 미쳤다고 보고했습니다. 가운데에 놓인 정보는 효과적으로 활용되기 어려울 수 있습니다. 즉, 윈도우 길이가 곧바로 유효 활용 능력을 뜻하지는 않지만, 이를 근거로 모든 신규 모델이 같은 수준의 위치 편의를 가진다고 단정할 수는 없습니다. 논문 원문
엔지니어링 관점에서는 우선 무관한 정보를 줄이고, 조건을 온전히 보존하며, 증거 순서를 테스트해야 합니다. 압축과 요약은 token을 절약할 수 있지만 부정 표현, 수치, 예외 조항을 놓칠 수 있으므로 원본 증거와의 매핑을 유지해야 합니다.
인용도 검증해야 합니다. 링크가 열린다는 사실은 출처가 존재한다는 것만 증명합니다. 그 링크가 바로 옆에 있는 결론을 실제로 뒷받침하는지도 확인해야 합니다. 계산이 포함되면 프로그램이 추출한 데이터를 계산하게 하고, 모델이 결과를 해석하게 할 수 있습니다.
7. 긴 대화와 멀티모달 문서에는 서로 다른 구성 방식이 필요합니다
전체 대화 기록을 저장한다고 해서 매 턴마다 그 entirety를 모델에 전송해야 하는 것은 아닙니다. 채팅 기록은 감사와 제품 화면 표시에 쓰이고, Prompt는 현재 추론을 위한 것입니다.
사용 가능한 컨텍스트 조합은 안정적인 규칙, 현재 작업 요약, 필요한 장기 사실, 최근 몇 턴의 대화, 이번 검색의 증거입니다. 작업 요약에는 사용자 목표, 제약 조건, 완료된 동작, 실패 원인, 미완료 항목, 증거 위치를 특히 담아야 합니다.
“사용자가 고객에게 연락하기를 원함”과 “이메일이 성공적으로 전송됨”은 반드시 따로 저장해야 합니다. 의도를 실행 사실로 요약하면 이후 판단이 바로 오염됩니다. 핵심 상태는 모델의 자유로운 요약이 아니라 지속화된 도구 결과와 비즈니스 데이터에서 가져와야 합니다.
멀티모달 문서를 일률적으로 순수 텍스트로 압축해서도 안 됩니다. 표는 머리글, 단위, 행과 열의 관계를 보존해야 합니다. 이미지는 설명을 이용해 검색을 보조하고, 답변할 때 필요하면 원본을 다시 읽을 수 있습니다. 차트와 스캔 문서는 페이지와 영역 위치를 저장해 검증하기 쉽게 해야 합니다. 이미지 설명과 OCR은 모두 오류가 생길 수 있으므로, 핵심 수치는 원본 출처에서 확인해야 합니다.
십만 행에 이르는 판매 데이터에서 질문이 “각 지역 매출 합계는 얼마인가?”라면, 권한 부여, 접근 제한, 검증을 거치는 SQL이나 데이터 분석 도구가 더 적합합니다. Embedding은 관련 자료를 찾는 데 도움이 되지만, 결정론적인 집계 계산을 대신할 수는 없습니다.
마찬가지로 “이 계약의 위약금은 얼마인가?”는 관련 조항을 찾는 문제지만, “이 계약 전체의 주요 위험을 요약해 달라”는 질문은 여러 섹션을 다뤄야 합니다. 국소적인 사실 질문과 전체 요약에는 서로 다른 정보 획득 전략이 필요합니다.
8. Agent Runtime: 모델이 결정하게 하고 시스템이 실행을 통제하게 한다
Workflow의 경로는 주로 코드가 미리 정의합니다. Agent는 모델이 다음 동작을 동적으로 결정하게 합니다. 고정된 문서 처리 흐름에는 Workflow를, 개방형 장애 조사에는 통제된 노드 안에서 Agent를 사용할 수 있으며, 둘을 결합할 수도 있습니다. Anthropic: Building effective agents
Agent의 개념적 반복 구조는 복잡하지 않습니다.
실제 엔지니어링 작업은 이 반복 구조의 주변에 있습니다. 런타임은 총 단계 수, 도구 호출 횟수, 시간, token, 비용을 제한해야 합니다. 모델 호출과 도구 호출 각각에 시간outs를 설정하고, 취소 신호를 전파하며, 각 단계의 상태를 기록하고, 더 이상 진전이 없으면 중단해야 합니다.
예를 들어 동일한 도구 호출과 정규화된 파라미터 및 결과가 연속으로 반복되는지 감지할 수 있습니다. 하지만 반복 호출이 반드시 무한 루프를 뜻하지는 않습니다. 실행 중인 작업을 조회하면 같은 상태가 여러 번 반환될 수 있습니다. 판단에는 도구의 의미, 합리적인 폴링 간격, 전체 종료 기한을 함께 반영해야 합니다.
이러한 예산은 Run에 속하고, 복구 후에도 이어져야 합니다. 프로세스가 재시작되면 카운터가 0으로 초기화되는데, 그러면 제한 자체가 무의미해집니다. 병렬 단계에서는 원자적으로 예산을 먼저 예약한 뒤 실제 사용량에 따라 정산해야 합니다. 여러 Worker가 동시에 같은 잔액을 보는 일을 막아야 합니다.
도구 입력도 계층별로 검사해야 합니다. JSON을 파싱할 수 있다는 것은 문법이 올바르다는 뜻일 뿐입니다. Schema를 만족한다는 것도 업무상 허용된다는 뜻은 아닙니다. 사용자가 어떤 문서를 볼 권한이 있다고 해서 그 문서를 수정하거나 외부로 보낼 권한이 있는 것은 아닙니다. 실행 계층에서 업무 검증과 권한 부여를 독립적으로 수행해야 합니다.
파라미터 복구는 구체적인 오류를 모델에 반환할 수 있지만, 시도 횟수는 제한해야 합니다. 네트워크 지연처럼 재시도 가능한 오류는 전체 종료 기한을 지켜야 하고, 무작위 지터를 더한 백오프를 적용하며, 적절한 경우 Retry-After를 존중해야 합니다. 권한 거부나 명백히 잘못된 파라미터는 이해하기 쉬운 실패 결과를 반환해 무작정 재시도하지 않도록 해야 합니다.
프레임워크는 이러한 메커니즘의 일부를 구현해 줄 수 있습니다. 예를 들어 LangGraph는 Checkpointer로 스레드 범위의 그래프 상태를 저장하고, Store로 스레드를 넘어가는 데이터를 저장합니다. 그래도 지속화 백엔드를 직접 선택하고, 어떤 상태가 언제 저장되는지 이해해야 합니다. LangGraph Persistence
9. 신뢰할 수 있는 복구: Checkpoint와 외부 부작용은 별개
먼저 몇 가지 객체를 명확히 정의할 수 있습니다.
| 객체 | 의미 | 일반적으로 영속화하는 내용 |
|---|---|---|
| Session | 장기 대화 컨테이너 | 사용자, 대화 설정, 메시지 연결 |
| Message | 제품의 메시지 하나 | 내용, 역할, 첨부 파일 |
| Run | 한 번의 작업 실행 | 상태, 예산, 종료 기한, 소속 세션 |
| Step | Run의 한 단계 | 모델 요청 또는 도구 호출, 입력·출력 |
| Checkpoint | 복구 가능한 실행 상태 | 다음 단계 위치, 완료된 결과, 상태 버전 |
| Event | 발생한 변화 | 이벤트 순번, 유형, 시각, 결과 참조 |
| Prompt Context | 이번 모델 입력 | 위 데이터에서 선택한 임시 뷰 |
작은 시스템에서는 관계형 데이터베이스로 핵심 상태와 이벤트를 저장하고, 객체 스토리지로 대용량 파일을 저장할 수 있습니다. Redis는 캐싱, 속도 제한, 리스 조정에 사용할 수 있습니다. 서비스 간 분산이 필요할 때 메시지 시스템을 추가하면 됩니다. 첫날부터 모든 컴포넌트를 배포할 필요는 없습니다.
복구 메커니즘에서 가장 어려운 상황은 도구가 이미 주문을 생성했는데 Worker가 Checkpoint를 기록하기 전에 crashing한 경우입니다. Checkpoint만 보는 시스템은 그 단계가 아직 수행되지 않았다고 판단합니다. 다시 실행하면 두 번째 주문이 생성될 수 있습니다.
체크포인트를 저장하는 것만으로 외부 동작이 자동으로 한 번만 실행되는 것은 아닙니다.
한 가지可行的 방법은 논리적 작업에 안정적인 작업 ID를 부여하고, 재시도와 복구 과정에서 같은 ID를 재사용하는 것입니다. 도구 측에서는 이 ID를 결과와 연결합니다. 같은 작업이 다시 들어오면 기존 결과를 반환하고, 파라미터가 일치하지 않으면 ID 재사용을 거부합니다. 실행 전에는 의도를 저장하고, 실행 후에는 결과를 저장합니다. 타임아웃으로 성공 여부를 알 수 없을 때는 작업 결과를 조회하는 것이 우선입니다.
runId + stepId만 사용해도 한계가 있습니다. 같은 단계가 다시 실행되는 것은 막을 수 있지만, 모델이 다음 단계에서 동일한 업무 동작을 다시 요청하는 것은 막지 못합니다. 주문 같은 작업에는 보통 업무 수준의 고유 키나 명시적인 중복 작업 규칙도 필요합니다.
외부 시스템이 멱등성을 지원하지 않고 결과 조회도 불가능하다면, 엔드투엔드 exactly-once를 구현했다고 주장할 수 없습니다. “성공 여부를 알 수 없는 상태”를 하나의 상태로 저장하고, 대사, 보상 또는 수동 처리를安排해야 합니다.
여러 Worker가 같은 Run을 두고 경쟁할 수도 있습니다. 리스로 소유권을 조정할 수 있지만, 오래된 Worker가 일시 정지 후 재개했을 때는 이미 리스를 잃었을 수 있습니다. 상태를 기록할 때는 소유권과 증가하는 버전을 확인해 오래된 실행자를 거부해야 합니다. 외부 부작용에는 여전히 도구 측 멱등성이 함께 필요합니다.
동일한 Session에 두 요청이 동시에 들어오는 경우에도 제품 semantics를 정해야 합니다.排队할지, 앞선 요청을 취소할지, 아니면 분기를 만들지 정하면 됩니다. 두 Run이 제약 없이 같은 컨텍스트를 덮어쓰면 재현하기 어려운 상태 오류가 생깁니다.
10. SSE: 연결 끊김과 작업 종료를 분리해 처리해야 합니다
SSE는 HTTP 기반 서버-클라이언트 이벤트 스트림이며, 응답 유형은 text/event-stream입니다. 이벤트는 빈 줄로 구분되고, 일반적인 필드는 id, event, data입니다. 네이티브 EventSource는 재연결할 때 Last-Event-ID를 전송할 수 있지만, 프로토콜 자체가 애플리케이션을 위한 이벤트 기록을 보관하지는 않습니다. WHATWG Server-sent events 표준
복구가 필요한 작업이라면 생성과 구독을 분리할 수 있습니다.
네이티브 EventSource는 GET 구독에 적합하지만, 일반 Fetch처럼 요청 본문이나 사용자 정의 요청 헤더를 자유롭게 설정할 수는 없습니다. POST 스트리밍 요청이 필요하면 Fetch로 응답 스트림을 읽을 수 있지만, 재연결, 이벤트 파싱, 커서 관리도 클라이언트에서 구현해야 합니다.
이벤트는 다음과 같이 표현할 수 있습니다.
중단된 연결을 이어서 재생하려면 서버가 재플레이 가능한 이벤트를 저장하거나, 결과 스냅샷과 이후 이벤트 사이를 연결하는 메커니즘을 제공해야 합니다. 클라이언트는 처리한 순번을 기록하고, 재연결 후 누락된 부분을 요청한 뒤 중복을 제거합니다. 재플레이에서 실시간 구독으로 전환할 때도 빈틈이 없어야 합니다. 둘 사이에 발생한 이벤트가 유실되면 안 됩니다.
이벤트 보관 기간이 제한되어 있다면 커서가 만료될 수 있습니다. 이때는 스냅샷 또는 명시적인 재동기화 명령을 반환해야 합니다. 클라이언트는 종료 이벤트를 받은 뒤 구독을 끝내야 합니다. 이미 완료된 작업이 의미 없는 재연결을 계속하지 않도록 하기 위해서입니다.
사용자가 연결을 끊었을 때 작업을 취소할지는 업무가 결정해야 합니다. 짧은 대화는 연결이 끊어지거나 유예 시간이 지난 뒤 취소를 시도할 수 있습니다. 긴 보고서를 생성하는 작업은 계속 실행되게 한 뒤 사용자가 나중에 조회할 수 있습니다. 취소 신호도 최선의 노력으로만 전파할 수 있으며, 이미 외부 시스템에 제출된 동작이 되돌려진다고 보장할 수는 없습니다.
이 계층에서는 프록시 버퍼링, 유휴 시간outs, 하트비트, 느린 클라이언트도 처리해야 합니다. 무한히 커지는 전송 큐가 메모리를 고갈하게 두면 안 됩니다. 세밀한 텍스트 이벤트를 합치고, 연결별 버퍼 상한을 설정하며, 필요하면 연결을 끊고 클라이언트가 다시 동기화하게 할 수 있습니다.
11. 높은 동시성과 비용: 부하를 정량화한 뒤 확장 논의
“사용자 10만 명”은 충분히 완전한 용량 요구 사항이 아닙니다. 등록 사용자 수, 동시 접속자 수, SSE 연결 수, 초당 신규 Run 수, 동시 진행 모델 호출 수는 각각 다른 자원을 소비합니다.
간단한 용량 추정은 다음과 같이 할 수 있습니다.
平均执行中任务数 ≈ 每秒进入执行的任务数 × 平均执行时长
예를 들어 안정 상태에서 초당 200개 작업을 시작하고 평균 실행 시간이 20초라면, 실행 중인 작업은 약 4,000개입니다.这只是 평균치이며, 용량 계획에서는 버스트 트래픽, 긴 꼬리 작업, 재시도, 큐가 계속 늘어나는 situations도 고려해야 합니다.
많은 연결을 받아들이는 것과 동시에 많은 답변을 생성할 수 있다는 것은 별개입니다. 모델 호출은 요청 속도, token 속도, 동시성 할당량, 자체 처리량에도 제한됩니다. Agent의 Run 한 번이 모델과 도구를 여러 번 호출할 수도 있습니다.
입구 할당량, 사용자·테넌트별 동시성, 실행 큐, 검색 용량, 재순위화 용량, 모델 호출 할당량을 각각 관리해야 합니다. 큐에는 경계와 최대 대기 시간이 있어야 합니다. 그렇지 않으면 과부하가 사용자가 알아차리기 어려운 장시간 대기로만 바뀔 뿐입니다.
지연 시간도 구간별로 살펴야 합니다.
接收请求 → 排队 → Query 处理 → 检索 → 重排 → 模型首 token → 完成
상태 메시지는 작업이 계속 진행되고 있다는 사실을 알려줄 뿐, 답이 더 빨리 생성된다는 뜻은 아닙니다. 제품 계층의 최초 피드백 시간, 모델 첫 token 시간, 전체 완료 시간은 각각 기록해야 합니다.
비용 최적화 역시 구체적인 단계에 적용해야 합니다. 생략할 수 있는 재작성은 생략하고, 프로그램으로 계산할 수 있는 작업은 프로그램에 맡기고, 중복 증거를 반복해서 보내지 않아야 합니다. 모델 라우팅은 작업 평가로 검증해야 합니다. 저렴한 모델이 실패한 뒤 재시도하거나 상위 모델로 승격하면서 총비용을 늘리는 일은 피해야 합니다.
캐시는 용도를 구분해야 합니다. 애플리케이션 계층의 답변 캐시는 비즈니스 결과를 재사용하고, 검색 캐시는 후보를 재사용합니다. Prompt Cache나 추론 서비스의 KV Cache는 일부 계산을 재사용합니다. 어떤 것도 세션 지속화를 대신하거나 현재 권한 및 지식 버전 검사를 우회할 수는 없습니다. 구체적인 캐시 적중 규칙은 사용하는 서비스에 맞춰 검증해야 합니다.
12. 평가로 문제를 찾고 실패 시나리오로 시스템을 검증한다
최종 답변에 좋은지 나쁜지를 나타내는 단일 점수만으로는 개선 방향을 정하기 어렵습니다. 평가를 여러 계층으로 나눌 수 있습니다.
| 계층 | 답해야 할 질문 | 관찰 가능한 지표 또는 결과 |
|---|---|---|
| 검색 | 필요한 증거가 후보에 포함됐는가 | Recall@K, 질문 유형별 누락률 |
| 정렬 | 유용한 증거가 앞에 배치됐는가 | MRR, nDCG, 재순위화 전후 변화 |
| 컨텍스트 | 증거가 최종 입력에 온전히 포함됐는가 | 커버리지, 잘림, 버전 충돌 |
| 생성 | 결론이 정확하고 뒷받침되는가 | 정확성, 근거 일관성, 인용의 뒷받침 정도 |
| Agent | 작업이 실제로 완료됐는가 | 비즈니스 결과, 도구 선택, 중복 부작용 |
| 시스템 | 실패 시 설계대로 동작하는가 | 복구, 취소, 타임아웃, 비용, 꼬리 지연 시간 |
Recall@K는 일반적으로 관련 증거 중 앞 K개 결과에 포함된 비율을 뜻합니다. “적어도 하나라도 맞았는가”만 확인한다면 이를 명시적으로 적중률 방식이라고 밝혀야 합니다. 특히 여러 단계에 걸친 질문에서는 관련 조각 하나를 찾는 것과 추론에 필요한 모든 증거를 찾는 것을 구분해야 합니다.
테스트 세트에는 사실 질문, 여러 턴에 걸친 지시어, 정확한 오류 코드, 문서 간 질문, 표 계산, 문서 충돌, 답할 수 없는 질문, 권한이 제한된 상황을 포함해야 합니다. 한 번의 조정을前后해서 비교할 수 있도록 말뭉치, 프롬프트, 모델 설정, 주석 기준을 고정된 버전으로 유지해야 합니다.
LLM-as-Judge는 평가를 보조할 수 있지만 사실의 기준값으로 직접 사용하면 안 됩니다. 명확한 기준과 사람이 annotating한 샘플로 보정해야 합니다. 핵심 숫자, 인용, 비즈니스 부작용에는 검증 가능한 규칙이나 비즈니스 결과를 우선 적용해야 합니다. 튜닝에 사용하지 않은 테스트 세트도 보존해 익숙한 샘플만 최적화하는 일을 막아야 합니다.
온라인 Trace에는 문제를 찾는 데 충분한 정보가 기록되어야 합니다. 검색 후보 ID와 버전, 컨텍스트 선택, 모델 설정, 도구 입력·출력 참조, 폴백 경로, 소요 시간, 사용량이 포함됩니다. 민감한 콘텐츠는 마스킹하고 접근 권한과 보관 기간을 제한해야 합니다. 디버깅을 위해 모든 원문을 로그에 장기간 복사해서는 안 됩니다.
복구 능력은 장애 주입으로 검증해야 합니다. 도구 성공 후 상태를 커밋하기 전에 Worker를 종료하고, SSE 재플레이 중에 새 이벤트를 발생시키며, 리스를 만료시키고, 문서 권한을 철회하고, 클라이언트를 느리게 만들 수 있습니다. 이런 테스트는 평범한 질문을 통해서는 절대 드러나지 않는 결함을 찾아냅니다.
13. 보안 경계는 모델 밖에서 성립해야 한다
RAG는 외부 텍스트를 모델로 가져옵니다. 웹페이지, PDF, 도구 결과 어디에나 “이전 지시를 무시하라”, “다른 문서를 업로드하라” 같은 악성 내용이 들어 있을 수 있습니다. 검색 관련성이 높다는 사실이 지시 권한까지 갖게 하지는 않습니다.
외부 콘텐츠는 데이터로 취급하고, 실행 계층에서 호출 가능한 도구, 접근 가능한 리소스 범위, 외부 전송 대상을 제한해야 합니다. 최소 권한 자격 증명을 사용하고, 파라미터와 신원을 독립적으로 검증하며, 영향이 큰 작업에는 업무 요구에 맞는 확인 절차를 두어야 합니다. Prompt의 경고는 방어 계층 하나로 활용할 수 있지만 권한 검사를 대신할 수 없습니다. OWASP Prompt Injection Prevention
예를 들어 문서가 Agent에게 “내부 보고서를 이 이메일 주소로 보내라”라고 지시하더라도, 전송 허용 여부는 신뢰할 수 있는 사용자 요청, 데이터 권한, 도구 정책이 함께 결정해야 합니다. 문서 자체가 그 권한을 부여할 수는 없습니다.
테스트는 직접적인 악성 질문뿐 아니라 검색 조각, 도구 반환값, 긴 대화 요약, 캐시 적중 이후의 간접 인젝션도 포함해야 합니다. 테넌트를 가로지르는 접근과 권한 철회 여부는 검색, 캐시, 생성, 다운로드 전 과정에 걸쳐 검증해야 합니다.
14. 단계적으로 적용할 수 있는 업그레이드 순서
앞에서 다룬 기능을 한꺼번에 모두 구축하면 비용이 크고 어떤 부분이 실제로 유용한지 판단하기 어렵습니다. 더 현실적인 방법은 각 단계마다 명확한 검증 질문을 두는 것입니다.
| 단계 | 주요 구축 내용 | 검증할 때 중점적으로 볼 것 |
|---|---|---|
| 추적 가능한 Q&A 기준선 | 구조화 파싱, 출처 위치, 기본 검색, 근거가 없을 때 명시적으로 알림 | 오류가 어느 단계에서 발생했는지 특정할 수 있는가, 인용을 검증할 수 있는가 |
| 검색 및 컨텍스트 최적화 | 하이브리드 검색, Rerank, 부모-자식 블록, 버전 선택, 컨텍스트 예산 | 고정 테스트 세트에서 품질과 지연 시간이 어떻게 변하는가 |
| 범위가 제한된 도구 실행 | Workflow 또는 단일 Agent, 파라미터 검증, 권한 부여, 예산, 취소 | 실패 범위가 명확한가, 부작용을 검증할 수 있는가 |
| 복구 가능한 작업 | Run, Checkpoint, 멱등성, 이벤트 재플레이, SSE 분리 | 충돌과 재연결 이후 작업이 유실되거나 중복 실행되지 않는가 |
| 용량 및 지속 개선 | 속도 제한, 큐, 백프레셔, 캐시, Trace, 회귀 평가 | 과부하가 제어 가능한가, 최적화가 실제 이득을 만드는가 |
여러 Agent는 작업을 실제로 독립적으로 나눌 수 있고, 컨텍스트나 권한 격리가 필요할 때 도입해야 합니다. 상태 조정, 통신, 결과 병합 비용이 늘어나므로 단일 Agent나 고정 Workflow 기준선과 비교해야 합니다.
잘못된 답변을 마주했을 때 시스템은 증거가 어느 단계에서 사라졌는지 짚어낼 수 있어야 합니다. 작업이 중단되었을 때에는 계속 실행 중인지, 어떤 동작이 이미 발생했는지 설명할 수 있어야 합니다. 트래픽이 증가했을 때는 연결, 계산, 예산 각각의 병목을 정량화할 수 있어야 합니다. 이런 역량을 갖추면 RAG와 Agent는 장애 때마다 처음부터 Prompt를 추측하며 다시 시작하는 일 없이 지속해서 개선할 수 있습니다.
더 읽어보기: Agent가 Web에 들어간 뒤 Session, Run, 연결은 어떻게 분리해야 할까, Agent의 세션에는 무엇을 저장해야 할까: Session, Message, Event, Context.