Lisp 语言在提示词编程中的应用具有以下特点:
李继刚写的提示词有个特点——它们是用Lisp语言编写的。比如,[下面这段提示词](https://mp.weixin.qq.com/s?__biz=MzkxMzc1NzM1Mw==&mid=2247483803&idx=1&sn=485c2f271172a7efcb0735310f8ff191&scene=21#wechat_redirect):Lisp是一门“古老”的编程语言。Lisp语言有个特点——它的语法非常地简洁且优美。它的语法核心是List结构,或者叫“S表达式“,其典型特征就是需要用一对括号把元素括起来。List结构里又可以嵌套List,所以Lisp程序呈现出来的样子就是一对对括号里嵌套着一对对括号嵌套着一对对括号。List结构使得Lisp的语法足够简洁,同时又有非常强的表达能力。这两者兼备,才可以称得上优美。也许你会觉得,语言最重要的功能是传递语义,语法其实没那么重要。如果你是程序员,你就不会这么觉得了。对编程语言来讲,syntax matters[1]。对于大语言模型来讲,语法重要吗?看了李继刚的提示词,你可能会有一个随之而来的疑问:有必要用Lisp语言写提示词吗?接下来,我们就以“小确幸”这段提示词为例子来解构一下。
我们平时写提示词用的更多的是Markdown语法。Markdown语法很简单,并且大语言模型能很好的“理解”标题、列表、加粗强调等这些语法。用Markdown写提示词不是更直白吗?我们把这段Lisp提示词翻译成Markdown试试效果。Markdown版本与Lisp版本的提示词在语义上几乎是一致的。只是中间我多加了一句“一步步思考,严格按照大步骤以及处理流程执行。”因为不加这句,Claude还是不能保证会逐步执行。下面是Lisp版本提示词的输出效果。不知啥原因,我通过API调用Claude,输出效果很难达到李继刚文章中那种水平,用网页版也许会好一些。下面是Markdown版本的提示词输出的结果:对比下两者的效果,可以发现一个明显差异:在Lisp版本中,SVG图形的丰富度和表现力稳定地优于Markdown版本。这是个意外发现!我们会在后面再细细探讨。Markdown版本与Lisp版本提示词的另一个重要差异在执行过程,它会输出中间“思考”过程。小确幸这个函数的所有子步骤都被充分展开并且按顺序执行了。而且,由于大语言模型的自回归机制,前面步骤的输出,会自然地被作为上下文传入给后面的步骤。虽然在这个任务中,从文本处理后的输出结果上可能看不出太大差异,但在多数场景下,这样一步一步思考是会有正向收益的。除此之外,让大语言模型将“思考”过程外化出来后有一个很大的好处,就是你可以调试优化这个流程。从过程输出中你可以看到哪些步骤生成了有用的增量信息,哪些步骤是无用的。另外在调试过程中,你还可能从大语言模型的输出中发现新的灵感。然而,用Lisp版本的提示词,很难让大语言模型这么有条理地执行一个流程。
除去角色设定部分(村里有爱函数),“小确幸”这段提示词主要做了两件事:一、对用户输入的文本进行处理,生成小确幸表达(小确幸函数);二、生成SVG卡片(SVG-Card函数)。实际上它用Lisp语言描述了一个简单的工作流。小确幸这个函数内部又包含一系列的子步骤:不幸中的幸运→显微镜视角→独特经验→意外偶然→肯定生命→温柔感受→温暖表达→提炼一句。作者期望大语言模型能按照这些子步骤对用户输入进行逐步处理,输出一条响应。函数里还定义了一些few-shot examples,以供大语言模型学习。最后,这个函数里调用了SVG-Card函数,传入响应作为参数。SVG-Card函数根据响应完成生成SVG卡片的任务。这短短的一段文本里竟然压缩了这么多信息!这一方面是编程语言自身的特性所致,因为编程语言特别擅长描述程序(procedure)。另一方便则体现了Lisp这门特殊的编程语言的优势,它特别简洁,且特别有表达力。程序很难懂,因为你在读一段代码的时候,需要做大量的解码。并且,你得按照程序解释器运行程序的逻辑去解读代码。那么,大语言模型能“读懂”这段程序吗?你把这段程序丢给AI,让它去解释,它应该能解释的很好。但是,把这段程序作为提示词,让大语言模型去解释并按照程序描述的逻辑去运行,很难。这段程序还相对简单,如果程序涉及到更复杂的函数调用,大语言模型更难处理,因为大语言模型就不是用来做这件事的。术业有专攻,这是解释器的活。这就带来一个问题:怎么保证程序里定义的子步骤被按照正确顺序且无遗漏的执行了?