一架梯子,一头程序猿,仰望星空!
LangChain教程(JS版本) > 内容正文

LangChain实践指南


在本指南中,我们将介绍一些常见的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,因此它们可以通过管道相互连接。



关联主题