尽管大语言模型已经呈现出了强大的威力,但是如何让它完美地完成一个大的问题,仍然是一个巨大的挑战。
需要精心地给予大模型许多的提示(Prompt)。对于一个复杂的应用场景,编写一套完整的,准确无误的提示,并不容易。另一方面,尽管大模型已经具备了一些拆解问题,一步步解接的能力。但是就目前而言,这种分析,推理能力还是不能能准确地做出推理
另一方面,对于大多数特定的问题而言,人类本身具有了成熟,有效的分析问题,解决问题的能力。我们出生起,就不断地学习如何一步步地拆解问题,通过步步地解决小问题,最终解决一个复杂的问题。
许多的研究者提出了各种提示大模型拆解问题的方法。例如 计划和解题(Plan-and-Solve Prompting),反思 ReAct 等等。但是不同的问题,有不同的解决思路。我们从小在学校里解决应用题时,老师总是教我们解题思路。对于各种问题,大模型需要能够动态地做出解题方法。使用静态的提示来实现动态地计划是十分复杂,。在笔者看来,使用计算机语言来动态规划大模型解决问题的思路更加有效。
从实例谈起
我们计划编写一个增强个人记忆力的大模型应用,该项目叫做 回忆(Recall)。在这个应用中,使用者要不断地告诉大模型一些关于个人的信息。例如:
- 个人简历:包括姓名,性别,出身日期,出生地,家庭成员,教育和工作简历等等。
- 个人爱好:自己的爱好,包括饮食,业余爱好,购物的品牌等等
- 个人活动:比如一些主要的活动,比如逛街,朋友聚会,就医等等活动。
- 备忘录:一些需要备忘的事情,例如 我的衣服放在哪里了。每天吃什么药等等。
这个项目貌似比较简单,与windows AI PC 中的Recall ,开源项目Rewind 有相似之处。按照网络上的各种大模型架构的方法,主要使用如下方式
- 使用大模型的Memory 功能实现对话的记忆
- 将用户的个人信息,爱好,个人活动写入Vector 数据库中,使用RAG 技术在会话过程中读取相关信息
- 构建ReAct Agent 进行 Action -Throught-Observation 的过程
- 调用合适的工具(Agent Tools)
vector 数据库 可以使用内存Memory 也可以使用永久VectorDB ,例如Croma VectorDB。
大模型我们测试了下列几种:
- openai
- 本地 llama-3
- 文心一言
- kimi
- 零一万物
但是结果并不令人满意,主要表现在如下几个方面
- 并非所有的大模型都支持 Function Call,Agent,Memory ,RAG等功能的API。
- Vector 数据库要使用Embedding 功能实现text -splite .耗费的时间很长。
- Momory 功能是将输入和回答都一股脑地存储了起来。会造成某些噪声混乱。
- 简单的提问查询Vector数据库时,无法精切地匹配数据库的内容。
- ReAct 的效果并不理想,有时后会乱想,反复地循环。明明得到了结果,却无法停止对话。
- 延时长,耗费的token 多。
实验下来,openai 效果最好,其它国内的大模型或多多少地出现问题。
观点
经过一段时间的实验之后,我们对大模型应用进行了新的思考,形成了下面几个观点:
- 让大模型分层思考
将复杂的问题分解为若干的小问题,通过解决小问题,最后解决大问题。这种方式具有如下的优点:
-大模型回答简单的问题,有利于保证其确定性
-使提示工程变得简单
提示也被分解成小提示,小问题的提示更具有针对性
-不依赖具体的大模型API
简单地使用chat 就可以。
-有利于采纳本地小模型与远程大模型相结合 ,降低使用大模型的成本,提高响应时间
- 使用程序设计的方法动态地编排大模型的思维过程
对于特定的一类问题,可以实现根据人类的经验,制定一套完整的思维过程。这样做的优点:
-融入了人类的思维方式,更具有针对性。推理的速度更快
-有利于对大模型的回答做确定性判断
-有利于对大模型的回答做确定性验证
动态思维的流程
我们继续使用上面的实例来讨论动态思维流程。
- 判断语句是陈述句,还是询问句
- 如果是陈述句,内容要存储到数据库中,如果是提问句,那么要从数据库中获取相关的信息
- 为了对信息做分类,要判断陈述或者提问的内容的分类。
- 如果是其它类型的提问,就直接有大模型回答
思维流程的编排方法
可以用程序或者图形方式来编排大模型的思维流程,在我们的实验中,采取了工业控制领域中功能块的编排方法。
基于我们的经验,决定借用IEC61499 事件功能块的概念和方法,这样做的另一个意图是实现语言功能块和IEC61499 功能块的融合。
IEC61499 的基本概念包括:
- 基本功能块
- 复合功能块
- 功能块网络
IEC61499 功能块由事件输入,事件输出,数据输入和数据输出。事件用来控制程序执行 的流程,数据用来表示数据的流动。
大语言功能块内部由大语言模型来回答一个特定的问题。 其内部结构如下:
大模型思维流程
大语言思维流程由大语言功能块网络组成,通过功能块网络运行时解释执行。功能块共享环境信息,环境信息包含了基本信息(对话者的姓名, 今天几号,星期几等等)和功能块通过数据库中提取的信息。一个功能块系统的结构如下
实验平台
为了实验langFunctionblock 的想法,我们简单地搭建列一个实验平台:
- 基于Nodejs/Javascript
- 基于langchain库
- 一个Javascript 实现的功能块运行时
- 一组基于大模型的功能块
- 不依赖大模型的API
App架构
实例的功能块网络
功能块
InputMessage
输入用户提问的功能块,当用户输入消息时。该功能块产生:
- Output 事件
- OutMessage 数据
应用程序通过 WriteData 和Execution 调用该功能块。
设置InputMessage和OutMessage功能块的主要目的是使功能块具有一个统一的入口和出口。
Check
主要判断输入语句是询问句还是陈述句。
Memory
该功能块判断陈数句内容的类型:个人信息,事件,备忘录,然后将语句的类型,语句和时间标签存储到MongoDB 数据库中。
Recall
该功能块判断陈数句内容的类型:个人信息,事件,备忘录,然后从数据库中读出相应类型的数据,添加在环境信息中。
Basic
这是一个基本大模型的功能块,将InMessage 结合环境信息一起构成Prompt 询问大模型,回答输出到OutMessage
OutMessage
该模块将信息返回给对话者。
程序的实例
Check功能块
class Check {
constructor(Parameters) {
this.Name = Parameters.Name;
this.Type = "CheckType";
this.model = Parameters.Model
this.ModelType = Parameters.ModelType
}
async Executive(runtime, EventType) {
if (EventType == "Invoke") {
console.log("Invoke:" + this.ModelType)
console.log(this.InMessage)
const Prefix = `请将下列语句分为下列几类:询问,陈述,请求。`
const Suffix = `。请以JSON形式输出语句的类型 :JSON的格式为:
{
class:"语句的类型"
}
如果无法判断语句的类型,直接输出 {class:"其它"}`
const Prompt = Prefix + this.InMessage + Suffix
const completion = await this.model.chat.completions.create({
messages: [
{
"role": "user",
"content": Prompt,
}],
model: this.ModelType,
});
const Content = await completion.choices[0].message.content
const JSonContent = JSON.parse(Content.replace("```json\n", "").replace("```", ""))
console.log(JSonContent.class)
if (JSonContent.class == "询问") {
this.OutMessage = this.InMessage
await runtime.WriteOutputData({ FBName: this.Name, DataName: "OutMessage", Value: this.OutMessage })
await runtime.EventNotify({ FBName: this.Name, EventName: "Ask" })
}
else if (JSonContent.class == "陈述") {
this.OutMessage = this.InMessage
await runtime.WriteOutputData({ FBName: this.Name, DataName: "OutMessage", Value: this.OutMessage })
await runtime.EventNotify({ FBName: this.Name, EventName: "Statment" })
} else if (JSonContent.class == "请求") {
this.OutMessage = this.InMessage;
await runtime.WriteOutputData({ FBName: this.Name, DataName: "OutMessage", Value: this.OutMessage })
await runtime.EventNotify({ FBName: this.Name, EventName: "Request" })
}
else {
this.OutMessage = this.InMessage
await runtime.WriteOutputData({ FBName: this.Name, DataName: "OutMessage", Value: this.OutMessage })
await runtime.EventNotify({ FBName: this.Name, EventName: "Ask" })
}
}
}
async WriteData(Name, Value) {
if (Name == "InMessage") {
this.InMessage = Value;
}
}
async ReadData(Name) {
if (Name == "OutMessage")
return this.OutputMessage;
}
}
主程序
import express from 'express';
import path from 'path'
import url from 'url'
//import fs from 'fs'
import OpenAI from 'openai';
import {RunTime} from "./RunTime/RunTime.mjs"
const API_BASE = "https://api.lingyiwanwu.com/v1"
const API_KEY = "xxxxxxxxxxxxxxxxxxx"
const openai = new OpenAI({
apiKey: API_KEY,
baseURL:API_BASE,
model: "yi-large",
temperature: 0
});
const router = express.Router();
const app = express();
const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// var upload = multer({ dest: './documents' })
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json())
router.get('/index', function (req, res) {
res.sendFile(path.join(__dirname + '/views/indexB.html'));
});
router.post('/Request', async function (req, res) {
Request = req.body;
console.log(Request)
const Method = Request.Method;
const Message = Request.Message;
console.log(Method);
console.log(Message);
const result = await RunFBNetwork(Message)
res.send(JSON.stringify({
Method: "SendMessage",
Message: result
}))
})
app.use('/', router);
//RunTime Initialize
console.log("llm FunctionBlock Runtime Ver 1.0")
const runtime=new RunTime();
runtime.InitializeFunctionBlickList();
runtime.LoadFBNetwork(openai);
app.listen(process.env.port || 3000);
console.log('Running at Port 3000');
async function RunFBNetwork(InputMessage){
console.log("llm FunctionBlock Runtime Ver 1.0")
//RunTime Initialize
runtime.InitializeMongoDB()
runtime.InitializeEnvironment()
await runtime.WriteInputData({FBName:"InMessage",DataName:"InMessage",Value:InputMessage})
await runtime.Executive({FBName:"InMessage",EventType:"Request"})
//Running....
await runtime.run()
const Output=await runtime.ReadFBData({
FBName:"OutMessage",
DataName:"OutMessage"})
console.log(Output)
return (Output)
}
结果
经过我们的初步测试,结果要比采用大模型的memory,RAG,ReAct Agent等方式要好。主要表现为准确率高,速度快。
- 将复杂的问题拆解成为小问题更有效
- 对于特定的应用场景,能够利用人类分析问题的经验,动态地编写思维流程要比简单的将复杂任务交给大模型更好。效果远远超过ReAct Agent
- 功能块及其功能块网络适合大模型思维流程的编排。
感兴趣的读者可以进一步共同探讨。