Overview
背景
Langchain 是一套开源的基于 LLMs 开发应用程序的开发框架(解决方案),框架支持 python 和 Node.js 两种语言,同时提供 LangSmith、LangGraph、LangServe 等多种配套框架或方案。
本文会使用 LC 来简化 Langchain 的名称
Langchain 最早是 2022年10月在 github 开源的个人项目,Chat-GPT 是在 2022年11月发布的,Langchain 建立的初衷是为 LLMs 应用程序的开发建立标准的解决方案,而 Chart-GPT 的发布,直接推动了 Langchain 的发展。当然 LangChain 在 python 和 node.js 的双语言/框架的支持,也吸引了更多的参与者。
其他类似的解决方案如:LlamaIndex、MetaGPT、BabyAGI、Simpleaichat、AutoChain 都有其擅长的解决问题的方向,但是感受上不如 LangChain 提供的解决方案全面,企业级应用的复杂度和深度定制上的实现不如 LangChain。
AutoChain 是基于 Langchain + AutoGPT设计理念而来,但是对于抽象层的概念简化了不少,如果是开发简单的应用,AutoChain 会更容易些,嵌套之类的会少一些,感兴趣的可以了解:github.com/Forethought...
当然目前 LangChain 有点两极化,喜欢和支持 LangChain 的人认为基于LangChain构建稳定的、可观测的模型应用是简单的,这得益于 LangGraph 和 LangSmith 的推出,不喜欢 LangChain 的人则认为是个概念怪,包装过度,尤其是在 Agent 的构建方面,太复杂了,一个简单的应用,套了很多层概念。
个人认为,LangChain 的复杂度是基于其对解决方案的定义的,如三方接口服务的集成规范、模型的集成规范等等,简单的模型应用开发,三方工具的集成接口
基本组成
目前 Langchain 提供如下能力:
-
开源框架部分:
- Langchain.js( Python ):基础开发框架(Chain、Agent), github 地址: github.com/langchain-a... . Langchain 本身包含了多个包,以 langchain.js 举例,包含了
@langchain/core
核心能力,以及@langchain/openai
等三方模型厂商的快速 集成 包,除此之外,还提供了数据库如 MongoDB 等基础组件的集成能力。 - LangGraph:流程构建框架,可以理解为一个 DAG 的框架,不过支持更加复杂的循环、人工介入、持久化、断点恢复等能力,github 地址: github.com/langchain-a... 。而且基于 LangGraph 构建的流程,可以通过 jupyter 直接预览流程图,如下图:
- Langchain.js( Python ):基础开发框架(Chain、Agent), github 地址: github.com/langchain-a... . Langchain 本身包含了多个包,以 langchain.js 举例,包含了
-
商业系统和服务:
- LangSmith:在线 debug 平台(及服务提供),如果是使用 langchain 提供的模型集成包集成 LLM,每一步的执行都会通过日志在 LangSmith 监控,可以方便的 debug和排查问题。除此之外还提供类似 prompt 模板管理、一些部署能力.
- LangGraph Cloud:为基于 LangGraph 创建的应用流提供部署服务(将一个流部署成 REST API),提供给其他场景调用和执行流;与 LangSmith 集成,实现监控、调试等能力;提供了一个 Studio:
LangGraph Studio
,可以理解为开发 agent 的 IDE,进行可视化的调试、开发非常复杂的 graph。
- LangSmith:在线 debug 平台(及服务提供),如果是使用 langchain 提供的模型集成包集成 LLM,每一步的执行都会通过日志在 LangSmith 监控,可以方便的 debug和排查问题。除此之外还提供类似 prompt 模板管理、一些部署能力.
准备模型
LangChain 中的"模型"
LangChain 是基于大语言模型构建应用程序的解决放哪,因此我们首先需要有一个模型,LangChain 贴心的提供了诸多模型厂商的集成
关于 ChatModel 和 LLM:
-
ChatModel: 支持输入一组消息列表,消息列表中包含 system、agent、user 等角色的对话记录,这意味着模型可以从消息列表中获取上下文,而现在新的模型基本都具备这个能力,并不需要额外处理。可以在这里看到所有列表: js.langchain.com/docs/integr...
-
LLM:这里的 LLM 更多的是纯文本输入输出模型,简单来说我输入给模型一个文本,其会输出给我们需要的文本结果,这个过程中,没有上下文。如果要实现聊天模型的上下文理解能力,则需要我们自己去实现(也不是消息列表,而是自己拼装成字符串,全部丢给模型)可以在这里看到所有的 LLM 集成: js.langchain.com/docs/integr...
注:LangChain 会帮助我们将 LangChain 中的 Message 结构体(如果是一个列表)构建成纯文本模型输入需要的格式,也就是基于 LangChain ,纯文本输入输出模型也能实现上下文的读取能力,而不需要我们额外处理
- Embedding Model:将文本转成向量表示,可以用于向量存储或者是 Retrievers(检索)
代码 | 执行效果 |
---|---|
准备 ChatModel
这里我们使用 Mistral AI: mistral.ai/ , 如果不进行 fine-tune,则非商业使用都是免费的: mistral.ai/technology/...
创建账号然后进入 dashboard 创建一个新的 api key 即可
注意:api key 只有创建第一次才可以看到,记得保存,否则只能创建一个新的,最多可以创建十个,当然可以随时删除之前创建的。不同模型的使用限制(tokens 限制)可以在 console.mistral.ai/limits/ 看到
当我们准备好 Mistral 的 API key,就可以正式开始 LangChain.js 之旅了。
服务开发准备
服务开发框架
选择 gulux 作为开发框架,使用 @gulux/cli
初始化一个新的项目。
安装 langchain 依赖
@langchain/core
: langchain 生态系统中的核心依赖,做了最基础的抽象langchain
: 包本身是基于 langchain/core 做了上层的封装和实现,很多能力是从 @langchain/core 直接 export 出来的。@langchain/community
:由社区维护的三方能力的集成,是一些比较长尾的集成,比如一些模型、存储、向量等等。
比如下面 tongyi 的集成是放在 community
中的
@langchain/openai
:有很多非常流行的包,是单独抽离出来的,比如 openai、mistralai,他们足够流行,就会单独抽个包做集成,不会放在 community 中(还有一部分原因是,这些三方提供的能力很多,llm、chatmodel、embedding-model 都会提供,因此可以将他们单独抽离,单独维护和迭代)
@langchain/langgraph
:除了依赖 @langchain/core 外,其他的不依赖langsmith
:独立的包(bytetrace)
包与包之间的依赖关系如下:
从上面信息可以发现,因为我们使用 mistralAI 的模型服务,因此需要安装 mistralAI 的集成包 @langchain/mistralai
,当然我们也只需要先安装 @langchain/core
即可。
tsx
pnpm add @langchain/core @langchain/mistralai
Hello World:开发第一个基于 LLM 的能力
功能描述
一个基于 LLM 的翻译器
创建 controller
后续实例都是基于 gulux 的 HTTP 服务开发框架进行,不再重复类似内容
tsx
// controller/simple.ts
export default class SimpleController {
}
基于集成包初始化模型
在环境配置 open api key
关于 mistral 集成包的使用可以参考: www.npmjs.com/package/@la...
可以发现这个集成包需要我们在运行环境输出环境变量: MISTRAL_API_KEY
对于 langsmith、openai 等其他的集成包,langchain 也都是通过输出环境变量的方式读取 open api key。
tsx
export MISTRAL_API_KEY=your-api-key
为了避免在我们自己的 .bashrc
或者是 .zshrc
中写死,或者每次在终端中手动 export,我们使用 dotenv-cli
简化流程。
只需要在项目根目录创建 .env
,并且将环境变量配置上去,本地开发可以通过 dotenv-cli 启动的是时候自动设置。
我们的启动脚本会变成:
tsx
"dev": "GULUX_ENV=dev dotenv -e .env -- gulux dev --tsc",
实例化 MistralAI 模型
这里我们使用 open-mistral-7b
模型,通过集成包,我们可以非常简单的完成对模型服务的集成(如模型服务的 openapi 的调用)
tsx
import { ChatMistralAI } from '@langchain/mistralai';
export default class SimpleController {
private model: ChatMistralAI;
constructor() {
this.model = new ChatMistralAI({
model: 'open-mistral-7b',
temperature: 0,
});
}
}
上述 @langchain/mistralai
内部其实也是对 @mistralai/mistralai
的封装,具体的实现可以参考: github.com/langchain-a...
调用模型,传入 prompt
实现一个路由 test
,传入 prompt,通过 invoke
只需要将 messages 传入参数寄了。
ts
@Get('/test')
public async test() {
const messages = [new HumanMessage({ content: 'Translation the following content to Japanese: I am ikun' })];
const res = await this.model .invoke(messages);
this.logger.debug('res', JSON.stringify(res));
return res; // return json
}
执行结果:
解析模型响应内容
上面我们直接 invoke
传入一个 prompt 的时候,最后我们拿到的是一个可序列化的对象结构(具体说是一个 AIMessage 结构,将在下面统一介绍),并非是我们常见的回复文本结果。
如果我们想要解析其中的 content 一种方式是我们直接解析这个 object,除此之外,langchain 给我们抽象了 parser 的概念,内置了好几种 parser,可以在代码仓库中找到一些实现: github.com/langchain-a...
从代码的实现上可以发现很复杂,但如果抛去对流式响应的支持、image 等内容的支持,我们其实自己实现一个 parser 也很简单,只需要遵循 @langchain/core 中的 LCEL 规范(下面介绍)就可以
这里我们使用 StringOutputParser
来格式化输入结果
ts
import { StringOutputParser } from '@langchain/core/output_parsers';
@Get('/test/parser')
public async parser() {
const messages = [
new SystemMessage({ content: 'You are a translator, please translate the following content into Japanese' }),
new HumanMessage({ content: 'I am ikun' }),
];
const res = await this.model.invoke(messages);
const parser = new StringOutputParser();
const parsedContent = await parser.invoke(res);
this.logger.debug('parsedContent', JSON.stringify(res));
return parsedContent;
}
通过我们实例化的 StringOutputParser,我们可以直接 invoke 模型执行的结果,拿到文本输出
Demo 引申的概念
消息 Message
消息类型与消息结构
在上面的 Demo 中我们使用了下面的语法定义一条消息:
ts
new HumanMessage({ content: 'Translation the following content to Japanese: I am ikun' })
Langchain 有下面几种消息类型:
ts
export type MessageType =
| "human"
| "ai"
| "generic"
| "system"
| "function"
| "tool"
| "remove";
在 chatmodel 对话场景中,我们一般会用到三种消息类型,消息类型和他们对应的 Class 如下:
- human:
HumanMessage
,用户的输入内容 - ai:
AIMessage
,一般是 模型输出的内容 - system:System
Message
,我们为模型指定的系统消息
我们通过序列化查看每种消息的序列化内容:
ts
@Get('/test/messageType')
public async messageType() {
const messages = [
new SystemMessage({ content: 'You are a translator, please translate the following content into Japanese' }),
new HumanMessage({ content: 'I am ikun' }),
new AIMessage({ content: '私はいくんです' }),
];
return messages;
}
简化的构造消息
除了通过实例化 Class 外,还可以直接通过更简单的方式构造消息列表,如下所示:
ts
const simpleMessage = [
{ role: 'system', content: 'You are a translator, please translate the following content into Japanese' },
{ role: 'human', content: 'I am ikun' },
{ role: 'ai', content: '私はいくんです' },
]
支持的 role 如下:
下面两种构造消息的方式是一致的:
LCEL:LangChain Expression Language
在上面的 OutputParser 的示例中,我们通过 StringOutputParser
能够再次通过 invoke
去执行模型 invoke
后的结果:
arduino
await parser.invoke(await model.invoke(message))
上一步的输出,变成下一步的输入(是不是有链的味道了?)是基于 LCEL 的感念实现的,而实际上 LCEL 可以将我们的流程简化为:
ts
// 具体 demo 将在下面介绍
const res = awaot model. pipe(parse). inoke(message)
对比上面分步执行,上面的表达则更加的清晰的展示了 链
****
Runnable
Runnable 是 LC 的灵魂,目前阶段只需要简单了解,在后续的自定义应用中,可能会基于 Runnable 的基本能力去实现一些定制化的效果,比如接入自己的模型服务。
能够实现链的效果的基础是 Langchain 抽象了一个 Runnable 的协议,LCEL 也是基于 Runnable 来实现的,用来帮助我们构造能够基于 Runnable 运行的 链
Langchain 大部分内置的功能都实现了 Runnable 的协议,因此你可以快速的去连接不同的节点实现具体的目标(按照文档的说法,他们见过生产环境100多步骤的应用)
要实现一个 Runnable,而 Runnable 在 Lanchain 中是实现了 RunnableInterface
,Runnable 中主要会实现如下方法,我们在目前阶段主要关注下面几个方法即可
- invoke:给定输入,执行内部逻辑,并且会输出结果
- stream:主要用来流式响应结果
- batch:批量执行输入
- pipe:用来 chain 两个 Runnbale(见下面)
Chain Runnable
每个 Runnable 都会有 invoke 方法,也会有 pipe 方法。Chain 本质上是将N个 Runnable 连接起来,一个 Runnable invoke 的结果是下一个 Runnable invoke 的入参。
.pipe()
在这个过程中扮演着重要的角色。
在上面 StringOutputParser
的示例中,我们使用了两个 Runnable:
this.model.invoke()
:模型调用parser.invoke()
:输出结果解析
如果我们使用 pipe 连接两个 Runnable,可以如下实现:
ts
@Get('/test/runnable')
public async runnable(@Query('language') language: string, @Query('message') message: string) {
const messages = [
new SystemMessage({ content: `You are a translator, please translate the following content into ${language}:` }),
new HumanMessage({ content: message }),
];
const parser = new StringOutputParser();
const chain = this.model.pipe(parser);
const parsedContent = await chain.invoke(messages);
return parsedContent;
}
在上面的实现中,我们通过 this.model.pipe
连接了 parser 这个 Runnable,然后我们就得到了一个 chain。
此时如果观察 chain
变量的类型,发现他也是个 Runnable,也就是 chain 也可以被其他的 Runnable 通过 pipe()
连接。
LC 基于这种抽象和实现,可以轻松的构建子chain,然后复用子chain,以便于我们再实际的业务需求中通过组合、复用实现更加复杂的功能。
关于 Runnable 及基于 Runnable 的自定义扩展,计划在后续的文章中更新,这里不过多展开。
Prompt Template
基于变量构造 prompt
到目前为止,我们通过给模型输入 messages,完成了我们的翻译功能。
我们通过 SystemMessage 给模型设定一个角色和任务,然后将用户的输入 HumanMessage 传给模型(虽然是我们写死在代码中的),而在实际的应用中,我们可能需要做一些动态的事情,比如切换目标语言,以下面火山翻译为例
一种实现方式是,我们可以接受用户的参数,然后自己去替换我们的 prompt,比如:
ts
@Post('/test/weakPromptTemplate')
public async weakPromptTemplate(@Body('language') language = 'en', @Body('message') message: string) {
const sysTemplate = 'You are a translator, please translate the following content into {language}';
const sysMessage = new SystemMessage({ content: sysTemplate.replace('{language}', language) });
const humanMessage = new HumanMessage({ content: message });
const parser = new StringOutputParser();
const parsedContent = await this.model.pipe(parser).invoke([sysMessage, humanMessage]);
return parsedContent;
}
在上面的实现中,我们通过手动替换的方式,每次用户传参之后,更新 prompt 传给模型处理。这种简单的业务场景中,我们直接自己替换是OK的,不过 LC 提供了一个新的概念(LC是真的概念怪):Prompt Template
使用 ChatPromptTemplate
简单来说 prompt template 给我们提供了一种能力,允许我们设定一个 prompt 的模板,模板中我们会设定好变量有哪些(类似于我们上面的 {language}
)。设定好模板之后,我们只需要传入数据,prompt template 会自动帮我们完成你替换。
上面的示例中,我们在每次构建一个消息(如 SystemMessage)的时候,都要手动替换一下,如果基于 prompt template 则可以在设定好之后,一次替换即可。
同时 Prompt Template 也是一个 Runnable 的实现,这允许我们通过 pipe
串联成 chain。
上面的示例通过 Prompt Template 实现如下:
ts
import { ChatPromptTemplate } from '@langchain/core/prompts';
@Post('/test/promptTemplate')
public async promptTemplate(@Body('language') language: string, @Body('message') message: string) {
const promptTemplate = ChatPromptTemplate.fromMessages ([
[ 'system' , 'You are a translator, please translate the following content into {language}' ],
[ 'user' , '{content}'],
]);
const parser = new StringOutputParser();
// prompt template -> model -> parser
const parsedContent = promptTemplate.pipe(this.model).pipe(parser).invoke({ language, content: message });
return parsedContent;
}
注意:fromMessages 的方法中,不能使用 new SystemMessage 或者是 new HumanMessage 为该方法构造入参。建议使用上面的元组的形式,或者是参考下面更复杂的 SystemMessagePromptTemplate
的构造函数
例如,你不能通过下面的方式构造这个 prompt 模板,变量会无法解析,最终你得到的 prompt 都是静态的文本,你的prompt 也会是 '{content}'。具体的原因可以参考: github.com/langchain-a...
ts
// 这里的变量不会生效!!!!!!!!!!!!
// 这里的变量不会生效!!!!!!!!!!!!
// 这里的变量不会生效!!!!!!!!!!!!
const promptTemplate = ChatPromptTemplate.fromMessages([
new SystemMessage ({ content: 'You are a translator, please translate the following content into {language}' }),
new HumanMessage ({ content: '{content}' }),
]);
在上面我们实现的功能中,我们引入了 ChatPromptTemplate
,同时通过它提供的 fromMessages
方法,传入一个一个消息列表,分别是系统消息和用户消息,但是每个消息我们都使用了占位符:
{language}
{content}
如果我们是元组入参,则 PromptTemplate 会自动提取中间的元组,然后解析出这些诶变量。同时正如上面提到的,prompt template 的实现也是 Runnable,因此我们可以直接执行 promptTemplate.invoke
方法,同时将我们的变量传入进去:
ts
const promptTemplate = ChatPromptTemplate.fromMessages ([
[ 'system' , 'You are a translator, please translate the following content into {language}' ],
[ 'user' , '{content}'],
]);
const promptValue = await promptTemplate.invoke({
language,
content: message,// 传入 key-value
});
此时我们得到了一个 promptValue
, 如果我们直接序列化这个 promptValue
你会发现结构如下:
正如上面提到的,因为 PromptTemplate 也是个 Runnable,所以我们可以轻松的 pipe()
其他的 Runnable,直到实现我们的功能。
arduino
const parsedContent = promptTemplate. pipe (this.model). pipe (parser). invoke ({ language, content: message });
上面的代码中,我们有两个 pipe 和一个 invoke,按照 LC 的 chain 链路执行逻辑如下:
暂时无法在飞书文档外展示此内容
最终我们拿到的 result 如下图
Prompt Template 的类型
上面的示例中,我们使用的是 ChatPromptTemplate
,特点也很明显,我们可以基于它构造一个消息列表的 prompt template,并且最终传入变量 invoke 之后生成一个 Messages
****
除了 ChartPromptTemplate
之外,还有如下类型的 Prompt Template:
- PromptTemplate: 最简单的 String Template,只有一个 prompt,里面声明变量然后替换
ts
const promptTemplate = PromptTemplate.fromTemplate(
"Tell me a joke about {topic}"
);
- FewShotPromptTemplate:对于 fewshot 的场景提供了比较便捷的能力,将在 fewshot 部分(以后)着重展开
- ImagePromptTemplate:为多模态模型提供 prompt template 能力
- PipelinePromptTemplate:支持通过用 pipeline 的形式将多个 prompt template 组合在一起,然后只需要传入一次变量即可。
在复杂场景中再展开,下面是一个 demo,有三个 prompt template,每个里面都有变量,通过 PipelinePormptTemplate 可以方便的只需要传入一次变量,就可以拿到组合后的 prompt
复杂的方式声明 PromptTemplate
xxx.fromTemplate()
上面的示例我们使用了元组 + fromMessages
来声明 prompt template,这其实是简化的方式,同时我们也指出了,在这个 fromMessagges
中,你是不能使用 new SystemMessage
这样的构造方式,因为会无法提取变量。
如果不使用元组,我们应该如何声明出同样的效果?(一个系统消息的模板,一个用户消息的模板)
在 langchain/core/prompts
中有如下四个实现
- PromptTemplate
- SystemMessagePromptTemplate
- AIMessagePromptTemplate
- HumanMessagePromptTemplate
我们已经知道 PromptTemplate 是一个简单的文本字符串模板,可以通过 PromptTemplate.fromTemplate
来定义一个简单的 prompt 模板。
同样的 fromTemplate()
是关键的方法,我们可以通过 SystemMessagePromptTemplate.fromTemplate()
来直接定义一个系统消息的prompt 模板,因此下面两种方式声明的 prompt template 是等价的。
ts
const promptTemplate = ChatPromptTemplate.fromMessages([
['system', 'You are a translator, translate the content into {language}.'],
['user', '{content}'],
]);
const promptTemplate2 = ChatPromptTemplate.fromMessages([
SystemMessagePromptTemplate.fromTemplate ('You are a translator, translate the content into {language}.'),
HumanMessagePromptTemplate.fromTemplate ('{content}'),
]);
通过上面这种方式,在构造 prompt template 的时候,LC 能够识别和解析出变量 language 和 content
new PromptTemplate()
除了通过 xxx.fromTemplate 的方式声明模板,并且 LC 自动提取参数外,我们有的时候需要手动指定有哪些变量,例如 {language}
中 {}
会作为变量识别符,但是一些特殊场景下,我们的 prompt 中会存在 {}
,LC 可能误认为是一个变量。
PromptTemplate 除了有 fromTemplate()
方法配置和自动解析变量外,本身是一个类,我们可以通过构造函数直接实例化,当然 SystemMessagePromptTemplate
和 HumanMessagePromptTemplate
一样,都是支持直接实例化的,只是这两者的构造函数需要传入 PromptTemplate
的实例。
下面的实现,和上面的实现是一样的。
ts
const sysPrompt = new PromptTemplate({
template: `You are a translator {{ikun}} , translate the content into {language}.`,
inputVariables: ['language'],
partialVariables: { language: 'Japanese' },
});
const humanPrompt = new PromptTemplate({
template: '{content}',
inputVariables: ['content'],
});
const promptTemplate3 = ChatPromptTemplate.fromMessages([
new SystemMessagePromptTemplate(sysPrompt),
new HumanMessagePromptTemplate(humanPrompt),
]);
await promptTemplate3.invoke({content: message}) // 注意:这里可以不传入 language
由此我们可以发现,LC 在实现一些能力的时候,的确是搞了非常多的概念出来, 比如 PromptTemplate 的 fromTemplate,SystemMessagePromptTemplate 等等,这其实对于开发者会造成困扰。
不过上面的示例中,我使用 partialVariable
来定义一些默认值,下面的 invoke 是不需要传入 language 也能正常运行的,在一些高阶应用的时候,这种设计也帮助我们做更加规范化的实现。
执行三种不同方式声明的 prompt template 获取 prompt,可以发现他们最终的结果是一致的。
变量识别和变量替换
变量识别
在上面示例中我们有这样一个声明:
ts
'You are a translator {{ikun}} , translate the content into {language}. '
对于 {language}
,LC 会认为是一个变量,但是如果我们的 prompt template 中,我们就是需要 {}
这个符号,又不希望被解析成变量的,可以通过 {{}}
来声明,最终这个 Template 执行之后,会得到如下的 prompt:
变量替换
目前我们使用的都是 string 类型的变量,如果是Object、Array 等类型的数据替换会发生什么事情?
ts
@Post('/test/promptTemplateVars')
public async promptTemplateVars() {
const promptTemplate = ChatPromptTemplate.fromMessages([
['system', 'You need to find all keys which value is number in the JSON'],
['user', '{content}'],
]);
const obj = {
a: 'a',
b: 1,
c: 2,
};
const promptValue = await promptTemplate.invoke({
content: obj,
});
return promptValue;
}
执行之后,我们可以得到下面的 promptValue,可以发现这个对象会被序列化:
如果将 prompt 传递给模型,我们可以得到处理后的结果
ts
const parsedContent = promptTemplate.pipe(this.model).pipe(parser).invoke({ content: obj });
使用 LangSmith 监测 LLM 应用
关于 LangSmith
LangSmith 是一个为 LLM 运行提供监控和观测能力的 trace 平台,支持企业级内部部署。以 LC 开发的应用程序举例,每次执行都会打点,我们可以在 langsmith 中看到每次请求执行的全链路,帮助分析问题。
当然那 langsmith 不强依赖 LC,基于 langsmith 的 sdk,支持 python、ts 代码的打点监控,类似公司内部的BytedTrace 的能力。
API doc: api.smith.langchain.com/redoc
引入 LangSmith
安装依赖
ts
$ pnpm add langsmith
注册 langsmith 账号,登录平台创建自已 api key
同时创建一个项目(其实不需要我们手动创建什么,这里只是个流程,告诉我们只需要生成个 api key 然后安装依赖直接使用即可)
对于使用 langchain 和不使用 langchain 的项目,上报的方式会有不同
在项目中配置 api key,同样的我们在本地开发时,仍旧通过 .env
文件 + dotenv-cli
来自动设置环境变量:
我们需要设置的主要是下面两个环境变量:
- LANGCHAIN_API_KEY: api key
- LANGCHAIN_PROJECT: smith 中的项目
启动项目,自动开启追踪
我们再次运行上面实现的 prompt template + 翻译的能力:
ts
@Post('/test/promptTemplate')
public async promptTemplate(@Body('language') language: string, @Body('message') message: string) {
const promptTemplate = ChatPromptTemplate.fromMessages([
['system', 'You are a translator, translate the content into {language}.'],
['user', '{content}'],
]);
const parser = new StringOutputParser();
// prompt template -> model -> parser
const parsedContent = promptTemplate.pipe(this.model).pipe(parser).invoke({ language, content: message });
return parsedContent;
}
调用接口之后,打开 smith 可以看到我们的项目的监控面板
在项目的跟踪跟踪详情中,我们可以看到每个 Langchain 中的 Runnable 都能够被跟踪:
对于每个 Runnable 我们可以看到执行链路,输入输出分别是什么,token 有多大
错误监控
当 Runnable 在执行过程中发生异常时,也能够捕获,比如下面的代码中少传入了一个模板变量
对于这一次执行,你我们能够看到执行失败,并且失败在哪个 Runnable 中,错误原因是什么
总结
本文主要介绍了 Langchain 的一些基本概念,并且通过集成 MistralAI 完成了第一个基于 LLM 的应用。这个过程主要帮助理解什么事 LCEL、什么是 Runnable;
初次之外,可以理解 PromptTemplate 的使用,帮助我们更好的开发 LLM 应用;
同时了解如何通过 LangSmith 监控我们的 LLM 应用;