<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Ayoola’s Substack]]></title><description><![CDATA[My personal Substack]]></description><link>https://ayoolaolafenwa.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!Y-DC!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a229e10-eb1f-4f67-99f6-91f9e723d5c3_144x144.png</url><title>Ayoola’s Substack</title><link>https://ayoolaolafenwa.substack.com</link></image><generator>Substack</generator><lastBuildDate>Tue, 23 Jun 2026 15:27:05 GMT</lastBuildDate><atom:link href="https://ayoolaolafenwa.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Ayoola Olafenwa]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[ayoolaolafenwa@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[ayoolaolafenwa@substack.com]]></itunes:email><itunes:name><![CDATA[Ayoola Olafenwa]]></itunes:name></itunes:owner><itunes:author><![CDATA[Ayoola Olafenwa]]></itunes:author><googleplay:owner><![CDATA[ayoolaolafenwa@substack.com]]></googleplay:owner><googleplay:email><![CDATA[ayoolaolafenwa@substack.com]]></googleplay:email><googleplay:author><![CDATA[Ayoola Olafenwa]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Single Agent vs Multi-Agent: When to Build a Multi-Agent System]]></title><description><![CDATA[A practical guide to understanding AI agent design, ReAct workflows and when multi-agent systems become useful.]]></description><link>https://ayoolaolafenwa.substack.com/p/single-agent-vs-multi-agent-when</link><guid isPermaLink="false">https://ayoolaolafenwa.substack.com/p/single-agent-vs-multi-agent-when</guid><dc:creator><![CDATA[Ayoola Olafenwa]]></dc:creator><pubDate>Wed, 29 Apr 2026 08:50:53 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RKLS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a3ff39-7715-4e8b-bd63-2ed59831a787_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!RKLS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a3ff39-7715-4e8b-bd63-2ed59831a787_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!RKLS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a3ff39-7715-4e8b-bd63-2ed59831a787_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!RKLS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a3ff39-7715-4e8b-bd63-2ed59831a787_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!RKLS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a3ff39-7715-4e8b-bd63-2ed59831a787_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!RKLS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a3ff39-7715-4e8b-bd63-2ed59831a787_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!RKLS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a3ff39-7715-4e8b-bd63-2ed59831a787_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b8a3ff39-7715-4e8b-bd63-2ed59831a787_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1351645,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://ayoolaolafenwa.substack.com/i/195580240?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a3ff39-7715-4e8b-bd63-2ed59831a787_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!RKLS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a3ff39-7715-4e8b-bd63-2ed59831a787_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!RKLS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a3ff39-7715-4e8b-bd63-2ed59831a787_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!RKLS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a3ff39-7715-4e8b-bd63-2ed59831a787_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!RKLS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8a3ff39-7715-4e8b-bd63-2ed59831a787_1672x941.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>AI Agents</h2><p>AI agents have become popular because modern LLMs are now highly capable at tasks like coding, writing, reasoning, and solving problems across different fields. This has reduced the need to train custom models and shifted more attention toward building practical applications around existing LLMs. Tools like Codex, Claude Code, Cursor and Windsurf are already helping software engineers work faster, while businesses use agents for customer support, automation and other real-world tasks.</p><p>An AI agent is an application that uses an LLM to reason, plan and use tools to perform tasks, allowing the model to interact with its environment in a practical and useful way.</p><h2>Components of an AI Agent</h2><p>Some of the major components of most AI agents are the <strong>LLM, tools and memory</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4uqi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F270c8f80-d19a-4da3-85fc-dbeb940d8e06_1448x1086.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4uqi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F270c8f80-d19a-4da3-85fc-dbeb940d8e06_1448x1086.png 424w, https://substackcdn.com/image/fetch/$s_!4uqi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F270c8f80-d19a-4da3-85fc-dbeb940d8e06_1448x1086.png 848w, https://substackcdn.com/image/fetch/$s_!4uqi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F270c8f80-d19a-4da3-85fc-dbeb940d8e06_1448x1086.png 1272w, https://substackcdn.com/image/fetch/$s_!4uqi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F270c8f80-d19a-4da3-85fc-dbeb940d8e06_1448x1086.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4uqi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F270c8f80-d19a-4da3-85fc-dbeb940d8e06_1448x1086.png" width="1448" height="1086" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/270c8f80-d19a-4da3-85fc-dbeb940d8e06_1448x1086.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1086,&quot;width&quot;:1448,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:931146,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ayoolaolafenwa.substack.com/i/195580240?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F270c8f80-d19a-4da3-85fc-dbeb940d8e06_1448x1086.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4uqi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F270c8f80-d19a-4da3-85fc-dbeb940d8e06_1448x1086.png 424w, https://substackcdn.com/image/fetch/$s_!4uqi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F270c8f80-d19a-4da3-85fc-dbeb940d8e06_1448x1086.png 848w, https://substackcdn.com/image/fetch/$s_!4uqi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F270c8f80-d19a-4da3-85fc-dbeb940d8e06_1448x1086.png 1272w, https://substackcdn.com/image/fetch/$s_!4uqi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F270c8f80-d19a-4da3-85fc-dbeb940d8e06_1448x1086.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><ul><li><p><strong>LLM:</strong> This is the brain of the AI agent. It is the large language model that enables the agent to reason, plan, and decide how to solve a given task.</p><p></p></li><li><p><strong>Tools:</strong> These are helpers, usually in the form of code functions, that allow the LLM to interact with its environment. Tools help the agent connect to external data sources, search the internet, retrieve information from databases, access files, and carry out specific actions. For example, coding agents can use tools to write, debug, and save files, research agents can use web search or vector databases to gather information and customer support agents can use internal company documents to answer questions based on trusted business knowledge.</p><p></p></li><li><p><strong>Memory:</strong> This allows the agent to store relevant information from interactions and use it later to provide better and more consistent assistance. It helps the agent maintain context across tasks and improve the overall user experience.</p><p>Memory may be optional during early development, but it becomes an important part of many real-world AI agent systems, especially when the agent needs to handle follow-up questions, multi-step workflows or personalised interactions.</p><p>There are two major types of memory commonly used in AI agents: short-term memory and long-term memory. Short-term memory keeps track of information within the current session or task, while long-term memory stores useful information across multiple sessions or chats so the agent can use it later.</p><p></p></li></ul><h2><strong>ReAct (Reasoning + Acting) in Agents</strong></h2><p>An AI agent differs from a basic chatbot because a chatbot usually follows a more direct workflow: <strong>user query &#8594; LLM &#8594; response</strong>. The LLM receives the user&#8217;s message and generates a reply based mainly on the prompt and its existing context.</p><p>An AI agent goes beyond this by using the LLM to reason about the task, decide what needs to be done, choose whether tools are needed, call those tools, observe the results and continue until it can produce a useful answer.</p><p>This is where the <strong>ReAct</strong> approach comes in. <strong>ReAct means Reasoning + Acting</strong>. It is an agent pattern where the LLM reasons about a task and takes actions, usually through tools, based on that reasoning. It involves designing a core logic loop around an LLM. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8SpV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb149e4-3cce-425e-9c2c-800c1dc82683_1448x590.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8SpV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb149e4-3cce-425e-9c2c-800c1dc82683_1448x590.png 424w, https://substackcdn.com/image/fetch/$s_!8SpV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb149e4-3cce-425e-9c2c-800c1dc82683_1448x590.png 848w, https://substackcdn.com/image/fetch/$s_!8SpV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb149e4-3cce-425e-9c2c-800c1dc82683_1448x590.png 1272w, https://substackcdn.com/image/fetch/$s_!8SpV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb149e4-3cce-425e-9c2c-800c1dc82683_1448x590.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8SpV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb149e4-3cce-425e-9c2c-800c1dc82683_1448x590.png" width="1448" height="590" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/adb149e4-3cce-425e-9c2c-800c1dc82683_1448x590.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:590,&quot;width&quot;:1448,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:604509,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ayoolaolafenwa.substack.com/i/195580240?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb149e4-3cce-425e-9c2c-800c1dc82683_1448x590.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8SpV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb149e4-3cce-425e-9c2c-800c1dc82683_1448x590.png 424w, https://substackcdn.com/image/fetch/$s_!8SpV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb149e4-3cce-425e-9c2c-800c1dc82683_1448x590.png 848w, https://substackcdn.com/image/fetch/$s_!8SpV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb149e4-3cce-425e-9c2c-800c1dc82683_1448x590.png 1272w, https://substackcdn.com/image/fetch/$s_!8SpV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fadb149e4-3cce-425e-9c2c-800c1dc82683_1448x590.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>A basic ReAct workflow in an AI agent usually looks like this:</h3><ul><li><p><strong>Step 1: The agent receives a user query</strong></p><p>The LLM reasons over the task and decides whether it can answer directly or needs to use tools. It checks what tools are available and decides which ones are needed to solve the task.</p></li><li><p><strong>Step 2: The agent calls the required tools</strong></p><p>Based on its reasoning, the agent takes action by calling the necessary tools. These tools may search the web, retrieve documents from a vector database, access files, run code or connect to an external API. The results returned from these tools are known as <strong>tool outputs</strong>.</p></li><li><p><strong>Step 3: The tool outputs are sent back to the LLM</strong></p><p>The tool outputs are passed back to the LLM as additional context. This gives the agent more relevant information to work with instead of relying only on the original prompt.</p></li><li><p><strong>Step 4: The LLM checks the evidence and generates a response</strong></p><p>The LLM reviews the tool outputs and checks whether they are enough to solve the task. If the evidence is sufficient, it generates a grounded response for the user. If not, the agent may repeat the reasoning, tool-calling and observation steps until it has enough information to provide a useful answer.</p><p></p></li></ul><h2>Structure of AI Agents</h2><p>AI Agents can either be single or multi depending on the design structure.</p><h3>Single Agent vs Multi-Agent</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Rm_6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3369e108-ca4c-4857-856d-7f3e5da3ba84_1447x1087.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Rm_6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3369e108-ca4c-4857-856d-7f3e5da3ba84_1447x1087.png 424w, https://substackcdn.com/image/fetch/$s_!Rm_6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3369e108-ca4c-4857-856d-7f3e5da3ba84_1447x1087.png 848w, https://substackcdn.com/image/fetch/$s_!Rm_6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3369e108-ca4c-4857-856d-7f3e5da3ba84_1447x1087.png 1272w, https://substackcdn.com/image/fetch/$s_!Rm_6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3369e108-ca4c-4857-856d-7f3e5da3ba84_1447x1087.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Rm_6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3369e108-ca4c-4857-856d-7f3e5da3ba84_1447x1087.png" width="1447" height="1087" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3369e108-ca4c-4857-856d-7f3e5da3ba84_1447x1087.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1087,&quot;width&quot;:1447,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1395976,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ayoolaolafenwa.substack.com/i/195580240?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3369e108-ca4c-4857-856d-7f3e5da3ba84_1447x1087.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Rm_6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3369e108-ca4c-4857-856d-7f3e5da3ba84_1447x1087.png 424w, https://substackcdn.com/image/fetch/$s_!Rm_6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3369e108-ca4c-4857-856d-7f3e5da3ba84_1447x1087.png 848w, https://substackcdn.com/image/fetch/$s_!Rm_6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3369e108-ca4c-4857-856d-7f3e5da3ba84_1447x1087.png 1272w, https://substackcdn.com/image/fetch/$s_!Rm_6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3369e108-ca4c-4857-856d-7f3e5da3ba84_1447x1087.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A single agent is an agent design where one LLM handles the whole task. It reasons, plans and calls the required tools when needed. Most AI agents start as single-agent systems because they are simpler, easier to maintain and usually enough for many tasks.</p><p>A multi-agent system uses specialised agents to solve different parts of a task. It often has a central agent, usually called an <strong>orchestrator</strong>, <strong>supervisor</strong> or <strong>planner</strong>, that coordinates the other agents and decides when each one should act. Each specialised agent can have its own role, tools and reasoning logic, making the system more modular and suitable for complex workflows.</p><h3>When to Build A Multi-Agent System</h3><p>A single-agent design works well for simple tasks that require limited tool use. For example, a personal assistant agent that can access your calendar to book reminders, a calculator agent that only uses a calculator tool, or a web search agent that uses a web search API to retrieve up-to-date information.</p><p>However, a single agent can become overloaded when the task requires many tools, multi-step reasoning, different responsibilities or verification before the final response is returned to the user. Common issues include overloaded prompting, poor tool routing, unclear agent responsibilities and reduced reliability due to too much complexity in one agent.</p><p>A <strong>multi-agent system</strong> is a better choice when the task may overwhelm a single-agent design and when you need specialised agents with clear roles, their own tools and separate responsibilities.</p><p>For example, a <strong>software engineering agent</strong> may work better as a multi-agent system:</p><pre><code>Orchestrator &#8594; Coder &#8594; Tester &#8594; Reviewer</code></pre><p>The <strong>Orchestrator</strong> coordinates the workflow, the <strong>Coder</strong> <strong>agent </strong>generates the code, the <strong>Tester</strong> <strong>agent</strong> checks whether the code works, and the <strong>Reviewer</strong> <strong>agent </strong>reviews the solution to check for missing parts or possible improvements.</p><p>Another example is a <strong>research agent</strong> that researches a topic, retrieves information from different data sources and generates grounded content:</p><pre><code>Orchestrator &#8594; Retriever &#8594; Writer &#8594; Verifier</code></pre><p>The <strong>Retriever</strong> <strong>agent </strong>gathers information from the web and local documents stored in a vector database. The <strong>Writer</strong> <strong>agent</strong> writes based on the retrieved content. The <strong>Verifier</strong> <strong>agent </strong>checks the written content for errors, citations and factual accuracy before the final response is returned.</p><p>Multi-agent systems make the workflow more modular and give each stage a clear role. However, they should be used only when the task genuinely needs that design, because they usually increase latency, cost and maintenance complexity due to more LLM calls and more moving parts.</p><p>A simple rule is:</p><p><em>Use a single agent when the task is simple, has fewer steps and needs only a few tools. Use a multi-agent system when the task requires specialised roles, multi-step reasoning, stronger verification or coordination across different tools and workflows.</em></p><h2>Walkthrough of A Multi-Agent Project</h2><p>To make the idea of multi-agent systems more practical, I built a project called <strong>Multi-Agent RAG Researcher</strong>.</p><p>The goal of the project is to show how a central agent can coordinate multiple specialised agents to research a topic, retrieve evidence from documents and the web, write a grounded content and verify the content before returning it to the user.  Instead of using one agent to handle everything, the system splits the workflow into different responsibilities.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!m9ps!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f34073e-2903-4b55-bff6-6f72e2c18093_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!m9ps!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f34073e-2903-4b55-bff6-6f72e2c18093_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!m9ps!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f34073e-2903-4b55-bff6-6f72e2c18093_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!m9ps!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f34073e-2903-4b55-bff6-6f72e2c18093_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!m9ps!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f34073e-2903-4b55-bff6-6f72e2c18093_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!m9ps!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f34073e-2903-4b55-bff6-6f72e2c18093_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5f34073e-2903-4b55-bff6-6f72e2c18093_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1219015,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ayoolaolafenwa.substack.com/i/195580240?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f34073e-2903-4b55-bff6-6f72e2c18093_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!m9ps!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f34073e-2903-4b55-bff6-6f72e2c18093_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!m9ps!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f34073e-2903-4b55-bff6-6f72e2c18093_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!m9ps!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f34073e-2903-4b55-bff6-6f72e2c18093_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!m9ps!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f34073e-2903-4b55-bff6-6f72e2c18093_1672x941.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Check the project on github: <a href="https://github.com/ayoolaolafenwa/multi-agent-rag-researcher">https://github.com/ayoolaolafenwa/multi-agent-rag-researcher</a></p><p><strong>Clone Project repo</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;3235e18a-b6af-43d4-aa23-01dfefcef7ce&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">git clone https://github.com/ayoolaolafenwa/multi-agent-rag-researcher.git</code></pre></div><p>Clone the repo to followup with the code along the post. When the repo is cloned, the project structure will look like this: </p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;3b3bdb0f-ea35-4aac-8ae1-7868d30ec3e1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">.
&#9500;&#9472;&#9472; docs/                         # Default PDF files
&#9500;&#9472;&#9472; memory/                       # SQLite-backed session memory helpers
&#9500;&#9472;&#9472; qdrant_vector_database/       # PDF ingestion and similarity search
&#9500;&#9472;&#9472; ui/                           # Gradio app and UI handlers
&#9500;&#9472;&#9472; utils/
&#9474;   &#9500;&#9472;&#9472; requirements.txt          # Python dependencies
&#9500;&#9472;&#9472; worker_agents/                # Retriever, writer, and verifier
&#9500;&#9472;&#9472; orchestrator_agent.py         # Main coordinator
&#9492;&#9472;&#9472; run_orchestrator.py           # CLI entry point</code></pre></div><h3><strong>Multi-Agent Architecture</strong></h3><h4><strong>Data Sources</strong></h4><p>There are two major data sources: </p><ol><li><p><code>Qdrant Vector Database</code></p></li></ol><p>Information retrieval from PDFs is handled in the following stages:</p><ul><li><p>Multiple PDFs can be loaded from the <code>docs/</code> folder or uploaded through the UI.</p></li><li><p>Documents are split into chunks, converted into embeddings, and stored in a local Qdrant collection.</p></li><li><p>Similarity search is then used to retrieve the most relevant chunks across the indexed documents.</p></li><li><p>The retrieved chunks include citation metadata such as document name and page number.</p></li></ul><blockquote><p>The document retrieval part of the project where Qdrant vector database is setup, PDF ingestion, chunking, embedding, and similarity search are managed is handled in  <a href="https://github.com/ayoolaolafenwa/multi-agent-rag-researcher/blob/main/qdrant_vector_database/vector_store.py">qdrant_vector_database/vector_store.py</a>  . </p></blockquote><ol start="2"><li><p><code>Tavily Web Search</code></p></li></ol><p>Tavily is used to retrieve up-to-date or external information from the web. The retriever agent can use it when:</p><ul><li><p>the indexed PDFs do not cover the query</p></li><li><p>document evidence is weak or incomplete</p></li><li><p>newer information is needed</p></li></ul><h3><strong>Worker Agents</strong></h3><ol><li><p><code>Retriever Agent</code></p></li></ol><p>The role is:</p><ul><li><p>It uses two tools: PDF document retrieval and web search.</p></li><li><p>Given a query, it decides whether to use local documents, web search or both.</p></li><li><p>If local document evidence is missing or weak, it can fall back to web search to gather broader or more up-to-date context.</p></li></ul><blockquote><p>The code for the retriever agent with tavily web search available in  <a href="https://github.com/ayoolaolafenwa/multi-agent-rag-researcher/blob/main/worker_agents/retriever_agent.py">worker_agents/retriever.py</a> .  It uses gpt-5.4-mini with low reasoning effort. </p></blockquote><p></p><ol start="2"><li><p><code>Writer Agent</code></p></li></ol><p>The role is:</p><ul><li><p>It receives the retrieved information from the Retriever Agent.</p></li><li><p>It writes a grounded draft based on the available evidence.</p></li><li><p>It includes supporting citations from PDFs or web sources when they are available.</p></li></ul><blockquote><p>The code for the writer agent available in  <a href="https://github.com/ayoolaolafenwa/multi-agent-rag-researcher/blob/main/worker_agents/writer_agent.py">worker_agents/writer.py</a> . It uses gpt-5.4 with low reasoning effort. </p></blockquote><ol start="3"><li><p><code>Verifier Agent</code></p></li></ol><p>The role is:</p><ul><li><p>It receives the draft from the Writer Agent together with the evidence.</p></li><li><p>It checks whether the claims in the draft are supported by the retrieved evidence.</p></li><li><p>It returns the final verified response.</p></li></ul><blockquote><p>The code for the worker agent is available in <a href="https://github.com/ayoolaolafenwa/multi-agent-rag-researcher/blob/main/worker_agents/verifier_agent.py">worker_agents/verifier.py</a> . It uses gpt-5.4 with low reasoning effort. </p></blockquote><p></p><h3><strong>Memory</strong></h3><p>SQLite is used to provide short-term memory for the multi-agent workflow. For a given session ID, the system stores:</p><ul><li><p>the latest user query</p></li><li><p>the latest retrieved evidence for that session</p></li></ul><p>This allows the orchestrator to reuse relevant evidence for follow-up questions instead of retrieving the same information again every time.</p><blockquote><p>The code for the memory is available in <a href="https://github.com/ayoolaolafenwa/multi-agent-rag-researcher/blob/main/memory/memory.py">memory/memory.py</a> . </p></blockquote><h3><strong>Orchestrator</strong></h3><p>The orchestrator coordinates the three worker agents: <strong>Retriever</strong>, <strong>Writer</strong> and <strong>Verifier</strong>.</p><h4><strong>How the Orchestrator coordinates the Multi-Agent Workflow</strong></h4><ul><li><p>It receives the user query and, depending on the query, may respond directly or begin the evidence-based workflow.</p></li><li><p>For a research query, it first checks whether relevant cached evidence from the memory for the current session can be reused.</p></li><li><p>If cached evidence is not enough, it calls the <strong>Retriever Agent</strong> to gather evidence from PDFs, the web or both.</p></li><li><p>If there is document evidence but the evidence is weak, the <strong>Retriever Agent</strong> can also fetch up-to-date information from the web to supplement the local document information.</p></li><li><p>The orchestrator then passes the active evidence and the user query to the <strong>Writer Agent</strong> so it can generate a grounded draft.</p></li><li><p>Next, it sends the draft and evidence to the <strong>Verifier Agent</strong>, which checks the claims and returns the final verified report.</p></li><li><p>During the session, the latest query and retrieved evidence are stored in memory for follow-up questions.</p></li><li><p>In follow-up questions, the orchestrator may reuse cached evidence instead of calling the <strong>Retriever Agent</strong> again, then continue with the <strong>Writer Agent</strong> and <strong>Verifier Agent</strong> to generate the final response.</p><p></p></li></ul><blockquote><p>The code for the orchestrator is in <a href="https://github.com/ayoolaolafenwa/multi-agent-rag-researcher/blob/main/orchestrator_agent.py">orchestrator_agent.py</a> . It uses gpt-5.4-mini with low reasoning effort.</p><p>The orchestrator has a guardrail that keeps the system focused on research and factual questions. It refuses unrelated general tasks such as coding help or simple math because the goal of the system is to function as a research assistant.</p></blockquote><h4><strong>Note: </strong>For the models used in the orchestrator and worker agents, you can change them from gpt-5.4 to any openai provided model of your choice. </h4><h3><strong>Setup Project</strong></h3><h4><strong>Prerequisites</strong></h4><ul><li><p>Python 3.10 or newer</p></li><li><p>OpenAI API key: <a href="https://auth.openai.com/create-account">Create an OpenAI Account</a> if you don&#8217;t have one and <a href="https://platform.openai.com/api-keys">Generate an API Key</a>.</p></li><li><p>Tavily API key: Tavily is a specialized web-search tool for AI agents. Create an account on <a href="https://www.tavily.com/">Tavily.com</a>, once your profile is set up, an API key will be generated that you can copy into your environment. New account receives 1000 free credits that can be used for up to 1000 web searches.</p></li></ul><h4><strong>Installation</strong></h4><ol><li><p>Create and activate a virtual environment:</p></li></ol><pre><code>python3 -m venv env
source env/bin/activate</code></pre><ol start="3"><li><p>Install the dependencies:</p></li></ol><pre><code>cd multi-agent-rag-researcher
pip3 install -r utils/requirements.txt</code></pre><ol start="4"><li><p>Create a <code>utils/var.env</code> file and store your API keys:</p></li></ol><pre><code>OPENAI_API_KEY=your_openai_api_key
TAVILY_API_KEY=your_tavily_api_key</code></pre><ol start="5"><li><p>Place the PDFs you want to index in the <code>docs/</code> folder, or upload PDFs later through the UI. The project already includes existing PDFs in <code>docs/</code>, currently <em>Gemma 3 Technical Report.pdf</em> and <em>DeepSeek-V3.2.pdf</em>, so you can use those directly or replace them with your own documents.</p></li></ol><h4><strong>Run Project</strong></h4><p>Start the command-line app:</p><pre><code>python3 run_orchestrator.py</code></pre><p>When the CLI starts, it ingests the PDFs in <code>docs/</code> into the local Qdrant store. Type <code>q</code> or <code>exit</code> to end the session.</p><h4><strong>Run UI for Multi-Agent Chat</strong></h4><p>Start the Gradio UI:</p><pre><code>python3 ui/gradio_app.py</code></pre><p>The UI automatically loads the default PDFs from <code>docs/</code> on startup. If you upload new PDFs, they replace the active indexed document set for that UI session.</p><h3>Demo Video Showing the Multi Agent Agent RAG Researcher in Action</h3><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;a453c2d3-67de-4d2c-b8d2-1fa488ebd615&quot;,&quot;duration&quot;:null}"></div><h3><strong>Notes</strong></h3><ul><li><p>Session memory is stored in <code>utils/memory.db</code>.</p></li><li><p>Local Qdrant data is stored in <code>utils/qdrant_storage/</code>.</p></li><li><p>The system is designed for research and factual question answering, not for unrelated general-purpose tasks.</p></li></ul><h3>Conclusion</h3><p>In this post, I explained how an AI agent works, how it uses tools to interact with its environment, and how the ReAct approach helps it reason, plan, select tools and execute specific tasks.</p><p>I also covered the structural design of AI agents, which can be single-agent or multi-agent systems. I explained how both designs work, when to choose each one based on the workflow, and compared single-agent implementation with multi-agent architecture.</p><p>Finally, I did a walkthrough of the multi-agent design behind my Multi-Agent RAG Researcher project, showing how it uses an orchestrator to coordinate three worker agents, retrieve information from the web and local documents, use memory for consistency and write and verify grounded content before returning the final output.</p><h3><strong>Reach to me via:</strong></h3><blockquote><p>Email: <a href="https://mail.google.com/mail/u/0/#inbox">olafenwaayoola@gmail.com</a></p><p>Linkedin: <a href="https://www.linkedin.com/in/ayoola-olafenwa-003b901a9/">https://www.linkedin.com/in/ayoola-olafenwa-003b901a9/</a></p></blockquote><h3>References</h3><p><a href="https://developers.openai.com/cookbook">https://developers.openai.com/cookbook</a></p><p><a href="https://developers.openai.com/api/docs/guides/function-calling">https://developers.openai.com/api/docs/guides/function-calling</a></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ayoolaolafenwa.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Ayoola&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Building an Agentic RAG with Function Calling]]></title><description><![CDATA[How Agentic RAG Works and How to Build One]]></description><link>https://ayoolaolafenwa.substack.com/p/building-an-agentic-rag-with-function</link><guid isPermaLink="false">https://ayoolaolafenwa.substack.com/p/building-an-agentic-rag-with-function</guid><dc:creator><![CDATA[Ayoola Olafenwa]]></dc:creator><pubDate>Fri, 31 Oct 2025 11:53:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!PzF6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed02dc64-dd86-4062-84ce-eb456e61af1a_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3></h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PzF6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed02dc64-dd86-4062-84ce-eb456e61af1a_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PzF6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed02dc64-dd86-4062-84ce-eb456e61af1a_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!PzF6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed02dc64-dd86-4062-84ce-eb456e61af1a_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!PzF6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed02dc64-dd86-4062-84ce-eb456e61af1a_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!PzF6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed02dc64-dd86-4062-84ce-eb456e61af1a_1024x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PzF6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed02dc64-dd86-4062-84ce-eb456e61af1a_1024x1024.png" width="1024" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ed02dc64-dd86-4062-84ce-eb456e61af1a_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!PzF6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed02dc64-dd86-4062-84ce-eb456e61af1a_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!PzF6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed02dc64-dd86-4062-84ce-eb456e61af1a_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!PzF6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed02dc64-dd86-4062-84ce-eb456e61af1a_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!PzF6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed02dc64-dd86-4062-84ce-eb456e61af1a_1024x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>AI agent connects a Large Language Model (LLM) to external tools, allowing it to interact with the world and complete tasks like writing code and building projects. Retrieval-Augmented Generation (RAG) improves LLM responses by retrieving relevant information. RAG systems apply the core ideas of AI agents, which makes them more accurate, efficient and more autonomous.</p><h3>Vanilla RAG vs Agentic RAG</h3><p>A basic Retrieval-Augmented Generation (RAG) setup includes a Large Language Model (LLM) for text generation, an embedding model for finding similar text, and a vector database for fast storage and retrieval. For each user prompt, the embedding model processes the prompt and searches the vector database for similar context. That context is then added to the prompt and sent to the LLM to generate a more accurate response.</p><h4>Basic Structure of Vanilla RAG</h4><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XjKx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac4b267c-c2d5-46c1-8aa1-9cdb53e8a7e4_992x147.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XjKx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac4b267c-c2d5-46c1-8aa1-9cdb53e8a7e4_992x147.jpeg 424w, https://substackcdn.com/image/fetch/$s_!XjKx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac4b267c-c2d5-46c1-8aa1-9cdb53e8a7e4_992x147.jpeg 848w, https://substackcdn.com/image/fetch/$s_!XjKx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac4b267c-c2d5-46c1-8aa1-9cdb53e8a7e4_992x147.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!XjKx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac4b267c-c2d5-46c1-8aa1-9cdb53e8a7e4_992x147.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XjKx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac4b267c-c2d5-46c1-8aa1-9cdb53e8a7e4_992x147.jpeg" width="992" height="147" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ac4b267c-c2d5-46c1-8aa1-9cdb53e8a7e4_992x147.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:147,&quot;width&quot;:992,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!XjKx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac4b267c-c2d5-46c1-8aa1-9cdb53e8a7e4_992x147.jpeg 424w, https://substackcdn.com/image/fetch/$s_!XjKx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac4b267c-c2d5-46c1-8aa1-9cdb53e8a7e4_992x147.jpeg 848w, https://substackcdn.com/image/fetch/$s_!XjKx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac4b267c-c2d5-46c1-8aa1-9cdb53e8a7e4_992x147.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!XjKx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac4b267c-c2d5-46c1-8aa1-9cdb53e8a7e4_992x147.jpeg 1456w" sizes="100vw"></picture><div></div></div></a></figure></div><p>The Vanilla RAG system follows a static workflow and is not very efficient because the Large Language Model cannot decide when to retrieve information and when not to. It is also limited in retrieving data from multiple sources.</p><h4>Basic Structure of an Agentic RAG</h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xbJx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44fe16f2-e512-4f99-a2d5-30864c413a52_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xbJx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44fe16f2-e512-4f99-a2d5-30864c413a52_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!xbJx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44fe16f2-e512-4f99-a2d5-30864c413a52_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!xbJx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44fe16f2-e512-4f99-a2d5-30864c413a52_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!xbJx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44fe16f2-e512-4f99-a2d5-30864c413a52_1024x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xbJx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44fe16f2-e512-4f99-a2d5-30864c413a52_1024x1024.png" width="1024" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/44fe16f2-e512-4f99-a2d5-30864c413a52_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!xbJx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44fe16f2-e512-4f99-a2d5-30864c413a52_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!xbJx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44fe16f2-e512-4f99-a2d5-30864c413a52_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!xbJx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44fe16f2-e512-4f99-a2d5-30864c413a52_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!xbJx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44fe16f2-e512-4f99-a2d5-30864c413a52_1024x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Agentic Retrieval-Augmented Generation (RAG) builds on standard RAG by adding automation that lets the Large Language Model decide when it should retrieve information and when it doesn&#8217;t need to. An Agentic RAG system can use multiple data sources for context, such as PDFs or web search. When a query is asked, the system first checks if it can answer from its own knowledge or if it needs to retrieve something.</p><p>For example, if the system has a PDF of financial records and the user asks about those records, it will retrieve the information from the PDF. If the user asks &#8220;What is the weather like today?&#8221;, it will run a web search. But if the user asks &#8220;Write a tutorial on Python,&#8221; it will generate the answer directly from what it already knows without calling any retrieval function.</p><h3>Building an Agentic RAG with Function Calling</h3><p>Function calling is a technique that connects a Large Language Model (LLM) to an external tool like an API or a database. It is used to create AI agents that let LLMs interact with tools.</p><p>In an agent, this usually works like this:</p><ul><li><p>A function is defined to call a specific API, such as a weather API to fetch the latest forecast.</p></li><li><p>A function can also be defined in a RAG-based agent to extract information from unstructured data like PDFs.</p></li><li><p>Custom instructions, usually written as a JSON schema, tell the LLM what to do when it sees a certain task and which tool (function) it should call to solve it.</p></li></ul><blockquote><p>In this post, I will build an Agentic RAG system using function calling, with one function that retrieves information from a local document and another that searches the internet for up-to-date information.</p></blockquote><h3>Prerequisite</h3><h3>Create OpenAI account and generate an API key</h3><p>1:<strong> </strong>Create an <a href="https://auth.openai.com/create-account">OpenAI Account</a> if you don&#8217;t have one</p><p>2: <a href="https://platform.openai.com/account/api-keys">Generate an API Key</a></p><h3>Set up and Activate Environment</h3><pre><code>python3 -m venv env
source env/bin/activate</code></pre><h3>Export OpenAI API Key</h3><pre><code>export OPENAI_API_KEY=&#8221;Your Openai API Key&#8221;</code></pre><h3>Setup Tavily for Web Search</h3><p>Tavily is a specialized web-search tool for AI agents. Create an account on <a href="https://www.tavily.com/">Tavily.com</a>, once your profile is set up, an API key will be generated that you can copy into your environment. New account receives 1000 free credits that can be used for up to 1000 web searches.</p><pre><code>export TAVILY_API_KEY=&#8221;Your Tavily API Key&#8221;</code></pre><h3>Install Packages</h3><pre><code>openai
tavily-python
qdrant-client
spacy
langchain
langchain-community
langchain-text-splitters
pypdf</code></pre><p>Paste the packages above in a <em><strong>requirements.txt file</strong></em> and install using:</p><pre><code>pip3 install -r requirements.txt</code></pre><h3>Process Local Document for RAG</h3><p>The local document to be used will be Veo 3 Model Card Paper. Veo 3 is a state of the art video generation model from Google. The goal of the agentic RAG project is for us to have an agent that can answer questions based on the local document whenever it is asked question about Veo 3 video Model and do a web search whenever a query requires up to date information.</p><h4>Load and Process PDF File</h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8jGo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db36d86-6ea4-4bb7-81f9-7f0ceee34988_1456x840.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8jGo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db36d86-6ea4-4bb7-81f9-7f0ceee34988_1456x840.jpeg 424w, https://substackcdn.com/image/fetch/$s_!8jGo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db36d86-6ea4-4bb7-81f9-7f0ceee34988_1456x840.jpeg 848w, https://substackcdn.com/image/fetch/$s_!8jGo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db36d86-6ea4-4bb7-81f9-7f0ceee34988_1456x840.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!8jGo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db36d86-6ea4-4bb7-81f9-7f0ceee34988_1456x840.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8jGo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db36d86-6ea4-4bb7-81f9-7f0ceee34988_1456x840.jpeg" width="1456" height="840" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6db36d86-6ea4-4bb7-81f9-7f0ceee34988_1456x840.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:840,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8jGo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db36d86-6ea4-4bb7-81f9-7f0ceee34988_1456x840.jpeg 424w, https://substackcdn.com/image/fetch/$s_!8jGo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db36d86-6ea4-4bb7-81f9-7f0ceee34988_1456x840.jpeg 848w, https://substackcdn.com/image/fetch/$s_!8jGo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db36d86-6ea4-4bb7-81f9-7f0ceee34988_1456x840.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!8jGo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6db36d86-6ea4-4bb7-81f9-7f0ceee34988_1456x840.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Download <a href="https://storage.googleapis.com/deepmind-media/Model-Cards/Veo-3-Model-Card.pdf">Veo 3 Model Card paper</a>. It is a 4-page PDF paper containing information on how Veo 3 was trained, the data used, its architecture, evaluation methods, and its ethics and safety.</p><pre><code>from langchain_community.document_loaders import PyPDFLoader

def load_pdf(pdf_path: str, page1_on_second: bool = True) -&gt; str:
    loader = PyPDFLoader(pdf_path)
    pages = loader.load()
    parts = []
    
    for p in pages:
        idx0 = p.metadata.get("page", 0)  
        label = idx0 if page1_on_second else (idx0 + 1)
        parts.append(f"[PAGE {label}]\n{p.page_content.strip()}")

    return "\n\n".join(parts).strip()
</code></pre><p>The <em><strong>load_pdf</strong></em> function extracts and returns content from a PDF file.</p><h4>Data Chunking and Overlapping</h4><p>LLMs can only read a fixed number of tokens per inference (the context window), which varies by model, from a few thousand to over a million. Since PDFs and other unstructured data often exceed that limit, chunking splits a document into smaller pieces that fit the window and support efficient retrieval. The main drawback is boundary loss, where important details can get cut at chunk edges. Overlapping fixes this by repeating a small slice of text between adjacent chunks, preserving continuity and improving accuracy and response quality in RAG.</p><pre><code>import spacy
from typing import List
from langchain_text_splitters import TokenTextSplitter
import re

def chunk_data(
    text: str,
    chunk_size: int = 1500,
    chunk_overlap: int = 225
) -&gt; List[str]:
    nlp = spacy.blank("en")
    if "sentencizer" not in nlp.pipe_names:
        nlp.add_pipe("sentencizer")

    splitter = TokenTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=chunk_overlap,
        encoding_name="cl100k_base",
    )

    chunks: List[str] = []

    parts = re.split(r"\n?\[PAGE\s+(\d+)\]\n", text)
    it = iter(parts[1:])
    for page_label, page_text in zip(it, it):
        sentences = [s.text.strip() for s in nlp(page_text).sents if s.text.strip()]
        page_chunks = splitter.split_text("\n".join(sentences))
        for chunk in page_chunks:
            chunks.append(f"[Page {page_label}] {chunk}")

    return chunks</code></pre><p>The <em><strong>chunk_data </strong></em>function takes in raw text, breaks it into page sections, cleans and splits each section into sentences with <em><strong>spaCy,</strong></em> then uses <em><strong>TokenTextSplitter</strong></em> (with the cl100k_base tokenizer) to generate overlapping 1,500-token chunks and tags each chunk with its page number.</p><p><strong>Note:</strong> The chosen chunk size and overlap are fine for small PDFs (the Veo 3 model card paper is only 4 pages). These values can be adjusted for larger documents that may contain hundreds or thousands of pages.</p><h3>Setup Qdrant for Vector Database</h3><p>Qdrant is an open-source vector database used for storing and retrieving vector embeddings. I will use Qdrant as the vector store for the RAG system.</p><pre><code>from qdrant_client.models import VectorParams, Distance
from qdrant_client.models import PointStruct
from qdrant_client import QdrantClient

qdrant_client = QdrantClient(":memory:")
collection_name = "vector_store"
qdrant_client.create_collection(
    collection_name = collection_name,
    vectors_config=VectorParams(
        size=1536,
        distance=Distance.COSINE,
),)</code></pre><p>This code initializes an in-memory Qdrant client and creates a collection called &#8220;vector_store&#8221; configured to store <em><strong>1536-dimensional embeddings</strong></em> and compare them using cosine similarity to find semantically similar text.</p><h3>Helper Code for uploading document to Vector Database</h3><p>The next step is to define the code that uploads the processed PDF file to the Qdrant vector database. I will use an embedding model from the OpenAI API to generate vector embeddings for the text, which will then be stored in the vector database.</p><pre><code>import os
from openai import OpenAI

openai_client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

def create_upload_embeddings(chunks):
    model_name = "text-embedding-3-small"
    response = openai_client.embeddings.create(input=chunks, model=model_name)

    embeddings = [record.embedding for record in response.data]

    points = [
        PointStruct(
            id=idx,
            vector=vec,
            payload={"text": text},
        )
        for idx, (vec, text) in enumerate(zip(embeddings, chunks))
    ]

    qdrant_client.upsert(
        collection_name=collection_name,
        wait=True,
        points=points,
    )</code></pre><p><em><strong>create_upload_embeddings</strong></em> function generates embeddings for the text chunks using the <em><strong>text-embedding-3-small</strong></em> model, then uploads each chunk and its embedding into the Qdrant collection for semantic search .</p><h4>Upload Local Document(PDF) to Vector Store</h4><pre><code>text = load_pdf("Veo-3-Model-Card.pdf")
chunks = chunk_data(text)

create_upload_embeddings(chunks)</code></pre><p>The PDF <em>&#8220;Veo-3-Model-Card.pdf&#8221;</em> is chunked using the <em><strong>chunk_data </strong></em>function and uploaded to the vector store with the <em><strong>create_upload_embeddings</strong></em> function.</p><h3>Setup Tools (Functions) for Function Calling for the Agentic RAG</h3><p>I will define two functions that provide different data sources for the RAG system: one for retrieving information from the PDF file and another for searching the web.</p><h4>1. Function for Local Document(PDF)</h4><pre><code>def retrieve_document(query: str, top_k: int = 5) -&gt; str:
    model_name = "text-embedding-3-small"
    query_embedding = openai_client.embeddings.create(
        input=[query], model=model_name
    ).data[0].embedding

    results = qdrant_client.query_points(collection_name, query_embedding, limit=top_k, with_payload=True)
    retrieved_texts = [output.payload["text"] for output in results.points if output.payload and "text" in output.payload]
    context = "\n\n---\n\n".join(retrieved_texts) if retrieved_texts else "no relevant context found"

    output = f"""Based on the following context:
        &lt;context&gt;
        {context}
        &lt;/context&gt;

        Provide a relevant response to:

        &lt;query&gt;
        {query}
        &lt;/query&gt;
        """.strip()
    
    return output</code></pre><p>The <em><strong>retrieve_document</strong></em> function takes a user query, embeds it, searches Qdrant for the top 5 most similar chunks from the PDF and returns a formatted prompt that includes the retrieved context plus the original query.</p><h4>2. Function for Web Search</h4><p>A web search function is implemented using Tavily serving as the tool to use in the agentic RAG when up-to-date information is required.</p><pre><code>from tavily import TavilyClient

tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))

def web_search(query: str, num_results: int = 10):
    try:
        result = tavily.search(
            query=query,
            search_depth="basic",
            max_results=num_results,
            include_answer=False,       
            include_raw_content=False,
            include_images=False
        )

        results = result.get("results", [])

        return {
            "query": query,
            "results": results, 
            "sources": [
                {"title": r.get("title", ""), "url": r.get("url", "")}
                for r in results
            ]
        }

    except Exception as e:
        return {
            "error": f"Search error: {e}",
            "query": query,
            "results": [],
            "sources": [],
        }</code></pre><p><em><strong>web_search</strong></em> function runs an internet search for a given query (up to 10 results), and returns a structured object with the query, the results and their sources, which the agent then uses as context for answering questions that need real-time information.</p><h4>Create Tool Schema</h4><p>The tool schema defines custom instructions for an AI model on when it should call a tool. In this case, there are two tools: one for retrieving information from a document and another for performing a web search. The schema also specifies the conditions and actions to be taken when the model calls a tool.A json tool schema is defined below based on the <a href="https://platform.openai.com/docs/guides/structured-outputs?context=with_parse#supported-schemas">OpenAI tool schema structure</a>.</p><pre><code>tool_schemas = [
   {
        "type": "function",
        "name": "retrieve_document",
        "description": """
        Search the internal PDF file containing Veo 3 Model Card.
        Use this tool when the user requests information about Veo 3
        that only appear in this document and
        for every answer you give include page-number citations in the form [page. X]. 
        """,
        "strict": True,
        "parameters": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "Query to be searched in the PDF corpus.",
                },
            },
            "required": ["query"],
            "additionalProperties": False
        },
    },
    
   {
        "type": "function",
        "name": "web_search",
        "description": """Execute a web search to fetch up to date information. Synthesize a concise, 
        self-contained answer from the content of the results of the visited pages.
        Fetch pages, extract text, and provide the best available result while citing 1-3 sources (title + URL). 
        If sources conflict, surface the uncertainty and prefer the most recent evidence.
        """,
        "strict": True,
        "parameters": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "Query to be searched on the web.",
                },
            },
            "required": ["query"],
            "additionalProperties": False
        },
    },
]</code></pre><ul><li><p><em><strong>type:</strong></em> Specifies that the type of tool is a function.</p></li><li><p><em><strong>name:</strong></em> The function name to be used for the tool call: the first tool schema has the function name retrieve_document, and the second tool schema has the function name web_search.</p></li><li><p><em><strong>description: </strong></em>Describes what the AI model should do when calling a tool. The first tool schema instructs the model to use <em><strong>retrieve_document</strong></em> when it receives a query about Veo 3 model. The second tool schema instructs the model to search the internet using the <em><strong>web_search</strong></em> function to fetch up-to-date information and extract relevant details to generate the best response.</p></li><li><p><em><strong>strict:</strong></em> It is set to true, this property instructs the LLM to strictly follow the tool schema&#8217;s instructions.</p></li><li><p><em><strong>parameters: </strong></em>Defines the parameters that will be passed into the functions in the tool schemas. In first tool schema <em><strong>query</strong></em> represents the questions on Veo 3 model to be searched in the pdf file, while in the second tool schema <em><strong>query</strong></em> represents the search term to look up on the internet.</p></li><li><p><em><strong>required:</strong></em> Instructs the LLM that query is a mandatory parameter for both tools.</p></li><li><p><em><strong>additionalProperties: </strong></em>it is set to false, meaning that the tool&#8217;s arguments object cannot include any parameters other than those defined under <em><strong>parameters.properties</strong></em>.</p></li></ul><p></p><h4>Create Agentic RAG Using GPT-5 and Function Calling</h4><p>Finally, I will build an agent that we can chat with. It will retrieve information from the PDF file for queries about the Veo 3 model and perform a web search when up-to-date information is needed. I will use GPT-5-mini, a fast and accurate model from OpenAI along with function calling to invoke the tool schemas and the <em><strong>web_search</strong></em> and <em><strong>retrieve_document</strong></em> functions already defined.</p><pre><code>from datetime import datetime, timezone
import json

# tracker for the last model&#8217;s response id to maintain conversation&#8217;s state 
prev_response_id = None

# a list for storing tool&#8217;s results from the function call 
tool_results = []

while True:
    # if the tool results is empty prompt message 
    if len(tool_results) == 0:
        user_message = input("User: ")

        # commands for exiting chat 
        if isinstance(user_message, str) and user_message.strip().lower() in {"exit", "q"}:
            print("Exiting chat. Goodbye!")
            break

    else:
        # set the user&#8217;s messages to the tool results to be sent to the model 
        user_message = tool_results.copy()
    
        # clear the tool results for the next call 
        tool_results = []

    # obtain current&#8217;s date to be passed into the model as an instruction to assist in decision making
    today_date = datetime.now(timezone.utc).date().isoformat()     

    response = openai_client.responses.create(
        model = "gpt-5-mini",
        input = user_message,
        instructions=f"Current date is {today_date}.",
        tools = tool_schemas,
        previous_response_id=prev_response_id,
        text = {"verbosity": "low"},
        reasoning={
            "effort": "low",
        },
        store=True,
        )
    
    prev_response_id = response.id

    # Handles model response&#8217;s output 
    for output in response.output:
        
        if output.type == "reasoning":
            print("Assistant: ","Reasoning ....")

            for reasoning_summary in output.summary:
                print("Assistant: ",reasoning_summary)

        elif output.type == "message":
            for item in output.content:
                print("Assistant: ",item.text)

        # checks if the output type is a function call and append the function call&#8217;s results to the tool results list
        elif output.type == "function_call":
            # obtain function name 
            function_name = globals().get(output.name)
            # loads function arguments 
            args = json.loads(output.arguments)
            function_response = function_name(**args)
            # append tool results list with the the function call&#8217;s id and function&#8217;s response 
            tool_results.append(
                {
                    "type": "function_call_output",
                    "call_id": output.call_id,
                    "output": json.dumps(function_response)
                }
            )</code></pre><h4>Step by Step Code Breakdown</h4><pre><code>from openai import OpenAI
import os 

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
prev_response_id = None
tool_results = []</code></pre><ul><li><p>Initialized two variables <em><strong>prev_response_id</strong></em> and <em><strong>tool_results</strong></em>. <em><strong>prev_response_id </strong></em>keeps track of the model&#8217;s response to maintain conversation state, and <em><strong>tool_results </strong></em>is a list that stores outputs returned from the<em><strong> </strong></em>function call.</p></li></ul><h4>Code Walkthrough of the Loop</h4><pre><code>if len(tool_results) == 0:
    user_message = input("User: ")
    if isinstance(user_message, str) and user_message.strip().lower() in {"exit", "q"}:
        print("Exiting chat. Goodbye!")
        break

else:
    user_message = tool_results.copy()
    tool_results = []

today_date = datetime.now(timezone.utc).date().isoformat()     

response = client.responses.create(
    model = "gpt-5-mini",
    input = user_message,
    instructions=f"Current date is {today_date}.",
    tools = tool_schema,
    previous_response_id=prev_response_id,
    text = {"verbosity&#8221;: "low"},
    reasoning={
        "effort": "low",
    },
    store=True,
    )

prev_response_id = response.id</code></pre><p><strong>Checks if tool_results is empty</strong>:</p><ul><li><p>If it&#8217;s empty, ask the user for a message. The user can also type exit or q to quit.</p></li><li><p>If it&#8217;s not empty, set user_message to the collected tool outputs instead, then clear tool_results so they aren&#8217;t sent again.</p></li></ul><p><strong>Gets today_date so the model can reason with the current date.</strong></p><p><strong>Calls client.responses.create with</strong>:</p><ul><li><p>model: gpt-5-mini</p></li><li><p>input: the user_message</p></li><li><p>instructions: the current date</p></li><li><p>tools: the tool schema</p></li><li><p>previous_response_id: the last response ID, to keep context</p></li><li><p>text.verbosity: low, for concise replies</p></li><li><p>reasoning.effort: low, for faster answers (can be high for harder tasks)</p></li><li><p>store: save this response for continuity</p></li></ul><p>Finally, prev_response_id is updated to the new response ID so the next loop stays in the same conversation.</p><pre><code>for output in response.output:
    if output.type == "reasoning":
        print("Assistant: ","Reasoning ....")

        for reasoning_summary in output.summary:
            print("Assistant: ",reasoning_summary)

    elif output.type == "message":
        for item in output.content:
            print("Assistant: ",item.text)

    elif output.type == "function_call":
        # obtain function name 
        function_name = globals().get(output.name)
        # loads function arguments 
        args = json.loads(output.arguments)
        function_response = function_name(**args)
        # append tool results list with the the function call&#8217;s id and function&#8217;s response 
        tool_results.append(
            {
                "type": "function_call_output",
                "call_id": output.call_id,
                "output": json.dumps(function_response)
            }
        )</code></pre><p>Loops through response.output and handles each output type:</p><ul><li><p><strong>reasoning:</strong> print &#8220;Reasoning &#8230;.&#8221; and then print each item in the reasoning summary.</p></li><li><p><strong>message:</strong> go through each content item and print its text.</p></li><li><p><strong>function_call:</strong> get the function name, load its arguments, call the function (retrieve_document or web_search), and store the result.</p></li></ul><p>After calling the function, append its response and call_id to tool_results. This lets the next loop send the tool result back to the model.</p><p>Below is a sample output from the terminal.</p><pre><code>User: What is the model architecture used in Veo3 model?
Assistant:  Reasoning ....
Assistant:  Reasoning ....
Assistant:  Veo 3 uses a latent diffusion architecture &#8212; diffusion applied to temporal audio latents and to spatio&#8209;temporal video latents (latent diffusion model). [page. 1]

User: What is Apple&#8217;s latest MacBook model?
Assistant:  Reasoning ....
Assistant:  Reasoning ....
Assistant:  As of 2025, Apple&#8217;s latest MacBook is the MacBook Air (13-inch, M4, 2025). Source: Apple Newsroom - Apple introduces the new MacBook Air with the M4 chip (https://www.apple.com/newsroom/2025/03/apple-introduces-the-new-macbook-air-with-the-m4-chip-and-a-sky-blue-color/).
User: Write a code to return the product of a list of numbers. 
Assistant:  Reasoning ....
Assistant:  Python (works on all versions):
def product(nums):
    result = 1
    for n in nums:
        result *= n
    return result
# Examples
print(product([2, 3, 4]))  # 24
(If using Python 3.8+, you can also use math.prod: import math; math.prod(nums).)
User: q
Exiting chat. Goodbye!</code></pre><p>In the results above, each response generated using a tool (function) call was returned with its corresponding source.</p><ul><li><p>For the query <strong>&#8220;</strong><em><strong>What is the model architecture used in the Veo 3 model?</strong></em><strong>&#8221;</strong>, the agent generates:<br><em>&#8220;<strong>Veo 3 uses a latent diffusion architecture&#8202;&#8212;&#8202;diffusion applied to temporal audio latents and to spatio&#8209;temporal video latents (latent diffusion model). [page. 1]</strong></em>&#8221;<br>It cites <strong>page 1</strong> of the <em>Veo 3 Model Card</em> PDF, retrieved by the <em>retrieve_document </em>tool (function).</p></li><li><p>For the question <strong>&#8220;</strong><em><strong>What is Apple&#8217;s latest MacBook model?</strong></em><strong>&#8221;</strong>, the agent performs a web search using the <em>web_search </em>tool (function) and returns a response that cites the source URL: <em>&#8220;<strong>As of 2025, Apple&#8217;s latest MacBook is the MacBook Air (13-inch, M4, 2025). Source: Apple Newsroom&#8202;&#8212;&#8202;Apple introduces the new MacBook Air with the M4 chip (https://www.apple.com/newsroom/2025/03/apple-introduces-the-new-macbook-air-with-the-m4-chip-and-a-sky-blue-color/).</strong>&#8220;</em></p></li><li><p>For the prompt <strong>&#8220;</strong><em><strong>Write code to return the product of a list of numbers.</strong></em><strong>&#8221;</strong>, the agent does not call any tool and generates the response directly.</p></li></ul><h3>Note</h3><p>This is the <a href="https://github.com/ayoolaolafenwa/GenAI-Courses/blob/main/AI%20Agents/Notebooks/agentic_rag.ipynb">Jupyter notebook</a> for the full code.</p><h3>Conclusion</h3><p>In this article I explained RAG, and how agentic RAG improves vanilla RAG by allowing a Large Language Model to decide when to retrieve information and pull from multiple data sources. I walked through building an agentic RAG step by step using function calling, where the model can call two different functions to get information from a local document or from the web. By the end, we had an agentic RAG that can retrieve context from a PDF or the internet and generate a context-aware response for the user.</p><blockquote><p>Check out my GitHub repo, <em><a href="https://github.com/ayoolaolafenwa/GenAI-Courses/tree/main">GenAI-Courses</a> </em>where I have published courses on various Generative AI topics. </p></blockquote><h2><strong>Reach to me via:</strong></h2><blockquote><p>Email: <a href="https://mail.google.com/mail/u/0/#inbox">olafenwaayoola@gmail.com</a></p><p>Linkedin: <a href="https://www.linkedin.com/in/ayoola-olafenwa-003b901a9/">https://www.linkedin.com/in/ayoola-olafenwa-003b901a9/</a></p></blockquote><h3>References</h3><p><a href="https://platform.openai.com/docs/guides/function-calling?api-mode=responses">https://platform.openai.com/docs/guides/function-calling?api-mode=responses</a></p><p><a href="https://docs.tavily.com/documentation/api-reference/endpoint/search">https://docs.tavily.com/documentation/api-reference/endpoint/search</a></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ayoolaolafenwa.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Ayoola&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[How to Build a Web Search Agent with Function Calling and GPT-5]]></title><description><![CDATA[How an AI Agent Works: A Step-by-Step Guide]]></description><link>https://ayoolaolafenwa.substack.com/p/how-to-build-a-web-search-agent-with</link><guid isPermaLink="false">https://ayoolaolafenwa.substack.com/p/how-to-build-a-web-search-agent-with</guid><dc:creator><![CDATA[Ayoola Olafenwa]]></dc:creator><pubDate>Mon, 29 Sep 2025 14:17:30 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!svyq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36893a05-e739-46e1-ab2e-0c36535f535a_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!svyq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36893a05-e739-46e1-ab2e-0c36535f535a_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!svyq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36893a05-e739-46e1-ab2e-0c36535f535a_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!svyq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36893a05-e739-46e1-ab2e-0c36535f535a_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!svyq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36893a05-e739-46e1-ab2e-0c36535f535a_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!svyq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36893a05-e739-46e1-ab2e-0c36535f535a_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!svyq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36893a05-e739-46e1-ab2e-0c36535f535a_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/36893a05-e739-46e1-ab2e-0c36535f535a_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2149610,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://ayoolaolafenwa.substack.com/i/171969608?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36893a05-e739-46e1-ab2e-0c36535f535a_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!svyq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36893a05-e739-46e1-ab2e-0c36535f535a_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!svyq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36893a05-e739-46e1-ab2e-0c36535f535a_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!svyq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36893a05-e739-46e1-ab2e-0c36535f535a_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!svyq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F36893a05-e739-46e1-ab2e-0c36535f535a_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>AI Agent and Large Language Models (LLMs)</h2><p><strong>Large language models (LLMs)</strong> are advanced AI systems built on deep neural network such as transformers and trained on vast amounts of text to generate human-like language. LLMs like ChatGPT, Claude, Gemini and Grok can tackle many challenging tasks and are used across fields such as science, healthcare, education, and finance.</p><p>An AI agent extends the capabilites of LLMs to solve tasks that are beyond their pre-trained knowledge.  An LLM can write a Python tutorial from what it learned during training.  If you ask it to book a flight, the task requires access to your calendar, web search and the ability to take actions, these fall beyond the LLM&#8217;s pre-trained knowledge. Some of the common actions include:</p><ul><li><p><em><strong>Weather forecast:</strong> </em>The LLM connects to a web search tool to fetch latest weather forecast.</p></li><li><p><em><strong>Booking agent: </strong></em>An AI agent that can check a user&#8217;s calendar, search the web to visit a booking site like Expedia to find available options for flight and hotel, present them to the user for confirmation and complete the booking on behalf of the user.</p><p></p></li></ul><h2>How an AI Agent Works</h2><p>AI agent is a system that uses a Large Language Model to plan, reason and take steps to interact with its environment using tools suggested from the model&#8217;s reasoning to solve a particular task. </p><h3>Basic Structure of an AI Agent</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yJwd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe63c78f6-6c41-4031-9de4-d3945057e65b_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yJwd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe63c78f6-6c41-4031-9de4-d3945057e65b_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!yJwd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe63c78f6-6c41-4031-9de4-d3945057e65b_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!yJwd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe63c78f6-6c41-4031-9de4-d3945057e65b_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!yJwd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe63c78f6-6c41-4031-9de4-d3945057e65b_1024x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yJwd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe63c78f6-6c41-4031-9de4-d3945057e65b_1024x1024.png" width="1024" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e63c78f6-6c41-4031-9de4-d3945057e65b_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1299455,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ayoolaolafenwa.substack.com/i/171969608?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe63c78f6-6c41-4031-9de4-d3945057e65b_1024x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!yJwd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe63c78f6-6c41-4031-9de4-d3945057e65b_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!yJwd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe63c78f6-6c41-4031-9de4-d3945057e65b_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!yJwd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe63c78f6-6c41-4031-9de4-d3945057e65b_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!yJwd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe63c78f6-6c41-4031-9de4-d3945057e65b_1024x1024.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><ul><li><p><em><strong>Large Language Model(LLM):</strong></em> LLM is the <em><strong>brain</strong></em> of an AI agent. It takes a user&#8217;s prompt, plans and reasons through the request and breaks the problem into steps that determine which tools it should use to complete the task.</p></li><li><p><em><strong> Tool </strong></em>is the framework that the agent uses to perform an action based on the plan and reasoning from the Large Language Model.  If you ask an LLM to book a table for you at a restaurant, possible tools that will be used include calendar to check your availability and a web search tool to access the restaurant website and make a reservation for you. </p></li></ul><h3>Ilustrated Decision Making of a Booking AI Agent</h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zyyD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72f53a9e-26f5-4ee1-a1fe-93a0dddbf63f_1248x832.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zyyD!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72f53a9e-26f5-4ee1-a1fe-93a0dddbf63f_1248x832.png 424w, https://substackcdn.com/image/fetch/$s_!zyyD!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72f53a9e-26f5-4ee1-a1fe-93a0dddbf63f_1248x832.png 848w, https://substackcdn.com/image/fetch/$s_!zyyD!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72f53a9e-26f5-4ee1-a1fe-93a0dddbf63f_1248x832.png 1272w, https://substackcdn.com/image/fetch/$s_!zyyD!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72f53a9e-26f5-4ee1-a1fe-93a0dddbf63f_1248x832.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zyyD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72f53a9e-26f5-4ee1-a1fe-93a0dddbf63f_1248x832.png" width="1248" height="832" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/72f53a9e-26f5-4ee1-a1fe-93a0dddbf63f_1248x832.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:832,&quot;width&quot;:1248,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1218304,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ayoolaolafenwa.substack.com/i/171969608?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72f53a9e-26f5-4ee1-a1fe-93a0dddbf63f_1248x832.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!zyyD!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72f53a9e-26f5-4ee1-a1fe-93a0dddbf63f_1248x832.png 424w, https://substackcdn.com/image/fetch/$s_!zyyD!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72f53a9e-26f5-4ee1-a1fe-93a0dddbf63f_1248x832.png 848w, https://substackcdn.com/image/fetch/$s_!zyyD!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72f53a9e-26f5-4ee1-a1fe-93a0dddbf63f_1248x832.png 1272w, https://substackcdn.com/image/fetch/$s_!zyyD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72f53a9e-26f5-4ee1-a1fe-93a0dddbf63f_1248x832.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p>AI agents can access different tools depending on the task. A tool might be a data store, such as a database. For example, a customer-support agent could access a customer&#8217;s account details and purchase history and decide when to retrieve that information to help resolve an issue.</p></blockquote><p>AI agents are used to solve a wide range of tasks, and there are many powerful agents available. Coding agents, particularly agentic IDEs such as Cursor, Windsurf, and GitHub Copilot help engineers write and debug code faster and build projects quickly.  CLI Coding agents like Claude Code and Codex CLI can interact with a user&#8217;s desktop and terminal to carry out coding tasks. ChatGPT supports agents that can perform actions such as booking reservations on a user&#8217;s behalf.  Agents are also integrated into customer support workflows to communicate with customers and resolve their issues.</p><h1>Function Calling </h1><p>Function calling a technique for connecting a large language model (LLM) to external tools such as APIs or databases. It is used in creating AI agents to connect LLMs to tools. In function calling, each tool is defined as a code function (for example, a weather API to fetch the latest forecast) along with a JSON Schema that specifies the function&#8217;s parameters and instructs the LLM on when and how to call the function for a given task.</p><p> The type of function defined depends on the task the agent is designed to perform. For example, for a customer support agent we can define a function that can extract information from unstructured data, such as PDFs containing details about a business&#8217;s products.</p><p>In this post I will demonstrate how to use function calling to build a simple web search agent using GPT-5 as the large language model.</p><h1>Basic Structure of Web Search Agent</h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Qvz8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea8b9849-b92a-469e-b276-932ffe219a0d_717x717.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Qvz8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea8b9849-b92a-469e-b276-932ffe219a0d_717x717.png 424w, https://substackcdn.com/image/fetch/$s_!Qvz8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea8b9849-b92a-469e-b276-932ffe219a0d_717x717.png 848w, https://substackcdn.com/image/fetch/$s_!Qvz8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea8b9849-b92a-469e-b276-932ffe219a0d_717x717.png 1272w, https://substackcdn.com/image/fetch/$s_!Qvz8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea8b9849-b92a-469e-b276-932ffe219a0d_717x717.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Qvz8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea8b9849-b92a-469e-b276-932ffe219a0d_717x717.png" width="717" height="717" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ea8b9849-b92a-469e-b276-932ffe219a0d_717x717.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:717,&quot;width&quot;:717,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:415256,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://ayoolaolafenwa.substack.com/i/171969608?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea8b9849-b92a-469e-b276-932ffe219a0d_717x717.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Qvz8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea8b9849-b92a-469e-b276-932ffe219a0d_717x717.png 424w, https://substackcdn.com/image/fetch/$s_!Qvz8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea8b9849-b92a-469e-b276-932ffe219a0d_717x717.png 848w, https://substackcdn.com/image/fetch/$s_!Qvz8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea8b9849-b92a-469e-b276-932ffe219a0d_717x717.png 1272w, https://substackcdn.com/image/fetch/$s_!Qvz8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea8b9849-b92a-469e-b276-932ffe219a0d_717x717.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>The main logic behind the web search agent:</h2><ul><li><p>Define a code function to handle the web search.</p></li><li><p>Define custom instructions that guide the large language model  in determining when to call the web search function based on the query. For example, if the query asks about the current weather, the web search agent will recognize the need to search the internet to get the latest weather reports. However, if the query asks it to write a tutorial about a programming language like Python, something it can answer from its pre-trained knowledge it will not call the web search function and will respond directly instead.</p><p></p></li></ul><h1>Prerequisite</h1><h3><strong>Create OpenAI account and generate an API key</strong></h3><p>1:<strong> </strong> Create an <a href="https://auth.openai.com/create-account">OpenAI Account</a> if you don&#8217;t have one</p><p>2: <a href="https://platform.openai.com/account/api-keys">Generate an API Key</a> </p><h3>Set up and Activate Environment </h3><pre><code>python3 -m venv env
source env/bin/activate</code></pre><h3>Export OpenAI API Key</h3><pre><code>export OPENAI_API_KEY="Your Openai API Key"</code></pre><h3>Setup Tavily for Web Search </h3><p> Tavily is a specialized web-search tool for AI agents. Create an account on <a href="https://www.tavily.com/">Tavily.com</a>, once your profile is set up, an API key will be generated that you can copy into your environment. New account receives 1000 free credits that can be used for up to 1000 web searches. </p><pre><code>export TAVILY_API_KEY="Your Tavily API Key"</code></pre><h3> Install Packages</h3><pre><code>pip3 install openai
pip3 install tavily-python</code></pre><h3>Step by Step Building A Web Search Agent with Function Calling</h3><h4><strong>Step1: Create Web Search Function with Tavily</strong></h4><p>A web search function is implemented using Tavily, serving as the tool for function calling in the web search agent.</p><pre><code>from tavily import TavilyClient
import os

tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))

def web_search(query: str, num_results: int = 10):
    try:
        result = tavily.search(
            query=query,
            search_depth="basic",
            max_results=num_results,
            include_answer=False,       
            include_raw_content=False,
            include_images=False
        )

        results = result.get("results", [])

        return {
            "query": query,
            "results": results, 
            "sources": [
                {"title": r.get("title", ""), "url": r.get("url", "")}
                for r in results
            ]
        }

    except Exception as e:
        return {
            "error": f"Search error: {e}",
            "query": query,
            "results": [],
            "sources": [],
        }</code></pre><h4><strong>Web function code break down</strong></h4><p>Tavily is initialized with its API key. In the <em><strong>web_search</strong></em> function, the following steps are performed:</p><ul><li><p>Tavily search function is called to search the internet and retrieve the top 10 results.</p></li><li><p>The search results and their corresponding sources are returned.</p></li></ul><p>This returned output will serve as relevant context for the web search agent: which we will define later in this article, to fetch up-to-date information for queries (prompts) that require real-time data such as weather forecasts. </p><p></p><h4><strong>Step2: Create Tool Schema</strong></h4><p>The tool schema defines custom instructions for an AI model on when it should call a tool, in this case the tool that will be used is a web search function. It also specifies the conditions and actions to be taken when the model calls a tool. A json tool schema is defined below based on the <a href="https://platform.openai.com/docs/guides/structured-outputs?context=with_parse#supported-schemas">OpenAI tool schema structure</a>.</p><pre><code>tool_schema = [
    {
        "type": "function",
        "name": "web_search",

        "description": """Execute a web search to fetch up to date information. Synthesize a concise, 
        self-contained answer from the content of the results of the visited pages.
        Fetch pages, extract text, and provide the best available result while citing 1-3 sources (title + URL). 
        If sources conflict, surface the uncertainty and prefer the most recent evidence.
        """,

        "strict": True,
        "parameters": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "Query to be searched on the web.",
                },
            },
            "required": ["query"],
            "additionalProperties": False
        },
    },
]</code></pre><h4>Tool schema&#8217;s Properties</h4><ul><li><p><em><strong>type:</strong></em> Specifies that the type of tool is a function.</p></li><li><p><em><strong>name:</strong></em> the name of the function that will be used for tool call, which is <em><strong>web_search</strong></em>.</p></li><li><p><em><strong>description:</strong></em> Describes what the AI model should do when calling the web search tool. It instructs the model to search the internet using the <em><strong>web_search</strong></em> function to fetch up-to-date information and extract relevant details to generate the best response.</p></li><li><p><em><strong>strict: </strong></em>It is set to true, this property instructs the LLM to strictly follow the tool schema&#8217;s instructions.</p></li><li><p><em><strong>parameters: </strong></em>Defines the parameters that will be passed into the <em><strong>web_search</strong></em> function. In this case, there is only one parameter: <em><strong>query</strong></em> which represents the search term to look up on the internet. </p></li><li><p><em><strong>required: </strong></em> Instructs the LLM that query is a mandatory parameter for the <em><strong>web_search</strong></em> function. </p></li><li><p><em><strong>additionalProperties:</strong></em> it is set to false, meaning that the tool&#8217;s <em>arguments object</em> cannot include any parameters other than those defined under <em><strong>parameters.properties.</strong></em></p></li></ul><p></p><h4>Step3: Create Web Search Agent Using GPT-5 and Function Calling</h4><p>Finally I will build an agent that we can chat with, which can search the web when it needs up-to-date information. I will use <strong>GPT-5-mini</strong>, a fast and accurate model from OpenAI, along with function calling to invoke the <em><strong>tool schema </strong></em>and the <em><strong>web search function </strong></em>already defined.</p><pre><code>from datetime import datetime, timezone
import json
from openai import OpenAI
import os 

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

# tracker for the last model's response id to maintain conversation's state 
prev_response_id = None

# a list for storing tool's results from the function call 
tool_results = []

while True:
    # if the tool results is empty prompt message 
    if len(tool_results) == 0:
        user_message = input("User: ")

        """ commands for exiting chat """
        if isinstance(user_message, str) and user_message.strip().lower() in {"exit", "q"}:
            print("Exiting chat. Goodbye!")
            break

    else:
        # set the user's messages to the tool results to be sent to the model 
        user_message = tool_results.copy()
    
        # clear the tool results for the next call 
        tool_results = []

    # obtain current's date to be passed into the model as an instruction to assist in decision making
    today_date = datetime.now(timezone.utc).date().isoformat()     

    response = client.responses.create(
        model = "gpt-5-mini",
        input = user_message,
        instructions=f"Current date is {today_date}.",
        tools = tool_schema,
        previous_response_id=prev_response_id,
        text = {"verbosity": "low"},
        reasoning={
            "effort": "low",
        },
        store=True,
        )
    
    prev_response_id = response.id

    # Handles model response's output 
    for output in response.output:
        
        if output.type == "reasoning":
            print("Assistant: ","Reasoning ....")

            for reasoning_summary in output.summary:
                print("Assistant: ",reasoning_summary)

        elif output.type == "message":
            for item in output.content:
                print("Assistant: ",item.text)

        # checks if the output type is a function call and append the function call's results to the tool results list
        elif output.type == "function_call":
            # obtain function name 
            function_name = globals().get(output.name)
            # loads function arguments 
            args = json.loads(output.arguments)
            function_response = function_name(**args)
            # append tool results list with the the function call's id and function's response 
            tool_results.append(
                {
                    "type": "function_call_output",
                    "call_id": output.call_id,
                    "output": json.dumps(function_response)
                }
            )</code></pre><h4>Step by Step Code Breakdown</h4><pre><code>from openai import OpenAI
import os 

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
prev_response_id = None
tool_results = []</code></pre><ul><li><p>Initialized the OpenAI model API with an API key.</p></li><li><p>Initialized two variables <em><strong>prev_response_id</strong></em> and <em><strong>tool_results</strong></em>. <em><strong>prev_response_id </strong></em>keeps track of the model&#8217;s response to maintain conversation state, and <em><strong>tool_results </strong></em>is a list that stores outputs returned from the <em><strong>web_search </strong></em>function call.</p></li></ul><p>The chat runs inside the<em><strong> loop</strong></em>. A user enters a message and the model called with tool schema accepts the message, reasons over it,  decides whether to call the  web search tool, and then the tool&#8217;s output is passed back to the model. The model generates a context-aware response. This continues until the user exits the chat.</p><h4>Code Walkthrough of the Loop</h4><pre><code>if len(tool_results) == 0:
    user_message = input("User: ")
    if isinstance(user_message, str) and user_message.strip().lower() in {"exit", "q"}:
        print("Exiting chat. Goodbye!")
        break

else:
    user_message = tool_results.copy()
    tool_results = []

today_date = datetime.now(timezone.utc).date().isoformat()     

response = client.responses.create(
    model = "gpt-5-mini",
    input = user_message,
    instructions=f"Current date is {today_date}.",
    tools = tool_schema,
    previous_response_id=prev_response_id,
    text = {"verbosity": "low"},
    reasoning={
        "effort": "low",
    },
    store=True,
    )

prev_response_id = response.id</code></pre><ul><li><p>Checks if the <em><strong>tool_results</strong></em> is empty. If it is, the user will be prompted to type in a message, with an option to quit using <em><strong>exit</strong></em> or <em><strong>q</strong></em>.</p></li><li><p>If the <em><strong>tool_results</strong></em> is not empty,  <em><strong>user_message </strong></em>will be set to the collected tool outputs to be sent to the model.  <em><strong>tool_results</strong></em> is cleared to avoid resending the same <em><strong>tool outputs </strong></em>on the next loop iteration.</p></li><li><p>The current date (<em><strong>today_date</strong></em>)  is obtained to be used by the model to make time-aware decisions.</p></li><li><p>Calls <em><strong>client.responses.create</strong></em> to generate the model&#8217;s response and it accepts the following parameters:</p><ul><li><p>model: set to <em><strong>gpt-5-mini</strong></em>.   </p></li><li><p>input: accepts the user&#8217;s message. </p></li><li><p>instructions: set to current&#8217;s date (today_date).</p></li><li><p>tools: set to the tool schema that was defined earlier. </p></li><li><p>previous_response_id: set to the previous response&#8217;s id so the model can maintain conversation state.</p></li><li><p>text: verbosity is set to low to keep model&#8217;s response  concise. </p></li><li><p>reasoning: GPT-5-mini is a reasoning model, set the reasoning&#8217;s effort to low for faster&#8217;s response. For more complex tasks we can set it to high.</p></li><li><p>store: tells the model to store the current&#8217;s response so it can be retrieved later and helps with conversation continuity.</p></li></ul></li><li><p><em><strong>prev_response_id</strong></em><code> </code>is set to current&#8217;s response id so the next function call can thread onto the same conversation.                 </p></li></ul><pre><code>for output in response.output:
    if output.type == "reasoning":
        print("Assistant: ","Reasoning ....")

        for reasoning_summary in output.summary:
            print("Assistant: ",reasoning_summary)

    elif output.type == "message":
        for item in output.content:
            print("Assistant: ",item.text)

    elif output.type == "function_call":
        # obtain function name 
        function_name = globals().get(output.name)
        # loads function arguments 
        args = json.loads(output.arguments)
        function_response = function_name(**args)
        # append tool results list with the the function call's id and function's response 
        tool_results.append(
            {
                "type": "function_call_output",
                "call_id": output.call_id,
                "output": json.dumps(function_response)
            }
        )</code></pre><p>Process the model&#8217;s response output and do the following;</p><ul><li><p>If the output type is reasoning, print each item in the reasoning summary.</p></li><li><p>If the output type is message, iterate through the content and print each text item.</p></li><li><p>If the output type is a function call, obtain the function&#8217;s name, parse its arguments, and pass them to the function (<em><strong>web_search</strong></em>) to generate a response. In this case, the web search response contains up-to-date information relevant to the user&#8217;s message.  Finally appends the function call&#8217;s response and function call id to  <em><strong>tool_results</strong></em>. This lets the next loop send the tool result back to the model.</p></li></ul><p></p><h3>Full Code for Web Search Agent</h3><pre><code>from datetime import datetime, timezone
import json
from openai import OpenAI
import os 
from tavily import TavilyClient

tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))

def web_search(query: str, num_results: int = 10):
    try:
        result = tavily.search(
            query=query,
            search_depth="basic",
            max_results=num_results,
            include_answer=False,       
            include_raw_content=False,
            include_images=False
        )

        results = result.get("results", [])

        return {
            "query": query,
            "results": results, 
            "sources": [
                {"title": r.get("title", ""), "url": r.get("url", "")}
                for r in results
            ]
        }

    except Exception as e:
        return {
            "error": f"Search error: {e}",
            "query": query,
            "results": [],
            "sources": [],
        }


tool_schema = [
    {
        "type": "function",
        "name": "web_search",
        "description": """Execute a web search to fetch up to date information. Synthesize a concise, 
        self-contained answer from the content of the results of the visited pages.
        Fetch pages, extract text, and provide the best available result while citing 1-3 sources (title + URL). "
        If sources conflict, surface the uncertainty and prefer the most recent evidence.
        """,
        "strict": True,
        "parameters": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "Query to be searched on the web.",
                },
            },
            "required": ["query"],
            "additionalProperties": False
        },
    },
]

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

# tracker for the last model's response id to maintain conversation's state 
prev_response_id = None

# a list for storing tool's results from the function call 
tool_results = []

while True:
    # if the tool results is empty prompt message 
    if len(tool_results) == 0:
        user_message = input("User: ")

        """ commands for exiting chat """
        if isinstance(user_message, str) and user_message.strip().lower() in {"exit", "q"}:
            print("Exiting chat. Goodbye!")
            break

    else:
        # set the user's messages to the tool results to be sent to the model 
        user_message = tool_results.copy()
    
        # clear the tool results for the next call 
        tool_results = []

    # obtain current's date to be passed into the model as an instruction to assist in decision making
    today_date = datetime.now(timezone.utc).date().isoformat()     

    response = client.responses.create(
        model = "gpt-5-mini",
        input = user_message,
        instructions=f"Current date is {today_date}.",
        tools = tool_schema,
        previous_response_id=prev_response_id,
        text = {"verbosity": "low"},
        reasoning={
            "effort": "low",
        },
        store=True,
        )
    
    prev_response_id = response.id


    # Handles model response's output 
    for output in response.output:
        
        if output.type == "reasoning":
            print("Assistant: ","Reasoning ....")

            for reasoning_summary in output.summary:
                print("Assistant: ",reasoning_summary)

        elif output.type == "message":
            for item in output.content:
                print("Assistant: ",item.text)

        # checks if the output type is a function call and append the function call's results to the tool results list
        elif output.type == "function_call":
            # obtain function name 
            function_name = globals().get(output.name)
            # loads function arguments 
            args = json.loads(output.arguments)
            function_response = function_name(**args)
            # append tool results list with the the function call's id and function's response 
            tool_results.append(
                {
                    "type": "function_call_output",
                    "call_id": output.call_id,
                    "output": json.dumps(function_response)
                }
            )
</code></pre><p>When you run the code, you can easily chat with the agent to ask questions that require the latest information, such as the current weather or the latest product releases. The agent responds with up-to-date information along with the corresponding sources from the internet. Below is a sample output from the terminal.</p><pre><code>User: What is the weather like in London today?
Assistant:  Reasoning ....
Assistant:  Reasoning ....
Assistant:  Right now in London: overcast, about 18&#176;C (64&#176;F), humidity ~88%, light SW wind ~16 km/h, no precipitation reported. Source: WeatherAPI (current conditions) &#8212; https://www.weatherapi.com/

User: What is the latest iPhone model?
Assistant:  Reasoning ....
Assistant:  Reasoning ....
Assistant:  The latest iPhone models are the iPhone 17 lineup (including iPhone 17, iPhone 17 Pro, iPhone 17 Pro Max) and the new iPhone Air &#8212; announced by Apple on Sept 9, 2025. Source: Apple Newsroom &#8212; https://www.apple.com/newsroom/2025/09/apple-debuts-iphone-17/

User: Multiply 500 by 12.           
Assistant:  Reasoning ....
Assistant:  6000
User: exit   
Exiting chat. Goodbye!</code></pre><p>You can see the results with their corresponding web sources. When you ask it to perform a task that doesn&#8217;t require up-to-date information, such as maths calculations or writing code the agent responds directly without any web search.</p><blockquote><p>Note: The web search agent is a simple, single-tool agent. Advanced agentic systems orchestrate multiple specialized tools and use efficient memory to maintain context, plan, and solve more complex tasks.</p><p></p></blockquote><h3>Conclusion</h3><p>In this post I explained how an AI agent works and how it extends the capabilities of a large language model to interact with its environment, perform actions and solve tasks through the use of tools. I also explained function calling and how it enables LLMs to call tools. I demonstrated how to create a tool schema for function calling that defines when and how an LLM should call a tool to perform an action. I defined a web search function using Tavily to fetch information from the web and then showed step by step how to build a web search agent using function calling and GPT-5-mini as the LLM. In the end, we built a web search agent capable of retrieving up-to-date information from the internet to answer user queries.</p><blockquote><p>Check out my GitHub repo, <em><a href="https://github.com/ayoolaolafenwa/GenAI-Courses/tree/main">GenAI-Courses</a> </em>where I have published more courses on various Generative AI topics. It also includes a guide on building an <a href="https://github.com/ayoolaolafenwa/GenAI-Courses/blob/main/AI%20Agents/Agentic_RAG.md">Agentic RAG using function calling.</a></p></blockquote><h2><strong>Reach to me via:</strong></h2><blockquote><p>Email: <a href="https://mail.google.com/mail/u/0/#inbox">olafenwaayoola@gmail.com</a></p><p>Linkedin: <a href="https://www.linkedin.com/in/ayoola-olafenwa-003b901a9/">https://www.linkedin.com/in/ayoola-olafenwa-003b901a9/</a></p></blockquote><h3>References</h3><p><a href="https://platform.openai.com/docs/guides/function-calling?api-mode=responses">https://platform.openai.com/docs/guides/function-calling?api-mode=responses</a></p><p><a href="https://docs.tavily.com/documentation/api-reference/endpoint/search">https://docs.tavily.com/documentation/api-reference/endpoint/search</a></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ayoolaolafenwa.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Ayoola&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[How Multimodal Large Language Model Works ]]></title><description><![CDATA[Review of Phi-4 Multimodal Model]]></description><link>https://ayoolaolafenwa.substack.com/p/how-multimodal-large-language-model</link><guid isPermaLink="false">https://ayoolaolafenwa.substack.com/p/how-multimodal-large-language-model</guid><pubDate>Tue, 22 Apr 2025 09:05:15 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!syQO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e1825c9-940d-407d-882d-52a3d2b11d80_1600x900.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3></h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!syQO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e1825c9-940d-407d-882d-52a3d2b11d80_1600x900.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!syQO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e1825c9-940d-407d-882d-52a3d2b11d80_1600x900.jpeg 424w, https://substackcdn.com/image/fetch/$s_!syQO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e1825c9-940d-407d-882d-52a3d2b11d80_1600x900.jpeg 848w, https://substackcdn.com/image/fetch/$s_!syQO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e1825c9-940d-407d-882d-52a3d2b11d80_1600x900.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!syQO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e1825c9-940d-407d-882d-52a3d2b11d80_1600x900.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!syQO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e1825c9-940d-407d-882d-52a3d2b11d80_1600x900.jpeg" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9e1825c9-940d-407d-882d-52a3d2b11d80_1600x900.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!syQO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e1825c9-940d-407d-882d-52a3d2b11d80_1600x900.jpeg 424w, https://substackcdn.com/image/fetch/$s_!syQO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e1825c9-940d-407d-882d-52a3d2b11d80_1600x900.jpeg 848w, https://substackcdn.com/image/fetch/$s_!syQO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e1825c9-940d-407d-882d-52a3d2b11d80_1600x900.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!syQO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e1825c9-940d-407d-882d-52a3d2b11d80_1600x900.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://unsplash.com/photos/a-computer-circuit-board-with-a-brain-on-it-_0iV9LmPDn0">Source</a></figcaption></figure></div><h4>Multimodal Large Language Model</h4><p>It is an advanced large language model (LLM) designed to process and interpret various modalities such as text, images, and audio. It is used for tasks that go beyond text processing, such as answering questions based on documents, analyzing speech inputs, and describing image contents. Popular multimodal models include GPT-4 Vision, Gemini, Phi-4 Multimodal, and LLaMA Vision.</p><h4>Working of A Basic Multimodal Model</h4><p>Most multimodal models operate in the following manner: there is a base language model, such as GPT, which processes text and integrates other modalities through the interaction of an <strong>encoder</strong>, an <strong>adapter</strong>, and <strong>language integration</strong> mechanisms. A multimodal model that handles text and images typically works as follows:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1o6U!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf4bf98a-f356-4298-96bc-20ead86fb702_1600x141.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1o6U!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf4bf98a-f356-4298-96bc-20ead86fb702_1600x141.png 424w, https://substackcdn.com/image/fetch/$s_!1o6U!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf4bf98a-f356-4298-96bc-20ead86fb702_1600x141.png 848w, https://substackcdn.com/image/fetch/$s_!1o6U!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf4bf98a-f356-4298-96bc-20ead86fb702_1600x141.png 1272w, https://substackcdn.com/image/fetch/$s_!1o6U!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf4bf98a-f356-4298-96bc-20ead86fb702_1600x141.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1o6U!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf4bf98a-f356-4298-96bc-20ead86fb702_1600x141.png" width="1456" height="128" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bf4bf98a-f356-4298-96bc-20ead86fb702_1600x141.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:128,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!1o6U!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf4bf98a-f356-4298-96bc-20ead86fb702_1600x141.png 424w, https://substackcdn.com/image/fetch/$s_!1o6U!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf4bf98a-f356-4298-96bc-20ead86fb702_1600x141.png 848w, https://substackcdn.com/image/fetch/$s_!1o6U!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf4bf98a-f356-4298-96bc-20ead86fb702_1600x141.png 1272w, https://substackcdn.com/image/fetch/$s_!1o6U!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf4bf98a-f356-4298-96bc-20ead86fb702_1600x141.png 1456w" sizes="100vw"></picture><div></div></div></a><figcaption class="image-caption">Image By Author</figcaption></figure></div><p><strong>Image Encoder:</strong> It is a deep neural network responsible for converting raw images into high-dimensional feature vectors known as image embeddings. These embeddings contains extracted information from the image. Some of the commonly used image encoders in multimodal models are CLIP, SigLIP.</p><p><strong>Adapter:</strong> The image embeddings from the image encoder is not compatible directly with a language model. Adapter is used to adapt image embeddings into the format compatible with the language model. The type of image adapter depend on the architecture of a multimodal model. It can be in the form of a module such as a linear projection to scale the dimension of the image embeddings to match the expected dimension for the language model. The image adapter makes it possible for a language model to treat images as text tokens.</p><p><strong>Language Integration:</strong> The adapted embeddings are integrated into the language model. This model combines the visual and textual information to produce the desired output.</p><p>This approach is used in solving different multimodal tasks such as:</p><ul><li><p><strong>Image Captioning</strong>: Generating a textual description of an image.</p></li><li><p><strong>Visual Question Answering</strong>: Answering a question about an image.</p></li><li><p><strong>Image-Text Retrieval</strong>: Matching images with relevant text descriptions.</p></li></ul><p>To gain a deeper understanding of how a multimodal model works, I will review <strong>Phi-4 Multimodal</strong>, a state-of-the-art open-source multimodal model.</p><h3>Review of Phi-4 Multimodal</h3><p>Phi-4-Multimodal is a <strong>5.6-billion-parameter </strong>multimodal model built upon the <strong>Phi-4-Mini </strong>language model<strong>- </strong>a<strong> 3.8 billion parameters</strong>. It supports multiple modalities: <strong>text</strong>, <strong>image and audio inputs.</strong></p><p>For vision-related tasks, Phi-4-Multimodal was trained on high-quality multimodal instruction datasets covering tasks like <strong>general image understanding, OCR (Optical Character Recognition), chart interpretation, diagram comprehension, </strong>and<strong> video summarization</strong>.</p><p>For speech/audio tasks, the model was trained on approximately<strong> </strong>2 million hours of anonymized speech-text pairs for <strong>automatic speech recognition (ASR)</strong>, and on diverse audio instruction datasets for tasks like <strong>speech summarization, speech question-answering, automatic speech translation (AST), </strong>and<strong> audio understanding</strong>.</p><p>Phi-4-Multimodal is a highly performant model achieving exceptional results on both vision and speech benchmarks. It rivals closed-source models such as <strong>Gemini and GPT-4o</strong> in various vision tasks, and it is currently ranked as the <em><strong>number 1 model</strong></em> for speech recognition on the <strong><a href="https://huggingface.co/spaces/hf-audio/open_asr_leaderboard">HuggingFace OpenASR Leaderboard</a></strong>.</p><h4>Training Overview</h4><p>The base language model, <strong>Phi-4 Mini</strong>, was frozen and new modalities (vision and audio) were incorporated into it using a technique called &#8220;<strong>Mixture of LoRAs</strong>&#8221;.</p><ul><li><p><strong>Mixture of LoRAs : </strong>LoRA (<strong>Low-Rank Adaptation</strong>) is a method for adapting a pre-trained model to new tasks or modalities without significantly altering its original parameters. It involves inserting small, trainable low-rank matrices (adapters) into specific layers such as the linear layers within attention and feed-forward modules of the pre-trained Transformer model.</p></li></ul><p>In <strong>Phi-4 Multimodal, </strong>dedicated LoRA adapters were trained specifically for vision and audio tasks. These adapters were integrated into the linear layers of the frozen <strong>Phi-4 Mini</strong> model, enabling it to process new modalities without changing the original model weights.</p><h4>Architecture Overview</h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UOuN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864d9bc2-4162-424c-af80-a914869afc85_1600x863.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UOuN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864d9bc2-4162-424c-af80-a914869afc85_1600x863.png 424w, https://substackcdn.com/image/fetch/$s_!UOuN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864d9bc2-4162-424c-af80-a914869afc85_1600x863.png 848w, https://substackcdn.com/image/fetch/$s_!UOuN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864d9bc2-4162-424c-af80-a914869afc85_1600x863.png 1272w, https://substackcdn.com/image/fetch/$s_!UOuN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864d9bc2-4162-424c-af80-a914869afc85_1600x863.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UOuN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864d9bc2-4162-424c-af80-a914869afc85_1600x863.png" width="1456" height="785" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/864d9bc2-4162-424c-af80-a914869afc85_1600x863.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:785,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!UOuN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864d9bc2-4162-424c-af80-a914869afc85_1600x863.png 424w, https://substackcdn.com/image/fetch/$s_!UOuN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864d9bc2-4162-424c-af80-a914869afc85_1600x863.png 848w, https://substackcdn.com/image/fetch/$s_!UOuN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864d9bc2-4162-424c-af80-a914869afc85_1600x863.png 1272w, https://substackcdn.com/image/fetch/$s_!UOuN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864d9bc2-4162-424c-af80-a914869afc85_1600x863.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Diagram from Phi-4 Paper</figcaption></figure></div><p>I will explain how the Phi-4 Multimodal model processes image and audio modalities.</p><h4>Image Modality</h4><ul><li><p><strong>Dynamic Cropping Strategy:</strong> Phi-4 uses this technique to divide image into smaller crops for handling different image resolutions before it is passed into the image encoder.</p></li><li><p><strong>Vision(Image)Encoder: </strong>Phi-4 uses <strong>SigLIP-400M </strong>as its image encoder. This model converts cropped images into image embeddings that represent visual content and details.</p></li><li><p><strong>Vision Projector: </strong>A <strong>2-layer MLP</strong>(Multilayer Perceptron) module is used by Phi-4 to adapt image embeddings from the image encoder for the language model(Phi-4 mini). It projects the extracted features from the image encoder into the embedding dimension supported by the language model.</p></li><li><p><strong>Vision LoRA Adapter</strong>: The vision LoRA adapter integrated with the Phi-4 model allows the language model to process adapted image embeddings from the vision projector and perform vision-language tasks, such as image captioning or visual question answering.</p></li></ul><h4><strong>In summary what happens within the Image modality:</strong></h4><p><strong>Step 1:</strong> The image is preprocessed via <strong>dynamic cropping</strong> and passed through the image encoder.</p><p><strong>Step 2:</strong> The <strong>image encoder</strong> processed the cropped images and extracts visual information from them and pass them to the <strong>vision projector</strong>.</p><p><strong>Step 3:</strong> The <strong>vision projector</strong> then transforms the <strong>image encoder&#8217;s</strong> output into the embedding dimension supported language model(Phi-4 mini).</p><p><strong>Step 4:</strong> Finally, the <strong>vision LoRA adapter</strong> injects the adapted features from the <strong>vision projector</strong> into the language model&#8217;s (Phi-4 mini) processing pipeline, allowing it to generate outputs that incorporate visual context. These connected components enable <strong>Phi&#8209;4&#8209;Multimodal</strong> to solve image tasks like <strong>image captioning</strong> and <strong>visual question answering.</strong></p><h4>Audio Modality</h4><ul><li><p><strong>Audio Processing:</strong> Audio input is first transformed into <strong>80-dimensional log-Mel filter-bank features</strong>,<strong> </strong>a correct format to represent audio signals capturing the essential frequency components.</p></li><li><p><strong>Audio Encoder:</strong> It is consists of <strong>3 convolution layers and 24 Conformer blocks</strong> (conformer is a specialized model for audio processing). The encoder extracts sound information from the processed audio signals.</p></li><li><p><strong>Audio Projector: </strong>Phi-4 uses a <strong>2-layer MLP</strong> to project the extracted audio features from the audio encoder to the same embedding dimension supported by the language model(Phi-4 Mini)</p></li><li><p><strong>Audio LoRA Adapter: </strong>The <strong>audio LoRA adapter</strong> integrated with the Phi-4 model processes the adapted embeddings from the audio projector and solve tasks like automatic speech recognition (speech-to-text), audio summarization, and audio question answering.</p></li></ul><h4><strong>In summary what happens within the Audio modality:</strong></h4><ul><li><p><strong>Step 1:</strong> The audio(speech) input is preprocessed into <strong>80-dimensional log-Mel filter-bank features </strong>for correct audio signals representation and passed into the audio encoder</p></li><li><p><strong>Step 2:</strong> The audio encoder extracts sound information from the the processed audio signals and passed it into the <strong>audio projector.</strong></p></li><li><p><strong>Step 3:</strong> The <strong>audio projector </strong>transforms the <strong>audio encoder&#8217;s</strong> output into a text-friendly embedding dimension, aligning the audio features with the language model&#8217;s (Phi-4 mini) input format.</p></li><li><p><strong>Step 4:</strong> Finally, the <strong>audio LoRA adapter</strong> injects these adapted audio features into the language model&#8217;s (Phi-4 mini) processing pipeline,, allowing the language model to generate outputs that incorporate audio context. Together, these connected components enable <strong>Phi&#8209;4&#8209;Multimodal</strong> to seamlessly integrate speech data into its text-based generation framework and solve tasks like describing the content of an audio file.</p></li></ul><p><strong>Phi-4 multimodal </strong>can process speech(audio) in multiple languages: English, Chinese, German, French, Italian, Japanese, Spanish, Portuguese</p><h3>Code for Running Phi-4 Multimodal</h3><h4>Install Required Packages</h4><p><em><strong>requirements.txt file</strong></em></p><pre><code>flash_attn==2.7.4.post1
torch==2.6.0
transformers==4.48.2
accelerate==1.3.0
soundfile==0.13.1
pillow==11.1.0
scipy==1.15.2
torchvision==0.21.0
backoff==2.2.1
peft==0.13.2</code></pre><p>Paste the packages above in a txt file and install using:</p><pre><code>pip3 install -r requirements.txt</code></pre><h4>Load Model</h4><pre><code>from transformers import AutoModelForCausalLM, AutoProcessor,GenerationConfig

# Define model path
model_path = "microsoft/Phi-4-multimodal-instruct"

# Load model and processor
processor = AutoProcessor.from_pretrained(model_path, trust_remote_code=True)

model = AutoModelForCausalLM.from_pretrained(model_path, device_map="cuda",
    torch_dtype="auto",trust_remote_code=True,
    # if you do not use Ampere or later GPUs, change attention to "eager"
    _attn_implementation='flash_attention_2'
).cuda()

# Load generation config
generation_config = GenerationConfig.from_pretrained(model_path)

# Define prompt template
user_prompt = '&lt;|user|&gt;'
assistant_prompt = '&lt;|assistant|&gt;'
prompt_suffix = '&lt;|end|&gt;'</code></pre><p><strong>Step By Step Code Breakdown</strong></p><ul><li><p>Loaded the Phi-4 multimodal model from Hugging Face Transformers, along with the processor for input preprocessing during inference.</p></li><li><p>Defined the generation configuration to be used during inference.</p></li><li><p>Created a prompt template for formatting prompts before passing them to the model.</p></li></ul><h4>Function for Running an Image</h4><pre><code>import requests
from PIL import Image
from urllib.parse import urlparse

def process_image(prompt, image_path):
    prompt = f'{user_prompt}&lt;|image_1|&gt;{prompt}{prompt_suffix}{assistant_prompt}'
    
    # Check if the image_path is a URL or a local file
    parsed_url = urlparse(image_path)
    is_url = bool(parsed_url.scheme and parsed_url.netloc)
    
    if is_url:
        image = Image.open(requests.get(image_path, stream=True).raw)
    else:
        image = Image.open(image_path)
    
    # Process image
    inputs = processor(text=prompt, images=image,  return_tensors='pt').to('cuda:0')

    # Generate output
    generate_ids = model.generate(
        **inputs,
        max_new_tokens=1000,
        generation_config=generation_config,
    )

    # Retrieve response from the generated output
    generate_ids = generate_ids[:, inputs['input_ids'].shape[1]:]
    response = processor.batch_decode(
        generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False
    )[0]
    
    return response</code></pre><p><strong>Step By Step Code Breakdown</strong></p><p>The function <em><strong>process_image</strong></em> accepts a prompt and an image path. It performs the following tasks:</p><ul><li><p>Loads the image and passes both the prompt and image to the processor for preprocessing.</p></li><li><p>The processed inputs from the processor are passed to the <em><strong>generate</strong></em> function to produce an output based on the image and prompt.</p></li><li><p>The generated output is post-processed to extract the response, which is then returned.</p></li></ul><p><strong>Sample Image</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9xf7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b5562a2-cf74-4d1d-9e4a-c5c9952f75c0_1280x1280.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9xf7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b5562a2-cf74-4d1d-9e4a-c5c9952f75c0_1280x1280.jpeg 424w, https://substackcdn.com/image/fetch/$s_!9xf7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b5562a2-cf74-4d1d-9e4a-c5c9952f75c0_1280x1280.jpeg 848w, https://substackcdn.com/image/fetch/$s_!9xf7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b5562a2-cf74-4d1d-9e4a-c5c9952f75c0_1280x1280.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!9xf7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b5562a2-cf74-4d1d-9e4a-c5c9952f75c0_1280x1280.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9xf7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b5562a2-cf74-4d1d-9e4a-c5c9952f75c0_1280x1280.jpeg" width="1280" height="1280" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0b5562a2-cf74-4d1d-9e4a-c5c9952f75c0_1280x1280.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1280,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9xf7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b5562a2-cf74-4d1d-9e4a-c5c9952f75c0_1280x1280.jpeg 424w, https://substackcdn.com/image/fetch/$s_!9xf7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b5562a2-cf74-4d1d-9e4a-c5c9952f75c0_1280x1280.jpeg 848w, https://substackcdn.com/image/fetch/$s_!9xf7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b5562a2-cf74-4d1d-9e4a-c5c9952f75c0_1280x1280.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!9xf7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b5562a2-cf74-4d1d-9e4a-c5c9952f75c0_1280x1280.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://pixabay.com/photos/kingfisher-bird-animal-plumage-1068684/">Source</a></figcaption></figure></div><pre><code>image_path = "https://cdn.pixabay.com/photo/2015/11/29/13/08/kingfisher-1068684_1280.jpg"
prompt = "What is shown in this image?"
process_image(prompt, image_path)</code></pre><p><strong>Generated Response</strong></p><pre><code>The image depicts a kingfisher in mid-flight, with its wings spread wide 
as it dives towards the water. The bird is captured in a moment of action, 
with water droplets visible around it, indicating the speed of its descent. 
The background is blurred, focusing the viewer's attention on the bird and 
the splashing water.</code></pre><p><strong>Sample Image 2</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!q9En!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1544116d-1c66-4a15-ac22-2d6d5fe98f8e_1280x853.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!q9En!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1544116d-1c66-4a15-ac22-2d6d5fe98f8e_1280x853.jpeg 424w, https://substackcdn.com/image/fetch/$s_!q9En!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1544116d-1c66-4a15-ac22-2d6d5fe98f8e_1280x853.jpeg 848w, https://substackcdn.com/image/fetch/$s_!q9En!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1544116d-1c66-4a15-ac22-2d6d5fe98f8e_1280x853.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!q9En!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1544116d-1c66-4a15-ac22-2d6d5fe98f8e_1280x853.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!q9En!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1544116d-1c66-4a15-ac22-2d6d5fe98f8e_1280x853.jpeg" width="1280" height="853" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1544116d-1c66-4a15-ac22-2d6d5fe98f8e_1280x853.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:853,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!q9En!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1544116d-1c66-4a15-ac22-2d6d5fe98f8e_1280x853.jpeg 424w, https://substackcdn.com/image/fetch/$s_!q9En!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1544116d-1c66-4a15-ac22-2d6d5fe98f8e_1280x853.jpeg 848w, https://substackcdn.com/image/fetch/$s_!q9En!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1544116d-1c66-4a15-ac22-2d6d5fe98f8e_1280x853.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!q9En!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1544116d-1c66-4a15-ac22-2d6d5fe98f8e_1280x853.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://pixabay.com/photos/photographer-man-camera-beach-sea-1867417/">Source</a></figcaption></figure></div><pre><code>image_path = "https://cdn.pixabay.com/photo/2016/11/29/04/54/photographer-1867417_1280.jpg"
prompt = "Describe this image."
process_image(prompt, image_path)</code></pre><p><strong>Generated Response </strong></p><pre><code>The image shows a person with a blurred face, wearing a black jacket 
and a watch, holding a Sony camera with a lens cap on. 
The background features a body of water and a hilly landscape.</code></pre><blockquote><p>The model was able to generate accurate responses describing the images.</p></blockquote><h4>Function for Running on Audio</h4><pre><code>import soundfile as sf
import io
from urllib.request import urlopen

def process_audio(prompt, audio_path):
    prompt = f'{user_prompt}&lt;|audio_1|&gt;{prompt}{prompt_suffix}{assistant_prompt}'
    # Download and open audio file
    audio, samplerate = sf.read(io.BytesIO(urlopen(audio_path).read()))
    
    # Process with the audio
    inputs = processor(text=prompt, audios=[(audio, samplerate)], return_tensors='pt').to('cuda:0')

    # Generate response
    generate_ids = model.generate(
        **inputs,
        max_new_tokens=1000,
        generation_config=generation_config,
    )
    generate_ids = generate_ids[:, inputs['input_ids'].shape[1]:]
    response = processor.batch_decode(
        generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False
    )[0]

    return response</code></pre><p><strong>Step By Step Code Breakdown</strong></p><p>The function <em><strong>process_audio </strong></em>accepts a prompt and an audio path. It performs a similar task to the image processing function. It does the following:</p><ul><li><p>Loads the audio file and passes both the prompt and audio to the processor for preprocessing.</p></li><li><p>The processed inputs from the processor are passed to the <em>generate</em> function to produce an output based on the audio and prompt.</p></li><li><p>The generated output is post-processed to extract the response, which is then returned.</p></li></ul><pre><code>audio_prompt = """Transcribe the audio to text"""
audio_url = "https://upload.wikimedia.org/wikipedia/commons/b/b0/Barbara_Sahakian_BBC_Radio4_The_Life_Scientific_29_May_2012_b01j5j24.flac"
print(process_audio(audio_prompt, audio_url))</code></pre><p><strong>Generated Response from Audio</strong></p><pre><code>what we do as a society we have to think about where we're moving to i frequently talk to students 
about cognitive enhancing drugs and a lot of students take them for 
studying and exams but other students feel angry about this they feel 
those students are cheating and we have no long-term health and safety 
studies in healthy people and we really need those 
before people start taking them. </code></pre><p><strong>Read more about the full technical details of Phi-4 mini and Phi-4 Multimodal from the paper:</strong></p><p><a href="https://arxiv.org/abs/2503.01743">Phi-4-Mini Technical Report: Compact yet Powerful Multimodal Language Models via Mixture-of-LoRAs</a></p><h4>Conclusion</h4><p>I explained how a basic multimodal model works including the fundamental logic behind how most multimodal models that process text and images align these modalities, allowing images to be treated similarly to texts to solve visual tasks. I reviewed the Phi-4 multimodal model, describing how it was trained to handle both text and audio modalities. Additionally, I explained step-by-step the architectural design of the Phi-4 multimodal model detailing how it processes image and audio (speech) inputs and adapts to tasks such as image captioning and audio description. I also provided code samples demonstrating how to run the Phi-4 multimodal model on images and audio to generate responses.</p><p><strong>Reach to me via:</strong></p><p>Email: <a href="https://mail.google.com/mail/u/0/#inbox">olafenwaayoola@gmail.com</a></p><p>Linkedin: <a href="https://www.linkedin.com/in/ayoola-olafenwa-003b901a9/">https://www.linkedin.com/in/ayoola-olafenwa-003b901a9/</a></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ayoolaolafenwa.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Ayoola&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Deploying Large Language Models: vLLM and Quantization]]></title><description><![CDATA[Step by Step Guide on How to Accelerate Large Language Models]]></description><link>https://ayoolaolafenwa.substack.com/p/deploying-large-language-models-vllm</link><guid isPermaLink="false">https://ayoolaolafenwa.substack.com/p/deploying-large-language-models-vllm</guid><dc:creator><![CDATA[Ayoola Olafenwa]]></dc:creator><pubDate>Fri, 05 Apr 2024 08:49:31 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!B7xw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a8e9193-380d-4ed1-ba78-48177caf15aa_1600x1333.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!B7xw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a8e9193-380d-4ed1-ba78-48177caf15aa_1600x1333.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!B7xw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a8e9193-380d-4ed1-ba78-48177caf15aa_1600x1333.jpeg 424w, https://substackcdn.com/image/fetch/$s_!B7xw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a8e9193-380d-4ed1-ba78-48177caf15aa_1600x1333.jpeg 848w, https://substackcdn.com/image/fetch/$s_!B7xw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a8e9193-380d-4ed1-ba78-48177caf15aa_1600x1333.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!B7xw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a8e9193-380d-4ed1-ba78-48177caf15aa_1600x1333.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!B7xw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a8e9193-380d-4ed1-ba78-48177caf15aa_1600x1333.jpeg" width="1456" height="1213" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9a8e9193-380d-4ed1-ba78-48177caf15aa_1600x1333.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1213,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!B7xw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a8e9193-380d-4ed1-ba78-48177caf15aa_1600x1333.jpeg 424w, https://substackcdn.com/image/fetch/$s_!B7xw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a8e9193-380d-4ed1-ba78-48177caf15aa_1600x1333.jpeg 848w, https://substackcdn.com/image/fetch/$s_!B7xw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a8e9193-380d-4ed1-ba78-48177caf15aa_1600x1333.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!B7xw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a8e9193-380d-4ed1-ba78-48177caf15aa_1600x1333.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://unsplash.com/photos/a-computer-chip-with-the-letter-a-on-top-of-it-eGGFZ5X2LnA">source</a></figcaption></figure></div><h2>Deployment of Large Language Models&nbsp;(LLMs)</h2><p>We live in an amazing time of Large Language Models like ChatGPT, GPT-4, and Claude that can perform multiple amazing tasks. In practically every field, ranging from education, healthcare to arts and business, Large Language Models are being used to facilitate efficiency in delivering services. Over the past year, many brilliant open-source Large Language Models, such as Llama, Mistral, Falcon, and Gemma, have been released. These open-source LLMs are available for everyone to use, but deploying them can be very challenging as they can be very slow and require a lot of GPU compute power to run for real-time deployment. Different tools and approaches have been created to simplify the deployment of Large Language Models.</p><p>Many deployment tools have been created for serving LLMs with faster inference, such as vLLM, c2translate, TensorRT-LLM, and llama.cpp. Quantization techniques are also used to optimize GPUs for loading very large Language Models. In this article, I will explain how to deploy Large Language Models with vLLM and quantization.</p><h3><strong>Latency and Throughput</strong></h3><p>Some of the major factors that affect the speed performance of a Large Language Model are GPU hardware requirements and model size. The larger the size of the model, the more GPU compute power is required to run it. Common benchmark metrics used in measuring the speed performance of a Large Language Model are <em><strong>Latency</strong></em> and <em><strong>Throughput</strong></em>.</p><p><strong>Latency:</strong> This is the time required for a Large Language Model to generate a response. It is usually measured in seconds or milliseconds.</p><p><strong>Throughput:</strong> This is the number of tokens generated per second or millisecond from a Large Language Model.</p><h3>Install Required&nbsp;Packages</h3><p>Below are the two required packages for running a Large Language Model: Hugging Face <em><strong>transformers </strong></em>and <em><strong>accelerate</strong></em>.&nbsp;</p><pre><code>pip3 install transformers
pip3 install accelerate</code></pre><h3>Run Phi-2 Model</h3><p><em><strong>Phi-2</strong></em> is a state-of-the-art foundation model from Microsoft with 2.7 billion parameters. It was pre-trained with a variety of data sources, ranging from code to textbooks. Learn more about <em><strong>Phi-2</strong></em> from <a href="https://huggingface.co/microsoft/phi-2">here</a>.</p><h4>Benchmarking LLM Latency and Throughput with Hugging Face Transformers</h4><pre><code>import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
import time

torch.set_default_device("cuda")

model = AutoModelForCausalLM.from_pretrained("microsoft/phi-2", torch_dtype = "auto", trust_remote_code=True)

tokenizer = AutoTokenizer.from_pretrained("microsoft/phi-2", trust_remote_code=True)

prompt = "Generate a python code that accepts a list of numbers and returns the sum."

inputs = tokenizer(prompt, return_tensors="pt", return_attention_mask=False)</code></pre><ul><li><p>Loaded <em><strong>Phi-2 </strong></em>model and its tokenizer.</p></li><li><p>Created the prompt &#8220;<em><strong>Generate a python code that accepts a list of numbers and returns the sum.</strong></em>&#8221;&nbsp;to be passed to the model </p></li><li><p>Tokenized the prompt. </p></li></ul><pre><code>start = time.time()
response = model.generate(**inputs, max_new_tokens= 200,
temperature = 0.5)
end = time.time()

latency = end-start
print(f"Latency: {latency} seconds")

output_tokens = len(response[0])
through_put = output_tokens / latency
print(f"Throughput: {through_put} tokens/second")

text = tokenizer.decode(response[0])
print(text)</code></pre><ul><li><p>Generated a response from the model.</p></li><li><p>Obtained the <em><strong>latency</strong></em> by calculating the time required to generate the response.&nbsp;</p></li><li><p>Obtained the total length of tokens in the response generated, divided it by the <em><strong>latency</strong></em> and calculated the <em><strong>throughput</strong></em>. &nbsp;</p></li><li><p>Printed the generated response</p></li></ul><h4>Generated Output</h4><pre><code>Latency: 2.739394464492798 seconds
Throughput: 32.36171766303386 tokens/second
Generate a python code that accepts a list of numbers and returns the sum. [1, 2, 3, 4, 5]
A: def sum_list(numbers):
    total = 0
    for num in numbers:
        total += num
    return total

print(sum_list([1, 2, 3, 4, 5]))</code></pre><p>This model was run on an A1000 (16GB GPU), and it achieves a <em><strong>latency</strong></em> of <em><strong>2.7 seconds</strong></em> and a throughput of <em><strong>32 tokens/second.&nbsp;</strong></em></p><h2>Deployment of A Large Language Model with&nbsp;vLLM</h2><p>vLLM is an open source LLM library for serving Large Language Models at low <em><strong>latency</strong></em> and high <em><strong>throughput</strong></em>.</p><h3>How vLLM&nbsp;works</h3><p>The transformer is the building block of Large Language Models. The transformer network uses a mechanism called the <em><strong>attention mechanism</strong></em>, which is used by the network to study and understand the context of words. The <em><strong>attention mechanism</strong></em> is made up of a bunch of mathematical calculations of matrices known as attention keys and values. The memory used by the interaction of these attention keys and values affects the speed of the model. </p><p>vLLM introduced a new attention mechanism called <em><strong>PagedAttention</strong></em> that efficiently manages the allocation of memory for the transformer&#8217;s attention keys and values during the generation of tokens. The memory efficiency of vLLM has proven very useful in running Large Language Models at low latency and high throughput. This is a high-level explanation of how vLLM works. To learn more in-depth technical details, read the <a href="https://blog.vllm.ai/2023/06/20/vllm.html">vLLM documentation</a>.</p><h4><strong>Install vLLM</strong></h4><pre><code>pip3 install vllm==0.3.3</code></pre><h4><strong>Run Phi-2 with&nbsp;vLLM</strong></h4><pre><code>from vllm import LLM, SamplingParams
import time

llm = LLM("microsoft/phi-2", trust_remote_code = True)

sampling_params = SamplingParams(temperature = 0.5, max_tokens = 200)</code></pre><ul><li><p>Imported required packages from vLLM for running <em><strong>Phi-2</strong></em>.&nbsp;</p></li><li><p>Loaded <em><strong>Phi-2</strong></em> with vLLM. </p></li><li><p>Set important parameters for  model&#8217;s generation.</p></li></ul><pre><code>prompt = "Generate a python code that accepts a list of numbers and returns the sum."

start = time.time()
response = llm.generate(prompt, sampling_params)
end = time.time()

latency = end-start
print(f"Latency: {latency}seconds")

output_tokens = len(response[0].outputs[0].token_ids)

through_put = output_tokens / latency

print(f"Throughput: {through_put}tokens/second")

generated_text = response[0].outputs[0].text
print(generated_text)</code></pre><ul><li><p>Defined the prompt, &#8220;<em><strong>Generate a python code that accepts a list of numbers and returns the sum.</strong></em>&#8221;</p></li><li><p>Generated the model&#8217;s response using <em><strong>llm.generate </strong></em>and computed the <em><strong>latency</strong></em>.<em><strong>&nbsp;</strong></em></p></li><li><p>Obtained the length of total tokens generated from the response.</p></li><li><p>Divided the length of tokens by the <em><strong>latency</strong></em> to get the <em><strong>throughput</strong></em> </p></li><li><p>Printed the generated text.</p></li></ul><h4>Generated Output</h4><pre><code>Latency: 1.218436622619629seconds
Throughput: 63.15334836428132tokens/second
 [1, 2, 3, 4, 5]
A: def sum_list(numbers):
    total = 0
    for num in numbers:
        total += num
    return total

numbers = [1, 2, 3, 4, 5]
print(sum_list(numbers))</code></pre><p>I ran <em><strong>Phi-2</strong></em> with vLLM on the same prompt, <em><strong>&#8220;Generate a python code that accepts a list of numbers and returns the sum.&#8221;</strong></em> On the same GPU, an A1000 (16GB GPU), vLLM produces a <em><strong>latency</strong></em> of <em><strong>1.2 seconds</strong></em> and a <em><strong>throughput</strong></em> of <em><strong>63 tokens/second</strong></em>, compared to Hugging Face transformers&#8217; <em><strong>latency</strong></em> of <em><strong>2.85 seconds</strong></em> and a <em><strong>throughput</strong></em> of <em><strong>32 tokens/second.</strong></em> Running a Large Language Model with vLLM produces the same accurate result as using Hugging Face, with much lower latency and higher throughput.</p><h3> <strong>Note</strong> </h3><blockquote><p>The metrics (latency and throughput) I obtained for vLLM are estimated benchmarks for vLLM performance. The model generation speed depends on many factors, such as the length of the input prompt and the size of the GPU. According to the official vLLM report, running an LLM model on a powerful GPU like the A100 in a production setting with vLLM achieves 24 times higher throughput than Hugging Face Transformers</p></blockquote><h2>Benchmarking Latency and Throughput in Real&nbsp;Time</h2><p>The way I calculated the latency and throughput for running Phi-2 is experimental, and I did this to explain how vLLM accelerates a Large Language Model&#8217;s performance. In the real-world use case of LLMs, such as a chat-based system where the model outputs a token as it is generated, measuring the latency and throughput is more complex.</p><p>A chat-based system is based on streaming output tokens. Some of the major factors that affect the LLM metrics are <em><strong>Time to First Token</strong></em> (the time required for a model to generate the first token), <em><strong>Time Per Output Token</strong></em> (the time spent per output token generated), <em><strong>the input sequence length, the expected output, the total expected output tokens</strong></em>, and <em><strong>the model size</strong></em>. In a chat-based system, the latency is usually a combination of <em><strong>Time to First Token</strong></em> and <em><strong>Time Per Output Token</strong></em> multiplied by the total expected output tokens.</p><p>The longer the input sequence length passed into a model, the slower the response. Some of the approaches used in running LLMs in real-time involve batching users&#8217; input requests or prompts to perform inference on the requests concurrently, which helps in improving the throughput. Generally, using a powerful GPU and serving LLMs with efficient tools like vLLM improves both the latency and throughput in real-time.</p><h3><strong><a href="https://colab.research.google.com/drive/171tVs8nndleyYHoFr1PVm6YRvYg6kBCx?usp=sharing">Run the vLLM deployment on Google Colab</a></strong></h3><p></p><h2>Quantization of Large Language&nbsp;Models</h2><p>Quantization is the conversion of a machine learning model from a higher precision to a lower precision by shrinking the model&#8217;s weights into smaller bits, usually <em><strong>8-bit</strong></em> or <em><strong>4-bit</strong></em>. Deployment tools like vLLM are very useful for inference serving of Large Language Models at very low latency and high throughput. We are able to run <em><strong>Phi-2</strong></em> with Hugging Face and vLLM conveniently on the T4 GPU on Google Colab because it is a smaller LLM with <em><strong>2.7 billion parameters</strong></em>. For example, a <em><strong>7-billion-parameter </strong></em>model like <em><strong>Mistral 7B</strong></em> cannot be run on Colab with either Hugging Face or vLLM. Quantization is best for managing GPU hardware requirements for Large Language Models. When GPU availability is limited and we need to run a very large Language Model, quantization is the best approach to load LLMs on constrained devices.</p><h3>BitsandBytes</h3><p>It is a python library built with custom quantization functions for shrinking model&#8217;s weights into lower bits(<em><strong>8-bit</strong></em> and <em><strong>4-bit</strong></em>).&nbsp;</p><p><strong>Install BitsandBytes</strong></p><pre><code>pip3 install bitsandbytes</code></pre><h3>Quantization of Mistral 7B&nbsp;Model&nbsp;</h3><p><em><strong>Mistral 7B</strong></em>, a 7-billion-parameter model from MistralAI, is one of the best state-of-the-art open-source Large Language Models. I will go through a step-by-step process of running <em><strong>Mistral 7B</strong></em> with different quantization techniques that can be run on the T4 GPU on Google Colab.</p><h3><strong>Quantization with 8-bit Precision</strong></h3><p>This is the conversion of a machine learning model&#8217;s weight into 8-bit precision. <em><strong>BitsandBytes</strong></em> has been integrated with Hugging Face transformers to load a language model using the same Hugging Face code, but with minor modifications for quantization.&nbsp;</p><pre><code>from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig

quant_config = BitsAndBytesConfig(
    load_in_8bit=True
)

model = AutoModelForCausalLM.from_pretrained("mistralai/Mistral-7B-Instruct-v0.2", device_map = "auto",quantization_config = quant_config)

tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.2")</code></pre><ul><li><p>Imported the needed packages for running model, including the <em><strong>BitsandBytesConfig</strong></em> library.&nbsp;</p></li><li><p>Defined the quantization config and set the parameter <em><strong>load_in_8bit</strong></em> to <em><strong>true</strong></em> for loading the model&#8217;s weights in <em><strong>8-bit </strong></em>precision.&nbsp; </p></li><li><p>Passed the quantization config into the function for loading the model, set the parameter <em><strong>device_map</strong></em> for <em><strong>bitsandbytes</strong></em> to automatically allocate appropriate GPU memory for loading the model.</p></li><li><p>Loaded the tokenizer weights.</p></li></ul><p></p><h3><strong>Quantization with 4-bit Precision</strong></h3><p>This is the conversion of a machine learning model&#8217;s weight into <em><strong>4-bi</strong></em>t precision.</p><pre><code>from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch

quant_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_compute_dtype=torch.bfloat16
)

model = AutoModelForCausalLM.from_pretrained("mistralai/Mistral-7B-Instruct-v0.2",device_map = "auto", quantization_config = quant_config)

tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.2")</code></pre><p>The code for loading <em><strong>Mistral 7B</strong></em> in 4-bit precision is similar to that of <em><strong>8-bit</strong></em> precision except for a few changes:</p><ul><li><p>Changed <em><strong>load_in_8bit </strong></em>to <em><strong>load_in_4bit</strong></em>.&nbsp;</p></li><li><p>A new parameter <em><strong>bnb_4bit_compute_dtype</strong></em> is introduced into the <em><strong>BitsandBytesConfig</strong></em> to perform the model&#8217;s computation in <em><strong>bfloat16</strong></em>. <em><strong>bfloat16</strong></em> is computation data type for loading model&#8217;s weights for faster inference. It can be used with both <strong>4-bit</strong> and <em><strong>8-bit </strong></em>precisions<em><strong>. </strong></em>If it is in <em><strong>8-bit</strong></em> you just need to change the parameter from <em><strong>bnb_4bit_compute_dtype </strong></em>to<em><strong> bnb_8bit_compute_dtype.</strong></em></p></li></ul><h3>NF4(4-bit Normal Float) and <strong>Double Quantization</strong></h3><p><strong>NF4 (4-bit Normal Float)</strong> from QLoRA is an optimal quantization approach that yields better results than the standard 4-bit quantization. It is integrated with double quantization, where quantization occurs twice; quantized weights from the first stage of quantization are passed into the next stage of quantization, yielding optimal float range values for the model&#8217;s weights. According to the report from the QLoRA paper, <em><strong>NF4 with double quantization</strong></em> does not suffer from a drop in accuracy performance. Read more in-depth technical details about NF4 and Double Quantization from the <a href="https://arxiv.org/abs/2305.14314">QLoRA paper</a>.</p><pre><code>from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch

quant_config = BitsAndBytesConfig(
   load_in_4bit=True,
   bnb_4bit_quant_type="nf4",
   bnb_4bit_use_double_quant=True,
   bnb_4bit_compute_dtype=torch.bfloat16
)

model = AutoModelForCausalLM.from_pretrained("mistralai/Mistral-7B-Instruct-v0.2", device_map = "auto", quantization_config = quant_config)

tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.2")</code></pre><p>The code for loading model in <em><strong>nf4 </strong></em>and <em><strong>double quantization</strong></em> is similar with extra parameters set in the <em><strong>BitsandBytesConfig:</strong></em></p><ul><li><p><em><strong>load_4bit:</strong></em> Loading model in 4-bit precision is set to true.</p></li><li><p><em><strong>bnb_4bit_quant_type:</strong></em> The type of quantization is set to <em><strong>nf4</strong></em>.</p></li><li><p><em><strong>bnb_4bit_use_double_quant:</strong></em> Double quantization is set to True.</p></li><li><p><em><strong>bnb_4_bit_compute_dtype:</strong></em> <em><strong>bfloat16</strong></em> Computation data type is used for faster inference.&nbsp;</p></li></ul><p><strong>Line 11&#8211;13: </strong>Loaded the model&#8217;s weights and tokenizer.&nbsp;</p><h4><strong>Full Code for Model Quantization</strong></h4><pre><code>from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch

quant_config = BitsAndBytesConfig(
   load_in_4bit=True,
   bnb_4bit_quant_type="nf4",
   bnb_4bit_use_double_quant=True,
   bnb_4bit_compute_dtype=torch.bfloat16
)

#uncomment for 8bit precision
"""quant_config = BitsAndBytesConfig(
    load_in_8bit=True
)"""

#uncomment for 4bit precision
"""quant_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_compute_dtype=torch.bfloat16
) """

model = AutoModelForCausalLM.from_pretrained("mistralai/Mistral-7B-Instruct-v0.2", device_map = "auto",quantization_config = quant_config)

tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.2")

prompt = [
    {"role": "user", "content": "What is Natural Language Processing?"}
]
inputs = tokenizer.apply_chat_template(prompt, return_tensors="pt")

model_inputs = inputs.to("cuda")

generated_ids = model.generate(model_inputs, max_new_tokens=200, do_sample=True)

decoded = tokenizer.batch_decode(generated_ids)
print(decoded[0])</code></pre><h4>Generated Output</h4><pre><code>&lt;s&gt; [INST] What is Natural Language Processing? [/INST] Natural Language Processing (NLP) is a subfield of artificial intelligence (AI) and
computer science that deals with the interaction between computers and human language. Its main objective is to read, decipher, 
understand, and make sense of the human language in a valuable way. It can be used for various tasks such as speech recognition, 
text-to-speech synthesis, sentiment analysis, machine translation, part-of-speech tagging, name entity recognition, 
summarization, and question-answering systems. NLP technology allows machines to recognize, understand,
 and respond to human language in a more natural and intuitive way, making interactions more accessible and efficient.&lt;/s&gt;</code></pre><p>Quantization is a very good approach for optimizing the running of very Large Language Models on smaller GPUs and can be applied to any model, such as Llama 70B, Falcon 40B, and mpt-30b. According to reports from the <a href="https://arxiv.org/abs/2208.07339">LLM.int8 paper</a>, very Large Language Models suffer less from accuracy drops when quantized compared to smaller ones. Quantization is best applied to very Large Language Models and does not work well for smaller models because of the loss in accuracy performance.</p><h3><strong><a href="https://colab.research.google.com/drive/1aYPlWaHC4iy6DLFjR291iEMxFvgeM1ia?usp=sharing">Run Mistral 7B Quantization on Google Colab</a></strong></h3><h3>Conclusion</h3><p>In this article, I provided a step-by-step approach to measuring the speed performance of a Large Language Model, explained how vLLM works, and how it can be used to improve the latency and throughput of a Large Language Model. Finally, I explained quantization and how it is used to load Large Language Models on small-scale GPUs.</p><h2><strong>Reach to me via:</strong></h2><p>Email: <a href="https://mail.google.com/mail/u/0/#inbox">olafenwaayoola@gmail.com</a></p><p>Linkedin: <a href="https://www.linkedin.com/in/ayoola-olafenwa-003b901a9/">https://www.linkedin.com/in/ayoola-olafenwa-003b901a9/</a></p><h3><strong>References</strong></h3><p><a href="https://blog.vllm.ai/2023/06/20/vllm.html">vLLM: Easy, Fast, and Cheap LLM Serving with PagedAttention</a></p><p><a href="https://huggingface.co/blog/4bit-transformers-bitsandbytes">Making LLMs even more accessible with bitsandbytes, 4-bit quantization and QLoRA</a></p><p><a href="https://www.tensorops.ai/post/what-are-quantized-llms">What are Quantized LLMs</a></p><p><a href="https://www.baseten.co/blog/understanding-performance-benchmarks-for-llm-inference/">Understanding performance benchmarks for LLM inference</a></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://ayoolaolafenwa.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Ayoola&#8217;s Substack! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>