函数调用#

工具是LLM可以用来与其内部知识之外的世界进行交互的手段。从技术上讲,检索器是帮助LLM获取更多相关上下文的工具,而记忆是LLM进行对话的工具。 决定何时、使用哪个工具以及如何使用工具,甚至创建工具,都是一种代理行为: 函数调用是一个向LLM展示一系列函数定义并提示其选择一个或几个的过程。 许多地方将工具和函数调用互换使用。

在本笔记中,我们将讨论函数调用,包括

  1. 函数调用演练

  2. 总体设计

  3. 函数调用示例

快速入门#

用户可能已经通过其API了解了OpenAI的函数调用功能(https://platform.openai.com/docs/guides/function-calling)。

def get_current_weather(location, unit="fahrenheit"):
 """Get the current weather in a given location"""
 import json

 if "tokyo" in location.lower():
     return json.dumps({"location": "Tokyo", "temperature": "10", "unit": unit})
 elif "san francisco" in location.lower():
     return json.dumps(
         {"location": "San Francisco", "temperature": "72", "unit": unit}
     )
 elif "paris" in location.lower():
     return json.dumps({"location": "Paris", "temperature": "22", "unit": unit})
 else:
     return json.dumps({"location": location, "temperature": "unknown"})

对于上述函数,它以以下格式显示给LLM:

function_definition = {
    "type": "function",
    "function": {
        "name": "get_current_weather",
        "description": "Get the current weather in a given location",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city and state, e.g. San Francisco, CA",
                },
                "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
            },
            "required": ["location"],
        },
    },
}

然后API将响应一个函数名称和参数的列表:

Function(arguments='{"location": "San Francisco, CA"}', name='get_current_weather')

然后需要将输出解析为参数,然后传递给函数:

function_name = tool_call.function.name
function_to_call = available_functions[function_name]
function_args = json.loads(tool_call.function.arguments)
function_response = function_to_call(
    location=function_args.get("location"),
    unit=function_args.get("unit"),
)

范围与设计#

即使使用API,用户也必须(1)创建函数定义,(2)解析响应,(3)执行函数。 使用API时缺少的是:(1)如何向LLM展示函数定义,(2)如何结构化输出格式。

AdalFlow 将提供内置功能,通过提示简化函数调用,而无需依赖工具 API。

设计目标

要求LLM使用关键字参数调用函数是实现函数调用的最简单方法。 但它有限制:

  1. 如果参数值是一个更复杂的数据结构怎么办?

  2. 如果你想使用一个变量作为参数怎么办?

AdalFlow 还将提供 FunctionExpression,其中调用函数是要求 LLM 直接编写函数调用的代码片段:

'get_current_weather("San Francisco, CA", unit="celsius")'

这不仅更加灵活,而且也是调用函数的一种更高效/紧凑的方式。

数据模型

我们有四个DataClass模型:FunctionDefinitionFunctionFunctionExpressionFunctionOutput来处理函数调用。

这些类不仅有助于数据结构化,而且作为DataClass的子类,它可以轻松地在提示中使用。 Function有三个重要的属性:nameargskwargs,分别用于函数名称、位置参数和关键字参数。 FunctionExpression只有一个用于函数调用表达式的操作。 两者都可以用于格式化提示中的输出。我们稍后将演示如何使用它。

组件

我们有两个组件:FunctionToolToolManager 来简化以下生命周期的流程:(1) 创建函数定义 (2) 使用定义和输出格式格式化提示 (3) 解析响应 (4) 执行函数。

FunctionTool 是一个单一函数的容器。它处理函数的定义和执行。它支持同步和异步函数。 ToolManager 管理所有工具。它处理执行和用于安全解析函数的 context_map。

ToolManager 是进行函数调用的简化方式。

属性/方法

描述

属性

tools

由ToolManager管理的工具列表。每个工具都是FunctionTool的实例或派生类。

context

一个结合工具定义和额外上下文的字典,用于执行函数表达式。

方法

__init__

使用工具和附加上下文初始化一个新的ToolManager实例。工具可以是FunctionTool或任何函数。

yaml_definitions

返回由ToolManager管理的所有工具的YAML定义。

json_definitions

返回由ToolManager管理的所有工具的JSON定义。

function_definitions

返回所有工具的函数定义列表。

parse_func_expr

解析一个FunctionExpression并返回一个准备执行的Function对象。

execute_func

执行给定的Function对象,并返回其输出,该输出包装在FunctionOutput中。支持同步和异步函数。

execute_func_expr

直接解析并执行一个FunctionExpression,将执行结果返回为FunctionOutput。支持同步和异步函数。

execute_func_expr_via_sandbox

通过沙箱执行函数表达式。仅支持同步函数。

execute_func_expr_via_eval

通过eval执行函数表达式。仅支持同步函数。

函数调用实战#

我们将在本说明中使用以下函数作为示例:

from dataclasses import dataclass
import numpy as np
import time
import asyncio


def multiply(a: int, b: int) -> int:
    """Multiply two numbers."""
    time.sleep(1)
    return a * b


def add(a: int, b: int) -> int:
    """Add two numbers."""
    time.sleep(1)
    return a + b


async def divide(a: float, b: float) -> float:
    """Divide two numbers."""
    await asyncio.sleep(1)
    return float(a) / b


async def search(query: str) -> List[str]:
    """Search for query and return a list of results."""
    await asyncio.sleep(1)
    return ["result1" + query, "result2" + query]


def numpy_sum(arr: np.ndarray) -> float:
    """Sum the elements of an array."""
    return np.sum(arr)


x = 2

@dataclass
class Point:
    x: int
    y: int


def add_points(p1: Point, p2: Point) -> Point:
    return Point(p1.x + p2.x, p1.y + p2.y)

我们特意涵盖了异步和同步,使用变量和更复杂的数据结构作为参数的示例。 我们将展示结构以及如何使用每个数据模型和组件以不同的方式调用上述函数。

1. 功能工具#

首先,让我们看看我们如何帮助向LLM描述函数。

以上述函数为例,如果用户没有传入,FunctionTool 会自动为每个函数生成 FunctionDefinition

from adalflow.core.func_tool import FunctionTool

functions =[multiply, add, divide, search, numpy_sum, add_points]
tools = [
    FunctionTool(fn=fn) for fn in functions
]
for tool in tools:
    print(tool)

打印输出显示了每个函数的三个属性:fn_is_asyncdefinition

FunctionTool(fn: <function multiply at 0x14d9d3f60>, async: False, definition: FunctionDefinition(func_name='multiply', func_desc='multiply(a: int, b: int) -> int\nMultiply two numbers.', func_parameters={'type': 'object', 'properties': {'a': {'type': 'int'}, 'b': {'type': 'int'}}, 'required': ['a', 'b']}))
FunctionTool(fn: <function add at 0x14d9e4040>, async: False, definition: FunctionDefinition(func_name='add', func_desc='add(a: int, b: int) -> int\nAdd two numbers.', func_parameters={'type': 'object', 'properties': {'a': {'type': 'int'}, 'b': {'type': 'int'}}, 'required': ['a', 'b']}))
FunctionTool(fn: <function divide at 0x14d9e40e0>, async: True, definition: FunctionDefinition(func_name='divide', func_desc='divide(a: float, b: float) -> float\nDivide two numbers.', func_parameters={'type': 'object', 'properties': {'a': {'type': 'float'}, 'b': {'type': 'float'}}, 'required': ['a', 'b']}))
FunctionTool(fn: <function search at 0x14d9e4180>, async: True, definition: FunctionDefinition(func_name='search', func_desc='search(query: str) -> List[str]\nSearch for query and return a list of results.', func_parameters={'type': 'object', 'properties': {'query': {'type': 'str'}}, 'required': ['query']}))
FunctionTool(fn: <function numpy_sum at 0x14d9e4220>, async: False, definition: FunctionDefinition(func_name='numpy_sum', func_desc='numpy_sum(arr: numpy.ndarray) -> float\nSum the elements of an array.', func_parameters={'type': 'object', 'properties': {'arr': {'type': 'ndarray'}}, 'required': ['arr']}))
FunctionTool(fn: <function add_points at 0x14d9e4360>, async: False, definition: FunctionDefinition(func_name='add_points', func_desc='add_points(p1: __main__.Point, p2: __main__.Point) -> __main__.Point\nNone', func_parameters={'type': 'object', 'properties': {'p1': {'type': 'Point', 'properties': {'x': {'type': 'int'}, 'y': {'type': 'int'}}, 'required': ['x', 'y']}, 'p2': {'type': 'Point', 'properties': {'x': {'type': 'int'}, 'y': {'type': 'int'}}, 'required': ['x', 'y']}}, 'required': ['p1', 'p2']}))

查看add_point的定义以及get_current_weather函数的字典格式:

print(tools[-2].definition.to_dict())

输出将是:

{
    "func_name": "numpy_sum",
    "func_desc": "numpy_sum(arr: numpy.ndarray) -> float\nSum the elements of an array.",
    "func_parameters": {
        "type": "object",
        "properties": {"arr": {"type": "ndarray"}},
        "required": ["arr"],
        },
}

使用 to_jsonto_yaml 可以直接获取可以输入到提示中的字符串。 我们更倾向于在这里使用 yaml 格式,因为它更节省令牌:

我们选择不仅用文档字符串来描述函数,即对数组的元素求和。,而且还用函数签名来描述,即numpy_sum(arr: numpy.ndarray) -> float。 这将为LLM提供代码级别的函数视图,并有助于函数调用。

注意

用户应更好地使用类型提示和良好的文档字符串,以帮助LLM更好地理解函数。

相比之下,这是我们对于get_current_weather的定义:

{
    "func_name": "get_current_weather",
    "func_desc": "get_current_weather(location, unit='fahrenheit')\nGet the current weather in a given location",
    "func_parameters": {
        "type": "object",
        "properties": {
            "location": {"type": "Any"},
            "unit": {"type": "Any", "default": "fahrenheit"},
        },
        "required": ["location"],
    },
}

要使用函数名称执行函数,我们需要管理一个函数映射。在这种情况下,我们使用FunctionTool而不是原始函数。

context_map = {tool.definition.func_name: tool for tool in tools}

要执行一个函数,我们可以这样做:

function_name = "add"
function_to_call = context_map[function_name]
function_args = {"a": 1, "b": 2}
function_response = function_to_call.call(**function_args)

如果我们使用异步函数,我们可以使用acallexecute是一个包装器,无论函数类型如何,您都可以以同步和异步方式调用函数。 查看API文档以获取更多详细信息。

2. 工具管理器#

在所有上述函数上使用ToolManager

from adalflow.core.tool_manager import ToolManager

tool_manager = ToolManager(tools=functions)
print(tool_manager)

工具管理器可以接受FunctionTool、函数和异步函数。 打印输出:

ToolManager(Tools: [FunctionTool(fn: <function multiply at 0x105e3b920>, async: False, definition: FunctionDefinition(func_name='multiply', func_desc='multiply(a: int, b: int) -> int\nMultiply two numbers.', func_parameters={'type': 'object', 'properties': {'a': {'type': 'int'}, 'b': {'type': 'int'}}, 'required': ['a', 'b']})), FunctionTool(fn: <function add at 0x105e3bc40>, async: False, definition: FunctionDefinition(func_name='add', func_desc='add(a: int, b: int) -> int\nAdd two numbers.', func_parameters={'type': 'object', 'properties': {'a': {'type': 'int'}, 'b': {'type': 'int'}}, 'required': ['a', 'b']})), FunctionTool(fn: <function divide at 0x104970220>, async: True, definition: FunctionDefinition(func_name='divide', func_desc='divide(a: float, b: float) -> float\nDivide two numbers.', func_parameters={'type': 'object', 'properties': {'a': {'type': 'float'}, 'b': {'type': 'float'}}, 'required': ['a', 'b']})), FunctionTool(fn: <function search at 0x104970400>, async: True, definition: FunctionDefinition(func_name='search', func_desc='search(query: str) -> List[str]\nSearch for query and return a list of results.', func_parameters={'type': 'object', 'properties': {'query': {'type': 'str'}}, 'required': ['query']})), FunctionTool(fn: <function numpy_sum at 0x1062a2840>, async: False, definition: FunctionDefinition(func_name='numpy_sum', func_desc='numpy_sum(arr: numpy.ndarray) -> float\nSum the elements of an array.', func_parameters={'type': 'object', 'properties': {'arr': {'type': 'ndarray'}}, 'required': ['arr']})), FunctionTool(fn: <function add_points at 0x106d691c0>, async: False, definition: FunctionDefinition(func_name='add_points', func_desc='add_points(p1: __main__.Point, p2: __main__.Point) -> __main__.Point\nNone', func_parameters={'type': 'object', 'properties': {'p1': {'type': 'Point', 'properties': {'x': {'type': 'int'}, 'y': {'type': 'int'}}, 'required': ['x', 'y']}, 'p2': {'type': 'Point', 'properties': {'x': {'type': 'int'}, 'y': {'type': 'int'}}, 'required': ['x', 'y']}}, 'required': ['p1', 'p2']}))], Additional Context: {})

我们将在下一节中展示更多如何使用它。

3. 函数调用端到端#

现在,让我们添加提示并开始通过LLMs进行函数调用。 我们使用以下提示来进行单个函数调用。

template = r"""<SYS>You have these tools available:
{% if tools %}
<TOOLS>
{% for tool in tools %}
{{ loop.index }}.
{{tool}}
------------------------
{% endfor %}
</TOOLS>
{% endif %}
<OUTPUT_FORMAT>
{{output_format_str}}
</OUTPUT_FORMAT>
</SYS>
User: {{input_str}}
You:
"""

在提示中传递工具

我们在这里使用yaml格式,并展示一个使用较少工具的示例。

from adalflow.core.prompt_builder import Prompt

prompt = Prompt(template=template)
small_tool_manager = ToolManager(tools=tools[:2])

renered_prompt = prompt(tools=small_tool_manager.yaml_definitions)
print(renered_prompt)

输出是:

<SYS>You have these tools available:
<TOOLS>
1.
func_name: multiply
func_desc: 'multiply(a: int, b: int) -> int

Multiply two numbers.'
func_parameters:
type: object
properties:
    a:
    type: int
    b:
    type: int
required:
- a
- b

------------------------
2.
func_name: add
func_desc: 'add(a: int, b: int) -> int

Add two numbers.'
func_parameters:
type: object
properties:
    a:
    type: int
    b:
    type: int
required:
- a
- b

------------------------
</TOOLS>
<OUTPUT_FORMAT>
None
</OUTPUT_FORMAT>
</SYS>
User: None
You:

传递输出格式

我们有两种方法来指示LLM调用函数:

  1. 使用函数名称和参数,我们将利用Function作为LLM的输出数据类型。

from adalflow.core.types import Function

output_data_class = Function
output_format_str = output_data_class.to_json_signature(exclude=["thought", "args"])

renered_prompt= prompt(output_format_str=output_format_str)
print(renered_prompt)

我们排除了thoughtargs,因为使用kwargs来表示参数更为方便。 输出为:

<SYS>You have these tools available:
<OUTPUT_FORMAT>
{
    "name": "The name of the function (str) (optional)",
    "kwargs": "The keyword arguments of the function (Optional) (optional)"
}
</OUTPUT_FORMAT>
</SYS>
User: None
You:
  1. 使用我们将使用FunctionExpression的函数调用表达式。

from adalflow.core.types import FunctionExpression

output_data_class = FunctionExpression
output_format_str = output_data_class.to_json_signature(exclude=["thought"])
print(prompt(output_format_str=output_format_str))

输出是:

<SYS>You have these tools available:
<OUTPUT_FORMAT>
{
    "action": "FuncName(<kwargs>)                 Valid function call expression.                 Example: \"FuncName(a=1, b=2)\"                 Follow the data type specified in the function parameters.                e.g. for Type object with x,y properties, use \"ObjectType(x=1, y=2) (str) (required)"
}
</OUTPUT_FORMAT>
</SYS>
User: None
You:

我们将使用components.output_parsers.outputs.JsonOutputParser来简化输出数据类型的格式化。

from adalflow.components.output_parsers import JsonOutputParser

func_parser = JsonOutputParser(data_class=Function)
instructions = func_parser.format_instructions(exclude=["thought", "args"])
print(instructions)

输出是:

Your output should be formatted as a standard JSON instance with the following schema:
```
{
    "name": "The name of the function (str) (optional)",
    "kwargs": "The keyword arguments of the function (Optional) (optional)"
}
```
-Make sure to always enclose the JSON output in triple backticks (```). Please do not add anything other than valid JSON output!
-Use double quotes for the keys and string values.
-Follow the JSON formatting conventions.

函数输出格式#

现在,让我们使用上述提示、Function 数据类和 JsonOutputParser 来准备我们的生成器。

from adalflow.core.generator import Generator
from adalflow.core.types import ModelClientType

model_kwargs = {"model": "gpt-3.5-turbo"}
prompt_kwargs = {
    "tools": tool_manager.yaml_definitions,
    "output_format_str": func_parser.format_instructions(
        exclude=["thought", "args"]
    ),
}
generator = Generator(
    model_client=ModelClientType.OPENAI(),
    model_kwargs=model_kwargs,
    template=template,
    prompt_kwargs=prompt_kwargs,
    output_processors=func_parser,
)

运行查询

我们将使用Function.from_dict从json对象中获取最终输出类型。这里我们使用core.tool_manager.ToolManager.execute_func()直接执行它。

queries = [
    "add 2 and 3",
    "search for something",
    "add points (1, 2) and (3, 4)",
    "sum numpy array with arr = np.array([[1, 2], [3, 4]])",
    "multiply 2 with local variable x",
    "divide 2 by 3",
    "Add 5 to variable y",
]

for idx, query in enumerate(queries):
    prompt_kwargs = {"input_str": query}
    print(f"\n{idx} Query: {query}")
    print(f"{'-'*50}")
    try:
        result = generator(prompt_kwargs=prompt_kwargs)
        # print(f"LLM raw output: {result.raw_response}")
        func = Function.from_dict(result.data)
        print(f"Function: {func}")
        func_output = tool_manager.execute_func(func)
        print(f"Function output: {func_output}")
    except Exception as e:
        print(
            f"Failed to execute the function for query: {query}, func: {result.data}, error: {e}"
        )

从下面显示的输出中,我们得到了所有查询的有效Function解析为输出。 然而,我们看到它失败了三个函数执行: (1)函数add_points由于其参数类型是一个数据类,以及multiply和最后一个add由于难以表示函数调用中的局部变量xy

0 Query: add 2 and 3
--------------------------------------------------
Function: Function(thought=None, name='add', args=[], kwargs={'a': 2, 'b': 3})
Function output: FunctionOutput(name='add', input=Function(thought=None, name='add', args=(), kwargs={'a': 2, 'b': 3}), parsed_input=None, output=5, error=None)

1 Query: search for something
--------------------------------------------------
Function: Function(thought=None, name='search', args=[], kwargs={'query': 'something'})
Function output: FunctionOutput(name='search', input=Function(thought=None, name='search', args=(), kwargs={'query': 'something'}), parsed_input=None, output=['result1something', 'result2something'], error=None)

2 Query: add points (1, 2) and (3, 4)
--------------------------------------------------
Function: Function(thought=None, name='add_points', args=[], kwargs={'p1': {'x': 1, 'y': 2}, 'p2': {'x': 3, 'y': 4}})
Error at calling <function add_points at 0x117b98360>: 'dict' object has no attribute 'x'
Function output: FunctionOutput(name='add_points', input=Function(thought=None, name='add_points', args=(), kwargs={'p1': {'x': 1, 'y': 2}, 'p2': {'x': 3, 'y': 4}}), parsed_input=None, output=None, error="'dict' object has no attribute 'x'")

3 Query: sum numpy array with arr = np.array([[1, 2], [3, 4]])
--------------------------------------------------
Function: Function(thought=None, name='numpy_sum', args=[], kwargs={'arr': [[1, 2], [3, 4]]})
Function output: FunctionOutput(name='numpy_sum', input=Function(thought=None, name='numpy_sum', args=(), kwargs={'arr': [[1, 2], [3, 4]]}), parsed_input=None, output=10, error=None)

4 Query: multiply 2 with local variable x
--------------------------------------------------
Function: Function(thought=None, name='multiply', args=[], kwargs={'a': 2, 'b': 'x'})
Function output: FunctionOutput(name='multiply', input=Function(thought=None, name='multiply', args=(), kwargs={'a': 2, 'b': 'x'}), parsed_input=None, output='xx', error=None)

5 Query: divide 2 by 3
--------------------------------------------------
Function: Function(thought=None, name='divide', args=[], kwargs={'a': 2.0, 'b': 3.0})
Function output: FunctionOutput(name='divide', input=Function(thought=None, name='divide', args=(), kwargs={'a': 2.0, 'b': 3.0}), parsed_input=None, output=0.6666666666666666, error=None)

6 Query: Add 5 to variable y
--------------------------------------------------
Function: Function(thought=None, name='add', args=[], kwargs={'a': 5, 'b': 'y'})
Error at calling <function add at 0x11742eca0>: unsupported operand type(s) for +: 'int' and 'str'
Function output: FunctionOutput(name='add', input=Function(thought=None, name='add', args=(), kwargs={'a': 5, 'b': 'y'}), parsed_input=None, output=None, error="unsupported operand type(s) for +: 'int' and 'str'")

注意

如果用户更喜欢使用Function,为了提高成功率,请确保您的函数参数是基于字典的类对象。您始终可以将字典转换为类。

函数表达式输出格式#

我们将使用工具管理器轻松调整上述代码,以使用FunctionExpression作为输出格式。 这次我们将在解析器中使用FunctionExpression。并且我们添加了必要的上下文来处理局部变量xynp.array

tool_manager = ToolManager(
    tools=functions,
    additional_context={"x": x, "y": 0, "np.array": np.array, "np": np},
)
func_parser = JsonOutputParser(data_class=FunctionExpression)

此外,我们还可以在之后使用以下提示将additional_context传递给LLM。

context = r"""<CONTEXT>
Your function expression also have access to these context:
{{context_str}}
</CONTEXT>
"""

这次,让我们尝试同时执行所有函数,并将它们全部视为异步函数。

async def run_async_function_call(self, generator, tool_manager):
    answers = []
    start_time = time.time()
    tasks = []
    for idx, query in enumerate(queries):
        tasks.append(self.process_query(idx, query, generator, tool_manager))

    results = await asyncio.gather(*tasks)
    answers.extend(results)
    end_time = time.time()
    print(f"Total time taken: {end_time - start_time :.2f} seconds")
    return answers

async def process_query(self, idx, query, generator, tool_manager: ToolManager):
    print(f"\n{idx} Query: {query}")
    print(f"{'-'*50}")
    try:
        result = generator(prompt_kwargs={"input_str": query})
        func_expr = FunctionExpression.from_dict(result.data)
        print(f"Function_expr: {func_expr}")
        func = tool_manager.parse_func_expr(func_expr)
        func_output = await tool_manager.execute_func_async(func)
        print(f"Function output: {func_output}")
        return func_output
    except Exception as e:
        print(
            f"Failed to execute the function for query: {query}, func: {result.data}, error: {e}"
        )
        return None

在这种情况下,我们使用了core.tool_manager.ToolManager.parse_func_expr()core.tool_manager.ToolManager.execute_func()来执行函数。 或者我们可以直接使用core.tool_manager.ToolManager.execute_func_expr()来执行函数表达式。两者是等价的。

从下面显示的输出中,这次我们得到了所有函数调用都成功执行的结果。

0 Query: add 2 and 3
--------------------------------------------------
Function_expr: FunctionExpression(thought=None, action='add(a=2, b=3)')

1 Query: search for something
--------------------------------------------------
Function_expr: FunctionExpression(thought=None, action='search(query="something")')

2 Query: add points (1, 2) and (3, 4)
--------------------------------------------------
Function_expr: FunctionExpression(thought=None, action='add_points(p1=Point(x=1, y=2), p2=Point(x=3, y=4))')

3 Query: sum numpy array with arr = np.array([[1, 2], [3, 4]])
--------------------------------------------------
Function_expr: FunctionExpression(thought=None, action='numpy_sum(arr=np.array([[1, 2], [3, 4]]))')

4 Query: multiply 2 with local variable x
--------------------------------------------------
Function_expr: FunctionExpression(thought=None, action='multiply(a=2, b=2)')

5 Query: divide 2 by 3
--------------------------------------------------
Function_expr: FunctionExpression(thought=None, action='divide(a=2.0, b=3.0)')

6 Query: Add 5 to variable y
--------------------------------------------------
Function_expr: FunctionExpression(thought=None, action='add(a=0, b=5)')
Function output: FunctionOutput(name='add_points', input=Function(thought=None, name='add_points', args=(), kwargs={'p1': Point(x=1, y=2), 'p2': Point(x=3, y=4)}), parsed_input=None, output=Point(x=4, y=6), error=None)
Function output: FunctionOutput(name='numpy_sum', input=Function(thought=None, name='numpy_sum', args=(), kwargs={'arr': array([[1, 2],
    [3, 4]])}), parsed_input=None, output=10, error=None)
Function output: FunctionOutput(name='add', input=Function(thought=None, name='add', args=(), kwargs={'a': 2, 'b': 3}), parsed_input=None, output=5, error=None)
Function output: FunctionOutput(name='multiply', input=Function(thought=None, name='multiply', args=(), kwargs={'a': 2, 'b': 2}), parsed_input=None, output=4, error=None)
Function output: FunctionOutput(name='search', input=Function(thought=None, name='search', args=(), kwargs={'query': 'something'}), parsed_input=None, output=['result1something', 'result2something'], error=None)
Function output: FunctionOutput(name='divide', input=Function(thought=None, name='divide', args=(), kwargs={'a': 2.0, 'b': 3.0}), parsed_input=None, output=0.6666666666666666, error=None)
Function output: FunctionOutput(name='add', input=Function(thought=None, name='add', args=(), kwargs={'a': 0, 'b': 5}), parsed_input=None, output=5, error=None)

并行函数调用#

我们将稍微调整输出格式指令,使其输出json数组,这仍然可以使用json解析器进行解析。

multple_function_call_template = r"""<SYS>You have these tools available:
{% if tools %}
<TOOLS>
{% for tool in tools %}
{{ loop.index }}.
{{tool}}
------------------------
{% endfor %}
</TOOLS>
{% endif %}
<OUTPUT_FORMAT>
Here is how you call one function.
{{output_format_str}}
-Always return a List using `[]` of the above JSON objects, even if its just one item.
</OUTPUT_FORMAT>
<SYS>
{{input_str}}
You:
"""

由于LLM在调用add_point时存在问题,我们将添加一个示例,并使用core.types.FunctionExpression.from_function()生成它。 我们将更新我们的输出解析器以使用该示例:

example = FunctionExpression.from_function(
        func=add_points, p1=Point(x=1, y=2), p2=Point(x=3, y=4)
)
func_parser = JsonOutputParser(
        data_class=FunctionExpression, examples=[example]
)

以下是提示中更新的输出格式:

<OUTPUT_FORMAT>
Here is how you call one function.
Your output should be formatted as a standard JSON instance with the following schema:
```
{
    "action": "FuncName(<kwargs>)                 Valid function call expression.                 Example: \"FuncName(a=1, b=2)\"                 Follow the data type specified in the function parameters.                e.g. for Type object with x,y properties, use \"ObjectType(x=1, y=2) (str) (required)"
}
```
Here is an example:
```
{
    "action": "add_points(p1=Point(x=1, y=2), p2=Point(x=3, y=4))"
}
```
-Make sure to always enclose the JSON output in triple backticks (```). Please do not add anything other than valid JSON output!
-Use double quotes for the keys and string values.
-Follow the JSON formatting conventions.
Awlays return a List using `[]` of the above JSON objects. You can have length of 1 or more.
Do not call multiple functions in one action field.
</OUTPUT_FORMAT>

在这个案例中,我们将展示使用execute_func_expr_via_sandbox执行函数表达式的响应。

for idx in range(0, len(queries), 2):
    query = " and ".join(queries[idx : idx + 2])
    prompt_kwargs = {"input_str": query}
    print(f"\n{idx} Query: {query}")
    print(f"{'-'*50}")
    try:
        result = generator(prompt_kwargs=prompt_kwargs)
        # print(f"LLM raw output: {result.raw_response}")
        func_expr: List[FunctionExpression] = [
            FunctionExpression.from_dict(item) for item in result.data
        ]
        print(f"Function_expr: {func_expr}")
        for expr in func_expr:
            func_output = tool_manager.execute_func_expr_via_sandbox(expr)
            print(f"Function output: {func_output}")
    except Exception as e:
        print(
            f"Failed to execute the function for query: {query}, func: {result.data}, error: {e}"
        )

通过使用一个例子来帮助调用add_point,我们现在可以成功执行所有函数调用。

0 Query: add 2 and 3 and search for something
--------------------------------------------------
Function_expr: [FunctionExpression(thought=None, action='add(a=2, b=3)'), FunctionExpression(thought=None, action='search(query="something")')]
Function output: FunctionOutput(name='add(a=2, b=3)', input=FunctionExpression(thought=None, action='add(a=2, b=3)'), parsed_input=None, output=FunctionOutput(name='add', input=Function(thought=None, name='add', args=(), kwargs={'a': 2, 'b': 3}), parsed_input=None, output=5, error=None), error=None)
Function output: FunctionOutput(name='search(query="something")', input=FunctionExpression(thought=None, action='search(query="something")'), parsed_input=None, output=FunctionOutput(name='search', input=Function(thought=None, name='search', args=(), kwargs={'query': 'something'}), parsed_input=None, output=['result1something', 'result2something'], error=None), error=None)

2 Query: add points (1, 2) and (3, 4) and sum numpy array with arr = np.array([[1, 2], [3, 4]])
--------------------------------------------------
Function_expr: [FunctionExpression(thought=None, action='add_points(p1=Point(x=1, y=2), p2=Point(x=3, y=4))'), FunctionExpression(thought=None, action='numpy_sum(arr=[[1, 2], [3, 4]])')]
Function output: FunctionOutput(name='add_points(p1=Point(x=1, y=2), p2=Point(x=3, y=4))', input=FunctionExpression(thought=None, action='add_points(p1=Point(x=1, y=2), p2=Point(x=3, y=4))'), parsed_input=None, output=FunctionOutput(name='add_points', input=Function(thought=None, name='add_points', args=(), kwargs={'p1': Point(x=1, y=2), 'p2': Point(x=3, y=4)}), parsed_input=None, output=Point(x=4, y=6), error=None), error=None)
Function output: FunctionOutput(name='numpy_sum(arr=[[1, 2], [3, 4]])', input=FunctionExpression(thought=None, action='numpy_sum(arr=[[1, 2], [3, 4]])'), parsed_input=None, output=FunctionOutput(name='numpy_sum', input=Function(thought=None, name='numpy_sum', args=(), kwargs={'arr': [[1, 2], [3, 4]]}), parsed_input=None, output=10, error=None), error=None)

4 Query: multiply 2 with local variable x and divide 2 by 3
--------------------------------------------------
Function_expr: [FunctionExpression(thought=None, action='multiply(a=2, b=x)'), FunctionExpression(thought=None, action='divide(a=2.0, b=3.0)')]
Function output: FunctionOutput(name='multiply(a=2, b=x)', input=FunctionExpression(thought=None, action='multiply(a=2, b=x)'), parsed_input=None, output=FunctionOutput(name='multiply', input=Function(thought=None, name='multiply', args=(), kwargs={'a': 2, 'b': 2}), parsed_input=None, output=4, error=None), error=None)
Function output: FunctionOutput(name='divide(a=2.0, b=3.0)', input=FunctionExpression(thought=None, action='divide(a=2.0, b=3.0)'), parsed_input=None, output=FunctionOutput(name='divide', input=Function(thought=None, name='divide', args=(), kwargs={'a': 2.0, 'b': 3.0}), parsed_input=None, output=0.6666666666666666, error=None), error=None)

6 Query: Add 5 to variable y
--------------------------------------------------
Function_expr: [FunctionExpression(thought=None, action='add(a=y, b=5)')]
Function output: FunctionOutput(name='add(a=y, b=5)', input=FunctionExpression(thought=None, action='add(a=y, b=5)'), parsed_input=None, output=FunctionOutput(name='add', input=Function(thought=None, name='add', args=(), kwargs={'a': 0, 'b': 5}), parsed_input=None, output=5, error=None), error=None)

参考文献

  1. OpenAI 工具 API: https://beta.openai.com/docs/api-reference/tools