Skip to content

Commit

Permalink
Merge pull request #21 from zhengxs2018/llmapi
Browse files Browse the repository at this point in the history
  • Loading branch information
CGQAQ authored Dec 6, 2023
2 parents a7ca27f + 1837b33 commit d1b42b2
Show file tree
Hide file tree
Showing 21 changed files with 1,696 additions and 140 deletions.
11 changes: 10 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,13 @@ QWEN_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxx"

# OpenAI
# See https://platform.openai.com/account/api-keys
OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxx"
OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxx"

# Minimax
# See https://api.minimax.chat/user-center/basic-information/interface-key
MINIMAX_API_ORG="xxxxxxxx"
MINIMAX_API_KEY="eyJhxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# Imagine Art
# see https://platform.imagine.art/dashboard
VYRO_API_KEY="vk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/node_modules
node_modules/

.nx/installation
.nx/cache
.env
Expand Down
1 change: 1 addition & 0 deletions .node-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
18.x.x
4 changes: 4 additions & 0 deletions web/core/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ const { defineConfig } = require('eslint-define-config')

module.exports = defineConfig({
root: true,
env: {
node: true,
browser: true
},
extends: [
'eslint:recommended',
],
Expand Down
37 changes: 37 additions & 0 deletions web/llmapi/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "@studio-b3/llmapi",
"version": "0.0.1",
"type": "module",
"main": "dist/index.mjs",
"types": "dist-types/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs"
},
"./package.json": "./package.json"
},
"typesVersions": {
"*": {
"*": [
"./dist-types/index.d.ts",
"./dist-types/*"
]
}
},
"sideEffects": false,
"files": [
"dist",
"dist-types",
"src"
],
"scripts": {
"watch": "vite build --watch",
"build": "vite build",
"lint": "eslint . --ext ts,tsx,.cjs --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint . --ext .ts,.cjs --fix --fix-type [problem,suggestion]"
},
"dependencies": {
"openai": "^4.20.0"
},
"devDependencies": {}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import OpenAI, { APIError, OpenAIError } from "openai";
import { APIClient, type Fetch } from "openai/core";
import { Stream } from "openai/streaming";
import OpenAI, { APIError, OpenAIError } from 'openai';
import { APIClient, type Fetch } from 'openai/core';
import { Stream } from 'openai/streaming';

export type ErnieAPIOptions = {
import { APIResource } from './resource';
import { ensureArray } from './util';

export type ErnieAIOptions = {
baseURL?: string;
token?: string;
timeout?: number | undefined;
Expand All @@ -15,14 +18,14 @@ export type ErnieAPIOptions = {
// 之前 AI Studio 的文档是有文档的,但现在不知道去哪了
// 参考:
// - https://cloud.baidu.com/doc/WENXINWORKSHOP/s/jlil56u11
// - https://github.com/PaddlePaddle/ERNIE-Bot-SDK/blob/develop/erniebot/backends/aistudio.py
export class ErnieAPI extends APIClient {
// - https://github.com/PaddlePaddle/ERNIE-Bot-SDK/blob/develop/ErnieAI/backends/aistudio.py
export class ErnieAI extends APIClient {
protected token: string;

constructor(options?: ErnieAPIOptions) {
constructor(options?: ErnieAIOptions) {
const {
token = process.env.AISTUDIO_ACCESS_TOKEN || "",
baseURL = "https://aistudio.baidu.com/llm/lmapi/v1",
token = process.env.AISTUDIO_ACCESS_TOKEN || '',
baseURL = 'https://aistudio.baidu.com/llm/lmapi/v1',
timeout = 30000,
fetch = globalThis.fetch,
httpAgent = undefined,
Expand All @@ -37,8 +40,6 @@ export class ErnieAPI extends APIClient {
...rest,
});

// ok(token, "token is required");

this.token = token;
}

Expand All @@ -55,14 +56,6 @@ export class ErnieAPI extends APIClient {
}
}

export class APIResource {
protected _client: APIClient;

constructor(client: APIClient) {
this._client = client;
}
}

export class Chat extends APIResource {
completions = new Completions(this._client);
}
Expand All @@ -72,38 +65,38 @@ export class Completions extends APIResource {
// 使用模型名称是为了和 OpenAI 的 API 保持一致
// 同时也是为了方便使用
protected resources: Map<
ErnieBot.ChatModel,
ErnieAI.ChatModel,
{
id: ErnieBot.ChatModel;
id: ErnieAI.ChatModel;
endpoint: string;
}
> = new Map([
[
"ernie-bot",
'ernie-bot',
{
id: "ernie-bot",
endpoint: "/chat/completions",
id: 'ernie-bot',
endpoint: '/chat/completions',
},
],
[
"ernie-bot-turbo",
'ernie-bot-turbo',
{
id: "ernie-bot-turbo",
endpoint: "/chat/eb-instant",
id: 'ernie-bot-turbo',
endpoint: '/chat/eb-instant',
},
],
[
"ernie-bot-4",
'ernie-bot-4',
{
id: "ernie-bot-4",
endpoint: "/chat/completions_pro",
id: 'ernie-bot-4',
endpoint: '/chat/completions_pro',
},
],
[
"ernie-bot-8k",
'ernie-bot-8k',
{
id: "ernie-bot-8k",
endpoint: "/chat/ernie_bot_8k",
id: 'ernie-bot-8k',
endpoint: '/chat/ernie_bot_8k',
},
],
]);
Expand All @@ -127,7 +120,7 @@ export class Completions extends APIResource {
OverrideOpenAIChatCompletionCreateParams,
options?: OpenAI.RequestOptions
) {
const { model = "ernie-bot", ...body } = this.buildCreateParams(params);
const { model = 'ernie-bot', ...body } = this.buildCreateParams(params);
const resource = this.resources.get(model);

if (!resource) {
Expand All @@ -139,7 +132,7 @@ export class Completions extends APIResource {
const headers = {
...options?.headers,
// Note: 如果是 stream 的话,需要设置 Accept 为 text/event-stream
Accept: stream ? "text/event-stream" : "application/json",
Accept: stream ? 'text/event-stream' : 'application/json',
};

const response: Response = await this._client.post(resource.endpoint, {
Expand All @@ -155,7 +148,7 @@ export class Completions extends APIResource {
if (stream) {
const controller = new AbortController();

options?.signal?.addEventListener("abort", () => {
options?.signal?.addEventListener('abort', () => {
controller.abort();
});

Expand All @@ -172,22 +165,22 @@ export class Completions extends APIResource {
protected buildCreateParams(
params: OpenAI.ChatCompletionCreateParams &
OverrideOpenAIChatCompletionCreateParams
): ErnieBot.ChatCompletionCreateParams {
): ErnieAI.ChatCompletionCreateParams {
const { messages = [], presence_penalty, user, stop, ...rest } = params;

const head = messages[0];

// 文心一言的 system 是独立字段
//(1)长度限制1024个字符
//(2)如果使用functions参数,不支持设定人设system
const system = head && head.role === "system" ? head.content : undefined;
const system = head && head.role === 'system' ? head.content : undefined;

// 移除 system 角色的消息
if (system) {
messages.splice(0, 1);
}

const data: ErnieBot.ChatCompletionCreateParams = {
const data: ErnieAI.ChatCompletionCreateParams = {
...rest,
system,
messages,
Expand All @@ -209,10 +202,6 @@ export class Completions extends APIResource {
}
}

function ensureArray<T>(value: T | T[]): T[] {
return Array.isArray(value) ? value : [value];
}

/**
* 如果 code 不为 0,抛出 APIError
*
Expand Down Expand Up @@ -265,7 +254,7 @@ function makeAPIError(code: number, message: string) {
*/
function fromOpenAIStream(
model: string,
stream: Stream<ErnieBot.APIResponse>,
stream: Stream<ErnieAI.APIResponse>,
controller: AbortController
): Stream<OpenAI.ChatCompletionChunk> {
async function* iterator(): AsyncIterator<
Expand All @@ -282,7 +271,7 @@ function fromOpenAIStream(
const choice: OpenAI.ChatCompletionChunk.Choice = {
index: 0,
delta: {
role: "assistant",
role: 'assistant',
content: data.result || '',
},
finish_reason: null,
Expand All @@ -291,18 +280,18 @@ function fromOpenAIStream(
// TODO 需要确认 is_truncated 是否和 is_end 互斥
// TODO 需要确认 functions 是否响应式不一样
if (data.is_end) {
choice.finish_reason = "stop";
choice.finish_reason = 'stop';
} else if (data.is_truncated) {
choice.finish_reason = "length";
choice.finish_reason = 'length';
} else if (data.need_clear_history) {
choice.finish_reason = "content_filter";
choice.finish_reason = 'content_filter';
}

yield {
id: data.id,
model,
choices: [choice],
object: "chat.completion.chunk",
object: 'chat.completion.chunk',
created: parseInt(data.created, 10),
};
}
Expand All @@ -317,7 +306,7 @@ function fromOpenAIStream(
*/
function fromResponse(
model: string,
data: ErnieBot.APIResponse
data: ErnieAI.APIResponse
): OpenAI.ChatCompletion {
const { errorCode, errorMsg, result } = data;

Expand All @@ -326,41 +315,41 @@ function fromResponse(
const choice: OpenAI.ChatCompletion.Choice = {
index: 0,
message: {
role: "assistant",
role: 'assistant',
content: result.result,
},
finish_reason: "stop",
finish_reason: 'stop',
};

// TODO 需要确认 is_truncated 是否和 is_end 互斥
// TODO 需要确认 functions 是否响应式不一样
if (result.is_end) {
choice.finish_reason = "stop";
choice.finish_reason = 'stop';
} else if (result.is_truncated) {
choice.finish_reason = "length";
choice.finish_reason = 'length';
} else if (result.need_clear_history) {
choice.finish_reason = "content_filter";
choice.finish_reason = 'content_filter';
}

return {
id: result.id,
model: model,
choices: [choice],
created: parseInt(result.created, 10),
object: "chat.completion",
object: 'chat.completion',
usage: result.usage,
};
}

// 用于覆盖 OpenAI.ChatCompletionCreateParams 的参数
type OverrideOpenAIChatCompletionCreateParams = {
model: ErnieBot.ChatModel;
model: ErnieAI.ChatModel;
disable_search?: boolean | null;
enable_citation?: boolean | null;
};

// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace ErnieBot {
export namespace ErnieAI {
export type ChatModel =
| 'ernie-bot'
| 'ernie-bot-turbo'
Expand All @@ -371,7 +360,7 @@ export namespace ErnieBot {
/**
* 模型名称
*/
model: ErnieBot.ChatModel;
model: ErnieAI.ChatModel;

/**
* 是否强制关闭实时搜索功能,默认 false,表示不关闭
Expand Down Expand Up @@ -478,3 +467,5 @@ export namespace ErnieBot {
result: APIResult;
};
}

export default ErnieAI;
Loading

0 comments on commit d1b42b2

Please sign in to comment.