每一个听说我造这么一个轮子的人都会有这样的疑问:
- promplate 到底解决了什么问题?
- 在大模型和提示框架百花齐放的当下,重新造一个轮子的意义何在?
我一个一个回答。
promplate 解决了什么问题?
上面这句是我为promplate整个生态撰写的slogan。什么意思呢,我希望它是一个能帮助到无论是非开发者,还是初学者,还是提示工程师的任何一个人。
- 对于普通人(非开发者),可以在我的在线编辑器playground上测试提示词。每个人都可以是发挥自己的创意
- 对于初学者,可以用极简到一个单个形参的函数直接调用我的免费API来开始提示工程之旅。换言之,0门槛
- 对于提示工程师,我的库具有非常大的可自定义度,它最初只是实现了我心目中的提示工程的最佳实践范式。在其上的开发和元开发都无比简单
认识我的人可能知道,我是一个对代码有着极端的洁癖的人。因此我在开发这个库的时候,是极大程度上地用效率换取对上述优势的保证。显然,要做到上面提到的那样“老少咸宜”,也就是说效率和灵活性要得兼,是非常不容易的。我通过发布一个保证灵活性、保证优雅实现、尽可能提高效率的core包,和一个约定式的提供各种效率工具的toolkit包来做到这一点。
重新造轮子的意义是什么?
造轮子,当然是因为现在没有我理想的轮子。
现在有什么轮子?有大名鼎鼎的 langchain,但是我认为它太大而杂了。这只是从优雅层面的批评,更重要的是:
- 开发效率低。我认为它抽象程度不够,灵活性不够,约定式的预设也不够用,元开发也难以进行。总之经过我在两个公司的算法岗的实践,我觉得它在不少场景下开发起来不如不用框架。我不是贬低它,只是认为它的意义更多的在于历史意义。langchain 就好比旧 React 和 Vue 2,他们在历史上有很高的价值,但是他们现在看来效率并不高。我要做的就是一个 Svelte,专注于开发效率,更本质上,是探索提示工程的最佳实践。
- 臃肿,与其它框架冲突。比如,我装一个 langchain 可能并不会用上任何本地的模型,但是它也会安装一个 1GB 的 torch 包;再比如它依赖于旧版本的 pydantic,这使得我没法把它集成到我需要 pydantic 2.x 的 WEB 服务中。
- 源码充斥着向后兼容,难以阅读。而理解源码几乎是通过继承 langchain 的类来二次开发的必经之路。丑陋的源码就意味着低灵活性,我一直这么认为。事实上,我一直认为,0.x 这样的版本号就是为了无负担地探索无限可能,而不是拖着沉重的身躯爬行。langchain 顶着 0.0.x 这样的版本号,我不认为它应该做像现在这样的向后兼容。如果这是开发者的炫技,我觉得这是害了这个项目。
所以 langchain 不是足够好的框架。还有什么呢?有,出身 Microsoft 的 guidance。它其实给了我不小的启发,它发明了一种模板语言,一个提示工程就是一个脚本,可读性挺高的。我非常欣赏它的尝试,它的模板语言能力丰富,也高效,另外它在提示工程的周边也做了很多实用工具(我自己做不出来的那种),代码也优雅得多,我非常欣赏这个项目。
但它也不够完美,因为它把整个 chain 写在一个模板中,我认为这过于激进了,或者说过于“约定式”了,灵活性在一些方面降低了。比如,将逻辑写在模板中,就没有语法高亮了;另外我也认为这不符合代码和模板分离的原则。promplate 作为后起之秀,会关注到这些坑,并尽可能避免。
除此之外
我一般把“通过重载各种语法,实现一些方便的调用方式”这种实践也称为元编程。我在 promplate 这个框架中也加入了一些“有底线的激进”的元编程,比如:
- 自动命名:一个模板或者chain的节点的实例,在__str__的时候会显示一个名字,而这个名字默认就是它初始化时赋值给的第一个变量名
- 用算术运算符描述一个流程:重载了算数运算符,以实现直观地组建一个 chain 的效果
- import 一个 template:直接用原生的语法 import 一个模板,算是一种语法糖(但是有一些副作用)
- 用 raise 的方法控制流程:这使得 chain 成为有限状态机,相当于一个 agent
回到最初的问题
为什么我要开发 promplate?显而易见,这是一个目标为提示工程的最佳实践的激进探索,探索成功了利己利人,失败了呢……我会谨慎行事,不允许失败 哈哈