Buckets:
| <meta charset="utf-8" /><meta name="hf:doc:metadata" content="{"title":"Building Your First LangGraph","local":"building-your-first-langgraph","sections":[{"title":"Our Workflow","local":"our-workflow","sections":[],"depth":2},{"title":"Setting Up Our Environment","local":"setting-up-our-environment","sections":[],"depth":2},{"title":"Step 1: Define Our State","local":"step-1-define-our-state","sections":[],"depth":2},{"title":"Step 2: Define Our Nodes","local":"step-2-define-our-nodes","sections":[],"depth":2},{"title":"Step 3: Define Our Routing Logic","local":"step-3-define-our-routing-logic","sections":[],"depth":2},{"title":"Step 4: Create the StateGraph and Define Edges","local":"step-4-create-the-stategraph-and-define-edges","sections":[],"depth":2},{"title":"Step 5: Run the Application","local":"step-5-run-the-application","sections":[],"depth":2},{"title":"Step 6: Inspecting Our Mail Sorting Agent with Langfuse 📡","local":"step-6-inspecting-our-mail-sorting-agent-with-langfuse-","sections":[],"depth":2},{"title":"Visualizing Our Graph","local":"visualizing-our-graph","sections":[],"depth":2},{"title":"What We’ve Built","local":"what-weve-built","sections":[],"depth":2},{"title":"Key Takeaways","local":"key-takeaways","sections":[],"depth":2},{"title":"What’s Next?","local":"whats-next","sections":[],"depth":2}],"depth":1}"> | |
| <link href="/docs/agents-course/pr_545/en/_app/immutable/assets/0.e3b0c442.css" rel="modulepreload"> | |
| <link rel="modulepreload" href="/docs/agents-course/pr_545/en/_app/immutable/entry/start.1596c81c.js"> | |
| <link rel="modulepreload" href="/docs/agents-course/pr_545/en/_app/immutable/chunks/scheduler.37c15a92.js"> | |
| <link rel="modulepreload" href="/docs/agents-course/pr_545/en/_app/immutable/chunks/singletons.8a3d92bd.js"> | |
| <link rel="modulepreload" href="/docs/agents-course/pr_545/en/_app/immutable/chunks/index.18351ede.js"> | |
| <link rel="modulepreload" href="/docs/agents-course/pr_545/en/_app/immutable/chunks/paths.5b2602e7.js"> | |
| <link rel="modulepreload" href="/docs/agents-course/pr_545/en/_app/immutable/entry/app.856a784e.js"> | |
| <link rel="modulepreload" href="/docs/agents-course/pr_545/en/_app/immutable/chunks/index.2bf4358c.js"> | |
| <link rel="modulepreload" href="/docs/agents-course/pr_545/en/_app/immutable/nodes/0.1cd5790a.js"> | |
| <link rel="modulepreload" href="/docs/agents-course/pr_545/en/_app/immutable/chunks/each.e59479a4.js"> | |
| <link rel="modulepreload" href="/docs/agents-course/pr_545/en/_app/immutable/nodes/40.48b5584a.js"> | |
| <link rel="modulepreload" href="/docs/agents-course/pr_545/en/_app/immutable/chunks/Tip.363c041f.js"> | |
| <link rel="modulepreload" href="/docs/agents-course/pr_545/en/_app/immutable/chunks/CodeBlock.4e987730.js"> | |
| <link rel="modulepreload" href="/docs/agents-course/pr_545/en/_app/immutable/chunks/Heading.8ada512a.js"> | |
| <link rel="modulepreload" href="/docs/agents-course/pr_545/en/_app/immutable/chunks/getInferenceSnippets.031140c2.js"><!-- HEAD_svelte-u9bgzb_START --><meta name="hf:doc:metadata" content="{"title":"Building Your First LangGraph","local":"building-your-first-langgraph","sections":[{"title":"Our Workflow","local":"our-workflow","sections":[],"depth":2},{"title":"Setting Up Our Environment","local":"setting-up-our-environment","sections":[],"depth":2},{"title":"Step 1: Define Our State","local":"step-1-define-our-state","sections":[],"depth":2},{"title":"Step 2: Define Our Nodes","local":"step-2-define-our-nodes","sections":[],"depth":2},{"title":"Step 3: Define Our Routing Logic","local":"step-3-define-our-routing-logic","sections":[],"depth":2},{"title":"Step 4: Create the StateGraph and Define Edges","local":"step-4-create-the-stategraph-and-define-edges","sections":[],"depth":2},{"title":"Step 5: Run the Application","local":"step-5-run-the-application","sections":[],"depth":2},{"title":"Step 6: Inspecting Our Mail Sorting Agent with Langfuse 📡","local":"step-6-inspecting-our-mail-sorting-agent-with-langfuse-","sections":[],"depth":2},{"title":"Visualizing Our Graph","local":"visualizing-our-graph","sections":[],"depth":2},{"title":"What We’ve Built","local":"what-weve-built","sections":[],"depth":2},{"title":"Key Takeaways","local":"key-takeaways","sections":[],"depth":2},{"title":"What’s Next?","local":"whats-next","sections":[],"depth":2}],"depth":1}"><!-- HEAD_svelte-u9bgzb_END --> <p></p> <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>Building Your First LangGraph</span></h1> <p data-svelte-h="svelte-vhd0jr">Now that we understand the building blocks, let’s put them into practice by building our first functional graph. We’ll implement Alfred’s email processing system, where he needs to:</p> <ol data-svelte-h="svelte-1v71265"><li>Read incoming emails</li> <li>Classify them as spam or legitimate</li> <li>Draft a preliminary response for legitimate emails</li> <li>Send information to Mr. Wayne when legitimate (printing only)</li></ol> <p data-svelte-h="svelte-1qg9fnr">This example demonstrates how to structure a workflow with LangGraph that involves LLM-based decision-making. While this can’t be considered an Agent as no tool is involved, this section focuses more on learning the LangGraph framework than Agents.</p> <div class="course-tip bg-gradient-to-br dark:bg-gradient-to-r before:border-green-500 dark:before:border-green-800 from-green-50 dark:from-gray-900 to-white dark:to-gray-950 border border-green-50 text-green-700 dark:text-gray-400">You can follow the code in <a href="https://huggingface.co/agents-course/notebooks/blob/main/unit2/langgraph/mail_sorting.ipynb" target="_blank" data-svelte-h="svelte-1wsoqbe">this notebook</a> that you can run using Google Colab.</div> <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>Our Workflow</span></h2> <p data-svelte-h="svelte-9r2hfn">Here’s the workflow we’ll build:</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>Setting Up Our Environment</span></h2> <p data-svelte-h="svelte-1n92y5t">First, let’s install the required packages:</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-rnel3w">Next, let’s import the necessary modules:</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>Step 1: Define Our State</span></h2> <p data-svelte-h="svelte-54271l">Let’s define what information Alfred needs to track during the email processing workflow:</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"># The email being processed</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"># Contains subject, sender, body, etc.</span> | |
| <span class="hljs-comment"># Category of the email (inquiry, complaint, etc.)</span> | |
| email_category: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] | |
| <span class="hljs-comment"># Reason why the email was marked as spam</span> | |
| spam_reason: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] | |
| <span class="hljs-comment"># Analysis and decisions</span> | |
| is_spam: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">bool</span>] | |
| <span class="hljs-comment"># Response generation</span> | |
| email_draft: <span class="hljs-type">Optional</span>[<span class="hljs-built_in">str</span>] | |
| <span class="hljs-comment"># Processing metadata</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"># Track conversation with LLM for analysis</span><!-- HTML_TAG_END --></pre></div> <blockquote data-svelte-h="svelte-10gtfo"><p>💡 <strong>Tip:</strong> Make your state comprehensive enough to track all the important information, but avoid bloating it with unnecessary details.</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>Step 2: Define Our Nodes</span></h2> <p data-svelte-h="svelte-1d4gnq4">Now, let’s create the processing functions that will form our nodes:</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"># Initialize our 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">"""Alfred reads and logs the incoming email"""</span> | |
| email = state[<span class="hljs-string">"email"</span>] | |
| <span class="hljs-comment"># Here we might do some initial preprocessing</span> | |
| <span class="hljs-built_in">print</span>(<span class="hljs-string">f"Alfred is processing an email from <span class="hljs-subst">{email[<span class="hljs-string">'sender'</span>]}</span> with subject: <span class="hljs-subst">{email[<span class="hljs-string">'subject'</span>]}</span>"</span>) | |
| <span class="hljs-comment"># No state changes needed here</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">"""Alfred uses an LLM to determine if the email is spam or legitimate"""</span> | |
| email = state[<span class="hljs-string">"email"</span>] | |
| <span class="hljs-comment"># Prepare our prompt for the LLM</span> | |
| prompt = <span class="hljs-string">f""" | |
| As Alfred the butler, analyze this email and determine if it is spam or legitimate. | |
| Email: | |
| 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> | |
| First, determine if this email is spam. If it is spam, explain why. | |
| If it is legitimate, categorize it (inquiry, complaint, thank you, etc.). | |
| """</span> | |
| <span class="hljs-comment"># Call the LLM</span> | |
| messages = [HumanMessage(content=prompt)] | |
| response = model.invoke(messages) | |
| <span class="hljs-comment"># Simple logic to parse the response (in a real app, you'd want more robust parsing)</span> | |
| 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 | |
| <span class="hljs-comment"># Extract a reason if it's spam</span> | |
| 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() | |
| <span class="hljs-comment"># Determine category if legitimate</span> | |
| 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> | |
| <span class="hljs-comment"># Update messages for tracking</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-comment"># Return state updates</span> | |
| <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">"""Alfred discards spam email with a note"""</span> | |
| <span class="hljs-built_in">print</span>(<span class="hljs-string">f"Alfred has marked the email as spam. Reason: <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">"The email has been moved to the spam folder."</span>) | |
| <span class="hljs-comment"># We're done processing this email</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">"""Alfred drafts a preliminary response for legitimate emails"""</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">"general"</span> | |
| <span class="hljs-comment"># Prepare our prompt for the LLM</span> | |
| prompt = <span class="hljs-string">f""" | |
| As Alfred the butler, draft a polite preliminary response to this email. | |
| Email: | |
| 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> | |
| This email has been categorized as: <span class="hljs-subst">{category}</span> | |
| Draft a brief, professional response that Mr. Hugg can review and personalize before sending. | |
| """</span> | |
| <span class="hljs-comment"># Call the LLM</span> | |
| messages = [HumanMessage(content=prompt)] | |
| response = model.invoke(messages) | |
| <span class="hljs-comment"># Update messages for tracking</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-comment"># Return state updates</span> | |
| <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">"""Alfred notifies Mr. Hugg about the email and presents the draft response"""</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"Sir, you've received an email from <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"Subject: <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"Category: <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">"\nI've prepared a draft response for your review:"</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-comment"># We're done processing this email</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>Step 3: Define Our Routing Logic</span></h2> <p data-svelte-h="svelte-1n2afnf">We need a function to determine which path to take after classification:</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">"""Determine the next step based on spam classification"""</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-k4q46u"><p>💡 <strong>Note:</strong> This routing function is called by LangGraph to determine which edge to follow after the classification node. The return value must match one of the keys in our conditional edges mapping.</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>Step 4: Create the StateGraph and Define Edges</span></h2> <p data-svelte-h="svelte-1c3pli7">Now we connect everything together:</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"># Create the graph</span> | |
| email_graph = StateGraph(EmailState) | |
| <span class="hljs-comment"># Add nodes</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"># Start the edges</span> | |
| email_graph.add_edge(START, <span class="hljs-string">"read_email"</span>) | |
| <span class="hljs-comment"># Add edges - defining the flow</span> | |
| email_graph.add_edge(<span class="hljs-string">"read_email"</span>, <span class="hljs-string">"classify_email"</span>) | |
| <span class="hljs-comment"># Add conditional branching from classify_email</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"># Add the final edges</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"># Compile the graph</span> | |
| compiled_graph = email_graph.<span class="hljs-built_in">compile</span>()<!-- HTML_TAG_END --></pre></div> <p data-svelte-h="svelte-18k1jtw">Notice how we use the special <code>END</code> node provided by LangGraph. This indicates terminal states where the workflow completes.</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>Step 5: Run the Application</span></h2> <p data-svelte-h="svelte-1kryxnq">Let’s test our graph with a legitimate email and a spam email:</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"># Example legitimate email</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">"Question about your services"</span>, | |
| <span class="hljs-string">"body"</span>: <span class="hljs-string">"Dear Mr. Hugg, I was referred to you by a colleague and I'm interested in learning more about your consulting services. Could we schedule a call next week? Best regards, John Smith"</span> | |
| } | |
| <span class="hljs-comment"># Example spam email</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">"YOU HAVE WON $5,000,000!!!"</span>, | |
| <span class="hljs-string">"body"</span>: <span class="hljs-string">"CONGRATULATIONS! You have been selected as the winner of our international lottery! To claim your $5,000,000 prize, please send us your bank details and a processing fee of $100."</span> | |
| } | |
| <span class="hljs-comment"># Process the legitimate email</span> | |
| <span class="hljs-built_in">print</span>(<span class="hljs-string">"\nProcessing legitimate email..."</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"># Process the spam email</span> | |
| <span class="hljs-built_in">print</span>(<span class="hljs-string">"\nProcessing spam email..."</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>Step 6: Inspecting Our Mail Sorting Agent with Langfuse 📡</span></h2> <p data-svelte-h="svelte-xyrseq">As Alfred fine-tunes the Mail Sorting Agent, he’s growing weary of debugging its runs. Agents, by nature, are unpredictable and difficult to inspect. But since he aims to build the ultimate Spam Detection Agent and deploy it in production, he needs robust traceability for future monitoring and analysis.</p> <p data-svelte-h="svelte-i70kr9">To do this, Alfred can use an observability tool such as <a href="https://langfuse.com/" rel="nofollow">Langfuse</a> to trace and monitor the agent.</p> <p data-svelte-h="svelte-fhqopd">First, we pip install 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-1ketwty">Second, we pip install Langchain (LangChain is required because we use 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-1i2d6na">Next, we add the Langfuse API keys and host address as environment variables. You can get your Langfuse credentials by signing up for <a href="https://cloud.langfuse.com" rel="nofollow">Langfuse Cloud</a> or <a href="https://langfuse.com/self-hosting" rel="nofollow">self-host Langfuse</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"># Get keys for your project from the project settings page: 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 region</span> | |
| <span class="hljs-comment"># os.environ["LANGFUSE_HOST"] = "https://us.cloud.langfuse.com" # 🇺🇸 US region</span><!-- HTML_TAG_END --></pre></div> <p data-svelte-h="svelte-1bzsazf">Then, we configure the <a href="https://langfuse.com/docs/integrations/langchain/tracing#add-langfuse-to-your-langchain-application" rel="nofollow">Langfuse <code>callback_handler</code></a> and instrument the agent by adding the <code>langfuse_callback</code> to the invocation of the graph: <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"># Initialize Langfuse CallbackHandler for LangGraph/Langchain (tracing)</span> | |
| langfuse_handler = CallbackHandler() | |
| <span class="hljs-comment"># Process legitimate email</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-blbazh">Alfred is now connected 🔌! The runs from LangGraph are being logged in Langfuse, giving him full visibility into the agent’s behavior. With this setup, he’s ready to revisit previous runs and refine his Mail Sorting Agent even further.</p> <p data-svelte-h="svelte-3rgcl6"><img src="https://langfuse.com/images/cookbook/huggingface-agent-course/langgraph-trace-legit.png" alt="Example trace in Langfuse"></p> <p data-svelte-h="svelte-1kmw2a1"><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">Public link to the trace with the legit email</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>Visualizing Our Graph</span></h2> <p data-svelte-h="svelte-1wkuxx2">LangGraph allows us to visualize our workflow to better understand and debug its structure:</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-hhguub">This produces a visual representation showing how our nodes are connected and the conditional paths that can be taken.</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>What We’ve Built</span></h2> <p data-svelte-h="svelte-1vjenz">We’ve created a complete email processing workflow that:</p> <ol data-svelte-h="svelte-eyxtqr"><li>Takes an incoming email</li> <li>Uses an LLM to classify it as spam or legitimate</li> <li>Handles spam by discarding it</li> <li>For legitimate emails, drafts a response and notifies Mr. Hugg</li></ol> <p data-svelte-h="svelte-1evobzg">This demonstrates the power of LangGraph to orchestrate complex workflows with LLMs while maintaining a clear, structured flow.</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>Key Takeaways</span></h2> <ul data-svelte-h="svelte-w0bhrr"><li><strong>State Management</strong>: We defined comprehensive state to track all aspects of email processing</li> <li><strong>Node Implementation</strong>: We created functional nodes that interact with an LLM</li> <li><strong>Conditional Routing</strong>: We implemented branching logic based on email classification</li> <li><strong>Terminal States</strong>: We used the END node to mark completion points in our workflow</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>What’s Next?</span></h2> <p data-svelte-h="svelte-1kcwc84">In the next section, we’ll explore more advanced features of LangGraph, including handling human interaction in the workflow and implementing more complex branching logic based on multiple conditions.</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/en/unit2/langgraph/first_graph.mdx" target="_blank"><span data-svelte-h="svelte-1kd6by1"><</span> <span data-svelte-h="svelte-x0xyl0">></span> <span data-svelte-h="svelte-1dajgef"><span class="underline ml-1.5">Update</span> on GitHub</span></a> <p></p> | |
| <script> | |
| { | |
| __sveltekit_17hovx6 = { | |
| assets: "/docs/agents-course/pr_545/en", | |
| base: "/docs/agents-course/pr_545/en", | |
| env: {} | |
| }; | |
| const element = document.currentScript.parentElement; | |
| const data = [null,null]; | |
| Promise.all([ | |
| import("/docs/agents-course/pr_545/en/_app/immutable/entry/start.1596c81c.js"), | |
| import("/docs/agents-course/pr_545/en/_app/immutable/entry/app.856a784e.js") | |
| ]).then(([kit, app]) => { | |
| kit.start(app, element, { | |
| node_ids: [0, 40], | |
| data, | |
| form: null, | |
| error: null | |
| }); | |
| }); | |
| } | |
| </script> | |
Xet Storage Details
- Size:
- 61.8 kB
- Xet hash:
- 3f5e59ba2345808b6406fc900d54fcc56fa12532e3dd6bff2d6737b7f9054ade
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.