update
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@kevisual/ai",
|
"name": "@kevisual/ai",
|
||||||
"version": "0.0.18",
|
"version": "0.0.19",
|
||||||
"description": "AI Center Services",
|
"description": "AI Center Services",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"basename": "/root/ai-center-services",
|
"basename": "/root/ai-center-services",
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
import { BailianProvider } from '../../provider/index.ts'
|
|
||||||
import dotenv from 'dotenv';
|
|
||||||
|
|
||||||
dotenv.config();
|
|
||||||
import { App } from '@kevisual/router'
|
|
||||||
import util from 'node:util';
|
|
||||||
const ai = new BailianProvider({
|
|
||||||
apiKey: process.env.BAILIAN_API_KEY || '',
|
|
||||||
model: 'qwen-turbo-latest',
|
|
||||||
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1'
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
// const res = await ai.chat([
|
|
||||||
|
|
||||||
// {
|
|
||||||
// role: 'user',
|
|
||||||
// content: `1+1等于多少?`
|
|
||||||
// },
|
|
||||||
|
|
||||||
// ],
|
|
||||||
// )
|
|
||||||
// // console.log('AI Response:', res);
|
|
||||||
// const content = res.choices[0].message?.content || ''
|
|
||||||
|
|
||||||
// console.log(util.inspect(res, { depth: null }))
|
|
||||||
|
|
||||||
// console.log('responseText', ai.responseText)
|
|
||||||
|
|
||||||
|
|
||||||
// const res = await ai.chatStream([
|
|
||||||
|
|
||||||
// {
|
|
||||||
// role: 'user',
|
|
||||||
// content: `1+1等于多少?`
|
|
||||||
// },
|
|
||||||
|
|
||||||
// ],
|
|
||||||
// )
|
|
||||||
// // console.log('AI Response:', res);
|
|
||||||
// export const readStream = async (chatStream) => {
|
|
||||||
// let buffer = '';
|
|
||||||
// for await (const chunk of chatStream) {
|
|
||||||
// // chunk 已经是解码后的字符串,直接拼接即可
|
|
||||||
// buffer += chunk;
|
|
||||||
// }
|
|
||||||
// console.log('AI Response:', buffer);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// await readStream(res);
|
|
||||||
|
|
||||||
const embe = await ai.generateEmbeddingCore([
|
|
||||||
'你好,世界!',
|
|
||||||
'Hello, world!',
|
|
||||||
], {
|
|
||||||
model: 'text-embedding-v4'
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Embedding Response:', util.inspect(embe, { depth: null }));
|
|
||||||
@@ -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,18 +0,0 @@
|
|||||||
import { Jimen } from "../jimeng/index.ts"
|
|
||||||
import dotenv from 'dotenv';
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
const jimeng = new Jimen({
|
|
||||||
token: process.env.JIMENG_TOKEN,
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log("Generating image...");
|
|
||||||
|
|
||||||
await jimeng.generateImage({
|
|
||||||
prompt: "创建一幅未来城市的数字艺术作品,充满科技感和创新元素,色彩鲜艳,细节丰富",
|
|
||||||
resolution: "2k"
|
|
||||||
}).then((res) => {
|
|
||||||
console.log("Image generation response:", res);
|
|
||||||
}).catch((err) => {
|
|
||||||
console.error("Error generating image:", err);
|
|
||||||
});
|
|
||||||
@@ -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,35 +0,0 @@
|
|||||||
curl --request POST \
|
|
||||||
--url https://api.siliconflow.cn/v1/chat/completions \
|
|
||||||
--header 'Authorization: Bearer sk-qbiigkzoaamuqxtwlgkugodncebkfbosemadfubjrseobpvx' \
|
|
||||||
--header 'Content-Type: application/json' \
|
|
||||||
--data '{
|
|
||||||
"model": "Qwen/Qwen3-14B",
|
|
||||||
"messages": [
|
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"content": "计算a+b的值"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stream": false,
|
|
||||||
"max_tokens": 512,
|
|
||||||
"stop": null,
|
|
||||||
"temperature": 0.7,
|
|
||||||
"top_p": 0.7,
|
|
||||||
"top_k": 50,
|
|
||||||
"frequency_penalty": 0.5,
|
|
||||||
"n": 1,
|
|
||||||
"response_format": {
|
|
||||||
"type": "text"
|
|
||||||
},
|
|
||||||
"tools": [
|
|
||||||
{
|
|
||||||
"type": "function",
|
|
||||||
"function": {
|
|
||||||
"description": "计算a,b,c算法的值,a=1,b=2,c=3",
|
|
||||||
"name": "compouted",
|
|
||||||
"parameters": {},
|
|
||||||
"strict": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}'
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
import { SiliconFlow } from '../../provider/chat-adapter/siliconflow.ts';
|
|
||||||
import { Ollama } from '../../provider/chat-adapter/ollama.ts';
|
|
||||||
import dotenv from 'dotenv';
|
|
||||||
|
|
||||||
dotenv.config();
|
|
||||||
const siliconflow = new SiliconFlow({
|
|
||||||
apiKey: process.env.SILICONFLOW_API_KEY,
|
|
||||||
model: 'Qwen/Qwen3-14B',
|
|
||||||
});
|
|
||||||
const ollama = new Ollama({
|
|
||||||
model: 'qwen3:32b',
|
|
||||||
apiKey: process.env.OLLAMA_API_KEY,
|
|
||||||
baseURL: process.env.OLLAMA_BASE_URL,
|
|
||||||
});
|
|
||||||
const main = async () => {
|
|
||||||
const usage = await siliconflow.getUsageInfo();
|
|
||||||
console.log(usage);
|
|
||||||
};
|
|
||||||
// 1. 定义工具函数
|
|
||||||
const availableFunctions: Record<string, (args: any) => Promise<any>> = {
|
|
||||||
get_time: async (args: { location: string }) => {
|
|
||||||
// 模拟API调用
|
|
||||||
console.log('time', args);
|
|
||||||
return {
|
|
||||||
time: '2022-03-22 12:00:00',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
get_location: async (args: { symbol: string }) => {
|
|
||||||
// 模拟API调用
|
|
||||||
console.log('location', args);
|
|
||||||
return {
|
|
||||||
city: 'Beijing',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// main();
|
|
||||||
const funcCall = async (model = siliconflow) => {
|
|
||||||
const tools = [
|
|
||||||
{
|
|
||||||
type: 'function',
|
|
||||||
function: {
|
|
||||||
name: 'get_time',
|
|
||||||
description: '获取当前时间',
|
|
||||||
parameters: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
place: {
|
|
||||||
type: 'string',
|
|
||||||
description: '位置',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ['place'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'function',
|
|
||||||
function: {
|
|
||||||
name: 'get_location',
|
|
||||||
description: '获取当前位置',
|
|
||||||
// parameters: {},
|
|
||||||
parameters: {},
|
|
||||||
strict: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const messages: any[] = [{ role: 'user', content: '获取当前位置的当前时间' }];
|
|
||||||
const res = await model.chat(messages, {
|
|
||||||
tools: tools as any,
|
|
||||||
});
|
|
||||||
console.log(res.choices[0]);
|
|
||||||
const assistantMessage = res.choices[0].message;
|
|
||||||
const finish_reason = res.choices[0].finish_reason;
|
|
||||||
messages.push(assistantMessage);
|
|
||||||
let toolCalls = assistantMessage.tool_calls;
|
|
||||||
console.log("toolCalls", JSON.stringify(toolCalls));
|
|
||||||
let maxRetries = 3;
|
|
||||||
while (toolCalls && toolCalls.length > 0) {
|
|
||||||
// 处理每个函数调用
|
|
||||||
for (const toolCall of toolCalls) {
|
|
||||||
const functionName = toolCall.function.name;
|
|
||||||
const functionArgs = JSON.parse(toolCall.function.arguments);
|
|
||||||
// 调用本地函数
|
|
||||||
const functionResponse = await availableFunctions[functionName](functionArgs);
|
|
||||||
// 将结果添加到消息历史
|
|
||||||
messages.push({
|
|
||||||
role: 'tool',
|
|
||||||
name: functionName,
|
|
||||||
content: JSON.stringify(functionResponse),
|
|
||||||
tool_call_id: toolCall.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 第二次调用 - 将函数结果发送给模型获取最终回复
|
|
||||||
const secondResponse = await model.chat(messages, {
|
|
||||||
tools: tools as any,
|
|
||||||
});
|
|
||||||
|
|
||||||
const finalMessage = secondResponse.choices[0].message;
|
|
||||||
messages.push(finalMessage);
|
|
||||||
const _toolCalls = finalMessage.tool_calls;
|
|
||||||
console.log("toolCalls", JSON.stringify(toolCalls) ,finalMessage.role);
|
|
||||||
toolCalls = _toolCalls ? _toolCalls : [];
|
|
||||||
maxRetries--;
|
|
||||||
if (maxRetries <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('tool calls', toolCalls);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(messages);
|
|
||||||
};
|
|
||||||
|
|
||||||
funcCall(ollama as any);
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { ModelScope } from '../../provider/chat-adapter/model-scope.ts';
|
|
||||||
import { logger } from '../../modules/logger.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();
|
|
||||||
logger.info('test', res);
|
|
||||||
};
|
|
||||||
|
|
||||||
main();
|
|
||||||
const mainChat = async () => {
|
|
||||||
const res = await chat.chat(chatMessage as any);
|
|
||||||
logger.info('chat', res);
|
|
||||||
};
|
|
||||||
|
|
||||||
// mainChat();
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { Knowledge } from '../../../../src/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 '../../../../src/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,7 +0,0 @@
|
|||||||
import { ProviderManager } from '../..//provider/index.ts';
|
|
||||||
import { config } from 'dotenv';
|
|
||||||
config();
|
|
||||||
const providerConfig = { provider: 'ModelScope', model: 'Qwen/Qwen2.5-Coder-32B-Instruct', apiKey: process.env.MODEL_SCOPE_API_KEY };
|
|
||||||
const provider = await ProviderManager.createProvider(providerConfig);
|
|
||||||
const result = await provider.chat([{ role: 'user', content: '你好' }]);
|
|
||||||
console.log(result);
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
/**
|
|
||||||
* 尝试从字符串中提取JSON对象
|
|
||||||
*/
|
|
||||||
export const getJsonFromString = (str: string) => {
|
|
||||||
try {
|
|
||||||
const jsonMatch = str.match(/```json\s*([\s\S]*?)\s*```/);
|
|
||||||
if (jsonMatch && jsonMatch[1]) {
|
|
||||||
return JSON.parse(jsonMatch[1]);
|
|
||||||
}
|
|
||||||
} catch (error) {}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user