Skip to content

Commit

Permalink
feat: 浏览器SDK (#533)
Browse files Browse the repository at this point in the history
* v0.0.10-beta.6

* v0.0.10-beta.7

* v0.0.10-beta.8

* feat: 浏览器SDK

* fix: 文档更新

* fix: 文档更新

---------

Co-authored-by: wangting31 <[email protected]>
  • Loading branch information
wangting829 and wangting31 authored May 17, 2024
1 parent a189acd commit 274f08d
Show file tree
Hide file tree
Showing 9 changed files with 580 additions and 284 deletions.
Binary file added docs/imgs/proxy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 58 additions & 2 deletions javascript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ yarn add @baiducloud/qianfan

SDK 支持从当前目录的 .env 中读取配置,也可以修改环境变量 QIANFAN_ACCESS_KEY 和 QIANFAN_SECRET_KEY ,同时支持初始化手动传入 AK/SK 。

浏览器使用不需要鉴权,连接proxy使用,方法如下:

1. 需要先安装python>=3.8
2. pip install qianfan
3. qianfan proxy
在执行qianfan proxy的同级目录下,新建 .env文件,设置 QIANFAN_ACCESS_KEY 和 QIANFAN_SECRET_KEY 即可

注意:在Vue或react项目中集成使用时,需确保webpack为4以下,如果5以上版本需要根据提示配置polyfills,在后续的迭代中会逐步优化。

![proxy](../docs/imgs/proxy.png)

#### env 读取

##### env 文件示例
Expand All @@ -54,7 +65,7 @@ QIANFAN_ACCESS_KEY=another_access_key
QIANFAN_SECRET_KEY=another_secret_key
```

#### 修改 env 的配置
#### 修改 env 的配置 (仅 node可使用)

```ts
import {setEnvVariable} from "@baiducloud/qianfan";
Expand All @@ -77,12 +88,18 @@ const client = new ChatCompletion({ QIANFAN_ACCESS_KEY: '***', QIANFAN_SECRET_KE
可以使用 `ChatCompletion` 对象完成对话相关操作

```ts
// node 环境
import {ChatCompletion} from "@baiducloud/qianfan";

// 直接读取 env
const client = new ChatCompletion();
// 手动传 AK/SK
// const client = new ChatCompletion({ QIANFAN_AK: '***', QIANFAN_SK: '***'});

// 浏览器环境,必须传入QIANFAN_BASE_URL,(proxy启动后地址), QIANFAN_CONSOLE_API_BASE_URL不传时,只能使用预置模型,传入后可以使用动态模型
import {ChatCompletion} from "@baiducloud/qianfan";
const client = new ChatCompletion({QIANFAN_BASE_URL: 'http://172.18.184.85:8002', QIANFAN_CONSOLE_API_BASE_URL: 'http://172.18.184.85:8003'});

async function main() {
const resp = await client.chat({
messages: [
Expand Down Expand Up @@ -123,13 +140,18 @@ async function main() {
对于不需要对话,仅需要根据 prompt 进行补全的场景来说,用户可以使用 `Completions` 来完成这一任务。

```ts
// node环境
import {Completions} from "@baiducloud/qianfan";
// 直接读取 env
const client = new Completions();

// 手动传 AK/SK
// const client = new Completions({ QIANFAN_AK: '***', QIANFAN_SK: '***'});

// 浏览器环境,必须传入QIANFAN_BASE_URL,(proxy启动后地址), QIANFAN_CONSOLE_API_BASE_URL不传时,只能使用预置模型,传入后可以使用动态模型
import {Completions} from "@baiducloud/qianfan";
const client = Completions({QIANFAN_BASE_URL: 'http://172.18.184.85:8002', QIANFAN_CONSOLE_API_BASE_URL: 'http://172.18.184.85:8003'});

async function main() {
const resp = await client.completions({
prompt: 'Introduce the city Beijing',
Expand Down Expand Up @@ -160,12 +182,18 @@ main();
千帆 SDK 同样支持调用千帆大模型平台中的模型,将输入文本转化为用浮点数表示的向量形式。转化得到的语义向量可应用于文本检索、信息推荐、知识挖掘等场景。

```ts
// node环境
import {Eembedding} from "@baiducloud/qianfan";
// 直接读取 env
const client = new Eembedding();

// 手动传 AK/SK 测试
// const client = new Eembedding({ QIANFAN_AK: '***', QIANFAN_SK: '***'});

// 浏览器环境,必须传入QIANFAN_BASE_URL,(proxy启动后地址), QIANFAN_CONSOLE_API_BASE_URL不传时,只能使用预置模型,传入后可以使用动态模型
import {Eembedding} from "@baiducloud/qianfan";
const client = Eembedding({QIANFAN_BASE_URL: 'http://172.18.184.85:8002', QIANFAN_CONSOLE_API_BASE_URL: 'http://172.18.184.85:8003'});

async function main() {
const resp = await client.embedding({
input: [ 'Introduce the city Beijing'],
Expand All @@ -185,13 +213,18 @@ main();
Stable-Diffusion-XL

```ts
// node环境
import * as http from 'http';
import {Text2Image} from "@baiducloud/qianfan";
// 直接读取 env
const client = new Text2Image();

// 手动传 AK/SK 测试
// const client = new Text2Image({ QIANFAN_AK: '***', QIANFAN_SK: '***'});

// 浏览器环境,必须传入QIANFAN_BASE_URL,(proxy启动后地址), QIANFAN_CONSOLE_API_BASE_URL不传时,只能使用预置模型,传入后可以使用动态模型
import {Text2Image} from "@baiducloud/qianfan";
const client = Text2Image({QIANFAN_BASE_URL: '***', QIANFAN_CONSOLE_API_BASE_URL: '***'});

async function main() {
const resp = await client.text2Image({
prompt: '生成爱莎公主的图片',
Expand Down Expand Up @@ -224,12 +257,18 @@ main();
注意事项:调用本文API,推荐使用安全认证AK/SK鉴权,调用流程及鉴权介绍详见SDK安装及使用流程

```ts
// node 环境
import {Image2Text} from "@baiducloud/qianfan";
// 直接读取 env
const client = new Image2Text({Endpoint: '***'});

// 手动传 AK/SK 测试
// const client = new Image2Text({ QIANFAN_AK: '***', QIANFAN_SK: '***'});

// 浏览器环境,必须传入QIANFAN_BASE_URL,(proxy启动后地址), QIANFAN_CONSOLE_API_BASE_URL不传时,只能使用预置模型,传入后可以使用动态模型
import {Image2Text} from "@baiducloud/qianfan";
const client = Image2Text({QIANFAN_BASE_URL: 'http://172.18.184.85:8002', QIANFAN_CONSOLE_API_BASE_URL: 'http://172.18.184.85:8003'});

async function main() {
const resp = await client.image2Text({
prompt: '分析一下图片画了什么',
Expand All @@ -242,11 +281,16 @@ main();
```

```ts
// node环境
import {Image2Text} from "@baiducloud/qianfan";
// 直接读取 env
// 使用预置服务Fuyu-8B
const client = new Image2Text();

// 浏览器环境,必须传入QIANFAN_BASE_URL,(proxy启动后地址), QIANFAN_CONSOLE_API_BASE_URL不传时,只能使用预置模型,传入后可以使用动态模型
import {Image2Text} from "@baiducloud/qianfan";
const client = Image2Text({QIANFAN_BASE_URL: 'http://172.18.184.85:8002', QIANFAN_CONSOLE_API_BASE_URL: 'http://172.18.184.85:8003'});

// 手动传 AK/SK 测试
// const client = new Image2Text({ QIANFAN_AK: '***', QIANFAN_SK: '***'});
async function main() {
Expand All @@ -267,9 +311,15 @@ SDK支持使用平台插件能力,以帮助用户快速构建 LLM 应用或将
#### 千帆插件

```ts
// node环境
import {Plugin} from "@baiducloud/qianfan";
// 注意:千帆插件需要传入Endpoint, 一言插件不用
const client = new Plugin({Endpoint: '***'});

// 浏览器环境,必须传入QIANFAN_BASE_URL,(proxy启动后地址), QIANFAN_CONSOLE_API_BASE_URL不传时,只能使用预置模型,传入后可以使用动态模型
import {Plugin} from "@baiducloud/qianfan";
const client = Image2Text({QIANFAN_BASE_URL: 'http://172.18.184.85:8002', QIANFAN_CONSOLE_API_BASE_URL: 'http://172.18.184.85:8003'});

// 天气插件
async function main() {
const resp = await client.plugins({
Expand Down Expand Up @@ -317,12 +367,18 @@ main();
参数传入 stream 为 `true` 时,返回流式结果

```ts
// node环境
import {Plugin} from "@baiducloud/qianfan";
// 直接读取 env
const client = new Plugin();

// 手动传 AK/SK 测试
// const client = new Plugins({ QIANFAN_AK: '***', QIANFAN_SK: '***'});

// 浏览器环境,必须传入QIANFAN_BASE_URL,(proxy启动后地址), QIANFAN_CONSOLE_API_BASE_URL不传时,只能使用预置模型,传入后可以使用动态模型
import {Plugin} from "@baiducloud/qianfan";
const client = Image2Text({QIANFAN_BASE_URL: 'http://172.18.184.85:8002', QIANFAN_CONSOLE_API_BASE_URL: 'http://172.18.184.85:8003'});

async function main() {
const stream = await client.plugins({
query: '深圳今天天气如何',
Expand Down
11 changes: 4 additions & 7 deletions javascript/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@baiducloud/qianfan",
"version": "0.0.10",
"version": " 0.0.11-alpha.0",
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
Expand All @@ -26,14 +26,12 @@
"@types/node-fetch": "^2.6.11",
"async-mutex": "^0.5.0",
"bottleneck": "^2.19.5",
"debug": "^3.1.0",
"crypto-browserify": "^3.12.0",
"dotenv": "^16.4.1",
"node-fetch": "2.7.0",
"rollup": "^3.29.4",
"tslib": "^2.6.2",
"typescript": "^5.3.3",
"underscore": "^1.9.1",
"urlencode": "^1.1.0"
"rollup-plugin-node-polyfills": "^0.2.1",
"typescript": "^5.3.3"
},
"devDependencies": {
"@babel/core": "^7.24.0",
Expand All @@ -50,7 +48,6 @@
"@rollup/plugin-typescript": "^11.1.6",
"@types/jest": "^29.5.11",
"@types/node": "^20.11.13",
"@types/underscore": "^1.11.15",
"@typescript-eslint/parser": "^7.1.0",
"babel-jest": "^29.7.0",
"eslint": "^8.57.0",
Expand Down
10 changes: 8 additions & 2 deletions javascript/src/Fetch/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import nodeFetch, {RequestInit, Response} from 'node-fetch';
import {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, getCurrentEnvironment, parseHeaders} from '../utils';
import {Resp, RespBase, AsyncIterableType} from '../interface';

const fetchInstance = getCurrentEnvironment() === 'node' ? nodeFetch : fetch;
let fetchInstance;
if (getCurrentEnvironment() === 'node') {
fetchInstance = require('node-fetch');
}
else {
fetchInstance = window.fetch.bind(window);
}
export interface FetchConfig {
retries: number;
timeout: number;
Expand Down
45 changes: 18 additions & 27 deletions javascript/src/HttpClient/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import * as util from 'util';
import * as crypto from 'crypto';
import _ from 'underscore';
import createDebug from 'debug';
import crypto from 'crypto-browserify';

import * as H from './headers';
import {normalize, trim} from './strings';
import {getCurrentEnvironment} from '../utils';

const debug = createDebug('bce-sdk:Auth');
let cryptoClass;
if (getCurrentEnvironment() === 'node') {
cryptoClass = require('crypto');
}
else {
cryptoClass = crypto;
}

class Auth {
private ak: string;
Expand All @@ -41,35 +45,23 @@ class Auth {
headersToSign?: string[]
): string {
const now = this.getTimestamp(timestamp);
const rawSessionKey = util.format('bce-auth-v1/%s/%s/%d', this.ak, now, expirationInSeconds || 1800);
debug('rawSessionKey = %j', rawSessionKey);
const rawSessionKey = `bce-auth-v1/${this.ak}/${now}/${expirationInSeconds || 1800}`;
const sessionKey = this.hash(rawSessionKey, this.sk);
const canonicalUri = this.generateCanonicalUri(resource);
const canonicalQueryString = this.queryStringCanonicalization(params || {});

const rv = this.headersCanonicalization(headers || {}, headersToSign);
const canonicalHeaders = rv[0];
const signedHeaders = rv[1];
debug('canonicalUri = %j', canonicalUri);
debug('canonicalQueryString = %j', canonicalQueryString);
debug('canonicalHeaders = %j', canonicalHeaders);
debug('signedHeaders = %j', signedHeaders);

const rawSignature = util.format(
'%s\n%s\n%s\n%s',
method,
canonicalUri,
canonicalQueryString,
canonicalHeaders
);
debug('rawSignature = %j', rawSignature);
debug('sessionKey = %j', sessionKey);

const rawSignature = `${method}\n${canonicalUri}\n${canonicalQueryString}\n${canonicalHeaders}`;

const signature = this.hash(rawSignature, sessionKey);

if (signedHeaders.length) {
return util.format('%s/%s/%s', rawSessionKey, signedHeaders.join(';'), signature);
return `${rawSessionKey}/${signedHeaders.join(';')}/${signature}`;
}
return util.format('%s//%s', rawSessionKey, signature);
return `${rawSessionKey}//${signature}`;
}

private normalize(string: string, encodingSlash: boolean = true): string {
Expand Down Expand Up @@ -143,7 +135,6 @@ class Auth {
if (!headersToSign || !headersToSign.length) {
headersToSign = [H.HOST, H.CONTENT_MD5, H.CONTENT_LENGTH, H.CONTENT_TYPE];
}
debug('headers = %j, headersToSign = %j', headers, headersToSign);

const headersMap: Record<string, boolean> = {};
headersToSign.forEach(item => {
Expand All @@ -153,13 +144,13 @@ class Auth {
const canonicalHeaders: string[] = [];
Object.keys(headers).forEach(key => {
let value = headers[key];
value = _.isString(value) ? trim(value) : value;
value = Object.prototype.toString.call(value) === '[object String]' ? trim(value) : value;
if (value == null || value === '') {
return;
}
key = key.toLowerCase();
if (/^x\-bce\-/.test(key) || headersMap[key] === true) {
canonicalHeaders.push(util.format('%s:%s', normalize(key), normalize(value)));
canonicalHeaders.push(`${normalize(key)}:${normalize(value)}`);
}
});

Expand All @@ -174,7 +165,7 @@ class Auth {
}

private hash(data: string, key: string): string {
const sha256Hmac = crypto.createHmac('sha256', key);
const sha256Hmac = cryptoClass.createHmac('sha256', key);
sha256Hmac.update(data);
return sha256Hmac.digest('hex');
}
Expand Down
Loading

0 comments on commit 274f08d

Please sign in to comment.