# OpenAI函数调用

OpenAI的新函数调用API改变了一切,包括MyScale。

新的OpenAI发布

OpenAI刚刚发布了许多更新,包括更便宜的16K上下文GPT-3.5 Turbo和更多可用的GPT-4模型。我们还获得了一个名为函数调用 (opens new window)的新API接口,这对开发人员来说是一个强大的工具。

函数调用改变了我们处理提示的方式。已经有很多尝试自动化结构化查询和向量搜索的好方法,例如LangChain的SQLChain (opens new window)自查询检索器 (opens new window)。几乎所有这些方法都需要大量的提示,这意味着它们既昂贵又难以调整。OpenAI的新函数调用可以节省大量用于提示的令牌 - 您不需要使用多个长示例来演示函数的用法。

我们发现新发布的GPT-3.5(gpt-3.5-turbo-0613)与新的函数调用非常配合。只需几次尝试,我们就成功开发出了一个与LangChain的自查询检索器等效的提示。无需定义运算符和比较器,只需提示。您可以在github (opens new window)上看到我们是如何实现的。

# 开始之前...

  • 您需要安装一些依赖项:
pip3 install langchain openai clickhouse-connect
  • 在MyScale上注册一个帐户,并在MyScale控制台上免费创建一个集群。
  • 在控制台上获取您的连接详细信息。您将需要这些信息来运行笔记本。
  • 您还需要有效的OpenAI API密钥。

不知道怎么做?请查看我们的集群管理官方文档。

# 查看提示

为了简单起见,我们重用了LangChain的向量存储和自查询检索器的一些辅助函数。

# 创建向量存储并插入数据

我们为您提供了来自ArXiv的8篇论文的元数据和摘要 (opens new window) (感谢arXiv (opens new window)提供其开放访问互操作性)。更多数据可以在我们的AWS存储 (opens new window)上找到。

现在看看代码:

import json
from langchain.schema import Document
from langchain.vectorstores import MyScale
from langchain.embeddings import HuggingFaceEmbeddings
def str2doc(_str):
    j = json.loads(_str)
    return Document(page_content=j['abstract'], metadata=j['metadata'])
with open('func_call_data.jsonl') as f:
    docs = [str2doc(l) for l in f.readlines()]
vectorstore = MyScale.from_documents(
    documents=docs, embedding=HuggingFaceEmbeddings())

# 定义元数据列

在这里,我们借用了LangChain的_format_attribute_info函数。它帮助我们将AttributeInfo转换为适合提示的纯字符串。

您可以根据需要修改此代码以使用更多的数据类型和函数。您可以使用函数定义列,例如,length(metadata.categories)表示该类别的长度。在ClickHouse文档 (opens new window)上尝试找到更多有趣的函数,我们原生支持它们以及向量搜索。别忘了加入我们的Discord (opens new window),与我们分享您的新发现或新想法,我们将非常兴奋地与您一起改进它们!

from langchain.chains.query_constructor.base import _format_attribute_info, AttributeInfo
metadata_field_info=[
    AttributeInfo(
        name="metadata.pubdate",
        description="论文发表日期,需要使用`parseDateTime32BestEffort()`将字符串格式的时间戳转换为可比较的格式。", 
        type="timestamp", 
    ),
    AttributeInfo(
        name="metadata.authors",
        description="作者姓名列表", 
        type="list[string]", 
    ),
    AttributeInfo(
        name="metadata.title",
        description="论文标题", 
        type="string", 
    ),
    AttributeInfo(
        name="text",
        description="论文摘要", 
        type="string", 
    ),
    AttributeInfo(
        name="metadata.categories",
        description="arxiv类别",
        type="list[string]"
    ),
    AttributeInfo(
        name="length(metadata.categories)",
        description="arxiv类别的长度",
        type="int"
    ),
]
formated = _format_attribute_info(metadata_field_info)

# 函数调用构建过滤的向量搜索

与自查询检索器类似,基于函数调用的查询器也输出三个参数来调用向量存储的接口:

  • query:将在后续转换为嵌入的查询字符串。
  • where_str:用于执行过滤的通用WHERE字符串,但不包括WHERE关键字。
  • limit:返回前limit个元素,默认为4个。

这是我们使用OpenAI函数调用的方式:

import openai
# 简单吧?
query = "什么是贝叶斯网络?"
# 让我们对它做一些约束...
query += "请使用2013年2月之后发布的文章 "
# 还想要更多?关键词过滤器。
query += "并且摘要中包含`artificial`的文章 "
# 这样好多了!跨模态的论文...
query += "具有超过2个类别 "
# 我是一个计算机视觉的人,只想要一些计算机视觉的论文
query += "并且其类别中必须包含`cs.CV`。"
# 现在是结构化提示:
completion = openai.ChatCompletion.create(
    # 我们使用了新的gpt模型
    model="gpt-3.5-turbo-0613",
    # 温度设置为0以确保稳定的行为
    temperature=0,
    # 现在是函数调用:
    # 您需要:
    # 1. 为函数命名。
    # 2. 为您的函数提供描述
    # 3. 在名称和类型中定义参数
    functions=[{"name": "to_structued_sql",
                "description":
                ("将查询转换为查询字符串和过滤字符串以进行过滤。"
                 "在检查元素是否在列表中时,请使用`has(column, element)`。"),
                "parameters": {"type": "object",
                               "properties": {
                                   "query": {"type": "string"},
                                   "where_str": {"type": "string", },
                                   "limit": {
                                       "type": "integer",
                                       "description": "默认为4"}
                               },
                               "required": ["subject", "where_str", "limit"]
                               }
                },],
    # 调用的函数名称,如果为'auto',模型将自行决定
    function_call="auto",
    # 这是我们的老朋友:聊天完成提示...
    # 您可以编写一些规则来限制模型的行为。
    messages=[
        {
            "role": "system",
            "content": ("您需要提供`metadata`来构建SQL。"
                        "我将使用`parseDateTime32BestEffort()`"
                        "将字符串格式的时间戳转换为可比较的格式。"),
        },
        {
            "role": "user",
            "content": f"元数据:{formated}"
        },
        {
            "role": "system",
            "content": "现在您可以输入查询",
        },
        {
            "role": "user",
            "content": query
        },
    ],
)

然后进行搜索!

与LangChain自查询检索器完全相同。而且它更灵活 - 它可以编写任何SQL...甚至是用户定义的函数。没有限制,所以自己制定一个案例吧!

ret = vectorstore.similarity_search(**search_kwargs)
for r in ret:
    print(r)

# 使用函数调用的提示

以下是使用函数调用的一些建议:

  1. 可读性强的名称为LLM提供了更多信息,无论您是使用这种新的函数调用还是传统的提示。
  2. 如果您想对列设置一些规则,例如,在与其他值进行比较时进行类型转换,在我们的示例中,请使用列定义进行操作。规则应与相关数据紧密相关。
  3. LLM倾向于模仿其熟悉的数据之间的映射函数。就像这个演示一样,自然语言和SQL对LLM来说都很常见,所以结果超出了预期。

我们相信您可以发现更多!为什么不在Discord (opens new window)上与MyScale分享您的发现呢?

# 最后

我们相信这将是人们或智能系统与向量数据库一起工作的未来。我们对这样的升级和新功能感到非常兴奋,以开发一个更可扩展和稳定的AGI系统。🚀我们希望MyScale能帮助您成长和成功,我们将使这成为现实!立即加入我们的Discord (opens new window),收到一个温暖的拥抱!🤗 🤗