众所周知,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. QT原理与源码分析之QT对象类型QObject源码中的间接的设计思想

    这一篇文章介绍QT框架中QT对象类型QObject类型的源代码在设计上的一个比较优秀的设计思想. QObject类型定义 QObject 直接来看QObject的源代码.为了表达更简洁更直观,这里省略 ...

  2. CentOS7 安装配置笔记 v2

    1.通过镜像安装 CentOS72.安装 wget 下载工具3.修改镜像地址4.安装 nano 文本编辑工具5.安装 dotnet core6.安装vsftpd7.设置 firewalld8.为 do ...

  3. Eigen矩阵除法

    看了网上很多帖子,很多都没有说Eigen如何做矩阵除法.我这里补充一下.其他运算一般都可以查到: 对于Matrix来说,我们需要先将其转换成数组,因为Eigen矩阵不能做除法(很烦). 比如我们一个2 ...

  4. 关于BarchNorm的一些学习

    <Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift> ...

  5. flops, params = profile(model, inputs=(x,))计算

    计算量:FLOPs,FLOP时指浮点运算次数,s是指秒,即每秒浮点运算次数的意思,考量一个网络模型的计算量的标准.参数量:Params,是指网络模型中需要训练的参数总数. flops(G) = flo ...

  6. vue前端开发仿钉图系列(5)右侧编辑页面的开发详解

    右侧编辑页面主要有两个入口,一是添加marker或者线面双击结束的时候,新建数据信息:二是点击底部数据的单元行或者查看编辑或者点击地图上的marker以及线面,编辑相关数据.整理总结不易,如需全部代码 ...

  7. SQL语法-列的新增、删除

    MySQL的语法: 新增列 ALTER TABLE `xxdb`.`xxtable` ADD COLUMN `xx_flag` varchar(1) NULL; 删除列 ALTER TABLE `xx ...

  8. 在mac上配置nginx 并将前端的打包文件运行

    .markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...

  9. 解决数据库表的字段id中间自增断层问题(删除自增主键其中的任意一条数据后,再次插入数据发现id排序出现问题)

    万能解决办法: 先将该表的id字段删除,然后再重新按照见表需求创建该字段 注意!!!!!!!!!!!!! 注意!!!!!!!!!!!!! 注意!!!!!!!!!!!!! 删除之前一定要复制建表时候的S ...

  10. T3 出行云原生容器化平台实践

    公司简介 T3 出行是南京领行科技股份有限公司打造的智慧出行生态平台,由中国第一汽车集团有限公司.东风汽车集团有限公司.重庆长安汽车股份有限公司发起,联合腾讯.阿里巴巴等互联网企业共同投资打造.公司以 ...