LLM native策略的内部状态是否应该结构化 【2023Q3】
本文于2023.7.5于知乎首发,有删改。
1、场景介绍
先介绍一下本文讨论的问题场景,以传统的对话机器人为例。
考虑这样一个场景:通过对话机器人来订火车票。传统方案的最简单实现可以概括如下:
订火车票需要的信息是固定的,包括:乘车人、时间、车次、座别等。那么就可以在内部为该场景设计一个需要填充的表格,包含如上信息。
在跟用户对话的过程中,从用户的输入中提取相关的字段信息更新到表格中,并对表格中尚未被填充的信息向用户进行提问。不断重复该过程直到表格都已经被填满。
根据完整的信息表执行任务。
在这个场景下,虽然跟用户的交互渠道是非结构化的自然语言对话,但策略内部是将其提取为一个完全结构化的表示的:字段的设置是预先确定的,字段的值也是完全结构化的。
2、场景1:内部状态明确
如果业务的场景仍然是上述举例的订火车票,那么很明显内部状态是【明确】的。
这个明确的标准是:对于绝大部分case,都可以人工标注出内部状态的真值,来供ML算法优化。
如果发现做不到能够非常明确的标注,那么很可能意味着这个系统的内部状态并没有我们想象的那么标准化和结构化。
对于这种场景,那么内部状态仍然跟传统一样是结构化的。结构化虽然并不是唯一途径了,但有一些明显的好处:
容易调试和观察系统状态变化
可以在内部状态的层面进行人工标注来改善系统。
3、场景2:内部状态存在模糊性
举一个场景例子:通过与用户对话交流的方式生成一个简历模板。并且这个模板并不是从已有的模板库中选择、或者是重新组合出来,而是根据在沟通中用户所指示的那种“感觉”来“直接”生成一种定制的模板。
在这个场景中,本质上我们并没有一个完备的结构化中间状态表示。
3.1、半结构化表示
首先仍然尝试以一种结构化的方式来进行内部状态设计。我们仍然采用类KV的方式,使用map<string, string>的结构。其中Key的部分仍然是大部分由人工事先确定,也可以接受LLM自己产生一些Key。Value的部分无法结构化,首选方案是仍然使用自然语言文本。整体上就变成了一种半结构化的表示方式。
Key的设计方面,我们能够从平面设计的角度上确定一些维度,例如:布局、颜色、字体、空间、对比、平衡、一致性等等。但我们会发现很难精确的从与用户的沟通中准确的提取出对应的Value:
如何确定哪些信息该填入【布局】而不是【空间】呢?不同字段的界限如何划定?
可能就算是专业的设计师群体也并不能就99%的case都达成共识。
在对话中,用户对某方面进行了补充和修改,那么对应的Value该如何更新呢?更新后的真值该如何标注呢?
都靠专业的设计师进行“仍然不足够精确”的标注么?
系统第一版上线后,希望优化某些维度的“效果”,似乎用户提供的“某些信息”并没有能够被提取到中间状态中,我们该如何知道是哪些字段出了问题,以及该往哪些方面来调整呢?
这个系统的开发只能靠一个【平面设计师】兼【算法工程师】兼【LLM微调算法工程师】才能优化么?这个人就能够对他的判断有把握么?
我们也许能够凭感觉搭出第一版的方案,但这个系统中充满了太多的拍脑袋,系统开发者面对一个60分的系统很难明确后续的具体改进方向。
就算是有领域专家的加持,也仍然会在实操方面遇到很多问题:
要么领域专家 与 非领域专家的研发、产品、数据标注员之间会有明显的冲突 或 极大量的拉齐认知沟通
要么【领域专家兼其他岗位的跨界能力者】太难招聘,也太贵。即使这样我们也不能完全把这个问题标准化
因为本质上很多领域就是尚没有被完全标准化的状态:领域专家可以视为一个模糊的技能执行器,他在正常工作时没有问题,只要能够交付一个符合大家预期的结果就好。但他经常并不能把自己的能力量化的写下来,以及很多模糊的感受他自己也很难形容。
半结构化表示本质上仍然是我们在“结构化表示时代”思路的延续,但当我们考虑的表示方式有一部分开始变得模糊时,我们传统习惯的经验还仍然奏效么?还仍然是最佳的方案么?这似乎并不明确。虽然大家在这方面有一种说不清楚的模糊偏好,但这也许只是我们没能拆掉思维中已经过时的墙。
4、非结构化的状态表示方式
这里抛砖引玉,介绍一种非结构化状态表示的设计思路。
这个方案的起点很简单:如果连半结构化都存在问题,那么就还是直接用非结构化的方式就好了。我们非要提取出一个中间表示么?直接使用对话历史不是也可以做么?
在这里就会有一些质疑,我们分别讨论:
【Q1】如果用户对话太多,超过了LLM的context window怎么办?
【Q2】这种方式构建的系统又该如何优化?
4.1、【Q1】Context量的问题
这个问题又可以从两个方面考虑:
【Q1.1】目前的LLM的Context window还不够大,如果context超过了限制怎么办?
【Q1.2】用户对话历史中信息密度是否太低,都放进Context中是否会造成明显的浪费。
关于【Q1.1】:为多大的Context window进行设计?
目前开源的LLM的Context window确实还不够大,但GPT-3.5-turbo-16k的context window其实已经不小了。而且目前Context window的长度已经是整个社区当前关注的重点问题之一,在这方面的进步速度可能会比较快。
而且对于中文的场景来说,随着6月的几个中文基座LLM的发布,我们可以看到由于tokenizer更适应中文语料,在中文语料上训练的基座LLM在同等context window长度下对于中文信息的处理容量其实是比之前提升的了。现在的4k context其实已经对应了 4k/0.7 = 5.7k汉字的水平,这比之前的LLAMA1时代大了很多。
也许上层的策略设计者现在就得考虑调整自己的认知,不要把自己的设计限定在LLM的“早期时代”了。这就好比说:在内存容量快速提升的时代中,逐个字节的抠struct内存布局的意义已经没有那么大了。
我相信:在1年之内,商用LLM API在大部分场景下Context window已经够大,能够支撑大部分场景的99%的需求。
关于【Q1.1】:真的超过了基座LLM的能力怎么办?
虽然能够覆盖大部分需求,但仍然有一些case会突破这个限制,这时候有两个思路:
对于这个case,策略升级到使用更大context window(也更贵的)LLM模型。
使用LLM对这个context进行“压缩”,剔除掉其中与问题无关的部分、剔除掉已经被后续对话覆盖的旧信息。
显式地要求用户再开一个新的session,更简练的输入相关信息。
第一个方式较为简单,但可能主要适用于调用LLM API的场景。
第二个方式需要一些prompt工程调优,应该还是基本可行的。
第三个方式看起来很生硬,但其实在商业上是可行的。可以分为以下情况讨论:
第一种情况:用户在对话中不断修改了自己的要求,产生了大量冗余。这时候在新session中就没有这些问题了,用户对于这个场景了解的更明确了,实际上用户自己成长了。
第二种情况:用户并没有浪费沟通量,但用户确实知道如何更简练的表述,或者是舍弃掉一些他不看重的点。
这两种方式虽然都对用户体验有损伤,但损伤并不大。而且用户的再构建新session的成本并不算太高,用户并不用重新录入所有文字,可以从之前的对话历史中复制需要的部分过来。
4.2、【Q2】全系统的优化
【Q2.1】策略的拆解
首先需要提醒的是:“使用整个对话历史作为后续策略的输入”并不意味着“只能通过一步LLM请求解决这个问题”。我们仍然可以分步地初步解决一个复杂的问题,把原始的输入和中间的结果都输入给后续步骤即可。实际上,AutoGPT也是这么工作的。
当然这种方式会导致随着系统环节的增多,后续任务的输入context量越来越大,更容易超过基座LLM的context window。我们可以使用“让LLM从整个context历史分段的提取与目标子任务相关的信息重新组装一个context”的方式来压缩控制给下游context的量。不过这里目前还并没有一个低成本且无损的银弹,基本上还需要按需定制一个粗糙版本。
当然这个拆分并不是完全任意的,从前面的讨论我们就知道了一个标准:每步的结果最好应该是可以被人工精确标注的,或者是可以通过RLHF所需要的方式标注的。
【Q2.2】策略系统的优化
当我们不再依赖于一个结构化的中间表示之后,对全系统的优化思路就更加显然了:(对于每个步骤)进行一个端到端的优化。中间状态的表示重要么?不那么重要。更复杂的中间步骤已经有了各种XoT的框架。甚至我们可以设计一些方式通过RL的过程来自动探索一个更好的中间表达方式,只不过这个的成本真的挺高。
其实当我们有一个中间状态的表示时候,优化的思路也经常只能是这样来优化后续的模型,只是这个模型的输入是我们人工干预过后的一个结果,这里可能丢失一些信息。
相对于结构化或者半结构化的中间表示方式,这里的方式可能会丢失我们对于一些中间表达方式可以引入人工特征工程知识的优势。但这其实不是问题,我们可以旁路的加入类似的数据流,将这些特征工程后的信息也加入到后续流程中即可,这种方式并不排斥原有的经验,前提是它们真的也能在这里也提供性能增益。
我们也可以把一个步骤拆解为更多子问题来降低难度,提升最终的准确率,ReAct模式就是这样的。
交流与合作
如果希望和我交流讨论,或参与相关的讨论群,或者建立合作,请私信联系,见 联系方式。
希望留言可以到知乎对应文章下留言。