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的子类,可以参考LangChain补充二:LCEL和Runnable更加方便的创建调用链RunnableWithMessageHistory,实现上下文的处理

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更🐮,都替代
 
posted @ 2024-07-17 20:38  山上有风景  阅读(428)  评论(0编辑  收藏  举报