在之前的文章中,我提到未来的编程会是自然语言的编程。在这一好奇心的驱使下,我着手实践,试图打造一个完整的闭环,来让用户可以通过输入一段带有编程性质的自然语言,就可以指挥系统完成输入中指定的任务。在经过昨天的思考后,今天我用1个小时实现了一个粗糙的样例,而实现的过程,主要是借助了LLM来帮我发明一个DSL,并让它帮我实现一个可以编译该DSL的compiler。接下来,我就详细讲解一下,我是怎么利用AI,把AI作为产品基础依赖实现这一过程的。
效果预览
上面的演示图片中,我首先在左侧输入框中输入了一段有明显编程暗示的自然语言文本,在文本中使用@来调用某个Agent。当我提交这段文本后,可以获得右侧弹出层内的结果,具体内容如下:
把用户发送过来的数据用@data_extract (AI数据提取)提取出其中的年份,以及每年的产量。将提取出来的数据用@data-transformer (数据格式化)转化为JSON格式。
经过翻译后的DSL如下:
# 定义Agent agent_data_extract = Agent("@data_extract", 6) agent_data_transformer = Agent("@data-transformer", 9) # 定义任务 task_extract_data = Task(agent_data_extract, "提取用户数据中的年份和每年产量") task_transform_to_json = Task(agent_data_transformer, "将提取的数据转化为JSON格式") # 定义流程 flow_data_processing = Flow([ task_extract_data, task_transform_to_json ]) # 运行流程 execute flow_data_processing
你可以看到,AI理解了我输入的自然语言,以及对该文本进行了意图理解和语句分析,最终得到了一份看上去完全符合预期的DSL,并且从Task的任务内容来看,AI并不是简单的把文本进行拼凑,而是按照自己的理解进行了重新输出。
思考过程
我以前有实现过DSL的经历,在 https://www.tangshuang.net/8445.html 一文中,我实现了一门用于Restful API的查询语言,可见我自己是有能力去完成一门DSL的。在播客《Robust:程序员的TALK PLACE》中,我还讲过基于这一思路去实现中文编程也不是梦。另外,请关注我的微信 wwwtangshuangnet 持续接收最新技术干货。不过,从设计到实现一门DSL,还是比较熬人的。更何况我们设计的DSL都是有严格语法的,在面对上述自然语言的编程目标来说,还是毫无头绪。
我的第一个想法是,既然我自己无法完成从自然语言到具体代码的过程,那么能否让AI帮我完成呢?从我之前的文章中,你大概可以了解到,市面上目前还是以项目级别的代码生成比较多,例如最近刚火的Devin,而能够完成细粒度的任务编排的,比较少。
既然没有现成的轮子可以用,又没时间自己造,于是我的第二个想法冒出来:让AI帮我把自然语言转化为设计好的DSL,再把DSL丢到解释器中执行,这样不就完美解决了吗?
激动的手,颤抖的心,说干就干。
实践过程
万事开头难,自然语言到DSL,让AI来翻译,嗯……怎么开始呢?
首先,设计一门DSL。
我一开始想到自己之前的经验,既然是DSL,那么就向世界上最伟大的SQL对齐,发明一些命令、指令,比如SELECT xx TODO xx之类的。然而,很快我就打消了这种命令式DSL的语法设计,因为在我的设想中,用户是希望去完成一项任务,而完成任务一定意味着时间,也就是说,这门DSL的执行过程,一定是具有时间流动性的,而命令式的语言根本不存在这一概念。
既然如此纠结,不如让AI帮我设计DSL。我假想并整理了几个例子,把这几个文本丢给AI,并让它总结其中规律,为我发明一种DSL。
过了不到5秒钟,它返回了它设计的结果给我:
一看到开头的“基本元素”部分,我就有点兴奋,挺像那么回事的。仔细观察之后,我不大满意,告诉它突出“流程”“任务”的概念,也是10秒钟不到的时间,它给了我第二版:
这一版非常棒,概念明确了,但是我仍然希望引入“事件”来实现上一篇文章说的解耦问题。于是继续让它调整,最终我得到了感觉不错的一个版本。它给了“词汇表”“语法”“示例”“注意事项”4个部分,而且看上去非常专业。我让它导出为一个txt文件给我,之后我下载到了本地保存。
就这样,我花了大概20分钟左右,就设计好了一门基本符合我需求的DSL了。
接下来,自然语言转换为DSL。
当我们拿到用户输入的自然语言文本之后,我们把它提交给AI,让AI帮我们转化为DSL即可。
不过我们需要让AI较为准确的识别用户自然语言与DSL之间的对应关系。我们都知道LLM具有幻觉,有的时候转换并不准确。为了提高这种准确性,我们可以通过大量样本对模型进行微调,成本稍大。另一种方法则是提供更多的examples,利用LLM的推理能力,它可以在examples中找到规律,从而给出匹配度更高的结果。
在提交给AI时,我们在system前置预设中,把AI预设为DSL转化角色,把下载下来的DSL语法定义文件作为前提,把用户输入作为user消息进行提交。在AI返回的结果中,我们直接提取出代码部分,最终就得到了我们想要的DSL。
最后,执行DSL。
正如我前文说的,解释执行DSL其实也挺熬人的,不过,我们现在有了AI,我们可以让AI帮我们完成compiler的编写。
不过可惜的是,由于目前所有大模型工具在tokens上都有限制,我并没有得到完整的代码内容。此时,我们就可以利用Devin这类AI编程机器人帮我们来完成整个代码的编写,然后再自己对代码做微调。
由于这部分我还没有实现,这里就不继续深入了。
遗留问题,如何解决?
在上文我给的DEMO中,其实还有遗留问题,就是Task的内容还是自然语言。
task_transform_to_json = Task(agent_data_transformer, "将提取的数据转化为JSON格式")
这显然也无法被真正用来作为执行的内容。不过,有了上述的实战经验之后,解决这部分的办法就很清晰了。我们仍然要让AI帮我们确定该任务调用的方法是什么。
首先,我们需要为每一个agent定义方法,例如:
agent_data_transformer .toJSON: 转化为JSON格式 .toXML: 转化为xml格式 agent_other .method1: 用作什么未曾预料的用途 .method2: 或许在遥远地方会有另外一个作用
接下来,我们将此agent方法定义文档交给AI,并让AI根据Task中的参数和自然语言文本,确定实际应该调用什么方法,并让AI输出对应的方法名。
这样我们就可以得到如下形式的结果:
task_transform_to_json = Task(agent_data_transformer, "toJSON")
这样,我们就得到了真正的可用于解释执行的DSL代码了。
结语
在过去的1小时里,我的大脑在燃烧,然而,我并没有像以往一样,因为DSL设计、代码编写、算法、目录结构、状态管理等等问题纠结,我把这些烧脑的工作丢AI帮我完成,而我主要是思考怎么让AI理解我的意图和目标,怎样让它给到我令人满意的结果。之前我有一篇文章讲,李厂长的话要听,如今看看,真的是有道理的。
2024-03-26 2041