<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Matt Norris</title>
    <description>The latest articles on DEV Community by Matt Norris (@mattnorris).</description>
    <link>https://dev.to/mattnorris</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1183630%2F79e6346f-415c-4dab-8877-6b78b20462f0.jpg</url>
      <title>DEV Community: Matt Norris</title>
      <link>https://dev.to/mattnorris</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mattnorris"/>
    <language>en</language>
    <item>
      <title>How to make an AI web search agent</title>
      <dc:creator>Matt Norris</dc:creator>
      <pubDate>Wed, 28 May 2025 15:58:17 +0000</pubDate>
      <link>https://dev.to/mattnorris/how-to-make-an-ai-web-search-agent-g6j</link>
      <guid>https://dev.to/mattnorris/how-to-make-an-ai-web-search-agent-g6j</guid>
      <description>&lt;h2&gt;
  
  
  AI agents
&lt;/h2&gt;

&lt;p&gt;As a software developer, I create scripts and applications to automate the tedious parts of my job - and my life. Rather than tools in a tool belt, I have always thought of these apps as a menagerie of creatures. Each has its own abilities. To solve a problem, I can simply select one and unleash it on the world, where it can run along and do my bidding.&lt;/p&gt;

&lt;p&gt;In our modern world of AI agents, this fantasy of mine has grown more real.&lt;/p&gt;

&lt;p&gt;AI is now at the point where these little minions can each be imbued with their own intelligence. Any one of us can have our own digital workforce acting on our behalf. Any one can control their own menagerie of AI agents.&lt;/p&gt;

&lt;p&gt;At work, we create agents using frameworks like &lt;a href="https://mattnorris.dev/notes/what-is-langgraph" rel="noopener noreferrer"&gt;LangGraph&lt;/a&gt;. LangGraph allows us to wrangle these agents into a defined structure, a graph. Without this graph, you may find yourself feeling like Sorcerer Mickey from the movie Fantasia. His army of brooms were well-intentioned, but they did not heed his commands. So it can be with your agents.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6gvyidupo59mgcxzfn8j.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6gvyidupo59mgcxzfn8j.jpeg" alt="Sorcerer Mickey trampled by his army of brooms" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the most popular agents available is the research assistant. These agents scour the web reading articles and gathering resources for you. They summarize the results and answer your question, citing their sources. If you have ever used &lt;a href="https://mattnorris.dev/notes/what-is-perplexity" rel="noopener noreferrer"&gt;Perplexity&lt;/a&gt;, you've seen one in action.&lt;/p&gt;

&lt;p&gt;My team built an agent to research companies. It is powered by &lt;a href="https://mattnorris.dev/notes/what-is-tavily" rel="noopener noreferrer"&gt;Tavily&lt;/a&gt;, an API designed to fetch real-time search results for AI applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;The results from Tavily were great! We gave it a company name and got up-to-date information about that company. But when we pushed the agent to our servers, it stopped working.&lt;/p&gt;

&lt;p&gt;We were puzzled. Why would the same code work locally but fail when pushed to production servers?&lt;/p&gt;

&lt;p&gt;Like most bugs, the problem was hard to pinpoint, but the fix was simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://mattnorris.dev/notes/what-is-langchain" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt; relies heavily on environment variables for configuration. This is convenient, but it can be a problem when you need more control for security reasons. Some of these variables, like API keys, are &lt;a href="https://discourse.jupyter.org/t/set-protected-environment-variables/5231/2" rel="noopener noreferrer"&gt;secrets that should not be exposed&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To gain control of how LangChain configures Tavily, use the &lt;code&gt;TavilySearchAPIWrapper&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Tools for the web search agent.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_community.tools.tavily_search&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TavilySearchResults&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_community.utilities.tavily_search&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TavilySearchAPIWrapper&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.search_tool_config&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SearchToolConfig&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_search_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search_tool_config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SearchToolConfig&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get the search tool.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# NOTE: Use the search wrapper to avoid environment variable issues.
&lt;/span&gt;    &lt;span class="n"&gt;wrapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TavilySearchAPIWrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tavily_api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;search_tool_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;max_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;search_tool_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_results&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;TavilySearchResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_wrapper&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_results&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;max_results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://replit.com/@mattnorris/agentwebsearch" rel="noopener noreferrer"&gt;Remix this app on Replit&lt;/a&gt; to try it out. You may also &lt;a href="https://github.com/mattnorris/agent_web_search/blob/main/src/agent_web_search/tools/tools.py" rel="noopener noreferrer"&gt;view the code&lt;/a&gt; within the context of its project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Greater context
&lt;/h2&gt;

&lt;p&gt;So if the fix was simple, why create an entire code repository around it? Why write an article? Surely a Gist would suffice.&lt;/p&gt;

&lt;p&gt;My initial instinct was to simply repost the solution from the GitHub discussion. That way it could help others facing the same issue. But I couldn't help considering the greater context. I then built a better example with this context in mind.&lt;/p&gt;

&lt;h3&gt;
  
  
  Proofs-of-concept vs. production
&lt;/h3&gt;

&lt;p&gt;Proofs-of-concept are just that - proof. They prove that an idea can work. But getting something working on your machine is different than hosting it for others to use.&lt;/p&gt;

&lt;p&gt;When you launch an application - when you push its code to a hosted environment - you must consider more than just its functionality. You must consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How you work with others to support and maintain the app&lt;/li&gt;
&lt;li&gt;How to share secrets and other sensitive information with your teammates&lt;/li&gt;
&lt;li&gt;How to protect those secrets from the public&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You must consider how you &lt;em&gt;configure&lt;/em&gt; your application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration vs. code
&lt;/h3&gt;

&lt;p&gt;What if your private code base was made public right now?&lt;/p&gt;

&lt;p&gt;Would your personal API tokens be leaked to the Internet for anyone to use? Would people learn your co-workers' names because you used them for testing purposes?&lt;/p&gt;

&lt;p&gt;The answer to this question is a good measure of how you've configured your code. If you felt no panic - aside from any code copyright or IP issues - then you have configured it well. If your heart started racing, you may want to take another look at your project 🙂&lt;/p&gt;

&lt;p&gt;Separating sensitive information from the code is not only a good security practice, it makes code easier to move from environment to environment - from your machine, to a test environment, to production. It gives you control and flexibility. It allows you to emulate different environments without changing code. Testing and deployment become easier because you have made your code more &lt;em&gt;modular&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modularity
&lt;/h3&gt;

&lt;p&gt;Software is comprised of many modules. You may create these modules yourself, co-locating related code into directories. Or you install these modules from other sources like &lt;a href="https://mattnorris.dev/notes/what-is-pypi" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt; or &lt;a href="https://mattnorris.dev/notes/what-is-npm" rel="noopener noreferrer"&gt;npm&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I created &lt;a href="https://github.com/mattnorris/agent_web_search" rel="noopener noreferrer"&gt;this repository&lt;/a&gt; with modularity in mind.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The agent can run in &lt;a href="https://mattnorris.dev/notes/what-is-langgraph" rel="noopener noreferrer"&gt;LangGraph Studio&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://mattnorris.dev/notes/what-is-poetry" rel="noopener noreferrer"&gt;Poetry&lt;/a&gt; not only manages its dependencies, but it packages the code for reuse. This package is published to PyPi, then ultimately installed in another project.&lt;/li&gt;
&lt;li&gt;It can be imported into a &lt;a href="https://mattnorris.dev/notes/what-is-jupyter" rel="noopener noreferrer"&gt;Jupyter&lt;/a&gt; notebook. But as I mentioned earlier, &lt;a href="https://discourse.jupyter.org/t/set-protected-environment-variables/5231/2" rel="noopener noreferrer"&gt;there isn't a good way to update the environment variables securely&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;It can be imported into a &lt;a href="https://mattnorris.dev/notes/what-is-streamlit" rel="noopener noreferrer"&gt;Streamlit&lt;/a&gt; application and &lt;a href="https://replit.com/@mattnorris/agentwebsearch" rel="noopener noreferrer"&gt;hosted on Replit&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As you build your own menagerie of helper agents, refer to &lt;a href="https://github.com/mattnorris/agent_web_search" rel="noopener noreferrer"&gt;this code&lt;/a&gt; and keep these practices in mind. Make things modular and configure them properly, and you will go from POC to prod in no time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/mattnorris/agent_web_search" rel="noopener noreferrer"&gt;Web search agent code | GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://replit.com/@mattnorris/agentwebsearch" rel="noopener noreferrer"&gt;Web search agent demo | Replit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/langchain-ai/langchain/discussions/20447#discussioncomment-9381122" rel="noopener noreferrer"&gt;Can't pass TavilySearchResults api key via argument in a Jupyter notebook | GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://12factor.net/config" rel="noopener noreferrer"&gt;Config | The Twelve-Factor App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Photo by &lt;a href="https://unsplash.com/@coopery?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Mohamed Nohassi&lt;/a&gt; | &lt;a href="https://unsplash.com/photos/a-group-of-white-robots-sitting-on-top-of-laptops-2iUrK025cec?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>langchain</category>
      <category>langgraph</category>
    </item>
  </channel>
</rss>
