DoraCMS

您现在的位置是:首页>文档内容页

文档详情

AIGC之 funtion-calling

doramart 2024-05-02 21:48:13 原创教程1159
针对近期学习,在本地尝试跑tabby,但是由于系统原因没成功(好像需要M系列的苹果芯片);突然看到近期出现的llama3 7B出来了,结合 ollama 在本地跑起来了


针对近期学习,在本地尝试跑tabby,但是由于系统原因没成功(好像需要M系列的苹果芯片);突然看到近期出现的llama3 7B出来了,结合 ollama 在本地跑起来了,通过终端调用稍微有点卡,通过 open webUI 调用卡的相当厉害,调用方式如下图:



 针对正课近期学习了 function calling ,指导思想是在通过调用 open ai 的过程中可以通过自己的方式来获取想要的数据,open ai 通过自然语言分析是否调用 function 并给出相应的回答,我做了两个例子


① 通过 function call 的方式调用接口,但是并不是所有情况都会调用接口,要不断尝试

# 初始化
from openai import OpenAI
from dotenv import load_dotenv, find_dotenv
import json
_ = load_dotenv(find_dotenv())
client = OpenAI()

def print_json(data):
  """
  打印参数。如果参数是有结构的(如字典或列表),则以格式化的 JSON 形式打印;
  否则,直接打印该值。
  """
  if hasattr(data, "model_dump_json"):
      data = json.loads(data.model_dump_json())
  if isinstance(data, (list)):
      for item in data:
          print_json(item)
  elif isinstance(data, (dict)):
      print(json.dumps(data, indent=4, ensure_ascii=False))
  else:
      print(data)

def get_completion(messages, model="gpt-3.5-turbo"):
  response = client.chat.completions.create(
      model=model,
      messages=messages,
      temperature=0,
      seed=1024,  # 随机种子保持不变,temperature 和 prompt 不变的情况下,输出就会不变
      tool_choice="auto",  # 默认值,由 GPT 自主决定返回 function call 还是返回文字回复。也可以强制要求必须调用指定的函数,详见官方文档
      tools=[
          {
              "type": "function",
              "function": {
                  "name": "get_content_list",
                  "description": "根据关键字,优先从查出包含该关键字的文章列表,关键字可以存在于文章标题,标签,或者文章内容等",
                  "parameters": {
                      "type": "object",
                      "properties": {
                          "searchkey": {
                              "type": "string",
                              "description": "文章关键字",
                          },
                      },
                  },
              },
          },
      ],
  )
  return response.choices[0].message

import requests

def get_content_list(searchkey):
  # 使用replace方法去掉"DoraCMS"
  newSearchkey = searchkey.replace("DoraCMS", "")
  # 使用strip方法去掉空格
  final_string = newSearchkey.strip()
  url = f"https://www.html-js.cn/api/content/getList?tagName=${searchkey}"
  print(url)
  r = requests.get(url)
  result = r.json()
  # print(result)
  if "data" in result and result["data"]["docs"]:
      return result["data"]["docs"][0]
  return None

prompt = "DoraCMS的前端知识体系"
# prompt = "我到北京出差,给我推荐三里屯的酒店,和五道口附近的咖啡" # 一次请求两个调用
messages = [
  {
      "role": "system",
      "content": "你是一个精通基于Nodejs的内容管理系统 DoraCMS 的全栈开发者。",
  },
  {"role": "user", "content": prompt},
]
response = get_completion(messages)
messages.append(response)  # 把大模型的回复加入到对话中
print("=====GPT回复=====")
print_json(response)
while response.tool_calls is not None:
  # 支持一次返回多个函数调用请求,所以要考虑到这种情况
  for tool_call in response.tool_calls:
      args = json.loads(tool_call.function.arguments)
      print("函数参数展开:")
      print_json(args)
      # 函数路由
      if tool_call.function.name == "get_content_list":
          print("Call: get_content_list")
          result = get_content_list(**args)
      print("=====函数返回=====")
      print_json(result)
      messages.append(
          {
              "tool_call_id": tool_call.id,  # 用于标识函数调用的 ID
              "role": "tool",
              "name": tool_call.function.name,
              "content": str(result),  # 数值result 必须转成字符串
          }
      )
  response = get_completion(messages)
  messages.append(response)  # 把大模型的回复加入到对话中
print("=====最终回复=====")
print(response.content)
print("=====对话历史=====")
print_json(messages)

对话历史

=====对话历史=====
{
   "role": "system",
   "content": "你是一个精通基于Nodejs的内容管理系统 DoraCMS 的全栈开发者。"
}
{
   "role": "user",
   "content": "DoraCMS的前端知识体系"
}
{
   "content": null,
   "role": "assistant",
   "function_call": null,
   "tool_calls": [
       {
           "id": "call_rzVGg6qvlYQhCZTcuW7ThGSw",
           "function": {
               "arguments": "{\"searchkey\":\"DoraCMS前端知识体系\"}",
               "name": "get_content_list"
           },
           "type": "function"
       }
   ]
}
{
   "tool_call_id": "call_rzVGg6qvlYQhCZTcuW7ThGSw",
   "role": "tool",
   "name": "get_content_list",
   "content": "{'type': '1', 'categories': [{'enable': True, 'defaultUrl': 'document', '_id': 'Ek7skiaw', 'name': 'DoraCMS', 'contentTemp': None, 'url': '/document___Ek7skiaw', 'id': 'Ek7skiaw'}, {'enable': True, 'defaultUrl': 'document/softUpdate', '_id': '4yzPes6w', 'name': '版本升级', 'contentTemp': None, 'url': '/document/softUpdate___4yzPes6w', 'id': '4yzPes6w'}], 'tags': [{'_id': 'IZHCVQcry', 'name': 'nodecms', 'url': '/tag/nodecms', 'id': 'IZHCVQcry'}, {'_id': 'WXN4r2Ct', 'name': 'eggcms', 'url': '/tag/eggcms', 'id': 'WXN4r2Ct'}], 'keywords': [], 'sImg': 'https://cdn.html-js.cn/cms/upload/images/20210925/1632580364803065432.png', 'videoImg': '', 'state': '2', 'isTop': 1, 'roofPlacement': '0', 'clickNum': 7345, '_id': 'bTtSErSoF', 'title': 'Nodejs 内容管理系统 DoraCMS 2.1.8 发布', 'stitle': 'DoraCMS 2.1.7-- 版本发布', 'author': {'logo': 'https://cdn.html-js.cn/cms/upload/smallimgs/img1448202744000.jpg', '_id': '4JiWCMhzg', 'userName': 'doramart'}, 'discription': '近期对DoraCMS做了一些优化,也修复了一些bug,为了让vscode不再出现令人讨厌的格式问题,加入了eslint和prettier进行格式化校验', 'uAuthor': {'logo': 'https://cdn.html-js.cn/cms/upload/smallimgs/img1447739082000.jpg', 'group': '0', '_id': '41oT6sQXl', 'userName': 'doramart', 'name': '生哥', 'id': '41oT6sQXl', 'had_followed': False}, 'date': '2021-09-25 22:32:48', 'updateDate': '2021-09-25 22:32:48', 'url': '/details/bTtSErSoF.html', 'id': 'bTtSErSoF', 'hasPraised': False, 'hasComment': False, 'hasFavorite': False, 'hasDespise': False, 'commentNum': 0, 'likeNum': 1, 'favoriteNum': 0, 'despiseNum': 0}"
}
{
   "content": "我找到了关于DoraCMS前端知识体系的文章:[Nodejs 内容管理系统 DoraCMS 2.1.8 发布](https://cdn.html-js.cn/cms/upload/images/20210925/1632580364803065432.png)。该文章介绍了DoraCMS 2.1.8 版本的发布内容,包括优化、bug修复以及格式化校验等方面的内容。你可以查看详细信息和了解更多内容。",
   "role": "assistant",
   "function_call": null,
   "tool_calls": null
}

② 通过 function calling 的方式读取数据库,并查询;结果比较满意

# 读取数据库
import json
from openai import OpenAI
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())

import pymysql
# # MariaDB连接配置
config = {
  "host": "192.168.31.69",  # 例如: 'your_remote_host.com'
  "user": "root",  # 例如: 'your_username'
  "password": "******",  # 例如: 'your_password'
  "database": "doracms",  # 例如: 'your_database'
  "charset": "utf8mb4",  # 防止乱码
  "cursorclass": pymysql.cursors.DictCursor,  # 返回字典类型的查询结果
  "port": 3309,
}
# 连接数据库
connection = pymysql.connect(**config)

# 一个辅助函数,只为演示方便。不重要
def print_json(data):
  """
  打印参数。如果参数是有结构的(如字典或列表),则以格式化的 JSON 形式打印;
  否则,直接打印该值。
  """
  if hasattr(data, "model_dump_json"):
      data = json.loads(data.model_dump_json())
  if isinstance(data, (list, dict)):
      print(json.dumps(data, indent=4, ensure_ascii=False))
  else:
      print(data)

client = OpenAI()

database_schema_string = """
CREATE TABLE `doracms_user` (
`id` int(11) NOT NULL,
`enable` tinyint(1) NOT NULL DEFAULT 1,
`name` varchar(255) DEFAULT NULL,
`user_name` varchar(255) DEFAULT NULL,
`password` varchar(100) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`qq` int(11) DEFAULT NULL,
`phone_num` varchar(255) DEFAULT NULL,
`country_code` varchar(255) DEFAULT NULL,
`id_no` int(11) DEFAULT NULL,
`id_type` varchar(255) DEFAULT '1',
`comments` varchar(255) DEFAULT '',
`introduction` varchar(255) DEFAULT '',
`position` varchar(255) DEFAULT NULL,
`profession` varchar(255) DEFAULT NULL,
`industry` varchar(255) DEFAULT NULL,
`experience` varchar(255) DEFAULT NULL,
`company` varchar(255) DEFAULT NULL,
`website` varchar(255) DEFAULT NULL,
`logo` varchar(500) DEFAULT '/static/upload/images/defaultlogo.png',
`group` varchar(255) DEFAULT '0',
`province` varchar(255) DEFAULT NULL,
`city` varchar(255) DEFAULT NULL,
`gender` varchar(255) DEFAULT '0',
`state` varchar(255) DEFAULT '1',
`retrieve_time` bigint(20) DEFAULT NULL,
`login_active` tinyint(1) DEFAULT 0,
`device_id` varchar(255) DEFAULT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
"""

def get_sql_completion(messages, model="gpt-3.5-turbo"):
  response = client.chat.completions.create(
      model=model,
      messages=messages,
      temperature=0,
      tools=[
          {  # 摘自 OpenAI 官方示例 https://github.com/openai/openai-cookbook/blob/main/examples/How_to_call_functions_with_chat_models.ipynb
              "type": "function",
              "function": {
                  "name": "ask_database",
                  "description": "Use this function to answer user questions about business. \
                          Output should be a fully formed SQL query.",
                  "parameters": {
                      "type": "object",
                      "properties": {
                          "query": {
                              "type": "string",
                              "description": f"""
                          SQL query extracting info to answer the user's question.
                          SQL should be read or write using this database schema:
                          {database_schema_string}
                          The query should be returned in plain text, not in JSON.
                          The query should only contain grammars supported by Mairadb.
                          """,
                          }
                      },
                      "required": ["query"],
                  },
              },
          }
      ],
  )
  return response.choices[0].message

def ask_database(query):
  try:
      with connection.cursor() as cursor:
          # 执行查询脚本
          # 例如: SELECT * FROM your_table WHERE your_condition
          # sql = "SELECT * FROM your_table WHERE your_condition"
          cursor.execute(query)
          # 获取查询结果
          result = cursor.fetchall()
          # print("-----", result)
          return result
  finally:
      # 关闭数据库连接
      connection.close()

prompt = "DoraCMS站点注册用户数是多少"
messages = [
  {
      "role": "system",
      "content": "你是全栈开发者,熟悉DoraCMS管理系统实现的基本原理和数据库结构",
  },
  {"role": "user", "content": prompt},
]
response = get_sql_completion(messages)
if response.content is None:
  response.content = ""
messages.append(response)
print("====Function Calling====")
print_json(response)
if response.tool_calls is not None:
  tool_call = response.tool_calls[0]
  if tool_call.function.name == "ask_database":
      arguments = tool_call.function.arguments
      args = json.loads(arguments)
      print("====SQL====")
      print(args["query"])
      result = ask_database(args["query"])
      print("====DB Records====")
      print(result)
      messages.append(
          {
              "tool_call_id": tool_call.id,
              "role": "tool",
              "name": "ask_database",
              "content": str(result),
          }
      )
      response = get_sql_completion(messages)
      print("====最终回复====")
      print(response.content)

执行结果

====Function Calling====
{
   "content": "",
   "role": "assistant",
   "function_call": null,
   "tool_calls": [
       {
           "id": "call_LRm7MZQKYEDh4b6JQhimuuL9",
           "function": {
               "arguments": "{\"query\":\"SELECT COUNT(id) AS total_users FROM doracms_user\"}",
               "name": "ask_database"
           },
           "type": "function"
       }
   ]
}
====SQL====
SELECT COUNT(id) AS total_users FROM doracms_user
====DB Records====
[{'total_users': 808}]
====最终回复====
DoraCMS站点注册用户数为808。


总结: function-calling 的指导思想是可以自定义回调函数,这个回调函数可以是调用一个接口,也可以通过提示词告诉 gpt 数据库相关基础信息,让gpt生成 SQL语句并执行,通过执行结果作为入参继续调用gpt来获取最终结果。function-calling 适用场景为在网上不容易查到的信息,原因是大语言模型是数据并不是最新的(虽然可以联网),需要注意,通过function-calling 查询到的私有数据上送 gpt 是有风险的,对数据敏感的场景最好是私有的大模型来处理相关逻辑

文章评论

取消回复
登录 参与评论

评论列表(