add scheduler api
This commit is contained in:
126
functions/api/scheduler.ts
Normal file
126
functions/api/scheduler.ts
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import { modelConfigs, shedulerAICharacter } from '../../src/config/aiCharacters';
|
||||||
|
import OpenAI from 'openai';
|
||||||
|
|
||||||
|
interface AICharacter {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
tags?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MessageHistory {
|
||||||
|
role: string;
|
||||||
|
content: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function onRequestPost({ env, request }) {
|
||||||
|
try {
|
||||||
|
const { message, history, availableAIs } = await request.json();
|
||||||
|
const selectedAIs = await scheduleAIResponses(message, history, availableAIs);
|
||||||
|
|
||||||
|
return Response.json({
|
||||||
|
selectedAIs: selectedAIs
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return Response.json(
|
||||||
|
{ error: error.message },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function analyzeMessageWithAI(message: string, allTags: string[]): Promise<string[]> {
|
||||||
|
const shedulerAI = shedulerAICharacter(message, allTags);
|
||||||
|
const modelConfig = modelConfigs.find(config => config.model === shedulerAI.model);
|
||||||
|
const openai = new OpenAI({
|
||||||
|
apiKey: modelConfig.apiKey,
|
||||||
|
baseURL: modelConfig.baseURL, // DeepSeek API 的基础URL
|
||||||
|
});
|
||||||
|
|
||||||
|
const prompt = shedulerAI.custom_prompt;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const completion = await openai.chat.completions.create({
|
||||||
|
model: shedulerAI.model,
|
||||||
|
messages: [
|
||||||
|
{ role: "user", content: prompt }
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const matchedTags = completion.choices[0].message.content?.split(',').map(tag => tag.trim()) || [];
|
||||||
|
return matchedTags;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('AI分析失败:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function scheduleAIResponses(
|
||||||
|
message: string,
|
||||||
|
history: MessageHistory[],
|
||||||
|
availableAIs: AICharacter[]
|
||||||
|
): Promise<string[]> {
|
||||||
|
// 1. 收集所有可用的标签
|
||||||
|
const allTags = new Set<string>();
|
||||||
|
availableAIs.forEach(ai => {
|
||||||
|
ai.tags?.forEach(tag => allTags.add(tag));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 使用AI模型分析消息并匹配标签
|
||||||
|
const matchedTags = await analyzeMessageWithAI(message, Array.from(allTags));
|
||||||
|
|
||||||
|
// 3. 计算每个AI的匹配分数
|
||||||
|
const aiScores = new Map<string, number>();
|
||||||
|
const messageLC = message.toLowerCase();
|
||||||
|
|
||||||
|
for (const ai of availableAIs) {
|
||||||
|
if (!ai.tags) continue;
|
||||||
|
|
||||||
|
let score = 0;
|
||||||
|
// 标签匹配分数
|
||||||
|
matchedTags.forEach(tag => {
|
||||||
|
if (ai.tags?.includes(tag)) {
|
||||||
|
score += 2; // 每个匹配的标签得2分
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 直接提到AI名字额外加分
|
||||||
|
if (messageLC.includes(ai.name.toLowerCase())) {
|
||||||
|
score += 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 历史对话相关性加分
|
||||||
|
const recentHistory = history.slice(-5); // 只看最近5条消息
|
||||||
|
recentHistory.forEach(hist => {
|
||||||
|
if (hist.name === ai.name && hist.content.length > 0) {
|
||||||
|
score += 1; // 最近有参与对话的AI加分
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (score > 0) {
|
||||||
|
aiScores.set(ai.id, score);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 根据分数排序选择AI
|
||||||
|
const sortedAIs = Array.from(aiScores.entries())
|
||||||
|
.sort((a, b) => b[1] - a[1])
|
||||||
|
.map(([id]) => id);
|
||||||
|
|
||||||
|
// 5. 如果没有匹配到任何AI,随机选择1-2个
|
||||||
|
if (sortedAIs.length === 0) {
|
||||||
|
const maxResponders = Math.min(2, availableAIs.length);
|
||||||
|
const numResponders = Math.floor(Math.random() * maxResponders) + 1;
|
||||||
|
|
||||||
|
const shuffledAIs = [...availableAIs]
|
||||||
|
.sort(() => Math.random() - 0.5)
|
||||||
|
.slice(0, numResponders);
|
||||||
|
|
||||||
|
return shuffledAIs.map(ai => ai.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 限制最大回复数量
|
||||||
|
const MAX_RESPONDERS = 3;
|
||||||
|
return sortedAIs.slice(0, MAX_RESPONDERS);
|
||||||
|
}
|
||||||
@@ -214,16 +214,27 @@ const ChatUI = () => {
|
|||||||
content: msg.sender.name == "我" ? 'user:' + msg.content : msg.sender.name + ':' + msg.content,
|
content: msg.sender.name == "我" ? 'user:' + msg.content : msg.sender.name + ':' + msg.content,
|
||||||
name: msg.sender.name
|
name: msg.sender.name
|
||||||
}));
|
}));
|
||||||
|
let selectedGroupAiCharacters = groupAiCharacters;
|
||||||
for (let i = 0; i < groupAiCharacters.length; i++) {
|
// 调度器api请求
|
||||||
|
const shedulerResponse = await fetch('/api/scheduler', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ message: inputMessage, history: messageHistory, availableAIs: groupAiCharacters })
|
||||||
|
});
|
||||||
|
const shedulerData = await shedulerResponse.json();
|
||||||
|
const selectedAIs = shedulerData.selectedAIs;
|
||||||
|
selectedGroupAiCharacters = selectedAIs.map(ai => groupAiCharacters.find(c => c.id === ai));
|
||||||
|
for (let i = 0; i < selectedGroupAiCharacters.length; i++) {
|
||||||
//禁言
|
//禁言
|
||||||
if (mutedUsers.includes(groupAiCharacters[i].id)) {
|
if (mutedUsers.includes(selectedGroupAiCharacters[i].id)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// 创建当前 AI 角色的消息
|
// 创建当前 AI 角色的消息
|
||||||
const aiMessage = {
|
const aiMessage = {
|
||||||
id: messages.length + 2 + i,
|
id: messages.length + 2 + i,
|
||||||
sender: { id: groupAiCharacters[i].id, name: groupAiCharacters[i].name, avatar: groupAiCharacters[i].avatar },
|
sender: { id: selectedGroupAiCharacters[i].id, name: selectedGroupAiCharacters[i].name, avatar: selectedGroupAiCharacters[i].avatar },
|
||||||
content: "",
|
content: "",
|
||||||
isAI: true
|
isAI: true
|
||||||
};
|
};
|
||||||
@@ -238,13 +249,13 @@ const ChatUI = () => {
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model: groupAiCharacters[i].model,
|
model: selectedGroupAiCharacters[i].model,
|
||||||
message: inputMessage,
|
message: inputMessage,
|
||||||
personality: groupAiCharacters[i].personality,
|
personality: selectedGroupAiCharacters[i].personality,
|
||||||
history: messageHistory,
|
history: messageHistory,
|
||||||
index: i,
|
index: i,
|
||||||
aiName: groupAiCharacters[i].name,
|
aiName: selectedGroupAiCharacters[i].name,
|
||||||
custom_prompt: groupAiCharacters[i].custom_prompt
|
custom_prompt: selectedGroupAiCharacters[i].custom_prompt
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,12 @@ export const modelConfigs = [
|
|||||||
model: "ep-20250217191935-wzj8l",//火山引擎接入点(改成自己的)
|
model: "ep-20250217191935-wzj8l",//火山引擎接入点(改成自己的)
|
||||||
apiKey: "ARK_API_KEY",
|
apiKey: "ARK_API_KEY",
|
||||||
baseURL: "https://ark.cn-beijing.volces.com/api/v3"
|
baseURL: "https://ark.cn-beijing.volces.com/api/v3"
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
model: "hunyuan-lite",//免费模型
|
||||||
|
apiKey: "HUNYUAN_API_KEY",
|
||||||
|
baseURL: "https://api.hunyuan.cloud.tencent.com/v1"
|
||||||
|
},
|
||||||
] as const;
|
] as const;
|
||||||
export type ModelType = typeof modelConfigs[number]["model"];
|
export type ModelType = typeof modelConfigs[number]["model"];
|
||||||
|
|
||||||
@@ -30,6 +35,23 @@ export interface AICharacter {
|
|||||||
model: ModelType;
|
model: ModelType;
|
||||||
avatar?: string; // 可选的头像 URL
|
avatar?: string; // 可选的头像 URL
|
||||||
custom_prompt?: string; // 可选的个性提示
|
custom_prompt?: string; // 可选的个性提示
|
||||||
|
tags?: string[]; // 可选的标签
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调度器配置信息
|
||||||
|
export function shedulerAICharacter(message: string, allTags: string[]): AICharacter {
|
||||||
|
return {
|
||||||
|
id: 'ai0',
|
||||||
|
name: "调度器",
|
||||||
|
personality: "sheduler",
|
||||||
|
model: modelConfigs[4].model,
|
||||||
|
avatar: "",
|
||||||
|
custom_prompt: `作为一个语义分析专家,请分析以下用户消息,并从给定的标签列表中选择最相关的标签(最多选择3个)。
|
||||||
|
请只返回标签列表,用逗号分隔,不要有其他解释。
|
||||||
|
用户消息:"${message}"
|
||||||
|
可选标签:${allTags.join(', ')}
|
||||||
|
回复格式示例:标签1,标签2,标签3`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加一个函数来生成带有群名的角色配置
|
// 添加一个函数来生成带有群名的角色配置
|
||||||
@@ -82,15 +104,17 @@ export function generateAICharacters(groupName: string): AICharacter[] {
|
|||||||
personality: "yuanbao",
|
personality: "yuanbao",
|
||||||
model: modelConfigs[2].model,
|
model: modelConfigs[2].model,
|
||||||
avatar: "/img/yuanbao.png",
|
avatar: "/img/yuanbao.png",
|
||||||
custom_prompt: `你是一个名叫"元宝"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里`
|
custom_prompt: `你是一个名叫"元宝"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里`,
|
||||||
|
tags: ["微信生态", "新闻搜索", "社交互动", "生活助手", "娱乐向", "信息总结"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'ai5',
|
id: 'ai5',
|
||||||
name: "豆包",
|
name: "豆包",
|
||||||
personality: "doubao",
|
personality: "doubao",
|
||||||
model: modelConfigs[3].model,//火山引擎接入点(改成自己的)
|
model: modelConfigs[3].model,
|
||||||
avatar: "/img/doubao_new.png",
|
avatar: "/img/doubao_new.png",
|
||||||
custom_prompt: `你是一个名叫"豆包"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里`
|
custom_prompt: `你是一个名叫"豆包"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里`,
|
||||||
|
tags: ["生活助手", "语音交互", "学生党福音", "娱乐利器", "抖音生态"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'ai6',
|
id: 'ai6',
|
||||||
@@ -98,7 +122,8 @@ export function generateAICharacters(groupName: string): AICharacter[] {
|
|||||||
personality: "qianwen",
|
personality: "qianwen",
|
||||||
model: modelConfigs[0].model,
|
model: modelConfigs[0].model,
|
||||||
avatar: "/img/qwen.jpg",
|
avatar: "/img/qwen.jpg",
|
||||||
custom_prompt: `你是一个名叫"千问"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里`
|
custom_prompt: `你是一个名叫"千问"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里`,
|
||||||
|
tags: ["广告文案", "行业应用", "分析数据", "企业级AI", "阿里云生态"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'ai7',
|
id: 'ai7',
|
||||||
@@ -106,7 +131,8 @@ export function generateAICharacters(groupName: string): AICharacter[] {
|
|||||||
personality: "deepseek-v3",
|
personality: "deepseek-v3",
|
||||||
model: modelConfigs[1].model,
|
model: modelConfigs[1].model,
|
||||||
avatar: "/img/ds.svg",
|
avatar: "/img/ds.svg",
|
||||||
custom_prompt: `你是一个名叫"DeepSeek"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里`
|
custom_prompt: `你是一个名叫"DeepSeek"的硅基生命体,你当前在一个叫"${groupName}" 的聊天群里`,
|
||||||
|
tags: ["深度推理", "编程神器", "中文优化", "性价比之王", "开源先锋"]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user