Langchain.js 入门及应用:HelloWorld、模型集成、PromptTemplate、LangSmith

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 直接预览流程图,如下图:
  • 商业系统和服务:

    • LangSmith:在线 debug 平台(及服务提供),如果是使用 langchain 提供的模型集成包集成 LLM,每一步的执行都会通过日志在 LangSmith 监控,可以方便的 debug和排查问题。除此之外还提供类似 prompt 模板管理、一些部署能力.
    • LangGraph Cloud:为基于 LangGraph 创建的应用流提供部署服务(将一个流部署成 REST API),提供给其他场景调用和执行流;与 LangSmith 集成,实现监控、调试等能力;提供了一个 Studio: LangGraph Studio ,可以理解为开发 agent 的 IDE,进行可视化的调试、开发非常复杂的 graph。

一些介绍:langchain-ai.github.io/langgraph/c...

准备模型

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 有下面几种消息类型:

github.com/langchain-a... t

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

smith.langchain.com

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 应用;

相关推荐
我有一棵树32 分钟前
输入框相关,一篇文章总结所有前端文本输入的应用场景和实现方法,(包含源码,建议收藏)
前端·vue.js·富文本编辑器·输入框·textarea
会蹦的鱼36 分钟前
算法16(力扣451)——根据字符出现频率排序
前端·算法·leetcode
java_python源码41 分钟前
[含文档+PPT+源码等]精品基于Python实现的django个性化健康餐计划订制系统
开发语言·前端·python
盖泽1 小时前
【Vue 计算属性 vs 监听器:深度对比与哲学思考】
前端
关山月1 小时前
使用 TinyMCE 和 Vue.js 实现评论
前端·vue.js
晚时之秋2 小时前
vue中使用lodash的debounce(防抖函数)
前端·javascript·vue.js
游客5202 小时前
自动化办公|xlwings快速入门
linux·前端·python
momo_via2 小时前
前端技术学习——ES6核心基础
前端·javascript·es6
℡52Hz★2 小时前
Node.js开发属于自己的npm包(发布到npm官网)
前端·npm·node.js
Hello.Reader2 小时前
Rust 文件读取:实现我们的 “迷你 grep”
服务器·前端·rust