Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 浏览器增加鉴权开关 & 流式数据切分优化 #689

Merged
merged 3 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion javascript/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@baiducloud/qianfan",
"version": "0.1.5",
"version": "0.1.6-beta.0",
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
Expand Down
7 changes: 5 additions & 2 deletions javascript/src/Base/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export class BaseClient {
protected headers = DEFAULT_HEADERS;
protected fetchInstance;
protected fetchConfig: FetchConfig;
protected enableOauth: boolean;
access_token = '';
expires_in = 0;

Expand All @@ -49,6 +50,7 @@ export class BaseClient {
QIANFAN_LLM_API_RETRY_BACKOFF_FACTOR?: string;
QIANFAN_LLM_API_RETRY_COUNT?: string;
QIANFAN_LLM_RETRY_MAX_WAIT_INTERVAL?: string;
ENABLE_OAUTH: boolean;
Endpoint?: string;
}) {
const defaultConfig = getDefaultConfig();
Expand All @@ -66,6 +68,7 @@ export class BaseClient {
= options?.QIANFAN_LLM_API_RETRY_BACKOFF_FACTOR ?? defaultConfig.QIANFAN_LLM_API_RETRY_BACKOFF_FACTOR;
this.qianfanLlmApiRetryCount
= options?.QIANFAN_LLM_API_RETRY_COUNT ?? defaultConfig.QIANFAN_LLM_API_RETRY_COUNT;
this.enableOauth = options?.ENABLE_OAUTH ?? defaultConfig.ENABLE_OAUTH;
this.controller = new AbortController();
this.fetchInstance = new Fetch({
maxRetries: Number(this.qianfanLlmApiRetryCount),
Expand Down Expand Up @@ -141,8 +144,8 @@ export class BaseClient {
stream = false
): Promise<Resp | AsyncIterableType> {
let fetchOptions;
// 如果baseUrl是aip.baidubce.com,证明用户未配置proxy url,则认为需要放开鉴权
if (this.qianfanBaseUrl.includes('aip.baidubce.com')) {
// 如果enableOauth开启, 则放开鉴权
if (this.enableOauth) {
// 检查鉴权信息
if (!(this.qianfanAccessKey && this.qianfanSecretKey) && !(this.qianfanAk && this.qianfanSk)) {
throw new Error('请设置AK/SK或QIANFAN_ACCESS_KEY/QIANFAN_SECRET_KEY');
Expand Down
1 change: 1 addition & 0 deletions javascript/src/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const DEFAULT_CONFIG: DefaultConfig = {
QIANFAN_RPM_LIMIT: '',
QIANFAN_TPM_LIMIT: '',
version: '1',
ENABLE_OAUTH: false,
};

export const RETRY_CODE = [
Expand Down
14 changes: 12 additions & 2 deletions javascript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ import Embedding from './Embedding';
import Plugin from './Plugin';
import {Text2Image, Image2Text} from './Images';
import Reranker from './Reranker';
import {setEnvVariable} from './utils';
import {setEnvVariable, setBrowserVariable} from './utils';

export {ChatCompletion, Completions, Embedding, Plugin, Text2Image, Image2Text, Reranker, setEnvVariable};
export {
ChatCompletion,
Completions,
Embedding,
Plugin,
Text2Image,
Image2Text,
Reranker,
setEnvVariable,
setBrowserVariable,
};
2 changes: 2 additions & 0 deletions javascript/src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export interface DefaultConfig {
QIANFAN_RPM_LIMIT: string;
QIANFAN_TPM_LIMIT: string;
version: string;
// 浏览器字段是否开启鉴权,是则使用鉴权,否则不使用鉴权
ENABLE_OAUTH: boolean;
}

/**
Expand Down
99 changes: 41 additions & 58 deletions javascript/src/streaming/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,15 @@ class SSEDecoder {
}

decode(line: string) {
if (line.endsWith('\r')) {
line = line.substring(0, line.length - 1);
}

if (!line) {
if (!this.event && !this.data.length) {
return null;
}

const sse: ServerSentEvent = {
event: this.event,
data: this.data.join('\n'),
data: this.data.join(''),
raw: this.chunks,
};

this.event = null;
this.data = [];
this.chunks = [];
Expand Down Expand Up @@ -88,33 +82,12 @@ class LineDecoder {
textDecoder: TextDecoder;

constructor() {
this.buffer = [];
this.textDecoder = new TextDecoder('utf-8');
}

decode(chunk: Bytes): string[] {
decode(chunk: Bytes): string {
let text = this.decodeText(chunk);
if (!text) {
return [];
}

const trailingNewline = text.endsWith('\n') || text.endsWith('\r');
let lines = text.split(LineDecoder.NEWLINE_REGEXP);

if (lines.length === 1 && !trailingNewline) {
this.buffer.push(lines[0]);
return [];
}
if (this.buffer.length > 0) {
lines = [this.buffer.join('') + lines[0], ...lines.slice(1)];
this.buffer = [];
}

if (!trailingNewline) {
this.buffer = [lines.pop() || ''];
}

return lines;
return text;
}

decodeText(bytes: Bytes): string {
Expand Down Expand Up @@ -164,35 +137,20 @@ export class Stream<Item> implements AsyncIterable<Item> {
}

const lineDecoder = new LineDecoder();
let buffer = new Uint8Array(); // 初始化缓存的 Buffer
let previousChunkLastByte: number | null = null;
let buffer = new Uint8Array();
const iter = readableStreamAsyncIterable<Bytes>(response.body);

for await (const chunk of iter) {
if (previousChunkLastByte === 10) {
buffer = concatUint8Arrays(buffer, chunk as Uint8Array);

for (const line of lineDecoder.decode(buffer)) {
const sse = decoder.decode(line);
if (sse) {
yield sse;
}
buffer = concatUint8Arrays(buffer, chunk as Uint8Array);
// 按换行符(ASCII 码 10)分割 buffer
const [lines, remaining] = splitUint8Array(buffer, 10);
for (const line of lines) {
const lineStr = lineDecoder.decode(line);
const sse = decoder.decode(lineStr);
if (sse) {
yield sse;
}

buffer = new Uint8Array();
}
else {
buffer = concatUint8Arrays(buffer, chunk as Uint8Array);
}
// 保存当前 chunk 的最后一个字节
previousChunkLastByte = chunk[chunk.length - 1] as number; ;
}

for (const line of lineDecoder.flush()) {
const sse = decoder.decode(line);
if (sse) {
yield sse;
}
buffer = remaining;
}
}

Expand All @@ -207,7 +165,6 @@ export class Stream<Item> implements AsyncIterable<Item> {
if (done) {
continue;
}

if (sse.data.startsWith('[DONE]')) {
done = true;
continue;
Expand All @@ -227,8 +184,9 @@ export class Stream<Item> implements AsyncIterable<Item> {
if (data && data.error) {
throw new Error(data.error);
}

yield data;
if (data) {
yield data;
}
}
}
done = true;
Expand Down Expand Up @@ -413,3 +371,28 @@ export function readableStreamAsyncIterable<T>(stream: any): AsyncIterableIterat
},
};
}


/**
* 使用指定的分隔符将 Uint8Array 数组拆分为多个子数组和剩余部分。
*
* @param array 要拆分的 Uint8Array 数组。
* @param delimiter 分隔符的数值。
* @returns 包含拆分后的多个子数组和剩余部分的数组。
* 第一个元素是拆分后的子数组列表,类型为 Uint8Array[]。
* 第二个元素是剩余部分的 Uint8Array 数组。
*/
function splitUint8Array(array: Uint8Array, delimiter: number): [Uint8Array[], Uint8Array] {
const result: Uint8Array[] = [];
let start = 0;

for (let i = 0; i < array.length; i++) {
if (array[i] === delimiter) {
result.push(array.subarray(start, i));
start = i + 1; // 跳过 delimiter
}
}

// 返回分割后的数组和剩余的部分
return [result, array.subarray(start)];
}
20 changes: 18 additions & 2 deletions javascript/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// limitations under the License.

import {BASE_PATH, DEFAULT_CONFIG} from './constant';
import {IAMConfig, QfLLMInfoMap, ReqBody} from './interface';
import {IAMConfig, QfLLMInfoMap, ReqBody, DefaultConfig} from './interface';
import * as packageJson from '../package.json';

/**
Expand Down Expand Up @@ -143,7 +143,7 @@ export function readEnvVariable(key: string) {
*
* @returns 返回一个字符串类型的键值对对象,包含环境变量
*/
export function getDefaultConfig(): Record<string, string> {
export function getDefaultConfig(): DefaultConfig {
const envVariables = Object.keys(DEFAULT_CONFIG);
if (getCurrentEnvironment() === 'browser') {
return {...DEFAULT_CONFIG};
Expand Down Expand Up @@ -286,3 +286,19 @@ export function parseHeaders(headers): {[key: string]: string} {
});
return headerObj;
}

interface Variables {
[key: string]: any;
}

/**
* 设置浏览器变量
*
* @param variables 要设置的变量对象,其中每个属性名对应一个变量名,属性值对应变量的值
* @returns 无返回值
*/
export function setBrowserVariable(variables: Variables): void {
Object.entries(variables).forEach(([key, value]) => {
DEFAULT_CONFIG[key] = value;
});
}