原文参考我的公众号文章 ChatGPT Token 优化与突破长度限制双管齐下~
计算 Token
通过计算能够发现,中文是非常吃亏的,甚至是讲一个中文字拆分然后算 token 的(偏旁部首...),所以尽量用中文向 LLM 提问。
在线体验
https://platform.openai.com/tokenizer
代码里使用
NodeJS:gpt-3-encoder
Python:tiktoken
参考链接
- https://www.npmjs.com/package/gpt-3-encoder
- 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问答:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。