2024.10.3日OpenAI发布了ChatGPT web端的canvas功能,看起来很像是Claude之前的Artifact功能,不过仅有文本和代码,没有Artifact的UI显示。
但实际用起来会发现,canvas功能比Artifact更加丰富,canvas功能包括:
可以对文本生成侧边注释,并可以进行一定的交互。
用户可以直接编辑生成的canvas内容,甚至让它创建一个空canvas,自己复制内容进去。
而当我去研究了canvas的通讯协议之后,发现并非我原本所想,这个功能的实现跟artifact并不相似。此外,在我对其行为进行一些研究之后,发现能够观察到的细节颇多,所以才写此文进行讨论,以及作为后续一些文章的素材。
本文更多的是讨论其服务端的可能实现方式,而不是通讯协议细节,所以本文只会讨论其逻辑上的通讯过程。
相关阅读:
展望o1路线的可改进方向 中的3.5节,LLM decode阶段的workspace设计
1、回顾 Claude Artifact 的实现
在分析canvas之前,先回顾一下artifact的实现方式作为热身,以及作为一个实现更简单的同类功能,也有对比的意义。
目前的artifact的功能更像是response中结构化对象的一种新的展示方式。它的交互功能比较有限,可以选择部分内容进行继续提问等,此时选择部分会以文本形式插入到用户的问题中发送给模型。
实际上从Claude网页端的SSE流式返回的消息中也可以看到,实际上artifact对象是以一个特殊的HTML tag的格式返回的:
tag的开始标签会等到它生成完整之后一起返回,在此之前会有一个明显的延迟,并会在一个event中完整的返回tag的开始标签。
在标签内部,内容仍然是流式输出的。
整个对话中所有artifact的版本都在对话message历史中,同一个内容的不同版本使用相同的identifier关联在一起。
在前端显示的时候,会将这个特殊的tag进行artifact形式的UI展示。
这是一个比较简单的交互方式,如果模型能力足够强,内部大概没有太多额外的逻辑,除了artifact开始标签的处理。
1.1、Claude Artifact内部实现方式猜测
Artifact的开始标签如下
"xxxx" type="text/html" title="xxxx">
内部是否采用了某种特殊的可以直接生成artifact结构的方式不得而知,我观察到如下情况:
Artifact的开始标签是完整了之后一起给出的,而不是进入tag之后逐个属性字段生成。
给出开始标签之前的延时时间并不太长,这个过程中模型能够生成的token有限,大致跟开始标签的token数量接近。
我的猜测是:
内部仍然是以接近于上述原始tag的方式来输出的,但流式输出上对这个tag做了一个完整性校验。目的可能是为了避免小概率的生成错误导致UI直接展示不能正确显示的broken artifact内容。
其他有可能的内部实现,但我猜它没做:
在具体类型的artifact内部,有可能使用了基于该类型语法的约束解码,但我无法确认。
Artifact的生成过程中也可能使用了复杂的方式,例如与下面OpenAI canvas类似,或者像我前面说的LLM decode阶段的workspace设计等。
2、OpenAI Canvas 的逆向分析
2.1、Canvas 的通讯协议 及 表现
Canvas的协议相对来说就复杂得多了,在我来看它并不是artifact那样只是一种结构化元素的特殊显示,它的协议看起来更像是服务端在以SSE消息操作网页上的UI元素,会指名向特定组件发送消息。
我对它协议的观察如下:
【A1】对于Canvas内容的更新是结构化的,而不是以文本方式放在流式输出的消息中,是通过流式消息中独立的结构化的part来传递。
【A2】用户对于canvas的编辑,例如修改canvas的内容,删除侧边评论等。这些是通过独立的类REST API完成的。
【A3】用户的其他对于canvas引用及指令,canvas右下的指令等,通过特殊的结构化的message方式发送给服务器,并会被保存在message列表中,但不会显示给用户。对这些指令有兴趣的同学可以直接去抓这个原始消息列表查看,prompt其实写的很简单。
【A4】对于canvas内容的所有修改都会绑定独立的版本号,用户编辑、系统生成注释、用户对注释进行操作等都会增加版本号。
【A5】一次操作涉及多个注释时,每个注释都会导致版本号递增1
【A6】由于A2的存在,历史对话消息中没有直接保存所有的canvas版本,这部分版本仅在服务器端保存。
对于代码内容:
【B1】服务器端如果返回全文重写指令,仍然会在服务端对新旧内容做diff,按diff chunk数量来递增版本号。
【B2】服务器端如果返回多个替换指令,每个都会递增版本号。(一次替换的多次命中是否会独立递增尚未测试)
对响应模式进行观察,还有如下特性:
【C1】目前模型对于引用的canvas的版本似乎还有一些问题,我有遇到它引用错了版本,答非所问的情况。
【C2】对于文本内容,生成侧边备注时有时候会失败。有时明明页面上已经能够显示一些备注,但又出现生成失败被撤回的情况。
【C3】模型很容易变成使用英文回答,即使我前面的对话都是中文。估计是训练数据偏颇的问题。
【D1】对于代码的部分改写也经常会触发全文流式生成,而不是我直觉的生成片段替换。并且流式生成的过程和延时跟普通生成类似,看起来并不像是在服务端生成diff进行merge后再快速下发未修改部分。
【D2】触发代码的全文生成时,对于原始未涉及部分的保留做的比较好,但如果其中包含一些语法错误,在流式生成中会自动修正。
【D3】对于代码内容,生成侧边备注几乎都会失败,我没有遇到成功的时候。
【D4】对于代码内容,模型经常会过度触发侧边备注生成功能。
【D5】模型是可以在回答过程中产生对于canvas的修改的,但对于代码经常会无视指令要求在回答开始时就先产生对于canvas的修改然后再回答。应该是训练数据偏颇的问题。
【D6】对于代码内容的全文改写时,在前端会对于当前内容和旧版本的后续内容尝试做匹配,如果当前输出的行能够匹配上旧版本的行时后续未生成内容会直接链接旧版本。这个匹配过程不依赖于前后行数一致。
2.2、实现方式猜测
2.2.1、服务器端的workspace
很明显在服务器端有一个workspace保存了所有canvas文件的所有版本,我们都能在消息中看到每次修改的版本号。
在通讯过程中可以看到大部分对于workspace中文件的修改操作,但workspace的文件是如何在模型生成回答的过程中插入到prompt中的,不得而知。有两种可能:
所有文件的版本都以类似system message的方式一起交给模型处理。但这可能对context占用太多,以及版本多了容易造成模型混淆。这方面可以考虑做超长对话和文件测试,我目前没做。
每次生成对话时候,先触发tool调用,召回当前涉及到的文件版本,加入到message history的后部位置,然后进行回答。由于模型很喜欢一上来就先生成改写操作,且模型生成速度已经太快,不好通过流式token的延时来猜测是否存在这种召回tool的调用。
而且这个workspace是长生命周期的,与ChatGPT左侧的每个对话的生命周期一致。
2.2.2、服务器端是否做了原始patch到merge结果的转换
生成patch然后再merge到文件的方式有一些显著的优点:
在对长内容做少量编辑时候,等待时间和生成token量与内容总长无关
更容易保持对于未要求修改部分的一致性。
而canvas目前很喜欢做全文替换,所以“是否它在服务器端做了merge再下发结果”就成了一个分析的重点。我做了如下测试:
输入较长的代码,要求改写中间一部分。canvas目前仍然会按之前的速度生成整个内容,修改点前后都没有变快。除非服务端想要故意模拟这种缓慢的效果,否则没有必要把响应时间拖得更长。通讯协议里也支持对于片段的直接替换,在一些请求下服务端也可以下发这样的替换操作。
如前面D2所说,如果在代码其他位置引入一些语法错误,虽然这些错误与指令要求修改的部分不在一起,但canvas在全文生成时候仍然会把这些错误都修改掉,且这些额外的修改也会导致版本号递增。
我的猜测是:
目前是否是全文重写还是局部修改,是模型通过类似tool调用的方式自主判断的,而且判断的感觉不太准。
如果下发的是全文修改,大概率模型原始就是生成的全文,这样生成速度能够匹配上。
生成全文的时候,解码阶段大概率做了约束解码,确保语法正确。
目前看起来在全文改写的时候,仍然会在服务器端做独立的diff操作和逐个diff chunk的版本号累加,这与产品功能无关,这些中间版本也无法在web端查看旧版本时查看到,实现它的原因未知。可能是为了由此构造一些未来的训练数据的考虑。
虽然总是改写全文有点笨,但这种方式比较容易做语法约束,不容易生成语法错误的代码。如果生成的diff不对,就不太好修。不过其实生成diff时候也可以做流式的merge和语法检查,估计可能OpenAI现在没做这种有点麻烦的事情吧。整体来说对于自己模型的能力还是不太自信的,不过也可能是为了能用更便宜模型的考虑。
3、个人评论
3.1、产品方面
虽然canvas的产品功能看起来比artifact要多,但实际用起来感觉小问题还是不少的,还是OpenAI的那种“坑我先占了,但目前交付的方案就是不完善”的感觉。也不知道后续是否能够打磨的比较好。
产品功能细节上也有不少缺陷,例如明明可以一上来建立一个空canvas,让用户直接贴文字,但就是不提供功能,必须要用户自己通过文字prompt下达这个指令。
以及让模型做修改的时候,如果能够先显示diff,让用户确认变更了什么,以及是否接受变更,这是更好的体验,也没有那么难做。而现在我看着修改后的结果经常是一懵“之前是啥样的?它改了啥?”
虽然说某种意义上这已经是一个智能IDE或者编辑器的雏形,但实际用起来问题还太多,功能还太弱,暂时还抢不了cursor类产品的地位。未来不知道OpenAI到底想不想认真往这方面来做,OpenAI做的这种ChatGPT上的小feature半成品实在太多了。
不过它这一发布半成品,估计很多大厂小厂就知道往这上投入了。
3.2、OpenAI 给大家演示怎么做 Agent
在分析协议的过程中,最让我惊讶的是,这个协议与其说是LLM chatbot,不如说已经是更接近Agent的协议,也可以看成是一个远程操作UI界面元素的协议。
我不知道OpenAI内部怎么看这个设计,但在我来看无论它内部是否有这个认识,canvas和o1都是在朝着做Agent的方向前进。o1已经向大家证明了模型厂直接做联合优化的Agent可以到什么效果,canvas这边只要它想做也可以实现类似的效果提升(相对于应用层自己搞的方案)。
虽然它现在内部的实现还比较简单,但如果它或者别人就用同样的协议做了一个内部很复杂很完善的智能IDE或其他类Agent的东西,其他人又该如何追赶?说实话也就是这次它的一些特征还比较明显,我还能在这里分析一下。等我“担心”的那些实现真的都做了之后,外人又能从这种通讯协议上分析出多少信息呢?
之前大家对OpenAI的印象都是模型不好抄,但上层的应用还是抄起来很简单的。但现在我们已经越来越不能这么说了,OpenAI的产品正在积累越来越多的服务端黑盒策略,追赶者越来越多的无法靠分析,而是得靠自研。
3.3、赶工的痕迹仍然明显
整套功能看下来,我觉得模型之上的工程部分还是有一定复杂度的,这不是一个很短期rush出来的项目,我猜至少3个月前就已经启动了,大概就是Claude的artifact上线不久。
不过整体上canvas确实是相对artifact在整体设计上做的更好,artifact更像是一个补丁,而canvas是一个云端workspace+场景功能的产品。
不过就使用体验来说,我觉得问题还颇多,属于“虽然可以用,但得小心一点”。如果说之前的GPTs和Assistant API的产品功能还可以说是把“最核心的功能做了,主要面向小白用户”,而这次的完善度我觉得还不如前面这两位。而OpenAI打磨AI应用层产品的能力是一直让我担心的。
OpenAI现在的特色就是整体设计好,细节实现稀烂。可能这也反映了其内部人员的能力结构?还是说我的要求太高了呢?
网友评论