diff --git a/javascript/package.json b/javascript/package.json index 0e378e64..755b502e 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -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/" diff --git a/javascript/src/Base/index.ts b/javascript/src/Base/index.ts index eedef5dd..493b050c 100644 --- a/javascript/src/Base/index.ts +++ b/javascript/src/Base/index.ts @@ -35,6 +35,7 @@ export class BaseClient { protected headers = DEFAULT_HEADERS; protected fetchInstance; protected fetchConfig: FetchConfig; + protected enableOauth: boolean; access_token = ''; expires_in = 0; @@ -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(); @@ -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), @@ -141,8 +144,8 @@ export class BaseClient { stream = false ): Promise { 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'); diff --git a/javascript/src/constant.ts b/javascript/src/constant.ts index 5f41d8d8..9ceb3cbe 100644 --- a/javascript/src/constant.ts +++ b/javascript/src/constant.ts @@ -34,6 +34,7 @@ export const DEFAULT_CONFIG: DefaultConfig = { QIANFAN_RPM_LIMIT: '', QIANFAN_TPM_LIMIT: '', version: '1', + ENABLE_OAUTH: false, }; export const RETRY_CODE = [ diff --git a/javascript/src/index.ts b/javascript/src/index.ts index 675acc13..238d5b17 100644 --- a/javascript/src/index.ts +++ b/javascript/src/index.ts @@ -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, +}; diff --git a/javascript/src/interface.ts b/javascript/src/interface.ts index 5f16411d..6b8a4121 100644 --- a/javascript/src/interface.ts +++ b/javascript/src/interface.ts @@ -27,6 +27,8 @@ export interface DefaultConfig { QIANFAN_RPM_LIMIT: string; QIANFAN_TPM_LIMIT: string; version: string; + // 浏览器字段是否开启鉴权,是则使用鉴权,否则不使用鉴权 + ENABLE_OAUTH: boolean; } /** diff --git a/javascript/src/streaming/index.ts b/javascript/src/streaming/index.ts index f9478f83..627a730c 100644 --- a/javascript/src/streaming/index.ts +++ b/javascript/src/streaming/index.ts @@ -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 = []; @@ -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 { @@ -164,35 +137,20 @@ export class Stream implements AsyncIterable { } const lineDecoder = new LineDecoder(); - let buffer = new Uint8Array(); // 初始化缓存的 Buffer - let previousChunkLastByte: number | null = null; + let buffer = new Uint8Array(); const iter = readableStreamAsyncIterable(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; } } @@ -207,7 +165,6 @@ export class Stream implements AsyncIterable { if (done) { continue; } - if (sse.data.startsWith('[DONE]')) { done = true; continue; @@ -227,8 +184,9 @@ export class Stream implements AsyncIterable { if (data && data.error) { throw new Error(data.error); } - - yield data; + if (data) { + yield data; + } } } done = true; @@ -413,3 +371,28 @@ export function readableStreamAsyncIterable(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)]; +} \ No newline at end of file diff --git a/javascript/src/utils.ts b/javascript/src/utils.ts index 2d91b3a0..2a77221a 100644 --- a/javascript/src/utils.ts +++ b/javascript/src/utils.ts @@ -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'; /** @@ -143,7 +143,7 @@ export function readEnvVariable(key: string) { * * @returns 返回一个字符串类型的键值对对象,包含环境变量 */ -export function getDefaultConfig(): Record { +export function getDefaultConfig(): DefaultConfig { const envVariables = Object.keys(DEFAULT_CONFIG); if (getCurrentEnvironment() === 'browser') { return {...DEFAULT_CONFIG}; @@ -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; + }); +} \ No newline at end of file