阅读视图

发现新文章,点击刷新页面。

第12章 支付宝SDK

支付宝SDK(Software Development Kit)是一套由支付宝官方提供的软件开发工具包,开发者可以将支付宝的核心功能便捷地集成到各类应用或网站中。其核心作用是打通商业场景与支付宝平台的支付、营销及数据能力,例如实现App或网页中的收款、转账、账单查询等交易流程,以及会员认证、芝麻信用、生活缴费等扩展服务。通过调用封装好的接口,开发者无需从零构建支付系统,即可安全高效地接入支付宝的生态服务。

支付宝的SDK支持多平台使用,包括iOS、Android、Web及多种后端语言版本(如Java、PHP、Python等),提供清晰的接口文档、示例代码和安全加固方案。开发者通过集成SDK,不仅能快速实现支付功能,还可利用支付宝的实名验证、风险监控等能力保障交易安全。典型应用场景涵盖电商购物、线下扫码、订阅扣费、公共服务等领域,大幅降低了企业接入移动支付的技术门槛与运维成本。

接下来,我们就学习如何接入支付宝SDK。需要从npm官网平台安装alipay-sdk,地址:www.npmjs.com/package/ali…

image-20251219191515750

图12-1 支付宝SDK接入文档

## 12.1 初始化项目

对支付宝SDK有一个初步的了解后,我们开始初始化对应的项目,需要如下两步:

(1)创建SDK项目文件夹,在该文件夹下创建index.ts文件(用于编写本次示例的逻辑代码)。

(2)npm安装alipay-sdk,并在package.json文件中设置ES模块。

# 安装命令
npm install alipay-sdk --save

安装好alipay-sdk库之后,package.json文件如下所示:

{
  "type": "module",
  "dependencies": {
    "alipay-sdk": "^4.14.0"
  }
}

12.2 初始化SDK

在支付宝SDK的接入文档中,初始化SDK有普通公钥模式和证书模式,选择普通公钥模式就可以。

import { AlipaySdk } from 'alipay-sdk';

// 实例化客户端
const alipaySdk = new AlipaySdk({
  // 设置应用 ID
  appId: 'your-APPID',
  // 设置应用私钥
  privateKey: fs.readFileSync('/path/to/private-key.pem', 'ascii'),
  // 设置支付宝公钥
  alipayPublicKey: fs.readFileSync('/path/to/alipay-public-key.pem', 'ascii'),
  // 密钥类型,请与生成的密钥格式保持一致,参考平台配置一节
  // keyType: 'PKCS1',
  // 设置网关地址,默认是 https://openapi.alipay.com
  // endpoint: 'https://openapi.alipay.com',
});

在接入文档所提供的示例中,需要应用私钥和支付宝公钥,需要先申请这两者。

应用私钥:opendocs.alipay.com/common/02ki…

通过点击上方应用私钥对应的地址链接,进入支付宝开放平台的密钥工具下载文档,如图12-2所示。在Windows和MacOS系统中,选择适配自己电脑的系统选项。

image-20251219195229876

图12-2 密钥下载文档

安装好支付宝开放平台密钥工具如下所示,选择运行支付宝开放平台密钥工具,如图12-3所示。然后点击完成进入下一步。

image-20251219195558500

图12-3 支付宝开放平台密钥工具安装

进入支付宝开放平台密钥工具后,选择生成密钥,其中加签方式和加密算法都不需要变动,直接生成密钥,如图12-4所示。

image-20251219195814919

图12-4 支付宝开放平台密钥生成

生成密钥之后,会生成应用公钥和应用私钥,如图12-5所示。先复制应用公钥,等下会使用到。

image-20251219195947690

图12-5 密钥工具生成应用公钥

支付宝公钥:openhome.alipay.com/develop/san…

通过点击上方支付宝公钥对应的地址链接,进入支付宝开放平台的控制条(用支付宝登录扫码),如图12-6所示。

image-20251219195034838

图12-6 支付宝开放平台-控制台

点击左侧边栏的沙箱应用,在界面展示中有开发信息选项,下方是接口加签方式,如图12-7所示。

image-20251219200627902

图12-7 支付宝开放平台-接口加签方式

在加签方式中选择自定义密钥,再选择公钥模式,进入设置并查看,会要求我们填写应用公钥,如图12-8所示。把图12-5的应用公钥粘贴到图12-8所示的位置。

image-20251219200705432

图12-8 加签内容配置

填入应用公钥并保存之后,会进入加签内容完成环节,通过应用公钥,生成对应的支付宝公钥。

image-20251219201141370

图12-9 加签内容完成

到目前为止,我们就通过密钥工具生成了应用公钥和应用私钥,然后通过应用公钥加签,得到支付宝公钥。则初始化SDK所需的应用私钥和支付宝公钥都已经获取到。

但应用私钥不能直接使用,需要对应用私钥进行转换,非Java环境(Node.js环境)的加密方式不太一样。点击如图12-5所示的打开文件位置,会有一个密钥+时间戳的文件夹,点击该文件夹内的应用私钥RSA2048,把应用私钥内部的内容复制一下,回到支付宝开放平台密钥工具的左侧边栏的格式转换,将应用私钥粘贴进去,转换成PKCS1格式的应用私钥,如图12-10所示。

image-20251219202809269

图12-10 应用私钥格式转换

## 12.3 案例搭建

现在可以回到项目中的index.ts文件中开始编写代码。其中appid不能随意填写,需要从支付宝开放平台的沙箱应用中,也就是如图12-6所在的位置,找到应用信息选项,在应用信息的基本信息中有APPID内容,如图12-11所示。将该内容复制到初始化SKD所需的appid中。

image-20251219203246647

图12-11 APPID获取方式

其次还需要支付宝网关地址,获取位置在应用信息下方的开发信息的支付宝网关地址中,这部分都是一致的,但之后是有可能发生变化的,所以如果使用下方的支付宝网关地址不行的话,就需要自行去支付宝开放平台获取。

如果项目要上线的话,需要将支付宝网关地址换成线上的,目前的网关地址是沙箱环境的测试地址(有很多的钱可以模拟测试)。

import { AlipaySdk } from 'alipay-sdk'

// 初始化SDK
const alipay = new AlipaySdk({
  // 设置应用ID
  appId: "",
  // 支付宝网关地址
  gateway: "https://openapi-sandbox.dl.alipaydev.com/gateway.do",
  // 设置应用私钥
  privateKey: "",
  // 设置应用公钥
  alipayPublicKey: ""
})

接下来,我们主要调用支付宝用于网站支付接口请求连接生成,传入前台访问输入密码完成支付的接口。在支付宝接口文档中有对应信息,示例内容如下。

const bizContent = {
  out_trade_no: "ALIPfdf1211sdfsd12gfddsgs3",
  product_code: "FAST_INSTANT_TRADE_PAY",
  subject: "abc",
  body: "234",
  total_amount: "0.01"
};

// 支付页面接口,返回 HTML 代码片段,内容为 Form 表单
const html = alipaySdk.pageExecute('alipay.trade.page.pay', 'POST', {
  bizContent,
  returnUrl: 'https://www.taobao.com'
});

我们需要做出如下修改:

(1)支付宝页面接口从alipaySdk切换为我们的alipay。

(2)将POST请求切换为GET请求,因为用不到表单。

const bizContent = {  // 定义业务参数对象,包含订单核心信息
  // 订单编号,通过算法生成(需确保唯一性)
  out_trade_no: "ALIPfdf1211sdfsd12gfddsgs3",  // 商户自定义订单号,用于支付宝系统识别
  product_code: "FAST_INSTANT_TRADE_PAY",  // 产品码,代表即时到账支付类型
  subject: "abc",  // 订单标题,显示在支付页面
  body: "234",  // 订单描述,可填写商品详情等附加信息
  total_amount: "0.01"  // 订单总金额(单位:元,需为字符串格式)
};

// 支付页面接口,返回 HTML 代码片段,内容为 Form 表单
const html = alipay.pageExecute('alipay.trade.page.pay', 'GET', {  // 调用支付宝SDK生成支付页面表单
  bizContent,  // 传入上述业务参数对象
  returnUrl: 'https://www.taobao.com'  // 设置支付完成后,用户会同步跳转的返回地址
});
console.log(html);

修改完成后,使用Node.js运行index.ts文件,会输出html信息,说明SDK搭建完成,如图12-12所示。

image-20251219205659434

图12-12 SDK搭建完成

SDK搭建完成之后,输出的html是一个URL地址,点击该地址进入浏览器,会进入支付宝即时到账交易界面,如图12-13所示。

image-20251219210252175

图12-13 支付宝即时到账交易界面

那么这次即时到账交易所需的支付宝账户名和支付密码从哪里获取?回到支付宝开放平台的控制台中,左侧边栏有沙箱账号,点击之后,会弹出商家信息和买家信息。我们复制买家账号和支付密码,回到即时到账交易界面进行填写,其中密码不能粘贴,需要手动输入。

image-20251219210536964

图12-14 支付宝沙箱账号

此时填写好信息之后,会进入收银台界面,如图12-15所示。需要再次输入支付宝支付密码,从而确定付款。

image-20251219210835265

图12-15 支付宝收银台

输入支付宝支付密码并确认付款之后,会显示交易付款成功,并且开始跳转至商户页面。支付宝交易付款成功如图12-16所示。

5bdd93c8c150570ff23bf10f9f2bbce8

图12-16 支付宝交易付款成功

跳转商户页面,实际会跳转到淘宝网页中,这是因为在代码中returnUrl字段信息所填写的是'www.taobao.com'(淘宝网页端)。

12.4 案例修改

但一般单页应用不需要重定向到商户页面,所以会使用异步通知的方式。

在单页应用(SPA)架构中,页面通过JavaScript动态更新内容而非整页刷新,若使用returnUrl进行同步跳转,会导致整个应用重新加载,破坏SPA的无刷新体验和前端路由状态。更重要的是,returnUrl的跳转依赖用户浏览器,可能因网络中断、页面关闭或拦截而丢失结果,且该方式仅作为支付完成后的引导跳转,其携带的URL参数易被篡改,不适合作为可信的业务状态变更依据。

异步通知(notify_url)由支付宝服务器在支付完成后主动回调商户后端接口,不依赖前端环境,保证了支付结果送达的可靠性。商户后端可独立验证签名、处理业务逻辑(如更新订单状态),再通过WebSocket、轮询或状态推送机制通知SPA前端更新界面,实现支付状态与业务数据的强一致性。这种前后端解耦的设计,既保持了SPA流畅的用户体验,也符合支付系统安全可靠的设计原则。

接下来回到index.ts文件,开始修改案例代码。

我们使用notify_url字段,但它只能接受一个公网地址,因为阿里服务器是没办法通知到我们电脑本地的。所以我们只有两种方式可以实现异步通知:

(1)将代码部署到服务器上。

(2)使用内网穿透。

// 支付页面接口,返回 HTML 代码片段,内容为 Form 表单
const html = alipay.pageExecute('alipay.trade.page.pay', 'GET', {  // 调用支付宝SDK生成支付页面表单
  bizContent,  // 传入上述业务参数对象
  notify_url:"",
  // returnUrl: 'https://www.taobao.com'  // 设置支付完成后用户同步跳转的返回地址
});

在内网开发的情况中,使用内网穿透是会更方便一些。

首先需要先提供一个接口地址,因此需要在本地起一个服务,我们通过express来操作。

npm i express
npm i --save-dev @types/express

编写如下express初始化代码,因为阿里的回调时通过Unicode的方式去回调,而不是通过JSON格式,所以需要通过express.urlencoded()方法去解析一下post请求。

import express from "express"

const app = express()
app.use(express.json())
app.use(express.urlencoded({ extended: true })) // 解析post请求

然后编写接口请求,如果不知道要通过post请求还是get请求,则可以使用all(同时支持post与get请求),其实使用post请求就可以。因为支付宝的支付结果是异步返回的,即用户支付成功后,支付宝服务器会主动向商户的后台服务器发送一个POST请求,通知支付结果。这个通知是异步的,不依赖于用户浏览器跳转(即使用户关闭浏览器,支付宝也会发送这个通知)。

具体来说,分以下3步:

(1)我们在发起支付请求时,通过notify_url参数指定支付宝服务器发送异步通知的地址(例如:yourdomain.com/alipay/noti…

(2)当支付完成后,支付宝服务器会向这个地址发送一个POST请求,携带支付结果的信息。

(3)我们的服务器接收到这个通知后,需要验证这个通知的合法性(验证签名),然后根据支付结果更新我们自己的订单状态,并返回一个成功响应给支付宝。

最后,监听本地3000端口。

app.all('/alipay/notify', (req, res) => {
  console.log(req.body);
  // 返回成功响应给支付宝
  res.send('success');
})

app.listen(3000, () => {
  console.log('Server is running on port 3000');
})

接下来需要内网穿透,然后将公网地址填写到notify_url字段中,启动express服务。重复支付宝的付款操作,当成功付款后,支付宝会回调一系列参数信息给我们。我们只需要把这些参数放入数据库中就可以。

第11章 LangChain

LangChain 是一个用于开发基于大语言模型(LLM)应用程序的开源框架,它通过提供模块化的抽象组件和链式调用工具,将 LLM 与外部数据源(如文档、数据库)和计算工具(如搜索引擎、代码解释器)智能连接,从而构建出具备记忆、推理和行动能力的增强型 AI 应用,典型场景包括智能问答、内容生成和智能体(Agent)系统。

LangChain官方文档:docs.langchain.com/。网址的组成逻辑和Ne…

LangChain支持Python和TypeScript两种编程语言,如图11-1所示。

image-20251219041533100

图11-1 LangChain开源代理框架-语言选项

LangChain开源代理框架有3种选择方案:

(1)LangChain:一些普通对话,音频识别、文字生成,图片生成等等与AIGC相关的,用这选择方案就够了。

(2)LangGraph:想做工作流,类似Dify,Coze,那么就需要使用该方案。

(3)Deep Agents:想做一些大型的AI相关高级应用,就需要使用该方案,Deep Agents是深度集成的意思。

LangChain 作为基础框架,适合构建常规的AIGC应用(如对话、文生图);LangGraph 专注于通过有状态、可循环的图结构来编排复杂、多步骤的智能体工作流,是开发类Dify/Coze平台或自动化业务流程的核心选择;而 Deep Agents 则代表了一种更深度集成、能处理高复杂度任务与自主决策的高级智能体架构,常用于需要多智能体协作或模拟人类工作流的大型企业级AI应用。

我们这里学习的话,使用第一个LangChain就完全够用。LangChain下载使用说明如图11-2所示。我们点击如图11-1所示的第一个选项后,会跳转到如图11-2所示的界面,需要点击左侧边栏的install选项。官方文档有对应的使用说明。

image-20251219042243761

图11-2 LangChain下载使用说明

## 11.1 初始化项目

接下来,我们要开始初始化这次的项目。会沿用第10章 SSE魔改的代码,在该基础上,需要补充以下安装步骤:

(1)@langchain/core:LangChain 的核心基础库,包含链、提示模板、检索器等核心抽象。

(2)@langchain/deepseek: LangChain 为DeepSeek 模型专门提供的集成包,让我们能在 LangChain 框架中直接调用 DeepSeek 的 API。安装规则是@langchain/所需AI大模型,例如@langchain/openai。

(3)langchain:LangChain 的主包,提供了高级、易于使用的接口来组合和使用 LLM。

安装LangChain之后,我们需要一个AI大模型来支持,在这次示例中,选择DeepSeek,因为它的API非常便宜。

// 终端执行命令
npm install @langchain/core @langchain/deepseek langchain

安装之后的package.json文件如下所示。

{
  "type": "module",
  "dependencies": {
    "@langchain/core": "^1.1.6",
    "@langchain/deepseek": "^1.0.3",
    "@types/cors": "^2.8.19",
    "@types/express": "^5.0.6",
    "cors": "^2.8.5",
    "express": "^5.2.1",
    "langchain": "^1.2.1"
  }
}

由于我们在7.1小节已经升级过Node.js版本,因此可以直接用Node.js去运行ts后缀文件,满足Node.js版本大于23的都可以这么做,如果无法运行ts后缀文件,需要检查一下Node.js版本或者采用ts-node。

接下来到index.ts文件中初始化后端服务。

// index.ts
import express from 'express'
import cors from 'cors'

const app = express()
app.use(cors())
app.use(express.json())

app.post('/api/chat', async (req, res) => { })

app.listen(3000, () => {
  console.log('Server is running on port 3000')
})

11.2 接入大模型

接着接入我们的AI大模型DeepSeek,还是在index.ts文件中。

在这里引入了一个key.ts文件,该文件存放着DeepSeek API的Key。

import { ChatDeepSeek } from '@langchain/deepseek'
import { key } from './key.ts'
const deepseek = new ChatDeepSeek({
    apiKey: key,
    model: 'deepseek-chat',
    temperature: 1.3,
    maxTokens: 1000, //500-600个汉字
    topP: 1, //设得越小,AI 说话越"死板";设得越大,AI 说话越"放飞自我"
    frequencyPenalty: 0,//防复读机诉 AI:"你别老重复同一个词!"-2   2
    presencePenalty: 0,//鼓励换话题告诉 AI:"别老聊同一件事!" -2   2
})

获取DeepSeek的key,如下4步骤:

(1)打开DeepSeek的API开放平台:platform.deepseek.com/

(2)微信扫码登录,去实名认证。

(3)点击左侧边栏的用量信息选择去充值选项,有在线充值和对公汇款两个选项,选择在线充值,自定义输入1块钱(够用了),然后自己选择支付宝或者微信支付去付款。

(4)付款成功后,点击左侧边栏的API keys选项,创建API key,随便输入一个名称(中英文都可以),然后会弹出key值,此时复制key值再关闭弹窗,因为当你关闭后,就再也拿不到这个key值了。忘记就只能重新再建一个,DeepSeek会提醒你的,如图11-3所示。

友情提示:保护好你的key值,别暴露在公网中,在开源项目上传GitHub中,可以让git忽略key.ts文件。或者如果你的DeepSeek API就只充了1块钱,然后用得剩几毛钱,并且以后都不怎么打算充,那想不想保护key值,就看你心情了。

image.png

图11-3 创建API key注意事项

通过以上步骤获取到DeepSeek的key值后,在项目创建key.ts文件,创建常量key,填入你的key值并导出。

export const key = '你DeepSeek的key值'

回到index.ts文件,接入DeepSeek大模型之后,ChatDeepSeek有一个model字段,这是用于选择我们模型的。已有的模型类型需要从DeepSeek官方文档中获取:模型 & 价格 | DeepSeek API Docs

// DeepSeek字段
apiKey: key,
model: 'deepseek-chat',
temperature: 1.3,
maxTokens: 1000, // 500-600个汉字
topP: 1, // 设得越小,AI 说话越"死板";设得越大,AI 说话越"放飞自我"
frequencyPenalty: 0,// 防复读机诉 AI:"你别老重复同一个词!"-2   2
presencePenalty: 0,// 鼓励换话题告诉 AI:"别老聊同一件事!" -2   2

目前可选的模型有deepseek-chat和deepseek-reasoner。DeepSeek官网价格计算是以百万Token为单位,其中1 个英文字符 ≈ 0.3 个 token;1 个中文字符 ≈ 0.6 个 token。只要大概知道很便宜就足够了。

image-20251219050542592

图11-4 DeepSeek模型选择

temperature字段是温度的含义,在DeepSeek官方文档中有直接给出对应的建议,我们的示例是打算用于对话,因此设置1.3就足够了,Temperature参数设置如图11-5所示。

从应用场景,我们可以理解为temperature字段大概是理性与感性的权衡度,逻辑性越强的场景,温度越低;越感性的场景温度越高。所有AI大模型都是类似的,从他们对应的官方文档去获取对应信息就可以了。

image-20251219050952588

图11-5 DeepSeek模型-Temperature参数设置

其余4个参数maxTokens、topP,frequencyPenalty和presencePenalty如下:

(1)maxTokens 直接决定了 AI 回复的最大长度,限制了单次响应的文本量;

(2)topP(核采样参数)通过控制候选词的概率分布来影响文本的创造性与稳定性——值越低则 AI 的选词越集中和可预测,输出趋于“死板”,值越高则选词范围越宽,输出越“放飞自我”并富有创意。

(3)而 frequencyPenalty 与 presencePenalty 则分别从词频和话题层面抑制重复:frequencyPenalty 正值会惩罚在当前回复中已经频繁出现的词语,促使用词更加多样;presencePenalty 正值则会惩罚在已生成的上下文中出现过的所有主题,鼓励 AI 主动切换到新的话题或角度,从而共同确保生成内容的多样性和连贯性,避免陷入单调或循环重复的表达。

这些值具体设置多少,则需要根据具体场景的经验以及自身的理解,推荐看我写的AI使用手册,开头有讲解到这一部分注意力机制:AI精准提问手册:从模糊需求到精准输出的核心技能(上)

11.3 AI对话

接下来需要从langchain引入createAgent方法,并使用我们设置好的deepseek实例对象。我们调用agent身上的invoke()方法,该方法更适合单次输出(一次性直接返回),即非流式返回。

通过createAgent方法除了可以设置接入的大模型,还可以通过systemPrompt字段去设置Prompt提示词。

通过LangChain代理的stream()方法调用DeepSeek模型处理用户请求:将客户端发送的req.body.message作为用户消息输入,并设置streamMode为 "messages" 来获取结构化的消息流响应;在等待代理完成流式生成后,将整个结果集作为JSON数据一次性返回给客户端。

import express from 'express'
import cors from 'cors'
import { ChatDeepSeek } from '@langchain/deepseek'
import { key } from './key.ts'
import { createAgent } from 'langchain'
const deepseek = new ChatDeepSeek({
  apiKey: key,
  model: 'deepseek-chat',
  temperature: 1.3,
  maxTokens: 1000, // 500-600个汉字
  topP: 1, // 设得越小,AI 说话越"死板";设得越大,AI 说话越"放飞自我"
  frequencyPenalty: 0, // 防复读机诉 AI:"你别老重复同一个词!"-2   2
  presencePenalty: 0, // 鼓励换话题告诉 AI:"别老聊同一件事!" -2   2
})

const app = express()
app.use(cors())
app.use(express.json())

app.post('/api/chat', async (req, res) => {
  res.setHeader('Content-Type', 'application/json')
  res.setHeader('Cache-Control', 'no-cache')
  res.setHeader('Connection', 'keep-alive')
  const agent = createAgent({
    model: deepseek,
    systemPrompt: `你是一个聊天机器人,请根据用户的问题给出回答。`,
  })
  const result = await agent.invoke({
    messages: [
      {
        role: 'user',
        content: req.body.message,
      }
    ]
  })
  res.json(result)
})

app.listen(3000, () => {
  console.log('Server is running on port 3000')
})

接下来回到index.html文件一下,我们需要设置客户端返回给后端的问题,也就是往req.body.message里塞一下咨询AI的问题。

<script>
    fetch('http://localhost:3000/api/chat', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({ message: '请问你是什么AI大模型' })
    }).then(async res=>{
        const reader = res.body.getReader()
        const decoder = new TextDecoder()
        while (true) {
            const { done, value } = await reader.read()
            if (done) {
                break
            }
            const text = decoder.decode(value, { stream: true })
            console.log(text)
        }
    })
</script>

我们问了一个:“你是什么AI大模型”的问题,浏览器返回AI对话信息如图11-6所示。

image-20251219054656481

图11-6 DeepSeek模型-Temperature参数设置

到这为止,我们就正式打通了AI对话的环节。并且如果我们打开网络选项卡,可以发现AI对话返回的内容是post请求的。如果我们想改成流式输出也是post请求,在第10.3小节所学习的SSE设置post请求就可以用上了。

11.4 流式输出AI对话

如果我们想修改成流式输出对话的话,需要修改3个地方:

(1)后端设置的Content-Type类型改成事件流类型。

(2)agent不使用invoke()方法,该换专门的agent.stream()流输出方法,并调整对应参数。

(3)agent.stream()流输出方法返回迭代器,针对迭代器去调整输出形式。

import express from 'express'
import cors from 'cors'
import { ChatDeepSeek } from '@langchain/deepseek'
import { key } from './key.ts'
import { createAgent } from 'langchain'
const deepseek = new ChatDeepSeek({
  apiKey: key,
  model: 'deepseek-chat',
  temperature: 1.3,
  maxTokens: 1000, // 500-600个汉字
  topP: 1, // 设得越小,AI 说话越"死板";设得越大,AI 说话越"放飞自我"
  frequencyPenalty: 0, // 防复读机诉 AI:"你别老重复同一个词!"-2   2
  presencePenalty: 0, // 鼓励换话题告诉 AI:"别老聊同一件事!" -2   2
})

const app = express()
app.use(cors())
app.use(express.json())

app.post('/api/chat', async (req, res) => {
  res.setHeader('Content-Type', 'application/event-stream')
  res.setHeader('Cache-Control', 'no-cache')
  res.setHeader('Connection', 'keep-alive')
  const agent = createAgent({
    model: deepseek,
    systemPrompt: `你是一个聊天机器人,请根据用户的问题给出回答。`,
  })
  const result = await agent.stream({
    messages: [
      {
        role: 'user',
        content: req.body.message,
      }
    ]
  }, { streamMode: "messages" })
  for await (const chunk of result) {
    res.write(`data: ${JSON.stringify(chunk)}\n\n`)
  }
  res.end()
})

app.listen(3000, () => {
  console.log('Server is running on port 3000')
})

agent.stream()方法有第二个参数,用于指定流式输出的数据格式和粒度,决定了从流中接收到的是原始令牌、结构化消息还是其他中间结果。agent.stream()方法第二个参数的选项如表11-1所示。我们选择messages就可以了。如果想要真正存粹的打字效果并且节约token,可以使用values选项。

表11-1 agent.stream()方法第二个参数的选项

流模式 返回的数据类型 典型用途 示例输出(逐块)
"messages" 完整的消息对象 需要处理结构化对话(如获取AI回复的完整消息) {"role": "assistant", "content": "你好"}
"values" 底层值(如原始token) 需要实现逐字打印效果或最低级控制 "你" "好"
"stream" 混合事件流 需要同时获取token和消息等多样信息 {"type": "token", "value": "你"}
const result = await agent.stream({
    messages: [
      {
        role: 'user',
        content: req.body.message,
      }
    ]
  }, { streamMode: "values" })

其次,由于agent.stream()方法的返回值类型是IterableReadableStream<StreamMessageOutput>,说明返回值就是一个迭代器。因此可以使用for await of语法糖来流式输出内容,不用手动的去调用迭代器的next()方法。

  for await (const chunk of result) {
    res.write(`data: ${JSON.stringify(chunk)}\n\n`)
  }
  res.end()

AI对话-流式输出如图11-7所示。会按顺序返回非常多的JSON格式数据,通过data字段下的kwargs的content可以看到AI返回内容以三两字的形式不断输出。并且在前端的接收流式输出,不会因post请求而出现问题。

image-20251219060557208

图11-7 AI对话-流式输出

流式输出AI对话的完整代码如下:

// index.ts
import express from 'express'
import cors from 'cors'
import { ChatDeepSeek } from '@langchain/deepseek'
import { key } from './key.ts'
import { createAgent } from 'langchain'
const deepseek = new ChatDeepSeek({
  apiKey: key,
  model: 'deepseek-chat',
  temperature: 1.3,
  maxTokens: 1000, // 500-600个汉字
  topP: 1, // 设得越小,AI 说话越"死板";设得越大,AI 说话越"放飞自我"
  frequencyPenalty: 0, // 防复读机诉 AI:"你别老重复同一个词!"-2   2
  presencePenalty: 0, // 鼓励换话题告诉 AI:"别老聊同一件事!" -2   2
})

const app = express()
app.use(cors())
app.use(express.json())

app.post('/api/chat', async (req, res) => {
  res.setHeader('Content-Type', 'application/event-stream')
  res.setHeader('Cache-Control', 'no-cache')
  res.setHeader('Connection', 'keep-alive')
  const agent = createAgent({
    model: deepseek,
    systemPrompt: `你是一个聊天机器人,请根据用户的问题给出回答。`,
  })
  const result = await agent.stream({
    messages: [
      {
        role: 'user',
        content: req.body.message,
      }
    ]
  }, { streamMode: "messages" })
  for await (const chunk of result) {
    res.write(`data: ${JSON.stringify(chunk)}\n\n`)
  }
  res.end()
})

app.listen(3000, () => {
  console.log('Server is running on port 3000')
})
// index.html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    fetch('http://localhost:3000/api/chat', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ message: '请问你是什么AI大模型' })
    }).then(async res => {
      const reader = res.body.getReader()
      const decoder = new TextDecoder()
      while (true) {
        const { done, value } = await reader.read()
        if (done) {
          break
        }
        const text = decoder.decode(value, { stream: true })
        console.log(text)
      }
    })
  </script>
</body>

</html>

以上就是使用LangChain接入DeepSeek大模型并实现AI对话和基础提示词的案例。

❌