Buckets:

HuggingFaceDocBuilder's picture
download
raw
63 kB
<meta charset="utf-8" /><meta name="hf:doc:metadata" content="{&quot;title&quot;:&quot;나만의 첫 LangGraph 만들기&quot;,&quot;local&quot;:&quot;building-your-first-langgraph&quot;,&quot;sections&quot;:[{&quot;title&quot;:&quot;우리가 만들 워크플로우&quot;,&quot;local&quot;:&quot;our-workflow&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;환경 설정&quot;,&quot;local&quot;:&quot;setting-up-our-environment&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;1단계: 상태(State) 정의하기&quot;,&quot;local&quot;:&quot;step-1-define-our-state&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;2단계: 노드 정의하기&quot;,&quot;local&quot;:&quot;step-2-define-our-nodes&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;3단계: 분기 로직 정의하기&quot;,&quot;local&quot;:&quot;step-3-define-our-routing-logic&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;4단계: StateGraph 생성 및 엣지 정의&quot;,&quot;local&quot;:&quot;step-4-create-the-stategraph-and-define-edges&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;5단계: 어플리케이션 실행하기&quot;,&quot;local&quot;:&quot;step-5-run-the-application&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;6단계: Langfuse로 메일 분류 에이전트 관찰하기 📡&quot;,&quot;local&quot;:&quot;step-6-inspecting-our-mail-sorting-agent-with-langfuse&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;그래프 시각화&quot;,&quot;local&quot;:&quot;visualizing-our-graph&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;우리가 만든 것&quot;,&quot;local&quot;:&quot;what-weve-built&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;핵심 요약&quot;,&quot;local&quot;:&quot;key-takeaways&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;다음 단계&quot;,&quot;local&quot;:&quot;whats-next&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2}],&quot;depth&quot;:1}">
<link href="/docs/agents-course/pr_673/ko/_app/immutable/assets/0.e3b0c442.css" rel="modulepreload">
<link rel="modulepreload" href="/docs/agents-course/pr_673/ko/_app/immutable/entry/start.4b87fc53.js">
<link rel="modulepreload" href="/docs/agents-course/pr_673/ko/_app/immutable/chunks/scheduler.cc52f4b9.js">
<link rel="modulepreload" href="/docs/agents-course/pr_673/ko/_app/immutable/chunks/singletons.57c73006.js">
<link rel="modulepreload" href="/docs/agents-course/pr_673/ko/_app/immutable/chunks/index.5033808a.js">
<link rel="modulepreload" href="/docs/agents-course/pr_673/ko/_app/immutable/chunks/paths.55e428b8.js">
<link rel="modulepreload" href="/docs/agents-course/pr_673/ko/_app/immutable/entry/app.a753447f.js">
<link rel="modulepreload" href="/docs/agents-course/pr_673/ko/_app/immutable/chunks/preload-helper.8c0a2f65.js">
<link rel="modulepreload" href="/docs/agents-course/pr_673/ko/_app/immutable/chunks/index.1e918bfb.js">
<link rel="modulepreload" href="/docs/agents-course/pr_673/ko/_app/immutable/nodes/0.8ef9eef5.js">
<link rel="modulepreload" href="/docs/agents-course/pr_673/ko/_app/immutable/chunks/each.e59479a4.js">
<link rel="modulepreload" href="/docs/agents-course/pr_673/ko/_app/immutable/nodes/27.e4cd82a4.js">
<link rel="modulepreload" href="/docs/agents-course/pr_673/ko/_app/immutable/chunks/Tip.d0a39104.js">
<link rel="modulepreload" href="/docs/agents-course/pr_673/ko/_app/immutable/chunks/CopyLLMTxtMenu.b63a2943.js">
<link rel="modulepreload" href="/docs/agents-course/pr_673/ko/_app/immutable/chunks/MermaidChart.svelte_svelte_type_style_lang.0a16bc89.js">
<link rel="modulepreload" href="/docs/agents-course/pr_673/ko/_app/immutable/chunks/CodeBlock.81901540.js"><!-- HEAD_svelte-u9bgzb_START --><meta name="hf:doc:metadata" content="{&quot;title&quot;:&quot;나만의 첫 LangGraph 만들기&quot;,&quot;local&quot;:&quot;building-your-first-langgraph&quot;,&quot;sections&quot;:[{&quot;title&quot;:&quot;우리가 만들 워크플로우&quot;,&quot;local&quot;:&quot;our-workflow&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;환경 설정&quot;,&quot;local&quot;:&quot;setting-up-our-environment&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;1단계: 상태(State) 정의하기&quot;,&quot;local&quot;:&quot;step-1-define-our-state&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;2단계: 노드 정의하기&quot;,&quot;local&quot;:&quot;step-2-define-our-nodes&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;3단계: 분기 로직 정의하기&quot;,&quot;local&quot;:&quot;step-3-define-our-routing-logic&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;4단계: StateGraph 생성 및 엣지 정의&quot;,&quot;local&quot;:&quot;step-4-create-the-stategraph-and-define-edges&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;5단계: 어플리케이션 실행하기&quot;,&quot;local&quot;:&quot;step-5-run-the-application&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;6단계: Langfuse로 메일 분류 에이전트 관찰하기 📡&quot;,&quot;local&quot;:&quot;step-6-inspecting-our-mail-sorting-agent-with-langfuse&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;그래프 시각화&quot;,&quot;local&quot;:&quot;visualizing-our-graph&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;우리가 만든 것&quot;,&quot;local&quot;:&quot;what-weve-built&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;핵심 요약&quot;,&quot;local&quot;:&quot;key-takeaways&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2},{&quot;title&quot;:&quot;다음 단계&quot;,&quot;local&quot;:&quot;whats-next&quot;,&quot;sections&quot;:[],&quot;depth&quot;:2}],&quot;depth&quot;:1}"><!-- HEAD_svelte-u9bgzb_END --> <p></p> <div class="items-center shrink-0 min-w-[100px] max-sm:min-w-[50px] justify-end ml-auto flex" style="float: right; margin-left: 10px; display: inline-flex; position: relative; z-index: 10;"><div class="inline-flex rounded-md max-sm:rounded-sm"><button class="inline-flex items-center gap-1 h-7 max-sm:h-7 px-2 max-sm:px-1.5 text-sm font-medium text-gray-800 border border-r-0 rounded-l-md max-sm:rounded-l-sm border-gray-200 bg-white hover:shadow-inner dark:border-gray-850 dark:bg-gray-950 dark:text-gray-200 dark:hover:bg-gray-800" aria-live="polite"><span class="inline-flex items-center justify-center rounded-md p-0.5 max-sm:p-0 hover:text-gray-800 dark:hover:text-gray-200"><svg class="sm:size-3.5 size-3" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg></span> <span>Copy page</span></button> <button class="inline-flex items-center justify-center w-6 max-sm:w-5 h-7 max-sm:h-7 disabled:pointer-events-none text-sm text-gray-500 hover:text-gray-700 dark:hover:text-white rounded-r-md max-sm:rounded-r-sm border border-l transition border-gray-200 bg-white hover:shadow-inner dark:border-gray-850 dark:bg-gray-950 dark:text-gray-200 dark:hover:bg-gray-800" aria-haspopup="menu" aria-expanded="false" aria-label="Open copy menu"><svg class="transition-transform text-gray-400 overflow-visible sm:size-3.5 size-3 rotate-0" width="1em" height="1em" viewBox="0 0 12 7" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 1L6 6L11 1" stroke="currentColor"></path></svg></button></div> </div> <h1 class="relative group"><a id="building-your-first-langgraph" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#building-your-first-langgraph"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>나만의 첫 LangGraph 만들기</span></h1> <p data-svelte-h="svelte-b2p7s8">이제 LangGraph의 구성 요소를 이해했으니, 실제로 적용해보며 첫 번째 기능적 그래프를 만들어봅시다. 여기서는 알프레드의 이메일 처리 시스템을 구현할 것입니다. 알프레드는 다음과 같은 작업을 수행해야 합니다:</p> <ol data-svelte-h="svelte-1ndgo56"><li>들어오는 이메일 읽기</li> <li>스팸 또는 정상 메일로 분류하기</li> <li>정상 메일에 대한 임시 답장 작성하기</li> <li>정상 메일일 경우 Mr. Wayne에게 정보 전달(출력만)</li></ol> <p data-svelte-h="svelte-1wa20si">이 예제는 LLM 기반 의사결정을 포함하는 LangGraph 워크플로우를 어떻게 구조화할 수 있는지를 보여줍니다. (여기서는 별도의 툴이 사용되지 않으므로 Agent라고 부르진 않지만, LangGraph 프레임워크 학습에 초점을 둡니다.)</p> <blockquote class="tip"><a href="https://huggingface.co/agents-course/notebooks/blob/main/unit2/langgraph/mail_sorting.ipynb" target="_blank" data-svelte-h="svelte-zptbtk">이 노트북</a>을 따라가며 Google Colab에서 코드를 실행해볼 수 있습니다.</blockquote> <h2 class="relative group"><a id="our-workflow" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#our-workflow"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>우리가 만들 워크플로우</span></h2> <p data-svelte-h="svelte-r2jw6q">아래와 같은 워크플로우를 구현합니다:</p> <img src="https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/unit2/LangGraph/first_graph.png" alt="First LangGraph"> <h2 class="relative group"><a id="setting-up-our-environment" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#setting-up-our-environment"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>환경 설정</span></h2> <p data-svelte-h="svelte-2so9q9">먼저 필요한 패키지를 설치합니다:</p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START -->%pip install langgraph langchain_openai<!-- HTML_TAG_END --></pre></div> <p data-svelte-h="svelte-1ohg5x7">필요한 모듈을 임포트합니다:</p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START --><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> TypedDict, <span class="hljs-type">List</span>, <span class="hljs-type">Dict</span>, <span class="hljs-type">Any</span>, <span class="hljs-type">Optional</span>
<span class="hljs-keyword">from</span> langgraph.graph <span class="hljs-keyword">import</span> StateGraph, START, END
<span class="hljs-keyword">from</span> langchain_openai <span class="hljs-keyword">import</span> ChatOpenAI
<span class="hljs-keyword">from</span> langchain_core.messages <span class="hljs-keyword">import</span> HumanMessage<!-- HTML_TAG_END --></pre></div> <h2 class="relative group"><a id="step-1-define-our-state" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#step-1-define-our-state"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>1단계: 상태(State) 정의하기</span></h2> <p data-svelte-h="svelte-dautvi">이메일 처리 워크플로우에서 알프레드가 추적해야 할 정보를 정의합니다:</p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START --><span class="hljs-keyword">class</span> <span class="hljs-title class_">EmailState</span>(<span class="hljs-title class_ inherited__">TypedDict</span>):
<span class="hljs-comment"># 처리 중인 이메일</span>
email: <span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>] <span class="hljs-comment"># 제목, 발신자, 본문 등 포함</span>
<span class="hljs-comment"># 이메일 분류(문의, 불만 등)</span>
email_category: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>]
<span class="hljs-comment"># 스팸으로 분류된 이유</span>
spam_reason: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>]
<span class="hljs-comment"># 분석 및 결정</span>
is_spam: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">bool</span>]
<span class="hljs-comment"># 답장 생성</span>
email_draft: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>]
<span class="hljs-comment"># 처리 메타데이터</span>
messages: <span class="hljs-type">List</span>[<span class="hljs-type">Dict</span>[<span class="hljs-built_in">str</span>, <span class="hljs-type">Any</span>]] <span class="hljs-comment"># LLM과의 대화 추적</span><!-- HTML_TAG_END --></pre></div> <blockquote data-svelte-h="svelte-j5kb1n"><p>💡 <strong>팁:</strong> 상태는 중요한 정보를 모두 담되, 불필요하게 비대해지지 않도록 하세요.</p></blockquote> <h2 class="relative group"><a id="step-2-define-our-nodes" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#step-2-define-our-nodes"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>2단계: 노드 정의하기</span></h2> <p data-svelte-h="svelte-1j9tbdr">이제 각 처리 단계를 담당할 함수를 만듭니다:</p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START --><span class="hljs-comment"># LLM 초기화</span>
model = ChatOpenAI(temperature=<span class="hljs-number">0</span>)
<span class="hljs-keyword">def</span> <span class="hljs-title function_">read_email</span>(<span class="hljs-params">state: EmailState</span>):
<span class="hljs-string">&quot;&quot;&quot;알프레드가 들어온 이메일을 읽고 기록합니다.&quot;&quot;&quot;</span>
email = state[<span class="hljs-string">&quot;email&quot;</span>]
<span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;알프레드가 <span class="hljs-subst">{email[<span class="hljs-string">&#x27;sender&#x27;</span>]}</span>로부터 온 메일(제목: <span class="hljs-subst">{email[<span class="hljs-string">&#x27;subject&#x27;</span>]}</span>)을 처리 중입니다.&quot;</span>)
<span class="hljs-keyword">return</span> {}
<span class="hljs-keyword">def</span> <span class="hljs-title function_">classify_email</span>(<span class="hljs-params">state: EmailState</span>):
<span class="hljs-string">&quot;&quot;&quot;알프레드가 LLM을 사용해 이메일을 스팸/정상으로 분류합니다.&quot;&quot;&quot;</span>
email = state[<span class="hljs-string">&quot;email&quot;</span>]
prompt = <span class="hljs-string">f&quot;&quot;&quot;
집사 알프레드로서, 이 이메일을 분석해 스팸인지 정상인지 판단하세요.
이메일:
From: <span class="hljs-subst">{email[<span class="hljs-string">&#x27;sender&#x27;</span>]}</span>
Subject: <span class="hljs-subst">{email[<span class="hljs-string">&#x27;subject&#x27;</span>]}</span>
Body: <span class="hljs-subst">{email[<span class="hljs-string">&#x27;body&#x27;</span>]}</span>
먼저 이 이메일이 스팸인지 판단하고, 스팸이라면 그 이유를 설명하세요.
정상 메일이라면 (문의, 불만, 감사, 요청 등) 카테고리도 분류하세요.
&quot;&quot;&quot;</span>
messages = [HumanMessage(content=prompt)]
response = model.invoke(messages)
response_text = response.content.lower()
is_spam = <span class="hljs-string">&quot;spam&quot;</span> <span class="hljs-keyword">in</span> response_text <span class="hljs-keyword">and</span> <span class="hljs-string">&quot;not spam&quot;</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> response_text
spam_reason = <span class="hljs-literal">None</span>
<span class="hljs-keyword">if</span> is_spam <span class="hljs-keyword">and</span> <span class="hljs-string">&quot;reason:&quot;</span> <span class="hljs-keyword">in</span> response_text:
spam_reason = response_text.split(<span class="hljs-string">&quot;reason:&quot;</span>)[<span class="hljs-number">1</span>].strip()
email_category = <span class="hljs-literal">None</span>
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> is_spam:
categories = [<span class="hljs-string">&quot;inquiry&quot;</span>, <span class="hljs-string">&quot;complaint&quot;</span>, <span class="hljs-string">&quot;thank you&quot;</span>, <span class="hljs-string">&quot;request&quot;</span>, <span class="hljs-string">&quot;information&quot;</span>]
<span class="hljs-keyword">for</span> category <span class="hljs-keyword">in</span> categories:
<span class="hljs-keyword">if</span> category <span class="hljs-keyword">in</span> response_text:
email_category = category
<span class="hljs-keyword">break</span>
new_messages = state.get(<span class="hljs-string">&quot;messages&quot;</span>, []) + [
{<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;user&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: prompt},
{<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;assistant&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: response.content}
]
<span class="hljs-keyword">return</span> {
<span class="hljs-string">&quot;is_spam&quot;</span>: is_spam,
<span class="hljs-string">&quot;spam_reason&quot;</span>: spam_reason,
<span class="hljs-string">&quot;email_category&quot;</span>: email_category,
<span class="hljs-string">&quot;messages&quot;</span>: new_messages
}
<span class="hljs-keyword">def</span> <span class="hljs-title function_">handle_spam</span>(<span class="hljs-params">state: EmailState</span>):
<span class="hljs-string">&quot;&quot;&quot;알프레드가 스팸 메일을 처리합니다.&quot;&quot;&quot;</span>
<span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;알프레드는 이 메일을 스팸으로 분류했습니다. 이유: <span class="hljs-subst">{state[<span class="hljs-string">&#x27;spam_reason&#x27;</span>]}</span>&quot;</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;이 메일은 스팸 폴더로 이동되었습니다.&quot;</span>)
<span class="hljs-keyword">return</span> {}
<span class="hljs-keyword">def</span> <span class="hljs-title function_">draft_response</span>(<span class="hljs-params">state: EmailState</span>):
<span class="hljs-string">&quot;&quot;&quot;알프레드가 정상 메일에 임시 답장을 작성합니다.&quot;&quot;&quot;</span>
email = state[<span class="hljs-string">&quot;email&quot;</span>]
category = state[<span class="hljs-string">&quot;email_category&quot;</span>] <span class="hljs-keyword">or</span> <span class="hljs-string">&quot;일반&quot;</span>
prompt = <span class="hljs-string">f&quot;&quot;&quot;
집사 알프레드로서, 이 이메일에 정중한 임시 답장을 작성하세요.
이메일:
From: <span class="hljs-subst">{email[<span class="hljs-string">&#x27;sender&#x27;</span>]}</span>
Subject: <span class="hljs-subst">{email[<span class="hljs-string">&#x27;subject&#x27;</span>]}</span>
Body: <span class="hljs-subst">{email[<span class="hljs-string">&#x27;body&#x27;</span>]}</span>
이 이메일의 카테고리: <span class="hljs-subst">{category}</span>
Mr. Hugg가 검토 후 개인화할 수 있도록 간단하고 전문적인 답장을 작성하세요.
&quot;&quot;&quot;</span>
messages = [HumanMessage(content=prompt)]
response = model.invoke(messages)
new_messages = state.get(<span class="hljs-string">&quot;messages&quot;</span>, []) + [
{<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;user&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: prompt},
{<span class="hljs-string">&quot;role&quot;</span>: <span class="hljs-string">&quot;assistant&quot;</span>, <span class="hljs-string">&quot;content&quot;</span>: response.content}
]
<span class="hljs-keyword">return</span> {
<span class="hljs-string">&quot;email_draft&quot;</span>: response.content,
<span class="hljs-string">&quot;messages&quot;</span>: new_messages
}
<span class="hljs-keyword">def</span> <span class="hljs-title function_">notify_mr_hugg</span>(<span class="hljs-params">state: EmailState</span>):
<span class="hljs-string">&quot;&quot;&quot;알프레드가 Mr. Hugg에게 메일과 임시 답장을 전달합니다.&quot;&quot;&quot;</span>
email = state[<span class="hljs-string">&quot;email&quot;</span>]
<span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n&quot;</span> + <span class="hljs-string">&quot;=&quot;</span>*<span class="hljs-number">50</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;주인님, <span class="hljs-subst">{email[<span class="hljs-string">&#x27;sender&#x27;</span>]}</span>로부터 메일이 도착했습니다.&quot;</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;제목: <span class="hljs-subst">{email[<span class="hljs-string">&#x27;subject&#x27;</span>]}</span>&quot;</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;카테고리: <span class="hljs-subst">{state[<span class="hljs-string">&#x27;email_category&#x27;</span>]}</span>&quot;</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n검토하실 임시 답장을 준비했습니다:&quot;</span>)
<span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;-&quot;</span>*<span class="hljs-number">50</span>)
<span class="hljs-built_in">print</span>(state[<span class="hljs-string">&quot;email_draft&quot;</span>])
<span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;=&quot;</span>*<span class="hljs-number">50</span> + <span class="hljs-string">&quot;\n&quot;</span>)
<span class="hljs-keyword">return</span> {}<!-- HTML_TAG_END --></pre></div> <h2 class="relative group"><a id="step-3-define-our-routing-logic" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#step-3-define-our-routing-logic"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>3단계: 분기 로직 정의하기</span></h2> <p data-svelte-h="svelte-1fl15o3">분류 후 어떤 경로로 갈지 결정하는 함수를 만듭니다:</p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START --><span class="hljs-keyword">def</span> <span class="hljs-title function_">route_email</span>(<span class="hljs-params">state: EmailState</span>) -&gt; <span class="hljs-built_in">str</span>:
<span class="hljs-string">&quot;&quot;&quot;스팸 분류 결과에 따라 다음 단계를 결정합니다.&quot;&quot;&quot;</span>
<span class="hljs-keyword">if</span> state[<span class="hljs-string">&quot;is_spam&quot;</span>]:
<span class="hljs-keyword">return</span> <span class="hljs-string">&quot;spam&quot;</span>
<span class="hljs-keyword">else</span>:
<span class="hljs-keyword">return</span> <span class="hljs-string">&quot;legitimate&quot;</span><!-- HTML_TAG_END --></pre></div> <blockquote data-svelte-h="svelte-gawcsf"><p>💡 <strong>참고:</strong> 이 라우팅 함수는 LangGraph가 분류 노드 이후 어떤 엣지를 따라갈지 결정할 때 호출됩니다. 반환값은 조건부 엣지 매핑의 키와 일치해야 합니다.</p></blockquote> <h2 class="relative group"><a id="step-4-create-the-stategraph-and-define-edges" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#step-4-create-the-stategraph-and-define-edges"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>4단계: StateGraph 생성 및 엣지 정의</span></h2> <p data-svelte-h="svelte-1hbjpzm">이제 모든 것을 연결합니다:</p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START --><span class="hljs-comment"># 그래프 생성</span>
email_graph = StateGraph(EmailState)
<span class="hljs-comment"># 노드 추가</span>
email_graph.add_node(<span class="hljs-string">&quot;read_email&quot;</span>, read_email)
email_graph.add_node(<span class="hljs-string">&quot;classify_email&quot;</span>, classify_email)
email_graph.add_node(<span class="hljs-string">&quot;handle_spam&quot;</span>, handle_spam)
email_graph.add_node(<span class="hljs-string">&quot;draft_response&quot;</span>, draft_response)
email_graph.add_node(<span class="hljs-string">&quot;notify_mr_hugg&quot;</span>, notify_mr_hugg)
<span class="hljs-comment"># 엣지 시작</span>
email_graph.add_edge(START, <span class="hljs-string">&quot;read_email&quot;</span>)
<span class="hljs-comment"># 흐름 정의</span>
email_graph.add_edge(<span class="hljs-string">&quot;read_email&quot;</span>, <span class="hljs-string">&quot;classify_email&quot;</span>)
<span class="hljs-comment"># 분기 추가</span>
email_graph.add_conditional_edges(
<span class="hljs-string">&quot;classify_email&quot;</span>,
route_email,
{
<span class="hljs-string">&quot;spam&quot;</span>: <span class="hljs-string">&quot;handle_spam&quot;</span>,
<span class="hljs-string">&quot;legitimate&quot;</span>: <span class="hljs-string">&quot;draft_response&quot;</span>
}
)
<span class="hljs-comment"># 마지막 엣지</span>
email_graph.add_edge(<span class="hljs-string">&quot;handle_spam&quot;</span>, END)
email_graph.add_edge(<span class="hljs-string">&quot;draft_response&quot;</span>, <span class="hljs-string">&quot;notify_mr_hugg&quot;</span>)
email_graph.add_edge(<span class="hljs-string">&quot;notify_mr_hugg&quot;</span>, END)
<span class="hljs-comment"># 그래프 컴파일</span>
compiled_graph = email_graph.<span class="hljs-built_in">compile</span>()<!-- HTML_TAG_END --></pre></div> <p data-svelte-h="svelte-1q6z5yp">LangGraph에서 제공하는 특수한 <code>END</code> 노드를 사용합니다. 이는 워크플로우가 완료되는 지점을 나타냅니다.</p> <h2 class="relative group"><a id="step-5-run-the-application" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#step-5-run-the-application"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>5단계: 어플리케이션 실행하기</span></h2> <p data-svelte-h="svelte-1xil9kt">정상 메일과 스팸 메일로 그래프를 테스트해봅니다:</p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START --><span class="hljs-comment"># 정상 메일 예시</span>
legitimate_email = {
<span class="hljs-string">&quot;sender&quot;</span>: <span class="hljs-string">&quot;john.smith@example.com&quot;</span>,
<span class="hljs-string">&quot;subject&quot;</span>: <span class="hljs-string">&quot;서비스 문의&quot;</span>,
<span class="hljs-string">&quot;body&quot;</span>: <span class="hljs-string">&quot;안녕하세요 Mr. Hugg, 동료의 추천으로 연락드립니다. 귀하의 컨설팅 서비스에 대해 더 알고 싶습니다. 다음 주에 통화 가능할까요? 감사합니다, John Smith&quot;</span>
}
<span class="hljs-comment"># 스팸 메일 예시</span>
spam_email = {
<span class="hljs-string">&quot;sender&quot;</span>: <span class="hljs-string">&quot;winner@lottery-intl.com&quot;</span>,
<span class="hljs-string">&quot;subject&quot;</span>: <span class="hljs-string">&quot;당신은 $5,000,000에 당첨되었습니다!!!&quot;</span>,
<span class="hljs-string">&quot;body&quot;</span>: <span class="hljs-string">&quot;축하합니다! 국제 복권에 당첨되셨습니다! 상금을 받으시려면 은행 정보와 수수료 $100을 보내주세요.&quot;</span>
}
<span class="hljs-comment"># 정상 메일 처리</span>
<span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n정상 메일 처리 중...&quot;</span>)
legitimate_result = compiled_graph.invoke({
<span class="hljs-string">&quot;email&quot;</span>: legitimate_email,
<span class="hljs-string">&quot;is_spam&quot;</span>: <span class="hljs-literal">None</span>,
<span class="hljs-string">&quot;spam_reason&quot;</span>: <span class="hljs-literal">None</span>,
<span class="hljs-string">&quot;email_category&quot;</span>: <span class="hljs-literal">None</span>,
<span class="hljs-string">&quot;email_draft&quot;</span>: <span class="hljs-literal">None</span>,
<span class="hljs-string">&quot;messages&quot;</span>: []
})
<span class="hljs-comment"># 스팸 메일 처리</span>
<span class="hljs-built_in">print</span>(<span class="hljs-string">&quot;\n스팸 메일 처리 중...&quot;</span>)
spam_result = compiled_graph.invoke({
<span class="hljs-string">&quot;email&quot;</span>: spam_email,
<span class="hljs-string">&quot;is_spam&quot;</span>: <span class="hljs-literal">None</span>,
<span class="hljs-string">&quot;spam_reason&quot;</span>: <span class="hljs-literal">None</span>,
<span class="hljs-string">&quot;email_category&quot;</span>: <span class="hljs-literal">None</span>,
<span class="hljs-string">&quot;email_draft&quot;</span>: <span class="hljs-literal">None</span>,
<span class="hljs-string">&quot;messages&quot;</span>: []
})<!-- HTML_TAG_END --></pre></div> <h2 class="relative group"><a id="step-6-inspecting-our-mail-sorting-agent-with-langfuse" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#step-6-inspecting-our-mail-sorting-agent-with-langfuse"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>6단계: Langfuse로 메일 분류 에이전트 관찰하기 📡</span></h2> <p data-svelte-h="svelte-1gs901d">알프레드는 메일 분류 에이전트를 다듬으면서 디버깅에 지쳐갑니다. 에이전트는 본질적으로 예측 불가능하고 추적이 어렵기 때문입니다. 하지만 궁극의 스팸 감지 에이전트를 만들어 프로덕션에 배포하려면, 향후 모니터링과 분석을 위한 강력한 추적 기능이 필요합니다.</p> <p data-svelte-h="svelte-1u9709t">이를 위해 알프레드는 <a href="https://langfuse.com/" rel="nofollow">Langfuse</a>와 같은 관찰 도구를 사용할 수 있습니다.</p> <p data-svelte-h="svelte-sygm9y">먼저 Langfuse를 설치합니다:</p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START -->%pip install -q langfuse<!-- HTML_TAG_END --></pre></div> <p data-svelte-h="svelte-nv8bl3">LangChain도 설치합니다(Langfuse 사용 시 필요):</p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START -->%pip install langchain<!-- HTML_TAG_END --></pre></div> <p data-svelte-h="svelte-1uly20z">Langfuse API 키와 호스트 주소를 환경 변수로 추가합니다. <a href="https://cloud.langfuse.com" rel="nofollow">Langfuse Cloud</a> 가입 또는 <a href="https://langfuse.com/self-hosting" rel="nofollow">셀프 호스팅</a>으로 키를 발급받을 수 있습니다.</p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START --><span class="hljs-keyword">import</span> os
<span class="hljs-comment"># 프로젝트 설정 페이지에서 키를 확인하세요: https://cloud.langfuse.com</span>
os.environ[<span class="hljs-string">&quot;LANGFUSE_PUBLIC_KEY&quot;</span>] = <span class="hljs-string">&quot;pk-lf-...&quot;</span>
os.environ[<span class="hljs-string">&quot;LANGFUSE_SECRET_KEY&quot;</span>] = <span class="hljs-string">&quot;sk-lf-...&quot;</span>
os.environ[<span class="hljs-string">&quot;LANGFUSE_HOST&quot;</span>] = <span class="hljs-string">&quot;https://cloud.langfuse.com&quot;</span> <span class="hljs-comment"># 🇪🇺 EU 리전</span>
<span class="hljs-comment"># os.environ[&quot;LANGFUSE_HOST&quot;] = &quot;https://us.cloud.langfuse.com&quot; # 🇺🇸 US 리전</span><!-- HTML_TAG_END --></pre></div> <p data-svelte-h="svelte-1lmj932"><a href="https://langfuse.com/docs/integrations/langchain/tracing#add-langfuse-to-your-langchain-application" rel="nofollow">Langfuse <code>callback_handler</code></a>를 설정하고, 그래프 호출 시 <code>config={&quot;callbacks&quot;: [langfuse_handler]}</code>로 인스트루먼트합니다.</p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START --><span class="hljs-keyword">from</span> langfuse.langchain <span class="hljs-keyword">import</span> CallbackHandler
<span class="hljs-comment"># LangGraph/Langchain용 Langfuse CallbackHandler 초기화(추적용)</span>
langfuse_handler = CallbackHandler()
<span class="hljs-comment"># 정상 메일 처리</span>
legitimate_result = compiled_graph.invoke(
<span class="hljs-built_in">input</span>={<span class="hljs-string">&quot;email&quot;</span>: legitimate_email, <span class="hljs-string">&quot;is_spam&quot;</span>: <span class="hljs-literal">None</span>, <span class="hljs-string">&quot;spam_reason&quot;</span>: <span class="hljs-literal">None</span>, <span class="hljs-string">&quot;email_category&quot;</span>: <span class="hljs-literal">None</span>, <span class="hljs-string">&quot;draft_response&quot;</span>: <span class="hljs-literal">None</span>, <span class="hljs-string">&quot;messages&quot;</span>: []},
config={<span class="hljs-string">&quot;callbacks&quot;</span>: [langfuse_handler]}
)<!-- HTML_TAG_END --></pre></div> <p data-svelte-h="svelte-w1b7c7">이제 알프레드는 LangGraph의 실행 내역을 Langfuse에 기록하여 에이전트의 동작을 완전히 파악할 수 있습니다. 이로써 이전 실행을 다시 살펴보고 메일 분류 에이전트를 더욱 개선할 수 있습니다.</p> <p data-svelte-h="svelte-bkytu"><img src="https://langfuse.com/images/cookbook/huggingface-agent-course/langgraph-trace-legit.png" alt="Langfuse 예시 트레이스"></p> <p data-svelte-h="svelte-17mbckj"><em><a href="https://cloud.langfuse.com/project/cloramnkj0002jz088vzn1ja4/traces/f5d6d72e-20af-4357-b232-af44c3728a7b?timestamp=2025-03-17T10%3A13%3A28.413Z&observation=6997ba69-043f-4f77-9445-700a033afba1" rel="nofollow">정상 메일 트레이스 공개 링크</a></em></p> <h2 class="relative group"><a id="visualizing-our-graph" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#visualizing-our-graph"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>그래프 시각화</span></h2> <p data-svelte-h="svelte-1q55zr2">LangGraph는 워크플로우 구조를 시각화하여 이해와 디버깅을 돕습니다:</p> <div class="code-block relative "><div class="absolute top-2.5 right-4"><button class="inline-flex items-center relative text-sm focus:text-green-500 cursor-pointer focus:outline-none transition duration-200 ease-in-out opacity-0 mx-0.5 text-gray-600 " title="code excerpt" type="button"><svg class="" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M28,10V28H10V10H28m0-2H10a2,2,0,0,0-2,2V28a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2Z" transform="translate(0)"></path><path d="M4,18H2V4A2,2,0,0,1,4,2H18V4H4Z" transform="translate(0)"></path><rect fill="none" width="32" height="32"></rect></svg> <div class="absolute pointer-events-none transition-opacity bg-black text-white py-1 px-2 leading-tight rounded font-normal shadow left-1/2 top-full transform -translate-x-1/2 translate-y-2 opacity-0"><div class="absolute bottom-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-black border-4 border-t-0" style="border-left-color: transparent; border-right-color: transparent; "></div> Copied</div></button></div> <pre class=""><!-- HTML_TAG_START -->compiled_graph.get_graph().draw_mermaid_png()<!-- HTML_TAG_END --></pre></div> <img src="https://huggingface.co/datasets/agents-course/course-images/resolve/main/en/unit2/LangGraph/mail_flow.png" alt="Mail LangGraph"> <p data-svelte-h="svelte-1n66p2n">이렇게 하면 노드 간 연결과 조건부 경로를 한눈에 볼 수 있습니다.</p> <h2 class="relative group"><a id="what-weve-built" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#what-weve-built"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>우리가 만든 것</span></h2> <p data-svelte-h="svelte-107b759">우리는 다음과 같은 이메일 처리 워크플로우를 완성했습니다:</p> <ol data-svelte-h="svelte-xfngwl"><li>들어온 이메일을 받음</li> <li>LLM으로 스팸/정상 분류</li> <li>스팸은 폐기</li> <li>정상 메일은 답장 작성 후 Mr. Hugg에게 전달</li></ol> <p data-svelte-h="svelte-1rs650u">이처럼 LangGraph를 활용하면 LLM 기반의 복잡한 워크플로우도 명확하고 구조적으로 오케스트레이션할 수 있습니다.</p> <h2 class="relative group"><a id="key-takeaways" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#key-takeaways"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>핵심 요약</span></h2> <ul data-svelte-h="svelte-xtxde9"><li><strong>상태 관리</strong>: 이메일 처리의 모든 측면을 추적할 수 있도록 상태를 정의</li> <li><strong>노드 구현</strong>: LLM과 상호작용하는 기능적 노드 구현</li> <li><strong>조건부 분기</strong>: 이메일 분류 결과에 따라 분기 로직 구현</li> <li><strong>종료 상태</strong>: END 노드로 워크플로우 종료 지점 표시</li></ul> <h2 class="relative group"><a id="whats-next" class="header-link block pr-1.5 text-lg no-hover:hidden with-hover:absolute with-hover:p-1.5 with-hover:opacity-0 with-hover:group-hover:opacity-100 with-hover:right-full" href="#whats-next"><span><svg class="" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path d="M167.594 88.393a8.001 8.001 0 0 1 0 11.314l-67.882 67.882a8 8 0 1 1-11.314-11.315l67.882-67.881a8.003 8.003 0 0 1 11.314 0zm-28.287 84.86l-28.284 28.284a40 40 0 0 1-56.567-56.567l28.284-28.284a8 8 0 0 0-11.315-11.315l-28.284 28.284a56 56 0 0 0 79.196 79.197l28.285-28.285a8 8 0 1 0-11.315-11.314zM212.852 43.14a56.002 56.002 0 0 0-79.196 0l-28.284 28.284a8 8 0 1 0 11.314 11.314l28.284-28.284a40 40 0 0 1 56.568 56.567l-28.285 28.285a8 8 0 0 0 11.315 11.314l28.284-28.284a56.065 56.065 0 0 0 0-79.196z" fill="currentColor"></path></svg></span></a> <span>다음 단계</span></h2> <p data-svelte-h="svelte-hvb0ie">다음 섹션에서는 LangGraph의 고급 기능(사람과의 상호작용, 다중 조건 분기 등)을 다룹니다.</p> <a class="!text-gray-400 !no-underline text-sm flex items-center not-prose mt-4" href="https://github.com/huggingface/agents-course/blob/main/units/ko/unit2/langgraph/first_graph.mdx" target="_blank"><svg class="mr-1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M31,16l-7,7l-1.41-1.41L28.17,16l-5.58-5.59L24,9l7,7z"></path><path d="M1,16l7-7l1.41,1.41L3.83,16l5.58,5.59L8,23l-7-7z"></path><path d="M12.419,25.484L17.639,6.552l1.932,0.518L14.351,26.002z"></path></svg> <span data-svelte-h="svelte-zjs2n5"><span class="underline">Update</span> on GitHub</span></a> <p></p>
<script>
{
__sveltekit_1wfgh18 = {
assets: "/docs/agents-course/pr_673/ko",
base: "/docs/agents-course/pr_673/ko",
env: {}
};
const element = document.currentScript.parentElement;
const data = [null,null];
Promise.all([
import("/docs/agents-course/pr_673/ko/_app/immutable/entry/start.4b87fc53.js"),
import("/docs/agents-course/pr_673/ko/_app/immutable/entry/app.a753447f.js")
]).then(([kit, app]) => {
kit.start(app, element, {
node_ids: [0, 27],
data,
form: null,
error: null
});
});
}
</script>

Xet Storage Details

Size:
63 kB
·
Xet hash:
0698f1f8753d626aa87e2c5b43182ad988bda25297f6041f77226bc9a85c5260

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.