一、了解 LangChain
LangChain 基本概念
LangChain 是一个用于开发由大型语言模型(LLMs)驱动的应用程序的框架,旨在将大型语言模型(如 GPT-4)与外部计算和数据源结合起来。它的定位是帮助开发人员构建更高级的应用程序,使语言模型不仅能处理文本,还能与外部环境进行交互。
LangChain 关键特性
LangChain 具备以下关键特性:
-
组件(Components)
LangChain 提供了多种组件,用于封装大型语言模型的接口、模板提示和信息检索索引。这些组件可以独立使用,也可以组合起来解决复杂任务。
-
链(Chains) 链是 LangChain 的核心概念之一,它允许开发者将多个组件组合在一起,形成一个工作流。例如,可以将信息检索和语言生成组件组合起来,构建一个智能问答系统。
-
代理(Agents) 代理使得语言模型能够与外部环境进行交互,例如通过 API 请求执行操作。这使得语言模型不仅能处理文本,还能执行具体的任务。
-
索引(Indexes) 索引帮助从大型语言模型中提取相关信息,提升信息检索的效率和准确性。
-
提示模板(Prompt Templates) 提示模板允许动态生成查询,避免硬编码文本输入。这使得开发者可以根据用户输入动态生成查询,并发送给语言模型。
-
嵌入与向量存储(Embedding and Vector Stores) LangChain 支持将文本转换为向量表示,并存储在向量数据库中,以便进行相似性搜索和信息检索
LangChain 的这种结构设计使得大型语言模型不仅能够处理文本,还能够在更广泛的应用环境中进行操作和响应,大大扩展了它们的应用范围和有效性。
二、LangChain 表达式语言(LCEL)和语言模型(Model I/O)
LangChain 表达式语言(LCEL)和 Model I/O 是 LangChain 框架中的两个核心概念,它们在整体架构中扮演着重要角色,并且相互关联,共同构建了一个灵活且强大的语言处理框架:
- LCEL 作为框架:
- LCEL 提供了一个声明式的语言,用于定义和组合不同的组件(包括 Model I/O 组件)。
- 通过 LCEL,开发者可以轻松地将输入提示、模型调用和输出解析组合成一个完整的工作流。
- Model I/O 作为组件:
- Model I/O 提供了标准化的接口,用于与语言模型进行交互。
- 这些接口可以作为 LCEL 中的组件,参与到更复杂的工作流中。
LangChain 表达式语言(LCEL)
LangChain 表达式语言(LCEL) 是一种声明式语言,用于定义和组合不同的组件以形成复杂的工作流。它的设计目标是简化从原型到生产环境的过渡,使开发者能够轻松地构建和管理复杂的语言处理任务。LCEL 的核心概念之一是 Runnable。Runnable 提供了一组标准化的接口,使得不同的组件可以在一个统一的框架内协作,从而实现更灵活和模块化的语言处理系统。
Runnable 接口定义了一些标准方法,用于执行和管理链条中的操作。以下是主要的接口方法:
- invoke:直接调用链条上的输入,适用于单一输入的情况。
- batch:对一组输入进行批量处理,适用于需要同时处理多个输入的情况。
- stream:流式处理输出,适用于需要逐步获取输出的情况。
LangChain 中的大多数组件必须实现了 Runnable 接口,包括:
LangChain 语言模型(Model I/O)
Model I/O 是 LangChain 中用于与大型语言模型(LLMs)进行交互的标准化接口。它将模型的使用过程拆解为三个主要部分:输入提示、模型调用和输出解析。其中,语言模型组件是其核心部分之一,旨在与各种大型语言模型(LLMs)进行交互。LangChain 支持多种类型的语言模型组件,可分为三类:
1、大型语言模型(LLMs): 这些模型接受文本字符串作为输入,并返回文本字符串作为输出。例如:OpenAI 的 GPT-3.5、GPT-4。
2、聊天模型(Chat Models): 这些模型基于语言模型,但接口更结构化,接受聊天消息列表作为输入,并返回聊天消息。例如:OpenAI 的 ChatGPT。
3、文本嵌入模型(Text Embedding Models): 这些模型将文本转换为向量表示,用于相似性搜索、聚类等任务。例如:Hugging Face 的嵌入模型。
三、开发过程实录
理解 LangChain 的继承关系
LangChain 的大部分组件都是基于 LangChain 表达式语言(LCEL)定义的,即继承自 Runnable 基类。
- Runnable 抽象类:提供了
batch和stream方法的默认实现。invoke方法是抽象方法,需要子类实现。 - BaseLangChain 抽象类:继承自
Runnable基类,增加了verbose日志输出标识、metadata元数据、callbacks回调函数和tags标签等基础属性。 - BaseLanguageModel 抽象类:继承自
BaseLangChain基类,增加了 Token 计算方法。 - BaseChatModel 抽象类:继承自
BaseLanguageModel基类,实现了invoke方法,并定义了抽象方法_generate,用于语言模型生成接口。
星火大模型集成实现
- 目标
实现星火大模型与 LangChain 的集成,通过自定义语言模型类,支持流式和非流式调用。
- 设计思路
2.1 自定义语言模型类
- 继承:自定义语言模型类继承自 LangChain 的
BaseChatModel。 - 实现方法:实现
_generate方法,用于解析星火大模型的数据。
// 处理星火大模型分片输出
let response: ChatCompletionResponse = { result: "" };
for await (const chunk of streams) {
const data = JSON.parse(chunk) as ChatCompletionChunk;
const { header, payload } = data;
if (header.code === 0) {
if (header.status === 0) {
response.result = payload.choices?.text[0]?.content ?? "";
} else if (header.status === 1) {
response.result += payload.choices?.text[0]?.content ?? "";
} else if (header.status === 2) {
response = { ...response, usage: payload.usage?.text };
break;
}
} else {
break;
}
}
2.2 通信协议
- WebSocket:星火大模型使用 WebSocket 进行通信。
// 定义抽象方法 openWebSocketStream,Web 和 Node 入口分别实现 Websocket 连接方式
/**
* Method that retrieves the auth websocketStream for making requests to the Iflytek Xinghuo API.
* @returns The auth websocketStream for making requests to the Iflytek Xinghuo API.
*/
abstract openWebSocketStream<T extends BaseWebSocketStream<string>>(
options: WebSocketStreamOptions
): Promise<T>;
- 双入口设计:
- Web 入口:使用浏览器环境的 WebSocket 建立连接通信。
- Node 入口:使用
ws库建立 WebSocket 连接后进行通信。
import WebSocket from "ws";
class WebSocketStream extends BaseWebSocketStream {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
openWebSocket(url: string, options: WebSocketStreamOptions): WebSocket {
return new WebSocket(url, options.protocols ?? []);
}
}
/**
* @example
* ```typescript
* const model = new ChatIflytekXinghuo();
* const response = await model.invoke([new HumanMessage("Nice to meet you!")]);
* console.log(response);
* ```
*/
export class ChatIflytekXinghuo extends BaseChatIflytekXinghuo {
async openWebSocketStream<WebSocketStream>(
options: WebSocketStreamOptions
): Promise<WebSocketStream> {
const host = "spark-api.xf-yun.com";
const date = new Date().toUTCString();
const url = `GET /${this.version}/chat HTTP/1.1`;
const { createHmac } = await import("node:crypto");
const hash = createHmac("sha256", this.iflytekApiSecret)
.update(`host: ${host}\ndate: ${date}\n${url}`)
.digest("base64");
const authorization_origin = `api_key="${this.iflytekApiKey}", algorithm="hmac-sha256", headers="host date request-line", signature="${hash}"`;
const authorization = Buffer.from(authorization_origin).toString("base64");
let authWebSocketUrl = this.apiUrl;
authWebSocketUrl += `?authorization=${authorization}`;
authWebSocketUrl += `&host=${encodeURIComponent(host)}`;
authWebSocketUrl += `&date=${encodeURIComponent(date)}`;
return new WebSocketStream(authWebSocketUrl, options) as WebSocketStream;
}
}
class WebSocketStream extends BaseWebSocketStream {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
openWebSocket(url: string, options: WebSocketStreamOptions): WebSocket {
return new window.WebSocket(url, options.protocols ?? []);
}
}
/**
* @example
* ```typescript
* const model = new ChatIflytekXinghuo();
* const response = await model.invoke([new HumanMessage("Nice to meet you!")]);
* console.log(response);
* ```
*/
async openWebSocketStream<WebSocketStream>(options: WebSocketStreamOptions): Promise<WebSocketStream> {
const host = "spark-api.xf-yun.com";
const date = new Date().toUTCString();
const url = `GET /${this.version}/chat HTTP/1.1`;
const keyBuffer = new TextEncoder().encode(this.iflytekApiSecret);
const dataBuffer = new TextEncoder().encode(`host: ${host}\ndate: ${date}\n${url}`);
const cryptoKey = await crypto.subtle.importKey(
"raw",
keyBuffer,
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", cryptoKey, dataBuffer);
const hash = window.btoa(String.fromCharCode(...new Uint8Array(signature)));
const authorization_origin = `api_key="${this.iflytekApiKey}", algorithm="hmac-sha256", headers="host date request-line", signature="${hash}"`;
const authorization = window.btoa(authorization_origin);
let authWebSocketUrl = this.apiUrl;
authWebSocketUrl += `?authorization=${authorization}`;
authWebSocketUrl += `&host=${encodeURIComponent(host)}`;
authWebSocketUrl += `&date=${encodeURIComponent(date)}`;
return new WebSocketStream(authWebSocketUrl, options) as WebSocketStream;
}
2.3 调用方式
- 流式调用:星火大模型仅支持流式调用。
- 封装接口:封装语言模型生成接口,支持流式和非流式输出,并根据流式输出标识指定调用方式。
/**
* Calls the Xinghuo API completion.
* @param request The request to send to the Xinghuo API.
* @param signal The signal for the API call.
* @returns The response from the Xinghuo API.
*/
async completion(
request: ChatCompletionRequest,
stream: true,
signal?: AbortSignal
): Promise<IterableReadableStream<string>>;
async completion(
request: ChatCompletionRequest,
stream: false,
signal?: AbortSignal
): Promise<ChatCompletionResponse>;
开源贡献 PR 链接:github.com/langchain-a…
四、使用方式
基础用法
在这个示例中,我们展示了如何使用 LangChain 集成星火大模型进行基本的聊天操作:
- 导入模块:首先,我们从
@langchain/community/chat_models/iflytek_xinghuo导入ChatIflytekXinghuo模型,并从@langchain/core/messages导入HumanMessage。 - 初始化模型:创建一个
ChatIflytekXinghuo模型实例。 - 发送消息:创建两个不同的消息数组,每个数组包含一个
HumanMessage实例。 - 调用模型:使用
invoke方法调用模型,并传递消息数组。 - 输出结果:将模型的响应输出到控制台。
import { ChatIflytekXinghuo } from "@langchain/community/chat_models/iflytek_xinghuo";
import { HumanMessage } from "@langchain/core/messages";
const model = new ChatIflytekXinghuo();
const messages1 = [new HumanMessage("Nice to meet you!")];
const res1 = await model.invoke(messages1);
console.log(res1);
const messages2 = [new HumanMessage("Hello")];
const res2 = await model.invoke(messages2);
console.log(res2);
ReAct Agent 实现
ReAct(Reasoning and Acting)是一种 AI Agent 的设计思想,强调在执行任务时结合推理和行动两个方面,使 Agent 能够在复杂和动态的环境中更有效地工作。其通常涉及以下关键步骤:
- 理解上下文:Agent 首先理解所处的环境和任务的上下文,包括理解自然语言指令、感知环境状态或识别问题本质。
- 推理:基于理解的上下文进行逻辑推理,确定最佳行动方案,包括规划、决策制定、问题解决或预测可能的结果。
- 规划:在推理基础上制定行动计划,确定一系列有序步骤以实现既定目标或响应特定指令。
- 执行:根据规划的步骤执行行动,可能与环境进行交互,如使用 API 调用、操作用户界面或执行其他形式的 I/O 操作。
- 反馈和迭代:执行行动后收集反馈,以评估行动效果,并据此调整推理和规划策略,改进未来性能。
import { ChatIflytekXinghuo } from "@langchain/community/chat_models/iflytek_xinghuo";
import { TavilySearchResults } from "@langchain/community/tools/tavily_search";
import type { PromptTemplate } from "@langchain/core/prompts";
import { pull } from "langchain/hub";
import { AgentExecutor, createReactAgent } from "langchain/agents";
// Define the tools the agent will have access to.
const tools = [new TavilySearchResults({ maxResults: 1 })];
const llm = new ChatIflytekXinghuo ({
temperature: 0,
});
// Get the prompt to use - you can modify this!
// If you want to see the prompt in full, you can at:
// https://smith.langchain.com/hub/hwchase17/react
const prompt = await pull<PromptTemplate>("hwchase17/react");
const agent = await createReactAgent({
llm,
tools,
prompt,
});
const agentExecutor = new AgentExecutor({
agent,
tools,
});
// See public LangSmith trace here: https://smith.langchain.com/public/d72cc476-e88f-46fa-b768-76b058586cc1/r
const result = await agentExecutor.invoke({
input: "what is LangChain?",
});
console.log(result);
// Get the prompt to use - you can modify this!
// If you want to see the prompt in full, you can at:
// https://smith.langchain.com/hub/hwchase17/react-chat
const promptWithChat = await pull<PromptTemplate>("hwchase17/react-chat");
const agentWithChat = await createReactAgent({
llm,
tools,
prompt: promptWithChat,
});
const agentExecutorWithChat = new AgentExecutor({
agent: agentWithChat,
tools,
});
const result2 = await agentExecutorWithChat.invoke({
input: "what's my name?",
// Notice that chat_history is a string, since this prompt is aimed at LLMs, not chat models
chat_history: "Human: Hi! My name is Cob\nAI: Hello Cob! Nice to meet you",
});
console.log(result2);
MRKL Agent 实现
MRKL 是 Modular Reasoning, Knowledge and Language 的缩写,中文意思是模块化推理、知识和语言。MRKL 代理是一种具有特定结构和功能的代理系统。
一个 MRKL 代理通常由以下几个部分组成:
- 工具(Tools):代理可以使用的工具,例如进行搜索、计算等功能的工具。
- LLM 链(LLMChain):用于生成文本,该文本会以某种方式解析以确定要采取的操作。
- 代理类本身:负责解析 LLM 链的输出,从而确定要执行的具体操作。
import { initializeAgentExecutorWithOptions } from "langchain/agents";
import { ChatIflytekXinghuo } from "@langchain/community/chat_models/iflytek_xinghuo";
import { Calculator } from "@langchain/community/tools/calculator";
import { SerpAPI } from "@langchain/community/tools/serpapi";
export const run = async () => {
const model = new ChatIflytekXinghuo({ temperature: 0 });
const tools = [
new SerpAPI(process.env.SERPAPI_API_KEY, {
location: "Austin,Texas,United States",
hl: "en",
gl: "us",
}),
new Calculator(),
];
const executor = await initializeAgentExecutorWithOptions(tools, model, {
agentType: "chat-zero-shot-react-description",
returnIntermediateSteps: true,
});
console.log("Loaded agent.");
const input = `Who is Olivia Wilde's boyfriend? What is his current age raised to the 0.23 power?`;
console.log(`Executing with input "${input}"...`);
const result = await executor.invoke({ input });
console.log(`Got output ${result.output}`);
console.log(
`Got intermediate steps ${JSON.stringify(
result.intermediateSteps,
null,
2
)}`
);
};