大家好,我是feng,欢迎关注公众号和我一起探索。如果文章对你有所启发,请为我点赞、转发!
一、回顾
复习一下之前介绍Typescript开发AI应用的相关知识点:
1.1 配置本地大模型
使用Ollama搭建和管理、运行本地大模型,使用Quasar、Vue3、LangChain等框架技术开发了一个类似ChatGPT的对话式AI应用实例。
1.2 大模型响应的流式输出与处理
AI探索实践10 - Typescript开发AI应用2:前端实现本地模型流式响应输出https://mp.csdn.net/mp_blog/creation/editor/136522923LangChain提供了3种方式向大模型发送请求,分别是:
- invoke:单次请求响应类型。可获取一次请求的完整结果。
- batch:一次发送多个请求。可获取批量请求的完整结构。
- stream:单词请求、流式响应类型。可获取一次请求的流方式的响应数据数组。
我们可以根据不同的需求场景,采用不同的调用方式。
1.3 使用提示语模板
了解提示语模板的作用,通过提示语模板功能,我们可以为用户发送的提示语,补充更多的上下文信息,从而得到更好的答案。我们可以在提示语中间指定模型的角色、明确模型要做的任务,以及说明需要识别用户的输入占位符等。
1.4 大模型输出的格式化
AI探索实践12 - Typescript开发AI应用4:大模型响应数据的格式化输出https://mp.csdn.net/mp_blog/creation/editor/136570566我们可以对大模型的输出数据格式化为实际需要的类型,比如字符串、数组、json对象等。
1.5 抓取网页、加载和分割文档、向量存储与检索
AI探索实践13 - Typescript开发AI应用5:抓取网页、文档分割、向量存储与检索链语义检索的使用 【推荐】https://mp.csdn.net/mp_blog/creation/editor/136588067 这篇博文比较重要,可视为一个RAG应用的基本实现示例。
1.6 为提示语模板附加对话历史
AI探索实践14 - Typescript开发AI应用6:将对话历史记录添加到上下文https://mp.csdn.net/mp_blog/creation/editor/136641393了解如何使用LangChain的API来设置对话历史,主要有3种类型:
- AIMessage: 代表大模型消息
- HumanMessage:代表用户的消息
- SystemMessage:代表系统消息
本文将介绍另一个重要概念:Agent,它是实现企业AI应用的重要组成部分。
二、认识 Agent
2.1 从功能角度看Agent
Agent(智能体) = 一个设置了一些目标或任务,可以迭代运行的大型语言模型。这与大型语言模型(LLM)在像ChatGPT这样的工具中“通常”的使用方式不同。在ChatGPT中,你提出一个问题并获得一个答案作为回应。而Agent拥有复杂的工作流程,模型本质上可以自我对话,而无需人类驱动每一部分的交互。
这个定义有几个关键词:
- 任务:有明确的目的
- 迭代:能够循环执行某些逻辑或任务
- 大语言模型:agent的核心或者本质是大语言模型。
- 流程:按照一定的程序
- 自动:不需要人工干预
所以如果这个定义按照关键词来理解就是:一个能够解决明确问题的、按照一定流程执行的、不需要人工干预的、如有必要可以循环执行任务的大模型。
如果给这个定义加上:视觉、听觉和金属外壳,这不就是科幻电影中的人工智能的机器人嘛!
2.2 从开发角度看Agent
来看看LangChain上对Agent是如何定义的:Agents | 🦜️🔗 Langchain
The core idea of agents is to use a language model to choose a sequence of actions to take. In chains, a sequence of actions is hardcoded (in code). In agents, a language model is used as a reasoning engine to determine which actions to take and in which order.
中文含义就是:Agent的核心思想是使用语言模型来选择要采取的一系列操作。在链中,一系列操作被硬编码(在代码中)。在Agent中,语言模型被用作推理引擎来确定要采取哪些操作以及按什么顺序。
可以看出从开发角度和功能角度的定义是不同的。这个定义有2个关键词:
- 语言模型:个人理解指的是至少包含LLM的模型列表,因为只有LLM才具备推理能力。
- 系列操作:和ChatGPT等对话类大模型应用不同,Agent是可以进行“一系列操作”的。这说明Agent可以处理内部或外部的操作。内部操作,主要是大模型推理操作,以及后面对问题的响应。外部操作,是指可以调用外部资源的能力。比如抓取网页、加载文档、调用API获取某些能力等。
可以看出在LangChain中,链里面可以包含多个Agent即Agents,链把大模型抽象出来当做可重用的部分,每个Agent定义了一组操作(工具),由大模型根据“操作”的结果来决定下一步该执行哪一个“操作”。当大模型认为最终实现了任务目标时,代表了该angent执行完成。
Agents = n * agent + 1 * 大模型
三、实现Agent
我将结合前面的知识,实现一个用于解答LCEL的Agent的示例。
3.1 定义模型和提示语:
const model = new ChatOllama({
baseUrl: 'http://localhost:11434', // Default value
model: 'qwen:4b',
temperature: 0.7,
});
const prompt = ChatPromptTemplate.fromMessages([
('system', '你是一个对我非常有帮助的助理,你的名字叫做 Mask'),
('human', '{input}'),
new MessagesPlaceholder('agent_scratchpad'),
]);
3.2 创建一个agent
import { createOpenAIFunctionsAgent, AgentExecutor } from 'langchain/agents';
// 定义一个agent可以调用的工具列表
const tools = [];
// 创建一个agent
const agent = await createOpenAIFunctionsAgent({
llm: model,
prompt,
tools,
});
// 创建一个agent执行器
const agentExecutor = new AgentExecutor({
agent,
tools,
});
agent有多个类型,可以参考连接: Agent Types | 🦜️🔗 Langchain 。
这里,我们使用OpenAIFunctions类型。
为了便于演示,我将创建一个node的控制台程序的逻辑,以便于我们在控制台下可以输入问题和获得LLM的响应。为此,我们需要安装:readline,控制台下执行:
yarn add readline
3.3 控制台应用逻辑
增加代码:
// 在控制台下获得用户的输入和程序的输出
const r1 = createInterface({
input: process.stdin,
output: process.stdout,
});
const askQuestion = () => {
r1.question('User: ', async (input) => {
// 如果用户输入 exit 则退出控制台应用程序
if (input.toLocaleLowerCase() === 'exit') {
r1.close();
return;
}
// 调用 agent
const response = await agentExecutor.invoke({
input: input,
});
//
console.log('Agent: ', response.output);
// 回到问答状态
askQuestion();
});
};
// 执行
askQuestion();
我们先执行一下程序:
可以看到在提示语模板中,预定义了大模型的名字:Mask,大模型也能正确回答,但是当我问自己的名字时,大模型产生了“幻觉”。这是因为当前逻辑中,上下文中没有历史记录。
3.4 添加对话记录到上下文中
根据之前的介绍,我们可以轻松的将对话记录添加到上下文中。在提示语模板增加变量:chat_history
const prompt = ChatPromptTemplate.fromMessages([
('system', '你是一个对我非常有帮助的助理,你的名字叫做 Mask'),
new MessagesPlaceholder('chat_history'),
('human', '{input}'),
new MessagesPlaceholder('agent_scratchpad'),
]);
调整相关代码:
// 增加对话历史
const chatHistory = [];
const askQuestion = () => {
r1.question('User: ', async (input) => {
// 如果用户输入 exit 则退出控制台应用程序
if (input.toLocaleLowerCase() === 'exit') {
r1.close();
return;
}
// 调用 agent
const response = await agentExecutor.invoke({
input: input,
chat_history: chatHistory,
});
// LLM输出
console.log('Agent: ', response.output);
// 将用户输入,增加到对话记录中
chatHistory.push(new HumanMessage(input));
// 将模型响应,添加到对话记录中
chatHistory.push(new AIMessage(response.output));
// 回到问答状态
askQuestion();
});
};
运行结果:
可以看到,模型可以回答历史对话中的内容了。但是当我们问一个它不知道的问题时,当前的逻辑就无法解决了。此时,agent可以调用操作(工具 )的能力就有用武之地了。
3.5 创建检索链工具
利用之前摘取网页的知识,我们将抓取网页的检索链,创造一个工具,并添加到angent工具列表中:
import { CheerioWebBaseLoader } from 'langchain/document_loaders/web/cheerio';
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
import { OllamaEmbeddings } from '@langchain/community/embeddings/ollama';
import { MemoryVectorStore } from 'langchain/vectorstores/memory';
import { createRetrieverTool } from 'langchain/tools/retriever';
const loader = new CheerioWebBaseLoader(
'https://js.langchain.com/docs/expression_language/',
);
const docs = await loader.load();
// 创建一个递归文本拆分器
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 200,
chunkOverlap: 20,
});
// 将抓取到的网页内容分割成更小的文档数组
const splitDocs = await splitter.splitDocuments(docs);
//console.log(splitDocs);
const embeddings = new OllamaEmbeddings({
model: 'qwen:4b',
});
// 这里简单的使用内存向量数据库
const vectorStore = await MemoryVectorStore.fromDocuments(
splitDocs,
embeddings,
);
// 检索数据,设置相关性最高的数量:2个
const retriever = vectorStore.asRetriever({
k: 2,
});
const retrieverTool = createRetrieverTool(retriever, {
name: 'lecl_search',
// 这描述,告诉LLM,遇到什么情况时使用这个工具
description:
//'使用这个工具来搜索有关于LangChain Expression Language (LCEL)的信息',
'Use this tool when searching for informatioin about LangChain Expression Language (LCEL)',
});
//const searchTool = new TavilySearchResults();
// 定义一个agent可以调用的工具列表
const tools = [retrieverTool];
运行结果:
可以看到,LLM利用了agent的工具:检索链,来回答了用户的问题。
四、总结
本文的Agent实例,是利用了之前介绍的几个技能:附加对话历史、文档加载、文档分割、向量数据库、检索链等作为工具,并附加到agent的工具列表中。当面大模型遇到问题推理到需要使用某个工具时,就会调用该工具来实现一定的业务。
其中的重点,则是创建agent工具,以及对这个工具的详细描述,以便大模型能够理解。
示例中检索链工具中文的描述,还不能让qwen:4b正确回答问题。推测应该是模型本身参数少,推理能力较弱,因此换成英文描述得到正确的结果。因此如果想获得好的结果,有两点需要注意:
- 大模型本身的能力
- 提示语(注释)需要尽可能的详细,以便于大模型的理解