mirror of
https://github.com/Jerryplusy/rc-plugin.git
synced 2025-10-14 16:19:18 +00:00
✨ feat(tools.js): 修复第三方服务商无法使用总结一下问题 && 为KIMI类官方模型添加工具调用(tool_calls)
[#82]
This commit is contained in:
parent
d45fa452ec
commit
8eb5bc80d5
2
.gitignore
vendored
2
.gitignore
vendored
@ -23,3 +23,5 @@ yarn.lock
|
|||||||
.vitepress
|
.vitepress
|
||||||
bun.lockb
|
bun.lockb
|
||||||
.next
|
.next
|
||||||
|
CLAUDE.md
|
||||||
|
.claude
|
111
apps/tools.js
111
apps/tools.js
@ -56,7 +56,8 @@ import {
|
|||||||
TWITTER_TWEET_INFO,
|
TWITTER_TWEET_INFO,
|
||||||
WEIBO_SINGLE_INFO,
|
WEIBO_SINGLE_INFO,
|
||||||
WEISHI_VIDEO_INFO,
|
WEISHI_VIDEO_INFO,
|
||||||
XHS_REQ_LINK
|
XHS_REQ_LINK,
|
||||||
|
CRAWL_TOOL
|
||||||
} from "../constants/tools.js";
|
} from "../constants/tools.js";
|
||||||
import BiliInfoModel from "../model/bili-info.js";
|
import BiliInfoModel from "../model/bili-info.js";
|
||||||
import config from "../model/config.js";
|
import config from "../model/config.js";
|
||||||
@ -2644,31 +2645,113 @@ export class tools extends plugin {
|
|||||||
|
|
||||||
if (e.msg.startsWith("#总结一下")) {
|
if (e.msg.startsWith("#总结一下")) {
|
||||||
name = "网页总结";
|
name = "网页总结";
|
||||||
summaryLink = e.msg.replace("#总结一下", ""); // 如果需要进一步处理 summaryLink,可以在这里添加相关逻辑
|
summaryLink = e.msg.replace("#总结一下", "");
|
||||||
} else {
|
} else {
|
||||||
({ name: name, summaryLink: summaryLink } = contentEstimator(e.msg));
|
({ name, summaryLink } = contentEstimator(e.msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断是否有总结的条件
|
// 判断是否有总结的条件
|
||||||
if (_.isEmpty(this.aiApiKey) || _.isEmpty(this.aiApiKey)) {
|
if (_.isEmpty(this.aiApiKey)) {
|
||||||
// e.reply(`没有配置 Kimi,无法为您总结!${ HELP_DOC }`)
|
// e.reply(`没有配置 Kimi,无法为您总结!${ HELP_DOC }`)
|
||||||
await this.tempSummary(name, summaryLink, e);
|
await this.tempSummary(name, summaryLink, e);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const builder = await new OpenaiBuilder()
|
const builder = await new OpenaiBuilder()
|
||||||
.setBaseURL(this.aiBaseURL)
|
.setBaseURL(this.aiBaseURL)
|
||||||
.setApiKey(this.aiApiKey)
|
.setApiKey(this.aiApiKey)
|
||||||
.setModel(this.aiModel)
|
.setModel(this.aiModel)
|
||||||
.setPrompt(SUMMARY_PROMPT)
|
.setPrompt(SUMMARY_PROMPT);
|
||||||
.build();
|
|
||||||
e.reply(`${ this.identifyPrefix }识别:${ name },正在为您总结,请稍等...`, true, { recallMsg: MESSAGE_RECALL_TIME });
|
if (this.aiModel.includes('deepseek')) {
|
||||||
const { ans: kimiAns, model } = await builder.kimi(summaryLink);
|
builder.setProvider('deepseek');
|
||||||
// 计算阅读时间
|
}
|
||||||
const stats = estimateReadingTime(kimiAns);
|
|
||||||
const titleMatch = kimiAns.match(/(Title|标题)([::])\s*(.*?)\n/)?.[3];
|
await builder.build();
|
||||||
e.reply(`《${ titleMatch }》 预计阅读时间: ${ stats.minutes } 分钟,总字数: ${ stats.words }`);
|
|
||||||
const Msg = await Bot.makeForwardMsg(textArrayToMakeForward(e, [`「R插件 x ${ model }」联合为您总结内容:`, kimiAns]));
|
e.reply(`${this.identifyPrefix}识别:${name},正在为您总结,请稍等...`, true, { recallMsg: MESSAGE_RECALL_TIME });
|
||||||
await e.reply(Msg);
|
|
||||||
|
let messages = [{ role: "user", content: summaryLink }];
|
||||||
|
|
||||||
|
// 兜底策略:检测模型是否支持 tool_calls
|
||||||
|
if (!this.aiModel.includes("kimi") && !this.aiModel.includes("moonshot")) {
|
||||||
|
// 不支持 tool_calls 的模型,直接爬取内容并总结
|
||||||
|
try {
|
||||||
|
// 直接使用llmRead爬取链接内容
|
||||||
|
const crawled_content = await llmRead(summaryLink);
|
||||||
|
// 重新构造消息,将爬取到的内容直接放入对话历史
|
||||||
|
messages = [
|
||||||
|
{ role: "user", content: `这是网页链接: ${summaryLink}` },
|
||||||
|
{ role: "assistant", content: `好的,我已经爬取了网页内容,内容如下:\n${crawled_content}` },
|
||||||
|
{ role: "user", content: "请根据以上内容进行总结。" }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 调用kimi进行总结,此时不传递任何工具
|
||||||
|
const response = await builder.chat(messages); // 不传递 CRAWL_TOOL
|
||||||
|
const { ans: kimiAns, model } = response;
|
||||||
|
// 估算阅读时间并提取标题
|
||||||
|
const stats = estimateReadingTime(kimiAns);
|
||||||
|
const titleMatch = kimiAns.match(/(Title|标题)([::])\s*(.*)/)?.[3];
|
||||||
|
e.reply(`《${titleMatch || '未知标题'}》 预计阅读时间: ${stats.minutes} 分钟,总字数: ${stats.words}`);
|
||||||
|
// 将总结内容格式化为合并转发消息
|
||||||
|
const Msg = await Bot.makeForwardMsg(textArrayToMakeForward(e, [`「R插件 x ${model}」联合为您总结内容:`, kimiAns]));
|
||||||
|
await e.reply(Msg);
|
||||||
|
} catch (error) {
|
||||||
|
e.reply(`总结失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为了防止无限循环,设置一个最大循环次数
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
const response = await builder.chat(messages, [CRAWL_TOOL]);
|
||||||
|
|
||||||
|
// 如果Kimi返回了工具调用
|
||||||
|
if (response.tool_calls) {
|
||||||
|
const tool_calls = response.tool_calls;
|
||||||
|
messages.push({
|
||||||
|
role: 'assistant',
|
||||||
|
content: null,
|
||||||
|
tool_calls: tool_calls,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 遍历并处理每一个工具调用
|
||||||
|
for (const tool_call of tool_calls) {
|
||||||
|
if (tool_call.function.name === 'crawl') {
|
||||||
|
try {
|
||||||
|
const args = JSON.parse(tool_call.function.arguments);
|
||||||
|
const urlToCrawl = args.url;
|
||||||
|
// 执行爬取操作
|
||||||
|
const crawled_content = await llmRead(urlToCrawl);
|
||||||
|
messages.push({
|
||||||
|
role: 'tool',
|
||||||
|
tool_call_id: tool_call.id,
|
||||||
|
name: 'crawl',
|
||||||
|
content: crawled_content,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
messages.push({
|
||||||
|
role: 'tool',
|
||||||
|
tool_call_id: tool_call.id,
|
||||||
|
name: 'crawl',
|
||||||
|
content: `爬取错误: ${error.message}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果没有工具调用,说明得到了最终的总结
|
||||||
|
const { ans: kimiAns, model } = response;
|
||||||
|
// 计算阅读时间
|
||||||
|
const stats = estimateReadingTime(kimiAns);
|
||||||
|
const titleMatch = kimiAns.match(/(Title|标题)([::])\s*(.*?)\n/)?.[3];
|
||||||
|
e.reply(`《${titleMatch || '未知标题'}》 预计阅读时间: ${stats.minutes} 分钟,总字数: ${stats.words}`);
|
||||||
|
const Msg = await Bot.makeForwardMsg(textArrayToMakeForward(e, [`「R插件 x ${model}」联合为您总结内容:`, kimiAns]));
|
||||||
|
await e.reply(Msg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.reply("处理超出限制,请重试");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,3 +259,26 @@ export const PearAPI_CRAWLER = "https://api.pearktrue.cn/api/llmreader/?url={}&t
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
export const PearAPI_DEEPSEEK = "https://api.pearktrue.cn/api/deepseek/";
|
export const PearAPI_DEEPSEEK = "https://api.pearktrue.cn/api/deepseek/";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TOOL_CALL 爬虫工具
|
||||||
|
* 用于Kimi模型Tool-Calling的爬虫工具
|
||||||
|
* 当Kimi模型判断需要从网页获取信息时,会调用此工具。
|
||||||
|
*/
|
||||||
|
export const CRAWL_TOOL = {
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "crawl",
|
||||||
|
description: "根据网站地址(URL)获取网页内容。",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
required: ["url"],
|
||||||
|
properties: {
|
||||||
|
url: {
|
||||||
|
type: "string",
|
||||||
|
description: "需要获取内容的网站地址(URL),通常情况下从搜索结果中可以获取网站的地址。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -6,7 +6,7 @@ export class OpenaiBuilder {
|
|||||||
this.apiKey = ""; // 默认API密钥
|
this.apiKey = ""; // 默认API密钥
|
||||||
this.prompt = "描述一下这个图片"; // 默认提示
|
this.prompt = "描述一下这个图片"; // 默认提示
|
||||||
this.model = 'claude-3-haiku-20240307'
|
this.model = 'claude-3-haiku-20240307'
|
||||||
this.path = ''; // 上传文件的路径
|
this.provider = "kimi"; // 默认提供商
|
||||||
}
|
}
|
||||||
|
|
||||||
setBaseURL(baseURL) {
|
setBaseURL(baseURL) {
|
||||||
@ -29,6 +29,11 @@ export class OpenaiBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setProvider(provider) {
|
||||||
|
this.provider = provider;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
setPath(path) {
|
setPath(path) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
return this;
|
return this;
|
||||||
@ -48,24 +53,66 @@ export class OpenaiBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
async kimi(query) {
|
/**
|
||||||
// 请求Kimi
|
* 调用与OpenAI兼容的API(如Kimi/Moonshot)。
|
||||||
const completion = await this.client.post("/v1/chat/completions", {
|
* @param {Array<Object>} messages - 发送给模型的消息列表。
|
||||||
model: "moonshot-v1-8k",
|
* @param {Array<Object>} [tools=[]] - (可选) 一个描述可供模型使用的工具的数组。
|
||||||
messages: [
|
* @returns {Promise<Object>} 返回一个包含模型响应的对象。如果模型决定调用工具,则包含 'tool_calls' 字段;否则,包含 'ans' 文本响应。
|
||||||
{
|
*/
|
||||||
"role": "system",
|
async chat(messages, tools = []) {
|
||||||
"content": this.prompt,
|
if (this.provider === 'deepseek') {
|
||||||
},
|
const content = messages.find(m => m.role === 'user')?.content;
|
||||||
{
|
const ans = await deepSeekChat(content, this.prompt);
|
||||||
role: "user",
|
return {
|
||||||
content: query
|
"model": "deepseek",
|
||||||
},
|
"ans": ans
|
||||||
],
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
// 准备发送给API的消息
|
||||||
|
let requestMessages = [...messages];
|
||||||
|
// 检查是否已存在系统提示
|
||||||
|
const hasSystemPrompt = requestMessages.some(m => m.role === 'system');
|
||||||
|
|
||||||
|
// 如果没有系统提示并且builder中已设置,则添加
|
||||||
|
if (!hasSystemPrompt && this.prompt) {
|
||||||
|
requestMessages.unshift({
|
||||||
|
role: 'system',
|
||||||
|
content: this.prompt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建API请求的负载
|
||||||
|
const payload = {
|
||||||
|
model: this.model, // 使用在builder中设置的模型
|
||||||
|
messages: requestMessages,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果提供了工具,将其添加到负载中,并让模型自动决定是否使用
|
||||||
|
if (tools && tools.length > 0) {
|
||||||
|
payload.tools = tools;
|
||||||
|
payload.tool_choice = "auto";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送POST请求到聊天完成端点
|
||||||
|
const completion = await this.client.post("/v1/chat/completions", payload);
|
||||||
|
const message = completion.data.choices[0].message;
|
||||||
|
|
||||||
|
// 从响应中获取实际使用的模型名称
|
||||||
|
const modelName = completion.data.model;
|
||||||
|
|
||||||
|
// 如果模型的响应中包含工具调用
|
||||||
|
if (message.tool_calls) {
|
||||||
|
return {
|
||||||
|
"model": modelName,
|
||||||
|
"tool_calls": message.tool_calls
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 否则,返回包含文本答案的响应
|
||||||
return {
|
return {
|
||||||
"model": "月之暗面 Kimi",
|
"model": modelName,
|
||||||
"ans": completion.data.choices[0].message.content
|
"ans": message.content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user