8
快速上手:Node.js 接入文心一言 API
大模型开发/实践案例
- API
- 文心大模型
- LLM
2023.09.0410291看过
本文介绍如何去申请、调用ERNIE-Bot,实现一个 Node 版本的「文心一言」。
百度智能云目前已经向第三方开发者提供了接入大模型的 API,本文旨在介绍如何快速简单地在 Node.js 端接入文心 API。
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 tokenasync 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 daysvalue: 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 三个参数
temperature
、top_p
、penalty_score
都有类似的作用,即让接口返回的回答更加随机多样,在需要生成大量文本的情况下,可以尝试使用 -
本文调用的接口只提供了最基础的大模型对话能力,千帆平台上支持对既有的模型做调优工作,以适用更多特化的对话场景
-
prompt 的好坏对大模型返回的回答质量影响很大,应该也是因为这个千帆平台上搞了个「Prompt 工程」模块,便于大家学习对 AI 「提问的艺术」:)
评论