logo
8

快速上手:Node.js 接入文心一言 API

本文介绍如何去申请、调用ERNIE-Bot,实现一个 Node 版本的「文心一言」。
百度智能云目前已经向第三方开发者提供了接入大模型的 API,本文旨在介绍如何快速简单地在 Node.js 端接入文心 API。
关于文心的诸多能力,都被集成在了千帆大模型平台上(文档在这里),API 的购买、大模型的自定义训练、promp 模板参考等内容都可以在这里找到。
ERNIE-Bot 是 文心一言底层的模型,ERNIE-Bot API 是千帆平台上最基础的 API 之一,这次我们介绍的就是如何去申请、调用这个 API,实现一个 Node 版本的「文心一言」。

准备工作

目前使用千帆平台还需要申请,申请通过后根据文档中的说明创建应用,拿到 API Key 和 Secret Key 就可以了。
这两个值的作用是用来获取 Access Token,Access Token 是调用文心 API 的必需参数。Access Token 是通过将 API Key 和 Secret Key 作为参数请求一个独立的接口得到。本文用 axios 作为 Node 端的网络请求库,下面的函数用来请求接口,拿到 token:
  
  
  
  
  
  
import axios from 'axios';
const API_KEY = '<API Key>';
const SECRET_KEY = '<Secret Key>';
const ACCESS_TOKEN_URL = 'https://aip.baidubce.com/oauth/2.0/token';
// 获取 access token
async function fetchAccessToken() {
const accessTokenRes = await axios.post(ACCESS_TOKEN_URL, null, {
params: {
'grant_type': 'client_credentials',
'client_id': API_KEY,
'client_secret': SECRET_KEY,
},
});
return accessTokenRes.data.access_token;
}
Access Token 的有效期是 30 天,需要及时更新,在一个需要持久运行的 Node 应用里,最好把 Access Token 和它对应的生成时间成对记录,以便在 token 过期之前及时更新:
  
  
  
  
  
  
// 全局存储一个 access token -> 过期时间对象
let accessToken = {
expiredTime: 0,
value: '',
};
async function getAccessToken() {
if (accessToken.value && Date.now() < accessToken.expiredTime) {
return accessToken.value;
}
const token = await fetchAccessToken();
accessToken = {
expiredTime: Date.now() + 29 * 86400 * 1000, // 29 days
value: token,
};
return token;
}

调用 ERNIT-Bot API

基本调用

千帆平台上有多个模型的 API 可以调用,不同 API 用到的参数比较类似,这里用最基本的 ERNIT-Bot API 来做示例(文档在这里)。请求的地址是 https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions,Access Token 和对话信息是唯二的必填参数,前者以 query 的形式传入,后者则是作为 POST 请求体。下面是一个简单的调用示例:
  
  
  
  
  
  
const CHAT_URL = 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions';
async function ask(question) {
const messages = [{ role: 'user', content: question }];
const token = await getAccessToken();
const res = await axios.post(
CHAT_URL,
{ messages },
{ params: { 'access_token': token } }
);
const { data } = res;
return data;
}
用 Node 的命令行参数作为问题测试下一下:
  
  
  
  
  
  
async function main() {
const question = process.argv[2];
if (!question) {
console.log('Usage: node main.mjs <question>');
return;
}
const res = await ask(question);
console.log(res);
}
main();
可以看到,返回的内容里不仅包括回答的文本,还包括占用 token 数(与计费相关)、回答是否有被截断的等信息。
这个接口支持直接完整返回和流式返回两种方式,默认是一次性完整返回整个回答的内容。一次性返回的方式用起来比较简单,缺点是如果文心的回答内容比较长,等待返回的时间可能会稍微长一些(不过观察下来返回速度一直有在优化,现在已经挺快了)。流式返回则需要多加一个 stream: true参数,这样接口返回的 content-type 会是 text/event-stream
文心一言和 ChatGPT 的 Web 端都是用流式返回的形式实现的,这种方式的好处显而易见,可以在 Web 端看到回答生成的过程,用户体验会好很多。要实现这种效果需要在 Web 端用 EventSource 做配合。

多轮对话

OpenAI API 多轮对话是通过维护一个固定 conversation id 的形式实现的,文心 API 则有所不同,要实现多轮对话,需要在调用接口时携带上之前所有的提问和回答。比如最开始我们提问了「把我接下来说的话都翻译成英文」,文心回答「好的,我会尽力把您接下来想要表达的内容翻译成英文。请随时告诉我您想要说的话。」,那么我们再一次提问的时候,message 参数就需要是:
  
  
  
  
  
  
[
{ role: 'user', content: '把我接下来说的话都翻译成英文' },
{ role: 'assistant', content: '好的,我会尽力把您接下来想要表达的内容翻译成英文。请随时告诉我您想要说的话。' },
{ role: 'user', content: '你好,我是一名程序员。' }
]
可以看出,对话轮数越多,请求体就会越大。文心能接受的多轮对话 content 字符数有限,根据文档的说法,总 content 长度大于 2000 字符,文心就会开始遗忘更早的历史对话。
为了能比较方便地使用多轮对话的能力,可以封装一个对话类:
  
  
  
  
  
  
class Conversation {
constructor() {
this.messages = [];
}
async ask(question) {
this.messages.push({ role: 'user', content: prompt });
const token = await getAccessToken();
const res = await axios.post(
CHAT_URL,
{ messages: this.messages },
{ params: { 'access_token': token } }
);
const { data } = res;
this.messages.push({ role: 'assistant', content: data.result });
return data;
}
}
这样就可以比较方便地使用多轮对话的能力:
  
  
  
  
  
  
const conversation = new Conversation();
cosnt answerA = await conversation.ask('把我接下来说的话都翻译成英文').result;
cosnt answerB = await conversation.ask('你好,我是一名程序员。').result;

其他

  • API 三个参数 temperaturetop_ppenalty_score都有类似的作用,即让接口返回的回答更加随机多样,在需要生成大量文本的情况下,可以尝试使用
  • 本文调用的接口只提供了最基础的大模型对话能力,千帆平台上支持对既有的模型做调优工作,以适用更多特化的对话场景
  • prompt 的好坏对大模型返回的回答质量影响很大,应该也是因为这个千帆平台上搞了个「Prompt 工程」模块,便于大家学习对 AI 「提问的艺术」:)
评论
用户头像