大模型WebUI:Gradio全解12——使用Agents和Tools智能代理工具构建UI(1)
- 前言
- 本篇摘要
- 12. 使用Agents和Tools智能代理工具构建UI
- 12.1 transformers.agents原理及示例
- 12.1.1 代理概念、类型和构建
- 1. 代理概念
- 2. 代理类型
- 3. 如何构建代理?
- 12.1.2 定义大模型引擎Engines
- 1. 模型函数:llm_engine
- 2. TransformersEngine类
- 3. HfApiEngine类
- 4. HfEngine
- 12.1.3 创建和使用工具Tools
- 1. 默认工具箱
- 2. 创建新工具
- 3. 管理代理的工具箱
- 4. 使用工具集合
- 12.1.4 系统提示
- 1. 系统提示示例
- 2. 系统提示格式
- 3. 修改系统提示
- 12.1.5 其它设置
- 1. 导入代码
- 2. run的运行参数
- 3. 检查代理运行情况
- 12.1.6 使用transformers.agents的构建Gradio UI
- 1. ChatMessage数据类
- 2. 构建Gradio UI示例
- 参考文献
前言
本系列文章主要介绍WEB界面工具Gradio。Gradio是Hugging Face发布的简易WebUI开发框架,它基于FastAPI和svelte,可以使用机器学习模型、python函数或API开发多功能界面,并可部署人工智能模型,是当前热门的非常易于展示机器学习大语言模型LLM及扩散模型DM的WebUI框架。
本系列文章分为五部分:Gradio介绍、HuggingFace资源与工具库、Gradio基础功能实战、Gradio与大模型融合实战和Gradio高级功能实战。第一部分Gradio介绍,包括三章内容:第一章先介绍Gradio的概念,包括详细技术架构、历史、应用场景、与其他框架Gradio/NiceGui/StreamLit/Dash/PyWebIO的区别,然后详细介绍了Gradio的安装与运行,安装包括Linux/Win/Mac三类系统安装,运行包括普通方式和热重载方式;第二章介绍Gradio的4种部署方式,包括本地部署launch()、huggingface托管、FastAPI挂载和Gradio-Lite浏览器集成;第三章介绍Gradio的三种Client,包括python客户端、javascript客户端和curl客户端,方便读者对Gradio整体把握。第二部分介绍著名网站Hugging Face的各类资源和工具库,因为Gradio演示中经常用到Hugging Face的models及某些场景需要部署在spaces,包括两章内容:第四章详解三类资源models/datasets/spaces的使用,第五章实战六类工具库transformers/diffusers/datasets/PEFT/accelerate/optimum实战。第三部分是Gradio基础功能实战,进入本系列文章的核心,包括四章内容:第六章讲解Gradio库的模块架构和环境变量,第七章讲解Gradio高级抽象界面类Interface,第八章讲解Gradio底层区块类Blocks,第九章讲解补充特性Additional Features。第四部分是Gradio与大模型融合的实战,包括三章内容:第十章讲解融合大模型的多模态聊天机器人组件Chatbot,第十一章讲解使用Agents和Tools智能代理工具构建UI,第十二章讲述将Gradio用于LLM Agents的Gradio Tools。第五部分详解Gradio高级功能,包括三章内容:第十三章讲述Discord Bot/Slack Bot/Website Widget部署,第十四章讲述数据科学与绘图Data Science And Plots,第十五章讲述数据流Streaming。
本系列文章讲解细致,涵盖Gradio大部分组件和功能,代码均可运行并附有大量运行截图,方便读者理解并应用到开发中,Gradio一定会成为每个技术人员实现各种奇思妙想的最称手工具。
本系列文章目录如下:
- 《Gradio全解1——Gradio简介》
- 《Gradio全解1——Gradio的安装与运行》
- 《Gradio全解2——Gradio的3+1种部署方式实践》
- 《Gradio全解2——浏览器集成Gradio-Lite》
- 《Gradio全解3——Gradio Client:python客户端》
- 《Gradio全解3——Gradio Client:javascript客户端》
- 《Gradio全解3——Gradio Client:curl客户端》
- 《Gradio全解4——剖析Hugging Face:详解三类资源models/datasets/spaces》
- 《Gradio全解5——剖析Hugging Face:实战六类工具库transformers/diffusers/datasets/PEFT/accelerate/optimum》
- 《Gradio全解6——Gradio库的模块架构和环境变量》
- 《Gradio全解7——Interface:高级抽象界面类(上)》
- 《Gradio全解7——Interface:高级抽象界面类(下)》
- 《Gradio全解8——Blocks:底层区块类(上)》
- 《Gradio全解8——Blocks:底层区块类(下)》
- 《Gradio全解9——Additional Features:补充特性(上)》
- 《Gradio全解9——Additional Features:补充特性(下)》
- 《Gradio全解10——Chatbot:融合大模型的多模态聊天机器人(1)》
- 《Gradio全解10——Chatbot:融合大模型的多模态聊天机器人(2)》
- 《Gradio全解10——Chatbot:融合大模型的多模态聊天机器人(3)》
- 《Gradio全解10——Chatbot:融合大模型的多模态聊天机器人(4)》
- 《Gradio全解10——Chatbot:融合大模型的多模态聊天机器人(5)》
- 《Gradio全解11——使用Agents和Tools智能代理工具构建UI(1)》
- 《Gradio全解11——使用Agents和Tools智能代理工具构建UI(2)》
- 《Gradio全解11——使用Agents和Tools智能代理工具构建UI(3)》
- 《Gradio全解11——使用Agents和Tools智能代理工具构建UI(4)》
- 《Gradio全解12——Gradio Tools:将Gradio用于LLM Agents》
- 《Gradio全解系列13——Discord Bot/Slack Bot/Website Widget部署》
- 《Gradio全解系列14——Data Science And Plots:数据科学与绘图》
- 《Gradio全解15——Streaming:数据流(上)》
- 《Gradio全解15——Streaming:数据流(下)》
本篇摘要
本章介绍如何使用Agents和Tools智能代理工具构建Gradio UI,主要内容包括transformers.agents原理及示例用法、langchain agents的原理及示例用法、langgraph的原理及示例用法和使用显示思考的Gemini 2.0 Flash Thinking API构建UI。
12. 使用Agents和Tools智能代理工具构建UI
Gradio的组件Chatbot原生支持显示中间思考过程和工具使用情况(参考其参数metadata用法),这使得它非常适合为LLM Agent、思维链(Chain-of-Thought, CoT)或推理演示创建用户界面,本章将展示如何使用gr.Chatbot和gr.ChatInterface来显示思考过程和工具使用情况。
本章讲解四种代理和工具方式构建用户界面,其中前三种为代理方式,包括transformers.agents、langchain agents和langgraph,第四种使用显示思考的思维链工具Gemini 2.0 Flash Thinking API构建UI。因为Agents作为连接各大模型和工具的桥梁,是打通人工通用智能(AGI)的最后一公里,对从业者和人工智能行业都是至关重要的一环,所以本章将重点放在Agents智能体的原理及应用步骤拆解,然后才是通过Gradio构建UI。作者为此阅读了大量文献,以尽量阐述清楚智能代理,请读者不吝三连和评论,共同进步变强。
12.1 transformers.agents原理及示例
本节讲述transformers.agents原理及示例,内容包括代理概念类型和构建、定义大模型引擎Engines、创建和使用Tools、系统提示、其它设置和使用transformers.agents构建Gradio UI。
12.1.1 代理概念、类型和构建
本小节讲述代理的概念、类型和构建方法,对理解代理至关重要。
1. 代理概念
经过大量因果语言建模(causal language modeling)训练的大型语言模型(LLMs)能够处理多种任务,但在逻辑、计算和搜索等基本任务上往往表现不佳。当在这些不擅长的领域中被prompt时,大模型通常无法生成我们期望的答案,克服这一弱点的一种方法是创建代理LLM Agent。
LLM Agent的定义非常宽泛:通常指的是所有将LLM作为核心引擎,并能够根据观察对其环境施加影响的系统。这些系统能够通过多次迭代“感知 ⇒ 思考 ⇒ 行动”的循环来实现既定任务,并常常融入规划或知识管理系统以提升其表现效能。并且LLM Agent可以访问工具(Tools),这些工具通常是用于执行任务的函数,它们必须包含代理正确使用它们所需的所有描述。我们可以在论文《The Rise and Potential of Large Language Model Based Agents: A Survey》的研究中找到对智能体领域的精彩评述。
2. 代理类型
代理可以设计为一系列动作/工具(actions/tools)并一次性运行它们,也可以逐个计划后执行动作/工具,并在启动下一个动作之前等待前面每个动作的结果。因此根据代理设计理念的不同,一般分为两种类型:
- Code agent:该代理有一个规划步骤,然后生成Python代码以一次性执行所有动作。它原生支持处理工具内不同的输入和输出类型,因此是多模态任务的推荐选择;
- React agent:它采用一种基于“推理 (Reasoning)”与“行动 (Acting)”结合的方式逐步解决给定任务,并以此来构建智能体。Transformers实现了三种版本的React agent:
(1)ReactAgent:原始的推理执行代理,它的行动将从大语言模型(LLM)的输出中解析出来,它并不常用,经常会被下面两种衍生代理代替;
(2)ReactJsonAgent:工具调用将由LLM以JSON代码块生成,然后解析并执行;
(3)ReactCodeAgent:是一种新型的ReactJsonAgent,工具调用将由LLM以Python代码块生成,这对于具有强大编码性能的LLM非常有效。
我们将重点放在ReAct智能体上,因为它是解决推理任务的首选代理。在提示词中,我们阐述了模型能够利用哪些工具,并引导它逐步思考“step by step” (亦称为思维链行为Chain-of-Thought),以规划并实施其后续动作。ReAct框架(ReAct: Synergizing Reasoning and Acting in Language Models)使代理可以在基于先前观察的基础上进行多次非常高效的思考。读者可以阅读Open-source LLMs as LangChain Agents博客文章(https://huggingface.co/blog/open-source-llms-as-agents)以了解更多关于使用ReAct代理的信息。
单步Code agent与多步React agent执行流程区别可参考下图:
3. 如何构建代理?
要初始化一个代理,需要以下参数:
- llm_engine:此参数设置为代理提供动力引擎的LLM,代理并不完全是LLM,它更像是一个使用LLM作为引擎的程序;
- 工具箱:代理从中挑选工具来执行任务;
- 系统提示(system prompt):LLM引擎将根据这个提示生成其输出;
- 解析器:用于从LLM的输出中提取需要调用的工具及其参数。
在代理系统初始化时,工具的属性被用来生成工具描述,将其嵌入到代理的system_prompt中,以便代理知道它可以使用哪些工具以及为什么使用这些工具。然后LLM根据系统提示输出解决方案,解析器解析LLM的输出后,决定调用哪些工具继续执行。最后当已经得到答案或者满足停止条件将结束会话,否则继续迭代执行。下面对这四方面内容逐一讲述。当然开始之前,请使用以下命令额外安装transformers的agents以安装所有默认依赖项:pip install transformers[agents]。
12.1.2 定义大模型引擎Engines
我们可以自由创建和使用用于代理框架的引擎Engines,但需要满足以下条件:
- 它遵循输入消息的消息格式(List[Dict[str, str]]),并返回一个字符串;
- 它在传入的参数stop_sequences指定的序列处停止生成输出。
下面就来了解下四种不同的创建引擎方法:模型函数llm_engine、TransformersEngine、HfApiEngine和HfEngine,请注意区分它们适用的场景。
1. 模型函数:llm_engine
我们可以通过定义一个接受消息列表并返回文本的函数llm_engine来构建LLM引擎,这个可调用对象还需要接受一个stop参数以指示何时停止生成。
from huggingface_hub import login, InferenceClientlogin("<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.contentreturn answer
此外,llm_engine还可以接受一个grammar参数。如果在代理初始化时指定了grammar,这个参数将连同初始化时定义的grammar一起传递给llm_engine的调用,以实现受约束的生成,从而强制代理生成格式正确的输出。
2. TransformersEngine类
为了方便起见,我们可以添加一个TransformersEngine,它通过预初始化的Pipeline作为输入实现上述功能,或可选的使用model_id,以便使用transformers在本地机器上运行推理。示例代码如下:
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline, TransformersEnginemodel_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 "
3. HfApiEngine类
由于代理行为通常需要更强的模型,如Llama-3.1-70B-Instruct,这些模型目前较难在本地运行,因此我们还提供了HfApiEngine类,HfApiEngine是一个封装了Hugging Face Inference API客户端的引擎,即在底层初始化了一个huggingface_hub.InferenceClient,用于执行大语言模型(LLM)的调用。
该引擎通过Hugging Face的推理API与语言模型进行通信,它可以在无服务器模式(serverless)下使用,也可以与专用端点(dedicated endpoint)一起使用,并支持诸如停止序列(stop sequences)和语法自定义(grammar customization)等功能。程序中甚至可以留空llm_engine参数,默认情况下会创建一个HfApiEngine。示例代码如下:
from transformers import HfApiEnginellm_engine = HfApiEngine(model="meta-llama/Meta-Llama-3-70B-Instruct")
4. HfEngine
对于本地部署的具有Inference API的LLM,我们还可以直接使用包中提供的 HfEngine 类来获取一个调用我们自己的Inference API的LLM引擎,示例代码如下:
from transformers.agents import HfEnginellm_engine = HfEngine("meta-llama/Meta-Llama-3-70B-Instruct")
12.1.3 创建和使用工具Tools
工具是代理使用的原子函数,包括各种属性和执行方法。例如PythonInterpreterTool:它有一个名称、描述、输入描述、输出类型,以及一个执行操作的 call 方法。当代理初始化时,工具属性会被用来生成工具描述,并将其嵌入到代理的系统提示中,这让代理知道它可以使用哪些工具以及为什么使用这些工具。工具是代理的核心部分,工具的好坏和数量决定了代理能力的大小。本节将从默认工具箱、创建新工具、管理工具箱及使用工具集等方面讲解如何使用工具。
1. 默认工具箱
Transformers附带了一个默认工具箱,用于增强代理的功能。构建Agent时需要一个tools参数,它接受一个工具列表(List[Tools])——这个列表可以是空的,但可以在代理初始化时,通过定义可选参数add_base_tools=True,将默认工具箱添加到工具列表。默认工具箱中工具如下:
- 文档问答:给定一个图像格式的文档(如PDF),回答关于该文档的问题(Donut);
- 图像问答:给定一张图像,回答关于该图像的问题(VILT);
- 语音转文本:给定一段人声录音,将语音转录为文本(Whisper);
- 文本转语音:将文本转换为语音(SpeechT5);
- 翻译:将给定的句子从源语言翻译为目标语言;
- DuckDuckGo搜索*:使用DuckDuckGo浏览器执行网络搜索;
- Python代码解释器:在安全环境中运行由LLM生成的Python代码。此工具仅在初始化 ReactJsonAgent时通过add_base_tools=True添加,因为基于代码的代理已经可以原生执行Python代码。
我们还可以通过调用load_tool()函数并指定要执行的任务来手动使用工具,示例代码如下:
from transformers import load_tooltool = load_tool("text-to-speech")
audio = tool("This is a text to speech tool")
2. 创建新工具
我们可以为Hugging Face默认工具未涵盖的用例创建自己的工具。以返回HF Hub上某个任务下载量最多的模型为例创建一个工具,步骤如下:
- 创建核心功能代码:
from huggingface_hub import list_modelstask = "text-classification"model = next(iter(list_models(filter=task, sort="downloads", direction=-1)))
print(model.id)
- 然后将核心代码封装为工具函数以快速将其转换为工具,操作时只需将其包装在一个函数中并添加工具装饰器@tool即可:
from transformers import tool@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
此函数需要:
- 清晰的函数名:名称通常描述工具的功能,由于代码返回某个任务下载量最多的模型,我们可以命名为model_download_tool;
- 输入和输出的类型提示:输入的类型提示为函数的入参,输出类型提示为符号->后的类型,方便大模型调用;
- 函数描述:描述函数的作用和返回说明,其中包括一个“Args:”部分,描述每个参数的作用(注意这里不需要类型指示,因为会从类型提示中提取)。所有这些将在初始化时自动嵌入到代理的系统提示中,因此请尽量使它们清晰易懂!
- 最后将工具直接添加到初始化代理的参数tools中。现在,我们可以创建一个代理,比如CodeAgent,并使用创建的工具model_download_tool,示例代码如下:
from transformers import CodeAgent, HfApiEnginellm_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."
3. 管理代理的工具箱
当我们已经初始化了一个带有工具箱的代理时,从头开始重新初始化以添加想使用的工具会很不方便。此时可以使用Transformers代理,通过toolbox中函数add_tool()和update_tool()来添加或替换工具来管理代理的工具箱。
让我们将上面定义的model_download_tool添加到一个仅使用默认工具箱初始化的现有代理中,代码如下:
from transformers import CodeAgentagent = CodeAgent(tools=[], llm_engine=llm_engine, add_base_tools=True)
agent.toolbox.add_tool(model_download_tool)
现在我们可以同时利用新工具和之前的文本转语音工具:
agent.run("Can you read out loud the name of the model that has the most downloads in the 'text-to-video' task on the Hugging Face Hub and return the audio?"
)
在为已经运行良好的代理添加工具时要小心,因为它可能会偏向选择你定义的工具,或者选择与原工具不同的工具。
我们还可以使用方法agent.toolbox.update_tool()替换代理工具箱中的现有工具,尤其是新工具一对一替换现有工具时将非常有用,因为代理已经知道如何执行该特定任务。替换时只需确保新工具遵循与被替换工具相同的API,或者调整系统提示模板以确保更新所有使用被替换工具的示例。
4. 使用工具集合
我们还可以通过使用transformers中的对象ToolCollection来利用工具集合,并通过参数collection_slug指定想要使用的集合片,它们将作为列表传递给代理进行初始化,示例代码如下:
from transformers import ToolCollection, ReactCodeAgentimage_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.")
为了加快启动速度,工具只有在被代理调用时才会加载。另外,实际运行时可能会报错:ImportError: cannot import name ‘cached_download’ from ‘huggingface_hub’. Did you mean: ‘hf_hub_download’?。原因可能是工具内部调用有问题,只能等官方更新。运行正常时,上面代码将会产生以下类似图片:
12.1.4 系统提示
在代理系统初始化时,工具的属性被用来生成工具描述,将其嵌入到代理的system_prompt中,以便代理知道它可以使用哪些工具以及为什么使用这些工具,然后LLM根据系统提示输出解决方案。本小节将展示系统提示示例、解析系统提示格式以及如何修改系统提示。
1. 系统提示示例
系统提示(system prompt)和输出解析器(output parser)是自动定义的,但我们可以通过调用代理上的system_prompt_template和tool_parser轻松查看它们。
代理,或者更准确地说,驱动代理的LLM,会根据系统提示生成输出。系统提示可以根据预期的任务进行定制和调整。在系统提示中,尽可能清楚地解释我们想要执行的任务非常重要。由于代理是由LLM驱动的,且每次run()操作都是独立的,提示中的微小变化可能会产生完全不同的结果,因此也可以连续运行代理以执行不同的任务:每次运行时,agent.task和agent.logs属性都会重新初始化。
例如,查看ReactCodeAgent的系统提示(以下版本略有简化):
>>> 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.At each step, in the 'Thought:' sequence, you should first explain your reasoning towards solving the task and the tools that you want to use.
Then in the 'Code:' sequence, you should write the code in simple Python. The code sequence must end with '<end_action>' sequence.
During each intermediate step, you can use 'print()' to save whatever important information you will then need.
These print outputs will then appear in the 'Observation:' field, which will be available as input for the next step.
In the end you have to return a final answer using the `final_answer` tool.Here are a few examples using notional tools:
---
Task: "Generate an image of the oldest person in this document."Thought: I will proceed step by step and use the following tools: `document_qa` to find the oldest person in the document, then `image_generator` to generate an image according to the answer.
Code:
```py
answer = document_qa(document=document, question="Who is the oldest person mentioned?")
print(answer)
```<end_action>
Observation: "The oldest person in the document is John Doe, a 55 year old lumberjack living in Newfoundland."Thought: I will now generate an image showcasing the oldest person.
Code:
```py
image = image_generator("A portrait of John Doe, a 55-year-old man living in Canada.")
final_answer(image)
```<end_action>---
Task: "What is the result of the following operation: 5 + 3 + 1294.678?"Thought: I will use python code to compute the result of the operation and then return the final answer using the `final_answer` tool
Code:
```py
result = 5 + 3 + 1294.678
final_answer(result)
```<end_action>---
Task: "Which city has the highest population: Guangzhou or Shanghai?"Thought: I need to get the populations for both cities and compare them: I will use the tool `search` to get the population of both cities.
Code:
```py
population_guangzhou = search("Guangzhou population")
print("Population Guangzhou:", population_guangzhou)
population_shanghai = search("Shanghai population")
print("Population Shanghai:", population_shanghai)
```<end_action>
Observation:
Population Guangzhou: ['Guangzhou has a population of 15 million inhabitants as of 2021.']
Population Shanghai: '26 million (2019)'Thought: Now I know that Shanghai has the highest population.
Code:
```py
final_answer("Shanghai")
```<end_action>---
Task: "What is the current age of the pope, raised to the power 0.36?"Thought: I will use the tool `wiki` to get the age of the pope, then raise it to the power 0.36.
Code:
```py
pope_age = wiki(query="current pope age")
print("Pope age:", pope_age)
```<end_action>
Observation:
Pope age: "The pope Francis is currently 85 years old."Thought: I know that the pope is 85 years old. Let's compute the result using python code.
Code:
```py
pope_current_age = 85 ** 0.36
final_answer(pope_current_age)
```<end_action>Above example were using notional tools that might not exist for you. On top of performing computations in the Python code snippets that you create, you have access to these tools (and no other tool):<<tool_descriptions>><<managed_agents_descriptions>>Here are the rules you should always follow to solve your task:
1. Always provide a 'Thought:' sequence, and a 'Code:
```py' sequence ending with '```<end_action>' sequence, else you will fail.
2. Use only variables that you have defined!
3. Always use the right arguments for the tools. DO NOT pass the arguments as a dict as in 'answer = wiki({'query': "What is the place where James Bond lives?"})', but use the arguments directly as in 'answer = wiki(query="What is the place where James Bond lives?")'.
4. Take care to not chain too many sequential tool calls in the same code block, especially when the output format is unpredictable. For instance, a call to search has an unpredictable return format, so do not have another tool call that depends on its output in the same block: rather output results with print() to use them in the next block.
5. Call a tool only when needed, and never re-do a tool call that you previously did with the exact same parameters.
6. Don't name any new variable with the same name as a tool: for instance don't name a variable 'final_answer'.
7. Never create any notional variables in our code, as having these in your logs might derail you from the true variables.
8. You can use imports in your code, but only from the following list of modules: <<authorized_imports>>
9. The state persists between code executions: so if in one step you've created variables or imported modules, these will all persist.
10. Don't give up! You're in charge of solving the task, not providing directions to solve it.Now Begin! If you solve the task correctly, you will receive a reward of $1,000,000.
2. 系统提示格式
观察上面系统提示示例,可以发现系统提示一般包括:
- 一段介绍,解释代理应如何行为以及工具是什么;
- 所有工具的描述,这些描述由<<tool_descriptions>>标记定义,该标记在运行时动态替换为用户定义/选择的工具,工具描述来源于工具属性,包括名称、描述、输入和输出类型,以及一个用户可以优化的简单jinja2模板;
- 示例examples,示例中尽量包含所提到的工具及使用方法;
- 预期的输出格式,对final_answer的具体要求。
系统提示格式只是起到引导作用,并没有具体的格式规定,可根据自己需求添加或删除,但一般情况是:越具体越清晰的系统提示会产生更好的输出效果!
3. 修改系统提示
我们可以改进系统提示,例如通过添加对输出格式的解释以强制产生需要的输出。为了获得最大的灵活性,甚至可以通过将自定义提示作为参数传递给system_prompt参数来覆盖整个系统提示模板。具体操作时,可通过agent.system_prompt_template获取初始化的系统提示,根据自己需要修改后进行替换,替换代码如下:
from transformers import ReactJsonAgent
from transformers.agents import PythonInterpreterToolagent = ReactJsonAgent(tools=[PythonInterpreterTool()], system_prompt="{your_custom_prompt}")
请确保在模板中的某个位置定义<<tool_descriptions>>字符串,以便代理知道可用的工具。
12.1.5 其它设置
除了引擎、工具和系统提示外,还有一些辅助设置,可以帮助我们更好的使用代理,比如对于CodeAgent、ReactCodeAgent,在执行生成代码时,可能需要导入某些运行库;或者agent运行时需添加句子、文件等可替换参数;当代理运行完毕后查看代理的运行情况等,下面逐一讲述。
1. 导入代码
Python解释器会在一组与工具一起传递的输入上执行代码,这应该是安全的,因为解释器只能调用传入的工具函数(特别是Hugging Face工具)和print函数,因此这里已经限制了可以执行的内容。
默认情况下,Python解释器也不允许在安全列表之外进行导入,因此所有最明显的攻击都不应该成为问题。但我们仍然可以通过在初始化ReactCodeAgent或CodeAgent时,将授权的模块作为字符串列表传递给additional_authorized_imports参数来授权额外的导入,示例如下:
from transformers import ReactCodeAgentagent = 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解释器会在尝试执行任何非法操作的代码处停止,或者当代理生成的代码存在常规的Python错误时也会停止。LLM可以生成任意代码后执行,但请注意,不要添加任何不安全的导入!
2. run的运行参数
智能体在底层是如何工作的?本质上,智能体的作用是“允许 LLM 使用工具”。智能体有一个关键的 agent.run() 方法,该方法:
- 在一个 特定提示 中向你的 LLM 提供关于工具使用的信息。这样,LLM 可以选择运行工具来解决任务;
- 解析 来自 LLM 输出的工具调用 (可以通过代码、JSON 格式或任何其他格式)并执行调用;
- 如果智能体被设计为对先前的输出进行迭代,它会保留先前的工具调用和观察的记忆。这个记忆可以根据你希望它持续的时间长短而变得更加或更少细致。
当代理调用方法run()时,可以添加额外的参数,比如参数sentence可以将文本作为额外参数传递给模型,示例如下:
from transformers import CodeAgent, HfApiEnginellm_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?",
)
另外,也可以使用这个参数来指示模型使用的本地或远程文件的路径,如下::
from transformers import ReactCodeAgentagent = ReactCodeAgent(tools=[], llm_engine=llm_engine, add_base_tools=True)agent.run("Why does Mike not know many people in New York?", audio="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/recording.mp3")
3. 检查代理运行情况
当代理成功运行后,可以通过一些有用的属性检查运行后发生的情况:agent.logs 存储了代理的详细日志。在代理运行的每一步,所有内容都会被存储在一个字典中,然后追加到agent.logs中。
运行agent.write_inner_memory_from_logs() 会为代理的日志创建一个内部记忆,化作聊天消息列表供LLM查看。此方法会遍历日志的每一步,并仅将其感兴趣的内容存储为消息:例如,它会将系统提示和任务保存在不同的消息中,然后对于每一步,它会将LLM输出存储为一条消息,工具调用输出存储为另一条消息。如果我们想在更高层次的视角来了解发生了什么,可以使用这个方法——但并非所有日志都会被此方法转录。
为方便理解ReAct流程,可以参考下面ReactCodeAgent解决问题时的执行步骤:
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 N = 6 identical layers. Each layer has two sub-layers. The first is a multi-head self-attention mechanism, and the second is a simple, position- 2 Page 3 Figure 1: The Transformer - model architecture.====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: 6Final answer: 6
12.1.6 使用transformers.agents的构建Gradio UI
在用transformers.agents的构建Gradio UI之前,先回顾下Gradio的数据类ChatMessage,然后使用ChatMessage构建UI。
1. ChatMessage数据类
在Gradio的聊天机器人chatbot中,每条消息都是一个类型为ChatMessage的数据类(假设聊天机器人的type=“message”,推荐使用此类型,因为tuple类型即将被废弃)。ChatMessage及其子类MetadataDict、OptionDict的结构如下:
@dataclass
class ChatMessage:content: str | Componentrole: Literal["user", "assistant"]metadata: MetadataDict = Noneoptions: list[OptionDict] = Noneclass MetadataDict(TypedDict):title: NotRequired[str]id: NotRequired[int | str]parent_id: NotRequired[int | str]log: NotRequired[str]duration: NotRequired[float]status: NotRequired[Literal["pending", "done"]]class OptionDict(TypedDict):label: NotRequired[str]value: str
对于我们的需求来说,最重要的键是metadata键,它接受一个字典MetadataDict。如果这个字典中包含消息的标题(title),它将会显示在一个可折叠的框中,代表一个“思考”。就是这么简单,看看这个例子:
import gradio as grwith gr.Blocks() as demo:chatbot = gr.Chatbot(type="messages",value=[gr.ChatMessage(role="user", content="What is the weather in San Francisco?"),gr.ChatMessage(role="assistant", content="I need to use the weather API tool?",metadata={"title": "🧠 Thinking"}])demo.launch()
运行截图参照上一章内容。除了title之外,提供给metadata的字典还可以包含以下几个可选键:
- log:一个可选的字符串值,会以较淡的字体显示在思考标题旁边;
- duration:一个可选的数值,表示思考/工具使用的持续时间(以秒为单位),会以较淡的字体显示在思考标题旁边的括号内;
- status:如果设置为pending,会在思考标题旁边显示一个加载动画;如果设置为done,思考的可折叠框会关闭;如果未提供,思考的可折叠框会默认打开且不显示加载动画;
- id和parent_id:如果提供了这些键,可以用来将思考嵌套在其他思考中。
下面,我们展示了一个完整的示例,演示如何使用gr.Chatbot和gr.ChatInterface来展示工具使用或思考的用户界面。
2. 构建Gradio UI示例
我们将创建一个简单的Gradio应用程序代理,该代理可以使用文本生成图像的工具。提示:请确保先理解了前面内容或阅读了transformers.agents文档,地址:https://huggingface.co/docs/transformers/en/agents。
我们将从导入transformers和gradio中的必要类开始,代码如下:
import gradio as gr
from dataclasses import asdict
from transformers import Tool, ReactCodeAgent # type: ignore
from transformers.agents import stream_to_gradio, HfApiEngine # type: ignore# from huggingface_hub import login
# from google.colab import userdata# login(userdata.get('HF_TOKEN'))
## Import tool from Hub
image_generation_tool = Tool.from_space( # type: ignorespace_id="black-forest-labs/FLUX.1-schnell",name="image_generator",description="Generates an image following your prompt. Returns a PIL Image.",api_name="/infer",
)llm_engine = HfApiEngine("Qwen/Qwen2.5-Coder-32B-Instruct")
# Initialize the agent with both tools and engine
agent = ReactCodeAgent(tools=[image_generation_tool], llm_engine=llm_engine)# Building UI
def interact_with_agent(prompt, history):messages = []yield messagesfor msg in stream_to_gradio(agent, prompt):messages.append(asdict(msg)) # type: ignoreyield messagesyield messagesdemo = gr.ChatInterface(interact_with_agent,chatbot= gr.Chatbot(label="Agent",type="messages",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?"],],type="messages",
)if __name__ == "__main__":demo.launch(debug=True)
运行截图如下:
代码讲解:
- 导入transformers和gradio中的必要类,其中函数stream_to_gradio的作用是:使用给定的任务运行代理,并将来自代理的消息作为gradio ChatMessages流式传输。此外还有函数launch_gradio_demo,用于启动工具的gradio演示,其入参相应的工具类需要正确实现类属性input_type和output_type。
- 从Hugging Face Hub中导入login和工具,登录后可获得更长token权限,此处工具为生成图像工具:image_generator,并定义大模型引擎和代理。
- 定义与代理交互的函数interact_with_agent,它输入prompt和history之后,利用函数stream_to_gradio从代理中产生流式输出消息,消息转换为字典格式并添加到已输出消息后集体返回。以上是和transformers代理相关的部分。
- 最后通过gr.ChatInterface和gr.Chatbot定义Gradio UI,并与交互函数interact_with_agent绑定,同时添加type和examples等内容。
作者在本地运行时会报JSON解析错误:JSONDecodeError: Expecting value: line 1 column 1 (char 0),解决办法无从查找,可能和使用VPN代理环境有关。读者可以将代码复制到Google Colab上运行,也可以直接通过Hugging Face的演示查看效果:https://huggingface.co/spaces/gradio/agent_chatbot。
从输出可以看到思考、工具调用和输出结果的调用过程,正是ReactCodeAgent代理的推理执行过程。
参考文献
- Gradio - guides - Chatbots
- License to Call: Introducing Transformers Agents 2.0
- Hugging Face - Transformers - Agents and tools