几个月前,在 Thoughtworks 的内部 AIGC 研讨会里,我们一直达成了一系列一致观点,诸如于:如果没有 “开源模型” 降低企业应用 LLM 的成本,那么 LLM 会很快消亡。所以,我们相信开源 LLM + LoRA 微调会成为企业的一种主流方式。现今,我们可以看到 LLaMA 2、Code LLaMA 2 等模型在不断刷新这种可能性。
而在模型不是问题之后,作为架构师、开发人员,我们应该致力于:将 LLM 以工程化的方式落地。于是,在过去的几个月里,我们开发了一系列不同领域的 LLM 应用 PoC,尝试从不同的角度思考如何构建好 LLM 应用。诸如于:
其它的一些问题,还包含如何通过小模型、传统 LLM 降低大模型花费?每个问题都是一个比较有意思的问题,也是我们在落地时要考虑的。
已经有大量的企业尝试使用 Python + LangChain 去构建知识增强工具的 PoC。从工程的角度意味着,我们需要考虑:
而因为 Python 的动态特性,影响了 IDE 的智能分析,进而影响了开发效率 —— 哪怕是有了 Pydantic 这样的类型库。于是 ,我对于语言的第一个考虑是:与企业现有基础设施相结合。特别是,我现有的各种库、框架使用的都是 JVM 语言编写。
于是乎,我们使用 Java/Kotlin、TypeScript、Rust 语言开发了不同类型、不同场景下的应用,以查看不同语言是否能构建起 LLM 应用。
从现有的体系来看,主流的编程语言都具备深度学习相关的代码库。我们可以以 Python 生态体系下,作为一个参考示例。诸如于:
但是在大部分场景下,我们并不需要什么 AI 基础设施。作为一个基本的 AI 应用,可能只需要的是计算 Token 长度这样的功能,以便于避免无谓的 LLM 花费。
在这时,而 Tiktoken/Tokenizer ****便是我们的第一个需要 FFI (Foreign Function Interface,外部函数接口)功能的库,用于计算 Token 长度。FFI 允许不同编程语言之间的代码相互调用和交互,诸如于使用 Python 来调用 Rust 实现的底层库,以实现更快的计算速度。
除此,从应用侧的角度来说,不论是客户端,还是服务端,都需要引入一些小的推理模型。如下是两个比较常用的基于 FFI 库:
唯一对我们影响比较大的点是,在某些语言下,我们可能没有那么多的参考代码、参考架构作为示例,在开发这一类应用时,时间会比较长。
与常规的应用开发相比,LLM 应用并没有什么太大的区别。只是我们需要考虑 LLM 带来的影响,以及在不同的场景下,如何去管理这些架构。
通常来说,根据不同的业务模式,结合 AI 的程度也是不一样的,我们将其总结为三种不同的模式。
因此,根据我们先前构造的几个 PoC,我们将其总结为四个架构设计的基本原则。
在这里我们思考的四个架构原则,其实是受限于 LLM 能力所产生的:
而从实践的情况而言,引导用户、完善上下文是我们工程化落地的最大难点。
Prompt 的模型与 Prompt 策略本身是息息相关的,通常来说,它与我们解析复杂问题的方式是相关的。
在开发的时候编写 prompt 是一件痛苦的事情,在我们构建了不同的思维链模板之后,我们需要提供更好的示例。为此,我们会对 prompt 进行建模,以更好的管理和测试 prompt。以 LangChain 源码中的 prompt 作为示例:
Human: What is 2+2?
AI: 4
Human: What is 2+3?
AI: 5
Human: What is 4+4?
对应的 Python 代码如下:
examples = [
{"input": "2+2", "output": "4"},
{"input": "2+3", "output": "5"},
]
为此,根据我们的 Prompt 模式,需要不同的 PromptTemplate
模式,如 LangChain 里便构建了一系列复杂的 Prompt 策略模式,如:FewShotPromptWithTemplates
、FunctionExplainerPromptTemplate
等。
而在我们的 PoC 项目中,不只一种类型的 example。因此需要考虑:如何去持续对它们进行建模?如下是我们使用 Kotlin 构建的项目的一个 QA 模板示例:
@Serializable
data class QAUpdateExample(
override val question: String,
override val answer: String,
val nextAction: String = "",
val finalOutput: String = "",
) : PromptExample {
}
也是随着我们迭代的过程中,不断优化出来。而由于 prompt 中需要结合一下上下文信息,通过变量的方式,所以我们还需要诸如于 Apache Velocity 这样的模板引擎,将 prompt 中的变量采用真实的数据来替换。
毫无疑问,在现有的 LLM (大语言模型)应用里,与智能客服、知识问答相关的场景是最多的。在这些场景之下,开发人员一直在探索更好的 RAG(Retrieval-augmented generation,检查增强生成)模式,来使 LLM 回答得更加准确。
然而,事实上,在大部分的场景之下,我们可以构建专有的模式,它们的质量往往更好。但是,通用性不多,依赖于专家来构建,因此显得性价比不足。
毫无疑问,代码生成相关的工具是第二大热门的 LLM 工具。与知识场景不同的是,你不一定需要 RAG 来帮你完成上下文的构建。在这个场景下,每个问题都很 “具体”,可以在 IDE、编辑器里拿到足够多的上下文。
不论是 GitHub Copilot、JetBrains AI Assistant,还是我们开源的 AutoDev,都只是在根据用户的行为,得到的结果,来生成对应的 prompt。如 Copilot 会根据最近的 20 个文件,计算与当前代码相差的 code chunk,生成 prompt。而在 AutoDev 里,我们觉得应该将规范编写入代码生成的 prompt,以生成规范化的代码。
也因此,我们相信在特定领域里,根据领域的上下文设计 DSL,设计 prompt 策略,再结合 RAG 才是最合理的方式。
在内部的培训材料里,我将 RAG 视为 prompt 策略的一种。基础的 RAG 模式需要结合向量数据库、构建知识索引。在基础 RAG 模式之下,构建出来的 prompt 并不会达到令人满意的结果。
用户都是小白,并不会以我们预期的方式来操作系统。他们的输入是模糊的,我们的挑战便是:如何将一个模糊的问题具象化?
与半年前的 LLM 一样,RAG 相关的内容在未来几个月里仍然将激烈的演进,我们依旧需要持续的学习。
在与 LLM 进行交互时,需要由自然语言作为 API。总体上可以分为两大类场景:
语言非常的奇妙,而结合 LLM 的本质则是将不确实性转为确定的函数调用参数、DSL 等。
简单来说,就是类似于如下的 prompt 方式:
你的任务是回答关于代码库的问题。你应该使用一组工具来收集信息,以帮助你回答问题。以下工具可供使用:
从实践上来说,通常可以分为这三种模型。
而除了上述的场景之后,还可以由 LLM 生成 DSL,如 JSON 等方式,由程序来处理这个函数实现类似的功能。
通常来说,我们会采用 DSL 作为用户与 LLM 的中间语言。即方便于 LLM 理解,也方便于人类理解,还适合程序解析。当然了,也有一些 DSL 并不需要解析,诸如于:
----------------------------------------------
| Navigation(10x) |
----------------------------------------------
| Empty(2x) | ChatHeader(8x) | Empty(2x) |
----------------------------------------------
| MessageList(10x) |
----------------------------------------------
| MessageInput(10x) |
----------------------------------------------
| Footer(10x) |
----------------------------------------------
它可以用为中间的呈现模式,以便于 LLM 进一步生成代码。
除此,如何基于 DSL 模型引导用户,就是一件非常有意思的事。
在这一篇文章里,我们总结了过去几个月里,构建 LLM 应用的一些经验。而从这些经验里,我们发现了越来越多可复用的模式。
我们将探索如何更好地沉淀下这些模式 ,以用于支撑更快速的 LLM 应用开发。
围观我的Github Idea墙, 也许,你会遇到心仪的项目