<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Manoj Aggarwal - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Manoj Aggarwal - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 21 May 2026 10:20:33 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/manojag/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Protect Sensitive Data by Running LLMs Locally with Ollama ]]>
                </title>
                <description>
                    <![CDATA[ Whenever engineers are building AI-powered applications, use of sensitive data is always a top priority. You don't want to send users' data to an external API that you don't control. For me, this happ ]]>
                </description>
                <link>https://www.freecodecamp.org/news/protect-sensitive-data-with-local-llms/</link>
                <guid isPermaLink="false">69a99b623728a9dc358a5d85</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ollama ]]>
                    </category>
                
                    <category>
                        <![CDATA[ langchain ]]>
                    </category>
                
                    <category>
                        <![CDATA[ langgraph ]]>
                    </category>
                
                    <category>
                        <![CDATA[ LLM&#39;s  ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manoj Aggarwal ]]>
                </dc:creator>
                <pubDate>Thu, 05 Mar 2026 15:04:02 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5fc16e412cae9c5b190b6cdd/92c9b0b4-5ff8-40ab-b5f5-a060765e99b4.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Whenever engineers are building AI-powered applications, use of sensitive data is always a top priority. You don't want to send users' data to an external API that you don't control.</p>
<p>For me, this happened when I was building <a href="https://github.com/manojag115/FinanceGPT">FinanceGPT</a>, which is my personal open-source project that helps me with my finances. This application lets you upload your bank statements, tax forms like 1099s, and so on, and then you can ask questions in plain English like, "How much did I spend on groceries this month?" or "What was my effective tax rate last year?"</p>
<p>The problem is that answering these questions means sending all the sensitive transaction history, W-2s and income data to OpenAI or Anthropic or Google, which I was not comfortable with. Even after redacting PII data from these documents, I was not ok with the trade-off.</p>
<p>This is where Ollama comes in. Ollama lets you run large language models entirely on your own laptop. You don't need any API keys or cloud infrastructure and no data leaves your machine.</p>
<p>In this tutorial, I will walk you through what Ollama is, how to get started with it, and how to use it in a real Python application so that users of the application can choose to keep their data completely local.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#what-is-ollama">What is Ollama</a></p>
</li>
<li><p><a href="#how-ollamas-api-works">How Ollama's API works</a></p>
</li>
<li><p><a href="#how-to-call-ollama-from-python">How to call Ollama from Python</a></p>
</li>
<li><p><a href="#how-to-integrate-ollama-into-a-langchain-app">How to Integrate Ollama into a LangChain App</a></p>
</li>
<li><p><a href="#how-to-build-an-llm-provider-agnostic-app">How to Build an LLM-Provider Agnostic App</a></p>
</li>
<li><p><a href="#how-to-use-ollama-with-langgraph">How to use Ollama with LangGraph</a></p>
</li>
<li><p><a href="#how-financegpt-uses-this-in-practice">How FinanceGPT Uses This in Practice</a></p>
</li>
<li><p><a href="#tradeoffs-to-be-aware-of">Tradeoffs to be Aware Of</a></p>
</li>
<li><p><a href="#conclusion">Conclusion</a></p>
</li>
<li><p><a href="#check-out-financegpt">Check Out FinanceGPT</a></p>
</li>
<li><p><a href="#resources">Resources</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You will need the following at a minimum:</p>
<ul>
<li><p>Python 3.10+</p>
</li>
<li><p>A machine with at least 8GB of RAM (16GB recommended for larger models)</p>
</li>
<li><p>Basic familiarity with Python and pip</p>
</li>
</ul>
<h2 id="heading-what-is-ollama">What is Ollama?</h2>
<p>Ollama is an open-source tool that makes running LLMs locally very easy. You can think of it as Docker but for AI models. You can pull models using just one command and Ollama handles everything else like downloading the weights, managing memory and the serving the model through a local REST API.</p>
<p>The local REST API is compatible with OpenAI's API format which means any application that can talk to OpenAI, can switch to using Ollama without changing any code.</p>
<h3 id="heading-installation">Installation</h3>
<p>First thing you would need is to download the installer from <a href="https://ollama.com/">ollama.com</a>. Once installed, you can verify it is running:</p>
<pre><code class="language-shell">ollama --version
</code></pre>
<p>The above command checks whether Ollama was installed correctly and prints the current version.</p>
<h3 id="heading-pull-and-run-your-first-model">Pull and Run Your First Model</h3>
<p>Ollama hosts a variety of models on <a href="https://ollama.com/library">ollama.com/library</a>. To pull and immediately chat with one, just do:</p>
<pre><code class="language-shell">ollama run llama3.2
</code></pre>
<p>This command will download the model from ollama and start an interactive chat session with it. Note: the model size would be a few GBs depending on which model is downloaded. Alternatively, if you want to download a specific model only:</p>
<pre><code class="language-shell">ollama pull mistral
</code></pre>
<p>This downloads a model to your machine without starting a chat session which is useful when you want to set up models in advance.</p>
<p>You can run the following command to list the models you have installed:</p>
<pre><code class="language-shell">ollama list
</code></pre>
<p>This shows all models you've downloaded locally along with their sizes.</p>
<p>I have used the following models and they have worked great for specific tasks:</p>
<table>
<thead>
<tr>
<th>Model</th>
<th>Size</th>
<th>Good For</th>
</tr>
</thead>
<tbody><tr>
<td><code>llama3.2</code></td>
<td>~2GB</td>
<td>Fast, general purpose</td>
</tr>
<tr>
<td><code>mistral</code></td>
<td>~4GB</td>
<td>Strong instruction following</td>
</tr>
<tr>
<td><code>qwen2.5:7b</code></td>
<td>~4GB</td>
<td>Multilingual, reasoning</td>
</tr>
<tr>
<td><code>deepseek-r1:7b</code></td>
<td>~4GB</td>
<td>Complex reasoning tasks</td>
</tr>
</tbody></table>
<h2 id="heading-how-ollamas-api-works">How Ollama's API works</h2>
<p>Once Ollama is running, it will be served on localhost:11434. You can call it directly using curl:</p>
<pre><code class="language-shell">curl http://localhost:11434/api/chat -d '{
  "model": "llama3.2",
  "messages": [{ "role": "user", "content": "What is compound interest?" }],
  "stream": false
}'
</code></pre>
<p>This sends a chat message directly to Ollama's REST API from the command line, with streaming disabled so you get the full response at once. The above endpoint is to simply chat with the model. The more useful endpoint is <code>http://localhost:11434/v1</code> as this is OpenAI-compatible. This is the key feature that makes it easy to drop into existing apps that use OpenAI or other LLMs.</p>
<h2 id="heading-how-to-call-ollama-from-python">How to Call Ollama from Python</h2>
<h3 id="heading-how-to-use-the-ollama-python-library">How to Use the Ollama Python Library</h3>
<p>Ollama has its own Python library that is pretty intuitive to use:</p>
<pre><code class="language-shell">pip install ollama
</code></pre>
<pre><code class="language-python">from ollama import chat

response = chat(
    model='llama3.2',
    messages=[
        {'role': 'user', 'content': 'Explain what a Roth IRA is in simple terms.'}
    ]
)

print(response.message.content)
</code></pre>
<p>The above code uses Ollama's native Python SDK to send a message and print the model's reply, which is the most straightforward way to call Ollama from Python</p>
<h3 id="heading-how-to-use-the-openai-sdk-with-ollama-as-the-backend">How to Use the OpenAI SDK with Ollama as the Backend</h3>
<p>As mentioned earlier, Ollama has an endpoint that is OpenAI compatible, so you can also use the OpenAI Python SDK and just point it to your local server:</p>
<pre><code class="language-shell">pip install openai
</code></pre>
<pre><code class="language-python">from openai import OpenAI

client = OpenAI(
    base_url='http://localhost:11434/v1',
    api_key='ollama',  # Required by the SDK, but ignored by Ollama
)

response = client.chat.completions.create(
    model='llama3.2',
    messages=[
        {'role': 'user', 'content': 'Explain what a Roth IRA is in simple terms.'}
    ]
)

print(response.choices[0].message.content)
</code></pre>
<p>This uses the standard OpenAI Python SDK but redirects it to your local Ollama server. The <code>api_key</code> field is required by the SDK but ignored by Ollama. This pattern makes using Ollama seamless for existing applications. The code is nearly identical to what you would write for OpenAI.</p>
<h2 id="heading-how-to-integrate-ollama-into-a-langchain-app">How to Integrate Ollama into a LangChain App</h2>
<p>Most production applications are built with an orchestration framework like LangChain, which has a native Ollama support. This means swapping providers is just a one-line change.</p>
<p>Install the integration:</p>
<pre><code class="language-shell">pip install langchain-ollama
</code></pre>
<h3 id="heading-how-to-create-a-chat-model">How to Create a Chat Model</h3>
<pre><code class="language-python">from langchain_ollama import ChatOllama

llm = ChatOllama(model="llama3.2")

response = llm.invoke("What is the difference between a W-2 and a 1099?")
print(response.content)
</code></pre>
<p>This creates a LangChain-compatible chat model backed by a local Ollama model, a one-line swap from <code>ChatOpenAI</code>.</p>
<p>Compare this to the OpenAI version and you will see that the interface is almost identical:</p>
<pre><code class="language-python">from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o")
</code></pre>
<h2 id="heading-how-to-build-an-llm-provider-agnostic-app">How to Build an LLM-Provider Agnostic App</h2>
<p>The real power of the application comes from the abstraction of LLM providers. Applications like Perplexity lets users choose the LLM they want to use for their tasks. Here's a simple factory pattern that returns the right LLM based on the configuration:</p>
<pre><code class="language-python">from langchain_openai import ChatOpenAI
from langchain_ollama import ChatOllama
from langchain_anthropic import ChatAnthropic

def get_llm(provider: str, model: str):
    """
    Return the appropriate LangChain LLM based on the provider.
    
    Args:
        provider: One of "openai", "ollama", "anthropic"
        model: The model name (e.g. "gpt-4o", "llama3.2", "claude-3-5-sonnet")
    
    Returns:
        A LangChain chat model ready to use
    """
    if provider == "openai":
        return ChatOpenAI(model=model)
    elif provider == "ollama":
        return ChatOllama(model=model)
    elif provider == "anthropic":
        return ChatAnthropic(model=model)
    else:
        raise ValueError(f"Unknown provider: {provider}")
</code></pre>
<p>The above snippet shows a helper that returns the right LangChain model based on a provider string, so the rest of your app never needs to know which LLM is running underneath.</p>
<p>Now the rest of your code does not need to know about the provider who's LLM is running underneath. This includes your chains, your agents and your tools. You pass <code>llm</code> around and it just works.</p>
<h2 id="heading-how-to-use-ollama-with-langgraph">How to use Ollama with LangGraph</h2>
<p>If you're using LangGraph to build agents (as I covered in my <a href="https://www.freecodecamp.org/news/how-to-develop-ai-agents-using-langgraph-a-practical-guide/">previous article on AI agents</a>), plugging in Ollama is equally seamless:</p>
<pre><code class="language-python">from langgraph.prebuilt import create_react_agent
from langchain_ollama import ChatOllama
from langchain_core.tools import tool

@tool
def get_spending_summary(category: str) -&gt; str:
    """Get total spending for a given category this month."""
    # In a real app, this would query your database
    return f"You spent $342.50 on {category} this month."

llm = ChatOllama(model="llama3.2")

agent = create_react_agent(
    model=llm,
    tools=[get_spending_summary]
)

response = agent.invoke({
    "messages": [{"role": "user", "content": "How much did I spend on groceries?"}]
})

print(response["messages"][-1].content)
</code></pre>
<p>This snippet builds a ReAct agent that uses a locally-running model to decide when to call tools while keeping all data on-device even during agentic workflows.</p>
<p>The agent will decide to call the <code>get_spending_summary</code> tool when needed and get the result using the locally running model instead of sending your data over the internet to OpenAI.</p>
<h2 id="heading-how-financegpt-uses-this-in-practice">How FinanceGPT Uses This in Practice</h2>
<p>FinanceGPT is built to support OpenAI, Anthropic, Google and Ollama as LLM providers. The user sets their preference on the UI or in a config file and the application instantiates the right model using a pattern very similar to the factory pattern above.</p>
<p>When the user chooses Ollama, here's what happens:</p>
<ol>
<li><p>Their bank statements and other sensitive documents are parsed locally</p>
</li>
<li><p>Sensitive fields like SSNs are masked before any LLM call</p>
</li>
<li><p>The masked data and query goes to the local Ollama server running on their own machine</p>
</li>
<li><p>The response comes back locally and nothing ever leaves their network</p>
</li>
</ol>
<p>To run FinanceGPT locally with Ollama, the setup looks like this:</p>
<pre><code class="language-shell"># 1. Pull a capable model
ollama pull llama3.2

# 2. Clone and configure FinanceGPT
git clone https://github.com/manojag115/FinanceGPT.git
cd FinanceGPT
cp .env.example .env

# 3. In .env, set your LLM provider to Ollama
# LLM_PROVIDER=ollama
# LLM_MODEL=llama3.2

# 4. Start the full stack
docker compose -f docker-compose.quickstart.yml up -d
</code></pre>
<p>With this setup, the entire application including the frontend, backend and LLM, runs on your own hardware.</p>
<h2 id="heading-tradeoffs-to-be-aware-of">Tradeoffs to be Aware Of</h2>
<p>Ollama is a great local alternative to using cloud LLMs, but it comes with its own problems.</p>
<h3 id="heading-response-quality">Response Quality</h3>
<p>Ollama models are essentially 7B parameter models running locally, so by design they will not match GPT-4o on complex reasoning tasks. For simple Q&amp;A and summarization tasks, the results would be comparable, but for multi-step reasoning or nuanced judgement calls, the gap is noticeable.</p>
<h3 id="heading-speed">Speed</h3>
<p>Inference speed depends on the hardware that is running the model. Without a GPU, the Ollama models can take several seconds to respond. On Apple Silicon (M1/M2/M3), the performance is surprisingly good even without a dedicated GPU.</p>
<h3 id="heading-hardware-requirements">Hardware Requirements</h3>
<p>Small models (7B parameters) need around 8GB of RAM, however larger models (13B+) need 16GB or more. If you are building your application for end users, you cannot guarantee they have the hardware.</p>
<h3 id="heading-tool-use-and-function-calling">Tool Use and Function Calling</h3>
<p>Not all local models support function calling reliably. If your agent depends heavily on tool use, test your chosen model carefully. Models like <code>qwen2.5</code> and <code>mistral</code> generally handle this better than others.</p>
<p>The right mental model: use cloud models when you need maximum capability, and local models when privacy or cost constraints make cloud models impractical.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, you learned what Ollama is, how to install it and pull models, and three different ways to call it from Python: the native Ollama library, the OpenAI-compatible SDK, and LangChain. You also saw how to build a provider-agnostic factory pattern so your app can switch between cloud and local models with a single config change.</p>
<p>Ollama makes local LLMs genuinely practical for production apps. The OpenAI-compatible API means integration is nearly zero-friction, and LangChain's native support means you can build provider-agnostic apps from the start.</p>
<p>The finance domain is an obvious fit — but the same principle applies anywhere sensitive data is involved: healthcare, legal tech, HR, personal productivity. If your app processes data that users wouldn't want stored on someone else's server, giving them a local option isn't just a nice-to-have. It's a trust feature.</p>
<h2 id="heading-check-out-financegpt"><strong>Check Out FinanceGPT</strong></h2>
<p>All the code examples here came from <a href="https://github.com/manojag115/FinanceGPT">FinanceGPT</a>. If you want to see these patterns in a complete app, poke around the repo. It's got document processing, portfolio tracking, tax optimization – all built with LangGraph.</p>
<p>If you find this helpful, <a href="https://github.com/manojag115/FinanceGPT">give the project a star on GitHub</a> – it helps other developers discover it.</p>
<h2 id="heading-resources">Resources</h2>
<ul>
<li><p><a href="https://ollama.com/docs">Ollama Documentation</a></p>
</li>
<li><p><a href="https://ollama.com/library">Ollama Model Library</a></p>
</li>
<li><p><a href="https://python.langchain.com/docs/integrations/chat/ollama/">LangChain Ollama Integration</a></p>
</li>
<li><p><a href="https://www.freecodecamp.org/news/how-to-develop-ai-agents-using-langgraph-a-practical-guide/">How to Build AI Agents with LangGraph (my previous article)</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Develop AI Agents Using LangGraph: A Practical Guide ]]>
                </title>
                <description>
                    <![CDATA[ AI agents are all the rage these days. They’re like traditional chatbots, but they have the ability to utilize a plethora of tools in the background. They can also decide which tool to use and when to ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-develop-ai-agents-using-langgraph-a-practical-guide/</link>
                <guid isPermaLink="false">69965d1013f3e8d4dfe2a929</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ langgraph ]]>
                    </category>
                
                    <category>
                        <![CDATA[ agentic AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI Agent Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Artificial Intelligence ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manoj Aggarwal ]]>
                </dc:creator>
                <pubDate>Thu, 19 Feb 2026 00:45:04 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1771461883355/00e4ae2d-048d-461c-93f9-184a67280770.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>AI agents are all the rage these days. They’re like traditional chatbots, but they have the ability to utilize a plethora of tools in the background. They can also decide which tool to use and when to use it to answer your questions.</p>
<p>In this tutorial, I’ll show you how to build this type of agent using <code>LangGraph</code>. We’ll dig into real code from my personal project <a href="https://github.com/manojag115/FinanceGPT">FinanceGPT</a>, an open-source financial assistant I created to help me with my finances.</p>
<p>You’ll walk away understanding how AI agents actually work under the hood, and you’ll be able to build your own agent for whatever domain you are working on.</p>
<h2 id="heading-what-ill-cover">What I’ll Cover:</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-what-are-ai-agents">What Are AI Agents?</a></p>
</li>
<li><p><a href="#heading-what-is-langgraph">What is LangGraph?</a></p>
</li>
<li><p><a href="#heading-core-concept-1-tools">Core Concept 1: Tools</a></p>
</li>
<li><p><a href="#heading-core-concept-2-agent-state">Core Concept 2: Agent State</a></p>
</li>
<li><p><a href="#heading-core-concept-3-the-agent-graph">Core Concept 3: The Agent Graph</a></p>
</li>
<li><p><a href="#heading-how-to-put-it-all-together">How to Put it All Together</a></p>
</li>
<li><p><a href="#heading-how-the-agent-thinks">How the Agent Thinks</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a href="#heading-resources-worth-checking-out">Resources Worth Checking Out</a></p>
</li>
<li><p><a href="#heading-check-out-financegpt">Check Out FinanceGPT</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before diving in, you should be comfortable with the following:</p>
<p><strong>Python knowledge</strong>: You should know how to write Python functions, work with async/await syntax, and understand decorators. The code examples use all three extensively.</p>
<p><strong>Basic LLM/chatbot familiarity</strong>: You don't need to be an expert, but knowing what a large language model is and having some experience calling one (via OpenAI's API or similar) will help you follow along.</p>
<p><strong>LangChain basics</strong>: We'll be using LangGraph, which is built on top of LangChain. If you've never used LangChain before, it's worth skimming their <a href="https://python.langchain.com/docs/get_started/quickstart">quickstart guide first.</a></p>
<p>You'll also need the following tools installed:</p>
<ul>
<li><p>Python 3.10+</p>
</li>
<li><p><a href="https://python.langchain.com/docs/get_started/quickstart">An OpenAI API ke</a>y (the examples use <code>gpt-4-turbo-preview</code>)</p>
</li>
<li><p>The following packages, installable via pip:</p>
</li>
</ul>
<pre><code class="language-python">  pip install langchain langgraph langchain-openai sqlalchemy
</code></pre>
<p>If you're planning to follow along with the full FinanceGPT project rather than just the code snippets, you'll also want a PostgreSQL database set up, but that's optional for understanding the core concepts covered here.</p>
<h2 id="heading-what-are-ai-agents">What Are AI Agents?</h2>
<p>Think of AI agents as traditional chatbots that can answer user questions. But they specialize in figuring out what tools they need and can chain multiple actions together to get an answer.</p>
<p>Here’s an example conversation with my FinanceGPT AI agent:</p>
<pre><code class="language-plaintext">User: "How much did I spend on groceries this month?"

Agent: [Thinks: I need transaction data filtered by category]

Agent: [Calls search_transactions(category="Groceries")]

Agent: [Gets back: $1,245.67 across 23 transactions]

Agent: "You spent $1,245.67 on groceries this month."
</code></pre>
<p>The agent broke down the problem, picked the right tool to use, and generated the answer. This matters a lot when you’re working with messy real world problems where:</p>
<ul>
<li><p>Questions don’t fit into specific categories</p>
</li>
<li><p>You need to pull data from multiple sources</p>
</li>
<li><p>Users want to ask followup questions</p>
</li>
</ul>
<h2 id="heading-what-is-langgraph">What is LangGraph?</h2>
<p><code>LangGraph</code> is an open sourced extension of <code>LangChain</code> that’s useful for creating stateful AI agents by modeling workflows as nodes and edges in a graph. You can think of your agent’s logic as a flowchart where:</p>
<ul>
<li><p><strong>Nodes</strong> are the actions (for example “ask the LLM” or “run this tool”)</p>
</li>
<li><p><strong>Edges</strong> are the arrows (what happens next)</p>
</li>
<li><p><strong>State</strong> is the information passed around</p>
</li>
</ul>
<p>LangGraph is especially good at providing the following benefits:</p>
<ol>
<li><p><strong>Flow control</strong>: You define exactly what happens when.</p>
</li>
<li><p><strong>Stateful</strong>: The framework preserves conversation history for you.</p>
</li>
<li><p><strong>Easy to use</strong>: Just adding a decorator to an existing Python function makes it a tool.</p>
</li>
<li><p><strong>Production-ready</strong>: It has built-in error handling and retries.</p>
</li>
</ol>
<h2 id="heading-core-concept-1-tools">Core Concept 1: Tools</h2>
<p>Think of tools as just Python functions your AI agent can call. The LLM utilizes the function name, docstring, parameters, and return value to know what the functions are doing and when to use them.</p>
<p><code>LangChain</code> has a <code>@tool</code> decorator that can convert any function into a tool, for example:</p>
<pre><code class="language-python">from langchain_core.tools import tool

@tool
def get_current_weather(location: str) -&gt; str:
    """Get the current weather for a location.
    
    Use this when the user asks about weather conditions.
    
    Args:
        location: City name (e.g., "San Francisco", "New York")
    
    Returns:
        Weather description string
    """
    # In real life, you'd call a weather API here
    return f"The weather in {location} is sunny, 72°F"
</code></pre>
<p>Notice that the docstring is self-explanatory, as that’s how the LLM decides whether this tool is the right choice or not.</p>
<p>Here is a real example from FinanceGPT. This is a tool that searches through financial transactions:</p>
<pre><code class="language-python">from langchain_core.tools import tool
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select

def create_search_transactions_tool(search_space_id: int, db_session: AsyncSession):
    """
    Factory function that creates a search tool with database access.
    
    This pattern lets you inject dependencies (database, user context)
    while keeping the tool signature clean for the LLM.
    """
    
    @tool
    async def search_transactions(
        keywords: str | None = None,
        category: str | None = None
    ) -&gt; dict:
        """Search financial transactions by merchant or category.
        
        Use when users ask about:
        - Spending at specific merchants ("How much at Starbucks?")
        - Spending in categories ("How much on groceries?")
        - Both combined ("Show me restaurant spending at McDonald's")
        
        Args:
            keywords: Merchant name to search for
            category: Spending category (e.g., "Groceries", "Gas")
        
        Returns:
            Dictionary with transactions, total amount, and count
        """
        # Query the database
        query = select(Document.document_metadata).where(
            Document.search_space_id == search_space_id
        )
        result = await db_session.execute(query)
        documents = result.all()
        
        # Filter transactions based on criteria
        all_transactions = []
        for (doc_metadata,) in documents:
            transactions = doc_metadata.get("financial_data", {}).get("transactions", [])
            
            for txn in transactions:
                # Apply filters
                if category and category.lower() not in str(txn.get("category", "")).lower():
                    continue
                if keywords and keywords.lower() not in txn.get("description", "").lower():
                    continue
                
                # Include matching transaction
                all_transactions.append({
                    "date": txn.get("date"),
                    "description": txn.get("description"),
                    "amount": float(txn.get("amount", 0)),
                    "category": txn.get("category"),
                })
        
        # Calculate total and return
        total = sum(abs(t["amount"]) for t in all_transactions if t["amount"] &lt; 0)
        
        return {
            "transactions": all_transactions[:20],  # Limit results
            "total_amount": total,
            "count": len(all_transactions),
            "summary": f"Found {len(all_transactions)} transactions totaling ${total:,.2f}"
        }
    
    return search_transactions
</code></pre>
<p>Let’s dive into what this code is doing.</p>
<p><strong>The factory function pattern</strong>: The tool only takes parameters the LLM can provide (a keyword and category), but it also needs a database session and <code>search_space_id</code> to know whose data to query. The factory function solves this by capturing those dependencies in a closure, so the LLM sees a clean interface while the database wiring stays hidden.</p>
<p><strong>The filtering logic</strong>: We loop through all transactions and apply the optional filters. If <code>category</code> is provided, it must appear in the transaction's category field. If <code>keywords</code> is provided, it must appear in the merchant description. Both can be used together, letting the LLM handle questions like "How much did I spend at McDonald's in the Restaurants category?"</p>
<p><strong>The return value</strong>: Instead of a raw list, the tool returns a structured dict with a capped result set, a pre-calculated total, and a plain-English summary string. The summary means the LLM can read <code>"Found 23 transactions totaling $1,245.67"</code> and immediately know what to say, rather than parsing the raw data itself.</p>
<h3 id="heading-key-tool-design-principles">Key Tool Design Principles</h3>
<p>These are the principles that differentiate a good tool from a great tool:</p>
<ol>
<li><p><strong>Docstrings:</strong> Instead of vague descriptions, you need to be thorough with the explanation of the tool in the docstring. The more examples you give, the better the LLM gets at picking the right tool.</p>
</li>
<li><p><strong>Clean signature:</strong> The tool should only take the parameters that the LLM has access to and can provide. If the tool needs user ids, or database connections (and so on), you can hide those in factory functions using closures.</p>
</li>
<li><p><strong>Return both data and summaries:</strong> Instead of just the raw data, if you include a summary field, the agent can just use that to understand the output better. Here’s an example:</p>
<pre><code class="language-json">{
    "transactions": [...],           # For detailed analysis
    "total_amount": 1245.67,         # Pre-calculated
    "summary": "Found 23 transactions..."  # Ready to send to user
}
</code></pre>
</li>
<li><p><strong>Limited context window:</strong> Capping results to a finite amount like 20-50 items depending on the use case will make sure your LLM doesn’t choke or hit context limits.</p>
</li>
</ol>
<h2 id="heading-core-concept-2-agent-state">Core Concept 2: Agent State</h2>
<p>Your agent carries around information as it works. This is called the agent’s state. For a chatbot, it’s usually the conversation history.</p>
<p>In <code>LangGraph</code>, state is defined with a <code>TypeDict</code>:</p>
<pre><code class="language-python">from typing import Annotated, Sequence, TypedDict
from langchain_core.messages import BaseMessage

class AgentState(TypedDict):
    """
    This is what flows through your agent.
    
    Messages is a list that keeps growing:
    - User questions
    - Agent responses
    - Tool results
    """
    messages: Annotated[Sequence[BaseMessage], "The conversation history"]
</code></pre>
<p>For complex agents, you can track more than just messages, like:</p>
<pre><code class="language-python">class FancierState(TypedDict):
    messages: Sequence[BaseMessage]
    user_id: str
    retry_count: int
    last_tool_used: str | None
</code></pre>
<p>This matters more than it might look. Each field here has a real purpose in a sophisticated production-grade agent. <code>user_id</code> tells every node whose data to fetch without you having to pass it around manually. <code>retry_count</code> helps agent detect when its stuck in a loop so it can bail out gracefully. <code>last_tool_used</code> helps the agent avoid redundant calls.</p>
<p>As the agent grows in complexity, state becomes the single source of truth that keeps every node coordinated.</p>
<h3 id="heading-why-state-matters">Why State Matters</h3>
<p>State is what separates an agent which is conversational from an API call that is stateless. Without it, every message would be processed in isolation and the agent would have no recollection of what was asked earlier, what tools it already used, and what data it retrieved already.</p>
<p>With state, the full conversation history is passed through each step of the agent’s execution.</p>
<p>Here's what that looks like in practice for our grocery spending example:</p>
<pre><code class="language-plaintext">When the conversation starts:
{
    "messages": []
}

User asks something:
{
    "messages": [
        HumanMessage("How much did I spend on groceries?")
    ]
}

Agent decides to use a tool:
{
    "messages": [
        HumanMessage("How much did I spend on groceries?"),
        AIMessage(tool_calls=[{name: "search_transactions", ...}]),
        ToolMessage({"total_amount": 1245.67, ...}),
    ]
}

Agent responds with the answer:
{
    "messages": [
        HumanMessage("How much did I spend on groceries?"),
        AIMessage(tool_calls=[...]),
        ToolMessage({...}),
        AIMessage("You spent $1,245.67 on groceries this month.")
    ]
}
</code></pre>
<p>Notice that the state is always growing with every tool call and every result. This means that when user has a followup like “How does that compare to last month?”, the agent can just look back and know what “that” refers to.</p>
<h2 id="heading-core-concept-3-the-agent-graph">Core Concept 3: The Agent Graph</h2>
<p>The graph is the backbone of your agent. Think of it as a collection of tools and an LLM, combined together to reason, act and respond in a structured way. Specifically, it determines the order of operations – that is, what runs first, what happens next, and what conditions determine which path to take.</p>
<p>Without a graph, you would have to manually orchestrate the workflow: calling the LLM, then checking whether it wants to use a tool, executing the tool, and then feeding the result back to it and deciding when to stop. The graph encodes this logic explicitly so that your agent figures out the right sequence.</p>
<p>Each node in the graph is an action like “ask the LLM” or “run a tool” and each edge is a connection between those actions.</p>
<p>With that in mind, let's build one step by step.</p>
<h3 id="heading-step-1-create-the-agent-node">Step 1: Create the Agent Node</h3>
<p>The agent node is where the LLM makes a decision like “Should I use a tool?” or “Which tool to use?”. Let’s take an example:</p>
<pre><code class="language-python">from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

# Create the LLM with tools
llm = ChatOpenAI(model="gpt-4-turbo-preview", temperature=0)

# Create your tools
tools = [
    create_search_transactions_tool(search_space_id, db_session),
    # ... other tools
]

# Bind tools to the LLM so it knows what's available
llm_with_tools = llm.bind_tools(tools)

# Create the system prompt
system_prompt = """You are a helpful AI financial assistant.

Your capabilities:
- Search transactions by merchant, category, or date
- Analyze portfolio performance
- Find tax optimization opportunities

Guidelines:
- Be concise and cite specific data
- Format currency as $X,XXX.XX
- Remind users to consult professionals for tax/investment advice"""

prompt = ChatPromptTemplate.from_messages([
    ("system", system_prompt),
    MessagesPlaceholder(variable_name="messages"),
])

# Define the agent node function
async def call_agent(state: AgentState):
    """
    The agent node calls the LLM to decide the next action.
    
    The LLM can:
    1. Call one or more tools
    2. Generate a text response
    3. Both
    """
    messages = state["messages"]
    
    # Format messages with system prompt
    formatted = prompt.format_messages(messages=messages)
    
    # Call the LLM
    response = await llm_with_tools.ainvoke(formatted)
    
    # Return state update (add the LLM's response)
    return {"messages": [response]}
</code></pre>
<p>Let’s walk through what's happening here.</p>
<p>First, we initialize the LLM with <code>temperature=0</code>, which makes the model deterministic and consistent. This is important for an agent that needs to make reliable decisions rather than creative ones.</p>
<p>Next, we call <code>llm.bind_tools(tools)</code>. It tells the LLM what tools are available by passing along their names, descriptions, and parameter schemas. Without this, the LLM would have no idea it could call any tools at all. With it, the LLM can look at a user's question and decide both whether a tool is needed and which one to use.</p>
<p>The prompt is built using <code>ChatPromptTemplate</code>, which combines a static system prompt with a <code>MessagesPlaceholder</code>. The placeholder is where the full conversation history gets inserted at runtime, meaning the LLM always has the complete context of the conversation when making its decision.</p>
<p>Last, <code>call_agent</code> is the actual node function. It pulls the current messages from state, formats them with the prompt, calls the LLM, and returns the response to be appended to state. This is the function LangGraph will call every time execution reaches the agent node.</p>
<h3 id="heading-step-2-create-the-tool-node">Step 2: Create the Tool Node</h3>
<p><code>LangGraph</code> has a pre-built <code>ToolNode</code> that executes tools:</p>
<pre><code class="language-python">from langgraph.prebuilt import ToolNode

# This node automatically executes any tools the LLM requested
tool_node = ToolNode(tools)
</code></pre>
<p>When the LLM includes tool calls in its response, <code>ToolNode</code> will:</p>
<ol>
<li><p>extract the tool calls,</p>
</li>
<li><p>execute each tool with specific params, and</p>
</li>
<li><p>add <code>ToolMessage</code> object with the result to state</p>
</li>
</ol>
<h3 id="heading-step-3-define-control-flow">Step 3: Define Control Flow</h3>
<p>This is where we need to decide when the tool should be used and when it ends.</p>
<pre><code class="language-python">from langgraph.graph import END

def should_continue(state: AgentState):
    """
    Router function that determines the next step.
    
    Returns:
        "tools" - if the LLM wants to use tools
        END - if the LLM is done (just text response)
    """
    last_message = state["messages"][-1]
    
    # Check if the LLM included tool calls
    if hasattr(last_message, "tool_calls") and last_message.tool_calls:
        return "tools"
    
    # No tool calls means we're done
    return END
</code></pre>
<p>This tiny function is the decision-maker of your entire agent. After the LLM responds, LangGraph calls <code>should_continue</code> to figure out what to do next. It works by inspecting the last message in state: the LLM's most recent response. If that response contains tool calls, it means the LLM has decided it needs more data before it can answer, so we return <code>"tools"</code> to route execution to the tool node. If there are no tool calls, the LLM has produced a final answer and we return <code>END</code> to stop execution.</p>
<p>This is the mechanism that makes the agent loop. The agent doesn't just call one tool and stop, but it can call a tool, see the result, decide it needs another tool, call that one too, and only stop when it has everything it needs to respond.</p>
<h3 id="heading-step-4-assemble-the-graph">Step 4: Assemble the Graph</h3>
<p>Now, we can connect everything:</p>
<pre><code class="language-python">from langgraph.graph import StateGraph

# Create the graph
workflow = StateGraph(AgentState)

# Add nodes
workflow.add_node("agent", call_agent)
workflow.add_node("tools", tool_node)

# Set entry point
workflow.set_entry_point("agent")

# Add conditional edge from agent
workflow.add_conditional_edges(
    "agent",           # From this node
    should_continue,   # Use this function to decide
    {
        "tools": "tools",  # If "tools" is returned, go to tools node
        END: END           # If END is returned, finish
    }
)

# After tools execute, go back to agent
workflow.add_edge("tools", "agent")

# Compile into a runnable agent
agent = workflow.compile()
</code></pre>
<p>This is where everything gets wired together. We start by creating a <code>StateGraph</code> and passing it our <code>AgentState</code> type. This tells LangGraph what shape the state will take as it flows through the graph.</p>
<p>We then register our two nodes with <code>add_node</code>. The string name we give each node ("agent" and "tools") is what we'll use to reference them when defining edges. <code>set_entry_point</code> tells LangGraph where execution should begin which in our case is the agent node.</p>
<p>The conditional edge is where the routing logic plugs in. We're telling LangGraph: "After the agent node runs, call <code>should_continue</code> to decide what happens next, then use this mapping to translate that decision into the next node." If <code>should_continue</code> returns <code>"tools"</code>, go to the tools node. If it returns <code>END</code>, stop.</p>
<p>Finally, <code>add_edge("tools", "agent")</code> creates an unconditional edge: after the tools node runs, always go back to the agent node. This is what creates the loop, letting the agent review the tool results and decide whether it's done or needs to keep going. Calling <code>workflow.compile()</code> locks everything in and returns a runnable agent.</p>
<h3 id="heading-understanding-the-flow">Understanding the Flow</h3>
<p>Here’s what happens when you run the agent:</p>
<pre><code class="language-plaintext">User Question
    ↓
[AGENT NODE]
    ↓
[SHOULD_CONTINUE]
    ↓
  Tools needed?
    ↓ YES   ↓ NO
[TOOLS]    [END]
    ↓
[AGENT NODE]
    ↓
[SHOULD_CONTINUE]
    ↓
    ...
</code></pre>
<p>The loop above allows the agent to:</p>
<ol>
<li><p>Use a tool</p>
</li>
<li><p>See the results</p>
</li>
<li><p>Decide if more tools are needed</p>
</li>
<li><p>Use more tools or generate final answer</p>
</li>
</ol>
<h2 id="heading-how-to-put-it-all-together">How to Put it All Together</h2>
<p>Let’s see the complete agent in one place:</p>
<pre><code class="language-python">from typing import Annotated, Sequence, TypedDict
from langchain_core.messages import BaseMessage, HumanMessage
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode

# 1. Define State
class AgentState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], "Conversation history"]

# 2. Create Agent Function
def create_agent(tools):
    # Set up LLM
    llm = ChatOpenAI(model="gpt-4-turbo-preview", temperature=0)
    llm_with_tools = llm.bind_tools(tools)
    
    # Create prompt
    prompt = ChatPromptTemplate.from_messages([
        ("system", "You are a helpful AI assistant."),
        MessagesPlaceholder(variable_name="messages"),
    ])
    
    # Define nodes
    async def call_agent(state: AgentState):
        formatted = prompt.format_messages(messages=state["messages"])
        response = await llm_with_tools.ainvoke(formatted)
        return {"messages": [response]}
    
    def should_continue(state: AgentState):
        last_message = state["messages"][-1]
        if hasattr(last_message, "tool_calls") and last_message.tool_calls:
            return "tools"
        return END
    
    # Build graph
    workflow = StateGraph(AgentState)
    workflow.add_node("agent", call_agent)
    workflow.add_node("tools", ToolNode(tools))
    workflow.set_entry_point("agent")
    workflow.add_conditional_edges("agent", should_continue, {"tools": "tools", END: END})
    workflow.add_edge("tools", "agent")
    
    return workflow.compile()

# 3. Use the Agent
async def main():
    # Create tools (simplified example)
    tools = [create_search_transactions_tool(user_id=1, db_session=session)]
    
    # Create agent
    agent = create_agent(tools)
    
    # Run agent
    result = await agent.ainvoke({
        "messages": [HumanMessage(content="How much did I spend on groceries?")]
    })
    
    # Get final response
    final_response = result["messages"][-1].content
    print(final_response)
</code></pre>
<h2 id="heading-how-the-agent-thinks">How the Agent Thinks</h2>
<p>Let’s use an example to see how the agent reasons.</p>
<p><strong>Example: “How much did I spend on groceries this month?”</strong></p>
<h3 id="heading-step-1-user-input">Step 1: User Input</h3>
<pre><code class="language-python">State: {
    "messages": [HumanMessage("How much did I spend on groceries this month?")]
}
</code></pre>
<h3 id="heading-step-2-agent-node">Step 2: Agent Node</h3>
<p>The LLM gets:</p>
<ul>
<li><p>A system prompt, like the one we defined above</p>
</li>
<li><p>User question: “How much did I spend on groceries this month?”</p>
</li>
<li><p>List of available tools: <code>search_transactions(keywords, category)</code></p>
</li>
</ul>
<p>The LLM reasons that this is about spending in a specific category and decides that it should use <code>search_transactions</code> with <code>category=’groceries’</code>. It responds with a tool call:</p>
<pre><code class="language-python">AIMessage(
    content="",
    tool_calls=[{
        "name": "search_transactions",
        "args": {"category": "Groceries"},
        "id": "call_123"
    }]
)
</code></pre>
<h3 id="heading-step-3-should-continue">Step 3: Should Continue</h3>
<p>The router sees tool calls and returns “tools”.</p>
<h3 id="heading-step-4-tools-node">Step 4: Tools Node</h3>
<p>It executes <code>search_transactions(category="Groceries")</code> and gets:</p>
<pre><code class="language-python">{
    "transactions": [...],
    "total_amount": 1245.67,
    "count": 23,
    "summary": "Found 23 transactions totaling $1,245.67"
}
</code></pre>
<p>And adds this to the state:</p>
<pre><code class="language-python">ToolMessage(
    content='{"transactions": [...], "total_amount": 1245.67, ...}',
    tool_call_id="call_123"
)
</code></pre>
<h3 id="heading-step-5-agent-node-again">Step 5: Agent Node Again</h3>
<p>The LLM now sees the user question, its previous tool, and the results. The LLM thinks: “I now have the data, the user spent $1245.67 on groceries. I can answer now.” And the LLM responds with:</p>
<pre><code class="language-python">AIMessage(content="You spent $1,245.67 on groceries this month across 23 transactions.")
</code></pre>
<h3 id="heading-step-6-should-continue">Step 6: Should Continue</h3>
<p>No tool calls this time, so returns END.</p>
<p><strong>Final State:</strong></p>
<pre><code class="language-python">{
    "messages": [
        HumanMessage("How much did I spend on groceries this month?"),
        AIMessage("", tool_calls=[...]),
        ToolMessage('{"total_amount": 1245.67, ...}'),
        AIMessage("You spent $1,245.67 on groceries this month across 23 transactions.")
    ]
}
</code></pre>
<p>The user receives: "You spent $1245.67 on groceries this month across 23 transactions."</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Building an AI agent boils down to three ideas:</p>
<ol>
<li><p>Tools</p>
</li>
<li><p>State</p>
</li>
<li><p>Graph</p>
</li>
</ol>
<p>LangGraph gives you control, so you are not left hoping that the agent does the right thing – instead, you’re explicitly defining what the “right thing” is.</p>
<p>The FinanceGPT example shows how this works in a real application. By learning these concepts, now you can build specialized agents for different jobs.</p>
<h2 id="heading-resources-worth-checking-out">Resources Worth Checking Out</h2>
<p>These helped me learn LangGraph:</p>
<ul>
<li><p><a href="https://python.langchain.com/docs/langgraph">Official LangGraph docs</a>: Start here</p>
</li>
<li><p><a href="https://python.langchain.com/docs/concepts/langgraph">LangGraph conceptual guide</a>: Deeper theory</p>
</li>
<li><p><a href="https://python.langchain.com/docs/concepts/agents">LangChain agent patterns</a>: Alternative approaches</p>
</li>
</ul>
<h2 id="heading-check-out-financegpt"><strong>Check Out FinanceGPT</strong></h2>
<p>All the code examples here came from <a href="https://github.com/manojag115/FinanceGPT">FinanceGPT</a>. If you want to see these patterns in a complete app, poke around the repo. It's got document processing, portfolio tracking, tax optimization – all built with LangGraph.</p>
<p>If you find this helpful, <a href="https://github.com/manojag115/FinanceGPT">give the project a star on GitHub</a> – it helps other developers discover it.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
