Skip to content

Commit

Permalink
feat: 浏览器SDK (#525)
Browse files Browse the repository at this point in the history
Co-authored-by: wangting31 <wangting31@baidu.com>
  • Loading branch information
wangting829 and wangting31 authored May 15, 2024
1 parent 8847bb2 commit 86adc61
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 55 deletions.
135 changes: 91 additions & 44 deletions javascript/src/Base/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import HttpClient from '../HttpClient';
import Fetch, {FetchConfig} from '../Fetch/fetch';
import {DEFAULT_HEADERS} from '../constant';
import {getAccessTokenUrl, getIAMConfig, getDefaultConfig, getPath} from '../utils';
import {getAccessTokenUrl, getIAMConfig, getDefaultConfig, getPath, getCurrentEnvironment} from '../utils';
import {Resp, AsyncIterableType, AccessTokenResp} from '../interface';
import DynamicModelEndpoint from '../DynamicModelEndpoint';

Expand Down Expand Up @@ -107,55 +107,102 @@ export class BaseClient {
requestBody: string,
stream = false
): Promise<Resp | AsyncIterableType> {
// 检查鉴权信息
if (!(this.qianfanAccessKey && this.qianfanSecretKey) && !(this.qianfanAk && this.qianfanSk)) {
throw new Error('请设置AK/SK或QIANFAN_ACCESS_KEY/QIANFAN_SECRET_KEY');
}

// 判断当前环境,node需要鉴权,浏览器不需要鉴权(需要设置proxy的baseUrl、consoleUrl)·
const env = getCurrentEnvironment();
let fetchOptions;
// IAM鉴权
if (this.qianfanAccessKey && this.qianfanSecretKey) {
const config = getIAMConfig(this.qianfanAccessKey, this.qianfanSecretKey, this.qianfanBaseUrl);
const client = new HttpClient(config);
const dynamicModelEndpoint = new DynamicModelEndpoint(
client,
this.qianfanConsoleApiBaseUrl,
this.qianfanBaseUrl
);
let IAMPath = '';
if (this.Endpoint) {
IAMPath = getPath({
Authentication: 'IAM',
api_base: this.qianfanBaseUrl,
endpoint: this.Endpoint,
type,
});
if (env === 'node') {
// 检查鉴权信息
if (!(this.qianfanAccessKey && this.qianfanSecretKey) && !(this.qianfanAk && this.qianfanSk)) {
throw new Error('请设置AK/SK或QIANFAN_ACCESS_KEY/QIANFAN_SECRET_KEY');
}
else {
IAMPath = await dynamicModelEndpoint.getEndpoint(type, model);
// IAM鉴权
if (this.qianfanAccessKey && this.qianfanSecretKey) {
const config = getIAMConfig(this.qianfanAccessKey, this.qianfanSecretKey, this.qianfanBaseUrl);
const client = new HttpClient(config);
const dynamicModelEndpoint = new DynamicModelEndpoint(
client,
this.qianfanConsoleApiBaseUrl,
this.qianfanBaseUrl
);
let IAMPath = '';
if (this.Endpoint) {
IAMPath = getPath({
Authentication: 'IAM',
api_base: this.qianfanBaseUrl,
endpoint: this.Endpoint,
type,
});
}
else {
IAMPath = await dynamicModelEndpoint.getEndpoint(type, model);
}
if (!IAMPath) {
throw new Error(`${model} is not supported`);
}
fetchOptions = await client.getSignature({
httpMethod: 'POST',
path: IAMPath,
body: requestBody,
headers: this.headers,
});
}
if (!IAMPath) {
throw new Error(`${model} is not supported`);
// AK/SK鉴权
if (this.qianfanAk && this.qianfanSk) {
if (this.expires_in < Date.now() / 1000) {
await this.getAccessToken();
}
const url = `${AKPath}?access_token=${this.access_token}`;
fetchOptions = {
url: url,
method: 'POST',
headers: this.headers,
body: requestBody,
};
}
fetchOptions = await client.getSignature({
httpMethod: 'POST',
path: IAMPath,
body: requestBody,
headers: this.headers,
});
}
// AK/SK鉴权
if (this.qianfanAk && this.qianfanSk) {
if (this.expires_in < Date.now() / 1000) {
await this.getAccessToken();
else if (env === 'browser') {
// 浏览器环境 需要设置proxy
if (this.qianfanBaseUrl.includes('aip.baidubce.com')) {
throw new Error('请设置proxy的baseUrl');
}
// 如果设置了管控api,则使用管控api获取最新模型
if (this.qianfanConsoleApiBaseUrl && !this.qianfanConsoleApiBaseUrl.includes('qianfan.baidubce.com')) {
const dynamicModelEndpoint = new DynamicModelEndpoint(
null,
this.qianfanConsoleApiBaseUrl,
this.qianfanBaseUrl
);
let IAMPath = '';
if (this.Endpoint) {
IAMPath = getPath({
Authentication: 'IAM',
api_base: this.qianfanBaseUrl,
endpoint: this.Endpoint,
type,
});
}
else {
IAMPath = await dynamicModelEndpoint.getEndpoint(type, model);
}
if (!IAMPath) {
throw new Error(`${model} is not supported`);
}
fetchOptions = {
url: `${this.qianfanBaseUrl}${IAMPath}`,
method: 'POST',
headers: this.headers,
body: requestBody,
};
}
else {
const url = `${AKPath}`;
fetchOptions = {
url: url,
method: 'POST',
headers: this.headers,
body: requestBody,
};
}
const url = `${AKPath}?access_token=${this.access_token}`;
fetchOptions = {
url: url,
method: 'POST',
headers: this.headers,
body: requestBody,
};
}
try {
const {url, ...rest} = fetchOptions;
Expand Down
24 changes: 20 additions & 4 deletions javascript/src/DynamicModelEndpoint/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,32 @@ class DynamicModelEndpoint {
*/
async updateDynamicModelEndpoint(type: string) {
const url = `${this.qianfanConsoleApiBaseUrl}${SERVER_LIST_API}`;
const fetchOption = await this.client.getSignature({
httpMethod: 'POST',
path: url,
let fetchOption = {};
// 设置默认的请求选项
const defaultOptions = {
body: JSON.stringify({
apiTypefilter: [type],
}),
headers: {
...DEFAULT_HEADERS,
},
});
};
// 如果有node环境,则获取签名
if (this.client !== null) {
fetchOption = await this.client.getSignature({
...defaultOptions,
httpMethod: 'POST',
path: url,
});
}
// 浏览器环境下走proxy代理,不需要签名
else {
fetchOption = {
...defaultOptions,
method: 'POST',
url,
};
}

try {
const res = await this.fetchInstance.makeRequest(url, fetchOption);
Expand Down
7 changes: 4 additions & 3 deletions javascript/src/Fetch/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import fetch, {RequestInit, Response} from 'node-fetch';
import nodeFetch, {RequestInit, Response} from 'node-fetch';
import {Readable} from 'stream';
import {RateLimiter, TokenLimiter} from '../Limiter';
import {RETRY_CODE} from '../constant';
import {Stream} from '../streaming';
import {isOpenTpm} from '../utils';
import {isOpenTpm, getCurrentEnvironment} from '../utils';
import {Resp, RespBase, AsyncIterableType} from '../interface';

const fetchInstance = getCurrentEnvironment() === 'node' ? nodeFetch : fetch;
export interface FetchConfig {
retries: number;
timeout: number;
Expand Down Expand Up @@ -129,7 +130,7 @@ export class Fetch {
const timeout = setTimeout(() => controller.abort(), ms);

return this.rateLimiter.schedule(() =>
fetch(url, {signal: controller.signal as any, ...options})
fetchInstance(url, {signal: controller.signal as any, ...(options as any)})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error, status = ${response.status}`);
Expand Down
18 changes: 14 additions & 4 deletions javascript/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import dotenv from 'dotenv';

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

dotenv.config({path: '../.env'});

/**
* 获取访问令牌的URL地址
*
Expand Down Expand Up @@ -250,3 +246,17 @@ export function getUpperCaseModelAndModelMap(model: string, modelMap?: QfLLMInfo
modelUppercase,
};
}

/**
* 获取当前运行环境
*
* @returns 如果运行在浏览器中,返回 'browser';如果运行在 Node.js 环境中,返回 'node';否则返回 'unknown'
*/
export function getCurrentEnvironment() {
if (typeof window !== 'undefined') {
return 'browser';
} else if (typeof process !== 'undefined' && process.release.name === 'node') {
return 'node';
}
return 'unknown';
}

0 comments on commit 86adc61

Please sign in to comment.