问题背景
目前,国内大模型基本上都还不支持类似openai的自定义函数绑定,即:llm.bindtools方法,也就无法实现自定义ReAct Agent,如果要使用国内大模型,需要考虑自己实现。本文基于国内的阿里通义千问,实现了类似OpenAI 的ReAct Agent效果。
实现思路
- 使用通义千问的原生接口调用方法,以千问大模型支持的方式来调用工具
- 使用langchain的react agent提示词模板,传给通义大模型,实现大模型的ReAct推理方式
- 解析通义大模型的返回
- 根据大模型返回内容,判断是否已经拿到了最终推理结果,如果没有,就继续递归调用,如果有,返回最终推理结果。
实现代码
以下是实现代码
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.tools import tool
from operator import itemgetter
from dashscope import Generation
from langchain_core.utils.function_calling import convert_to_openai_tool
def get_model_output(**kwargs):
"""通过ReAct递归推理,获取最终答案"""
question = kwargs.get('question')
tools = kwargs.get('tools')
msgs = kwargs.get('msgs', [])
verbose = kwargs.get('verbose', False)
n = kwargs.get('n', 1)
tool_names = [tool.name for tool in tools]
system_prompt = "Answer the following questions as best you can. You have access to the following tools:"
react_prompt = f"""
Use the following format:
Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [{tool_names}]
Action Input: the input to the action
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question
Begin!
Question: {question}
Thought:"""
# 初始化通义大模型输入消息
if not msgs:
msgs = [{"role": "system", "content": system_prompt}, {"role": "user", "content": react_prompt}]
# 调用通义大模型的原生接口获取返回
llm_response = Generation.call(
model="qwen-turbo",
messages=msgs,
tools=[convert_to_openai_tool(tool) for tool in tools],
result_format='message'
)
res_content = llm_response.output.choices[0].message["content"]
if verbose == True:
print("第{}轮ReAct:{}".format(n, res_content))
if res_content.find("Action Input:") != -1:
n = n + 1
# 没拿到Final answer就根据模型店返回继续调用工具
res_chain = call_tool(res_content, tools)
# 重新生成msgs
react_prompt = react_prompt + res_content + "\nObservation:" + str(res_chain) + "\n"
msgs = [{"role": "system", "content": system_prompt}, {"role": "user", "content": react_prompt}]
# 递归调用本方法
res_content = get_model_output(question=question, tools=tools, n=n, msgs=msgs, verbose=verbose)
return res_content
def call_tool(res_content, tools):
"""根据大模型返回内容和给定的工具集,执行工具调用,返回工具输出内容"""
# 创建一个工具MAP
tool_map = {tool.name: tool for tool in tools}
# 创建JSON输出解析器
parser = JsonOutputParser()
# 从大模型回复中解析本次调用的工具名称
tool_call = str(res_content.split("Action:")[1].split("Action Input:")[0]).strip()
# 从大模型回复中解析本次调用工具的入参
tool_call_arguments = str(res_content.split("Action:")[1].split("Action Input:")[1]).strip()
# 组装本次工具调用的schema
model_output = {"name": tool_call, "arguments": parser.parse(tool_call_arguments)}
chosen_tool = tool_map[model_output["name"]]
# 创建工具调用链
chain = itemgetter("arguments") | chosen_tool
# 返回工具调用链的计算结果
return chain.invoke(model_output)
@tool
def add(first_int: int, second_int: int) -> int:
"""将两个数相加。"""
return first_int + second_int
@tool
def minus(first_int: int, second_int: int) -> int:
"""将两个数相减"""
return first_int - second_int
@tool
def multiply(first_int: int, second_int: int) -> int:
"""将两个数相乘"""
return first_int * second_int
@tool
def exponentiate(base: int, exponent: int) -> int:
"""对底数求指数幂"""
return base ** exponent
# 创建工具集
tools = [add, minus, multiply, exponentiate]
question = "4加22乘以10的结果3次方减去10086等于多少?"
react_result = get_model_output(
question=question,
tools=tools,
verbose=True
)
# print(react_result)
以下是开启调试结果后,每次大模型的推理、行动输出
第1轮ReAct: 我们需要按照数学中的运算顺序来解决这个问题:先进行乘法和指数运算,然后做加法,最后做减法。所以,我们需要分别计算22乘以10的结果,这个结果的3次方,然后从得到的结果中减去10086。我需要使用`multiply`工具来计算乘法,`exponentiate`工具来计算指数,以及`minus`工具来进行最后的减法。
Action: multiply
Action Input: {"first_int": 22, "second_int": 10}
第2轮ReAct:Thought: 现在我得到了22乘以10的结果。下一步,我需要计算这个结果的3次方,再进行减法操作。我会用`exponentiate`工具来计算220的三次方。
Action: exponentiate
Action Input: {"base": 220, "exponent": 3}
第3轮ReAct:Thought: 我已经得到了220的三次方。接下来,我需要将这个结果与4相加,然后减去10086。我将使用`add`和`minus`工具来进行这些运算。
Action: add
Action Input: {"first_int": 10648000, "second_int": 4}
第4轮ReAct:Thought: 现在我知道了10648004的结果。接下来我需要从这个结果中减去10086。
Action: minus
Action Input: {"first_int": 10648004, "second_int": 10086}
第5轮ReAct:Thought: 我现在知道了最终答案。
Final Answer: 4加22乘以10的结果3次方减去10086等于10637918。