在本指南中,我们将介绍一些常见的Langchain各个组件组合的应用模式。
PromptTemplate + LLM
PromptTemplate -> LLM 提示词模板 + LLM模型是最简单的langchain使用模式
import { PromptTemplate } from "langchain/prompts";
import { ChatOpenAI } from "langchain/chat_models/openai";
const model = new ChatOpenAI({});
const promptTemplate = PromptTemplate.fromTemplate(
"告诉我一个关于{topic}的笑话"
);
const chain = promptTemplate.pipe(model);
const result = await chain.invoke({ topic: "熊" });
console.log(result);
/*
AIMessage {
content: "为什么熊不穿鞋子?\n\n因为它们有熊脚!",
}
*/
PromptTemplate + LLM + OutputParser(输出解析器)
我们还可以添加输出解析器,以便将原始的LLM/聊天模型输出轻松转换为一致的字符串格式:
import { PromptTemplate } from "langchain/prompts";
import { ChatOpenAI } from "langchain/chat_models/openai";
import { RunnableSequence } from "langchain/schema/runnable";
import { StringOutputParser } from "langchain/schema/output_parser";
const model = new ChatOpenAI({});
const promptTemplate = PromptTemplate.fromTemplate(
"告诉我一个关于{topic}的笑话"
);
const outputParser = new StringOutputParser();
const chain = RunnableSequence.from([promptTemplate, model, outputParser]);
const result = await chain.invoke({ topic: "熊" });
console.log(result);
/*
"为什么熊不穿鞋子?\n\n因为它们有熊脚!"
*/
透传(Passthroughs)
在构建链的过程中,通常需要将原始输入变量传递给链中的未来步骤。具体如何做取决于输入的具体形式:
- 如果原始输入是字符串,则可能只需传递字符串。可以使用
RunnablePassthrough
来实现。有关示例,请参见LLMChain + Retriever
。 - 如果原始输入是对象,则可能需要传递特定的键。为此,您可以使用一个接收对象作为输入并提取所需键的箭头函数。有关示例,请参见下面的
Mapping multiple input keys
。
LLMChain + Retriever(检索链)
这种模式适合用在基于本地知识库的问答场景,通过Retriever组件查询本地数据,交给LLMChain进行最终问题解答。
现在让我们看一下添加检索步骤的方法,这将形成一个“检索增强生成”链:
import { ChatOpenAI } from "langchain/chat_models/openai";
import { HNSWLib } from "langchain/vectorstores/hnswlib";
import { OpenAIEmbeddings } from "langchain/embeddings/openai";
import { PromptTemplate } from "langchain/prompts";
import {
RunnableSequence,
RunnablePassthrough,
} from "langchain/schema/runnable";
import { StringOutputParser } from "langchain/schema/output_parser";
import { Document } from "langchain/document";
const model = new ChatOpenAI({});
const vectorStore = await HNSWLib.fromTexts(
["线粒体是细胞的动力库"],
[{ id: 1 }],
new OpenAIEmbeddings()
);
const retriever = vectorStore.asRetriever();
const prompt =
PromptTemplate.fromTemplate(`仅基于以下背景回答问题:
{context}
问题:{question}`);
const serializeDocs = (docs: Document[]) =>
docs.map((doc) => doc.pageContent).join("\n");
const chain = RunnableSequence.from([
{
context: retriever.pipe(serializeDocs),
question: new RunnablePassthrough(),
},
prompt,
model,
new StringOutputParser(),
]);
const result = await chain.invoke("细胞的动力库是什么?");
console.log(result);
/*
"细胞的动力库是线粒体。"
*/
绑定选项(Binding options)
通常,我们希望将kwargs附加到传入的模型上。为此,可运行对象包含.bind
方法。以下是如何使用它:
添加停止序列
import { PromptTemplate } from "langchain/prompts";
import { ChatOpenAI } from "langchain/chat_models/openai";
const prompt = PromptTemplate.fromTemplate(`给我讲一个关于 {subject} 的笑话`);
const model = new ChatOpenAI({});
const chain = prompt.pipe(model.bind({ stop: ["\n"] }));
const result = await chain.invoke({ subject: "熊" });
console.log(result);
/*
AIMessage {
contents: "为什么熊不用手机?"
}
*/
## 对话式检索链
由于`RunnableSequence.from`和`runnable.pipe`都接受类似runnable的对象,包括单参数函数,我们可以通过格式化函数添加对话历史记录。这允许我们重新创建流行的`ConversationalRetrievalQAChain`,以“与数据交谈”为目标:
```javascript
import { PromptTemplate } from "langchain/prompts";
import {
RunnableSequence,
RunnablePassthrough,
} from "langchain/schema/runnable";
import { Document } from "langchain/document";
import { ChatOpenAI } from "langchain/chat_models/openai";
import { HNSWLib } from "langchain/vectorstores/hnswlib";
import { OpenAIEmbeddings } from "langchain/embeddings/openai";
import { StringOutputParser } from "langchain/schema/output_parser";
const model = new ChatOpenAI({});
const condenseQuestionTemplate = \`给定以下对话和后续问题,请将后续问题重述为独立问题,使用原始语言。
对话历史:
{chat_history}
后续输入:{question}
独立问题:\`;
const CONDENSE_QUESTION_PROMPT = PromptTemplate.fromTemplate(
condenseQuestionTemplate
);
const answerTemplate = \`仅基于以下上下文回答问题:
{context}
问题:{question}
\`;
const ANSWER_PROMPT = PromptTemplate.fromTemplate(answerTemplate);
const combineDocumentsFn = (docs: Document[], separator = "\n\n") => {
const serializedDocs = docs.map((doc) => doc.pageContent);
return serializedDocs.join(separator);
};
const formatChatHistory = (chatHistory: [string, string][]) => {
const formattedDialogueTurns = chatHistory.map(
(dialogueTurn) => \`人:${dialogueTurn[0]}\n助手:${dialogueTurn[1]}\`
);
return formattedDialogueTurns.join("\n");
};
const vectorStore = await HNSWLib.fromTexts(
[
"线粒体是细胞的动力源",
"线粒体由脂质构成",
],
[{ id: 1 }, { id: 2 }],
new OpenAIEmbeddings()
);
const retriever = vectorStore.asRetriever();
type ConversationalRetrievalQAChainInput = {
question: string;
chat_history: [string, string][];
};
const standaloneQuestionChain = RunnableSequence.from([
{
question: (input: ConversationalRetrievalQAChainInput) => input.question,
chat_history: (input: ConversationalRetrievalQAChainInput) =>
formatChatHistory(input.chat_history),
},
CONDENSE_QUESTION_PROMPT,
model,
new StringOutputParser(),
]);
const answerChain = RunnableSequence.from([
{
context: retriever.pipe(combineDocumentsFn),
question: new RunnablePassthrough(),
},
ANSWER_PROMPT,
model,
]);
const conversationalRetrievalQAChain =
standaloneQuestionChain.pipe(answerChain);
const result1 = await conversationalRetrievalQAChain.invoke({
question: "细胞的动力源是什么?",
chat_history: [],
});
console.log(result1);
/*
AIMessage { content: "细胞的动力源是线粒体。" }
*/
const result2 = await conversationalRetrievalQAChain.invoke({
question: "它们由什么组成?",
chat_history: [
[
"细胞的动力源是什么?",
"细胞的动力源是线粒体。",
],
],
});
console.log(result2);
/*
AIMessage { content: "线粒体由脂质构成。" }
}
请注意,我们创建的各个链本身都是Runnables
,因此它们可以通过管道相互连接。
工具库
这种模式主要用在你希望AI调用你自己开发的工具,或者第三方开发的工具
import { SerpAPI } from "langchain/tools";
import { ChatOpenAI } from "langchain/chat_models/openai";
import { PromptTemplate } from "langchain/prompts";
import { StringOutputParser } from "langchain/schema/output_parser";
const search = new SerpAPI();
const prompt =
PromptTemplate.fromTemplate(`将以下用户输入转换为搜索引擎的搜索查询:
{input}`);
const model = new ChatOpenAI({});
const chain = prompt.pipe(model).pipe(new StringOutputParser()).pipe(search);
const result = await chain.invoke({
input: "马来西亚现任总理是谁?",
});
console.log(result);
/*
安华·易卜拉欣
*/
}
请注意,我们创建的链也是Runnables
,因此它们可以通过管道相互连接。