使用工具调用解析器的OpenAI代理¶
很遗憾,OpenAI工具调用并不总是有效的json,特别是来自较旧版本的API。在包括OpenAI API版本1106在内的版本中,如果参数是一个长字符串(例如Python脚本),这个问题就相对频繁,例如可以参考这里。
使用默认的工具调用解析器,OpenAI代理将无法解析这些工具调用,并尝试在下一步中修复工具调用。这需要另一个llm调用,这是缓慢且昂贵的。
本笔记本演示了如何定义一个自定义的工具调用解析器,可以处理某些类型的格式不正确的函数调用。 以下步骤是从OpenAI代理笔记本中复制的,同时添加了自定义的工具调用解析器。
初始设置¶
让我们从导入一些简单的构建模块开始。
我们主要需要以下内容:
- OpenAI API(使用我们自己的
llama_index
LLM 类) - 一个用于保存对话历史记录的地方
- 一个定义我们的代理可以使用的工具的定义。
如果您在colab上打开这个笔记本,您可能需要安装LlamaIndex 🦙。
In [ ]:
Copied!
%pip install llama-index-agent-openai
%pip install llama-index-llms-openai
%pip install llama-index-agent-openai
%pip install llama-index-llms-openai
In [ ]:
Copied!
!pip install llama-index
!pip install llama-index
In [ ]:
Copied!
import json
from llama_index.core.tools import FunctionTool
import nest_asyncio
nest_asyncio.apply()
import json
from llama_index.core.tools import FunctionTool
import nest_asyncio
nest_asyncio.apply()
让我们为我们的代理定义一些非常简单的计算器工具。
In [ ]:
Copied!
def multiply(a: int, b: int) -> int:
"""将两个整数相乘并返回结果整数"""
return a * b
multiply_tool = FunctionTool.from_defaults(fn=multiply)
def multiply(a: int, b: int) -> int:
"""将两个整数相乘并返回结果整数"""
return a * b
multiply_tool = FunctionTool.from_defaults(fn=multiply)
In [ ]:
Copied!
def add(a: int, b: int) -> int:
"""将两个整数相加并返回结果整数"""
return a + b
add_tool = FunctionTool.from_defaults(fn=add)
def add(a: int, b: int) -> int:
"""将两个整数相加并返回结果整数"""
return a + b
add_tool = FunctionTool.from_defaults(fn=add)
工具调用解析器的定义¶
有时,OpenAI工具调用并不是有效的json格式。
在定义自己的工具调用解析器时,您需要定义一个函数,该函数接受一个OpenAIToolCall并返回一个字典。该字典将作为**kwargs传递给工具函数。
如果解析器无法解析工具调用,则应引发ValueError。这将返回给代理程序,代理程序将在下一步尝试修复调用。
In [ ]:
Copied!
from typing import Dict
from llama_index.llms.openai.utils import OpenAIToolCall
import re
# 相同的解析器也可以从llama_index.agent.openai导入advanced_tool_call_parser
def custom_tool_call_parser(tool_call: OpenAIToolCall) -> Dict:
r"""解析不是标准json的工具调用。
还解析以下形式的工具调用:
variable = \"\"\"Some long text\"\"\"
variable = "Some long text"'
variable = '''Some long text'''
variable = 'Some long text'
"""
arguments_str = tool_call.function.arguments
if len(arguments_str.strip()) == 0:
# 对于不包含参数的函数,OpenAI返回空字符串
return {}
try:
tool_call = json.loads(arguments_str)
if not isinstance(tool_call, dict):
raise ValueError("工具调用必须是字典")
return tool_call
except json.JSONDecodeError as e:
# 匹配变量名和引号内的内容的模式
pattern = r'([a-zA-Z_][a-zA-Z_0-9]*)\s*=\s*["\']+(.*?)["\']+'
match = re.search(pattern, arguments_str)
if match:
variable_name = match.group(1) # 这是变量名
content = match.group(2) # 这是引号内的内容
return {variable_name: content}
raise ValueError(f"无效的工具调用: {e!s}")
from typing import Dict
from llama_index.llms.openai.utils import OpenAIToolCall
import re
# 相同的解析器也可以从llama_index.agent.openai导入advanced_tool_call_parser
def custom_tool_call_parser(tool_call: OpenAIToolCall) -> Dict:
r"""解析不是标准json的工具调用。
还解析以下形式的工具调用:
variable = \"\"\"Some long text\"\"\"
variable = "Some long text"'
variable = '''Some long text'''
variable = 'Some long text'
"""
arguments_str = tool_call.function.arguments
if len(arguments_str.strip()) == 0:
# 对于不包含参数的函数,OpenAI返回空字符串
return {}
try:
tool_call = json.loads(arguments_str)
if not isinstance(tool_call, dict):
raise ValueError("工具调用必须是字典")
return tool_call
except json.JSONDecodeError as e:
# 匹配变量名和引号内的内容的模式
pattern = r'([a-zA-Z_][a-zA-Z_0-9]*)\s*=\s*["\']+(.*?)["\']+'
match = re.search(pattern, arguments_str)
if match:
variable_name = match.group(1) # 这是变量名
content = match.group(2) # 这是引号内的内容
return {variable_name: content}
raise ValueError(f"无效的工具调用: {e!s}")
使用工具调用解析器定义OpenAI代理¶
在这个notebook中,我们将使用OpenAI Gym环境和一个工具调用解析器来定义一个简单的OpenAI代理。我们将使用一个简单的Q-learning算法来训练代理,以便它能够在给定环境中执行任务。
In [ ]:
Copied!
from llama_index.agent.openai import OpenAIAgent
from llama_index.llms.openai import OpenAI
from llama_index.agent.openai import OpenAIAgent
from llama_index.llms.openai import OpenAI
In [ ]:
Copied!
llm = OpenAI(model="gpt-3.5-turbo-0613")
agent = OpenAIAgent.from_tools(
[multiply_tool, add_tool],
llm=llm,
verbose=True,
tool_call_parser=custom_tool_call_parser,
)
llm = OpenAI(model="gpt-3.5-turbo-0613")
agent = OpenAIAgent.from_tools(
[multiply_tool, add_tool],
llm=llm,
verbose=True,
tool_call_parser=custom_tool_call_parser,
)
聊天¶
In [ ]:
Copied!
response = agent.chat("What is (121 * 3) + 42?")
print(str(response))
response = agent.chat("What is (121 * 3) + 42?")
print(str(response))
Added user message to memory: What is (121 * 3) + 42? === Calling Function === Calling function: multiply with args: { "a": 121, "b": 3 } Got output: 363 ======================== === Calling Function === Calling function: add with args: { "a": 363, "b": 42 } Got output: 405 ======================== (121 * 3) + 42 is equal to 405.
In [ ]:
Copied!
# 检查数据源
print(response.sources)
# 检查数据源
print(response.sources)
[ToolOutput(content='363', tool_name='multiply', raw_input={'args': (), 'kwargs': {'a': 121, 'b': 3}}, raw_output=363), ToolOutput(content='405', tool_name='add', raw_input={'args': (), 'kwargs': {'a': 363, 'b': 42}}, raw_output=405)]