头图

原文参考我的公众号文章 ChatGPT Token 优化与突破长度限制双管齐下~

计算 Token

通过计算能够发现,中文是非常吃亏的,甚至是讲一个中文字拆分然后算 token 的(偏旁部首...),所以尽量用中文向 LLM 提问。

在线体验

https://platform.openai.com/tokenizer

代码里使用

NodeJS:gpt-3-encoder

Python:tiktoken

参考链接

  1. https://www.npmjs.com/package/gpt-3-encoder
  2. https://community.openai.com/t/gpt-3-encoder-now-available-in...

优化 Token

  • 1.优化提示词
  • 2.总结上下文
  • 3.text-embedding

优化提示词

  • 用英文提问,英文回答。将回答翻译成需要展示的语言。
  • 精简提示词,去掉无必要符号。
  • ...

这个就要根据经验并结合提示词技巧来做提示词优化了,但是效果比较一般,不适合长对话场景。

处理上下文

对于长对话聊天来说,随着问答轮数越来越多,每次携带的上下文内容也就越多,很容易达到单次模型提问时的
token 上限。我一般采用两种操作:

1.最近上下文:当记录超过 N 条后,只取第一条和最后 M 条作为对话上下文;

2.压缩上下文:当记录超过 N 条后,向 LLM 发起一个「总结聊天内容」的对话,它的回答也就是对话历史的总结
摘录一段 LangChain 的总结上下文提示词:

Progressively summarize the lines of conversation provided, adding onto the previous
summary recrning a new summary.
EXAMPLE
Current summary:
The human asks what the AI thinks of artificial intelligence. The AI thinks artificial intelligence is a force for good.
New lines of conversation:
Human: Why do you think artificial intelligence is a force for good?
AI: Because artificial intelligence will help humans reach their full potential.
New summary:
The human asks what the AI thinks of artificial intelligence. The AI thinks artificial intelligence is a force for good because it will help humans reach their full potential.
END OF EXAMPLE
Current summary:
(summary}
New lines of conversation:
(new_lines}
New summary:

text-embedding

这种方式适用于针对特定文档或已有文本资料进行提问,比如企业内部文档、各种说明文档等。可以通过 openai 官方的 textembedding 接口处理,也可以使用 llama-index(都是需要 openai key 的)。

主要流程就是:

1.text-to-vec:使用 embedding 技术,把目标文档或知识库转换成向量,可以存在本地磁盘,也可以存储在专业的向量数据库(如:Qdrant)

2.prompt-to-vec:依然是用 text-embedding 技术,把用户发送的提示词转成向量,记作“VP”。拿 VP 在本地磁盘或者专业向量数据库中检索,用一种叫做“余弦相似度”的技术,把相关内容匹配出来(相似度 0 ~ 1,可以定义相似度的值进行结果过滤)。

3.newprompt-to-gpt:第 2 步中匹配的内容是一条条按照相似度降序排列的记录,每条记录里都包含了「原始文本-向量」的映射。我们只需要取每条记录的「原始文本」,把它注入到新的 Prompt 中,作为最终向 LLM 模型提问的提示词。这样就实现了「针对有效内容的自然语言提问」,节约了 token。

newPromptLike = `We have the opportunity to refine the above answer 
(only if needed) with some more context below.
------------
{similar_context_msg}
------------
Given the new context, refine the original answer to better 
answer the question. 
If the context isn't useful, output the original answer again.`;

在 text-embedding 过程中有一些细活儿需要注意,比如:为了控制每次 embedding 的 token 数量,以及匹配出来文本量的大小,要给文档内容分段,分段的策略也会影响匹配结果的质量。

下面找了一张网上的图,流程很清晰。

一个 QA 文档 embedding 处理示例

以下是我通过 openai 官方 python 包 实现的 text-embedding 过程,整体分为两部份「Build」和「Query」。

1.build:将文档向量化处理

import pandas as pd
import tiktoken

from openai.embeddings_utils import get_embedding

# embedding model parameters
embedding_model = "text-embedding-ada-002"
embedding_encoding = "cl100k_base"  # this the encoding for text-embedding-ada-002
max_tokens = 8000  # the maximum for text-embedding-ada-002 is 8191

readfilename = 'qawiki'; # 原文档名称
savefilename = 'qawiki_with_embeddings'; # embedding后的文档存储名称

# load & inspect dataset
# to save space, we provide a pre-filtered dataset
input_datapath = f"data/{readfilename}.csv"
# 读取csv内容,并将第一列作为索引列
df = pd.read_csv(input_datapath, index_col=0)

# 从 DataFrame 中选择的列的列表
df = df[["Question", "Answer"]]
df = df.dropna()
df["combined"] = ("问: " + df.Question.str.strip() + "答: " + df.Answer.str.strip())
print(df["combined"])
df.head(2)

# subsample to 1k most recent reviews and remove samples that are too long
top_n = 1000

encoding = tiktoken.get_encoding(embedding_encoding)

# 省略太长而无法嵌入的评论
df["n_tokens"] = df.combined.apply(lambda x: len(encoding.encode(x)))
df = df[df.n_tokens <= max_tokens].tail(top_n)
len(df)


# Ensure you have your API key set in your environment per the README: https://github.com/openai/openai-python#usage

# This may take a few minutes
df["embedding"] = df.combined.apply(
    lambda x: get_embedding(x, engine=embedding_model))
df.to_csv(f"data/{savefilename}.csv")

2.query:搜索向量

from openai.embeddings_utils import get_embedding
from sklearn.metrics.pairwise import cosine_similarity
import pandas as pd
import ast
import numpy as np

input_datapath = "data/qawiki_with_embeddings.csv"
df = pd.read_csv(input_datapath)

# 将 embedding 列中的字符串转换为浮点数数组
df.embedding = df.embedding.apply(lambda x: np.array(ast.literal_eval(x)))

def search_reviews(df, product_description, n=3, pprint=True):
    # 获取问题的embedding
    embedding = get_embedding(
        product_description, engine='text-embedding-ada-002')

    # 计算embedding cosin相似度
    df['similarities'] = cosine_similarity(
        np.array(df.embedding.tolist()), np.array(embedding).reshape(1, -1))
    res = df.sort_values('similarities', ascending=False).head(n)
    return res

msg = input("有什么需要帮助的吗?\n")

# 输入「exit」退出程序
while msg != 'exit':
    # 获取查询结果
    res = search_reviews(df, msg, n=4)

    print('\n')
    print(res)
    print('\n')

    # 我需要把符合相似度条件的记录中的combined字段拼接起来,存储在relativecontent中
    relativecontent = ''
    for row in res.itertuples():
        # print('similarities: ' + str(row.similarities))
        if row.similarities > 0.7:
            relativecontent += row.combined + '\n'
        else:
            print('drop a row of low similarities: ' + str(row.similarities))

    # print(relativecontent)

    newprompt = f"你是公司的AI客服,请结合'''里的内容回答我的问题,不需要解释。如果'''里的内容没有用处,请输入原始回答。\n内容:\n'''{relativecontent}'''\n\n请问,{msg}"

    print('新的提示词\n')
    print(newprompt)

    # TODO
    # answer = callGPT(newprompt)
    # ...

    # 等待继续输入
    msg = input("\n\n还有什么问题?")

QueryEmbedding结果:

GPT3.5问答:


Believer
47 声望5 粉丝

无法忍受尘世间的丑 便看不到尘世间的美