Skip to content

Commit

Permalink
feat: 百度文心
Browse files Browse the repository at this point in the history
  • Loading branch information
lxfriday committed Aug 21, 2023
1 parent be9f8bc commit 2fb5978
Show file tree
Hide file tree
Showing 6 changed files with 302 additions and 2 deletions.
31 changes: 31 additions & 0 deletions demo/erniebot/demo1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ErnieBot } from '../../src/ErnieBot'

const bot = new ErnieBot({
apiKey: process.env.BAIDU_BCE_API_KEY as string,
secretKey: process.env.BAIDU_BCE_SK as string,
})

;(async () => {
await bot.sendStreamMessage({
initialMessages: [
{
role: 'user',
content: '介绍下你自己',
},
{
role: 'assistant',
content: '我是小胖AI,你的个人助手',
},
{
role: 'user',
content: '你叫什么?',
},
],
onProgress(t) {
process.stdout.write(t)
},
onEnd(t) {
console.log('end', t)
},
})
})()
52 changes: 52 additions & 0 deletions demo/raw/ernie-bot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import axios from 'axios'

async function getAccessToken(): Promise<string> {
const url =
`https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=${process.env.BAIDU_BCE_API_KEY}&client_secret=${process.env.BAIDU_BCE_SK}`
const response = await axios.post(url, '', {
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
})
return response.data.access_token
}

async function main() {
const url =
'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions?access_token=' +
(await getAccessToken())
const payload = {
messages: [
{
role: 'user',
content: '你是一个作家',
},
{
role: 'assistant',
content: '你好呀',
},
{
role: 'user',
content: '介绍下你自己',
},
{
role: 'assistant',
content: '我是文心一言',
},
],
}
const response = await axios.post(url, payload, {
headers: { 'Content-Type': 'application/json' },
})
console.log(response.data)
}

main()

// {
// id: 'as-cmmvbiyuf6',
// object: 'chat.completion',
// created: 1690189247,
// result: '您好,我是文心一言,英文名是ERNIE Bot。我能够与人对话互动,回答问题,协助创作,高效便捷地帮助人们获取信息、知识和灵感。',
// is_truncated: false,
// need_clear_history: false,
// usage: { prompt_tokens: 7, completion_tokens: 49, total_tokens: 56 }
// }
65 changes: 63 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"chalk": "^5.2.0",
"keyv": "^4.5.2",
"lru-cache": "^7.18.1",
"node-fetch": "^3.3.2",
"uuid": "^9.0.0"
},
"engines": {
Expand Down
115 changes: 115 additions & 0 deletions src/ErnieBot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import axios from 'axios'
import type {
IErnieBotAccessToken,
IErnieBotAccessTokenErr,
IErnieBotSendMessageOpts,
IErnieBotUserMessage,
IErnieBotAssistantMessage,
TErnieBotCommonMessage,
IErnieBotResponseErr,
} from './ErnieBotTypes'

interface IErnieBotParams {
apiKey: string
secretKey: string
debug?: boolean
}

export class ErnieBot {
#apiKey = ''
#model = ''
#secretKey = ''
#debug = false
constructor(opts: IErnieBotParams) {
const { apiKey, secretKey, debug = false } = opts
this.#apiKey = apiKey
this.#secretKey = secretKey
this.#debug = debug
}
private async getAccessToken(): Promise<string> {
const url = `https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=${
this.#apiKey
}&client_secret=${this.#secretKey}`

const response = await axios(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
validateStatus: (status) => true,
})

const data = response.data as IErnieBotAccessToken
if (!data.access_token)
throw { msg: 'access_token 获取失败', status: response.status }
return data.access_token
}
async sendStreamMessage(opts: IErnieBotSendMessageOpts) {
try {
const accessToken = await this.getAccessToken()
const apiUrl = `https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions?access_token=${accessToken}`

const requestData = {
messages: opts.initialMessages,
stream: true,
}

const headers = {
'Content-Type': 'application/json',
}

const axiosResponse = await axios.post(apiUrl, requestData, {
headers,
responseType: 'stream',
})
let responseText = ''
const stream = axiosResponse.data // 请求被取消之后变成 undefined
const status = axiosResponse.status
if (status === 200) {
let hasErr = false
let err: IErnieBotResponseErr
stream.on('data', (buf: Buffer) => {
try {
const dataArr = buf.toString().split('\n')
let onDataPieceText = ''
for (const dataStr of dataArr) {
// split 之后的空行,或者结束通知
if (dataStr[0] === '{') throw dataStr
if (dataStr.indexOf('data: ') !== 0) continue
const parsedData = JSON.parse(dataStr.slice(6)) // [data: ]
const pieceText = parsedData.result || ''
onDataPieceText += pieceText
}
opts.onProgress(onDataPieceText, buf.toString())
responseText += onDataPieceText
} catch (e) {
const dataStr = buf.toString()
const parsedData = JSON.parse(dataStr) // [data: ]
hasErr = true
err = parsedData
}
})
stream.on('end', async () => {
opts.onEnd({
success: !hasErr,
err,
text: responseText,
})
})
} else {
opts.onEnd({
success: false,
err: String(axiosResponse.data),
text: '',
})
}
} catch (error: any) {
opts.onEnd({
success: false,
err: String(error.message),
text: '',
})
}
}
}
40 changes: 40 additions & 0 deletions src/ErnieBotTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export interface IErnieBotAccessTokenErr {
error_description: string
error: string
}
export interface IErnieBotAccessToken {
refresh_token: string
expires_in: number
session_key: string
access_token: string
scope: string
session_secret: string
}
export interface IErnieBotUserMessage {
role: 'user'
content: string
}
export interface IErnieBotAssistantMessage {
role: 'assistant'
content: string
}
export type TErnieBotCommonMessage =
| IErnieBotUserMessage
| IErnieBotAssistantMessage

export interface IErnieBotResponseErr {
error_code: number
error_msg: string
id: string
}
export interface IErnieBotOnEndOpts {
success: boolean
err?: IErnieBotResponseErr | string
text: string
}
export interface IErnieBotSendMessageOpts {
initialMessages: TErnieBotCommonMessage[]
onProgress: (t: string, raw: string) => void
onEnd: (opts: IErnieBotOnEndOpts) => void
model?: string
}

0 comments on commit 2fb5978

Please sign in to comment.