본문 바로가기

AI Agent

[실전] 오픈소스 LLM 기반 멀티 에이전트 챗봇 제작기

반응형

미리 결론: 좋은 LLM API로 호출해서 사용합시다.

여러 명의 AI 친구들이 서로 협력해서 어려운 질문에도 척척 대답해 주는 멀티 에이전트 챗봇 시스템을 만들어 봤읍니다.

1. 시스템 개요

1.1 시스템 목적

제가 만든 이 시스템은 데이터 분석 기반 사용자 질의 응답 시스템입니다. 사용자가 평소에 친구와 대화하듯 자연스럽게 물어보는 질문을 찰떡같이 이해하고, 데이터를 검색하거나 지식 베이스를 뒤져서 정답을 찾아주는 아주 똑똑한 역할을 수행한답니다.

1.2 핵심 특징

제가 이 시스템을 만들면서 꼭 넣고 싶었던 일곱 가지 특별한 장점들이에요!

  • 멀티 에이전트 아키텍처: 모든 것을 조율하는 1개의 Orchestrator Agent와 각 분야의 전문가인 여러 개의 Sub-Agent들이 한 팀이 되어 일하도록 만들었습니다.
  • Self-Consistency 기법: 어떤 질문인지 분류할 때, 한 번만 생각하는 게 아니라 여러 번 투표해서 가장 정확한 답을 고르는 다수결 방식을 사용했습니다.
  • A2A 프로토콜 기반 통신: 에이전트들끼리 대화할 때는 **Agent-to-Agent(A2A)**라는 표준 약속을 사용해서 정보를 주고받게 설계했습니다.
  • LangGraph 워크플로우: 아주 복잡한 일의 순서도 상태 그래프로 만들어서 꼼꼼하게 관리할 수 있게 했습니다.
  • 하이브리드 메모리: 가장 최근에 나눈 대화뿐만 아니라, 질문 내용과 아주 관련이 깊은 예전 기록까지 함께 살펴보도록 만들었습니다. 히스토리 메모리는 ChromaDB를 사용헀읍니다.
  • 동적 LLM 모델 선택: 해야 할 일의 성격에 맞춰서 가장 어울리는 모델을 그때그때 골라 쓰도록 설정했습니다.
  • Map-Reduce 전략: 읽어야 할 정보(컨텍스트)가 너무 많을 때는 정보를 작게 쪼개서 각각의 결과를 만들고 (Map) 취합하여 최종 결과를 생성하는 (Reduce) 전략을 사용했습니다. 오픈소스 LLM 모델로 로컬에서 돌리니 많은 양의 컨텍스트를 한번에 처리 할수 없기 때문입니다.

1.3 사용 LLM 모델

저는 상용 API를 쓰지 않고, 제 컴퓨터에서 직접 돌리는 오픈소스 LLM 모델을 사용해서 이 시스템을 구축했습니다.

모델 구성은 이렇게 했어요!:

  • model_1 (Large Model): 정확한 의도 분류나 아주 복잡한 생각을 할 때 씁니다. 아주 정확하지만 대답 속도는 조금 느릴 수 있습니다. 복잡한 추론이나 어려운 QnA 작업을 할 때 사용합니다.
  • model_2 (Small Model): 질문을 빠르게 분석하거나 간단한 일을 할 때 씁니다. 대답 속도가 아주 빠르고 적당히 똑똑해서 주로 Orchestrator의 의도 분류나 적은 정보를 처리할 때 아주 유용합니다.

제가 오픈소스 모델을 선택한 이유는요!:

  1. 비용 절감: API 비용 걱정 없이 무제한으로 마음껏 쓸 수 있기 때문입니다.
  2. 데이터 보안: 소중한 정보를 외부 서버로 보내지 않아서 아주 안전합니다.
  3. 커스터마이징: 제가 원하는 분야에 딱 맞게 특별히 공부를 시킬 수 있습니다.
  4. 레이턴시 제어: 제 컴퓨터 환경에 맞춰서 대답 속도를 최적으로 조절할 수 있습니다.

직접 만들며 느낀 한계점들도 있어요:

  • 유료 API보다는 조금 덜 똑똑할 수 있어서 제가 추가적으로 엔지니어링을 많이 해줘야 했습니다.
  • Self-Consistency나 다시 시도하는 로직 같은 보완 기술이 반드시 필요했습니다.
  • 프롬프트(명령어)를 만드는 데 아주 많은 시간과 정성을 투자해야 했답니다.
  • 결과가 JSON이라는 예쁜 형식으로 잘 나오는지 확인하는 검증 로직도 따로 만들었습니다.

1.4 기술 스택

이 프로젝트를 완성하기 위해 제가 사용한 도구들을 소개합니다!

  • 프레임워크: FastAPI, LangChain, LangGraph
  • LLM 서빙: SGLang
  • 데이터베이스: Elasticsearch (데이터 저장용, 벡터/메타데이터 DB), Chroma DB (대화 기록 저장용)
  • 임베딩: Sentence Transformers
  • 배포: Docker, Uvicorn

2. 전체 아키텍처

2.1 시스템 구성도

제 시스템이 어떻게 움직이는지 한눈에 보여주는 그림이에요!

사용자가 질문을 던지면 가장 먼저 Orchestrator Agent가 그 질문을 받습니다. 이 친구는 '음, 이건 어떤 전문가가 해결해야 할까?'라고 고민한 뒤에, A2A Protocol이라는 약속을 따라 알맞은 Sub-Agents들에게 일을 시킨답니다.

그리고 실제 정보들은 아래에 있는 데이터 레이어에서 가져와요. 분석 데이터는 Elasticsearch에서 찾고, 예전 대화 기록은 Chroma DB에서 꺼내오죠. 그리고 AI가 생각을 할 수 있게 해주는 SGLang Server도 여기서 함께 열심히 일하고 있답니다.

3. 핵심 컴포넌트

제가 이 시스템을 만들면서 가장 신경을 많이 쓴 부분이 바로 이 핵심 컴포넌트들이에요. 우리 시스템이 똑똑하게 움직일 수 있는 이유를 하나씩 자세히 설명해 드릴게요!

3.1 Orchestrator Agent (오케스트레이터)

이 친구는 우리 팀의 조정자 역할을 합니다. 사용자의 질문을 분석하고, 어떤 전문가를 부를지 결정하며, 대화가 끊기지 않도록 기억을 관리하는 등 아주 많은 일을 도맡아 한답니다.

저는 이 친구를 OrchestratorAgent라는 클래스로 구현했는데요

  • 초기화 (init): 처음 시작할 때 우리 팀에 어떤 전문가(Sub-Agent)들이 있는지 전체 AgentCard를 쭉 훑어보고 명함을 모읍니다. 그리고 available_tasks_dict라는 일 목록을 만들고, LLM과 ChromaDB를 연결할 준비를 마칩니다.
  • 입력 처리 (handle_user_input): 사용자가 말을 걸면 가장 먼저 토큰 수 제한을 생각하며 예전 대화 기록을 불러옵니다. 그다음 Self-Consistency 기법으로 어떤 일을 할지 신중하게 결정하고(3번 추론 후 다수결로 선택), 워크플로우를 실행해 정답을 찾아옵니다. 대답이 끝나면 뒤에서 몰래 내용을 요약해서 저장하는 백그라운드 작업도 잊지 않아요.
  • 히스토리 관리 (save_chat_history_background): 대화가 끝나면 사용자의 질문과 AI의 답변을 예쁘게 요약해서 ChromaDB에 안전하게 넣어둡니다. 멀티 턴 챗에서 중요하지요.

3.2 OrchestratorService

  • 하이브리드 기억 찾기 (get_chat_history_from_chroma): 단순히 최근 말만 기억하는 게 아니라, 아주 똑똑한 방식으로 기억을 찾아옵니다. 최신 대화 5개와 질문 내용이랑 아주 비슷한 옛날 대화 5개를 함께 가져온 뒤, 중복을 지우고 시간 순서대로 정렬해 주죠. 이때 토큰이 너무 많아지지 않게 안전 임계값(safe_threshold)도 꼼꼼하게 체크합니다.
  • 신중한 Task 선택 (select_task_with_self_consistency): 혹시라도 AI가 실수할까 봐 최대 3번이나 물어보고 다수결로 할 일을 정합니다. 특히 에이전트 설명 버전(when_to_use)을 랜덤으로 섞어서 더 공정하게 판단하도록 했어요. 만약 처음 두 번 대답이 똑같으면 시간을 아끼기 위해 바로 결정을 내려 버립니다.
  • AI 호출 (call_llm): AI에게 물어볼 때 대답이 너무 길어서 잘리면(finish_reason='length'), max_tokens를 2배씩 늘려가며 최대 3번까지 다시 시도하도록 만들었습니다. 결과가 약속한 JSON 스키마 형식에 맞는지 검증하는 로직도 적용했읍니다.
  • 채팅 요약 (summarize_ai_output): 내용이 너무 길면 읽기 힘드니까, 입력+출력 텍스트의 토큰이 700개가 넘으면 LLM을 써서 예쁜 한 문장으로 요약해 줍니다.

3.3 LangGraph 워크플로우 (우리 팀의 업무 지도)

복잡한 문제를 풀 때는 단순히 질문하고 답하는 게 아니라, 여러 단계를 거쳐야 할 때가 많습니다. 그래서 저는 LangGraph라는 도구를 써서 일의 흐름을 하나의 '상태 그래프'로 모델링했습니다. 마치 목적지까지 가는 여러 갈래 길을 지도에 그려둔 것과 같답니다.

① WorkflowState(StateGraph)

에이전트들이 일을 하는 동안 꼭 기억해야 할 정보들을 WorkflowState라는 StateGraph에 담아두었습니다. 어떤 단계에서도 필요한 정보를 꺼내 쓸 수 있어요.

  • 사용자 정보: 말을 건 사람이 누구인지(user_id), 대화방 번호(session_id)와 요청 번호(request_id), 그리고 원래 질문(user_query)을 담습니다.
  • 작업 정보: 대장님이 골라준 일(selected_task)과 도와줄 전문가들의 정보(sub_agents_dict)를 보관합니다.
  • 입력 데이터: 작업에 필요한 파라미터(input_params)와 추가 자료(additional_data)를 챙깁니다.
  • 결과물: 중간중간 나온 결과들(intermediate_result)을 리스트로 모으고, 마지막에는 최종 대답(final_answer)을 완성합니다.

② 일의 정거장: 노드(Node) 설계

지도의 정거장 역할을 하는 '노드'는 크게 두 종류로 나누어 설계했습니다.

  • Task Node: 특정 Sub-Agent를 부르기 전에 데이터를 검증하고, 다음에 어디로 갈지 결정하기 위한 상태를 설정하는 준비 단계입니다.
  • Work Node: 실제로 전문가(Sub-Agent)에게 A2A 프로토콜로 통신을 해서(HTTP POST 요청) 일을 시키고, 받아온 결과를 파싱해서 intermediate_result, final_result에 업데이트하는 노드입니다.

③ 똑똑한 길 찾기: 조건부 라우팅(Conditional Routing)

상황에 따라 어디로 갈지 정하는 아주 중요한 규칙들입니다.

  • 의도에 따른 라우팅: route_by_task 함수를 써서, 결정된 Task에 따라 에이전트 A방으로 갈지 B방으로 갈지 결정합니다.
  • 데이터 형식에 따른 라우팅: route_by_input_type을 통해 들어온 정보의 종류에 따라 다른 처리 경로를 선택합니다.
  • 중간 결과에 따른 라우팅: route_by_intermediate_result로 이전 단계 결과가 잘 나왔으면 다음으로 가고, 문제가 생기면 예비(fallback) 경로로 보내기도 합니다.

4. 동작 원리 및 흐름

제가 만든 시스템이 사용자의 질문을 받고 어떻게 정답을 찾아내는지, 그 전체적인 과정을 보여드릴게요.

4.1 전체 처리 흐름 (한눈에 보기)

총 12단계의 꼼꼼한 과정을 거쳐서 대답을 완성합니다.

  1. 사용자 질문: 사용자가 궁금한 내용을 입력합니다.
  2. 기억 불러오기: ChromaDB에서 예전 대화 기록을 로드합니다.(최신순+관련성순)
  3. 의도 분석: Self-Consistency 기법으로 질문의 의도를 3번 분석합니다.
  4. 할 일 결정: 다수결 투표로 어떤 일을 할지(Task) 최종 결정합니다.
  5. 워크플로우 가동: 설계한 LangGraph 지도를 따라 실행을 시작합니다.
  6. 전문가 소환: A2A 프로토콜을 써서 알맞은 Sub-Agent를 호출합니다.
  7. 열일 중: 호출된 Sub-Agent가 DB 검색이나 추론 작업을 수행합니다.
  8. 결과 도착: Sub-Agent가 작업 결과를 대장님께 돌려줍니다.
  9. 예쁘게 다듬기: 받은 결과를 정해진 형식에 맞춰 포맷팅합니다.
  10. 정보 보강: 메타데이터를 조회, 보강해서 내용을 더 풍성하게 만듭니다.
  11. 대답 전달: 완성된 답변을 사용자에게 보여줍니다.
  12. 몰래 저장: 뒤에서 몰래 대화 기록을 요약해서 기억 저장소에 보관합니다. (Background 요약, ChromaDB에 히스토리저장)

4.2 단계별 상세 설명

[1-2] 질문 입력과 하이브리드 기억 찾기

사용자 질의가 입력되면, get_chat_history_from_chroma라는 기능을 써서 아주 똑똑하게 기억을 찾아옵니다.

  • 최신 기억: 가장 최근에 나눈 대화 5개를 먼저 챙깁니다.
  • 관련 기억: 질문 내용과 의미가 비슷한 옛날 대화 5개를 Vector 검색으로 찾아옵니다.
  • 정리 정돈: 중복된 기억은 지우고, 시간 순서대로 예쁘게 정렬한 뒤에 토큰이 너무 많아지지 않게 조절합니다.

[3-4] 신중한 의도 분류 (Self-Consistency)

AI가 실수를 하지 않도록 select_task_with_self_consistency라는 과정을 거치게 했습니다.

  • 3번 물어보기: 최대 3번이나 샘플링을 해서 질문을 분석합니다.
  • 랜덤 섞기: 각 에이전트의 설명서(when_to_use) 버전을 매번 랜덤으로 바꿔서 더 객관적으로 판단하게 했습니다.
  • 다수결 결정: 가장 많이 나온 결과를 선택하고, 만약 처음 2번 결과가 똑같으면 시간을 아끼기 위해 바로 결정을 내립니다.

[5-8] 워크플로우 실행과 전문가 호출

이제 결정된 길을 따라 실제로 일을 시킬 차례입니다.

  • 지도 따라가기: run 함수를 통해 LangGraph 워크플로우를 실행합니다.
  • A2A 호출: worknode에서 Sub-Agent의 주소로 AgentRequest를 보냅니다.
  • 안전한 통신: 이때 헤더에 해싱된 API 키(X-Agent-Auth)를 담아서 서로가 맞는지 꼭 확인합니다.

[9-11] 답변 다듬기와 최종 응답

나온 결과물들을 그냥 보여주지 않고 make_workflow_result라는 과정으로 정성껏 다듬습니다.

  • 맞춤 포맷팅: Task 종류에 따라 알맞은 양식을 골라 WorkflowResult 모델로 변환합니다.
  • 내용 보강: 구조화된 정보를 더해서 사용자가 이해하기 쉽게 만듭니다.

[12] 백그라운드 대화 요약 및 저장

  • 똑똑한 요약: 답변이 700토큰이 넘으면 LLM을 시켜서 한 문장으로 요약하고, 짧으면 그대로 저장합니다.
  • 기억 저장: "사용자: 질문\nAI: 요약" 형식으로 ChromaDB에 담는데, 나중에 또 꺼내 쓸 수 있게 순서 번호(turn)와 시간 정보를 함께 기록합니다.

5. 에이전트 간 통신 프로토콜

에이전트들이 서로 다른 방(포트)에 있다 보니, 대화를 하려면 공통된 약속이 필요합니다. 제가 이 시스템을 위해 설계한 A2A(Agent-to-Agent) 표준 프로토콜을 소개합니다!

5.1 A2A 프로토콜: 에이전트들의 표준 대화법

① 에이전트의 명함: Agent Card

각 Sub Agent가 어떤 친구인지 알려주는 명함 같은 정보 꾸러미입니다. AgentCard라는 모델에는 이런 정보들이 꼼꼼하게 담겨 있어요.

  • name: 에이전트의 이름입니다.
  • description & long_description: 에이전트가 어떤 일을 하는지 알려주는 짧고 긴 설명이에요.
  • when_to_use: 이 에이전트를 언제 불러야 하는지 알려주는 가이드라인입니다.
  • url: 에이전트가 살고 있는 집 주소(API 엔드포인트)예요.
  • version: 에이전트의 버전 정보입니다.
  • capabilities: 이 에이전트가 가진 전반적인 기능(능력치)입니다.
  • authentication: 어떤 인증 방식을 쓰는지 적혀 있어요.
  • skills: 제공하는 구체적인 스킬들의 목록입니다.

② 구체적인 능력: Skill

에이전트가 가진 개별적인 기술 하나하나를 말합니다.

  • id & name: 스킬의 고유한 이름과 식별 번호예요.
  • description: 이 스킬이 정확히 어떤 일을 하는지 설명합니다.
  • input & output: 일을 시작할 때 어떤 데이터(JSON 스키마)가 필요한지, 결과는 어떤 형식으로 주는지 미리 약속해 둔 것입니다.

5.2 명함 모으기: Agent Card 조회 프로세스

대장 에이전트가 처음 일을 시작할 때, 팀원들의 명함을 수집하는 과정이에요.

  • Sub-Agent 스캔: _scan_sub_agents라는 기능을 통해 미리 정해둔 Sub-Agent 주소들을 하나하나 방문합니다.
  • 숨겨진 파일 찾기: 각 주소의 끝에 /.well-known/agent.json이라는 경로를 붙여서 명함 파일을 불러옵니다.
  • 목록 만들기: 불러온 명함들을 AgentCard 모델로 꼼꼼하게 검사하고, 우리가 쓸 수 있는 일 목록(available_tasks_dict)으로 저장합니다.

5.3 비밀 암호: 인증 메커니즘

  • SHA256 해싱: 환경 변수에 숨겨둔 AGENT_API_KEY를 그대로 쓰지 않고, SHA256이라는 복잡한 방식으로 섞어서(Hashing) 아무도 알 수 없는 암호로 바꿉니다.
  • 비밀 헤더 추가: 에이전트에게 일을 시키러 갈 때, 편지 봉투 겉면(Header)에 X-Agent-Auth라는 이름으로 이 암호를 딱 붙여서 보냅니다.
  • 철저한 검문: 메시지를 받은 에이전트는 verify_agent_auth_header라는 기능을 써서 암호가 맞는지 확인한 뒤에야 일을 시작한답니다.

6. 메모리 및 대화 관리

제가 이 시스템을 만들면서 가장 중요하게 생각한 것 중 하나가 바로 "AI가 예전 대화를 얼마나 잘 기억하느냐"였어요. 단순히 기록만 하는 게 아니라, 필요한 순간에 쏙쏙 꺼내 쓸 수 있도록 설계를 했답니다.

6.1 Chroma DB 기반 대화 기록 (기억 저장소)

저는 에이전트들의 기억 저장소로 Chroma DB를 사용했습니다. 각 대화방(세션)마다 독립적인 공간(컬렉션)을 만들어 주어 기억이 섞이지 않게 했죠.

  • 저장 방식: 대화 내용을 무작정 다 넣는 게 아니라, "User: {질문} \n AI: {요약}"이라는 예쁜 형식으로 정리해서 저장합니다.
  • 꼬리표 달기(메타데이터): 나중에 찾기 쉽게 대화 종류(type="history"), 몇 번째 대화인지(turn), 그리고 언제 대화했는지(timestamp)라는 정보표를 꼭 붙여둡니다.
  • 이름표(ID): 각 기억 조각에는 "session_{session_id}_turn_{turn}"이라는 고유한 이름표를 붙여서 절대 잃어버리지 않게 관리합니다.

6.2 하이브리드 대화 기록 조회 (기억해내는 방법)

저는 우리 에이전트들이 단순히 어제 한 말만 기억하는 게 아니라, 아주 옛날에 했던 중요한 말까지 잘 찾아내도록 하이브리드 방식을 설계했습니다.

  1. 최신성 기반: 가장 최근에 나눈 따끈따끈한 대화 5개를 먼저 챙깁니다.
  2. 관련성 기반: 지금 질문과 내용이 아주 비슷한 옛날 대화를 Vector 검색으로 상위 5개까지 찾아냅니다.
  3. 정리 정돈: 이렇게 모은 기억들 중에서 중복된 것은 지우고, 대화 순서(turn)에 맞춰서 예쁘게 나열합니다.

이때, AI가 한 번에 읽을 수 있는 양이 정해져 있어서 **토큰(Token)**이라는 단위로 글자 수를 꼼꼼하게 계산합니다.

  • 토큰 계산법: tiktoken을 사용하여 텍스트의 토큰 수를 구했습니다.
    • 토크나이저를 쓸 수 없다면 한글은 문자 수에 1.5를 곱하고, 영어는 단어 수에 1.3을 곱해서 대략적인 양을 추정하는 방식도 있다고 합니다.
  • 안전 임계값(safe_threshold): sglang으로 오픈소스 LLM을 서빙하는 환경이므로 컨텍스트 길이를 넘지 않기 위해 주는 margin 이라고 보면 됩니다.

6.3 대화 요약 (기억을 짧게 줄이기)

대화 내용이 너무 길면 기억하기 힘드니까, 제가 똑똑한 요약 전략도 세워두었습니다. 기준점은 대략 700토큰으로 헀읍니다.(이유 없음)

  • 700토큰 이하일 때: 굳이 LLM을 쓰지 않고 제가 직접 정해둔 규칙대로 요약합니다. 예를 들어 "데이터 ID X: N개 항목" 이런 식으로 간단하게 정리하죠.
  • 700토큰을 넘을 때: 이때는 LLM 친구에게 부탁합니다. "한국어 존댓말 한 두 문장으로 완벽하게 요약해줘!"라고 시키고, 결과가 제가 정한 JSON 형식에 맞는지 꼼꼼하게 검사합니다.

7. 핵심 기법 상세

만악의 근원은 오픈소스 LLM 로컬 서빙입니다. API 호출로 LLM 사용이 가능한 환경이었으면 일이 쉬워졌겠으나..

7.1 재시도 로직 (Retry Logic)

오픈소스 LLM은 가끔 대답을 하다가 멈추거나, 약속한 형식을 지키지 못할 때가 있어요. 그래서 저는 시스템이 스스로 문제를 해결하고 다시 시도하는 재시도 로직을 꼼꼼하게 설계했습니다.

  • 초기 설정: 처음에는 max_tokens를 2048로 정하고, 10.0초의 타임아웃과 최대 3번의 재시도 기회를 줍니다. (timeout 값, max_tokens 값은 작업마다 다르게 적용했읍니다!)
  • 대답이 잘렸을 때 (finish_reason='length'): 만약 대답이 너무 길어서 끝까지 나오지 못했다면, 저는 똑똑하게 max_tokens를 2배로 늘려서 다시 물어봅니다.
    • GPT-OSS를 사용하면 reasoning 만 신나게 출력하다가 정작 필요한 결과를 만들어내지 못하는 경우가 빈번합니다. reasoning을 끄면 또 너무 멍청해집니다. 진퇴양난입니다.
  • 모델 바꾸기 전략: 만약 정확도가 높은 model_1이 계속 실패하면, 조금 더 빠르고 가벼운 model_2에게 2번 정도 기회를 더 줍니다.
    • 작업의 성격, 난이도에 따라서 반대인 경우도 많습니다. model_2 (light) -> model_1 (heavy) 로 변경.
    • 여기서 light는 gpt-oss 20B이고 heavy는 gpt-oss 120B 입니다.
  • JSON 스키마 검증: 결과가 나온 뒤에는 제가 미리 정해둔 JSON 스키마 형식에 맞는지, 필수 필드가 다 들어있는지 하나하나 검사합니다.
    • 필수 필드가 없으면 버리고 다시 호출합니다!

7.2 Map-Reduce 전략 (대용량 정보 처리 비법)

우리가 찾아낸 정보가 너무 많아서 AI가 한꺼번에 읽지 못할 때 쓰는 아주 강력한 전략이에요.

① 왜 필요한가요?

  • 문제: 검색 결과는 수백 개인데, AI가 한 번에 읽을 수 있는 양(컨텍스트)은 정해져 있어요.
  • 해결: 그래서 정보를 작게 쪼개서 처리한 뒤 다시 하나로 합치는 Map-Reduce 방식을 썼습니다.

② 어떻게 작동하나요?

  1. Map 단계: 컨텍스트 청킹 (Chunking):
    • 수백 개의 문서를 AI가 읽기 좋은 크기로 나눕니다.
    • 이때 TOTAL_LIMIT (SGLANG으로 서빙 시 적용한 context-length)에서 프롬프트와 SAFE_MARGIN 등을 뺀 나머지 공간을 정확히 계산해서 정보를 담습니다.
  2. LLM 관련성 판단:
    • 나눈 정보 조각들을 AI에게 보여주고 "이 중에서 진짜 질문이랑 상관있는 문서 번호가 뭐야?"라고 물어봅니다.
    • AI는 번호 배열(예: [1, 5, 12])을 결과로 줍니다.
  3. Reduce 단계: 최종 결과 종합:
    • 각 조각(Map)에서 AI가 골라준 중요한 정보들을 하나로 쏙쏙 모아서, 최종적인 결과 리스트를 완성합니다.
      • 여기에는 간략하게 적었으나 실제로는 Reduce로 최종 답변을 생성하기 위한 LLM 호출이 한번 더 들어갑니다!
    • 이렇게 정제된 핵심 정보들만 정리해서 사용자에게 돌려주게 됩니다.

③ 깨끗하게 정리하기 (중복 제거)

  • 내용이 겹치는 정보가 있으면 content_key를 만들어서 체크한 뒤, 하나만 남기고 지워서 아주 깔끔한 답변을 만든답니다.

결론: 돈 내고 API 호출로 쓰는 좋은 LLM 사용하면 인생이 편해집니다... 이거 작업하면서 술이 늘었읍니다.

반응형