在本地跑一个大语言模型(3) - 让模型自主使用工具

在前两篇文章里,我们已经介绍了如何在本地运行Ollama以及如何通过提供外部数据库的方式微调模型的答案。本篇文章将继续探索如何使用"函数调用(function-calling)"功能以扩展模型能力,使其在"智能"的道路上越走越远。

function-calling介绍

根据OpenAI官方文档,function-calling是使得大型语言模型具备可以连接到外部工具的能力。简而言之,开发者事先给模型提供了若干工具(函数),在模型理解用户的问题后,自行判断是否需要调用工具以获得更多上下文信息,帮助模型更好的决策

举个例子:在上一篇文章我们是利用Document loaders将事先准备好的文本作为上下文提供给模型,而使用function-calling以后,我们只要提供一个"搜索函数"作为工具,模型即可自己通过搜索引擎进行搜索然后得出答案。

得益于最新的模型训练,现在的模型既能够检测何时应调用函数(取决于输入),还能够以比以前的模型更贴近函数签名的方式响应 JSON。

用途

function-calling有什么用?官网给出3个例子:

  • 创建通过调用外部 API 回答问题的助手
  • 将自然语言转换为 API 调用
  • 从文本中提取结构化数据

下面我们就一步一步来理解一下function-calling

定义function

首先定义4个function,这4个function有不同的使用场景。

python 复制代码
def get_gas_prices(city: str) -> float:
    """Get gas prices for specified cities."""
    print(f'Get gas prices for {city}.')


def github(project: str) -> str:
    '''Get the infomations such as author from github with the project name.'''
    print(f'Access the github for {project}.')


def get_weather(city: str) -> str:
    """Get the current weather given a city."""
    print(f'Getting weather for {city}.')


def get_directions(start: str, destination: str) -> float:
    """Get directions from Google Directions API.
    start: start address as a string including zipcode (if any)
    destination: end address as a string including zipcode (if any)"""
    print(f'Get directions for {start} {destination}.')

定义Prompts

python 复制代码
    functions_prompt = f"""
You have access to the following tools:
{function_to_json(get_weather)}
{function_to_json(get_gas_prices)}
{function_to_json(get_directions)}
{function_to_json(github)}

You must follow these instructions:
Always select one or more of the above tools based on the user query
If a tool is found, you must respond in the JSON format matching the following schema:
{{
   "tools": {{
        "tool": "<name of the selected tool>",
        "tool_input": <parameters for the selected tool, matching the tool's JSON schema
   }}
}}
If there are multiple tools required, make sure a list of tools are returned in a JSON array.
If there is no tool that match the user request, you must respond empty JSON {{}}.

User Query:
    """

这是一个复杂的提示,让我们一步一步来看:

首先,告诉模型我提供了4个工具,让它自己去查询这4个工具的元数据。function_to_json函数返回的内容如下:

json 复制代码
{
  "name": "get_weather",
  "description": "Get the current weather given a city.",
  "parameters": {
    "type": "object",
    "properties": {
      "city": {
        "type": "str"
      }
    }
  },
  "returns": "str"
}

这一步就是让模型根据这几个函数的元数据来理解函数,尤其是description写的应该尽量详细。

第二步提示模型作出自己的判断,根据上面提供的工具选择一个或多个进行调用,并指定了模型返回数据格式------JSON以及这个JSONschema

第三步等待用户的问题。

完整代码

python 复制代码
import inspect
import json
import requests
from typing import get_type_hints


def generate_full_completion(model: str, prompt: str) -> dict[str, str]:
    params = {"model": model, "prompt": prompt, "stream": False}
    response = requests.post(
        "http://localhost:11434/api/generate",
        headers={"Content-Type": "application/json"},
        data=json.dumps(params),
        timeout=60,
    )
    return json.loads(response.text)


def get_gas_prices(city: str) -> float:
    """Get gas prices for specified cities."""
    print(f'Get gas prices for {city}.')


def github(project: str) -> str:
    '''Get the infomations such as author from github with the project name.'''
    print(f'Access the github for {project}.')


def get_weather(city: str) -> str:
    """Get the current weather given a city."""
    print(f'Getting weather for {city}.')


def get_directions(start: str, destination: str) -> float:
    """Get directions from Google Directions API.
    start: start address as a string including zipcode (if any)
    destination: end address as a string including zipcode (if any)"""
    print(f'Get directions for {start} {destination}.')


def get_type_name(t):
    name = str(t)
    if "list" in name or "dict" in name:
        return name
    else:
        return t.__name__


def function_to_json(func):
    signature = inspect.signature(func)
    type_hints = get_type_hints(func)

    function_info = {
        "name": func.__name__,
        "description": func.__doc__,
        "parameters": {"type": "object", "properties": {}},
        "returns": type_hints.get("return", "void").__name__,
    }

    for name, _ in signature.parameters.items():
        param_type = get_type_name(type_hints.get(name, type(None)))
        function_info["parameters"]["properties"][name] = {"type": param_type}

    return json.dumps(function_info, indent=2)


def main():
    functions_prompt = f"""
You have access to the following tools:
{function_to_json(get_weather)}
{function_to_json(get_gas_prices)}
{function_to_json(get_directions)}
{function_to_json(github)}

You must follow these instructions:
Always select one or more of the above tools based on the user query
If a tool is found, you must respond in the JSON format matching the following schema:
{{
   "tools": {{
        "tool": "<name of the selected tool>",
        "tool_input": <parameters for the selected tool, matching the tool's JSON schema
   }}
}}
If there are multiple tools required, make sure a list of tools are returned in a JSON array.
If there is no tool that match the user request, you must respond empty JSON {{}}.

User Query:
    """

    GPT_MODEL = "mistral:7b-instruct-v0.2-q8_0"

    prompts = [
        "What's the weather like in Beijing?",
        "What is the distance from Shanghai to Hangzhou and how much do I need to fill up the gas in advance to drive from Shanghai to Hangzhou?",
        "Who's the author of the 'snake-game' on github?",
        "What is the exchange rate between US dollar and Japanese yen?",
    ]

    for prompt in prompts:
        print(f"❓{prompt}")
        question = functions_prompt + prompt
        response = generate_full_completion(GPT_MODEL, question)
        try:
            data = json.loads(response.get("response", response))
            # print(data)
            for tool_data in data["tools"]:
                execute_fuc(tool_data)
        except Exception:
            print('No tools found.')
        print(f"Total duration: {int(response.get('total_duration')) / 1e9} seconds")


def execute_fuc(tool_data):
    func_name = tool_data["tool"]
    func_input = tool_data["tool_input"]

    # 获取全局命名空间中的函数对象
    func = globals().get(func_name)

    if func is not None and callable(func):
        # 如果找到了函数并且是可调用的,调用它
        func(**func_input)
    else:
        print(f"Unknown function: {func_name}")


if __name__ == "__main__":
    main()

整段代码就是用来测试模型对问题的理解能力以及是否能正确判断调用哪个工具的。

我们来看下设定的4个问题:

python 复制代码
  "What's the weather like in Beijing?",
  "What is the distance from Shanghai to Hangzhou and how much do I need to fill up the gas in advance to drive from Shanghai to Hangzhou?",
  "Who's the author of the 'snake-game' on github?",
  "What is the exchange rate between US dollar and Japanese yen?",

按照我们的设想,如果模型理解了我提供的工具的功能以及读懂了用户的问题,应该按照以下规则选择工具:

  • 问题1应该对应的是get_weather('Beijing')
  • 问题2应该对应的是get_directions('Shanghai', 'Hangzhou')get_gas_prices('Shanghai')
  • 问题3应该对应的是github('snake-game')
  • 问题4应该没有对应的函数,模型不选择任何工具

下一步,我们也写好了代码进行测试,我们在工具函数里进行参数打印,以查看是否达到我们的预期。

结果

vbnet 复制代码
❓What's the weather like in Beijing?
Getting weather for Beijing.
Total duration: 3.481918084 seconds

❓What is the distance from Shanghai to Hangzhou and how much do I need to fill up the gas in advance to drive from Shanghai to Hangzhou?
Get directions for Shanghai, China Hangzhou, China.
Get gas prices for Shanghai.
Total duration: 5.467253959 seconds

❓Who's the author of the 'snake-game' on github?
Access the github for snake-game.
Total duration: 2.510993791 seconds

❓What is the exchange rate between US dollar and Japanese yen?
{}
No tools found.
Total duration: 0.200526292 seconds

总结

本篇文章介绍了使用function-calling使得Ollama模型具备可以调用外部工具的能力。也对一些场景进行了测试,希望通过本系列的3篇文章,使大家对本地运行大语言模型有一些深入的了解。

这次的文章就到这里了,下回我们将继续介绍更多本地LLM的实用场景。

本文首发于:babyno.top/posts/2024/...

相关推荐
知来者逆10 小时前
Binoculars——分析证实大语言模型生成文本的检测和引用量按学科和国家明确显示了使用偏差的多样性和对内容类型的影响
人工智能·深度学习·语言模型·自然语言处理·llm·大语言模型
几米哥17 小时前
如何构建高效的AI代理系统:LLM应用实践与最佳方案的深度解析
llm·aigc
测试者家园19 小时前
ChatGPT生成接口文档实践案例(二)
软件测试·chatgpt·llm·测试用例·测试图书·质量效能·用chatgpt做测试
测试者家园1 天前
ChatGPT与Postman协作完成接口测试(一)
软件测试·chatgpt·接口测试·postman·用chatgpt做软件测试·测试图书·质量效能
智慧化智能化数字化方案1 天前
120页PPT讲解ChatGPT如何与财务数字化转型的业财融合
人工智能·chatgpt
s_yellowfish2 天前
Linux服务器pm2 运行chatgpt-on-wechat,搭建微信群ai机器人
linux·服务器·chatgpt
that's boy3 天前
突围边缘:OpenAI开源实时嵌入式API,AI触角延伸至微观世界
人工智能·gpt·chatgpt·开源·openai·midjourney
AIGC大时代3 天前
如何使用ChatGPT辅助文献综述,以及如何进行优化?一篇说清楚
人工智能·深度学习·chatgpt·prompt·aigc
hunteritself3 天前
AI Weekly『12月16-22日』:OpenAI公布o3,谷歌发布首个推理模型,GitHub Copilot免费版上线!
人工智能·gpt·chatgpt·github·openai·copilot
bastgia3 天前
Tokenformer: 下一代Transformer架构
人工智能·机器学习·llm