如何使用 OpenAI 最大上下文长度为 2049 个令牌?

Sea*_*ude 23 python openai-api

我想将各种 PDF 中的文本发送到OpenAI 的 API。特别是针对二年级学生的 SummarizeTL;DR 摘要API。

PyMuPDF我可以使用OpenAI 提示从 PDF 中提取文本。

问题:当令牌数超过允许的 2049 时,如何最好地准备提示?

  • 我是否只是截断文本然后发送多个请求?
  • 或者有没有办法对文本进行采样以“压缩”它以丢失关键点?

Jay*_*Jay 27

我遇到了同样的问题。这是我用来发送比 OpenAI 的 GPT3 令牌限制长得多的文本的策略。

\n

根据所使用的模型(达芬奇、居里等),请求最多可以使用提示和完成之间共享的4097 个令牌。

\n
    \n
  • 提示是您发送到 OpenAI 的输入,即您的“命令”,例如“总结以下文本”加上文本本身
  • \n
  • 完成是回复,即文本的完整摘要
  • \n
\n

如果您的提示是 4000 个令牌,那么您最多可以完成 97 个令牌。有关 OpenAI 代币以及如何计算代币的更多信息,请参阅此处。

\n

为了确保我们不\xe2\x80\x99t超过提示加完成的最大长度限制,我们需要确保提示(即您的文本)和完成(即摘要)放在一起始终适合4097令牌边界。

\n

因此,我们将整个文本分成多个文本块,独立地总结每个块,最后使用一个简单的" ".join()函数合并所有汇总的块。

\n

最大单词数 - 标记到单词的转换

\n

OpenAI 对代币的数量有固定的限制。然而,令牌与单词不同。因此,我们首先需要计算可以发送到 OpenAI 的最大字数。文档说:

\n

在此输入图像描述

\n

考虑到令牌与单词的比率,假设每个文本块有 5 个句子摘要,我们可以将大约2900 个单词发送到 OpenAI 的 GPT3。

\n
    \n
  • 每个请求的最大令牌数:4000 个令牌(留下 97 个令牌作为安全缓冲区)= 3000 个字
  • \n
  • 最大提示标记:\xe2\x80\x9c用五个句子总结以下文本\xe2\x80\x9d 有 7 个单词 = 10 个标记
  • \n
  • 返回摘要的最大标记(5 个句子):每句 20 个单词。5 * 20 = 100 个单词 = 133 个标记
  • \n
  • 文本块的最大标记:4000 - 10 - 133 = 3857 个标记 = 2900 个单词
  • \n
\n

文本分块

\n

我们可以从多种策略中进行选择,将整个文本分成更小的块。

\n

最简单的方法是通过在空格上分割整个文本来创建所有单词的单个列表,然后创建单词桶,其中单词均匀分布在所有桶中。缺点是我们可能会中途分割一个句子并失去句子的含义,因为 GPT 最终会独立于后半部分来总结句子的前半部分 \xe2\x80\x94 忽略两者之间的任何关系大块。

\n

其他选项包括分词器,例如 SentencePiece 和 spaCy\xe2\x80\x99s 句子分割器。选择后者会产生最稳定的结果。

\n

使用 spaCy 实现文本分块

\n

以下示例分割文本 \xe2\x80\x9cMy firstbirthday wasgreat。我的 2. 甚至更好。\xe2\x80\x9d 变成两个句子的列表。

\n
python -m spacy download en_core_web_sm\n
Run Code Online (Sandbox Code Playgroud)\n
import spacy\nfrom spacy.lang.en import English\n\nnlp = spacy.load("en_core_web_sm")\n\ntext = "My first birthday was great. My 2. was even better."\n    \nfor sentence in nlp(text).sents:\n  print(sentence.text)\n
Run Code Online (Sandbox Code Playgroud)\n

输出

\n
My first birthday was great.\nMy 2. was even better.\n
Run Code Online (Sandbox Code Playgroud)\n

spaCy 正确检测到了第二个句子,而不是在 \xe2\x80\x9c2.\xe2\x80\x9d 之后将其拆分。

\n

现在,让\xe2\x80\x99s 编写一个text_to_chunks辅助函数来生成句子块,其中每个块最多包含 2700 个单词。2900 个单词是最初计算的字数限制,但我们希望确保对于长度超过 1.33 个标记的单词有足够的缓冲区。

\n
My first birthday was great.\nMy 2. was even better.\n
Run Code Online (Sandbox Code Playgroud)\n
\n

OpenAI 最近推出了另一种确定文本标记数量的方法。该方法使用tiktokenOpenAI 的模型并针对其量身定制。

\n
def text_to_chunks(text):\n  chunks = [[]]\n  chunk_total_words = 0\n\n  sentences = nlp(text)\n\n  for sentence in sentences.sents:\n    chunk_total_words += len(sentence.text.split(" "))\n\n    if chunk_total_words > 2700:\n      chunks.append([])\n      chunk_total_words = len(sentence.text.split(" "))\n\n    chunks[len(chunks)-1].append(sentence.text)\n  \n  return chunks\n
Run Code Online (Sandbox Code Playgroud)\n
\n

接下来,我们将文本摘要逻辑包装到 summarize_text 函数中。

\n
import tiktoken\n\nencoding = tiktoken.encoding_for_model("gpt-3.5-turbo")\nnumber_of_tokens = len(encoding.encode("tiktoken is great!"))\nprint(number_of_tokens)\n
Run Code Online (Sandbox Code Playgroud)\n

我们的最后一段代码如下所示:

\n
def summarize_text(text):\n  prompt = f"Summarize the following text in 5 sentences:\\n{text}"\n\n  response = openai.Completion.create(\n      engine="text-davinci-003", \n      prompt=prompt,\n      temperature=0.3, \n      max_tokens=150, # = 112 words\n      top_p=1, \n      frequency_penalty=0,\n      presence_penalty=1\n  )\n\n  return response["choices"][0]["text"]\n
Run Code Online (Sandbox Code Playgroud)\n

参考

\n\n

  • 虽然这种策略可能适用于文本,但我认为它不太适用于代码重构,因为在优化/改进之前了解全局非常重要。我知道这不是这里的问题,但它是相关的,如果有人有任何想法或指示,我们非常欢迎。 (6认同)