<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Blog | Adarsh Divakaran - AI</title>
  <subtitle>Tech blog of Adarsh Divakaran</subtitle>
  <link href="https://blog.adarshd.dev/ai/feed.xml" rel="self" type="application/atom+xml"/>
  <link href="https://blog.adarshd.dev/ai/" rel="alternate" type="text/html"/>
  <updated>2026-06-04T06:50:00Z</updated>
  <id>https://blog.adarshd.dev/</id>
  <author>
    <name>Adarsh Divakaran</name>
    <email>adarshdevamritham@gmail.com</email>
  </author>
  <entry>
    <title>Building AI Agents in Python</title>
    <link href="https://blog.adarshd.dev/ai/posts/building-ai-agents-in-python/" rel="alternate" type="text/html"/>
    <id>https://blog.adarshd.dev/ai/posts/building-ai-agents-in-python/</id>
    <published>2026-06-04T06:50:00Z</published>
    <updated>2026-06-04T06:50:00Z</updated>
    <content type="html"><![CDATA[<p>2026 is shaping up to be a big year for AI agents. We are seeing more products where the AI not only answers a question but also does some work for the user.</p>
<p>You have probably used ChatGPT or a similar AI tool to answer a question, help with writing, or explain some code. You type something, the AI responds, and the conversation goes back and forth. That is powerful, but it is also limited. The AI is essentially stuck in a chat box - it can only talk to you; it cannot do anything on your behalf.</p>
<p>AI agents change that. An agent is an AI that can actually take actions - browse the web, read and write files, run code, call APIs, and more. It does not just answer your question; it works toward a goal, step by step, using whatever tools it needs. Tools like Lovable, Cursor, and Claude Code are examples of this in practice.</p>
<p>In this article, we will explore the concepts behind building an AI agent in Python. We will use the <a href="https://github.com/openai/openai-python">OpenAI Python SDK</a> (Responses API) for the examples, but the same ideas can be generalized to any other LLM SDK. We will use a low-level SDK with minimal abstractions so we can observe and implement most of the agent’s behavior on our end.</p>
<p><strong>TL;DR</strong></p>
<p>This tutorial explains how AI agents work by building a simple one in Python.</p>
<p>We will cover the core pieces: LLMs, prompts, context, memory, the agent loop, tools, MCP, and skills:</p>
<table>
<thead>
<tr>
<th>Component</th>
<th>What it does</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>LLM</strong></td>
<td>Acts as the reasoning engine that understands the user request and decides what to do next.</td>
</tr>
<tr>
<td><strong>System prompt</strong></td>
<td>Defines the agent’s role, behavior, boundaries, and response style.</td>
</tr>
<tr>
<td><strong>Context window</strong></td>
<td>Controls how much information the model can see at once, including prompts, history, tool results, and files.</td>
</tr>
<tr>
<td><strong>Memory</strong></td>
<td>Helps the agent remember useful information across steps or conversations.</td>
</tr>
<tr>
<td><strong>Agent loop</strong></td>
<td>Repeats the process of thinking, acting, observing results, and deciding the next step.</td>
</tr>
<tr>
<td><strong>Tool calling</strong></td>
<td>Lets the agent use external functions such as APIs, web search, file access, or code execution.</td>
</tr>
<tr>
<td><strong>MCP</strong></td>
<td>Provides a standard way to connect agents to reusable tools and data sources.</td>
</tr>
<tr>
<td><strong>Skills</strong></td>
<td>Package reusable instructions, workflows, examples, and scripts for specific tasks.</td>
</tr>
</tbody>
</table>
<h2 id="what-are-agents" tabindex="-1"><a class="header-anchor" href="#what-are-agents">What are Agents?</a></h2>
<p>An AI agent is an AI system that can autonomously plan and execute multi-step actions toward a goal.</p>
<p>To understand agents, it helps to first understand what is powering them under the hood - a large language model, or LLM. For example, ChatGPT is a product built on top of OpenAI GPT LLMs. When you type a message and get a response, an LLM is doing the heavy lifting. It takes text as input and generates text as output.</p>
<p>On their own, LLMs are impressive but limited. They can only respond with text. They cannot open your browser, read a file on your computer, or send an email. They also do not know what happened yesterday, because their knowledge comes from training data with a cutoff date, not a live connection to the world.</p>
<p>Agents fix this by giving LLMs access to tools. A tool is just a function your code exposes to the model - something like “search the web” or “read this file.” The model can decide to call a tool when it needs to, and your code actually runs it. This turns a passive text generator into something that can act.</p>
<p>A good way to see the difference is to compare using ChatGPT with using Claude Code for a coding task. With ChatGPT, you describe the problem, copy the suggested code, paste it into your editor, run it, copy the error back, and repeat. The model has no idea what is actually in your project. Claude Code is different - it is powered by an LLM but also has access to tools like bash and file reading. You describe what you want, and it reads your files, writes code, runs tests, and fixes errors on its own. You just watch and steer.</p>
<p>The simplest way to understand an agent is:</p>
<ol>
<li>The user gives a goal.</li>
<li>The model decides what step to take.</li>
<li>The agent runs that step using a tool.</li>
<li>The model looks at the result.</li>
<li>The process continues until the task is complete.</li>
</ol>
<p>This is different from a normal chatbot. A chatbot mainly responds. An agent can respond and act.</p>
<p>In a simple agent, the model may only call one tool and return the result. In a more capable agent, the model may make a plan, call multiple tools, observe the results, adjust the plan, and continue until the task is complete.</p>
<p>Before we build this kind of system, we need to choose the model that will drive it.</p>
<h2 id="llms" tabindex="-1"><a class="header-anchor" href="#llms">LLMs</a></h2>
<p>LLMs are trained on massive amounts of text data - entire open source repositories on GitHub, books, articles, websites, and more. Through training, the model learns patterns in language well enough to generate coherent, useful responses. The scale of this training is what makes them surprisingly capable across such a wide range of tasks.</p>
<p>At their core, LLMs are text-in, text-out systems. You send them a block of text (called a prompt), and they generate a response. Everything that happens - reasoning, answering questions, writing code, making decisions - is expressed through that text interface. When an agent calls a tool, it is really the model writing out a structured text request, and your code intercepts that and actually runs the function.</p>
<p>The key limitation to keep in mind: LLMs only know what they were trained on. They have no awareness of events after their training cutoff and no way to look things up in real time unless they are given a tool to do so. This is part of what makes tools so valuable - they extend the model’s reach into the real world.</p>
<h2 id="choosing-an-llm" tabindex="-1"><a class="header-anchor" href="#choosing-an-llm">Choosing an LLM</a></h2>
<p>For an AI agent, the LLM is its brain. The quality of the model affects how well the agent understands instructions, chooses tools, handles errors, and completes multi-step tasks.</p>
<p>At the same time, the most powerful model is not always the right choice. We also need to think about cost, speed, context window, reasoning ability, and where the model is hosted.</p>
<h3 id="benchmarks" tabindex="-1"><a class="header-anchor" href="#benchmarks">Benchmarks</a></h3>
<p>Benchmarks are standardized tests used to compare the performance of different models. For coding tasks, there is <a href="https://www.swebench.com/">SWE-bench</a>. For general reasoning, there is <a href="https://www.kaggle.com/benchmarks/open-benchmarks/mmlu">MMLU</a>. Each benchmark tests the model on a specific type of problem and gives it a score. A higher score generally means the model will perform better on that type of task.</p>
<p>Benchmarks are a useful starting point when choosing a model, but they are not the whole story. A model that scores well on a benchmark may still behave unexpectedly in your specific use case, so it is always worth testing with your actual workload.</p>
<h3 id="costs" tabindex="-1"><a class="header-anchor" href="#costs">Costs</a></h3>
<p>Choosing the best-scoring model from a benchmark may not always be the most intelligent decision.</p>
<p>Cost is a real factor, especially at scale. Most providers charge per token, which is the basic unit of text the model processes. A token is roughly four characters, or about three-quarters of a word on average. Both what you send to the model (input) and what it generates back (output) count toward your token usage.</p>
<p>For an agent that runs multiple steps in a loop, token usage adds up quickly. A good approach is to start with a capable model and then see if a smaller or cheaper one can do the same job well enough. Sometimes a smaller model handles simple tasks just fine.</p>
<p><img src="https://storage.ghost.io/c/a5/00/a5004977-0dd2-4bcd-9292-dd0e05d4c59e/content/images/2026/05/image-12.png" alt=""> <em>(Model costs table from <a href="https://github.com/simonw/llm-prices">https://github.com/simonw/llm-prices</a>)</em></p>
<h3 id="reasoning-level" tabindex="-1"><a class="header-anchor" href="#reasoning-level">Reasoning Level</a></h3>
<p>Some models are designed to think before they answer. These reasoning models break complex problems into smaller steps internally, often called reasoning traces. You can think of it as the model working through a scratchpad before writing its final response. This can improve performance for tasks that need planning, debugging, tool use, or careful decision-making.</p>
<p>More reasoning effort usually means higher cost, higher response time, and better accuracy for complex tasks.</p>
<p>Not every request needs high reasoning. If the task is simple, we can use a lower reasoning level or a cheaper model. If the task involves multiple steps, unknown errors, or important decisions, more reasoning can be useful.</p>
<p><img src="https://storage.ghost.io/c/a5/00/a5004977-0dd2-4bcd-9292-dd0e05d4c59e/content/images/2026/05/image-10.png" alt=""> <em>(Conversation with GPT-OSS LLM showing reasoning/thought traces)</em></p>
<h3 id="hosted-vs-local" tabindex="-1"><a class="header-anchor" href="#hosted-vs-local">Hosted vs Local</a></h3>
<p>Most people start with a hosted model - one that runs on a provider’s servers and is accessed via an API. These are easy to set up, well-maintained, and generally the most capable options available. The trade-off is that you pay per token, and your data is processed by a third party.</p>
<p>There are also open models that can run entirely on your own machine/server. They can avoid per-token API costs and give you more control over data. The downside is that they require capable hardware and are generally less powerful than the best hosted models today. That said, local models are getting better quickly. Previous generation frontier capabilities are being replicated in the next generation of local models, and this gap will continue to close. Examples of open-weight models that can be self-hosted, depending on hardware and quantization, include <a href="https://deepmind.google/models/gemma/gemma-4/">Gemma 4 series</a> and <a href="https://www.kimi.com/ai-models/kimi-k2-6">Kimi K2.6</a>.</p>
<p>There are already decent <a href="https://medium.com/google-cloud/i-ran-gemma-4-as-a-local-model-in-codex-cli-7fda754dc0d4">local coding models that people use</a> for simple code generation and verification. In the coming years, this will improve, and stronger models will become available on consumer devices.</p>
<p>Hosted models are still easier to use for many applications. They usually provide better quality, higher reliability, larger context windows, and managed infrastructure.</p>
<p>Local models give more control over data, cost, and deployment. But they also require more setup, hardware, monitoring, and optimization.</p>
<h2 id="configuring-the-llm" tabindex="-1"><a class="header-anchor" href="#configuring-the-llm">Configuring the LLM</a></h2>
<p>Once you have picked a model, there are two things you set up before the agent starts running: the system prompt and the context window.</p>
<h3 id="system-prompt" tabindex="-1"><a class="header-anchor" href="#system-prompt">System Prompt</a></h3>
<p>A system prompt is the model’s top-level instruction that guides its behavior during a conversation.</p>
<p>It can set rules such as:</p>
<ul>
<li>what role the AI should play</li>
<li>what tone it should use</li>
<li>what it should or should not do</li>
<li>how it should handle tools</li>
<li>how it should handle safety and user requests</li>
<li>how it should format the final answer</li>
</ul>
<p>For an agent, the system prompt is very important. It tells the model how to behave while using tools. It can also define boundaries, such as asking for permission before destructive actions or avoiding actions outside the user’s request.</p>
<p>Let’s see an example of this in practice:</p>
<pre class="language-python"><code class="language-python"><span class="token keyword">import</span> os

<span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">'__main__'</span><span class="token punctuation">:</span>
    <span class="token keyword">from</span> openai <span class="token keyword">import</span> OpenAI

    client <span class="token operator">=</span> OpenAI<span class="token punctuation">(</span>api_key<span class="token operator">=</span>os<span class="token punctuation">.</span>environ<span class="token punctuation">[</span><span class="token string">"OPENAI_API_KEY"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>

    response <span class="token operator">=</span> client<span class="token punctuation">.</span>responses<span class="token punctuation">.</span>create<span class="token punctuation">(</span>
        model<span class="token operator">=</span><span class="token string">"gpt-5.4-mini"</span><span class="token punctuation">,</span>
        <span class="token builtin">input</span><span class="token operator">=</span><span class="token punctuation">[</span>
            <span class="token punctuation">{</span>
                <span class="token string">"role"</span><span class="token punctuation">:</span> <span class="token string">"system"</span><span class="token punctuation">,</span>
                <span class="token string">"content"</span><span class="token punctuation">:</span> <span class="token string">"You are a friendly Python tutor. Refuse all requests unrelated to Python coding"</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span><span class="token punctuation">,</span>
            <span class="token punctuation">{</span>
                <span class="token string">"role"</span><span class="token punctuation">:</span> <span class="token string">"user"</span><span class="token punctuation">,</span>
                <span class="token string">"content"</span><span class="token punctuation">:</span> <span class="token builtin">input</span><span class="token punctuation">(</span><span class="token string">"Enter your Python question: "</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span>
    <span class="token keyword">print</span><span class="token punctuation">(</span>response<span class="token punctuation">.</span>output_text<span class="token punctuation">)</span>

</code></pre>
<p>In the above script, we initialize an <code>OpenAI</code> client and use <code>client.responses.create</code> to send a message to <code>gpt-5.4-mini</code> model. The system prompt is specified in the <code>input</code> list as the first entry. <code>&quot;role&quot;: &quot;system&quot;</code> designates the entry as the system prompt. In the above example, the model is instructed to act as a Python tutor and refuse requests unrelated to Python. As the next entry, we accept the user prompt via <code>input()</code> and pass it to the LLM for answering.</p>
<p>If the script is run and any unrelated queries are passed to the LLM, we get a refusal response similar to the below one:</p>
<pre class="language-text"><code class="language-text">Enter your Python question: How many states are there in the US?

Model response: I’m here to help with Python coding questions only. If you have a Python-related question, feel free to ask!

</code></pre>
<p>Even though the underlying large language model knows the answer to the user’s query, it refuses to answer as per direction in the system prompt.</p>
<h3 id="context-window" tabindex="-1"><a class="header-anchor" href="#context-window">Context Window</a></h3>
<p>The context window is the model’s working memory. It is the amount of information the model can see in one request.</p>
<p>The context can include the user message, conversation history, system prompt, tool results, files, documentation, and any other information we provide.</p>
<p>Most of the latest flagship models support up to 1M tokens, which is roughly 750,000 words or about 15 books. Older models like GPT-4 series models had a 128K token window, around 2 books’ worth. For agents that run long tasks or work with large documents, context window size matters a lot. When the context fills up, older information gets dropped, which can cause the agent to lose track of earlier steps in a long task.</p>
<p>A larger context window is useful, but it is not free. More context usually means more cost and slower responses. Also, just because a model can accept a lot of context does not mean every token is equally important.</p>
<p>Good agents manage context carefully. They include what is needed, summarize old information, and avoid filling the context with unnecessary data.</p>
<p>Once we understand the model and its context window, the next question is what the agent should remember across steps and conversations.</p>
<h2 id="memory" tabindex="-1"><a class="header-anchor" href="#memory">Memory</a></h2>
<p>Memory helps an agent remember useful information.</p>
<p>Short-term memory helps the agent remember what the user said earlier in the same conversation. This usually lives inside the context window.</p>
<p>Let’s consider an example. The snippet below accepts a user query inside a loop and sends it to a model to get the response:</p>
<pre class="language-python"><code class="language-python"><span class="token keyword">import</span> os

<span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">'__main__'</span><span class="token punctuation">:</span>
    <span class="token keyword">from</span> openai <span class="token keyword">import</span> OpenAI

    client <span class="token operator">=</span> OpenAI<span class="token punctuation">(</span>api_key<span class="token operator">=</span>os<span class="token punctuation">.</span>environ<span class="token punctuation">[</span><span class="token string">"OPENAI_API_KEY"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>

    <span class="token keyword">while</span> <span class="token boolean">True</span><span class="token punctuation">:</span>
        user_query <span class="token operator">=</span> <span class="token builtin">input</span><span class="token punctuation">(</span><span class="token string">"You: "</span><span class="token punctuation">)</span>

        <span class="token keyword">if</span> user_query<span class="token punctuation">.</span>lower<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">in</span> <span class="token punctuation">[</span><span class="token string">"exit"</span><span class="token punctuation">,</span> <span class="token string">"quit"</span><span class="token punctuation">]</span><span class="token punctuation">:</span>
            <span class="token keyword">break</span>

        response <span class="token operator">=</span> client<span class="token punctuation">.</span>responses<span class="token punctuation">.</span>create<span class="token punctuation">(</span>
            model<span class="token operator">=</span><span class="token string">"gpt-5.4-mini"</span><span class="token punctuation">,</span>
            <span class="token builtin">input</span><span class="token operator">=</span>user_query<span class="token punctuation">,</span>
        <span class="token punctuation">)</span>

        assistant_reply <span class="token operator">=</span> response<span class="token punctuation">.</span>output_text
        <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"Model: </span><span class="token interpolation"><span class="token punctuation">{</span>assistant_reply<span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span>
</code></pre>
<p>The code works, but there are issues:</p>
<pre class="language-text"><code class="language-text">You: Tell me about Taj Mahal in 1 sentence
Model: The Taj Mahal is a magnificent white marble mausoleum in Agra, India, built by Emperor Shah Jahan in memory of his wife Mumtaz Mahal, and is one of the world’s most famous symbols of love.

You: When was it built?
Model: I can help, but I need to know **what “it” refers to**.  
Please share the name, photo, or location of the building/structure/object, and I’ll tell you when it was built.

</code></pre>
<p>As seen from the transcript, the model fails to answer the user’s follow-up prompt. This is because, we did not implement short term memory. For the model to be able to respond to follow-ups properly, we need to store and pass the conversation history to LLM calls. The snippet improves on the above script with short term memory implementation:</p>
<pre class="language-python"><code class="language-python"><span class="token keyword">import</span> os
<span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">'__main__'</span><span class="token punctuation">:</span>
    <span class="token keyword">from</span> openai <span class="token keyword">import</span> OpenAI

    client <span class="token operator">=</span> OpenAI<span class="token punctuation">(</span>api_key<span class="token operator">=</span>os<span class="token punctuation">.</span>environ<span class="token punctuation">[</span><span class="token string">"OPENAI_API_KEY"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>

    conversation_history <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>

    <span class="token keyword">while</span> <span class="token boolean">True</span><span class="token punctuation">:</span>
        user_query <span class="token operator">=</span> <span class="token builtin">input</span><span class="token punctuation">(</span><span class="token string">"You: "</span><span class="token punctuation">)</span>

        <span class="token keyword">if</span> user_query<span class="token punctuation">.</span>lower<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">in</span> <span class="token punctuation">[</span><span class="token string">"exit"</span><span class="token punctuation">,</span> <span class="token string">"quit"</span><span class="token punctuation">]</span><span class="token punctuation">:</span>
            <span class="token keyword">break</span>

        conversation_history<span class="token punctuation">.</span>append<span class="token punctuation">(</span><span class="token punctuation">{</span>
            <span class="token string">"role"</span><span class="token punctuation">:</span> <span class="token string">"user"</span><span class="token punctuation">,</span>
            <span class="token string">"content"</span><span class="token punctuation">:</span> user_query<span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>

        response <span class="token operator">=</span> client<span class="token punctuation">.</span>responses<span class="token punctuation">.</span>create<span class="token punctuation">(</span>
            model<span class="token operator">=</span><span class="token string">"gpt-5.4-mini"</span><span class="token punctuation">,</span>
            <span class="token builtin">input</span><span class="token operator">=</span>conversation_history<span class="token punctuation">,</span>
        <span class="token punctuation">)</span>

        assistant_reply <span class="token operator">=</span> response<span class="token punctuation">.</span>output_text
        <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"Model: </span><span class="token interpolation"><span class="token punctuation">{</span>assistant_reply<span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span>

        conversation_history<span class="token punctuation">.</span>append<span class="token punctuation">(</span><span class="token punctuation">{</span>
            <span class="token string">"role"</span><span class="token punctuation">:</span> <span class="token string">"assistant"</span><span class="token punctuation">,</span>
            <span class="token string">"content"</span><span class="token punctuation">:</span> assistant_reply<span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre>
<p>We introduced a <code>conversation_history</code> list that stores previous messages. User messages are appended to this list with <code>&quot;role&quot;: &quot;user&quot;</code> and model responses are appended with <code>&quot;role&quot;: &quot;assistant&quot;</code>. This way, whenever a request is sent to the model, it gets the entire message history through the <code>input</code> argument and will be able to respond to follow-up prompts correctly.</p>
<pre class="language-text"><code class="language-text">You: Tell me about Taj Mahal in 1 sentence
Model: The Taj Mahal is a stunning white marble mausoleum in Agra, India, built by Emperor Shah Jahan in memory of his wife Mumtaz Mahal.

You: When was it built?
Model: It was built between 1632 and 1653.
</code></pre>
<p>Long-term memory stores information beyond one conversation and persists even after the current chat or task ends. This is useful when you want the agent to remember user preferences, past decisions, or domain-specific facts across sessions. Common approaches include RAG (retrieval-augmented generation), where relevant information is fetched from a database and added to the context as needed, and built-in memory systems like ChatGPT Memories, where key facts are stored and automatically recalled in future conversations.</p>
<h2 id="agent-loop" tabindex="-1"><a class="header-anchor" href="#agent-loop">Agent Loop</a></h2>
<p>The agent loop is the core flow of an agent.</p>
<p>A simple loop looks like this:</p>
<ol>
<li>User sends a message.</li>
<li>Agent adds the message to the conversation context.</li>
<li>Agent sends the context and system prompt to the LLM.</li>
<li>LLM decides what to do next.</li>
<li>If needed, the LLM calls a tool.</li>
<li>Agent runs the tool and sends the result back to the LLM.</li>
<li>LLM decides whether more steps are needed.</li>
<li>When done, the LLM generates the final response.</li>
<li>Agent sends the response to the user.</li>
</ol>
<p>This loop is what makes agents feel different from normal chatbots. A chatbot usually gives one response. An agent can act, observe, and continue.</p>
<p>In practice, the intermediate steps are where the interesting work happens. The model may call a tool, wait for the result, process that result, decide to call another tool, and keep going before it gives a final answer. The loop runs as many times as needed until the model decides the task is complete or the user stops it. This brings us to tools - what they are and how they actually work.</p>
<h2 id="tool-calling" tabindex="-1"><a class="header-anchor" href="#tool-calling">Tool Calling</a></h2>
<p>Tools are external capabilities that the agent can use.</p>
<p>Tools (also called functions) let an AI agent do things beyond generating text. They can be used to take actions or get information.</p>
<p>Examples of tools:</p>
<ul>
<li>search the web</li>
<li>call an API</li>
<li>read files</li>
<li>edit files</li>
<li>run code</li>
<li>send emails</li>
<li>query a database</li>
<li>create calendar events</li>
</ul>
<p>The agent chooses a tool when needed. The tool has a name, a description, and input parameters. The model decides which tool to call and what arguments to pass.</p>
<p>Tool descriptions are important. If a tool description is unclear, the model may call it at the wrong time or pass the wrong input. We should describe tools in simple language and make their inputs strict.</p>
<p>Here is an important detail: the model does not run the tool itself. When it decides to use a tool, it outputs a structured request with the tool name and the arguments it wants to pass. Your code intercepts this, runs the actual function, and passes the result back to the model. The model then reads the result and decides what to do next. This back-and-forth between the model and your code is what makes the agent loop so powerful.</p>
<p>Let’s see an example of tool calling in action:</p>
<pre class="language-python"><code class="language-python"><span class="token keyword">import</span> json
<span class="token keyword">import</span> os
<span class="token keyword">from</span> dotenv <span class="token keyword">import</span> load_dotenv

load_dotenv<span class="token punctuation">(</span><span class="token punctuation">)</span>


<span class="token keyword">def</span> <span class="token function">get_weather</span><span class="token punctuation">(</span>location<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token keyword">return</span> <span class="token punctuation">{</span>
        <span class="token string">"location"</span><span class="token punctuation">:</span> location<span class="token punctuation">,</span>
        <span class="token string">"temperature"</span><span class="token punctuation">:</span> <span class="token string">"24 C"</span><span class="token punctuation">,</span>
        <span class="token string">"condition"</span><span class="token punctuation">:</span> <span class="token string">"Sunny"</span><span class="token punctuation">,</span>
        <span class="token string">"humidity"</span><span class="token punctuation">:</span> <span class="token string">"52%"</span><span class="token punctuation">,</span>
        <span class="token string">"wind"</span><span class="token punctuation">:</span> <span class="token string">"11 km/h"</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span>


<span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">'__main__'</span><span class="token punctuation">:</span>
    <span class="token keyword">from</span> openai <span class="token keyword">import</span> OpenAI

    client <span class="token operator">=</span> OpenAI<span class="token punctuation">(</span>api_key<span class="token operator">=</span>os<span class="token punctuation">.</span>environ<span class="token punctuation">[</span><span class="token string">"OPENAI_API_KEY"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>

    tools <span class="token operator">=</span> <span class="token punctuation">[</span>
        <span class="token punctuation">{</span>
            <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"function"</span><span class="token punctuation">,</span>
            <span class="token string">"name"</span><span class="token punctuation">:</span> <span class="token string">"get_weather"</span><span class="token punctuation">,</span>
            <span class="token string">"description"</span><span class="token punctuation">:</span> <span class="token string">"Get the current weather for a destination."</span><span class="token punctuation">,</span>
            <span class="token string">"parameters"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
                <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"object"</span><span class="token punctuation">,</span>
                <span class="token string">"properties"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
                    <span class="token string">"location"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
                        <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"string"</span><span class="token punctuation">,</span>
                        <span class="token string">"description"</span><span class="token punctuation">:</span> <span class="token string">"The city or destination, e.g. Paris or Tokyo"</span><span class="token punctuation">,</span>
                    <span class="token punctuation">}</span>
                <span class="token punctuation">}</span><span class="token punctuation">,</span>
                <span class="token string">"required"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"location"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
                <span class="token string">"additionalProperties"</span><span class="token punctuation">:</span> <span class="token boolean">False</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span><span class="token punctuation">,</span>
            <span class="token string">"strict"</span><span class="token punctuation">:</span> <span class="token boolean">True</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">]</span>

    input_list <span class="token operator">=</span> <span class="token punctuation">[</span>
        <span class="token punctuation">{</span>
            <span class="token string">"role"</span><span class="token punctuation">:</span> <span class="token string">"system"</span><span class="token punctuation">,</span>
            <span class="token string">"content"</span><span class="token punctuation">:</span> <span class="token string">"You are Safar, a travel planning AI agent"</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">{</span>
            <span class="token string">"role"</span><span class="token punctuation">:</span> <span class="token string">"user"</span><span class="token punctuation">,</span>
            <span class="token string">"content"</span><span class="token punctuation">:</span> <span class="token builtin">input</span><span class="token punctuation">(</span><span class="token string">"Ask you travel questions: "</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span>

    response <span class="token operator">=</span> client<span class="token punctuation">.</span>responses<span class="token punctuation">.</span>create<span class="token punctuation">(</span>
        model<span class="token operator">=</span><span class="token string">"gpt-5.4-mini"</span><span class="token punctuation">,</span>
        <span class="token builtin">input</span><span class="token operator">=</span>input_list<span class="token punctuation">,</span>
        tools<span class="token operator">=</span>tools<span class="token punctuation">,</span>
        tool_choice<span class="token operator">=</span><span class="token string">"required"</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span>

    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"The model responded with:"</span><span class="token punctuation">)</span>
    <span class="token keyword">print</span><span class="token punctuation">(</span>response<span class="token punctuation">.</span>output<span class="token punctuation">)</span>

    input_list <span class="token operator">+=</span> response<span class="token punctuation">.</span>output

    <span class="token keyword">for</span> item <span class="token keyword">in</span> response<span class="token punctuation">.</span>output<span class="token punctuation">:</span>
        <span class="token keyword">if</span> item<span class="token punctuation">.</span><span class="token builtin">type</span> <span class="token operator">!=</span> <span class="token string">"function_call"</span><span class="token punctuation">:</span>
            <span class="token keyword">continue</span>

        <span class="token keyword">if</span> item<span class="token punctuation">.</span>name <span class="token operator">==</span> <span class="token string">"get_weather"</span><span class="token punctuation">:</span>
            args <span class="token operator">=</span> json<span class="token punctuation">.</span>loads<span class="token punctuation">(</span>item<span class="token punctuation">.</span>arguments<span class="token punctuation">)</span>
            <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"The model wants to call get_weather with: </span><span class="token interpolation"><span class="token punctuation">{</span>args<span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span>

            weather <span class="token operator">=</span> get_weather<span class="token punctuation">(</span>args<span class="token punctuation">[</span><span class="token string">"location"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
            <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"The local Python function returned: </span><span class="token interpolation"><span class="token punctuation">{</span>weather<span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span>

            input_list<span class="token punctuation">.</span>append<span class="token punctuation">(</span><span class="token punctuation">{</span>
                <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"function_call_output"</span><span class="token punctuation">,</span>
                <span class="token string">"call_id"</span><span class="token punctuation">:</span> item<span class="token punctuation">.</span>call_id<span class="token punctuation">,</span>
                <span class="token string">"output"</span><span class="token punctuation">:</span> json<span class="token punctuation">.</span>dumps<span class="token punctuation">(</span>weather<span class="token punctuation">)</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span><span class="token punctuation">)</span>

    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"Sending the tool result back to the model"</span><span class="token punctuation">)</span>
    final_response <span class="token operator">=</span> client<span class="token punctuation">.</span>responses<span class="token punctuation">.</span>create<span class="token punctuation">(</span>
        model<span class="token operator">=</span><span class="token string">"gpt-5.4-mini"</span><span class="token punctuation">,</span>
        <span class="token builtin">input</span><span class="token operator">=</span>input_list<span class="token punctuation">,</span>
        tools<span class="token operator">=</span>tools<span class="token punctuation">,</span>
    <span class="token punctuation">)</span>

    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"Final answer:"</span><span class="token punctuation">)</span>
    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"Model response: </span><span class="token interpolation"><span class="token punctuation">{</span>final_response<span class="token punctuation">.</span>output_text<span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span>
</code></pre>
<p>In the <code>tools</code> list, we have defined a function named <code>get_weather</code> according to <a href="https://developers.openai.com/api/docs/guides/function-calling">OpenAI function calling</a> guidelines and have specified the parameters that the model accepts using the <code>parameters</code> key. This definition follows <a href="https://json-schema.org/">JSON Schema specification.</a></p>
<p>Since, we add this <code>tools</code> list when making calls to OpenAI, the model will know that it has access to a weather tool and will be able to request a function call when needed.</p>
<p>In the script, you can see that when we receive a response from the model, we always check if the response type is a function call or not (<code>item.type != &quot;function_call&quot;</code>) and if the response is a request to call <code>get_weather</code> tool, we call the <code>get_weather()</code> Python function and send it back to the model:</p>
<pre class="language-python"><code class="language-python">weather <span class="token operator">=</span> get_weather<span class="token punctuation">(</span>args<span class="token punctuation">[</span><span class="token string">"location"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
input_list<span class="token punctuation">.</span>append<span class="token punctuation">(</span><span class="token punctuation">{</span>
                <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"function_call_output"</span><span class="token punctuation">,</span>
                <span class="token string">"call_id"</span><span class="token punctuation">:</span> item<span class="token punctuation">.</span>call_id<span class="token punctuation">,</span>
                <span class="token string">"output"</span><span class="token punctuation">:</span> json<span class="token punctuation">.</span>dumps<span class="token punctuation">(</span>weather<span class="token punctuation">)</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre>
<p>Let’s run the script and ask the agent a question that would require a weather tool call:</p>
<pre class="language-text"><code class="language-text">Ask you travel questions: Sunscreen needed in Goa?

The model responded with:
[ResponseFunctionToolCall(arguments='{&quot;location&quot;:&quot;Goa&quot;}', call_id='call_X9OBZhGwT3yhfmTAOclefWE8', name='get_weather', type='function_call', id='fc_05ba95ec38f46f7f006a17ce9e3bb0819a9a0b430001f7bd91', namespace=None, status='completed')]

The model wants to call get_weather with: {'location': 'Goa'}
The local Python function returned: {'location': 'Goa', 'temperature': '24 C', 'condition': 'Sunny', 'humidity': '52%', 'wind': '11 km/h'}

Sending the tool result back to the model

Final answer:
Model response: Yes — sunscreen is a good idea in Goa. It’s sunny there right now, so UV exposure can be strong even if it feels pleasant.

Quick tips:
- Use broad-spectrum SPF 30+ (SPF 50 if you’ll be at the beach a lot)
- Reapply every 2 hours, and after swimming/sweating
- Don’t forget ears, neck, hands, and feet
- A hat and sunglasses help too

If you want, I can also suggest a Goa beach-day packing list.
</code></pre>
<p>For our query, the model initially responds with a <code>ResponseFunctionToolCall</code> item. This requests our <code>get_weather</code> function to be called with location argument set as Goa.</p>
<p>Responding to this request, our script executes the function call and sends the function call response back to the model for getting the final response. The function call always returns temperature as 24 degree Celsius with condition as sunny. Trusting this data, the model produces its final response, suggesting the user to use a sunscreen.</p>
<p>The weather function defined in the above script is not a very useful one, it returns a hardcoded weather data for all requests. In a practical scenario, the function should make an actual call to a real Weather API to fetch data.</p>
<p>The above script illustrates the concept of an agent loop. Even though the example involves just one user request and model response, the agent takes intermediary steps (tool calls) before returning the final response.</p>
<p>Now let’s move to a real world example involving tools. We will provide web search capability to our agent by defining a custom SerpApi web search tool.</p>
<p>Providers usually have their own built-in tools for web search. However, <a href="https://serpapi.com/blog/build-an-ai-agent-with-claude-agent-sdk/#websearch-tool">these tools can be slow or unreliable</a> at times. To get live search data from search engines reliably, we can write a custom tool/function using <a href="https://pypi.org/project/serpapi/0.1.4/">SerpApi Python SDK</a>.</p>
<pre class="language-python"><code class="language-python"><span class="token keyword">import</span> json
<span class="token keyword">import</span> os


<span class="token keyword">def</span> <span class="token function">google_search</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token keyword">import</span> serpapi

    client <span class="token operator">=</span> serpapi<span class="token punctuation">.</span>Client<span class="token punctuation">(</span>api_key<span class="token operator">=</span>os<span class="token punctuation">.</span>environ<span class="token punctuation">[</span><span class="token string">"SERPAPI_KEY"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>

    results <span class="token operator">=</span> client<span class="token punctuation">.</span>search<span class="token punctuation">(</span><span class="token punctuation">{</span>
        <span class="token string">"engine"</span><span class="token punctuation">:</span> <span class="token string">"google"</span><span class="token punctuation">,</span>
        <span class="token string">"q"</span><span class="token punctuation">:</span> query<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>

    <span class="token keyword">return</span> <span class="token punctuation">[</span>
        <span class="token punctuation">{</span>
            <span class="token string">"title"</span><span class="token punctuation">:</span> result<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"title"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
            <span class="token string">"link"</span><span class="token punctuation">:</span> result<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"link"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
            <span class="token string">"snippet"</span><span class="token punctuation">:</span> result<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"snippet"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">for</span> result <span class="token keyword">in</span> results<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"organic_results"</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token number">5</span><span class="token punctuation">]</span>
    <span class="token punctuation">]</span>


<span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">'__main__'</span><span class="token punctuation">:</span>
    <span class="token keyword">from</span> openai <span class="token keyword">import</span> OpenAI

    client <span class="token operator">=</span> OpenAI<span class="token punctuation">(</span>api_key<span class="token operator">=</span>os<span class="token punctuation">.</span>environ<span class="token punctuation">[</span><span class="token string">"OPENAI_API_KEY"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>

    tools <span class="token operator">=</span> <span class="token punctuation">[</span>
        <span class="token punctuation">{</span>
            <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"function"</span><span class="token punctuation">,</span>
            <span class="token string">"name"</span><span class="token punctuation">:</span> <span class="token string">"google_search"</span><span class="token punctuation">,</span>
            <span class="token string">"description"</span><span class="token punctuation">:</span> <span class="token string">"Search Google with SerpApi and return web search results."</span><span class="token punctuation">,</span>
            <span class="token string">"parameters"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
                <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"object"</span><span class="token punctuation">,</span>
                <span class="token string">"properties"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
                    <span class="token string">"query"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
                        <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"string"</span><span class="token punctuation">,</span>
                        <span class="token string">"description"</span><span class="token punctuation">:</span> <span class="token string">"The Google search query to run"</span><span class="token punctuation">,</span>
                    <span class="token punctuation">}</span>
                <span class="token punctuation">}</span><span class="token punctuation">,</span>
                <span class="token string">"required"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"query"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
                <span class="token string">"additionalProperties"</span><span class="token punctuation">:</span> <span class="token boolean">False</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span><span class="token punctuation">,</span>
            <span class="token string">"strict"</span><span class="token punctuation">:</span> <span class="token boolean">True</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">]</span>

    input_list <span class="token operator">=</span> <span class="token punctuation">[</span>
        <span class="token punctuation">{</span>
            <span class="token string">"role"</span><span class="token punctuation">:</span> <span class="token string">"system"</span><span class="token punctuation">,</span>
            <span class="token string">"content"</span><span class="token punctuation">:</span> <span class="token string">"You are Safar, a travel planner. Use Google search when current destination information would improve your answer."</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">{</span>
            <span class="token string">"role"</span><span class="token punctuation">:</span> <span class="token string">"user"</span><span class="token punctuation">,</span>
            <span class="token string">"content"</span><span class="token punctuation">:</span> <span class="token builtin">input</span><span class="token punctuation">(</span><span class="token string">"What travel question should I research? "</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span>

    response <span class="token operator">=</span> client<span class="token punctuation">.</span>responses<span class="token punctuation">.</span>create<span class="token punctuation">(</span>
        model<span class="token operator">=</span><span class="token string">"gpt-5.4-mini"</span><span class="token punctuation">,</span>
        <span class="token builtin">input</span><span class="token operator">=</span>input_list<span class="token punctuation">,</span>
        tools<span class="token operator">=</span>tools<span class="token punctuation">,</span>
        tool_choice<span class="token operator">=</span><span class="token string">"required"</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span>

    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"The model responded with:"</span><span class="token punctuation">)</span>
    <span class="token keyword">print</span><span class="token punctuation">(</span>response<span class="token punctuation">.</span>output<span class="token punctuation">)</span>

    input_list <span class="token operator">+=</span> response<span class="token punctuation">.</span>output

    <span class="token keyword">for</span> item <span class="token keyword">in</span> response<span class="token punctuation">.</span>output<span class="token punctuation">:</span>
        <span class="token keyword">if</span> item<span class="token punctuation">.</span><span class="token builtin">type</span> <span class="token operator">!=</span> <span class="token string">"function_call"</span><span class="token punctuation">:</span>
            <span class="token keyword">continue</span>

        <span class="token keyword">if</span> item<span class="token punctuation">.</span>name <span class="token operator">==</span> <span class="token string">"google_search"</span><span class="token punctuation">:</span>
            args <span class="token operator">=</span> json<span class="token punctuation">.</span>loads<span class="token punctuation">(</span>item<span class="token punctuation">.</span>arguments<span class="token punctuation">)</span>
            <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"The model wants to call google_search with: </span><span class="token interpolation"><span class="token punctuation">{</span>args<span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span>

            search_results <span class="token operator">=</span> google_search<span class="token punctuation">(</span>args<span class="token punctuation">[</span><span class="token string">"query"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
            <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"Step 7: SerpApi returned </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token builtin">len</span><span class="token punctuation">(</span>search_results<span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token string"> search results"</span></span><span class="token punctuation">)</span>

            input_list<span class="token punctuation">.</span>append<span class="token punctuation">(</span><span class="token punctuation">{</span>
                <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"function_call_output"</span><span class="token punctuation">,</span>
                <span class="token string">"call_id"</span><span class="token punctuation">:</span> item<span class="token punctuation">.</span>call_id<span class="token punctuation">,</span>
                <span class="token string">"output"</span><span class="token punctuation">:</span> json<span class="token punctuation">.</span>dumps<span class="token punctuation">(</span>search_results<span class="token punctuation">)</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span><span class="token punctuation">)</span>

    final_response <span class="token operator">=</span> client<span class="token punctuation">.</span>responses<span class="token punctuation">.</span>create<span class="token punctuation">(</span>
        model<span class="token operator">=</span><span class="token string">"gpt-5.4-mini"</span><span class="token punctuation">,</span>
        <span class="token builtin">input</span><span class="token operator">=</span>input_list<span class="token punctuation">,</span>
        tools<span class="token operator">=</span>tools<span class="token punctuation">,</span>
    <span class="token punctuation">)</span>

    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"Model response: </span><span class="token interpolation"><span class="token punctuation">{</span>final_response<span class="token punctuation">.</span>output_text<span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span>
</code></pre>
<p>Here, we define a <code>google_search()</code> that accepts a query and performs a Google search with the query using SerpApi Python SDK. The function returns the first five search results obtained from Google.</p>
<p>Let’s see the results in action:</p>
<pre class="language-text"><code class="language-text">What travel question should I research? When is the Tomato festival - La Tomatina happening this year?

The model responded with:
[ResponseFunctionToolCall(arguments='{&quot;query&quot;:&quot;La Tomatina 2026 date official&quot;}', call_id='call_mk2KL4xnvR0mexyt2lXFTHgE', name='google_search', type='function_call', id='fc_01af4c5fc07e8479006a192316ab20819bb10273439c89fb9a', namespace=None, status='completed')]

The model wants to call google_search with: {'query': 'La Tomatina 2026 date official'}
Step 7: SerpApi returned 5 search results

Model response: La Tomatina is happening on **Wednesday, August 26, 2026** in **Buñol, Spain**.

If you want, I can also help with:
- tickets
- how to get there from Valencia
- where to stay nearby
</code></pre>
<p>This is the core idea behind tool calling. The model does not directly browse the web or fetch data by itself. Instead, it identifies when a tool is needed, asks for that tool to be called, and then uses the returned result to continue the conversation. This separation is useful because the model can focus on reasoning, while tools provide access to external systems and real-time information.</p>
<p>Without the <code>google_search</code> tool, the model would not be able to answer questions that require live data. It should respond with something like: “I don’t have access to real-time information.” By defining the tool, we give the model a safe and structured way to request the information it needs.</p>
<h2 id="mcp" tabindex="-1"><a class="header-anchor" href="#mcp">MCP</a></h2>
<p>As you build more agents with more tools, a new problem emerges: every tool integration is custom-built and cannot easily be reused elsewhere. If you build a GitHub integration for one agent, you would have to rebuild it from scratch for another. That is where MCP comes in.</p>
<p><a href="https://serpapi.com/blog/model-context-protocol-mcp-a-unified-standard-for-ai-agents-and-tools/">Model Context Protocol (MCP)</a> is like USB-C for AI integrations. It is a standard protocol that lets models connect to external tools and data sources in a consistent, reusable way. Instead of building a custom integration for every tool, you write an MCP server once, and any model that supports MCP can use it.</p>
<p>Examples include:</p>
<ul>
<li><a href="https://github.com/github/github-mcp-server">GitHub MCP</a></li>
<li><a href="https://www.figma.com/mcp-catalog/">Figma MCP</a></li>
<li><a href="https://github.com/serpapi/serpapi-mcp">SerpApi MCP</a></li>
<li>database MCP servers</li>
<li>browser MCP servers</li>
<li>file system MCP servers</li>
</ul>
<p>With MCP, the model can discover supported functionality and call tools when needed. This makes integrations reusable across different models, clients, and applications. For a small agent, normal tool calling may be enough. For larger systems with many integrations, MCP can make the architecture cleaner.</p>
<p>Let’s see an example of MCP usage in practice. The script below uses the <a href="https://serpapi.com/blog/introducing-serpapis-mcp-server/">SerpApi MCP server</a> - using this, the agent will be able to call all the SerpApi supported engines like <code>google</code>, <code>google_shopping</code>, <code>amazon</code>, etc.</p>
<pre class="language-python"><code class="language-python"><span class="token keyword">import</span> os

<span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">'__main__'</span><span class="token punctuation">:</span>
    <span class="token keyword">from</span> openai <span class="token keyword">import</span> OpenAI

    client <span class="token operator">=</span> OpenAI<span class="token punctuation">(</span>api_key<span class="token operator">=</span>os<span class="token punctuation">.</span>environ<span class="token punctuation">[</span><span class="token string">"OPENAI_API_KEY"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>

    serpapi_mcp_url <span class="token operator">=</span> <span class="token string-interpolation"><span class="token string">f"https://mcp.serpapi.com/</span><span class="token interpolation"><span class="token punctuation">{</span>os<span class="token punctuation">.</span>environ<span class="token punctuation">[</span><span class="token string">'SERPAPI_KEY'</span><span class="token punctuation">]</span><span class="token punctuation">}</span></span><span class="token string">/mcp"</span></span>

    response <span class="token operator">=</span> client<span class="token punctuation">.</span>responses<span class="token punctuation">.</span>create<span class="token punctuation">(</span>
        model<span class="token operator">=</span><span class="token string">"gpt-5.4"</span><span class="token punctuation">,</span>
        tools<span class="token operator">=</span><span class="token punctuation">[</span>
            <span class="token punctuation">{</span>
                <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"mcp"</span><span class="token punctuation">,</span>
                <span class="token string">"server_label"</span><span class="token punctuation">:</span> <span class="token string">"serpapi"</span><span class="token punctuation">,</span>
                <span class="token string">"server_description"</span><span class="token punctuation">:</span> <span class="token string">"SerpApi MCP server"</span><span class="token punctuation">,</span>
                <span class="token string">"server_url"</span><span class="token punctuation">:</span> serpapi_mcp_url<span class="token punctuation">,</span>
                <span class="token string">"require_approval"</span><span class="token punctuation">:</span> <span class="token string">"never"</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">]</span><span class="token punctuation">,</span>
        <span class="token builtin">input</span><span class="token operator">=</span><span class="token punctuation">[</span>
            <span class="token punctuation">{</span>
                <span class="token string">"role"</span><span class="token punctuation">:</span> <span class="token string">"system"</span><span class="token punctuation">,</span>
                <span class="token string">"content"</span><span class="token punctuation">:</span> <span class="token string">"You are Cartwise, a shopping assistant. Help users compare products, prices, reviews, and buying options."</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span><span class="token punctuation">,</span>
            <span class="token punctuation">{</span>
                <span class="token string">"role"</span><span class="token punctuation">:</span> <span class="token string">"user"</span><span class="token punctuation">,</span>
                <span class="token string">"content"</span><span class="token punctuation">:</span> <span class="token builtin">input</span><span class="token punctuation">(</span><span class="token string">"What do you want to shop for? "</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span>

    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"Full model response (includes MCP operations): "</span><span class="token punctuation">)</span>
    <span class="token keyword">print</span><span class="token punctuation">(</span>response<span class="token punctuation">.</span>output<span class="token punctuation">)</span>

    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"Model response: </span><span class="token interpolation"><span class="token punctuation">{</span>response<span class="token punctuation">.</span>output_text<span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span>
</code></pre>
<p>SerpApi exposes the MCP server via the URL <a href="https://mcp.serpapi.com/">https://mcp.serpapi.com/</a>. Users can supply the API Key via the URL path as seen in the example: <a href="https://mcp.serpapi.com/%7Bos.environ%5B'SERPAPI_KEY'%5D%7D/mcp"><code>https://mcp.serpapi.com/{os.environ['SERPAPI_KEY']}/mcp</code></a>.</p>
<p>The code here is relatively simpler compared to the tool calling example. We just need to provide the MCP server info via the <code>tools</code> argument:</p>
<pre class="language-python"><code class="language-python">tools<span class="token operator">=</span><span class="token punctuation">[</span>
            <span class="token punctuation">{</span>
                <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"mcp"</span><span class="token punctuation">,</span>
                <span class="token string">"server_label"</span><span class="token punctuation">:</span> <span class="token string">"serpapi"</span><span class="token punctuation">,</span>
                <span class="token string">"server_description"</span><span class="token punctuation">:</span> <span class="token string">"SerpApi MCP server"</span><span class="token punctuation">,</span>
                <span class="token string">"server_url"</span><span class="token punctuation">:</span> serpapi_mcp_url<span class="token punctuation">,</span>
                <span class="token string">"require_approval"</span><span class="token punctuation">:</span> <span class="token string">"never"</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">]</span>
</code></pre>
<p>From this definition alone, the model can discover supported MCP functionalities and it will be able to autonomously call the MCP server tools based on the user request.</p>
<p>Let’s ask the agent a shopping query. Here, I am asking it to find the price of a mobile device:</p>
<pre class="language-text"><code class="language-text">What do you want to shop for? Find best price for Moto Razr Ultra phone

Full model response (includes MCP operations): 
[ 
  McpListTools(id='mcpl_06176a7178fb9f5c006a17f6c23578819ab2c977e7bc2b0bc7', server_label='serpapi', ..., 

McpCall(id='mcp_06176a7178fb9f5c006a17f6c40930819aac6136e6a0f0ced8', arguments='{&quot;params&quot;:{&quot;q&quot;:&quot;Moto Razr Ultra phone price&quot;,&quot;engine&quot;:&quot;google_shopping&quot;,&quot;num&quot;:10},&quot;mode&quot;:&quot;compact&quot;}', name='search', server_label='serpapi', type='mcp_call', approval_request_id=None, error=None, output='{&quot;shopping_results&quot;: [{&quot;position&quot;: 1, &quot;title&quot;: &quot;Motorola Razr Ultra 2025&quot;, &quot;product_id&quot;: &quot;14521999409488109662&quot;, &quot;product_link&quot;: ...]}]}', status='completed'), 
  ResponseOutputMessage(id='msg_06176a7178fb9f5c006a17f6cc3e68819aa688012defa9cf78', content=[ResponseOutputText(annotations=[], text='Best price I found for a **new Moto Razr Ultra** is:...'
]

Model response: Best price I found for a **new Moto Razr Ultra** is:

- **$699.99 at Best Buy** — Motorola Razr Ultra 2025  
  - was **$1,300**
  - rating: **4.0/5** from **520 reviews**
  - free delivery by Sat

Also matching:
- **$699.99 at Motorola US** — Motorola Razr 2025
- **$764.00 at Etoren** — Motorola Razr 50 Ultra
- **$1,049.99+** for some Razr 60 Ultra / 2026 variants
</code></pre>
<p>The model response includes a series of operations:</p>
<ul>
<li><strong>McpListTools</strong>: This is the request from the model sent to the MCP server to list available operations. From this call, the model will know that SerpApi has a <code>google_shopping</code> engine for shopping searches.</li>
<li><strong>McpCall:</strong> Based on the above response, the model calls MCP servers <code>google_shopping</code> engine with the query <code>Moto Razr Ultra phone price</code>. This call will fetch the shopping results via SerpApi MCP.</li>
<li><strong>ResponseOutputMessage</strong>: Once the above response is obtained, the model has enough information regarding the prices to formulate its response to the user. The model responds by listing the device price across a number of retailers.</li>
</ul>
<p>If we omitted the SerpApi MCP definition in the above script, the model should have responded with something like: “I cannot access real-time prices.” This is because the model itself does not have live data access unless we explicitly connect it to external tools or systems. MCP is one way to provide that connection in a standard way.</p>
<p>Now that we have seen how MCP connects agents to external capabilities, let’s look at another way to extend agent behavior: skills.</p>
<h2 id="skills" tabindex="-1"><a class="header-anchor" href="#skills">Skills</a></h2>
<p>While tools handle actions, skills handle behavior. A <a href="https://agentskills.io/">skill</a> is a reusable set of instructions or a workflow that tells an agent how to perform a specific type of task well.</p>
<p>We have seen tools and MCP which are code-heavy. Tools are code that gets called by the model whereas MCP requires a server implementation according to the <a href="https://modelcontextprotocol.io/specification">Model Context Protocol spec</a>. Skills are relatively simple and can just be a plain text markdown file.</p>
<p>A skill can include:</p>
<ul>
<li>steps to follow</li>
<li>rules</li>
<li>examples</li>
<li>output formats</li>
<li>scripts</li>
<li>templates</li>
<li>best practices</li>
</ul>
<p>Skills are useful for repeated tasks. Examples include writing reports, analyzing PDFs, creating slides, debugging code, or handling customer support. Skills make agents more specialized.</p>
<p>Instead of putting every instruction into the system prompt, we can use skills where the model receives just the skill metadata in the context and will be able to load and use the full skill when the current task needs it.</p>
<p>A skill file is just a markdown file with the below format:</p>
<pre class="language-text"><code class="language-text">---
name: skill-name
description: A description of what this skill does and when to use it.
---
Skill contents in markdown
</code></pre>
<p>Let’s see a real-world example: The <a href="https://github.com/serpapi/skills">SerpApi Search Skill</a> provides instructions for the agent to interact with SerpApi realtime search APIs. You can see the <a href="https://github.com/serpapi/skills/blob/master/skills/serpapi-web-search/SKILL.md">skill.md file</a>, which provides instruction to the model to invoke various SerpApi API calls.</p>
<p>You can see a usage example below, where we use SerpApi skill to build a travel planning agent.</p>
<pre class="language-python"><code class="language-python"><span class="token keyword">import</span> os
<span class="token keyword">import</span> subprocess
<span class="token keyword">from</span> pathlib <span class="token keyword">import</span> Path

<span class="token keyword">from</span> openai <span class="token keyword">import</span> OpenAI


MODEL <span class="token operator">=</span> os<span class="token punctuation">.</span>getenv<span class="token punctuation">(</span><span class="token string">"OPENAI_MODEL"</span><span class="token punctuation">,</span> <span class="token string">"gpt-5.4-mini"</span><span class="token punctuation">)</span>
SKILL_PATH <span class="token operator">=</span> Path<span class="token punctuation">(</span>__file__<span class="token punctuation">)</span><span class="token punctuation">.</span>resolve<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>parent <span class="token operator">/</span> <span class="token string">"skills"</span> <span class="token operator">/</span> <span class="token string">"serpapi-web-search"</span>


<span class="token keyword">def</span> <span class="token function">run_shell_call</span><span class="token punctuation">(</span>shell_call<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"\nModel requested shell call: </span><span class="token interpolation"><span class="token punctuation">{</span>shell_call<span class="token punctuation">.</span>call_id<span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span>
    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"Commands: </span><span class="token interpolation"><span class="token punctuation">{</span>shell_call<span class="token punctuation">.</span>action<span class="token punctuation">.</span>commands<span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span>

    command_outputs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
    <span class="token keyword">for</span> command <span class="token keyword">in</span> shell_call<span class="token punctuation">.</span>action<span class="token punctuation">.</span>commands<span class="token punctuation">:</span>
        <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"\n[script] Running command: </span><span class="token interpolation"><span class="token punctuation">{</span>command<span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span>

        result <span class="token operator">=</span> subprocess<span class="token punctuation">.</span>run<span class="token punctuation">(</span>
            command<span class="token punctuation">,</span>
            shell<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">,</span>
            executable<span class="token operator">=</span><span class="token string">"/bin/zsh"</span><span class="token punctuation">,</span>
            capture_output<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">,</span>
            text<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">,</span>
            check<span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation">,</span>
        <span class="token punctuation">)</span>

        <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"[script] Exit code: </span><span class="token interpolation"><span class="token punctuation">{</span>result<span class="token punctuation">.</span>returncode<span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span>
        <span class="token keyword">if</span> result<span class="token punctuation">.</span>stdout<span class="token punctuation">:</span>
            <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"[script] stdout:\n</span><span class="token interpolation"><span class="token punctuation">{</span>result<span class="token punctuation">.</span>stdout<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token format-spec">1500]</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span>
        <span class="token keyword">if</span> result<span class="token punctuation">.</span>stderr<span class="token punctuation">:</span>
            <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"[script] stderr:\n</span><span class="token interpolation"><span class="token punctuation">{</span>result<span class="token punctuation">.</span>stderr<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token format-spec">1500]</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span>

        command_outputs<span class="token punctuation">.</span>append<span class="token punctuation">(</span><span class="token punctuation">{</span>
            <span class="token string">"stdout"</span><span class="token punctuation">:</span> result<span class="token punctuation">.</span>stdout<span class="token punctuation">,</span>
            <span class="token string">"stderr"</span><span class="token punctuation">:</span> result<span class="token punctuation">.</span>stderr<span class="token punctuation">,</span>
            <span class="token string">"outcome"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
                <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"exit"</span><span class="token punctuation">,</span>
                <span class="token string">"exit_code"</span><span class="token punctuation">:</span> result<span class="token punctuation">.</span>returncode<span class="token punctuation">,</span>
            <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span>

    output_item <span class="token operator">=</span> <span class="token punctuation">{</span>
        <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"shell_call_output"</span><span class="token punctuation">,</span>
        <span class="token string">"call_id"</span><span class="token punctuation">:</span> shell_call<span class="token punctuation">.</span>call_id<span class="token punctuation">,</span>
        <span class="token string">"output"</span><span class="token punctuation">:</span> command_outputs<span class="token punctuation">,</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">if</span> shell_call<span class="token punctuation">.</span>action<span class="token punctuation">.</span>max_output_length <span class="token keyword">is</span> <span class="token keyword">not</span> <span class="token boolean">None</span><span class="token punctuation">:</span>
        output_item<span class="token punctuation">[</span><span class="token string">"max_output_length"</span><span class="token punctuation">]</span> <span class="token operator">=</span> shell_call<span class="token punctuation">.</span>action<span class="token punctuation">.</span>max_output_length

    <span class="token keyword">return</span> output_item


<span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">"__main__"</span><span class="token punctuation">:</span>
    client <span class="token operator">=</span> OpenAI<span class="token punctuation">(</span>api_key<span class="token operator">=</span>os<span class="token punctuation">.</span>environ<span class="token punctuation">[</span><span class="token string">"OPENAI_API_KEY"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>

    input_list <span class="token operator">=</span> <span class="token punctuation">[</span>
        <span class="token punctuation">{</span>
            <span class="token string">"role"</span><span class="token punctuation">:</span> <span class="token string">"system"</span><span class="token punctuation">,</span>
            <span class="token string">"content"</span><span class="token punctuation">:</span> <span class="token string">"You are Safar, a travel planning assistant."</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">]</span>

    tools <span class="token operator">=</span> <span class="token punctuation">[</span>
        <span class="token punctuation">{</span>
            <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"shell"</span><span class="token punctuation">,</span>
            <span class="token string">"environment"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
                <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"local"</span><span class="token punctuation">,</span>
                <span class="token string">"skills"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>
                    <span class="token punctuation">{</span>
                        <span class="token string">"name"</span><span class="token punctuation">:</span> <span class="token string">"serpapi-web-search"</span><span class="token punctuation">,</span>
                        <span class="token string">"description"</span><span class="token punctuation">:</span> <span class="token string">"Search current travel information with the SerpApi CLI."</span><span class="token punctuation">,</span>
                        <span class="token string">"path"</span><span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">(</span>SKILL_PATH<span class="token punctuation">)</span><span class="token punctuation">,</span>
                    <span class="token punctuation">}</span>
                <span class="token punctuation">]</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">]</span>

    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"Type 'exit' or 'quit' to stop.\n"</span><span class="token punctuation">)</span>

    waiting_for_user <span class="token operator">=</span> <span class="token boolean">True</span>

    <span class="token keyword">while</span> <span class="token boolean">True</span><span class="token punctuation">:</span>

        <span class="token keyword">if</span> waiting_for_user<span class="token punctuation">:</span>
            user_query <span class="token operator">=</span> <span class="token builtin">input</span><span class="token punctuation">(</span><span class="token string">"You: "</span><span class="token punctuation">)</span>

            <span class="token keyword">if</span> user_query<span class="token punctuation">.</span>lower<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">in</span> <span class="token punctuation">[</span><span class="token string">"exit"</span><span class="token punctuation">,</span> <span class="token string">"quit"</span><span class="token punctuation">]</span><span class="token punctuation">:</span>
                <span class="token keyword">break</span>

            input_list<span class="token punctuation">.</span>append<span class="token punctuation">(</span><span class="token punctuation">{</span>
                <span class="token string">"role"</span><span class="token punctuation">:</span> <span class="token string">"user"</span><span class="token punctuation">,</span>
                <span class="token string">"content"</span><span class="token punctuation">:</span> user_query<span class="token punctuation">,</span>
            <span class="token punctuation">}</span><span class="token punctuation">)</span>
            waiting_for_user <span class="token operator">=</span> <span class="token boolean">False</span>

        <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"\n[script] Sending request to the model."</span><span class="token punctuation">)</span>
        response <span class="token operator">=</span> client<span class="token punctuation">.</span>responses<span class="token punctuation">.</span>create<span class="token punctuation">(</span>
            model<span class="token operator">=</span>MODEL<span class="token punctuation">,</span>
            <span class="token builtin">input</span><span class="token operator">=</span>input_list<span class="token punctuation">,</span>
            tools<span class="token operator">=</span>tools<span class="token punctuation">,</span>
        <span class="token punctuation">)</span>

        input_list <span class="token operator">+=</span> response<span class="token punctuation">.</span>output

        shell_calls <span class="token operator">=</span> <span class="token punctuation">[</span>item <span class="token keyword">for</span> item <span class="token keyword">in</span> response<span class="token punctuation">.</span>output <span class="token keyword">if</span> item<span class="token punctuation">.</span><span class="token builtin">type</span> <span class="token operator">==</span> <span class="token string">"shell_call"</span><span class="token punctuation">]</span>
        <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"[script] Shell calls requested: </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token builtin">len</span><span class="token punctuation">(</span>shell_calls<span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span>

        <span class="token keyword">if</span> <span class="token keyword">not</span> shell_calls<span class="token punctuation">:</span>
            <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"Model response: </span><span class="token interpolation"><span class="token punctuation">{</span>response<span class="token punctuation">.</span>output_text<span class="token punctuation">}</span></span><span class="token string">\n"</span></span><span class="token punctuation">)</span>
            waiting_for_user <span class="token operator">=</span> <span class="token boolean">True</span>
            <span class="token keyword">continue</span>

        <span class="token keyword">for</span> shell_call <span class="token keyword">in</span> shell_calls<span class="token punctuation">:</span>
            input_list<span class="token punctuation">.</span>append<span class="token punctuation">(</span>run_shell_call<span class="token punctuation">(</span>shell_call<span class="token punctuation">)</span><span class="token punctuation">)</span>

        <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"\n[script] Sending shell output back to the model."</span><span class="token punctuation">)</span>

</code></pre>
<p>The script uses local skills capability of OpenAI SDK - we have the skill files added in <code>skills/serpapi-web-search</code> folder relative to the scripts parent directory.</p>
<p>Skills can be specified using the below format:</p>
<pre class="language-python"><code class="language-python">tools <span class="token operator">=</span> <span class="token punctuation">[</span>
        <span class="token punctuation">{</span>
            <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"shell"</span><span class="token punctuation">,</span>
            <span class="token string">"environment"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span>
                <span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"local"</span><span class="token punctuation">,</span>
                <span class="token string">"skills"</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>
                    <span class="token punctuation">{</span>
                        <span class="token string">"name"</span><span class="token punctuation">:</span> <span class="token string">"serpapi-web-search"</span><span class="token punctuation">,</span>
                        <span class="token string">"description"</span><span class="token punctuation">:</span> <span class="token string">"Search current travel information with the SerpApi CLI."</span><span class="token punctuation">,</span>
                        <span class="token string">"path"</span><span class="token punctuation">:</span> <span class="token builtin">str</span><span class="token punctuation">(</span>SKILL_PATH<span class="token punctuation">)</span><span class="token punctuation">,</span>
                    <span class="token punctuation">}</span>
                <span class="token punctuation">]</span><span class="token punctuation">,</span>
            <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">]</span>
</code></pre>
<p>We provide the skill name, description and path to the agent. When using skills, OpenAI SDK will emit shell calls that must be run in the terminal. This is needed so that the agent can list and view the full skill file contents that are present locally. We have a <code>run_shell_call()</code> function defined for this. Whenever the model requests for a shell call, we will run this function and pass back the shell results to the model.</p>
<blockquote class="prompt-tip">
Since this example lets the model request shell commands, only run it in a trusted, sandboxed environment. Do not give shell access to untrusted prompts, repositories, or skill files without review.
</blockquote>
<p>Now let’s run the agent and ask it a travel planning question. We will ask the model about hotel prices in Goa, India:</p>
<pre class="language-text"><code class="language-text">Type 'exit' or 'quit' to stop.
You: Find Goa hotel prices for a vacation: two nights from 10 June 26

[script] Sending request to the model.
[script] Shell calls requested: 1

Model requested shell call: call_Cgs0D4tNOZFPZ30GNVJzJNHZ
Commands: ['cd .../skills/serpapi-web-search &amp;&amp; cat SKILL.md']

[script] Running command: cd .../skills/serpapi-web-search &amp;&amp; cat SKILL.md
[script] Exit code: 0
[script] stdout:
---
name: serpapi-web-search
description: &gt;-
  Search the web using SerpApi's 100+ search engines. Use this skill whenever
  the user needs current or web-sourced information: ...

[script] Sending shell output back to the model.

[script] Sending request to the model.
[script] Shell calls requested: 1

Model requested shell call: call_Y2PGcBkpSEkzIikqu1H30uRW
Commands: [&quot;cd .../skills/serpapi-web-search &amp;&amp; sed -n '1,220p' rules/ENGINES.md&quot;]

[script] Running command: cd .../skills/serpapi-web-search &amp;&amp; sed -n '1,220p' rules/ENGINES.md
[script] Exit code: 0
[script] stdout:
# SerpApi Search Engines Catalog

Complete list of 107 SerpApi search engines. Use the `engine` parameter to select the desired search engine.
...

[script] Sending shell output back to the model.

[script] Sending request to the model.
[script] Shell calls requested: 1

Model requested shell call: call_oncTqSWJWQoDbqJS06aJP1OD
Commands: ['cd .../skills/serpapi-web-search &amp;&amp; serpapi search engine=google_hotels q=&quot;Goa&quot; check_in_date=2026-06-10 check_out_date=2026-06-12 --fields \'properties[0:20]\'']

[script] Running command: cd .../skills/serpapi-web-search &amp;&amp; serpapi search engine=google_hotels q=&quot;Goa&quot; check_in_date=2026-06-10 check_out_date=2026-06-12 --fields 'properties[0:20]'
[script] Exit code: 0
[script] stdout:
{
  &quot;properties&quot;: [
    {
      &quot;type&quot;: &quot;hotel&quot;,
      &quot;name&quot;: &quot;Vivanta Goa, Miramar&quot;,
      &quot;description&quot;: &quot;Swanky hotel across from the beach featuring a rooftop pool, a stylish restaurant &amp; a 24-hour gym.&quot;,
      ...


[script] Sending shell output back to the model.

[script] Sending request to the model.
[script] Shell calls requested: 0

Model response: I found live Goa hotel prices for **2 nights: 10 Jun 2026 to 12 Jun 2026**.

Sample prices from Google Hotels:
- **Vivanta Goa, Miramar** — **$82/night**, **$164 total**
- **Aloha Holiday Resort** — **$17/night**, **$33 total**
- Other properties in the Goa search were showing a wide range, from budget stays to luxury resorts.

A few notes:
- These are **current live rates** and can change quickly.
- The prices shown are from hotel search results and may be **before taxes/fees**.
- I searched broadly for **Goa**; if you want, I can narrow it down by:
  - **North Goa / South Goa**
  - **Budget / mid-range / luxury**
  - **Beachfront**
  - **2 adults vs family**

If you want, I can make a short list of the **best 10 Goa hotels under a budget you choose**.
</code></pre>
<p>As seen from the output, the model initially requested a shell call that runs <code>cat SKILL.md</code> which is to read the skill contents.</p>
<p>With the skill contents obtained, the model proceeds with another shell call <code>sed -n '1,220p' rules/ENGINES.md</code> which lists all SerpApi supported engines. With this data, the model will be able to get all supported SerpApi search engines and choose the best one for the task.</p>
<p>Next, model requests running the command <code>serpapi search engine=google_hotels q=&quot;Goa&quot; check_in_date=2026-06-10 check_out_date=2026-06-12 --fields 'properties[0:20]'</code> which uses <a href="https://github.com/serpapi/serpapi-cli">SerpApi CLI</a> to get results from Google Hotels. We run this shell command on our end and pass the results to the model that includes JSON results from Google Hotels API.</p>
<p>With this data obtained, the model was able to generate its final response and give us suggestions for Hotels to book in Goa along with the prices.</p>
<p>Now that we have seen prompts, memory, tools, MCP, and skills, we can put these pieces into one simple stack.</p>
<h2 id="agent-capability-stack" tabindex="-1"><a class="header-anchor" href="#agent-capability-stack">Agent Capability Stack</a></h2>
<p>An agent can be understood as a stack of capabilities. We have seen the core building blocks of an agent: system prompts, tools, MCP, and skills. Now, let’s compare how they fit together in the agent capability stack.</p>
<p>At the bottom, we have the system prompt. This defines global behavior and constraints.</p>
<p>Then we have skills. Skills provide packaged procedures for specific task types.</p>
<p>Then we have tools. Tools let the agent do things in the world.</p>
<p>Then we have MCP. MCP gives us a standard way to connect models to tools, files, APIs, databases, IDEs, browsers, and other systems.</p>
<p>We can think about the stack like this:</p>
<table>
<thead>
<tr>
<th>Layer</th>
<th>Purpose</th>
<th>Use when</th>
</tr>
</thead>
<tbody>
<tr>
<td>System prompt</td>
<td>Global behavior and constraints</td>
<td>You want rules that apply every turn</td>
</tr>
<tr>
<td>Skills</td>
<td>Reusable workflows</td>
<td>You want the model to follow a repeatable process</td>
</tr>
<tr>
<td>Tools</td>
<td>External actions and information</td>
<td>You want the model to call APIs, read files, run code, or fetch live state</td>
</tr>
<tr>
<td>MCP</td>
<td>Standard integration layer</td>
<td>You want reusable integrations across models and clients</td>
</tr>
</tbody>
</table>
<p>Use a system prompt for safety boundaries, tone, refusal style, and stable rules.</p>
<p>Use a skill when you want the model to follow a repeatable workflow or use scripts and templates.</p>
<p>Use tools when the model must call external services, fetch live state, create side effects, or interact with the environment.</p>
<p>Use MCP when you want to expose tools and resources through a standard protocol.</p>
<h2 id="summary-and-next-steps" tabindex="-1"><a class="header-anchor" href="#summary-and-next-steps">Summary and Next Steps</a></h2>
<p>In this tutorial, we started out with the components of an AI agent and built a few simple agents for use cases such as shopping and travel. We provided capabilities to agents using tool calling, MCP, and Skill files.</p>
<p>To explore on your own, you can find the code snippets used in the tutorial in this <a href="https://github.com/serpapi/tutorials/tree/master/python_projects/ai_agents_in_python">GitHub repo</a>.</p>
<p>If you are looking for a different SDK or tool to start with like the Claude agent SDK or n8n, we have you covered:</p>
<ul>
<li>
<p><a href="https://serpapi.com/blog/build-an-ai-agent-with-claude-agent-sdk/">Build an AI Agent with the Claude Agent SDK (Tutorial 2026)</a></p>
</li>
<li>
<p><a href="https://serpapi.com/blog/how-to-build-an-ai-agent-with-n8n-and-live-google-search-data/">How to Build an AI Agent with n8n and Live Google Search Data</a></p>
</li>
</ul>
<hr>
<p>Even though we covered the basics for building simple agents, some important next steps to learn more about are:</p>
<ul>
<li>multi-agent systems</li>
<li>observability</li>
<li>error handling</li>
<li>permissions</li>
<li>context compaction</li>
<li>evaluation</li>
</ul>
<p>A multi-agent system has multiple agents, where each agent can be specialized for a specific goal. These agents can communicate with each other. We can also have verifier models that check the output from other models.</p>
<p>Similar to building a backend application, we need <strong>observability</strong> and error handling for agents. The model can hallucinate, choose the wrong tool, pass bad arguments, or get stuck in a loop. We need a way to monitor this behavior and improve the system over time.</p>
<p><strong>Permissions</strong> are also important. An agent that can read files is useful. An agent that can delete files or send emails should be more carefully controlled. We should decide which actions require user approval.</p>
<p><strong>Context compaction</strong> is another important idea. As the conversation grows, the agent cannot keep everything forever. It needs to summarize old information and keep only what is useful for the next step.</p>
<p><strong>Evaluation</strong> helps us understand whether the agent is actually doing a good job. We can test the agent on sample tasks, check if it used the right tools, verify whether the final answer is correct, and compare outputs across different prompts or models. Without evaluation, it is hard to know if the agent is improving or just producing confident-looking answers.</p>
<p>The best way to understand agents is to build small ones, give them real tasks, inspect their tool calls, and evaluate their outputs. Start with a simple loop, add tools carefully, introduce memory only when needed, and add observability before trusting the agent with important actions. And if your agent needs real-time data access, you can explore <a href="https://serpapi.com/search-engine-apis">SerpApi APIs</a> to extend its capabilities.</p>
]]></content>
  </entry>
  <entry>
    <title>Vibe Coding with Junie - JetBrains AI Coding Agent</title>
    <link href="https://blog.adarshd.dev/posts/vibe-coding-with-jetbrains-junie/" rel="alternate" type="text/html"/>
    <id>https://blog.adarshd.dev/posts/vibe-coding-with-jetbrains-junie/</id>
    <published>2025-08-05T12:50:00Z</published>
    <updated>2025-08-05T12:50:00Z</updated>
    <content type="html"><![CDATA[<p>I created a Python project recently to test out Junie - the new AI coding agent from JetBrains. This led to the creation
of <a href="https://github.com/adarshdigievo/pyzlides">Pyzlides</a>, a Python library that lets us build presentation slides
entirely as code. Interestingly, there are zero lines of code written by me; the entire codebase was generated by Junie.</p>
<p>I started using PyCharm IDE back in 2019 for my work, and I’ve been hooked on it ever since. Since then, I’ve explored
other AI-powered IDEs like Cursor and Windsurf, actively experimenting with each new release to see what they offer.</p>
<p>At PyCon US this year, I got to meet the JetBrains PyCharm team in person. They gave me a quick demo of Junie, and I
knew immediately I wanted to give it a shot.</p>
<p>Below, I’ll share the detailed steps I took while developing Pyzlides, along with a few tips on making the most of
AI-assisted coding.</p>
<h2 id="initial-setup" tabindex="-1"><a class="header-anchor" href="#initial-setup">Initial Setup</a></h2>
<p>For AI coding tools, context is the most important. I always spend lot of time to create the initial prompt which will
be given to the agent. I try to make it as detailed as possible with examples and intended usage.
Initially, I bootstrapped the environment using <code>uv</code> and installed the dependencies in the environment. After a bit of
brainstorming and help from ChatGPT to decide the project dependencies, I decided to use the below dependencies:</p>
<ul>
<li><strong>ReportLab</strong>: For generating the PDF slides.</li>
<li><strong>Pillow</strong>: For handling images in slides.</li>
<li><strong>PyYAML</strong>: For parsing the configuration file.</li>
</ul>
<p>I installed these libraries in the environment and this was my major point of contribution. After this point, the entire
building was done by Junie and I was only involved with writing prompts.</p>
<h2 id="getting-started-with-junie" tabindex="-1"><a class="header-anchor" href="#getting-started-with-junie">Getting started with Junie</a></h2>
<p>I used the Junie premium subscription for this project. Below is a screenshot of Junie’s chat interface:</p>
<p><img src="/assets/img/posts/vibe-coding-with-jetbrains-junie/junie_interface.png" alt="image"></p>
<p>Junie provides a simple, familiar chat interface to interact with. From the interface, we have two conversation modes:</p>
<ol>
<li>
<p><strong>Code mode</strong>: Junie directly generates or edits code.</p>
</li>
<li>
<p><strong>Ask mode</strong>: Useful for discussing the codebase, understanding unfamiliar code structure, or clarifying doubts
without making direct code changes.</p>
</li>
</ol>
<p>Additionally, using the <code>+</code> button, we can attach relevant files to give Junie more context about the project.</p>
<p>Junie includes a <strong>Brave Mode</strong> toggle. When enabled, Junie executes terminal commands (such as creating directories,
running the project, etc.) without asking for user confirmation. I recommend using Brave Mode only with simpler projects
where you fully understand and trust the codebase. Otherwise, it poses a risk of unintended actions or prompt injection.</p>
<p>Recent Junie versions introduced another toggle: <strong>Think More</strong>, prompting the AI model to reflect more carefully during
code
generation, improving the overall quality of its responses.</p>
<h3 id="setting-context" tabindex="-1"><a class="header-anchor" href="#setting-context">Setting context</a></h3>
<p>As we’ve previously discussed, AI coding assistants perform best when given ample context. Junie provides helpful ways
to ensure it understands our project’s codebase clearly:</p>
<ul>
<li>
<p><strong><code>.aiignore</code></strong>: From the <code>+</code> menu, we can generate an AI-ignore file, similar to <code>.gitignore</code>. Files added here won’t
be modified by Junie.</p>
</li>
<li>
<p><strong>Guidelines</strong>: Also available through the <code>+</code> button and example prompts are project-specific instructions. Junie
uses these as guidelines to stay aligned with project conventions. I will share the guidelines I created later in this
post.</p>
</li>
</ul>
<h3 id="junie-settings" tabindex="-1"><a class="header-anchor" href="#junie-settings">Junie Settings</a></h3>
<p>Before we dive deeper into Pyzlides, let’s quickly look at Junie’s settings:</p>
<p><img src="/assets/img/posts/vibe-coding-with-jetbrains-junie/junie_settings.png" alt="image"></p>
<ul>
<li>
<p><strong>Models</strong>:<br>
I chose <strong>Claude Sonnet 4</strong> as my model, as it’s currently one of the best for coding tasks.</p>
</li>
<li>
<p><strong>Connect MCP Servers</strong>:<br>
MCP servers allow Junie to interact with external resources. They can be configured globally in settings or
specifically for a project by adding them to the <code>/.junie/mcp</code> directory in the project’s root folder.<br>
For instance, if we integrate Stripe, connecting the Stripe MCP server ensures Junie understands and accurately
utilizes Stripe’s latest API operations, significantly improving the quality of generated code.</p>
</li>
<li>
<p><strong>Action Allowlist</strong>:<br>
Here, we can specify terminal commands Junie may run directly without asking for our confirmation.</p>
</li>
</ul>
<h3 id="init-pyzlides" tabindex="-1"><a class="header-anchor" href="#init-pyzlides">Init Pyzlides</a></h3>
<p>To help Junie understand the project clearly, I prepared an initial prompt using ChatGPT. The complete prompt is
available in the Pyzlides repo <a href="https://github.com/adarshdigievo/pyzlides/blob/main/original_instructions.md">here</a>.</p>
<p>Here’s a brief version of the initial prompt:</p>
<blockquote>
<p>Pyzlides: Python Presentation Creator</p>
<p>Overview</p>
<p>Pyzlides is a Python library that lets us create PDF presentation slides directly from Python code. Slides are
generated in standard presentation resolution, and elements like headers, body text, images, and layouts can be
defined
using simple Python classes. Pyzlides supports custom themes, layouts, and ordering slides through a configuration
file.</p>
<p>Features</p>
<ol>
<li><strong>Declarative Slide Creation</strong>: Define slides using Python classes like <code>Head1</code>, <code>BodyText</code>, <code>Img</code>, etc.</li>
<li><strong>Custom Layouts</strong>: Use custom layouts like <code>Center</code> or <code>Bottom</code> for positioning elements.</li>
<li><strong>Configurable Themes</strong>: Define themes such as colors, fonts, and backgrounds via a config file.</li>
<li><strong>Slide Ordering</strong>: Specify slide sequence in the configuration file.</li>
<li><strong>PDF Output</strong>: Generate presentations as PDF files.</li>
<li><strong>Extendable</strong>: Easily extend slide functionality by creating new element types or layouts.</li>
</ol>
<hr>
<p>Dependencies</p>
<ul>
<li><strong>ReportLab</strong>: PDF slide generation.</li>
<li><strong>Pillow</strong>: Image handling.</li>
<li><strong>PyYAML</strong>: Config file parsing.</li>
</ul>
<p>Core Classes</p>
<ol>
<li>
<p><strong>Base Slide Element Classes</strong></p>
<ul>
<li>
<p><code>Head1(text: str)</code>: Large header text.</p>
</li>
<li>
<p><code>BodyText(text: str)</code>: Paragraph-style text.</p>
</li>
<li>
<p><code>Img(path: str, caption: str)</code>: Image with optional caption.</p>
</li>
<li>
<p><code>Bold(text: str)</code>: Bold formatting.</p>
</li>
<li>
<p><code>Center(element)</code>: Center-align element.</p>
</li>
<li>
<p><code>Bottom(element)</code>: Bottom-align element.</p>
</li>
</ul>
</li>
</ol>
</blockquote>
<p>To begin, I clicked on the example prompt button in Junie to generate project instructions. Junie created detailed
guidelines automatically and stored them in the <code>.junie</code> directory. This file includes key features, project structure,
and expectations. You can view it <a href="https://github.com/adarshdigievo/pyzlides/blob/main/.junie/guidelines.md">here</a>.</p>
<h2 id="start-coding" tabindex="-1"><a class="header-anchor" href="#start-coding">Start Coding</a></h2>
<p>One aspect I liked about Junie was its ability to first generate a clear implementation plan before writing any code.
This made complex tasks manageable and easy to review.</p>
<p>Once Junie starts, it updates progress as each step completes. Below you can see Junie’s initial implementation plan for
Pyzlides.</p>
<p><img src="/assets/img/posts/vibe-coding-with-jetbrains-junie/initial_plan.png" alt="image"></p>
<p>Below is the expanded interface when the task was completed by Junie. The pane on the right shows the steps taken for
the implementation.</p>
<p><img src="/assets/img/posts/vibe-coding-with-jetbrains-junie/initial_plan_done.png" alt="image">
After finishing a task, Junie suggests helpful follow-up actions like adding files to Git or reverting unwanted changes.</p>
<p><img src="/assets/img/posts/vibe-coding-with-jetbrains-junie/done_actions.png" alt="image"></p>
<p>The initial implementation by Junie was already impressive. There were minor alignment issues, so I asked Junie to fix
these in the next prompt:</p>
<blockquote>
<p>the pages are made in portrait mode - change it to landscape mode.</p>
<p>also, bolding does not seem to work.</p>
<p>increase the text size - it is a presentation, not just a simple pdf file.</p>
<p>also make necessary changes so that this can be published as a library.
people can install this library and then add config and slide files and use the lib to generate pdf. sample slide
files and config can be moved to examples folder if needed. keep the library source directory clean</p>
</blockquote>
<p>Once fixed, I asked Junie to add more advanced features and create example slides to showcase Pyzlides capabilities
clearly:</p>
<blockquote>
<p>create multiple elements to support:</p>
<p>title slide - just a title in the centre - horizontally and vertically</p>
<p>image backgrounds - support images as slide backgrounds.</p>
<p>fix text getting cut out to the next sections.</p>
<p>add H1 to h3 tags - different type of sections.</p>
<p>in the example presentation, add slides to illustrate various types - title only, title with text, title with image,
title with image and text, dark mode presentation, white mode presentation, custom background image presentation.</p>
<p>make folders inside example folder for each example. add more elements to the definition as needed - to support a
fully featured slides functionality.</p>
<p>also, inline text colours can be specified to text elements.</p>
</blockquote>
<p>After this, I asked Junie to add features like code snippets and grid layouts. After these enhancements,
the library was nearly complete.</p>
<h2 id="finishing-up" tabindex="-1"><a class="header-anchor" href="#finishing-up">Finishing Up</a></h2>
<p>There were a few remaining issues, primarily with text overflowing slides. I prompted Junie to handle these scenarios
clearly:</p>
<blockquote>
<ol>
<li>
<p>detect when texts flow out of the view - and then fix it to go to next line. regenerate all examples after this
change. also test this with long headings, text, etc. there should be a bit of default margins on top botoom left
and right.</p>
</li>
<li>
<p>also, add a code component - that accept a code in markdown block and provide syntax highlighted output in text.
you can use the already installed pygments library for the same.</p>
</li>
</ol>
</blockquote>
<p>Next, I asked Junie to address a few bugs from previous implementations:</p>
<blockquote>
<ul>
<li>syntax highlight is not working.</li>
<li>when there is too much text, split it into multiple slides automatically. currently the contents are being cut out -
for example in the text and code example.</li>
<li>also the newline feature is not working as expected. since title texts are starting from centre of the page, some
text is still getting cut out horizontally - relook the calculation part</li>
</ul>
</blockquote>
<p>Throughout this project, I enabled Brave Mode. This allowed Junie to automatically run example code, generate PDFs, and
proactively identify and fix issues.</p>
<p>To make Pyzlides easy for new users, I also had Junie add a README to guide them through using the library. At this
stage, I noticed an unused <code>layout</code> section in the config files. I asked Junie to remove this:</p>
<blockquote>
<p>i can see that the layouts section in config is unused. remove this from example config files and Config class defined
in core</p>
</blockquote>
<p>Junie’s response was prompt and accurate:</p>
<blockquote>
<p>The <code>layouts</code> section was removed from the Config class in <a href="http://core.py">core.py</a> and from all example config files, confirming it
was unused. Functionality was verified by successfully generating presentations from both the basic-presentation and
grid-layout examples without any errors.</p>
</blockquote>
<h3 id="summary" tabindex="-1"><a class="header-anchor" href="#summary">Summary</a></h3>
<p>The entire Pyzlides project—including fixes and improvements—was completed with fewer than 10 prompts. Below is the
final file structure:</p>
<pre class="language-text"><code class="language-text">project_root/
├── .gitignore
├── pyproject.toml
├── pyzlides.py
├── readme.md
├── uv.lock
│
├── .junie/
│   └── guidelines.md
│
├── pyzlides/
│   ├── __init__.py
│   ├── __main__.py
│   ├── cli.py
│   └── core.py   # Core library functionality (1010 lines)
│
└── examples/
    ├── README.md
    │
    ├── images/
    │   ├── bg_black1.jpg
    │   ...
    │
    ├── basic-presentation/
    │   ...
</code></pre>
<p>These are the core files of the library:</p>
<ul>
<li>
<p><code>pyzlides.py</code>: Main command-line entry point for generating presentations.</p>
</li>
<li>
<p><code>pyzlides/</code></p>
<ul>
<li><code>cli.py</code>: Handles command-line interface functionality.</li>
<li><code>core.py</code>: Contains the core implementation and logic of Pyzlides. Includes the component definitions, parsing
logic and pdf generation code.</li>
</ul>
</li>
</ul>
<p><strong>My conclusion</strong>: I’m impressed with Junie - especially loved the planning mode. Few would guess an AI coding agent
could accomplish such a detailed
task within minutes.</p>
<blockquote>
<p>I haven’t fully refined or battle-tested Pyzlides yet, but it’s already an excellent starting point. You can review
the generated PDFs in the <code>examples</code> folder, and see for yourself :)</p>
</blockquote>
<h3 id="notes-for-ai-assisted-coding" tabindex="-1"><a class="header-anchor" href="#notes-for-ai-assisted-coding">Notes for AI-Assisted Coding</a></h3>
<ul>
<li>Use Brave Mode cautiously, especially in complex or sensitive projects.</li>
<li>Provide clear, detailed context for the best results.</li>
<li>Always verify the plan generated by the agent and course correct whenever needed.</li>
<li>Consider tools like <a href="https://gitingest.com/">GitIngest</a> to enhance context. Also, connect MCP servers when available
for deeper integrations.</li>
</ul>
<h3 id="links" tabindex="-1"><a class="header-anchor" href="#links">Links</a></h3>
<ul>
<li>
<p>Pyzlides GitHub project: <a href="https://github.com/adarshdigievo/pyzlides/">GitHub - adarshdigievo/pyzlides</a></p>
</li>
<li>
<p>Junie Docs: <a href="https://www.jetbrains.com/junie/">Junie, the AI coding agent by JetBrains</a></p>
</li>
<li>
<p>Junie Playbook: <a href="https://www.jetbrains.com/guide/ai/article/junie/pycharm/">PyCharm Junie Playbook - JetBrains Guide</a></p>
</li>
<li>
<p>Download Pycharm  : <a href="https://jb.gg/pcusad">PyCharm: The only Python IDE you need</a></p>
</li>
</ul>
<p>I’d like to thank the JetBrains team for collaborating on this project. I look forward to using Junie in future
experiments.</p>
]]></content>
  </entry>
  <entry>
    <title>Will AI Replace Junior Developers? I Asked Experts at Pycon US</title>
    <link href="https://blog.adarshd.dev/posts/pycon-us-ai-and-future-of-programming/" rel="alternate" type="text/html"/>
    <id>https://blog.adarshd.dev/posts/pycon-us-ai-and-future-of-programming/</id>
    <published>2025-06-16T13:50:00Z</published>
    <updated>2025-06-16T13:50:00Z</updated>
    <content type="html"><![CDATA[<p>I attended <em>Pycon US in Pittsburgh</em> - This was my first in-person Pycon US.</p>
<p>At this PyCon, I decided to try something different. I skipped most of the talks and focused on the hallway track - just
walking around, meeting people, and asking questions.</p>
<p>I was also presenting a poster at PyCon - on using Python to improve at
chess: <a href="https://github.com/adarshdigievo/talks/tree/main/Pycon%20US%2025%20-%20Improving%20in%20Chess">presentation materials here.</a></p>
<p>Before the conference, I had prepared a few questions I wanted to ask the Python people:</p>
<ul>
<li>
<p>What do you think is the future of programming?</p>
</li>
<li>
<p>Will AI take over programming jobs in the next 2–3 years?</p>
</li>
<li>
<p>What AI tools do you use in your daily work?</p>
</li>
<li>
<p>What advice do you have for Python developers?</p>
</li>
</ul>
<p>Most of the people I talked to are familiar names in the Python community. I’ve read their blog posts, watched their
talks, or used their open-source tools. There were many more people I wish I had a chance to talk to, but I’m glad I got
to speak with the ones I did.</p>
<p>One thing surprised me - very few people said AI will replace developers. I expected more.</p>
<p>Later I realized why. Most of the folks I spoke to are working on deep, core programming problems - like optimizing
CPython, or building dev tools. These are areas where current AI tools aren’t that useful. LLMs haven’t seen much of
that code. So maybe it’s not that surprising after all.</p>
<p>Here’s what they said:</p>
<hr>
<h2 id="guido-van-rossum-dutch-programmer" tabindex="-1"><a class="header-anchor" href="#guido-van-rossum-dutch-programmer">Guido van Rossum — Dutch programmer</a></h2>
<p>Guido said AI won’t replace programmers.</p>
<p>He’s not a fan of “vibe coding” (letting AI write code you don’t understand). He is not impressed with the quality of
the code generated by current tools.
But he does use GitHub Copilot (for autocomplete) and he expects AI assistants to get much better in the future -
something like a
supercharged Copilot.</p>
<hr>
<h2 id="anthony-shaw-psf-fellow-author-of-cpython-internals" tabindex="-1"><a class="header-anchor" href="#anthony-shaw-psf-fellow-author-of-cpython-internals">Anthony Shaw — PSF Fellow, Author of CPython Internals</a></h2>
<p>Anthony said AI is like the new Stack Overflow.</p>
<p>Earlier, people copied code from blogs or seniors. Now it’s from ChatGPT.<br>
AI is useful for boilerplate or things you already know. He uses <strong>Copilot Chat</strong> for regular stuff and <strong>agent mode</strong>
when he’s working on something unfamiliar (like frontend work).</p>
<p>His advice: <em>Don’t blindly rely on AI</em>. If you don’t keep learning, the gap between juniors and seniors will grow fast.</p>
<blockquote class="prompt-tip">
<p><a href="https://tonybaloney.github.io/">Anthony's Website</a> · <a href="https://tonybaloney.github.io/vscode-pets/">My favorite project – VSCode Pets</a></p>
</blockquote>
<hr>
<h2 id="anthony-sottile-maintainer-of-pre-commit-flake8-and-pytest" tabindex="-1"><a class="header-anchor" href="#anthony-sottile-maintainer-of-pre-commit-flake8-and-pytest">Anthony Sottile - Maintainer of pre-commit, flake8, and pytest</a></h2>
<p>He doesn’t use AI tools.</p>
<p>He said his problems are too complex for AI. It’s easier for him to just write the code than explain it to a chatbot.</p>
<p>He does agree that AI is good for boring or repetitive code.</p>
<blockquote class="prompt-tip">
<p><a href="https://github.com/asottile">Anthony's GitHub</a> · <a href="https://www.youtube.com/c/anthonywritescode">YouTube – Anthony Writes Code</a></p>
</blockquote>
<hr>
<h2 id="reuven-m-lerner-python-author-trainer" tabindex="-1"><a class="header-anchor" href="#reuven-m-lerner-python-author-trainer">Reuven M. Lerner - Python Author &amp; Trainer</a></h2>
<p>Reuven said companies should <em>not</em> replace junior devs.</p>
<p>He said juniors are like seeds. If companies don’t invest in them now, they’ll struggle later when they only have senior
devs and AI bots.</p>
<p>He uses AI mostly for writing—like reviewing posts or generating images. He rarely uses it for coding.</p>
<blockquote class="prompt-tip">
<p><a href="https://lerner.co.il/">Reuven's Website – lerner.co.il</a> · Great courses and newsletters for Python devs</p>
</blockquote>
<hr>
<h2 id="trey-hunner-python-morsels" tabindex="-1"><a class="header-anchor" href="#trey-hunner-python-morsels">Trey Hunner — Python Morsels</a></h2>
<p>Trey said AI won’t replace developers.</p>
<p>But it might change how we work.<br>
Developers might end up reading and reviewing more code instead of writing everything from scratch.</p>
<p>He doesn’t use Copilot or coding tools. He copies from ChatGPT when needed and uses a tool called Typingmind to talk to
AI.</p>
<blockquote class="prompt-tip">
<p><a href="https://treyhunner.com/">Trey's Website</a> · <a href="https://www.pythonmorsels.com/">Python Morsels</a></p>
</blockquote>
<p>It is also worth a mention that Trey taught us the <a href="https://treyhunner.com/2015/06/cabo-card-game/">Cabo card game</a>
during the evenings after PyCon.</p>
<hr>
<h2 id="samuel-colvin-founder-of-pydantic" tabindex="-1"><a class="header-anchor" href="#samuel-colvin-founder-of-pydantic">Samuel Colvin — Founder of Pydantic</a></h2>
<p>Samuel had a different view.<br>
He said AI <em>will</em> replace some programmers.</p>
<p>Companies will use AI to do more with fewer people.<br>
He sees AI as a way to speed things up and make work more efficient.</p>
<p>He uses tools like <code>trae</code>, <code>cline</code>, and others to boost his workflow.</p>
<blockquote class="prompt-tip">
<p><a href="https://scolvin.com/">Samuel's Website</a> · <a href="https://github.com/samuelcolvin">GitHub</a></p>
</blockquote>
<hr>
<h2 id="simon-willison-django-co-creator-creator-of-datasette" tabindex="-1"><a class="header-anchor" href="#simon-willison-django-co-creator-creator-of-datasette">Simon Willison — Django co-creator, Creator of Datasette</a></h2>
<p>Simon said AI won’t replace developers. But it will create new types of work and improve productivity.</p>
<p>He uses Claude, ChatGPT, and his own LLM CLI tool.<br>
For personal scripts, he uses AI to get things done quickly, and reviews the code properly when it matters.</p>
<blockquote class="prompt-tip">
<p><a href="https://simonwillison.net/">Simon's Website</a> · <a href="https://github.com/simonw">GitHub</a></p>
</blockquote>
<hr>
<h2 id="mariatta-wijaya-python-core-developer" tabindex="-1"><a class="header-anchor" href="#mariatta-wijaya-python-core-developer">Mariatta Wijaya — Python Core Developer</a></h2>
<p>Mariatta said AI is more helpful for senior developers.</p>
<p>Seniors can use AI to write code quickly and then fix what’s wrong.<br>
For juniors, it depends. If the task is small, AI can help. But juniors still need to understand what the code does.</p>
<p>She uses Copilot in her daily work.</p>
<p>She also recommends this talk from PyTexas:<br>
<a href="https://www.youtube.com/watch?v=FP9VoFPJsiQ">Heather’s Talk – How to Think Like a Senior Engineer</a></p>
<blockquote class="prompt-tip">
<p><a href="https://mariatta.ca/">Mariatta's Website</a> · <a href="https://github.com/mariatta">GitHub</a></p>
</blockquote>
<hr>
<h2 id="final-thoughts" tabindex="-1"><a class="header-anchor" href="#final-thoughts">Final Thoughts</a></h2>
<p>Most Python people agree: AI won’t replace developers anytime soon.</p>
<p>My thoughts: it <em>will</em> definitely change how we work.</p>
<p>If you’re using AI without understanding, you’ll likely fall behind.<br>
If you keep learning and adapt, you’ll do fine.</p>
<hr>
<h3 id="my-personal-favorite-moment" tabindex="-1"><a class="header-anchor" href="#my-personal-favorite-moment">My personal favorite moment</a></h3>
<p>One of the highlights of PyCon US for me was meeting <strong>Paul Everitt</strong>.</p>
<p>He gave me a sneak peek of the Python documentary trailer <em>before</em> it was released on YouTube. And even better—he handed
me a <strong>chocolate Cornetto</strong> from the PyCharm booth while telling stories. Instant core memory.</p>
<p>Paul is one of the most enthusiastic and genuine people I met at the conference. A natural storyteller.</p>
<p>A few things I didn’t know about him until then:</p>
<ul>
<li>
<p>He managed <a href="https://www.navy.mil">navy.mil</a> while serving in the U.S. military—one of the earliest websites on the
internet.</p>
</li>
<li>
<p>He was a keynote speaker at the <strong>first-ever Python conference</strong> in 1994.</p>
</li>
<li>
<p>He helped <strong>incorporate the Python Software Foundation</strong>.</p>
</li>
<li>
<p>He was Guido’s manager during his Plone days.</p>
</li>
</ul>
<p>The kind of person you meet once and never forget.</p>
<hr>
<p>That’s all from my hallway track this year.</p>
]]></content>
  </entry>
</feed>
