generated from tailored/app-template
Compare commits
13 Commits
fe716b8ea3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d9385e3c4e | |||
| 0c885e9012 | |||
| 6ced3676ab | |||
| 8d2401ea30 | |||
| 2369417961 | |||
| 2338242018 | |||
| 4a2d3e8d32 | |||
| b07bcc5454 | |||
| abac483610 | |||
| 6376773e13 | |||
| 0804bb9f2b | |||
| e0df4e490c | |||
| ac207ff374 |
@@ -1,2 +0,0 @@
|
||||
config.json
|
||||
.env*
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -18,4 +18,6 @@ logs
|
||||
|
||||
config.json
|
||||
|
||||
pack-dist
|
||||
pack-dist
|
||||
|
||||
videos/output*
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "submodules/query-config"]
|
||||
path = submodules/query-config
|
||||
url = git@git.xiongxiao.me:kevisual/kevisual-query-config.git
|
||||
37
bun.config.mjs
Normal file
37
bun.config.mjs
Normal file
@@ -0,0 +1,37 @@
|
||||
// @ts-check
|
||||
import { resolvePath } from '@kevisual/use-config/env';
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
const entry = 'src/index.ts';
|
||||
const naming = 'app';
|
||||
const external = ['sequelize', 'pg', 'sqlite3', 'ioredis', 'pm2'];
|
||||
/**
|
||||
* @type {import('bun').BuildConfig}
|
||||
*/
|
||||
await Bun.build({
|
||||
target: 'node',
|
||||
format: 'esm',
|
||||
entrypoints: [resolvePath(entry, { meta: import.meta })],
|
||||
outdir: resolvePath('./dist', { meta: import.meta }),
|
||||
naming: {
|
||||
entry: `${naming}.js`,
|
||||
},
|
||||
external,
|
||||
env: 'KEVISUAL_*',
|
||||
});
|
||||
|
||||
// const cmd = `dts -i src/index.ts -o app.d.ts`;
|
||||
// const cmd = `dts -i ${entry} -o ${naming}.d.ts`;
|
||||
// execSync(cmd, { stdio: 'inherit' });
|
||||
|
||||
await Bun.build({
|
||||
target: 'node',
|
||||
format: 'esm',
|
||||
entrypoints: [resolvePath('./src/run.ts', { meta: import.meta })],
|
||||
outdir: resolvePath('./dist', { meta: import.meta }),
|
||||
naming: {
|
||||
entry: `${'run'}.js`,
|
||||
},
|
||||
external,
|
||||
env: 'KEVISUAL_*',
|
||||
});
|
||||
87
package.json
87
package.json
@@ -1,83 +1,70 @@
|
||||
{
|
||||
"name": "@kevisual/ai-center-services",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"version": "0.0.5",
|
||||
"description": "后面需要把ai-center的provider模块提取出去",
|
||||
"main": "index.js",
|
||||
"basename": "/root/ai-center-services",
|
||||
"app": {
|
||||
"entry": "dist/app.mjs",
|
||||
"entry": "dist/app.js",
|
||||
"key": "ai-center-services",
|
||||
"type": "system-app"
|
||||
"type": "system-app",
|
||||
"runtime": [
|
||||
"client",
|
||||
"server"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"types"
|
||||
],
|
||||
"scripts": {
|
||||
"watch": "rollup -c rollup.config.mjs -w",
|
||||
"build": "rollup -c rollup.config.mjs",
|
||||
"dev": "cross-env NODE_ENV=development nodemon --delay 2.5 -e js,cjs,mjs --exec node dist/app.mjs",
|
||||
"test": "tsx test/**/*.ts",
|
||||
"dev:watch": "cross-env NODE_ENV=development concurrently -n \"Watch,Dev\" -c \"green,blue\" \"npm run watch\" \"sleep 1 && npm run dev\" ",
|
||||
"build": "npm run clean && bun bun.config.mjs",
|
||||
"dev": "bun run --watch bun.config.mjs",
|
||||
"clean": "rm -rf dist",
|
||||
"pub": "envision pack -p -u"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@10.7.1",
|
||||
"packageManager": "pnpm@10.19.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@kevisual/cache": "^0.0.2",
|
||||
"@kevisual/permission": "^0.0.1",
|
||||
"@kevisual/router": "0.0.10",
|
||||
"pino-pretty": "^13.0.0"
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npmjs.org/",
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kevisual/code-center-module": "0.0.18",
|
||||
"@kevisual/ai": "^0.0.11",
|
||||
"@kevisual/code-center-module": "0.0.24",
|
||||
"@kevisual/mark": "0.0.7",
|
||||
"@kevisual/query": "^0.0.15",
|
||||
"@kevisual/query-config": "workspace:*",
|
||||
"@kevisual/types": "^0.0.6",
|
||||
"@kevisual/use-config": "^1.0.10",
|
||||
"@rollup/plugin-alias": "^5.1.1",
|
||||
"@rollup/plugin-commonjs": "^28.0.3",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-replace": "^6.0.2",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"@kevisual/permission": "^0.0.3",
|
||||
"@kevisual/query": "^0.0.29",
|
||||
"@kevisual/query-config": "^0.0.2",
|
||||
"@kevisual/router": "0.0.30",
|
||||
"@kevisual/types": "^0.0.10",
|
||||
"@kevisual/use-config": "^1.0.19",
|
||||
"@types/bun": "^1.3.1",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/formidable": "^3.4.5",
|
||||
"@types/formidable": "^3.4.6",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^22.14.0",
|
||||
"@vitejs/plugin-basic-ssl": "^2.0.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"@types/node": "^24.9.1",
|
||||
"cookie": "^1.0.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"cross-env": "^10.1.0",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"dotenv": "^16.4.7",
|
||||
"formidable": "^3.5.2",
|
||||
"ioredis": "^5.6.0",
|
||||
"jsrepo": "^1.45.3",
|
||||
"dayjs": "^1.11.18",
|
||||
"dotenv": "^17.2.3",
|
||||
"formidable": "^3.5.4",
|
||||
"ioredis": "^5.8.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nanoid": "^5.1.5",
|
||||
"nodemon": "^3.1.9",
|
||||
"openai": "^4.91.1",
|
||||
"pg": "^8.14.1",
|
||||
"nanoid": "^5.1.6",
|
||||
"openai": "6.7.0",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"pino": "^9.6.0",
|
||||
"pm2": "^6.0.5",
|
||||
"pm2": "^6.0.13",
|
||||
"rimraf": "^6.0.1",
|
||||
"rollup": "^4.39.0",
|
||||
"rollup-plugin-copy": "^3.5.0",
|
||||
"rollup-plugin-dts": "^6.2.1",
|
||||
"rollup-plugin-esbuild": "^6.2.1",
|
||||
"sequelize": "^6.37.7",
|
||||
"tape": "^5.9.0",
|
||||
"tiktoken": "^1.0.20",
|
||||
"tsx": "^4.19.3",
|
||||
"typescript": "^5.8.2",
|
||||
"vite": "^6.2.5"
|
||||
"tiktoken": "^1.0.22"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kevisual/logger": "^0.0.4"
|
||||
}
|
||||
}
|
||||
3110
pnpm-lock.yaml
generated
3110
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,79 +0,0 @@
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import json from '@rollup/plugin-json';
|
||||
import path from 'path';
|
||||
import esbuild from 'rollup-plugin-esbuild';
|
||||
import alias from '@rollup/plugin-alias';
|
||||
import replace from '@rollup/plugin-replace';
|
||||
import pkgs from './package.json' with {type: 'json'};
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
const input = isDev ? './src/dev.ts' : './src/index.ts';
|
||||
/**
|
||||
* @type {import('rollup').RollupOptions}
|
||||
*/
|
||||
const config = {
|
||||
input,
|
||||
output: {
|
||||
dir: './dist',
|
||||
entryFileNames: 'app.mjs',
|
||||
chunkFileNames: '[name]-[hash].mjs',
|
||||
format: 'esm',
|
||||
},
|
||||
plugins: [
|
||||
replace({
|
||||
preventAssignment: true, // 防止意外赋值
|
||||
DEV_SERVER: JSON.stringify(isDev), // 替换 process.env.NODE_ENV
|
||||
// VERSION: JSON.stringify(pkgs.version),
|
||||
}),
|
||||
alias({
|
||||
// only esbuild needs to be configured
|
||||
entries: [
|
||||
{ find: '@', replacement: path.resolve('src') }, // 配置 @ 为 src 目录
|
||||
{ find: 'http', replacement: 'node:http' },
|
||||
{ find: 'https', replacement: 'node:https' },
|
||||
{ find: 'fs', replacement: 'node:fs' },
|
||||
{ find: 'path', replacement: 'node:path' },
|
||||
{ find: 'crypto', replacement: 'node:crypto' },
|
||||
{ find: 'zlib', replacement: 'node:zlib' },
|
||||
{ find: 'stream', replacement: 'node:stream' },
|
||||
{ find: 'net', replacement: 'node:net' },
|
||||
{ find: 'tty', replacement: 'node:tty' },
|
||||
{ find: 'tls', replacement: 'node:tls' },
|
||||
{ find: 'buffer', replacement: 'node:buffer' },
|
||||
{ find: 'timers', replacement: 'node:timers' },
|
||||
// { find: 'string_decoder', replacement: 'node:string_decoder' },
|
||||
{ find: 'dns', replacement: 'node:dns' },
|
||||
{ find: 'domain', replacement: 'node:domain' },
|
||||
{ find: 'os', replacement: 'node:os' },
|
||||
{ find: 'events', replacement: 'node:events' },
|
||||
{ find: 'url', replacement: 'node:url' },
|
||||
{ find: 'assert', replacement: 'node:assert' },
|
||||
{ find: 'util', replacement: 'node:util' },
|
||||
],
|
||||
}),
|
||||
resolve({
|
||||
preferBuiltins: true, // 强制优先使用内置模块
|
||||
browser: false,
|
||||
}),
|
||||
commonjs(),
|
||||
esbuild({
|
||||
target: 'node22', //
|
||||
minify: false, // 启用代码压缩
|
||||
tsconfig: 'tsconfig.json',
|
||||
}),
|
||||
json(),
|
||||
],
|
||||
external: [
|
||||
/@kevisual\/router(\/.*)?/, //, // 路由
|
||||
/@kevisual\/use-config(\/.*)?/, //
|
||||
|
||||
'sequelize', // 数据库 orm
|
||||
'ioredis', // redis
|
||||
'pg', // pg
|
||||
'pino', // pino
|
||||
'pino-pretty', // pino-pretty
|
||||
|
||||
],
|
||||
};
|
||||
export default config;
|
||||
@@ -1,27 +1,10 @@
|
||||
import { pino } from 'pino';
|
||||
import { useConfig } from '@kevisual/use-config/env';
|
||||
|
||||
import { Logger } from '@kevisual/logger';
|
||||
const config = useConfig();
|
||||
|
||||
export const logger = pino({
|
||||
export const logger = new Logger({
|
||||
level: config.LOG_LEVEL || 'info',
|
||||
transport: {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
colorize: true,
|
||||
translateTime: 'SYS:standard',
|
||||
ignore: 'pid,hostname',
|
||||
},
|
||||
},
|
||||
serializers: {
|
||||
error: pino.stdSerializers.err,
|
||||
req: pino.stdSerializers.req,
|
||||
res: pino.stdSerializers.res,
|
||||
},
|
||||
base: {
|
||||
app: 'ai-chat',
|
||||
env: process.env.NODE_ENV || 'development',
|
||||
},
|
||||
showTime: true,
|
||||
});
|
||||
|
||||
export const logError = (message: string, data?: any) => logger.error({ data }, message);
|
||||
|
||||
6
src/modules/logger.ts
Normal file
6
src/modules/logger.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Logger } from '@kevisual/logger';
|
||||
|
||||
export const logger = new Logger({
|
||||
level: process?.env?.LOG_LEVEL || 'info',
|
||||
showTime: true,
|
||||
});
|
||||
@@ -6,8 +6,9 @@ export type OllamaOptions = BaseChatOptions;
|
||||
* 自定义模型
|
||||
*/
|
||||
export class Custom extends BaseChat {
|
||||
static BASE_URL = 'https://api.deepseek.com/v1/';
|
||||
constructor(options: OllamaOptions) {
|
||||
const baseURL = options.baseURL || 'https://api.deepseek.com/v1/';
|
||||
const baseURL = options.baseURL || Custom.BASE_URL;
|
||||
super({ ...(options as BaseChatOptions), baseURL: baseURL });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@ import { BaseChat, BaseChatOptions } from '../core/chat.ts';
|
||||
|
||||
export type DeepSeekOptions = Partial<BaseChatOptions>;
|
||||
export class DeepSeek extends BaseChat {
|
||||
static BASE_URL = 'https://api.deepseek.com/v1/';
|
||||
constructor(options: DeepSeekOptions) {
|
||||
const baseURL = options.baseURL || 'https://api.deepseek.com/v1/';
|
||||
const baseURL = options.baseURL || DeepSeek.BASE_URL;
|
||||
super({ ...(options as BaseChatOptions), baseURL: baseURL });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ import { BaseChat, BaseChatOptions } from '../core/chat.ts';
|
||||
|
||||
export type ModelScopeOptions = Partial<BaseChatOptions>;
|
||||
export class ModelScope extends BaseChat {
|
||||
static BASE_URL = 'https://api-inference.modelscope.cn/v1/';
|
||||
constructor(options: ModelScopeOptions) {
|
||||
const baseURL = options.baseURL || 'https://api-inference.modelscope.cn/v1/';
|
||||
const baseURL = options.baseURL || ModelScope.BASE_URL;
|
||||
super({ ...options, baseURL: baseURL } as any);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,9 @@ type OllamaModel = {
|
||||
};
|
||||
};
|
||||
export class Ollama extends BaseChat {
|
||||
static BASE_URL = 'http://localhost:11434/v1';
|
||||
constructor(options: OllamaOptions) {
|
||||
const baseURL = options.baseURL || 'http://localhost:11434/v1';
|
||||
const baseURL = options.baseURL || Ollama.BASE_URL;
|
||||
super({ ...(options as BaseChatOptions), baseURL: baseURL });
|
||||
}
|
||||
async chat(messages: ChatMessage[], options?: ChatMessageOptions) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BaseChat, BaseChatOptions } from '../core/chat.ts';
|
||||
import { OpenAI } from 'openai';
|
||||
|
||||
type SiliconFlowOptions = Partial<BaseChatOptions>;
|
||||
export type SiliconFlowOptions = Partial<BaseChatOptions>;
|
||||
|
||||
type SiliconFlowUsageData = {
|
||||
id: string;
|
||||
@@ -24,8 +24,9 @@ type SiliconFlowUsageResponse = {
|
||||
data: SiliconFlowUsageData;
|
||||
};
|
||||
export class SiliconFlow extends BaseChat {
|
||||
static BASE_URL = 'https://api.siliconflow.cn/v1';
|
||||
constructor(options: SiliconFlowOptions) {
|
||||
const baseURL = options.baseURL || 'https://api.siliconflow.com/v1';
|
||||
const baseURL = options.baseURL || SiliconFlow.BASE_URL;
|
||||
super({ ...(options as BaseChatOptions), baseURL: baseURL });
|
||||
}
|
||||
async getUsageInfo(): Promise<SiliconFlowUsageResponse> {
|
||||
|
||||
@@ -2,8 +2,9 @@ import { BaseChat, BaseChatOptions } from '../core/chat.ts';
|
||||
|
||||
export type VolcesOptions = Partial<BaseChatOptions>;
|
||||
export class Volces extends BaseChat {
|
||||
static BASE_URL = 'https://ark.cn-beijing.volces.com/api/v3/';
|
||||
constructor(options: VolcesOptions) {
|
||||
const baseURL = options.baseURL || 'https://ark.cn-beijing.volces.com/api/v3/';
|
||||
const baseURL = options.baseURL || Volces.BASE_URL;
|
||||
super({ ...(options as BaseChatOptions), baseURL: baseURL });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import { OpenAI } from 'openai';
|
||||
import type { BaseChatInterface, ChatMessageComplete, ChatMessage, ChatMessageOptions, BaseChatUsageInterface } from './type.ts';
|
||||
import type {
|
||||
BaseChatInterface,
|
||||
ChatMessageComplete,
|
||||
ChatMessage,
|
||||
ChatMessageOptions,
|
||||
BaseChatUsageInterface,
|
||||
ChatStream,
|
||||
EmbeddingMessage,
|
||||
EmbeddingMessageComplete,
|
||||
} from './type.ts';
|
||||
|
||||
export type BaseChatOptions<T = Record<string, any>> = {
|
||||
/**
|
||||
@@ -9,7 +18,7 @@ export type BaseChatOptions<T = Record<string, any>> = {
|
||||
/**
|
||||
* 默认模型
|
||||
*/
|
||||
model: string;
|
||||
model?: string;
|
||||
/**
|
||||
* 默认apiKey
|
||||
*/
|
||||
@@ -87,7 +96,7 @@ export class BaseChat implements BaseChatInterface, BaseChatUsageInterface {
|
||||
if (createParams.response_format) {
|
||||
throw new Error('response_format is not supported in stream mode');
|
||||
}
|
||||
return this.openai.chat.completions.create(createParams) as any;
|
||||
return this.openai.chat.completions.create(createParams) as unknown as ChatStream;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,4 +116,28 @@ export class BaseChat implements BaseChatInterface, BaseChatUsageInterface {
|
||||
completion_tokens: this.completion_tokens,
|
||||
};
|
||||
}
|
||||
getHeaders(headers?: Record<string, string>) {
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${this.apiKey}`,
|
||||
...headers,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 生成embedding 内部
|
||||
* @param text
|
||||
* @returns
|
||||
*/
|
||||
async generateEmbeddingCore(text: string | string[], options?: EmbeddingMessage): Promise<EmbeddingMessageComplete> {
|
||||
const embeddingModel = options?.model || this.model;
|
||||
const res = await this.openai.embeddings.create({
|
||||
model: embeddingModel,
|
||||
input: text,
|
||||
encoding_format: 'float',
|
||||
...options,
|
||||
});
|
||||
this.prompt_tokens += res.usage.prompt_tokens;
|
||||
this.total_tokens += res.usage.total_tokens;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,27 @@
|
||||
export * from './chat.ts';
|
||||
export * from './type.ts';
|
||||
import { ChatStream } from './type.ts';
|
||||
|
||||
// export type { BaseChat, BaseChatOptions } from './chat.ts';
|
||||
export * from './chat.ts'
|
||||
// export {
|
||||
// ChatMessage,
|
||||
// ChatMessageOptions, //
|
||||
// ChatMessageComplete,
|
||||
// ChatMessageStream,
|
||||
// BaseChatInterface,
|
||||
// BaseChatUsageInterface,
|
||||
// ChatStream,
|
||||
// EmbeddingMessage,
|
||||
// EmbeddingMessageComplete,
|
||||
// } from './type.ts';
|
||||
export * from './type.ts'
|
||||
/**
|
||||
* for await (const chunk of chatStream) {
|
||||
* console.log(chunk);
|
||||
* }
|
||||
* @param chatStream
|
||||
*/
|
||||
export const readStream = async (chatStream: ChatStream) => {
|
||||
for await (const chunk of chatStream) {
|
||||
console.log(chunk);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import OpenAI from 'openai';
|
||||
|
||||
export type ChatMessage = OpenAI.Chat.Completions.ChatCompletionMessageParam ;
|
||||
export type ChatMessage = OpenAI.Chat.Completions.ChatCompletionMessageParam;
|
||||
export type ChatMessageOptions = Partial<OpenAI.Chat.Completions.ChatCompletionCreateParams>;
|
||||
export type ChatMessageComplete = OpenAI.Chat.Completions.ChatCompletion;
|
||||
export type ChatMessageStream = OpenAI.Chat.Completions.ChatCompletion;
|
||||
|
||||
export type EmbeddingMessage = Partial<OpenAI.Embeddings.EmbeddingCreateParams>;
|
||||
export type EmbeddingMessageComplete = OpenAI.Embeddings.CreateEmbeddingResponse;
|
||||
export interface BaseChatInterface {
|
||||
chat(messages: ChatMessage[], options?: ChatMessageOptions): Promise<ChatMessageComplete>;
|
||||
}
|
||||
@@ -23,3 +25,5 @@ export interface BaseChatUsageInterface {
|
||||
*/
|
||||
completion_tokens: number;
|
||||
}
|
||||
|
||||
export type ChatStream = AsyncGenerator<ChatMessageComplete, void, unknown>;
|
||||
|
||||
@@ -40,13 +40,15 @@ export class ProviderManager {
|
||||
if (!Provider) {
|
||||
throw new Error(`Provider ${provider} not found`);
|
||||
}
|
||||
console.log('pm', 'Provider', ProviderMap[provider]);
|
||||
|
||||
this.provider = new Provider({
|
||||
const providerConfig = {
|
||||
model,
|
||||
apiKey,
|
||||
baseURL,
|
||||
});
|
||||
};
|
||||
if (!providerConfig.baseURL) {
|
||||
delete providerConfig.baseURL;
|
||||
}
|
||||
this.provider = new Provider(providerConfig);
|
||||
}
|
||||
static async createProvider(config: ProviderManagerConfig) {
|
||||
if (!config.baseURL) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BaseChat, BaseChatOptions } from '../core/chat.ts';
|
||||
import { numTokensFromString } from '../utils/token.ts';
|
||||
import { EmbeddingMessage } from '../core/type.ts';
|
||||
|
||||
export type KnowledgeOptions<T = Record<string, string>> = BaseChatOptions<
|
||||
{
|
||||
@@ -29,49 +29,28 @@ export class KnowledgeBase extends BaseChat {
|
||||
this.total_tokens = 0;
|
||||
this.batchSize = options.batchSize || 4;
|
||||
}
|
||||
/**
|
||||
* 生成embedding 内部
|
||||
* @param text
|
||||
* @returns
|
||||
*/
|
||||
async generateEmbeddingCore(text: string | string[]) {
|
||||
const res = await this.openai.embeddings.create({
|
||||
model: this.embeddingModel,
|
||||
input: text,
|
||||
encoding_format: 'float',
|
||||
});
|
||||
this.prompt_tokens += res.usage.prompt_tokens;
|
||||
this.total_tokens += res.usage.total_tokens;
|
||||
return res;
|
||||
}
|
||||
async generateEmbeddingBatchCore(text: string[]) {
|
||||
const res = await this.openai.embeddings.create({
|
||||
model: this.embeddingModel,
|
||||
input: text,
|
||||
encoding_format: 'float',
|
||||
});
|
||||
this.prompt_tokens += res.usage.prompt_tokens;
|
||||
this.total_tokens += res.usage.total_tokens;
|
||||
return res.data.map((item) => item.embedding);
|
||||
}
|
||||
/**
|
||||
* 生成embedding
|
||||
* @param text
|
||||
* @returns
|
||||
*/
|
||||
async generateEmbedding(text: string | string[]) {
|
||||
if (Array.isArray(text)) {
|
||||
// size token 不能超过 8192
|
||||
const allSize = text.reduce((acc, item) => acc + numTokensFromString(item), 0);
|
||||
if (allSize > 8192) {
|
||||
throw new Error('text size 不能超过 8192');
|
||||
try {
|
||||
const res = await this.generateEmbeddingCore(text, { model: this.embeddingModel });
|
||||
return { code: 200, data: res.data };
|
||||
} catch (error) {
|
||||
const has413 = error?.message?.includes('413');
|
||||
if (has413) {
|
||||
return {
|
||||
code: 413,
|
||||
message: '请求过大,请分割文本',
|
||||
};
|
||||
}
|
||||
return {
|
||||
code: error?.code || 500,
|
||||
message: '生成embedding失败',
|
||||
};
|
||||
}
|
||||
const res = await this.generateEmbeddingCore(text);
|
||||
if (Array.isArray(text)) {
|
||||
return res.data.map((item) => item.embedding);
|
||||
}
|
||||
return [res.data[0].embedding];
|
||||
}
|
||||
/**
|
||||
* 批量生成embedding
|
||||
@@ -83,8 +62,10 @@ export class KnowledgeBase extends BaseChat {
|
||||
const embeddings: number[][] = [];
|
||||
for (let i = 0; i < textArray.length; i += batchSize) {
|
||||
const batch = textArray.slice(i, i + batchSize);
|
||||
const res = await this.generateEmbeddingBatchCore(batch);
|
||||
embeddings.push(...res);
|
||||
const res = await this.generateEmbedding(batch);
|
||||
if (res.code === 200) {
|
||||
embeddings.push(...res.data.map((item) => item.embedding));
|
||||
}
|
||||
}
|
||||
return embeddings;
|
||||
}
|
||||
|
||||
1
src/provider/media/index.ts
Normal file
1
src/provider/media/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './video/siliconflow.ts';
|
||||
37
src/provider/media/video/siliconflow.ts
Normal file
37
src/provider/media/video/siliconflow.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { SiliconFlow } from './../../chat-adapter/siliconflow.ts';
|
||||
export class VideoSiliconFlow extends SiliconFlow {
|
||||
constructor(opts: any) {
|
||||
super(opts);
|
||||
}
|
||||
|
||||
async uploadAudioVoice(audioBase64: string | Blob | File) {
|
||||
const pathname = 'uploads/audio/voice';
|
||||
const url = `${this.baseURL}/${pathname}`;
|
||||
const headers = {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
Authorization: `Bearer ${this.apiKey}`,
|
||||
};
|
||||
const formData = new FormData();
|
||||
// formData.append('audio', 'data:audio/mpeg;base64,aGVsbG93b3JsZA==');
|
||||
// formData.append('audio', audioBase64);
|
||||
formData.append('file', audioBase64);
|
||||
formData.append('model', 'FunAudioLLM/CosyVoice2-0.5B');
|
||||
formData.append('customName', 'test_name');
|
||||
formData.append('text', '在一无所知中, 梦里的一天结束了,一个新的轮回便会开始');
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: formData,
|
||||
}).then((res) => res.json());
|
||||
console.log('uploadAudioVoice', res);
|
||||
}
|
||||
async audioSpeech() {
|
||||
this.openai.audio.speech.create({
|
||||
model: 'FunAudioLLM/CosyVoice2-0.5B',
|
||||
voice: 'alloy',
|
||||
input: '在一无所知中, 梦里的一天结束了,一个新的轮回便会开始',
|
||||
response_format: 'mp3',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Permission } from '@kevisual/permission';
|
||||
import CryptoJS from 'crypto-js';
|
||||
import AES from 'crypto-js/aes.js';
|
||||
import Utf8 from 'crypto-js/enc-utf8.js';
|
||||
|
||||
const CryptoJS = { AES, enc: { Utf8 } };
|
||||
// 加密函数
|
||||
export function encryptAES(plainText: string, secretKey: string) {
|
||||
return CryptoJS.AES.encrypt(plainText, secretKey).toString();
|
||||
@@ -58,7 +60,7 @@ export type GetProviderOpts = {
|
||||
export type ProviderResult = {
|
||||
provider: string;
|
||||
model: string;
|
||||
group: string;
|
||||
group?: string;
|
||||
apiKey: string;
|
||||
dayLimit?: number;
|
||||
tokenLimit?: number;
|
||||
@@ -72,7 +74,7 @@ export type ProviderResult = {
|
||||
export type AIConfig = {
|
||||
title?: string;
|
||||
description?: string;
|
||||
models: AIModel[];
|
||||
models?: AIModel[];
|
||||
secretKeys: SecretKey[];
|
||||
permission?: Permission;
|
||||
filter?: {
|
||||
@@ -167,7 +169,7 @@ export class AIConfigParser {
|
||||
decrypt(cipherText: string, secretKey: string) {
|
||||
return decryptAES(cipherText, secretKey);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取模型配置
|
||||
* @returns
|
||||
@@ -183,4 +185,22 @@ export class AIConfigParser {
|
||||
};
|
||||
});
|
||||
}
|
||||
getConfig(keepSecret?: boolean, config?: AIConfig) {
|
||||
const chatConfig = config ?? this.config;
|
||||
if (keepSecret) {
|
||||
return chatConfig;
|
||||
}
|
||||
// 过滤掉secret中的所有apiKey,移除掉并返回chatConfig
|
||||
const { secretKeys = [], ...rest } = chatConfig || {};
|
||||
return {
|
||||
...rest,
|
||||
secretKeys: secretKeys.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
apiKey: undefined,
|
||||
decryptKey: undefined,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ app
|
||||
let { username, model, group, getFull = false } = ctx.query;
|
||||
const tokenUser = ctx.state.tokenUser || {};
|
||||
const tokenUsername = tokenUser.username;
|
||||
const token = ctx.query.token || ctx.state.token;
|
||||
const options = ctx.query.options || {};
|
||||
let aiChatHistory: AiChatHistoryModel;
|
||||
if (id) {
|
||||
@@ -46,6 +47,7 @@ app
|
||||
model,
|
||||
group,
|
||||
username: tokenUsername,
|
||||
token,
|
||||
});
|
||||
if (!isSelf && username !== 'root') {
|
||||
const aiConfig = chatServices.aiConfig;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { AIConfigParser, type AIConfig } from '@/provider/utils/parse-config.ts'
|
||||
import { redis } from '@/modules/db.ts';
|
||||
import { CustomError } from '@kevisual/router';
|
||||
import { queryConfig } from '@/modules/query.ts';
|
||||
import { log } from '@/logger/index.ts';
|
||||
import { logger } from '@/modules/logger.ts';
|
||||
export class ChatConfigServices {
|
||||
cachePrefix = 'ai:chat:config';
|
||||
// 使用谁的模型
|
||||
@@ -11,6 +11,7 @@ export class ChatConfigServices {
|
||||
username: string;
|
||||
aiConfig?: AIConfig;
|
||||
isOwner: boolean;
|
||||
token?: string;
|
||||
/**
|
||||
* username 是使用的模型的用户名,使用谁的模型
|
||||
* @param username
|
||||
@@ -19,6 +20,7 @@ export class ChatConfigServices {
|
||||
this.owner = owner;
|
||||
this.username = username;
|
||||
this.isOwner = owner === username;
|
||||
// this.token = token;
|
||||
}
|
||||
getKey() {
|
||||
return `${this.cachePrefix}:${this.owner}`;
|
||||
@@ -28,13 +30,14 @@ export class ChatConfigServices {
|
||||
* @param keepSecret 是否需要清除secret 默认 不清除 为true
|
||||
* @returns
|
||||
*/
|
||||
async getChatConfig(keepSecret = true, token?: string) {
|
||||
async getChatConfig(keepSecret = true, newToken?: string) {
|
||||
const key = this.getKey();
|
||||
const cache = await redis.get(key);
|
||||
let modelConfig = null;
|
||||
if (cache) {
|
||||
modelConfig = JSON.parse(cache);
|
||||
}
|
||||
const token = newToken || this.token;
|
||||
if (!modelConfig) {
|
||||
if (this.owner !== this.username) {
|
||||
throw new CustomError(
|
||||
@@ -45,6 +48,7 @@ export class ChatConfigServices {
|
||||
if (res.code === 200 && res.data?.data) {
|
||||
modelConfig = res.data.data;
|
||||
} else {
|
||||
logger.error('获取ai.json配置失败', res, 'username', this.username);
|
||||
throw new CustomError(400, 'get config failed');
|
||||
}
|
||||
}
|
||||
@@ -57,10 +61,8 @@ export class ChatConfigServices {
|
||||
await redis.set(key, JSON.stringify(modelConfig), 'EX', cacheTime);
|
||||
}
|
||||
this.aiConfig = modelConfig;
|
||||
if (!keepSecret) {
|
||||
modelConfig = this.filterApiKey(modelConfig);
|
||||
}
|
||||
return modelConfig;
|
||||
const aiConfigParser = new AIConfigParser(modelConfig);
|
||||
return aiConfigParser.getConfig(keepSecret);
|
||||
}
|
||||
async clearCache() {
|
||||
const key = this.getKey();
|
||||
@@ -74,20 +76,6 @@ export class ChatConfigServices {
|
||||
const aiConfigParser = new AIConfigParser(config || this.aiConfig);
|
||||
return aiConfigParser.getSelectOpts();
|
||||
}
|
||||
async filterApiKey(chatConfig: AIConfig) {
|
||||
// 过滤掉secret中的所有apiKey,移除掉并返回chatConfig
|
||||
const { secretKeys = [], ...rest } = chatConfig;
|
||||
return {
|
||||
...rest,
|
||||
secretKeys: secretKeys.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
apiKey: undefined,
|
||||
decryptKey: undefined,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 获取和检测当前用户的额度, 当使用 root 账号的时候,才需要检测
|
||||
* username是当前使用用户
|
||||
|
||||
@@ -14,6 +14,7 @@ export type ChatServicesConfig = {
|
||||
model: string;
|
||||
group: string;
|
||||
decryptKey?: string;
|
||||
token?: string;
|
||||
};
|
||||
export class ChatServices {
|
||||
cachePrefix = 'ai-chat:model:';
|
||||
@@ -39,6 +40,7 @@ export class ChatServices {
|
||||
modelConfig?: ProviderResult;
|
||||
aiConfig?: AIConfig;
|
||||
chatProvider?: BaseChat;
|
||||
token?: string;
|
||||
constructor(opts: ChatServicesConfig) {
|
||||
this.owner = opts.owner;
|
||||
this.model = opts.model;
|
||||
@@ -91,7 +93,8 @@ export class ChatServices {
|
||||
return cache;
|
||||
}
|
||||
async getConfig(username: string) {
|
||||
const services = new ChatConfigServices(this.owner, username);
|
||||
const token = this.token;
|
||||
const services = new ChatConfigServices(this.owner, username, token);
|
||||
return services.getChatConfig();
|
||||
}
|
||||
|
||||
@@ -145,7 +148,7 @@ export class ChatServices {
|
||||
return item;
|
||||
});
|
||||
}
|
||||
static async createServices(opts: Partial<ChatServicesConfig> & { username: string }) {
|
||||
static async createServices(opts: Partial<ChatServicesConfig> & { username: string; token?: string }) {
|
||||
const owner = opts.owner || 'root';
|
||||
const model = opts.model || 'deepseek-chat';
|
||||
const group = opts.group || 'deepseek';
|
||||
|
||||
1
src/run.ts
Normal file
1
src/run.ts
Normal file
@@ -0,0 +1 @@
|
||||
console.log('run commander')
|
||||
@@ -1,65 +0,0 @@
|
||||
import { getChunks } from '../../provider/utils/chunk.ts';
|
||||
|
||||
const str = 'Hello world this is a test 你好沙盒 very big';
|
||||
|
||||
|
||||
const str2 = `不能直接使用 tiktoken(OpenAI的分词器)来计算 Qwen 模型的 Token 数量,因为两者的分词规则(Tokenization)和词表(Vocabulary)完全不同。
|
||||
|
||||
为什么不能混用?
|
||||
词表不同
|
||||
|
||||
tiktoken 是 OpenAI 为 GPT 系列设计的(如 gpt-3.5-turbo, gpt-4),其词表针对英语和代码优化。
|
||||
|
||||
Qwen 使用独立训练的 BPE 词表,对中文、多语言的支持更友好,分词粒度可能不同。
|
||||
|
||||
分词结果差异大
|
||||
同一段文本,tiktoken 和 Qwen 的分词结果可能完全不同。例如:
|
||||
|
||||
OpenAI (tiktoken): "你好" → ['你', '好'](2 Tokens)
|
||||
|
||||
Qwen: "你好" → ['你好'](1 Token,如果词表中包含该组合)
|
||||
|
||||
性能问题
|
||||
即使强制使用 tiktoken 计算 Qwen 的 Token,结果也不准确,可能导致:
|
||||
|
||||
输入超出模型上下文限制(因统计偏差)。
|
||||
|
||||
API 计费或本地推理时出现意外错误。
|
||||
|
||||
正确方法:用 Qwen 的分词器
|
||||
通过 Hugging Face transformers 加载 Qwen 的原生分词器:
|
||||
|
||||
python
|
||||
复制
|
||||
from transformers import AutoTokenizer
|
||||
|
||||
# 加载 Qwen 的分词器(以 Qwen-7B 为例)
|
||||
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen-7B", trust_remote_code=True)
|
||||
|
||||
text = "你好,Qwen模型!"
|
||||
tokens = tokenizer.tokenize(text) # 查看分词结果
|
||||
token_count = len(tokenizer.encode(text, add_special_tokens=False))
|
||||
|
||||
print("分词结果:", tokens)
|
||||
print("Token数量:", token_count)
|
||||
常见问题
|
||||
为什么需要 trust_remote_code=True?
|
||||
Qwen 的分词器是自定义实现的(非 Hugging Face 原生),此参数允许从模型仓库加载运行代码。
|
||||
|
||||
其他语言的 Token 计算?
|
||||
Qwen 对非英语(如中文、日文)的分词效率较高,但仍需用其原生分词器统计。
|
||||
|
||||
与 tiktoken 的速度对比?
|
||||
tiktoken 是纯 Python 实现,速度较快;Qwen 的分词器基于 Hugging Face,可能稍慢但对齐模型需求。
|
||||
|
||||
总结
|
||||
禁止混用:tiktoken ≠ Qwen 分词器。
|
||||
|
||||
始终使用模型配套工具:Qwen 需通过 transformers 加载其官方分词器。
|
||||
|
||||
中文场景特别注意:Qwen 对中文的分词更高效,直接使用可避免偏差。
|
||||
|
||||
如果需要验证分词规则,可通过 tokenizer.vocab 查看词表内容(但注意词表通常较大)。`
|
||||
|
||||
const chunks = getChunks(str2);
|
||||
console.log(chunks);
|
||||
@@ -1,9 +0,0 @@
|
||||
import { encryptAES, decryptAES } from '../../provider/utils/parse-config.ts';
|
||||
|
||||
const plainx = process.env.API_KEY;
|
||||
const decryptKey = process.env.DECRYPT_KEY;
|
||||
const encrypt = encryptAES(plainx, decryptKey);
|
||||
console.log('encrypt', encrypt);
|
||||
|
||||
const decrypt = decryptAES(encrypt, decryptKey);
|
||||
console.log(decrypt);
|
||||
@@ -1,26 +0,0 @@
|
||||
import { ModelScope } from '../../provider/chat-adapter/model-scope.ts';
|
||||
import { logInfo } from '../../logger/index.ts';
|
||||
import util from 'util';
|
||||
import { config } from 'dotenv';
|
||||
config();
|
||||
|
||||
const chat = new ModelScope({
|
||||
apiKey: process.env.MODEL_SCOPE_API_KEY,
|
||||
model: 'Qwen/Qwen2.5-Coder-32B-Instruct',
|
||||
});
|
||||
|
||||
// chat.chat([{ role: 'user', content: 'Hello, world! 1 + 1 equals ?' }]);
|
||||
const chatMessage = [{ role: 'user', content: 'Hello, world! 1 + 1 equals ?' }];
|
||||
|
||||
const main = async () => {
|
||||
const res = await chat.test();
|
||||
logInfo('test', res);
|
||||
};
|
||||
|
||||
// main();
|
||||
const mainChat = async () => {
|
||||
const res = await chat.chat(chatMessage as any);
|
||||
logInfo('chat', res);
|
||||
};
|
||||
|
||||
mainChat();
|
||||
@@ -1,37 +0,0 @@
|
||||
import { Knowledge } from '../provider/knowledge/knowledge.ts';
|
||||
import fs from 'fs';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
const knowledge = new Knowledge({
|
||||
embeddingModel: 'bge-m3:latest',
|
||||
baseURL: 'https://ollama.xiongxiao.me/v1',
|
||||
model: 'qwq:latest',
|
||||
apiKey: process.env.OLLAMA_API_KEY,
|
||||
});
|
||||
|
||||
const main = async () => {
|
||||
const res = await knowledge.generateEmbeddingCore('Hello world this is a test 你好沙盒 very big');
|
||||
fs.writeFileSync('docs/embedding.json', JSON.stringify(res, null, 2));
|
||||
console.log(res);
|
||||
};
|
||||
|
||||
main();
|
||||
|
||||
const main2 = async () => {
|
||||
const text1 = 'Hello, world! this is a test';
|
||||
const text2 = 'Hello, world! this is a test 2';
|
||||
const text3 = 'Hello, world! this is a test 3';
|
||||
const text4 = 'Hello, world! this is a test 4';
|
||||
const text5 = 'Hello, world! this is a test 5';
|
||||
const text6 = 'Hello, world! this is a test 6';
|
||||
const text7 = 'Hello, world! this is a test 7';
|
||||
const text8 = 'Hello, world! this is a test 8';
|
||||
const text9 = 'Hello, world! this is a test 9';
|
||||
const text10 = 'Hello, world! this is a test 10';
|
||||
const res = await knowledge.generateEmbeddingCore([text1, text2, text3, text4, text5, text6, text7, text8, text9, text10]);
|
||||
fs.writeFileSync('docs/embedding2.json', JSON.stringify(res, null, 2));
|
||||
console.log(res);
|
||||
};
|
||||
|
||||
// main2();
|
||||
@@ -1,86 +0,0 @@
|
||||
import { Ollama } from '../provider/chat-adapter/ollama.ts';
|
||||
import util from 'util';
|
||||
const chat = new Ollama({
|
||||
baseURL: 'https://ollama.xiongxiao.me/v1',
|
||||
apiKey: 'xiongxiao2233',
|
||||
model: 'qwq:latest',
|
||||
});
|
||||
|
||||
// chat.chat([{ role: 'user', content: 'Hello, world!' }]);
|
||||
|
||||
const main = async () => {
|
||||
const res = await chat.test();
|
||||
console.log(util.inspect(res, { depth: null, colors: true }));
|
||||
};
|
||||
|
||||
// main();
|
||||
|
||||
const getJson = async () => {
|
||||
const res = await chat.chat(
|
||||
[
|
||||
{ role: 'system', content: '把发送的数据,返回给我对应的json,只处理完发送的数据。如果发送了多个,给我一个数组' },
|
||||
// { role: 'user', content: '{"name":"John","age":30}' },
|
||||
{ role: 'user', content: 'name: 张三' },
|
||||
{ role: 'user', content: 'name: 李四, age: 18' },
|
||||
],
|
||||
{
|
||||
response_format: {
|
||||
type: 'json_schema',
|
||||
json_schema: {
|
||||
name: 'user',
|
||||
description: '用户信息',
|
||||
schema: {
|
||||
type: 'object',
|
||||
// properties: {
|
||||
// name: { type: 'string' },
|
||||
// // age: { type: 'number' },
|
||||
// },
|
||||
// // required: ['name', 'age'],
|
||||
// required: ['name'],
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
age: { type: 'number' },
|
||||
},
|
||||
required: ['name', 'age'],
|
||||
},
|
||||
},
|
||||
},
|
||||
n: 10,
|
||||
},
|
||||
);
|
||||
console.log(util.inspect(res, { depth: null, colors: true }));
|
||||
};
|
||||
|
||||
// getJson();
|
||||
|
||||
const createChat1 = async () => {
|
||||
const res = await chat.chat(
|
||||
[
|
||||
{ role: 'user', content: 'a=1, b=2, c=3' },
|
||||
{ role: 'user', content: 'a+b+c=?' },
|
||||
{ role: 'assistant', content: '给定的值为 \\( a = 1 \\), \\( b = 2 \\), \\( c = 3 \\)。\n' + '\n' + '因此,\\( a + b + c = 1 + 2 + 3 = 6 \\)。' },
|
||||
{ role: 'user', content: 'a+b+c+4=?' },
|
||||
],
|
||||
{
|
||||
model: 'qwen2.5:7b',
|
||||
},
|
||||
);
|
||||
console.log(util.inspect(res, { depth: null, colors: true }));
|
||||
};
|
||||
|
||||
// createChat1();
|
||||
|
||||
const getTags = async () => {
|
||||
const res = await chat.listModels();
|
||||
console.log(util.inspect(res, { depth: null, colors: true }));
|
||||
};
|
||||
|
||||
// getTags();
|
||||
|
||||
const getRunModels = async () => {
|
||||
const res = await chat.listRunModels();
|
||||
console.log('current', new Date().toISOString());
|
||||
console.log(util.inspect(res, { depth: null, colors: true }));
|
||||
};
|
||||
|
||||
// getRunModels();
|
||||
@@ -1,6 +0,0 @@
|
||||
import { ProviderManager } from '../../provider/index.ts';
|
||||
|
||||
const providerConfig = { provider: 'ModelScope', model: 'Qwen/Qwen2.5-Coder-32B-Instruct', apiKey: 'a4cc0e94-3633-4374-85a6-06f455e17bea' };
|
||||
const provider = await ProviderManager.createProvider(providerConfig);
|
||||
const result = await provider.chat([{ role: 'user', content: '你好' }]);
|
||||
console.log(result);
|
||||
@@ -1,15 +0,0 @@
|
||||
import { SiliconFlow } from '../../provider/chat-adapter/siliconflow.ts';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
const siliconflow = new SiliconFlow({
|
||||
apiKey: process.env.SILICONFLOW_API_KEY,
|
||||
model: 'Qwen/Qwen2-7B-Instruct',
|
||||
});
|
||||
|
||||
const main = async () => {
|
||||
const usage = await siliconflow.getUsage();
|
||||
console.log(usage);
|
||||
};
|
||||
|
||||
main();
|
||||
Submodule submodules/query-config deleted from 53cd97454d
@@ -1,25 +1,8 @@
|
||||
{
|
||||
"extends": "@kevisual/types/json/backend.json",
|
||||
"compilerOptions": {
|
||||
"module": "nodenext",
|
||||
"target": "esnext",
|
||||
"noImplicitAny": false,
|
||||
"outDir": "./dist",
|
||||
"sourceMap": false,
|
||||
"allowJs": true,
|
||||
"newLine": "LF",
|
||||
"baseUrl": "./",
|
||||
"typeRoots": [
|
||||
"node_modules/@types",
|
||||
"node_modules/@kevisual/types"
|
||||
],
|
||||
"declaration": true,
|
||||
"noEmit": false,
|
||||
"allowImportingTsExtensions": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"moduleResolution": "NodeNext",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"esModuleInterop": true,
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import basicSsl from '@vitejs/plugin-basic-ssl';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [basicSsl()],
|
||||
server: {
|
||||
port: 3000,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user