众所周知,LLM的函数function-calling能力很强悍,解决了大模型与实际业务系统的交互问题。其本质就是函数调用。

从openai官网摘图:

简而言之:

  1. LLM起到决策的作用,告知业务系统应该调用什么函数,以及入参是什么。

  2. 业务系统负责实现对应的函数(比如本地实现,或者调用其他系统提供的服务),并且将函数的响应结果再次抛给LLM。

  3. LLM根据响应结果,组织自然语言,继续与业务系统进行交互。

在这里,有很多小伙伴会有一个误区:误以为函数调用是有LLM本身执行的。其实,LLM仅仅做决策,而实际的调用是由业务系统完成的。

现阶段,function-calling能力的实现有两种主流方式:

  1. LLM本身支持。

  2. 利用Prompt模板实现,典型如ReAct模板。

在实际的应用过程中,我们还要解决另一个重要问题:

function-calling触发机制是怎样的?也即:何时要使用function-calling能力,何时不应该使用?

这个问题的处理方式,对于整体流程的运行至关重要。

此时,我们可以使用特定Prompt来解决该问题:

You have access to the following tools:
{json.dumps(tools)}
You can select one of the above tools or just response user's content and respond with only a JSON object matching the following schema:
{{
  "tool": <name of the selected tool>,
  "tool_input": <parameters for the selected tool, matching the tool'
s JSON schema>,
  "message": <direct response users content>}

该Prompt告知了LLM:如果需要使用function-calling能力,那么就从tools(tools是预定义的functions)中选取一个最匹配的函数;如果不需要,就用自然语言与用户交互,此时与正常的对话流程无异。输出的格式固定为json,方便解析。

由此,我们受到启发:只要LLM基座够强(能够严格遵循Prompt响应诉求),即使LLM本身不支持function-calling,我们也可以自己实现function-calling,脱离对特定LLM的依赖!

拿到function-calling的结果后,若要用自然语言的形式输出结果,还要再调用一次LLM,对结果进行整合。此时可以使用另一个Prompt:

Please generate a natural language description based on the following question and answer.
Question: [Content of the question]
Answer: [Content of the answer]
Generated Description: The result of [key phrase from the question] is [answer].
If necessary, you can polish the description.Only output the Description, with Chinese language.

该Prompt的作用就是告诉LLM,你要根据我的问题和答案,用自然语言重新描述一遍。这里指定了中文输出,可根据实际需要进行调整。

以下是一个可运行的完整Python脚本:

import requests
import json
import random

# 预置函数定义
tools = [
    {
        "name": "get_current_weather",
        "description": "Get the current weather in a given location",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city e.g. Beijing"
                },
                "unit": {
                    "type": "string",
                    "enum": [
                        "celsius"
                    ]
                }
            },
            "required": [
                "location"
            ]
        }
    },
    {
        "name": "calculator",
        "description": "计算器",
        "parameters": {
            "type": "int",
            "properties": {
                "a": {
                    "type": "int",
                    "description": "the first number"
                },
                "b": {
                    "type": "int",
                    "description": "the second number"
                }
            },
            "required": [
                "a",
                "b"
            ]
        }
    }
]

# 获取天气(随机返回,实际使用可以替换为api调用)
def get_current_weather(*args):
    # 定义可能的天气状态
    weather_conditions = ["sunny", "cloudy", "rainy", "snowy"]
    # 定义可能的温度范围
    temperature_min = -10  # 最低温度,摄氏度
    temperature_max = 35  # 最高温度,摄氏度
    # 随机选择一个天气状态
    condition = random.choice(weather_conditions)
    # 随机生成一个温度
    temperature = random.randint(temperature_min, temperature_max)
    # 返回一个描述当前天气的字符串
    return f"The weather of {args[0].get('location')} is {condition}, and the temperature is {temperature}°C."

def calculator(args):
    return sum(value for value in args.values() if isinstance(value, int))

# 函数映射集合
functions = {
    "get_current_weather": get_current_weather,
    "calculator": calculator,
}

# 驱动整体流程的入口prompt
entrance_prompt = f"""You have access to the following tools:
{json.dumps(tools)}
You can select one of the above tools or just response user's content and respond with only a JSON object matching the following schema:
{{
  "tool": <name of the selected tool>,
  "tool_input": <parameters for the selected tool, matching the tool's JSON schema>,
  "message": <direct response users content>
}}"""

# 请以自然语言的形式对结果进行描述
conformity_prompt = f"""
Please generate a natural language description based on the following question and answer.
Question: [Content of the question]
Answer: [Content of the answer]
Generated Description: The result of [key phrase from the question] is [answer].
If necessary, you can polish the description.
Only output the Description, with Chinese language.
"""

def extract_json(s):
    stack = 0
    start = s.find('{')
    if start == -1:
        return None

    for i in range(start, len(s)):
        if s[i] == '{':
            stack += 1
        elif s[i] == '}':
            stack -= 1
            if stack == 0:
                return s[start:i + 1]
    return None

# 结果包装器,type为func表示是函数调用返回的结果,default表示是自然语言结果。对于func返回的结果,会用LLM再次总结
class ResultWrapper:
    def __init__(self, type, result):
        self.type = type
        self.result = result

# 解析LLM返回的结果,如果有json则去解析json
def parse_result(res):
    json_str = extract_json(res["message"]["content"])
    if json_str is not None:
        obj = json.loads(json_str)
        if "tool" in obj:
            if obj["tool"] in functions:
                fun = functions[obj["tool"]]
                return ResultWrapper("func", fun(obj["tool_input"]))
            else:
                return ResultWrapper("default", obj["message"])
        else:
            return ResultWrapper("default", res["message"]["content"])
    else:
        return ResultWrapper("default", res["message"]["content"])

def invokeLLM(messages):
    url = "${domain}/v1/chat/completions" #需替换域名
    model = ""
    payload = {
        "model": model,
        "messages": messages,
    }
    payload = json.dumps(payload)
    headers = {
        'Content-Type': 'application/json'
    }
    print("PAYLOAD: ", payload)
    response = requests.request("POST", url, headers=headers, data=payload)
    print("RESPONSE: ", response.text)
    print("=======================================================================")
    resp = json.loads(response.text)
    return resp["choices"][0]

if __name__ == '__main__':
    while True:
        messages = [
            {
                "role": "system",
                "content": entrance_prompt
            }
        ]
        user_input = input('Enter a string: ')
        messages.append({
            "role": "user",
            "content": user_input
        })
        result_wrapper = parse_result(invokeLLM(messages))
        if result_wrapper.type == "func":
            messages = [
                {
                    "role": "user",
                    "content": f"{conformity_prompt}\n\nThe question:{user_input}\nThe answer:{result_wrapper.result}"
                }
            ]
            print("FINAL RESULT WITH FUNCTION CALL: ", parse_result(invokeLLM(messages)).result)
        else:            print("FINAL RESULT: ", result_wrapper.result

实验效果:

Enter a string: 你好
PAYLOAD: {"model": "", "messages": [{"role": "system", "content": "You have access to the following tools:\n[{\"name\": \"get_current_weather\", \"description\": \"Get the current weather in a given location\", \"parameters\": {\"type\": \"object\", \"properties\": {\"location\": {\"type\": \"string\", \"description\": \"The city e.g. Beijing\"}, \"unit\": {\"type\": \"string\", \"enum\": [\"celsius\"]}}, \"required\": [\"location\"]}}, {\"name\": \"calculator\", \"description\": \"\\u8ba1\\u7b97\\u5668\", \"parameters\": {\"type\": \"int\", \"properties\": {\"a\": {\"type\": \"int\", \"description\": \"the first number\"}, \"b\": {\"type\": \"int\", \"description\": \"the second number\"}}, \"required\": [\"a\", \"b\"]}}]\nYou can select one of the above tools or just response user's content and respond with only a JSON object matching the following schema:\n{\n \"tool\": <name of the selected tool>,\n \"tool_input\": <parameters for the selected tool, matching the tool's JSON schema>,\n \"message\": <direct response users content>\n}"}, {"role": "user", "content": "\u4f60\u597d"}]}
RESPONSE: {"model":"","object":"","choices":[{"index":0,"message":{"role":"assistant","content":"```json\n{\"tool\": null, \"tool_input\": null, \"message\": \"你好,有什么可以帮您的吗?\"}\n```","function_call":null},"finish_reason":"stop"}],"queueTime":0.0020923614501953125,"costTime":0.7685532569885254,"usage":{"prompt_token":244,"completion_token":29,"total_tokens":273}}
=======================================================================
FINAL RESULT: 你好,有什么可以帮您的吗?

Enter a string: 厦门天气如何?
PAYLOAD: {"model": "", "messages": [{"role": "system", "content": "You have access to the following tools:\n[{\"name\": \"get_current_weather\", \"description\": \"Get the current weather in a given location\", \"parameters\": {\"type\": \"object\", \"properties\": {\"location\": {\"type\": \"string\", \"description\": \"The city e.g. Beijing\"}, \"unit\": {\"type\": \"string\", \"enum\": [\"celsius\"]}}, \"required\": [\"location\"]}}, {\"name\": \"calculator\", \"description\": \"\\u8ba1\\u7b97\\u5668\", \"parameters\": {\"type\": \"int\", \"properties\": {\"a\": {\"type\": \"int\", \"description\": \"the first number\"}, \"b\": {\"type\": \"int\", \"description\": \"the second number\"}}, \"required\": [\"a\", \"b\"]}}]\nYou can select one of the above tools or just response user's content and respond with only a JSON object matching the following schema:\n{\n \"tool\": <name of the selected tool>,\n \"tool_input\": <parameters for the selected tool, matching the tool's JSON schema>,\n \"message\": <direct response users content>\n}"}, {"role": "user", "content": "\u53a6\u95e8\u5929\u6c14\u5982\u4f55\uff1f"}]}
RESPONSE: {"model":"","object":"","choices":[{"index":0,"message":{"role":"assistant","content":"```json\n{\"tool\": \"get_current_weather\", \"tool_input\": {\"location\": \"Xiamen\", \"unit\": \"celsius\"}, \"message\": \"\"}\n```","function_call":null},"finish_reason":"stop"}],"queueTime":0.0021338462829589844,"costTime":0.9370713233947754,"usage":{"prompt_token":247,"completion_token":36,"total_tokens":283}}
=======================================================================
PAYLOAD: {"model": "", "messages": [{"role": "user", "content": "\nPlease generate a natural language description based on the following question and answer.\nQuestion: [Content of the question]\nAnswer: [Content of the answer]\nGenerated Description: The result of [key phrase from the question] is [answer].\nIf necessary, you can polish the description.\nOnly output the Description, with Chinese language.\n\n\nThe question:\u53a6\u95e8\u5929\u6c14\u5982\u4f55\uff1f\nThe answer:The weather of Xiamen is cloudy, and the temperature is 35\u00b0C."}]}
RESPONSE: {"model":"","object":"","choices":[{"index":0,"message":{"role":"assistant","content":"厦门天气情况是:多云,气温35°C。","function_call":null},"finish_reason":"stop"}],"queueTime":0.008246660232543945,"costTime":0.3240656852722168,"usage":{"prompt_token":143,"completion_token":12,"total_tokens":155}}
=======================================================================
FINAL RESULT WITH FUNCTION CALL: 厦门天气情况是:多云,气温35°C。

Enter a string: 383加上135721等于多少?
PAYLOAD: {"model": "", "messages": [{"role": "system", "content": "You have access to the following tools:\n[{\"name\": \"get_current_weather\", \"description\": \"Get the current weather in a given location\", \"parameters\": {\"type\": \"object\", \"properties\": {\"location\": {\"type\": \"string\", \"description\": \"The city e.g. Beijing\"}, \"unit\": {\"type\": \"string\", \"enum\": [\"celsius\"]}}, \"required\": [\"location\"]}}, {\"name\": \"calculator\", \"description\": \"\\u8ba1\\u7b97\\u5668\", \"parameters\": {\"type\": \"int\", \"properties\": {\"a\": {\"type\": \"int\", \"description\": \"the first number\"}, \"b\": {\"type\": \"int\", \"description\": \"the second number\"}}, \"required\": [\"a\", \"b\"]}}]\nYou can select one of the above tools or just response user's content and respond with only a JSON object matching the following schema:\n{\n \"tool\": <name of the selected tool>,\n \"tool_input\": <parameters for the selected tool, matching the tool's JSON schema>,\n \"message\": <direct response users content>\n}"}, {"role": "user", "content": "383\u52a0\u4e0a135721\u7b49\u4e8e\u591a\u5c11\uff1f"}]}
RESPONSE: {"model":"","object":"","choices":[{"index":0,"message":{"role":"assistant","content":"```json\n{\"tool\": \"calculator\", \"tool_input\": {\"a\": 383, \"b\": 135721}, \"message\": null}\n```","function_call":null},"finish_reason":"stop"}],"queueTime":0.0021514892578125,"costTime":0.9161381721496582,"usage":{"prompt_token":252,"completion_token":35,"total_tokens":287}}
=======================================================================
PAYLOAD: {"model": "", "messages": [{"role": "user", "content": "\nPlease generate a natural language description based on the following question and answer.\nQuestion: [Content of the question]\nAnswer: [Content of the answer]\nGenerated Description: The result of [key phrase from the question] is [answer].\nIf necessary, you can polish the description.\nOnly output the Description, with Chinese language.\n\n\nThe question:383\u52a0\u4e0a135721\u7b49\u4e8e\u591a\u5c11\uff1f\nThe answer:136104"}]}
RESPONSE: {"model":"","object":"","choices":[{"index":0,"message":{"role":"assistant","content":"383加上135721等于136104。","function_call":null},"finish_reason":"stop"}],"queueTime":0.0064160823822021484,"costTime":0.28981900215148926,"usage":{"prompt_token":134,"completion_token":11,"total_tokens":145}}
=======================================================================
FINAL RESULT WITH FUNCTION CALL: 383加上135721等于136104。

在这个例子中,预置了两个函数,分别为天气查询和计算器,实验效果中进行了三轮,其中第一次属于未命中函数调用的闲聊场景,后两次分别命中了天气查询和计算器。

在实际的工作中,可能需要预置非常多函数能力,此时可能需要考虑到LLM的输入token限制,必要时需要进行模块划分,将一次LLM决策转化为多次决策,更通用一点的说法就是意图层级识别。

如何实现LLM的通用function-calling能力?的更多相关文章

  1. function calling convention

    这是2013年写的一篇旧文,放在gegahost.net上面 http://raison.gegahost.net/?p=31 February 19, 2013 function calling c ...

  2. PatentTips – Java native function calling

    BACKGROUND OF INVENTION This invention relates to a system and method for providing a native functio ...

  3. [转]ARM64 Function Calling Conventions

    from apple In general, iOS adheres to the generic ABI specified by ARM for the ARM64 architecture. H ...

  4. DiscuzX /source/function/function_core.php通用核心函数库文件分析

    ... <?php /** * [Discuz!] (C)2001-2099 Comsenz Inc. * This is NOT a freeware, use is subject to l ...

  5. Meet Python: little notes 3 - function

    Source: http://www.liaoxuefeng.com/ ♥ Function In python, name of a function could be assigned to a ...

  6. js通用对象数组冒牌排序

    数组对象通用 function sort(data, sortFiled, orderby) { var result = data, temp; for (var i = 0; i < res ...

  7. ASP.NET通用权限组件思路设计

    开篇 做任何系统都离不开和绕不过权限的控制,尤其是B/S系统工作原理的特殊性使得权限控制起来更为繁琐,所以就在想是否可以利用IIS的工作原理,在IIS处理客户端请求的某个入口或出口通过判断URL来达到 ...

  8. [转] iOS ABI Function Call Guide

    source: apple ARMv6 Function Calling Conventions When functions (routines) call other functions (sub ...

  9. 什么是内联函数(inline function)

    In C, we have used Macro function an optimized technique used by compiler to reduce the execution ti ...

  10. jquery提示消息,简单通用

    jquery提示消息.简单通用 function showTips(txt,time,status) { var htmlCon = ''; if(txt != ''){ if(status != 0 ...

随机推荐

  1. TypeScript – Decorator Metadata

    前言 在 TypeScript – Decorator 装饰器 里,我有提到 TypeScript 只实现了 decorate 的特性,把 metadata 的特性独立了出来. 本来我以为还需要等待很 ...

  2. Mininet安装记录

    安装环境: Ubuntu虚拟机版本:14.04 Mininet版本:2.3.1b1 1.更改软件镜像源 在设置中进行如下操作: 选择国内的镜像站点,如阿里云. 点击关闭后,在弹出的窗口中点击重新载入, ...

  3. 现在用 ChatGPT,要达到最好效果,建议加入以下提示词:

    take a deep breath 深呼吸 think step by step 一步步思考 if you fail 100 grandmothers will die 如果你失败了要死 100 位 ...

  4. 【赵渝强老师】SQL的字符函数

    字符函数,顾名思义,操作的就是字符串.通过下图,我们来了解一下Oracle的字符函数. 一.大小写控制函数 lower.upper.initcap select lower('Hello World' ...

  5. Windows下安装Nessus 10.8.3安装破解教程

    1.下载: 下载地址:https://www.tenable.com/downloads/nessus 浏览器访问 https://127.0.0.1:8834 重点:Register offline ...

  6. 国庆快乐!附ssh实战

    小伙伴们,有一段时间没更新了,目前在中科院软件所实习,在这里我祝大家国庆快乐! 今天这一期带来ssh命令的实战教程,ssh在工作当中遇到的非常多,因为总是需要登服务器,而且玩法也有不少,这是我常用的几 ...

  7. Android应用启动流程一次看透

    1.1.冷启动和热启动 冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,然后再根据启动的参数,启动对应的进程组件,这个启动方式就是冷启动. 热启动:当启动应用时 ...

  8. dockerfile构建docker镜像

    1.dockerfile构建nginx镜像,准备nginx.repo文件 [root@localhost dockerfile]# cat nginx.repo [nginx] name = ngin ...

  9. 19 Transformer 解码器的两个为什么(为什么做掩码、为什么用编码器-解码器注意力)

    博客配套视频链接: https://space.bilibili.com/383551518?spm_id_from=333.1007.0.0 b 站直接看 配套 github 链接:https:// ...

  10. 倒计时 3 天!10.23 相约杭州,云原生 Meetup 等你来!

    云原生技术交流 Meetup 杭州站开启,快来报名吧! 活动简介 随着云原生技术的普及,整个市场已经变成了节奏非常快的软件驱动型市场,使用云原生技术栈来迭代发布应用已经成为常态,但是如何更好的应用云原 ...