Buckets:
| <meta charset="utf-8" /><meta name="hf:doc:metadata" content="{"title":"나만의 첫 LangGraph 만들기","local":"building-your-first-langgraph","sections":[{"title":"우리가 만들 워크플로우","local":"our-workflow","sections":[],"depth":2},{"title":"환경 설정","local":"setting-up-our-environment","sections":[],"depth":2},{"title":"1단계: 상태(State) 정의하기","local":"step-1-define-our-state","sections":[],"depth":2},{"title":"2단계: 노드 정의하기","local":"step-2-define-our-nodes","sections":[],"depth":2},{"title":"3단계: 분기 로직 정의하기","local":"step-3-define-our-routing-logic","sections":[],"depth":2},{"title":"4단계: StateGraph 생성 및 엣지 정의","local":"step-4-create-the-stategraph-and-define-edges","sections":[],"depth":2},{"title":"5단계: 어플리케이션 실행하기","local":"step-5-run-the-application","sections":[],"depth":2},{"title":"6단계: Langfuse로 메일 분류 에이전트 관찰하기 📡","local":"step-6-inspecting-our-mail-sorting-agent-with-langfuse","sections":[],"depth":2},{"title":"그래프 시각화","local":"visualizing-our-graph","sections":[],"depth":2},{"title":"우리가 만든 것","local":"what-weve-built","sections":[],"depth":2},{"title":"핵심 요약","local":"key-takeaways","sections":[],"depth":2},{"title":"다음 단계","local":"whats-next","sections":[],"depth":2}],"depth":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="{"title":"나만의 첫 LangGraph 만들기","local":"building-your-first-langgraph","sections":[{"title":"우리가 만들 워크플로우","local":"our-workflow","sections":[],"depth":2},{"title":"환경 설정","local":"setting-up-our-environment","sections":[],"depth":2},{"title":"1단계: 상태(State) 정의하기","local":"step-1-define-our-state","sections":[],"depth":2},{"title":"2단계: 노드 정의하기","local":"step-2-define-our-nodes","sections":[],"depth":2},{"title":"3단계: 분기 로직 정의하기","local":"step-3-define-our-routing-logic","sections":[],"depth":2},{"title":"4단계: StateGraph 생성 및 엣지 정의","local":"step-4-create-the-stategraph-and-define-edges","sections":[],"depth":2},{"title":"5단계: 어플리케이션 실행하기","local":"step-5-run-the-application","sections":[],"depth":2},{"title":"6단계: Langfuse로 메일 분류 에이전트 관찰하기 📡","local":"step-6-inspecting-our-mail-sorting-agent-with-langfuse","sections":[],"depth":2},{"title":"그래프 시각화","local":"visualizing-our-graph","sections":[],"depth":2},{"title":"우리가 만든 것","local":"what-weve-built","sections":[],"depth":2},{"title":"핵심 요약","local":"key-takeaways","sections":[],"depth":2},{"title":"다음 단계","local":"whats-next","sections":[],"depth":2}],"depth":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">"""알프레드가 들어온 이메일을 읽고 기록합니다."""</span> | |
| email = state[<span class="hljs-string">"email"</span>] | |
| <span class="hljs-built_in">print</span>(<span class="hljs-string">f"알프레드가 <span class="hljs-subst">{email[<span class="hljs-string">'sender'</span>]}</span>로부터 온 메일(제목: <span class="hljs-subst">{email[<span class="hljs-string">'subject'</span>]}</span>)을 처리 중입니다."</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">"""알프레드가 LLM을 사용해 이메일을 스팸/정상으로 분류합니다."""</span> | |
| email = state[<span class="hljs-string">"email"</span>] | |
| prompt = <span class="hljs-string">f""" | |
| 집사 알프레드로서, 이 이메일을 분석해 스팸인지 정상인지 판단하세요. | |
| 이메일: | |
| From: <span class="hljs-subst">{email[<span class="hljs-string">'sender'</span>]}</span> | |
| Subject: <span class="hljs-subst">{email[<span class="hljs-string">'subject'</span>]}</span> | |
| Body: <span class="hljs-subst">{email[<span class="hljs-string">'body'</span>]}</span> | |
| 먼저 이 이메일이 스팸인지 판단하고, 스팸이라면 그 이유를 설명하세요. | |
| 정상 메일이라면 (문의, 불만, 감사, 요청 등) 카테고리도 분류하세요. | |
| """</span> | |
| messages = [HumanMessage(content=prompt)] | |
| response = model.invoke(messages) | |
| response_text = response.content.lower() | |
| is_spam = <span class="hljs-string">"spam"</span> <span class="hljs-keyword">in</span> response_text <span class="hljs-keyword">and</span> <span class="hljs-string">"not spam"</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">"reason:"</span> <span class="hljs-keyword">in</span> response_text: | |
| spam_reason = response_text.split(<span class="hljs-string">"reason:"</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">"inquiry"</span>, <span class="hljs-string">"complaint"</span>, <span class="hljs-string">"thank you"</span>, <span class="hljs-string">"request"</span>, <span class="hljs-string">"information"</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">"messages"</span>, []) + [ | |
| {<span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"content"</span>: prompt}, | |
| {<span class="hljs-string">"role"</span>: <span class="hljs-string">"assistant"</span>, <span class="hljs-string">"content"</span>: response.content} | |
| ] | |
| <span class="hljs-keyword">return</span> { | |
| <span class="hljs-string">"is_spam"</span>: is_spam, | |
| <span class="hljs-string">"spam_reason"</span>: spam_reason, | |
| <span class="hljs-string">"email_category"</span>: email_category, | |
| <span class="hljs-string">"messages"</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">"""알프레드가 스팸 메일을 처리합니다."""</span> | |
| <span class="hljs-built_in">print</span>(<span class="hljs-string">f"알프레드는 이 메일을 스팸으로 분류했습니다. 이유: <span class="hljs-subst">{state[<span class="hljs-string">'spam_reason'</span>]}</span>"</span>) | |
| <span class="hljs-built_in">print</span>(<span class="hljs-string">"이 메일은 스팸 폴더로 이동되었습니다."</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">"""알프레드가 정상 메일에 임시 답장을 작성합니다."""</span> | |
| email = state[<span class="hljs-string">"email"</span>] | |
| category = state[<span class="hljs-string">"email_category"</span>] <span class="hljs-keyword">or</span> <span class="hljs-string">"일반"</span> | |
| prompt = <span class="hljs-string">f""" | |
| 집사 알프레드로서, 이 이메일에 정중한 임시 답장을 작성하세요. | |
| 이메일: | |
| From: <span class="hljs-subst">{email[<span class="hljs-string">'sender'</span>]}</span> | |
| Subject: <span class="hljs-subst">{email[<span class="hljs-string">'subject'</span>]}</span> | |
| Body: <span class="hljs-subst">{email[<span class="hljs-string">'body'</span>]}</span> | |
| 이 이메일의 카테고리: <span class="hljs-subst">{category}</span> | |
| Mr. Hugg가 검토 후 개인화할 수 있도록 간단하고 전문적인 답장을 작성하세요. | |
| """</span> | |
| messages = [HumanMessage(content=prompt)] | |
| response = model.invoke(messages) | |
| new_messages = state.get(<span class="hljs-string">"messages"</span>, []) + [ | |
| {<span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"content"</span>: prompt}, | |
| {<span class="hljs-string">"role"</span>: <span class="hljs-string">"assistant"</span>, <span class="hljs-string">"content"</span>: response.content} | |
| ] | |
| <span class="hljs-keyword">return</span> { | |
| <span class="hljs-string">"email_draft"</span>: response.content, | |
| <span class="hljs-string">"messages"</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">"""알프레드가 Mr. Hugg에게 메일과 임시 답장을 전달합니다."""</span> | |
| email = state[<span class="hljs-string">"email"</span>] | |
| <span class="hljs-built_in">print</span>(<span class="hljs-string">"\n"</span> + <span class="hljs-string">"="</span>*<span class="hljs-number">50</span>) | |
| <span class="hljs-built_in">print</span>(<span class="hljs-string">f"주인님, <span class="hljs-subst">{email[<span class="hljs-string">'sender'</span>]}</span>로부터 메일이 도착했습니다."</span>) | |
| <span class="hljs-built_in">print</span>(<span class="hljs-string">f"제목: <span class="hljs-subst">{email[<span class="hljs-string">'subject'</span>]}</span>"</span>) | |
| <span class="hljs-built_in">print</span>(<span class="hljs-string">f"카테고리: <span class="hljs-subst">{state[<span class="hljs-string">'email_category'</span>]}</span>"</span>) | |
| <span class="hljs-built_in">print</span>(<span class="hljs-string">"\n검토하실 임시 답장을 준비했습니다:"</span>) | |
| <span class="hljs-built_in">print</span>(<span class="hljs-string">"-"</span>*<span class="hljs-number">50</span>) | |
| <span class="hljs-built_in">print</span>(state[<span class="hljs-string">"email_draft"</span>]) | |
| <span class="hljs-built_in">print</span>(<span class="hljs-string">"="</span>*<span class="hljs-number">50</span> + <span class="hljs-string">"\n"</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>) -> <span class="hljs-built_in">str</span>: | |
| <span class="hljs-string">"""스팸 분류 결과에 따라 다음 단계를 결정합니다."""</span> | |
| <span class="hljs-keyword">if</span> state[<span class="hljs-string">"is_spam"</span>]: | |
| <span class="hljs-keyword">return</span> <span class="hljs-string">"spam"</span> | |
| <span class="hljs-keyword">else</span>: | |
| <span class="hljs-keyword">return</span> <span class="hljs-string">"legitimate"</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">"read_email"</span>, read_email) | |
| email_graph.add_node(<span class="hljs-string">"classify_email"</span>, classify_email) | |
| email_graph.add_node(<span class="hljs-string">"handle_spam"</span>, handle_spam) | |
| email_graph.add_node(<span class="hljs-string">"draft_response"</span>, draft_response) | |
| email_graph.add_node(<span class="hljs-string">"notify_mr_hugg"</span>, notify_mr_hugg) | |
| <span class="hljs-comment"># 엣지 시작</span> | |
| email_graph.add_edge(START, <span class="hljs-string">"read_email"</span>) | |
| <span class="hljs-comment"># 흐름 정의</span> | |
| email_graph.add_edge(<span class="hljs-string">"read_email"</span>, <span class="hljs-string">"classify_email"</span>) | |
| <span class="hljs-comment"># 분기 추가</span> | |
| email_graph.add_conditional_edges( | |
| <span class="hljs-string">"classify_email"</span>, | |
| route_email, | |
| { | |
| <span class="hljs-string">"spam"</span>: <span class="hljs-string">"handle_spam"</span>, | |
| <span class="hljs-string">"legitimate"</span>: <span class="hljs-string">"draft_response"</span> | |
| } | |
| ) | |
| <span class="hljs-comment"># 마지막 엣지</span> | |
| email_graph.add_edge(<span class="hljs-string">"handle_spam"</span>, END) | |
| email_graph.add_edge(<span class="hljs-string">"draft_response"</span>, <span class="hljs-string">"notify_mr_hugg"</span>) | |
| email_graph.add_edge(<span class="hljs-string">"notify_mr_hugg"</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">"sender"</span>: <span class="hljs-string">"john.smith@example.com"</span>, | |
| <span class="hljs-string">"subject"</span>: <span class="hljs-string">"서비스 문의"</span>, | |
| <span class="hljs-string">"body"</span>: <span class="hljs-string">"안녕하세요 Mr. Hugg, 동료의 추천으로 연락드립니다. 귀하의 컨설팅 서비스에 대해 더 알고 싶습니다. 다음 주에 통화 가능할까요? 감사합니다, John Smith"</span> | |
| } | |
| <span class="hljs-comment"># 스팸 메일 예시</span> | |
| spam_email = { | |
| <span class="hljs-string">"sender"</span>: <span class="hljs-string">"winner@lottery-intl.com"</span>, | |
| <span class="hljs-string">"subject"</span>: <span class="hljs-string">"당신은 $5,000,000에 당첨되었습니다!!!"</span>, | |
| <span class="hljs-string">"body"</span>: <span class="hljs-string">"축하합니다! 국제 복권에 당첨되셨습니다! 상금을 받으시려면 은행 정보와 수수료 $100을 보내주세요."</span> | |
| } | |
| <span class="hljs-comment"># 정상 메일 처리</span> | |
| <span class="hljs-built_in">print</span>(<span class="hljs-string">"\n정상 메일 처리 중..."</span>) | |
| legitimate_result = compiled_graph.invoke({ | |
| <span class="hljs-string">"email"</span>: legitimate_email, | |
| <span class="hljs-string">"is_spam"</span>: <span class="hljs-literal">None</span>, | |
| <span class="hljs-string">"spam_reason"</span>: <span class="hljs-literal">None</span>, | |
| <span class="hljs-string">"email_category"</span>: <span class="hljs-literal">None</span>, | |
| <span class="hljs-string">"email_draft"</span>: <span class="hljs-literal">None</span>, | |
| <span class="hljs-string">"messages"</span>: [] | |
| }) | |
| <span class="hljs-comment"># 스팸 메일 처리</span> | |
| <span class="hljs-built_in">print</span>(<span class="hljs-string">"\n스팸 메일 처리 중..."</span>) | |
| spam_result = compiled_graph.invoke({ | |
| <span class="hljs-string">"email"</span>: spam_email, | |
| <span class="hljs-string">"is_spam"</span>: <span class="hljs-literal">None</span>, | |
| <span class="hljs-string">"spam_reason"</span>: <span class="hljs-literal">None</span>, | |
| <span class="hljs-string">"email_category"</span>: <span class="hljs-literal">None</span>, | |
| <span class="hljs-string">"email_draft"</span>: <span class="hljs-literal">None</span>, | |
| <span class="hljs-string">"messages"</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">"LANGFUSE_PUBLIC_KEY"</span>] = <span class="hljs-string">"pk-lf-..."</span> | |
| os.environ[<span class="hljs-string">"LANGFUSE_SECRET_KEY"</span>] = <span class="hljs-string">"sk-lf-..."</span> | |
| os.environ[<span class="hljs-string">"LANGFUSE_HOST"</span>] = <span class="hljs-string">"https://cloud.langfuse.com"</span> <span class="hljs-comment"># 🇪🇺 EU 리전</span> | |
| <span class="hljs-comment"># os.environ["LANGFUSE_HOST"] = "https://us.cloud.langfuse.com" # 🇺🇸 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={"callbacks": [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">"email"</span>: legitimate_email, <span class="hljs-string">"is_spam"</span>: <span class="hljs-literal">None</span>, <span class="hljs-string">"spam_reason"</span>: <span class="hljs-literal">None</span>, <span class="hljs-string">"email_category"</span>: <span class="hljs-literal">None</span>, <span class="hljs-string">"draft_response"</span>: <span class="hljs-literal">None</span>, <span class="hljs-string">"messages"</span>: []}, | |
| config={<span class="hljs-string">"callbacks"</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.