@kevisual/ai 0.0.26 → 0.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +33 -0
- package/dist/agent.js +65 -0
- package/dist/ai-provider-browser.d.ts +2 -140
- package/dist/ai-provider-browser.js +2 -19455
- package/dist/ai-provider.d.ts +2 -140
- package/dist/ai-provider.js +2 -1340
- package/package.json +18 -11
- package/src/agent/index.ts +67 -0
- package/src/provider/chat-adapter/cnb.ts +1 -1
- package/src/provider/index.ts +0 -2
- package/src/provider/utils/ai-config-type.ts +0 -52
- package/src/provider/utils/index.ts +0 -2
- package/src/provider/utils/parse-config.ts +0 -192
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kevisual/ai",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.28",
|
|
4
4
|
"description": "AI Center Services",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"basename": "/root/ai-center-services",
|
|
@@ -14,9 +14,8 @@
|
|
|
14
14
|
"types"
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
|
-
"build": "npm run clean && bun bun.config.
|
|
18
|
-
"dev": "bun run --watch bun.config.
|
|
19
|
-
"test": "tsx test/**/*.ts",
|
|
17
|
+
"build": "npm run clean && bun bun.config.ts",
|
|
18
|
+
"dev": "bun run --watch bun.config.ts",
|
|
20
19
|
"clean": "rm -rf dist",
|
|
21
20
|
"pub": "envision pack -p -u"
|
|
22
21
|
},
|
|
@@ -27,7 +26,7 @@
|
|
|
27
26
|
],
|
|
28
27
|
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
|
29
28
|
"license": "MIT",
|
|
30
|
-
"packageManager": "pnpm@10.
|
|
29
|
+
"packageManager": "pnpm@10.32.1",
|
|
31
30
|
"type": "module",
|
|
32
31
|
"publishConfig": {
|
|
33
32
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -37,30 +36,38 @@
|
|
|
37
36
|
".": "./dist/ai-provider-browser.js",
|
|
38
37
|
"./ai-provider": "./dist/ai-provider.js",
|
|
39
38
|
"./browser": "./dist/ai-provider-browser.js",
|
|
39
|
+
"./agent": "./dist/agent.js",
|
|
40
40
|
"./src/*": "./src/*"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@kevisual/
|
|
43
|
+
"@kevisual/code-builder": "^0.0.6",
|
|
44
|
+
"@kevisual/router": "0.1.1",
|
|
44
45
|
"@kevisual/types": "^0.0.12",
|
|
45
46
|
"@kevisual/use-config": "^1.0.30",
|
|
46
|
-
"@types/bun": "^1.3.
|
|
47
|
+
"@types/bun": "^1.3.10",
|
|
47
48
|
"@types/crypto-js": "^4.2.2",
|
|
48
|
-
"@types/formidable": "^3.
|
|
49
|
-
"@types/node": "^25.
|
|
49
|
+
"@types/formidable": "^3.5.0",
|
|
50
|
+
"@types/node": "^25.4.0",
|
|
50
51
|
"cross-env": "^10.1.0",
|
|
51
52
|
"crypto-js": "^4.2.0",
|
|
52
53
|
"dayjs": "^1.11.19",
|
|
53
54
|
"dotenv": "^17.3.1",
|
|
54
55
|
"formidable": "^3.5.4",
|
|
55
|
-
"openai": "6.
|
|
56
|
+
"openai": "6.27.0",
|
|
56
57
|
"pm2": "^6.0.14",
|
|
57
58
|
"rimraf": "^6.1.3",
|
|
58
59
|
"typescript": "^5.9.3",
|
|
59
60
|
"vite": "^7.3.1"
|
|
60
61
|
},
|
|
61
62
|
"dependencies": {
|
|
63
|
+
"@ai-sdk/anthropic": "^3.0.58",
|
|
64
|
+
"@ai-sdk/openai": "^3.0.41",
|
|
65
|
+
"@ai-sdk/openai-compatible": "^2.0.35",
|
|
66
|
+
"@kevisual/js-filter": "^0.0.6",
|
|
62
67
|
"@kevisual/logger": "^0.0.4",
|
|
63
68
|
"@kevisual/permission": "^0.0.4",
|
|
64
|
-
"@kevisual/query": "^0.0.
|
|
69
|
+
"@kevisual/query": "^0.0.53",
|
|
70
|
+
"ai": "^6.0.116",
|
|
71
|
+
"zod": "^4.3.6"
|
|
65
72
|
}
|
|
66
73
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type QueryRouterServer, type App, type RouteInfo } from '@kevisual/router'
|
|
2
|
+
import { generateText, tool, type ModelMessage, type LanguageModel, type GenerateTextResult } from 'ai';
|
|
3
|
+
import z from 'zod';
|
|
4
|
+
import { filter } from '@kevisual/js-filter'
|
|
5
|
+
export const createTool = async (app: QueryRouterServer | App, message: { path: string, key: string, token?: string }) => {
|
|
6
|
+
const route = app.findRoute({ path: message.path, key: message.key });
|
|
7
|
+
if (!route) {
|
|
8
|
+
console.error(`未找到路径 ${message.path} 和 key ${message.key} 的路由`);
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const _tool = tool({
|
|
12
|
+
description: route?.metadata?.summary || route?.description || '无描述',
|
|
13
|
+
inputSchema: z.object({
|
|
14
|
+
...route.metadata?.args
|
|
15
|
+
}), // 这里可以根据实际需要定义输入参数的 schema
|
|
16
|
+
execute: async (args: any) => {
|
|
17
|
+
const res = await app.run({ path: message.path, key: message.key, payload: args, token: message.token });
|
|
18
|
+
return res;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
return _tool;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const createTools = async (opts: { app: QueryRouterServer | App, token?: string }) => {
|
|
25
|
+
const { app, token } = opts;
|
|
26
|
+
const tools: Record<string, any> = {};
|
|
27
|
+
for (const route of app.routes) {
|
|
28
|
+
const id = route.id!;
|
|
29
|
+
const _tool = await createTool(app, { path: route.path!, key: route.key!, token });
|
|
30
|
+
if (_tool && id) {
|
|
31
|
+
tools[id] = _tool;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return tools;
|
|
35
|
+
}
|
|
36
|
+
type Route = Partial<RouteInfo>
|
|
37
|
+
type AgentResult = {
|
|
38
|
+
result: GenerateTextResult<Record<string, any>, any>,
|
|
39
|
+
messages: ModelMessage[],
|
|
40
|
+
outputText: string,
|
|
41
|
+
}
|
|
42
|
+
export const reCallAgent = async (opts: { messages?: ModelMessage[], tools?: Record<string, any>, languageModel: LanguageModel }): Promise<AgentResult> => {
|
|
43
|
+
const { messages = [], tools = {}, languageModel } = opts;
|
|
44
|
+
const result = await generateText({
|
|
45
|
+
model: languageModel,
|
|
46
|
+
messages,
|
|
47
|
+
tools,
|
|
48
|
+
});
|
|
49
|
+
const step = result.steps[0]!;
|
|
50
|
+
if (step.finishReason === 'tool-calls') {
|
|
51
|
+
messages.push(...result.response.messages);
|
|
52
|
+
return reCallAgent({ messages, tools, languageModel });
|
|
53
|
+
}
|
|
54
|
+
let outputText = result.output
|
|
55
|
+
return { result, messages, outputText };
|
|
56
|
+
}
|
|
57
|
+
export const runAgent = async (opts: { app: QueryRouterServer | App, messages?: ModelMessage[], routes?: Route[], query?: string, languageModel: LanguageModel, token: string }) => {
|
|
58
|
+
const { app, languageModel } = opts;
|
|
59
|
+
let messages = opts.messages || [];
|
|
60
|
+
|
|
61
|
+
let routes = opts?.routes || app.routes;
|
|
62
|
+
if (opts.query) {
|
|
63
|
+
routes = filter(routes, opts.query);
|
|
64
|
+
};
|
|
65
|
+
const tools = await createTools({ app, token: opts.token });
|
|
66
|
+
return await reCallAgent({ messages, tools, languageModel });
|
|
67
|
+
}
|
|
@@ -6,7 +6,7 @@ export class CNBChat extends BaseChat {
|
|
|
6
6
|
repo: string;
|
|
7
7
|
constructor(options: CNBOptions & { repo: string }) {
|
|
8
8
|
const baseURL = CNBChat.BASE_URL.replace('{repo}', options.repo);
|
|
9
|
-
super({ model: "
|
|
9
|
+
super({ model: "CNB-Models", ...(options as BaseChatOptions), baseURL: baseURL });
|
|
10
10
|
}
|
|
11
11
|
async query(params: CNBQueryParam): Promise<Result<QueryRag[]>> {
|
|
12
12
|
const url = this.baseURL.replace('/ai', '/knowledge/base/query');
|
package/src/provider/index.ts
CHANGED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import type { Permission } from '@kevisual/permission';
|
|
2
|
-
|
|
3
|
-
export type AIModel = {
|
|
4
|
-
/**
|
|
5
|
-
* 提供商
|
|
6
|
-
*/
|
|
7
|
-
provider: string;
|
|
8
|
-
/**
|
|
9
|
-
* 模型名称
|
|
10
|
-
*/
|
|
11
|
-
model: string;
|
|
12
|
-
/**
|
|
13
|
-
* 模型组
|
|
14
|
-
*/
|
|
15
|
-
group: string;
|
|
16
|
-
/**
|
|
17
|
-
* 每日请求频率限制
|
|
18
|
-
*/
|
|
19
|
-
dayLimit?: number;
|
|
20
|
-
/**
|
|
21
|
-
* 总的token限制
|
|
22
|
-
*/
|
|
23
|
-
tokenLimit?: number;
|
|
24
|
-
};
|
|
25
|
-
export type SecretKey = {
|
|
26
|
-
/**
|
|
27
|
-
* 组
|
|
28
|
-
*/
|
|
29
|
-
group: string;
|
|
30
|
-
/**
|
|
31
|
-
* API密钥
|
|
32
|
-
*/
|
|
33
|
-
apiKey: string;
|
|
34
|
-
/**
|
|
35
|
-
* 解密密钥
|
|
36
|
-
*/
|
|
37
|
-
decryptKey?: string;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export type AIConfig = {
|
|
41
|
-
title?: string;
|
|
42
|
-
description?: string;
|
|
43
|
-
models: AIModel[];
|
|
44
|
-
secretKeys: SecretKey[];
|
|
45
|
-
permission?: Permission;
|
|
46
|
-
filter?: {
|
|
47
|
-
objectKey: string;
|
|
48
|
-
type: 'array' | 'object';
|
|
49
|
-
operate: 'removeAttribute' | 'remove';
|
|
50
|
-
attribute: string[];
|
|
51
|
-
}[];
|
|
52
|
-
};
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import AES from 'crypto-js/aes.js';
|
|
2
|
-
import Utf8 from 'crypto-js/enc-utf8.js';
|
|
3
|
-
import type { AIConfig } from './ai-config-type.js';
|
|
4
|
-
const CryptoJS = { AES, enc: { Utf8 } };
|
|
5
|
-
// 加密函数
|
|
6
|
-
export function encryptAES(plainText: string, secretKey: string) {
|
|
7
|
-
return CryptoJS.AES.encrypt(plainText, secretKey).toString();
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// 解密函数
|
|
11
|
-
export function decryptAES(cipherText: string, secretKey: string) {
|
|
12
|
-
const bytes = CryptoJS.AES.decrypt(cipherText, secretKey);
|
|
13
|
-
return bytes.toString(CryptoJS.enc.Utf8);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
type AIModel = {
|
|
17
|
-
/**
|
|
18
|
-
* 提供商
|
|
19
|
-
*/
|
|
20
|
-
provider: string;
|
|
21
|
-
/**
|
|
22
|
-
* 模型名称
|
|
23
|
-
*/
|
|
24
|
-
model: string;
|
|
25
|
-
/**
|
|
26
|
-
* 模型组
|
|
27
|
-
*/
|
|
28
|
-
group: string;
|
|
29
|
-
/**
|
|
30
|
-
* 每日请求频率限制
|
|
31
|
-
*/
|
|
32
|
-
dayLimit?: number;
|
|
33
|
-
/**
|
|
34
|
-
* 总的token限制
|
|
35
|
-
*/
|
|
36
|
-
tokenLimit?: number;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
type SecretKey = {
|
|
40
|
-
/**
|
|
41
|
-
* 组
|
|
42
|
-
*/
|
|
43
|
-
group: string;
|
|
44
|
-
/**
|
|
45
|
-
* API密钥
|
|
46
|
-
*/
|
|
47
|
-
apiKey: string;
|
|
48
|
-
/**
|
|
49
|
-
* 解密密钥
|
|
50
|
-
*/
|
|
51
|
-
decryptKey?: string;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export type GetProviderOpts = {
|
|
55
|
-
model: string;
|
|
56
|
-
group: string;
|
|
57
|
-
decryptKey?: string;
|
|
58
|
-
};
|
|
59
|
-
export type ProviderResult = {
|
|
60
|
-
provider: string;
|
|
61
|
-
model: string;
|
|
62
|
-
group: string;
|
|
63
|
-
apiKey: string;
|
|
64
|
-
dayLimit?: number;
|
|
65
|
-
tokenLimit?: number;
|
|
66
|
-
baseURL?: string;
|
|
67
|
-
/**
|
|
68
|
-
* 解密密钥
|
|
69
|
-
*/
|
|
70
|
-
decryptKey?: string;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export class AIConfigParser {
|
|
74
|
-
private config: AIConfig;
|
|
75
|
-
result: ProviderResult;
|
|
76
|
-
constructor(config: AIConfig) {
|
|
77
|
-
this.config = config;
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* 获取模型配置
|
|
81
|
-
* @param opts
|
|
82
|
-
* @returns
|
|
83
|
-
*/
|
|
84
|
-
getProvider(opts: GetProviderOpts): ProviderResult {
|
|
85
|
-
const { model, group, decryptKey } = opts;
|
|
86
|
-
const modelConfig = this.config.models.find((m) => m.model === model && m.group === group);
|
|
87
|
-
const groupConfig = this.config.secretKeys.find((m) => m.group === group);
|
|
88
|
-
if (!modelConfig) {
|
|
89
|
-
throw new Error(`在模型组 ${group} 中未找到模型 ${model}`);
|
|
90
|
-
}
|
|
91
|
-
const mergeConfig = {
|
|
92
|
-
...modelConfig,
|
|
93
|
-
...groupConfig,
|
|
94
|
-
decryptKey: decryptKey || groupConfig?.decryptKey,
|
|
95
|
-
};
|
|
96
|
-
// 验证模型配置
|
|
97
|
-
if (!mergeConfig.provider) {
|
|
98
|
-
throw new Error(`模型 ${model} 未配置提供商`);
|
|
99
|
-
}
|
|
100
|
-
if (!mergeConfig.model) {
|
|
101
|
-
throw new Error(`模型 ${model} 未配置模型名称`);
|
|
102
|
-
}
|
|
103
|
-
if (!mergeConfig.apiKey) {
|
|
104
|
-
throw new Error(`组 ${group} 未配置 API 密钥`);
|
|
105
|
-
}
|
|
106
|
-
if (!mergeConfig.group) {
|
|
107
|
-
throw new Error(`组 ${group} 未配置`);
|
|
108
|
-
}
|
|
109
|
-
this.result = mergeConfig;
|
|
110
|
-
return mergeConfig;
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* 获取解密密钥
|
|
114
|
-
* @param opts
|
|
115
|
-
* @returns
|
|
116
|
-
*/
|
|
117
|
-
async getSecretKey(opts?: {
|
|
118
|
-
getCache?: (key: string) => Promise<string>;
|
|
119
|
-
setCache?: (key: string, value: string) => Promise<void>;
|
|
120
|
-
providerResult?: ProviderResult;
|
|
121
|
-
}) {
|
|
122
|
-
const { getCache, setCache, providerResult } = opts || {};
|
|
123
|
-
const { apiKey, decryptKey, group = '', model } = providerResult || this.result;
|
|
124
|
-
const cacheKey = `${group}--${model}`;
|
|
125
|
-
if (!decryptKey) {
|
|
126
|
-
return apiKey;
|
|
127
|
-
}
|
|
128
|
-
if (getCache) {
|
|
129
|
-
const cache = await getCache(cacheKey);
|
|
130
|
-
if (cache) {
|
|
131
|
-
return cache;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
const secretKey = decryptAES(apiKey, decryptKey);
|
|
135
|
-
if (setCache) {
|
|
136
|
-
await setCache(cacheKey, secretKey);
|
|
137
|
-
}
|
|
138
|
-
return secretKey;
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* 加密
|
|
142
|
-
* @param plainText
|
|
143
|
-
* @param secretKey
|
|
144
|
-
* @returns
|
|
145
|
-
*/
|
|
146
|
-
encrypt(plainText: string, secretKey: string) {
|
|
147
|
-
return encryptAES(plainText, secretKey);
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* 解密
|
|
151
|
-
* @param cipherText
|
|
152
|
-
* @param secretKey
|
|
153
|
-
* @returns
|
|
154
|
-
*/
|
|
155
|
-
decrypt(cipherText: string, secretKey: string) {
|
|
156
|
-
return decryptAES(cipherText, secretKey);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* 获取模型配置
|
|
161
|
-
* @returns
|
|
162
|
-
*/
|
|
163
|
-
getSelectOpts() {
|
|
164
|
-
const { models, secretKeys = [] } = this.config;
|
|
165
|
-
|
|
166
|
-
return models.map((model) => {
|
|
167
|
-
const selectOpts = secretKeys.find((m) => m.group === model.group);
|
|
168
|
-
return {
|
|
169
|
-
...model,
|
|
170
|
-
...selectOpts,
|
|
171
|
-
};
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
getConfig(keepSecret?: boolean, config?: AIConfig) {
|
|
175
|
-
const chatConfig = config ?? this.config;
|
|
176
|
-
if (keepSecret) {
|
|
177
|
-
return chatConfig;
|
|
178
|
-
}
|
|
179
|
-
// 过滤掉secret中的所有apiKey,移除掉并返回chatConfig
|
|
180
|
-
const { secretKeys = [], ...rest } = chatConfig || {};
|
|
181
|
-
return {
|
|
182
|
-
...rest,
|
|
183
|
-
secretKeys: secretKeys.map((item) => {
|
|
184
|
-
return {
|
|
185
|
-
...item,
|
|
186
|
-
apiKey: undefined,
|
|
187
|
-
decryptKey: undefined,
|
|
188
|
-
};
|
|
189
|
-
}),
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
}
|