第13章 集成代理:Langchain和Transformers
第13章 集成代理:Langchain和Transformers
第13章 集成代理:Langchain和Transformers
本章讲述Gradio如何集成代理,包括两种智能体:LangChain Agent和Transformers Agent,先概述两者的概念和构建流程后,再介绍如何集成到Gradio应用。因为Agent作为连接各大模型和工具的桥梁,是打通人工通用智能(AGI)的最后一公里,对从业者和人工智能行业都是至关重要的一环,所以本章重点放在智能体的原理及应用步骤拆解,最后引入Smolagents示例作为对比。
13.1 LangChain Agent概念及AgentExecutor
本节介绍Agent相关概念,包括LangChain Agent与LangGraph区别、传统代理概念AgentExecutor及流行架构ReAct代理。Agent可译作智能体或代理,一般所指为智能体,本文会根据场景交替使用。
LangChain Agent与LangGraph区别。LangChain Agent是一种智能系统,它接受高级任务,并使用大型语言模型(LLM)作为推理引擎,由LLM决定采取哪些操作以及所需的输入;在通过工具调用(Tool-calling)执行操作后,结果将反馈给LLM,以判断是否需要执行更多操作或者结束任务。更多信息请参考🖇️链接13-1。
LangGraph是LangChain的一个扩展,专门用于创建高度可控和可定制的Agent。目前官方建议使用LangGraph来构建Agent,这里限于篇幅不便扩展。LangGraph的更多信息请参考以下资源:
传统代理概念:AgentExecutor。LangChain之前引入了AgentExecutor作为代理的运行环境,虽然它是一个很好的起点,但在处理更复杂和定制化的代理时,灵活性显得不足,局限性逐渐显现。因此官方正在逐步淘汰AgentExecutor转而采用LangGraph,它是一个灵活且高度可控的运行环境。
但目前Gradio官方资料仍采用AgentExecutor,为了让读者更全面了解代理概念,仍对其原理及用法进行详细讲解。AgentExecutor更多信息请参阅:
- 操作指南:Build an Agent with AgentExecutor (Legacy)🖇️链接13-4。
- 从AgentExecutor过渡到LangGraph:How to migrate from legacy LangChain agents to LangGraph🖇️链接13-5。
流行架构:ReAct代理。构建Agent的一种流行架构是ReAct,它将推理(Reasoning)和行动(Acting)结合在一个迭代过程中——事实上,“ReAct”这个名字就代表了“推理”和“行动”,其基本流程如下:
(1)模型在接收到输入或之前的观察结果后,会“思考”应该采取什么步骤。
(2)模型或直接响应用户,或从可用工具中选择一个并为该工具生成参数。
(3)当选择工具时,代理运行环境(执行器)会解析出工具,并使用参数调用它。
(4)执行器会将工具执行的结果作为观察结果返回给模型。
(5)这个过程会重复进行,直到代理选择响应为止。
有一些基于通用提示的实现不需要模型任何特定功能,但最可靠的实现通常使用模型的工具调用(tool calling)等功能,以确保输出格式的可靠性并减少变异性。
13.2 AgentExecutor的构建流程
本节构建一个可以与搜索引擎交互的代理,用户能够向它提问,观察它调用搜索工具并与之对话。下面将创建工具、使用大语言模型激发工具、创建Agent并执行工具调用、添加记忆功能等方面使用AgentExecutor构建一个完整的代理。
13.2.1 构建步骤与IDE选择
准备工作包括构建AgentExecutor的步骤,并比较Google Colab与国内的在线平台阿里云天池实验室、华为云ModelArts。
构建步骤。构建与搜索引擎交互的代理的步骤如下所述:
- 创建搜索工具(Tool),方便在线查找信息。
- 创建检索器(Retriever),以便向代理提供特定信息。
- 使用语言模型(LLM),特别是它们的工具调用能力。
- 创建并执行代理(Agent),包括选择引导提示词。
- 聊天历史记录(Chat History),使聊天机器人能够“记住”过去的交互,并在回答后续问题时考虑这些信息。
- 使用LangSmith进行调试和追踪,以便优化应用程序。
开始之前请安装LangChain、设置LangSmith、选择合适的IDE。国外较出名的在线运行平台是Google Colab,而国内较著名的在线运行平台有阿里云天池实验室(🖇️链接13-6)及华为云ModelArts(🖇️链接13-7)。作者经过试用发现并不尽如人意,除了繁琐的云实名认证和授权配置,两者运行环境的默认Python版本较低且无法升级,与LangChain的许多子库(如langchain-community)无法兼容,或许自己创建虚拟环境才能使用LangChain的完整功能,与Google Colab打开即用的实用风格还有很大差距,不过喜欢探索的读者可以尝试。
13.2.2 创建工具——在线搜索器Tavily和检索器Retriever
首先需要创建要使用的工具,这里将使用两个工具:在线搜索引擎Tavily和基于本地索引构建的检索器Retriever。
Tavily在线搜索。LangChain内置了可直接使用Tavily搜索引擎的工具,可免费申请API密钥(🖇️链接13-8)。Tavily还提供免费服务层级,所以可以暂时不申请API密钥。获取并设置API密钥后,需定义并激发搜索工具,如代码13-1所示:
import getpass
import os
from langchain_community.tools.tavily_search import TavilySearchResults
os.environ["TAVILY_API_KEY"] = getpass.getpass("Please input TAVILY_API_KEY:")
search_tool = TavilySearchResults(max_results=2)
search_tool.invoke("what is the weather in SF?")
-> [{'title': 'Saturday, March 29, 2025. San Francisco, CA - Weather Forecast',
'url': 'https://weathershogun.com/weather/usa/ca/san-francisco/480/march/2025-03-29',
'content': 'San Francisco, California Weather: Saturday, March 29, 2025. Cloudy weather, overcast skies with clouds. Day 59°. Night 50°.',
'score': 0.95598555},
{'title': ...}]
关于Tavily搜索工具的更详细用法请参阅:Tavily Search🖇️链接13-9。此外,还可以在Tavily官网在线测试运行结果:Tavily Playground🖇️链接13-10。
除了langchain_community,还可使用langchain-tavily,如代码13-2所示:
#!pip install -qU langchain-tavily
from langchain_tavily import TavilySearch
search_tool = TavilySearch(max_results=2, topic="general")
Retriever检索器。对搜索到的数据创建一个检索器RAG(检索增强生成),有关创建RAG步骤的深入解释,请参阅:Build a Retrieval Augmented Generation (RAG) App🖇️链接13-11。创建检索器如代码13-3所示:
#!pip install -qU faiss-cpu langchain-community langchain-huggingface
from langchain_community.document_loaders import WebBaseLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
loader = WebBaseLoader("https://docs.smith.langchain.com/overview")
docs = loader.load()
documents = RecursiveCharacterTextSplitter(
chunk_size=1000, chunk_overlap=200
).split_documents(docs)
import faiss
from langchain_community.vectorstores import FAISS
from langchain_community.docstore.in_memory import InMemoryDocstore
from langchain_huggingface.embeddings import HuggingFaceEmbeddings
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")
embedding_dim = len(embeddings.embed_query("hello world"))
index = faiss.IndexFlatL2(embedding_dim)
vector = FAISS.from_documents(documents, embeddings)
retriever = vector.as_retriever()
retriever.invoke("how to upload a dataset")[0]
这段代码实现了一个完整的RAG管道。首先文档加载与分块,WebBaseLoader从URL抓取网页内容,自动提取正文文本;RecursiveCharacterTextSplitter递归分割文本,优先按段落、句子、单词的层级切分,每块1000字符,重叠200字符,保持相邻块之间的上下文连续性。然后,创建向量存储组件,初始化嵌入模型,获取嵌入维度(768维的高质量嵌入模型),创建FAISS索引(L2欧氏距离)。最后,构建检索器,从文档创建向量数据库,创建检索器并执行检索查询。完整回复内容如下所示:
Document(id='bdae82ba-a15a-4438-8f7d-fbbebd5eed41', metadata={'source': 'https://docs.smith.langchain.com/overview', 'title': 'Get started with LangSmith | \uf8ffü¶úÔ∏è\uf8ffüõ†Ô∏è LangSmith', 'description': 'LangSmith is a platform for building production-grade LLM applications.', 'language': 'en'}, page_content='Get started by adding tracing to your application. ...')
现在已经创建具有索引数据的检索器,然后利用函数create_retriever_tool将其转换为检索器工具(代理正确使用它所需的格式),最后根据工具search_tool和retriever_tool,创建一个将在下游使用的工具列表,如代码13-4所示:
from langchain.tools.retriever import create_retriever_tool
retriever_tool = create_retriever_tool(retriever, "langsmith_search",
"Search for information about LangSmith. For any questions about LangSmith, you must use this tool!")
tools = [search_tool , retriever_tool]
13.2.3 使用大模型DeepSeek激发工具
创建大模型并执行激发。LangChain支持多种大模型,例如DeepSeek、OpenAI、Anthropic、Azure、Groq、Mistral AI及xAI等,以DeepSeek为例创建大模型对象,并传入消息列表来调用大语言模型,默认返回的是内容字符串如代码13-5所示:
# pip install -Uq langchain langchain-core
import getpass
import os
from langchain_deepseek import ChatDeepSeek
if not os.environ.get("DEEPSEEK_API_KEY"):
os.environ["DEEPSEEK_API_KEY"] = getpass.getpass("Enter API key for DeepSeek: ")
model = ChatDeepSeek(model="deepseek-chat", temperature=0,
max_tokens=None, timeout=None, max_retries=2)
from langchain_core.messages import HumanMessage
response = model.invoke([HumanMessage(content="hi!")])
response.content
->Hello! How can I assist you today? 😊
绑定和使用工具。使用bind_tools()方法让大模型知晓这些工具的存在,然后先用普通消息进行调用并观察响应的content字段和tool_calls字段,如代码13-6所示:
model_with_tools = model.bind_tools(tools)
query = "Hi!"
response = model_with_tools.invoke([{"role": "user", "content": query}])
print(f"Message content: {response.text()}\n")
print(f"Tool calls: {response.tool_calls}")
->Message content: Hello! I'm here to help you...
Tool calls: []
可以看到模型无工具调用。尝试输入预期会触发工具调用的内容:
query = "Search for the weather in SF"
response = model_with_tools.invoke([{"role": "user", "content": query}])
print(f"Message content: {response.text()}\n")
print(f"Tool calls: {response.tool_calls}")
->Message content: I'll help you search for information about the weather in SF.
Tool calls: [{'name': 'tavily_search', 'args': {'query': 'current weather San Francisco'}, 'id': 'toolu_015gdPn1jbB2Z21DmN2RAnti', 'type': 'tool_call'}]
此时可以看到返回结果出现了tool_calls字段!这表明大模型希望调用Tavily搜索工具。注意:目前这仅是工具调用指令,尚未实际执行调用。要实现真正的工具调用,就需要创建调用工具的智能体代理(Agent)。
13.2.4 导入提示词、创建Agent及AgentExecutor
在完成工具和大语言模型的定义后,可以创建Agent实际执行工具调用。
选择提示词:prompt。首先选定用于指导智能体行为的提示词模板,如需查看该提示词的具体内容(需LangSmith访问权限),可访问LangChain Hub🖇️链接13-12。比如本例使用的hwchase17/openai-functions-agent,如图13-1所示:
导入提示词,如代码13-7所示:
from langchain import hub
prompt = hub.pull("hwchase17/openai-functions-agent")
prompt.messages
->[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')]
创建代理和AgentExecutor。在大模型的回复中得到工具调用指令后,需要代理执行具体的工具调用,这是工具调用型智能体所完成的功能。通过以下要素初始化智能体:大语言模型(LLM)、工具集和提示词模板,它的核心职责是接收输入并决策需要执行的动作,但不负责实际执行——该功能由后续的AgentExecutor实现。注意传入的是基础模型而非工具绑定版模型,因为create_tool_calling_agent()会在底层自动调用bind_tools()。将智能体(决策中枢)与工具集通过AgentExecutor整合,它将循环调用智能体获取决策、实际执行工具调用及管理整个工作流程的运转。如代码13-8所示:
from langchain.agents import create_tool_calling_agent, AgentExecutor
agent = create_tool_calling_agent(model, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools)
13.2.5 测试代理的工具调用
现在用几个查询来测试智能体了。请注意,当前测试的都是无状态查询,系统不会记住之前的交互记录。
- 测试1:无需调用工具的情况。首先观察智能体在不需要调用工具时的响应行为:
agent_executor.invoke({"input": "hi!"})
->{'input': 'hi!', 'output': 'Hello! How can I assist you today? 😊'}
验证:可通过查看LangSmith跟踪记录,确认确实没有触发任何工具调用。
- 测试2:触发检索器调用。测试需要调用本地检索器的场景:
agent_executor.invoke({"input": "how can langsmith help with testing?"})
->{'input': 'how can langsmith help with testing?',
'output': 'LangSmith is a platform that aids in building production-grade Language Learning Model (LLM) applications. ...'}
验证:检查LangSmith跟踪日志,通过检索query生成情况及返回结果的处理流程,确认检索器被正确调用。
- 测试3:触发搜索引擎调用。测试需要调用Tavily搜索引擎的场景:
agent_executor.invoke({"input": "whats the weather in beijing?"})
->{'input': 'whats the weather in beijing?',
'output': 'The current weather in Beijing is sunny with a temperature of 11.3°C (52.3°F)...\n\nFor more details, you can check [Weather API](https://www.weatherapi.com/) or [Weather Underground](https://www.wunderground.com/weather/cn/beijing).'}
验证:通过LangSmith跟踪确认搜索关键词的生成逻辑、API调用的耗时统计及搜索结果摘要的生成质量。由于LangSmith需要收费,所以使用官方的LangSmith跟踪界面进行展示(可能与本示例有细节出入,但不影响验证结果,只需将ChatOpenAI看作ChatDeepSeek即可),如图13-2所示:
13.2.6 记录对话历史与自动记忆管理
如前所述,当前智能体是无状态的,这意味着它不会记住之前的交互记录。要为其添加记忆能力,需要传入历史对话记录(chat_history)。
记录对话历史:chat_history。注意,由于当前使用的提示词模板hwchase17/openai-functions-agent的硬性要求,历史记录变量必须命名为chat_history,如果使用不同的提示词模板,可以更改这个变量名。
首先,通过函数invoke在第一个消息中传入空列表chat_history;然后,将第一步回复的信息插入到chat_history,用户输入使用消息类型HumanMessage,而大模型的回复使用消息类型AIMessage,如代码13-9所示:
agent_executor.invoke({"input": "hi! my name is bob", "chat_history": []})
from langchain_core.messages import AIMessage, HumanMessage
agent_executor.invoke(
{
"chat_history": [
HumanMessage(content="hi! my name is bob"),
AIMessage(content="Hi, Bob! How can I assist you today?"),
],
"input": "what's my name?",
}
)
-> {'chat_history': [HumanMessage(content='hi! my name is bob', additional_kwargs={}, response_metadata={}),
AIMessage(content='Hi, Bob! How can I assist you today?', additional_kwargs={}, response_metadata={})],
'input': "what's my name?",
'output': 'Your name is Bob! You introduced yourself earlier. 😊 How can I help you, Bob?'}
自动记忆管理:RunnableWithMessageHistory。实现对话历史的自动跟踪,由于涉及多个输入源,需要明确指定两个关键参数:①input_messages_key:标识要加入对话历史的输入键名;②history_messages_key:标识加载历史消息的目标键名。
最后,定义并调用RunnableWithMessageHistory封装器,需配置session_id,用法如代码13-10所示:
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
store = {}
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
agent_with_chat_history = RunnableWithMessageHistory(
agent_executor,
get_session_history,
input_messages_key="input",
history_messages_key="chat_history",
)
agent_with_chat_history.invoke(
{"input": "hi! I'm bob"},
config={"configurable": {"session_id": "<foo>"}},
)
->{'input': "hi! I'm bob",
'chat_history': [],
'output': 'Hi, Bob! How can I assist you today? 😊'}
再次调用时传入session_id,代理会将其识别为同一会话,如代码13-11所示:
agent_with_chat_history.invoke(
{"input": "what's my name?"},
config={"configurable": {"session_id": "<foo>"}},
)
{'input': "what's my name?",
'chat_history': [HumanMessage(content="hi! I'm bob", additional_kwargs={}, response_metadata={}),
AIMessage(content='Hi, Bob! How can I assist you today? 😊', additional_kwargs={}, response_metadata={})],
'output': 'Your name is Bob.'}
至此构建了一个完整的AgentExecutor!本次快速入门教程已完整演示了如何创建一个基础Agent,Agent是一个复杂的主题,仍有大量知识值得深入探索!重要提示:本教程介绍的是使用LangChain原生智能体的构建方法,虽然适合入门学习,但当需求发展到一定复杂度时,可能会需要更大的灵活性和控制力,要开发更高级的智能体,推荐转向LangGraph框架。
13.2.7 练习11:AgentExecutor访问搜索引擎
大语言模型本身无法执行操作——它们只能输出文本。LangChain的一个重要应用场景是创建代理,LLM能够自主决定某任务采取哪些行动,然后代理决定具体执行并返回结果,LLM观察结果决定下一步行动,重复此过程直到任务完成。而LangChain Agent集成了LLM,它本身可以独立完成任务,可以使用它访问搜索引擎。
**创建AgentExecutor。**从导入库和设置Langchain Agent开始,设置秘钥SERPAPI_API_KEY、HF_TOKEN和DEEPSEEK_API_KEY三个API秘钥为环境变量,并创建模型、Agent及AgentExecutor,如代码13-12所示:
# !pip install -Uq langchain-community langchain-deepseek gradio google-search-results
from langchain import hub
from langchain.agents import AgentExecutor, create_openai_tools_agent, load_tools
from langchain_deepseek import ChatDeepSeek
from gradio import ChatMessage
import gradio as gr
import os
if not (os.getenv("SERPAPI_API_KEY") and os.getenv("DEEPSEEK_API_KEY")):
with gr.Blocks() as demo:
gr.Markdown("""
# Chat with a LangChain Agent 🦜⛓ and see its thoughts 💭
In order to run this space, duplicate it and add the following space secrets:
* SERPAPI_API_KEY - create an account at serpapi.com and get an API key
* DEEPSEEK_API_KEY - create an deepseek account and get an API key
""")
demo.launch()
model = ChatDeepSeek(model="deepseek-chat", temperature=0,
max_tokens=None, timeout=None, max_retries=2)
tools = load_tools(["serpapi"])
prompt = hub.pull("hwchase17/openai-tools-agent")
agent = create_openai_tools_agent(
model.with_config({"tags": ["agent_llm"]}), tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools).with_config(
{"run_name": "Agent"})
代码构建了一个基于DeepSeek模型的AI工具代理系统,主要功能如下:
- 检查API秘钥:当SERPAPI_API_KEY或DEEPSEEK_API_KEY为空时,输出提示。
- 模型初始化:使用ChatDeepSeek创建了一个名为"deepseek-chat"的模型实例,参数设置为确定性输出,无最大token限制和超时限制,并最多重试2次。
- 工具加载:通过load_tools加载了SerpAPI工具(一个搜索引擎API工具),使代理能够执行网络搜索。
- 提示词获取:从LangChain Hub获取"hwchase17/openai-tools-agent"预定义提示模板,用于指导代理的行为。
- 代理创建:将模型、工具和提示词组合,创建了一个具备工具使用能力的AI代理,并为模型添加了"agent_llm"标签。
- 执行器配置:最终创建了一个AgentExecutor实例来运行这个代理和执行工具,并为执行器设置了"Agent"的运行名称。
创建Gradio UI。如代码13-13所示:
async def interact_with_langchain_agent(prompt, messages):
messages.append(ChatMessage(role="user", content=prompt))
yield messages
async for chunk in agent_executor.astream({"input": prompt}):
if "steps" in chunk:
for step in chunk["steps"]:
messages.append(ChatMessage(role="assistant", content=step.action.log,
metadata={"title": f"🛠 Used tool {step.action.tool}"}))
yield messages
if "output" in chunk:
messages.append(ChatMessage(role="assistant", content=chunk["output"]))
yield messages
with gr.Blocks() as demo:
gr.Markdown("# Chat with a LangChain Agent 🦜⛓ and see its thoughts 💭")
chatbot_2 = gr.Chatbot(label="Agent",
avatar_images=(None,
"https://em-content.zobj.net/source/twitter/141/parrot_1f99c.png"))
input_2 = gr.Textbox(lines=1, label="Chat Message")
input_2.submit(interact_with_langchain_agent, [input_2, chatbot_2], [chatbot_2])
demo.launch(debug=True)
这段代码展示了如何创建一个基于LangChain的交互式聊天代理界面,使用Gradio作为前端框架。以下是详细解析:
- 核心功能函数:interact_with_langchain_agent。此异步函数处理用户与LangChain代理的交互流程:①消息记录,将用户输入添加到消息历史中。②流式处理,通过agent_executor.astream异步流式处理用户输入。③工具使用追踪,当代理使用工具时,记录工具使用步骤。④输出处理,捕获并返回代理的最终输出。
- Gradio界面配置。界面包含以下核心组件:①标题,使用gr.Markdown显示。②聊天机器人,配置gr.Chatbot的消息格式、标签及聊天头像。③输入框:gr.Textbox用于接收用户输入。④交互绑定:将输入框的submit事件绑定interact_with_langchain_agent函数。
运行Gradio。安装包:langchain-community、langchain_openai、serpapi、google-search-results、gradio后重启会话,然后设置DEEPSEEK_API_KEY和SERPAPI_API_KEY。未设置API时出现提示信息,设置API后运行界面如图13-3所示:
读者也可以在Spaces查看完整演示:gradio/langchain-agent🖇️链接13-13。
13.3 Transformers Agent概念、分类和ReAct
gr.Chatbot原生支持显示中间思考过程和工具使用情况(参考其参数metadata用法),这使得它非常适合为LLM Agent、思维链(Chain-of-Thought, CoT)或推理演示创建用户界面。下面将展示如何使用gr.Chatbot和gr.ChatInterface来显示Transformers Agent的思考过程和工具使用情况,详细资料请参考:License to Call: Introducing Transformers Agents 2.0🖇️链接13-14。
在开始之前,请确保先阅读并理解smolagents🖇️链接13-15。随着LLM Agent的用途越来越重要,Transformers实现了Agent,但随着智能体概念爆火,库agents已从Transformers独立,成为现在的smolagents。但本书还是以前例为主,这两个库的API非常相似,切换很容易。
Transformers Agent概念。经过大量因果语言建模(causal language modeling)训练的大语言模型能处理多种任务,但在逻辑、计算和搜索等基本任务上往往表现不佳,通常无法生成期望答案,克服这一弱点的方法是创建LLM Agent。
LLM Agent的定义非常宽泛:通常指的是所有将LLM作为核心引擎,并能够根据观察对其环境施加影响的系统。这些系统能够通过多次迭代“感知 ⇒ 思考 ⇒ 行动”的循环来实现既定任务,并常常融入规划或知识管理系统以提升其表现效能。并且LLM Agent可以访问工具(Tools),这些工具通常是用于执行任务的函数,必须包含代理正确使用所需的描述。对智能体领域的精彩评述论文:The Rise and Potential of Large Language Model Based Agents: A Survey(🖇️链接13-16)。
Agent分类。LLM Agent可以设计为一系列动作/工具(actions/tools)并一次性运行它们,也可以逐个计划后执行动作/工具,并在启动下一个动作之前等待前面每个动作的结果。因此根据代理设计理念的不同,Transformers将代理分为两种类型:
(1)Code agent:该代理先有一个规划步骤,然后生成Python代码以一次性执行所有动作。它原生支持处理工具内不同的输入输出类型,因此是多模态任务的首选。
(2)React agent:它采用一种基于“推理 (Reasoning)”与“行动 (Acting)”结合的方式逐步解决给定任务,并以此来构建智能体。Transformers有三种版本:
①ReactAgent:原始的推理执行代理,它的行动将从LLM的输出中解析出来,它并不常用,经常会被下面两种衍生代理代替。
②ReactJsonAgent:工具调用将由LLM以JSON代码块生成,然后解析并执行。
③ReactCodeAgent:是一种新型的ReactJsonAgent,工具调用将由LLM以Python代码块生成,这对于具有强大编码性能的LLM非常有效。
单步Code agent与多步React agent执行流程区别如图13-4所示:
详解ReAct智能体。它是解决推理任务的首选代理,在提示词中阐述了模型能够利用哪些工具,并引导它逐步思考“step by step” (即Chain-of-Thought,思维链),以规划并实施其后续动作,ReAct框架使代理可以在基于先前观察的基础上进行多次非常高效的思考。可以阅读论文:ReAct: Synergizing Reasoning and Acting in Language Models(🖇️链接13-17)以了解更多关于使用ReAct代理的信息。为方便理解ReAct流程,查看ReactCodeAgent解决问题的执行步骤,如代码13-14所示:
agent.run("How many more blocks (also denoted as layers) in BERT base encoder than the encoder from the architecture proposed in Attention is All You Need?")
=====New task=====
How many more blocks (also denoted as layers) in BERT base encoder than the encoder from the architecture proposed in Attention is All You Need?
====Agent is executing the code below:
bert_blocks = search(query="number of blocks in BERT base encoder")
print("BERT blocks:", bert_blocks)
====
Print outputs:
BERT blocks: twelve encoder blocks
====Agent is executing the code below:
attention_layer = search(query="number of layers in Attention is All You Need")
print("Attention layers:", attention_layer)
====
Print outputs:
Attention layers: Encoder: The encoder is composed of a stack of 6 identical layers.
====Agent is executing the code below:
bert_blocks = 12
attention_layers = 6
diff = bert_blocks - attention_layers
print("Difference in blocks:", diff)
final_answer(diff)
====
Print outputs:
Difference in blocks: 6
Final answer: 6
13.4 构建Transformers Agent参数详解
构建Agent的方法。需要设置transformers中各类Agent的构造参数:
(1)大模型引擎(llm_engine):此参数设置为代理提供动力引擎的LLM,代理并不完全是LLM,它更像是一个使用LLM作为引擎的程序。
(2)工具箱(tools):代理从中挑选工具来执行任务。
(3)系统提示(system prompt):LLM引擎将根据这个提示生成输出。
(4)工具解析器(tool_parser):用于从LLM的输出中提取需要调用的工具及其参数。
在Agent系统初始化时,工具的属性被用来生成工具描述,并将其嵌入到代理的system_prompt中,以便大模型引擎知道它可以使用哪些工具以及为什么使用这些工具。然后LLM根据系统提示输出解决方案,解析器解析LLM输出后,决定调用哪些工具继续执行。最后当得到答案或者满足停止条件后将结束会话,否则继续迭代执行。
本节讲解如何构建Transformers Agent,主要是定义其四个构造参数,还有一些其他设置,下面逐一讲述。开始之前,使用命令额外安装Transformers的agents,它会安装所有默认依赖项:pip install transformers[agents]。更详细信息请参考:Transformers - Agents & Tools🖇️链接13-18。
13.4.1 四类大模型引擎
可以自由创建和使用智能体框架的引擎,但需满足以下条件:
- 引擎遵循输入消息的消息格式(List[Dict[str, str]]),并返回一个字符串。
- 引擎在传入的参数stop_sequences指定的序列处停止生成输出。
下面就来了解下四类创建引擎方法,请注意区分它们适用的场景。
模型引擎函数:llm_engine。可以通过定义一个接受消息列表并返回文本的函数llm_engine()来构建LLM引擎,还接受一个stop参数,如代码13-15所示:
from huggingface_hub import login, InferenceClient
from transformers import CodeAgent
login("<YOUR_HUGGINGFACEHUB_API_TOKEN>")
client = InferenceClient(model="meta-llama/Meta-Llama-3-70B-Instruct")
def llm_engine(messages, stop_sequences=["Task"]) -> str:
response = client.chat_completion(messages, stop=stop_sequences, max_tokens=1000)
answer = response.choices[0].message.content
return answer
agent = CodeAgent(llm_engine=llm_engine)
此外,llm_engine()还可以接受一个grammar参数。如果在智能体初始化时指定了grammar,这个参数将连同初始化时定义的grammar一起传递给llm_engine的调用,以实现受约束的生成,从而强制智能体生成格式正确的输出。
TransformersEngine类。它将预初始化的Pipeline作为输入实现上述功能,或使用可选的model_id,以便使用transformers在本地运行推理。如代码13-16所示:
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline, TransformersEngine
model_name = "HuggingFaceTB/SmolLM-135M-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)
pipe = pipeline("text-generation", model=model, tokenizer=tokenizer)
engine = TransformersEngine(pipe)
engine([{"role": "user", "content": "Ok!"}], stop_sequences=["great"])
# 输出 "What a "
HfApiEngine类。由于智能体通常需要更强的模型,如Llama-3.1-70B-Instruct,这些模型目前较难在本地运行,因此有HfApiEngine类,它是封装了Hugging Face Inference API客户端的引擎,在底层初始化huggingface_hub.InferenceClient,用于执行大语言模型(LLM)的调用。该引擎通过Hugging Face的推理API与语言模型进行通信,它即可以在无服务器模式下使用,也可以与专用端点(dedicated endpoint)一起使用,并支持诸如停止序列和语法自定义等功能。程序中甚至可以留空llm_engine参数,默认情况下会创建一个HfApiEngine。用法如代码13-17所示:
from transformers import HfApiEngine
llm_engine = HfApiEngine(model="meta-llama/Meta-Llama-3-70B-Instruct")
HfEngine类。对于本地部署的具有Inference API的LLM,还可以直接使用包中提供的HfEngine类来获取,如代码13-18所示:
from transformers.agents import HfEngine
llm_engine = HfEngine("meta-llama/Meta-Llama-3-70B-Instruct")
13.4.2 工具箱与创建加载工具
工具是Agent使用的原子函数,例如PythonInterpreterTool,包括各种属性和执行方法。当Agent初始化时,工具的属性被用来生成工具描述,并将其嵌入到Agent的系统提示中,这让Agent知道它如何使用这些工具以及为什么使用。工具是Agent的核心部分,工具的好坏和数量决定了Agent能力的大小。
1. 默认工具箱与管理方法
默认工具箱(toolbox)。Transformers附带了一个默认工具箱,用于增强Agent的功能。构建Agent时需要一个tools参数,它接受一个工具列表(List[Tools]),这个列表可以为空。在Agent初始化时,可通过定义可选参数add_base_tools=True添加默认工具箱到工具列表。默认工具箱中工具如下:
- 文档问答(document_question_answering):给定一个图像格式的文档(如PDF),回答关于该文档的问题(Donut)。
- 图像问答(image_question_answering):给定图像,回答关于它的问题(VILT)。
- 语音转文本(speech_to_text):给定一段人声录音,将语音转录为文本(Whisper)。
- 文本转语音(text_to_speech):将文本转换为语音(SpeechT5)。
- 翻译(translation):将给定的句子从源语言翻译为目标语言。
管理工具箱方法。当已经初始化带有工具箱的Agent时,从头重新初始化并添加工具会很不方便。此时可使用toolbox,通过add_tool()和update_tool()来添加或替换工具来管理代理的工具箱。比如通过add_tool()将model_download_tool()添加到一个仅使用默认工具箱初始化的已有代理中,如代码13-19所示:
from transformers import CodeAgent
agent = CodeAgent(tools=[], llm_engine=llm_engine, add_base_tools=True)
agent.toolbox.add_tool(model_download_tool)
现在就可以同时利用新工具和旧工具。注意:在为已经运行良好的Agent添加工具时要小心,因为它可能会偏向选择自定义的工具,或者选择与原工具不同的工具。
另外,还可以使用方法agent.toolbox.update_tool()替换工具箱中的现有工具,尤其是新工具一对一替换现有工具时将非常有用。由于Agent已经知道如何执行该特定任务,因此替换时只需确保新工具遵循与被替换工具相同的API,或者调整系统提示模板以确保更新所有使用被替换工具的示例。
2. 创建新工具、load_tool()与ToolCollection
创建新工具。可以为Hugging Face默认工具中未涵盖的用例创建自定义工具,以返回Hub上某类任务的下载量最多的模型为例,将核心代码封装为工具函数以快速将其转换为工具,操作时只需添加工具装饰器@tool即可,如代码13-20所示:
from transformers import tool
from huggingface_hub import list_models
@tool
def model_download_tool(task: str) -> str:
"""This is a tool that returns the most downloaded model of a given task on the Hugging Face Hub.
It returns the name of the checkpoint.
Args:
task: The task for which"""
model = next(iter(list_models(filter="text-classification", sort="downloads", direction=-1)))
return model.id
代码封装为工具函数,此函数需要:
- 清晰的函数名:名称通常描述工具的功能,通常要切合完成的具体任务。
- 输入和输出的类型提示:输入类型提示为函数的入参,输出类型提示为符号->后的类型,方便大模型调用。
- 函数描述:描述函数的作用和返回说明,其中包括一个“Args:”部分,描述每个参数的作用(注意这里不需要类型指示,因为会从参数中提取)。
所有这些信息都将在初始化时自动嵌入到系统提示中,因此请尽量清晰易懂!最后将工具直接添加到初始化代理的参数tools中。假设创建智能体CodeAgent并使用工具model_download_tool,如代码13-21所示:
from transformers import CodeAgent, HfApiEngine
llm_engine = HfApiEngine(model="meta-llama/Meta-Llama-3-70B-Instruct")
agent = CodeAgent(tools=[model_download_tool], llm_engine=llm_engine)
agent.run("Can you give me the name of the model that has the most downloads in the 'text-to-video' task on the Hugging Face Hub?")
======== New task ========
Can you give me the name of the model that has the most downloads in the 'text-to-video' task on the Hugging Face Hub?
==== Agent is executing the code below:
most_downloaded_model = model_download_tool(task="text-to-video")
print(f"The most downloaded model for the 'text-to-video' task is {most_downloaded_model}.")
==== The output:
"The most downloaded model for the 'text-to-video' task is ByteDance/AnimateDiff-Lightning."
load_tool()函数加载工具。该函数手动加载模型为工具,如代码13-22所示:
from transformers import load_tool
tool = load_tool("text-to-speech")
audio = tool("This is a text to speech tool")
工具集合ToolCollection。还可以通过transformers中的对象ToolCollection来利用工具集合,并通过参数collection_slug指定想要使用的集合片,它们将作为列表传递给Agent进行初始化,如代码13-23所示:
from transformers import ToolCollection, ReactCodeAgent
image_tool_collection = ToolCollection(collection_slug="huggingface-tools/diffusion-tools-6630bb19a942c2306a2cdb6f")
agent = ReactCodeAgent(tools=[*image_tool_collection.tools], add_base_tools=True)
agent.run("Please draw me a picture of rivers and lakes.")
需要注意的是,为了加快启动速度,工具只有在被调用时才会加载。
13.4.3 系统提示和工具解析器
在Agent系统初始化时,工具的属性被用来生成工具描述,将其嵌入到智能体的system_prompt中,以便智能体知道它可以使用哪些工具以及工具用途,然后大模型根据系统提示输出解决方案,本小节将详解系统提示及工具解析器。
系统提示和工具解析器示例。系统提示(system prompt)和工具解析器(tool parser)是自动定义的,可以通过调用代理的system_prompt_template和tool_parser进行查看。Agent由LLM驱动,它会根据系统提示生成输出。在系统提示中,尽可能清楚地解释想要执行的任务非常重要,它还可以根据预期的任务进行定制和调整。由于Agent是由LLM驱动的,且每次run()操作都是独立的,提示中的微小变化可能会产生完全不同的结果,因此也可以连续运行Agent以执行不同的任务:每次运行时,agent.task和agent.logs属性都会重新初始化。
例如,查看ReactCodeAgent示例中的工具解析器和系统提示(输出有简化):
>>> print(agent.tool_parser)
<function parse_json_tool_call at 0x79c1edcddb20>
>>> print(agent.system_prompt_template)
You are an expert assistant who can solve any task using code blobs. You will be given a task to solve as best you can.
To do so, you have been given access to a list of tools: these tools are basically Python functions which you can call with code.
To solve the task, you must plan forward to proceed in a series of steps, in a cycle of 'Thought:', 'Code:', and 'Observation:' sequences.
...
工具解析器只列出了工具名称和内存地址。而观察系统提示示例,可以发现系统提示极为详细,一般包括:
- 一段介绍,解释代理应如何行为以及工具是什么。
- 所有工具的描述,这些描述由<<tool_descriptions>>标记定义,该标记在运行时动态替换为用户定义/选择的工具,工具描述来源于工具属性,包括名称、描述、输入和输出类型,以及一个用户可以优化的简单jinja2模板。
- 示例examples,示例中尽量包含所提到的工具及使用方法。
- 预期的输出格式,对final_answer的具体要求。
系统提示格式只是起到引导作用,并没有具体的格式规定,可根据自己需求添加或删除,但一般情况是:越具体越清晰的系统提示会产生更好的输出效果!
修改系统提示。可以修改系统提示,例如通过添加对输出格式的解释以强制产生需要的输出。为了获得最大的灵活性,甚至可以通过将自定义提示作为参数传递给system_prompt参数来覆盖整个系统提示模板。具体操作时,可通过agent.system_prompt_template获取初始化的系统提示,然后根据自己需要修改后进行替换,操作如代码13-24所示:
from transformers import ReactJsonAgent
from transformers.agents import PythonInterpreterTool
agent = ReactJsonAgent(tools=[PythonInterpreterTool()], system_prompt="{your_custom_prompt}")
请确保模板中某个位置定义<<tool_descriptions>>,以便代理获取工具信息。
13.4.4 运行库、运行参数和运行状况
除了引擎、工具、系统提示和解析器外,还有一些辅助设置有助于使用智能体。比如CodeAgent在执行生成代码时,可能需要导入某些运行库;或者Agent运行时需添加句子、文件等可替换参数;当代理运行完毕后查看代理的运行情况等。
导入运行库。Python解释器会在一组与工具一起传递的输入上执行代码,这理应是安全的,因为解释器只能调用传入的工具函数(特别是Hugging Face工具)和print函数,因此已经限制了可以执行的内容。Python解释器默认不允许在安全列表之外导入,因此所有显式攻击都不应该得逞。但仍然可以通过在初始化ReactCodeAgent或CodeAgent时,将授权模块作为字符串列表传递给additional_authorized_imports参数来授权额外的导入,如代码13-25所示:
from transformers import ReactCodeAgent
agent = ReactCodeAgent(tools=[], additional_authorized_imports=['requests', 'bs4'])
agent.run("Could you get me the title of the page at url 'https://huggingface.co/blog'?")
(...)
'Hugging Face – Blog'
python解释器会尝试在执行任何非法操作或存在常规错误的代码处停止。LLM可以生成任意代码后执行,但不要添加任何不安全的导入!
agent.run()的运行参数。智能体在底层是如何工作的?本质上,智能体的作用是“允许LLM使用工具”。智能体有一个关键的方法agent.run(),该方法:
- 在一个特定提示中向LLM提供关于工具使用的信息。
- 解析来自LLM输出的工具调用 (可通过代码、JSON或任何其他格式)并执行。
- 如果智能体需要对先前的输出进行迭代,那么它会保留先前的工具调用和观察存储。这个存储可以根据持续的时间长短而变得更精炼或更细致。
当代理调用方法run()时,可以添加额外的参数,比如参数sentence可以将文本作为额外参数传递给模型,如代码13-26所示:
from transformers import CodeAgent, HfApiEngine
llm_engine = HfApiEngine(model="meta-llama/Meta-Llama-3-70B-Instruct")
agent = CodeAgent(tools=[], llm_engine=llm_engine, add_base_tools=True)
agent.run("Could you translate this sentence from French, say it out loud and return the audio.", sentence="Où est la boulangerie la plus proche?")
另外,可以使用audio参数来指示模型使用本地或远程文件,如代码13-27所示:
运行agent.write_inner_memory_from_logs()会为代理的日志创建一个内部存储,并化作聊天消息列表供LLM查看。此方法会遍历日志的每一步,并仅将感兴趣的内容存储为消息,它会将系统提示和任务保存在不同的消息中;对于每一步,它还会将LLM输出存储为一条消息,工具调用输出存储为另一条消息。如果想在更高层次的视角来了解发生了什么,可以使用这个方法,但并非所有日志都会被此方法转录。
13.4.5 练习12:Smolagents与Qwen实现文生图
源代码解读。使用独立的Smolagents创建一个简单的Gradio应用代理,该代理可以使用文本生成图像的工具。读者可对比Smolagents和Transformer.agent的区别,两者接口极其相似,只是实现细节有所差别,如代码13-28所示:
import gradio as gr
from dataclasses import asdict
from smolagents import Tool, CodeAgent, InferenceClientModel
from smolagents import stream_to_gradio
from huggingface_hub import login
import os
os.environ["HF_TOKEN"] = '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'
login(os.environ.get("HF_TOKEN"))
image_generation_tool = Tool.from_space(
space_id="black-forest-labs/FLUX.1-dev",
name="image_generator",
description="Generates an image following your prompt. Returns a PIL Image.",
api_name="/infer"
)
llm_engine = InferenceClientModel("Qwen/Qwen2.5-Coder-32B-Instruct")
agent = CodeAgent(tools=[image_generation_tool], model=llm_engine)
def interact_with_agent(prompt, history):
messages = []
yield messages
for msg in stream_to_gradio(agent, prompt):
if hasattr(msg, '__dict__'):
messages.append(asdict(msg))
else:
messages.append(msg)
yield messages
yield messages
demo = gr.ChatInterface(interact_with_agent,
chatbot= gr.Chatbot(label="Agent",
avatar_images=(
None,
"https://em-content.zobj.net/source/twitter/53/robot-face_1f916.png")),
examples=[["Generate an image of an astronaut riding an alligator"],
["I am writing a children's book for my daughter. Can you help me with some illustrations?"]])
if __name__ == "__main__":
demo.launch(debug=True)
代码构建一个具备图像生成和代码处理能力的AI代理,讲解如下:
(1)导入transformers和gradio中的必要工具和类,其中函数stream_to_gradio使用代理运行提示任务,并将代理消息作为gr.ChatMessages实现流式传输。
(2)从Hub中导入登录函数login,登录后可获得更长token。通过Tool.from_space加载了FLUX.1-dev平台的图像生成API,API端点为/infer。使用Qwen2.5-Coder-32B-Instruct模型定义大模型引擎,该模型特别针对代码相关任务进行了优化。随后使用绘画工具和大模型引擎创建Agent。
(3)定义与代理交互的函数interact_with_agent,输入prompt和history之后,利用函数stream_to_gradio从代理中产生消息,消息转换为字典格式并添加到已输出消息,实时更新消息历史并返回。以上是和transformers代理相关的部分。
(4)最后通过gr.ChatInterface和gr.Chatbot定义Gradio UI,并与交互函数interact_with_agent绑定,同时添加type和examples等内容。
运行。要求transformers==4.47.0,gradio==5.49.1,运行界面如图13-5所示:
从输出可以看到显示思考、工具调用和输出结果的过程,正是ReactCodeAgent的推理过程。也可在Hugging Face查看:gradio/agent_chatbot🖇️链接13-19。
更多推荐




所有评论(0)