Our first “agent” will be a simple system where an LLM can chose to call one (or more) tools in order to answer the user’s query. (think ChatGPT searching the web). The response is then assessed by the same LLM to decide if the information is relevant and respond to the user.
As outlined earlier, we shall be building our system in Python with LangGraph. Familiarity with Python, therefore, is important.
As with all AI projects, we’re going to need a few API keys.
For this tutorial we’re using Anthropic (for the LLM) and Tavily (for Web Search)
Once API keys are obtained they need to be imported into the project.
If you’re working with a regular script, create a .env file in the same directory.
ANTHROPIC_API_KEY=sk-ant-XXX
TAVILY_API_KEY=tvly-XXX
Make sure you have dotenv installed then add to your imports:
from dotenv import load_dotenv
load_dotenv()
LangGraph works with many chat models. We’ll be using Anthropic’s Claude.
As per LangChain, inference is run by the method llm.invoke()
from langchain_anthropic import ChatAnthropic
llm = ChatAnthropic(model="claude-3-5-haiku-latest")
response = llm.invoke("How many r's are there in strawberry?")
print(response)
Things start getting fun when we introduce tools.
Tavily Search is a simple API for searching the web to retrieve information. It has a ready made LangChain package. This can be run independently with .invoke(), but we’re going to be integrating it with our agent. To do this we create a tools array and “bind” it to our llm.
search_tool = TavilySearchResults(max_results=2)
# just for reference
# search_tool.invoke("Where is San Francisco?")
tools = [search_tool]
llm_with_tools = llm.bind_tools(tools)
# but if you run this, you don't actually get a search
response = llm_with_tools.invoke("Where is San Francisco?")
print(response)
===================== RESPONSE ============================="""
content=[{'text': "Let me search for some precise information about San Francisco's location.",
'type': 'text'}, {'id': 'toolu_013QEy4JA6jFCDFHiotmFTSU',
'input': {'query': 'San Francisco location geography California'},
'name': 'tavily_search_results_json', 'type': 'tool_use'}] additional_kwargs={}
response_metadata={'id': 'msg_01QYirdgr2UR2vEFuXghuG6x', 'model': 'claude-3-5-haiku-20241022',
'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0,
'cache_read_input_tokens': 0, 'input_tokens': 372, 'output_tokens': 76}} id='run-9e8beb9b-a69d-43a0-9abc-5c31f5a38ba4-0'
tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'San Francisco location geography California'},
'id': 'toolu_013QEy4JA6jFCDFHiotmFTSU', 'type': 'tool_call'}] usage_metadata={'input_tokens': 372, 'output_tokens': 76,
'total_tokens': 448, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}}"""
This doesn’t call the search tool just yet, but if we look at response.tool_calls this contains the details to call the correct tool when passed to a router.
print(response.tool_calls)
===============================
[{'name': 'tavily_search_results_json',
'args': {'query': 'San Francisco location geography California'},
'id': 'toolu_013QEy4JA6jFCDFHiotmFTSU',
'type': 'tool_call'}]