赞
踩
目录
OpenAI Assistant API支持function calling
LangChain Assistant API支持function calling
函数调用(Function Calling)是OpenAI
在今年6月13日对外发布的新能力。根据OpenAI官方博客描述,函数调用能力可以让大模型输出一个请求调用函数的消息,其中包含所需调用的函数信息、以及调用函数时所携带的参数信息。这是一种将大模型
(LLM)能力与外部工具/API
连接起来的新方式。
比如用户输入:
What’s the weather like in Tokyo?
使用function calling,可实现函数执行get_current_weather(location: string)
,从而获取函数输出,即得到对应地理位置的天气情况。这其中,location
这个参数及其取值是借助大模型能力从用户输入中抽取出来的,同时,大模型判断得到调用的函数为get_current_weather
。
开发人员可以使用大模型的function calling能力实现:
• 在进行自然语言交流时,通过调用外部工具回答问题(类似于ChatGPT插件);
• 将自然语言转换为调用API调用,或数据库查询语句;
• 从文本中抽取结构化数据
• 其它
那么,在OpenAI发布的模型中,是如何实现function calling的呢?
本文中,使用的第三方模块信息如下:
- openai==1.3.2
- langchain==0.0.339
我们以函数get_weather_info
为例,其实现逻辑(模拟实现世界中的API调用,获取对应城市的天气状况)如下:
- def get_weather_info(city: str):
- weather_info = {"Shanghai": "Rainy", "Beijing": "Snow"}
- return weather_info.get(city, "Sunny")
该函数只有一个参数:字符串变量city,即城市名称。为了实现function calling功能,需配置函数描述(类似JSON化的API描述),代码如下:
- functions = [
- {
- "name": "get_weather_info",
- "description": "Get the weather information of a city",
- "parameters": {
- "type": "object",
- "properties": {
- "city": {
- "type": "string",
- "description": "The name of the city, e.g. Shanghai",
- },
- },
- "required": ["city"],
- }
- }
- ]
对于一般的用户输入(query),大模型回复结果如下:
- import json
- from openai import OpenAI
-
- client = OpenAI(api_key="sk-xxx")
-
- query = "What is the capital of france?"
- response = client.chat.completions.create(
- model="gpt-3.5-turbo-0613",
- messages=[{"role": "user", "content": query}],
- functions=functions
- )
- message = response.dict()["choices"][0]["message"]
- print(message)
-
- >>> {'content': 'The capital of France is Paris.', 'role': 'assistant', 'function_call': None, 'tool_calls': None}
此时function_call
为None,即大模型判断不需要function calling.
对于查询天气的query,大模型输出结果如下:
- import json
- from openai import OpenAI
-
- client = OpenAI(api_key="sk-xxx")
-
- query = "What is the weather like in Beijing?"
- response = client.chat.completions.create(
- model="gpt-3.5-turbo-0613",
- messages=[{"role": "user", "content": query}],
- functions=functions
- )
- message = response.dict()["choices"][0]["message"]
- print(message)
-
- >>> {'content': None, 'role': 'assistant', 'function_call': {'arguments': '{\n "city": "Beijing"\n}', 'name': 'get_weather_info'}, 'tool_calls': None}
此时我们看到了令人吃惊的输出,大模型的输出内容为空,而判断需要function calling, 函数名称为get_weather_info
,参数为{'arguments': '{\n "city": "Beijing"\n}
。
下一步,我们可以调用该函数,传入参数,得到函数输出,并再次调用大模型得到答案回复。
- func_name = message["function_call"]["name"]
- func_args = json.loads(message["function_call"]["arguments"])
- print("func name and args: ", func_name, func_args)
- func_response = get_weather_info(**func_args)
-
- final_response = client.chat.completions.create(
- model="gpt-3.5-turbo-0613",
- messages=[
- {"role": "user", "content": query},
- {"role": "assistant", "content": None, "function_call": message["function_call"]},
- {
- "role": "function",
- "name": func_name,
- "content": func_response
- },
- ],
- )
- print("answer: ", final_response.dict()["choices"][0]["message"]["content"])
输出结果如下:
- func name and args: get_weather_info {'city': 'Beijing'}
- answer: The weather in Beijing is currently snowy.
以上仅是function calling的简单示例,采用一步一步的详细过程来演示大模型中function calling如何使用。
在实际场景中,我们还需要实现中间过程的函数执行过程。
以下将介绍在OpenAI, LangChain中如何实现function calling。后面我们将使用的3个函数(这些函数仅用于测试,实际场景中可替换为具体的工具或API)如下:
- def get_pizza_info(pizza_name: str):
- # get pizza info by pizza name
- pizza_info = {
- "name": pizza_name,
- "price": "10.99"
- }
- return json.dumps(pizza_info)
-
-
- def get_weather_info(city: str):
- # get city weather info with mock result
- weather_info = {"Shanghai": "Rainy", "Beijing": "Snow"}
- return weather_info.get(city, "Sunny")
-
-
- def get_rectangle_area(width: float, length: float):
- # calculate the rectangle with given width and length
- return f"The area of this rectangle is {width * length}."
在OpenAI的官方模块openai
中实现function calling
的代码如下:
- # -*- coding: utf-8 -*-
- from openai import OpenAI
- import json
-
- client = OpenAI(api_key="sk-xxx")
-
-
- def get_pizza_info(pizza_name: str):
- pizza_info = {
- "name": pizza_name,
- "price": "10.99"
- }
- return json.dumps(pizza_info)
-
-
- def get_weather_info(city: str):
- weather_info = {"Shanghai": "Rainy", "Beijing": "Snow"}
- return weather_info.get(city, "Sunny")
-
-
- def get_rectangle_area(width: float, length: float):
- return f"The area of this rectangle is {width * length}."
-
-
- function_mapping = {"get_pizza_info": get_pizza_info,
- "get_weather_info": get_weather_info,
- "get_rectangle_area": get_rectangle_area}
-
-
- functions = [
- {
- "name": "get_pizza_info",
- "description": "Get name and price of a pizza of the restaurant",
- "parameters": {
- "type": "object",
- "properties": {
- "pizza_name": {
- "type": "string",
- "description": "The name of the pizza, e.g. Salami",
- },
- },
- "required": ["pizza_name"],
- }
- },
- {
- "name": "get_weather_info",
- "description": "Get the weather information of a city",
- "parameters": {
- "type": "object",
- "properties": {
- "city": {
- "type": "string",
- "description": "The name of the city, e.g. Shanghai",
- },
- },
- "required": ["city"],
- }
- },
- {
- "name": "get_rectangle_area",
- "description": "Get the area of a rectangle with given width and length",
- "parameters": {
- "type": "object",
- "properties": {
- "width": {
- "type": "number",
- "description": "The width of a rectangle",
- },
- "length": {
- "type": "number",
- "description": "The length of a rectangle",
- }
- },
- "required": ["width", "length"],
- }
- }
- ]
-
-
- def chat(query):
- response = client.chat.completions.create(
- model="gpt-3.5-turbo-0613",
- messages=[{"role": "user", "content": query}],
- functions=functions
- )
- message = response.dict()["choices"][0]["message"]
- print('message: ', message)
-
- function_call_info = message.get("function_call")
- if not function_call_info:
- return message
- else:
- function_name = function_call_info["name"]
- arg_name = json.loads(function_call_info["arguments"])
- print(f"function name and arg name: ", function_name, arg_name)
-
- if function_name in function_mapping:
- function_response = function_mapping[function_name](**arg_name)
-
- final_response = client.chat.completions.create(
- model="gpt-3.5-turbo-0613",
- messages=[
- {"role": "user", "content": query},
- {"role": "assistant", "content": None, "function_call": function_call_info},
- {
- "role": "function",
- "name": function_name,
- "content": function_response
- },
- ],
- )
- return final_response.dict()["choices"][0]["message"]
- else:
- return "wrong function name with function call"
-
-
- query1 = "What is the capital of france?"
- print("answer: ", chat(query1), end="\n\n")
-
- query2 = "How much does pizza Domino cost?"
- print("answer: ", chat(query2), end="\n\n")
-
- query3 = "What is the weather like in Beijing?"
- print("answer: ", chat(query3), end="\n\n")
-
- query4 = "calculate the rectangle area with width 3 and length 5."
- print("answer: ", chat(query4), end="\n\n")
输出结果如下:
- query: What is the capital of france?
- message: {'content': 'The capital of France is Paris.', 'role': 'assistant', 'function_call': None, 'tool_calls': None}
- answer: {'content': 'The capital of France is Paris.', 'role': 'assistant', 'function_call': None, 'tool_calls': None}
-
- query: How much does pizza Domino cost?
- message: {'content': None, 'role': 'assistant', 'function_call': {'arguments': '{\n "pizza_name": "Domino"\n}', 'name': 'get_pizza_info'}, 'tool_calls': None}
- function name and arg name: get_pizza_info {'pizza_name': 'Domino'}
- answer: The cost of a Domino pizza is $10.99.
-
- query: What is the weather like in Beijing?
- message: {'content': None, 'role': 'assistant', 'function_call': {'arguments': '{\n "city": "Beijing"\n}', 'name': 'get_weather_info'}, 'tool_calls': None}
- function name and arg name: get_weather_info {'city': 'Beijing'}
- answer: The weather in Beijing is currently experiencing snow.
-
- query: calculate the rectangle area with width 3 and length 5.
- message: {'content': None, 'role': 'assistant', 'function_call': {'arguments': '{\n "width": 3,\n "length": 5\n}', 'name': 'get_rectangle_area'}, 'tool_calls': None}
- function name and arg name: get_rectangle_area {'width': 3, 'length': 5}
- answer: The area of a rectangle can be calculated by multiplying its width by its length. In this case, the width is 3 and the length is 5. Therefore, the area of the rectangle is 3 * 5 = 15 square units.
在langchain
中实现function calling
的代码相对简洁写,function calling
的结果在Message中的additional_kwargs变量中,实现代码如下:
- # -*- coding: utf-8 -*-
- import os
- import json
- from langchain.chat_models import ChatOpenAI
- from langchain.schema import HumanMessage, AIMessage, ChatMessage
-
- os.environ["OPENAI_API_KEY"] = "sk-xxx"
-
-
- def get_pizza_info(pizza_name: str):
- pizza_info = {
- "name": pizza_name,
- "price": "10.99"
- }
- return json.dumps(pizza_info)
-
-
- def get_weather_info(city: str):
- weather_info = {"Shanghai": "Rainy", "Beijing": "Snow"}
- return weather_info.get(city, "Sunny")
-
-
- def get_rectangle_area(width: float, length: float):
- return f"The area of this rectangle is {width * length}."
-
-
- function_mapping = {"get_pizza_info": get_pizza_info,
- "get_weather_info": get_weather_info,
- "get_rectangle_area": get_rectangle_area}
-
-
- functions = [
- {
- "name": "get_pizza_info",
- "description": "Get name and price of a pizza of the restaurant",
- "parameters": {
- "type": "object",
- "properties": {
- "pizza_name": {
- "type": "string",
- "description": "The name of the pizza, e.g. Salami",
- },
- },
- "required": ["pizza_name"],
- }
- },
- {
- "name": "get_weather_info",
- "description": "Get the weather information of a city",
- "parameters": {
- "type": "object",
- "properties": {
- "city": {
- "type": "string",
- "description": "The name of the city, e.g. Shanghai",
- },
- },
- "required": ["city"],
- }
- },
- {
- "name": "get_rectangle_area",
- "description": "Get the area of a rectangle with given width and length",
- "parameters": {
- "type": "object",
- "properties": {
- "width": {
- "type": "number",
- "description": "The width of a rectangle",
- },
- "length": {
- "type": "number",
- "description": "The length of a rectangle",
- }
- },
- "required": ["width", "length"],
- }
- }
- ]
-
-
- def chat(query):
- llm = ChatOpenAI(model="gpt-3.5-turbo-0613")
- message = llm.predict_messages(
- [HumanMessage(content=query)], functions=functions
- )
- print('message: ', message, type(message))
-
- function_call_info = message.additional_kwargs.get("function_call", None)
- if not function_call_info:
- return message
- else:
- function_name = function_call_info["name"]
- arg_name = json.loads(function_call_info["arguments"])
- print(f"function name and arg name: ", function_name, arg_name)
-
- if function_name in function_mapping:
- function_response = function_mapping[function_name](**arg_name)
-
- final_response = llm.predict_messages(
- [
- HumanMessage(content=query),
- AIMessage(content=str(message.additional_kwargs)),
- ChatMessage(
- role="function",
- additional_kwargs={
- "name": function_name
- },
- content=function_response
- ),
- ]
- )
- return final_response.content
- else:
- return "wrong function name with function call"
-
-
- query1 = "What is the capital of Japan?"
- print("answer:", chat(query1), end='\n\n')
-
- query2 = "How much does pizza Domino cost?"
- print("answer:", chat(query2), end='\n\n')
-
- query3 = "What is the weather like in Paris?"
- print("answer: ", chat(query3), end="\n\n")
-
- query4 = "calculate the rectangle area with width 3 and length 10"
- print("answer: ", chat(query4), end="\n\n")
Assistant API
是OpenAI在今年OpenAI开发者大会中提出的创新功能。Assistants API
允许用户在自己的应用程序中构建AI助手。助手有指令,可以利用模型、工具和知识来响应用户查询。Assistants API
目前支持三种类型的工具:代码解释器(Code Interpreter)、检索(Retrieval)和函数调用(Function Calling)。
我们来看看,在openai中的Assistant API如何支持function calling。
- import time
- import json
- from openai import OpenAI
-
-
- def get_pizza_info(pizza_name: str):
- pizza_info = {
- "name": pizza_name,
- "price": "10.99"
- }
- return json.dumps(pizza_info)
-
-
- def get_weather_info(city: str):
- weather_info = {"Shanghai": "Rainy", "Beijing": "Snow"}
- return weather_info.get(city, "Sunny")
-
-
- def get_rectangle_area(width: float, length: float):
- return f"The area of this rectangle is {width * length}."
-
-
- function_mapping = {"get_pizza_info": get_pizza_info,
- "get_weather_info": get_weather_info,
- "get_rectangle_area": get_rectangle_area}
-
-
- functions = [
- {
- "name": "get_pizza_info",
- "description": "Get name and price of a pizza of the restaurant",
- "parameters": {
- "type": "object",
- "properties": {
- "pizza_name": {
- "type": "string",
- "description": "The name of the pizza, e.g. Salami",
- },
- },
- "required": ["pizza_name"],
- }
- },
- {
- "name": "get_weather_info",
- "description": "Get the weather information of a city",
- "parameters": {
- "type": "object",
- "properties": {
- "city": {
- "type": "string",
- "description": "The name of the city, e.g. Shanghai",
- },
- },
- "required": ["city"],
- }
- },
- {
- "name": "get_rectangle_area",
- "description": "Get the area of a rectangle with given width and length",
- "parameters": {
- "type": "object",
- "properties": {
- "width": {
- "type": "number",
- "description": "The width of a rectangle",
- },
- "length": {
- "type": "number",
- "description": "The length of a rectangle",
- }
- },
- "required": ["width", "length"],
- }
- }
- ]
-
-
- client = OpenAI(api_key="sk-xxx")
-
- assistant = client.beta.assistants.create(
- name="assistant test",
- instructions="You are a helpful assistant, ready to answer user's questions.",
- model="gpt-3.5-turbo-0613",
- )
- MATH_ASSISTANT_ID = assistant.id
- print("assistant id: ", MATH_ASSISTANT_ID)
-
- assistant = client.beta.assistants.update(
- MATH_ASSISTANT_ID,
- tools=[
- {"type": "function", "function": function} for function in functions
- ],
- )
- print("assistant id: ", assistant.id)
-
-
- def submit_message(assistant_id, thread, user_message):
- client.beta.threads.messages.create(
- thread_id=thread.id, role="user", content=user_message
- )
- return client.beta.threads.runs.create(
- thread_id=thread.id,
- assistant_id=assistant_id,
- )
-
-
- def create_thread_and_run(user_input):
- thread = client.beta.threads.create()
- run = submit_message(MATH_ASSISTANT_ID, thread, user_input)
- return thread, run
-
-
- def wait_on_run(run, thread):
- while run.status == "queued" or run.status == "in_progress":
- run = client.beta.threads.runs.retrieve(
- thread_id=thread.id,
- run_id=run.id,
- )
- time.sleep(0.5)
- return run
-
-
- def get_response(thread):
- return client.beta.threads.messages.list(thread_id=thread.id, order="asc")
-
-
- # Pretty printing helper
- def pretty_print(messages):
- print("# Messages")
- for m in messages:
- print(f"{m.role}: {m.content[0].text.value}")
- print()
-
-
- query = "How much does pizza Domino cost?"
-
- thread, run = create_thread_and_run(query)
- run = wait_on_run(run, thread)
-
- # Extract single tool call
- tool_call = run.required_action.submit_tool_outputs.tool_calls[0]
- name = tool_call.function.name
- arguments = json.loads(tool_call.function.arguments)
- my_response = function_mapping[name](**arguments)
- print("function response: ", my_response)
-
- # use function response for rerun
- final_run = client.beta.threads.runs.submit_tool_outputs(
- thread_id=thread.id,
- run_id=run.id,
- tool_outputs=[
- {
- "tool_call_id": tool_call.id,
- "output": json.dumps(my_response),
- }
- ],
- )
-
- final_run = wait_on_run(final_run, thread)
- pretty_print(get_response(thread))
输出结果如下:
- assistant id: asst_ElovRUJRLqBeYk2Gu2CUIiU9
- assistant id: asst_ElovRUJRLqBeYk2Gu2CUIiU9
- function response: {"name": "Domino", "price": "10.99"}
- # Messages
- user: How much does pizza Domino cost?
- assistant: The pizza Domino from the restaurant costs $10.99.
可以看到在openai模块中,在Assistant API中实现function calling,较为麻烦。而新版的langchain(0.0.339)中已经添加对Assistant API的支持,我们来看看在langchain中如何支持function calling。
实现代码如下:
- # -*- coding: utf-8 -*-
- # @place: Pudong, Shanghai
- # @contact: lianmingjie@shanda.com
- # @file: assistant_api_with_functions.py
- # @time: 2023/11/23 11:00
- import os
- import json
- from langchain.agents.openai_assistant import OpenAIAssistantRunnable
- from langchain.schema.agent import AgentFinish
- from langchain.agents import Tool
- from langchain.tools import StructuredTool
-
-
- os.environ["OPENAI_API_KEY"] = "sk-xxx"
-
-
- def get_pizza_info(pizza_name: str):
- pizza_info = {
- "name": pizza_name,
- "price": "10.99"
- }
- return json.dumps(pizza_info)
-
-
- def get_weather_info(city: str):
- weather_info = {"Shanghai": "Rainy", "Beijing": "Snow"}
- return weather_info.get(city, "Sunny")
-
-
- def get_rectangle_area(width: float = 1.0, length: float = 1.0):
- return f"The area of this rectangle is {width * length}."
-
-
- function_mapping = {"get_pizza_info": get_pizza_info,
- "get_weather_info": get_weather_info,
- "get_rectangle_area": get_rectangle_area}
-
-
- functions = [
- {
- "name": "get_pizza_info",
- "description": "Get name and price of a pizza of the restaurant",
- "parameters": {
- "type": "object",
- "properties": {
- "pizza_name": {
- "type": "string",
- "description": "The name of the pizza, e.g. Salami",
- },
- },
- "required": ["pizza_name"],
- }
- },
- {
- "name": "get_weather_info",
- "description": "Get the weather information of a city",
- "parameters": {
- "type": "object",
- "properties": {
- "city": {
- "type": "string",
- "description": "The name of the city, e.g. Shanghai",
- },
- },
- "required": ["city"],
- }
- },
- {
- "name": "get_rectangle_area",
- "description": "Get the area of a rectangle with given width and length",
- "parameters": {
- "type": "object",
- "properties": {
- "width": {
- "type": "number",
- "description": "The width of a rectangle",
- },
- "length": {
- "type": "number",
- "description": "The length of a rectangle",
- }
- },
- "required": ["width", "length"],
- }
- }
- ]
-
- tools = [Tool(name=func["name"],
- func=function_mapping[func["name"]],
- description=func["description"]) for func in functions[:-1]]
-
- tools.append(StructuredTool.from_function(get_rectangle_area, name="get_rectangle_area", description="Get the area of a rectangle with given width and length"))
-
- agent = OpenAIAssistantRunnable.create_assistant(
- name="langchain assistant",
- instructions="You are a helpful assistant, ready to answer user's questions.",
- tools=tools,
- model="gpt-3.5-turbo-0613",
- as_agent=True,
- )
-
-
- def execute_agent(agent, tools, input):
- tool_map = {tool.name: tool for tool in tools}
- response = agent.invoke(input)
- while not isinstance(response, AgentFinish):
- tool_outputs = []
- for action in response:
- print(action.tool, action.tool_input)
- tool_output = tool_map[action.tool].invoke(action.tool_input)
- print(action.tool, action.tool_input, tool_output, end="\n\n")
- tool_outputs.append(
- {"output": tool_output, "tool_call_id": action.tool_call_id}
- )
- response = agent.invoke(
- {
- "tool_outputs": tool_outputs,
- "run_id": action.run_id,
- "thread_id": action.thread_id,
- }
- )
-
- return response
-
-
- query = "How much does pizza Domino cost?"
- query = "What is the capital of france?"
- query = "What is the weather like in Beijing?"
- query = "calculate the rectangle area with width 3 and length 5."
-
- response = execute_agent(agent, tools, {"content": query})
- print(response.return_values["output"])
注意,函数get_rectangle_area
为多参数输入,因此需使用StructuredTool.
在OpenAI中的官网中,Assistant已经支持function calling.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。