-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy path19-lc04-agent.qmd
394 lines (297 loc) · 12.3 KB
/
19-lc04-agent.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
---
title: LangChain 04 - Build an Agent
jupyter: python3
---
:::info Prerequisites
This guide assumes familiarity with the following concepts:
- [Chat Models](https://python.langchain.com/docs/concepts/chat_models)
- [Tools](https://python.langchain.com/docs/concepts/tools)
- [Agents](https://python.langchain.com/docs/concepts/agents)
:::
By themselves, language models can't take actions - they just output text.
A big use case for LangChain is creating **agents**.
Agents are systems that use LLMs as reasoning engines to determine which actions
to take and the inputs necessary to perform the action.
After executing actions, the results can be fed back into the LLM to determine
whether more actions are needed, or whether it is okay to finish.
In this tutorial we will build an agent that can interact with a search engine.
You will be able to ask this agent questions, watch it call the search tool, and
have conversations with it.
## End-to-end agent
The code snippet below represents a fully functional agent that uses an LLM to
decide which tools to use. It is equipped with a generic search tool. It has
conversational memory - meaning that it can be used as a multi-turn chatbot.
In the rest of the guide, we will walk through the individual components and what
each part does - but if you want to just grab some code and get started, feel
free to use this!
```{python}
# Import relevant functionality
from langchain_anthropic import ChatAnthropic
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.messages import HumanMessage
from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import create_react_agent
# Create the agent
memory = MemorySaver()
model = ChatAnthropic(model_name="claude-3-sonnet-20240229")
search = TavilySearchResults(max_results=2)
tools = [search]
agent_executor = create_react_agent(model, tools, checkpointer=memory)
# Use the agent
config = {"configurable": {"thread_id": "abc123"}}
for chunk in agent_executor.stream(
{"messages": [HumanMessage(content="hi im bob! and i live in sf")]}, config
):
print(chunk)
print("----")
for chunk in agent_executor.stream(
{"messages": [HumanMessage(content="whats the weather where I live?")]}, config
):
print(chunk)
print("----")
```
## Setup
### Jupyter Notebook
This guide (and most of the other guides in the documentation) uses
[Jupyter notebooks](https://jupyter.org/) and assumes the reader is as well.
Jupyter notebooks are perfect interactive environments for learning how to work
with LLM systems because oftentimes things can go wrong (unexpected output, API
down, etc), and observing these cases is a great way to better understand building
with LLMs.
This and other tutorials are perhaps most conveniently run in a Jupyter notebook.
See [here](https://jupyter.org/install) for instructions on how to install.
### Installation
To install LangChain run:
```{python}
%pip install -U langchain-community langgraph langchain-anthropic tavily-python langgraph-checkpoint-sqlite
```
For more details, see our [Installation guide](https://python.langchain.com/docs/how_to/installation).
### LangSmith
Many of the applications you build with LangChain will contain multiple steps
with multiple invocations of LLM calls.
As these applications get more and more complex, it becomes crucial to be able
to inspect what exactly is going on inside your chain or agent.
The best way to do this is with [LangSmith](https://smith.langchain.com).
After you sign up at the link above, make sure to set your environment variables
to start logging traces:
```shell
export LANGCHAIN_TRACING_V2="true"
export LANGCHAIN_API_KEY="..."
```
Or, if in a notebook, you can set them with:
```python
import getpass
import os
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = getpass.getpass()
```
### Tavily
We will be using
[Tavily](https://python.langchain.com/docs/integrations/tools/tavily_search)
(a search engine) as a tool.
In order to use it, you will need to get and set an API key:
```bash
export TAVILY_API_KEY="..."
```
Or, if in a notebook, you can set it with:
```python
import getpass
import os
os.environ["TAVILY_API_KEY"] = getpass.getpass()
```
## Define tools
We first need to create the tools we want to use. Our main tool of choice will be
[Tavily](https://python.langchain.com/docs/integrations/tools/tavily_search) - a
search engine. We have a built-in tool in LangChain to easily use Tavily search
engine as tool.
```{python}
from langchain_community.tools.tavily_search import TavilySearchResults
search = TavilySearchResults(max_results=2)
search_results = search.invoke("what is the weather in SF")
print(search_results)
# If we want, we can create other tools.
# Once we have all the tools we want, we can put them in a list that we will reference later.
tools = [search]
```
## Using Language Models
Next, let's learn how to use a language model by to call tools. LangChain
supports many different language models that you can use interchangably - select
the one you want to use below!
import ChatModelTabs from "@theme/ChatModelTabs";
<ChatModelTabs openaiParams={`model="gpt-4"`} />
```{python}
#| output: false
#| echo: false
from langchain_anthropic import ChatAnthropic
model = ChatAnthropic(model="claude-3-sonnet-20240229")
```
You can call the language model by passing in a list of messages. By default,
the response is a `content` string.
```{python}
from langchain_core.messages import HumanMessage
response = model.invoke([HumanMessage(content="hi!")])
response.content
```
We can now see what it is like to enable this model to do tool calling. In order
to enable that we use `.bind_tools` to give the language model knowledge of these
tools
```{python}
model_with_tools = model.bind_tools(tools)
```
We can now call the model. Let's first call it with a normal message, and see
how it responds. We can look at both the `content` field as well as the
`tool_calls` field.
```{python}
response = model_with_tools.invoke([HumanMessage(content="Hi!")])
print(f"ContentString: {response.content}")
print(f"ToolCalls: {response.tool_calls}")
```
Now, let's try calling it with some input that would expect a tool to be called.
```{python}
response = model_with_tools.invoke([HumanMessage(content="What's the weather in SF?")])
print(f"ContentString: {response.content}")
print(f"ToolCalls: {response.tool_calls}")
```
We can see that there's now no text content, but there is a tool call! It wants
us to call the Tavily Search tool.
This isn't calling that tool yet - it's just telling us to. In order to actually
call it, we'll want to create our agent.
## Create the agent
Now that we have defined the tools and the LLM, we can create the agent. We will
be using [LangGraph](https://python.langchain.com/docs/concepts/architecture/#langgraph)
to construct the agent.
Currently, we are using a high level interface to construct the agent, but the
nice thing about LangGraph is that this high-level interface is backed by a
low-level, highly controllable API in case you want to modify the agent logic.
Now, we can initialize the agent with the LLM and the tools.
Note that we are passing in the `model`, not `model_with_tools`. That is because
`create_react_agent` will call `.bind_tools` for us under the hood.
```{python}
from langgraph.prebuilt import create_react_agent
agent_executor = create_react_agent(model, tools)
```
## Run the agent
We can now run the agent with a few queries! Note that for now, these are all
**stateless** queries (it won't remember previous interactions). Note that the
agent will return the **final** state at the end of the interaction (which
includes any inputs, we will see later on how to get only the outputs).
First up, let's see how it responds when there's no need to call a tool:
```{python}
response = agent_executor.invoke({"messages": [HumanMessage(content="hi!")]})
response["messages"]
```
In order to see exactly what is happening under the hood (and to make sure it's
not calling a tool) we can take a look at the
[LangSmith trace](https://smith.langchain.com/public/28311faa-e135-4d6a-ab6b-caecf6482aaa/r)
Let's now try it out on an example where it should be invoking the tool
```{python}
response = agent_executor.invoke(
{"messages": [HumanMessage(content="whats the weather in sf?")]}
)
response["messages"]
```
We can check out the
[LangSmith trace](https://smith.langchain.com/public/f520839d-cd4d-4495-8764-e32b548e235d/r)
to make sure it's calling the search tool effectively.
## Streaming Messages
We've seen how the agent can be called with `.invoke` to get a final response.
If the agent executes multiple steps, this may take a while. To show intermediate
progress, we can stream back messages as they occur.
```{python}
for chunk in agent_executor.stream(
{"messages": [HumanMessage(content="whats the weather in sf?")]}
):
print(chunk)
print("----")
```
## Streaming tokens
In addition to streaming back messages, it is also useful to stream back tokens.
We can do this with the `.astream_events` method.
:::important
This `.astream_events` method only works with Python 3.11 or higher.
:::
```{python}
async for event in agent_executor.astream_events(
{"messages": [HumanMessage(content="whats the weather in sf?")]}, version="v1"
):
kind = event["event"]
if kind == "on_chain_start":
if (
event["name"] == "Agent"
): # Was assigned when creating the agent with `.with_config({"run_name": "Agent"})`
print(
f"Starting agent: {event['name']} with input: {event['data'].get('input')}"
)
elif kind == "on_chain_end":
if (
event["name"] == "Agent"
): # Was assigned when creating the agent with `.with_config({"run_name": "Agent"})`
print()
print("--")
print(
f"Done agent: {event['name']} with output: {event['data'].get('output')['output']}"
)
if kind == "on_chat_model_stream":
content = event["data"]["chunk"].content
if content:
# Empty content in the context of OpenAI means
# that the model is asking for a tool to be invoked.
# So we only print non-empty content
print(content, end="|")
elif kind == "on_tool_start":
print("--")
print(
f"Starting tool: {event['name']} with inputs: {event['data'].get('input')}"
)
elif kind == "on_tool_end":
print(f"Done tool: {event['name']}")
print(f"Tool output was: {event['data'].get('output')}")
print("--")
```
## Adding in memory
As mentioned earlier, this agent is stateless. This means it does not remember
previous interactions. To give it memory we need to pass in a checkpointer. When
passing in a checkpointer, we also have to pass in a `thread_id` when invoking
the agent (so it knows which thread/conversation to resume from).
```{python}
from langgraph.checkpoint.memory import MemorySaver
memory = MemorySaver()
```
```{python}
agent_executor = create_react_agent(model, tools, checkpointer=memory)
config = {"configurable": {"thread_id": "abc123"}}
```
```{python}
for chunk in agent_executor.stream(
{"messages": [HumanMessage(content="hi im bob!")]}, config
):
print(chunk)
print("----")
```
```{python}
for chunk in agent_executor.stream(
{"messages": [HumanMessage(content="whats my name?")]}, config
):
print(chunk)
print("----")
```
Example [LangSmith trace](https://smith.langchain.com/public/fa73960b-0f7d-4910-b73d-757a12f33b2b/r)
If you want to start a new conversation, all you have to do is change the
`thread_id` used
```{python}
config = {"configurable": {"thread_id": "xyz123"}}
for chunk in agent_executor.stream(
{"messages": [HumanMessage(content="whats my name?")]}, config
):
print(chunk)
print("----")
```
## Conclusion
That's a wrap! In this quick start we covered how to create a simple agent.
We've then shown how to stream back a response - not only with the intermediate
steps, but also tokens!
We've also added in memory so you can have a conversation with them.
Agents are a complex topic with lots to learn!
For more information on Agents, please check out the
[LangGraph](https://python.langchain.com/docs/concepts/architecture/#langgraph)
documentation. This has it's own set of concepts, tutorials, and how-to guides.