CAMEL-Task2-Agent的构成组件
本文笔记主要关于2.7章节:CAMEL框架中agents如何使用工具。
一、工具说明
为什么要额外使用工具?
- agents本身的知识和能力有限,比如一些问题需要联网搜索才能准确回答(而不是乱答,即“幻觉”),一些计算需要数学工具才能符合精度要求等。
- agents本身不能和世界交互,需要依赖接口。比如:agents可以基于LLM的语言理解和生成能力对问题给予回应,但无法访问外部数据或执行具体的操作。
此时,工具的存在相当于为agents配备与外界交互的接口。工具的本质是一个具有名称、描述、输入参数和输出的函数,当agents“认为”自己需要使用工具时,只要agents能够正确地生成调用工具名称、参数,对应的工具就会被调用,工具的输出会成为agents继续回答的依据。
CAMEL提供了各种常用工具(包),也支持自定义工具。
二、工具自定义
以数学计算为例子,展示如何通过自定义工具,让agents的回答具备需要的精度。此处需要agents原生具备调用工具的能力(即支持function call)
无使用工具
from camel.agents import ChatAgent
from camel.models import ModelFactory
from camel.types import ModelPlatformTypeimport os
from dotenv import load_dotenv
import mathload_dotenv()# 定义系统消息
sys_msg = "你是一个数学大师,擅长各种数学问题。"# 初始化agent
api_key = os.getenv('QWEN_API_KEY')model = ModelFactory.create(model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,model_type="Qwen/Qwen2.5-72B-Instruct",url='https://api-inference.modelscope.cn/v1/',api_key=api_key
)# 创建agent并添加工具
agent = ChatAgent(system_message=sys_msg,model=model,output_language='中文',tools=[]
)# 定义用户消息
usr_msg = "2的平方根是多少?"# 发送消息给agent
response = agent.step(usr_msg)
print(response.msgs[0].content)
此时得到的输出:
2的平方根是 \(\sqrt{2}\),约等于 1.414。
虽然没有错误,但精度不够高。此时可以借助数学计算工具达到更高的精度。
使用(自定义)工具
此处以“计算一个数的平方根”为例进行工具定义。示例代码(tool.py
)如下:
from camel.toolkits import FunctionTool
import mathdef calculate_sqrt(x: float) -> float:r"""计算一个数的平方根。Args:x (float): 需要计算平方根的数字。Returns:float: 输入数字的平方根。"""return math.sqrt(x)# 用 FunctionTool 包装该函数
sqrt_tool = FunctionTool(calculate_sqrt)print(sqrt_tool.get_function_name()) # 检索函数的名称
print(sqrt_tool.get_function_description()) # 获取函数作用的描述
输出:
calculate_sqrt
计算一个数的平方根。
接下来将工具sqrt_tool
引入,重新完成前述数学计算任务,代码如下:
from camel.agents import ChatAgent
from camel.models import ModelFactory
from camel.types import ModelPlatformType
from tool import sqrt_toolimport os
from dotenv import load_dotenv
import mathload_dotenv()# 定义系统消息
sys_msg = "你是一个数学大师,擅长各种数学问题。"# 初始化agent
api_key = os.getenv('QWEN_API_KEY')model = ModelFactory.create(model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,model_type="Qwen/Qwen2.5-72B-Instruct",url='https://api-inference.modelscope.cn/v1/',api_key=api_key
)# 定义系统消息
sys_msg = "你是一个数学大师,擅长各种数学问题。当你遇到数学问题的时候,你要调用工具,将工具计算的结果作为答案"# 定义用户消息
usr_msg = "2的平方根是多少?"# 创建agent并添加工具
tool_agent = ChatAgent(tools = [sqrt_tool], # 使用自定义的sqrt_toolsystem_message=sys_msg,model=model,output_language="中文")# 重新发送消息给toolagent
response = tool_agent.step(usr_msg)
print(response.msgs[0].content)
print(response.info['tool_calls']) # 检查工具是否真的被调用
输出:
2的平方根是1.4142135623730951。
[ToolCallingRecord(tool_name='calculate_sqrt', args={'x': 2}, result=1.4142135623730951, tool_call_id='call_7c18733f4d324bfb98944f', images=None)]
可以看到agents给出了更精确的结果,并且工具确实被调用。
三、使用CAMEL内置工具
此处以“查询牛津大学校龄”为问题(假设现在是2024年,牛津大学的成立年份,并计算出其当前年龄。然后再将这个年龄加上10年。使用百度搜索工具。),考察agents在CAMEL框架下能否正确使用工具(SearchToolkit
,MathToolkit
)回答问题。
代码如下:
from camel.toolkits import SearchToolkit,MathToolkit
from camel.types.agents import ToolCallingRecord
from camel.models import ModelFactory
from camel.types import ModelPlatformType
from camel.societies import RolePlaying
from camel.utils import print_text_animated
from colorama import Foreimport os
from dotenv import load_dotenvload_dotenv()
api_key = os.getenv('QWEN_API_KEY')#定义工具包
tools_list = [*SearchToolkit().get_tools(),*MathToolkit().get_tools()
]# 设置任务
task_prompt = ("假设现在是2024年,""牛津大学的成立年份,并计算出其当前年龄。""然后再将这个年龄加上10年。使用百度搜索工具。")# 创建模型
model = ModelFactory.create(model_platform=ModelPlatformType.OPENAI_COMPATIBLE_MODEL,model_type="Qwen/Qwen2.5-72B-Instruct",url='https://api-inference.modelscope.cn/v1/',api_key=api_key
)# 设置角色扮演
role_play_session = RolePlaying(assistant_role_name="搜索者",user_role_name="教授",assistant_agent_kwargs=dict(model=model,tools=tools_list, # 允许agent使用SearchToolkit,MathToolkit),user_agent_kwargs=dict(model=model,),task_prompt=task_prompt,with_task_specify=False,output_language='中文'
)# 设置聊天轮次限制
chat_turn_limit=10print(Fore.GREEN+ f"AI助手系统消息:\n{role_play_session.assistant_sys_msg}\n"
)
print(Fore.BLUE + f"AI用户系统消息:\n{role_play_session.user_sys_msg}\n"
)print(Fore.YELLOW + f"原始任务提示:\n{task_prompt}\n")
print(Fore.CYAN+ "指定的任务提示:"+ f"\n{role_play_session.specified_task_prompt}\n"
)
print(Fore.RED + f"最终任务提示:\n{role_play_session.task_prompt}\n")n = 0
input_msg = role_play_session.init_chat()
while n < chat_turn_limit:n += 1assistant_response, user_response = role_play_session.step(input_msg)if assistant_response.terminated:print(Fore.GREEN+ ("AI助手终止。原因: "f"{assistant_response.info['termination_reasons']}."))breakif user_response.terminated:print(Fore.GREEN+ ("AI用户终止。"f"原因: {user_response.info['termination_reasons']}."))break# 打印用户的输出print_text_animated(Fore.BLUE + f"AI用户:\n\n{user_response.msg.content}\n")if "CAMEL_TASK_DONE" in user_response.msg.content:break# 打印助手的输出,包括任何函数执行信息print_text_animated(Fore.GREEN + "AI助手:")tool_calls: list[ToolCallingRecord] = assistant_response.info['tool_calls']for func_record in tool_calls:print_text_animated(f"{func_record}")print_text_animated(f"{assistant_response.msg.content}\n")input_msg = assistant_response.msg
先安装
beautifulsoup4
这个python包,否则后续调用工具时,会因为缺少这个包而无法调用。
输出记录
AI助手系统消息
BaseMessage(role_name='搜索者', role_type=<RoleType.ASSISTANT: 'assistant'>, meta_dict={'task': '假设现在是2024年,牛津大学的成立年份,并计算出其当前年龄。然后再将这个年龄加上10年。使用百度搜索工具。', 'assistant_role': '搜索者', 'user_role': '教授'}, content='===== RULES OF ASSISTANT =====\nNever forget you are a 搜索者 and I am a 教授. Never flip roles! Never instruct me!\nWe share a common interest in collaborating to successfully complete a task.\nYou must help me to complete the task.\nHere is the task: 假设现在是2024年,牛津大学的成立年份,并计算出其当前年龄。然后再将这个年龄加上10年。使用百度搜索工具。. Never forget our task!\nI must instruct you based on your expertise and my needs to complete the task.\n\nI must give you one instruction at a time.\nYou must write a specific solution that appropriately solves the requested instruction and explain your solutions.\nYou must decline my instruction honestly if you cannot perform the instruction due to physical, moral, legal reasons or your capability and explain the reasons.\nUnless I say the task is completed, you should always start with:\n\nSolution: <YOUR_SOLUTION>\n\n<YOUR_SOLUTION> should be very specific, include detailed explanations and provide preferable detailed implementations and examples and lists for task-solving.\nAlways end <YOUR_SOLUTION> with: Next request.\nRegardless of the input language, you must output text in 中文.', video_bytes=None, image_list=None, image_detail='auto', video_detail='auto', parsed=None)
AI用户系统消息
BaseMessage(role_name='教授', role_type=<RoleType.USER: 'user'>, meta_dict={'task': '假设现在是2024年,牛津大学的成立年份,并计算出其当前年龄。然后再将这个年龄加上10年。使用百度搜索工具。', 'assistant_role': '搜索者', 'user_role': '教授'}, content='===== RULES OF USER =====\nNever forget you are a 教授 and I am a 搜索者. Never flip roles! You will always instruct me.\nWe share a common interest in collaborating to successfully complete a task.\nI must help you to complete the task.\nHere is the task: 假设现在是2024年,牛津大学的成立年份,并计算出其当前年龄。然后再将这个年龄加上10年。使用百度搜索工具。. Never forget our task!\nYou must instruct me based on my expertise and your needs to solve the task ONLY in the following two ways:\n\n1. Instruct with a necessary input:\nInstruction: <YOUR_INSTRUCTION>\nInput: <YOUR_INPUT>\n\n2. Instruct without any input:\nInstruction: <YOUR_INSTRUCTION>\nInput: None\n\nThe "Instruction" describes a task or question. The paired "Input" provides further context or information for the requested "Instruction".\n\nYou must give me one instruction at a time.\nI must write a response that appropriately solves the requested instruction.\nI must decline your instruction honestly if I cannot perform the instruction due to physical, moral, legal reasons or my capability and explain the reasons.\nYou should instruct me not ask me questions.\nNow you must start to instruct me using the two ways described above.\nDo not add anything else other than your instruction and the optional corresponding input!\nKeep giving me instructions and necessary inputs until you think the task is completed.\nWhen the task is completed, you must only reply with a single word <CAMEL_TASK_DONE>.\nNever say <CAMEL_TASK_DONE> unless my responses have solved your task.\nRegardless of the input language, you must output text in 中文.', video_bytes=None, image_list=None, image_detail='auto', video_detail='auto', parsed=None)
任务提示
原始任务提示:
假设现在是2024年,牛津大学的成立年份,并计算出其当前年龄。然后再将这个年龄加上10年。使用百度搜索工具。指定的任务提示:
None最终任务提示:
假设现在是2024年,牛津大学的成立年份,并计算出其当前年龄。然后再将这个年龄加上10年。使用百度搜索工具。Warning: No results found. Check if Baidu HTML structure has changed.
AI用户
Instruction: 使用百度搜索工具查找牛津大学的成立年份。
Input: None
AI助手
Tool Execution: search_baiduArgs: {'query': '牛津大学 成立年份', 'number_of_result_pages': 1}Result: {'results': []}
Tool Execution: subArgs: {'a': 2024, 'b': 1167}Result: 857
看起来直接从百度搜索结果中没有获取到具体的信息,但我可以根据已知的资料来回答您的问题。牛津大学的成立年份普遍认为是在1096年或者1167年,但确切的日期并不确定,因为大学是一个逐渐形成的过程而不是一个具体的事件。如果我们假设牛津大学的成立年份为1167年,那么到2024年,牛津大学的年龄将是857年。接下来,我将这个年龄再加上10年。857 + 10 = 867年。因此,如果现在是2024年,那么牛津大学在2024年的年龄是857年,而再过10年后,它的年龄将会是867年。如果您需要进一步确认牛津大学的确切成立年份,请告诉我,我可以尝试通过其他途径查询。 Next request.
AI用户
Instruction: 确认牛津大学的成立年份是否为1167年,并计算其在2024年的年龄以及再过10年的年龄。
Input: 1167年
AI助手
Tool Execution: search_baiduArgs: {'query': '牛津大学 确切成立年份', 'number_of_result_pages': 1}Result: {'results': [{'result_id': 1, 'title': '...世界中最古老的大学。A哈佛大学B剑桥大学B剑桥大学C牛津大学D...', 'description': '', 'url': 'http://www.baidu.com/link?url=R4hnTfV9RxjVwHauc21YLg_AUDUwVSCLVJ2A6a7rLBeHkg2sIHQXi7D6NUMtvyLfliWl5mPj2NPg_HMkLVtjZdeWIw1jNGDYnh9L2YrL6nWt3HnADJ4UGP8oZsFTRsqWmKBPYaaYC5NumQRQYwRCmK'}]}
根据百度搜索的结果,虽然没有直接明确指出牛津大学的具体成立年份,但根据历史记载和学术界的共识,牛津大学的成立年份通常被认为是1167年。因此,按照1167年作为牛津大学的成立年份,我们可以计算出:
- 到2024年,牛津大学的年龄为857年(2024 - 1167 = 857)。
- 再过10年,即2034年,牛津大学的年龄将是867年(857 + 10 = 867)。所以,牛津大学在2024年的年龄是857年,而在2034年的年龄将是867年。 Next request.
AI用户
Instruction: 确认上述计算结果是否正确,并完成任务。
Input: 857年,867年
AI助手
根据之前的计算和信息确认,牛津大学的成立年份为1167年。因此:- 2024年时,牛津大学的年龄为 \(2024 - 1167 = 857\) 年。
- 再过10年,即2034年时,牛津大学的年龄为 \(857 + 10 = 867\) 年。这些计算结果是正确的。至此,我们已经完成了任务,确认了牛津大学在2024年的年龄为857年,以及再过10年后其年龄为867年。如果您有其他任何问题或需要进一步的帮助,请随时告知。任务已完成。 Next request.
AI用户
CAMEL_TASK_DONE
观察
此案例中同样使用RolePlaying。由于使用的LLM基座不是很厉害,所以AI用户和AI助手的对话其实会随着每次运行的不同而改变,这里选取了一次确实调用了工具查证后再回答问题的对话记录进行分析。
是的,有时候agents会假装自己用了工具,但实际上工具函数的输入参数是自己编的。
在AI用户和AI助手的对话过程中,第一回合里面AI助手其实工具调用没有返回有用的信息,但自己先假设了一个结果,再第二回合里再去验证。验证的时候,工具返回了有用的信息(可以通过response.info['tool_calls']
查看工具调用的具体信息),这使得后续对话内容更具有真实性。