LLM:函数调用(Function Calling)

1 函数调用

  虽然大模型能解决很多问题,但大模型并不能知晓一切。比如,大模型不知道最新消息(GPT-3.5 的知识截至 2021年9月,GPT-4 是 2023 年12月)。另外,大模型没有“真逻辑”。它表现出的逻辑、推理,是训练文本的统计规律,而不是真正的逻辑,所以有幻觉。所以大模型需要连接真实世界,并对接真逻辑系统。这就需要用到“函数调用”。
  函数调用(Function Calling)可以使LLM具有与外部API交互的能力。让用户能够使用高效的外部工具、与外部API进行交互。其使用机制如下:
在这里插入图片描述
关于function calling,有以下几点需要注意:

  • 在最新版本的OpenAI API中,可以使用tools参数对函数进行描述。并让大模型智能地选择输出包含函数参数的JSON对象来调用一个或多个函数。
  • 最新的GPT模型(gpt-3.5-turbo-0125 and gpt-4-turbo-preview)可以自动检测何时应该调用函数(还有一个相关的参数tool_choice,一般不用自己设置),还可以输出更加符合函数签名的JSON串。
  • GPT不负责调用和执行外部函数,需要用户自己完成。

2 使用GPT进行函数调用

  在使用GPT模型进行函数调用时,需要用到tools参数进行函数声明,关于该参数有以下几点需要说明:

2.1 使用函数调用完成加法计算

  大模型可以做加法是因为大模型记住了简单加法的统计规律,但大模型无法保证每次都能得到正确的加法计算结果。这里我们使用函数调用来完成加法计算。具体代码如下:

from openai import OpenAI
from dotenv import load_dotenv,find_dotenv
import json
from math import *

_=load_dotenv(find_dotenv())
client=OpenAI()

def get_completion(messages,model="gpt-3.5-turbo"):
    response=client.chat.completions.create(
        model=model,
        messages=messages,
        temperature=0.7,
        tools=[{
            "type":"function",
            "function":{
                "name":"sum",
                "description":"加法器,计算一组数的和",
                "parameters":{
                    "type":"object",
                    "properties":{
                        "numbers":{
                            "type":"array",
                            "items":{"type":"number"}
                        }
                    }
                }
            }
        }],
    )
    return response.choices[0].message

prompt="计算这些数据的和:345,2313,89,632."
messages=[
    {"role":"system","content":"你是一个数学家"},
    {"role":"user","content":prompt}
]
response=get_completion(messages)
print(response)
#GPT模型第一次的回复中有关于函数调用信息,包括GPT生成的函数调用的参数,所以这些信息需要返回给GPT模型。
messages.append(response)
if response.tool_calls is not None:
    tool_call=response.tool_calls[0]
    if tool_call.function.name=="sum":
        args=json.loads(tool_call.function.arguments)
        result=sum(args["numbers"])
        messages.append({
            "tool_call_id":tool_call.id,
            "role":"tool",
            "name":"sum",
            "content":str(result) 
        })
        print(get_completion(messages).content)

其结果如下:

ChatCompletionMessage(content=None, role=‘assistant’, function_call=None, tool_calls=[ChatCompletionMessageToolCall(id=‘call_vYramfrhZX7kLZLYhqDiFVHP’, function=Function(arguments=‘{“numbers”:[345,2313,89,632]}’, name=‘sum’), type=‘function’)])
这些数据的和是3379.

2.2 同时启动多个函数调用

借助上述加法函数的代码,可以一次启动同一个函数的多次调用,具体代码如下:

from openai import OpenAI 
import json
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())
client=OpenAI()
def run_conversation(prompt,model='gpt-3.5-turbo'):
    messages=[{"role":"user","content":prompt}]
    tools=[{ 
            "type": "function",
            "function": {
                "name": "sum",
                "description": "加法器,计算一组数的和",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "numbers": {"type": "array", "items": { "type": "number"}}
                    }
                }
            }
        }
    ]
    response=client.chat.completions.create(
        model=model,
        messages=messages,
        tools=tools,
        tool_choice="auto",
    )
    response_message=response.choices[0].message
    messages.append(response_message)
    print(response_message)
    tool_calls=response_message.tool_calls
    if tool_calls:
        for tool_call in tool_calls:
            function_name=tool_call.function.name
            function_args=json.loads(tool_call.function.arguments)
            function_response=sum(function_args.get("numbers"))
            messages.append(
                {"tool_call_id":tool_call.id,
                "role":"tool",
                "name":function_name,
                "content":str(function_response)}
            )
        second_response=client.chat.completions.create(
            model=model,
            messages=messages,
        )
        return second_response.choices[0].message.content
if __name__=="__main__":
    prompt="小明第一天买了5本书2个苹果,第二天买了3本书4个橘子,第三天买了7个梨和10本书,那么小明总共买了多个水果和多少本书?"
    print(prompt)
    print("====GPT回复====")
    print(run_conversation(prompt))

其执行结果如下:

小明第一天买了5本书2个苹果,第二天买了3本书4个橘子,第三天买了7个梨和10本书,那么小明总共买了多个水果和多少本书?
ChatCompletionMessage(content=None, role=‘assistant’, function_call=None, tool_calls=[ChatCompletionMessageToolCall(id=‘call_XB4SBFVfhMtyeo4zRu1lvpim’, function=Function(arguments=‘{“numbers”: [2, 4, 7]}’, name=‘sum’), type=‘function’), ChatCompletionMessageToolCall(id=‘call_d0B4e1j7Fhi1OPxxH9skJRAi’, function=Function(arguments=‘{“numbers”: [5, 3, 10]}’, name=‘sum’), type=‘function’)])
GPT回复: 小明总共买了13个水果和18本书。

关于这段代码,需要注意一点:

  • 这段代码中的模型使用的是gpt-3.5-turbo,更确切的说是最新的gpt-3.5-turbo-0125。OpenAI官方已经将gpt-3.5-turbo指向了gpt-3.5-turbo-0125。但如果使用的是国内代理的key的话,可能gpt-3.5-turbogpt-3.5-turbo-0125还是两个不同的模型,那运行上述代码时可能会遇到如下错误(输出second_response可以看到报错信息):

Invalid parameter: messages with role ‘tool’ must be a response to a preceeding message with ‘tool_calls’

2.3 同时定义多个函数调用

假设我们现在同时定义了加法和乘法的函数调用,让大模型自动完成加法和乘法的调用。具体代码如下:

from openai import OpenAI 
import json
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())
client=OpenAI()
def math_multiply(number,price):
    return number*price
def run_conversation(prompt,model='gpt-3.5-turbo-0125'):
    messages=[{"role":"user","content":prompt}]
    tools=[{ 
            "type": "function",
            "function": {
                "name": "sum",
                "description": "加法器,计算一组数的和",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "numbers": {"type": "array", "items": { "type": "number"}}
                    }
                }
            }
        },
           {"type":"function",
           "function":{
               "name":"multiply",
               "description":"乘法器,计算两个数的乘积",
               "parameters":{
                   "type":"object",
                   "properties":{
                      "price":{"type":"number","description":"一种物品的价格"},
                       "number":{"type":"integer","description":"一种物品的数量"},
                   },
                   "required":["price","number"],
               }
           }
           }
    ]
    response=client.chat.completions.create(
        model=model,
        messages=messages,
        tools=tools,
        tool_choice="auto",
    )
    response_message=response.choices[0].message
    available_function={
        "sum":sum,
        "multiply":math_multiply,
    }
    while response_message.tool_calls:
        print(response_message)
        messages.append(response_message)
        tool_calls=response_message.tool_calls
        for tool_call in tool_calls:
            function_name=tool_call.function.name
            function_args=json.loads(tool_call.function.arguments)
            function=available_function[function_name]
            if function==sum:
                function_response=function(function_args.get("numbers"))
            elif function==math_multiply:
                function_response=function(function_args.get('number'),function_args.get('price'))
            messages.append(
                {"tool_call_id":tool_call.id,
                "role":"tool",
                "name":function_name,
                "content":str(function_response)}
            )
        response_message=client.chat.completions.create(
            model=model,
            messages=messages,
            tools=tools,
        ).choices[0].message
    return response_message.content
if __name__=="__main__":
    prompt="小明第一天买了5本书和7个苹果,第二天买了3本书和2个苹果,第三天买了18本书和20个苹果,每一本书的价格是65元/本,每一个苹果的价格是7.5元/个,那么小明总共花了多少钱?"
    print(prompt)
    print("GPT回复:",run_conversation(prompt))

其执行结果如下(从输出内容可以知道,大模型先调用了两次加法运算完成书籍数量和水果数量的计算,接着调用两次乘法完成书本总价和水果总价的计算,最后调用加法完成总成本的计算。):

小明第一天买了5本书和7个苹果,第二天买了3本书和2个苹果,第三天买了18本书和20个苹果,每一本书的价格是65元/本,每一个苹果的价格是7.5元/个,那么小明总共花了多少钱?
ChatCompletionMessage(content=None, role=‘assistant’, function_call=None, tool_calls=[ChatCompletionMessageToolCall(id=‘call_xkGwaSApyRoTXmsNFhrtNQci’, function=Function(arguments=‘{“numbers”: [5, 3, 18]}’, name=‘sum’), type=‘function’), ChatCompletionMessageToolCall(id=‘call_DQEKyCWNJT2JysmqH25OGLRO’, function=Function(arguments=‘{“numbers”: [7, 2, 20]}’, name=‘sum’), type=‘function’), ChatCompletionMessageToolCall(id=‘call_5VlzZ8U5EhDixYnbwhh2Lljf’, function=Function(arguments=‘{“price”: 65, “number”: 26}’, name=‘multiply’), type=‘function’), ChatCompletionMessageToolCall(id=‘call_qN12sj2Ze7TvcuF0vzcwZH9H’, function=Function(arguments=‘{“price”: 7.5, “number”: 29}’, name=‘multiply’), type=‘function’)])
ChatCompletionMessage(content=None, role=‘assistant’, function_call=None, tool_calls=[ChatCompletionMessageToolCall(id=‘call_XIdn5N1lhcRy8vG0UodSMfO9’, function=Function(arguments=‘{“numbers”:[1690,217.5]}’, name=‘sum’), type=‘function’)])
GPT回复: 小明总共花了1907.5元。

最后需要注意一点,是否执行函数调用由大模型自己决定。

### 如何在代码中实现LLM函数调用 LLM函数调用的核心在于使模型能够理解并执行特定的功能,这些功能通常由外部服务或工具提供。以下是关于如何实现这一过程的具体方法。 #### 使用LangChain框架进行异步调用 为了简化复杂的LLM应用程序开发流程,`LangChain`框架提供了高级抽象支持,使得开发者可以通过简单的接口轻松管理多个LLM调用以及与其他工具的集成[^3]。下面是一个具体的Python代码示例: ```python from langchain.llms import OpenAI from langchain.agents import initialize_agent, Tool from langchain.tools import BaseTool class DataExtractor(BaseTool): name = "data_extractor" description = "Useful for extracting data from structured inputs" def _run(self, query: str): # 假设这里有一个数据提取逻辑 return f"Extracted {query}" llm = OpenAI(temperature=0) tools = [DataExtractor()] agent = initialize_agent(tools, llm, agent="zero-shot-react-description", verbose=True) response = agent.run("Please extract the user's information.") print(response) ``` 这段代码展示了如何创建自定义工具类 `DataExtractor` 并将其与 LangChain 集成在一起,从而实现了对结构化输入的数据提取操作。 #### 定义和调用具体函数 除了利用现有的库之外,还可以手动定义适合业务需求的函数,并通过适当的方式传递给LLM模型以便于其识别和调用。例如,在某些情况下可能需要定义像 `extract_data(name: string, birthday: string)` 这样的简单函数[^4]。以下是如何在一个假设场景下实现此类函数的一个例子: ```python def extract_data(name: str, birthday: str) -> dict: """A function to simulate extraction of personal details.""" return {"name": name, "birthday": birthday} # Assuming we have an LLM instance that supports function calls. functions = [ { "name": "extract_data", "description": "Extracts and returns a dictionary containing 'name' and 'birthday'.", "parameters": { "type": "object", "properties": { "name": {"type": "string"}, "birthday": {"type": "string"} }, "required": ["name", "birthday"] } } ] prompt = """ You are now able to call functions based on your understanding of the input text. Input Text: The person is named John Doe born on January 1st, 2000. Action: Call the appropriate function with parameters derived from the Input Text. """ function_call_result = some_llm_instance(prompt=prompt, functions=functions).execute() if isinstance(function_call_result, dict): print(f"The extracted data is as follows:\n{function_call_result}") else: raise ValueError("Unexpected result type received!") ``` 此部分演示了如何构建一个JSON Schema来描述参数期望值,并将之嵌入到提示语境当中去引导LLMs正确地解析信息并作出相应的动作响应。 #### Prompt格式优化技巧 最后值得注意的是,良好的Prompt设计对于提升LLM函数调用的成功率至关重要。研究表明精心调整后的Prompt样式可以帮助提高系统的整体表现效果[^5]。因此,在实际应用过程中应当重视这方面的工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值