课程学习自 知乎知学堂 https://www.zhihu.com/education/learning
如果侵权,请联系删除,感谢!
相关参考: https://platform.openai.com/docs/guides/function-calling
1. 课程摘要
大模型,没有真逻辑,只是推测下一个字的概率 gpt 可以根据函数的描述来决定是否调用该函数,但是真正的执行,需要我们自行调用
2. 调用本地函数
2.1 help 函数
代码语言:javascript复制from openai import OpenAI # openai 1.3.3
import os
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())
client = OpenAI(
api_key=os.getenv("OPENAI_API_KEY"),
base_url=os.getenv("OPENAI_API_BASE")
)
def get_completion_with_tools(messages, tools=[], model="gpt-3.5-turbo-1106"):
response = client.chat.completions.create( # 注意,以前的 client.ChatCompletion 要换成 client.chat.completions
model=model,
messages=messages,
temperature=0, # 模型输出的随机性,0 表示随机性最小
tools=tools, # 用 JSON 描述函数。可以定义多个。由大模型决定调用谁,
seed=1024,
tool_choice='auto',
# response_format={'type': 'json_object'} # 如果加了这个,请在prompt中显式指出使用json格式输出
)
return response.choices[0].message
def get_completion_with_messages(messages,model="gpt-3.5-turbo-1106"):
response = client.chat.completions.create(
model=model,
messages=messages,
temperature=0, # 模型输出的随机性,0 表示随机性最小
)
return response.choices[0].message
2.2 求和
调用python内置 sum
函数
# _*_ coding: utf-8 _*_
# @Time : 2023/11/20 0:01
# @Author : Michael
# @File : 03-function-call.py
# @desc :
import json
from agi_class.utils.chat import client, get_completion_with_messages, get_completion_with_tools # openai 1.3.3 python 3.11.5
tools = [
{ # 用 JSON 描述函数。可以定义多个。由大模型决定调用谁
"type": "function",
"function": {
"name": "sum",
"description": "计算一组数的加和",
"parameters": {
"type": "object",
"properties": {
"numbers": {
"type": "array",
"items": {
"type": "number"
}
}
}
}
}
}
]
prompt = "求一下这些数字的和 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1000"
messages = [
{"role": "system", "content": "你是一个高级数学老师,会用幽默的方法讲述数学题目"},
{"role": "user", "content": prompt}
]
response = get_completion_with_tools(messages, tools)
print(response)
# ChatCompletionMessage(content=None, role='assistant', function_call=None,
# tool_calls=[ChatCompletionMessageToolCall(id='call_lxJk0LzId1FBKwdf715LAwkN', function=Function(arguments='{"numbers": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}', name='sum'), type='function'),
# ChatCompletionMessageToolCall(id='call_8978FU0b9MukKsjop8QNkUzi', function=Function(arguments='{"numbers": [1000]}', name='sum'), type='function')])
messages.append(response)
# 如果返回的是函数调用结果
if response.tool_calls is not None:
available_functions = {
"sum": sum,
}
for tool_call in response.tool_calls:
function_name = tool_call.function.name
function_to_call = available_functions[function_name]
function_args = json.loads(tool_call.function.arguments)
function_response = function_to_call(
function_args.get("numbers"),
)
messages.append(
{
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": str(function_response),
}
)
# 把函数调用(需要自行调用)的结果,返给gpt,再次调用生成回复
print(get_completion_with_messages(messages).content)
# 这些数字的和是55和1000。看起来1000有点孤单,需要更多的朋友来凑热闹!
2.3 eval
求表达式的值
代码语言:javascript复制from math import sqrt
tools = [{
"type": "function",
"function": {
"name": "calculate",
"description": "计算一个数学表达式的值",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "一个python语法的数学表达式",
}
}
}
}
}]
prompt = "5的平方根加上1再开根号"
messages = [
{"role": "system", "content": "你是一个计算器,你可以计算任何数学表达式"},
{"role": "user", "content": prompt}
]
response = get_completion_with_tools(messages, tools)
print(response)
# ChatCompletionMessage(content=None, role='assistant', function_call=None,
# tool_calls=[ChatCompletionMessageToolCall(id='call_GslHVfRmFrKgJQtDaofoucNs', function=Function(arguments='{"expression": "sqrt(5)"}', name='calculate'), type='function'),
# ChatCompletionMessageToolCall(id='call_HYp2QqlZnfpi6d1WIuHABxBO', function=Function(arguments='{"expression": "sqrt(sqrt(5) 1)"}', name='calculate'), type='function')])
messages.append(response)
# 如果返回的是函数调用结果
if response.tool_calls is not None:
available_functions = {
"calculate": eval,
}
for tool_call in response.tool_calls:
function_name = tool_call.function.name
function_to_call = available_functions[function_name]
function_args = json.loads(tool_call.function.arguments)
function_response = function_to_call(
function_args.get("expression"),
)
messages.append(
{
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": str(function_response),
}
)
# 再次调用大模型
print("=====最终回复=====")
print(get_completion_with_messages(messages).content)
# 5的平方根是约等于2.236,然后再加上1得到3.236,最后开根号得到约等于1.799。
2.4 调用高德地图 api
代码语言:javascript复制import requests
# 调用地图api
map_api_key=os.getenv("A_MAP_KEY")
def get_location_coordinate(location, city="北京"):
url = f"https://restapi.amap.com/v5/place/text?key={map_api_key}&keywords={location}®ion={city}"
# print(url)
r = requests.get(url)
result = r.json()
if "pois" in result and result["pois"]:
return result["pois"][0]
return None
def search_nearby_pois(longitude, latitude, keyword):
url = f"https://restapi.amap.com/v5/place/around?key={map_api_key}&keywords={keyword}&location={longitude},{latitude}"
# print(url)
r = requests.get(url)
result = r.json()
ans = ""
if "pois" in result and result["pois"]:
for i in range(min(3, len(result["pois"]))):
name = result["pois"][i]["name"]
address = result["pois"][i]["address"]
distance = result["pois"][i]["distance"]
ans = f"{name}n{address}n距离:{distance}米nn"
return ans
代码语言:javascript复制tools=[{
"type": "function",
"function": {
"name": "get_location_coordinate",
"description": "根据POI名称,获得POI的经纬度坐标",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "POI名称,必须是中文",
},
"city": {
"type": "string",
"description": "POI所在的城市名,必须是中文",
}
},
"required": ["location", "city"],
}
}
},
{
"type": "function",
"function": {
"name": "search_nearby_pois",
"description": "搜索给定坐标附近的poi",
"parameters": {
"type": "object",
"properties": {
"longitude": {
"type": "string",
"description": "中心点的经度",
},
"latitude": {
"type": "string",
"description": "中心点的纬度",
},
"keyword": {
"type": "string",
"description": "目标poi的关键字",
}
},
"required": ["longitude", "latitude", "keyword"],
}
}
}]
prompt = "北京启明国际大厦附近的医院"
messages = [
{"role": "system", "content": "你是一个地图通,你可以找到任何地址。"},
{"role": "user", "content": prompt}
]
response = get_completion_with_tools(messages, tools)
messages.append(response) # 把大模型的回复加入到对话中
print("=====GPT回复=====")
print(response)
# 如果返回的是函数调用结果,则打印出来
while response.tool_calls is not None:
# 1106 版新模型支持一次返回多个函数调用请求
for tool_call in response.tool_calls:
args = json.loads(tool_call.function.arguments)
print(f'参数:{args}')
if tool_call.function.name == "get_location_coordinate":
print("Call: get_location_coordinate")
result = get_location_coordinate(**args)
elif tool_call.function.name == "search_nearby_pois":
print("Call: search_nearby_pois")
result = search_nearby_pois(**args)
print("=====函数返回=====")
print(result)
messages.append({
"tool_call_id": tool_call.id, # 用于标识函数调用的 ID
"role": "tool",
"name": tool_call.function.name,
"content": str(result) # 数值result 必须转成字符串
})
response = get_completion_with_tools(messages, tools)
messages.append(response) # 把大模型的回复加入到对话中
print('====所有 messages=====')
print(messages)
print("=====最终回复=====")
print(response.content)
输出:
代码语言:javascript复制=====GPT回复=====
ChatCompletionMessage(content=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_rJTZh2IMtp280dITbPocNf3A', function=Function(arguments='{"location":"北京启明国际大厦","city":"北京"}', name='get_location_coordinate'), type='function')])
参数:{'location': '北京启明国际大厦', 'city': '北京'}
Call: get_location_coordinate
=====函数返回=====
{'parent': '', 'address': '望京利泽中园101号', 'distance': '', 'pcode': '110000', 'adcode': '110105', 'pname': '北京市', 'cityname': '北京市', 'type': '商务住宅;楼宇;商务写字楼', 'typecode': '120201', 'adname': '朝阳区', 'citycode': '010', 'name': '启明国际大厦', 'location': '116.475071,40.012434', 'id': 'B000A87P48'}
参数:{'longitude': '116.475071', 'latitude': '40.012434', 'keyword': '医院'}
Call: search_nearby_pois
=====函数返回=====
东湖社区卫生服务中心
望京花园东区204号楼
距离:480米
国宗济世中医院门诊
河荫中路与广顺北大街交叉口东80米
距离:1030米
北京固生堂京广顺中医医院
望京西园二区京电底商综合楼2
距离:1045米
====所有 messages=====
[{'role': 'system', 'content': '你是一个地图通,你可以找到任何地址。'},
{'role': 'user', 'content': '北京启明国际大厦附近的医院'},
ChatCompletionMessage(content=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_rJTZh2IMtp280dITbPocNf3A', function=Function(arguments='{"location":"北京启明国际大厦","city":"北京"}', name='get_location_coordinate'), type='function')]), {'tool_call_id': 'call_rJTZh2IMtp280dITbPocNf3A', 'role': 'tool', 'name': 'get_location_coordinate', 'content': "{'parent': '', 'address': '望京利泽中园101号', 'distance': '', 'pcode': '110000', 'adcode': '110105', 'pname': '北京市', 'cityname': '北京市', 'type': '商务住宅;楼宇;商务写字楼', 'typecode': '120201', 'adname': '朝阳区', 'citycode': '010', 'name': '启明国际大厦', 'location': '116.475071,40.012434', 'id': 'B000A87P48'}"},
ChatCompletionMessage(content=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_AnwdLAUJWvTWDJ4LN3LW6sFc', function=Function(arguments='{"longitude":"116.475071","latitude":"40.012434","keyword":"医院"}', name='search_nearby_pois'), type='function')]), {'tool_call_id': 'call_AnwdLAUJWvTWDJ4LN3LW6sFc', 'role': 'tool', 'name': 'search_nearby_pois', 'content': '东湖社区卫生服务中心n望京花园东区204号楼n距离:480米nn国宗济世中医院门诊n河荫中路与广顺北大街交叉口东80米n距离:1030米nn北京固生堂京广顺中医医院n望京西园二区京电底商综合楼2n距离:1045米nn'},
ChatCompletionMessage(content='北京启明国际大厦附近有以下医院:n1. 东湖社区卫生服务中心n 地址:望京花园东区204号楼n 距离:480米nn2. 国宗济世中医院门诊n 地址:河荫中路与广顺北大街交叉口东80米n 距离:1030米nn3. 北京固生堂京广顺中医医院n 地址:望京西园二区京电底商综合楼2n 距离:1045米', role='assistant', function_call=None, tool_calls=None)]
=====最终回复=====
北京启明国际大厦附近有以下医院:
1. 东湖社区卫生服务中心
地址:望京花园东区204号楼
距离:480米
2. 国宗济世中医院门诊
地址:河荫中路与广顺北大街交叉口东80米
距离:1030米
3. 北京固生堂京广顺中医医院
地址:望京西园二区京电底商综合楼2
距离:1045米
试了很多次,发现并没有调用 search_nearby_pois
函数,调整成老师写的 tools 里面的描述才可以,模型对 tools 里面的自然语言描述比较敏感
2.5 输出 JSON
create
函数中加入参数 response_format={'type': 'json_object'} # 如果加了这个,请在prompt中显式指出使用json格式输出
,否则会报错
tools = [{
"type": "function",
"function": {
"name": "add_contact",
"description": "添加联系人",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "联系人姓名"
},
"address": {
"type": "string",
"description": "联系人地址"
},
"tel": {
"type": "string",
"description": "联系人电话"
},
}
}
}
}]
prompt = "帮我把这个文件快递给张三,地址是北京市朝阳区亮启明国际大厦,电话123456789。使用json格式输出"
messages = [
{"role": "system", "content": "你是一个联系人录入员。"},
{"role": "user", "content": prompt}
]
response = get_completion_with_tools(messages, tools)
print("====GPT回复====")
print(response)
args = json.loads(response.tool_calls[0].function.arguments)
print("====函数参数====")
print(args)
输出:
代码语言:javascript复制====GPT回复====
ChatCompletionMessage(content=None, role='assistant', function_call=None,
tool_calls=[ChatCompletionMessageToolCall(id='call_baWP1OICnj0LEfq9mGyxkyP4', function=Function(arguments='{"name": "张三", "address": "北京市朝阳区亮启明国际大厦", "tel": "123456789"}', name='add_contact'), type='function'), ChatCompletionMessageToolCall(id='call_7qwlc196ik6XywFI7vAdOLFp', function=Function(arguments='{"name": "快递公司", "address": "北京市朝阳区", "tel": "快递公司电话"}', name='add_contact'), type='function')])
====函数参数====
{'name': '张三', 'address': '北京市朝阳区亮启明国际大厦', 'tel': '123456789'}
2.6 查询数据库
进行多表的join
查询
# 查询数据库
# 描述数据库表结构
database_schema_string = """
CREATE TABLE course_selection (
id INT PRIMARY KEY NOT NULL, -- 主键,不允许为空
student_id INT NOT NULL, -- 学生ID,不允许为空
course_id STR NOT NULL, -- 课程ID,不允许为空
registration_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 报名时间,默认为当前时间
status INT NOT NULL, -- 选课状态,整数类型,不允许为空。0代表未选中,1代表已选中,2代表已取消选课
credits INT NOT NULL, -- 学分,不允许为空
FOREIGN KEY (student_id) REFERENCES students(id), -- 外键关联,学生ID关联到students表的id字段
FOREIGN KEY (course_id) REFERENCES courses(id) -- 外键关联,课程ID关联到courses表的id字段
);
"""
course_table_schema = """
CREATE TABLE courses (
id STR PRIMARY KEY NOT NULL, -- 课程ID,不允许为空
name VARCHAR(255) NOT NULL, -- 课程名称,不允许为空
professor VARCHAR(255) NOT NULL -- 授课教授,不允许为空
);
"""
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 student course.
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 written using this database schema:
{database_schema_string}n
{course_table_schema}n
The query should be returned in plain text, not in JSON.
The query should only contain grammars supported by SQLite.
""",
}
},
"required": ["query"],
}
}
}]
import sqlite3
# 创建数据库连接
conn = sqlite3.connect(':memory:')
cursor = conn.cursor()
# 创建orders表
cursor.execute(database_schema_string)
cursor.execute(course_table_schema)
# 插入明确的模拟记录
mock_data = [
(101, 1, 'C001', '2023-01-01 10:00:00', 1, 4),
(102, 1, 'C002', '2023-01-02 11:30:00', 1, 3),
(100, 1, 'C003', '2023-01-02 11:30:00', 2, 3),
(103, 2, 'C001', '2023-01-03 09:45:00', 1, 4),
(104, 2, 'C003', '2023-01-04 14:15:00', 1, 3),
(105, 3, 'C002', '2023-01-05 13:20:00', 1, 3)
]
for record in mock_data:
cursor.execute('''
INSERT INTO course_selection (id, student_id, course_id, registration_time, status, credits)
VALUES (?, ?, ?, ?, ?, ?)
''', record)
mock_data = [
('C001', '数据库基础', '刘教授'),
('C002', '计算机网络', '王教授'),
('C003', '数据结构', '陈教授')
]
for record in mock_data:
cursor.execute('''
INSERT INTO courses (id, name, professor)
VALUES (?, ?, ?)
''', record)
# 提交事务
conn.commit()
def ask_database(query):
cursor.execute(query)
records = cursor.fetchall()
return records
prompt = "学生id是1的学生,现在有哪些课程选中了,分别是什么课程名、教师是谁,这些课程总共多少学分"
messages = [
{"role": "system", "content": "基于 course_selection, courses 表回答用户问题"},
{"role": "user", "content": prompt}
]
response = get_completion_with_tools(messages, tools)
messages.append(response)
print("====Function Calling====")
print(response)
while response.tool_calls is not None:
for tool_call in response.tool_calls:
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_completion_with_tools(messages, tools)
print("====GPT回复====")
print(response.content)
输出:
代码语言:javascript复制====Function Calling====
ChatCompletionMessage(content=None, role='assistant', function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_JrLGU5uI5RvpfPwdOXF8UI8E', function=Function(arguments='{"query":"SELECT c.name, c.professor, cs.credits FROM course_selection cs JOIN courses c ON cs.course_id = c.id WHERE cs.student_id = 1 AND cs.status = 1;"}', name='ask_database'), type='function')])
====SQL====
SELECT c.name, c.professor, cs.credits FROM course_selection cs JOIN courses c
ON cs.course_id = c.id WHERE cs.student_id = 1 AND cs.status = 1;
====DB Records====
[('数据库基础', '刘教授', 4), ('计算机网络', '王教授', 3)]
====GPT回复====
学生ID为1的学生已选中了以下课程:
1. 课程名:数据库基础,教师:刘教授,学分:4
2. 课程名:计算机网络,教师:王教授,学分:3
这些课程总共的学分是 7。
3. 支持Function Call的国产模型
- 文心
- MiniMax
- ChatGLM3-6B
- 星火3.0