LangChain补充四:Agent知识点和案例补充
https://www.alang.ai/langchain/101/lc07
一:基本流程和概念
(一)概念
LangChain Agent的核心思想是,使用大语言模型选择一系列要执行的动作。
在Chain中,一系列动作是硬编码在代码中的。
在Agent中,大语言模型被用作推理引擎,以确定要采取的动作及其顺序。
它包括 3 个组件:
- 规划:将任务分解为较小的子目标
- 记忆:短期(聊天历史)/ 长期(向量存储)
- 工具使用:可以利用不同的工具来扩展其功能。
(二)基本流程
工具定义--->定义工具集--->定义prompt--->创建模型实例--->创建Agent(传递进入llm、tools、prompt)--->创建Agent Executor
补充:OpenAI API已弃用
functions
,而推荐使用tools
。两者之间的区别是,tools API允许一次模型请求使用多个函数调用,这可以减少响应时间。1.工具定义(这里以Serp搜索工具、自定义工具、RAG检索工具为例)
serp搜索需要用到ApiKey,参考https://serpapi.com/manage-api-key
- Serp搜索
SerpAPI使用案例:
from langchain_community.utilities import SerpAPIWrapper
search = SerpAPIWrapper()
res= search.run("周星驰生日那天是星期几")
print(res)
SerpAPI工具定义:通过第三方api和调用内部封装方式(下面就是工具集合)
#三方api
search = SerpAPIWrapper() #搜索引擎
Tool.from_function(
func=search.run,
name="Search",
description="useful for when you need to answer questions about current events" #问一下实时的事情
)
#内部的封装好的
tool = load_tools(["serpapi"])[0]
- 自定义tool:最常用扩展方式
from datetime import datetime
from langchain_core.tools import tool
#函数自定义
@tool("weekday")
def weekday(date_str:str) -> str:
"""Convert date to weekday name"""
date_object = datetime.strptime(date_str, '%Y-%m-%d')
weekday_number = date_object.weekday()
weekdays = ['星期一','星期二','星期三','星期四','星期五','星期六','星期日']
return weekdays[weekday_number]
print(weekday("2024-07-08"))
- 创建RAG检索工具(里面有调用大模型编码api)
RAG使用:
#pip install beautifulsoup4
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
header = {}
loader = WebBaseLoader("https://www.cnblogs.com/ssyfj/p/18233391",header_template=header)
docs = loader.load()
documents = RecursiveCharacterTextSplitter(
chunk_size=50, chunk_overlap=10
).split_documents(docs)
vector = FAISS.from_documents(documents, OpenAIEmbeddings())
retriever = vector.as_retriever()
print(retriever.invoke("周星驰在2001年拍了什么电影,饰演什么角色"))
添加RAG工具:
from langchain.tools.retriever import create_retriever_tool
retriever_tool = create_retriever_tool(
retriever=retriever,
name="movie_search",
description="按电影发布年份搜索关于周星驰的电影,并且查询关于周星驰扮演的角色,对于这种类型的问题,you must use this tool!",
)
2.定义工具集
tools = [searchTool,weekday,retriever_tool]
3.定义prompt
#pip install langchainhub
from langchain import hub
prompt = hub.pull("hwchase17/openai-functions-agent")
[
SystemMessagePromptTemplate(
prompt=PromptTemplate(input_variables=[], template='You are a helpful assistant')),
MessagesPlaceholder(
variable_name='chat_history', optional=True),
HumanMessagePromptTemplate(
prompt=PromptTemplate(input_variables=['input'], template='{input}')),
MessagesPlaceholder(variable_name='agent_scratchpad')
]
注意:这里prompt里面指定了我们传递的变量key是input,如果后续需要传递历史消息,变量是chat_history
4.创建模型实例
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
5.创建Agent(传递进入llm、tools、prompt):这里以create_openai_tools_agent为例
from langchain.agents import create_openai_tools_agent
agent = create_openai_tools_agent(llm, tools, prompt)
6.创建Agent Executor
from langchain.agents import AgentExecutor
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
#设置 verbose=True,以观察 Agent 调用过程
(三)调用实例
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
from langchain_community.utilities import SerpAPIWrapper
from langchain.tools.retriever import create_retriever_tool
from langchain_core.tools import tool,Tool
from datetime import datetime
from langchain import hub
from langchain_openai import ChatOpenAI
from langchain.agents import create_openai_tools_agent
from langchain.agents import AgentExecutor
import os
#------------工具定义-----------
#1.三方API工具
search = SerpAPIWrapper() #搜索引擎
searchTool = Tool.from_function(
func=search.run,
name="Search",
description="useful for when you need to answer questions about current events" #问一下实时的事情
)
#2.函数自定义tool
@tool("weekday")
def weekday(date_str:str) -> str:
"""Convert date to weekday name"""
date_object = datetime.strptime(date_str, '%Y-%m-%d')
weekday_number = date_object.weekday()
weekdays = ['星期一','星期二','星期三','星期四','星期五','星期六','星期日']
return weekdays[weekday_number]
#3.RAG检索工具
header = {
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'accept-language': 'zh-CN,zh;q=0.9',
'cache-control': 'no-cache',
'cookie': '_ga_M4YHF1JL8J=GS1.2.1689911135.1.1.1689911171.0.0.0; _ga=GA1.1.944087138.1658297907; _ga_7DSFGJNPL4=GS1.1.1690275412.1.0.1690275414.0.0.0; cto_bidid=C83jBV93JTJCSENpcUE3SDVRREJLb3lkMFdXR1d3NTd0ZUdzOUtMUyUyRiUyQkhFa1VXTlllTUZXTmlYeVk3eGpjTW5PWTNhNUhTbHJ0dkp4eCUyRkU2M1pPczNaRG1hNDZ0NGlMcTlHNk4za2hQcHFDWHhQVGFCQUQ3MkZZeDI0WUV6Y2duYzFSWGRJYWE5QUpIWllNcDI4SjVkUDlNQWNxZyUzRCUzRA; __gads=ID=b60ba53f946e1b2e:T=1692005090:RT=1705929053:S=ALNI_MYsgrRdYyIC1T6TS3wkOwjoq1PaPA; __gpi=UID=00000c2c613e3967:T=1692005090:RT=1705929053:S=ALNI_MbXkLNn1vu8qyLrFk19uNuW591dMA; cto_bundle=cAdv_19kJTJCMVR5bCUyQk5WU0E1bHljM0NobkNoeXBjUTVLYXRJTlc2TVdFaVNhY2hXV3o5QXlVTUdNbU5sUUJnaXpwZzh6cXQ5aWxqN1ZybDh4Q01FVnlGM0ZqWmhaRnR1YnFNR092VlFOa2xGUlMzZURaVXF4U1UxWkhOZWRTS2dUUzlpelRPTHRSTGU3MUdOQ2l2dUV1bXNGaUJnJTNEJTNE; .AspNetCore.Antiforgery.b8-pDmTq1XM=CfDJ8C838EyK0EpKpQQcC9VwxBnSb0hM2bTYiKpSxegzCmK7_AE1z_6GOV_tnyqU0cLMIMGsd9Kh8VNURue8BMvp3HTJcCJ5MCandNV3ej4U51Osib__saUI57bcyOn5_l3TXtViuQh1oYWuXHn-fhM6YM4; yjs_id=009fc6ee0de8623a57a53e9e74e83d94; _ga_3Q0DVSGN10=GS1.1.1716353244.10.1.1716353251.53.0.0; Hm_lvt_866c9be12d4a814454792b1fd0fed295=1719232908; .Cnblogs.AspNetCore.Cookies=CfDJ8ONv0caE5GtEh3YJ5j088knFknN6JjsF6H8D1jGHhbTE5YdvkAh-A1xVe03PlKfF4D1UOvChU-6yIAj3dr12_rDR3H1WM8e7_ekJIqaoD41X7qpccR9pde5t9Pnh0Aa6ugNqhZ3X3r0YJxXdxjn4oUb7zUf5-DAro5aKTDPEVc3m6gaySuZ4HRPI_-b9mTXEBD09K7J5qeYT0MzklBXFyV27fcuChdsfvdOky4xGntJ20qc1PV6nmUYOV-BX1AwGgYPjWqY7nUa5UplAhSd1bsYhKDB38TvEIAUeyrrPIfvlqsre3P6QelucFGcR63PrErC_-_maf6rVCkYwHp8UP1gs0X5piJUDVORyWNy98eFLQk0n1ABNuenTGGBDgDRHK4iVNRZN9kR7lJ71vBrOoRBPcmYaLqM-GPrrZ2FcQmKuAqSnU2tVrwxzFaoJryBEc8N2QPX057m8sBW9kRHBxlY710vNQxTa6-qiRMsQxgBULa21EeKh6g855GuP8mtz4TvRQ_Ylg_oknpxEpE8aslvQ6L3ZLyS31dsjeTbIBvEsZG9iM56Uty7YC4TKI8DczwV6zYj-9ijG1EG5X-qDPvOSi4ItHHDJA1zXfThXnpoC; Hm_lpvt_866c9be12d4a814454792b1fd0fed295=1720429794; HMACCOUNT=F60606535C826707; _ga_C2LFP3RFGH=GS1.1.1720429796.26.1.1720429819.0.0.0; _ga_M95P3TTWJZ=GS1.1.1720429795.133.1.1720429819.0.0.0; RT="z=1&dm=cnblogs.com&si=0928aa82-fe37-45da-8ed2-dc749fa2b67e&ss=lycrh8xn&sl=3&tt=2k1&bcn=https%3A%2F%2Ffclog.baidu.com%2Flog%2Fweirwood%3Ftype%3Dperf&ld=lps&ul=zod"',
'pragma': 'no-cache',
'priority': 'u=0, i',
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'sec-fetch-dest': 'document',
'sec-fetch-mode': 'navigate',
'sec-fetch-site': 'none',
'sec-fetch-user': '?1',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
}
loader = WebBaseLoader("https://www.cnblogs.com/ssyfj/p/18233391",header_template=header)
docs = loader.load()
documents = RecursiveCharacterTextSplitter(
chunk_size=50, chunk_overlap=10
).split_documents(docs)
vector = FAISS.from_documents(documents, OpenAIEmbeddings())
retriever = vector.as_retriever()
retriever_tool = create_retriever_tool(
retriever=retriever,
name="movie_search",
description="按电影发布年份搜索关于周星驰的电影,并且查询关于周星驰扮演的角色,对于这种类型的问题,you must use this tool!",
)
#------------定义工具集-----------
tools = [searchTool,weekday,retriever_tool]
#------------定义prompt-----------
prompt = hub.pull("hwchase17/openai-functions-agent")
#------------创建模型实例-----------
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
#------------创建Agent-----------
agent = create_openai_tools_agent(llm, tools, prompt)
#------------创建Agent Executor-----------
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
1.不需要调用tool
res = agent_executor.invoke({"input": "hi!"})
print(res)
2.调用search工具
res = agent_executor.invoke({"input": "周星驰是谁?"})
print(res)
3.调用search和weekday工具
res = agent_executor.invoke({"input": "周星驰的生日是星期几"})
print(res)
4.调用 RAG 检索工具
res = agent_executor.invoke({"input": "周星驰在2004年主演了什么电影?饰演的角色叫什么?"})
print(res)
二:增加记忆力
class Runnable(Generic[Input, Output], ABC)
/ \
|
|
|
class RunnableSerializable(Serializable, Runnable[Input, Output])
/ \
|
|
|
class Chain(RunnableSerializable[Dict[str, Any], Dict[str, Any]], ABC)
/ \
|
|
|
class AgentExecutor(Chain)
我们可以看到agent继承chain,两者都是Runnable的子类,可以参考
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
message_history = ChatMessageHistory()
agent_with_chat_history = RunnableWithMessageHistory(
agent_executor,
lambda session_id: message_history, #这里没有考虑session_id,对话只维护一个历史
input_messages_key="input",
history_messages_key="chat_history", #prompt指定了占位
)
agent_with_chat_history.invoke(
{"input": "周星驰在2004年主演了什么电影"},
config={"configurable": {"session_id": "1"}},
)
agent_with_chat_history.invoke(
{"input": "饰演了什么角色呢?"},
config={"configurable": {"session_id": "1"}},
)
三:LangChain的代理对比
https://python.langchain.com/v0.1/docs/modules/agents/agent_types/
https://cloud.tencent.com/developer/article/2404258
LLM Completions 已经被标记为 Legacy(不建议使用),所以在实际应用中,建议使用 Chat Model 类型的 Agent 就可以了
代理类型 | 模型类型 | 历史对话支持 | 支持多输入的工具 | 支持同时调用多工具 | 使用时机 |
Tool Calling | Chat | ✅ | ✅ | ✅ | 使用tool-calling模型时 |
OpenAI Tools | Chat | ✅ | ✅ | ✅ | 废弃,tool-calling代替 |
OpenAI Functions | Chat | ✅ | ✅ | 废弃,tool-calling代替可以并发调用 | |
XML | LLM | ✅ | 使用擅长XML的模型时(比如:Anthropic),就可以用到这个agent | ||
Structured Chat | Chat | ✅ | ✅ | 需要支持多输入的工具时 | |
JSON Chat | Chat | ✅ | 擅长json的模型,普通chat模型加上prompt即可 | ||
ReAct | LLM | ✅ | 简单的模型 | ||
Self Ask With Search | LLM | 简单的模型,只有一个搜索工具 |
补充:Structured Chat 和 JSON Chat 的区别在于对 tool 入参类型的支持上,参数越多,LLM 对工具的学习成本就会越高,Agent 有可能会越不稳定。
Json chat只支持单参数的tool
@tool
def search(query: str) -> str:
"""Look up things online."""
return "LangChain"
Structured chat支持多参数tool,可以替代json chat
@tool
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
Tool calling更🐮,都替代