BETA
为满足大家对无限上下文对话的需求,我们推出了无尽模式。下面分别对这个项目的原理、局限性、参与内测条件进行一个简要说明。
由于无限制上下文不可避免地导致流量增大、token 消耗增加、计算量增大等问题,如果你加入了免费内测,我们希望你:
给予我更多的捐赠:根据前两天的观察,Endless Chat 的成本约为 Free Chat 的 3-5 倍
在内测群里积极反馈关于上下文的问题,这能帮助我们改进算法、控制成本
体验地址是 endless.chat.bnu120.space,就是比 Free Chat 的网址多了个 endless 前缀。网页暂时有密码保护,只是为了限制人数,在一段时间内先算算成本。扫码加入内测群,在群公告中可以看到最新的密码:
你可以想象 Endless Chat 为一个记忆力有限的人,它虽然能不停地跟你对话,它只能记住你的一部分信息,所以以下几种你不愿意发生的事情是有可能发生的(其中部分会在我们未来的更新中逐渐解决):
当你发送了一段很长很长的文本后,它把你们之前说的内容忘记了
仍然存在说一半话突然被截断的情况
说出之前说过的重复的话
我们在实现前面原理中谈到的第 3~4 点后情况会大有好转,但这需要时间,请大家给我耐心!谢谢大家!
众所周知,openai 对 ChatGPT 处理文本的长度进行了限制,具体来说就是,上下文+本次生成的内容总长度小于4096个token,所以无限长度上下文的对话本质上是不可能的,但正如大家在官方的 Web APP 中看到的那样,不限长度的对话是可能的 —— 建立在抛弃相关性较小的上下文片段的前提下。Endless Chat 致力于打造这种体验。
因为传给 ChatGPT 的上下文不能太长,所以我们必须缩减上下文,不可能全部对话记录都传过去。目前的方案是:
必须保留最近一条消息和自定义场景,且每条消息不得超过 3500 tokens,之和不得超过 3700 tokens (这是为了给生成回复留有足够的空间),若总长仍小于 3700 tokens,则继续从后往前添加上下文)
相关部分的实现代码如下:
xenc = tiktoken.encoding_for_model("gpt-3.5-turbo-0301")
MAX_SINGLE_MESSAGE = 3500
MAX_CONTEXT_LENGTH = 3700
def num_tokens_from_single_message(message):
# every message follows <|start|>{role}\n{content}<|end|>
return 4 + sum((len(enc.encode(v)) for v in message.values()))
def num_tokens_from_messages(messages):
"""Returns the number of tokens used by a list of messages."""
# every reply is primed with <|start|>assistant<|message|>
return 3 + sum(map(num_tokens_from_single_message, messages))
def get_messages_for_requests(messages_in: list[dict[str, str]]):
messages_out = []
total = 0
messages_in.reverse()
first_message = messages_in[-1]
if first_message["role"] == "system":
system_messages = [first_message]
n = num_tokens(first_message)
if n > MAX_SINGLE_MESSAGE:
raise InvalidRequestError("系统消息过长")
total += n
messages_in.pop()
# next message
first_message = messages_in.pop(0)
n = num_tokens(first_message)
if n + total > MAX_CONTEXT_LENGTH:
raise InvalidRequestError("系统消息+最新消息过长")
messages_out.append(first_message)
total += n
else:
system_messages = []
first_message = messages_in.pop(0)
n = num_tokens(first_message)
if n > MAX_SINGLE_MESSAGE:
raise InvalidRequestError("单条消息过长")
messages_out.append(first_message)
total += n
for message in messages_in:
n = num_tokens(message)
if n + total > MAX_CONTEXT_LENGTH:
break
messages_out.append(message)
total += n
messages_out.reverse()
return system_messages + messages_out, total
目前从大家反馈来看效果还不错(大家继续反馈关于上下文的问题哈),未来可能的改进方向有:
将先前的对话“总结”,发送总结和最近的数条聊天记录
为先前的对话建立 embedding 索引,在用户下一条消息出现时进行语义搜索,发送搜索结果和用户最近的两三条消息记录
总的来说,无非是用两种思路减短上下文:
筛选 —— 只传相关的上下文
压缩 —— 将必须的上下文用更简练的方式表述
目前只实现了简单的筛选。如果你有好的想法实现,欢迎告诉我 ~