<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>John Kim · Cloud, Tech, AI &amp; Business – Docs</title><link>https://johklo.github.io/docs/</link><description>Recent content in Docs on John Kim · Cloud, Tech, AI &amp; Business</description><generator>Hugo -- gohugo.io</generator><language>ko</language><atom:link href="https://johklo.github.io/docs/index.xml" rel="self" type="application/rss+xml"/><item><title>00. 책의 구성</title><link>https://johklo.github.io/docs/tech/ai-agents-and-applications/00-overview/</link><pubDate>Mon, 19 Oct 2026 21:37:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/ai-agents-and-applications/00-overview/</guid><description>
&lt;p&gt;『AI Agents and Applications』은 Roberto Infante가 쓴 책이다. LLM 자체를 학습하는 방법보다 이미 존재하는 모델을 애플리케이션의 구성 요소로 사용하는 방법에 집중한다. 프롬프트를 코드에서 호출하는 일부터 문서 요약, 검색 기반 질의응답, 도구 실행과 여러 에이전트의 협업까지 이어진다.&lt;/p&gt;
&lt;p&gt;전체는 5개 Part와 14개 챕터로 구성된다. 앞부분에서 만든 웹 리서치 엔진과 여행 정보 챗봇이 뒤에서 다시 등장한다. 기능을 처음부터 새로 만드는 대신 기존 구조의 한계를 확인하고, 그 한계를 해결하는 구성 요소를 붙이는 흐름이다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="part-1-llm-애플리케이션의-기초"&gt;Part 1. LLM 애플리케이션의 기초&lt;span class="hx:absolute hx:-mt-20" id="part-1-llm-애플리케이션의-기초"&gt;&lt;/span&gt;
&lt;a href="#part-1-llm-%ec%95%a0%ed%94%8c%eb%a6%ac%ec%bc%80%ec%9d%b4%ec%85%98%ec%9d%98-%ea%b8%b0%ec%b4%88" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;장&lt;/th&gt;
&lt;th&gt;다루는 내용&lt;/th&gt;
&lt;th&gt;구분할 개념&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="../01-introduction/"&gt;1. AI 애플리케이션과 에이전트&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;애플리케이션 유형과 LangChain의 구성 요소&lt;/td&gt;
&lt;td&gt;엔진, 챗봇, 에이전트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="../02-prompts/"&gt;2. 프롬프트 실행&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;API 호출, 템플릿, 예시 기반 프롬프트&lt;/td&gt;
&lt;td&gt;지시, 입력 데이터, 출력 형식&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;LLM은 자연어를 입력받고 출력하지만 프로그램은 구조화된 값과 명확한 실행 조건을 필요로 한다. 따라서 좋은 문장을 작성하는 일뿐 아니라 모델 입력을 조립하고 응답을 검증하는 과정이 중요하다.&lt;/p&gt;
&lt;p&gt;프롬프트 엔지니어링, RAG, 파인튜닝도 서로 다른 문제를 푼다. 프롬프트는 현재 요청에서 할 일을 정하고, RAG는 외부 근거를 공급하며, 파인튜닝은 모델의 학습된 동작을 조정한다. 새 지식을 넣는 문제와 응답 형식을 맞추는 문제를 같은 방식으로 해결하려고 하면 비용과 복잡도가 커질 수 있다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="part-2-요약과-리서치"&gt;Part 2. 요약과 리서치&lt;span class="hx:absolute hx:-mt-20" id="part-2-요약과-리서치"&gt;&lt;/span&gt;
&lt;a href="#part-2-%ec%9a%94%ec%95%bd%ea%b3%bc-%eb%a6%ac%ec%84%9c%ec%b9%98" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;장&lt;/th&gt;
&lt;th&gt;다루는 내용&lt;/th&gt;
&lt;th&gt;중심 문제&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="../03-summarization/"&gt;3. 문서 요약&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;긴 문서와 여러 문서의 요약&lt;/td&gt;
&lt;td&gt;문맥 길이 제한과 정보 손실&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="../04-research-engine/"&gt;4. 리서치 요약 엔진&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;검색, 본문 수집, 요약, 보고서 생성&lt;/td&gt;
&lt;td&gt;단계별 입출력과 출처 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="../05-langgraph-workflows/"&gt;5. LangGraph 워크플로&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;상태와 조건부 분기&lt;/td&gt;
&lt;td&gt;고정된 순서에서 반복 가능한 흐름으로 전환&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;문서를 나눠 요약하는 MapReduce와 기존 요약에 다음 문서를 반영하는 Refine은 처리 방식이 다르다. 전자는 독립 처리가 쉬운 대신 조각 사이의 연결을 잃기 쉽고, 후자는 앞선 요약을 다음 단계에 전달하지만 순서의 영향을 받는다.&lt;/p&gt;
&lt;p&gt;4장에서는 웹 검색 결과를 자동으로 수집해 보고서를 만든다. 5장은 같은 작업을 그래프로 옮긴다. 검색 결과가 부족하면 다시 검색하고, 충분하면 작성 단계로 이동하는 식으로 실행 경로를 명시할 수 있다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="part-3-rag-질의응답"&gt;Part 3. RAG 질의응답&lt;span class="hx:absolute hx:-mt-20" id="part-3-rag-질의응답"&gt;&lt;/span&gt;
&lt;a href="#part-3-rag-%ec%a7%88%ec%9d%98%ec%9d%91%eb%8b%b5" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;장&lt;/th&gt;
&lt;th&gt;다루는 내용&lt;/th&gt;
&lt;th&gt;중심 문제&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="../06-rag-fundamentals/"&gt;6. RAG와 ChromaDB&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;임베딩과 벡터 검색을 이용한 기본 RAG&lt;/td&gt;
&lt;td&gt;검색할 자료를 준비하는 과정과 질문 처리 과정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="../07-conversational-rag/"&gt;7. 대화형 RAG와 LangSmith&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;문서 적재, 후속 질문, 대화 이력, 추적&lt;/td&gt;
&lt;td&gt;이전 대화와 현재 검색의 연결&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;RAG는 전체 자료를 모델에 학습시키는 방식이 아니다. 질문과 관련된 자료를 찾아 현재 프롬프트에 넣는다. 그래서 문서가 바뀔 때 검색 자료를 갱신할 수 있지만, 필요한 자료를 못 찾으면 생성 모델만 바꿔도 문제가 해결되지 않는다.&lt;/p&gt;
&lt;p&gt;대화 이력과 문서 지식도 다르다. 이력은 사용자가 앞에서 무엇을 물었는지 알려 주고, 문서는 답을 뒷받침할 사실을 제공한다. “그곳의 교통편은?” 같은 후속 질문에서는 대화로 대상 장소를 복원한 뒤 검색해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="part-4-고급-rag"&gt;Part 4. 고급 RAG&lt;span class="hx:absolute hx:-mt-20" id="part-4-고급-rag"&gt;&lt;/span&gt;
&lt;a href="#part-4-%ea%b3%a0%ea%b8%89-rag" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;장&lt;/th&gt;
&lt;th&gt;다루는 내용&lt;/th&gt;
&lt;th&gt;바꾸는 위치&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="../08-advanced-indexing/"&gt;8. 고급 인덱싱&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;청크 분할, 부모와 자식 청크, 표와 이미지&lt;/td&gt;
&lt;td&gt;검색 자료를 준비하는 방법&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="../09-question-transformations/"&gt;9. 질문 변환&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;재작성, 다중 질의, Step-back, HyDE, 분해&lt;/td&gt;
&lt;td&gt;검색에 넣는 질문&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="../10-query-routing/"&gt;10. 질의 생성과 라우팅&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;메타데이터 필터, SQL, 그래프, 결과 후처리&lt;/td&gt;
&lt;td&gt;검색 대상과 결과의 조합&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;RAG의 품질 문제를 세 위치로 나눠 볼 수 있다. 자료가 잘못 나뉘었다면 인덱싱을, 질문이 검색 자료와 맞지 않으면 질의를, 검색 결과가 많고 중복되면 후처리를 바꾸는 식이다.&lt;/p&gt;
&lt;div role="img" aria-label="다이어그램"&gt;
&lt;pre class="mermaid hx:mt-6"&gt;
flowchart TD
accTitle: RAG 품질을 개선하는 세 위치
accDescr: 문서 준비에서는 분할과 임베딩을, 검색 전에는 질문과 라우팅을, 검색 후에는 정렬과 문맥 구성을 개선한다.
D[&amp;#34;원본 문서&amp;#34;] --&amp;gt; I[&amp;#34;8장: 분할과 임베딩&amp;#34;]
I --&amp;gt; V[&amp;#34;검색 인덱스&amp;#34;]
Q[&amp;#34;사용자 질문&amp;#34;] --&amp;gt; T[&amp;#34;9장: 질문 변환&amp;#34;]
T --&amp;gt; R[&amp;#34;10장: 질의 생성과 라우팅&amp;#34;]
V --&amp;gt; S[&amp;#34;자료 검색&amp;#34;]
R --&amp;gt; S
S --&amp;gt; P[&amp;#34;10장: 후처리와 근거 구성&amp;#34;]
P --&amp;gt; A[&amp;#34;답변 생성&amp;#34;]
&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;고급 기법을 많이 넣는다고 반드시 좋아지는 것은 아니다. 질의가 늘면 호출 수와 중복 결과가 늘고, 검색 범위를 넓히면 관련 없는 내용도 들어온다. 어떤 실패를 해결하려는지와 변경 전후의 검색 결과를 함께 봐야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="part-5-에이전트"&gt;Part 5. 에이전트&lt;span class="hx:absolute hx:-mt-20" id="part-5-에이전트"&gt;&lt;/span&gt;
&lt;a href="#part-5-%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;장&lt;/th&gt;
&lt;th&gt;다루는 내용&lt;/th&gt;
&lt;th&gt;중심 문제&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="../11-tool-agents/"&gt;11. 도구 기반 에이전트&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;모델의 도구 선택과 LangGraph 실행 루프&lt;/td&gt;
&lt;td&gt;도구 호출 요청과 실제 실행의 분리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="../12-multi-agent-systems/"&gt;12. 다중 에이전트&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Router와 Supervisor&lt;/td&gt;
&lt;td&gt;작업 위임과 결과 회수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="../13-mcp-servers/"&gt;13. MCP 서버&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;도구를 표준 프로토콜로 제공하고 소비&lt;/td&gt;
&lt;td&gt;Host, Client, Server의 역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="../14-production-agents/"&gt;14. 프로덕션 에이전트&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;메모리, 체크포인트, 가드레일&lt;/td&gt;
&lt;td&gt;상태 보존과 실행 통제&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;도구를 사용할 수 있는 모델도 외부 함수를 스스로 실행하는 것은 아니다. 모델은 도구 이름과 인자를 내고 애플리케이션이 실행한다. 실행 결과를 모델에 돌려주면 추가 호출이나 최종 답변을 결정하는 루프가 된다.&lt;/p&gt;
&lt;p&gt;다중 에이전트도 이 경계 위에 있다. 역할을 나누는 것과 여러 작업의 결과를 통합하는 것은 다른 문제다. Router가 한 경로를 고르는 구조와 Supervisor가 결과를 받아 다음 위임을 결정하는 구조를 비교하면 차이가 분명해진다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="프레임워크의-역할"&gt;프레임워크의 역할&lt;span class="hx:absolute hx:-mt-20" id="프레임워크의-역할"&gt;&lt;/span&gt;
&lt;a href="#%ed%94%84%eb%a0%88%ec%9e%84%ec%9b%8c%ed%81%ac%ec%9d%98-%ec%97%ad%ed%95%a0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구성 요소&lt;/th&gt;
&lt;th&gt;맡는 역할&lt;/th&gt;
&lt;th&gt;대신 해결하지 않는 것&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LangChain&lt;/td&gt;
&lt;td&gt;모델, 프롬프트, 검색기와 도구의 인터페이스 및 조합&lt;/td&gt;
&lt;td&gt;데이터 품질과 올바른 업무 요구&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LCEL&lt;/td&gt;
&lt;td&gt;실행 단위를 연결하는 표현 방식&lt;/td&gt;
&lt;td&gt;입력과 출력의 의미적 일치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LangGraph&lt;/td&gt;
&lt;td&gt;상태, 노드, 분기와 반복 실행&lt;/td&gt;
&lt;td&gt;무제한 자율 실행의 안전성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LangSmith&lt;/td&gt;
&lt;td&gt;실행 추적과 평가&lt;/td&gt;
&lt;td&gt;평가 기준의 자동 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ChromaDB&lt;/td&gt;
&lt;td&gt;임베딩과 관련 데이터 저장 및 검색&lt;/td&gt;
&lt;td&gt;검색 결과의 사실성 보증&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;td&gt;도구와 문맥 제공자의 연결 규약&lt;/td&gt;
&lt;td&gt;사용자 인증과 작업 승인 전체&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;라이브러리의 역할과 모델의 능력은 분리해서 봐야 한다. 프레임워크가 실행을 잘 기록하더라도 답이 맞는지는 평가가 필요하고, 모델이 적절한 도구를 골라도 그 도구의 권한과 입력을 검사해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="부록"&gt;부록&lt;span class="hx:absolute hx:-mt-20" id="부록"&gt;&lt;/span&gt;
&lt;a href="#%eb%b6%80%eb%a1%9d" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;부록 A는 Jupyter 환경에서 LangChain과 LCEL을 실행하는 작은 예제다. 부록 B는 Python, 가상환경, Jupyter 설치를 다룬다. 부록 C는 모델 선택 기준과 모델 유형을 비교한다.&lt;/p&gt;
&lt;p&gt;예제의 클래스와 패키지 경로는 설치 버전과 함께 확인해야 한다. 특히 LangChain과 LangGraph의 사전 구성 에이전트 API, MCP의 전송과 연결 방식은 버전에 따라 달라질 수 있다. 동작 원리와 특정 버전의 호출 문법을 같은 것으로 외우지 않는 편이 낫다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="계속-확인할-질문"&gt;계속 확인할 질문&lt;span class="hx:absolute hx:-mt-20" id="계속-확인할-질문"&gt;&lt;/span&gt;
&lt;a href="#%ea%b3%84%ec%86%8d-%ed%99%95%ec%9d%b8%ed%95%a0-%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;어떤 정보가 모델에 들어갔는가? 검색한 원문과 최종 답변은 어떻게 연결되는가? 다음 실행 경로는 코드와 모델 중 누가 정하는가? 상태를 저장했다면 어떤 시점으로 복원할 수 있는가?&lt;/p&gt;
&lt;p&gt;이 네 질문을 따라가면 요약 엔진과 RAG 챗봇, 도구 기반 에이전트가 별개의 유행어가 아니라 같은 구성 요소를 다르게 연결한 시스템이라는 점이 보인다.&lt;/p&gt;
&lt;p&gt;&lt;a href="../"&gt;시리즈 목차&lt;/a&gt; / &lt;a href="../01-introduction/"&gt;1. AI 애플리케이션과 에이전트&lt;/a&gt;&lt;/p&gt;</description></item><item><title>00. 책의 구성과 북클럽 읽기 지도</title><link>https://johklo.github.io/docs/tech/machine-learning-platform-engineering/00-overview/</link><pubDate>Sun, 18 Oct 2026 21:30:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/machine-learning-platform-engineering/00-overview/</guid><description>
&lt;p&gt;모델을 API로 배포할 수 있어도 그 모델을 다시 만들고 안전하게 바꾸는 일은 별개의 문제입니다. 학습 데이터와 코드가 무엇이었는지, 어떤 평가를 통과했는지, 운영 중 문제가 생기면 어디까지 되돌릴 수 있는지를 함께 봐야 합니다.&lt;/p&gt;
&lt;p&gt;『Machine Learning Platform Engineering』은 이 전체 흐름을 다루는 책입니다. 저자는 Benjamin Tan Wei Hao, Shanoop Padmanabhan, Varun Mallya이고 출판사는 Manning입니다. 4개 Part와 13개 챕터, 설치와 YAML을 다루는 부록 두 개로 구성됩니다.&lt;/p&gt;
&lt;p&gt;제가 이 책에서 먼저 이해하고 싶은 부분도 모델 하나의 구현보다 학습과 배포 사이를 연결하는 구조입니다. 도구마다 어떤 문제를 해결하고 어디까지 책임지는지 구분하면서 읽으려고 합니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="책-전체를-관통하는-질문"&gt;책 전체를 관통하는 질문&lt;span class="hx:absolute hx:-mt-20" id="책-전체를-관통하는-질문"&gt;&lt;/span&gt;
&lt;a href="#%ec%b1%85-%ec%a0%84%ec%b2%b4%eb%a5%bc-%ea%b4%80%ed%86%b5%ed%95%98%eb%8a%94-%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;모델이 바뀌어도 서비스를 믿을 수 있으려면 무엇을 기록하고 자동화해야 할까요?&lt;/p&gt;
&lt;div role="img" aria-label="다이어그램"&gt;
&lt;pre class="mermaid hx:mt-6"&gt;
flowchart TD
accTitle: 책의 네 Part와 학습 흐름
accDescr: 1장부터 3장에서 기초를 만들고, 4장부터 6장에서 플랫폼 기능을 연결한다. 7장부터 11장에서 객체 탐지와 추천에 적용한 뒤 12장과 13장에서 RAG와 LLM 운영으로 확장한다.
A[&amp;#34;Part 1, 1~3장&amp;lt;br/&amp;gt;MLOps 이해와 실행 기반&amp;#34;] --&amp;gt; B[&amp;#34;Part 2, 4~6장&amp;lt;br/&amp;gt;실험, 피처, 파이프라인, 배포&amp;#34;]
B --&amp;gt; C[&amp;#34;Part 3, 7~11장&amp;lt;br/&amp;gt;객체 탐지와 영화 추천에 적용&amp;#34;]
C --&amp;gt; D[&amp;#34;Part 4, 12~13장&amp;lt;br/&amp;gt;RAG 구축과 프로덕션 LLM 운영&amp;#34;]
E[&amp;#34;부록 A, 설치&amp;lt;br/&amp;gt;부록 B, YAML&amp;#34;] -.-&amp;gt; A
E -.-&amp;gt; B
&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;앞부분에서 소개한 도구가 뒤쪽 프로젝트에 다시 나옵니다. 같은 설계라도 이미지, 표 데이터, 문서 검색에서 조건이 어떻게 달라지는지 비교할 수 있습니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="책의-흐름"&gt;책의 흐름&lt;span class="hx:absolute hx:-mt-20" id="책의-흐름"&gt;&lt;/span&gt;
&lt;a href="#%ec%b1%85%ec%9d%98-%ed%9d%90%eb%a6%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="part-1-mlops의-기초-13장"&gt;Part 1. MLOps의 기초 (1~3장)&lt;span class="hx:absolute hx:-mt-20" id="part-1-mlops의-기초-13장"&gt;&lt;/span&gt;
&lt;a href="#part-1-mlops%ec%9d%98-%ea%b8%b0%ec%b4%88-13%ec%9e%a5" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;ML 프로젝트를 한 번 끝내는 작업이 아니라 계속 돌아오는 생애주기로 봅니다. 컨테이너와 Kubernetes, 배포 자동화와 기본 모니터링으로 모델이 올라갈 바닥을 만드는 구간입니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;글&lt;/th&gt;
&lt;th&gt;원문 챕터&lt;/th&gt;
&lt;th&gt;읽으며 답할 질문&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="../01-getting-started/"&gt;01. 시작하기&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Getting started with MLOps and ML engineering&lt;/td&gt;
&lt;td&gt;실험에서 서비스로 넘어갈 때 무엇이 추가되는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="../02-what-is-mlops/"&gt;02. MLOps란 무엇인가&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;What is MLOps?&lt;/td&gt;
&lt;td&gt;자동화의 대상은 모델 파일인가, 모델을 만드는 과정인가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="../03-kubernetes-foundation/"&gt;03. Kubernetes 기반&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Building applications on Kubernetes&lt;/td&gt;
&lt;td&gt;같은 애플리케이션을 반복해서 배포하고 관측하려면 무엇이 필요한가?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;이 구간에서는 명령어보다 실행 환경과 배포, 관측의 역할을 구분하는 것이 중요하다고 생각했습니다. 뒤에서 소개하는 ML 도구들도 결국 이 기반 위에서 실행됩니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="part-2-공통-ml-플랫폼-기능-46장"&gt;Part 2. 공통 ML 플랫폼 기능 (4~6장)&lt;span class="hx:absolute hx:-mt-20" id="part-2-공통-ml-플랫폼-기능-46장"&gt;&lt;/span&gt;
&lt;a href="#part-2-%ea%b3%b5%ed%86%b5-ml-%ed%94%8c%eb%9e%ab%ed%8f%bc-%ea%b8%b0%eb%8a%a5-46%ec%9e%a5" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;실험 결과를 기록하고 피처를 재사용하며, 여러 작업을 연결해 예측을 서비스합니다. 소득 분류 예제로 각 기능을 연결합니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;글&lt;/th&gt;
&lt;th&gt;원문 챕터&lt;/th&gt;
&lt;th&gt;읽으며 답할 질문&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;04. 실험과 피처 관리&lt;/td&gt;
&lt;td&gt;Designing reliable ML systems&lt;/td&gt;
&lt;td&gt;이 모델이 어떤 실험과 피처에서 나왔는지 설명할 수 있는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;05. ML 파이프라인&lt;/td&gt;
&lt;td&gt;Orchestrating ML pipelines&lt;/td&gt;
&lt;td&gt;서로 다른 컨테이너의 작업을 어떻게 하나의 실행으로 묶는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;06. 모델의 프로덕션화&lt;/td&gt;
&lt;td&gt;Productionizing ML models&lt;/td&gt;
&lt;td&gt;배포 후 입력 데이터가 달라지면 어떻게 알아차리는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;MLflow, Feast, Kubeflow Pipelines, BentoML, Evidently가 차례로 등장합니다. 처음에는 도구 이름이 많아 보이지만, 실험 기록과 피처 관리, 실행 순서와 배포, 데이터 변화 관측이라는 서로 다른 문제를 맡습니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="part-3-실제-프로젝트에-적용-711장"&gt;Part 3. 실제 프로젝트에 적용 (7~11장)&lt;span class="hx:absolute hx:-mt-20" id="part-3-실제-프로젝트에-적용-711장"&gt;&lt;/span&gt;
&lt;a href="#part-3-%ec%8b%a4%ec%a0%9c-%ed%94%84%eb%a1%9c%ec%a0%9d%ed%8a%b8%ec%97%90-%ec%a0%81%ec%9a%a9-711%ec%9e%a5" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;신분증 이미지에서 카드 영역을 찾는 객체 탐지와 사용자, 영화 평점을 쓰는 추천을 다룹니다. 이미지와 표 데이터의 차이가 저장, 검증, 운영 관측에 어떻게 영향을 주는지 비교할 구간입니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;글&lt;/th&gt;
&lt;th&gt;원문 챕터&lt;/th&gt;
&lt;th&gt;읽으며 답할 질문&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;07. 데이터 분석과 준비&lt;/td&gt;
&lt;td&gt;Data analysis and preparation&lt;/td&gt;
&lt;td&gt;노트북의 탐색을 재실행 가능한 데이터 준비 작업으로 바꾸려면?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;08. 학습과 검증 I&lt;/td&gt;
&lt;td&gt;Model training and validation: Part 1&lt;/td&gt;
&lt;td&gt;학습이 끝났다는 사실과 모델이 쓸 만하다는 사실은 어떻게 다른가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;09. 학습과 검증 II&lt;/td&gt;
&lt;td&gt;Model training and validation: Part 2&lt;/td&gt;
&lt;td&gt;데이터 이동 비용과 실험 추적을 어떻게 함께 설계할까?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10. 추론과 서빙&lt;/td&gt;
&lt;td&gt;Model inference and serving&lt;/td&gt;
&lt;td&gt;모델 파일을 다른 사람이 쓸 수 있는 서비스로 만들려면?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11. 모니터링과 설명가능성&lt;/td&gt;
&lt;td&gt;Monitoring and explainability&lt;/td&gt;
&lt;td&gt;서비스가 살아 있는지와 예측이 유용한지를 각각 어떻게 볼까?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;책은 OCR 시스템이라는 넓은 맥락도 쓰지만 주요 학습 대상은 신분증 객체 탐지입니다. 카드 위치를 찾는 것과 카드 안의 글자를 읽는 OCR은 구분해 둡니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="part-4-llm-운영-1213장"&gt;Part 4. LLM 운영 (12~13장)&lt;span class="hx:absolute hx:-mt-20" id="part-4-llm-운영-1213장"&gt;&lt;/span&gt;
&lt;a href="#part-4-llm-%ec%9a%b4%ec%98%81-1213%ec%9e%a5" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;마지막에는 사내 문서를 찾아 답하는 DakkaBot을 만듭니다. 직접 학습한 모델의 가중치를 관리하던 문제에 검색 문서, 벡터 인덱스, 프롬프트, 외부 모델 API 관리가 더해집니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;글&lt;/th&gt;
&lt;th&gt;원문 챕터&lt;/th&gt;
&lt;th&gt;읽으며 답할 질문&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;12. LLM 기반 시스템&lt;/td&gt;
&lt;td&gt;Designing LLM-powered systems&lt;/td&gt;
&lt;td&gt;잘못된 답변의 원인이 검색인지 생성인지 구분할 수 있는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13. 프로덕션 LLM 설계&lt;/td&gt;
&lt;td&gt;Production LLM system design&lt;/td&gt;
&lt;td&gt;답변 품질, 안전, 비용을 바꾸는 변경을 어떻게 검증하고 되돌릴까?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;RAG는 필요한 문서를 먼저 찾고 그 내용을 참고해 답하게 하는 방식입니다. 문서를 잘못 찾으면 모델이 커져도 답이 좋아지지 않을 수 있습니다. 검색을 붙였다고 환각이나 정보 유출 위험이 사라지는 것도 아닙니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="도구-이름보다-역할을-기억하기"&gt;도구 이름보다 역할을 기억하기&lt;span class="hx:absolute hx:-mt-20" id="도구-이름보다-역할을-기억하기"&gt;&lt;/span&gt;
&lt;a href="#%eb%8f%84%ea%b5%ac-%ec%9d%b4%eb%a6%84%eb%b3%b4%eb%8b%a4-%ec%97%ad%ed%95%a0%ec%9d%84-%ea%b8%b0%ec%96%b5%ed%95%98%ea%b8%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;th&gt;책에서 다루는 도구&lt;/th&gt;
&lt;th&gt;해결하려는 문제&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;실행 환경과 클러스터&lt;/td&gt;
&lt;td&gt;Docker, Kubernetes, Helm&lt;/td&gt;
&lt;td&gt;의존성과 자원을 관리하며 반복 배포하기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;빌드와 배포 자동화&lt;/td&gt;
&lt;td&gt;GitLab CI, Argo CD&lt;/td&gt;
&lt;td&gt;변경을 검증하고 선언된 상태로 배포하기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;실험 추적과 모델 관리&lt;/td&gt;
&lt;td&gt;MLflow&lt;/td&gt;
&lt;td&gt;어떤 조건으로 만든 모델인지 추적하기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;피처 재사용&lt;/td&gt;
&lt;td&gt;Feast&lt;/td&gt;
&lt;td&gt;학습, 배치, 온라인 예측에 필요한 피처 제공하기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;작업 오케스트레이션&lt;/td&gt;
&lt;td&gt;Kubeflow Pipelines&lt;/td&gt;
&lt;td&gt;단계 사이의 데이터와 실행 의존성 관리하기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;모델 서비스&lt;/td&gt;
&lt;td&gt;BentoML, MLflow Serving, KServe&lt;/td&gt;
&lt;td&gt;모델을 사용할 수 있는 인터페이스 제공하기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;운영 관측과 학습 분석&lt;/td&gt;
&lt;td&gt;Prometheus, Grafana, Loki, Alertmanager, TensorBoard&lt;/td&gt;
&lt;td&gt;서비스 상태와 학습 과정을 각각 살피기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 변화와 모델 설명&lt;/td&gt;
&lt;td&gt;Evidently, Deepchecks, Eigen-CAM, EMF&lt;/td&gt;
&lt;td&gt;입력 변화와 예측 행동 조사하기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAG와 대화 UI&lt;/td&gt;
&lt;td&gt;Gemini, FAISS, LangChain, Chainlit&lt;/td&gt;
&lt;td&gt;문서 검색과 답변 생성을 연결하기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM 품질, 안전, 비용&lt;/td&gt;
&lt;td&gt;Langfuse, DeepEval, Promptfoo, Guardrails AI, RedisVL&lt;/td&gt;
&lt;td&gt;추적, 평가, 방어, 캐시를 운영하기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;도구를 모두 도입할 필요는 없습니다. 해결할 문제가 무엇인지, 기존 체계로는 왜 부족한지부터 따져야 합니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="함께-볼-부록"&gt;함께 볼 부록&lt;span class="hx:absolute hx:-mt-20" id="함께-볼-부록"&gt;&lt;/span&gt;
&lt;a href="#%ed%95%a8%ea%bb%98-%eb%b3%bc-%eb%b6%80%eb%a1%9d" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;부록 A, Installation and setup은 클러스터와 Kubeflow, MLflow 및 주변 구성 요소의 설치 참고 자료입니다. 로컬 안내는 Mac, Linux 중심이므로 Windows 실습에서는 WSL2나 별도 Linux 환경 등 실행 환경부터 정할 필요가 있습니다.&lt;/p&gt;
&lt;p&gt;부록 B, Basics of YAML은 Kubernetes 설정을 읽는 보조 지도입니다. 들여쓰기, 문자열, 리스트, 맵, 앵커, 별칭은 3장과 함께 확인할 항목입니다.&lt;/p&gt;
&lt;p&gt;책의 명령어와 API는 집필 당시 버전이 전제입니다. KFP의 데이터 전달, BentoML의 Service, Runner API, MLflow의 모델 단계 관리는 현재 버전과 다를 수 있습니다. 책의 동작 원리와 최신 버전에서 그대로 실행되는지는 별개로 확인해야 합니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="실제-업무와-연결할-지점"&gt;실제 업무와 연결할 지점&lt;span class="hx:absolute hx:-mt-20" id="실제-업무와-연결할-지점"&gt;&lt;/span&gt;
&lt;a href="#%ec%8b%a4%ec%a0%9c-%ec%97%85%eb%ac%b4%ec%99%80-%ec%97%b0%ea%b2%b0%ed%95%a0-%ec%a7%80%ec%a0%90" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;도구가 설치되어 있어도 운영 책임과 장애 대응이 빠져 있으면 플랫폼이 완성됐다고 보기 어렵습니다. 각 구간에서 필요한 결과물을 같이 생각해 볼 수 있습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;읽는 구간&lt;/th&gt;
&lt;th&gt;엔지니어가 남길 결과&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1~3장&lt;/td&gt;
&lt;td&gt;생애주기, 책임 경계, 재현 가능한 실행, 배포 기반&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4~6장&lt;/td&gt;
&lt;td&gt;실험, 피처 계보, 작업 계약, 서비스와 드리프트 대응&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7~9장&lt;/td&gt;
&lt;td&gt;데이터 계약, 검증 게이트, 학습 자원, 성능 측정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10~11장&lt;/td&gt;
&lt;td&gt;출시 명세, 부하, 복구 결과, SLO와 실행 지침&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12~13장&lt;/td&gt;
&lt;td&gt;검색, 답변 평가, 권한 경계, 변경, 비용 통제&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-hextra-search-id="프로젝트에서-확인할-것"&gt;프로젝트에서 확인할 것&lt;span class="hx:absolute hx:-mt-20" id="프로젝트에서-확인할-것"&gt;&lt;/span&gt;
&lt;a href="#%ed%94%84%eb%a1%9c%ec%a0%9d%ed%8a%b8%ec%97%90%ec%84%9c-%ed%99%95%ec%9d%b8%ed%95%a0-%ea%b2%83" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;문제와 기준: 업무 목표, 데이터 사용 범위, 현재 병목과 성공 기준.&lt;/li&gt;
&lt;li&gt;설계 결정: 채택, 제외한 대안, 의존성, 운영 비용과 담당자.&lt;/li&gt;
&lt;li&gt;검증 증거: 데이터, 모델, 서비스 시험 결과, 실패 재현과 복구 기록.&lt;/li&gt;
&lt;li&gt;인수 자료: 배포, 롤백 절차, SLO, 알림, 실행 지침, 접근, 보존 정책.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;설계한 구조와 실제 구현한 구조는 다를 수 있습니다. 성능과 복구 시간은 실행 결과로 확인해야 합니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="남는-질문"&gt;남는 질문&lt;span class="hx:absolute hx:-mt-20" id="남는-질문"&gt;&lt;/span&gt;
&lt;a href="#%eb%82%a8%eb%8a%94-%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;정확도가 가장 높은 모델을 바로 배포해도 될까요? 지연 시간과 데이터 누수, 재현성과 롤백까지 생각하면 점수 하나만으로 결정하기는 어렵습니다.&lt;/p&gt;
&lt;p&gt;책을 읽으면서도 이 질문을 계속 기준으로 두려고 합니다. 어떤 도구를 쓸지보다 그 도구로 어떤 근거를 남기고 어떤 실패를 줄일지 설명할 수 있어야겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://livebook.manning.com/book/machine-learning-platform-engineering" target="_blank" rel="noopener"&gt;Manning liveBook&lt;/a&gt; / &lt;a href="https://github.com/practical-mlops" target="_blank" rel="noopener"&gt;저자 코드&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="../"&gt;시리즈 목차&lt;/a&gt;, &lt;a href="../01-getting-started/"&gt;다음: 01. MLOps와 ML 엔지니어링 시작하기&lt;/a&gt;&lt;/p&gt;</description></item><item><title>01. AI 애플리케이션과 에이전트</title><link>https://johklo.github.io/docs/tech/ai-agents-and-applications/01-introduction/</link><pubDate>Mon, 19 Oct 2026 21:37:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/ai-agents-and-applications/01-introduction/</guid><description>
&lt;p&gt;LLM을 호출하는 코드와 LLM 애플리케이션은 범위가 다르다. 모델은 입력을 받아 출력을 생성하지만, 어떤 자료를 넣고 결과를 어디에 쓰며 실패하면 어떻게 처리할지는 주변 프로그램의 책임이다. 이 경계를 구분해야 프롬프트의 문제와 시스템 구성의 문제를 따로 볼 수 있다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="11-llm-기반-애플리케이션과-에이전트"&gt;1.1 LLM 기반 애플리케이션과 에이전트&lt;span class="hx:absolute hx:-mt-20" id="11-llm-기반-애플리케이션과-에이전트"&gt;&lt;/span&gt;
&lt;a href="#11-llm-%ea%b8%b0%eb%b0%98-%ec%95%a0%ed%94%8c%eb%a6%ac%ec%bc%80%ec%9d%b4%ec%85%98%ea%b3%bc-%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;기본 흐름은 입력을 받고, 필요한 문맥을 모아 프롬프트를 구성한 뒤, 모델 응답을 다음 단계로 넘기는 것이다. 여기에 대화 이력을 붙이면 챗봇이 되고, 모델의 판단으로 도구를 선택하고 실행 결과를 다시 처리하면 에이전트가 된다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="111-요약-엔진과-질의응답-엔진"&gt;1.1.1 요약 엔진과 질의응답 엔진&lt;span class="hx:absolute hx:-mt-20" id="111-요약-엔진과-질의응답-엔진"&gt;&lt;/span&gt;
&lt;a href="#111-%ec%9a%94%ec%95%bd-%ec%97%94%ec%a7%84%ea%b3%bc-%ec%a7%88%ec%9d%98%ec%9d%91%eb%8b%b5-%ec%97%94%ec%a7%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;엔진은 특정 자연어 처리를 다른 프로그램에 제공한다. 문서 저장 시스템이 새 보고서를 받으면 요약 엔진을 호출해 요약문을 저장하는 식이다. 사람이 대화창 앞에 있을 필요는 없다.&lt;/p&gt;
&lt;p&gt;입력이 문서와 원하는 요약 조건이라면 출력은 요약문이다. 질의응답 엔진은 문서와 질문을 받아 답을 생성한다. REST API로 제공할 수도 있고 배치 작업 내부에서 호출할 수도 있다. API는 접근 방식이고 요약이나 질의응답은 수행할 기능이다.&lt;/p&gt;
&lt;p&gt;처리 결과가 자연어라는 점도 중요하다. 출력이 다음 프로그램의 입력이 된다면 빈 응답, 잘못된 형식, 근거 없는 내용을 검사해야 한다. 모델이 응답했다는 사실만으로 처리가 성공했다고 볼 수는 없다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="112-챗봇"&gt;1.1.2 챗봇&lt;span class="hx:absolute hx:-mt-20" id="112-챗봇"&gt;&lt;/span&gt;
&lt;a href="#112-%ec%b1%97%eb%b4%87" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;챗봇은 사람과 여러 차례 대화한다. “그 내용을 더 짧게 해 줘”처럼 이전 답변을 가리키는 요청이 가능하려면 과거 메시지나 그 요약을 다음 모델 호출에 넣어야 한다.&lt;/p&gt;
&lt;p&gt;모델이 대화창 밖에 있는 이력을 자동으로 아는 것은 아니다. 애플리케이션이 어떤 이력을 저장하고 다시 보낼지 정한다. 대화가 길어지면 모든 내용을 계속 넣을 수도 없으므로 문맥 길이와 비용, 개인정보를 함께 고려해야 한다.&lt;/p&gt;
&lt;p&gt;메시지에는 system, user, assistant 등의 역할이 붙는다. 역할은 서비스 지침, 사용자 요청, 이전 응답을 구분하는 구조다. 문서를 검색해 넣는 경우에도 문서 본문과 시스템 지침을 같은 신뢰 수준으로 취급하면 안 된다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="113-ai-에이전트"&gt;1.1.3 AI 에이전트&lt;span class="hx:absolute hx:-mt-20" id="113-ai-에이전트"&gt;&lt;/span&gt;
&lt;a href="#113-ai-%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;에이전트는 결과만 생성하지 않고 다음에 할 일을 선택하는 루프를 가진다. 여행 요청에서 관광 정보를 검색한 뒤 날씨를 확인하고, 필요한 정보가 모이면 답을 작성하는 식이다.&lt;/p&gt;
&lt;p&gt;여기서 모델이 외부 함수를 직접 실행하는 것은 아니다. 모델은 도구 이름과 인자를 포함한 호출 요청을 내고, 프로그램이 요청을 검증한 뒤 실행한다. 도구 결과를 모델에 돌려주면 추가 작업이 필요한지 또는 답을 끝낼지 판단한다.&lt;/p&gt;
&lt;div role="img" aria-label="다이어그램"&gt;
&lt;pre class="mermaid hx:mt-6"&gt;
flowchart TD
accTitle: 엔진과 챗봇에서 에이전트로 달라지는 실행 책임
accDescr: 엔진은 정해진 입력을 처리하고, 챗봇은 대화 이력을 포함한다. 에이전트는 모델이 요청한 도구를 프로그램이 실행한 뒤 결과를 다시 모델에 전달한다.
U[&amp;#34;요청과 필요한 문맥&amp;#34;] --&amp;gt; M[&amp;#34;모델 호출&amp;#34;]
M --&amp;gt; D{&amp;#34;도구 호출 요청인가?&amp;#34;}
D --&amp;gt;|&amp;#34;아니요&amp;#34;| A[&amp;#34;최종 응답&amp;#34;]
D --&amp;gt;|&amp;#34;예&amp;#34;| V[&amp;#34;인자와 권한 확인&amp;#34;]
V --&amp;gt; T[&amp;#34;애플리케이션이 도구 실행&amp;#34;]
T --&amp;gt; R[&amp;#34;실행 결과를 메시지에 추가&amp;#34;]
R --&amp;gt; M
&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;자율성은 권한과 별개다. 모델이 예약 도구를 골랐다고 실제 결제까지 허용해야 하는 것은 아니다. 호출 횟수와 종료 조건, 실행 권한은 프로그램이 통제해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="12-langchain"&gt;1.2 LangChain&lt;span class="hx:absolute hx:-mt-20" id="12-langchain"&gt;&lt;/span&gt;
&lt;a href="#12-langchain" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;LLM 애플리케이션에는 반복되는 작업이 많다. 문서를 읽고 나누는 일, 임베딩을 만들고 검색하는 일, 프롬프트를 조립하고 응답을 해석하는 일이 그렇다. LangChain은 이 구성 요소를 공통 인터페이스로 연결한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="121-아키텍처"&gt;1.2.1 아키텍처&lt;span class="hx:absolute hx:-mt-20" id="121-아키텍처"&gt;&lt;/span&gt;
&lt;a href="#121-%ec%95%84%ed%82%a4%ed%85%8d%ec%b2%98" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;문서를 적재하는 과정과 질문에 답하는 과정을 나눠 보면 구조가 단순해진다.&lt;/p&gt;
&lt;p&gt;문서 적재에서는 원본을 읽고 청크로 나눈 뒤 임베딩과 원문을 저장한다. 질문 처리에서는 질문과 관련된 청크를 검색하고, 검색한 원문을 질문과 함께 생성 모델에 전달한다.&lt;/p&gt;
&lt;p&gt;임베딩 모델과 생성 모델은 역할이 다르다. 임베딩 모델은 비교할 벡터를 만들고, 생성 모델은 문맥을 읽고 답을 작성한다. 검색에 사용한 벡터 자체를 자연어 근거처럼 읽는 것이 아니다.&lt;/p&gt;
&lt;p&gt;LangChain을 쓰면 저장소나 모델 구현을 교체하기 쉬워질 수 있다. 다만 인터페이스가 같아도 출력 품질, 지원 기능, 지연과 오류는 달라진다. 구현 교체가 곧 동작의 동등성을 뜻하지는 않는다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="122-핵심-객체-모델"&gt;1.2.2 핵심 객체 모델&lt;span class="hx:absolute hx:-mt-20" id="122-핵심-객체-모델"&gt;&lt;/span&gt;
&lt;a href="#122-%ed%95%b5%ec%8b%ac-%ea%b0%9d%ec%b2%b4-%eb%aa%a8%eb%8d%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;중심 객체인 &lt;code&gt;Document&lt;/code&gt;는 본문과 메타데이터를 묶는다. 본문은 검색이나 요약에 쓰고, 메타데이터는 파일명, 출처, 페이지, 문서 식별자 등을 보존하는 데 쓴다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구성 요소&lt;/th&gt;
&lt;th&gt;입력과 출력&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Loader&lt;/td&gt;
&lt;td&gt;파일이나 URL에서 &lt;code&gt;Document&lt;/code&gt; 목록 생성&lt;/td&gt;
&lt;td&gt;자료 형식을 공통 형태로 바꾸기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Splitter&lt;/td&gt;
&lt;td&gt;문서를 여러 &lt;code&gt;Document&lt;/code&gt;로 분할&lt;/td&gt;
&lt;td&gt;문맥 길이와 검색 단위 조정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embedding model&lt;/td&gt;
&lt;td&gt;텍스트를 숫자 벡터로 변환&lt;/td&gt;
&lt;td&gt;의미적 유사도 비교의 기반&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vector store&lt;/td&gt;
&lt;td&gt;벡터와 관련 자료를 저장하고 검색&lt;/td&gt;
&lt;td&gt;가까운 문서 후보 찾기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retriever&lt;/td&gt;
&lt;td&gt;질문을 받아 문서 목록 반환&lt;/td&gt;
&lt;td&gt;검색 구현을 호출 측에서 분리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt template&lt;/td&gt;
&lt;td&gt;변수로 프롬프트나 메시지 구성&lt;/td&gt;
&lt;td&gt;고정 지침과 요청별 데이터 결합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language model&lt;/td&gt;
&lt;td&gt;프롬프트를 받아 모델 응답 생성&lt;/td&gt;
&lt;td&gt;분류, 요약, 답변 등의 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output parser&lt;/td&gt;
&lt;td&gt;모델 응답을 문자열이나 구조화된 값으로 해석&lt;/td&gt;
&lt;td&gt;후속 코드가 사용할 형태 만들기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;Runnable&lt;/code&gt;은 이런 실행 단위를 조합하는 인터페이스다. LCEL의 &lt;code&gt;|&lt;/code&gt;는 앞 구성 요소의 결과를 다음 구성 요소로 넘긴다. 셸 파이프와 비슷해 보이지만 텍스트만 흐르는 것은 아니다. 사전, 문서 목록, 프롬프트 값, 메시지 객체처럼 각 단계의 자료형이 바뀔 수 있다.&lt;/p&gt;
&lt;p&gt;예를 들어 &lt;code&gt;prompt | model | parser&lt;/code&gt;에서 템플릿은 변수가 담긴 사전을 받고, 모델은 완성된 프롬프트를 받으며, 파서는 모델 메시지를 해석한다. 체인이 실패하면 각 단계의 입력과 출력 형태부터 확인할 필요가 있다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="13-대표-사용-사례"&gt;1.3 대표 사용 사례&lt;span class="hx:absolute hx:-mt-20" id="13-대표-사용-사례"&gt;&lt;/span&gt;
&lt;a href="#13-%eb%8c%80%ed%91%9c-%ec%82%ac%ec%9a%a9-%ec%82%ac%eb%a1%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;LLM의 활용은 자유로운 글 생성에만 한정되지 않는다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;작업&lt;/th&gt;
&lt;th&gt;예시&lt;/th&gt;
&lt;th&gt;확인할 조건&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;분류와 감정 분석&lt;/td&gt;
&lt;td&gt;문의 유형이나 리뷰의 감정 분류&lt;/td&gt;
&lt;td&gt;허용 범주와 애매한 입력 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;이해와 생성&lt;/td&gt;
&lt;td&gt;문서 요약, 문체 변환, 설명 작성&lt;/td&gt;
&lt;td&gt;원문 사실과 생성한 표현의 구분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;의미 검색&lt;/td&gt;
&lt;td&gt;표현이 다른 질문으로 관련 문서 찾기&lt;/td&gt;
&lt;td&gt;유사도가 실제 관련성을 나타내는지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;추론과 작업 실행&lt;/td&gt;
&lt;td&gt;여러 조건을 확인해 여행 일정 구성&lt;/td&gt;
&lt;td&gt;계산과 외부 사실의 검증&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;구조화된 정보 추출&lt;/td&gt;
&lt;td&gt;계약서에서 날짜와 당사자 추출&lt;/td&gt;
&lt;td&gt;스키마와 누락 값, 출처 위치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;코드 이해와 생성&lt;/td&gt;
&lt;td&gt;함수 설명이나 수정안 작성&lt;/td&gt;
&lt;td&gt;실행 결과와 테스트&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;같은 모델을 사용해도 성공 기준은 다르다. 생성 문장이 자연스러운지와 추출한 날짜가 맞는지, 적절한 도구를 선택했는지는 서로 다른 평가다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="14-목적에-맞게-llm을-조정하는-방법"&gt;1.4 목적에 맞게 LLM을 조정하는 방법&lt;span class="hx:absolute hx:-mt-20" id="14-목적에-맞게-llm을-조정하는-방법"&gt;&lt;/span&gt;
&lt;a href="#14-%eb%aa%a9%ec%a0%81%ec%97%90-%eb%a7%9e%ea%b2%8c-llm%ec%9d%84-%ec%a1%b0%ec%a0%95%ed%95%98%eb%8a%94-%eb%b0%a9%eb%b2%95" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="141-프롬프트-엔지니어링"&gt;1.4.1 프롬프트 엔지니어링&lt;span class="hx:absolute hx:-mt-20" id="141-프롬프트-엔지니어링"&gt;&lt;/span&gt;
&lt;a href="#141-%ed%94%84%eb%a1%ac%ed%94%84%ed%8a%b8-%ec%97%94%ec%a7%80%eb%8b%88%ec%96%b4%eb%a7%81" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;현재 요청에서 수행할 작업과 근거, 제약, 출력 형식을 정한다. 입력 안에 예시를 넣어 패턴을 보여 주는 in-context learning도 여기에 포함된다.&lt;/p&gt;
&lt;p&gt;이때 모델 가중치가 바뀌지는 않는다. 예시를 제거하면 그 예시에 의존하던 동작도 달라질 수 있다. “프롬프트로 학습했다”는 표현과 모델을 실제로 훈련한 것을 구분해야 한다.&lt;/p&gt;
&lt;p&gt;반복되는 지침은 템플릿으로 두고 문서나 질문처럼 매번 바뀌는 부분을 변수로 넣는다. 템플릿으로 관리하면 어느 조건을 바꿨는지 비교하기도 쉽다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="142-rag"&gt;1.4.2 RAG&lt;span class="hx:absolute hx:-mt-20" id="142-rag"&gt;&lt;/span&gt;
&lt;a href="#142-rag" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;RAG는 답에 필요한 자료를 검색해 프롬프트에 넣는다. 사내 규정처럼 자주 바뀌는 정보를 다룰 때 모델의 사전학습 내용만 믿지 않고 현재 문서를 제공할 수 있다.&lt;/p&gt;
&lt;p&gt;검색은 필요한 근거를 찾는 일이고 생성은 그 근거로 답을 만드는 일이다. 검색 결과가 틀렸는데 모델의 문장 생성 능력만 높여도 원하는 답을 얻기 어렵다. 검색된 문서와 실제 프롬프트, 최종 답변을 나눠 볼 수 있어야 한다.&lt;/p&gt;
&lt;p&gt;RAG가 곧 벡터 검색만을 뜻하지는 않는다. 키워드, 메타데이터, 관계형 DB 등에서 얻은 자료도 근거가 될 수 있다. 이 책은 벡터 검색으로 출발해 뒤에서 검색 방식을 확장한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="143-파인튜닝"&gt;1.4.3 파인튜닝&lt;span class="hx:absolute hx:-mt-20" id="143-파인튜닝"&gt;&lt;/span&gt;
&lt;a href="#143-%ed%8c%8c%ec%9d%b8%ed%8a%9c%eb%8b%9d" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;파인튜닝은 예제 데이터로 모델의 학습된 동작을 조정한다. 특정 응답 형식이나 전문 작업의 일관성을 개선할 때 검토할 수 있다.&lt;/p&gt;
&lt;p&gt;RAG와 달리 모델을 학습하고 버전을 관리하는 과정이 추가된다. 좋은 학습 데이터와 별도 평가 데이터가 필요하며, 운영 정보를 갱신하는 문제를 모두 파인튜닝으로 해결하려 하면 변경 비용이 커질 수 있다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;방법&lt;/th&gt;
&lt;th&gt;주로 바꾸는 것&lt;/th&gt;
&lt;th&gt;잘 맞는 문제&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;프롬프트&lt;/td&gt;
&lt;td&gt;현재 호출의 지침과 예시&lt;/td&gt;
&lt;td&gt;작업과 응답 형식 지정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAG&lt;/td&gt;
&lt;td&gt;현재 호출에 들어가는 외부 근거&lt;/td&gt;
&lt;td&gt;최신 문서와 출처 기반 답변&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;파인튜닝&lt;/td&gt;
&lt;td&gt;모델의 학습된 동작&lt;/td&gt;
&lt;td&gt;반복되는 특화 작업과 형식의 일관성&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;세 방법은 상호 배타적이지 않다. 파인튜닝한 모델에 검색 자료를 넣을 수도 있다. 핵심은 부족한 것이 지시인지, 정보인지, 학습된 수행 능력인지 구분하는 것이다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="15-모델-선택"&gt;1.5 모델 선택&lt;span class="hx:absolute hx:-mt-20" id="15-모델-선택"&gt;&lt;/span&gt;
&lt;a href="#15-%eb%aa%a8%eb%8d%b8-%ec%84%a0%ed%83%9d" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;모델 선택은 벤치마크 점수 하나로 끝나지 않는다. 입력 길이, 도구 호출 지원, 구조화된 출력, 응답 지연, 비용, 배포 환경과 데이터 처리 조건을 함께 본다.&lt;/p&gt;
&lt;p&gt;문맥 창은 한 번에 처리할 수 있는 토큰 예산이다. 토큰은 단어와 같지 않으며 언어와 인코더에 따라 수가 달라진다. 문서 외에도 시스템 지침, 대화 이력, 도구 정의와 생성할 답변이 예산을 사용한다.&lt;/p&gt;
&lt;p&gt;작은 모델은 짧은 분류나 요약에 충분할 수 있지만 복잡한 질의 분해에서는 부족할 수 있다. 반대로 모든 단계에 가장 큰 모델을 쓰면 비용과 지연이 커진다. 단계별로 다른 모델을 쓰더라도 각 단계의 품질을 따로 확인해야 한다.&lt;/p&gt;
&lt;p&gt;외부 API와 자체 호스팅의 차이도 있다. API는 모델 운영 부담을 줄일 수 있지만 요청 제한과 제공자의 데이터 처리 조건이 중요하다. 자체 호스팅은 환경 통제가 쉬운 대신 연산 자원과 모델 서버 운영을 맡아야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="16-뒤의-프로젝트로-이어지는-흐름"&gt;1.6 뒤의 프로젝트로 이어지는 흐름&lt;span class="hx:absolute hx:-mt-20" id="16-뒤의-프로젝트로-이어지는-흐름"&gt;&lt;/span&gt;
&lt;a href="#16-%eb%92%a4%ec%9d%98-%ed%94%84%eb%a1%9c%ec%a0%9d%ed%8a%b8%eb%a1%9c-%ec%9d%b4%ec%96%b4%ec%a7%80%eb%8a%94-%ed%9d%90%eb%a6%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;프롬프트 호출은 요약 체인의 구성 요소가 된다. 요약 체인은 웹 검색과 결합해 리서치 엔진이 되고, 상태와 조건부 분기를 붙이면 LangGraph 워크플로로 바뀐다.&lt;/p&gt;
&lt;p&gt;다른 흐름에서는 문서를 검색 가능한 형태로 준비한 뒤 질의응답에 사용한다. 검색기를 도구로 제공하면 에이전트가 필요할 때 호출할 수 있다. MCP는 그 도구를 별도 서버에서 제공하는 연결 규약이고, 체크포인트와 가드레일은 실행을 보존하고 통제하는 기반이다.&lt;/p&gt;
&lt;p&gt;새 프레임워크 이름을 외우기보다 입력, 상태, 검색 근거, 실행 권한이 어디에 있는지 따라가는 것이 중요하다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="질문"&gt;질문&lt;span class="hx:absolute hx:-mt-20" id="질문"&gt;&lt;/span&gt;
&lt;a href="#%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="문서-검색-기능이-있으면-에이전트일까"&gt;문서 검색 기능이 있으면 에이전트일까?&lt;span class="hx:absolute hx:-mt-20" id="문서-검색-기능이-있으면-에이전트일까"&gt;&lt;/span&gt;
&lt;a href="#%eb%ac%b8%ec%84%9c-%ea%b2%80%ec%83%89-%ea%b8%b0%eb%8a%a5%ec%9d%b4-%ec%9e%88%ec%9c%bc%eb%a9%b4-%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8%ec%9d%bc%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;검색과 답변 생성이 고정된 순서라면 RAG 파이프라인일 수 있다. 모델이 도구를 고르고 결과에 따라 다음 행동을 정하는지까지 봐야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="모델이-모든-지식을-학습하면-외부-데이터가-필요-없을까"&gt;모델이 모든 지식을 학습하면 외부 데이터가 필요 없을까?&lt;span class="hx:absolute hx:-mt-20" id="모델이-모든-지식을-학습하면-외부-데이터가-필요-없을까"&gt;&lt;/span&gt;
&lt;a href="#%eb%aa%a8%eb%8d%b8%ec%9d%b4-%eb%aa%a8%eb%93%a0-%ec%a7%80%ec%8b%9d%ec%9d%84-%ed%95%99%ec%8a%b5%ed%95%98%eb%a9%b4-%ec%99%b8%eb%b6%80-%eb%8d%b0%ec%9d%b4%ed%84%b0%ea%b0%80-%ed%95%84%ec%9a%94-%ec%97%86%ec%9d%84%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;학습 시점 이후의 변경과 접근 권한, 답의 출처는 남는다. 기억하고 있는 내용과 현재 확인할 수 있는 근거는 다른 문제다.&lt;/p&gt;
&lt;p&gt;&lt;a href="../00-overview/"&gt;이전: 책의 구성&lt;/a&gt; / &lt;a href="../"&gt;시리즈 목차&lt;/a&gt; / &lt;a href="../02-prompts/"&gt;다음: 프롬프트 실행&lt;/a&gt;&lt;/p&gt;</description></item><item><title>01. MLOps와 ML 엔지니어링 시작하기</title><link>https://johklo.github.io/docs/tech/machine-learning-platform-engineering/01-getting-started/</link><pubDate>Sun, 18 Oct 2026 21:30:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/machine-learning-platform-engineering/01-getting-started/</guid><description>
&lt;p&gt;1장은 모델을 만드는 일과 서비스로 운영하는 일이 어디서 달라지는지 설명합니다. 학습한 가중치가 있어도 전처리와 로딩 코드, 실행 환경이 맞아야 실제 추론이 가능합니다.&lt;/p&gt;
&lt;p&gt;이번 장에서 가장 중요하게 본 부분은 모델 파일을 남기는 것과 모델의 출처를 남기는 일이 다르다는 점입니다. 배포만 자동화되어 있어도 어떤 데이터와 코드로 만든 모델인지 설명하지 못할 수 있습니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="11-ml-생애주기"&gt;1.1 ML 생애주기&lt;span class="hx:absolute hx:-mt-20" id="11-ml-생애주기"&gt;&lt;/span&gt;
&lt;a href="#11-ml-%ec%83%9d%ec%95%a0%ec%a3%bc%ea%b8%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;ML 생애주기는 문제 정의부터 데이터 준비, 학습, 평가, 배포와 개선까지 연결한 지도입니다. 직선처럼 그릴 수 있어도 실제로는 앞 단계로 자주 돌아갑니다. 지표가 나쁘다면 모델뿐 아니라 데이터와 문제 정의도 다시 봐야 합니다.&lt;/p&gt;
&lt;p&gt;학습과 추론은 먼저 구분할 필요가 있습니다. 학습은 데이터와 정답을 이용해 모델의 가중치를 구하는 과정입니다. 추론은 이미 학습한 모델에 새 입력을 넣어 예측을 얻는 과정입니다. 새 사진 한 장을 받아 카드 위치를 찾는 API 요청이 들어올 때마다 모델을 다시 학습하는 것은 아닙니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="111-실험-단계"&gt;1.1.1 실험 단계&lt;span class="hx:absolute hx:-mt-20" id="111-실험-단계"&gt;&lt;/span&gt;
&lt;a href="#111-%ec%8b%a4%ed%97%98-%eb%8b%a8%ea%b3%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;실험의 첫 질문은 “이 문제에 ML이 필요한가?”입니다. 단순한 규칙으로 충분하다면 더 저렴하고 설명하기 쉬울 수 있습니다. ML이 필요하다면 어떤 의사결정을 개선할지와 성공 기준부터 정해야 합니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;단계&lt;/th&gt;
&lt;th&gt;해야 할 일&lt;/th&gt;
&lt;th&gt;남길 근거&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;문제 정의&lt;/td&gt;
&lt;td&gt;사용자, 업무 목표와 오판의 비용 확인&lt;/td&gt;
&lt;td&gt;기준선, 기대 효과, 평가 기준&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 수집, 준비&lt;/td&gt;
&lt;td&gt;라벨링과 학습, 검증, 테스트 분할&lt;/td&gt;
&lt;td&gt;원천, 라벨 기준, 분할 규칙&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 버전 관리&lt;/td&gt;
&lt;td&gt;어떤 데이터를 사용했는지 고정&lt;/td&gt;
&lt;td&gt;스냅샷 또는 재구성 가능한 식별자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;학습&lt;/td&gt;
&lt;td&gt;데이터, 코드, 설정으로 모델 생성&lt;/td&gt;
&lt;td&gt;실행 조건, 가중치, 로그&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;평가&lt;/td&gt;
&lt;td&gt;학습에 쓰지 않은 데이터로 성능 측정&lt;/td&gt;
&lt;td&gt;평가셋 버전, 지표, 오류 사례&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용 목적 검증&lt;/td&gt;
&lt;td&gt;업무 요구를 만족하는지 확인&lt;/td&gt;
&lt;td&gt;승인, 보류 기준과 판단 근거&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;여기서 평가(evaluation)는 측정에, 검증(validation)은 의도한 용도에 적합한지 확인하는 데 초점을 둡니다. 조직마다 용어가 다를 수 있으므로 이름보다 절차와 책임을 합의하는 것이 중요합니다.&lt;/p&gt;
&lt;p&gt;신분증 탐지를 예로 보면 학습 데이터는 사진만으로 구성되지 않습니다. 사진에서 카드가 어디에 있는지 표시한 경계 상자와 클래스 정보도 필요합니다. 사진은 입력이고 상자와 클래스가 정답입니다. 학습 결과는 이 대응 관계를 반영한 모델 가중치입니다.&lt;/p&gt;
&lt;p&gt;데이터를 나누는 이유도 각각 다릅니다. 학습셋은 가중치를 학습하는 데, 검증셋은 모델과 설정을 선택하는 데 사용합니다. 테스트셋은 그 선택에 쓰지 않고 마지막 성능을 확인하는 용도로 남깁니다. 같은 영상에서 뽑은 거의 동일한 프레임이 학습셋과 테스트셋에 섞이면 새로운 촬영 상황에서의 성능을 과대평가하기 쉽습니다.&lt;/p&gt;
&lt;p&gt;같은 코드라도 데이터가 바뀌면 다른 모델이 나옵니다. 계보(lineage)는 완성품에서 재료와 제조 이력까지 거슬러 올라가는 연결입니다. 여기서는 데이터, 코드, 설정과 실행, 결과의 관계를 뜻합니다.&lt;/p&gt;
&lt;div role="img" aria-label="다이어그램"&gt;
&lt;pre class="mermaid hx:mt-6"&gt;
flowchart TD
accTitle: 실험의 출처와 운영 피드백을 연결하는 ML 생애주기
accDescr: 문제 정의 후 데이터, 코드, 설정을 학습 실행과 연결한다. 평가와 용도 검증을 거쳐 출시하고, 운영 관측 결과를 다음 실험으로 연결한다.
A[&amp;#34;문제와 평가 기준&amp;#34;] --&amp;gt; B[&amp;#34;데이터 버전 + 코드 버전 + 학습 설정&amp;#34;]
B --&amp;gt; C[&amp;#34;학습 실행&amp;#34;]
C --&amp;gt; D[&amp;#34;평가 + 사용 목적 검증&amp;#34;]
D --&amp;gt; E[&amp;#34;모델 산출물 + 배포 버전&amp;#34;]
E --&amp;gt; F[&amp;#34;운영 관측과 개선 결정&amp;#34;]
F --&amp;gt;|&amp;#34;다음 실험의 근거&amp;#34;| A
&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;자동화는 마지막에 붙이는 장식이 아닙니다. 실험부터 준비, 학습, 평가를 파이프라인으로 묶으면 수동 순서와 누락을 줄이고 운영에서도 같은 절차를 재사용하기 쉽습니다. 다만 기록이 있다는 사실만으로 비트 단위의 동일한 결과까지 보장되지는 않습니다.&lt;/p&gt;
&lt;p&gt;실험 결과가 나쁘면 가중치 파일만으로는 이유를 알기 어렵습니다. 어떤 이미지와 라벨을 썼는지, 분할은 어떻게 했는지, 전처리와 학습 설정은 무엇이었는지 함께 찾아야 합니다. 모델 파일을 보관하는 일과 모델을 다시 만들 수 있는 상태를 유지하는 일은 다릅니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="112-개발-스테이징-프로덕션-단계"&gt;1.1.2 개발, 스테이징, 프로덕션 단계&lt;span class="hx:absolute hx:-mt-20" id="112-개발-스테이징-프로덕션-단계"&gt;&lt;/span&gt;
&lt;a href="#112-%ea%b0%9c%eb%b0%9c-%ec%8a%a4%ed%85%8c%ec%9d%b4%ec%a7%95-%ed%94%84%eb%a1%9c%eb%8d%95%ec%85%98-%eb%8b%a8%ea%b3%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;실험이 “어떤 방법이 좋은가?”를 찾는다면, 운영에서는 그 방법을 반복해서 안전하게 실행하고 바꾸는 능력이 중요합니다. 스테이징은 실제 출시 전 조합을 확인하는 검증 환경입니다. 여기서 확인한 모델, 전처리, 환경 조합이 프로덕션에도 일치해야 합니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;실험에서 묻는 질문&lt;/th&gt;
&lt;th&gt;운영에서 추가되는 질문&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;어떤 데이터와 모델이 효과적인가?&lt;/td&gt;
&lt;td&gt;그 데이터, 코드, 설정을 다시 찾을 수 있는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;평가 점수가 충분한가?&lt;/td&gt;
&lt;td&gt;실제 입력에서도 품질과 응답 시간이 유지되는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;학습을 실행할 수 있는가?&lt;/td&gt;
&lt;td&gt;실패를 감지하고 재실행하거나 되돌릴 수 있는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;이번 결과가 좋은가?&lt;/td&gt;
&lt;td&gt;다음 변경도 같은 기준으로 검증할 수 있는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;운영에서는 모델을 API나 배치 작업으로 배포하고 부하, 오류, 데이터 변화, 예측 품질을 관측합니다. 새 데이터나 관측 결과가 재학습의 계기가 될 수 있지만 재학습 자동화와 무조건적인 자동 출시는 구분해야 합니다.&lt;/p&gt;
&lt;p&gt;배포에는 가중치 외에도 입력을 모델이 기대하는 형식으로 바꾸는 전처리와 결과를 응답으로 만드는 코드가 포함됩니다. 학습에서는 이미지를 일정한 크기로 바꾸고 픽셀값을 정규화했는데 API에서는 원본을 그대로 넣는다면 같은 모델도 다른 입력을 받습니다. 이런 차이가 학습과 서빙 사이의 불일치입니다.&lt;/p&gt;
&lt;p&gt;재학습은 새 후보를 만드는 과정이고 출시는 그 후보를 사용자에게 연결하는 결정입니다. 기존 모델보다 점수가 좋아도 특정 문서 유형에서 실패하거나 응답 시간이 늘었다면 출시를 보류할 수 있습니다. 문제가 생겼을 때는 모델만이 아니라 호환되는 전처리와 서비스 코드까지 함께 되돌려야 합니다.&lt;/p&gt;
&lt;p&gt;배포 전에는 되돌릴 모델과 코드, 설정 조합을 정해 둬야 합니다. 새 모델의 점수뿐 아니라 실제 요청 지연과 자원 사용, 승인과 복구를 맡을 사람도 필요합니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="12-mlops에-필요한-역량"&gt;1.2 MLOps에 필요한 역량&lt;span class="hx:absolute hx:-mt-20" id="12-mlops에-필요한-역량"&gt;&lt;/span&gt;
&lt;a href="#12-mlops%ec%97%90-%ed%95%84%ec%9a%94%ed%95%9c-%ec%97%ad%eb%9f%89" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="121-ml-엔지니어의-역량"&gt;1.2.1 ML 엔지니어의 역량&lt;span class="hx:absolute hx:-mt-20" id="121-ml-엔지니어의-역량"&gt;&lt;/span&gt;
&lt;a href="#121-ml-%ec%97%94%ec%a7%80%eb%8b%88%ec%96%b4%ec%9d%98-%ec%97%ad%eb%9f%89" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;ML 엔지니어는 알고리즘 하나만 잘 알아서는 부족합니다. 소프트웨어 설계, 테스트, 디버깅, 데이터 처리, 모델 평가, 실행 환경과 자동화를 연결해야 합니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;역량&lt;/th&gt;
&lt;th&gt;현장에서 답해야 할 질문&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;소프트웨어 엔지니어링&lt;/td&gt;
&lt;td&gt;오류를 재현하고 원인을 좁힐 수 있는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ML, 통계 이해&lt;/td&gt;
&lt;td&gt;지표와 분할 방식이 목표에 맞는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 엔지니어링&lt;/td&gt;
&lt;td&gt;스키마, 시점, 품질 문제를 찾을 수 있는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;인프라, 자동화&lt;/td&gt;
&lt;td&gt;실행과 배포를 반복하고 관측, 복구할 수 있는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;예측 오류가 발생하면 코드 버그인지, 데이터 오류인지, 모델의 한계인지, 인프라 장애인지부터 좁혀야 합니다.&lt;/p&gt;
&lt;p&gt;예를 들어 모델이 카드를 잘 찾지 못한다는 증상도 원인은 여러 가집니다. 이미지 회전 정보를 전처리에서 누락했을 수도 있고, 새 카메라의 촬영 조건이 학습 데이터와 다를 수도 있습니다. 모델 로딩 실패로 다른 버전이 실행되고 있을 가능성도 있습니다. 알고리즘 지식과 서비스 디버깅을 따로 떼기 어려운 이유입니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="122-선수-지식"&gt;1.2.2 선수 지식&lt;span class="hx:absolute hx:-mt-20" id="122-선수-지식"&gt;&lt;/span&gt;
&lt;a href="#122-%ec%84%a0%ec%88%98-%ec%a7%80%ec%8b%9d" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Python과 기본적인 ML 흐름을 이해하고, 익숙하지 않은 프레임워크, 운영 도구를 배울 준비가 필요합니다. 책은 Kubernetes 기초를 설명한 뒤 플랫폼을 점진적으로 붙이는 구성입니다.&lt;/p&gt;
&lt;p&gt;한 사람이 모든 영역의 전문가여야 한다는 뜻은 아닐 것 같습니다. 데이터와 평가를 잘 아는 사람, 서비스와 인프라를 잘 아는 사람이 같은 실행 흐름을 보면서 책임과 연결 지점을 합의하는 것이 먼저입니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="13-ml-플랫폼-구축"&gt;1.3 ML 플랫폼 구축&lt;span class="hx:absolute hx:-mt-20" id="13-ml-플랫폼-구축"&gt;&lt;/span&gt;
&lt;a href="#13-ml-%ed%94%8c%eb%9e%ab%ed%8f%bc-%ea%b5%ac%ec%b6%95" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;플랫폼은 도구를 모아 놓은 화면이 아니라 실험과 학습, 배포와 관측을 반복할 수 있게 하는 공통 기반입니다. 원천 데이터 수집과 데이터 웨어하우스 전체를 모두 플랫폼의 책임으로 넣을 필요는 없습니다. 어떤 입력을 받아 무엇을 제공하는지, 누가 운영하는지부터 정해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://johklo.github.io/images/ml-platform-engineering/figure-1-04.png?v=2" alt="업무 요구사항에서 데이터 준비와 모델 학습 및 평가를 거쳐 API 배포와 모니터링으로 이어지는 ML 플랫폼 구성. 피처 스토어, 실험 추적, 모델 레지스트리가 각 단계를 연결한다." loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;그림 1.4. ML 플랫폼의 구성. 출처: Machine Learning Platform Engineering, 1장.&lt;/p&gt;
&lt;p&gt;이 그림은 위에서 아래로 내려오는 모델 개발 흐름과 옆에서 받쳐 주는 공통 기능을 함께 보여 줍니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;업무 요구에서 문제와 모델을 정의합니다. 어떤 문제를 풀지 결정하는 일까지 플랫폼이 대신하지는 않습니다.&lt;/li&gt;
&lt;li&gt;파란 영역에서는 데이터를 준비하고 모델을 학습한 뒤 평가하고 배포합니다. 파이프라인 오케스트레이션은 이 단계의 순서와 실행 의존성을 관리합니다.&lt;/li&gt;
&lt;li&gt;실험 추적에는 파라미터와 지표를 남깁니다. 모델 레지스트리에는 관리할 모델과 버전을 등록합니다. 둘을 연결해야 배포한 모델이 어느 실험에서 나왔는지 찾을 수 있습니다.&lt;/li&gt;
&lt;li&gt;아래의 Model API는 사용자 요청을 받습니다. 모니터링 결과는 다시 개선이나 재학습의 근거가 됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;피처 스토어에서 나오는 선이 학습과 API 양쪽으로 연결되는 점도 중요합니다. 학습 때 계산한 특성을 실제 요청에서도 같은 의미로 제공해야 한다는 뜻입니다. 다만 그림의 부품을 전부 설치해야 한다는 의미는 아닙니다. 이미지 모델처럼 전처리가 서비스 안에서 끝나는 경우와 여러 팀이 공통 특성을 쓰는 표 데이터 모델은 필요한 구성이 다릅니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="131-자체-구축과-구매"&gt;1.3.1 자체 구축과 구매&lt;span class="hx:absolute hx:-mt-20" id="131-자체-구축과-구매"&gt;&lt;/span&gt;
&lt;a href="#131-%ec%9e%90%ec%b2%b4-%ea%b5%ac%ec%b6%95%ea%b3%bc-%ea%b5%ac%eb%a7%a4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;직접 구축하면 각 구성 요소의 역할과 한계를 파악할 수 있습니다. 그렇다고 실제 도입에서도 자체 구축이 항상 유리한 것은 아닙니다.&lt;/p&gt;
&lt;p&gt;관리형 서비스는 구축 부담을 줄일 수 있지만 통합, 권한, 비용 관리가 남습니다. 자체 구축은 조정의 자유가 큰 대신 업그레이드와 장애, 사용자 지원을 감당해야 합니다. 같은 요구사항과 기간으로 총비용을 비교해야 합니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;조건&lt;/th&gt;
&lt;th&gt;관리형 서비스에서 볼 것&lt;/th&gt;
&lt;th&gt;자체 구축에서 볼 것&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;빠른 초기 도입&lt;/td&gt;
&lt;td&gt;기존 데이터와 인증 체계에 연결 가능한가?&lt;/td&gt;
&lt;td&gt;설치 이후 운영할 인력이 있는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;특수한 실행 환경&lt;/td&gt;
&lt;td&gt;필요한 GPU와 네트워크 구성을 지원하는가?&lt;/td&gt;
&lt;td&gt;드라이버와 실행 환경을 직접 유지할 수 있는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 통제&lt;/td&gt;
&lt;td&gt;보관 위치와 접근 정책이 요구에 맞는가?&lt;/td&gt;
&lt;td&gt;권한과 감사 기록을 직접 설계할 수 있는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;장기 비용&lt;/td&gt;
&lt;td&gt;사용량 증가와 이전 비용은 어느 정도인가?&lt;/td&gt;
&lt;td&gt;업그레이드와 장애 대응 비용을 포함했는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;직접 구축하면 사용료가 사라진다는 식의 비교는 맞지 않습니다. 운영 담당자의 시간과 장애 위험도 비용입니다. 반대로 관리형이라고 플랫폼과 모델의 운영 책임까지 모두 없어지는 것도 아닙니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="132-mlops에서-llmops로"&gt;1.3.2 MLOps에서 LLMOps로&lt;span class="hx:absolute hx:-mt-20" id="132-mlops에서-llmops로"&gt;&lt;/span&gt;
&lt;a href="#132-mlops%ec%97%90%ec%84%9c-llmops%eb%a1%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;LLM 애플리케이션에서도 버전, 배포와 모니터링은 유효합니다. 여기에 문서 처리, 임베딩, 벡터 검색, 프롬프트, 답변 평가, 안전과 토큰 비용이 추가됩니다.&lt;/p&gt;
&lt;p&gt;즉 모델 파일만이 아니라 어떤 문서와 인덱스, 프롬프트와 모델 API의 조합으로 답했는지도 추적해야 합니다. 이 장에서는 확장을 예고하고 자세한 구현은 후반부에서 다룹니다.&lt;/p&gt;
&lt;p&gt;임베딩은 문서나 질문을 벡터로 표현하는 방식이고, 벡터 검색은 그 표현의 유사도를 이용해 관련 자료를 찾는 방식입니다. 여기서는 두 역할을 구분하는 정도로 이해했습니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="133-책에서-사용하는-도구"&gt;1.3.3 책에서 사용하는 도구&lt;span class="hx:absolute hx:-mt-20" id="133-책에서-사용하는-도구"&gt;&lt;/span&gt;
&lt;a href="#133-%ec%b1%85%ec%97%90%ec%84%9c-%ec%82%ac%ec%9a%a9%ed%95%98%eb%8a%94-%eb%8f%84%ea%b5%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;공통 능력&lt;/th&gt;
&lt;th&gt;도구, 기반&lt;/th&gt;
&lt;th&gt;관리할 대상&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ML 작업 실행&lt;/td&gt;
&lt;td&gt;Kubeflow Pipelines&lt;/td&gt;
&lt;td&gt;단계의 의존성과 실행 산출물&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;피처 제공&lt;/td&gt;
&lt;td&gt;Feast&lt;/td&gt;
&lt;td&gt;정의, 시점, 학습/서빙 조회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;실험 추적, 모델 등록&lt;/td&gt;
&lt;td&gt;MLflow&lt;/td&gt;
&lt;td&gt;실행 조건, 평가, 모델 버전&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;배포&lt;/td&gt;
&lt;td&gt;컨테이너, 레지스트리, CI/CD&lt;/td&gt;
&lt;td&gt;실행 환경과 변경된 서비스&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;MLflow를 설치했다고 데이터 품질이 보장되지는 않고, 피처 스토어를 넣었다고 학습, 서빙의 모든 차이가 사라지지도 않습니다. 도구 사이의 데이터와 설정을 일관되게 연결해야 합니다.&lt;/p&gt;
&lt;p&gt;오케스트레이터는 모델을 학습하는 알고리즘이 아니라 작업을 실행하고 연결하는 쪽입니다. 실험 추적은 어떤 조건에서 어떤 결과가 나왔는지 기록하고, 레지스트리는 그중 어떤 모델을 관리하고 서비스할지 연결합니다. 역할이 겹쳐 보이는 도구를 비교할 때도 이 차이를 먼저 봐야 합니다.&lt;/p&gt;
&lt;p&gt;새 도구를 제안할 때는 해결할 문제와 기존 체계와의 연결, 운영 담당자를 함께 정해야 합니다. 책의 조합이 모든 조직에 그대로 맞지는 않습니다.&lt;/p&gt;
&lt;h4 data-hextra-search-id="run-artifact와-피처-시점"&gt;Run, artifact와 피처 시점&lt;span class="hx:absolute hx:-mt-20" id="run-artifact와-피처-시점"&gt;&lt;/span&gt;
&lt;a href="#run-artifact%ec%99%80-%ed%94%bc%ec%b2%98-%ec%8b%9c%ec%a0%90" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;&lt;a href="https://mlflow.org/docs/latest/ml/tracking/" target="_blank" rel="noopener"&gt;MLflow 공식 문서&lt;/a&gt;에서 run은 한 번의 실행, experiment는 관련 실행을 묶는 단위, artifact는 가중치, 그래프처럼 실행이 남긴 파일 산출물로 구분합니다. 모델 파일을 저장하는 것과 그 모델을 만든 과정을 추적하는 것은 다른 일입니다.&lt;/p&gt;
&lt;p&gt;피처 스토어는 모델 입력으로 쓸 특성값의 정의와 조회, 재사용을 관리합니다. Point-in-time join은 지금의 최신 값이 아니라 학습 샘플의 과거 기준 시점에 맞는 값을 붙이는 방식입니다.&lt;/p&gt;
&lt;div role="img" aria-label="다이어그램"&gt;
&lt;pre class="mermaid hx:mt-6"&gt;
flowchart TD
accTitle: 피처 조회에서 예측 기준 시점을 지켜야 하는 이유
accDescr: 오전 10시의 예측을 재구성할 때 그 전에 관측되고 사용할 수 있던 값은 후보가 된다. 오전 11시의 값이나 나중에 보정된 값이 들어가면 미래 정보가 섞일 수 있다.
T[&amp;#34;재구성할 예측 시점, 오전 10시&amp;#34;]
A[&amp;#34;오전 9시에 관측, 제공된 피처&amp;#34;] --&amp;gt;|&amp;#34;조건에 맞으면 사용&amp;#34;| T
B[&amp;#34;오전 11시에 생긴 피처&amp;#34;] -.-&amp;gt;|&amp;#34;예측 이후 값, 제외&amp;#34;| T
C[&amp;#34;오전 9시 값이지만 오후에 보정, 제공&amp;#34;] -.-&amp;gt;|&amp;#34;당시 사용 가능 여부 별도 확인&amp;#34;| T
&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;&lt;a href="https://docs.feast.dev/getting-started/concepts/point-in-time-joins" target="_blank" rel="noopener"&gt;Feast의 point-in-time join 문서&lt;/a&gt;를 확인하면 이벤트 시각과 값이 실제로 만들어진 시각도 구분해야 합니다. 시간 컬럼 하나가 있다고 미래 정보 누수가 자동으로 막히는 것은 아닙니다. 저장소, 버전이 제공하는 시점 처리와 지연 도착, 보정 데이터 정책까지 확인할 부분입니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="14-ml-시스템-만들기"&gt;1.4 ML 시스템 만들기&lt;span class="hx:absolute hx:-mt-20" id="14-ml-시스템-만들기"&gt;&lt;/span&gt;
&lt;a href="#14-ml-%ec%8b%9c%ec%8a%a4%ed%85%9c-%eb%a7%8c%eb%93%a4%ea%b8%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="141-프로젝트를-통해-배우는-것"&gt;1.4.1 프로젝트를 통해 배우는 것&lt;span class="hx:absolute hx:-mt-20" id="141-프로젝트를-통해-배우는-것"&gt;&lt;/span&gt;
&lt;a href="#141-%ed%94%84%eb%a1%9c%ec%a0%9d%ed%8a%b8%eb%a5%bc-%ed%86%b5%ed%95%b4-%eb%b0%b0%ec%9a%b0%eb%8a%94-%ea%b2%83" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;프로젝트는 한 번 끝내는 튜토리얼이 아니라 데이터와 요구가 바뀌면 앞 단계로 돌아가는 생애주기의 연습입니다. 모델 성능이 낮으면 더 큰 모델부터 찾기보다 데이터와 평가의 가정도 다시 확인합니다.&lt;/p&gt;
&lt;p&gt;신분증 객체 탐지, 영화 추천, 문서 기반 RAG는 데이터와 모델은 다르지만 준비 → 실행 → 검증 → 배포 → 관측 → 개선이라는 문제를 공유합니다. 이미지에서 익힌 데이터 전달과 배포 원칙을 표 데이터와 LLM에도 다시 적용하는 구조입니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="142-세-프로젝트의-차이와-운영-질문"&gt;1.4.2 세 프로젝트의 차이와 운영 질문&lt;span class="hx:absolute hx:-mt-20" id="142-세-프로젝트의-차이와-운영-질문"&gt;&lt;/span&gt;
&lt;a href="#142-%ec%84%b8-%ed%94%84%eb%a1%9c%ec%a0%9d%ed%8a%b8%ec%9d%98-%ec%b0%a8%ec%9d%b4%ec%99%80-%ec%9a%b4%ec%98%81-%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;프로젝트&lt;/th&gt;
&lt;th&gt;데이터, 기능&lt;/th&gt;
&lt;th&gt;비교할 운영 문제&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;신분증 탐지&lt;/td&gt;
&lt;td&gt;이미지에서 카드 위치 탐지&lt;/td&gt;
&lt;td&gt;라벨, 좌표, 영상 분할, 이미지별 실패&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;영화 추천&lt;/td&gt;
&lt;td&gt;사용자, 아이템, 평점으로 추천&lt;/td&gt;
&lt;td&gt;시점, 식별자, 새 사용자, 추천 품질&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DakkaBot&lt;/td&gt;
&lt;td&gt;사내 문서를 검색해 답변 생성&lt;/td&gt;
&lt;td&gt;문서 최신성, 권한, 근거, 토큰 비용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;첫 프로젝트는 OCR이라는 넓은 문맥 안에 있지만 카드 위치를 찾는 객체 탐지와 글자 인식은 다른 작업입니다. 세 프로젝트를 비교할 때도 기능의 이름보다 실제 입력, 출력과 실패 비용을 구분해야 합니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="플랫폼-경계와-책임"&gt;플랫폼 경계와 책임&lt;span class="hx:absolute hx:-mt-20" id="플랫폼-경계와-책임"&gt;&lt;/span&gt;
&lt;a href="#%ed%94%8c%eb%9e%ab%ed%8f%bc-%ea%b2%bd%ea%b3%84%ec%99%80-%ec%b1%85%ec%9e%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;ML 플랫폼팀이 데이터 수집부터 모든 모델의 업무 성과까지 책임진다고 가정하면 경계가 무한히 커집니다. 플랫폼이 제공하는 기능, 모델팀이 결정하는 품질, 데이터팀이 보장하는 입력, 업무팀이 승인하는 출시 조건을 먼저 나눠야 합니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;설계 판단&lt;/th&gt;
&lt;th&gt;확인할 증거&lt;/th&gt;
&lt;th&gt;결정 결과&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ML 도입이 필요한가?&lt;/td&gt;
&lt;td&gt;규칙 기반 기준선, 예상 효과, 오판 비용&lt;/td&gt;
&lt;td&gt;ML, 규칙, 수동 처리 중 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;공통 플랫폼이 필요한가?&lt;/td&gt;
&lt;td&gt;팀별 반복 업무, 배포 빈도, 장애 기록&lt;/td&gt;
&lt;td&gt;공통화할 기능과 남겨 둘 자율성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;자체 구축할 것인가?&lt;/td&gt;
&lt;td&gt;요구 기능, 통합, 운영 인력, 종료, 이전 비용&lt;/td&gt;
&lt;td&gt;관리형, 자체, 혼합 구조와 이유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;누가 변경을 승인하는가?&lt;/td&gt;
&lt;td&gt;현재 승인 경로, 업무 위험과 규정&lt;/td&gt;
&lt;td&gt;역할별 승인, 운영, 장애 책임&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;총비용은 라이선스나 클라우드 청구서만이 아닙니다. 구축, 통합 작업, 업그레이드, 온콜, 보안 검토, 사용자 지원과 향후 이전 비용을 같은 기간으로 비교해야 합니다. 오픈소스의 무료 사용과 무료 운영은 다릅니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="모델-이력에-필요한-정보"&gt;모델 이력에 필요한 정보&lt;span class="hx:absolute hx:-mt-20" id="모델-이력에-필요한-정보"&gt;&lt;/span&gt;
&lt;a href="#%eb%aa%a8%eb%8d%b8-%ec%9d%b4%eb%a0%a5%ec%97%90-%ed%95%84%ec%9a%94%ed%95%9c-%ec%a0%95%eb%b3%b4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;중고차 가격 예측 모델이라면 다음 정보를 함께 관리해야 합니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;적어 볼 내용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;해결하려는 문제&lt;/td&gt;
&lt;td&gt;가격을 추천해 누구의 어떤 결정을 돕는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;입력과 정답&lt;/td&gt;
&lt;td&gt;예측 시점에 실제로 얻을 수 있는 정보인가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;재현 정보&lt;/td&gt;
&lt;td&gt;데이터 스냅샷, 코드 커밋, 학습 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;배포 조건&lt;/td&gt;
&lt;td&gt;품질 기준, 지연 시간, 승인 담당자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;실패 대응&lt;/td&gt;
&lt;td&gt;이전 버전과 수동 처리 절차&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;채우기 어려운 항목은 모델링보다 먼저 합의해야 할 부분입니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="질문"&gt;질문&lt;span class="hx:absolute hx:-mt-20" id="질문"&gt;&lt;/span&gt;
&lt;a href="#%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="모델-성능이-좋아도-프로젝트를-중단할-수-있을까요"&gt;모델 성능이 좋아도 프로젝트를 중단할 수 있을까요?&lt;span class="hx:absolute hx:-mt-20" id="모델-성능이-좋아도-프로젝트를-중단할-수-있을까요"&gt;&lt;/span&gt;
&lt;a href="#%eb%aa%a8%eb%8d%b8-%ec%84%b1%eb%8a%a5%ec%9d%b4-%ec%a2%8b%ec%95%84%eb%8f%84-%ed%94%84%eb%a1%9c%ec%a0%9d%ed%8a%b8%eb%a5%bc-%ec%a4%91%eb%8b%a8%ed%95%a0-%ec%88%98-%ec%9e%88%ec%9d%84%ea%b9%8c%ec%9a%94" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;문제 자체의 가치가 작거나 정답 수집과 운영 비용이 효과보다 클 수 있습니다. 평가 지표와 실제 의사결정의 관계를 먼저 설명해야 합니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="플랫폼을-직접-만들어야-할까요"&gt;플랫폼을 직접 만들어야 할까요?&lt;span class="hx:absolute hx:-mt-20" id="플랫폼을-직접-만들어야-할까요"&gt;&lt;/span&gt;
&lt;a href="#%ed%94%8c%eb%9e%ab%ed%8f%bc%ec%9d%84-%ec%a7%81%ec%a0%91-%eb%a7%8c%eb%93%a4%ec%96%b4%ec%95%bc-%ed%95%a0%ea%b9%8c%ec%9a%94" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;반복되는 불편 하나를 기준으로 직접 구축, 관리형 서비스, 간단한 스크립트의 유지 비용을 비교해야 합니다. 학습을 위한 구축과 회사의 도입 결정도 분리할 필요가 있습니다.&lt;/p&gt;
&lt;p&gt;REST API가 응답한다고 프로덕션 전환이 끝난 것은 아닙니다. 데이터 변화와 부하, 롤백도 고려해야 합니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="마치며"&gt;마치며&lt;span class="hx:absolute hx:-mt-20" id="마치며"&gt;&lt;/span&gt;
&lt;a href="#%eb%a7%88%ec%b9%98%eb%a9%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;처음에는 플랫폼을 구성하는 도구에 눈이 갔지만, 1장에서 더 중요하게 느낀 것은 모델의 생성 근거와 운영 책임을 연결하는 일이었습니다. 실험 기록과 데이터 버전, 코드와 배포 버전을 따로 보관하는 것만으로는 충분하지 않습니다.&lt;/p&gt;
&lt;p&gt;어떤 모델이 왜 만들어졌고 어떤 근거로 배포됐는지 따라갈 수 있어야 합니다. 이후 장에서 도구를 보더라도 이 연결이 어디에서 만들어지는지 먼저 확인하려고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;a href="../00-overview/"&gt;이전: 책 전체 구성&lt;/a&gt;, &lt;a href="../"&gt;시리즈 목차&lt;/a&gt;, &lt;a href="../02-what-is-mlops/"&gt;다음: 02. MLOps란 무엇인가&lt;/a&gt;&lt;/p&gt;</description></item><item><title>02. MLOps란 무엇인가</title><link>https://johklo.github.io/docs/tech/machine-learning-platform-engineering/02-what-is-mlops/</link><pubDate>Wed, 21 Oct 2026 07:33:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/machine-learning-platform-engineering/02-what-is-mlops/</guid><description>
&lt;p&gt;1장에서 전체 생애주기를 봤다면, 2장은 그 흐름을 왜 계속 반복해야 하는지 설명합니다. 일반적인 배포 자동화와 겹치는 부분이 많지만 코드 외에 데이터와 모델도 바뀐다는 점이 다릅니다.&lt;/p&gt;
&lt;p&gt;서비스 배포에 CI/CD가 있다는 사실과 ML 파이프라인이 자동화되어 있다는 사실을 구분할 필요가 있었습니다. 이 장의 성숙도 모델도 어떤 도구가 설치됐는지가 아니라 무엇을 자동화했는지를 기준으로 이해했습니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="21-반복되는-mlops-생애주기"&gt;2.1 반복되는 MLOps 생애주기&lt;span class="hx:absolute hx:-mt-20" id="21-반복되는-mlops-생애주기"&gt;&lt;/span&gt;
&lt;a href="#21-%eb%b0%98%eb%b3%b5%eb%90%98%eb%8a%94-mlops-%ec%83%9d%ec%95%a0%ec%a3%bc%ea%b8%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;이 장은 데이터 수집, 탐색적 데이터 분석(EDA), 모델링, 학습, 평가, 배포, 모니터링, 유지, 개선을 하나의 고리로 묶습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://johklo.github.io/images/image.png" alt="데이터 수집과 분석, 모델 학습과 평가, 배포와 모니터링을 반복하고 필요에 따라 모델을 폐기하는 ML 생애주기." loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;그림 2.2. 순환하는 ML 생애주기. 출처: Machine Learning Platform Engineering, 2장.&lt;/p&gt;
&lt;p&gt;마지막에 모델 폐기까지 있다는 점을 놓치기 쉽습니다. 성능이 떨어졌다고 항상 새 모델을 학습해야 하는 것은 아닙니다. 풀던 문제의 가치가 없어졌거나 다른 절차가 더 효과적이라면 운영을 끝내는 것이 맞을 수 있습니다. 개선 여부를 판단하려면 처음 정한 업무 목적과 현재 운영 결과를 다시 연결해야 합니다.&lt;/p&gt;
&lt;p&gt;모니터링에서 문제가 발견되었다고 항상 학습으로 돌아가지는 않습니다. 입력 스키마가 깨졌다면 수집을, 평가 기준이 잘못됐다면 문제 정의를, 서버가 느리다면 서빙 구성을 먼저 고쳐야 합니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="211-데이터-수집"&gt;2.1.1 데이터 수집&lt;span class="hx:absolute hx:-mt-20" id="211-데이터-수집"&gt;&lt;/span&gt;
&lt;a href="#211-%eb%8d%b0%ec%9d%b4%ed%84%b0-%ec%88%98%ec%a7%91" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;문제와 측정 가능한 목표를 정한 뒤 필요한 데이터, 라벨을 수집합니다. 공개 데이터가 있더라도 실제 서비스의 사용자와 상황을 대표하는지는 별도로 확인해야 합니다.&lt;/p&gt;
&lt;p&gt;책은 신분증 이미지, 영화 평점, RAG 문서처럼 다른 형태의 데이터를 같은 생애주기에서 다룹니다. 이미지의 촬영, 라벨 기준, 평점에서 관측되지 않은 선호, 문서의 최신성과 권한이 서로 다른 문제를 만듭니다.&lt;/p&gt;
&lt;p&gt;수집 경로와 사용 범위, 표본의 편향, 라벨 책임자와 갱신 주기를 남겨야 합니다. 데이터가 많다는 사실보다 실제 의사결정을 평가할 수 있는지가 중요합니다.&lt;/p&gt;
&lt;p&gt;라벨도 객관적으로 주어진 정답이라고만 볼 수는 없습니다. 신분증의 어느 부분까지 상자를 그릴지 기준이 다르면 같은 사진에 다른 정답이 생깁니다. 추천에서는 평점이 없는 영화를 싫어한다고 해석할 수도 없습니다. 데이터가 어떻게 만들어졌는지 모르면 모델의 오류와 수집 과정의 한계를 구분하기 어렵습니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="212-탐색적-데이터-분석"&gt;2.1.2 탐색적 데이터 분석&lt;span class="hx:absolute hx:-mt-20" id="212-탐색적-데이터-분석"&gt;&lt;/span&gt;
&lt;a href="#212-%ed%83%90%ec%83%89%ec%a0%81-%eb%8d%b0%ec%9d%b4%ed%84%b0-%eb%b6%84%ec%84%9d" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;탐색적 데이터 분석(EDA)은 분포를 구경하는 일이 아니라 스키마, 결측, 이상치, 불균형, 계절성에 대한 가정을 확인하는 과정입니다. 무엇을 모델이 배울 수 있고 어떤 입력에서 실패할지 가설을 만듭니다.&lt;/p&gt;
&lt;p&gt;“평점은 항상 허용 범위에 있다”, “사용자 ID는 비어 있지 않다”는 가정은 실행 가능한 품질 검사로 바꿀 수 있습니다. 소수 집단의 표본 부족은 추가 수집과 평가 범위의 한계로 기록할 문제입니다.&lt;/p&gt;
&lt;p&gt;데이터 사전과 품질 규칙, 확인되지 않은 가정을 구분해 남깁니다. EDA는 처음 한 번으로 끝나지 않으며 새 데이터와 운영 실패가 들어오면 갱신해야 합니다.&lt;/p&gt;
&lt;p&gt;EDA에서 발견한 사실은 후속 처리로 이어져야 합니다. 이미지 크기가 제각각이면 모델 입력 크기로 바꾸는 규칙이 필요하고, 평점에 빈 값이 있으면 제외할지 다른 의미가 있는지 정해야 합니다. 잘못된 값을 모두 0으로 바꾸는 것처럼 손쉬운 처리도 데이터의 의미를 바꿀 수 있습니다.&lt;/p&gt;
&lt;p&gt;스키마가 맞는 것과 데이터가 유효한 것도 다릅니다. &lt;code&gt;rating&lt;/code&gt; 열이 숫자여도 허용 범위를 벗어난 값이 들어갈 수 있습니다. 타입 검사와 값의 범위, 다른 열과의 관계를 함께 봐야 합니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="213-모델링과-학습"&gt;2.1.3 모델링과 학습&lt;span class="hx:absolute hx:-mt-20" id="213-모델링과-학습"&gt;&lt;/span&gt;
&lt;a href="#213-%eb%aa%a8%eb%8d%b8%eb%a7%81%ea%b3%bc-%ed%95%99%ec%8a%b5" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;모델은 데이터와 코드, 설정을 적용해 만든 산출물입니다. 모델 구조, 학습 코드, 하이퍼파라미터, 데이터를 분리해 버전과 계보를 남기면 실험을 비교하고 원인을 조사하기 쉽습니다. 하이퍼파라미터는 학습이 스스로 얻는 가중치와 달리 학습률, 모델 크기처럼 실험에서 정하는 조건입니다.&lt;/p&gt;
&lt;p&gt;실험 추적은 좋은 결과만이 아니라 실패한 시도도 보존합니다. 하이퍼파라미터 탐색에도 비교 기준과 자원 예산이 필요합니다. 모든 값을 설정으로 빼기보다 실제로 바꿀 항목부터 드러내는 편이 낫습니다.&lt;/p&gt;
&lt;p&gt;모델을 비교할 때 바꾼 조건을 구분해야 합니다. 데이터와 분할, 전처리까지 동시에 바뀌었다면 점수 차이가 모델 구조 때문인지 알기 어렵습니다. 최소한 데이터 버전, 코드 버전, 설정, 평가셋을 실행 결과와 묶어 두어야 이후 비교가 가능합니다.&lt;/p&gt;
&lt;p&gt;실행을 다시 찾는 것과 수치가 완전히 같아지는 것은 다른 요구입니다. 어떤 환경과 데이터, 설정으로 나온 결과인지 설명하는 것부터 확인해야 합니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="214-모델-평가"&gt;2.1.4 모델 평가&lt;span class="hx:absolute hx:-mt-20" id="214-모델-평가"&gt;&lt;/span&gt;
&lt;a href="#214-%eb%aa%a8%eb%8d%b8-%ed%8f%89%ea%b0%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;모든 문제에 같은 지표를 쓰면 안 됩니다. 분류에서는 precision, recall, 회귀에서는 MAE, MSE 등 후보가 다르며, 같은 분류 문제도 오탐과 미탐의 비용에 따라 선택이 달라집니다.&lt;/p&gt;
&lt;p&gt;스팸 차단을 예로 들면 정상 메일을 차단하는 비용과 스팸을 놓치는 비용이 같지 않습니다. “정확도 95%”라는 숫자만으로 배포 여부를 정할 수 없는 이유입니다.&lt;/p&gt;
&lt;p&gt;Precision은 차단한 것 중 실제 스팸의 비율, recall은 전체 스팸 중 잡아낸 비율입니다. MAE는 예측 오차의 절댓값을 평균한 값, MSE는 오차를 제곱해 평균한 값이라 큰 오차의 영향이 더 커집니다. 지표 이름보다 어떤 실패에 무게를 두는지 이해할 필요가 있습니다.&lt;/p&gt;
&lt;p&gt;메일 10,000개 중 스팸이 100개인 경우를 생각해 볼 수 있습니다. 전부 정상 메일로 통과시켜도 정확도는 99%입니다. 이 값만 보면 괜찮아 보이지만 스팸은 하나도 잡지 못합니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;실제 데이터&lt;/th&gt;
&lt;th&gt;스팸으로 차단&lt;/th&gt;
&lt;th&gt;정상으로 통과&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;스팸 100개&lt;/td&gt;
&lt;td&gt;80개&lt;/td&gt;
&lt;td&gt;20개&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;정상 메일 9,900개&lt;/td&gt;
&lt;td&gt;40개&lt;/td&gt;
&lt;td&gt;9,860개&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;위 결과에서는 차단한 120개 중 80개가 스팸이므로 precision은 약 66.7%입니다. 실제 스팸 100개 중 80개를 잡았으므로 recall은 80%입니다. 정상 메일을 잃는 비용이 크다면 precision이, 스팸을 놓치는 비용이 크다면 recall이 더 중요한 판단 기준이 됩니다. 어느 쪽을 택할지는 업무의 손실 구조에 달려 있습니다.&lt;/p&gt;
&lt;p&gt;다중 클래스에서는 macro, micro 등 집계 방식까지 맞춰야 지표를 비교할 수 있습니다. 정의와 평균 방식은 &lt;a href="https://scikit-learn.org/stable/modules/model_evaluation.html" target="_blank" rel="noopener"&gt;scikit-learn 평가 문서&lt;/a&gt;를 참고할 수 있습니다.&lt;/p&gt;
&lt;p&gt;평가에는 대표성을 갖춘 독립 데이터, 기준선과 동일한 비교 조건, 오류 유형 분석이 필요합니다. 최종 테스트 결과를 보며 계속 후보를 고르면 그 데이터도 선택 과정에 사용됩니다.&lt;/p&gt;
&lt;p&gt;전체 평균뿐 아니라 중요한 사용자 집단과 데이터 조건별 결과, 표본 수도 확인합니다. 평가 데이터나 지표, 출시 기준이 바뀌면 변경 이유도 모델 버전과 연결해야 합니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="215-배포"&gt;2.1.5 배포&lt;span class="hx:absolute hx:-mt-20" id="215-배포"&gt;&lt;/span&gt;
&lt;a href="#215-%eb%b0%b0%ed%8f%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;배포는 API, 배치, 엣지 장치 등 모델을 소비하는 방식에 맞춰 설계합니다. 학습 환경에서 빠르다는 사실만으로 실제 CPU, GPU, 장치에서 요구사항을 충족한다고 볼 수 없습니다.&lt;/p&gt;
&lt;p&gt;변환, 최적화한 모델은 최종 형식에서도 품질과 지연을 다시 확인해야 합니다. 모델뿐 아니라 전처리, 의존성과 서비스 규격도 함께 배포, 복구할 수 있어야 합니다.&lt;/p&gt;
&lt;p&gt;스테이징에서 확인한 조합과 출시된 조합이 일치하는지, 롤백할 버전이 실제로 실행 가능한지 기록합니다. 배포 자동화는 승인 정책을 대체하지 않습니다.&lt;/p&gt;
&lt;p&gt;검증된 모델을 서비스에 연결하는 과정에서도 문제가 생길 수 있습니다. 전처리 열 순서가 달라졌거나 모델이 기대하는 라이브러리 버전이 없으면 평가할 때와 다른 결과가 나옵니다. 모델 파일을 올렸다는 사실보다 같은 입력에 대해 기대한 출력이 나오는지 확인하는 것이 중요합니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="216-모니터링"&gt;2.1.6 모니터링&lt;span class="hx:absolute hx:-mt-20" id="216-모니터링"&gt;&lt;/span&gt;
&lt;a href="#216-%eb%aa%a8%eb%8b%88%ed%84%b0%eb%a7%81" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;데이터 분포와 품질, 시스템 오류와 지연, 모델의 성능과 잘못된 예측을 구분해 관측합니다. 로그와 버전 정보가 있어야 이상이 어떤 변경과 관련되는지 조사할 수 있습니다.&lt;/p&gt;
&lt;p&gt;운영에서는 정답이 늦게 들어오기도 합니다. 서비스 지연과 오류는 즉시 관측할 수 있어도 예측이 맞았는지는 나중에 알 수 있습니다. 즉시 볼 대리 신호와 정답이 도착한 뒤 볼 실제 품질 지표를 구분해야 합니다.&lt;/p&gt;
&lt;p&gt;모델이 내는 신뢰 점수인 confidence나 분포 변화인 드리프트는 조사 신호이지 정확도의 증명이 아닙니다. 알림 담당자와 다음 행동까지 정하지 않으면 대시보드가 있어도 대응은 늦어집니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;관측 대상&lt;/th&gt;
&lt;th&gt;바로 알 수 있는 것&lt;/th&gt;
&lt;th&gt;추가로 필요한 것&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API 오류와 지연&lt;/td&gt;
&lt;td&gt;요청 처리의 기술적 실패&lt;/td&gt;
&lt;td&gt;장애 원인과 사용자 영향&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;입력 분포와 결측률&lt;/td&gt;
&lt;td&gt;들어오는 데이터의 변화&lt;/td&gt;
&lt;td&gt;수집 오류인지 실제 환경 변화인지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;예측값과 confidence&lt;/td&gt;
&lt;td&gt;모델 행동의 변화&lt;/td&gt;
&lt;td&gt;실제 정답과 오류 유형&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;정답 기반 평가&lt;/td&gt;
&lt;td&gt;실제 예측 품질&lt;/td&gt;
&lt;td&gt;정답 수집 지연과 표본의 대표성&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;정답이 나중에 도착하는 문제라면 요청 시점의 모델 버전과 예측을 정답에 연결할 식별자가 필요합니다. 이 연결이 없으면 운영 중 어떤 버전이 잘못된 예측을 했는지 집계하기 어렵습니다.&lt;/p&gt;
&lt;h4 data-hextra-search-id="데이터-드리프트와-개념-드리프트"&gt;데이터 드리프트와 개념 드리프트&lt;span class="hx:absolute hx:-mt-20" id="데이터-드리프트와-개념-드리프트"&gt;&lt;/span&gt;
&lt;a href="#%eb%8d%b0%ec%9d%b4%ed%84%b0-%eb%93%9c%eb%a6%ac%ed%94%84%ed%8a%b8%ec%99%80-%ea%b0%9c%eb%85%90-%eb%93%9c%eb%a6%ac%ed%94%84%ed%8a%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;데이터 드리프트는 입력 분포 &lt;code&gt;P(X)&lt;/code&gt;의 변화, 개념 드리프트는 입력이 주어졌을 때 정답의 관계 &lt;code&gt;P(Y|X)&lt;/code&gt;의 변화입니다. &lt;a href="https://www.evidentlyai.com/ml-in-production/data-drift" target="_blank" rel="noopener"&gt;Evidently의 설명&lt;/a&gt;에서도 두 개념을 구분합니다.&lt;/p&gt;
&lt;p&gt;같은 가격, 이용 조건에도 사람들이 구매하는 방식이 달라지면 개념 변화의 문제입니다. 사용자 구성만 바뀌고 집단별 구매 방식은 같다면 입력 분포 변화일 수 있습니다. 둘은 함께 생길 수도 있고, 변화가 있다고 반드시 모델 품질이 하락하는 것도 아닙니다.&lt;/p&gt;
&lt;p&gt;용어 자체가 조직마다 넓게 쓰이므로 “드리프트가 났다”에서 끝내지 말고 무엇의 분포나 관계를 어떤 기간과 비교했는가를 확인해야 합니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="217-유지-업데이트-검토"&gt;2.1.7 유지, 업데이트, 검토&lt;span class="hx:absolute hx:-mt-20" id="217-유지-업데이트-검토"&gt;&lt;/span&gt;
&lt;a href="#217-%ec%9c%a0%ec%a7%80-%ec%97%85%eb%8d%b0%ec%9d%b4%ed%8a%b8-%ea%b2%80%ed%86%a0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;모니터링 결과를 버그 수정, 데이터 복구, 재학습, 새 모델 비교와 업무 목표 재검토에 연결합니다. 서비스의 가치가 없어지면 모델을 폐기하는 결정도 생애주기에 포함됩니다.&lt;/p&gt;
&lt;p&gt;변경 이유와 검증 내용, 그 결과를 남겨야 합니다. 자동 재학습이 수집 장애를 학습 데이터에 흡수하는 경로가 되어서는 안 됩니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="22-견고한-mlops가-중요한-이유"&gt;2.2 견고한 MLOps가 중요한 이유&lt;span class="hx:absolute hx:-mt-20" id="22-견고한-mlops가-중요한-이유"&gt;&lt;/span&gt;
&lt;a href="#22-%ea%b2%ac%ea%b3%a0%ed%95%9c-mlops%ea%b0%80-%ec%a4%91%ec%9a%94%ed%95%9c-%ec%9d%b4%ec%9c%a0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;노트북 하나의 성공 뒤에는 데이터 처리, 환경, 모델과 서비스 사이의 의존성이 숨어 있습니다. 모델과 팀이 늘어날수록 수동 전달, 중복 구현, 불명확한 버전이 변경 속도와 신뢰성을 떨어뜨립니다.&lt;/p&gt;
&lt;p&gt;MLOps에 투자하는 이유는 도구를 많이 갖추기 위해서가 아니라 재작업을 줄이고 실패에 대응하면서 실험을 지속하기 위해서입니다. 작은 팀에서도 필요한 반복을 선택적으로 줄일 수 있습니다.&lt;/p&gt;
&lt;p&gt;수동 절차를 전부 없애기보다 오류 빈도와 영향, 반복 비용을 측정해 먼저 개선할 구간을 골라야 합니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="23-성숙한-조직에서의-mlops-역할"&gt;2.3 성숙한 조직에서의 MLOps 역할&lt;span class="hx:absolute hx:-mt-20" id="23-성숙한-조직에서의-mlops-역할"&gt;&lt;/span&gt;
&lt;a href="#23-%ec%84%b1%ec%88%99%ed%95%9c-%ec%a1%b0%ec%a7%81%ec%97%90%ec%84%9c%ec%9d%98-mlops-%ec%97%ad%ed%95%a0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;성숙한 조직에서는 좋은 실행 경로가 공통 기능으로 제공됩니다. 실험 기록과 모델 등록을 기본으로 사용하고, 데이터 과학자와 엔지니어가 매번 배포 절차를 새로 협의하지 않게 만드는 것입니다.&lt;/p&gt;
&lt;p&gt;실험 속도뿐 아니라 협업, 감사 가능한 변경, 일관된 출시와 유지보수가 쉬워집니다. 다만 표준화를 강제하는 것만으로 성공하지는 않습니다. 팀이 실제로 사용하고 지원받을 수 있어야 합니다.&lt;/p&gt;
&lt;p&gt;플랫폼 이용률뿐 아니라 실험과 출시의 대기 시간, 지원 요청, 우회하는 수동 작업도 봐야 합니다. 채택되지 않는 표준은 공통 기능으로 정착하기 어렵습니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="24-devops와-mlops"&gt;2.4 DevOps와 MLOps&lt;span class="hx:absolute hx:-mt-20" id="24-devops와-mlops"&gt;&lt;/span&gt;
&lt;a href="#24-devops%ec%99%80-mlops" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;DevOps도 변경, 자동화, 모니터링과 협업을 다룹니다. MLOps에서는 여기에 데이터와 모델이 코드와 별도로 바뀐다는 점이 추가됩니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;관점&lt;/th&gt;
&lt;th&gt;일반적인 소프트웨어 운영&lt;/th&gt;
&lt;th&gt;ML 운영에서 더 확인할 것&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;변경의 원인&lt;/td&gt;
&lt;td&gt;코드, 설정, 의존성 변경&lt;/td&gt;
&lt;td&gt;데이터, 학습 조건, 모델 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검증&lt;/td&gt;
&lt;td&gt;기능과 시스템 요구 충족&lt;/td&gt;
&lt;td&gt;통계적 품질, 데이터 누수, 집단별 성능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;배포 단위&lt;/td&gt;
&lt;td&gt;애플리케이션과 인프라&lt;/td&gt;
&lt;td&gt;모델, 전처리, 피처 정의, 학습 파이프라인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;관측&lt;/td&gt;
&lt;td&gt;오류율, 지연, 자원&lt;/td&gt;
&lt;td&gt;입력 분포, 예측 품질, 비즈니스 결과&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;일반 소프트웨어도 외부 데이터의 영향을 받습니다. 절대적인 경계가 아니라 ML에서 무엇을 더 명시적으로 추적해야 하는지 보는 비교입니다.&lt;/p&gt;
&lt;h4 data-hextra-search-id="ci-cd와-ct의-차이"&gt;CI, CD와 CT의 차이&lt;span class="hx:absolute hx:-mt-20" id="ci-cd와-ct의-차이"&gt;&lt;/span&gt;
&lt;a href="#ci-cd%ec%99%80-ct%ec%9d%98-%ec%b0%a8%ec%9d%b4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;&lt;a href="https://cloud.google.com/architecture/mlops-continuous-delivery-and-automation-pipelines-in-machine-learning" target="_blank" rel="noopener"&gt;Google Cloud의 MLOps 아키텍처 문서&lt;/a&gt;는 CI와 CD에 CT를 더해 설명합니다. CI는 코드 변경을 통합하고 검사하는 과정, CD는 검증된 변경을 전달하거나 배포하는 과정, CT는 데이터와 조건의 변화에 따른 지속 학습입니다.&lt;/p&gt;
&lt;p&gt;CD는 continuous delivery와 continuous deployment 양쪽 의미로 쓰입니다. 전달 가능한 상태로 준비하는 것과 운영에 자동 출시하는 것은 다르므로 승인 경계를 명시해야 합니다.&lt;/p&gt;
&lt;div role="img" aria-label="다이어그램"&gt;
&lt;pre class="mermaid hx:mt-6"&gt;
flowchart TD
accTitle: 파이프라인 코드 변경과 지속 학습의 다른 경로
accDescr: 코드 변경은 검사, 빌드를 거쳐 학습 파이프라인으로 배포된다. 데이터나 일정의 트리거는 그 파이프라인을 실행한다. 새 모델은 품질 검증과 출시 정책을 거쳐 서비스에 반영된다.
A[&amp;#34;파이프라인, 컴포넌트 코드 변경&amp;#34;] --&amp;gt; B[&amp;#34;CI, 검사와 빌드&amp;#34;]
B --&amp;gt; C[&amp;#34;CD, 파이프라인 배포&amp;#34;]
D[&amp;#34;새 데이터, 일정, 감지된 변화&amp;#34;] --&amp;gt; E[&amp;#34;CT, 검증, 학습, 평가 실행&amp;#34;]
C --&amp;gt; E
E --&amp;gt; F[&amp;#34;후보 모델&amp;#34;]
F --&amp;gt; G[&amp;#34;출시 기준과 승인 정책&amp;#34;]
G --&amp;gt; H[&amp;#34;예측 서비스 반영&amp;#34;]
&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;실행을 자동화하더라도 잘못된 데이터나 품질이 낮은 후보의 출시를 막을 수 있어야 합니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="25-mlops-성숙도"&gt;2.5 MLOps 성숙도&lt;span class="hx:absolute hx:-mt-20" id="25-mlops-성숙도"&gt;&lt;/span&gt;
&lt;a href="#25-mlops-%ec%84%b1%ec%88%99%eb%8f%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;책은 Google의 자동화 수준 구분을 소개합니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;수준&lt;/th&gt;
&lt;th&gt;중심 특징&lt;/th&gt;
&lt;th&gt;무엇을 반복 가능하게 만드는가&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Level 0&lt;/td&gt;
&lt;td&gt;수동, 스크립트 중심의 학습과 배포&lt;/td&gt;
&lt;td&gt;개별 모델을 사람이 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Level 1&lt;/td&gt;
&lt;td&gt;데이터, 모델 검증을 포함한 학습 파이프라인 자동화&lt;/td&gt;
&lt;td&gt;새 데이터로 모델을 다시 만드는 과정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Level 2&lt;/td&gt;
&lt;td&gt;파이프라인 구성 요소 자체의 CI/CD 자동화&lt;/td&gt;
&lt;td&gt;학습 절차와 구성 요소를 바꾸는 과정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-hextra-search-id="251-level-0-수동-중심"&gt;2.5.1 Level 0 수동 중심&lt;span class="hx:absolute hx:-mt-20" id="251-level-0-수동-중심"&gt;&lt;/span&gt;
&lt;a href="#251-level-0-%ec%88%98%eb%8f%99-%ec%a4%91%ec%8b%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;사람이 스크립트와 파일을 전달하며 학습, 배포를 진행합니다. 초기 검증에는 빠를 수 있지만 반복할수록 환경과 버전 차이, 담당자 의존성이 부담이 됩니다. 첫 개선은 클러스터 도입보다 실행 기록과 검증 절차를 정리하는 일일 수 있습니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="252-level-1-학습-파이프라인-자동화"&gt;2.5.2 Level 1 학습 파이프라인 자동화&lt;span class="hx:absolute hx:-mt-20" id="252-level-1-학습-파이프라인-자동화"&gt;&lt;/span&gt;
&lt;a href="#252-level-1-%ed%95%99%ec%8a%b5-%ed%8c%8c%ec%9d%b4%ed%94%84%eb%9d%bc%ec%9d%b8-%ec%9e%90%eb%8f%99%ed%99%94" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;새 데이터가 들어오면 검증, 학습, 평가를 거쳐 후보 모델을 만듭니다. 개별 모델을 수동으로 전달하는 대신 재사용 가능한 학습 절차를 운영합니다. 실험과 운영에서 같은 절차를 사용하는 것도 중요한 성질입니다.&lt;/p&gt;
&lt;p&gt;예를 들어 매주 새 데이터를 넣어 같은 전처리와 평가를 수행할 수 있다면, 모델을 만드는 절차가 반복 가능한 상태입니다. 다만 전처리 코드를 바꿀 때 여전히 수동으로 파일을 옮겨야 한다면 파이프라인 자체의 변경은 아직 자동화되지 않은 것입니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="253-level-2-파이프라인의-cicd"&gt;2.5.3 Level 2 파이프라인의 CI/CD&lt;span class="hx:absolute hx:-mt-20" id="253-level-2-파이프라인의-cicd"&gt;&lt;/span&gt;
&lt;a href="#253-level-2-%ed%8c%8c%ec%9d%b4%ed%94%84%eb%9d%bc%ec%9d%b8%ec%9d%98-cicd" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;컴포넌트와 파이프라인 코드를 바꿀 때도 검사, 빌드, 배포를 자동화합니다. 기존 파이프라인을 다시 실행하는 것과 파이프라인 자체를 안전하게 수정, 배포하는 것은 다른 능력입니다.&lt;/p&gt;
&lt;p&gt;전처리 컴포넌트 수정이 코드 검사와 이미지 빌드를 거쳐 새 파이프라인으로 배포되고, 그 파이프라인으로 만든 모델이 정해진 평가를 통과하는 흐름입니다. 모델이 자주 만들어지는지와 모델을 만드는 시스템을 안전하게 바꿀 수 있는지는 따로 봐야 합니다.&lt;/p&gt;
&lt;p&gt;높은 수준을 무조건 목표로 할 필요는 없습니다. 사용 사례와 변경 빈도에 맞게 투자해야 하며, 학습 자동화가 있어도 출시 승인이나 예외 처리에는 사람이 관여할 수 있습니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="자동화-수준을-확인할-기준"&gt;자동화 수준을 확인할 기준&lt;span class="hx:absolute hx:-mt-20" id="자동화-수준을-확인할-기준"&gt;&lt;/span&gt;
&lt;a href="#%ec%9e%90%eb%8f%99%ed%99%94-%ec%88%98%ec%a4%80%ec%9d%84-%ed%99%95%ec%9d%b8%ed%95%a0-%ea%b8%b0%ec%a4%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;한 조직에서도 팀과 모델마다 자동화 수준은 다릅니다. 조직 전체에 점수 하나를 매기기보다 데이터 검증, 학습, 평가, 출시, 운영 대응을 따로 진단하는 편이 실용적입니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;진단 대상&lt;/th&gt;
&lt;th&gt;인터뷰 외에 확인할 증거&lt;/th&gt;
&lt;th&gt;다음 개선 후보&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;실험 재현&lt;/td&gt;
&lt;td&gt;실행 기록으로 같은 입력을 다시 찾는 과정&lt;/td&gt;
&lt;td&gt;코드, 데이터, 환경 연결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;변경 검증&lt;/td&gt;
&lt;td&gt;출시 전 실제 검사 결과와 탈락 사례&lt;/td&gt;
&lt;td&gt;데이터, 모델 검증 게이트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;전달 속도&lt;/td&gt;
&lt;td&gt;최근 변경의 작업, 대기 시간&lt;/td&gt;
&lt;td&gt;반복 수동 전달 제거&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;운영 대응&lt;/td&gt;
&lt;td&gt;최근 장애의 감지, 복구 기록&lt;/td&gt;
&lt;td&gt;책임자, 실행 지침, 복구 경로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;파이프라인 변경&lt;/td&gt;
&lt;td&gt;구성 요소 변경의 시험, 배포 기록&lt;/td&gt;
&lt;td&gt;파이프라인 자체의 CI/CD&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;“자동 재학습 중”이라는 답만으로 Level 1이라고 확정할 수 없습니다. 잘못된 데이터에서 실행이 중단되는지, 새 모델이 기존 모델보다 나쁠 때 출시가 막히는지 확인해야 합니다.&lt;/p&gt;
&lt;p&gt;실험에서 운영으로 옮길 때 다른 전처리 코드를 다시 작성한다면 자동화가 있어도 불일치 위험이 남습니다. 공유 컴포넌트뿐 아니라 같은 입력에서의 결과 비교로 실험, 운영 대칭성을 확인해야 합니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="반복-작업의-비용"&gt;반복 작업의 비용&lt;span class="hx:absolute hx:-mt-20" id="반복-작업의-비용"&gt;&lt;/span&gt;
&lt;a href="#%eb%b0%98%eb%b3%b5-%ec%9e%91%ec%97%85%ec%9d%98-%eb%b9%84%ec%9a%a9" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;자동화 우선순위를 정하려면 최근 모델 변경에서 데이터 준비, 학습, 평가, 배포에 각각 얼마나 걸렸는지 봐야 합니다. 실행 시간과 승인 대기를 구분하면 병목이 달라 보일 수 있습니다.&lt;/p&gt;
&lt;p&gt;결과물에는 자동화할 작업, 성공 기준, 실패 알림을 받을 사람이 함께 있어야 합니다. “Level 2로 간다”보다 실행 가능한 목표입니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="질문"&gt;질문&lt;span class="hx:absolute hx:-mt-20" id="질문"&gt;&lt;/span&gt;
&lt;a href="#%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="재학습을-매일-돌리면-mlops가-잘된-것일까요"&gt;재학습을 매일 돌리면 MLOps가 잘된 것일까요?&lt;span class="hx:absolute hx:-mt-20" id="재학습을-매일-돌리면-mlops가-잘된-것일까요"&gt;&lt;/span&gt;
&lt;a href="#%ec%9e%ac%ed%95%99%ec%8a%b5%ec%9d%84-%eb%a7%a4%ec%9d%bc-%eb%8f%8c%eb%a6%ac%eb%a9%b4-mlops%ea%b0%80-%ec%9e%98%eb%90%9c-%ea%b2%83%ec%9d%bc%ea%b9%8c%ec%9a%94" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;데이터 검증, 모델 비교, 출시 기준이 없다면 잘못된 모델을 더 자주 만들 뿐일 수 있습니다. 빈도보다 반복 과정의 통제가 중요합니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="언제-재학습보다-데이터-파이프라인을-고쳐야-할까요"&gt;언제 재학습보다 데이터 파이프라인을 고쳐야 할까요?&lt;span class="hx:absolute hx:-mt-20" id="언제-재학습보다-데이터-파이프라인을-고쳐야-할까요"&gt;&lt;/span&gt;
&lt;a href="#%ec%96%b8%ec%a0%9c-%ec%9e%ac%ed%95%99%ec%8a%b5%eb%b3%b4%eb%8b%a4-%eb%8d%b0%ec%9d%b4%ed%84%b0-%ed%8c%8c%ec%9d%b4%ed%94%84%eb%9d%bc%ec%9d%b8%ec%9d%84-%ea%b3%a0%ec%b3%90%ec%95%bc-%ed%95%a0%ea%b9%8c%ec%9a%94" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;특정 시점부터 결측률이 급증하거나 단위가 바뀐 경우가 그렇습니다. 정상 데이터를 복구하지 않고 재학습하면 장애를 새로운 정상으로 학습할 수 있습니다.&lt;/p&gt;
&lt;p&gt;성숙도 단계는 다음 개선 지점을 찾는 기준입니다. 모든 조직이 같은 속도로 올라가야 하는 서열은 아닙니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="마치며"&gt;마치며&lt;span class="hx:absolute hx:-mt-20" id="마치며"&gt;&lt;/span&gt;
&lt;a href="#%eb%a7%88%ec%b9%98%eb%a9%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Level 1과 Level 2의 차이를 모델 배포 횟수로만 보면 놓치는 부분이 있습니다. 같은 학습 절차를 다시 실행하는 것과 그 절차 자체를 검사하고 바꾸는 것은 다른 능력입니다.&lt;/p&gt;
&lt;p&gt;그래서 현재 상태를 볼 때도 배포 자동화가 있는지 하나만 묻기보다 데이터 검증, 학습과 평가, 모델 등록과 출시를 나눠 확인해야겠다고 생각했습니다. 어디가 수동으로 끊기는지 알아야 다음 개선도 정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href="../01-getting-started/"&gt;이전: 01. 시작하기&lt;/a&gt;, &lt;a href="../"&gt;시리즈 목차&lt;/a&gt;, &lt;a href="../03-kubernetes-foundation/"&gt;다음: 03. Kubernetes 기반&lt;/a&gt;&lt;/p&gt;</description></item><item><title>02. 프롬프트 실행</title><link>https://johklo.github.io/docs/tech/ai-agents-and-applications/02-prompts/</link><pubDate>Mon, 19 Oct 2026 21:37:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/ai-agents-and-applications/02-prompts/</guid><description>
&lt;p&gt;대화창에서 좋은 답을 한 번 얻는 것과 프로그램에서 같은 작업을 반복하는 것은 다르다. 코드에서는 입력 변수를 조립하고, 응답 객체에서 필요한 값을 꺼내며, 잘못된 출력과 호출 실패를 처리해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="21-프로그램에서-프롬프트-실행"&gt;2.1 프로그램에서 프롬프트 실행&lt;span class="hx:absolute hx:-mt-20" id="21-프로그램에서-프롬프트-실행"&gt;&lt;/span&gt;
&lt;a href="#21-%ed%94%84%eb%a1%9c%ea%b7%b8%eb%9e%a8%ec%97%90%ec%84%9c-%ed%94%84%eb%a1%ac%ed%94%84%ed%8a%b8-%ec%8b%a4%ed%96%89" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;프롬프트 호출은 원격 서비스 요청이다. 인증과 모델 식별자, 요청 메시지를 보내고 응답을 받는다. 모델의 작업 실패와 네트워크나 인증 실패를 같은 문제로 취급하면 원인을 찾기 어렵다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="211-실행-환경"&gt;2.1.1 실행 환경&lt;span class="hx:absolute hx:-mt-20" id="211-실행-환경"&gt;&lt;/span&gt;
&lt;a href="#211-%ec%8b%a4%ed%96%89-%ed%99%98%ea%b2%bd" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;가상환경은 프로젝트별 Python과 라이브러리 의존성을 분리한다. Jupyter 커널이 실제로 어느 환경을 사용하는지도 확인해야 한다. 터미널에 설치한 패키지와 노트북에서 실행하는 인터프리터가 다르면 import가 실패하거나 다른 버전이 실행될 수 있다.&lt;/p&gt;
&lt;p&gt;API 키는 코드나 노트북 출력에 직접 넣지 않는다. 환경 변수나 비밀 입력으로 전달하고, 버전 관리에 포함하지 않는다. 실행 환경을 재현하려면 라이브러리 버전과 모델 설정도 함께 남겨야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="212-최소-호출"&gt;2.1.2 최소 호출&lt;span class="hx:absolute hx:-mt-20" id="212-최소-호출"&gt;&lt;/span&gt;
&lt;a href="#212-%ec%b5%9c%ec%86%8c-%ed%98%b8%ec%b6%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;최소 호출에서도 입력, 메시지, 응답을 분리해서 본다. 사용자 입력 문자열을 역할이 있는 메시지로 감싸 모델에 보내고, 응답 객체에서 생성된 내용을 읽는다.&lt;/p&gt;
&lt;p&gt;응답 객체에는 본문 외에 종료 이유나 사용량 같은 정보가 들어갈 수 있다. 출력 토큰 제한으로 잘렸거나 도구 호출을 요청한 응답을 일반적인 최종 문장과 똑같이 처리해서는 안 된다.&lt;/p&gt;
&lt;p&gt;모델 이름을 바꾸면 지원하는 파라미터와 출력 형태도 달라질 수 있다. 예제의 모델 식별자를 설치 환경과 제공자 문서에 맞춰 확인해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="22-langchain으로-실행"&gt;2.2 LangChain으로 실행&lt;span class="hx:absolute hx:-mt-20" id="22-langchain으로-실행"&gt;&lt;/span&gt;
&lt;a href="#22-langchain%ec%9c%bc%eb%a1%9c-%ec%8b%a4%ed%96%89" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;LangChain은 제공자별 클라이언트 위에 공통 호출 인터페이스를 제공한다. &lt;code&gt;invoke()&lt;/code&gt;로 한 번 실행하고, 필요하면 스트리밍이나 배치 같은 실행 방식으로 확장할 수 있다.&lt;/p&gt;
&lt;p&gt;텍스트 모델의 출력과 채팅 모델의 출력은 구분해야 한다. 채팅 모델은 보통 &lt;code&gt;AIMessage&lt;/code&gt; 같은 메시지 객체를 반환한다. 이를 그대로 문자열이라고 가정하면 이후 JSON 해석이나 문자열 결합이 실패할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;prompt | model | parser&lt;/code&gt;처럼 연결하면 각 단계의 책임이 드러난다. 템플릿은 입력을 조립하고 모델은 응답을 만들며 파서는 다음 코드가 사용할 값으로 해석한다. 파서가 JSON 문법을 처리해도 필드의 의미와 값의 범위까지 자동으로 맞는 것은 아니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="23-프롬프트-템플릿"&gt;2.3 프롬프트 템플릿&lt;span class="hx:absolute hx:-mt-20" id="23-프롬프트-템플릿"&gt;&lt;/span&gt;
&lt;a href="#23-%ed%94%84%eb%a1%ac%ed%94%84%ed%8a%b8-%ed%85%9c%ed%94%8c%eb%a6%bf" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;템플릿은 고정된 지침과 요청마다 달라지는 값을 분리한다. 요약이라면 원문, 목표 길이, 문체가 변수일 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="231-python-함수로-구성"&gt;2.3.1 Python 함수로 구성&lt;span class="hx:absolute hx:-mt-20" id="231-python-함수로-구성"&gt;&lt;/span&gt;
&lt;a href="#231-python-%ed%95%a8%ec%88%98%eb%a1%9c-%ea%b5%ac%ec%84%b1" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;문자열 포맷팅으로도 템플릿을 만들 수 있다. 핵심은 지침과 입력을 섞어서 임의의 문자열을 만드는 대신, 어떤 변수가 필요한지 명시하는 것이다.&lt;/p&gt;
&lt;p&gt;예를 들어 요약 요청은 “다음 텍스트의 핵심 주장과 근거를 세 문단으로 정리한다”는 고정 지침과 실제 문서 본문으로 나눌 수 있다. 원문 안에 “앞의 지침을 무시하라”는 문장이 있어도 분석할 데이터이지 애플리케이션의 지시가 아니다.&lt;/p&gt;
&lt;p&gt;문자열이 완성됐다는 사실과 프롬프트의 경계가 명확하다는 사실도 다르다. 문서, 질문, 출력 조건을 구분하고 외부 입력을 어떤 신뢰 수준으로 넣는지 정해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="232-prompttemplate"&gt;2.3.2 PromptTemplate&lt;span class="hx:absolute hx:-mt-20" id="232-prompttemplate"&gt;&lt;/span&gt;
&lt;a href="#232-prompttemplate" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;PromptTemplate&lt;/code&gt;은 템플릿 변수와 포맷 과정을 공통 객체로 관리한다. 입력 사전에 필요한 키가 있는지, 템플릿에서 어떤 값이 사용되는지 확인하기 쉽다.&lt;/p&gt;
&lt;p&gt;중괄호를 쓰는 JSON 예시를 템플릿에 넣을 때는 변수 자리와 JSON 중괄호를 구분해야 한다. 리터럴 중괄호의 이스케이프 방식도 템플릿 문법에 따른다.&lt;/p&gt;
&lt;p&gt;템플릿을 버전 관리하면 모델이 달라진 것인지 지침이 달라진 것인지 비교할 수 있다. 모델 응답만 저장하고 완성된 프롬프트를 남기지 않으면 결과 차이의 원인을 찾기 어렵다. 단, 실제 입력에 개인정보가 있다면 기록 범위와 마스킹도 필요하다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="24-작업별-프롬프트"&gt;2.4 작업별 프롬프트&lt;span class="hx:absolute hx:-mt-20" id="24-작업별-프롬프트"&gt;&lt;/span&gt;
&lt;a href="#24-%ec%9e%91%ec%97%85%eb%b3%84-%ed%94%84%eb%a1%ac%ed%94%84%ed%8a%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="241-텍스트-분류"&gt;2.4.1 텍스트 분류&lt;span class="hx:absolute hx:-mt-20" id="241-텍스트-분류"&gt;&lt;/span&gt;
&lt;a href="#241-%ed%85%8d%ec%8a%a4%ed%8a%b8-%eb%b6%84%eb%a5%98" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;분류에서는 허용 범주와 판단 기준을 먼저 정한다. “고객 문의를 분류하라”보다 “오류 신고, 기능 요청, 결제 문의 중 하나를 반환하라”가 출력 공간을 명확하게 만든다.&lt;/p&gt;
&lt;p&gt;범주를 고를 수 없는 입력도 있다. 여러 목적이 섞인 문의를 한 범주로 강제할지, 복수 범주를 허용할지, 검토 대상으로 넘길지 정해야 한다. 출력 예시에 없는 범주가 나왔다면 코드에서도 검증해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="242-감정-분석"&gt;2.4.2 감정 분석&lt;span class="hx:absolute hx:-mt-20" id="242-감정-분석"&gt;&lt;/span&gt;
&lt;a href="#242-%ea%b0%90%ec%a0%95-%eb%b6%84%ec%84%9d" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;감정 분석은 긍정, 중립, 부정 같은 태도를 분류하는 작업이다. 주제와 감정의 대상을 함께 봐야 한다. “배송은 늦었지만 제품은 좋다”는 문장을 전체 감정 하나로 압축하면 중요한 차이를 잃을 수 있다.&lt;/p&gt;
&lt;p&gt;여러 문장을 한 번에 처리할 때는 입력 식별자를 결과에 유지해야 한다. 행 순서가 바뀌거나 일부 결과가 누락될 수 있으므로 텍스트만 나열한 출력보다 식별자와 범주를 가진 구조가 안전하다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="243-요약"&gt;2.4.3 요약&lt;span class="hx:absolute hx:-mt-20" id="243-요약"&gt;&lt;/span&gt;
&lt;a href="#243-%ec%9a%94%ec%95%bd" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;요약은 원문에서 무엇을 남길지 결정하는 작업이다. 분량만 정하면 핵심 주장보다 눈에 띄는 예시를 남길 수도 있다. 목적, 포함할 내용, 유지할 수치와 조건을 명시해야 한다.&lt;/p&gt;
&lt;p&gt;“세 문장”은 출력 길이의 조건이고 “결론의 적용 조건을 빠뜨리지 않는다”는 정보 보존의 조건이다. 두 조건이 충돌하면 어느 쪽을 우선할지도 중요하다. 짧고 매끄러운 문장이 원문의 의미를 보존했다는 보장은 없다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="244-글-작성"&gt;2.4.4 글 작성&lt;span class="hx:absolute hx:-mt-20" id="244-글-작성"&gt;&lt;/span&gt;
&lt;a href="#244-%ea%b8%80-%ec%9e%91%ec%84%b1" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;제공된 사실을 바탕으로 글을 구성하는 작업이다. 요약과 달리 문장 구조와 연결 표현을 새로 만들 수 있지만, 주어진 사실에 없는 수치나 사례까지 추가해도 된다는 뜻은 아니다.&lt;/p&gt;
&lt;p&gt;사실 목록과 원하는 문체, 대상 독자, 출력 구조를 나누면 결과를 검토하기 쉽다. 근거가 없는 부분을 창작해도 되는 작업과 그렇지 않은 작업도 구분해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="245-질의응답"&gt;2.4.5 질의응답&lt;span class="hx:absolute hx:-mt-20" id="245-질의응답"&gt;&lt;/span&gt;
&lt;a href="#245-%ec%a7%88%ec%9d%98%ec%9d%91%eb%8b%b5" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;질의응답은 질문에 필요한 내용을 문맥에서 찾아 답하는 작업이다. 전체 문서를 요약하는 것과 달리 관련 정보의 위치와 조건을 정확하게 집어야 한다.&lt;/p&gt;
&lt;p&gt;문서에 답이 없는 경우를 포함해 규칙을 정해야 한다. 답을 찾지 못했는데 일반 지식으로 메워 버리면 문서 기반 응답이라는 계약이 깨진다. 출처 위치를 함께 반환하면 사실과 근거를 비교하기 쉽다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="246-추론"&gt;2.4.6 추론&lt;span class="hx:absolute hx:-mt-20" id="246-추론"&gt;&lt;/span&gt;
&lt;a href="#246-%ec%b6%94%eb%a1%a0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;추론은 주어진 정보에서 관계를 적용해 결론을 내리는 작업이다. 단순한 사실 추출보다 중간의 분류와 계산이 정확해야 한다.&lt;/p&gt;
&lt;p&gt;예를 들어 숫자 목록에서 조건에 맞는 수를 고른 뒤 합계를 구한다면, 원소 선택 오류와 덧셈 오류는 다른 실패다. 결과 숫자 하나만 보면 어디서 틀렸는지 알기 어렵다. 선택한 항목과 계산 결과를 분리해 확인하는 편이 낫다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="25-예시와-단계로-작업을-구체화하기"&gt;2.5 예시와 단계로 작업을 구체화하기&lt;span class="hx:absolute hx:-mt-20" id="25-예시와-단계로-작업을-구체화하기"&gt;&lt;/span&gt;
&lt;a href="#25-%ec%98%88%ec%8b%9c%ec%99%80-%eb%8b%a8%ea%b3%84%eb%a1%9c-%ec%9e%91%ec%97%85%ec%9d%84-%ea%b5%ac%ec%b2%b4%ed%99%94%ed%95%98%ea%b8%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Zero-shot은 예시 없이 지침만 제공하는 방식이다. One-shot과 few-shot은 예시를 넣어 원하는 대응 관계를 보여 준다. 여기서 learning은 현재 문맥 안의 패턴 적용이며 가중치를 재학습하는 과정이 아니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="251-one-shot"&gt;2.5.1 One-shot&lt;span class="hx:absolute hx:-mt-20" id="251-one-shot"&gt;&lt;/span&gt;
&lt;a href="#251-one-shot" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;예시 하나는 출력 모양을 보여 주는 데 유용하지만 규칙의 모든 경우를 설명하지 못할 수 있다. 회문 판별에서 &lt;code&gt;121&lt;/code&gt;만 보여 주면 한 자리 숫자나 두 자리 숫자, 0으로 시작하는 문자열을 어떻게 처리할지 여전히 모호하다.&lt;/p&gt;
&lt;p&gt;예시는 정답과 입력의 관계를 드러내야 한다. 원하는 결과 하나를 보여 준 것과 일반적인 판별 규칙을 정의한 것은 다르다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="252-two-shot"&gt;2.5.2 Two-shot&lt;span class="hx:absolute hx:-mt-20" id="252-two-shot"&gt;&lt;/span&gt;
&lt;a href="#252-two-shot" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;두 예시는 적용 범위를 넓힐 수 있지만, 단순히 수를 늘리는 것만으로 오류가 사라지지는 않는다. 같은 형태의 쉬운 예시 두 개보다 서로 다른 경계 조건을 보여 주는 예시가 유용할 수 있다.&lt;/p&gt;
&lt;p&gt;책의 숫자 문제도 예시를 추가해도 일부 항목을 누락한다. 모델의 자연스러운 설명과 실제 판별 결과가 일치하는지 따로 확인해야 한다는 사례다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="253-처리-단계-제공"&gt;2.5.3 처리 단계 제공&lt;span class="hx:absolute hx:-mt-20" id="253-처리-단계-제공"&gt;&lt;/span&gt;
&lt;a href="#253-%ec%b2%98%eb%a6%ac-%eb%8b%a8%ea%b3%84-%ec%a0%9c%ea%b3%b5" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;조건 확인, 대상 추출, 계산, 결과 점검처럼 작업을 분해하면 요구가 분명해진다. 실패를 분석할 단위도 생긴다.&lt;/p&gt;
&lt;p&gt;다만 단계를 적었다고 계산 정확도가 보장되는 것은 아니다. 결정적인 규칙이나 산술은 코드로 처리하는 편이 나을 수 있다. LLM이 자연어 요구를 해석하고 코드가 검증 가능한 계산을 수행하도록 역할을 나눌 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="254-few-shot"&gt;2.5.4 Few-shot&lt;span class="hx:absolute hx:-mt-20" id="254-few-shot"&gt;&lt;/span&gt;
&lt;a href="#254-few-shot" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;여러 예시로 입력과 출력의 패턴을 전달한다. 예시에는 정상 사례뿐 아니라 여러 조건이 동시에 맞는 경우, 어느 조건에도 맞지 않는 경우를 포함할 필요가 있다.&lt;/p&gt;
&lt;p&gt;예를 들어 두 조건에 따라 서로 다른 범주를 붙인다면 첫 조건만 맞는 입력, 둘째만 맞는 입력, 둘 다 맞는 입력, 둘 다 아닌 입력이 서로 다른 역할을 한다. 모델이 이미 아는 유명한 규칙을 재현한 것인지, 제공한 새 규칙을 적용한 것인지도 구분해야 한다.&lt;/p&gt;
&lt;p&gt;예시를 많이 넣으면 토큰 비용과 문맥 사용량이 늘어난다. 모순된 예시나 잘못된 예시가 있으면 오히려 동작이 불안정해질 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="255-langchain의-few-shot-템플릿"&gt;2.5.5 LangChain의 few-shot 템플릿&lt;span class="hx:absolute hx:-mt-20" id="255-langchain의-few-shot-템플릿"&gt;&lt;/span&gt;
&lt;a href="#255-langchain%ec%9d%98-few-shot-%ed%85%9c%ed%94%8c%eb%a6%bf" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;예시 하나의 입력과 출력을 표현하는 템플릿을 만들고, 여러 예시와 공통 지침, 현재 질문을 조합한다. &lt;code&gt;FewShotPromptTemplate&lt;/code&gt;은 이 구성을 관리하는 객체다.&lt;/p&gt;
&lt;p&gt;예시 목록과 실제 사용자 입력은 분리해야 한다. 테스트할 정답을 프롬프트 예시에 잘못 포함하면 성능을 과대평가할 수 있다. 예시 선택 기준과 순서를 바꾸는 것도 프롬프트 변경에 포함된다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="256-chain-of-thought"&gt;2.5.6 Chain of Thought&lt;span class="hx:absolute hx:-mt-20" id="256-chain-of-thought"&gt;&lt;/span&gt;
&lt;a href="#256-chain-of-thought" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;복합 문제를 중간 단계로 나누고 그 예시를 함께 제공하는 접근이다. 단순히 최종 답만 요구할 때보다 어떤 규칙과 절차를 적용할지 구체화한다.&lt;/p&gt;
&lt;p&gt;출력된 설명이 실제 내부 계산을 그대로 보여 준다고 가정할 수는 없다. 설명이 그럴듯해도 최종 답과 중간 값이 틀릴 수 있다. 필요한 것은 장문의 사고 과정 자체보다 선택한 항목, 계산 결과, 확인할 근거처럼 검증 가능한 산출물이다.&lt;/p&gt;
&lt;p&gt;추론형 모델은 제공자가 권장하는 지침이 일반 채팅 모델과 다를 수 있다. 모든 모델에 같은 형식의 단계별 설명을 강제하기보다 실제 작업에서 정확도와 비용을 비교해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="26-프롬프트-구조"&gt;2.6 프롬프트 구조&lt;span class="hx:absolute hx:-mt-20" id="26-프롬프트-구조"&gt;&lt;/span&gt;
&lt;a href="#26-%ed%94%84%eb%a1%ac%ed%94%84%ed%8a%b8-%ea%b5%ac%ec%a1%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;요소&lt;/th&gt;
&lt;th&gt;정하는 것&lt;/th&gt;
&lt;th&gt;예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;역할&lt;/td&gt;
&lt;td&gt;수행할 업무의 관점&lt;/td&gt;
&lt;td&gt;문서의 사실을 점검하는 편집자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;문맥&lt;/td&gt;
&lt;td&gt;작업을 이해하는 데 필요한 배경&lt;/td&gt;
&lt;td&gt;문서의 목적과 적용 범위&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;지시&lt;/td&gt;
&lt;td&gt;수행할 동작&lt;/td&gt;
&lt;td&gt;핵심 주장과 조건 추출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;입력&lt;/td&gt;
&lt;td&gt;실제 처리할 자료&lt;/td&gt;
&lt;td&gt;원문, 질문, 항목 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;단계&lt;/td&gt;
&lt;td&gt;필요한 처리 순서&lt;/td&gt;
&lt;td&gt;분류 후 계산하고 결과 검증&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;문체&lt;/td&gt;
&lt;td&gt;표현 방식&lt;/td&gt;
&lt;td&gt;간결한 기술 설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;출력 형식&lt;/td&gt;
&lt;td&gt;후속 코드가 받을 구조&lt;/td&gt;
&lt;td&gt;식별자와 결과를 가진 JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;모든 요청에 모든 요소가 필요한 것은 아니다. 단순 분류에 장문의 역할극이 항상 도움이 되는 것도 아니다. 작업에서 불확실한 부분을 줄이는 데 필요한 요소를 선택한다.&lt;/p&gt;
&lt;p&gt;출력 형식을 JSON으로 요청해도 파싱과 스키마 검증은 필요하다. 필수 필드가 없거나 숫자 자리에 문장이 들어가면 실패로 다뤄야 한다. 유효한 JSON이더라도 내용의 사실성은 별도 평가다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="결과를-비교할-때"&gt;결과를 비교할 때&lt;span class="hx:absolute hx:-mt-20" id="결과를-비교할-때"&gt;&lt;/span&gt;
&lt;a href="#%ea%b2%b0%ea%b3%bc%eb%a5%bc-%eb%b9%84%ea%b5%90%ed%95%a0-%eb%95%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;프롬프트를 개선하려면 대표 입력을 고정해 이전 버전과 비교해야 한다. 쉬운 예시 하나의 결과만 보고 전체 품질이 좋아졌다고 판단하면 안 된다.&lt;/p&gt;
&lt;p&gt;분류에서는 경계 사례와 허용 범주, 요약에서는 누락된 사실과 추가된 주장, 질의응답에서는 근거와 답할 수 없는 질문을 확인한다. 모델 버전과 프롬프트, 생성 설정을 함께 기록해야 변경의 영향을 해석할 수 있다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="질문"&gt;질문&lt;span class="hx:absolute hx:-mt-20" id="질문"&gt;&lt;/span&gt;
&lt;a href="#%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="예시가-많을수록-좋은-프롬프트일까"&gt;예시가 많을수록 좋은 프롬프트일까?&lt;span class="hx:absolute hx:-mt-20" id="예시가-많을수록-좋은-프롬프트일까"&gt;&lt;/span&gt;
&lt;a href="#%ec%98%88%ec%8b%9c%ea%b0%80-%eb%a7%8e%ec%9d%84%ec%88%98%eb%a1%9d-%ec%a2%8b%ec%9d%80-%ed%94%84%eb%a1%ac%ed%94%84%ed%8a%b8%ec%9d%bc%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;예시의 대표성과 정확성이 먼저다. 서로 중복되거나 모순된 예시는 비용만 늘리거나 판단을 흐릴 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="json으로-응답하면-프로그램에서-바로-믿고-써도-될까"&gt;JSON으로 응답하면 프로그램에서 바로 믿고 써도 될까?&lt;span class="hx:absolute hx:-mt-20" id="json으로-응답하면-프로그램에서-바로-믿고-써도-될까"&gt;&lt;/span&gt;
&lt;a href="#json%ec%9c%bc%eb%a1%9c-%ec%9d%91%eb%8b%b5%ed%95%98%eb%a9%b4-%ed%94%84%eb%a1%9c%ea%b7%b8%eb%9e%a8%ec%97%90%ec%84%9c-%eb%b0%94%eb%a1%9c-%eb%af%bf%ea%b3%a0-%ec%8d%a8%eb%8f%84-%eb%90%a0%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;문법, 스키마, 업무상 의미가 모두 맞아야 한다. 세 검증은 서로 다르다.&lt;/p&gt;
&lt;p&gt;&lt;a href="../01-introduction/"&gt;이전: AI 애플리케이션과 에이전트&lt;/a&gt; / &lt;a href="../"&gt;시리즈 목차&lt;/a&gt; / &lt;a href="../03-summarization/"&gt;다음: 문서 요약&lt;/a&gt;&lt;/p&gt;</description></item><item><title>03. 문서 요약</title><link>https://johklo.github.io/docs/tech/ai-agents-and-applications/03-summarization/</link><pubDate>Mon, 19 Oct 2026 21:37:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/ai-agents-and-applications/03-summarization/</guid><description>
&lt;p&gt;긴 문서의 요약은 문장을 줄이는 문제이면서 정보를 어떻게 나누고 다시 합칠지 정하는 문제다. 모델에 한 번에 넣을 수 없는 자료라면 분할이 필요하고, 분할하는 순간 조각 사이의 관계가 약해질 수 있다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="31-문맥-창보다-긴-문서"&gt;3.1 문맥 창보다 긴 문서&lt;span class="hx:absolute hx:-mt-20" id="31-문맥-창보다-긴-문서"&gt;&lt;/span&gt;
&lt;a href="#31-%eb%ac%b8%eb%a7%a5-%ec%b0%bd%eb%b3%b4%eb%8b%a4-%ea%b8%b4-%eb%ac%b8%ec%84%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;문맥 창에는 문서뿐 아니라 지침과 생성할 응답도 들어간다. 문서가 모델의 최대 입력 길이보다 짧아도 대화나 출력 예산을 포함하면 넘칠 수 있다. 모든 문서를 프롬프트에 넣는 방식은 단순하지만 길이와 비용, 모델의 정보 활용 능력을 함께 확인해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;
&lt;img src="https://johklo.github.io/images/ai-agents-and-applications/figure-3-01.png" title="그림 3.1. 긴 문서의 MapReduce 요약. 출처: AI Agents and Applications, 3장." alt="긴 문서를 청크로 분할하고 각 청크를 독립적으로 요약한 뒤 청크 요약들을 다시 요약해 최종 결과를 만드는 MapReduce 순서." loading="lazy" /&gt;
&lt;figcaption&gt;그림 3.1. 긴 문서의 MapReduce 요약. 출처: AI Agents and Applications, 3장.&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;그림의 Split은 텍스트를 나누는 작업이다. Map에서 실제 모델 호출이 청크마다 발생하고, Reduce에서도 모델을 호출해 여러 요약을 통합한다. 파일 하나를 처리한다고 모델 호출도 한 번인 것은 아니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="311-document와-청크"&gt;3.1.1 Document와 청크&lt;span class="hx:absolute hx:-mt-20" id="311-document와-청크"&gt;&lt;/span&gt;
&lt;a href="#311-document%ec%99%80-%ec%b2%ad%ed%81%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;Document&lt;/code&gt;는 본문과 메타데이터를 가진다. 문서를 나눈 뒤에도 출처와 원래 위치를 알 수 있어야 요약문이 어떤 부분에서 나온 것인지 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;청크는 요약 모델이 처리할 입력 단위다. 문자 수로 나누는 것과 토큰 수로 나누는 것은 다르다. 언어와 인코더에 따라 같은 길이의 문장도 토큰 수가 달라지므로, 모델의 문맥 예산을 관리하려면 실제 토큰 기준이 필요하다.&lt;/p&gt;
&lt;p&gt;너무 작은 청크는 정의와 예시, 주장과 조건을 다른 조각으로 나눌 수 있다. 너무 큰 청크는 호출당 비용이 커지고 중요한 세부 내용을 놓치기 쉽다. 분할 크기는 단순한 성능 파라미터가 아니라 보존할 의미의 단위를 결정한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="312-split"&gt;3.1.2 Split&lt;span class="hx:absolute hx:-mt-20" id="312-split"&gt;&lt;/span&gt;
&lt;a href="#312-split" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;책은 텍스트를 나누는 함수를 &lt;code&gt;RunnableLambda&lt;/code&gt;로 감싸 체인의 실행 단위로 만든다. 앞 단계에서 받은 문자열을 청크 목록으로 바꾸는 구조다.&lt;/p&gt;
&lt;p&gt;이 단계는 보통 모델 생성이 아니라 결정적인 텍스트 처리다. 문서 길이와 청크 수, 중첩 구간을 확인할 수 있어야 한다. 경계에 걸친 내용을 보존하려고 overlap을 두면 중복 처리량도 늘어난다.&lt;/p&gt;
&lt;p&gt;문서 제목이나 페이지, 절 정보가 분할 과정에서 사라지면 이후 요약의 출처도 약해진다. 본문 조각만 남길지 위치를 포함한 객체로 전달할지 정해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="313-map"&gt;3.1.3 Map&lt;span class="hx:absolute hx:-mt-20" id="313-map"&gt;&lt;/span&gt;
&lt;a href="#313-map" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Map은 각 청크에 같은 작업을 적용한다. 청크마다 주요 주장과 세부 내용을 뽑아 부분 요약을 만든다.&lt;/p&gt;
&lt;p&gt;독립 처리라서 병렬화하기 쉽지만, 한 청크는 다른 청크를 모른다. 앞부분의 정의를 뒤에서 수정하거나 예외를 제시하는 문서에서는 각각의 요약이 서로 충돌할 수 있다.&lt;/p&gt;
&lt;p&gt;부분 요약의 형식도 중요하다. 원문 위치, 핵심 사실, 수치와 조건을 분리해 남기면 최종 통합에서 비교하기 쉽다. 짧은 문장 하나로 압축하면 그 단계에서 버린 정보를 Reduce가 되살릴 수 없다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="314-reduce"&gt;3.1.4 Reduce&lt;span class="hx:absolute hx:-mt-20" id="314-reduce"&gt;&lt;/span&gt;
&lt;a href="#314-reduce" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Reduce는 부분 요약을 모아 최종 요약을 만든다. 단순 연결과 달리 반복을 제거하고 공통 내용과 차이를 정리한다.&lt;/p&gt;
&lt;p&gt;부분 요약을 모은 결과도 문맥 창을 넘을 수 있다. 예를 들어 청크 요약이 100개이고 각 500토큰이면 그것만으로 50,000토큰이다. 한 번에 처리하기 어렵다면 그룹별 중간 요약을 만든 뒤 다시 합치는 계층적 처리가 필요하다.&lt;/p&gt;
&lt;p&gt;압축 단계를 늘릴수록 앞 단계의 누락과 오류가 누적될 수 있다. 최종 요약이 자연스럽다는 사실보다 원문의 핵심 사실과 적용 조건이 유지됐는지를 확인해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="315-mapreduce-체인"&gt;3.1.5 MapReduce 체인&lt;span class="hx:absolute hx:-mt-20" id="315-mapreduce-체인"&gt;&lt;/span&gt;
&lt;a href="#315-mapreduce-%ec%b2%b4%ec%9d%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;체인은 대체로 다음 형태다.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;원문 문자열
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 청크 목록
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 각 청크의 부분 요약
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 부분 요약 결합
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 최종 요약&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;LCEL의 &lt;code&gt;.map()&lt;/code&gt;은 목록의 각 항목에 같은 Runnable을 적용한다. 동시 실행을 활용할 수 있지만, 실제 병렬성은 실행 설정과 모델 API의 요청 제한에 영향을 받는다. 단순히 &lt;code&gt;.map()&lt;/code&gt;을 붙였다고 무제한 호출을 허용하는 것은 아니다.&lt;/p&gt;
&lt;p&gt;최종 결과를 해석하는 파서가 어느 단계에 있는지도 확인해야 한다. 부분 요약이 메시지 객체인데 문자열 목록처럼 합치면 모델 본문 외의 객체 표현이 섞일 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="316-실행과-비용"&gt;3.1.6 실행과 비용&lt;span class="hx:absolute hx:-mt-20" id="316-실행과-비용"&gt;&lt;/span&gt;
&lt;a href="#316-%ec%8b%a4%ed%96%89%ea%b3%bc-%eb%b9%84%ec%9a%a9" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;원문이 60,000토큰이고 청크를 5,000토큰으로 나누면 중첩을 제외하고 12개가 된다. 청크당 400토큰의 요약을 만들면 Reduce 입력은 약 4,800토큰에 지침 등이 추가된다.&lt;/p&gt;
&lt;p&gt;모델 호출은 Map 12회와 Reduce 1회로 최소 13회다. 재시도나 계층적 Reduce가 생기면 더 늘어난다. 원문 길이뿐 아니라 중간 결과의 크기도 비용에 들어간다.&lt;/p&gt;
&lt;p&gt;병렬 실행에서 요청 제한 오류가 발생했다면 한 번에 처리하는 개수와 재시도 정책을 조정해야 한다. 실패한 청크를 빈 요약으로 바꾸면 전체 결과는 성공처럼 보여도 일부 원문이 사라진다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="32-여러-문서의-요약"&gt;3.2 여러 문서의 요약&lt;span class="hx:absolute hx:-mt-20" id="32-여러-문서의-요약"&gt;&lt;/span&gt;
&lt;a href="#32-%ec%97%ac%eb%9f%ac-%eb%ac%b8%ec%84%9c%ec%9d%98-%ec%9a%94%ec%95%bd" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;여러 문서를 요약할 때는 문서들이 같은 주제의 근거인지, 서로 다른 주제의 독립 자료인지부터 구분해야 한다. 관련 없는 문서를 억지로 하나의 결론으로 묶으면 존재하지 않는 관계를 만들 수 있다.&lt;/p&gt;
&lt;p&gt;MapReduce는 문서마다 요약한 뒤 합치는 방식이다. Refine은 앞에서 만든 요약을 다음 문서와 함께 모델에 전달해 조금씩 수정하는 방식이다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="321-document-목록-만들기"&gt;3.2.1 Document 목록 만들기&lt;span class="hx:absolute hx:-mt-20" id="321-document-목록-만들기"&gt;&lt;/span&gt;
&lt;a href="#321-document-%eb%aa%a9%eb%a1%9d-%eb%a7%8c%eb%93%a4%ea%b8%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;파일 하나가 반드시 Document 하나가 되는 것은 아니다. 페이지마다 객체를 반환하는 Loader도 있고 한 웹 자료에서 여러 문서를 가져오는 Loader도 있다.&lt;/p&gt;
&lt;p&gt;그 결과를 합칠 때 객체 수와 실제 원문 수를 구분해야 한다. 출처가 같은 자료를 여러 번 읽거나 같은 본문이 중복되면 특정 내용의 중요성을 과대평가할 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="322-wikipedia-자료"&gt;3.2.2 Wikipedia 자료&lt;span class="hx:absolute hx:-mt-20" id="322-wikipedia-자료"&gt;&lt;/span&gt;
&lt;a href="#322-wikipedia-%ec%9e%90%eb%a3%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;전용 Loader는 본문 추출과 메타데이터 구성 과정을 줄여 준다. 다만 반환하는 문서 수와 범위가 요청한 제목 하나에만 제한되는지는 설정과 구현을 확인해야 한다.&lt;/p&gt;
&lt;p&gt;검색 결과의 관련 문서까지 가져올 수 있다면 자료 범위와 비용이 함께 늘어난다. 모델에 넣기 전에 각 문서의 제목과 출처, 길이를 확인하는 것이 필요하다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="323-파일-기반-자료"&gt;3.2.3 파일 기반 자료&lt;span class="hx:absolute hx:-mt-20" id="323-파일-기반-자료"&gt;&lt;/span&gt;
&lt;a href="#323-%ed%8c%8c%ec%9d%bc-%ea%b8%b0%eb%b0%98-%ec%9e%90%eb%a3%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;TXT, Word, PDF는 원문을 얻는 과정이 다르다. 줄바꿈, 표, 머리말과 꼬리말, 여러 단의 읽기 순서가 텍스트 추출 결과에 영향을 준다.&lt;/p&gt;
&lt;p&gt;모델이 요약에 실패한 것처럼 보여도 실제로는 표의 열 순서가 뒤섞이거나 본문 일부가 누락됐을 수 있다. 추출 단계에서 의미가 깨진 입력을 후속 요약이 완전히 복구해 주리라 기대하면 안 된다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="324-목록-합치기"&gt;3.2.4 목록 합치기&lt;span class="hx:absolute hx:-mt-20" id="324-목록-합치기"&gt;&lt;/span&gt;
&lt;a href="#324-%eb%aa%a9%eb%a1%9d-%ed%95%a9%ec%b9%98%ea%b8%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;다양한 Loader의 결과를 공통 Document 목록으로 합치면 같은 요약 체인에 넣을 수 있다. 공통 자료형을 사용한다는 것과 자료의 품질이 같다는 것은 별개다.&lt;/p&gt;
&lt;p&gt;문서 식별자와 원문 위치, 날짜를 유지하면 같은 사실의 중복과 서로 다른 시점의 주장을 구분할 수 있다. 자료의 순서를 정하는 일도 다음 Refine 단계의 동작에 영향을 준다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="325-refine"&gt;3.2.5 Refine&lt;span class="hx:absolute hx:-mt-20" id="325-refine"&gt;&lt;/span&gt;
&lt;a href="#325-refine" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Refine은 첫 문서로 초안을 만들고, 다음 문서와 현재 초안을 함께 보내 수정한다. 이후 문서도 같은 방식으로 처리한다.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;문서 1 → 요약 1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;요약 1 + 문서 2 → 요약 2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;요약 2 + 문서 3 → 최종 요약&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;각 단계가 직전 요약을 필요로 하므로 순차 의존성이 생긴다. Map처럼 모든 문서를 독립적으로 처리하기는 어렵다.&lt;/p&gt;
&lt;p&gt;장점은 이전 내용을 보며 새 내용을 반영할 수 있다는 점이다. 하지만 모든 내용을 반드시 보존한다는 보장은 없다. 뒤의 문서가 초기 결론을 덮어쓰거나 요약이 점점 길어질 수 있고, 초기에 생긴 오류가 남을 수도 있다. 자료 순서와 갱신 지침, 요약 길이를 함께 관리해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="33-요약-방법의-선택"&gt;3.3 요약 방법의 선택&lt;span class="hx:absolute hx:-mt-20" id="33-요약-방법의-선택"&gt;&lt;/span&gt;
&lt;a href="#33-%ec%9a%94%ec%95%bd-%eb%b0%a9%eb%b2%95%ec%9d%98-%ec%84%a0%ed%83%9d" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;방식&lt;/th&gt;
&lt;th&gt;처리 흐름&lt;/th&gt;
&lt;th&gt;장점&lt;/th&gt;
&lt;th&gt;주요 한계&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stuff&lt;/td&gt;
&lt;td&gt;자료를 한 프롬프트에 넣음&lt;/td&gt;
&lt;td&gt;단순하고 자료 사이를 함께 볼 수 있음&lt;/td&gt;
&lt;td&gt;문맥 예산과 긴 입력의 활용 한계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MapReduce&lt;/td&gt;
&lt;td&gt;부분 요약 후 통합&lt;/td&gt;
&lt;td&gt;독립 처리와 병렬화가 쉬움&lt;/td&gt;
&lt;td&gt;청크 간 관계와 세부 정보 손실&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refine&lt;/td&gt;
&lt;td&gt;현재 요약을 자료마다 갱신&lt;/td&gt;
&lt;td&gt;앞선 내용을 보며 새 자료 반영&lt;/td&gt;
&lt;td&gt;순서 의존성과 누적 오류, 순차 지연&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;문맥 창이 크더라도 요약 목적은 남는다. 전체를 다 넣는 비용과 필요한 정보의 위치, 문서 구조를 함께 봐야 한다.&lt;/p&gt;
&lt;p&gt;법률 조건이나 실험의 예외처럼 빠뜨리면 의미가 바뀌는 내용은 짧은 최종 요약만으로 검증하기 어렵다. 원문의 핵심 사실 목록과 요약 결과를 비교하거나 중요한 문장을 출처와 연결해 남길 필요가 있다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="질문"&gt;질문&lt;span class="hx:absolute hx:-mt-20" id="질문"&gt;&lt;/span&gt;
&lt;a href="#%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="reduce에서-원문의-누락을-보완할-수-있을까"&gt;Reduce에서 원문의 누락을 보완할 수 있을까?&lt;span class="hx:absolute hx:-mt-20" id="reduce에서-원문의-누락을-보완할-수-있을까"&gt;&lt;/span&gt;
&lt;a href="#reduce%ec%97%90%ec%84%9c-%ec%9b%90%eb%ac%b8%ec%9d%98-%eb%88%84%eb%9d%bd%ec%9d%84-%eb%b3%b4%ec%99%84%ed%95%a0-%ec%88%98-%ec%9e%88%ec%9d%84%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;부분 요약에 해당 내용이 없다면 보통 어렵다. Reduce가 받은 정보 안에서만 통합할 수 있기 때문에 Map 단계의 정보 보존이 중요하다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="refine이-mapreduce보다-항상-정확할까"&gt;Refine이 MapReduce보다 항상 정확할까?&lt;span class="hx:absolute hx:-mt-20" id="refine이-mapreduce보다-항상-정확할까"&gt;&lt;/span&gt;
&lt;a href="#refine%ec%9d%b4-mapreduce%eb%b3%b4%eb%8b%a4-%ed%95%ad%ec%83%81-%ec%a0%95%ed%99%95%ed%95%a0%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;다른 정보를 보며 갱신한다는 장점은 있지만 순서와 길이, 모델의 요약 품질에 영향을 받는다. 목적에 맞는 사실이 유지되는지 직접 비교해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;a href="../02-prompts/"&gt;이전: 프롬프트 실행&lt;/a&gt; / &lt;a href="../"&gt;시리즈 목차&lt;/a&gt; / &lt;a href="../04-research-engine/"&gt;다음: 리서치 요약 엔진&lt;/a&gt;&lt;/p&gt;</description></item><item><title>03. Kubernetes 위에 ML 애플리케이션의 기반 놓기</title><link>https://johklo.github.io/docs/tech/machine-learning-platform-engineering/03-kubernetes-foundation/</link><pubDate>Sun, 18 Oct 2026 21:30:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/machine-learning-platform-engineering/03-kubernetes-foundation/</guid><description>
&lt;p&gt;3장은 ML 도구를 올리기 전에 컨테이너와 Kubernetes, CI/CD와 모니터링의 역할을 설명합니다. 예제는 간단한 FastAPI 서비스지만 모델을 서빙할 때도 같은 실행 기반이 필요합니다.&lt;/p&gt;
&lt;p&gt;이번 장에서는 각 도구를 따로 외우기보다 코드 변경이 실제 서비스가 되기까지의 경로를 따라가려고 했습니다. 이미지를 만드는 일, Pod를 실행하는 일, 요청을 연결하는 일은 서로 다른 단계입니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="31-컨테이너와-도구"&gt;3.1 컨테이너와 도구&lt;span class="hx:absolute hx:-mt-20" id="31-컨테이너와-도구"&gt;&lt;/span&gt;
&lt;a href="#31-%ec%bb%a8%ed%85%8c%ec%9d%b4%eb%84%88%ec%99%80-%eb%8f%84%ea%b5%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;이 장은 ML 모델 대신 간단한 FastAPI 애플리케이션을 사용합니다. 모델의 복잡도를 빼고 포장, 실행, 배포, 관측을 먼저 이해하려는 구성입니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;책임&lt;/th&gt;
&lt;th&gt;책에서 사용하는 도구&lt;/th&gt;
&lt;th&gt;확인할 결과&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;실행 환경 포장&lt;/td&gt;
&lt;td&gt;Docker&lt;/td&gt;
&lt;td&gt;같은 식별자의 이미지를 가져와 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;배치, 복제본 관리&lt;/td&gt;
&lt;td&gt;Kubernetes&lt;/td&gt;
&lt;td&gt;원하는 워크로드 상태 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검사, 빌드&lt;/td&gt;
&lt;td&gt;GitLab CI&lt;/td&gt;
&lt;td&gt;변경을 검사하고 이미지, 배포 설정 연결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;선언된 상태 배포&lt;/td&gt;
&lt;td&gt;Argo CD&lt;/td&gt;
&lt;td&gt;Git 선언과 실제 상태 비교, 동기화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;운영 수치 관측&lt;/td&gt;
&lt;td&gt;Prometheus, Grafana&lt;/td&gt;
&lt;td&gt;지연, 오류, 트래픽, 자원 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div role="img" aria-label="다이어그램"&gt;
&lt;pre class="mermaid hx:mt-6"&gt;
flowchart TD
accTitle: 코드에서 Kubernetes 배포까지의 흐름
accDescr: 코드를 CI에서 검사하고 이미지를 빌드해 레지스트리에 저장한다. Git의 배포 설정을 Argo CD로 동기화하고 Kubernetes가 지정 이미지를 가져와 실행한다.
A[&amp;#34;코드 + 의존성 + Dockerfile&amp;#34;] --&amp;gt; B[&amp;#34;CI, 검사와 이미지 빌드&amp;#34;]
B --&amp;gt; C[&amp;#34;컨테이너 레지스트리&amp;#34;]
B --&amp;gt; D[&amp;#34;Git의 배포 설정에 이미지 버전 반영&amp;#34;]
D --&amp;gt; E[&amp;#34;Argo CD, 상태 비교와 동기화&amp;#34;]
E --&amp;gt; F[&amp;#34;Kubernetes, Pod 실행&amp;#34;]
C --&amp;gt;|&amp;#34;이미지 가져오기&amp;#34;| F
F --&amp;gt; G[&amp;#34;Prometheus와 Grafana, 상태 관측&amp;#34;]
&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;도구의 경계를 이해해야 장애도 분리할 수 있습니다. 이미지 빌드가 성공해도 클러스터에서 이미지를 가져오지 못할 수 있고, Pod가 실행되어도 Service가 잘못된 포트를 가리킬 수 있습니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="32-docker"&gt;3.2 Docker&lt;span class="hx:absolute hx:-mt-20" id="32-docker"&gt;&lt;/span&gt;
&lt;a href="#32-docker" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Dockerfile은 이미지를 만드는 레시피, 이미지는 배포할 포장, 컨테이너는 그 이미지를 실행한 인스턴스입니다. 레지스트리는 이미지를 저장하고 다른 환경에서 가져올 수 있게 하는 저장소입니다.&lt;/p&gt;
&lt;p&gt;이미지 하나에서 여러 컨테이너를 실행할 수 있습니다. 각 컨테이너의 메모리와 실행 상태는 별개입니다. 학습한 모델을 이미지에 넣을 수도 있고 실행할 때 외부 저장소에서 읽을 수도 있습니다. 후자라면 이미지 버전만으로는 실제 사용한 모델을 특정할 수 없으므로 모델 버전도 따로 남겨야 합니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="321-애플리케이션-코드-작성"&gt;3.2.1 애플리케이션 코드 작성&lt;span class="hx:absolute hx:-mt-20" id="321-애플리케이션-코드-작성"&gt;&lt;/span&gt;
&lt;a href="#321-%ec%95%a0%ed%94%8c%eb%a6%ac%ec%bc%80%ec%9d%b4%ec%85%98-%ec%bd%94%eb%93%9c-%ec%9e%91%ec%84%b1" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;책의 예제는 요청을 받으면 응답을 돌려주는 FastAPI 서비스입니다. 먼저 로컬에서 입력 경로, 응답 형식과 의존성을 확인한 다음 컨테이너로 옮기는 순서입니다.&lt;/p&gt;
&lt;p&gt;실행 명령과 환경 변수, 포트를 코드 밖에서도 알 수 있어야 합니다. 프로세스가 루프백 주소에만 바인딩되어 있으면 포트 매핑을 해도 외부 요청이 도달하지 않을 수 있습니다.&lt;/p&gt;
&lt;p&gt;여기서 구분할 것은 애플리케이션의 오류와 실행 환경의 오류입니다. 로컬에서도 같은 요청이 실패한다면 앱의 입력 처리와 의존성부터 봐야 합니다. 로컬에서는 되지만 컨테이너에서 실패한다면 파일 경로와 설치된 패키지, 실행 명령과 환경 변수가 달라졌는지 확인합니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="322-dockerfile-작성"&gt;3.2.2 Dockerfile 작성&lt;span class="hx:absolute hx:-mt-20" id="322-dockerfile-작성"&gt;&lt;/span&gt;
&lt;a href="#322-dockerfile-%ec%9e%91%ec%84%b1" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;주요 지시어&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;th&gt;점검할 사항&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FROM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;기반 이미지 선택&lt;/td&gt;
&lt;td&gt;버전, 아키텍처, 보안 업데이트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WORKDIR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;작업 경로 지정&lt;/td&gt;
&lt;td&gt;코드와 상대 경로의 일관성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;COPY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;빌드 문맥의 파일 복사&lt;/td&gt;
&lt;td&gt;비밀, 불필요한 데이터 제외&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RUN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;빌드 중 명령 실행&lt;/td&gt;
&lt;td&gt;시스템, Python 의존성의 재현성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ARG&lt;/code&gt; / &lt;code&gt;ENV&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;빌드 인자 / 환경 변수&lt;/td&gt;
&lt;td&gt;자격 증명을 이미지에 남기지 않기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ENTRYPOINT&lt;/code&gt; / &lt;code&gt;CMD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;기본 실행 방식&lt;/td&gt;
&lt;td&gt;정상 기동, 종료와 인자 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Python 패키지가 설치되어도 OS 라이브러리나 GPU 런타임까지 갖춰지는 것은 아닙니다. 호스트와 이미지의 역할을 구분하고, 환경별 비밀 정보는 실행 시점의 승인된 비밀 관리 방식으로 전달해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RUN&lt;/code&gt;은 이미지를 만드는 동안 실행되고, &lt;code&gt;ENTRYPOINT&lt;/code&gt;와 &lt;code&gt;CMD&lt;/code&gt;는 컨테이너가 시작될 때의 실행을 정합니다. 빌드가 성공했다는 것은 패키지를 만들었다는 뜻이지, API가 정상 기동한다는 뜻은 아닙니다. 이미지를 실제로 실행해 요청을 보내는 확인이 별도로 필요합니다.&lt;/p&gt;
&lt;p&gt;의존성 파일을 먼저 복사하고 설치한 뒤 앱 코드를 복사하면 코드만 바뀐 빌드에서 의존성 설치 계층을 재사용하기 쉽습니다. 반대로 비밀 파일이나 대용량 학습 데이터를 무심코 복사하면 이미지가 커지고 배포 속도와 보안에 영향을 줍니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="323-이미지-빌드와-푸시"&gt;3.2.3 이미지 빌드와 푸시&lt;span class="hx:absolute hx:-mt-20" id="323-이미지-빌드와-푸시"&gt;&lt;/span&gt;
&lt;a href="#323-%ec%9d%b4%eb%af%b8%ec%a7%80-%eb%b9%8c%eb%93%9c%ec%99%80-%ed%91%b8%ec%8b%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;로컬 빌드 → 로컬 컨테이너 실행 확인 → 레지스트리 푸시 → 배포 환경에서 가져오기 순서입니다. 애플리케이션, 포트 전달, 레지스트리 인증 중 어디가 잘못됐는지 나눠 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;이미지 태그는 다시 지정될 수 있습니다. 변경 가능한 이름만 기록하지 말고 실제 빌드, 배포에 사용한 커밋과 불변 digest를 연결해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/containers/images/" target="_blank" rel="noopener"&gt;Kubernetes 이미지 문서&lt;/a&gt;에서 tag는 옮겨 붙일 수 있는 이름, digest는 이미지 내용을 식별하는 해시로 구분합니다. &lt;code&gt;latest&lt;/code&gt;라는 이름을 기록하는 것과 실제 배포한 내용을 기록하는 것은 다릅니다.&lt;/p&gt;
&lt;p&gt;컨테이너를 사용하면 환경 차이를 줄일 수 있지만 모두 없어지지는 않습니다. CPU 아키텍처, GPU 드라이버, 외부 서비스와 런타임 설정은 여전히 영향을 줍니다.&lt;/p&gt;
&lt;p&gt;포트도 호스트와 컨테이너를 나눠 봐야 합니다. 컨테이너 안의 앱이 8083 포트에서 듣고 있고 호스트의 8081을 그 포트에 연결했다면 브라우저에서는 호스트의 8081로 요청합니다. 이미지에 포트를 선언하는 것과 실제 프로세스가 그 포트에서 실행되는 것, 외부 포트를 연결하는 것은 서로 다른 설정입니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="33-kubernetes"&gt;3.3 Kubernetes&lt;span class="hx:absolute hx:-mt-20" id="33-kubernetes"&gt;&lt;/span&gt;
&lt;a href="#33-kubernetes" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Kubernetes는 컨테이너를 어디에 배치하고 원하는 상태를 어떻게 유지할지 관리합니다. &lt;code&gt;docker run&lt;/code&gt;을 여러 번 실행하는 것보다 넓은 범위의 조정 체계입니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="331-kubernetes-아키텍처"&gt;3.3.1 Kubernetes 아키텍처&lt;span class="hx:absolute hx:-mt-20" id="331-kubernetes-아키텍처"&gt;&lt;/span&gt;
&lt;a href="#331-kubernetes-%ec%95%84%ed%82%a4%ed%85%8d%ec%b2%98" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;컨트롤 플레인은 API server, etcd, scheduler와 controller 등으로 상태, 배치, 조정을 관리합니다. 워커 노드에서는 kubelet과 컨테이너 런타임이 실제 워크로드를 실행합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://johklo.github.io/images/ml-platform-engineering/figure-3-04.png?v=2" alt="API server, etcd, Scheduler와 Controller를 가진 관리 노드와 kubelet 및 컨테이너 런타임으로 컨테이너를 실행하는 워커 노드의 구분." loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;그림 3.4. Kubernetes 구성 요소. 출처: Machine Learning Platform Engineering, 3장.&lt;/p&gt;
&lt;p&gt;그림의 Master node는 현재 주로 컨트롤 플레인이라고 부르는 관리 영역입니다. 박스의 구분은 책임을 나타냅니다. 워커가 etcd에 직접 요청한다는 뜻으로 읽으면 안 됩니다. 각 구성 요소는 API server를 통해 상태를 읽고 갱신하며 etcd는 그 클러스터 상태를 저장합니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구성 요소&lt;/th&gt;
&lt;th&gt;맡는 일&lt;/th&gt;
&lt;th&gt;맡지 않는 일&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API server&lt;/td&gt;
&lt;td&gt;자원 생성과 조회 요청을 받고 API 제공&lt;/td&gt;
&lt;td&gt;앱의 추론 요청을 대신 처리하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;etcd&lt;/td&gt;
&lt;td&gt;클러스터의 선언과 상태 저장&lt;/td&gt;
&lt;td&gt;학습 이미지와 모델 가중치 보관소가 아님&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scheduler&lt;/td&gt;
&lt;td&gt;배치되지 않은 Pod에 적합한 노드 선택&lt;/td&gt;
&lt;td&gt;컨테이너 프로세스를 직접 실행하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Controller&lt;/td&gt;
&lt;td&gt;원하는 상태와 현재 상태의 차이 조정&lt;/td&gt;
&lt;td&gt;모든 장애의 업무상 원인을 판단하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kubelet&lt;/td&gt;
&lt;td&gt;자기 노드의 Pod 실행 상태 관리&lt;/td&gt;
&lt;td&gt;클러스터 전체의 배치 결정을 하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;컨테이너 런타임&lt;/td&gt;
&lt;td&gt;이미지에서 컨테이너를 실행하고 관리&lt;/td&gt;
&lt;td&gt;어떤 모델을 출시할지 결정하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Deployment를 제출하면 API server가 선언을 받아 저장합니다. Controller가 필요한 Pod를 만들고 Scheduler가 노드를 배정합니다. 그 노드의 kubelet이 런타임을 통해 컨테이너를 실행합니다. 그래서 자원이 부족해 배치되지 않은 문제와 프로세스가 실행된 뒤 죽는 문제는 조사할 위치가 다릅니다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/architecture/" target="_blank" rel="noopener"&gt;Kubernetes 아키텍처 문서&lt;/a&gt; 기준으로 컨테이너 런타임은 containerd, CRI-O 등이고, kubelet은 CRI(Container Runtime Interface)라는 인터페이스로 런타임과 연결됩니다. Docker로 이미지를 빌드했다고 워커도 Docker Engine으로 실행한다는 뜻은 아닙니다.&lt;/p&gt;
&lt;p&gt;빌드 도구와 실행 런타임을 구분하고 GPU 드라이버와 런타임의 호환 조건도 별도로 확인해야 합니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="332-kubectl"&gt;3.3.2 kubectl&lt;span class="hx:absolute hx:-mt-20" id="332-kubectl"&gt;&lt;/span&gt;
&lt;a href="#332-kubectl" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;kubectl&lt;/code&gt;은 API server와 상호작용하는 명령행 도구입니다. context는 어느 클러스터, 인증 정보, 기본 Namespace 조합에 접속할지 고르는 설정입니다. 먼저 대상이 맞는지 확인하고 상태, 이벤트, 로그를 순서대로 보는 것이 안전합니다.&lt;/p&gt;
&lt;p&gt;상태를 확인할 때 사용하는 명령어. &lt;code&gt;&amp;lt;namespace&amp;gt;&lt;/code&gt;와 &lt;code&gt;&amp;lt;pod-name&amp;gt;&lt;/code&gt;은 대상 환경에 맞게 바꿉니다.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl config current-context
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get nodes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get pods -n &amp;lt;namespace&amp;gt; -o wide
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl describe pod &amp;lt;pod-name&amp;gt; -n &amp;lt;namespace&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl logs &amp;lt;pod-name&amp;gt; -n &amp;lt;namespace&amp;gt; --previous
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get services,endpointslices -n &amp;lt;namespace&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;--previous&lt;/code&gt;는 이전에 종료된 컨테이너의 로그가 있을 때 사용합니다. 다중 컨테이너 Pod라면 대상 컨테이너도 지정해야 합니다. &lt;code&gt;Pending&lt;/code&gt;은 배치, 볼륨, 자원, &lt;code&gt;ImagePullBackOff&lt;/code&gt;는 이미지 경로, 접근, 반복 재시작은 프로세스, 설정, probe 문제 등으로 가설을 좁힙니다. EndpointSlice는 Service 뒤의 접속 대상과 준비 상태를 담는 자원입니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="333-kubernetes-객체"&gt;3.3.3 Kubernetes 객체&lt;span class="hx:absolute hx:-mt-20" id="333-kubernetes-객체"&gt;&lt;/span&gt;
&lt;a href="#333-kubernetes-%ea%b0%9d%ec%b2%b4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;객체&lt;/th&gt;
&lt;th&gt;쉬운 역할&lt;/th&gt;
&lt;th&gt;혼동하지 말아야 할 점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pod&lt;/td&gt;
&lt;td&gt;함께 실행되는 컨테이너의 최소 배치 단위&lt;/td&gt;
&lt;td&gt;영구적인 서버 주소나 데이터 보관함이 아님&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ReplicaSet&lt;/td&gt;
&lt;td&gt;정해진 수의 Pod 복제본 유지&lt;/td&gt;
&lt;td&gt;보통 Deployment를 통해 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;버전 교체와 복제본의 선언적 관리&lt;/td&gt;
&lt;td&gt;애플리케이션 상태를 선언하는 것이지 모델 품질을 보장하는 것이 아님&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src="https://johklo.github.io/images/ml-platform-engineering/figure-3-05.png?v=2" alt="Deployment가 ReplicaSet을 관리하고 ReplicaSet이 Pod의 수를 유지하며 Pod 안에서 컨테이너가 실행되는 포함 관계." loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;그림 3.5. Deployment, ReplicaSet, Pod와 Container의 관계. 출처: Machine Learning Platform Engineering, 3장.&lt;/p&gt;
&lt;p&gt;Deployment → ReplicaSet → Pod의 관리 관계를 이해하면 Pod를 수동으로 지워도 왜 다시 만들어지는지 설명할 수 있습니다. 선언 자체가 잘못되었다면 재시작을 반복해도 문제가 해결되지 않습니다.&lt;/p&gt;
&lt;p&gt;ReplicaSet은 원하는 수의 Pod를 유지하고 Deployment는 새 버전의 Pod 템플릿과 ReplicaSet을 관리합니다. 이미지를 바꾸면 기존 Pod의 프로세스를 그대로 고치는 것이 아니라 새 템플릿에 맞는 Pod를 만드는 흐름입니다. 그래서 서비스 주소를 특정 Pod에 직접 고정하지 않습니다.&lt;/p&gt;
&lt;p&gt;Pod 안의 컨테이너는 네트워크 공간을 공유합니다. 여러 컨테이너가 함께 있어야 하는 경우에 같은 Pod로 묶을 수 있지만, API 복제본 세 개를 같은 Pod 안에 넣는 방식으로 가용성을 확보하는 것은 아닙니다. 독립적으로 배치할 복제본은 별도 Pod로 다룹니다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/architecture/controller/" target="_blank" rel="noopener"&gt;Controller 문서&lt;/a&gt;의 핵심은 reconciliation, 즉 원하는 상태와 관측된 상태의 차이를 반복해서 줄이는 조정 루프입니다. 온도조절기가 설정 온도와 실제 온도를 비교하는 것에 가깝습니다.&lt;/p&gt;
&lt;div role="img" aria-label="다이어그램"&gt;
&lt;pre class="mermaid hx:mt-6"&gt;
flowchart TD
accTitle: 선언한 상태와 관측한 상태의 조정 루프
accDescr: 세 개의 Pod를 원하지만 두 개만 관측되면 Controller가 API를 통해 새 Pod를 요청한다. 스케줄러와 kubelet이 실행을 담당하고, 새 관측 결과로 다시 비교한다.
D[&amp;#34;원하는 상태, 복제본 3개&amp;#34;] --&amp;gt; C[&amp;#34;Controller, 차이 비교&amp;#34;]
O[&amp;#34;관측한 현재 상태&amp;lt;br/&amp;gt;예: Pod 2개&amp;#34;] --&amp;gt; C
C --&amp;gt; F{&amp;#34;차이가 있는가?&amp;#34;}
F --&amp;gt;|&amp;#34;부족함&amp;#34;| A[&amp;#34;API에 부족한 Pod 생성 요청&amp;#34;]
F --&amp;gt;|&amp;#34;일치&amp;#34;| O
A --&amp;gt; R[&amp;#34;Scheduler, kubelet, 런타임의 배치와 실행&amp;#34;]
R --&amp;gt; O
&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;실패한 Pod가 다른 노드로 그대로 이동하는 것이 아니라 새 UID의 Pod로 대체될 수 있습니다. 컨테이너 재시작, Pod 교체, 모델 버전 롤백은 서로 다른 복구입니다. &lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/" target="_blank" rel="noopener"&gt;Pod 생애주기&lt;/a&gt;에서 이 차이를 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;복제본이 여러 개여도 같은 노드나 저장소, 외부 서비스에 의존하면 공통 장애에 취약합니다. 복제본 수와 장애 격리는 다릅니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="334-네트워크와-service"&gt;3.3.4 네트워크와 Service&lt;span class="hx:absolute hx:-mt-20" id="334-네트워크와-service"&gt;&lt;/span&gt;
&lt;a href="#334-%eb%84%a4%ed%8a%b8%ec%9b%8c%ed%81%ac%ec%99%80-service" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Pod의 주소와 수명이 바뀌어도 안정적으로 접근하도록 Service를 사용합니다. selector는 label 조건으로 대상을 고르는 방식입니다. 대상 선택, Service의 &lt;code&gt;port&lt;/code&gt;, 실제 프로세스가 받는 &lt;code&gt;targetPort&lt;/code&gt;의 연결을 확인해야 합니다.&lt;/p&gt;
&lt;p&gt;예를 들어 Service가 &lt;code&gt;app: detector&lt;/code&gt;라는 label을 가진 Pod를 대상으로 삼고 &lt;code&gt;port: 80&lt;/code&gt;, &lt;code&gt;targetPort: 8083&lt;/code&gt;으로 설정되었다면, 내부 사용자는 Service의 80으로 요청하고 실제 앱은 Pod 안의 8083에서 받습니다. 앱이 8000에서 실행되고 있다면 Pod가 Running이어도 요청은 실패할 수 있습니다.&lt;/p&gt;
&lt;p&gt;이때는 Service의 selector가 Pod label과 일치하는지, EndpointSlice에 대상이 있는지, 대상 포트가 앱과 맞는지 순서대로 확인합니다. Service 객체가 생성됐다는 사실만으로 요청 경로가 정상이라는 보장은 없습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;유형&lt;/th&gt;
&lt;th&gt;기본 사용 범위&lt;/th&gt;
&lt;th&gt;운영 시 확인할 것&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ClusterIP&lt;/td&gt;
&lt;td&gt;클러스터 내부의 안정적 접근&lt;/td&gt;
&lt;td&gt;DNS, selector, 준비된 대상 Pod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NodePort&lt;/td&gt;
&lt;td&gt;노드 포트를 통한 노출&lt;/td&gt;
&lt;td&gt;방화벽, 노드 접근, 노출 범위&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LoadBalancer&lt;/td&gt;
&lt;td&gt;제공자 또는 구현체의 외부 로드밸런서 연계&lt;/td&gt;
&lt;td&gt;지원 환경, 외부 주소, 비용, 접근 정책&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;port-forward&lt;/code&gt;는 개발, 점검에는 유용하지만 일반 사용자의 접속 경로는 아닙니다. Service가 있다고 인증, TLS, 외부 접근 정책까지 자동으로 완성되는 것도 아닙니다. TLS는 통신 구간을 보호하는 계층이며 사용자 권한 판정과 별개입니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="335-기타-객체"&gt;3.3.5 기타 객체&lt;span class="hx:absolute hx:-mt-20" id="335-기타-객체"&gt;&lt;/span&gt;
&lt;a href="#335-%ea%b8%b0%ed%83%80-%ea%b0%9d%ec%b2%b4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;객체&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;th&gt;운영상 주의&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Namespace&lt;/td&gt;
&lt;td&gt;이름과 자원의 관리 범위&lt;/td&gt;
&lt;td&gt;RBAC, 네트워크 정책, 쿼터가 별도로 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ConfigMap&lt;/td&gt;
&lt;td&gt;일반 설정을 코드와 분리&lt;/td&gt;
&lt;td&gt;변경이 실행 중 프로세스에 언제 반영되는지 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secret&lt;/td&gt;
&lt;td&gt;민감한 설정 전달&lt;/td&gt;
&lt;td&gt;인코딩을 암호화로 오해하지 않고 접근, 저장 보호&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;RBAC는 역할에 따라 API 자원 접근을 허용하는 권한 관리 방식입니다. 네트워크 정책은 워크로드 사이 통신을 제한하고, 쿼터는 자원 사용 범위를 제한합니다. Namespace 하나로 이 셋이 모두 해결되지는 않습니다.&lt;/p&gt;
&lt;p&gt;설정과 비밀을 분리해도 계정 권한, 로그 노출과 보존은 별도로 관리해야 합니다. 환경 변수로 주입한 설정은 객체만 바꾼다고 기존 프로세스의 값이 즉시 바뀌지는 않습니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="336-helm-차트"&gt;3.3.6 Helm 차트&lt;span class="hx:absolute hx:-mt-20" id="336-helm-차트"&gt;&lt;/span&gt;
&lt;a href="#336-helm-%ec%b0%a8%ed%8a%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;하나의 서비스에도 Deployment, Service와 설정 등 여러 객체가 필요합니다. Helm은 템플릿과 &lt;code&gt;values&lt;/code&gt;를 가진 차트로 이 조합을 패키징합니다.&lt;/p&gt;
&lt;p&gt;차트 버전, 환경별 값, 이미지, 모델 버전을 연결하고 실제 렌더링된 명세를 검토해야 합니다. 차트가 문법적으로 유효해도 잘못된 selector나 과도한 자원 요구 같은 의미적 오류는 남을 수 있습니다.&lt;/p&gt;
&lt;p&gt;템플릿은 공통 구조이고 values는 환경에 따라 바꿀 값입니다. 같은 앱을 개발과 운영에 배포하면서 복제본 수나 자원 요청만 바꾸는 경우에 유용합니다. 다만 환경별 값 안에 모델 참조나 전처리 설정이 섞여 있으면 차트 버전만 같아도 실제 서비스 동작은 달라질 수 있습니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="337-정리"&gt;3.3.7 정리&lt;span class="hx:absolute hx:-mt-20" id="337-정리"&gt;&lt;/span&gt;
&lt;a href="#337-%ec%a0%95%eb%a6%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Docker로 포장한 앱을 Kubernetes가 실행하고 Service로 연결하며 Helm으로 관련 선언을 관리할 수 있습니다. 배포를 여전히 사람이 한다면 반복, 실수, 권한 문제는 남습니다. 다음 절은 이 변경 경로의 자동화입니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="34-지속적-통합과-배포"&gt;3.4 지속적 통합과 배포&lt;span class="hx:absolute hx:-mt-20" id="34-지속적-통합과-배포"&gt;&lt;/span&gt;
&lt;a href="#34-%ec%a7%80%ec%86%8d%ec%a0%81-%ed%86%b5%ed%95%a9%ea%b3%bc-%eb%b0%b0%ed%8f%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;CI는 변경된 코드를 검사하고 이미지를 만드는 통합 과정입니다. CD는 검증한 변경을 전달, 배포하는 과정입니다. 자동 출시까지 할지는 별도 정책입니다. 빌드와 배포의 책임을 구분해야 어떤 검사를 통과한 버전이 운영에 있는지 설명할 수 있습니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="341-gitlab-ci"&gt;3.4.1 GitLab CI&lt;span class="hx:absolute hx:-mt-20" id="341-gitlab-ci"&gt;&lt;/span&gt;
&lt;a href="#341-gitlab-ci" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;책은 GitLab CI로 변경에 따른 작업을 정의합니다. 핵심은 UI보다 코드 커밋 → 검사 → 이미지 식별자 → 배포 설정 변경의 연결입니다.&lt;/p&gt;
&lt;p&gt;레지스트리 인증과 배포 권한은 CI의 승인된 비밀 저장소, 최소 권한 계정으로 관리해야 합니다. 테스트가 실패해도 운영 참조 갱신이 계속되지 않는지, 빌드한 것과 배포하는 것이 같은지 확인할 부분입니다.&lt;/p&gt;
&lt;p&gt;코드 커밋 A로 만든 이미지의 digest를 배포 설정에 기록하고 그 설정도 커밋하면, 실제 서비스에서 원래 코드까지 거슬러 올라갈 수 있습니다. 반대로 매번 같은 &lt;code&gt;latest&lt;/code&gt; 태그만 덮어쓰면 Git의 설정이 바뀌지 않았는데 실제 이미지 내용은 달라질 수 있습니다.&lt;/p&gt;
&lt;p&gt;CI가 테스트와 이미지를 만들었다면 CD는 그 결과를 운영 환경에 연결하는 역할입니다. 모든 CI 계정에 클러스터 관리자 권한을 주는 것과 Git의 배포 선언만 갱신하게 하는 것은 권한 범위가 다릅니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="342-argo-cd"&gt;3.4.2 Argo CD&lt;span class="hx:absolute hx:-mt-20" id="342-argo-cd"&gt;&lt;/span&gt;
&lt;a href="#342-argo-cd" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;GitOps는 Git에 버전 관리한 선언을 기준으로 실제 운영 상태를 맞추는 방식입니다. Argo CD가 Git의 선언과 클러스터 상태를 비교하는 흐름이 그 사례입니다.&lt;/p&gt;
&lt;p&gt;다만 차이가 있다고 항상 자동으로 덮어쓰지는 않습니다. &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/auto_sync/" target="_blank" rel="noopener"&gt;Argo CD 자동 동기화 정책&lt;/a&gt;에서 auto-sync, self-heal, prune은 서로 구분된 설정입니다. 새 선언을 자동 반영하는 것, 수동 변경을 되돌리는 것, Git에서 사라진 자원을 삭제하는 것은 같은 동작이 아닙니다.&lt;/p&gt;
&lt;p&gt;코드 커밋, 이미지 버전, 배포 설정 변경을 연결하면 “누가 서버에서 무엇을 바꿨나?” 대신 “어떤 변경이 배포되었나?”를 추적할 수 있습니다.&lt;/p&gt;
&lt;p&gt;긴급 수동 조치가 다음 동기화에서 덮어써질 수 있습니다. 긴급 변경을 Git에 어떻게 반영하고 승인하거나 되돌릴지 정해야 합니다. &lt;code&gt;Synced&lt;/code&gt;와 &lt;code&gt;Healthy&lt;/code&gt;는 선언과 자원 상태의 신호입니다. ML 품질을 승인했다는 뜻은 아닙니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Synced&lt;/code&gt;는 관측한 자원 명세가 Git의 선언과 맞는지를, &lt;code&gt;Healthy&lt;/code&gt;는 자원 종류별 상태 판단이 정상인지를 나타냅니다. 틀린 모델 버전을 Git에 적어 두어도 동기화는 성공할 수 있습니다. 따라서 배포 상태와 모델의 업무상 품질 검증은 분리해야 합니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="35-prometheus와-grafana"&gt;3.5 Prometheus와 Grafana&lt;span class="hx:absolute hx:-mt-20" id="35-prometheus와-grafana"&gt;&lt;/span&gt;
&lt;a href="#35-prometheus%ec%99%80-grafana" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Prometheus는 애플리케이션이 노출한 메트릭 엔드포인트에서 수치를 수집하고 저장합니다. 이 수집을 scrape라고 합니다. Grafana는 그 데이터를 질의, 시각화하고, Alertmanager는 알림을 그룹화, 라우팅합니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;메트릭 유형&lt;/th&gt;
&lt;th&gt;의미와 예시&lt;/th&gt;
&lt;th&gt;해석 주의&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Counter&lt;/td&gt;
&lt;td&gt;요청, 오류처럼 누적되는 횟수&lt;/td&gt;
&lt;td&gt;재시작에 따른 초기화를 고려해 변화율 해석&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gauge&lt;/td&gt;
&lt;td&gt;현재 메모리, 대기 작업처럼 오르내리는 값&lt;/td&gt;
&lt;td&gt;한 시점과 장기 추세를 구분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Histogram&lt;/td&gt;
&lt;td&gt;지연 등 관측값의 구간별 분포&lt;/td&gt;
&lt;td&gt;버킷 설계와 표본 수, 집계 방식 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Summary&lt;/td&gt;
&lt;td&gt;관측값의 개수, 합과 설정한 분위수&lt;/td&gt;
&lt;td&gt;인스턴스별 분위수를 단순 합산할 수 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;a href="https://prometheus.io/docs/concepts/metric_types/" target="_blank" rel="noopener"&gt;Prometheus 메트릭 유형 문서&lt;/a&gt;는 Counter, Gauge, Histogram, Summary를 구분합니다. 표의 버킷 설명은 classic histogram 기준입니다. Native histogram은 구조가 다르므로 계측 라이브러리와 서버 버전을 같이 확인해야 합니다.&lt;/p&gt;
&lt;p&gt;먼저 메트릭 노출, 수집 대상 발견, scrape 성공, 질의 결과를 순서대로 확인합니다. 대시보드가 비었다고 요청이 없었다고 단정하면 안 됩니다.&lt;/p&gt;
&lt;p&gt;요청 수 Counter의 현재 값이 10,000이라고 해도 그 자체로 지금 부하가 높다는 뜻은 아닙니다. 언제 시작한 프로세스의 누적값인지에 따라 다릅니다. 일정 구간의 증가율을 봐야 현재 초당 요청량을 알 수 있습니다.&lt;/p&gt;
&lt;p&gt;지연은 평균만 보면 일부 사용자의 느린 요청을 놓칠 수 있습니다. p95는 관측한 요청의 약 95%가 그 시간 이하였다는 뜻입니다. Histogram에서는 관측값이 구간에 모이므로 계산한 분위수는 버킷 경계와 표본에 영향을 받습니다. “p95가 낮다”는 결론에도 어떤 구간과 트래픽을 집계했는지가 필요합니다.&lt;/p&gt;
&lt;p&gt;요청 수, 오류율, 응답 시간, 자원 사용량이 정상인 것과 ML 예측이 맞는 것은 다릅니다. 데이터 변화와 실제 예측 품질은 별도 관측입니다.&lt;/p&gt;
&lt;p&gt;사용자 ID나 요청 ID를 메트릭 label로 무제한 추가하면 시계열 수가 폭증할 수 있습니다. 서로 다른 label 조합이 만드는 시계열 수가 카디널리티(cardinality)입니다. 개별 요청의 맥락은 로그와 트레이스로 나누고 알림에는 담당자와 대응 절차를 연결해야 합니다.&lt;/p&gt;
&lt;p&gt;label 설계는 &lt;a href="https://prometheus.io/docs/practices/naming/" target="_blank" rel="noopener"&gt;Prometheus 이름, label 지침&lt;/a&gt;을 참고했습니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="운영에서-확인할-부분"&gt;운영에서 확인할 부분&lt;span class="hx:absolute hx:-mt-20" id="운영에서-확인할-부분"&gt;&lt;/span&gt;
&lt;a href="#%ec%9a%b4%ec%98%81%ec%97%90%ec%84%9c-%ed%99%95%ec%9d%b8%ed%95%a0-%eb%b6%80%eb%b6%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;애플리케이션이 실행되는 것과 운영팀이 인수할 수 있는 상태는 다릅니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;영역&lt;/th&gt;
&lt;th&gt;설계할 것&lt;/th&gt;
&lt;th&gt;증거로 확인할 것&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;자원&lt;/td&gt;
&lt;td&gt;CPU, 메모리 requests/limits, GPU와 스케줄링&lt;/td&gt;
&lt;td&gt;대표 부하에서 사용량, 대기, OOM 여부&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시작과 상태&lt;/td&gt;
&lt;td&gt;초기화 시간, startup, readiness, liveness의 의미&lt;/td&gt;
&lt;td&gt;느린 모델 로딩 때 불필요한 재시작이 없는지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;권한과 네트워크&lt;/td&gt;
&lt;td&gt;최소 권한 ServiceAccount, RBAC, 통신 경계&lt;/td&gt;
&lt;td&gt;허용, 거부되어야 할 접근의 실제 결과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비밀과 이미지&lt;/td&gt;
&lt;td&gt;Secret 전달, 불변 이미지 식별, 취약점 점검&lt;/td&gt;
&lt;td&gt;이미지에 자격 증명이 없고 출처를 추적하는지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;배포와 복구&lt;/td&gt;
&lt;td&gt;변경 승인, 롤아웃, 이전 버전 복구&lt;/td&gt;
&lt;td&gt;배포 실패 때 트래픽, 모델 버전을 되돌리는지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Probe는 컨테이너 상태를 확인하는 검사입니다. startup은 초기화, readiness는 트래픽을 받을 준비, liveness는 실행 중 복구 필요 여부를 판단하는 데 사용합니다. requests/limits는 자원 요청, 상한이고, OOM은 메모리 부족으로 실행이 중단되는 경우입니다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/probes/" target="_blank" rel="noopener"&gt;Probe 공식 문서&lt;/a&gt;에서 확인할 차이는 실패 뒤의 행동입니다. readiness 실패는 일반적인 Service 트래픽에서 대상을 제외하는 신호이고, liveness, startup 실패는 설정된 조건과 재시작 정책에 따라 컨테이너 재시작으로 이어집니다.&lt;/p&gt;
&lt;p&gt;Pod 자동 재시작은 모델, 설정의 올바른 복구와 다릅니다. 복제본 수, 노드 분산, 외부 저장소와 레지스트리 장애도 함께 봐야 합니다. 비용에서도 GPU가 모델 로딩과 대기로 묶이는 시간, 이미지 전송과 유휴 자원을 확인할 필요가 있습니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="장애-확인-순서"&gt;장애 확인 순서&lt;span class="hx:absolute hx:-mt-20" id="장애-확인-순서"&gt;&lt;/span&gt;
&lt;a href="#%ec%9e%a5%ec%95%a0-%ed%99%95%ec%9d%b8-%ec%88%9c%ec%84%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;테스트 환경에서 Deployment가 관리하는 Pod를 삭제하면 새 Pod가 만들어지는 과정을 볼 수 있습니다. 기존 Pod의 재시작과 새 Pod의 생성을 구분해서 확인할 부분입니다.&lt;/p&gt;
&lt;p&gt;“이미지 주소가 틀림 → Pod가 뜨지 않음 → Service에 정상 대상이 없음”처럼 증상이 이어집니다. 이미지 가져오기와 프로세스 기동, Service 연결을 나눠 보면 원인을 좁히기 쉽습니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="질문"&gt;질문&lt;span class="hx:absolute hx:-mt-20" id="질문"&gt;&lt;/span&gt;
&lt;a href="#%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="컨테이너-이미지가-같으면-모델-결과도-같을까요"&gt;컨테이너 이미지가 같으면 모델 결과도 같을까요?&lt;span class="hx:absolute hx:-mt-20" id="컨테이너-이미지가-같으면-모델-결과도-같을까요"&gt;&lt;/span&gt;
&lt;a href="#%ec%bb%a8%ed%85%8c%ec%9d%b4%eb%84%88-%ec%9d%b4%eb%af%b8%ec%a7%80%ea%b0%80-%ea%b0%99%ec%9c%bc%eb%a9%b4-%eb%aa%a8%eb%8d%b8-%ea%b2%b0%ea%b3%bc%eb%8f%84-%ea%b0%99%ec%9d%84%ea%b9%8c%ec%9a%94" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;데이터, 외부 피처, 모델 파일, 런타임 설정까지 같다는 보장은 없습니다. 이미지 재현성과 ML 실험 재현성을 나누어 봐야 합니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="모든-팀이-kubernetes부터-도입해야-할까요"&gt;모든 팀이 Kubernetes부터 도입해야 할까요?&lt;span class="hx:absolute hx:-mt-20" id="모든-팀이-kubernetes부터-도입해야-할까요"&gt;&lt;/span&gt;
&lt;a href="#%eb%aa%a8%eb%93%a0-%ed%8c%80%ec%9d%b4-kubernetes%eb%b6%80%ed%84%b0-%eb%8f%84%ec%9e%85%ed%95%b4%ec%95%bc-%ed%95%a0%ea%b9%8c%ec%9a%94" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;여러 워크로드의 공통 운영이 필요한지와 클러스터 유지 비용을 비교해야 합니다. 책에서는 뒤쪽 플랫폼 기능을 연결하는 기반으로 선택한 것입니다.&lt;/p&gt;
&lt;p&gt;복제본 수를 늘려도 모델 초기화, GPU와 스토리지 제약, 잘못된 입력은 해결되지 않습니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="마치며"&gt;마치며&lt;span class="hx:absolute hx:-mt-20" id="마치며"&gt;&lt;/span&gt;
&lt;a href="#%eb%a7%88%ec%b9%98%eb%a9%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;컨테이너와 Kubernetes가 모델의 품질까지 판단해 주는 것은 아닙니다. 지정한 이미지를 실행하고 선언한 상태를 유지하는 역할입니다. 잘못된 모델을 선언해도 배포 자체는 정상으로 보일 수 있습니다.&lt;/p&gt;
&lt;p&gt;결국 배포 상태와 모델의 평가 결과를 따로 확인하고 연결해야 합니다. 이후 ML 도구를 붙일 때도 기존 실행 기반과 모델 고유의 검증이 어디서 만나는지 살펴볼 필요가 있겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href="../02-what-is-mlops/"&gt;이전: 02. MLOps란 무엇인가&lt;/a&gt;, &lt;a href="../"&gt;시리즈 목차&lt;/a&gt;&lt;/p&gt;</description></item><item><title>04. 리서치 요약 엔진</title><link>https://johklo.github.io/docs/tech/ai-agents-and-applications/04-research-engine/</link><pubDate>Mon, 19 Oct 2026 21:37:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/ai-agents-and-applications/04-research-engine/</guid><description>
&lt;p&gt;리서치 엔진은 질문을 받아 검색하고, 자료를 읽고, 요약을 모아 보고서를 만든다. 모델이 인터넷을 안다고 가정하는 것이 아니라 검색과 수집을 실제 코드로 수행하고 그 결과를 모델에 전달하는 구조다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="41-리서치-요약-엔진의-구성"&gt;4.1 리서치 요약 엔진의 구성&lt;span class="hx:absolute hx:-mt-20" id="41-리서치-요약-엔진의-구성"&gt;&lt;/span&gt;
&lt;a href="#41-%eb%a6%ac%ec%84%9c%ec%b9%98-%ec%9a%94%ec%95%bd-%ec%97%94%ec%a7%84%ec%9d%98-%ea%b5%ac%ec%84%b1" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;작업을 나누면 검색 질의 만들기, 검색 결과 URL 수집, 웹 본문 추출, 페이지별 요약, 최종 보고서 작성이다. 질문에서 곧바로 완성된 보고서를 생성하는 것과 달리 중간 근거가 남는다.&lt;/p&gt;
&lt;div role="img" aria-label="다이어그램"&gt;
&lt;pre class="mermaid hx:mt-6"&gt;
flowchart TD
accTitle: 웹 리서치 엔진의 자료 흐름
accDescr: 사용자 질문에서 여러 검색 질의를 만들고 URL을 찾는다. 각 URL에서 본문과 출처를 모아 요약하고 이를 최종 보고서에 통합한다.
Q[&amp;#34;사용자 질문&amp;#34;] --&amp;gt; I[&amp;#34;조사 관점과 지침&amp;#34;]
I --&amp;gt; W[&amp;#34;검색 질의 생성&amp;#34;]
W --&amp;gt; S[&amp;#34;웹 검색&amp;#34;]
S --&amp;gt; U[&amp;#34;URL 목록&amp;#34;]
U --&amp;gt; F[&amp;#34;본문 수집&amp;#34;]
F --&amp;gt; M[&amp;#34;본문별 요약과 출처&amp;#34;]
M --&amp;gt; R[&amp;#34;보고서 통합&amp;#34;]
&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;검색 결과의 제목과 짧은 설명은 원문 전체가 아니다. URL을 얻는 단계와 본문을 읽는 단계를 분리해야 한다. 최종 보고서에서도 수집한 사실과 모델이 덧붙인 해석을 구분할 수 있어야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="42-프로젝트-설정"&gt;4.2 프로젝트 설정&lt;span class="hx:absolute hx:-mt-20" id="42-프로젝트-설정"&gt;&lt;/span&gt;
&lt;a href="#42-%ed%94%84%eb%a1%9c%ec%a0%9d%ed%8a%b8-%ec%84%a4%ec%a0%95" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;가상환경과 의존성, 실행 인터프리터를 프로젝트 단위로 정한다. 노트북의 셀 상태에 의존하는 코드에서 실행 파일로 옮기면 환경 변수와 초기화 순서가 더 명확해야 한다.&lt;/p&gt;
&lt;p&gt;검색과 스크래핑, 모델 생성, 프롬프트를 각각 나누면 개별 기능을 독립적으로 확인할 수 있다. 예를 들어 웹 수집이 실패했을 때 모델 호출까지 실행할 필요 없이 수집 함수만 재현할 수 있다.&lt;/p&gt;
&lt;p&gt;API 키는 환경 변수 등으로 전달하고 설정 파일을 버전 관리에 넣을 때 비밀이 포함되지 않게 한다. 실행 결과를 비교하려면 모델과 패키지 버전, 검색과 요약의 제한값도 남겨야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="43-핵심-기능"&gt;4.3 핵심 기능&lt;span class="hx:absolute hx:-mt-20" id="43-핵심-기능"&gt;&lt;/span&gt;
&lt;a href="#43-%ed%95%b5%ec%8b%ac-%ea%b8%b0%eb%8a%a5" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="431-웹-검색"&gt;4.3.1 웹 검색&lt;span class="hx:absolute hx:-mt-20" id="431-웹-검색"&gt;&lt;/span&gt;
&lt;a href="#431-%ec%9b%b9-%ea%b2%80%ec%83%89" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;검색 함수는 질의를 받아 관련 결과의 URL 목록을 반환한다. 책은 DuckDuckGo 검색 래퍼를 사용하지만 다른 검색 서비스로 바꿀 수도 있다.&lt;/p&gt;
&lt;p&gt;서비스를 바꾸면 반환 필드와 검색 결과의 품질, 제한과 인증 방식도 달라진다. URL을 공통 구조로 정규화해 두면 이후 수집 코드가 검색 제공자에 덜 의존한다.&lt;/p&gt;
&lt;p&gt;검색에 성공했다고 유효한 자료를 확보한 것은 아니다. 중복 URL, 접근할 수 없는 페이지, 광고성 페이지가 섞일 수 있다. 검색 결과 수와 최종 근거 수는 구분해서 봐야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="432-웹-스크래핑"&gt;4.3.2 웹 스크래핑&lt;span class="hx:absolute hx:-mt-20" id="432-웹-스크래핑"&gt;&lt;/span&gt;
&lt;a href="#432-%ec%9b%b9-%ec%8a%a4%ed%81%ac%eb%9e%98%ed%95%91" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;스크래핑은 HTML에서 처리할 본문을 꺼내는 과정이다. Beautiful Soup 같은 파서는 문서 구조를 읽지만 어느 영역이 유용한 기사 본문인지 자동으로 보장하지는 않는다.&lt;/p&gt;
&lt;p&gt;메뉴와 푸터, 쿠키 안내가 많이 섞이면 요약 토큰을 낭비하고 중요한 내용이 잘릴 수 있다. JavaScript로 본문을 불러오는 페이지에서는 최초 HTML에 필요한 내용이 없을 수도 있다.&lt;/p&gt;
&lt;p&gt;시간 제한과 HTTP 상태, 빈 본문을 명시적으로 다뤄야 한다. 수집 실패를 빈 문자열로 숨긴 뒤 그럴듯한 보고서를 만들면 근거가 없는 상태를 알아차리기 어렵다.&lt;/p&gt;
&lt;p&gt;수집 범위와 이용 조건도 확인해야 한다. URL, 페이지 제목과 수집 시점을 남기면 원문 변경과 출처를 추적할 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="433-llm-클라이언트"&gt;4.3.3 LLM 클라이언트&lt;span class="hx:absolute hx:-mt-20" id="433-llm-클라이언트"&gt;&lt;/span&gt;
&lt;a href="#433-llm-%ed%81%b4%eb%9d%bc%ec%9d%b4%ec%96%b8%ed%8a%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;모델 초기화를 한곳에 모으면 생성 설정을 관리하기 쉽다. 검색 질의 생성과 페이지 요약, 보고서 작성에 같은 모델을 쓸 수도 있지만 필요한 성능과 비용이 다를 수 있다.&lt;/p&gt;
&lt;p&gt;클라이언트를 만들었다는 사실은 키와 모델, 할당량이 유효하다는 보장이 아니다. 인증 오류와 요청 제한, 출력 제한을 각각 확인해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="434-json-응답-변환"&gt;4.3.4 JSON 응답 변환&lt;span class="hx:absolute hx:-mt-20" id="434-json-응답-변환"&gt;&lt;/span&gt;
&lt;a href="#434-json-%ec%9d%91%eb%8b%b5-%eb%b3%80%ed%99%98" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;검색 질의 목록처럼 다음 코드가 순회할 값은 구조화된 출력이 필요하다. 문자열 JSON을 Python 객체로 바꾼 뒤 예상한 필드와 타입을 검증한다.&lt;/p&gt;
&lt;p&gt;책의 단순 변환 함수는 잘못된 JSON을 빈 사전으로 처리하는 예를 보인다. 실제 흐름에서 이 방식은 “검색할 항목이 없었다”와 “모델 출력 해석에 실패했다”를 섞을 수 있다. 문법 오류와 스키마 오류를 구분해 실패를 보고하는 편이 낫다.&lt;/p&gt;
&lt;p&gt;유효한 JSON이어도 질의가 원래 질문과 무관할 수 있다. 형식 검증과 조사 목적의 일치는 별도의 문제다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="44-검색-질의-재작성"&gt;4.4 검색 질의 재작성&lt;span class="hx:absolute hx:-mt-20" id="44-검색-질의-재작성"&gt;&lt;/span&gt;
&lt;a href="#44-%ea%b2%80%ec%83%89-%ec%a7%88%ec%9d%98-%ec%9e%ac%ec%9e%91%ec%84%b1" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;사용자의 자연어 요청을 그대로 검색어로 쓰는 대신 조사할 관점을 나눠 여러 질의를 만든다. 관광지 질문이라면 역사, 교통, 주요 명소처럼 나눌 수 있다.&lt;/p&gt;
&lt;p&gt;검색 범위를 넓힐 수 있지만 관점을 잘못 나누면 질문의 의도를 바꾸거나 중요한 조건을 잃을 수 있다. 사용자 질문을 중간 상태에 계속 남겨야 최종 작성에서 원래 목적을 다시 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;질의 수를 늘리면 검색 호출과 URL 수, 요약 호출이 함께 늘어난다. 같은 자료를 여러 질의가 반환할 수 있으므로 중복 제거도 중요하다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="45-단계별-프롬프트"&gt;4.5 단계별 프롬프트&lt;span class="hx:absolute hx:-mt-20" id="45-단계별-프롬프트"&gt;&lt;/span&gt;
&lt;a href="#45-%eb%8b%a8%ea%b3%84%eb%b3%84-%ed%94%84%eb%a1%ac%ed%94%84%ed%8a%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="451-검색-프롬프트"&gt;4.5.1 검색 프롬프트&lt;span class="hx:absolute hx:-mt-20" id="451-검색-프롬프트"&gt;&lt;/span&gt;
&lt;a href="#451-%ea%b2%80%ec%83%89-%ed%94%84%eb%a1%ac%ed%94%84%ed%8a%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;책은 먼저 질문에 맞는 조사 관점과 지침을 만들고, 그 지침으로 검색 질의를 생성한다. 질문, 조사 목적과 출력 질의 목록이 이어지는 구조다.&lt;/p&gt;
&lt;p&gt;역할을 동적으로 고르는 것이 항상 필요한 것은 아니다. 특정 업무만 다루는 서비스라면 고정된 지침이 더 단순할 수 있다. 동적 역할이 사실의 신뢰도를 높여 주는 것도 아니다.&lt;/p&gt;
&lt;p&gt;검색 질의에는 대상과 필요한 조건이 들어가야 한다. 특정 지역이나 시점이 빠지면 같은 단어의 다른 의미나 오래된 정보가 섞일 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="452-페이지-요약-프롬프트"&gt;4.5.2 페이지 요약 프롬프트&lt;span class="hx:absolute hx:-mt-20" id="452-페이지-요약-프롬프트"&gt;&lt;/span&gt;
&lt;a href="#452-%ed%8e%98%ec%9d%b4%ec%a7%80-%ec%9a%94%ec%95%bd-%ed%94%84%eb%a1%ac%ed%94%84%ed%8a%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;페이지 요약은 일반적인 원문 요약보다 현재 조사 질문과 관련된 사실을 뽑는 데 초점을 둔다. 관계없는 배경을 길게 남기면 최종 보고서의 문맥 예산을 낭비한다.&lt;/p&gt;
&lt;p&gt;요약을 저장할 때 원래 URL과 어떤 질의로 찾았는지도 함께 보존해야 한다. 요약문만 이어 붙이면 최종 작성 단계에서 개별 사실의 출처를 복원하기 어렵다.&lt;/p&gt;
&lt;p&gt;원문에 없는 판단과 수치를 보충하지 않도록 해야 한다. 자료 안에 포함된 명령문은 조사할 데이터이지 서비스 지침이 아니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="453-보고서-프롬프트"&gt;4.5.3 보고서 프롬프트&lt;span class="hx:absolute hx:-mt-20" id="453-보고서-프롬프트"&gt;&lt;/span&gt;
&lt;a href="#453-%eb%b3%b4%ea%b3%a0%ec%84%9c-%ed%94%84%eb%a1%ac%ed%94%84%ed%8a%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;보고서 생성은 부분 요약에서 반복을 줄이고 공통 내용과 차이를 종합하는 단계다. 모든 요약이 서로 같은 사실을 말한다고 가정해서는 안 된다.&lt;/p&gt;
&lt;p&gt;서로 다른 날짜의 자료나 상충하는 수치가 있다면 그 차이를 남겨야 한다. 자료가 없는 항목은 빈틈으로 드러내는 편이 근거 없는 문장으로 채우는 것보다 낫다.&lt;/p&gt;
&lt;p&gt;URL을 문장 옆에 붙였다는 사실만으로 인용이 정확해지는 것은 아니다. 해당 페이지가 그 주장을 실제로 뒷받침하는지 확인해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="46-순차-구현"&gt;4.6 순차 구현&lt;span class="hx:absolute hx:-mt-20" id="46-순차-구현"&gt;&lt;/span&gt;
&lt;a href="#46-%ec%88%9c%ec%b0%a8-%ea%b5%ac%ed%98%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="461-함수와-프롬프트-연결"&gt;4.6.1 함수와 프롬프트 연결&lt;span class="hx:absolute hx:-mt-20" id="461-함수와-프롬프트-연결"&gt;&lt;/span&gt;
&lt;a href="#461-%ed%95%a8%ec%88%98%ec%99%80-%ed%94%84%eb%a1%ac%ed%94%84%ed%8a%b8-%ec%97%b0%ea%b2%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;검색, 본문 수집, 모델 초기화, 출력 해석 함수를 준비하고 각 단계가 어떤 값을 주고받는지 정한다. 이 단계에서 공통 구조가 없으면 단계마다 필드명을 바꾸는 접착 코드가 늘어난다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="462-설정값과-입력"&gt;4.6.2 설정값과 입력&lt;span class="hx:absolute hx:-mt-20" id="462-설정값과-입력"&gt;&lt;/span&gt;
&lt;a href="#462-%ec%84%a4%ec%a0%95%ea%b0%92%ea%b3%bc-%ec%9e%85%eb%a0%a5" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;검색 질의 수, 질의당 결과 수, 본문 길이 제한을 설정으로 둔다. 사용자의 질문은 이 제한값과 달리 실행마다 들어오는 입력이다.&lt;/p&gt;
&lt;p&gt;본문을 문자 수로 자르는 것은 토큰 제한과 같지 않다. 중요한 내용이 문서 뒤에 있다면 앞부분만 자르는 방식으로 핵심 근거를 잃을 수 있다.&lt;/p&gt;
&lt;p&gt;예를 들어 질의 2개에 결과를 각각 3개씩 수집하면 중복이 없을 때 최대 6개 URL을 요약한다. 역할 선택 1회, 질의 생성 1회, 페이지 요약 6회, 보고서 작성 1회라면 모델 호출은 최소 9회다. 웹 검색과 본문 요청은 이와 별도의 외부 호출이다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="463-모델-초기화"&gt;4.6.3 모델 초기화&lt;span class="hx:absolute hx:-mt-20" id="463-모델-초기화"&gt;&lt;/span&gt;
&lt;a href="#463-%eb%aa%a8%eb%8d%b8-%ec%b4%88%ea%b8%b0%ed%99%94" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;선택한 모델과 생성 설정을 공통 클라이언트에 적용한다. 출력 길이와 비용을 제한하더라도 요약이 중간에서 끊겼는지 확인해야 한다. 짧게 잘린 출력은 간결한 요약과 다르다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="464-검색-질의-생성과-url-수집"&gt;4.6.4 검색 질의 생성과 URL 수집&lt;span class="hx:absolute hx:-mt-20" id="464-검색-질의-생성과-url-수집"&gt;&lt;/span&gt;
&lt;a href="#464-%ea%b2%80%ec%83%89-%ec%a7%88%ec%9d%98-%ec%83%9d%ec%84%b1%ea%b3%bc-url-%ec%88%98%ec%a7%91" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;사용자 질문으로 조사 지침을 만들고 검색 질의를 생성한다. 각 질의를 검색 서비스에 보내 결과 URL을 모은다.&lt;/p&gt;
&lt;p&gt;질의가 달라도 같은 URL이 나올 수 있다. URL뿐 아니라 질의와 결과의 관계를 남겨야 어느 관점에서 자료가 충분하거나 부족한지 알 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="465-본문-수집"&gt;4.6.5 본문 수집&lt;span class="hx:absolute hx:-mt-20" id="465-본문-수집"&gt;&lt;/span&gt;
&lt;a href="#465-%eb%b3%b8%eb%ac%b8-%ec%88%98%ec%a7%91" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;URL에서 본문을 추출하고 길이를 제한한다. 수집 결과에는 본문, URL, 질의를 함께 둔다.&lt;/p&gt;
&lt;p&gt;일부 URL만 실패했다면 전체를 중단할지, 남은 자료로 계속하되 부족함을 표시할지 정책이 필요하다. 실패한 자료가 정상적인 빈 문서처럼 섞이지 않아야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="466-자료별-요약"&gt;4.6.6 자료별 요약&lt;span class="hx:absolute hx:-mt-20" id="466-자료별-요약"&gt;&lt;/span&gt;
&lt;a href="#466-%ec%9e%90%eb%a3%8c%eb%b3%84-%ec%9a%94%ec%95%bd" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;각 페이지를 조사 질문에 맞게 요약하고 출처를 연결한다. 페이지별 요약이 이미 틀렸다면 최종 통합도 그 오류를 이어받을 수 있다.&lt;/p&gt;
&lt;p&gt;요약 전의 본문과 요약 후의 결과를 연결해 두면 누락과 왜곡을 확인하기 쉽다. 저장 비용과 민감한 자료의 보존 범위는 별도로 정해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="467-최종-보고서"&gt;4.6.7 최종 보고서&lt;span class="hx:absolute hx:-mt-20" id="467-최종-보고서"&gt;&lt;/span&gt;
&lt;a href="#467-%ec%b5%9c%ec%a2%85-%eb%b3%b4%ea%b3%a0%ec%84%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;모은 요약으로 보고서를 작성한다. 입력은 더 이상 웹페이지 전체가 아니라 앞 단계가 선택한 정보다.&lt;/p&gt;
&lt;p&gt;따라서 보고서에서 빠진 내용의 원인이 마지막 모델 호출에만 있는 것은 아니다. 질의 생성, 검색 순위, 본문 추출, 길이 제한과 부분 요약 중 어디서 사라졌는지 거슬러 올라가야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="47-lcel로-구성하기"&gt;4.7 LCEL로 구성하기&lt;span class="hx:absolute hx:-mt-20" id="47-lcel로-구성하기"&gt;&lt;/span&gt;
&lt;a href="#47-lcel%eb%a1%9c-%ea%b5%ac%ec%84%b1%ed%95%98%ea%b8%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;LCEL은 실행 단위를 연결하는 방식이다. 순차 구현에서 중간 변수로 전달하던 값을 Runnable의 입력과 출력으로 표현한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="471-assistant-instructions-체인"&gt;4.7.1 Assistant Instructions 체인&lt;span class="hx:absolute hx:-mt-20" id="471-assistant-instructions-체인"&gt;&lt;/span&gt;
&lt;a href="#471-assistant-instructions-%ec%b2%b4%ec%9d%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;질문에서 조사 지침을 만드는 작은 체인이다. 출력에는 이후 단계가 사용할 지침과 원래 질문이 필요하다.&lt;/p&gt;
&lt;p&gt;각 단계를 나눈다는 것은 모델 호출을 잘게 늘리는 것과 다르다. 실제로 독립적인 판단이 필요한지, 고정 설정으로 충분한지를 먼저 봐야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="472-web-searches-체인"&gt;4.7.2 Web Searches 체인&lt;span class="hx:absolute hx:-mt-20" id="472-web-searches-체인"&gt;&lt;/span&gt;
&lt;a href="#472-web-searches-%ec%b2%b4%ec%9d%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;지침과 사용자 질문을 받아 검색 질의 목록을 생성한다. 출력이 다음 반복 처리의 입력이므로 목록의 구조를 명확히 해야 한다.&lt;/p&gt;
&lt;p&gt;이 단계에서 원래 질문을 버리면 뒤에서 보고서 프롬프트를 조립할 때 문맥을 다시 전달해야 한다. 필요한 상태를 어떤 체인에서 보존할지 정하는 문제다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="473-search-and-summarization-체인"&gt;4.7.3 Search and Summarization 체인&lt;span class="hx:absolute hx:-mt-20" id="473-search-and-summarization-체인"&gt;&lt;/span&gt;
&lt;a href="#473-search-and-summarization-%ec%b2%b4%ec%9d%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;하나의 검색 질의에서 URL을 얻고 각 페이지를 수집하고 요약하는 하위 체인이다. URL별 작업은 서로 독립적일 수 있으므로 &lt;code&gt;.map()&lt;/code&gt; 등으로 같은 처리를 적용할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RunnableParallel&lt;/code&gt;은 같은 입력을 여러 분기로 보내고 결과를 이름별로 모으는 데 쓴다. &lt;code&gt;.map()&lt;/code&gt;이 목록의 각 원소에 같은 처리를 적용하는 것과 구분해야 한다.&lt;/p&gt;
&lt;p&gt;병렬화는 독립 작업의 대기 시간을 줄일 수 있지만 호출 수와 토큰을 없애지는 않는다. 동시 호출 수를 제한하고 어떤 하위 작업이 실패했는지 식별할 수 있어야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="474-web-research-체인"&gt;4.7.4 Web Research 체인&lt;span class="hx:absolute hx:-mt-20" id="474-web-research-체인"&gt;&lt;/span&gt;
&lt;a href="#474-web-research-%ec%b2%b4%ec%9d%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;검색 질의 생성, 질의별 검색과 요약, 최종 통합을 하나로 연결한다. 사용자는 질문 하나를 보내지만 내부에서는 여러 종류의 I/O와 모델 호출이 발생한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;실행 위치&lt;/th&gt;
&lt;th&gt;확인할 입력과 결과&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;질문 해석&lt;/td&gt;
&lt;td&gt;원래 질문과 생성한 조사 지침&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검색&lt;/td&gt;
&lt;td&gt;질의 목록과 URL, 중복과 실패&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;수집&lt;/td&gt;
&lt;td&gt;원문 길이와 실제 추출된 내용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;요약&lt;/td&gt;
&lt;td&gt;원문과 부분 요약의 대응&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;통합&lt;/td&gt;
&lt;td&gt;어떤 출처가 어떤 주장을 뒷받침하는지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;모듈로 나눈 장점은 재사용뿐 아니라 실패 위치를 좁힐 수 있다는 것이다. 다만 체인만으로는 증거가 부족할 때 어떤 단계로 돌아갈지 표현하기 번거로울 수 있다. 다음 장의 그래프가 이 제어 흐름을 다룬다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="질문"&gt;질문&lt;span class="hx:absolute hx:-mt-20" id="질문"&gt;&lt;/span&gt;
&lt;a href="#%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="병렬화하면-리서치-비용도-줄어들까"&gt;병렬화하면 리서치 비용도 줄어들까?&lt;span class="hx:absolute hx:-mt-20" id="병렬화하면-리서치-비용도-줄어들까"&gt;&lt;/span&gt;
&lt;a href="#%eb%b3%91%eb%a0%ac%ed%99%94%ed%95%98%eb%a9%b4-%eb%a6%ac%ec%84%9c%ec%b9%98-%eb%b9%84%ec%9a%a9%eb%8f%84-%ec%a4%84%ec%96%b4%eb%93%a4%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;대기 시간은 줄어들 수 있지만 요청 수와 토큰 사용량은 그대로이거나 재시도로 늘 수 있다. 시간과 비용은 따로 측정해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="보고서에-링크가-많으면-신뢰할-수-있을까"&gt;보고서에 링크가 많으면 신뢰할 수 있을까?&lt;span class="hx:absolute hx:-mt-20" id="보고서에-링크가-많으면-신뢰할-수-있을까"&gt;&lt;/span&gt;
&lt;a href="#%eb%b3%b4%ea%b3%a0%ec%84%9c%ec%97%90-%eb%a7%81%ed%81%ac%ea%b0%80-%eb%a7%8e%ec%9c%bc%eb%a9%b4-%ec%8b%a0%eb%a2%b0%ed%95%a0-%ec%88%98-%ec%9e%88%ec%9d%84%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;링크 수보다 주장과 근거의 대응이 중요하다. 같은 내용의 중복 자료나 실제 주장과 무관한 인용도 있을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;a href="../03-summarization/"&gt;이전: 문서 요약&lt;/a&gt; / &lt;a href="../"&gt;시리즈 목차&lt;/a&gt; / &lt;a href="../05-langgraph-workflows/"&gt;다음: LangGraph 워크플로&lt;/a&gt;&lt;/p&gt;</description></item><item><title>05. LangGraph 워크플로</title><link>https://johklo.github.io/docs/tech/ai-agents-and-applications/05-langgraph-workflows/</link><pubDate>Mon, 19 Oct 2026 21:37:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/ai-agents-and-applications/05-langgraph-workflows/</guid><description>
&lt;p&gt;앞 장의 리서치 엔진은 질문에서 보고서까지 정해진 순서로 진행했다. 검색 결과가 부실해도 다음 단계로 넘어간다면 자동화된 보고서가 자동화된 오류가 될 수 있다. 이번 장은 중간 결과에 따라 다시 검색하거나 작업을 끝내는 경로를 명시한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="51-워크플로와-에이전트"&gt;5.1 워크플로와 에이전트&lt;span class="hx:absolute hx:-mt-20" id="51-워크플로와-에이전트"&gt;&lt;/span&gt;
&lt;a href="#51-%ec%9b%8c%ed%81%ac%ed%94%8c%eb%a1%9c%ec%99%80-%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;두 구조 모두 LLM을 사용하지만 다음 행동을 결정하는 범위가 다르다. 워크플로는 개발자가 정한 경로 안에서 작업을 수행하고, 에이전트는 목표와 현재 결과를 보고 어떤 도구를 다음에 사용할지 더 넓게 선택한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="511-워크플로"&gt;5.1.1 워크플로&lt;span class="hx:absolute hx:-mt-20" id="511-워크플로"&gt;&lt;/span&gt;
&lt;a href="#511-%ec%9b%8c%ed%81%ac%ed%94%8c%eb%a1%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;워크플로는 고정된 순서만 뜻하지 않는다. 미리 정의한 분기와 반복도 포함할 수 있다. 분류 결과가 A이면 요약하고 B이면 검색하는 구조, 평가가 부족하면 재시도하는 구조도 워크플로다.&lt;/p&gt;
&lt;p&gt;Controller-Worker에서는 제어 역할이 작업을 나누고 작업자에게 맡긴다. Router는 요청을 보고 적합한 처리 경로를 고른다. 경로 선택에 LLM을 쓸 수 있지만, 선택할 수 있는 경로 자체는 프로그램에 정의되어 있다.&lt;/p&gt;
&lt;p&gt;예를 들어 “자료가 부족하면 질의 재작성, 충분하면 보고서 작성”이라는 두 경로가 있다면 모델은 현재 자료의 관련성을 평가할 수 있다. 그러나 실제로 어느 노드를 실행할지와 허용되는 반복 수는 그래프의 제어 규칙으로 제한할 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="512-에이전트"&gt;5.1.2 에이전트&lt;span class="hx:absolute hx:-mt-20" id="512-에이전트"&gt;&lt;/span&gt;
&lt;a href="#512-%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;에이전트에서는 모델이 도구 목록과 현재 상황을 보고 다음 행동을 고른다. 검색 결과로 장소를 알아낸 뒤 그 장소의 날씨를 조회하는 것처럼, 앞 작업의 결과가 뒤 작업의 입력을 결정할 수 있다.&lt;/p&gt;
&lt;p&gt;유연성은 늘지만 실행 횟수와 실패 경로도 늘어난다. 잘못된 질의를 반복하거나 이미 확보한 정보를 다시 검색할 수 있다. 자율적인 판단과 무제한 실행을 같은 것으로 보면 안 된다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="513-언제-필요한가"&gt;5.1.3 언제 필요한가&lt;span class="hx:absolute hx:-mt-20" id="513-언제-필요한가"&gt;&lt;/span&gt;
&lt;a href="#513-%ec%96%b8%ec%a0%9c-%ed%95%84%ec%9a%94%ed%95%9c%ea%b0%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;작업이 한 번의 호출로 충분하면 그래프를 도입해도 이점이 작다. 조건부 분기, 재실행, 명시적인 상태, 긴 작업의 추적이 필요할 때 가치가 커진다.&lt;/p&gt;
&lt;p&gt;여기서 비교할 대상은 “프레임워크가 있는 코드와 없는 코드”다. 일반 Python으로도 상태와 분기를 구현할 수 있다. LangGraph의 장점은 그 실행 구조를 공통 모델로 표현하고 상태 저장이나 추적 같은 기능과 연결하기 쉽다는 점이다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="514-에이전트-프레임워크"&gt;5.1.4 에이전트 프레임워크&lt;span class="hx:absolute hx:-mt-20" id="514-에이전트-프레임워크"&gt;&lt;/span&gt;
&lt;a href="#514-%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8-%ed%94%84%eb%a0%88%ec%9e%84%ec%9b%8c%ed%81%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;책은 LangGraph 외에도 CrewAI, AutoGen, Semantic Kernel, LlamaIndex와 시각적 워크플로 도구 등을 소개한다. 제품별 기능은 바뀌므로 이름의 목록보다 어떤 책임을 추상화하는지 보는 편이 낫다.&lt;/p&gt;
&lt;p&gt;일부는 역할별 에이전트를 조합하는 편의성에, 일부는 검색과 문서 처리에, 일부는 상태와 제어 흐름에 집중한다. 도구가 제공하는 기본 루프와 수정 가능한 범위, 상태 저장 방식과 관측 기능을 비교해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="52-langgraph의-기본-개념"&gt;5.2 LangGraph의 기본 개념&lt;span class="hx:absolute hx:-mt-20" id="52-langgraph의-기본-개념"&gt;&lt;/span&gt;
&lt;a href="#52-langgraph%ec%9d%98-%ea%b8%b0%eb%b3%b8-%ea%b0%9c%eb%85%90" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;그래프는 상태, 노드와 엣지로 구성된다. 상태는 작업 중 보관할 정보, 노드는 그 정보를 읽고 처리하는 단계, 엣지는 다음 실행 경로다.&lt;/p&gt;
&lt;p&gt;리서치 엔진의 상태에는 원래 질문, 조사 지침, 검색 질의, 검색 결과와 최종 보고서가 들어갈 수 있다. 검색 노드가 질문을 새로 만드는 것이 아니라 현재 질의로 자료를 찾고, 보고서 노드는 모인 자료를 사용한다.&lt;/p&gt;
&lt;p&gt;그래프의 모양과 데이터의 모양은 다른 문제다. 화살표가 연결되어 있어도 다음 노드가 필요한 필드가 없다면 실패한다. 노드마다 필요한 입력과 생성하는 상태 항목을 확인해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="53-체인에서-그래프로"&gt;5.3 체인에서 그래프로&lt;span class="hx:absolute hx:-mt-20" id="53-체인에서-그래프로"&gt;&lt;/span&gt;
&lt;a href="#53-%ec%b2%b4%ec%9d%b8%ec%97%90%ec%84%9c-%ea%b7%b8%eb%9e%98%ed%94%84%eb%a1%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;LCEL은 단순한 선형 처리만 가능한 도구는 아니다. 병렬 분기와 조건부 처리도 만들 수 있다. 다만 반복되는 흐름과 실행 상태를 여러 단계에서 관리할 때는 그래프가 제어 관계를 더 분명하게 드러낼 수 있다.&lt;/p&gt;
&lt;div role="img" aria-label="다이어그램"&gt;
&lt;pre class="mermaid hx:mt-6"&gt;
flowchart TD
accTitle: 리서치 워크플로의 관련성 평가와 재검색
accDescr: 질문을 바탕으로 검색하고 요약한 뒤 자료의 관련성을 평가한다. 부족하면 질의를 고쳐 다시 검색하고, 충분하면 보고서를 작성한다. 반복 한도에 도달하면 한계를 표시하고 종료한다.
A[&amp;#34;사용자 질문&amp;#34;] --&amp;gt; Q[&amp;#34;검색 질의 생성&amp;#34;]
Q --&amp;gt; S[&amp;#34;검색과 본문 수집&amp;#34;]
S --&amp;gt; M[&amp;#34;자료 요약&amp;#34;]
M --&amp;gt; E{&amp;#34;근거가 충분한가?&amp;#34;}
E --&amp;gt;|&amp;#34;예&amp;#34;| R[&amp;#34;보고서 작성&amp;#34;]
E --&amp;gt;|&amp;#34;아니요&amp;#34;| L{&amp;#34;반복 예산이 남았는가?&amp;#34;}
L --&amp;gt;|&amp;#34;예&amp;#34;| Q
L --&amp;gt;|&amp;#34;아니요&amp;#34;| F[&amp;#34;부족한 근거를 표시하고 종료&amp;#34;]
&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;재검색 경로에는 이전 시도가 무엇이었는지 남아 있어야 한다. 같은 질의로 같은 결과를 반복해 받는다면 루프가 존재한다는 사실만으로 품질이 좋아지지 않는다. 어떤 정보가 부족한지도 다음 질의의 입력이 되어야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="54-핵심-구성-요소"&gt;5.4 핵심 구성 요소&lt;span class="hx:absolute hx:-mt-20" id="54-핵심-구성-요소"&gt;&lt;/span&gt;
&lt;a href="#54-%ed%95%b5%ec%8b%ac-%ea%b5%ac%ec%84%b1-%ec%9a%94%ec%86%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="541-stategraph"&gt;5.4.1 StateGraph&lt;span class="hx:absolute hx:-mt-20" id="541-stategraph"&gt;&lt;/span&gt;
&lt;a href="#541-stategraph" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;StateGraph&lt;/code&gt;는 어떤 상태 구조를 사용하고 어떤 노드와 경로를 가질지 정의하는 빌더다. 노드와 엣지를 등록한 뒤 compile하면 실행 가능한 그래프가 된다.&lt;/p&gt;
&lt;p&gt;compile은 업무 로직이 옳다는 검증이 아니다. 모델이 맞게 판단하는지, 외부 API가 동작하는지, 종료 조건이 유효한지는 실행과 테스트가 필요하다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="542-상태와-타입"&gt;5.4.2 상태와 타입&lt;span class="hx:absolute hx:-mt-20" id="542-상태와-타입"&gt;&lt;/span&gt;
&lt;a href="#542-%ec%83%81%ed%83%9c%ec%99%80-%ed%83%80%ec%9e%85" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;상태 스키마는 노드가 어떤 필드를 읽고 갱신할지 명시한다. &lt;code&gt;TypedDict&lt;/code&gt;를 사용하면 정적 타입 도구가 구조를 이해하는 데 도움이 되지만, 그 자체가 모든 실행 시점의 값을 검증하는 것은 아니다.&lt;/p&gt;
&lt;p&gt;노드는 보통 전체 상태를 새로 반환하기보다 변경할 항목을 반환한다. 같은 필드의 여러 갱신을 어떻게 합칠지는 reducer에 따라 달라진다. 목록이라도 기본 동작이 무조건 이어 붙이기인 것은 아니다.&lt;/p&gt;
&lt;p&gt;예를 들어 검색 결과를 누적할 것인지 새 시도의 결과로 교체할 것인지 정해야 한다. 무작정 누적하면 중복과 오래된 자료가 늘고, 무작정 교체하면 이전 시도의 유효한 근거를 잃을 수 있다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;상태 항목&lt;/th&gt;
&lt;th&gt;용도&lt;/th&gt;
&lt;th&gt;확인할 조건&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;질문&lt;/td&gt;
&lt;td&gt;원래 목적 보존&lt;/td&gt;
&lt;td&gt;재작성 질의와 혼동하지 않기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;질의 목록&lt;/td&gt;
&lt;td&gt;검색할 관점&lt;/td&gt;
&lt;td&gt;이전 시도와 중복 여부&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검색 결과&lt;/td&gt;
&lt;td&gt;원문과 출처&lt;/td&gt;
&lt;td&gt;실패와 빈 결과의 구분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;평가 결과&lt;/td&gt;
&lt;td&gt;다음 경로 선택&lt;/td&gt;
&lt;td&gt;판단 기준과 불확실성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;반복 횟수&lt;/td&gt;
&lt;td&gt;실행 예산 통제&lt;/td&gt;
&lt;td&gt;종료 조건과 함께 갱신&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;보고서&lt;/td&gt;
&lt;td&gt;최종 결과&lt;/td&gt;
&lt;td&gt;사용한 근거와 연결&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-hextra-search-id="543-노드와-엣지"&gt;5.4.3 노드와 엣지&lt;span class="hx:absolute hx:-mt-20" id="543-노드와-엣지"&gt;&lt;/span&gt;
&lt;a href="#543-%eb%85%b8%eb%93%9c%ec%99%80-%ec%97%a3%ec%a7%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;노드는 상태를 받아 실제 작업을 수행하고 갱신 내용을 반환한다. 일반 엣지는 정해진 다음 노드로 연결하고, 조건부 엣지는 현재 상태의 판단 결과에 따라 다른 경로를 선택한다.&lt;/p&gt;
&lt;p&gt;LLM이 생성한 자유로운 문자열을 그대로 노드 이름으로 쓰면 정의되지 않은 경로로 갈 수 있다. 라우팅 결과는 허용된 값으로 제한하고 예상 밖의 출력은 명시적으로 처리해야 한다.&lt;/p&gt;
&lt;p&gt;노드 경계를 나누면 외부 요청 실패와 모델 출력 오류, 관련성 평가 실패를 구분하기 쉽다. 다만 지나치게 잘게 나누면 작은 변환마다 상태 필드를 늘리고 연결을 추적하는 부담이 커진다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="544-시작점과-종료-조건"&gt;5.4.4 시작점과 종료 조건&lt;span class="hx:absolute hx:-mt-20" id="544-시작점과-종료-조건"&gt;&lt;/span&gt;
&lt;a href="#544-%ec%8b%9c%ec%9e%91%ec%a0%90%ea%b3%bc-%ec%a2%85%eb%a3%8c-%ec%a1%b0%ea%b1%b4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;시작점은 처음 실행할 노드이고 종료 조건은 작업이 끝났음을 나타내는 기준이다. 그래프에 &lt;code&gt;END&lt;/code&gt;가 있다는 사실만으로 모든 경로가 그곳에 도달하는 것은 아니다.&lt;/p&gt;
&lt;p&gt;정상 완료, 근거 부족, 외부 서비스 실패와 예산 초과를 구분해야 한다. 근거가 부족한 상태를 성공 보고서와 같은 형태로 감추면 사용자가 한계를 알 수 없다.&lt;/p&gt;
&lt;p&gt;재귀나 반복 제한은 마지막 안전장치다. 사용자가 기다릴 수 있는 시간과 호출 비용을 기준으로 더 구체적인 예산을 두는 편이 낫다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="55-리서치-엔진의-전환"&gt;5.5 리서치 엔진의 전환&lt;span class="hx:absolute hx:-mt-20" id="55-리서치-엔진의-전환"&gt;&lt;/span&gt;
&lt;a href="#55-%eb%a6%ac%ec%84%9c%ec%b9%98-%ec%97%94%ec%a7%84%ec%9d%98-%ec%a0%84%ed%99%98" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="551-기존-구조"&gt;5.5.1 기존 구조&lt;span class="hx:absolute hx:-mt-20" id="551-기존-구조"&gt;&lt;/span&gt;
&lt;a href="#551-%ea%b8%b0%ec%a1%b4-%ea%b5%ac%ec%a1%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;기존 엔진은 조사 지침 생성, 검색 질의 생성, 검색, 요약과 보고서 작성을 순서대로 수행했다. 구현이 간단하고 정상 경로를 이해하기 쉽지만 중간 평가 뒤 되돌아가는 흐름은 눈에 덜 드러난다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="552-노드로-나눌-작업"&gt;5.5.2 노드로 나눌 작업&lt;span class="hx:absolute hx:-mt-20" id="552-노드로-나눌-작업"&gt;&lt;/span&gt;
&lt;a href="#552-%eb%85%b8%eb%93%9c%eb%a1%9c-%eb%82%98%eb%88%8c-%ec%9e%91%ec%97%85" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;책은 Assistant Selector, Query Generator, Web Searcher, Content Summarizer, Relevance Evaluator와 Report Writer로 역할을 나눈다.&lt;/p&gt;
&lt;p&gt;추가된 핵심은 관련성 평가다. 모인 자료가 질문에 답할 만큼 유효한지 보고 작성으로 갈지 검색으로 돌아갈지 결정한다. 단순히 검색 결과가 몇 개 있는지는 충분한 근거의 기준이 아니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="553-상태와-경로-설계"&gt;5.5.3 상태와 경로 설계&lt;span class="hx:absolute hx:-mt-20" id="553-상태와-경로-설계"&gt;&lt;/span&gt;
&lt;a href="#553-%ec%83%81%ed%83%9c%ec%99%80-%ea%b2%bd%eb%a1%9c-%ec%84%a4%ea%b3%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;먼저 전체 작업에 필요한 상태를 정의하고 기존 함수를 상태를 받는 노드로 감싼다. 검색 결과와 요약, 평가를 상태 필드로 연결한 뒤 분기와 종료를 정한다.&lt;/p&gt;
&lt;p&gt;관련성 점수가 높아도 잘못된 출처일 수 있고, 평가 모델이 근거의 허점을 놓칠 수 있다. 평가 결과를 사실의 보증으로 쓰기보다 명시된 수용 기준에 따라 다음 행동을 결정하는 입력으로 보는 편이 정확하다.&lt;/p&gt;
&lt;p&gt;반복할 때는 횟수와 질의 이력, 실패 원인을 상태에 남긴다. 보고서 작성 노드가 한계 정보도 받아야 근거가 부족한 부분을 최종 답에 반영할 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="554-얻는-것과-남는-책임"&gt;5.5.4 얻는 것과 남는 책임&lt;span class="hx:absolute hx:-mt-20" id="554-얻는-것과-남는-책임"&gt;&lt;/span&gt;
&lt;a href="#554-%ec%96%bb%eb%8a%94-%ea%b2%83%ea%b3%bc-%eb%82%a8%eb%8a%94-%ec%b1%85%ec%9e%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;그래프는 어느 상태에서 어떤 노드가 실행됐는지 추적하기 쉽다. 관련성 평가나 보고서 작성만 따로 시험하고 새 경로를 추가할 수도 있다.&lt;/p&gt;
&lt;p&gt;그러나 상태 스키마와 그림이 있다고 자동으로 복구와 영속성이 생기는 것은 아니다. 체크포인터, 외부 부작용의 중복 처리, 데이터 보존과 권한은 별도 설계다. 저장과 복원은 14장에서 다시 다룬다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="질문"&gt;질문&lt;span class="hx:absolute hx:-mt-20" id="질문"&gt;&lt;/span&gt;
&lt;a href="#%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="그래프로-옮기면-모두-에이전트가-되는가"&gt;그래프로 옮기면 모두 에이전트가 되는가?&lt;span class="hx:absolute hx:-mt-20" id="그래프로-옮기면-모두-에이전트가-되는가"&gt;&lt;/span&gt;
&lt;a href="#%ea%b7%b8%eb%9e%98%ed%94%84%eb%a1%9c-%ec%98%ae%ea%b8%b0%eb%a9%b4-%eb%aa%a8%eb%91%90-%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8%ea%b0%80-%eb%90%98%eb%8a%94%ea%b0%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;고정된 단계를 그래프로 표현할 수도 있다. 모델이 결정하는 범위와 프로그램이 보장하는 경로를 봐야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="관련성이-부족하면-계속-재검색하면-될까"&gt;관련성이 부족하면 계속 재검색하면 될까?&lt;span class="hx:absolute hx:-mt-20" id="관련성이-부족하면-계속-재검색하면-될까"&gt;&lt;/span&gt;
&lt;a href="#%ea%b4%80%eb%a0%a8%ec%84%b1%ec%9d%b4-%eb%b6%80%ec%a1%b1%ed%95%98%eb%a9%b4-%ea%b3%84%ec%86%8d-%ec%9e%ac%ea%b2%80%ec%83%89%ed%95%98%eb%a9%b4-%eb%90%a0%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;자료 자체가 없거나 수집이 불가능할 수 있다. 같은 실패를 반복하지 않도록 한도와 종료 응답이 필요하다.&lt;/p&gt;
&lt;p&gt;참고: &lt;a href="https://docs.langchain.com/oss/python/langgraph/overview" target="_blank" rel="noopener"&gt;LangGraph 개요&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="../04-research-engine/"&gt;이전: 리서치 요약 엔진&lt;/a&gt; / &lt;a href="../"&gt;시리즈 목차&lt;/a&gt; / &lt;a href="../06-rag-fundamentals/"&gt;다음: RAG와 ChromaDB&lt;/a&gt;&lt;/p&gt;</description></item><item><title>06. RAG와 ChromaDB</title><link>https://johklo.github.io/docs/tech/ai-agents-and-applications/06-rag-fundamentals/</link><pubDate>Mon, 19 Oct 2026 21:37:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/ai-agents-and-applications/06-rag-fundamentals/</guid><description>
&lt;p&gt;문서를 넣고 질문하는 것과 많은 문서에서 필요한 부분을 찾아 질문하는 것은 다르다. RAG는 검색 가능한 자료를 미리 준비하고 질문에 관련된 부분만 생성 모델에 전달하는 방식이다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="61-의미-검색"&gt;6.1 의미 검색&lt;span class="hx:absolute hx:-mt-20" id="61-의미-검색"&gt;&lt;/span&gt;
&lt;a href="#61-%ec%9d%98%eb%af%b8-%ea%b2%80%ec%83%89" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;키워드 검색은 나타난 단어나 형태를 중요한 신호로 사용한다. 의미 검색은 질문과 자료를 벡터로 표현해 유사한 내용을 찾는다. “대중교통으로 이동하는 방법”과 “기차와 버스 이용 안내”처럼 단어가 달라도 관련 자료를 찾으려는 목적이다.&lt;/p&gt;
&lt;p&gt;의미가 비슷한 문서가 곧 정확한 답은 아니다. 다른 지역의 교통 정보나 예전 요금표도 질문과 비슷하게 보일 수 있다. 대상과 날짜 같은 조건을 별도로 확인해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="611-문서-하나에-대한-질의응답"&gt;6.1.1 문서 하나에 대한 질의응답&lt;span class="hx:absolute hx:-mt-20" id="611-문서-하나에-대한-질의응답"&gt;&lt;/span&gt;
&lt;a href="#611-%eb%ac%b8%ec%84%9c-%ed%95%98%eb%82%98%ec%97%90-%eb%8c%80%ed%95%9c-%ec%a7%88%ec%9d%98%ec%9d%91%eb%8b%b5" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;문서가 짧으면 질문과 문서 전체를 한 번의 프롬프트에 넣을 수 있다. “제공한 문서 안에서 답하고 근거가 없으면 모른다고 답한다”는 규칙을 두는 기본 구조다.&lt;/p&gt;
&lt;p&gt;이 경우에도 답이 문서에 실제로 있는지 검사해야 한다. 모델이 지침을 받았다고 환각이 완전히 사라지지는 않는다. 후속 질문에 답하려면 이전 대화도 필요한 범위만큼 제공해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="612-지식베이스에-대한-질의응답"&gt;6.1.2 지식베이스에 대한 질의응답&lt;span class="hx:absolute hx:-mt-20" id="612-지식베이스에-대한-질의응답"&gt;&lt;/span&gt;
&lt;a href="#612-%ec%a7%80%ec%8b%9d%eb%b2%a0%ec%9d%b4%ec%8a%a4%ec%97%90-%eb%8c%80%ed%95%9c-%ec%a7%88%ec%9d%98%ec%9d%91%eb%8b%b5" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;문서 수와 길이가 늘어나면 모든 내용을 매 요청에 넣기 어렵다. 새 문서가 추가되거나 접근 범위가 달라지는 문제도 생긴다.&lt;/p&gt;
&lt;p&gt;사용자는 질문만 보내더라도 프로그램은 어떤 저장소에서 어떤 문서를 가져올지 알아야 한다. 모델 API를 호출했다는 사실만으로 사내 파일과 데이터베이스가 자동 연결되는 것은 아니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="613-rag-패턴"&gt;6.1.3 RAG 패턴&lt;span class="hx:absolute hx:-mt-20" id="613-rag-패턴"&gt;&lt;/span&gt;
&lt;a href="#613-rag-%ed%8c%a8%ed%84%b4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Retrieval은 근거를 찾는 단계, Augmentation은 그 근거를 질문과 함께 입력에 넣는 단계, Generation은 그 입력으로 답을 만드는 단계다.&lt;/p&gt;
&lt;p&gt;자료 준비는 질문마다 반복할 필요가 없다. 파일을 읽고 청크로 나눈 뒤 임베딩과 원문을 저장하는 과정을 인덱싱 또는 ingestion이라고 부른다.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;
&lt;img src="https://johklo.github.io/images/ai-agents-and-applications/figure-6-03.png" title="그림 6.3. RAG의 자료 적재 과정. 출처: AI Agents and Applications, 6장." alt="파일과 인트라넷 및 위키의 자료를 추출하고 청크로 분할한 뒤 임베딩과 원문을 벡터 데이터베이스에 적재하는 RAG 준비 과정." loading="lazy" /&gt;
&lt;figcaption&gt;그림 6.3. RAG의 자료 적재 과정. 출처: AI Agents and Applications, 6장.&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;왼쪽의 원본 형식은 달라도 분할 뒤에는 공통 검색 단위가 된다. 중요한 점은 오른쪽 저장소에 임베딩만이 아니라 나중에 생성 모델이 읽을 원문과 출처도 연결되어야 한다는 것이다. 벡터는 검색을 위한 표현이며 최종 답의 근거 자체를 대신하지 않는다.&lt;/p&gt;
&lt;p&gt;질문이 들어오면 질문을 임베딩하고 저장된 청크와 비교한다. 찾은 청크의 원문을 프롬프트에 넣고 생성 모델을 호출한다. 인덱스가 갱신되지 않으면 모델에 새 문서를 넣었다고 생각해도 실제 검색에는 반영되지 않을 수 있다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="62-벡터-저장소"&gt;6.2 벡터 저장소&lt;span class="hx:absolute hx:-mt-20" id="62-벡터-저장소"&gt;&lt;/span&gt;
&lt;a href="#62-%eb%b2%a1%ed%84%b0-%ec%a0%80%ec%9e%a5%ec%86%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="621-무엇을-저장하는가"&gt;6.2.1 무엇을 저장하는가&lt;span class="hx:absolute hx:-mt-20" id="621-무엇을-저장하는가"&gt;&lt;/span&gt;
&lt;a href="#621-%eb%ac%b4%ec%97%87%ec%9d%84-%ec%a0%80%ec%9e%a5%ed%95%98%eb%8a%94%ea%b0%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;임베딩은 텍스트나 이미지의 특성을 숫자 목록으로 표현한 벡터다. 벡터 저장소는 이런 표현을 보관하고 가까운 벡터를 찾는 기능을 제공한다.&lt;/p&gt;
&lt;p&gt;일반적인 기록에는 청크 ID, 임베딩, 원문, 메타데이터가 필요하다. 저장소가 모든 항목을 직접 보관하지 않는다면 원문 저장소와 식별자로 연결해야 한다.&lt;/p&gt;
&lt;p&gt;벡터의 각 차원을 사람이 읽을 수 있는 주제 이름처럼 해석하는 것은 적절하지 않다. 모델이 만든 표현 공간 안에서 상대적인 거리를 이용하는 것이다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="622-유사도와-검색"&gt;6.2.2 유사도와 검색&lt;span class="hx:absolute hx:-mt-20" id="622-유사도와-검색"&gt;&lt;/span&gt;
&lt;a href="#622-%ec%9c%a0%ec%82%ac%eb%8f%84%ec%99%80-%ea%b2%80%ec%83%89" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;질문 벡터와 문서 벡터 사이의 거리를 계산해 가까운 후보를 고른다. 유클리드 거리는 위치의 차이, 코사인 유사도는 주로 방향의 유사성을 비교한다. 어떤 척도가 적합한지는 임베딩 모델과 저장소 설정에 따른다.&lt;/p&gt;
&lt;p&gt;전체 벡터를 매번 비교하면 자료가 많을 때 비용이 커진다. 근사 최근접 이웃 검색인 ANN은 속도를 위해 정확한 이웃을 일부 놓칠 수 있는 검색 방식이다. 검색 품질에는 임베딩뿐 아니라 인덱스와 검색 설정도 영향을 준다.&lt;/p&gt;
&lt;p&gt;같은 차원의 벡터라고 모두 비교 가능한 것은 아니다. 서로 다른 모델이 만든 벡터는 같은 의미 공간이라는 보장이 없다. 문서 임베딩과 질문 임베딩은 호환되는 모델과 설정으로 생성해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="623-라이브러리와-데이터베이스"&gt;6.2.3 라이브러리와 데이터베이스&lt;span class="hx:absolute hx:-mt-20" id="623-라이브러리와-데이터베이스"&gt;&lt;/span&gt;
&lt;a href="#623-%eb%9d%bc%ec%9d%b4%eb%b8%8c%eb%9f%ac%eb%a6%ac%ec%99%80-%eb%8d%b0%ec%9d%b4%ed%84%b0%eb%b2%a0%ec%9d%b4%ec%8a%a4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;벡터 검색 라이브러리는 검색 인덱스 구현에 집중할 수 있다. 데이터베이스는 여기에 원문과 메타데이터, 갱신과 영속화, 접근과 운영 기능을 결합한다.&lt;/p&gt;
&lt;p&gt;다만 특정 라이브러리는 무조건 읽기 전용이고 특정 DB는 모든 운영 기능을 제공한다는 식으로 일반화할 수는 없다. 구현과 버전마다 범위가 다르다. 필요한 기능을 각각 확인해야 한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;확인 항목&lt;/th&gt;
&lt;th&gt;이유&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;원문과 메타데이터 보관&lt;/td&gt;
&lt;td&gt;검색 결과를 답의 근거로 복원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;필터와 식별자&lt;/td&gt;
&lt;td&gt;문서 범위와 사용자 접근 조건 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;추가와 갱신, 삭제&lt;/td&gt;
&lt;td&gt;최신 자료와 원천 문서 변경 반영&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;재시작 후 보존&lt;/td&gt;
&lt;td&gt;메모리 실험과 운영 저장의 구분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;동시 접근과 운영&lt;/td&gt;
&lt;td&gt;처리량과 백업, 권한의 요구 충족&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-hextra-search-id="624-저장소-선택"&gt;6.2.4 저장소 선택&lt;span class="hx:absolute hx:-mt-20" id="624-저장소-선택"&gt;&lt;/span&gt;
&lt;a href="#624-%ec%a0%80%ec%9e%a5%ec%86%8c-%ec%84%a0%ed%83%9d" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;FAISS 같은 검색 라이브러리, Chroma와 전용 벡터 DB, PostgreSQL의 pgvector처럼 기존 DB에 벡터를 넣는 방식이 있다.&lt;/p&gt;
&lt;p&gt;선택의 출발점은 데이터 규모뿐 아니라 기존 저장소와의 연결, 필터 요구, 변경 빈도와 운영 역량이다. 검색 속도만 비교하고 삭제와 백업, 문서 권한을 빼면 실제 서비스 요구와 어긋날 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="625-chroma에-저장하고-검색하기"&gt;6.2.5 Chroma에 저장하고 검색하기&lt;span class="hx:absolute hx:-mt-20" id="625-chroma에-저장하고-검색하기"&gt;&lt;/span&gt;
&lt;a href="#625-chroma%ec%97%90-%ec%a0%80%ec%9e%a5%ed%95%98%ea%b3%a0-%ea%b2%80%ec%83%89%ed%95%98%ea%b8%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;책은 Chroma 컬렉션에 문서와 ID를 넣고 질의한다. 컬렉션은 관련 기록을 묶는 단위다. 문서를 추가할 때 임베딩을 직접 전달하거나 설정한 임베딩 함수가 만들도록 할 수 있다.&lt;/p&gt;
&lt;p&gt;메모리 클라이언트와 영속 클라이언트는 수명이 다르다. 메모리에서 정상 검색됐다고 프로세스 재시작 뒤에도 자료가 남는 것은 아니다.&lt;/p&gt;
&lt;p&gt;검색 결과는 여러 질의를 지원하기 위해 중첩 목록 형태일 수 있다. 첫 질의의 문서와 ID, 거리 결과를 올바르게 대응시켜야 한다. 거리인지 정규화된 관련도 점수인지도 확인해야 하며 높은 값이 항상 더 좋은 것은 아니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;k&lt;/code&gt; 또는 반환 개수는 근거의 양을 정한다. 너무 작으면 필요한 부분을 놓치고, 너무 크면 잡음과 토큰이 늘어난다. 상위 결과는 관련 자료가 없어도 반환될 수 있으므로 개수만으로 충분함을 판단하면 안 된다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="63-rag-직접-구현"&gt;6.3 RAG 직접 구현&lt;span class="hx:absolute hx:-mt-20" id="63-rag-직접-구현"&gt;&lt;/span&gt;
&lt;a href="#63-rag-%ec%a7%81%ec%a0%91-%ea%b5%ac%ed%98%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="631-자료-검색"&gt;6.3.1 자료 검색&lt;span class="hx:absolute hx:-mt-20" id="631-자료-검색"&gt;&lt;/span&gt;
&lt;a href="#631-%ec%9e%90%eb%a3%8c-%ea%b2%80%ec%83%89" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;질문을 검색 함수에 넣고 관련 청크의 본문을 받는다. 이때부터 최종 답을 생성할 필요는 없다. 먼저 찾은 자료가 질문에 필요한 사실을 포함하는지 직접 볼 수 있어야 한다.&lt;/p&gt;
&lt;p&gt;다른 지역이나 다른 버전의 자료가 검색되었다면 원문 메타데이터와 인덱싱, 질문 표현을 확인한다. 검색이 틀린 상태에서 생성 프롬프트만 바꾸면 원인을 놓치기 쉽다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="632-생성-모델-호출"&gt;6.3.2 생성 모델 호출&lt;span class="hx:absolute hx:-mt-20" id="632-생성-모델-호출"&gt;&lt;/span&gt;
&lt;a href="#632-%ec%83%9d%ec%84%b1-%eb%aa%a8%eb%8d%b8-%ed%98%b8%ec%b6%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;사용자 질문과 검색한 문맥, 응답 규칙을 조합한다. 모델이 읽는 것은 일반적으로 검색된 텍스트이지 벡터 숫자가 아니다.&lt;/p&gt;
&lt;p&gt;원문이 부족하면 모른다고 답하도록 하고, 사실과 출처를 연결한다. 그러나 문서가 외부에서 왔다면 그 안의 지시를 실행하지 않도록 신뢰 경계를 유지해야 한다. RAG는 외부 텍스트를 모델 입력에 추가하기 때문에 입력의 정확성과 안전성이 모두 중요하다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="633-챗봇-조합"&gt;6.3.3 챗봇 조합&lt;span class="hx:absolute hx:-mt-20" id="633-챗봇-조합"&gt;&lt;/span&gt;
&lt;a href="#633-%ec%b1%97%eb%b4%87-%ec%a1%b0%ed%95%a9" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;기본 조합은 다음과 같다.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;사용자 질문
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 질문 임베딩
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 검색과 원문 조회
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 질문과 문맥으로 프롬프트 구성
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 생성 모델 호출
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 응답과 출처 반환&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;이 구조가 한 질문에 답한다고 대화 메모리가 생기는 것은 아니다. 이전 질문에서 가리킨 장소나 대상은 다음 요청에 별도로 전달해야 한다. 다음 장은 그 연결을 다룬다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="634-용어-정리"&gt;6.3.4 용어 정리&lt;span class="hx:absolute hx:-mt-20" id="634-용어-정리"&gt;&lt;/span&gt;
&lt;a href="#634-%ec%9a%a9%ec%96%b4-%ec%a0%95%eb%a6%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;용어&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;문서&lt;/td&gt;
&lt;td&gt;본문과 메타데이터를 가진 원천 단위&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;청크&lt;/td&gt;
&lt;td&gt;검색이나 처리에 사용할 문서 조각&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;임베딩&lt;/td&gt;
&lt;td&gt;자료와 질문의 유사도 비교를 위한 벡터 표현&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;인덱싱&lt;/td&gt;
&lt;td&gt;검색 가능한 자료를 준비하고 저장하는 과정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검색기&lt;/td&gt;
&lt;td&gt;질문에 맞는 문서 후보를 반환하는 구성 요소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;문맥&lt;/td&gt;
&lt;td&gt;최종 생성에 실제로 전달한 근거와 배경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;생성&lt;/td&gt;
&lt;td&gt;문맥과 지침으로 답변을 작성하는 과정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;검색 자료, 실제 문맥, 최종 답을 따로 기록하면 실패를 나눠 볼 수 있다. 자료를 찾았지만 길이 제한으로 문맥에서 빠질 수 있고, 문맥에 있어도 생성 모델이 잘못 해석할 수 있다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="질문"&gt;질문&lt;span class="hx:absolute hx:-mt-20" id="질문"&gt;&lt;/span&gt;
&lt;a href="#%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="자료를-많이-검색하면-환각이-사라질까"&gt;자료를 많이 검색하면 환각이 사라질까?&lt;span class="hx:absolute hx:-mt-20" id="자료를-많이-검색하면-환각이-사라질까"&gt;&lt;/span&gt;
&lt;a href="#%ec%9e%90%eb%a3%8c%eb%a5%bc-%eb%a7%8e%ec%9d%b4-%ea%b2%80%ec%83%89%ed%95%98%eb%a9%b4-%ed%99%98%ea%b0%81%ec%9d%b4-%ec%82%ac%eb%9d%bc%ec%a7%88%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;관련 없는 자료와 모순된 자료가 늘 수 있다. 근거의 관련성과 충분함, 답이 근거를 따르는지를 따로 확인해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="임베딩-모델을-바꾸고-기존-인덱스를-계속-써도-될까"&gt;임베딩 모델을 바꾸고 기존 인덱스를 계속 써도 될까?&lt;span class="hx:absolute hx:-mt-20" id="임베딩-모델을-바꾸고-기존-인덱스를-계속-써도-될까"&gt;&lt;/span&gt;
&lt;a href="#%ec%9e%84%eb%b2%a0%eb%94%a9-%eb%aa%a8%eb%8d%b8%ec%9d%84-%eb%b0%94%ea%be%b8%ea%b3%a0-%ea%b8%b0%ec%a1%b4-%ec%9d%b8%eb%8d%b1%ec%8a%a4%eb%a5%bc-%ea%b3%84%ec%86%8d-%ec%8d%a8%eb%8f%84-%eb%90%a0%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;차원이 같아도 표현 공간이 다를 수 있다. 기존 문서와 새 질문의 벡터 호환성을 확인하고 필요하면 전체를 다시 임베딩해야 한다.&lt;/p&gt;
&lt;p&gt;참고: &lt;a href="https://docs.trychroma.com/docs/overview/introduction" target="_blank" rel="noopener"&gt;Chroma 개요&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="../05-langgraph-workflows/"&gt;이전: LangGraph 워크플로&lt;/a&gt; / &lt;a href="../"&gt;시리즈 목차&lt;/a&gt; / &lt;a href="../07-conversational-rag/"&gt;다음: 대화형 RAG와 LangSmith&lt;/a&gt;&lt;/p&gt;</description></item><item><title>07. 대화형 RAG와 LangSmith</title><link>https://johklo.github.io/docs/tech/ai-agents-and-applications/07-conversational-rag/</link><pubDate>Mon, 19 Oct 2026 21:37:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/ai-agents-and-applications/07-conversational-rag/</guid><description>
&lt;p&gt;문서 검색과 생성이 한 번 동작해도 대화가 이어지면 다른 문제가 생긴다. 현재 질문에 주어가 없거나 이전 답을 가리킬 수 있고, 검색기가 본 질문과 생성 모델이 본 대화가 서로 다를 수 있다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="71-질의응답-객체-모델"&gt;7.1 질의응답 객체 모델&lt;span class="hx:absolute hx:-mt-20" id="71-질의응답-객체-모델"&gt;&lt;/span&gt;
&lt;a href="#71-%ec%a7%88%ec%9d%98%ec%9d%91%eb%8b%b5-%ea%b0%9d%ec%b2%b4-%eb%aa%a8%eb%8d%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;LangChain은 적재와 검색, 프롬프트와 모델을 공통 객체로 연결한다. 추상화는 제공자 교체와 조합을 돕지만 자료형과 실제 검색 결과를 이해할 필요까지 없애지는 않는다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="711-자료-적재"&gt;7.1.1 자료 적재&lt;span class="hx:absolute hx:-mt-20" id="711-자료-적재"&gt;&lt;/span&gt;
&lt;a href="#711-%ec%9e%90%eb%a3%8c-%ec%a0%81%ec%9e%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Loader가 &lt;code&gt;Document&lt;/code&gt;를 만들고 Splitter가 청크를 만든다. 임베딩 모델이 벡터를 생성하고 VectorStore가 검색할 기록을 보관한다.&lt;/p&gt;
&lt;p&gt;적재 결과에는 출처와 청크 식별자가 남아야 한다. 검색된 내용이 어떤 원문과 페이지에서 나온 것인지 모르겠다면 최종 답의 근거도 검증하기 어렵다.&lt;/p&gt;
&lt;p&gt;본문의 길이만이 아니라 분할 경계와 메타데이터가 검색 단위를 결정한다. 원문에 있는 제목이나 범위 조건이 다른 청크로 분리되면 청크 본문만으로 의미를 오해할 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="712-검색과-생성"&gt;7.1.2 검색과 생성&lt;span class="hx:absolute hx:-mt-20" id="712-검색과-생성"&gt;&lt;/span&gt;
&lt;a href="#712-%ea%b2%80%ec%83%89%ea%b3%bc-%ec%83%9d%ec%84%b1" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Retriever는 질문에서 자료를 찾고, PromptTemplate은 질문과 자료를 조립하며, 채팅 모델은 완성된 메시지로 답을 만든다.&lt;/p&gt;
&lt;p&gt;질문을 검색과 생성 양쪽에 전달해야 한다. 검색 결과만 모델에 보내면 어떤 질문에 답할지 모호해지고, 질문만 보내면 외부 자료를 사용한 RAG가 아니다.&lt;/p&gt;
&lt;p&gt;검색된 Document 목록을 모델 문맥으로 바꾸는 과정도 필요하다. 목록 객체의 디버그 문자열을 그대로 넣는 것보다 본문과 필요한 출처를 명확하게 구성하는 편이 낫다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="72-벡터-저장소에-자료-적재"&gt;7.2 벡터 저장소에 자료 적재&lt;span class="hx:absolute hx:-mt-20" id="72-벡터-저장소에-자료-적재"&gt;&lt;/span&gt;
&lt;a href="#72-%eb%b2%a1%ed%84%b0-%ec%a0%80%ec%9e%a5%ec%86%8c%ec%97%90-%ec%9e%90%eb%a3%8c-%ec%a0%81%ec%9e%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="721-문서-분할과-저장"&gt;7.2.1 문서 분할과 저장&lt;span class="hx:absolute hx:-mt-20" id="721-문서-분할과-저장"&gt;&lt;/span&gt;
&lt;a href="#721-%eb%ac%b8%ec%84%9c-%eb%b6%84%ed%95%a0%ea%b3%bc-%ec%a0%80%ec%9e%a5" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;책은 여러 형식의 자료를 읽어 청크를 만들고 Chroma에 저장한다. 작은 예제에서는 문자 수 기준으로 시작하지만 실제 모델의 문맥 예산은 토큰 기준이라는 점을 구분해야 한다.&lt;/p&gt;
&lt;p&gt;중첩은 경계 부근의 문맥 손실을 줄일 수 있지만 같은 내용이 여러 결과로 나올 수 있다. 청크 수와 중복률, 실제 질문에서 찾은 내용을 함께 봐야 한다.&lt;/p&gt;
&lt;p&gt;적재한 문서를 다시 실행할 때 어떻게 처리할지도 필요하다. 새 ID로 계속 추가하면 같은 자료가 여러 번 검색될 수 있다. 원문 버전과 청크 ID를 기준으로 추가, 갱신과 삭제 정책을 정한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="722-중복-코드-줄이기"&gt;7.2.2 중복 코드 줄이기&lt;span class="hx:absolute hx:-mt-20" id="722-중복-코드-줄이기"&gt;&lt;/span&gt;
&lt;a href="#722-%ec%a4%91%eb%b3%b5-%ec%bd%94%eb%93%9c-%ec%a4%84%ec%9d%b4%ea%b8%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;이 절의 중복 제거는 우선 여러 Loader에 반복되던 코드의 공통화다. 데이터 중복을 자동으로 제거한다는 뜻과 구분해야 한다.&lt;/p&gt;
&lt;p&gt;Loader에서 나온 목록을 같은 분할과 저장 함수에 전달하면 형식별 반복 코드를 줄일 수 있다. 다만 PDF와 HTML의 추출 품질이 다르다면 공통 처리 이전에 형식별 점검이 필요하다.&lt;/p&gt;
&lt;p&gt;함수로 추출한 뒤에도 어떤 자료를 읽었고 몇 개 청크를 만들었는지 남겨야 한다. 조용히 빈 목록을 반환하는 실패는 인덱스가 정상 생성된 것처럼 보이게 할 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="723-폴더에서-여러-문서-읽기"&gt;7.2.3 폴더에서 여러 문서 읽기&lt;span class="hx:absolute hx:-mt-20" id="723-폴더에서-여러-문서-읽기"&gt;&lt;/span&gt;
&lt;a href="#723-%ed%8f%b4%eb%8d%94%ec%97%90%ec%84%9c-%ec%97%ac%eb%9f%ac-%eb%ac%b8%ec%84%9c-%ec%9d%bd%ea%b8%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;확장자에 맞는 Loader를 선택하거나 DirectoryLoader를 사용할 수 있다. 폴더 전체를 읽는 편의성과 처리할 자료의 범위를 통제하는 것은 다른 문제다.&lt;/p&gt;
&lt;p&gt;하위 폴더와 숨김 파일, 임시 파일을 포함할지 정해야 한다. 지원하지 않는 형식이나 읽지 못한 파일이 있으면 누락을 알 수 있어야 한다.&lt;/p&gt;
&lt;p&gt;파서의 시스템 의존성도 설치 환경에 영향을 준다. 예제와 같은 Python 패키지를 설치했어도 OS 도구나 파일 인코딩이 다르면 다른 결과가 나올 수 있다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="73-여러-문서에-질의하기"&gt;7.3 여러 문서에 질의하기&lt;span class="hx:absolute hx:-mt-20" id="73-여러-문서에-질의하기"&gt;&lt;/span&gt;
&lt;a href="#73-%ec%97%ac%eb%9f%ac-%eb%ac%b8%ec%84%9c%ec%97%90-%ec%a7%88%ec%9d%98%ed%95%98%ea%b8%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="731-저장소-직접-질의"&gt;7.3.1 저장소 직접 질의&lt;span class="hx:absolute hx:-mt-20" id="731-저장소-직접-질의"&gt;&lt;/span&gt;
&lt;a href="#731-%ec%a0%80%ec%9e%a5%ec%86%8c-%ec%a7%81%ec%a0%91-%ec%a7%88%ec%9d%98" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;답변을 생성하기 전에 검색 결과부터 확인한다. 서로 다른 문서에 있는 사실을 묻는 질문이라면 필요한 근거가 실제로 함께 반환되는지 볼 수 있다.&lt;/p&gt;
&lt;p&gt;이 단계에서 결과가 틀리면 분할, 임베딩, 질의, 필터와 반환 개수의 문제다. 생성 모델의 문체나 출력 프롬프트는 아직 관여하지 않는다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="732-체인으로-질문하기"&gt;7.3.2 체인으로 질문하기&lt;span class="hx:absolute hx:-mt-20" id="732-체인으로-질문하기"&gt;&lt;/span&gt;
&lt;a href="#732-%ec%b2%b4%ec%9d%b8%ec%9c%bc%eb%a1%9c-%ec%a7%88%eb%ac%b8%ed%95%98%ea%b8%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;검색 문맥과 원래 질문을 템플릿에 넣고 채팅 모델을 호출한다. 프롬프트를 외부 Hub에서 가져올 수 있지만 그 내용과 버전을 확인해야 한다.&lt;/p&gt;
&lt;p&gt;공개된 템플릿도 현재 서비스의 권한, 자료 범위와 출력 요구를 자동으로 만족하지는 않는다. 짧은 답을 요구하는 기본 템플릿이 충분한 설명이나 출처를 생략할 수도 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="733-전체-rag-체인"&gt;7.3.3 전체 RAG 체인&lt;span class="hx:absolute hx:-mt-20" id="733-전체-rag-체인"&gt;&lt;/span&gt;
&lt;a href="#733-%ec%a0%84%ec%b2%b4-rag-%ec%b2%b4%ec%9d%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;같은 질문을 두 경로로 전달한다. 하나는 Retriever를 거쳐 문맥을 만들고, 다른 하나는 그대로 PromptTemplate의 질문 변수가 된다. &lt;code&gt;RunnablePassthrough&lt;/code&gt;는 입력을 유지해 전달하는 역할이다.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;질문 ── 검색기 ── 문맥 ─┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;질문 ── 그대로 전달 ──┼→ 프롬프트 → 모델 → 응답
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ┘&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;각 키에 Document 목록과 문자열 중 무엇이 들어오는지 확인해야 한다. 완성된 프롬프트를 보면 검색 결과를 잘못 결합하거나 질문을 잃는 오류를 찾기 쉽다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="734-후속-질문"&gt;7.3.4 후속 질문&lt;span class="hx:absolute hx:-mt-20" id="734-후속-질문"&gt;&lt;/span&gt;
&lt;a href="#734-%ed%9b%84%ec%86%8d-%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;“그들은 언제 이름을 바꿨는가?”처럼 이전 대상을 가리키는 질문은 문장 자체만으로 검색하기 어렵다. 모델에 이전 대화를 주지 않으면 답변 단계에서 대상을 모르고, 검색기에 현재 문장만 주면 근거 검색도 실패할 수 있다.&lt;/p&gt;
&lt;p&gt;대화형 RAG에서는 질문 해석과 자료 검색을 분리해서 생각해야 한다. 이력을 생성 프롬프트에 넣는 것만으로 검색 질의가 자동으로 명확해지지는 않는다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="74-메시지-이력"&gt;7.4 메시지 이력&lt;span class="hx:absolute hx:-mt-20" id="74-메시지-이력"&gt;&lt;/span&gt;
&lt;a href="#74-%eb%a9%94%ec%8b%9c%ec%a7%80-%ec%9d%b4%eb%a0%a5" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="741-프롬프트-수정"&gt;7.4.1 프롬프트 수정&lt;span class="hx:absolute hx:-mt-20" id="741-프롬프트-수정"&gt;&lt;/span&gt;
&lt;a href="#741-%ed%94%84%eb%a1%ac%ed%94%84%ed%8a%b8-%ec%88%98%ec%a0%95" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;채팅 프롬프트는 system 지침, 이전 메시지, 검색 문맥과 현재 질문으로 구성할 수 있다. 이력은 메시지 목록으로 넣어 역할과 순서를 유지한다.&lt;/p&gt;
&lt;p&gt;외부 문서를 assistant의 과거 답변처럼 취급하면 신뢰의 경계가 흐려질 수 있다. 검색 자료는 출처가 있는 참고 데이터로 구분하고 그 안의 명령을 시스템 지침으로 실행하지 않도록 해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="742-이력-갱신"&gt;7.4.2 이력 갱신&lt;span class="hx:absolute hx:-mt-20" id="742-이력-갱신"&gt;&lt;/span&gt;
&lt;a href="#742-%ec%9d%b4%eb%a0%a5-%ea%b0%b1%ec%8b%a0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;사용자 질문과 모델 응답을 완료된 대화 순서에 맞춰 저장한다. 실패한 호출을 성공 답변처럼 기록하면 다음 요청도 잘못된 전제를 받는다.&lt;/p&gt;
&lt;p&gt;동일한 이력 객체를 여러 사용자에게 공유하면 다른 사용자의 문맥이 섞인다. 세션별 식별자와 접근 통제가 필요하다. 메모리 안에 있는 객체는 프로세스가 종료되면 사라질 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="743-체인에-이력-전달"&gt;7.4.3 체인에 이력 전달&lt;span class="hx:absolute hx:-mt-20" id="743-체인에-이력-전달"&gt;&lt;/span&gt;
&lt;a href="#743-%ec%b2%b4%ec%9d%b8%ec%97%90-%ec%9d%b4%eb%a0%a5-%ec%a0%84%eb%8b%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;매 호출마다 최신 이력을 전달해야 한다. 체인 구성 시점에 목록의 복사본을 고정하면 이후 메시지를 추가해도 체인이 오래된 목록을 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;메시지의 역할과 기대하는 변수 이름도 프롬프트 정의와 맞아야 한다. 문자열로 합친 대화와 메시지 객체 목록은 같은 입력 형식이 아니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="744-전체-연결"&gt;7.4.4 전체 연결&lt;span class="hx:absolute hx:-mt-20" id="744-전체-연결"&gt;&lt;/span&gt;
&lt;a href="#744-%ec%a0%84%ec%b2%b4-%ec%97%b0%ea%b2%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;현재 질문, 현재 검색 자료와 과거 대화를 조립하고 응답이 끝나면 이력을 갱신한다. 실제로 필요한 흐름은 다음과 같이 확장할 수 있다.&lt;/p&gt;
&lt;div role="img" aria-label="다이어그램"&gt;
&lt;pre class="mermaid hx:mt-6"&gt;
flowchart TD
accTitle: 후속 질문의 대상 복원과 자료 검색
accDescr: 현재 질문과 대화 이력에서 검색 가능한 독립 질문을 만든다. 그 질문으로 자료를 찾고 원래 질문과 관련 이력, 검색 문맥을 모델에 전달한다.
Q[&amp;#34;현재 질문&amp;#34;] --&amp;gt; R[&amp;#34;대화의 대상과 조건 복원&amp;#34;]
H[&amp;#34;관련 대화 이력&amp;#34;] --&amp;gt; R
R --&amp;gt; S[&amp;#34;독립된 질의로 검색&amp;#34;]
S --&amp;gt; C[&amp;#34;근거 문맥&amp;#34;]
C --&amp;gt; A[&amp;#34;원래 질문에 답변&amp;#34;]
Q --&amp;gt; A
H --&amp;gt; A
A --&amp;gt; U[&amp;#34;세션 이력 갱신&amp;#34;]
&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;질의 재작성은 9장에서 자세히 다룬다. 여기서 중요한 것은 이력이 대명사 해석에 필요한 정보인지, 답의 사실 근거인지 구분하는 것이다.&lt;/p&gt;
&lt;p&gt;대화가 길어지면 메시지를 모두 넣기보다 관련 이력을 선택하거나 요약할 수 있다. 이때도 사용자의 중요한 제약이 빠지거나 이전 모델의 오류가 사실처럼 고정되는지 확인해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="75-langsmith-실행-추적"&gt;7.5 LangSmith 실행 추적&lt;span class="hx:absolute hx:-mt-20" id="75-langsmith-실행-추적"&gt;&lt;/span&gt;
&lt;a href="#75-langsmith-%ec%8b%a4%ed%96%89-%ec%b6%94%ec%a0%81" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;LangSmith는 체인의 실행과 모델 호출, 중간 입력과 출력을 연결해 보는 도구다. “답이 틀렸다”는 결과를 어떤 자료가 검색됐고 어떤 프롬프트로 모델을 호출했는지로 나눌 수 있다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;확인할 위치&lt;/th&gt;
&lt;th&gt;찾을 수 있는 문제&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;체인 입력&lt;/td&gt;
&lt;td&gt;사용자 질문이나 세션 정보 누락&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검색 단계&lt;/td&gt;
&lt;td&gt;관련 문서 미반환, 중복, 오래된 자료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;프롬프트&lt;/td&gt;
&lt;td&gt;문맥 누락, 잘못된 메시지 순서&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;모델 출력&lt;/td&gt;
&lt;td&gt;근거에 없는 주장, 잘린 응답&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;지연과 사용량&lt;/td&gt;
&lt;td&gt;외부 호출 병목, 불필요한 토큰&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;실행 추적은 품질 평가 자체가 아니다. 경로가 성공했어도 답은 틀릴 수 있다. 대표 질문과 정답 근거를 가진 평가셋으로 정확성과 관련성, 근거 충실성을 비교해야 한다.&lt;/p&gt;
&lt;p&gt;트레이스에 문서와 대화가 들어가면 민감한 자료도 저장될 수 있다. 기록할 범위와 마스킹, 접근 권한과 보존 기간을 함께 정해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="질문"&gt;질문&lt;span class="hx:absolute hx:-mt-20" id="질문"&gt;&lt;/span&gt;
&lt;a href="#%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="대화-이력을-넣으면-후속-질문의-검색도-해결될까"&gt;대화 이력을 넣으면 후속 질문의 검색도 해결될까?&lt;span class="hx:absolute hx:-mt-20" id="대화-이력을-넣으면-후속-질문의-검색도-해결될까"&gt;&lt;/span&gt;
&lt;a href="#%eb%8c%80%ed%99%94-%ec%9d%b4%eb%a0%a5%ec%9d%84-%eb%84%a3%ec%9c%bc%eb%a9%b4-%ed%9b%84%ec%86%8d-%ec%a7%88%eb%ac%b8%ec%9d%98-%ea%b2%80%ec%83%89%eb%8f%84-%ed%95%b4%ea%b2%b0%eb%90%a0%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;생성 모델이 이력을 봐도 검색기가 현재 질문만 받았다면 필요한 근거를 못 찾을 수 있다. 검색에 들어간 실제 문자열을 확인해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="중복-처리-함수를-만들면-중복-문서도-없어질까"&gt;중복 처리 함수를 만들면 중복 문서도 없어질까?&lt;span class="hx:absolute hx:-mt-20" id="중복-처리-함수를-만들면-중복-문서도-없어질까"&gt;&lt;/span&gt;
&lt;a href="#%ec%a4%91%eb%b3%b5-%ec%b2%98%eb%a6%ac-%ed%95%a8%ec%88%98%eb%a5%bc-%eb%a7%8c%eb%93%a4%eb%a9%b4-%ec%a4%91%eb%b3%b5-%eb%ac%b8%ec%84%9c%eb%8f%84-%ec%97%86%ec%96%b4%ec%a7%88%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;코드 공통화와 데이터 중복 제거는 다른 일이다. 문서 식별자와 갱신 정책을 별도로 관리해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;a href="../06-rag-fundamentals/"&gt;이전: RAG와 ChromaDB&lt;/a&gt; / &lt;a href="../"&gt;시리즈 목차&lt;/a&gt; / &lt;a href="../08-advanced-indexing/"&gt;다음: 고급 인덱싱&lt;/a&gt;&lt;/p&gt;</description></item><item><title>08. 고급 인덱싱</title><link>https://johklo.github.io/docs/tech/ai-agents-and-applications/08-advanced-indexing/</link><pubDate>Mon, 19 Oct 2026 21:37:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/ai-agents-and-applications/08-advanced-indexing/</guid><description>
&lt;p&gt;작은 청크는 검색할 내용을 선명하게 만들지만 답변에 필요한 배경을 잃을 수 있다. 큰 청크는 배경이 충분한 대신 여러 주제가 섞여 검색이 둔해질 수 있다. 이 장은 검색할 표현과 생성에 전달할 원문을 반드시 같은 크기로 둘 필요는 없다는 데서 출발한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="81-rag-정확도-개선"&gt;8.1 RAG 정확도 개선&lt;span class="hx:absolute hx:-mt-20" id="81-rag-정확도-개선"&gt;&lt;/span&gt;
&lt;a href="#81-rag-%ec%a0%95%ed%99%95%eb%8f%84-%ea%b0%9c%ec%84%a0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="811-자료-적재-단계"&gt;8.1.1 자료 적재 단계&lt;span class="hx:absolute hx:-mt-20" id="811-자료-적재-단계"&gt;&lt;/span&gt;
&lt;a href="#811-%ec%9e%90%eb%a3%8c-%ec%a0%81%ec%9e%ac-%eb%8b%a8%ea%b3%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;적재에서는 무엇을 청크로 만들고 어떤 벡터로 검색할지 결정한다. 같은 문서도 제목, 짧은 문장, 요약과 예상 질문 중 무엇을 임베딩하느냐에 따라 다른 질문에 반응할 수 있다.&lt;/p&gt;
&lt;p&gt;원문 추출이 틀렸거나 제목과 본문의 관계를 잃었다면 임베딩 모델을 키워도 충분하지 않을 수 있다. 청크의 본문과 메타데이터, 원문으로 돌아갈 식별자를 함께 봐야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="812-질문과-답변-단계"&gt;8.1.2 질문과 답변 단계&lt;span class="hx:absolute hx:-mt-20" id="812-질문과-답변-단계"&gt;&lt;/span&gt;
&lt;a href="#812-%ec%a7%88%eb%ac%b8%ea%b3%bc-%eb%8b%b5%eb%b3%80-%eb%8b%a8%ea%b3%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;적재가 괜찮아도 질문이 모호하거나 구조화된 데이터가 다른 DB에 있으면 검색에 실패할 수 있다. 검색 결과에 잡음이 많으면 생성 모델에 전달하기 전의 필터와 재정렬이 필요하다.&lt;/p&gt;
&lt;p&gt;8장은 자료 준비를, 9장은 질문 변환을, 10장은 검색 대상 선택과 결과 후처리를 다룬다. 실패가 생긴 위치에 맞춰 수정해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="82-고급-문서-인덱싱"&gt;8.2 고급 문서 인덱싱&lt;span class="hx:absolute hx:-mt-20" id="82-고급-문서-인덱싱"&gt;&lt;/span&gt;
&lt;a href="#82-%ea%b3%a0%ea%b8%89-%eb%ac%b8%ec%84%9c-%ec%9d%b8%eb%8d%b1%ec%8b%b1" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;기본 전략은 원문 청크 하나에 벡터 하나를 붙이는 것이다. 고급 전략은 검색에 유리한 여러 표현을 같은 원문에 연결한다.&lt;/p&gt;
&lt;p&gt;검색용 자식 청크나 요약이 적중하면 답변에는 더 넓은 부모 문서를 돌려줄 수 있다. 이때 검색 단위와 합성 단위가 나뉜다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;단위&lt;/th&gt;
&lt;th&gt;목적&lt;/th&gt;
&lt;th&gt;예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;검색 표현&lt;/td&gt;
&lt;td&gt;질문과 잘 대응하는 후보 찾기&lt;/td&gt;
&lt;td&gt;짧은 청크, 요약, 예상 질문&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;합성 자료&lt;/td&gt;
&lt;td&gt;생성 모델이 사실과 조건을 읽기&lt;/td&gt;
&lt;td&gt;원문 절, 부모 청크, 표, 이미지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;연결 정보&lt;/td&gt;
&lt;td&gt;검색 결과에서 원문 복원&lt;/td&gt;
&lt;td&gt;부모 ID, 문서 ID, 위치&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;검색 표현은 색인이지 최종 사실의 대체물이 아니다. 요약에 생긴 오류를 답변의 근거로 쓰지 않으려면 원문과 연결을 유지해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="83-분할-전략"&gt;8.3 분할 전략&lt;span class="hx:absolute hx:-mt-20" id="83-분할-전략"&gt;&lt;/span&gt;
&lt;a href="#83-%eb%b6%84%ed%95%a0-%ec%a0%84%eb%9e%b5" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="831-구조-기반과-크기-기반"&gt;8.3.1 구조 기반과 크기 기반&lt;span class="hx:absolute hx:-mt-20" id="831-구조-기반과-크기-기반"&gt;&lt;/span&gt;
&lt;a href="#831-%ea%b5%ac%ec%a1%b0-%ea%b8%b0%eb%b0%98%ea%b3%bc-%ed%81%ac%ea%b8%b0-%ea%b8%b0%eb%b0%98" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;구조 기반 분할은 장, 절, 제목과 문단 같은 원문의 경계를 따른다. 같은 주제의 문맥을 유지하기 쉽지만 청크 길이가 고르지 않을 수 있다.&lt;/p&gt;
&lt;p&gt;크기 기반 분할은 문자, 토큰, 문장 등의 길이를 기준으로 나눈다. 처리량과 길이 예산을 관리하기 쉽지만 정의와 예외, 표 제목과 표 본문을 갈라 놓을 수 있다.&lt;/p&gt;
&lt;p&gt;두 방식은 조합할 수 있다. 먼저 제목 단위로 나누고 너무 긴 절만 토큰 기준으로 다시 나누는 식이다. 제목 계층을 메타데이터로 보존하면 조각의 배경을 복원하는 데 도움이 된다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="832-고려할-조건"&gt;8.3.2 고려할 조건&lt;span class="hx:absolute hx:-mt-20" id="832-고려할-조건"&gt;&lt;/span&gt;
&lt;a href="#832-%ea%b3%a0%eb%a0%a4%ed%95%a0-%ec%a1%b0%ea%b1%b4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;문서 형식과 질문 유형을 함께 봐야 한다. API 문서에서 특정 파라미터를 묻는 질문과 제품 전반의 차이를 묻는 질문은 필요한 범위가 다르다.&lt;/p&gt;
&lt;p&gt;표와 이미지가 섞인 문서는 앞뒤 설명을 떼면 의미가 깨질 수 있다. 숫자가 속한 행과 열의 제목, 단위, 각주까지 유지해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="833-선택-기준"&gt;8.3.3 선택 기준&lt;span class="hx:absolute hx:-mt-20" id="833-선택-기준"&gt;&lt;/span&gt;
&lt;a href="#833-%ec%84%a0%ed%83%9d-%ea%b8%b0%ec%a4%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;청크 크기에 일반적인 정답은 없다. 대표 질문에 필요한 근거가 반환되는지, 반환된 원문만으로 답할 조건을 이해할 수 있는지 비교해야 한다.&lt;/p&gt;
&lt;p&gt;작은 청크에서 Recall@k가 좋아져도 모델에 전달할 문맥이 너무 짧으면 최종 답은 나빠질 수 있다. 반대로 부모 청크를 많이 넣으면 검색은 정확해도 문맥 비용이 커진다.&lt;/p&gt;
&lt;p&gt;평가에는 검색된 근거와 최종 답을 따로 남긴다. 청크 크기만 바꾸고 반환 개수와 문맥 길이를 무시하면 비교가 공정하지 않을 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="834-html-제목으로-분할"&gt;8.3.4 HTML 제목으로 분할&lt;span class="hx:absolute hx:-mt-20" id="834-html-제목으로-분할"&gt;&lt;/span&gt;
&lt;a href="#834-html-%ec%a0%9c%eb%aa%a9%ec%9c%bc%eb%a1%9c-%eb%b6%84%ed%95%a0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;HTMLHeaderTextSplitter&lt;/code&gt;는 HTML의 제목 계층을 기준으로 나눌 수 있다. 여행 문서라면 지역 아래 교통, 볼거리, 숙박 같은 절을 유지하는 데 유용하다.&lt;/p&gt;
&lt;p&gt;다만 실제 웹페이지의 제목 구조가 의미를 잘 표현한다는 보장은 없다. 메뉴와 반복 제목이 섞이거나 본문이 잘못 중첩될 수 있으므로 추출된 청크와 메타데이터를 직접 확인해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="84-임베딩-전략"&gt;8.4 임베딩 전략&lt;span class="hx:absolute hx:-mt-20" id="84-임베딩-전략"&gt;&lt;/span&gt;
&lt;a href="#84-%ec%9e%84%eb%b2%a0%eb%94%a9-%ec%a0%84%eb%9e%b5" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;figure&gt;
&lt;img src="https://johklo.github.io/images/ai-agents-and-applications/figure-8-04.png" title="그림 8.4. 부모와 자식 청크를 이용한 검색. 출처: AI Agents and Applications, 8장." alt="큰 원문 청크와 그 안의 작은 자식 청크를 각각 검색 표현으로 연결하는 구조. 넓은 질문은 큰 청크와, 세부 질문은 자식 청크와 대응할 수 있다." loading="lazy" /&gt;
&lt;figcaption&gt;그림 8.4. 부모와 자식 청크를 이용한 검색. 출처: AI Agents and Applications, 8장.&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;그림에서 중요한 것은 검색에 적중한 작은 내용으로부터 답변에 필요한 넓은 원문을 찾는 연결이다. 모든 구현이 부모와 자식 벡터를 둘 다 자동 생성한다는 뜻은 아니다. 사용하는 Retriever에 따라 실제로 색인하는 대상과 반환하는 대상이 다르다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="841-parentdocumentretriever"&gt;8.4.1 ParentDocumentRetriever&lt;span class="hx:absolute hx:-mt-20" id="841-parentdocumentretriever"&gt;&lt;/span&gt;
&lt;a href="#841-parentdocumentretriever" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;원문을 부모 청크로 나누고 그 안을 더 작은 자식 청크로 나눈다. 검색은 자식의 임베딩을 이용하고, 일치한 자식이 가리키는 부모를 문서 저장소에서 가져오는 것이 기본 패턴이다.&lt;/p&gt;
&lt;p&gt;“공항에서 역으로 가는 마지막 버스 시간” 같은 질문이 짧은 문장에 적중해도, 주변 문단을 함께 받으면 운행 요일과 예외 조건을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;부모가 지나치게 크면 관련 없는 내용까지 반환한다. 여러 자식이 같은 부모를 가리키면 부모의 중복을 제거해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="842-multivectorretriever"&gt;8.4.2 MultiVectorRetriever&lt;span class="hx:absolute hx:-mt-20" id="842-multivectorretriever"&gt;&lt;/span&gt;
&lt;a href="#842-multivectorretriever" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;하나의 원문에 여러 검색 표현을 연결하는 더 일반적인 구조다. 벡터 저장소에는 검색 표현과 부모 ID를, 문서 저장소에는 ID에 대응하는 원문을 둔다.&lt;/p&gt;
&lt;p&gt;검색 표현은 자식 청크일 수도 있고 요약이나 예상 질문일 수도 있다. 핵심은 한 원문과 여러 벡터의 다대일 관계다.&lt;/p&gt;
&lt;p&gt;두 저장소 중 하나만 갱신하면 연결이 깨진다. 벡터에는 ID가 있는데 원문이 없거나 원문은 바뀌었는데 오래된 요약 벡터가 남을 수 있다. 재색인과 삭제도 두 계층에서 일관되게 처리해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="843-문서-요약-임베딩"&gt;8.4.3 문서 요약 임베딩&lt;span class="hx:absolute hx:-mt-20" id="843-문서-요약-임베딩"&gt;&lt;/span&gt;
&lt;a href="#843-%eb%ac%b8%ec%84%9c-%ec%9a%94%ec%95%bd-%ec%9e%84%eb%b2%a0%eb%94%a9" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;긴 청크 전체보다 요약이 주제를 뚜렷하게 나타낼 수 있다. 요약의 임베딩으로 후보를 찾고 원문을 반환하면 검색 표현의 간결함과 원문의 상세함을 나눠 활용할 수 있다.&lt;/p&gt;
&lt;p&gt;하지만 요약에서 빠진 정보는 그 검색 표현으로 찾기 어려워진다. 원문 임베딩과 요약 임베딩을 함께 사용할지, 어떤 질문에서 각각 유효한지 확인해야 한다.&lt;/p&gt;
&lt;p&gt;요약 생성 비용도 인덱싱 비용에 포함된다. 원문이 바뀌면 요약과 그 임베딩도 다시 만들어야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="844-가상-질문-임베딩"&gt;8.4.4 가상 질문 임베딩&lt;span class="hx:absolute hx:-mt-20" id="844-가상-질문-임베딩"&gt;&lt;/span&gt;
&lt;a href="#844-%ea%b0%80%ec%83%81-%ec%a7%88%eb%ac%b8-%ec%9e%84%eb%b2%a0%eb%94%a9" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;각 원문이 답할 수 있는 질문을 모델로 생성해 색인한다. 사용자의 질문과 원문의 서술 방식이 다를 때 질문끼리의 유사성을 이용하려는 방법이다.&lt;/p&gt;
&lt;p&gt;예를 들어 원문이 버스 노선과 시간표를 설명한다면 “차 없이 이곳에 갈 수 있는가?” 같은 질문을 색인할 수 있다. 검색에 적중하면 생성한 질문이 아니라 실제 원문을 반환한다.&lt;/p&gt;
&lt;p&gt;생성한 질문이 원문에서 답할 수 없는 내용을 묻는다면 잘못된 후보를 끌어올 수 있다. 질문 수를 늘리는 것뿐 아니라 실제로 해당 원문이 근거가 되는지도 확인해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="85-작은-청크의-문맥-확장"&gt;8.5 작은 청크의 문맥 확장&lt;span class="hx:absolute hx:-mt-20" id="85-작은-청크의-문맥-확장"&gt;&lt;/span&gt;
&lt;a href="#85-%ec%9e%91%ec%9d%80-%ec%b2%ad%ed%81%ac%ec%9d%98-%eb%ac%b8%eb%a7%a5-%ed%99%95%ec%9e%a5" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;검색은 작은 청크로 하고 일치한 위치 앞뒤의 청크를 추가한다. 부모 청크 반환과 비슷하지만 고정된 부모 경계 대신 문서 위치를 기준으로 주변을 가져올 수 있다.&lt;/p&gt;
&lt;p&gt;필요한 메타데이터는 원문 ID와 청크 순서다. 다른 문서의 이웃 청크를 잘못 붙이거나 문서 경계 밖으로 넘어가면 안 된다.&lt;/p&gt;
&lt;p&gt;여러 검색 결과의 확장 구간이 겹치면 중복을 합쳐야 한다. 청크 하나가 적중했다고 앞뒤를 무조건 많이 넣으면 검색 정밀도의 이점이 사라진다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="86-반정형-자료"&gt;8.6 반정형 자료&lt;span class="hx:absolute hx:-mt-20" id="86-반정형-자료"&gt;&lt;/span&gt;
&lt;a href="#86-%eb%b0%98%ec%a0%95%ed%98%95-%ec%9e%90%eb%a3%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;표는 문장처럼 잘라 처리하면 행과 열의 관계를 잃을 수 있다. 책은 표의 요약을 검색 표현으로 만들고 실제 표는 별도로 보관하는 방식을 다룬다.&lt;/p&gt;
&lt;p&gt;질문이 표 요약에 적중하면 모델에는 원래 표를 전달한다. 요약은 “지역별 요금 비교표”라는 검색 단서가 될 수 있지만 실제 요금을 답하려면 행과 열의 값이 필요하다.&lt;/p&gt;
&lt;p&gt;단위와 날짜, 각주도 표의 일부다. 모델에 표만 던지고 열의 의미를 추측하게 하는 것보다 구조를 명시적으로 보존해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="87-멀티모달-rag"&gt;8.7 멀티모달 RAG&lt;span class="hx:absolute hx:-mt-20" id="87-멀티모달-rag"&gt;&lt;/span&gt;
&lt;a href="#87-%eb%a9%80%ed%8b%b0%eb%aa%a8%eb%8b%ac-rag" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;이미지 설명을 텍스트로 생성해 검색하고, 적중한 이미지 원본을 답변 모델에 보내는 구조로 확장할 수 있다. 또 다른 선택은 이미지와 텍스트를 직접 비교하는 멀티모달 임베딩이다.&lt;/p&gt;
&lt;p&gt;텍스트 요약만 받는 모델과 이미지까지 이해하는 모델은 전달할 수 있는 근거가 다르다. 차트의 수치나 도형 관계가 설명에서 빠지면 텍스트 경로만으로 정확한 답을 얻기 어려울 수 있다.&lt;/p&gt;
&lt;p&gt;원본 이미지의 해상도와 자르기, OCR과 설명 품질도 영향을 준다. 검색된 이미지가 맞는지와 모델이 이미지를 정확히 읽었는지는 따로 평가해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="질문"&gt;질문&lt;span class="hx:absolute hx:-mt-20" id="질문"&gt;&lt;/span&gt;
&lt;a href="#%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="작은-청크를-검색했으면-그대로-답변에-넣어야-할까"&gt;작은 청크를 검색했으면 그대로 답변에 넣어야 할까?&lt;span class="hx:absolute hx:-mt-20" id="작은-청크를-검색했으면-그대로-답변에-넣어야-할까"&gt;&lt;/span&gt;
&lt;a href="#%ec%9e%91%ec%9d%80-%ec%b2%ad%ed%81%ac%eb%a5%bc-%ea%b2%80%ec%83%89%ed%96%88%ec%9c%bc%eb%a9%b4-%ea%b7%b8%eb%8c%80%eb%a1%9c-%eb%8b%b5%eb%b3%80%ec%97%90-%eb%84%a3%ec%96%b4%ec%95%bc-%ed%95%a0%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;검색 표현과 답변 근거의 크기는 다를 수 있다. 필요한 조건을 읽을 수 있도록 부모나 주변 문맥을 반환하는 방법이 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="한-문서에-벡터를-많이-붙이면-항상-좋아질까"&gt;한 문서에 벡터를 많이 붙이면 항상 좋아질까?&lt;span class="hx:absolute hx:-mt-20" id="한-문서에-벡터를-많이-붙이면-항상-좋아질까"&gt;&lt;/span&gt;
&lt;a href="#%ed%95%9c-%eb%ac%b8%ec%84%9c%ec%97%90-%eb%b2%a1%ed%84%b0%eb%a5%bc-%eb%a7%8e%ec%9d%b4-%eb%b6%99%ec%9d%b4%eb%a9%b4-%ed%95%ad%ec%83%81-%ec%a2%8b%ec%95%84%ec%a7%88%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;중복 후보와 색인 비용이 늘고 부정확한 검색 표현이 추가될 수 있다. 어떤 질문을 더 잘 찾는지와 문맥 비용을 함께 비교해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;a href="../07-conversational-rag/"&gt;이전: 대화형 RAG와 LangSmith&lt;/a&gt; / &lt;a href="../"&gt;시리즈 목차&lt;/a&gt; / &lt;a href="../09-question-transformations/"&gt;다음: 질문 변환&lt;/a&gt;&lt;/p&gt;</description></item><item><title>09. 질문 변환</title><link>https://johklo.github.io/docs/tech/ai-agents-and-applications/09-question-transformations/</link><pubDate>Mon, 19 Oct 2026 21:37:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/ai-agents-and-applications/09-question-transformations/</guid><description>
&lt;p&gt;검색이 실패했다고 원문을 다시 나누는 것만이 해결책은 아니다. 사용자는 문서에 쓰인 표현과 다른 말로 질문하거나, 여러 요구를 한 문장에 넣을 수 있다. 이번 장은 문서 인덱스 대신 검색에 들어가는 질문을 바꾼다.&lt;/p&gt;
&lt;p&gt;검색용 질의와 사용자에게 답해야 할 질문은 구분해야 한다. 검색어를 바꾸더라도 최종 응답의 목적은 원래 질문에 답하는 것이다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="91-rewrite-retrieve-read"&gt;9.1 Rewrite-Retrieve-Read&lt;span class="hx:absolute hx:-mt-20" id="91-rewrite-retrieve-read"&gt;&lt;/span&gt;
&lt;a href="#91-rewrite-retrieve-read" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;기본 RAG는 질문을 바로 검색에 넣고 결과로 답한다. 여기에 검색 전 재작성 단계를 붙인 구조가 Rewrite-Retrieve-Read다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="911-원래-질문으로-검색"&gt;9.1.1 원래 질문으로 검색&lt;span class="hx:absolute hx:-mt-20" id="911-원래-질문으로-검색"&gt;&lt;/span&gt;
&lt;a href="#911-%ec%9b%90%eb%9e%98-%ec%a7%88%eb%ac%b8%ec%9c%bc%eb%a1%9c-%ea%b2%80%ec%83%89" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;먼저 원문 질문의 검색 결과를 기준선으로 확인한다. “Cornwall에서 재미있게 할 만한 일”은 자연스럽지만 취미, 가족 활동, 축제와 해변 중 어떤 내용을 찾는지 넓다.&lt;/p&gt;
&lt;p&gt;검색 결과가 부족한 이유가 표현 때문인지, 자료 자체가 없기 때문인지 구분해야 한다. 질의를 바꾸기 전에 어떤 근거를 기대하는지와 현재 반환된 자료를 남겨야 비교가 가능하다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="912-재작성-체인"&gt;9.1.2 재작성 체인&lt;span class="hx:absolute hx:-mt-20" id="912-재작성-체인"&gt;&lt;/span&gt;
&lt;a href="#912-%ec%9e%ac%ec%9e%91%ec%84%b1-%ec%b2%b4%ec%9d%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;질문을 검색에 적합한 형태로 바꾸도록 모델에 요청한다. 불필요한 대화 표현을 줄이고, 지역과 주제, 조건을 명시하는 방식이다.&lt;/p&gt;
&lt;p&gt;재작성은 원래 질문의 조건을 유지해야 한다. “차 없이 방문할 곳”에서 교통 조건이 사라지거나 “주말”이 특정 날짜로 잘못 바뀌면 검색이 풍부해져도 목적에 맞지 않는다.&lt;/p&gt;
&lt;p&gt;모호한 부분을 모델이 임의로 확정하는 것과 질의를 명료하게 만드는 것도 다르다. 날짜나 대상이 반드시 필요한데 알 수 없다면 사용자에게 확인하는 편이 맞을 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="913-재작성된-질의로-검색"&gt;9.1.3 재작성된 질의로 검색&lt;span class="hx:absolute hx:-mt-20" id="913-재작성된-질의로-검색"&gt;&lt;/span&gt;
&lt;a href="#913-%ec%9e%ac%ec%9e%91%ec%84%b1%eb%90%9c-%ec%a7%88%ec%9d%98%eb%a1%9c-%ea%b2%80%ec%83%89" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;같은 검색기와 반환 개수로 원래 질문과 새 질의를 비교한다. 어떤 근거가 새로 들어왔고 무엇이 빠졌는지 볼 수 있다.&lt;/p&gt;
&lt;p&gt;응답 문장이 좋아 보인다는 이유만으로 재작성이 성공했다고 판단하면 안 된다. 최종 생성의 변화와 검색 결과의 변화를 나눠 봐야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="914-전체-체인"&gt;9.1.4 전체 체인&lt;span class="hx:absolute hx:-mt-20" id="914-전체-체인"&gt;&lt;/span&gt;
&lt;a href="#914-%ec%a0%84%ec%b2%b4-%ec%b2%b4%ec%9d%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;검색 경로에는 재작성 질의를, 최종 생성에는 원래 질문을 전달한다. 원래 질문을 보존하지 않으면 답변도 검색어를 작성한 모델의 해석에 끌려갈 수 있다.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;원래 질문 → 재작성 → 검색 → 근거
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;원래 질문 ──────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;근거 ──────────────────────┴→ 최종 답변&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;재작성으로 추가된 조건과 실제 검색 문장을 기록하면 의도 왜곡을 조사하기 쉽다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="92-여러-질의-생성"&gt;9.2 여러 질의 생성&lt;span class="hx:absolute hx:-mt-20" id="92-여러-질의-생성"&gt;&lt;/span&gt;
&lt;a href="#92-%ec%97%ac%eb%9f%ac-%ec%a7%88%ec%9d%98-%ec%83%9d%ec%84%b1" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;한 가지 표현으로 관련 자료를 모두 찾기 어려울 때 여러 관점이나 표현으로 검색한다. 같은 질문의 표현 변형과 복합 질문의 하위 문제 분해는 목적이 조금 다르다.&lt;/p&gt;
&lt;p&gt;표현 변형은 같은 정보를 다른 말로 찾는 방식이다. 분해는 교통, 숙박, 활동처럼 각각 다른 정보를 모으는 방식이다. 둘을 섞어 쓰더라도 원래 요구가 모두 다뤄졌는지 확인해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="921-질의-생성-체인"&gt;9.2.1 질의 생성 체인&lt;span class="hx:absolute hx:-mt-20" id="921-질의-생성-체인"&gt;&lt;/span&gt;
&lt;a href="#921-%ec%a7%88%ec%9d%98-%ec%83%9d%ec%84%b1-%ec%b2%b4%ec%9d%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;원래 질문으로 여러 검색 문장을 생성한다. 질의 수를 고정하면 실행 예산을 관리하기 쉽지만 모든 질문에 같은 개수가 필요한 것은 아니다.&lt;/p&gt;
&lt;p&gt;예를 들어 “기차로 이동하기 좋은 해안 마을과 가족 숙소”라면 장소 접근성과 숙박 정보를 나눠 찾을 수 있다. 유사한 문장만 다섯 개 만들었다면 검색 범위는 기대만큼 넓어지지 않을 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="922-사용자-정의-multiqueryretriever"&gt;9.2.2 사용자 정의 MultiQueryRetriever&lt;span class="hx:absolute hx:-mt-20" id="922-사용자-정의-multiqueryretriever"&gt;&lt;/span&gt;
&lt;a href="#922-%ec%82%ac%ec%9a%a9%ec%9e%90-%ec%a0%95%ec%9d%98-multiqueryretriever" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;질의 생성 프롬프트와 출력 파서, 실제 검색기를 직접 조합한다. 각 질의 결과를 합칠 때 동일 문서나 동일 청크의 중복을 구분해야 한다.&lt;/p&gt;
&lt;p&gt;질의가 4개이고 각각 5개를 받으면 후보는 최대 20개지만 유효한 근거가 20개라는 뜻은 아니다. 중복과 주제 이탈, 문맥 예산을 고려해 반환할 자료를 선택해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MultiQueryRetriever&lt;/code&gt;는 일반적으로 질의 생성과 검색 결과 통합을 담당한다. 최종 자연어 답변은 그 결과를 프롬프트와 모델에 연결하는 별도 생성 단계다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="923-기본-구성-사용"&gt;9.2.3 기본 구성 사용&lt;span class="hx:absolute hx:-mt-20" id="923-기본-구성-사용"&gt;&lt;/span&gt;
&lt;a href="#923-%ea%b8%b0%eb%b3%b8-%ea%b5%ac%ec%84%b1-%ec%82%ac%ec%9a%a9" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;표준 생성 함수를 사용하면 질의 변형과 검색을 빠르게 조합할 수 있다. 그러나 기본 프롬프트가 서비스의 언어와 대상, 날짜 조건에 맞는지는 확인해야 한다.&lt;/p&gt;
&lt;p&gt;사용자 정의와 기본 구성의 차이는 코드 양만이 아니다. 어떤 질의가 생성됐고 중복을 어떻게 합쳤는지 관측할 수 있어야 문제를 개선할 수 있다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="93-step-back-질문"&gt;9.3 Step-back 질문&lt;span class="hx:absolute hx:-mt-20" id="93-step-back-질문"&gt;&lt;/span&gt;
&lt;a href="#93-step-back-%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;세부 질문을 더 일반적인 질문으로 바꿔 배경 원리나 넓은 문맥을 검색한다. 특정 장소의 특정 교통편만 찾는 대신 해당 지역의 교통 체계를 함께 확인하는 식이다.&lt;/p&gt;
&lt;p&gt;세부 조건을 없애는 것이 목적은 아니다. 일반 배경과 구체 근거를 함께 얻어 답변의 맥락을 보완한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="931-step-back-생성"&gt;9.3.1 Step-back 생성&lt;span class="hx:absolute hx:-mt-20" id="931-step-back-생성"&gt;&lt;/span&gt;
&lt;a href="#931-step-back-%ec%83%9d%ec%84%b1" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;질문에서 핵심 개념을 추려 덜 구체적인 질문을 만든다. 특정 상품 환불 사례라면 해당 상품군의 환불 정책을 묻는 질문으로 넓힐 수 있다.&lt;/p&gt;
&lt;p&gt;너무 넓히면 실제 문제와 관련 없는 문서가 나온다. 어떤 배경이 부족한지 모른 채 무조건 일반 질문을 추가하면 비용과 잡음만 늘 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="932-rag에-통합"&gt;9.3.2 RAG에 통합&lt;span class="hx:absolute hx:-mt-20" id="932-rag에-통합"&gt;&lt;/span&gt;
&lt;a href="#932-rag%ec%97%90-%ed%86%b5%ed%95%a9" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;원래 질문의 검색 결과와 Step-back 질문의 결과를 함께 사용할 수 있다. 어떤 자료가 배경이고 어떤 자료가 직접 답을 뒷받침하는지 구분해 구성한다.&lt;/p&gt;
&lt;p&gt;넓은 규칙과 구체적 예외가 충돌할 수 있다. 일반적인 정책을 찾았다고 특정 계약의 예외를 덮어쓰면 안 된다. 최종 답은 원래 질문의 대상과 조건을 기준으로 작성해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="94-hyde"&gt;9.4 HyDE&lt;span class="hx:absolute hx:-mt-20" id="94-hyde"&gt;&lt;/span&gt;
&lt;a href="#94-hyde" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;HyDE는 Hypothetical Document Embeddings다. 질문에 답할 법한 가상 문서를 생성하고 그 문서를 검색용 임베딩으로 사용하는 방식이다.&lt;/p&gt;
&lt;p&gt;8장의 가상 질문 임베딩은 문서 적재 시 원문에서 예상 질문을 만드는 쪽이다. HyDE는 질문이 들어올 때 가상 답변이나 문서를 만든다. 처리 시점과 바꾸는 대상이 다르다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="941-가상-문서-생성"&gt;9.4.1 가상 문서 생성&lt;span class="hx:absolute hx:-mt-20" id="941-가상-문서-생성"&gt;&lt;/span&gt;
&lt;a href="#941-%ea%b0%80%ec%83%81-%eb%ac%b8%ec%84%9c-%ec%83%9d%ec%84%b1" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;질문이 짧거나 실제 자료의 서술 방식과 다를 때, 답변 형태의 문장을 만들어 문서 표현에 가까워지도록 한다.&lt;/p&gt;
&lt;p&gt;중요한 점은 이 문장이 근거가 아니라 검색 도구라는 것이다. 모델이 상상한 요금과 장소가 들어갈 수 있다. 그 내용을 사실로 사용자에게 보내거나 근거 문서와 섞으면 환각을 검색 단계에서 확대할 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="942-전체-rag-연결"&gt;9.4.2 전체 RAG 연결&lt;span class="hx:absolute hx:-mt-20" id="942-전체-rag-연결"&gt;&lt;/span&gt;
&lt;a href="#942-%ec%a0%84%ec%b2%b4-rag-%ec%97%b0%ea%b2%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;가상 문서로 실제 저장소를 검색한 뒤, 반환된 실제 원문과 원래 질문으로 최종 답을 생성한다.&lt;/p&gt;
&lt;div role="img" aria-label="다이어그램"&gt;
&lt;pre class="mermaid hx:mt-6"&gt;
flowchart TD
accTitle: HyDE에서 가상 문서와 실제 근거의 구분
accDescr: 질문으로 생성한 가상 문서는 검색 벡터를 만드는 데만 사용한다. 최종 답변은 실제 저장소에서 반환한 원문과 원래 질문을 사용한다.
Q[&amp;#34;사용자 질문&amp;#34;] --&amp;gt; H[&amp;#34;가상 답변 문서&amp;#34;]
H --&amp;gt; E[&amp;#34;임베딩과 검색&amp;#34;]
D[&amp;#34;실제 문서 저장소&amp;#34;] --&amp;gt; E
E --&amp;gt; R[&amp;#34;실제 원문 근거&amp;#34;]
R --&amp;gt; A[&amp;#34;최종 답변 생성&amp;#34;]
Q --&amp;gt; A
&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;가상 문서가 잘못된 방향을 잡으면 검색도 그쪽으로 끌릴 수 있다. 원래 질문으로 검색한 결과와 비교해 어떤 질문 유형에서 도움이 되는지 확인해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="95-단일-단계와-다단계-분해"&gt;9.5 단일 단계와 다단계 분해&lt;span class="hx:absolute hx:-mt-20" id="95-단일-단계와-다단계-분해"&gt;&lt;/span&gt;
&lt;a href="#95-%eb%8b%a8%ec%9d%bc-%eb%8b%a8%ea%b3%84%ec%99%80-%eb%8b%a4%eb%8b%a8%ea%b3%84-%eb%b6%84%ed%95%b4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;독립적인 하위 질문은 한 번에 나눠 병렬 처리할 수 있다. 두 지역의 교통수단을 비교한다면 각각의 정보를 독립적으로 모을 수 있다.&lt;/p&gt;
&lt;p&gt;반면 “조건에 맞는 마을을 고르고 그곳의 숙박을 찾아라”는 앞 단계에서 마을이 정해져야 다음 검색을 할 수 있다. 이를 독립 질의 두 개로 무조건 병렬화하면 두 번째 질의에 필요한 대상이 없다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;방식&lt;/th&gt;
&lt;th&gt;의존성&lt;/th&gt;
&lt;th&gt;예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;표현 변형&lt;/td&gt;
&lt;td&gt;같은 목적의 다른 검색 문장&lt;/td&gt;
&lt;td&gt;같은 정책을 다른 말로 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;독립 분해&lt;/td&gt;
&lt;td&gt;하위 질문을 따로 처리 가능&lt;/td&gt;
&lt;td&gt;두 지역의 교통 비교&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;순차 분해&lt;/td&gt;
&lt;td&gt;이전 결과가 다음 입력&lt;/td&gt;
&lt;td&gt;장소 선정 후 해당 장소의 숙소 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;여러 단계를 수행하면 앞의 오류가 뒤로 전파된다. 장소를 잘못 골랐는데 숙박 검색이 정확해도 전체 요청은 실패다. 중간 결과가 원래 조건을 만족하는지 확인해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="선택할-때의-기준"&gt;선택할 때의 기준&lt;span class="hx:absolute hx:-mt-20" id="선택할-때의-기준"&gt;&lt;/span&gt;
&lt;a href="#%ec%84%a0%ed%83%9d%ed%95%a0-%eb%95%8c%ec%9d%98-%ea%b8%b0%ec%a4%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;질의가 모호하면 재작성, 표현 차이가 크면 다중 질의나 HyDE, 배경이 부족하면 Step-back, 정보가 여러 영역에 흩어져 있으면 분해를 검토할 수 있다.&lt;/p&gt;
&lt;p&gt;이 구분은 고정된 정답표는 아니다. 대표 질문으로 검색 근거의 적중률과 중복, 최종 답의 정확도, 호출 수와 지연을 비교해야 한다. 개선 기법마다 추가되는 모델 호출도 비용이다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="질문"&gt;질문&lt;span class="hx:absolute hx:-mt-20" id="질문"&gt;&lt;/span&gt;
&lt;a href="#%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="hyde가-만든-문장을-최종-답에-사용해도-될까"&gt;HyDE가 만든 문장을 최종 답에 사용해도 될까?&lt;span class="hx:absolute hx:-mt-20" id="hyde가-만든-문장을-최종-답에-사용해도-될까"&gt;&lt;/span&gt;
&lt;a href="#hyde%ea%b0%80-%eb%a7%8c%eb%93%a0-%eb%ac%b8%ec%9e%a5%ec%9d%84-%ec%b5%9c%ec%a2%85-%eb%8b%b5%ec%97%90-%ec%82%ac%ec%9a%a9%ed%95%b4%eb%8f%84-%eb%90%a0%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;검색을 돕기 위해 생성한 가설일 뿐이다. 실제 근거와 구분해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="여러-질의를-만들면-질문-분해가-끝난-것일까"&gt;여러 질의를 만들면 질문 분해가 끝난 것일까?&lt;span class="hx:absolute hx:-mt-20" id="여러-질의를-만들면-질문-분해가-끝난-것일까"&gt;&lt;/span&gt;
&lt;a href="#%ec%97%ac%eb%9f%ac-%ec%a7%88%ec%9d%98%eb%a5%bc-%eb%a7%8c%eb%93%a4%eb%a9%b4-%ec%a7%88%eb%ac%b8-%eb%b6%84%ed%95%b4%ea%b0%80-%eb%81%9d%eb%82%9c-%ea%b2%83%ec%9d%bc%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;하위 질문이 독립적인지, 결과를 어떤 순서로 연결해야 하는지까지 정해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;a href="../08-advanced-indexing/"&gt;이전: 고급 인덱싱&lt;/a&gt; / &lt;a href="../"&gt;시리즈 목차&lt;/a&gt; / &lt;a href="../10-query-routing/"&gt;다음: 질의 생성과 라우팅&lt;/a&gt;&lt;/p&gt;</description></item><item><title>10. 질의 생성과 라우팅</title><link>https://johklo.github.io/docs/tech/ai-agents-and-applications/10-query-routing/</link><pubDate>Mon, 19 Oct 2026 21:37:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/ai-agents-and-applications/10-query-routing/</guid><description>
&lt;p&gt;모든 정보를 벡터 검색으로 찾는 것은 비효율적일 수 있다. 지역 소개는 문서 검색에 맞지만 특정 날짜의 빈 객실 수와 가격은 구조화된 DB에서 확인하는 편이 정확하다. 이 장은 자연어 질문을 저장소가 이해하는 질의로 바꾸고, 결과를 정리해 생성 모델에 전달한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="101-자료-저장소에-맞는-질의"&gt;10.1 자료 저장소에 맞는 질의&lt;span class="hx:absolute hx:-mt-20" id="101-자료-저장소에-맞는-질의"&gt;&lt;/span&gt;
&lt;a href="#101-%ec%9e%90%eb%a3%8c-%ec%a0%80%ec%9e%a5%ec%86%8c%ec%97%90-%eb%a7%9e%eb%8a%94-%ec%a7%88%ec%9d%98" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;벡터 저장소는 유사한 자료를 찾고 관계형 DB는 명시적인 조건과 관계로 행을 조회한다. 그래프 DB는 개체 사이의 관계를 따라간다.&lt;/p&gt;
&lt;p&gt;같은 “숙박 정보”라도 주변 분위기와 관광지 접근성은 문서에, 현재 가격과 재고는 거래 시스템에 있을 수 있다. 질문의 어떤 부분에 어떤 데이터가 필요한지 나눠야 한다.&lt;/p&gt;
&lt;p&gt;Dense retrieval은 임베딩 공간의 유사성을, sparse 또는 lexical retrieval은 단어의 출현과 가중치를 활용한다. 메타데이터 필터는 그와 별도로 지역, 날짜, 문서 유형 같은 조건을 적용한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="102-self-querying"&gt;10.2 Self-querying&lt;span class="hx:absolute hx:-mt-20" id="102-self-querying"&gt;&lt;/span&gt;
&lt;a href="#102-self-querying" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Self-querying은 자연어 요청에서 의미 검색용 문장과 구조화된 필터를 함께 만드는 방식이다. “Cornwall에 있는 박물관”에서 박물관을 검색할 표현과 지역 필터를 나눌 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1021-메타데이터-보강"&gt;10.2.1 메타데이터 보강&lt;span class="hx:absolute hx:-mt-20" id="1021-메타데이터-보강"&gt;&lt;/span&gt;
&lt;a href="#1021-%eb%a9%94%ed%83%80%eb%8d%b0%ec%9d%b4%ed%84%b0-%eb%b3%b4%ea%b0%95" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;검색할 기록에 지역, 출처, 자료 유형과 날짜 같은 필드를 준비한다. 필터는 실제 저장한 값과 타입이 맞아야 동작한다.&lt;/p&gt;
&lt;p&gt;메타데이터를 모델로 생성한다면 틀린 태그도 들어갈 수 있다. 문서의 실제 지역과 모델이 추정한 지역을 구분하고, 허용 값과 누락 규칙을 정해야 한다.&lt;/p&gt;
&lt;p&gt;TF-IDF나 BM25는 주로 단어 기반 검색의 가중치와 순위를 위한 기법이다. 지역 같은 명시적 메타데이터와 같은 것으로 취급하면 안 된다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1022-메타데이터가-있는-자료에-질의"&gt;10.2.2 메타데이터가 있는 자료에 질의&lt;span class="hx:absolute hx:-mt-20" id="1022-메타데이터가-있는-자료에-질의"&gt;&lt;/span&gt;
&lt;a href="#1022-%eb%a9%94%ed%83%80%eb%8d%b0%ec%9d%b4%ed%84%b0%ea%b0%80-%ec%9e%88%eb%8a%94-%ec%9e%90%eb%a3%8c%ec%97%90-%ec%a7%88%ec%9d%98" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;직접 필터를 지정하거나 &lt;code&gt;SelfQueryRetriever&lt;/code&gt;처럼 모델이 필터를 생성하는 구성 요소를 사용할 수 있다. 필드 설명과 허용 연산을 제공해야 잘못된 열 이름과 조건을 줄일 수 있다.&lt;/p&gt;
&lt;p&gt;예를 들어 &lt;code&gt;region = &amp;quot;Cornwall&amp;quot;&lt;/code&gt;과 의미 검색 문장 “가족이 방문할 박물관”을 함께 적용할 수 있다. 검색 대상이 줄어 정밀도가 좋아질 수 있지만 잘못 만든 필터는 필요한 자료를 완전히 제외한다.&lt;/p&gt;
&lt;p&gt;사용자 권한은 모델이 생성하는 필터에 맡기지 않는다. 애플리케이션이 허용한 범위를 별도로 강제한 뒤 그 안에서 검색 조건을 적용해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="103-구조화된-sql-생성"&gt;10.3 구조화된 SQL 생성&lt;span class="hx:absolute hx:-mt-20" id="103-구조화된-sql-생성"&gt;&lt;/span&gt;
&lt;a href="#103-%ea%b5%ac%ec%a1%b0%ed%99%94%eb%90%9c-sql-%ec%83%9d%ec%84%b1" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;자연어를 SQL로 바꾸려면 모델이 실제 테이블과 열, 관계를 알아야 한다. 문법이 맞는 SQL도 존재하지 않는 열을 쓰거나 조인을 잘못하면 답이 틀린다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1031-sqlite"&gt;10.3.1 SQLite&lt;span class="hx:absolute hx:-mt-20" id="1031-sqlite"&gt;&lt;/span&gt;
&lt;a href="#1031-sqlite" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;SQLite는 하나의 파일을 DB로 사용하고 별도 서버 없이 접근할 수 있어 실습에 편하다. 하지만 이 실행 방식이 외부 서비스와 같은 운영 조건을 제공한다는 뜻은 아니다.&lt;/p&gt;
&lt;p&gt;SQL 문법과 사용 가능한 기능도 DB별로 다르다. SQLite 예제를 그대로 PostgreSQL에 적용할 수 있다고 가정하면 안 된다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1032-db-구성과-연결"&gt;10.3.2 DB 구성과 연결&lt;span class="hx:absolute hx:-mt-20" id="1032-db-구성과-연결"&gt;&lt;/span&gt;
&lt;a href="#1032-db-%ea%b5%ac%ec%84%b1%ea%b3%bc-%ec%97%b0%ea%b2%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;여행 예제는 목적지, 숙소와 특가 정보를 테이블로 나누고 키로 연결한다. 모델에는 실제 스키마와 필요한 관계를 제공한다.&lt;/p&gt;
&lt;p&gt;숙소와 특가를 잘못 조인하면 같은 제안이 여러 번 나오거나 다른 지역의 가격이 붙을 수 있다. 테이블을 읽을 수 있는 것과 올바른 관계로 답하는 것은 다르다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1033-자연어에서-sql-생성"&gt;10.3.3 자연어에서 SQL 생성&lt;span class="hx:absolute hx:-mt-20" id="1033-자연어에서-sql-생성"&gt;&lt;/span&gt;
&lt;a href="#1033-%ec%9e%90%ec%97%b0%ec%96%b4%ec%97%90%ec%84%9c-sql-%ec%83%9d%ec%84%b1" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;질문, 스키마, DB 종류와 필요한 예시를 이용해 SQL을 만든다. 생성 결과에 코드 블록이나 설명이 붙을 수 있으므로 SQL 추출과 구문 검사도 필요하다.&lt;/p&gt;
&lt;p&gt;“이번 주말”처럼 상대적인 날짜를 사용하면 기준 날짜와 시간대가 명확해야 한다. “가장 저렴한”이라는 표현도 총액인지 1박 요금인지, 객실 수와 세금이 포함되는지에 따라 질의가 달라진다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1034-sql-실행"&gt;10.3.4 SQL 실행&lt;span class="hx:absolute hx:-mt-20" id="1034-sql-실행"&gt;&lt;/span&gt;
&lt;a href="#1034-sql-%ec%8b%a4%ed%96%89" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;생성과 실행은 분리된 책임이다. 모델이 SQL을 만들 수 있다는 이유로 운영 DB에 임의 실행 권한을 줘서는 안 된다.&lt;/p&gt;
&lt;p&gt;읽기 전용 자격 증명, 허용 테이블과 연산, 행 수와 실행 시간 제한을 적용한다. 사용자별 접근 범위도 DB 또는 애플리케이션에서 강제해야 한다.&lt;/p&gt;
&lt;p&gt;문자열에서 코드 블록 표시만 제거하는 것은 검증이 아니다. 쓰기나 관리 명령을 막고 의도한 조회만 허용하는 통제가 필요하다. 파라미터가 있는 값은 DB 드라이버의 바인딩을 사용하는 편이 안전하다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="104-의미-기반-sql"&gt;10.4 의미 기반 SQL&lt;span class="hx:absolute hx:-mt-20" id="104-의미-기반-sql"&gt;&lt;/span&gt;
&lt;a href="#104-%ec%9d%98%eb%af%b8-%ea%b8%b0%eb%b0%98-sql" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="1041-일반-sql"&gt;10.4.1 일반 SQL&lt;span class="hx:absolute hx:-mt-20" id="1041-일반-sql"&gt;&lt;/span&gt;
&lt;a href="#1041-%ec%9d%bc%eb%b0%98-sql" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;일반 SQL의 조건과 조인은 정확한 필드와 규칙을 기준으로 한다. 문자열의 일치나 패턴 검색, 수치 비교와 집계를 수행할 수 있다.&lt;/p&gt;
&lt;p&gt;의미적 유사성과 다르지만 표현력이 단순한 정확 일치에만 한정되는 것은 아니다. 전문 검색이나 DB별 확장 기능을 조합할 수도 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1042-의미-검색과-결합"&gt;10.4.2 의미 검색과 결합&lt;span class="hx:absolute hx:-mt-20" id="1042-의미-검색과-결합"&gt;&lt;/span&gt;
&lt;a href="#1042-%ec%9d%98%eb%af%b8-%ea%b2%80%ec%83%89%ea%b3%bc-%ea%b2%b0%ed%95%a9" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;pgvector 같은 확장을 사용하면 관계형 데이터와 벡터를 함께 두고 유사도 검색을 수행할 수 있다. “조용하고 해변 가까운 숙소”는 설명의 의미로 찾고 지역과 가격은 정확한 필터로 제한하는 식이다.&lt;/p&gt;
&lt;p&gt;이름의 유사성만으로 같은 사람이나 같은 지역을 확정하면 안 된다. 벡터는 후보 검색에 도움이 될 수 있지만 식별과 권한 판정은 별도 기준이 필요하다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1043-임베딩-생성"&gt;10.4.3 임베딩 생성&lt;span class="hx:absolute hx:-mt-20" id="1043-임베딩-생성"&gt;&lt;/span&gt;
&lt;a href="#1043-%ec%9e%84%eb%b2%a0%eb%94%a9-%ec%83%9d%ec%84%b1" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;검색할 설명 필드 등을 임베딩해 행과 연결한다. 모델, 차원과 입력 구성 방식을 고정하고 원문 변경 때 갱신해야 한다.&lt;/p&gt;
&lt;p&gt;값이 바뀐 행에 오래된 임베딩이 남으면 질의 결과의 순위와 현재 내용이 어긋난다. 데이터 갱신과 재임베딩의 일관성이 필요하다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1044-의미-검색-실행"&gt;10.4.4 의미 검색 실행&lt;span class="hx:absolute hx:-mt-20" id="1044-의미-검색-실행"&gt;&lt;/span&gt;
&lt;a href="#1044-%ec%9d%98%eb%af%b8-%ea%b2%80%ec%83%89-%ec%8b%a4%ed%96%89" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;질문 벡터와 저장 벡터의 거리를 계산해 순서를 정한다. 사용하는 연산자가 유클리드 거리인지 코사인 거리인지 확인해야 한다.&lt;/p&gt;
&lt;p&gt;SQL에서 벡터 값을 문자열로 임의 연결하기보다 지원되는 드라이버와 파라미터 타입을 사용한다. 의미 순위가 높아도 가격이나 날짜 조건을 충족한다는 뜻은 아니므로 필터와 함께 해석해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1045-자동-생성"&gt;10.4.5 자동 생성&lt;span class="hx:absolute hx:-mt-20" id="1045-자동-생성"&gt;&lt;/span&gt;
&lt;a href="#1045-%ec%9e%90%eb%8f%99-%ec%83%9d%ec%84%b1" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;의미 검색까지 포함한 질의를 모델이 생성하도록 할 수 있다. 그 경우에도 모델이 벡터 숫자를 상상해서 만드는 것이 아니라 임베딩 함수가 계산한 값을 쿼리에 전달해야 한다.&lt;/p&gt;
&lt;p&gt;모델이 선택할 수 있는 필드와 연산을 제한하고 실행 전에 검증한다. 일반 SQL과 벡터 SQL 모두 실행 계층의 통제를 유지해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1046-얻을-수-있는-이점"&gt;10.4.6 얻을 수 있는 이점&lt;span class="hx:absolute hx:-mt-20" id="1046-얻을-수-있는-이점"&gt;&lt;/span&gt;
&lt;a href="#1046-%ec%96%bb%ec%9d%84-%ec%88%98-%ec%9e%88%eb%8a%94-%ec%9d%b4%ec%a0%90" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;관계형 필터와 조인, 유사도 검색을 같은 데이터 구조에 결합할 수 있다. 원문과 임베딩이 다른 시스템에 흩어질 때보다 일관성 관리가 단순해질 수 있다.&lt;/p&gt;
&lt;p&gt;반면 인덱스 크기와 갱신 비용, 근사 검색의 성능을 확인해야 한다. 기존 DB에 넣는다는 사실만으로 모든 규모에서 효율적인 것은 아니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="105-그래프-db-질의"&gt;10.5 그래프 DB 질의&lt;span class="hx:absolute hx:-mt-20" id="105-그래프-db-질의"&gt;&lt;/span&gt;
&lt;a href="#105-%ea%b7%b8%eb%9e%98%ed%94%84-db-%ec%a7%88%ec%9d%98" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;그래프는 개체를 노드로, 관계를 엣지로 표현한다. 특정 장소가 어느 지역에 속하고 어떤 교통수단으로 이어지는지처럼 관계를 따라가는 질문에 유용할 수 있다.&lt;/p&gt;
&lt;p&gt;RDF의 주어, 술어, 목적어와 SPARQL, 속성 그래프의 노드와 관계 및 Cypher 등은 서로 다른 표현과 질의 체계다. 실제 저장소의 스키마와 문법을 제공해야 한다.&lt;/p&gt;
&lt;p&gt;모델이 관계를 추론했다고 그 관계가 DB에 있는 사실은 아니다. 생성한 질의의 결과와 모델의 추가 추론을 구분해야 한다. 그래프를 사용한다고 근거의 정확성이나 설명가능성이 자동으로 보장되지는 않는다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="106-체인-라우팅"&gt;10.6 체인 라우팅&lt;span class="hx:absolute hx:-mt-20" id="106-체인-라우팅"&gt;&lt;/span&gt;
&lt;a href="#106-%ec%b2%b4%ec%9d%b8-%eb%9d%bc%ec%9a%b0%ed%8c%85" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;질문을 보고 적절한 검색 경로를 선택한다. 일반 관광 정보는 문서 검색, 실제 숙박 제안은 관계형 DB처럼 나눌 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1061-검색-경로-준비"&gt;10.6.1 검색 경로 준비&lt;span class="hx:absolute hx:-mt-20" id="1061-검색-경로-준비"&gt;&lt;/span&gt;
&lt;a href="#1061-%ea%b2%80%ec%83%89-%ea%b2%bd%eb%a1%9c-%ec%a4%80%eb%b9%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;각 경로는 같은 입력을 받아 최종 생성에 사용할 근거를 반환하도록 구성할 수 있다. 다만 문서와 SQL 행의 의미가 다르므로 공통으로 보낼 때 구조를 명시해야 한다.&lt;/p&gt;
&lt;p&gt;필드 이름이 없는 튜플의 숫자를 모델이 알아서 해석하게 두면 가격과 할인율을 혼동할 수 있다. 열 이름과 단위를 포함한 구조가 더 명확하다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1062-router"&gt;10.6.2 Router&lt;span class="hx:absolute hx:-mt-20" id="1062-router"&gt;&lt;/span&gt;
&lt;a href="#1062-router" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;모델이 허용된 경로 중 하나를 선택하도록 구조화된 출력을 사용한다. 선택할 범주는 열거형 등으로 제한한다.&lt;/p&gt;
&lt;p&gt;복합 질문을 한 경로로 강제로 보내면 일부 요구를 놓친다. 두 저장소가 모두 필요한지, 추가 확인이 필요한지 판단하는 정책도 필요하다. 모든 입력을 한 경로에 우겨 넣는 기본값은 실패를 감출 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1063-전체-rag-연결"&gt;10.6.3 전체 RAG 연결&lt;span class="hx:absolute hx:-mt-20" id="1063-전체-rag-연결"&gt;&lt;/span&gt;
&lt;a href="#1063-%ec%a0%84%ec%b2%b4-rag-%ec%97%b0%ea%b2%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;라우터의 선택에 따라 실제 검색을 수행하고 결과와 원래 질문으로 답을 만든다. 어떤 경로를 골랐는지와 검색한 근거를 함께 추적해야 한다.&lt;/p&gt;
&lt;p&gt;라우팅이 잘못되면 이후 검색과 생성이 정상 실행돼도 답은 틀릴 수 있다. 최종 문장 평가 외에 경로 선택 자체의 정답도 비교할 필요가 있다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="107-검색-결과-후처리"&gt;10.7 검색 결과 후처리&lt;span class="hx:absolute hx:-mt-20" id="107-검색-결과-후처리"&gt;&lt;/span&gt;
&lt;a href="#107-%ea%b2%80%ec%83%89-%ea%b2%b0%ea%b3%bc-%ed%9b%84%ec%b2%98%eb%a6%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="1071-유사도-기준"&gt;10.7.1 유사도 기준&lt;span class="hx:absolute hx:-mt-20" id="1071-유사도-기준"&gt;&lt;/span&gt;
&lt;a href="#1071-%ec%9c%a0%ec%82%ac%eb%8f%84-%ea%b8%b0%ec%a4%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;관련도가 낮은 후보를 제외하거나 거리가 큰 후보를 제거한다. 저장소별 점수의 의미와 범위를 확인해야 하며 같은 임계값을 모든 모델에 적용하면 안 된다.&lt;/p&gt;
&lt;p&gt;필터 뒤 결과가 없으면 근거 부족으로 다룬다. 무조건 한 개를 남기면 관계없는 자료가 최종 답의 근거가 될 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1072-키워드-기준"&gt;10.7.2 키워드 기준&lt;span class="hx:absolute hx:-mt-20" id="1072-키워드-기준"&gt;&lt;/span&gt;
&lt;a href="#1072-%ed%82%a4%ec%9b%8c%eb%93%9c-%ea%b8%b0%ec%a4%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;필수 단어와 제외 단어로 자료를 거를 수 있다. 명시적인 제약을 반영하기 쉽지만 동의어와 부정 표현, 언어 차이에 취약하다.&lt;/p&gt;
&lt;p&gt;“무료가 아니다”에도 “무료”라는 단어는 있다. 단어 존재만으로 문서의 의미를 판단할 수 없다는 한계를 기억해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1073-시간-가중치"&gt;10.7.3 시간 가중치&lt;span class="hx:absolute hx:-mt-20" id="1073-시간-가중치"&gt;&lt;/span&gt;
&lt;a href="#1073-%ec%8b%9c%ea%b0%84-%ea%b0%80%ec%a4%91%ec%b9%98" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;최근 접근한 자료나 최근 생성된 자료에 가중치를 줄 수 있다. 다만 최근 접근은 자료의 실제 최신성과 다르다.&lt;/p&gt;
&lt;p&gt;오래된 문서를 자주 검색하면 접근 시간이 계속 갱신될 수 있다. 규정과 요금처럼 현재 유효한 자료가 중요한 경우에는 발행일, 적용일과 원천 버전을 기준으로 판단해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1074-rrf"&gt;10.7.4 RRF&lt;span class="hx:absolute hx:-mt-20" id="1074-rrf"&gt;&lt;/span&gt;
&lt;a href="#1074-rrf" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Reciprocal Rank Fusion은 여러 검색 결과의 순위를 결합한다. 서로 척도가 다른 점수를 직접 더하지 않고 각 목록에서의 순위를 사용한다.&lt;/p&gt;
&lt;p&gt;개념적인 점수는 다음과 같다.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;RRF(d) = 각 검색 목록에서 d가 나온 경우의 1 / (c + rank(d)) 합&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;c&lt;/code&gt;는 순위 차이의 영향을 완화하는 상수다. 실제 구현에서는 순위가 0부터인지 1부터인지와 상수 설정을 일관되게 해야 한다. 반환 문서 수 &lt;code&gt;k&lt;/code&gt;와 혼동하지 않는다.&lt;/p&gt;
&lt;p&gt;두 목록에서 문서 A가 각각 1위와 4위, B가 2위와 2위라면 두 문서의 각 역순위 점수를 합쳐 비교한다. 한 목록에만 등장한 문서는 그 목록의 기여만 가진다.&lt;/p&gt;
&lt;p&gt;합치려면 동일 문서를 식별할 ID가 필요하다. 제목이 같다는 이유로 다른 버전의 문서를 하나로 보거나 같은 청크를 별개로 세면 순위가 왜곡된다. RRF도 근거가 틀렸는지 판단하는 사실 검증은 아니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="질문"&gt;질문&lt;span class="hx:absolute hx:-mt-20" id="질문"&gt;&lt;/span&gt;
&lt;a href="#%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="유효한-sql이면-바로-실행해도-될까"&gt;유효한 SQL이면 바로 실행해도 될까?&lt;span class="hx:absolute hx:-mt-20" id="유효한-sql이면-바로-실행해도-될까"&gt;&lt;/span&gt;
&lt;a href="#%ec%9c%a0%ed%9a%a8%ed%95%9c-sql%ec%9d%b4%eb%a9%b4-%eb%b0%94%eb%a1%9c-%ec%8b%a4%ed%96%89%ed%95%b4%eb%8f%84-%eb%90%a0%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;문법과 업무상 허용 범위는 다르다. 읽기 전용 권한과 실행 제한, 스키마와 조건 검증이 필요하다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="가장-최근에-검색된-자료가-가장-최신-자료일까"&gt;가장 최근에 검색된 자료가 가장 최신 자료일까?&lt;span class="hx:absolute hx:-mt-20" id="가장-최근에-검색된-자료가-가장-최신-자료일까"&gt;&lt;/span&gt;
&lt;a href="#%ea%b0%80%ec%9e%a5-%ec%b5%9c%ea%b7%bc%ec%97%90-%ea%b2%80%ec%83%89%eb%90%9c-%ec%9e%90%eb%a3%8c%ea%b0%80-%ea%b0%80%ec%9e%a5-%ec%b5%9c%ec%8b%a0-%ec%9e%90%eb%a3%8c%ec%9d%bc%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;접근 시점과 내용의 유효 시점은 다르다. 시간 가중치가 무엇을 측정하는지 확인해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;a href="../09-question-transformations/"&gt;이전: 질문 변환&lt;/a&gt; / &lt;a href="../"&gt;시리즈 목차&lt;/a&gt; / &lt;a href="../11-tool-agents/"&gt;다음: 도구 기반 에이전트&lt;/a&gt;&lt;/p&gt;</description></item><item><title>11. 도구 기반 에이전트</title><link>https://johklo.github.io/docs/tech/ai-agents-and-applications/11-tool-agents/</link><pubDate>Mon, 19 Oct 2026 21:37:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/ai-agents-and-applications/11-tool-agents/</guid><description>
&lt;p&gt;검색과 생성을 항상 실행하는 RAG와 달리 도구 기반 에이전트는 모델이 필요한 기능을 선택한다. 다만 선택은 모델의 역할이고 실제 실행은 프로그램의 역할이다. 이 구분을 따라가면 에이전트가 무엇을 했는지 추적하기 쉬워진다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="111-도구-하나를-가진-여행-정보-에이전트"&gt;11.1 도구 하나를 가진 여행 정보 에이전트&lt;span class="hx:absolute hx:-mt-20" id="111-도구-하나를-가진-여행-정보-에이전트"&gt;&lt;/span&gt;
&lt;a href="#111-%eb%8f%84%ea%b5%ac-%ed%95%98%eb%82%98%eb%a5%bc-%ea%b0%80%ec%a7%84-%ec%97%ac%ed%96%89-%ec%a0%95%eb%b3%b4-%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;먼저 여행 문서를 검색하는 도구 하나를 제공한다. 모델이 질문을 보고 검색을 요청하면 프로그램이 검색하고 그 결과를 돌려준다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1111-프로젝트-구성"&gt;11.1.1 프로젝트 구성&lt;span class="hx:absolute hx:-mt-20" id="1111-프로젝트-구성"&gt;&lt;/span&gt;
&lt;a href="#1111-%ed%94%84%eb%a1%9c%ec%a0%9d%ed%8a%b8-%ea%b5%ac%ec%84%b1" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;도구 구현, 모델 설정, 상태와 그래프 구성을 구분한다. 개별 도구를 직접 호출해 확인할 수 있어야 모델의 선택 오류와 도구 자체의 오류를 나눌 수 있다.&lt;/p&gt;
&lt;p&gt;예제는 실행 원리를 보여 주기 위한 작은 프로그램이다. 외부 호출의 시간 제한이나 인증, 입력 검증을 실제 서비스에서 생략해도 된다는 뜻은 아니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1112-환경-변수"&gt;11.1.2 환경 변수&lt;span class="hx:absolute hx:-mt-20" id="1112-환경-변수"&gt;&lt;/span&gt;
&lt;a href="#1112-%ed%99%98%ea%b2%bd-%eb%b3%80%ec%88%98" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;모델 API와 추적 서비스에 필요한 키를 실행 환경에서 읽는다. 없는 키와 잘못된 키를 구분해 오류를 드러내야 한다.&lt;/p&gt;
&lt;p&gt;환경 변수의 이름과 실행 위치도 중요하다. IDE에서 실행할 때와 터미널에서 실행할 때 읽는 설정 파일과 작업 디렉터리가 다르면 다른 환경으로 동작할 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1113-여행-정보-저장소"&gt;11.1.3 여행 정보 저장소&lt;span class="hx:absolute hx:-mt-20" id="1113-여행-정보-저장소"&gt;&lt;/span&gt;
&lt;a href="#1113-%ec%97%ac%ed%96%89-%ec%a0%95%eb%b3%b4-%ec%a0%80%ec%9e%a5%ec%86%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;여행 문서를 읽고 나눠 벡터 저장소를 만든다. 매 질문마다 전체 원문을 다시 내려받고 임베딩하면 불필요한 시간과 비용이 발생한다.&lt;/p&gt;
&lt;p&gt;책은 공통 저장소 인스턴스를 재사용한다. 재사용과 영속화는 다른 성질이다. 한 프로세스에서 한 번 만들었다고 프로세스 재시작이나 원문 갱신까지 해결되지는 않는다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="112-도구-호출"&gt;11.2 도구 호출&lt;span class="hx:absolute hx:-mt-20" id="112-도구-호출"&gt;&lt;/span&gt;
&lt;a href="#112-%eb%8f%84%ea%b5%ac-%ed%98%b8%ec%b6%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="1121-function-calling에서-tool-calling으로"&gt;11.2.1 Function calling에서 tool calling으로&lt;span class="hx:absolute hx:-mt-20" id="1121-function-calling에서-tool-calling으로"&gt;&lt;/span&gt;
&lt;a href="#1121-function-calling%ec%97%90%ec%84%9c-tool-calling%ec%9c%bc%eb%a1%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;모델의 응답이 자연어 문장뿐 아니라 도구 호출 요청을 포함할 수 있다. 함수명과 구조화된 인자를 반환하고 프로그램이 실행하는 방식이다.&lt;/p&gt;
&lt;p&gt;도구의 종류와 제공자에 따라 실행 주체는 달라질 수 있다. 사용자 정의 함수는 보통 애플리케이션이 실행하고, 일부 제공자 내장 도구는 제공자 쪽에서 실행될 수 있다. 응답 프로토콜과 실행 경계를 구분해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1122-도구-정의"&gt;11.2.2 도구 정의&lt;span class="hx:absolute hx:-mt-20" id="1122-도구-정의"&gt;&lt;/span&gt;
&lt;a href="#1122-%eb%8f%84%ea%b5%ac-%ec%a0%95%ec%9d%98" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;@tool&lt;/code&gt; 같은 구성을 사용하면 함수 이름, 설명과 인자 스키마를 모델에 제공할 수 있다. 모델은 함수 본문을 읽고 실행하는 것이 아니라 이 정의를 보고 선택한다.&lt;/p&gt;
&lt;p&gt;설명에는 언제 사용하는지, 필요한 입력과 반환값이 무엇인지 들어가야 한다. “검색 도구”처럼 막연한 설명보다 “현재 저장된 여행 문서에서 장소와 교통 정보를 찾는 도구”가 범위를 드러낸다.&lt;/p&gt;
&lt;p&gt;함수의 타입 힌트도 입력의 의미까지 전부 보장하지는 않는다. 문자열이라는 조건을 만족해도 존재하지 않는 장소나 허용 범위 밖의 경로일 수 있다. 실행 전에 업무 조건과 권한을 확인해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1123-모델에-바인딩"&gt;11.2.3 모델에 바인딩&lt;span class="hx:absolute hx:-mt-20" id="1123-모델에-바인딩"&gt;&lt;/span&gt;
&lt;a href="#1123-%eb%aa%a8%eb%8d%b8%ec%97%90-%eb%b0%94%ec%9d%b8%eb%94%a9" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;bind_tools&lt;/code&gt;는 모델 호출에 도구 정의를 연결한다. 이 시점에 도구 함수가 실제 실행되는 것은 아니다.&lt;/p&gt;
&lt;p&gt;모델이 도구 호출을 지원하는지, 사용하는 API와 라이브러리가 그 응답을 어떻게 표현하는지 확인해야 한다. 도구를 등록했어도 모델은 호출 없이 자기 지식으로 답하려 할 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1124-에이전트-상태"&gt;11.2.4 에이전트 상태&lt;span class="hx:absolute hx:-mt-20" id="1124-에이전트-상태"&gt;&lt;/span&gt;
&lt;a href="#1124-%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8-%ec%83%81%ed%83%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;기본 상태는 메시지 목록이다. 사용자 질문, 모델의 도구 호출 요청, 도구 결과와 최종 답이 순서대로 들어간다.&lt;/p&gt;
&lt;p&gt;새 노드의 메시지를 목록에 합칠 reducer가 필요하다. 목록을 통째로 덮어쓰면 직전 도구 요청이나 사용자 질문을 잃을 수 있다.&lt;/p&gt;
&lt;p&gt;동시에 여러 도구 요청이 생기면 각각을 구분할 호출 ID가 중요하다. 어떤 결과가 어떤 요청에 대응하는지 연결해야 모델이 다음 판단을 할 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1125-도구-실행"&gt;11.2.5 도구 실행&lt;span class="hx:absolute hx:-mt-20" id="1125-도구-실행"&gt;&lt;/span&gt;
&lt;a href="#1125-%eb%8f%84%ea%b5%ac-%ec%8b%a4%ed%96%89" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;실행 노드는 모델의 마지막 메시지에서 호출 요청을 꺼낸다. 허용된 이름인지와 인자를 검증하고 해당 함수를 실행한 뒤, 결과를 &lt;code&gt;ToolMessage&lt;/code&gt;로 만들어 요청 ID와 연결한다.&lt;/p&gt;
&lt;p&gt;도구 결과가 “자료 없음”인지 “외부 서비스 실패”인지 구분해야 한다. 둘을 같은 빈 문자열로 바꾸면 모델이 실패한 조회를 정상적인 부재로 해석할 수 있다.&lt;/p&gt;
&lt;p&gt;부작용이 있는 도구는 재시도를 더 신중히 다뤄야 한다. 조회는 다시 해도 되지만 예약이나 결제 요청은 중복 실행될 수 있다. 도구 호출 ID와 업무상의 중복 방지 키가 같은 의미인지도 확인해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1126-모델-노드"&gt;11.2.6 모델 노드&lt;span class="hx:absolute hx:-mt-20" id="1126-모델-노드"&gt;&lt;/span&gt;
&lt;a href="#1126-%eb%aa%a8%eb%8d%b8-%eb%85%b8%eb%93%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;모델 노드는 현재 메시지와 지침을 보내 새 응답을 받는다. 결과에 도구 호출 요청이 있으면 실행 노드로, 더 이상 없으면 종료로 보낸다.&lt;/p&gt;
&lt;p&gt;도구 호출과 자연어 내용이 함께 반환될 수도 있으므로 응답 전체를 단순 문자열이라고 가정하지 않는다. 스트리밍이라면 호출 인자가 여러 조각으로 도착할 수도 있다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="113-그래프-조립"&gt;11.3 그래프 조립&lt;span class="hx:absolute hx:-mt-20" id="113-그래프-조립"&gt;&lt;/span&gt;
&lt;a href="#113-%ea%b7%b8%eb%9e%98%ed%94%84-%ec%a1%b0%eb%a6%bd" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;시작점은 모델 노드다. 조건부 경로가 도구 요청 유무를 확인하고, 도구 실행 뒤에는 다시 모델로 돌아간다.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;
&lt;img src="https://johklo.github.io/images/ai-agents-and-applications/figure-11-02.png" title="그림 11.2. 모델과 도구 실행을 연결한 그래프. 출처: AI Agents and Applications, 11장." alt="시작 노드에서 모델 노드로 들어간 뒤 도구 호출이 있으면 tools로, 없으면 종료로 이동하고 도구 결과는 다시 모델 노드로 돌아오는 에이전트 그래프." loading="lazy" /&gt;
&lt;figcaption&gt;그림 11.2. 모델과 도구 실행을 연결한 그래프. 출처: AI Agents and Applications, 11장.&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;그림은 작은 구조지만 실제 모델 호출은 여러 번 생길 수 있다. 도구 호출을 결정하는 첫 응답과 도구 결과를 읽고 최종 답을 만드는 다음 응답은 다른 호출이다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="114-실행-구조"&gt;11.4 실행 구조&lt;span class="hx:absolute hx:-mt-20" id="114-실행-구조"&gt;&lt;/span&gt;
&lt;a href="#114-%ec%8b%a4%ed%96%89-%ea%b5%ac%ec%a1%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;ReAct는 판단과 행동, 관측 결과를 반복해서 사용하는 패턴이다. 이 구현에서는 메시지에 나타난 도구 요청과 실행 결과를 통해 반복을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;모델이 내놓는 설명을 내부 사고의 완전한 기록처럼 취급할 필요는 없다. 실제로 검증할 수 있는 것은 어떤 도구를 어떤 인자로 요청했고 프로그램이 무엇을 실행해 어떤 결과를 받았는지다.&lt;/p&gt;
&lt;p&gt;종료 조건도 필요하다. 모델이 계속 같은 도구를 호출하면 반복 횟수와 시간, 비용 예산으로 중단할 수 있어야 한다. 중단한 상태를 최종 답변 완료와 구분한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="115-repl"&gt;11.5 REPL&lt;span class="hx:absolute hx:-mt-20" id="115-repl"&gt;&lt;/span&gt;
&lt;a href="#115-repl" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;REPL은 입력을 읽고 실행하고 결과를 보여 주는 반복 인터페이스다. 콘솔에서 사용자의 질문을 받아 메시지로 만들고 그래프 결과를 출력한다.&lt;/p&gt;
&lt;p&gt;반복 인터페이스가 있다고 이전 대화가 저장되는 것은 아니다. 매번 새 상태로 호출하면 각 질문은 독립적이다. 대화 이력과 체크포인트는 따로 연결해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="116-요청-하나-따라가기"&gt;11.6 요청 하나 따라가기&lt;span class="hx:absolute hx:-mt-20" id="116-요청-하나-따라가기"&gt;&lt;/span&gt;
&lt;a href="#116-%ec%9a%94%ec%b2%ad-%ed%95%98%eb%82%98-%eb%94%b0%eb%9d%bc%ea%b0%80%ea%b8%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="1161-단계별-디버깅"&gt;11.6.1 단계별 디버깅&lt;span class="hx:absolute hx:-mt-20" id="1161-단계별-디버깅"&gt;&lt;/span&gt;
&lt;a href="#1161-%eb%8b%a8%ea%b3%84%eb%b3%84-%eb%94%94%eb%b2%84%ea%b9%85" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;한 요청에서 최소한 다음 항목을 확인할 수 있어야 한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;위치&lt;/th&gt;
&lt;th&gt;확인할 내용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;입력&lt;/td&gt;
&lt;td&gt;실제 사용자 질문과 system 지침&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;모델 응답&lt;/td&gt;
&lt;td&gt;호출한 도구 이름과 인자, 호출 ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;실행 노드&lt;/td&gt;
&lt;td&gt;어떤 함수가 실제 실행됐는지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;도구 결과&lt;/td&gt;
&lt;td&gt;원문, 값과 오류 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;다음 모델 호출&lt;/td&gt;
&lt;td&gt;도구 결과가 올바른 역할과 ID로 들어갔는지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;종료&lt;/td&gt;
&lt;td&gt;최종 답인지 예산 초과나 실패인지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;도구를 실행하지 않았는데 답이 나왔다면 모델이 추측했을 수 있다. 반대로 도구를 실행했어도 결과를 잘못 사용하면 답이 틀릴 수 있다. 도구 사용률 자체가 정답률은 아니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="117-날씨-도구-추가"&gt;11.7 날씨 도구 추가&lt;span class="hx:absolute hx:-mt-20" id="117-날씨-도구-추가"&gt;&lt;/span&gt;
&lt;a href="#117-%eb%82%a0%ec%94%a8-%eb%8f%84%ea%b5%ac-%ec%b6%94%ea%b0%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="1171-모의-날씨-서비스"&gt;11.7.1 모의 날씨 서비스&lt;span class="hx:absolute hx:-mt-20" id="1171-모의-날씨-서비스"&gt;&lt;/span&gt;
&lt;a href="#1171-%eb%aa%a8%ec%9d%98-%eb%82%a0%ec%94%a8-%ec%84%9c%eb%b9%84%ec%8a%a4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;책은 처음에 임의의 날씨와 온도를 반환하는 모형을 사용한다. 목적은 현재 날씨의 정확성을 확인하는 것이 아니라 도구 선택과 여러 호출의 흐름을 관찰하는 것이다.&lt;/p&gt;
&lt;p&gt;모의 데이터를 실제 관측값처럼 해석하면 안 된다. 테스트를 반복 가능하게 하려면 무작위 결과보다 고정된 응답을 사용하는 경우도 유용하다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1172-날씨-도구"&gt;11.7.2 날씨 도구&lt;span class="hx:absolute hx:-mt-20" id="1172-날씨-도구"&gt;&lt;/span&gt;
&lt;a href="#1172-%eb%82%a0%ec%94%a8-%eb%8f%84%ea%b5%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;도구는 장소를 받아 해당 장소의 조건과 온도를 반환한다. 장소를 찾을 수 없는 경우와 서비스가 실패한 경우를 구분한다.&lt;/p&gt;
&lt;p&gt;실제 서비스로 바꾸면 날짜와 시간대, 온도 단위, 현재 관측과 예보의 차이도 필요하다. “날씨가 좋다”는 판단에는 어떤 시간과 기준을 사용했는지 드러나야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1173-여러-도구-등록"&gt;11.7.3 여러 도구 등록&lt;span class="hx:absolute hx:-mt-20" id="1173-여러-도구-등록"&gt;&lt;/span&gt;
&lt;a href="#1173-%ec%97%ac%eb%9f%ac-%eb%8f%84%ea%b5%ac-%eb%93%b1%eb%a1%9d" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;여행 검색과 날씨 도구를 모델에 함께 등록한다. 그래프의 기본 루프는 유지하면서 모델이 도구를 선택할 수 있다.&lt;/p&gt;
&lt;p&gt;그러나 도구 수가 늘면 역할이 겹치거나 입력을 잘못 전달하는 경우도 늘어난다. 도구 설명과 출력 규격을 명확히 하고 조합 요청을 따로 시험해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="118-여러-도구-실행"&gt;11.8 여러 도구 실행&lt;span class="hx:absolute hx:-mt-20" id="118-여러-도구-실행"&gt;&lt;/span&gt;
&lt;a href="#118-%ec%97%ac%eb%9f%ac-%eb%8f%84%ea%b5%ac-%ec%8b%a4%ed%96%89" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="1181-초기-동작"&gt;11.8.1 초기 동작&lt;span class="hx:absolute hx:-mt-20" id="1181-초기-동작"&gt;&lt;/span&gt;
&lt;a href="#1181-%ec%b4%88%ea%b8%b0-%eb%8f%99%ec%9e%91" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;모델은 “좋은 날씨의 해안 마을”을 묻는 질문에 관광지를 자기 지식으로 정하고 날씨만 조회할 수 있다. 그러면 저장된 여행 자료를 사용한다는 의도가 지켜지지 않을 수 있다.&lt;/p&gt;
&lt;p&gt;도구를 제공하는 것과 반드시 근거로 사용하게 만드는 것은 다르다. 실제 트레이스에서 어떤 단계를 생략했는지 확인해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1182-system-지침-보완"&gt;11.8.2 System 지침 보완&lt;span class="hx:absolute hx:-mt-20" id="1182-system-지침-보완"&gt;&lt;/span&gt;
&lt;a href="#1182-system-%ec%a7%80%ec%b9%a8-%eb%b3%b4%ec%99%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;장소는 여행 검색 도구에서 찾고, 날씨는 날씨 도구로 확인하도록 지침과 설명을 구체화한다. 호출 순서를 반드시 지켜야 하는 요구라면 그래프에서도 그 순서를 강제할 수 있다.&lt;/p&gt;
&lt;p&gt;자연어 지침은 동작을 유도하지만 권한이나 강제 정책의 대체물이 아니다. 외부 행동의 허용 범위는 실행 코드에서 검사해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="119-사전-구성-에이전트"&gt;11.9 사전 구성 에이전트&lt;span class="hx:absolute hx:-mt-20" id="119-사전-구성-에이전트"&gt;&lt;/span&gt;
&lt;a href="#119-%ec%82%ac%ec%a0%84-%ea%b5%ac%ec%84%b1-%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="1191-기본-루프-재사용"&gt;11.9.1 기본 루프 재사용&lt;span class="hx:absolute hx:-mt-20" id="1191-기본-루프-재사용"&gt;&lt;/span&gt;
&lt;a href="#1191-%ea%b8%b0%eb%b3%b8-%eb%a3%a8%ed%94%84-%ec%9e%ac%ec%82%ac%ec%9a%a9" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;책은 수동으로 만든 도구 실행 루프를 사전 구성 ReAct 에이전트로 바꾼다. 반복 코드가 줄어들지만 어떤 상태 스키마와 종료 규칙을 사용하는지 이해해야 한다.&lt;/p&gt;
&lt;p&gt;사전 구성 에이전트의 생성 함수와 import 경로는 버전에 따라 달라질 수 있다. 책의 API와 현재 LangChain, LangGraph 문서의 권장 API를 섞지 않는다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1192-실행-결과"&gt;11.9.2 실행 결과&lt;span class="hx:absolute hx:-mt-20" id="1192-실행-결과"&gt;&lt;/span&gt;
&lt;a href="#1192-%ec%8b%a4%ed%96%89-%ea%b2%b0%ea%b3%bc" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;자연어 문자열뿐 아니라 텍스트 블록 등 구조화된 응답이 나올 수 있다. 화면에 출력할 본문과 추적할 메타데이터를 분리한다.&lt;/p&gt;
&lt;p&gt;추상화를 바꾼 뒤에는 이전과 같은 입력으로 도구 선택과 최종 결과가 유지되는지 비교해야 한다. 코드가 짧아졌다는 사실은 기능 동등성의 증거가 아니다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1193-langsmith-관측"&gt;11.9.3 LangSmith 관측&lt;span class="hx:absolute hx:-mt-20" id="1193-langsmith-관측"&gt;&lt;/span&gt;
&lt;a href="#1193-langsmith-%ea%b4%80%ec%b8%a1" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;프레임워크 내부 루프를 직접 디버깅하기 어려울 때 트레이스로 모델과 도구 호출을 연결한다. 결과가 어느 도구의 값에 의존했는지와 어디서 시간이 걸렸는지 볼 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1194-추적-설정"&gt;11.9.4 추적 설정&lt;span class="hx:absolute hx:-mt-20" id="1194-추적-설정"&gt;&lt;/span&gt;
&lt;a href="#1194-%ec%b6%94%ec%a0%81-%ec%84%a4%ec%a0%95" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;추적 프로젝트와 인증 정보를 설정한 뒤 실제 요청이 기록되는지 확인한다. 기록이 활성화되면 사용자 질문과 도구 결과도 외부 추적 시스템에 전송될 수 있다.&lt;/p&gt;
&lt;p&gt;민감한 입력을 무조건 모두 남기지 않고 수집 범위와 접근 권한, 보존 정책을 정한다. 관측을 위해 정보를 모으는 일에도 데이터 경계가 필요하다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="질문"&gt;질문&lt;span class="hx:absolute hx:-mt-20" id="질문"&gt;&lt;/span&gt;
&lt;a href="#%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="도구를-바인딩하면-함수가-바로-실행되는가"&gt;도구를 바인딩하면 함수가 바로 실행되는가?&lt;span class="hx:absolute hx:-mt-20" id="도구를-바인딩하면-함수가-바로-실행되는가"&gt;&lt;/span&gt;
&lt;a href="#%eb%8f%84%ea%b5%ac%eb%a5%bc-%eb%b0%94%ec%9d%b8%eb%94%a9%ed%95%98%eb%a9%b4-%ed%95%a8%ec%88%98%ea%b0%80-%eb%b0%94%eb%a1%9c-%ec%8b%a4%ed%96%89%eb%90%98%eb%8a%94%ea%b0%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;정의만 모델 호출에 전달된다. 실제 호출 요청을 받아 검증하고 실행하는 과정은 별도로 필요하다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="도구를-항상-호출하게-만들면-답의-정확성이-보장되는가"&gt;도구를 항상 호출하게 만들면 답의 정확성이 보장되는가?&lt;span class="hx:absolute hx:-mt-20" id="도구를-항상-호출하게-만들면-답의-정확성이-보장되는가"&gt;&lt;/span&gt;
&lt;a href="#%eb%8f%84%ea%b5%ac%eb%a5%bc-%ed%95%ad%ec%83%81-%ed%98%b8%ec%b6%9c%ed%95%98%ea%b2%8c-%eb%a7%8c%eb%93%a4%eb%a9%b4-%eb%8b%b5%ec%9d%98-%ec%a0%95%ed%99%95%ec%84%b1%ec%9d%b4-%eb%b3%b4%ec%9e%a5%eb%90%98%eb%8a%94%ea%b0%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;검색 결과가 틀리거나 인자와 해석이 잘못될 수 있다. 선택, 실행 결과, 최종 답을 각각 확인해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;a href="../10-query-routing/"&gt;이전: 질의 생성과 라우팅&lt;/a&gt; / &lt;a href="../"&gt;시리즈 목차&lt;/a&gt; / &lt;a href="../12-multi-agent-systems/"&gt;다음: 다중 에이전트&lt;/a&gt;&lt;/p&gt;</description></item><item><title>12. 다중 에이전트</title><link>https://johklo.github.io/docs/tech/ai-agents-and-applications/12-multi-agent-systems/</link><pubDate>Mon, 19 Oct 2026 21:37:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/ai-agents-and-applications/12-multi-agent-systems/</guid><description>
&lt;p&gt;도구가 늘어나면 모델 하나에 전부 제공하는 대신 업무별 에이전트로 나눌 수 있다. 다만 여러 에이전트가 있다는 사실만으로 협업이 되는 것은 아니다. 누가 요청을 받고 어떤 상태를 넘기며 결과를 어디로 돌려줄지 정해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="121-숙박-에이전트"&gt;12.1 숙박 에이전트&lt;span class="hx:absolute hx:-mt-20" id="121-숙박-에이전트"&gt;&lt;/span&gt;
&lt;a href="#121-%ec%88%99%eb%b0%95-%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;여행 정보 에이전트는 장소와 날씨를 다루고, 숙박 에이전트는 호텔과 B&amp;amp;B의 가용 객실 및 제안을 조회한다. 정보와 도구의 책임을 나누는 구조다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1211-호텔-도구"&gt;12.1.1 호텔 도구&lt;span class="hx:absolute hx:-mt-20" id="1211-호텔-도구"&gt;&lt;/span&gt;
&lt;a href="#1211-%ed%98%b8%ed%85%94-%eb%8f%84%ea%b5%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;책은 SQLite에 저장된 호텔 자료를 SQL 도구로 제공한다. 모델이 스키마를 확인하고 필요한 조회를 실행할 수 있도록 도구 집합을 연결한다.&lt;/p&gt;
&lt;p&gt;일반적인 SQL toolkit은 단일 “호텔 조회” 함수보다 넓은 기능을 노출할 수 있다. 어떤 테이블과 명령이 허용되는지 확인하고 읽기 전용 권한과 실행 제한을 적용해야 한다.&lt;/p&gt;
&lt;p&gt;현재 객실과 가격은 변경되는 데이터다. 결과에는 대상 날짜와 객실 수, 통화와 가격 기준을 포함해야 한다. 질문에 날짜가 없다면 임의로 정하기보다 확인이 필요할 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1212-bb-도구"&gt;12.1.2 B&amp;amp;B 도구&lt;span class="hx:absolute hx:-mt-20" id="1212-bb-도구"&gt;&lt;/span&gt;
&lt;a href="#1212-bb-%eb%8f%84%ea%b5%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;다른 제공자의 REST 서비스를 모의 구현으로 연결한다. 장소와 객실 수 등을 받아 제안 목록을 반환한다.&lt;/p&gt;
&lt;p&gt;SQL 행과 API 응답을 같은 의미로 비교하려면 필드와 단위를 맞춰야 한다. 1박 가격과 전체 숙박 가격, 객실 수와 인원 수를 섞으면 자연스러운 답변도 틀릴 수 있다.&lt;/p&gt;
&lt;p&gt;빈 결과와 오류도 구분해야 한다. 객실이 없는 것과 외부 API가 응답하지 않은 것은 다른 상황이다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1213-react-숙박-에이전트"&gt;12.1.3 ReAct 숙박 에이전트&lt;span class="hx:absolute hx:-mt-20" id="1213-react-숙박-에이전트"&gt;&lt;/span&gt;
&lt;a href="#1213-react-%ec%88%99%eb%b0%95-%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;호텔과 B&amp;amp;B 도구를 함께 제공해 조건에 맞는 결과를 모으도록 한다. 숙소 유형을 정하지 않았다면 둘 다 확인하는 정책을 둘 수 있다.&lt;/p&gt;
&lt;p&gt;책의 booking이라는 이름에도 실제 예제의 핵심은 가용 객실과 제안 조회다. 목록을 확인하는 기능과 객실을 확보하거나 결제를 실행하는 기능을 같은 것으로 보면 안 된다.&lt;/p&gt;
&lt;p&gt;실제 예약을 추가하면 사용자 확인, 재고 재검증, 중복 방지와 취소 절차가 필요하다. 조회 시점에 있던 객실이 예약 시점에도 있다는 보장은 없다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="122-router-기반-여행-도우미"&gt;12.2 Router 기반 여행 도우미&lt;span class="hx:absolute hx:-mt-20" id="122-router-기반-여행-도우미"&gt;&lt;/span&gt;
&lt;a href="#122-router-%ea%b8%b0%eb%b0%98-%ec%97%ac%ed%96%89-%eb%8f%84%ec%9a%b0%eb%af%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;질문을 보고 여행 정보 또는 숙박 에이전트로 한 번 위임한다. 각 전문 에이전트의 도구와 프롬프트는 독립적으로 관리할 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1221-라우터-설계"&gt;12.2.1 라우터 설계&lt;span class="hx:absolute hx:-mt-20" id="1221-라우터-설계"&gt;&lt;/span&gt;
&lt;a href="#1221-%eb%9d%bc%ec%9a%b0%ed%84%b0-%ec%84%a4%ea%b3%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;라우터는 사용 가능한 에이전트의 이름과 처리 범위를 알아야 한다. 자유 문자열보다 허용 이름을 가진 구조화된 출력으로 경로를 제한하는 편이 명확하다.&lt;/p&gt;
&lt;p&gt;모든 질문이 둘 중 하나에 속하지는 않는다. 범위 밖 요청이나 불명확한 입력을 어떻게 처리할지도 정해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1222-라우팅-로직"&gt;12.2.2 라우팅 로직&lt;span class="hx:absolute hx:-mt-20" id="1222-라우팅-로직"&gt;&lt;/span&gt;
&lt;a href="#1222-%eb%9d%bc%ec%9a%b0%ed%8c%85-%eb%a1%9c%ec%a7%81" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;질문을 분류하고 &lt;code&gt;Command&lt;/code&gt; 같은 실행 제어 객체로 대상 노드와 필요한 상태 갱신을 지정할 수 있다.&lt;/p&gt;
&lt;p&gt;에이전트 이름을 고르는 것과 업무가 성공하는 것은 다르다. 라우터가 올바른 이름을 반환해도 전달한 질문에서 날짜나 객실 수를 잃었다면 결과가 달라진다.&lt;/p&gt;
&lt;p&gt;상태를 그대로 넘기는 경우에도 다른 에이전트가 읽어도 되는 정보인지 확인해야 한다. 메시지 전체를 공유하는 방식은 간단하지만 불필요한 문맥과 민감한 정보가 넓게 퍼질 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1223-그래프-구성"&gt;12.2.3 그래프 구성&lt;span class="hx:absolute hx:-mt-20" id="1223-그래프-구성"&gt;&lt;/span&gt;
&lt;a href="#1223-%ea%b7%b8%eb%9e%98%ed%94%84-%ea%b5%ac%ec%84%b1" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;상위 경로는 Router에서 전문 에이전트로 이동하고, 전문 에이전트 내부에서는 도구 루프가 돌아간다. 바깥은 제한된 경로 선택이고 안쪽은 모델이 여러 도구를 선택하는 혼합 구조다.&lt;/p&gt;
&lt;p&gt;하위 에이전트가 끝났을 때 사용자 응답으로 종료할지 상위로 돌아올지가 중요한 차이다. Router 구조에서는 보통 선택된 한 경로에서 요청을 마친다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1224-실행-확인"&gt;12.2.4 실행 확인&lt;span class="hx:absolute hx:-mt-20" id="1224-실행-확인"&gt;&lt;/span&gt;
&lt;a href="#1224-%ec%8b%a4%ed%96%89-%ed%99%95%ec%9d%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;관광지 질문과 빈 객실 질문을 각각 넣어 어느 경로가 선택되는지 확인한다. 이름 분류뿐 아니라 적절한 도구를 사용했는지도 봐야 한다.&lt;/p&gt;
&lt;p&gt;“날씨가 좋은 해안 마을을 고르고 그곳의 숙소도 찾아 달라”는 요청은 두 업무를 포함한다. 한 경로로만 보내는 구조에서는 일부 요구를 놓칠 수 있다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="123-supervisor"&gt;12.3 Supervisor&lt;span class="hx:absolute hx:-mt-20" id="123-supervisor"&gt;&lt;/span&gt;
&lt;a href="#123-supervisor" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Supervisor는 전문 에이전트를 작업 도구처럼 호출하고 결과를 받은 뒤 다음 작업을 결정한다. 단순히 한 번 분류하는 Router보다 조정의 범위가 넓다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1231-에이전트를-조정하는-에이전트"&gt;12.3.1 에이전트를 조정하는 에이전트&lt;span class="hx:absolute hx:-mt-20" id="1231-에이전트를-조정하는-에이전트"&gt;&lt;/span&gt;
&lt;a href="#1231-%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8%eb%a5%bc-%ec%a1%b0%ec%a0%95%ed%95%98%eb%8a%94-%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;전문 에이전트마다 역할과 입력, 결과 형식을 명확히 해야 한다. Supervisor는 어떤 능력이 있고 어떤 경우에 호출해야 하는지 설명을 보고 위임한다.&lt;/p&gt;
&lt;p&gt;이름이 다른 모델 여러 개보다 업무 경계가 중요하다. 같은 모델을 사용해도 서로 다른 도구와 지침, 상태 범위를 가지면 역할을 분리할 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1232-결과-회수와-재위임"&gt;12.3.2 결과 회수와 재위임&lt;span class="hx:absolute hx:-mt-20" id="1232-결과-회수와-재위임"&gt;&lt;/span&gt;
&lt;a href="#1232-%ea%b2%b0%ea%b3%bc-%ed%9a%8c%ec%88%98%ec%99%80-%ec%9e%ac%ec%9c%84%ec%9e%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;figure&gt;
&lt;img src="https://johklo.github.io/images/ai-agents-and-applications/figure-12-03.png" title="그림 12.3. Supervisor 기반 여행 도우미. 출처: AI Agents and Applications, 12장." alt="Supervisor가 여행 정보 에이전트와 숙박 에이전트를 호출하고 결과를 돌려받는 구조. 각 전문 에이전트는 자체 검색 및 날씨, 호텔과 B&amp;B 도구를 사용한다." loading="lazy" /&gt;
&lt;figcaption&gt;그림 12.3. Supervisor 기반 여행 도우미. 출처: AI Agents and Applications, 12장.&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;그림의 핵심은 전문 에이전트에서 Supervisor로 돌아오는 경로다. 여행 정보에서 정한 마을을 받은 뒤 그 마을의 숙소를 조회할 수 있다. 한 번의 요청 안에서도 같은 전문 에이전트를 다시 호출할 수 있다.&lt;/p&gt;
&lt;p&gt;그러나 위임이 많을수록 지연과 비용, 정보 손실이 늘어난다. 전문 에이전트가 원문 근거 대신 짧은 결론만 반환하면 Supervisor가 잘못된 판단을 조사하기 어렵다. 필요한 사실과 출처, 실패 상태를 구조화해서 반환하는 편이 좋다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;Router&lt;/th&gt;
&lt;th&gt;Supervisor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;주된 판단&lt;/td&gt;
&lt;td&gt;누가 이 요청을 처리할지&lt;/td&gt;
&lt;td&gt;어떤 작업을 어떤 순서로 맡길지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;결과 경로&lt;/td&gt;
&lt;td&gt;전문 에이전트가 응답하고 종료 가능&lt;/td&gt;
&lt;td&gt;결과를 받아 추가 위임과 통합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;적합한 요청&lt;/td&gt;
&lt;td&gt;분리된 단일 업무&lt;/td&gt;
&lt;td&gt;여러 역할의 결과가 필요한 업무&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주요 부담&lt;/td&gt;
&lt;td&gt;오분류와 복합 요청 누락&lt;/td&gt;
&lt;td&gt;반복 위임, 비용, 상태 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-hextra-search-id="1233-복합-요청-실행"&gt;12.3.3 복합 요청 실행&lt;span class="hx:absolute hx:-mt-20" id="1233-복합-요청-실행"&gt;&lt;/span&gt;
&lt;a href="#1233-%eb%b3%b5%ed%95%a9-%ec%9a%94%ec%b2%ad-%ec%8b%a4%ed%96%89" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;“해안 마을을 찾고 현재 날씨를 확인한 뒤 객실을 조회”한다면 장소 선정이 먼저다. 숙박 도구가 실행됐다고 전체 성공이 아니라, 선정된 장소와 실제 조회한 장소가 같은지까지 확인해야 한다.&lt;/p&gt;
&lt;p&gt;결과에는 어떤 조건을 충족했고 어느 항목을 확인하지 못했는지 남긴다. 외부 서비스 하나가 실패한 경우 다른 에이전트의 성공 결과를 마치 모든 확인이 끝난 것처럼 표현하면 안 된다.&lt;/p&gt;
&lt;p&gt;종료 기준과 위임 한도가 필요하다. Supervisor가 더 나은 결과를 찾겠다고 같은 에이전트를 계속 호출할 수 있으므로 요청별 시간과 비용, 반복 횟수를 통제한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="여러-에이전트가-필요한지-판단하기"&gt;여러 에이전트가 필요한지 판단하기&lt;span class="hx:absolute hx:-mt-20" id="여러-에이전트가-필요한지-판단하기"&gt;&lt;/span&gt;
&lt;a href="#%ec%97%ac%eb%9f%ac-%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8%ea%b0%80-%ed%95%84%ec%9a%94%ed%95%9c%ec%a7%80-%ed%8c%90%eb%8b%a8%ed%95%98%ea%b8%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;업무와 도구를 분리하면 프롬프트 길이와 권한 범위를 줄이고 부분별 평가가 쉬워질 수 있다. 반대로 단순 작업을 인위적으로 여러 에이전트로 나누면 전달과 재해석의 비용만 늘 수 있다.&lt;/p&gt;
&lt;p&gt;한 에이전트와 몇 개의 명시적 도구로 충분한지부터 비교해야 한다. 에이전트 수가 시스템의 성숙도를 나타내는 것은 아니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="질문"&gt;질문&lt;span class="hx:absolute hx:-mt-20" id="질문"&gt;&lt;/span&gt;
&lt;a href="#%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="router도-여러-작업을-해결할-수-있을까"&gt;Router도 여러 작업을 해결할 수 있을까?&lt;span class="hx:absolute hx:-mt-20" id="router도-여러-작업을-해결할-수-있을까"&gt;&lt;/span&gt;
&lt;a href="#router%eb%8f%84-%ec%97%ac%eb%9f%ac-%ec%9e%91%ec%97%85%ec%9d%84-%ed%95%b4%ea%b2%b0%ed%95%a0-%ec%88%98-%ec%9e%88%ec%9d%84%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;별도의 분해와 반복 경로를 구현하면 가능하다. 이 장에서 비교하는 단순 Router는 한 번의 선택으로 끝나는 구조라 복합 요청에서 한계가 드러난다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="숙소를-조회했으면-예약까지-완료된-것일까"&gt;숙소를 조회했으면 예약까지 완료된 것일까?&lt;span class="hx:absolute hx:-mt-20" id="숙소를-조회했으면-예약까지-완료된-것일까"&gt;&lt;/span&gt;
&lt;a href="#%ec%88%99%ec%86%8c%eb%a5%bc-%ec%a1%b0%ed%9a%8c%ed%96%88%ec%9c%bc%eb%a9%b4-%ec%98%88%ec%95%bd%ea%b9%8c%ec%a7%80-%ec%99%84%eb%a3%8c%eb%90%9c-%ea%b2%83%ec%9d%bc%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;조회와 실제 거래는 다른 작업이다. 예약 확정 결과가 없으면 완료라고 답해서는 안 된다.&lt;/p&gt;
&lt;p&gt;&lt;a href="../11-tool-agents/"&gt;이전: 도구 기반 에이전트&lt;/a&gt; / &lt;a href="../"&gt;시리즈 목차&lt;/a&gt; / &lt;a href="../13-mcp-servers/"&gt;다음: MCP 서버&lt;/a&gt;&lt;/p&gt;</description></item><item><title>13. MCP 서버</title><link>https://johklo.github.io/docs/tech/ai-agents-and-applications/13-mcp-servers/</link><pubDate>Mon, 19 Oct 2026 21:37:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/ai-agents-and-applications/13-mcp-servers/</guid><description>
&lt;p&gt;도구마다 직접 API 연결 코드를 작성하면 애플리케이션이 늘어날수록 비슷한 래퍼가 반복된다. MCP는 서비스 제공자가 공통 규약으로 도구와 문맥을 노출하고 여러 애플리케이션이 이를 소비하게 하는 방식이다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="131-mcp-서버"&gt;13.1 MCP 서버&lt;span class="hx:absolute hx:-mt-20" id="131-mcp-서버"&gt;&lt;/span&gt;
&lt;a href="#131-mcp-%ec%84%9c%eb%b2%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="1311-연결이-많아질-때의-문제"&gt;13.1.1 연결이 많아질 때의 문제&lt;span class="hx:absolute hx:-mt-20" id="1311-연결이-많아질-때의-문제"&gt;&lt;/span&gt;
&lt;a href="#1311-%ec%97%b0%ea%b2%b0%ec%9d%b4-%eb%a7%8e%ec%95%84%ec%a7%88-%eb%95%8c%ec%9d%98-%eb%ac%b8%ec%a0%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;날씨, 검색, 문서 저장소와 업무 API마다 호출 방식과 응답 규격이 다르다. 모델에 제공할 도구 스키마와 실제 API의 인자를 연결하는 코드도 필요하다.&lt;/p&gt;
&lt;p&gt;같은 서비스가 여러 에이전트에 사용되면 변환과 오류 처리를 각 애플리케이션에서 반복하게 된다. API가 바뀌면 여러 래퍼를 함께 수정해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1312-model-context-protocol"&gt;13.1.2 Model Context Protocol&lt;span class="hx:absolute hx:-mt-20" id="1312-model-context-protocol"&gt;&lt;/span&gt;
&lt;a href="#1312-model-context-protocol" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;MCP는 도구와 자료를 제공하는 쪽과 소비하는 쪽 사이에 공통 인터페이스를 둔다. MCP를 사용한다고 서비스 고유의 업무 규칙이 사라지는 것은 아니다. 연결과 발견, 호출을 공통 규약으로 다룰 수 있게 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;
&lt;img src="https://johklo.github.io/images/ai-agents-and-applications/figure-13-01.png" title="그림 13.1. MCP Host와 Client, Server의 관계. 출처: AI Agents and Applications, 13장." alt="하나의 AI 애플리케이션인 MCP Host가 여러 Client를 통해 로컬 및 원격 MCP Server에 연결하고, 각 서버는 도구와 원천 자료 또는 API를 제공하는 구조." loading="lazy" /&gt;
&lt;figcaption&gt;그림 13.1. MCP Host와 Client, Server의 관계. 출처: AI Agents and Applications, 13장.&lt;/figcaption&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구성 요소&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Host&lt;/td&gt;
&lt;td&gt;모델과 사용자 상호작용, 연결과 사용 정책을 관리하는 앱&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;td&gt;Host 안에서 특정 서버와 통신하는 구성 요소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server&lt;/td&gt;
&lt;td&gt;도구, 자료와 프롬프트 등을 규약에 맞게 제공하는 프로그램&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;원천 서비스&lt;/td&gt;
&lt;td&gt;실제 날씨 API나 파일, 데이터베이스&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Client는 모델 자체가 아니다. 서버가 도구를 제공해도 어떤 도구를 사용자에게 허용하고 모델에 노출할지는 Host의 정책이다. 서버에 접근할 수 있다는 사실과 모든 기능을 실행할 권한이 있다는 사실도 다르다.&lt;/p&gt;
&lt;p&gt;Tools는 실행 가능한 기능, Resources는 읽을 문맥 자료, Prompts는 재사용 가능한 상호작용 템플릿을 제공하는 개념이다. 이 장의 실습은 그중 도구 노출에 집중한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1313-생태계"&gt;13.1.3 생태계&lt;span class="hx:absolute hx:-mt-20" id="1313-생태계"&gt;&lt;/span&gt;
&lt;a href="#1313-%ec%83%9d%ed%83%9c%ea%b3%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;MCP SDK와 Inspector, 프레임워크 어댑터를 사용하면 직접 메시지 규약을 구현하는 부담을 줄일 수 있다.&lt;/p&gt;
&lt;p&gt;서버를 찾는 것과 신뢰하는 것은 다르다. 어떤 코드를 실행하고 어떤 외부 주소에 데이터를 보내는지, 노출한 도구가 필요한 권한만 갖는지 확인해야 한다. 도구 설명과 결과도 무조건 신뢰되는 지시가 아니다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="132-서버-구성-방법"&gt;13.2 서버 구성 방법&lt;span class="hx:absolute hx:-mt-20" id="132-서버-구성-방법"&gt;&lt;/span&gt;
&lt;a href="#132-%ec%84%9c%eb%b2%84-%ea%b5%ac%ec%84%b1-%eb%b0%a9%eb%b2%95" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="1321-문서와-규격"&gt;13.2.1 문서와 규격&lt;span class="hx:absolute hx:-mt-20" id="1321-문서와-규격"&gt;&lt;/span&gt;
&lt;a href="#1321-%eb%ac%b8%ec%84%9c%ec%99%80-%ea%b7%9c%ea%b2%a9" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;a href="https://modelcontextprotocol.io/docs/learn/architecture" target="_blank" rel="noopener"&gt;MCP 공식 아키텍처 문서&lt;/a&gt;는 참여자와 연결 계층, 도구 및 자료의 개념을 설명한다. 프로토콜은 LLM의 판단 방식이나 에이전트의 업무 정책 전체를 정하지 않는다.&lt;/p&gt;
&lt;p&gt;규격은 버전에 따라 바뀐다. 책의 예제와 현재 SDK가 사용하는 프로토콜 버전, 초기 연결과 기능 발견 방식이 호환되는지 확인해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1322-언어별-sdk"&gt;13.2.2 언어별 SDK&lt;span class="hx:absolute hx:-mt-20" id="1322-언어별-sdk"&gt;&lt;/span&gt;
&lt;a href="#1322-%ec%96%b8%ec%96%b4%eb%b3%84-sdk" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;공식 언어별 SDK와 서버 작성에 사용하는 상위 라이브러리는 구분해야 한다. 책은 FastMCP 계열 도구로 Python 서버를 만든다.&lt;/p&gt;
&lt;p&gt;같은 이름의 예제를 보더라도 import 경로와 패키지, 지원 버전이 다른지 확인해야 한다. 라이브러리가 직렬화와 서버 구성을 도와도 외부 API 키와 오류 처리, 권한은 구현의 책임이다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1323-애플리케이션에서-소비"&gt;13.2.3 애플리케이션에서 소비&lt;span class="hx:absolute hx:-mt-20" id="1323-애플리케이션에서-소비"&gt;&lt;/span&gt;
&lt;a href="#1323-%ec%95%a0%ed%94%8c%eb%a6%ac%ec%bc%80%ec%9d%b4%ec%85%98%ec%97%90%ec%84%9c-%ec%86%8c%eb%b9%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Host 안의 Client가 서버에 연결해 도구 정의를 얻고, 프레임워크가 사용할 수 있는 도구 형태로 변환한다. 모델은 그 정의로 요청하고 실행은 연결된 Client를 거쳐 서버에 전달된다.&lt;/p&gt;
&lt;p&gt;로컬 표준 입출력 연결과 원격 HTTP 연결은 운영 조건이 다르다. 원격에서는 인증과 TLS, 시간 제한과 연결 수명, 네트워크 장애를 다뤄야 한다. 로컬 서버도 Host 권한으로 파일이나 프로세스에 접근할 수 있으므로 검토가 필요하다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="133-날씨-mcp-서버"&gt;13.3 날씨 MCP 서버&lt;span class="hx:absolute hx:-mt-20" id="133-날씨-mcp-서버"&gt;&lt;/span&gt;
&lt;a href="#133-%eb%82%a0%ec%94%a8-mcp-%ec%84%9c%eb%b2%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="1331-구현"&gt;13.3.1 구현&lt;span class="hx:absolute hx:-mt-20" id="1331-구현"&gt;&lt;/span&gt;
&lt;a href="#1331-%ea%b5%ac%ed%98%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;모의 날씨를 실제 외부 날씨 API로 바꾼다. MCP 서버가 장소 입력을 받고 원천 서비스에 질의한 결과를 반환한다.&lt;/p&gt;
&lt;p&gt;장소 이름을 실제 위치 식별자로 해석하는 과정이 필요할 수 있다. 같은 이름의 도시와 국가를 구분하고 조회한 위치를 결과에 남겨야 한다.&lt;/p&gt;
&lt;p&gt;API 키는 서버 쪽에 보관하고 모델이나 클라이언트 응답에 노출하지 않는다. 실제 호출에서는 인증 실패, 요청 제한, 없는 장소와 빈 응답을 구분한다.&lt;/p&gt;
&lt;p&gt;온도 단위와 관측 시간, 현재 날씨인지 예보인지도 응답 계약에 포함해야 한다. 값만 반환하면 에이전트가 다른 시점의 데이터를 현재 상태로 해석할 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1332-mcp-inspector"&gt;13.3.2 MCP Inspector&lt;span class="hx:absolute hx:-mt-20" id="1332-mcp-inspector"&gt;&lt;/span&gt;
&lt;a href="#1332-mcp-inspector" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Inspector로 서버에 연결하고 도구 목록과 입력 스키마를 확인한 뒤 직접 호출한다. 이 과정은 LLM의 도구 선택을 제외하고 서버 연결과 실행 자체를 검증한다.&lt;/p&gt;
&lt;p&gt;서버 목록이 보이는 것, 특정 도구 호출이 되는 것, 반환한 데이터가 맞는 것은 서로 다른 확인이다. 유효한 위치와 잘못된 위치, 원천 서비스 실패를 나눠 시험해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1333-테스트-host"&gt;13.3.3 테스트 Host&lt;span class="hx:absolute hx:-mt-20" id="1333-테스트-host"&gt;&lt;/span&gt;
&lt;a href="#1333-%ed%85%8c%ec%8a%a4%ed%8a%b8-host" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;코드에서 Client를 만들고 도구 발견과 호출을 수행한다. Inspector에서 수동으로 했던 연결을 반복 가능한 테스트로 옮기는 것이다.&lt;/p&gt;
&lt;p&gt;테스트에는 입력과 출력 스키마뿐 아니라 시간 제한과 오류 응답도 포함한다. 단순 HTTP 200이 업무상 성공을 뜻하지는 않는다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="134-에이전트에-날씨-도구-연결"&gt;13.4 에이전트에 날씨 도구 연결&lt;span class="hx:absolute hx:-mt-20" id="134-에이전트에-날씨-도구-연결"&gt;&lt;/span&gt;
&lt;a href="#134-%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8%ec%97%90-%eb%82%a0%ec%94%a8-%eb%8f%84%ea%b5%ac-%ec%97%b0%ea%b2%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="1341-기존-여행-에이전트-준비"&gt;13.4.1 기존 여행 에이전트 준비&lt;span class="hx:absolute hx:-mt-20" id="1341-기존-여행-에이전트-준비"&gt;&lt;/span&gt;
&lt;a href="#1341-%ea%b8%b0%ec%a1%b4-%ec%97%ac%ed%96%89-%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8-%ec%a4%80%eb%b9%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;기존 모의 날씨 함수와 도구를 제거하고 실제 MCP 도구를 연결한다. 같은 목적의 모의 도구와 실제 도구가 함께 등록되면 모델이 엉뚱한 쪽을 선택할 수 있다.&lt;/p&gt;
&lt;p&gt;로컬 도구와 원격 도구의 이름과 설명이 비슷해도 결과의 신뢰도와 수명은 다르다. 현재 관측 데이터라는 의미가 도구 응답에 드러나야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1342-원격-도구-통합"&gt;13.4.2 원격 도구 통합&lt;span class="hx:absolute hx:-mt-20" id="1342-원격-도구-통합"&gt;&lt;/span&gt;
&lt;a href="#1342-%ec%9b%90%ea%b2%a9-%eb%8f%84%ea%b5%ac-%ed%86%b5%ed%95%a9" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;서버에서 받은 도구를 프레임워크 도구 형태로 연결한다. 이때 Client와 연결의 수명이 실제 도구 실행까지 유지되는지 확인해야 한다.&lt;/p&gt;
&lt;p&gt;도구 목록을 가져온 함수가 끝난 뒤 연결을 닫아 버리면 나중에 모델이 선택한 도구를 실행하지 못할 수 있다. 사용하는 어댑터가 세션과 연결을 어떻게 관리하는지 확인한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1343-비동기-대화-루프"&gt;13.4.3 비동기 대화 루프&lt;span class="hx:absolute hx:-mt-20" id="1343-비동기-대화-루프"&gt;&lt;/span&gt;
&lt;a href="#1343-%eb%b9%84%eb%8f%99%ea%b8%b0-%eb%8c%80%ed%99%94-%eb%a3%a8%ed%94%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;원격 I/O를 기다리는 경로에 맞춰 비동기 호출을 사용한다. &lt;code&gt;ainvoke&lt;/code&gt;와 도구의 비동기 실행을 연결할 수 있다.&lt;/p&gt;
&lt;p&gt;비동기는 기다리는 동안 다른 작업을 처리하는 데 도움을 줄 뿐, 원천 API를 빠르게 만들거나 실패를 없애지는 않는다. 취소와 시간 제한, 동시 요청 수를 따로 관리해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1344-로컬과-원격-도구-결합"&gt;13.4.4 로컬과 원격 도구 결합&lt;span class="hx:absolute hx:-mt-20" id="1344-로컬과-원격-도구-결합"&gt;&lt;/span&gt;
&lt;a href="#1344-%eb%a1%9c%ec%bb%ac%ea%b3%bc-%ec%9b%90%ea%b2%a9-%eb%8f%84%ea%b5%ac-%ea%b2%b0%ed%95%a9" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;로컬 여행 검색 도구와 원격 날씨 도구를 함께 등록할 수 있다. 모델은 제공된 정의를 보고 선택하지만 실행 계층은 서로 다르다.&lt;/p&gt;
&lt;p&gt;로그에서 로컬 검색 실패와 MCP 연결 실패, 원천 날씨 API 실패를 구분해야 한다. 모두 “도구 오류” 하나로 남기면 원인 조사가 어렵다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1345-통합-확인"&gt;13.4.5 통합 확인&lt;span class="hx:absolute hx:-mt-20" id="1345-통합-확인"&gt;&lt;/span&gt;
&lt;a href="#1345-%ed%86%b5%ed%95%a9-%ed%99%95%ec%9d%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;먼저 도구 목록에 기대한 이름과 스키마가 있는지 보고 실제 에이전트 요청에서 서버가 호출됐는지 확인한다.&lt;/p&gt;
&lt;p&gt;정상 날씨 답변처럼 보여도 과거 대화나 모델 지식에서 나온 것일 수 있다. 원격 호출 기록과 관측 시점, 반환 장소를 연결해야 실제 도구 사용을 확인할 수 있다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1346-복합-질의"&gt;13.4.6 복합 질의&lt;span class="hx:absolute hx:-mt-20" id="1346-복합-질의"&gt;&lt;/span&gt;
&lt;a href="#1346-%eb%b3%b5%ed%95%a9-%ec%a7%88%ec%9d%98" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;여행 문서에서 장소를 찾고 각 장소의 날씨를 조회한 뒤 조건을 비교한다. 장소 이름을 전달하는 단계부터 같은 대상인지 확인해야 한다.&lt;/p&gt;
&lt;p&gt;날씨가 좋은 곳을 찾는 과정은 계절과 날짜, 사용자 기준에 영향을 받는다. 모든 장소의 실시간 조회가 실패했는데 모의 데이터로 채워 성공처럼 답하는 것은 피해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="질문"&gt;질문&lt;span class="hx:absolute hx:-mt-20" id="질문"&gt;&lt;/span&gt;
&lt;a href="#%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="mcp를-붙이면-도구의-권한-문제도-해결되는가"&gt;MCP를 붙이면 도구의 권한 문제도 해결되는가?&lt;span class="hx:absolute hx:-mt-20" id="mcp를-붙이면-도구의-권한-문제도-해결되는가"&gt;&lt;/span&gt;
&lt;a href="#mcp%eb%a5%bc-%eb%b6%99%ec%9d%b4%eb%a9%b4-%eb%8f%84%ea%b5%ac%ec%9d%98-%ea%b6%8c%ed%95%9c-%eb%ac%b8%ec%a0%9c%eb%8f%84-%ed%95%b4%ea%b2%b0%eb%90%98%eb%8a%94%ea%b0%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;연결 규약과 권한 정책은 다르다. Host와 서버, 원천 서비스 각각에서 인증과 허용 범위를 확인해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="inspector에서-동작하면-에이전트-통합도-끝났을까"&gt;Inspector에서 동작하면 에이전트 통합도 끝났을까?&lt;span class="hx:absolute hx:-mt-20" id="inspector에서-동작하면-에이전트-통합도-끝났을까"&gt;&lt;/span&gt;
&lt;a href="#inspector%ec%97%90%ec%84%9c-%eb%8f%99%ec%9e%91%ed%95%98%eb%a9%b4-%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8-%ed%86%b5%ed%95%a9%eb%8f%84-%eb%81%9d%eb%82%ac%ec%9d%84%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;모델이 올바른 도구와 인자를 선택하고 결과를 제대로 해석하는지는 별도 확인이다.&lt;/p&gt;
&lt;p&gt;&lt;a href="../12-multi-agent-systems/"&gt;이전: 다중 에이전트&lt;/a&gt; / &lt;a href="../"&gt;시리즈 목차&lt;/a&gt; / &lt;a href="../14-production-agents/"&gt;다음: 프로덕션 에이전트&lt;/a&gt;&lt;/p&gt;</description></item><item><title>14. 프로덕션 에이전트</title><link>https://johklo.github.io/docs/tech/ai-agents-and-applications/14-production-agents/</link><pubDate>Mon, 19 Oct 2026 21:37:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/ai-agents-and-applications/14-production-agents/</guid><description>
&lt;p&gt;한 번의 요청이 동작하는 것과 대화를 이어 가며 안전하게 운영하는 것은 다르다. 이전 문맥을 보존하고, 허용 범위를 벗어난 요청을 막고, 실패나 중단 뒤 어떤 상태에서 다시 시작할지 정해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="141-메모리"&gt;14.1 메모리&lt;span class="hx:absolute hx:-mt-20" id="141-메모리"&gt;&lt;/span&gt;
&lt;a href="#141-%eb%a9%94%eb%aa%a8%eb%a6%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;메모리는 모델의 가중치에 모든 대화를 새로 학습시키는 기능이 아니다. 애플리케이션이 필요한 상태를 저장하고 다음 실행의 입력으로 복원하는 구조다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1411-메모리의-범위"&gt;14.1.1 메모리의 범위&lt;span class="hx:absolute hx:-mt-20" id="1411-메모리의-범위"&gt;&lt;/span&gt;
&lt;a href="#1411-%eb%a9%94%eb%aa%a8%eb%a6%ac%ec%9d%98-%eb%b2%94%ec%9c%84" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;범위&lt;/th&gt;
&lt;th&gt;예시&lt;/th&gt;
&lt;th&gt;별도로 정할 것&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;단기 대화&lt;/td&gt;
&lt;td&gt;이번 여행에서 선택한 마을과 날짜&lt;/td&gt;
&lt;td&gt;Thread 구분과 문맥 예산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;장기 사용자&lt;/td&gt;
&lt;td&gt;여러 대화에서 사용할 선호&lt;/td&gt;
&lt;td&gt;동의, 수정과 삭제, 사용자 격리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;장기 애플리케이션&lt;/td&gt;
&lt;td&gt;공통 정책과 여행 정보&lt;/td&gt;
&lt;td&gt;출처, 유효 시점과 갱신&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;단기와 장기는 무조건 저장 매체로 구분되지 않는다. 대화 범위의 상태도 디스크 DB에 오래 보관할 수 있다. 무엇을 어느 범위에서 재사용하는지가 먼저다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1412-단기-메모리가-필요한-이유"&gt;14.1.2 단기 메모리가 필요한 이유&lt;span class="hx:absolute hx:-mt-20" id="1412-단기-메모리가-필요한-이유"&gt;&lt;/span&gt;
&lt;a href="#1412-%eb%8b%a8%ea%b8%b0-%eb%a9%94%eb%aa%a8%eb%a6%ac%ea%b0%80-%ed%95%84%ec%9a%94%ed%95%9c-%ec%9d%b4%ec%9c%a0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;“같은 마을에서 객실을 찾아 줘”라는 질문은 앞에서 고른 마을을 알아야 한다. 매 요청마다 새 상태로 호출하면 그 연결이 사라진다.&lt;/p&gt;
&lt;p&gt;도구 호출 대화에서는 사용자와 모델의 문장만이 아니라 호출 요청과 도구 결과의 관계도 중요하다. 일부 메시지만 보관하면 프로토콜상 대응이 끊기거나 결과를 잘못 해석할 수 있다.&lt;/p&gt;
&lt;p&gt;메모리가 있다고 모든 과거 메시지를 매번 모델에 넣어야 하는 것은 아니다. 필요한 범위를 선택하고 요약할 수 있지만 중요한 날짜와 제약, 미확인 사항을 잃지 않아야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1413-langgraph-체크포인트"&gt;14.1.3 LangGraph 체크포인트&lt;span class="hx:absolute hx:-mt-20" id="1413-langgraph-체크포인트"&gt;&lt;/span&gt;
&lt;a href="#1413-langgraph-%ec%b2%b4%ed%81%ac%ed%8f%ac%ec%9d%b8%ed%8a%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;체크포인트는 특정 실행 경계의 그래프 상태 스냅샷이다. 대화뿐 아니라 노드의 중간 결과와 다음 실행에 필요한 정보를 포함할 수 있다.&lt;/p&gt;
&lt;p&gt;LangGraph의 실행은 super-step이라는 단위로 이해할 수 있다. 같은 단계에서 병렬 노드가 실행될 수도 있으므로 “항상 함수 한 줄마다 저장” 또는 “무조건 각 개별 노드 직후에 독립 스냅샷”으로 외우는 것은 부정확하다.&lt;/p&gt;
&lt;p&gt;Thread ID는 상태를 이어 갈 실행 묶음의 식별자다. 같은 Thread에 새 질문을 넣으면 저장된 상태를 바탕으로 이어 가고, 다른 Thread면 별도 대화가 된다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1414-체크포인터-연결"&gt;14.1.4 체크포인터 연결&lt;span class="hx:absolute hx:-mt-20" id="1414-체크포인터-연결"&gt;&lt;/span&gt;
&lt;a href="#1414-%ec%b2%b4%ed%81%ac%ed%8f%ac%ec%9d%b8%ed%84%b0-%ec%97%b0%ea%b2%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;그래프를 compile할 때 체크포인터를 연결하고 호출 설정에 Thread ID를 제공한다. 인메모리 저장은 실습에 편하지만 프로세스를 재시작하면 사라진다.&lt;/p&gt;
&lt;p&gt;운영에서는 영속 저장소와 접근 정책, 보존 기간을 정해야 한다. Thread ID를 알고 있다는 사실만으로 그 상태를 읽을 권한이 있다고 봐서는 안 된다. 인증된 사용자와 Thread 소유권의 연결이 필요하다.&lt;/p&gt;
&lt;p&gt;모델 제공자의 이전 응답 ID를 사용하는 방식과 LangGraph의 상태 저장은 다른 계층이다. 제공자와 라이브러리 버전에 따라 메시지 전달 규칙이 다르므로 특정 설정이 모든 조합에서 반드시 필요하다고 일반화하지 않는다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1415-연속-실행"&gt;14.1.5 연속 실행&lt;span class="hx:absolute hx:-mt-20" id="1415-연속-실행"&gt;&lt;/span&gt;
&lt;a href="#1415-%ec%97%b0%ec%86%8d-%ec%8b%a4%ed%96%89" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;첫 요청에서 장소를 정하고 같은 Thread로 후속 질문을 보내 대상이 유지되는지 확인한다. 다른 Thread에서는 그 정보가 섞이지 않아야 한다.&lt;/p&gt;
&lt;p&gt;저장소에 상태가 남는 것과 모델이 적절한 문맥을 받는 것도 별개다. 모델 호출 전에 메시지를 줄이거나 재구성한다면 실제 전달한 내용을 확인해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1416-과거-상태로-돌아가기"&gt;14.1.6 과거 상태로 돌아가기&lt;span class="hx:absolute hx:-mt-20" id="1416-과거-상태로-돌아가기"&gt;&lt;/span&gt;
&lt;a href="#1416-%ea%b3%bc%ea%b1%b0-%ec%83%81%ed%83%9c%eb%a1%9c-%eb%8f%8c%ec%95%84%ea%b0%80%ea%b8%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;이전 체크포인트를 선택해 당시 상태를 확인하거나 다른 방향으로 실행을 이어 갈 수 있다. 같은 질문에서 다른 선택을 했을 때를 비교하는 데 유용하다.&lt;/p&gt;
&lt;p&gt;그러나 상태를 되감는다고 외부 세계도 되돌아가지는 않는다. 이미 보낸 메시지나 완료한 결제, 생성한 예약은 별도의 작업이다.&lt;/p&gt;
&lt;div role="img" aria-label="다이어그램"&gt;
&lt;pre class="mermaid hx:mt-6"&gt;
flowchart TD
accTitle: 그래프 상태 복원과 외부 부작용의 다른 수명
accDescr: 과거 체크포인트를 복원하면 저장된 그래프 상태로 돌아가지만 이미 실행한 외부 예약은 취소되지 않는다. 재실행에는 중복 방지와 별도 취소 절차가 필요하다.
C[&amp;#34;체크포인트 A&amp;#34;] --&amp;gt; T[&amp;#34;예약 도구 실행&amp;#34;]
T --&amp;gt; N[&amp;#34;체크포인트 B&amp;#34;]
T --&amp;gt; E[&amp;#34;외부 시스템에 예약 생성&amp;#34;]
N --&amp;gt;|&amp;#34;상태 복원&amp;#34;| C
C -.-&amp;gt;|&amp;#34;자동으로 취소되지 않음&amp;#34;| E
&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;재실행 가능한 노드에는 중복 방지 키와 이미 실행한 결과를 확인하는 절차가 필요하다. 읽기 전용 조회와 부작용이 있는 행동의 복구 정책을 나눠야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="142-가드레일"&gt;14.2 가드레일&lt;span class="hx:absolute hx:-mt-20" id="142-가드레일"&gt;&lt;/span&gt;
&lt;a href="#142-%ea%b0%80%eb%93%9c%eb%a0%88%ec%9d%bc" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;가드레일은 입력과 처리, 출력에 적용하는 범위와 정책 검사다. 잘못된 요청을 일찍 막고 모델이나 도구의 결과가 허용 조건을 만족하는지 확인한다.&lt;/p&gt;
&lt;p&gt;단순한 주제 분류, 구조 검증과 민감정보 검사, 도구 권한 통제는 서로 다른 역할이다. 모델에게 “규칙을 지켜라”라고 말하는 것만으로 모두 해결되지 않는다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1421-여행과-무관한-요청-차단"&gt;14.2.1 여행과 무관한 요청 차단&lt;span class="hx:absolute hx:-mt-20" id="1421-여행과-무관한-요청-차단"&gt;&lt;/span&gt;
&lt;a href="#1421-%ec%97%ac%ed%96%89%ea%b3%bc-%eb%ac%b4%ea%b4%80%ed%95%9c-%ec%9a%94%ec%b2%ad-%ec%b0%a8%eb%8b%a8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;입력에서 서비스 범위에 맞는 질문인지 분류한다. 여행 도우미를 일반적인 무료 모델 호출 창구처럼 사용하는 것을 줄일 수 있다.&lt;/p&gt;
&lt;p&gt;그렇지만 분류 모델도 틀릴 수 있다. 정상 요청을 막는 오차단과 범위 밖 요청을 통과시키는 미차단을 함께 평가해야 한다.&lt;/p&gt;
&lt;p&gt;주제 검사는 권한 검사가 아니다. 여행 질문이라는 이유로 다른 사용자의 예약 정보에 접근해도 되는 것은 아니다. 허용 자료와 작업은 실행 계층에서 강제한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1422-에이전트별-추가-제한"&gt;14.2.2 에이전트별 추가 제한&lt;span class="hx:absolute hx:-mt-20" id="1422-에이전트별-추가-제한"&gt;&lt;/span&gt;
&lt;a href="#1422-%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8%eb%b3%84-%ec%b6%94%ea%b0%80-%ec%a0%9c%ed%95%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;상위 입력이 여행 관련이어도 전문 에이전트가 지원하는 지역과 기능은 더 좁을 수 있다. Cornwall 자료만 가진 도구가 다른 지역의 사실을 추측하지 않도록 범위를 확인한다.&lt;/p&gt;
&lt;p&gt;상위 Router의 검사와 하위 에이전트의 검사는 역할이 다르다. 내부 경로가 바뀌거나 도구가 직접 호출되더라도 필요한 제약을 유지해야 한다.&lt;/p&gt;
&lt;p&gt;검사 서비스가 실패했을 때의 정책도 필요하다. 위험한 작업에서 확인 실패를 정상 통과로 처리하면 통제가 사라진다. 제한 응답이나 사람의 검토 등 실패 동작을 명시한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="143-운영에서-더-필요한-것"&gt;14.3 운영에서 더 필요한 것&lt;span class="hx:absolute hx:-mt-20" id="143-운영에서-더-필요한-것"&gt;&lt;/span&gt;
&lt;a href="#143-%ec%9a%b4%ec%98%81%ec%97%90%ec%84%9c-%eb%8d%94-%ed%95%84%ec%9a%94%ed%95%9c-%ea%b2%83" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="1431-장기-사용자와-애플리케이션-메모리"&gt;14.3.1 장기 사용자와 애플리케이션 메모리&lt;span class="hx:absolute hx:-mt-20" id="1431-장기-사용자와-애플리케이션-메모리"&gt;&lt;/span&gt;
&lt;a href="#1431-%ec%9e%a5%ea%b8%b0-%ec%82%ac%ec%9a%a9%ec%9e%90%ec%99%80-%ec%95%a0%ed%94%8c%eb%a6%ac%ec%bc%80%ec%9d%b4%ec%85%98-%eb%a9%94%eb%aa%a8%eb%a6%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;장기 메모리는 여러 Thread에서 재사용할 정보다. 사용자 선호와 공통 지식을 같은 범위에 저장하면 정보가 섞일 수 있다.&lt;/p&gt;
&lt;p&gt;저장할 사실의 출처와 유효 시점, 수정과 삭제 경로를 정해야 한다. 과거 모델의 추측을 사용자에 대한 확정 사실로 저장하지 않는다.&lt;/p&gt;
&lt;p&gt;체크포인터는 Thread의 그래프 상태를, Store는 Thread 밖의 애플리케이션 데이터를 보관하는 역할로 구분할 수 있다. &lt;a href="https://docs.langchain.com/oss/python/langgraph/persistence" target="_blank" rel="noopener"&gt;LangGraph 영속성 문서&lt;/a&gt;도 두 저장 체계를 나눠 설명한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1432-human-in-the-loop"&gt;14.3.2 Human-in-the-loop&lt;span class="hx:absolute hx:-mt-20" id="1432-human-in-the-loop"&gt;&lt;/span&gt;
&lt;a href="#1432-human-in-the-loop" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;사람의 검토는 모호한 요청이나 외부 행동 전에 실행을 멈추고 확인받는 과정이다. 사용자가 원하는 최종 조건과 실제 실행할 인자를 보여 줘야 의미 있는 승인이 된다.&lt;/p&gt;
&lt;p&gt;예약이라면 숙소, 날짜와 인원, 가격 및 취소 조건을 승인 대상에 연결한다. 승인 뒤 내용이 바뀌었다면 이전 승인을 새 요청에 재사용해서는 안 된다.&lt;/p&gt;
&lt;p&gt;중단된 실행을 누가 재개할 수 있고 얼마나 기다릴지, 승인 거절과 만료를 어떻게 처리할지도 필요하다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1433-출력-가드레일"&gt;14.3.3 출력 가드레일&lt;span class="hx:absolute hx:-mt-20" id="1433-출력-가드레일"&gt;&lt;/span&gt;
&lt;a href="#1433-%ec%b6%9c%eb%a0%a5-%ea%b0%80%eb%93%9c%eb%a0%88%ec%9d%bc" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;출력에서는 민감한 정보, 근거 없는 주장과 잘못된 형식을 확인한다. 사용자에게 보여 주는 자연어와 외부 도구에 보낼 구조화된 행동 요청은 다른 기준이 필요하다.&lt;/p&gt;
&lt;p&gt;존재하지 않는 인용을 검사할 때는 링크 문법뿐 아니라 해당 문서가 실제 주장을 뒷받침하는지 봐야 한다. 구조 검증만으로 의미적 안전을 보장할 수는 없다.&lt;/p&gt;
&lt;p&gt;사후 검사는 이미 실행한 행동을 막을 수 없다. 예약과 결제의 승인과 검증은 실행 전에 있어야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1434-평가"&gt;14.3.4 평가&lt;span class="hx:absolute hx:-mt-20" id="1434-평가"&gt;&lt;/span&gt;
&lt;a href="#1434-%ed%8f%89%ea%b0%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;평가&lt;/th&gt;
&lt;th&gt;확인할 것&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;기능&lt;/td&gt;
&lt;td&gt;질문에 맞는 근거와 완전한 답&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;행동&lt;/td&gt;
&lt;td&gt;범위와 정책 준수, 올바른 거절&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;도구&lt;/td&gt;
&lt;td&gt;선택, 인자, 실행 결과와 호출 순서&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;대화&lt;/td&gt;
&lt;td&gt;후속 질문의 문맥, 사용자 격리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;복구&lt;/td&gt;
&lt;td&gt;중단 뒤 재개, 중복 실행 방지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;성능과 비용&lt;/td&gt;
&lt;td&gt;지연, 호출 수, 토큰과 외부 API 비용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;회귀&lt;/td&gt;
&lt;td&gt;모델과 프롬프트, 도구 변경 전후 비교&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;최종 답만 맞았어도 필요 없는 도구를 수십 번 호출했다면 운영 비용이 클 수 있다. 반대로 지정한 경로를 모두 따랐어도 근거를 잘못 읽으면 답이 틀린다. 결과와 실행 경로를 함께 평가해야 한다.&lt;/p&gt;
&lt;p&gt;실시간 정보는 정답이 바뀐다. 회귀 시험에서는 고정된 도구 응답을 사용해 비교하고, 실제 연동 시험에서는 현재 서비스의 연결과 데이터 유효성을 별도로 확인하는 방법이 유용하다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="1435-배포"&gt;14.3.5 배포&lt;span class="hx:absolute hx:-mt-20" id="1435-배포"&gt;&lt;/span&gt;
&lt;a href="#1435-%eb%b0%b0%ed%8f%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;책은 LangGraph 기반 플랫폼과 호스팅 선택을 소개한다. 관리형 환경을 사용해도 서비스의 데이터 경계와 모델 품질, 권한과 비용을 모두 대신 결정해 주지는 않는다.&lt;/p&gt;
&lt;p&gt;자체 배포에서는 API 서버와 실행 워커, 상태 저장소와 도구 연결의 수명을 관리해야 한다. 긴 실행의 취소와 재개, 버전 변경 시 저장된 상태의 호환성도 필요하다.&lt;/p&gt;
&lt;p&gt;트레이스와 체크포인트에는 대화와 도구 결과가 남는다. 인증과 접근 통제, 암호화와 보존 정책을 적용하고 실제로 삭제할 수 있는지까지 확인해야 한다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="질문"&gt;질문&lt;span class="hx:absolute hx:-mt-20" id="질문"&gt;&lt;/span&gt;
&lt;a href="#%ec%a7%88%eb%ac%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="체크포인트가-있으면-실패한-작업을-그대로-재실행해도-될까"&gt;체크포인트가 있으면 실패한 작업을 그대로 재실행해도 될까?&lt;span class="hx:absolute hx:-mt-20" id="체크포인트가-있으면-실패한-작업을-그대로-재실행해도-될까"&gt;&lt;/span&gt;
&lt;a href="#%ec%b2%b4%ed%81%ac%ed%8f%ac%ec%9d%b8%ed%8a%b8%ea%b0%80-%ec%9e%88%ec%9c%bc%eb%a9%b4-%ec%8b%a4%ed%8c%a8%ed%95%9c-%ec%9e%91%ec%97%85%ec%9d%84-%ea%b7%b8%eb%8c%80%eb%a1%9c-%ec%9e%ac%ec%8b%a4%ed%96%89%ed%95%b4%eb%8f%84-%eb%90%a0%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;외부 부작용이 이미 발생했을 수 있다. 저장된 그래프 상태와 외부 시스템의 실제 상태를 함께 확인해야 한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="메모리와-가드레일을-붙이면-프로덕션-준비가-끝날까"&gt;메모리와 가드레일을 붙이면 프로덕션 준비가 끝날까?&lt;span class="hx:absolute hx:-mt-20" id="메모리와-가드레일을-붙이면-프로덕션-준비가-끝날까"&gt;&lt;/span&gt;
&lt;a href="#%eb%a9%94%eb%aa%a8%eb%a6%ac%ec%99%80-%ea%b0%80%eb%93%9c%eb%a0%88%ec%9d%bc%ec%9d%84-%eb%b6%99%ec%9d%b4%eb%a9%b4-%ed%94%84%eb%a1%9c%eb%8d%95%ec%85%98-%ec%a4%80%eb%b9%84%ea%b0%80-%eb%81%9d%eb%82%a0%ea%b9%8c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;평가, 관측, 권한과 비용, 복구 및 운영 책임이 남는다. 기능이 존재하는 것과 실제 요구에서 검증한 것은 다르다.&lt;/p&gt;
&lt;p&gt;&lt;a href="../13-mcp-servers/"&gt;이전: MCP 서버&lt;/a&gt; / &lt;a href="../"&gt;시리즈 목차&lt;/a&gt; / &lt;a href="../00-overview/"&gt;책의 구성&lt;/a&gt;&lt;/p&gt;</description></item><item><title>[책] 유한 게임 무한 게임</title><link>https://johklo.github.io/docs/book/2026-10-16-/</link><pubDate>Fri, 16 Oct 2026 10:14:00 +0900</pubDate><guid>https://johklo.github.io/docs/book/2026-10-16-/</guid><description>
&lt;p&gt;&amp;ldquo;There are at least two kinds of games: finite and infinite. A finite game is played for the purpose of winning, an infinite game for the purpose of continuing the play.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;유한게임은 이기려고 한다.&lt;br&gt;
규칙과 선수와 끝이 정해져 있고, 승자가 나오면 끝난다.&lt;br&gt;
무한게임은 계속하려고 한다.&lt;br&gt;
규칙은 바뀌고 경계는 움직인다.&lt;/p&gt;
&lt;p&gt;인생에는 승자가 없다.&lt;br&gt;
누구도 끝까지 남지 않지만 게임은 계속되기 때문이다.&lt;br&gt;
허무해서가 아니라, 애초에 승부의 대상이 아니라는 뜻이다.&lt;/p&gt;
&lt;p&gt;인생을 이기려고만 하면 유한한 게임을 할 수밖에 없다.&lt;br&gt;
이기려면 끝이 있어야 하고, 끝이 있으려면 경계를 그어야 하니까.&lt;br&gt;
그래서 결국 열어두는 일이다.&lt;/p&gt;
&lt;p&gt;닫으면 이길 수 있고, 열어두면 계속할 수 있다.&lt;br&gt;
다만 열어둔다는 것이 애쓰지 않는다는 뜻은 아니다.&lt;br&gt;
계속하려면 오히려 방식을 계속 고쳐야 한다.&lt;/p&gt;
&lt;p&gt;지금 이기려는 이것이 애초에 이겨도 되는 것인가.&lt;/p&gt;</description></item><item><title>Envoy Architecture</title><link>https://johklo.github.io/docs/tech/envoyarchitecture/</link><pubDate>Sat, 10 Oct 2026 07:33:00 +0000</pubDate><guid>https://johklo.github.io/docs/tech/envoyarchitecture/</guid><description>
&lt;h2 data-hextra-search-id="envoy-architecture"&gt;&lt;strong&gt;Envoy Architecture&lt;/strong&gt;&lt;span class="hx:absolute hx:-mt-20" id="envoy-architecture"&gt;&lt;/span&gt;
&lt;a href="#envoy-architecture" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;img src="https://ssup2.github.io/blog-software/docs/theory-analysis/envoy-architecture/images/envoy-architecture.png" alt="[Figure 1] Envoy Architecture" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;[Figure 1] Envoy Architecture&lt;/p&gt;
&lt;p&gt;[Figure 1]은 Envoy Architecture를 나타내고 있다. Envoy Architecture는 &lt;strong&gt;libevent&lt;/strong&gt;와 &lt;strong&gt;io_uring&lt;/strong&gt;을 기반으로 동작하는 &lt;strong&gt;Dispatcher&lt;/strong&gt;를 활용하여 &lt;strong&gt;Event Driven Architecture&lt;/strong&gt;를 구현하고 있으며, 성능 최적화를 위해서 각 Thread마다 전용 저장소인 &lt;strong&gt;TLS&lt;/strong&gt; (Thread Local Storage)를 활용하여 Lock 활용을 최소화하며 동작한다. Thread 관점에서는 &lt;strong&gt;Main Thread&lt;/strong&gt;, &lt;strong&gt;Worker Thread&lt;/strong&gt;, &lt;strong&gt;Flush Thread&lt;/strong&gt;로 구분된다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="11-main-thread"&gt;&lt;strong&gt;1.1. Main Thread&lt;/strong&gt;&lt;span class="hx:absolute hx:-mt-20" id="11-main-thread"&gt;&lt;/span&gt;
&lt;a href="#11-main-thread" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Main Thread는 Envoy 초기화 및 Worker Thread의 Control Plane 역할을 수행한다. Dispatcher를 통해서 OS Signal, Timer, Socket, inotify 등의 다양한 Event를 수신하고, 해당 Event를 적절한 Module에 전달하여 처리한다. 각 Module은 상태 저장이 필요한 경우 Main Thread의 TLS에 저장하며, Worker Thread에 Data를 전달이 필요한 경우에도 TLS와 Dispatcher를 통해서 전달한다. Main Thread에서 동작하는 Module은 다음과 같다.&lt;/p&gt;
&lt;h4 data-hextra-search-id="111-runtime"&gt;&lt;strong&gt;1.1.1. Runtime&lt;/strong&gt;&lt;span class="hx:absolute hx:-mt-20" id="111-runtime"&gt;&lt;/span&gt;
&lt;a href="#111-runtime" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Runtime&lt;/strong&gt;은 재시작 없이 동적으로 바꿀수 있는 설정 값인 Feature Flag를 관리하는 역할을 수행한다. Feature Flag는 &lt;strong&gt;Snapshot&lt;/strong&gt; 형태로 TLS Slot에 저장되어 이용된다. Feature Flag는 JSON 형태의 파일로 구성하는 &lt;strong&gt;Static Bootstrap&lt;/strong&gt; 방식, Directory와 File을 활용하여 계층으로 구성하는 &lt;strong&gt;File System Layout&lt;/strong&gt; 방식, &lt;code&gt;/runtime_modify&lt;/code&gt; Admin Endpoint를 통해서 관리하는 &lt;strong&gt;Admin Console&lt;/strong&gt; 방식, xDS중 하나인 RTDS (Runtime Discovery Service)를 활용하는 &lt;strong&gt;Runtime Discovery Service&lt;/strong&gt; 방식으로 관리된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Static Bootstrap&lt;/strong&gt; 방식을 제외하고는 모두 Envoy 재시작 없이 동적으로 Feature Flag를 변경할 수 있으며, File System Layout 방식을 이용할 경우 파일의 변화를 Dispatcher와 inotify를 통해서 감지하여 Feature Flag를 동적으로 변경한다.&lt;/p&gt;
&lt;h4 data-hextra-search-id="112-xds-extensible-discovery-service"&gt;&lt;strong&gt;1.1.2. xDS (eXtensible Discovery Service)&lt;/strong&gt;&lt;span class="hx:absolute hx:-mt-20" id="112-xds-extensible-discovery-service"&gt;&lt;/span&gt;
&lt;a href="#112-xds-extensible-discovery-service" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;xDS (eXtensible Discovery Service)는 Envoy가 Envoy를 관리하는 Control Plane으로부터 필요한 설정 정보를 동적으로 가져오는 역할을 수행한다. xDS는 역할에 따라서 다음과 같은 종류가 존재한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LDS (Listener Discovery Service)&lt;/strong&gt; : Listener의 Port, Listener/Network Filter Chain 설정 정보를 가져온다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RDS (Route Discovery Service)&lt;/strong&gt; : HTTP 기반으로 어느 Cluster로 요청을 전달할지 결정하는 HTTP 기반 라우팅 설정 정보를 가져온다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CDS (Cluster Discovery Service)&lt;/strong&gt; : Cluster의 정보를 가져온다. 여기서 Cluster는 Endpoint (Upstream Server)의 집합을 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;EDS (Endpoint Discovery Service)&lt;/strong&gt; : Cluster의 실제 Endpoint 정보를 가져온다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SDS (Secret Discovery Service)&lt;/strong&gt; : TLS 인증서 정보를 가져온다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RTDS (Runtime Discovery Service)&lt;/strong&gt; : Runtime Feature Flag 정보를 가져온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-hextra-search-id="113-listener-manager-cluster-manager-secret-manager-route-config-manager"&gt;&lt;strong&gt;1.1.3. Listener Manager, Cluster Manager, Secret Manager, Route Config Manager&lt;/strong&gt;&lt;span class="hx:absolute hx:-mt-20" id="113-listener-manager-cluster-manager-secret-manager-route-config-manager"&gt;&lt;/span&gt;
&lt;a href="#113-listener-manager-cluster-manager-secret-manager-route-config-manager" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;Listener Manager, Cluster Manager, Secret Manager, Route Config Manager는 xDS를 통해서 가져온 설정 정보를 관리하고, 필요에 따라서 Worker Thread에 &lt;strong&gt;TLS&lt;/strong&gt;와 &lt;strong&gt;Dispatcher&lt;/strong&gt;를 통해서 전달한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Listener Manager&lt;/strong&gt; : LDS를 통해서 가져온 Listener 및 Listenter/Network Filter Chain 설정 정보를 Worker Thread에 전달하여, Worker Thread가 Listener 설정 및 Listenter/Network Filter Chain을 생성하도록 만든다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cluster Manager&lt;/strong&gt; : CDS, EDS를 통해서 가져온 Cluster, Endpoint의 Worker Thread에 TLS에 설정하여 Network Filter의 Router Filter가 적절한 Upstream으로 요청을 전달하도록 만든다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secret Manager&lt;/strong&gt; : SDS를 통해서 가져온 TLS 인증서 정보를 Worker Thread의 TLS Transport Socket에 전달한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Route Config Manager&lt;/strong&gt; : RDS를 통해서 가져온 Route Config 정보를 HCM (HTTP Connection Manager)에 전달하여, HCM Router Filter가 적절한 Cluster로 요청을 전달하도록 만든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-hextra-search-id="113-stats-flush"&gt;&lt;strong&gt;1.1.3. Stats Flush&lt;/strong&gt;&lt;span class="hx:absolute hx:-mt-20" id="113-stats-flush"&gt;&lt;/span&gt;
&lt;a href="#113-stats-flush" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;Stats Flush는 Dispatcher와 Timer를 활용하여 주기적으로 깨어나며 Envoy 관련 통계/Metric 정보를 TLS에 저장하고, 설정에 따라서 StatsD Server로 통계/Metric 정보를 전송하는 역할을 수행한다.&lt;/p&gt;
&lt;h4 data-hextra-search-id="114-drain-manager"&gt;&lt;strong&gt;1.1.4. Drain Manager&lt;/strong&gt;&lt;span class="hx:absolute hx:-mt-20" id="114-drain-manager"&gt;&lt;/span&gt;
&lt;a href="#114-drain-manager" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;Drain Manager는 Dispatcher를 통해서 SIGTERM Signal을 수신하면, 곧바로 Envoy를 종료하는게 아니라 현재 처리중인 Request를 종료하고 종료 완료 후에 Envoy를 종료하는 역할을 수행한다.&lt;/p&gt;
&lt;h4 data-hextra-search-id="115-admin"&gt;&lt;strong&gt;1.1.5. Admin&lt;/strong&gt;&lt;span class="hx:absolute hx:-mt-20" id="115-admin"&gt;&lt;/span&gt;
&lt;a href="#115-admin" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;Admin은 Envoy 관리 및 상태 정보 조회를 위한 Endpoint를 제공하는 역할을 수행한다. 주요 Endpoint는 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/stats&lt;/code&gt; : Envoy 관련 통계/Metric 정보를 조회하는 Endpoint. 내부적으로 Stats Flush Module이 TLS에 저장한 정보를 조회하여 제공한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/stats/prometheus&lt;/code&gt; : Prometheus Exporter 형태로 Envoy 관련 통계/Metric 정보를 제공한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/reset_counters&lt;/code&gt; : 통계/Metric 정보를 초기화하는 기능을 제공한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/clusters&lt;/code&gt; : Cluster 정보를 제공한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/listeners&lt;/code&gt; : Listener 정보를 제공한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/memory&lt;/code&gt; : Envoy 메모리 사용 정보를 제공한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/ready&lt;/code&gt; : Envoy 준비 상태를 제공한다. (Readiness Probe)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/runtime&lt;/code&gt; : 현재 Runtime Feature Flag 정보를 제공한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/runtime_modify&lt;/code&gt; : Runtime Feature Flag를 동적으로 변경하는 Endpoint.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/server_info&lt;/code&gt; : Envoy 서버 정보를 제공한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/config_dump&lt;/code&gt; : Envoy 서버의 전체 설정 정보를 제공한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/logging&lt;/code&gt; : Logging Level을 변경하는 기능을 제공한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/drain_listeners&lt;/code&gt; : Listener를 종료하는 기능을 제공한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/quitquitquit&lt;/code&gt; : Envoy를 종료하는 기능을 제공한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/cpuprofile&lt;/code&gt; : CPU Profile을 수집하는 기능을 제공한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/memoryprofile&lt;/code&gt; : Memory Profile을 수집하는 기능을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-hextra-search-id="116-guarddog"&gt;&lt;strong&gt;1.1.6. GuardDog&lt;/strong&gt;&lt;span class="hx:absolute hx:-mt-20" id="116-guarddog"&gt;&lt;/span&gt;
&lt;a href="#116-guarddog" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;Main/Worker Thread가 동작하는지 검사하는 역할을 수행한다. GuardDog는 Main/Worker Thread가 동작하지 않는다라고 판단하면 Envoy를 종료하는 역할을 수행한다.&lt;/p&gt;
&lt;h4 data-hextra-search-id="117-hot-restart"&gt;&lt;strong&gt;1.1.7. Hot Restart&lt;/strong&gt;&lt;span class="hx:absolute hx:-mt-20" id="117-hot-restart"&gt;&lt;/span&gt;
&lt;a href="#117-hot-restart" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;Hot Restart는 Envoy 재시작시 Downtime 없이 새로운 Envoy 프로세스로 교체하는 기능을 의미한다. 일반적으로 Systemd와 같이 프로세스/Daemon을 관리하는 Component가 필요에 따라서 UDS (Unix Domain Socket)을 통해서 Envoy Hot Restart를 요청한다. Hot Restart는 다음과 같은 과정을 통해서 진행된다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;새로운 Envoy 프로세스를 생성한다.&lt;/li&gt;
&lt;li&gt;기존의 Envoy 프로세스의 Listen Socket을 새로운 Envoy 프로세스에서 UDS (Unix Domain Socket)을 통해서 전달한다.&lt;/li&gt;
&lt;li&gt;새로운 Envoy 프로세스는 전달받은 Listen Socket을 통해서 Client의 신규 Connection을 수락한다.&lt;/li&gt;
&lt;li&gt;기존의 Envoy 프로세스는 Drain Manager를 통해서 현재 처리중인 Request를 종료하고 종료 완료 후에 Envoy를 종료한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-hextra-search-id="118-access-logger-notification"&gt;&lt;strong&gt;1.1.8. Access Logger Notification&lt;/strong&gt;&lt;span class="hx:absolute hx:-mt-20" id="118-access-logger-notification"&gt;&lt;/span&gt;
&lt;a href="#118-access-logger-notification" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;Access Logger Notification은 Dispatcher와 Timer를 통해서 주기적으로 깨어나 Flush Thread를 깨우는 역할을 수행한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="12-worker-thread"&gt;&lt;strong&gt;1.2. Worker Thread&lt;/strong&gt;&lt;span class="hx:absolute hx:-mt-20" id="12-worker-thread"&gt;&lt;/span&gt;
&lt;a href="#12-worker-thread" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Worker Thread는 &lt;strong&gt;Downstream&lt;/strong&gt; (Client)의 요청을 받아 처리 이후에 &lt;strong&gt;Upstream&lt;/strong&gt; (Server)로 요청을 전달하는 Thread이다. Main Thread와 유사하게 Dispatcher를 통해서 Socket, Timer Event를 수신하며, 상태 저장이 필요한 경우에 각 Worker Thread마다 가지고 있는 전용 TLS에 저장한다.&lt;/p&gt;
&lt;p&gt;하나의 Downstream은 하나의 Worker Thread와 Connection을 맺는다. 반면에 모든 Worker Thread는 모든 Upstream과 Connection을 맺는다. 이러한 이유는 Worker Thread 사이에는 상태 정보를 공유하지 않기 때문에, Downstream이 어떤 Worker Thread와 Connection을 맺더라도 Upstream으로 요청을 전달할 수 있어야 하기 때문이다. 이 의미는 Worker Thread의 개수에 비례하여 Upstream과의 Connection 개수도 증가하는걸 의미하며, 따라서 너무 많은 Worker Thread를 생성하면 Upstream과의 Connection 유지를 위한 Memory 낭비가 발생하게 된다.&lt;/p&gt;
&lt;p&gt;[Figure 1]에서는 Downstream A/B가 각기 다른 Worker Thread와 Connnection을 맺고 있으며, 4개의 Worker Thread가 존재하기 때문에 모든 Worker Thread가 Upstream과의 Connection을 유지하기 위해서 Worker Thread의 개수인 4개의 Connection을 유지하고 있는것을 확인할 수 있다. Worker Thread에서 동작하는 Module은 다음과 같다.&lt;/p&gt;
&lt;h4 data-hextra-search-id="121-listener"&gt;&lt;strong&gt;1.2.1. Listener&lt;/strong&gt;&lt;span class="hx:absolute hx:-mt-20" id="121-listener"&gt;&lt;/span&gt;
&lt;a href="#121-listener" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;Listener는 TCP/UDP Listening을 수행하여 Downstream의 Connection 수락하고, Connection을 수학하며 생성된 Socket을 Dispatcher에 등록하는 역할을 수행한다. Listener는 각 Worker Thread 마다 별도로 존재하며 &lt;code&gt;SO_REUSEPORT&lt;/code&gt; Option을 통해서 모든 Listener는 동일한 IP/Port를 Listening 하도록 설정된다. 다수의 Listener가 동시에 Listening 하는 경우에는 Kernel은 임의의 Listener를 선택하여 Connection을 수락하게 된다. 따라서 Client가 어떤 Thread와 Connection을 맺을지는 Envoy가 아니라 Kernel에 의해서 결정된다.&lt;/p&gt;
&lt;h4 data-hextra-search-id="122-listener-filter-chain"&gt;&lt;strong&gt;1.2.2. Listener Filter Chain&lt;/strong&gt;&lt;span class="hx:absolute hx:-mt-20" id="122-listener-filter-chain"&gt;&lt;/span&gt;
&lt;a href="#122-listener-filter-chain" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;Listener Filter Chain은 SNI (Server Name Indication)와 같이 Connection의 Metadata를 추출하여 Connection의 추가 정보를 알아내는 역할을 수행한다. 각 Connection 또는 HTTP/2 Stream마다 별도의 Listener Filter Chain 생성된다.&lt;/p&gt;
&lt;h4 data-hextra-search-id="123-tls-transport-socket"&gt;&lt;strong&gt;1.2.3. TLS Transport Socket&lt;/strong&gt;&lt;span class="hx:absolute hx:-mt-20" id="123-tls-transport-socket"&gt;&lt;/span&gt;
&lt;a href="#123-tls-transport-socket" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;Listner Filter Chain을 통해서 추출된 Metadata를 이용하여 TLS로 암호화된 Data를 복호화하여 원본 Data로 변환 및 Network Chain Filter로 전달하는 역할을 수행한다. 만약 TLS로 암호화된 Data가 아닌 경우에는 복호화 과정을 거치지 않고 바로 원본 Data를 그대로 Network Chain Filter로 전달한다. 각 Connection 또는 HTTP/2 Stream마다 별도의 TLS Transport Socket이 생성된다.&lt;/p&gt;
&lt;h4 data-hextra-search-id="124-network-filter-chain"&gt;&lt;strong&gt;1.2.4. Network Filter Chain&lt;/strong&gt;&lt;span class="hx:absolute hx:-mt-20" id="124-network-filter-chain"&gt;&lt;/span&gt;
&lt;a href="#124-network-filter-chain" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h4&gt;&lt;p&gt;Network Filter Chain은 복호화된 요청/응답을 변조, Rate Limiting 수행, Circuit Breaking 수행 또는 어느 Upstream으로 요청을 전송할지 결정하는 Routing 등의 역할을 수행한다. 마지막 Network Filter Chain에는 HCM (HTTP Connection Manager)이 존재한다. HCM은 HTTP 관련 Filter Chain을 소유하고 있으며, HTTP/2 Codec을 통해서 HTTP/2 요청/응답을 Encoding/Decoding하는 역할도 수행한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="13-tls-thread-local-storage"&gt;&lt;strong&gt;1.3. TLS (Thread Local Storage)&lt;/strong&gt;&lt;span class="hx:absolute hx:-mt-20" id="13-tls-thread-local-storage"&gt;&lt;/span&gt;
&lt;a href="#13-tls-thread-local-storage" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;img src="https://ssup2.github.io/blog-software/docs/theory-analysis/envoy-architecture/images/envoy-tls.png" alt="[Figure 2] Envoy TLS (Thread Local Storage)" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;[Figure 2] Envoy TLS (Thread Local Storage)&lt;/p&gt;
&lt;p&gt;[Figure 2]는 Envoy TLS (Thread Local Storage)를 나타내고 있다. Main Thread와 Worker Thread는 자신만의 TLS를 가지고 있으며, Envoy는 TLS를 이용하여 성능 최적화를 위해서 Lock 활용을 최소화하며 동작한다. TLS는 CPP STL의 &lt;strong&gt;vector Class&lt;/strong&gt;를 이용하여 Array 형태로 관리된다. [Figure 2]에서 알수 있는 것처럼 Index별로 용도가 정해져 있으며, Main/Worker Thread의 Index별 용도는 모두 동일하다. 이러한 이유는 Main Thread에서 Index별 용도를 할당하고 초기화한 다음에, Worker Thread도 동일하게 Index별 용도를 할당하고 초기화하기 때문이다.&lt;/p&gt;
&lt;p&gt;TLS에 저장된 실제 데이터는 &lt;code&gt;shared_ptr&lt;/code&gt; Pointer이며, Pointer를 따라 실제 객체(데이터)를 참조하게 된다. TLS 용도에 따라서 &lt;code&gt;shared_ptr&lt;/code&gt; Pointer가 가리키는 객체는 Main/Worker Thread 사이에서 공유되거나, 공유되지 않고 각 Thread마다 독립적인 객체를 가지고 있을 수 있다. [Figure 2]에서는 Runtime, RouteConfig, DNS Cache의 경우에는 전자의 경우를 의미하며, ClusterManager, Stats의 경우에는 후자의 경우를 의미한다.&lt;/p&gt;
&lt;p&gt;Thread 사이에 공유되는 객체를 갱신할 때는 일반적으로 변경 사항을 반영한 새 객체와 그에 대한 &lt;code&gt;shared_ptr&lt;/code&gt; Pointer를 생성한 뒤, TLS에 저장된 기존 &lt;code&gt;shared_ptr&lt;/code&gt; Pointer를 새 &lt;code&gt;shared_ptr&lt;/code&gt; Pointer로 대체하여 Atomic 하게 갱신한다. 단 각 Thread 마다 TLS마다 존재하기 때문에 모든 TLS를 동시에 갱신하지는 않으며, 따라서 일시적으로 각 Thread는 서로 다른 객체를 참조할 수 있다. [Figure 2]에서 Runtime의 경우 모든 Thread들이 동일한 객체를 참고하고 있지만, RouteConfig, DNS Cache의 경우에는 데이터가 변경되면서 일시적으로 다른 객체를 참조하고 있는 모습을 나타내고 있다. 이러한 변경 방식은 Kernel의 &lt;strong&gt;RCU (Read Copy Update)&lt;/strong&gt; 기법과 유사한 방식이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;shared_ptr&lt;/code&gt; Pointer가 각 Thread마다 독립적인 객체를 가리키고 있는 경우에도, 독립적인 객체가 다시 Thread 사이의 공유 객체를 참조할 수 도 있다. [Figure 2]에서 ClusterManager의 경우 각 Thread마다 ClusterEntry가 존재하지만 다시 Thread 사이에 공유되는 ClusterInfo를 참조하고 있는 모습을 나타내고 있으며, Stats의 경우에도 유사하게 각 Thread마다 독립적인 ScopeCache가 존재하지만 다시 Thread 사이에 공유되는 Counter가 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://ssup2.github.io/blog-software/docs/theory-analysis/envoy-architecture/images/envoy-runonallthreads.png" alt="[Figure 3] Envoy Run on All Threads" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;[Figure 3] Envoy Run on All Threads&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cpp void InstanceImpl::runOnAllThreads(std::function&amp;lt;void()&amp;gt; cb, std::function&amp;lt;void()&amp;gt; all_threads_complete_cb) { ASSERT_IS_MAIN_OR_TEST_THREAD(); ASSERT(!shutdown_); cb(); // Run cb() function on Main Thread std::shared_ptr&amp;lt;std::function&amp;lt;void()&amp;gt;&amp;gt; cb_guard( new std::function&amp;lt;void()&amp;gt;(cb), [this, all_threads_complete_cb](std::function&amp;lt;void()&amp;gt;* cb) { main_thread_dispatcher_-&amp;gt;post(all_threads_complete_cb); delete cb; }); for (Event::Dispatcher&amp;amp; dispatcher : registered_threads_) { dispatcher.post([cb_guard]() -&amp;gt; void { (*cb_guard)(); }); } } &lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;[Code 1] runOnAllThreads() Function&lt;/p&gt;
&lt;p&gt;TLS 변경 과정은 일반적으로 &lt;code&gt;runOnAllThreads()&lt;/code&gt; 함수를 이용한다. [Figure 3]에서는 &lt;code&gt;runOnAllThreads()&lt;/code&gt; 함수의 동작 과정을 나타내고 있으며, [Code 1]은 실제 &lt;code&gt;runOnAllThreads()&lt;/code&gt; 함수의 코드를 나타내고 있다. &lt;code&gt;runOnAllThreads()&lt;/code&gt; 함수는 이름에서 유추할 수 있듯이 모든 Main/Thread에서 동일한 로직을 수행하도록 도와주는 함수이다. &lt;code&gt;runOnAllThreads()&lt;/code&gt; 함수를 통해서 모든 Main/Thread는 TLS를 변경하는 작업을 수행한다. &lt;code&gt;runOnAllThreads()&lt;/code&gt; 함수는 일반적으로 Main Thread에서 호출되기 때문에, TLS 변경 작업은 Main Thread에서 각 Worker Thread로 전파되는 형태로 동작한다. 따라서 만약에 Worker Thread에서 TLS를 변경하고 싶다면, Main Thread를 깨워서 Main Thread에서 &lt;code&gt;runOnAllThreads()&lt;/code&gt; 함수를 호출을 통해서 TLS 변경 작업을 수행하도록 만든다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;runOnAllThreads()&lt;/code&gt; 함수는 첫번째 Parameter추 Main/Worker Thread에서 실행되는 &lt;code&gt;cb&lt;/code&gt; Callback 함수를 받으며, 두번째 Parameter로 모든 Thread에서 &lt;code&gt;cb&lt;/code&gt; Callback 함수가 수행된 다음에 Main Thread에서 마지막으로 한번만 수행되는 &lt;code&gt;all_threads_complete_cb&lt;/code&gt; Callback 함수를 받는다. 다음과 같은 순서대로 동작한다. &lt;code&gt;cb&lt;/code&gt; Callback 함수에 TLS 변경 로직이 포함되며, &lt;code&gt;all_threads_complete_cb&lt;/code&gt; Callback 함수에는 모든 Worker Thread의 TLS 변경 작업이 완료된 후에 수행되어야 하는 로직이 포함되어 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Main Thread에서 먼저 전달 받은 &lt;code&gt;cb&lt;/code&gt; Callback 함수를 실행한다. &lt;code&gt;cb&lt;/code&gt; Callback 함수는 내부 로직에 따라서 Main Thread의 TLS를 초기화 또는 변경한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cb&lt;/code&gt; Callback 함수를 &lt;code&gt;cb_guard&lt;/code&gt; Pointer에 저장한다. &lt;code&gt;cb_guard&lt;/code&gt; Pointer는 &lt;code&gt;all_threads_complete_cb&lt;/code&gt; Callback 함수를 Main Thread에 Posting 및 &lt;code&gt;cb&lt;/code&gt; Callback 함수를 제거하는 Lambda를 Deleter로 등록한 CPP STL의 &lt;code&gt;shared_ptr&lt;/code&gt; 역할을 수행한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cb_guard&lt;/code&gt; Pointer에 저장된 &lt;code&gt;cb&lt;/code&gt; Callback 함수를 실행하는 Lambda를 모든 Worker Thread에 Posting 한다. 이때 &lt;code&gt;cb_guard&lt;/code&gt; Pointer는 Worker Thread의 개수만큼 Lambda가 생성되며 복사되어 저장되기 때문에, &lt;code&gt;cb_guard&lt;/code&gt;의 Reference Count 역시 Worker Thread의 개수만큼 증가하게 된다.&lt;/li&gt;
&lt;li&gt;Posting된 Worker Thread는 Dispatcher에 의해서 깨어나 &lt;code&gt;cb_guard&lt;/code&gt; Pointer에 저장된 &lt;code&gt;cb&lt;/code&gt; Callback 함수를 실행하는 Lambda를 실행하게 된다.&lt;/li&gt;
&lt;li&gt;실행이 완료된 Lambda는 종료되며 이때 &lt;code&gt;cb_guard&lt;/code&gt; Pointer의 Reference Count가 1씩 감소하게 되며, 마지막 Lambda가 종료되면 &lt;code&gt;cb_guard&lt;/code&gt; Pointer의 Reference Count가 &lt;code&gt;0&lt;/code&gt;이 된다.&lt;/li&gt;
&lt;li&gt;Reference Count가 &lt;code&gt;0&lt;/code&gt;이 되면 &lt;code&gt;cb_guard&lt;/code&gt; Pointer에 Deleter로 지정된 &lt;code&gt;all_threads_complete_cb&lt;/code&gt; Callback 함수를 Main Thread에 Posting하는 Lambda가 실행된다.&lt;/li&gt;
&lt;li&gt;Posting된 Main Thread에서 &lt;code&gt;all_threads_complete_cb&lt;/code&gt; Callback 함수를 실행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-hextra-search-id="14-flush-thread"&gt;&lt;strong&gt;1.4. Flush Thread&lt;/strong&gt;&lt;span class="hx:absolute hx:-mt-20" id="14-flush-thread"&gt;&lt;/span&gt;
&lt;a href="#14-flush-thread" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Envoy에서는 처리한 요청을 기록하는 &lt;strong&gt;Access Log&lt;/strong&gt; 기능을 제공하며, stdout/stderr를 포함하여 다수의 출력 경로(파일)를 설정할 수 있다. Envoy에서는 Access Log의 기록 요청은 Traffic을 실제로 처리하는 Worker Thread에서 발생한다. 하지만 Work Thread에 직접 Access Log를 기록하면 Lock, I/O Blocking이 발생하여 Traffic 처리를 방해하게 된다. Envoy에서는 이러한 이슈를 회피하기 위해서 Flush Thread를 도입하여 이용하고 있다.&lt;/p&gt;
&lt;p&gt;Flush Thread는 Access Log를 기록하기 위한 전용 Thread이며, Envoy는 Access Log 기록 과정중에 Lock 구간을 최소화 하기 위해서 다양한 Buffer와 Lock을 활용하며 동작한다. Network Filter Chain에 설정에 의해서 Worker Thread가 Access Log를 기록하기 위해서는 모든 Worker Thread 사이에서 공유되는 &lt;strong&gt;Access Logger&lt;/strong&gt;의 &lt;code&gt;Write&lt;/code&gt; 함수를 호출한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Write&lt;/code&gt; 함수는 Access Log를 실제 파일에 기록하지 않고 &lt;strong&gt;Flush Buffer&lt;/strong&gt;에 저장만 하고 종료하며, Flush Buffer는 Worker Thread 사이에 공유되는 Buffer이기 때문에 &lt;strong&gt;Write Lock&lt;/strong&gt;에 의해서 보호된다. Flush Buffer를 이용하는 이유는 &lt;code&gt;Write&lt;/code&gt; 함수에서 직접 Access Log를 남기면 느린 Disk I/O에 의해서 Worker Thread가 Traffic을 처리하는데 지연이 발생하는걸 막기 위해서이다.&lt;/p&gt;
&lt;p&gt;Flush Thread는 Main/Worker Thread와 다르게 Dispatcher를 이용하지 않고, Condition Variable을 통해서 깨어나는 방식으로 동작한다. Main Thread에서 Dispatcher와 Timer에 의해서 주기적으로 Flush Thread를 깨우거나, Worker Thread에서 &lt;code&gt;Write&lt;/code&gt; 함수를 통해서 Flush Buffer에 Access Log를 기록하다가 Flush Buffer가 가득찬 경우 Flush Thread를 깨운다.&lt;/p&gt;
&lt;p&gt;깨어난 Flush Thread는 Access Logger를 통해서 Flush Buffer에 저장된 Access Log를 바로 기록하지 않고 &lt;strong&gt;About to write Buffer&lt;/strong&gt;에 한번더 임시 저장한다. 이러한 이유는 Access Logger에서 바로 Access Log를 기록하면 Disk I/O에 의해서 Flush Buffer의 Write Lock을 오랜 시간동안 점유하게 되어, Worker Thread가 Flush Buffer 접근에 오랜 시간동안 대기하는걸 방지하기 위해서이다. About to write Buffer는 Flush Thread만 단독으로 접근하기 때문에 별도의 Lock이 존재하지 않는다.&lt;/p&gt;
&lt;p&gt;이후에 Flush Thread의 Access Logger는 About to write Buffer에 저장된 Access Log를 stdout/stderr 또는 실제 파일에 기록한다. stdout/stderr 또는 파일에는 File Lock을 통해서 보호되며, File Lock이 존재하는 이유는 Hot Restart 과정에서 일시적으로 동시에 다수의 Envoy 프로세스가 동일한 파일에 접근하는 경우 발생할 수 있는 Race Condition을 방지하기 위해서이다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yaml access_log: - name: envoy.access_loggers.file typed_config: &amp;quot;@type&amp;quot;: type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /var/log/access.log log_format: text_format_source: inline_string: &amp;quot;[%START_TIME%] FORMAT-A ...\n&amp;quot; # Logger A (File) - name: envoy.access_loggers.stdout typed_config: &amp;quot;@type&amp;quot;: type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog log_format: text_format_source: inline_string: &amp;quot;[%START_TIME%] FORMAT-B ...\n&amp;quot; # Logger B (stdout) &lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;[File 1] Envoy Access Log Configuration&lt;/p&gt;
&lt;p&gt;Flush Thread/Buffer/Lock 세트는 각 Access Logger 마다 독립적으로 존재한다. [Figure 1]에서는 [File 1]과 같이 File과 stdout 각각의 경로에 출력하는 2개의 Access Logger가 존재하기 때문에 2개의 Flush Thread/Buffer/Lock 세트가 존재하는 것을 확인할 수 있다. 이 의미 Worker Thread에서도 Access Logger의 개수만큼 여러번 &lt;code&gt;Write&lt;/code&gt; 함수를 호출하는 것을 의미하며, 따라서 너무 많은 Access Logger가 설정되어 있을 경우 성능 저하가 발생할 수 있다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="2-참조"&gt;&lt;strong&gt;2. 참조&lt;/strong&gt;&lt;span class="hx:absolute hx:-mt-20" id="2-참조"&gt;&lt;/span&gt;
&lt;a href="#2-%ec%b0%b8%ec%a1%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Life of a Request : &lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request" target="_blank" rel="noopener"&gt;https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Envoy Architecture : &lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/arch_overview" target="_blank" rel="noopener"&gt;https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/arch_overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Envoy Architecture : &lt;a href="https://cscscs.tistory.com/entry/Envoy-architecture-Introduction" target="_blank" rel="noopener"&gt;https://cscscs.tistory.com/entry/Envoy-architecture-Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Envoy Architecture : &lt;a href="https://www.youtube.com/watch?v=KsO4pw4tEGA" target="_blank" rel="noopener"&gt;https://www.youtube.com/watch?v=KsO4pw4tEGA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Envoy Threading Model : &lt;a href="https://medium.com/envoyproxy/envoy-threading-model-a8d44b922310" target="_blank" rel="noopener"&gt;https://medium.com/envoyproxy/envoy-threading-model-a8d44b922310&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>WebSocket</title><link>https://johklo.github.io/docs/tech/websocket/</link><pubDate>Fri, 09 Oct 2026 00:00:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/websocket/</guid><description>
&lt;h2 data-hextra-search-id="개요"&gt;개요&lt;span class="hx:absolute hx:-mt-20" id="개요"&gt;&lt;/span&gt;
&lt;a href="#%ea%b0%9c%ec%9a%94" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;WebSocket(RFC 6455)은 브라우저와 서버가 &lt;strong&gt;하나의 TCP 연결 위에서 동시에 양방향(full-duplex)으로&lt;/strong&gt; 메시지를 주고받게 해 주는 프로토콜이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;연결을 맺을 때(handshake)만 HTTP를 쓴다.&lt;/li&gt;
&lt;li&gt;handshake가 끝나면 HTTP를 더 이상 쓰지 않고, 같은 TCP 연결 위에서 WebSocket 고유의 프레임 형식으로 데이터를 주고받는다.&lt;/li&gt;
&lt;li&gt;포트는 HTTP와 같은 80(&lt;code&gt;ws://&lt;/code&gt;), 443(&lt;code&gt;wss://&lt;/code&gt;)을 그대로 쓰므로 기존 웹 인프라(프록시, 방화벽)를 통과하기 쉽다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;HTTP는 요청이 있어야 응답이 오는 구조라 서버가 먼저 데이터를 보내려면 polling이나 long polling 같은 우회 방법이 필요했다. WebSocket은 연결을 한 번 열어 두고 양쪽이 아무 때나 메시지를 보낼 수 있어서 채팅, 실시간 알림, 대시보드, 게임처럼 지연이 중요한 서비스에 쓰인다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="handshake-http에서-websocket으로-전환"&gt;Handshake: HTTP에서 WebSocket으로 전환&lt;span class="hx:absolute hx:-mt-20" id="handshake-http에서-websocket으로-전환"&gt;&lt;/span&gt;
&lt;a href="#handshake-http%ec%97%90%ec%84%9c-websocket%ec%9c%bc%eb%a1%9c-%ec%a0%84%ed%99%98" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;프로토콜 전환에는 HTTP/1.1의 &lt;strong&gt;Upgrade 메커니즘&lt;/strong&gt;을 쓴다. 클라이언트가 &amp;ldquo;이 연결을 websocket으로 바꾸자&amp;quot;고 요청하고, 서버가 &lt;code&gt;101 Switching Protocols&lt;/code&gt;로 승낙하면 그 순간부터 같은 TCP 연결이 WebSocket 연결이 된다.&lt;/p&gt;
&lt;div role="img" aria-label="다이어그램"&gt;
&lt;pre class="mermaid hx:mt-6"&gt;
sequenceDiagram
participant C as Client (Browser)
participant S as Server
C-&amp;gt;&amp;gt;S: TCP 연결 (3-way handshake)
C-&amp;gt;&amp;gt;S: HTTP GET /chat&amp;lt;br/&amp;gt;Upgrade: websocket&amp;lt;br/&amp;gt;Connection: Upgrade&amp;lt;br/&amp;gt;Sec-WebSocket-Key: ...
S--&amp;gt;&amp;gt;C: HTTP/1.1 101 Switching Protocols&amp;lt;br/&amp;gt;Upgrade: websocket&amp;lt;br/&amp;gt;Connection: Upgrade&amp;lt;br/&amp;gt;Sec-WebSocket-Accept: ...
Note over C,S: 이후 HTTP가 아닌 WebSocket 프레임으로 통신
C-&amp;gt;&amp;gt;S: Data Frame
S--&amp;gt;&amp;gt;C: Data Frame
S--&amp;gt;&amp;gt;C: Data Frame (서버가 먼저 보내도 됨)
C-&amp;gt;&amp;gt;S: Close Frame
S--&amp;gt;&amp;gt;C: Close Frame
&lt;/pre&gt;
&lt;/div&gt;&lt;h3 data-hextra-search-id="요청-예시-rfc-6455"&gt;요청 예시 (RFC 6455)&lt;span class="hx:absolute hx:-mt-20" id="요청-예시-rfc-6455"&gt;&lt;/span&gt;
&lt;a href="#%ec%9a%94%ec%b2%ad-%ec%98%88%ec%8b%9c-rfc-6455" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;GET&lt;/span&gt; &lt;span class="nn"&gt;/chat&lt;/span&gt; &lt;span class="kr"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="l"&gt;server.example.com&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Upgrade&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="l"&gt;websocket&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="l"&gt;Upgrade&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Sec-WebSocket-Key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="l"&gt;dGhlIHNhbXBsZSBub25jZQ==&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Origin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="l"&gt;http://example.com&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Sec-WebSocket-Protocol&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="l"&gt;chat, superchat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Sec-WebSocket-Version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="l"&gt;13&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-hextra-search-id="응답-예시"&gt;응답 예시&lt;span class="hx:absolute hx:-mt-20" id="응답-예시"&gt;&lt;/span&gt;
&lt;a href="#%ec%9d%91%eb%8b%b5-%ec%98%88%ec%8b%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;101&lt;/span&gt; &lt;span class="ne"&gt;Switching Protocols&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Upgrade&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="l"&gt;websocket&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="l"&gt;Upgrade&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Sec-WebSocket-Accept&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="l"&gt;s3pPLMBiTxaQ9kYGzzhZRbK+xOo=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Sec-WebSocket-Protocol&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="l"&gt;chat&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-hextra-search-id="주요-헤더"&gt;주요 헤더&lt;span class="hx:absolute hx:-mt-20" id="주요-헤더"&gt;&lt;/span&gt;
&lt;a href="#%ec%a3%bc%ec%9a%94-%ed%97%a4%eb%8d%94" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;헤더&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Upgrade: websocket&lt;/code&gt;, &lt;code&gt;Connection: Upgrade&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP 연결을 다른 프로토콜(websocket)로 바꾸자는 요청과 승낙&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Sec-WebSocket-Key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;클라이언트가 매번 새로 만드는 16바이트 난수(Base64)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Sec-WebSocket-Accept&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;서버가 Key로 계산해 돌려주는 값. 클라이언트는 이 값으로 자기 요청에 대한 정상 응답인지 확인한다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Sec-WebSocket-Protocol&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;그 위에서 쓸 애플리케이션 서브프로토콜(예: &lt;code&gt;chat&lt;/code&gt;, &lt;code&gt;stomp&lt;/code&gt;, &lt;code&gt;graphql-ws&lt;/code&gt;) 협상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Sec-WebSocket-Version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;프로토콜 버전. 현재는 &lt;code&gt;13&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-hextra-search-id="sec-websocket-accept-계산"&gt;Sec-WebSocket-Accept 계산&lt;span class="hx:absolute hx:-mt-20" id="sec-websocket-accept-계산"&gt;&lt;/span&gt;
&lt;a href="#sec-websocket-accept-%ea%b3%84%ec%82%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Sec-WebSocket-Accept = Base64( SHA-1( Sec-WebSocket-Key + &amp;#34;258EAFA5-E914-47DA-95CA-C5AB0DC85B11&amp;#34; ) )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Key 문자열 뒤에 RFC에 고정된 GUID를 이어 붙이고, SHA-1 해시를 구한 뒤 Base64로 인코딩한다. 위 예시의 Key &lt;code&gt;dGhlIHNhbXBsZSBub25jZQ==&lt;/code&gt;로 계산하면 &lt;code&gt;s3pPLMBiTxaQ9kYGzzhZRbK+xOo=&lt;/code&gt;가 나온다.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Node.js로 확인
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;crypto&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;dGhlIHNhbXBsZSBub25jZQ==&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;sha1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;258EAFA5-E914-47DA-95CA-C5AB0DC85B11&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;base64&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// =&amp;gt; &amp;#39;s3pPLMBiTxaQ9kYGzzhZRbK+xOo=&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;이 과정은 보안(암호화)을 위한 것이 아니다. WebSocket을 모르는 서버나 캐시가 잘못 응답한 것을 클라이언트가 걸러내기 위한 확인 절차다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="메시지와-data-frame"&gt;메시지와 Data Frame&lt;span class="hx:absolute hx:-mt-20" id="메시지와-data-frame"&gt;&lt;/span&gt;
&lt;a href="#%eb%a9%94%ec%8b%9c%ec%a7%80%ec%99%80-data-frame" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;handshake 후 주고받는 &lt;strong&gt;메시지&lt;/strong&gt;는 하나 이상의 &lt;strong&gt;프레임(frame)&lt;/strong&gt; 으로 나뉘어 전송된다. 프레임은 2~14바이트의 작은 헤더와 payload로 이루어진다.&lt;/p&gt;
&lt;div role="img" aria-label="다이어그램"&gt;
&lt;pre class="mermaid hx:mt-6"&gt;
flowchart LR
A[&amp;#34;FIN (1bit)&amp;lt;br/&amp;gt;마지막 프레임 여부&amp;#34;] --&amp;gt; B[&amp;#34;opcode (4bit)&amp;lt;br/&amp;gt;프레임 종류&amp;#34;]
B --&amp;gt; C[&amp;#34;MASK (1bit)&amp;lt;br/&amp;gt;마스킹 여부&amp;#34;]
C --&amp;gt; D[&amp;#34;Payload len&amp;lt;br/&amp;gt;7bit / +16bit / +64bit&amp;#34;]
D --&amp;gt; E[&amp;#34;Masking key&amp;lt;br/&amp;gt;(0 or 4 bytes)&amp;#34;]
E --&amp;gt; F[&amp;#34;Payload data&amp;#34;]
&lt;/pre&gt;
&lt;/div&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;opcode&lt;/th&gt;
&lt;th&gt;종류&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;이어지는 프레임(continuation)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;텍스트(UTF-8)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;바이너리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;연결 종료(Close)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x9&lt;/code&gt; / &lt;code&gt;0xA&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ping / Pong (연결 유지 확인)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;FIN&lt;/strong&gt;: 큰 메시지는 여러 프레임으로 나눠 보내고, 마지막 프레임에만 FIN=1을 붙인다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Masking&lt;/strong&gt;: 클라이언트 → 서버 프레임은 반드시 4바이트 키로 마스킹해야 한다. 중간 프록시 캐시를 오염시키는 공격을 막기 위한 규칙이다. 서버 → 클라이언트 프레임은 마스킹하지 않는다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ping/Pong&lt;/strong&gt;: 연결이 살아 있는지 확인하고, 중간 장비의 유휴 타임아웃으로 연결이 끊기는 것을 막는 데 쓴다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Close&lt;/strong&gt;: 한쪽이 Close 프레임을 보내면 상대도 Close로 응답한 뒤 TCP 연결을 닫는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-hextra-search-id="정리"&gt;정리&lt;span class="hx:absolute hx:-mt-20" id="정리"&gt;&lt;/span&gt;
&lt;a href="#%ec%a0%95%eb%a6%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;연결 수립은 &lt;strong&gt;HTTP Upgrade&lt;/strong&gt;, 이후 통신은 &lt;strong&gt;TCP 위의 WebSocket 프레임&lt;/strong&gt;으로 이루어진다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Sec-WebSocket-Key&lt;/code&gt;와 &lt;code&gt;Sec-WebSocket-Accept&lt;/code&gt;는 상대가 WebSocket을 이해하는지 확인하는 장치이며, 암호화는 &lt;code&gt;wss://&lt;/code&gt;(TLS)가 담당한다.&lt;/li&gt;
&lt;li&gt;메시지는 FIN, opcode, MASK, 길이로 구성된 가벼운 프레임 단위로 오가므로 HTTP 요청마다 헤더를 반복하는 것보다 오버헤드가 작다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-hextra-search-id="참고"&gt;참고&lt;span class="hx:absolute hx:-mt-20" id="참고"&gt;&lt;/span&gt;
&lt;a href="#%ec%b0%b8%ea%b3%a0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc6455" target="_blank" rel="noopener"&gt;RFC 6455: The WebSocket Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/WebSocket" target="_blank" rel="noopener"&gt;WebSocket - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ssup2.github.io/blog-software/docs/theory-analysis/websocket/" target="_blank" rel="noopener"&gt;ssup2 Blog - WebSocket&lt;/a&gt; (정리 출발점)&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>LLMOps</title><link>https://johklo.github.io/docs/tech/llmops/</link><pubDate>Sun, 12 Apr 2026 00:00:00 +0900</pubDate><guid>https://johklo.github.io/docs/tech/llmops/</guid><description>
&lt;hr&gt;
&lt;h2 data-hextra-search-id="llmops란"&gt;LLMOps란?&lt;span class="hx:absolute hx:-mt-20" id="llmops란"&gt;&lt;/span&gt;
&lt;a href="#llmops%eb%9e%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;LLMOps&lt;/strong&gt; (Large Language Model Operations)는 LLM 기반 애플리케이션을 프로덕션 환경에서 안정적으로 개발·배포·운영하기 위한 방법론과 도구의 집합이다.&lt;/p&gt;
&lt;p&gt;기존 MLOps가 모델을 처음부터 학습시키는 데 초점을 뒀다면, LLMOps는 &lt;strong&gt;이미 사전학습된 모델을 가져다 쓰는 것&lt;/strong&gt;을 전제로 한다. 핵심은 모델 자체보다 &lt;strong&gt;프롬프트, 에이전트, 평가, 모니터링&lt;/strong&gt;에 있다.&lt;/p&gt;
&lt;p&gt;Abi Aryan(&lt;em&gt;LLMOps&lt;/em&gt;, O&amp;rsquo;Reilly 2025)은 이 관계를 이렇게 표현한다. MLOps는 집 짓기, DevOps는 쇼핑몰 짓기라면, LLMOps는 부르즈 할리파 짓기다 — 재료는 같아도, 같은 건설 팀에 맡길 수 없다. 실제로 LLMOps는 MLOps보다 DevOps와 더 닮아 있다. LLM 애플리케이션은 소비자 대면 서비스(Software 3.0)로 배포되기 때문에 DevOps의 문제들이 다시 전면에 등장한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Traditional MLOps&lt;/th&gt;
&lt;th&gt;LLMOps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;핵심 산출물&lt;/td&gt;
&lt;td&gt;학습 데이터, 모델 가중치&lt;/td&gt;
&lt;td&gt;프롬프트, 에이전트, 체인, 플러그인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;평가 지표&lt;/td&gt;
&lt;td&gt;정확도, Loss&lt;/td&gt;
&lt;td&gt;일관성, 관련성, 근거성, 비용, 지연&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;모델&lt;/td&gt;
&lt;td&gt;직접 학습&lt;/td&gt;
&lt;td&gt;사전학습 모델 + 파인튜닝/RAG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;대상자&lt;/td&gt;
&lt;td&gt;데이터 사이언티스트&lt;/td&gt;
&lt;td&gt;ML 엔지니어 + 앱 개발자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 수집&lt;/td&gt;
&lt;td&gt;비교적 단순&lt;/td&gt;
&lt;td&gt;중복 제거, 독성 필터링, 다양성 관리까지 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;학습 시간&lt;/td&gt;
&lt;td&gt;빠름, 단일 노드 가능&lt;/td&gt;
&lt;td&gt;수일~수주, 대규모 GPU 클러스터 필수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;도메인 적응&lt;/td&gt;
&lt;td&gt;풀 파인튜닝이 일반적&lt;/td&gt;
&lt;td&gt;풀 파인튜닝은 너무 비쌈 → 프롬프트·RAG·PEFT가 표준&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;평가&lt;/td&gt;
&lt;td&gt;정답이 있고, 정확도로 측정 가능&lt;/td&gt;
&lt;td&gt;생성형이라 정답 없음, 측정 자체가 어려운 문제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;견고성&lt;/td&gt;
&lt;td&gt;프로덕션에서 행동이 고정됨&lt;/td&gt;
&lt;td&gt;사용자 상호작용에 따라 지속적으로 변함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;보안&lt;/td&gt;
&lt;td&gt;비교적 안전&lt;/td&gt;
&lt;td&gt;프롬프트 인젝션, 데이터 포이즈닝 등 LLM 고유 취약점&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 data-hextra-search-id="llmops-라이프사이클"&gt;LLMOps 라이프사이클&lt;span class="hx:absolute hx:-mt-20" id="llmops-라이프사이클"&gt;&lt;/span&gt;
&lt;a href="#llmops-%eb%9d%bc%ec%9d%b4%ed%94%84%ec%82%ac%ec%9d%b4%ed%81%b4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;세 개의 루프가 하나의 관리 루프(거버넌스·보안·컴플라이언스)로 묶인다.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;Explore → Build → Operationalize
↑ ↓
(반복 개선) (모니터링 피드백)
↑_____________↓
Management Loop&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-hextra-search-id="1-explore--탐색"&gt;1. Explore — 탐색&lt;span class="hx:absolute hx:-mt-20" id="1-explore--탐색"&gt;&lt;/span&gt;
&lt;a href="#1-explore--%ed%83%90%ec%83%89" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;비즈니스 요구사항 및 유즈케이스 정의&lt;/li&gt;
&lt;li&gt;아키텍처 설계 (RAG? Fine-tune? Agent?)&lt;/li&gt;
&lt;li&gt;모델 선택 — LLM vs SLM, 오픈소스 vs 독점&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-hextra-search-id="2-build--개발"&gt;2. Build — 개발&lt;span class="hx:absolute hx:-mt-20" id="2-build--개발"&gt;&lt;/span&gt;
&lt;a href="#2-build--%ea%b0%9c%eb%b0%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;프롬프트 엔지니어링 및 버전 관리&lt;/li&gt;
&lt;li&gt;에이전트 구성&lt;/li&gt;
&lt;li&gt;평가 파이프라인 구축 (수동 → 자동화)&lt;/li&gt;
&lt;li&gt;품질/안전 기준치 달성까지 반복&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-hextra-search-id="3-operationalize--운영"&gt;3. Operationalize — 운영&lt;span class="hx:absolute hx:-mt-20" id="3-operationalize--운영"&gt;&lt;/span&gt;
&lt;a href="#3-operationalize--%ec%9a%b4%ec%98%81" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;프로덕션 배포&lt;/li&gt;
&lt;li&gt;모니터링 및 트레이싱 설정&lt;/li&gt;
&lt;li&gt;비용·지연·품질 지속 추적&lt;/li&gt;
&lt;li&gt;GitHub Actions 등으로 CI/CD 자동화&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 data-hextra-search-id="llmops의-4대-목표"&gt;LLMOps의 4대 목표&lt;span class="hx:absolute hx:-mt-20" id="llmops의-4대-목표"&gt;&lt;/span&gt;
&lt;a href="#llmops%ec%9d%98-4%eb%8c%80-%eb%aa%a9%ed%91%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;LLMOps 프레임워크가 달성해야 할 목표는 네 가지다. 이게 그냥 원칙 나열이 아니라, 팀이 매일 하는 의사결정의 기준이 된다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;신뢰성(Reliability)&lt;/strong&gt; — LLM은 본질적으로 예측 불가하다. 컨텍스트 이해를 못하거나, 학습 데이터가 오래됐거나, 같은 입력에 다른 응답을 내놓는다. 가동률, 에러율, 고객 만족도(CSAT)로 측정한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;확장성(Scalability)&lt;/strong&gt; — LLM은 메모리 덩치가 커서 단일 머신에 안 올라간다. 분산 배포하면서 지연을 낮게 유지하는 게 핵심 난제다. TPM, 처리량, 응답 시간, 자원 확장 속도로 측정한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;견고성(Robustness)&lt;/strong&gt; — 데이터 드리프트, 모델 드리프트, 서드파티 모델 업데이트에도 성능이 유지돼야 한다. 데이터 신선도, 성능 저하율(&amp;lt;5%/6개월), 일관성으로 측정한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;보안(Security)&lt;/strong&gt; — 프롬프트 인젝션, 탈옥(jailbreaking), 적대적 공격에 취약하다. 다중 테넌트 환경에서 데이터 격리도 어렵다. 침투 탐지율, 보안 감사 빈도, 미인가 접근 시도 수로 측정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-hextra-search-id="slo--sla--kpi--목표를-측정-가능하게-만드는-도구"&gt;SLO / SLA / KPI — 목표를 측정 가능하게 만드는 도구&lt;span class="hx:absolute hx:-mt-20" id="slo--sla--kpi--목표를-측정-가능하게-만드는-도구"&gt;&lt;/span&gt;
&lt;a href="#slo--sla--kpi--%eb%aa%a9%ed%91%9c%eb%a5%bc-%ec%b8%a1%ec%a0%95-%ea%b0%80%eb%8a%a5%ed%95%98%ea%b2%8c-%eb%a7%8c%eb%93%9c%eb%8a%94-%eb%8f%84%ea%b5%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;이 세 개념은 엔지니어링 팀과 비즈니스 팀이 같은 언어를 쓰기 위한 장치다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SLO(서비스 수준 목표)&lt;/strong&gt; — 내부 달성 목표. &amp;ldquo;월간 가동률 99.9%&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SLA(서비스 수준 계약)&lt;/strong&gt; — 고객과의 외부 계약. &amp;ldquo;가동률 99.9% 미달 시 10% 환불&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KPI(핵심 성과 지표)&lt;/strong&gt; — 비즈니스 성과 측정. &amp;ldquo;CSAT 90점 이상&amp;rdquo;, &amp;ldquo;이탈률 월 5% 이하&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Gartner(2024)는 GenAI 프로젝트의 30%가 2025년까지 실패할 것이라 예측했다. 실패 원인 1위는 데이터 품질, 평가 프레임워크 부재, 스케일링 비용 — 전부 운영 문제지 기술 문제가 아니다. SLO/SLA/KPI가 명확하지 않으면 팀이 방향을 잃는다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 data-hextra-search-id="모델-선택"&gt;모델 선택&lt;span class="hx:absolute hx:-mt-20" id="모델-선택"&gt;&lt;/span&gt;
&lt;a href="#%eb%aa%a8%eb%8d%b8-%ec%84%a0%ed%83%9d" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="llm-vs-slm"&gt;LLM vs SLM&lt;span class="hx:absolute hx:-mt-20" id="llm-vs-slm"&gt;&lt;/span&gt;
&lt;a href="#llm-vs-slm" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LLM&lt;/strong&gt; (GPT-4, Llama3 70B, Mistral Large): 복잡한 추론, 긴 컨텍스트, 높은 비용&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SLM&lt;/strong&gt; (Phi-3, Llama3 8B, Mistral 7B): 빠르고 저렴, 엣지 디바이스 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-hextra-search-id="모델-유형"&gt;모델 유형&lt;span class="hx:absolute hx:-mt-20" id="모델-유형"&gt;&lt;/span&gt;
&lt;a href="#%eb%aa%a8%eb%8d%b8-%ec%9c%a0%ed%98%95" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Chat completion&lt;/strong&gt;: GPT-4, Mistral Large — 일반 텍스트 대화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reasoning&lt;/strong&gt;: o1, DeepSeek-R1 — 수학·코딩·전략&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multimodal&lt;/strong&gt;: GPT-4o, Phi-3 Vision — 텍스트 + 이미지&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embedding&lt;/strong&gt;: Ada, Cohere — RAG, 시맨틱 검색&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Image generation&lt;/strong&gt;: DALL-E 3, Stable Diffusion&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-hextra-search-id="선택-기준-4가지"&gt;선택 기준 4가지&lt;span class="hx:absolute hx:-mt-20" id="선택-기준-4가지"&gt;&lt;/span&gt;
&lt;a href="#%ec%84%a0%ed%83%9d-%ea%b8%b0%ec%a4%80-4%ea%b0%80%ec%a7%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Task type&lt;/strong&gt; — 텍스트만? 멀티모달?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Precision&lt;/strong&gt; — 베이스 모델로 충분? 파인튜닝 필요?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Openness&lt;/strong&gt; — 직접 파인튜닝 가능해야 하나?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deployment&lt;/strong&gt; — 로컬? 서버리스? 관리형 인프라?&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 data-hextra-search-id="프롬프트-버전-관리"&gt;프롬프트 버전 관리&lt;span class="hx:absolute hx:-mt-20" id="프롬프트-버전-관리"&gt;&lt;/span&gt;
&lt;a href="#%ed%94%84%eb%a1%ac%ed%94%84%ed%8a%b8-%eb%b2%84%ec%a0%84-%ea%b4%80%eb%a6%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;프롬프트는 코드다. 버전 관리하지 않으면 사고가 난다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;프롬프트 변경은 &lt;strong&gt;즉각 반영&lt;/strong&gt;된다 — 컴파일도, 테스트 게이트도 없다. 한 단어 바꾸면 수천 명의 사용자 경험이 달라진다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DevOps 관행을 프롬프트에 적용:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;DevOps 관행&lt;/th&gt;
&lt;th&gt;프롬프트 적용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;소스 컨트롤&lt;/td&gt;
&lt;td&gt;GitHub에 &lt;code&gt;.txt&lt;/code&gt;/&lt;code&gt;.md&lt;/code&gt;로 저장, Foundry에 직접 편집 금지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;코드 리뷰&lt;/td&gt;
&lt;td&gt;PR로 변경 검토 — 정확성·안전성·브랜드 보이스 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;환경 분리&lt;/td&gt;
&lt;td&gt;dev에서 검증 후 prod 배포&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;롤백&lt;/td&gt;
&lt;td&gt;이전 버전 즉시 복원 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;버전 없을 때 발생하는 문제:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;조용한 품질 저하 — 바꾼 것 같지 않은데 사용자가 불만&lt;/li&gt;
&lt;li&gt;환경 불일치 — dev와 prod가 다른 프롬프트를 씀&lt;/li&gt;
&lt;li&gt;위기 복구 불가 — prod 장애 시 이전 버전이 없음&lt;/li&gt;
&lt;li&gt;지식 소멸 — &amp;ldquo;불필요해 보이는&amp;rdquo; 안전 지침이 삭제됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;권장 레포 구조:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;project/
├── src/agents/
│ └── my_agent/
│ ├── agent.py # 배포 스크립트
│ └── prompts/
│ ├── v1_instructions.txt
│ ├── v2_instructions.txt
│ └── v3_instructions.txt
├── tests/
├── docs/
│ └── prompt-guidelines.md
└── .github/workflows/
├── test-agents.yml
└── deploy-agents.yml&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Git 태그로 배포 추적:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;Deploy agent V2&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git tag v2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push origin master --tags&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 data-hextra-search-id="도메인-적응-전략"&gt;도메인 적응 전략&lt;span class="hx:absolute hx:-mt-20" id="도메인-적응-전략"&gt;&lt;/span&gt;
&lt;a href="#%eb%8f%84%eb%a9%94%ec%9d%b8-%ec%a0%81%ec%9d%91-%ec%a0%84%eb%9e%b5" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;모델을 내 도메인에 맞게 만드는 방법은 여러 가지다. 비용 순으로 나열하면:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;방법&lt;/th&gt;
&lt;th&gt;비용&lt;/th&gt;
&lt;th&gt;언제 쓰나&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;프롬프트 엔지니어링&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;거의 없음&lt;/td&gt;
&lt;td&gt;기본값. 대부분의 유즈케이스 커버&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Few-shot 예시&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;거의 없음&lt;/td&gt;
&lt;td&gt;제로샷이 부족할 때. 보통 5~20% 품질 향상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RAG&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;낮음~중간&lt;/td&gt;
&lt;td&gt;도메인 지식이 컨텍스트 창보다 크거나 자주 바뀔 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PEFT / LoRA / QLoRA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;td&gt;프롬프트+RAG으로 부족할 때. 행동 자체를 바꿔야 할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;풀 파인튜닝&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;높음&lt;/td&gt;
&lt;td&gt;매우 드문 케이스. PEFT로 안 되는 게 증명됐을 때만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;처음부터 학습&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;극단적&lt;/td&gt;
&lt;td&gt;LLM 애플리케이션에선 거의 안 함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;풀 파인튜닝은 특정 모델 버전에 종속되고, 대규모 주석 데이터가 필요하다. 먼저 프롬프트와 RAG를 충분히 시도하고, 그래도 안 될 때 고려한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 data-hextra-search-id="에이전트-평가"&gt;에이전트 평가&lt;span class="hx:absolute hx:-mt-20" id="에이전트-평가"&gt;&lt;/span&gt;
&lt;a href="#%ec%97%90%ec%9d%b4%ec%a0%84%ed%8a%b8-%ed%8f%89%ea%b0%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;평가 없이 최적화는 없다. 측정하지 않으면 개선할 수 없다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-hextra-search-id="3가지-평가-차원"&gt;3가지 평가 차원&lt;span class="hx:absolute hx:-mt-20" id="3가지-평가-차원"&gt;&lt;/span&gt;
&lt;a href="#3%ea%b0%80%ec%a7%80-%ed%8f%89%ea%b0%80-%ec%b0%a8%ec%9b%90" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;차원&lt;/th&gt;
&lt;th&gt;지표&lt;/th&gt;
&lt;th&gt;예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;품질&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;의도 해소율, 관련성, 근거성&lt;/td&gt;
&lt;td&gt;사용자 요청을 제대로 해결했나?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;비용&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;토큰 사용량, 모델 단가&lt;/td&gt;
&lt;td&gt;GPT-4 vs GPT-4 mini — 75% 차이&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;성능&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;응답 시간, TTFT&lt;/td&gt;
&lt;td&gt;30초 이내?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-hextra-search-id="평가-방식-비교"&gt;평가 방식 비교&lt;span class="hx:absolute hx:-mt-20" id="평가-방식-비교"&gt;&lt;/span&gt;
&lt;a href="#%ed%8f%89%ea%b0%80-%eb%b0%a9%ec%8b%9d-%eb%b9%84%ea%b5%90" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;방식&lt;/th&gt;
&lt;th&gt;장점&lt;/th&gt;
&lt;th&gt;단점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;인간 평가&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;맥락 이해, 도메인 전문성&lt;/td&gt;
&lt;td&gt;느리고, 비싸고, 일관성 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;자동화 평가&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;빠르고, 일관적, 대규모&lt;/td&gt;
&lt;td&gt;맥락 부족, 검증 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Human-in-the-loop&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;속도 + 품질 균형&lt;/td&gt;
&lt;td&gt;설계가 필요함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-hextra-search-id="git-기반-실험-워크플로우"&gt;Git 기반 실험 워크플로우&lt;span class="hx:absolute hx:-mt-20" id="git-기반-실험-워크플로우"&gt;&lt;/span&gt;
&lt;a href="#git-%ea%b8%b0%eb%b0%98-%ec%8b%a4%ed%97%98-%ec%9b%8c%ed%81%ac%ed%94%8c%eb%a1%9c%ec%9a%b0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 실험별 브랜치 생성&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git checkout -b experiment/gpt4o-mini-model
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 평가 후 좋은 결과면 병합&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git checkout master
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git merge experiment/gpt4o-mini-model
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git tag promoted-to-prod-2026-04-12&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-hextra-search-id="자동화-평가-github-actions"&gt;자동화 평가 (GitHub Actions)&lt;span class="hx:absolute hx:-mt-20" id="자동화-평가-github-actions"&gt;&lt;/span&gt;
&lt;a href="#%ec%9e%90%eb%8f%99%ed%99%94-%ed%8f%89%ea%b0%80-github-actions" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;PR이 &lt;code&gt;prompts/&lt;/code&gt; 또는 &lt;code&gt;config/&lt;/code&gt; 파일을 수정하면 자동으로 평가 워크플로우가 실행된다:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pull_request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;master]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;prompts/**&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;config/**&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;평가 결과는 PR 코멘트로 자동 게시:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;## Evaluation Results
- Groundedness: 4.25
- Relevance: 4.10
- Coherence: 3.85
Status: ✅ PASSED (150 examples)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-hextra-search-id="왜-llm-평가는-이렇게-어려운가"&gt;왜 LLM 평가는 이렇게 어려운가&lt;span class="hx:absolute hx:-mt-20" id="왜-llm-평가는-이렇게-어려운가"&gt;&lt;/span&gt;
&lt;a href="#%ec%99%9c-llm-%ed%8f%89%ea%b0%80%eb%8a%94-%ec%9d%b4%eb%a0%87%ea%b2%8c-%ec%96%b4%eb%a0%a4%ec%9a%b4%ea%b0%80" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;일반 ML 모델은 &lt;strong&gt;결정론적&lt;/strong&gt;이다 — 같은 입력이면 항상 같은 출력. LLM은 학습할 때도, 추론할 때도 난수를 쓴다. 같은 질문에 매번 다른 답을 낸다. 이게 평가를 구조적으로 어렵게 만드는 근본 이유다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;정답이 없다 — 개방형 생성에는 &amp;ldquo;정답&amp;quot;이 존재하지 않음&lt;/li&gt;
&lt;li&gt;무한한 출력 공간 — 가능한 모든 시나리오를 테스트할 수 없음&lt;/li&gt;
&lt;li&gt;해석 도구 없음 — ML과 달리 내부 동작 설명 도구(interpretability)가 LLM에는 아직 없음&lt;/li&gt;
&lt;li&gt;&amp;ldquo;좋음&amp;quot;의 기준이 주관적 — 관련성, 창의성, 정확성이 서로 충돌할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;평가해야 할 6가지 차원:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;차원&lt;/th&gt;
&lt;th&gt;핵심 질문&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;정보성·사실성&lt;/td&gt;
&lt;td&gt;맞는 말인가? 충분한 정보가 있는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;유창성·일관성&lt;/td&gt;
&lt;td&gt;문법이 맞고 논리적 흐름이 있는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;참여도·스타일&lt;/td&gt;
&lt;td&gt;맥락에 맞는 어조인가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;안전성·편향&lt;/td&gt;
&lt;td&gt;유해하거나 편향된 내용이 있는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;근거성&lt;/td&gt;
&lt;td&gt;환각 없이 실제 정보에 근거하는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;효율성&lt;/td&gt;
&lt;td&gt;응답 생성에 얼마나 걸리는가? 비용은?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;BLEU, ROUGE 같은 n-gram 기반 메트릭은 번역·요약 같은 좁은 용도에만 유효하다. &amp;ldquo;파리&amp;quot;가 정답인데 &amp;ldquo;프랑스의 수도는 파리입니다&amp;quot;라고 답하면 16.6%만 맞다고 나온다. 개방형 평가에는 LLM-as-Judge가 훨씬 신뢰도가 높다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 data-hextra-search-id="모니터링"&gt;모니터링&lt;span class="hx:absolute hx:-mt-20" id="모니터링"&gt;&lt;/span&gt;
&lt;a href="#%eb%aa%a8%eb%8b%88%ed%84%b0%eb%a7%81" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;모니터링은 추측을 엔지니어링으로 바꾼다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-hextra-search-id="4가지-핵심-메트릭"&gt;4가지 핵심 메트릭&lt;span class="hx:absolute hx:-mt-20" id="4가지-핵심-메트릭"&gt;&lt;/span&gt;
&lt;a href="#4%ea%b0%80%ec%a7%80-%ed%95%b5%ec%8b%ac-%eb%a9%94%ed%8a%b8%eb%a6%ad" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;메트릭&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;th&gt;이상 징후&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;지연(Latency)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;요청 시작 ~ 첫 응답 시작&lt;/td&gt;
&lt;td&gt;높으면 배포 사이즈 부족 또는 과부하&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;처리량(Throughput)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;단위 시간당 처리 요청 수&lt;/td&gt;
&lt;td&gt;낮으면 컴퓨팅 또는 큐잉 병목&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;토큰 사용량&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;입력+출력 토큰 수&lt;/td&gt;
&lt;td&gt;급증하면 프롬프트 설계 문제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;에러율&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;타임아웃, 파싱 오류, 인프라 한계&lt;/td&gt;
&lt;td&gt;증가하면 최적화 필요 신호&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-hextra-search-id="메트릭-간-연관성"&gt;메트릭 간 연관성&lt;span class="hx:absolute hx:-mt-20" id="메트릭-간-연관성"&gt;&lt;/span&gt;
&lt;a href="#%eb%a9%94%ed%8a%b8%eb%a6%ad-%ea%b0%84-%ec%97%b0%ea%b4%80%ec%84%b1" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;토큰 급증 → 지연 상승 + 비용 상승&lt;/li&gt;
&lt;li&gt;처리량 초과 → 에러율 상승&lt;/li&gt;
&lt;li&gt;지연 감소 → 처리량 감소 가능 (트레이드오프)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-hextra-search-id="azure-기준-모니터링-스택"&gt;Azure 기준 모니터링 스택&lt;span class="hx:absolute hx:-mt-20" id="azure-기준-모니터링-스택"&gt;&lt;/span&gt;
&lt;a href="#azure-%ea%b8%b0%ec%a4%80-%eb%aa%a8%eb%8b%88%ed%84%b0%eb%a7%81-%ec%8a%a4%ed%83%9d" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;App Code
└─ AIInferenceInstrumentor() ← 자동 트레이싱
└─ OpenTelemetry
└─ Application Insights
└─ Azure Monitor Dashboard
└─ Alerts (이메일/웹훅)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 data-hextra-search-id="트레이싱"&gt;트레이싱&lt;span class="hx:absolute hx:-mt-20" id="트레이싱"&gt;&lt;/span&gt;
&lt;a href="#%ed%8a%b8%eb%a0%88%ec%9d%b4%ec%8b%b1" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;모니터링이 &amp;ldquo;무슨 일이 일어났나&amp;quot;를 알려준다면, 트레이싱은 &amp;ldquo;왜 그런 일이 일어났나&amp;quot;를 알려준다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="핵심-개념"&gt;핵심 개념&lt;span class="hx:absolute hx:-mt-20" id="핵심-개념"&gt;&lt;/span&gt;
&lt;a href="#%ed%95%b5%ec%8b%ac-%ea%b0%9c%eb%85%90" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Trace&lt;/strong&gt;: 요청이 시스템을 처음부터 끝까지 흐른 전체 여정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Span&lt;/strong&gt;: 트레이스 내의 개별 작업 단위 (모델 호출, API 콜 등) — 시작/종료 시간 포함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Attribute&lt;/strong&gt;: Span에 붙는 메타데이터 (세션 ID, 응답 길이, 파싱 성공 여부 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-hextra-search-id="트레이스-계층-예시"&gt;트레이스 계층 예시&lt;span class="hx:absolute hx:-mt-20" id="트레이스-계층-예시"&gt;&lt;/span&gt;
&lt;a href="#%ed%8a%b8%eb%a0%88%ec%9d%b4%ec%8a%a4-%ea%b3%84%ec%b8%b5-%ec%98%88%ec%8b%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;trail_guide_session ← 세션 전체
recommend_hike ← 비즈니스 로직
recommend_model_call ← 모델 호출 래퍼
chat gpt-4o ← SDK 자동 트레이싱
trip_profile_generation ← 비즈니스 로직
trip_profile_model_call
chat gpt-4o&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-hextra-search-id="트레이싱으로-발견할-수-있는-것들"&gt;트레이싱으로 발견할 수 있는 것들&lt;span class="hx:absolute hx:-mt-20" id="트레이싱으로-발견할-수-있는-것들"&gt;&lt;/span&gt;
&lt;a href="#%ed%8a%b8%eb%a0%88%ec%9d%b4%ec%8b%b1%ec%9c%bc%eb%a1%9c-%eb%b0%9c%ea%b2%ac%ed%95%a0-%ec%88%98-%ec%9e%88%eb%8a%94-%ea%b2%83%eb%93%a4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;성능 병목&lt;/strong&gt;: trip_profile_generation이 4-6초 vs recommend_hike 1초 이하 → 병목 특정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;에러 패턴&lt;/strong&gt;: &amp;ldquo;겨울 등산 장비&amp;quot;에선 실패, &amp;ldquo;여름&amp;quot;에선 성공 → 데이터 갭&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;비용 최적화&lt;/strong&gt;: GPT-4o 호출이 세션당 2번 → 프롬프트 통합으로 1번으로 줄이기 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UX 문제&lt;/strong&gt;: 전체 8-12초지만 사용자가 5-6초에 이탈 → 중간 피드백 메시지 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 data-hextra-search-id="llmops-팀과-역할"&gt;LLMOps 팀과 역할&lt;span class="hx:absolute hx:-mt-20" id="llmops-팀과-역할"&gt;&lt;/span&gt;
&lt;a href="#llmops-%ed%8c%80%ea%b3%bc-%ec%97%ad%ed%95%a0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;LLMOps를 도입할 때 가장 현실적인 질문은 &amp;ldquo;누가 이걸 하나&amp;quot;다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;기존 ML 팀이 있는 회사:&lt;/strong&gt; 주로 내부에서 8~10명을 다양한 부서(제품, 풀스택, 데이터, ML, 보안)에서 뽑아 팀을 꾸린다. 이미 비즈니스를 아는 사람이 유즈케이스 타당성을 검증하게 하는 게 낫기 때문이다. MLOps 엔지니어를 LLMOps로 업스킬링하는 게 외부 채용보다 훨씬 현실적이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;신규 스타트업:&lt;/strong&gt; 처음부터 팀 구성. LLMOps 인프라(SaaS 툴링) 빌드냐, LLM 유즈케이스(카피라이팅, 교육, 프로세스 자동화) 빌드냐에 따라 팀 모양이 완전히 달라진다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="핵심-4개-역할"&gt;핵심 4개 역할&lt;span class="hx:absolute hx:-mt-20" id="핵심-4개-역할"&gt;&lt;/span&gt;
&lt;a href="#%ed%95%b5%ec%8b%ac-4%ea%b0%9c-%ec%97%ad%ed%95%a0" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;th&gt;핵심 스킬&lt;/th&gt;
&lt;th&gt;LLM 특화 업무&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI 엔지니어&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;풀스택(React, Node, Django), LLMOps 프레임워크&lt;/td&gt;
&lt;td&gt;프롬프트 엔지니어링, RAG 시스템, API 통합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ML 사이언티스트&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PyTorch/TF/JAX, NLP 알고리즘&lt;/td&gt;
&lt;td&gt;모델 아키텍처, 학습, 파인튜닝, 토크나이저&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;데이터 엔지니어&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;파이프라인, ETL, 데이터 이동&lt;/td&gt;
&lt;td&gt;LLM 전용 청킹, 토크나이저 전략, 전처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LLMOps 엔지니어&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;배포, 모니터링, 최적화, IaC&lt;/td&gt;
&lt;td&gt;프로덕션 LLM 파이프라인의 신뢰성·확장성·견고성·보안&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;이상적인 페어링: AI 엔지니어 + LLMOps 엔지니어. AI 엔지니어가 모델을 조정·파인튜닝하는 동안 LLMOps 엔지니어가 배포와 최적화를 담당한다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="mlops-엔지니어를-llmops로-업스킬링"&gt;MLOps 엔지니어를 LLMOps로 업스킬링&lt;span class="hx:absolute hx:-mt-20" id="mlops-엔지니어를-llmops로-업스킬링"&gt;&lt;/span&gt;
&lt;a href="#mlops-%ec%97%94%ec%a7%80%eb%8b%88%ec%96%b4%eb%a5%bc-llmops%eb%a1%9c-%ec%97%85%ec%8a%a4%ed%82%ac%eb%a7%81" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;이미 갖고 있는 것: 모델 버전 관리, 데이터 버전 관리, 롤백, GitHub Actions. 좋은 출발점이다.&lt;/p&gt;
&lt;p&gt;추가로 배워야 할 것:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;트랜스포머 아키텍처와 어텐션 메커니즘&lt;/li&gt;
&lt;li&gt;LLM 전용 최적화 (양자화, 배치처리, 서빙)&lt;/li&gt;
&lt;li&gt;LLM 스케일 데이터 엔지니어링 (중복 제거, 독성 필터링, 청킹)&lt;/li&gt;
&lt;li&gt;분산 학습 시스템 설계&lt;/li&gt;
&lt;li&gt;생성형 출력을 위한 가드레일과 모니터링&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 data-hextra-search-id="llmops-성숙도-모델"&gt;LLMOps 성숙도 모델&lt;span class="hx:absolute hx:-mt-20" id="llmops-성숙도-모델"&gt;&lt;/span&gt;
&lt;a href="#llmops-%ec%84%b1%ec%88%99%eb%8f%84-%eb%aa%a8%eb%8d%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Abi Aryan이 Microsoft MLOps 성숙도 모델을 참고해 정의한 3단계 모델이다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;레벨&lt;/th&gt;
&lt;th&gt;상태&lt;/th&gt;
&lt;th&gt;특징&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Level 0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LLMOps 없음&lt;/td&gt;
&lt;td&gt;모델이 사일로에서 개발됨. 모니터링·가드레일 없음. 쉐보레 챗봇 사건이 전형적인 예 — 가드레일 없이 배포하니 사용자들이 대수학 숙제에 쓰고, 제값에 차를 팔겠다는 약속을 받아냈다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Level 1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MLOps는 있지만 LLMOps 없음&lt;/td&gt;
&lt;td&gt;학습·테스트·배포 파이프라인은 갖춰졌지만 LLM 특성(드리프트, 생성형 평가, 보안)에 최적화되지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Level 2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;완전한 LLMOps&lt;/td&gt;
&lt;td&gt;고급 문서화, 견고한 모니터링과 컴플라이언스, 정교한 오케스트레이션과 인간 검토 전략, 자동 스케일링·페일오버·롤백 완비&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Level 2가 되려면 이런 게 갖춰져야 한다:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;문서화 측면&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;비즈니스 목표와 KPI가 최신 상태로 문서화됨&lt;/li&gt;
&lt;li&gt;LLM 리스크 평가(편향, 공정성, 데이터 드리프트, 성능 저하)가 완비됨&lt;/li&gt;
&lt;li&gt;모델 선택 근거와 비용 비교가 기록됨&lt;/li&gt;
&lt;li&gt;API 구조, 데이터 흐름, 아키텍처 다이어그램이 최신 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;운영 측면&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;가드레일과 한계 감지(신뢰도 스코어링, 임계값)&lt;/li&gt;
&lt;li&gt;모든 입출력 자동 저장·인덱싱&lt;/li&gt;
&lt;li&gt;정기 A/B 테스트&lt;/li&gt;
&lt;li&gt;실시간 독성·편향 탐지 파이프라인&lt;/li&gt;
&lt;li&gt;GDPR·HIPAA 컴플라이언스&lt;/li&gt;
&lt;li&gt;정기 외부 보안 감사 포함&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 data-hextra-search-id="실무-적용-포인트"&gt;실무 적용 포인트&lt;span class="hx:absolute hx:-mt-20" id="실무-적용-포인트"&gt;&lt;/span&gt;
&lt;a href="#%ec%8b%a4%eb%ac%b4-%ec%a0%81%ec%9a%a9-%ed%8f%ac%ec%9d%b8%ed%8a%b8" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;평가 기준을 먼저 정하라&lt;/strong&gt; — 배포 전에 품질/비용/성능 임계값 합의. &amp;ldquo;4.2점 이상 또는 배포 안 함&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;프롬프트를 코드처럼 관리하라&lt;/strong&gt; — Git 없이 프롬프트 바꾸는 것은 운영 리스크&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;작게 시작하고 측정하라&lt;/strong&gt; — 100개 예시로 시작, 잘 구성된 소규모 &amp;gt; 무작위 대규모&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;자동화는 점진적으로&lt;/strong&gt; — 자동 테스트 → 모니터링 → 배포 자동화 순서&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;인간 감독을 유지하라&lt;/strong&gt; — 자동화는 인간 판단을 증폭시키는 것이지 대체하는 것이 아님&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 data-hextra-search-id="observatory--llmops-관점에서-본-갭-분석"&gt;Observatory — LLMOps 관점에서 본 갭 분석&lt;span class="hx:absolute hx:-mt-20" id="observatory--llmops-관점에서-본-갭-분석"&gt;&lt;/span&gt;
&lt;a href="#observatory--llmops-%ea%b4%80%ec%a0%90%ec%97%90%ec%84%9c-%eb%b3%b8-%ea%b0%ad-%eb%b6%84%ec%84%9d" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;직접 만들고 있는 플랫폼이라 조금 솔직하게 적는다.&lt;/p&gt;
&lt;p&gt;Observatory의 핵심 목표는 **&amp;ldquo;프로덕션 LLM 애플리케이션을 디버깅·평가·지속 개선할 수 있는 운영 가시성 제공&amp;rdquo;**이다. Datadog이나 LangFuse를 컴플라이언스 문제로 못 쓰는 팀을 위해, 어차피 App Insights에 쌓이는 OTel 텔레메트리를 KQL로 읽어서 대시보드로 보여주는 구조다. 데이터는 Azure 구독 밖으로 나가지 않는다.&lt;/p&gt;
&lt;h3 data-hextra-search-id="뭘-잘하고-뭐가-빠졌나"&gt;뭘 잘하고 뭐가 빠졌나&lt;span class="hx:absolute hx:-mt-20" id="뭘-잘하고-뭐가-빠졌나"&gt;&lt;/span&gt;
&lt;a href="#%eb%ad%98-%ec%9e%98%ed%95%98%ea%b3%a0-%eb%ad%90%ea%b0%80-%eb%b9%a0%ec%a1%8c%eb%82%98" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;관측(Operationalize) 쪽은 가장 잘 됐다.&lt;/strong&gt; Trace Explorer, Waterfall/Flame Graph, 에이전트·툴 호출 추적, 비용 추적, 멀티 워크스페이스 KQL까지 — 이건 설계 목표에 충실하게 나왔다고 본다.&lt;/p&gt;
&lt;p&gt;설계·실험(Explore) 쪽도 나쁘지 않다. Playground 4개 프로바이더, Prompt Registry 버전 이력, 실험 프레임워크에 t-test/Mann-Whitney 유의성 검증까지 내장됐다. 솔직히 이 부분은 상용 도구들도 잘 안 해주는 걸 넣었다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;문제는 평가(Build)와 피드백 루프(Management Loop)다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;현재 평가 엔진은 단어 겹침(word overlap) 기반 휴리스틱이다. Groundedness 점수가 높게 나와도 실제로 근거가 있는 응답인지 보장이 안 된다. 이 상태에서는 평가 점수를 CI/CD 게이트로 쓸 수 없다 — 신뢰를 못 하니까. LLM-as-Judge 없이는 EDD(Evaluation-Driven Development)가 반쪽짜리다.&lt;/p&gt;
&lt;p&gt;피드백 루프는 더 구멍이 크다. 지금은 Observatory가 문제를 &lt;strong&gt;보여줄&lt;/strong&gt; 수는 있어도 그 문제를 &lt;strong&gt;수집하고 다음 실험으로 연결하는&lt;/strong&gt; 흐름이 없다. 좋아요/싫어요 하나도 없고, 주석 큐도 없다. 트레이스에서 문제를 발견해도 &amp;ldquo;이게 왜 나쁜 응답인지&amp;rdquo; 레이블을 달고 데이터셋으로 만드는 과정이 전부 플랫폼 밖에서 수동으로 이루어진다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;갭&lt;/th&gt;
&lt;th&gt;현재 상황&lt;/th&gt;
&lt;th&gt;일정&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LLM-as-Judge&lt;/td&gt;
&lt;td&gt;휴리스틱만 — 배포 게이트 신뢰도 없음&lt;/td&gt;
&lt;td&gt;Sprint 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI/CD 평가 게이트&lt;/td&gt;
&lt;td&gt;없음 — 나쁜 프롬프트 변경이 조용히 배포됨&lt;/td&gt;
&lt;td&gt;Sprint 6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용자 피드백 수집&lt;/td&gt;
&lt;td&gt;없음 — 좋아요/싫어요조차 없음&lt;/td&gt;
&lt;td&gt;Sprint 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주석 큐&lt;/td&gt;
&lt;td&gt;없음 — 레이블링 워크플로우 전무&lt;/td&gt;
&lt;td&gt;Sprint 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gen AI 콘텐츠 표시&lt;/td&gt;
&lt;td&gt;KQL 모드에서 실제 대화 내용 안 보임&lt;/td&gt;
&lt;td&gt;Sprint 2 진행 중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;status 필터 버그&lt;/td&gt;
&lt;td&gt;&lt;code&gt;?status=error&lt;/code&gt; 필터가 KQL에서 무시됨&lt;/td&gt;
&lt;td&gt;즉시 수정 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-hextra-search-id="지금-위치"&gt;지금 위치&lt;span class="hx:absolute hx:-mt-20" id="지금-위치"&gt;&lt;/span&gt;
&lt;a href="#%ec%a7%80%ea%b8%88-%ec%9c%84%ec%b9%98" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;LLMOps 성숙도로 보면 &lt;strong&gt;Level 1 완성, Level 2 진입 중&lt;/strong&gt;이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Level 1 (Instrumented) — OTel 트레이싱, 비용/지연 대시보드, 프롬프트 버전 관리: ✅&lt;/li&gt;
&lt;li&gt;Level 2 (Systematic) — CI/CD 평가 게이트, 피드백 수집, 황금 데이터셋: 아직 절반&lt;/li&gt;
&lt;li&gt;Level 3 (Optimizing) — 구조화 주석, 실험이 배포를 게이팅: 아직&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;관측 도구로서는 꽤 완성됐다. 하지만 &lt;strong&gt;감지 → 레이블링 → 실험 → 배포 결정&lt;/strong&gt; 사이클이 자동화되려면 Sprint 3 평가 SDK와 Sprint 2 피드백 수집이 관건이다. 특히 CI/CD 게이트는 Sprint 6로 잡혀 있는데, Sprint 3 평가 SDK만 나오면 바로 붙일 수 있으니 앞당길 여지가 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 data-hextra-search-id="참고-자료-및-관련-독서"&gt;참고 자료 및 관련 독서&lt;span class="hx:absolute hx:-mt-20" id="참고-자료-및-관련-독서"&gt;&lt;/span&gt;
&lt;a href="#%ec%b0%b8%ea%b3%a0-%ec%9e%90%eb%a3%8c-%eb%b0%8f-%ea%b4%80%eb%a0%a8-%eb%8f%85%ec%84%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="https://johklo.github.io/docs/book/ai-reading-plan/"&gt;2026년 AI 책 읽기&lt;/a&gt; — 2026 LLM 독서 계획 (Production/MLOps 섹션 참고)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/johklo/llmopsdashboard" target="_blank" rel="noopener"&gt;johklo/llmopsdashboard&lt;/a&gt; — Azure LLM Observatory 플랫폼&lt;/li&gt;
&lt;li&gt;LLM Engineer&amp;rsquo;s Handbook&lt;/li&gt;
&lt;li&gt;LLMs in Production&lt;/li&gt;
&lt;li&gt;Building LLMs for Production&lt;/li&gt;
&lt;li&gt;AI Engineering (읽는 중)&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>[책] 지적자본론</title><link>https://johklo.github.io/docs/book/knowledge-capital/</link><pubDate>Thu, 01 Jan 2026 11:05:33 +0900</pubDate><guid>https://johklo.github.io/docs/book/knowledge-capital/</guid><description/></item><item><title>2026년 AI 책 읽기</title><link>https://johklo.github.io/docs/book/ai-reading-plan/</link><pubDate>Thu, 01 Jan 2026 09:19:27 +0900</pubDate><guid>https://johklo.github.io/docs/book/ai-reading-plan/</guid><description>
&lt;h1 data-hextra-search-id="-2026-llm-reading-plan"&gt;📚 2026 LLM Reading Plan&lt;/h1&gt;&lt;p&gt;LLM 관련 책들을 체계적으로 읽고 기록하는 페이지입니다.&lt;br&gt;
단순히 읽고 끝나는 것이 아니라,&lt;br&gt;
“이해 → 메모 → 적용”까지 가는 것을 목표로 합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 data-hextra-search-id="-목표"&gt;🎯 목표&lt;span class="hx:absolute hx:-mt-20" id="-목표"&gt;&lt;/span&gt;
&lt;a href="#-%eb%aa%a9%ed%91%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;핵심 LLM/AI 책 &lt;strong&gt;완독&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;요약 기록 + 블로그 컨텐츠로 확장&lt;/li&gt;
&lt;li&gt;실무/컨설팅에 적용 가능한 지식 체득&lt;/li&gt;
&lt;li&gt;최소 &lt;strong&gt;70% 이상 완독 달성&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 data-hextra-search-id="-진행-현황"&gt;📊 진행 현황&lt;span class="hx:absolute hx:-mt-20" id="-진행-현황"&gt;&lt;/span&gt;
&lt;a href="#-%ec%a7%84%ed%96%89-%ed%98%84%ed%99%a9" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;전체 책 수: &lt;strong&gt;24권&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;완료: &lt;strong&gt;1권&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;진행률: &lt;strong&gt;0%&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="gauge"&gt;
&lt;progress value="1" max="24"&gt;&lt;/progress&gt;
&lt;div class="label"&gt;4%&lt;/div&gt;
&lt;/div&gt;
&lt;style&gt;
.gauge{
position:relative;
width:300px;
height:20px;
}
.gauge progress{
width:100%;
height:100%;
}
.gauge .label{
position:absolute;
top:0;
left:0;
width:100%;
height:100%;
display:flex;
align-items:center;
justify-content:center;
font-size:12px;
font-weight:bold;
color:black;
}
&lt;/style&gt;
&lt;hr&gt;
&lt;h2 data-hextra-search-id="-전체-목록"&gt;📕 전체 목록&lt;span class="hx:absolute hx:-mt-20" id="-전체-목록"&gt;&lt;/span&gt;
&lt;a href="#-%ec%a0%84%ec%b2%b4-%eb%aa%a9%eb%a1%9d" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h3 data-hextra-search-id="-core--theory--foundation"&gt;🔹 Core / Theory / Foundation&lt;span class="hx:absolute hx:-mt-20" id="-core--theory--foundation"&gt;&lt;/span&gt;
&lt;a href="#-core--theory--foundation" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Large Language Models&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; What Is ChatGPT Doing … and Why Does It Work?&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; The Hundred-Page Language Models Book&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Build a Large Language Model (From Scratch)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 data-hextra-search-id="-development--engineering--applications"&gt;🔹 Development / Engineering / Applications&lt;span class="hx:absolute hx:-mt-20" id="-development--engineering--applications"&gt;&lt;/span&gt;
&lt;a href="#-development--engineering--applications" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;input checked="" disabled="" type="checkbox"&gt; AI Engineering&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; The Agentic AI Bible&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; AI Agents in Action&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Quick Start Guide to Large Language Models&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Developing Apps with GPT-4 and ChatGPT&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Building LLM Powered Applications&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Generative AI with LangChain&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; LangChain Crash Course&lt;/li&gt;
&lt;li&gt;[-] The AI Engineering Bible&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 data-hextra-search-id="-production--mlops--system"&gt;🔹 Production / MLOps / System&lt;span class="hx:absolute hx:-mt-20" id="-production--mlops--system"&gt;&lt;/span&gt;
&lt;a href="#-production--mlops--system" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; LLM Engineer’s Handbook&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; LLMs in Production&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Building LLMs for Production&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Creating Production-Ready LLMs&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Generative AI on AWS&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 data-hextra-search-id="-prompting--rag--specialized"&gt;🔹 Prompting / RAG / Specialized&lt;span class="hx:absolute hx:-mt-20" id="-prompting--rag--specialized"&gt;&lt;/span&gt;
&lt;a href="#-prompting--rag--specialized" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Prompt Engineering for Generative AI&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Prompt Engineering for LLMs&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; RAG-Driven Generative AI&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Super Study Guide&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; Natural Language Processing with Transformers&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; The Developer’s Playbook for Large Language Model Security&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 data-hextra-search-id="-notes--기록-방식"&gt;📝 Notes &amp;amp; 기록 방식&lt;span class="hx:absolute hx:-mt-20" id="-notes--기록-방식"&gt;&lt;/span&gt;
&lt;a href="#-notes--%ea%b8%b0%eb%a1%9d-%eb%b0%a9%ec%8b%9d" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;책 한 권씩 다 읽었다면:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/wiki/book/xxxx-review/&lt;/code&gt; 로 리뷰 페이지 생성&lt;/li&gt;
&lt;li&gt;요약 / 인사이트 / 적용 포인트 정리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;중간 기록도 OK:
&lt;ul&gt;
&lt;li&gt;이 페이지에 메모 추가하거나&lt;/li&gt;
&lt;li&gt;&lt;code&gt;note/&lt;/code&gt; 형태로 임시 기록 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href="https://github.com/johklo/awesome-llm-books" target="_blank" rel="noopener"&gt;https://github.com/johklo/awesome-llm-books&lt;/a&gt;&lt;/p&gt;</description></item><item><title>(책) Getting Things Done</title><link>https://johklo.github.io/docs/book/gtd/</link><pubDate>Fri, 03 Jan 2025 01:25:00 +0000</pubDate><guid>https://johklo.github.io/docs/book/gtd/</guid><description>
&lt;h2 data-hextra-search-id="할일을-정리하고-실제-실행하는-것"&gt;할일을 정리하고 실제 실행하는 것&lt;span class="hx:absolute hx:-mt-20" id="할일을-정리하고-실제-실행하는-것"&gt;&lt;/span&gt;
&lt;a href="#%ed%95%a0%ec%9d%bc%ec%9d%84-%ec%a0%95%eb%a6%ac%ed%95%98%ea%b3%a0-%ec%8b%a4%ec%a0%9c-%ec%8b%a4%ed%96%89%ed%95%98%eb%8a%94-%ea%b2%83" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;할일을 어떤 식으로 행동하고 원하는 결과가 무엇인지 파악하고 정리하는 것이 결국 머릿속에 가득한 일들을 덜어내고 해야 할 일을 해내는 방법&lt;/li&gt;
&lt;li&gt;단순히 할일을 간단히 기재하는 것으로 끝나는 것이 아니라 할일을 어떻게 행동하고 처리할지 정리하고 그 &amp;ldquo;할일&amp;quot;을 기록하는 것이 GTD의 핵심&lt;/li&gt;
&lt;li&gt;모든 일을 머리속에서 꺼내라.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-hextra-search-id="quote"&gt;Quote&lt;span class="hx:absolute hx:-mt-20" id="quote"&gt;&lt;/span&gt;
&lt;a href="#quote" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;진짜 문제는 프로젝트가 무엇인지, 그리고 다음에 어떤 행동이 필요한지 명확하게 정의되지 않은 것이다.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;일을 해내려면 두 가지 기본적인 요소가 필요하다. 그 일을 &amp;lsquo;완료한다는 것&amp;rsquo;이 무엇을 의미하는지(결과)와, &amp;lsquo;수행한다는 것&amp;rsquo;이 무엇인지(행동)를 정의하는 것이다.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;여러 번 생각하는 걸 좋아하지 않는 이상 같은 일을 두 번 생각할 필요는 없다.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;</description></item><item><title>(책) 네이비씰 승리의 기술</title><link>https://johklo.github.io/docs/book/seal/</link><pubDate>Sun, 12 Mar 2023 15:45:29 +0900</pubDate><guid>https://johklo.github.io/docs/book/seal/</guid><description>
&lt;h2 data-hextra-search-id="끄적끄적"&gt;끄적끄적&lt;span class="hx:absolute hx:-mt-20" id="끄적끄적"&gt;&lt;/span&gt;
&lt;a href="#%eb%81%84%ec%a0%81%eb%81%84%ec%a0%81" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;우선순위를 정해 실행하라.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;각 문제의 시급성을 평가한다.&lt;/li&gt;
&lt;li&gt;가장 시급하게 처리해야 할 과제를 간단명료한 용어로 정리한다.&lt;/li&gt;
&lt;li&gt;해결책을 모색한다. 이 과정에서 가능하면 하위 리더와 팀원들에게 의견을 구한다.&lt;/li&gt;
&lt;li&gt;해결책의 실행을 지시한다. 모든 노력과 자원은 최우선 과제에 집중되어야 한다.&lt;/li&gt;
&lt;li&gt;최우선 과제가 해결되면 그다음 과제로 넘어간다. 이 과정을 반복한다.&lt;/li&gt;
&lt;li&gt;팀 내에서 우선 과제가 변경될 경우 이 상황을 위아래로 전파한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;​&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;복잡하다는 것은 잘 모른다는 것이다. 누구나 이해할 수 있어야 실전에서 통한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;​&amp;gt; 우리 팀만 잘하면 된다는 생각을 버려야 답이 보인다.&lt;/p&gt;
&lt;p&gt;​&amp;gt; 무엇을 해야 하는지 말하기보다 왜 해야 하는지를 설명하라.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;나쁜 팀은 없다. 나쁜 리더만 있을 뿐.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;어떻게 할까요라고 묻지 말고 이것을 하겠습니다라고 말하라.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;일반적으로 한 사람이 관리할 수 있는 인원은 6&lt;del&gt;10명이다. 아무리 숙달된 지도자라도 수백 명은커녕 수십 명도 제대로 관리하기 어렵다. 관리할 인원이 많을 때는 각각 4&lt;/del&gt;5명의 소그룹으로 나누고, 소그룹의 리더를 확실히 정해야 한다. 소그룹의 리더는 조직의 조직 전체의 임무와 최종 목표, 즉 지휘관의 의도를 파악해야 한다. 임무를 효과적으로 달성하려면 초급 지휘관들에게 핵심 과업에 대한 결정을 내릴 수 있는 권한이 부여되어야 한다. 팀 안의 팀은 특정 임무에 최대한 효율성을 발휘할 수 있도록 조직되어야 하며, 각 지휘관의 책임과 권한이 명확히 기술되어야 한다. 전술적 단위의 팀을 이끄는 일선 지휘관들은 &amp;lsquo;무엇을 할 것인가&amp;rsquo;뿐만 아니라 &amp;lsquo;왜 해야 하는가&amp;rsquo;를 명확히 이해해야 한다. 초급 지휘관들은 자신에게 부여된 재량권의 범위와 한계를 명확하게 이해해야 한다. 상급 지휘관들과 일선 지휘관들이 단절되어 있어 실제로는 자기 부하들이 뭘 하고 있는지도 전혀 모르기 때문에 효과적으로 지시를 내리지 못한다.&lt;/p&gt;
&lt;/blockquote&gt;</description></item><item><title>Zach Williams - A hundred Highways</title><link>https://johklo.github.io/docs/insight/music/</link><pubDate>Tue, 14 Feb 2023 10:22:29 +0900</pubDate><guid>https://johklo.github.io/docs/insight/music/</guid><description>
&lt;blockquote&gt;
&lt;p&gt;All the times that I worked hard because I believed&lt;/p&gt;
&lt;p&gt;That life could have meanin&amp;rsquo; through things I&amp;rsquo;ve achieved&lt;/p&gt;
&lt;p&gt;Well, You knew I was lookin&amp;rsquo; for You&lt;/p&gt;
&lt;p&gt;Every time I thought romance might fill up the hole&lt;/p&gt;
&lt;p&gt;That incomplete feelin&amp;rsquo; still ached in my soul&lt;/p&gt;
&lt;p&gt;Oh, You knew I was lookin&amp;rsquo; for You&lt;/p&gt;
&lt;p&gt;Down a hundred highways of empty pursuit&lt;/p&gt;
&lt;p&gt;And a thousand foolish things I went through&lt;/p&gt;
&lt;p&gt;Didn&amp;rsquo;t know it back then but now I do&lt;/p&gt;
&lt;p&gt;I was lookin&amp;rsquo; for You, I was lookin&amp;rsquo; for You&lt;/p&gt;
&lt;p&gt;When I couldn&amp;rsquo;t find comfort in the things I could buy&lt;/p&gt;
&lt;p&gt;When the party was over and the bottle was dry&lt;/p&gt;
&lt;p&gt;Oh, You knew I was lookin&amp;rsquo; for You&lt;/p&gt;
&lt;p&gt;Oh, and I went searchin'&lt;/p&gt;
&lt;p&gt;Down a hundred highways of empty pursuit&lt;/p&gt;
&lt;p&gt;And a thousand foolish things I went through&lt;/p&gt;
&lt;p&gt;Didn&amp;rsquo;t know it back then but now I do&lt;/p&gt;
&lt;p&gt;I was lookin&amp;rsquo; for You, I was lookin&amp;rsquo; for You&lt;/p&gt;
&lt;p&gt;Through the canyons and the heartaches&lt;/p&gt;
&lt;p&gt;And the valleys of pain&lt;/p&gt;
&lt;p&gt;Down those pathways full of pleasure&lt;/p&gt;
&lt;p&gt;And those dead end roads of shame&lt;/p&gt;
&lt;p&gt;Oh, I couldn&amp;rsquo;t name the hunger&lt;/p&gt;
&lt;p&gt;That I tried to satisfy&lt;/p&gt;
&lt;p&gt;Down a hundred highways of empty pursuit&lt;/p&gt;
&lt;p&gt;And a thousand foolish things I went through&lt;/p&gt;
&lt;p&gt;In a moment I looked up from down on my knees&lt;/p&gt;
&lt;p&gt;And I cried out to Heaven, I could finally see&lt;/p&gt;
&lt;p&gt;Jesus, Jesus, I know it&amp;rsquo;s true&lt;/p&gt;
&lt;p&gt;All of my life I was lookin&amp;rsquo; for You&lt;/p&gt;
&lt;p&gt;Zach Williams - A hundred Highways&lt;/p&gt;
&lt;/blockquote&gt;</description></item><item><title>앵무조개</title><link>https://johklo.github.io/docs/insight/blah/</link><pubDate>Wed, 04 Jan 2023 20:25:29 +0900</pubDate><guid>https://johklo.github.io/docs/insight/blah/</guid><description>
&lt;blockquote&gt;
&lt;p&gt;해마다 해마다 침묵의 고통 속에서&lt;/p&gt;
&lt;p&gt;그의 빛나는 소용돌이는 퍼져 나갔다.&lt;/p&gt;
&lt;p&gt;하지만 점점 자라나면서&lt;/p&gt;
&lt;p&gt;지난해에 살던 집을 버리고&lt;/p&gt;
&lt;p&gt;빛나는 아치 길을 부드럽게 빠져나가&lt;/p&gt;
&lt;p&gt;사용하지 않는 문을 닫고&lt;/p&gt;
&lt;p&gt;새로운 집에 맞춰서&lt;/p&gt;
&lt;p&gt;낡은 집을 잊고 떠났다.&lt;/p&gt;
&lt;p&gt;오 나의 영혼이여, 좀 더 넓은 집을 짓자.&lt;/p&gt;
&lt;p&gt;빠르게 뒤바뀌는 계절을 따라&lt;/p&gt;
&lt;p&gt;저 낮은 천장의 과거로부터 떠나자.&lt;/p&gt;
&lt;p&gt;좁은 천장 밀실에서 뛰어나와&lt;/p&gt;
&lt;p&gt;날마다 새로운 나의 성전을 짓고 보다 고상한 꿈을 키우자.&lt;/p&gt;
&lt;p&gt;그대서 완전히 자유로워질 때까지&lt;/p&gt;
&lt;p&gt;그대의 좁은 껍데기에서 나와 쉬지 않고 움직이는 저 넓은 바다로 가자!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; - 올리버 웬들 홈스, 앵무조개(The Chambered Nautilus)
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;</description></item><item><title>(youtube) Managing Client Relationships as an Investment Banker, Lawyer or Consultant</title><link>https://johklo.github.io/docs/insight/client-relationship/</link><pubDate>Sun, 01 Jan 2023 20:25:29 +0900</pubDate><guid>https://johklo.github.io/docs/insight/client-relationship/</guid><description>
&lt;h2 data-hextra-search-id="client-relationship에-관해"&gt;Client Relationship에 관해&lt;span class="hx:absolute hx:-mt-20" id="client-relationship에-관해"&gt;&lt;/span&gt;
&lt;a href="#client-relationship%ec%97%90-%ea%b4%80%ed%95%b4" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Goldman Sachs의 Managing Director인 Jim Donovan이 Client Relationship에 관해 UVA에서 강의하는 내용이다. &lt;a href="https://www.youtube.com/watch?v=z8kqCIxXTEw&amp;amp;ab_channel=UniversityofVirginiaSchoolofLaw" target="_blank" rel="noopener"&gt;Managing Client Relationships as an Investment Banker, Lawyer or Consultant&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;단순히 고객 관계를 넘어 삶의 전반에 걸쳐 뼈가 되는 조언인듯.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-hextra-search-id="quote"&gt;Quote&lt;span class="hx:absolute hx:-mt-20" id="quote"&gt;&lt;/span&gt;
&lt;a href="#quote" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Covering clients is a learned skill; it is not an innate skill&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;(1) Learn from someone who is really good. Do everything you can to learn from him. In exchange, try to help him whatever you can (e.g: carrying his bags).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;(Find someone who is really good and learn from that person. Put my ego aside. Do everything you can to learn from him. In exchange, do everything to help him (carry his bags). Find someone who is really good and flatter the person.)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;(2) Become a strategic advisor - i.e. advise the client holistically. Do a great job in the task that your client assign you to do, but also use the oppportunity to engage with the client in other things. Advise the client holistically. Take the opportunity to help the client in other ways. If you do a good job, the client will trust you advice him in anything. Do a great job in the task but use the opportunity with the client to engage in other things.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;(3) Listen and ask open ended question. The more you listen, the better the client feel and the more you learn.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;(4) Be creative. When your client proposes something, you can say no. But, try to come out something else. It shows to the client that you are trying.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;(5) Put the client first. It is achieved in two ways. First, be prepared to give advice that is not in your interest. In the long run, the client will trust you more. Second, work really hard for the client. The client will believe that you are there for him.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;(6) Be upbeat. Don’t share the negative aspects of your life to your client. People want to be around upbeat people.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;(7) Take classes in the law and business. Two reasons. First, you will learn to understand business and how corporations function. Second, you will be able to grasp the legal and business jargons.&lt;/p&gt;
&lt;/blockquote&gt;</description></item><item><title>Book List</title><link>https://johklo.github.io/docs/book/book-list/</link><pubDate>Sun, 01 Jan 2023 11:25:00 +0000</pubDate><guid>https://johklo.github.io/docs/book/book-list/</guid><description>
&lt;h2 data-hextra-search-id="consulting"&gt;Consulting&lt;span class="hx:absolute hx:-mt-20" id="consulting"&gt;&lt;/span&gt;
&lt;a href="#consulting" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;[ ] The Secrets of Consulting: A Guide to Giving and Getting Advice Successfully&lt;/p&gt;
&lt;p&gt;[ ] Flawless Consulting: A Guide to Getting Your Expertise Used&lt;/p&gt;
&lt;p&gt;[ ] Getting Started in Consulting&lt;/p&gt;
&lt;p&gt;[ ] The McKinsey Way&lt;/p&gt;
&lt;p&gt;[ ] The Trusted Advisor&lt;/p&gt;
&lt;p&gt;[ ] The McKinsey Mind: Understanding and Implementing the Problem-Solving Tools and Management&lt;/p&gt;
&lt;p&gt;[ ] Techniques of the World’s Top Secret Consulting&lt;/p&gt;
&lt;p&gt;[ ] Case Interview Secrets: A Former McKinsey Interviewer Reveals How to Get Multiple Job Offers in Consulting&lt;/p&gt;
&lt;p&gt;[ ] Million Dollar Consulting: The Professional’s Guide to Growing a Practice&lt;/p&gt;
&lt;p&gt;[ ] The Lords of Strategy: The Secret Intellectual History of the New Corporate World&lt;/p&gt;
&lt;p&gt;[ ] Case in Point: Complete Case Interview Preparation&lt;/p&gt;
&lt;h2 data-hextra-search-id="tech"&gt;Tech&lt;span class="hx:absolute hx:-mt-20" id="tech"&gt;&lt;/span&gt;
&lt;a href="#tech" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;h2 data-hextra-search-id="org"&gt;Org&lt;span class="hx:absolute hx:-mt-20" id="org"&gt;&lt;/span&gt;
&lt;a href="#org" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;[X] 네이비씰 승리의 기술
[X] The Goal&lt;/p&gt;</description></item><item><title>(책) 달리기를 말할 때 내가 하고 싶은 이야기</title><link>https://johklo.github.io/docs/book/murakami-run/</link><pubDate>Sun, 01 Jan 2023 13:45:29 +0900</pubDate><guid>https://johklo.github.io/docs/book/murakami-run/</guid><description>
&lt;h2 data-hextra-search-id="끄적끄적"&gt;끄적끄적&lt;span class="hx:absolute hx:-mt-20" id="끄적끄적"&gt;&lt;/span&gt;
&lt;a href="#%eb%81%84%ec%a0%81%eb%81%84%ec%a0%81" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;작년부터 꾸준히 달리려고 노력하고 있다. 매주 적어도 3번 이상은 걷든 뛰든 남산 꼭대기를 뜀박질해서 올라가고 내려오는 행위를 하고 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;나이가 들수록 소소한 행위에도 의미를 부여하고자 하는 내 모습이 있고 그래서 찾아서 읽게 되었다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-hextra-search-id="quote"&gt;Quote&lt;span class="hx:absolute hx:-mt-20" id="quote"&gt;&lt;/span&gt;
&lt;a href="#quote" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;혼자 있고 싶다는 생각은 변함없이 항상 내안에 존재하고 있었따. 그런 까닭에 하루에 1시간쯤 달리며 나 자신만의 침묵의 시간을 확보한다는 것은, 나의 정신 위생에 중요한 의미를 지닌 작업이었다. 적어도 달리고 있는 동안은 누구와도 애기하지 않아도 괜찮고, 누구의 얘기도 듣지 않아도 된다. 그저 주위의 풍경을 바라보고, 자기 자신을 으시하면 되는것이다. 그것은 그 무엇과도 바꿀수 없는 귀중한 시간 이었다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;소설가가 되려는 것과 같은 야심이 있어썬 건 아니다. 나로서는 무엇이 어떻든 간에, 아무 생각 없이 소설이란느 것을 쓰고 싶었다. 무엇을 쓸 것인가 하는 구체적인 이미지도 없이 &amp;lsquo;지금 이람녀 뭔가 나 나름대로의 의미 있는 그럴듯한 소설을 쓸 수 있지 않을까&amp;rsquo; 하고 느꼈던 것이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;인간이라는 존재는 좋아하는 것은 자연히 계속할 수 있고, 좋아하지 않는 것은 계속할 수 없게 되어 있다. 거기에는 의지와 같은 것도 조금은 관계하고 있을 것이다. 그러나 아무리 의지가 강한 사람이라 해도, 아무리 지는것을 싫어하는 사람이라 해도, 마음에 들지 않는 일을 오래 계속할 수는 없다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;같은 10년이라고 해도, 멍하게 사는 10년보다는 확실한 목적을 지니고 생동감 있게 사는 10년 쪽이, 당연한 일이지만 훨씬 바람직하고, 달리는 것은 확실히 그러한 목적을 도와줄 것이라고 나는 생각하고 있다. 주어진 개개인의 한계 속에서 조금이라도 효과적으로 자기를 연소시켜 가는 일, 그것이 달리기의 본질이며, 그것은 또 사는 것의 메타포이기도 한 것이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;중요한 것은 시간과의 경쟁을 하는 것이 아니다. 어느 만큼의 충족감을 가지고 42킬로를 완주할 수 있는가, 얼마만큼 자기 자신을 즐길 수 있는가, 아마도 그것이 이제 부터 앞으로의 큰 의미를 가져오게 되는 것이 아닐까. 수치로 나타나지 않는 것을 나는 즐기며 평가하게 될 것이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;나는 하늘을 올려다본다. 거기에는 친절한 마음의 편린 같은 것이 보일까? 아니다, 보이지 않는다. 태평양 상공에 더그러니 떠 있는 무심한 여름 구름이 보일 분이다, 그것은 나에게 아무것도 말해주지 않는다. 구름은 언제나 말이 없다. 나는 하늘을 우려러보거나 하는 일은 하지 말았어야 했다. 시선을 향해야만 하는것은 아마도 자신의 안쪽인 것이다. 나는 자신의 내면으로 눈을 돌린다. 깊은 우물의 바닥을 보는 것 처럼.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;그런 인생을 옆에서 바라보며 -혹은 훨씬 높은 데서 내려다보면- 별다른 의미도 없느 ㄴ더없이 무익한 것으로서, 또는 매우 효율이 좋지 않은 것으로서 비쳐진다 해도, 또는 매우 효율이 좋지 않은 것으로서 비쳐진다고 해도, 또한 어쩔 수없는 일이 아닌가 하고 나는 생각한다.
가령 그것이 실제로 바닥에 작은 구멍이 뚫린 낡은 내밉에 물을 붓는 것과 같은 허망한 일에 지나지 않는다고 해도, 적어도 노력을 했다는 사실은 남는다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;그리고 진정으로 가치가 있는 것은 때때로 효율이 나쁜 행위를 통해서만이 획득할 수 있는 것이다.
비록 공허한 행위가 있었다고 해도, 그것은 결코 어리석은 행위는 아닐 것이다. 나는 그렇게 생각한다. 실감으로써, 그리고 경험칙으로써.&lt;/p&gt;
&lt;/blockquote&gt;</description></item><item><title>MathJax로 LaTeX 사용하기</title><link>https://johklo.github.io/docs/tech/mathjax-latex/</link><pubDate>Tue, 28 Nov 2017 22:56:29 +0900</pubDate><guid>https://johklo.github.io/docs/tech/mathjax-latex/</guid><description>
&lt;h2 data-hextra-search-id="개요"&gt;개요&lt;span class="hx:absolute hx:-mt-20" id="개요"&gt;&lt;/span&gt;
&lt;a href="#%ea%b0%9c%ec%9a%94" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;자신의 웹 페이지에서 LaTeX를 사용하고 싶다면 MathJax에서 제공하는 자바스크립트 라이브러리를 쓰면 된다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="설치-방법"&gt;설치 방법&lt;span class="hx:absolute hx:-mt-20" id="설치-방법"&gt;&lt;/span&gt;
&lt;a href="#%ec%84%a4%ec%b9%98-%eb%b0%a9%eb%b2%95" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://www.mathjax.org/" target="_blank" rel="noopener"&gt;MathJax.org&lt;/a&gt;의 &lt;a href="https://www.mathjax.org/#gettingstarted" target="_blank" rel="noopener"&gt;Getting Started&lt;/a&gt; 페이지에서 제공하는 설명대로 다음의 코드를 자신의 웹 페이지에 추가하면 된다.&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js?config=TeX-MML-AM_CHTML&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;그러면 해당 웹 페이지에 있는 텍스트 중, &lt;code&gt;$``$&lt;/code&gt;로 좌우가 감싸인 문자열은 LaTeX 문법으로 인식해, 수식으로 변환해준다.&lt;/p&gt;
&lt;h2 data-hextra-search-id="수식-사용-예제"&gt;수식 사용 예제&lt;span class="hx:absolute hx:-mt-20" id="수식-사용-예제"&gt;&lt;/span&gt;
&lt;a href="#%ec%88%98%ec%8b%9d-%ec%82%ac%ec%9a%a9-%ec%98%88%ec%a0%9c" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;간단한 제곱&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-latex" data-lang="latex"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sb"&gt;$$&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt; a^&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="nb"&gt; &lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="s"&gt;$$&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
\[( a^2 )\]&lt;ul&gt;
&lt;li&gt;이차방정식의 근의 공식&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-latex" data-lang="latex"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sb"&gt;$$&lt;/span&gt;&lt;span class="nb"&gt;x &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt; {&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;b &lt;/span&gt;&lt;span class="nv"&gt;\pm&lt;/span&gt;&lt;span class="nb"&gt; &lt;/span&gt;&lt;span class="nv"&gt;\sqrt&lt;/span&gt;&lt;span class="nb"&gt;{b^&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="nb"&gt;ac} &lt;/span&gt;&lt;span class="nv"&gt;\over&lt;/span&gt;&lt;span class="nb"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="nb"&gt;a}&lt;/span&gt;&lt;span class="s"&gt;$$&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
\[x = {-b \pm \sqrt{b^2-4ac} \over 2a}\]&lt;ul&gt;
&lt;li&gt;행렬&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-latex" data-lang="latex"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;\begin&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;pmatrix&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 1 &lt;span class="nb"&gt;&amp;amp;&lt;/span&gt; a&lt;span class="nb"&gt;_&lt;/span&gt;1 &lt;span class="nb"&gt;&amp;amp;&lt;/span&gt; a&lt;span class="nb"&gt;_&lt;/span&gt;1&lt;span class="nb"&gt;^&lt;/span&gt;2 &lt;span class="nb"&gt;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;\cdots&lt;/span&gt; &lt;span class="nb"&gt;&amp;amp;&lt;/span&gt; a&lt;span class="nb"&gt;_&lt;/span&gt;1&lt;span class="nb"&gt;^&lt;/span&gt;n &lt;span class="k"&gt;\\&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 1 &lt;span class="nb"&gt;&amp;amp;&lt;/span&gt; a&lt;span class="nb"&gt;_&lt;/span&gt;2 &lt;span class="nb"&gt;&amp;amp;&lt;/span&gt; a&lt;span class="nb"&gt;_&lt;/span&gt;2&lt;span class="nb"&gt;^&lt;/span&gt;2 &lt;span class="nb"&gt;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;\cdots&lt;/span&gt; &lt;span class="nb"&gt;&amp;amp;&lt;/span&gt; a&lt;span class="nb"&gt;_&lt;/span&gt;2&lt;span class="nb"&gt;^&lt;/span&gt;n &lt;span class="k"&gt;\\&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;\vdots&lt;/span&gt; &lt;span class="nb"&gt;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;\vdots&lt;/span&gt;&lt;span class="nb"&gt;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;\vdots&lt;/span&gt; &lt;span class="nb"&gt;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;\ddots&lt;/span&gt; &lt;span class="nb"&gt;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;\vdots&lt;/span&gt; &lt;span class="k"&gt;\\&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 1 &lt;span class="nb"&gt;&amp;amp;&lt;/span&gt; a&lt;span class="nb"&gt;_&lt;/span&gt;m &lt;span class="nb"&gt;&amp;amp;&lt;/span&gt; a&lt;span class="nb"&gt;_&lt;/span&gt;m&lt;span class="nb"&gt;^&lt;/span&gt;2 &lt;span class="nb"&gt;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;\cdots&lt;/span&gt; &lt;span class="nb"&gt;&amp;amp;&lt;/span&gt; a&lt;span class="nb"&gt;_&lt;/span&gt;m&lt;span class="nb"&gt;^&lt;/span&gt;n
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;\end&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;pmatrix&lt;span class="nb"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
\[\begin{pmatrix}
1 &amp;amp; a_1 &amp;amp; a_1^2 &amp;amp; \cdots &amp;amp; a_1^n \\
1 &amp;amp; a_2 &amp;amp; a_2^2 &amp;amp; \cdots &amp;amp; a_2^n \\
\vdots &amp;amp; \vdots&amp;amp; \vdots &amp;amp; \ddots &amp;amp; \vdots \\
1 &amp;amp; a_m &amp;amp; a_m^2 &amp;amp; \cdots &amp;amp; a_m^n
\end{pmatrix}\]&lt;ul&gt;
&lt;li&gt;나눗셈&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-latex" data-lang="latex"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sb"&gt;$$&lt;/span&gt;&lt;span class="nb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;\require&lt;/span&gt;&lt;span class="nb"&gt;{enclose}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;\begin&lt;/span&gt;&lt;span class="nb"&gt;{array}{r}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt; &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="nb"&gt; &lt;/span&gt;&lt;span class="nv"&gt;\\&lt;/span&gt;&lt;span class="o"&gt;[-&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="nb"&gt;pt&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="nb"&gt; &lt;/span&gt;&lt;span class="nv"&gt;\enclose&lt;/span&gt;&lt;span class="nb"&gt;{longdiv}{&lt;/span&gt;&lt;span class="m"&gt;52&lt;/span&gt;&lt;span class="nb"&gt;} &lt;/span&gt;&lt;span class="nv"&gt;\\&lt;/span&gt;&lt;span class="o"&gt;[-&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="nb"&gt;pt&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt; &lt;/span&gt;&lt;span class="nv"&gt;\underline&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="nb"&gt;}&lt;/span&gt;&lt;span class="nv"&gt;\phantom&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="nb"&gt;} &lt;/span&gt;&lt;span class="nv"&gt;\\&lt;/span&gt;&lt;span class="o"&gt;[-&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="nb"&gt;pt&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="nb"&gt; &lt;/span&gt;&lt;span class="nv"&gt;\\&lt;/span&gt;&lt;span class="o"&gt;[-&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="nb"&gt;pt&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nb"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt; &lt;/span&gt;&lt;span class="nv"&gt;\underline&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="nb"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;\end&lt;/span&gt;&lt;span class="nb"&gt;{array}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;$$&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="코드 복사"
aria-label="코드 복사"
data-copied-label="복사됨!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
\[
\require{enclose}
\begin{array}{r}
13 \\[-3pt]
4 \enclose{longdiv}{52} \\[-3pt]
\underline{4}\phantom{2} \\[-3pt]
12 \\[-3pt]
\underline{12}
\end{array}
\]&lt;h2 data-hextra-search-id="도구"&gt;도구&lt;span class="hx:absolute hx:-mt-20" id="도구"&gt;&lt;/span&gt;
&lt;a href="#%eb%8f%84%ea%b5%ac" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="http://detexify.kirelabs.org/classify.html" target="_blank" rel="noopener"&gt;detexify.kirelabs.org/classify.html&lt;/a&gt;: 기호를 마우스로 그리면, 필기 인식으로 내가 찾는 기호와 유사한 기호의 목록과 latex 코드를 제안해준다. 매우 편리하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-hextra-search-id="links"&gt;Links&lt;span class="hx:absolute hx:-mt-20" id="links"&gt;&lt;/span&gt;
&lt;a href="#links" class="subheading-anchor" aria-label="이 섹션에 대한 고유 링크"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.mathjax.org/" target="_blank" rel="noopener"&gt;MathJax.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://math.meta.stackexchange.com/questions/5020/mathjax-basic-tutorial-and-quick-reference" target="_blank" rel="noopener"&gt;MathJax basic tutorial and quick reference&lt;/a&gt;: 많은 예제가 있어 참고할 만하다.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://jsbin.com/zimuxulawu/edit?html,output" target="_blank" rel="noopener"&gt;jsbin.com/zimuxulawu&lt;/a&gt;: 여기서 연습해 볼 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://detexify.kirelabs.org/classify.html" target="_blank" rel="noopener"&gt;detexify.kirelabs.org/classify.html&lt;/a&gt;: 내가 찾는 기호와 유사한 기호의 목록과 latex 코드를 제안해준다.&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>