mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-16 13:27:08 +00:00
新增了了URL提取tool(来自小面包的代码),修改了自定义搜索tool的逻辑
This commit is contained in:
parent
9965a084f9
commit
35892712fc
3 changed files with 221 additions and 14 deletions
|
|
@ -19,6 +19,7 @@ import { SetTitleTool } from '../utils/tools/SetTitleTool.js'
|
|||
import {SerpTool} from '../utils/tools/SerpTool.js'
|
||||
import { APTool } from '../utils/tools/APTool.js'
|
||||
import { CustomSearchTool } from '../utils/tools/CustomSearchTool.js'
|
||||
import { UrlExtractionTool } from './UrlExtractionTool.js'
|
||||
|
||||
// 角色映射表
|
||||
const roleMap = {
|
||||
|
|
@ -133,7 +134,7 @@ export class bym extends plugin {
|
|||
|
||||
// 使用 specialUserNotice 更新提示词
|
||||
opt.system = `你的名字是“${Config.assistantLabel}”,你在一个qq群里,群号是${group},当前和你说话的人群名片是${card}, qq号是${sender}${specialUserNotice}, 请你结合用户的发言和聊天记录作出回应,要求表现得随性一点,最好参与讨论,混入其中。${specialUserNotice ? '你要重视他的发言。' : ''}与你相关的话题时,你可以去回应一下,不相关你就随便应付就行了,不要过分插科打诨,不知道说什么可以复读群友的话。
|
||||
当群友要求你做搜索、关键词搜索(也可以进行URL搜索摘要)、发图、发视频、画图、禁言、踢人、改头衔和昵称等操作时,需要使用工具,不可以直接发文字来蒙混过关。你必须根据需要来调用各种工具,并回复相关问题。要求优先使用中文进行对话。你也可以自行根据当前话题内容选择性的调用这些工具,增加趣味性,以及对违规的群友使用禁言工具进行惩罚,等这种自我主张式的调用方法来进行合理且富有人性的聊天,在进行搜索时需要详细的给出工具摘要后的内容,也可以在对不懂的问题自行调用自定义搜索工具来解答你的疑惑!
|
||||
当群友要求你做搜索、关键词搜索(也可以进行URL搜索摘要,当URL搜不到结果的时候,你可以尝试使用URL提取工具来获取结果)、发图、发视频、画图、禁言、踢人、改头衔和昵称等操作时,需要使用工具,不可以直接发文字来蒙混过关。你必须根据需要来调用各种工具,并回复相关问题。要求优先使用中文进行对话。你也可以自行根据当前话题内容选择性的调用这些工具,增加趣味性,以及对违规的群友使用禁言工具进行惩罚,等这种自我主张式的调用方法来进行合理且富有人性的聊天,在进行搜索时需要详细的给出工具摘要后的内容,也可以在对不懂的问题自行调用自定义搜索工具来解答你的疑惑!
|
||||
以下是你可以使用的表情列表:
|
||||
${Object.values(faceMap).map(face => `[/${face}]`).join(',')}
|
||||
请在回复中适当使用以上表情,但是不要过度频繁使用这些表情。禁止使用表情列表之外的任何表情,比如[/思考]。` +
|
||||
|
|
@ -168,6 +169,7 @@ ${Object.values(faceMap).map(face => `[/${face}]`).join(',')}
|
|||
new SendPictureTool(),
|
||||
new APTool(),
|
||||
new WebsiteTool(),
|
||||
new UrlExtractionTool(),
|
||||
new CustomSearchTool(),
|
||||
new WeatherTool()
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { AbstractTool } from './AbstractTool.js';
|
||||
import fetch from 'node-fetch';
|
||||
import { Config } from '../config.js';
|
||||
import { AbstractTool } from './AbstractTool.js'
|
||||
import fetch from 'node-fetch'
|
||||
import { Config } from '../config.js'
|
||||
import { UrlExtractionTool } from './UrlExtractionTool.js' // 1. 引入 UrlExtractionTool
|
||||
|
||||
/**
|
||||
* 自定义搜索工具类
|
||||
|
|
@ -39,37 +40,57 @@ export class CustomSearchTool extends AbstractTool {
|
|||
}
|
||||
|
||||
try {
|
||||
// 使用OpenAI API进行搜索或摘要
|
||||
const result = await searchOrSummarize(query, length);
|
||||
// 尝试使用OpenAI API进行搜索或摘要
|
||||
let result = await searchOrSummarize(query, length);
|
||||
|
||||
// 2. 修改 func 方法: 判断是否为 URL 且 searchOrSummarize 结果不理想
|
||||
const isUrl = /^(https?:\/\/)/i.test(query);
|
||||
if (isUrl && (!result || result.includes("failed") || result.trim() === '')) { // 假设不理想的结果包括空字符串或包含"failed"
|
||||
console.log(`[CustomSearchTool] searchOrSummarize failed or returned empty for URL, trying UrlExtractionTool...`);
|
||||
|
||||
// 创建 UrlExtractionTool 实例
|
||||
const urlTool = new UrlExtractionTool();
|
||||
|
||||
// 调用 UrlExtractionTool 的 func 方法提取 URL 内容
|
||||
const extractionResult = await urlTool.func({ message: query, appendContent: false });
|
||||
|
||||
// 3. 整合提取的内容: 如果提取成功,则将提取的内容作为 searchOrSummarize 的输入
|
||||
if (extractionResult && extractionResult.extractedContent) {
|
||||
console.log(`[CustomSearchTool] Successfully extracted content using UrlExtractionTool, summarizing...`);
|
||||
result = await searchOrSummarize(extractionResult.extractedContent, length);
|
||||
} else {
|
||||
// 如果 UrlExtractionTool 也失败了,则返回错误信息
|
||||
return `Failed to extract content from URL and summarize. ${extractionResult.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Search or summarization result: ${result}`);
|
||||
|
||||
// 返回搜索结果或摘要给AI
|
||||
return result;
|
||||
} catch (error) {
|
||||
// 4. 优化错误处理: 捕获 UrlExtractionTool 可能抛出的错误
|
||||
console.error('Search or summarization failed:', error);
|
||||
return `Search or summarization failed, please check the logs. ${error.message}`;
|
||||
}
|
||||
};
|
||||
|
||||
// 工具描述
|
||||
description = 'Use OpenAI API for custom search or summarize URL content, providing comprehensive search results or summaries.';
|
||||
description = 'Use OpenAI API for custom search or summarize URL content, providing comprehensive search results or summaries. If a URL is provided and summarization fails, it will attempt to extract the content first and then summarize.';
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用OpenAI API进行搜索或摘要
|
||||
* @param {string} query - 搜索关键词或URL
|
||||
* @param {string} query - 搜索关键词或URL提取的内容
|
||||
* @param {number} length - 期望的摘要长度(以句子为单位)
|
||||
* @returns {Promise<string>} - 搜索结果或摘要
|
||||
*/
|
||||
async function searchOrSummarize(query, length) {
|
||||
const apiKey = Config.apiKey;
|
||||
const apiBaseUrl = Config.openAiBaseUrl;
|
||||
const apiUrl = `${apiBaseUrl}/chat/completions`; // 将/chat/completions连接到基本URL
|
||||
const apiUrl = `${apiBaseUrl}/chat/completions`;
|
||||
const model = Config.model;
|
||||
|
||||
// 判断是URL还是关键词
|
||||
const isUrl = /^(https?:\/\/)/i.test(query);
|
||||
|
||||
const response = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
|
@ -81,11 +102,11 @@ async function searchOrSummarize(query, length) {
|
|||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: `You are a search and summarization assistant. ${isUrl ? `Please summarize the content of the following URL in English, with a length of ${length} sentences.` : `Please use English to search based on the following keywords and return a summary of ${length} sentences.`}`,
|
||||
content: `You are a search and summarization assistant. Please use English to search based on the following keywords or summarize the provided text, and return a summary of ${length} sentences.`,
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `${isUrl ? `Summarize this article: ${query}` : `Search: ${query}`}`, // 根据是URL还是关键词发送不同的内容
|
||||
content: `Search or summarize: ${query}`,
|
||||
},
|
||||
],
|
||||
max_tokens: 1000 * length,
|
||||
|
|
|
|||
184
utils/tools/UrlExtractionTool.js
Normal file
184
utils/tools/UrlExtractionTool.js
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
import { AbstractTool } from './AbstractTool.js'
|
||||
import fetch from 'node-fetch'
|
||||
|
||||
// 假设的日志记录器,实际应用中应替换为专业的日志库
|
||||
const logger = {
|
||||
mark: console.log,
|
||||
error: console.error,
|
||||
};
|
||||
|
||||
/**
|
||||
* URL提取工具类
|
||||
* @class UrlExtractionTool
|
||||
* @extends {AbstractTool}
|
||||
*/
|
||||
export class UrlExtractionTool extends AbstractTool {
|
||||
// 工具名称
|
||||
name = 'UrlExtractionTool';
|
||||
|
||||
// 工具参数
|
||||
parameters = {
|
||||
properties: {
|
||||
message: {
|
||||
type: 'string',
|
||||
description: 'The message containing URLs to be extracted.',
|
||||
},
|
||||
appendContent: {
|
||||
type: 'boolean',
|
||||
description: 'Whether to append the extracted content to the original message. Defaults to true.',
|
||||
},
|
||||
},
|
||||
required: ['message'],
|
||||
};
|
||||
|
||||
// 工具描述
|
||||
description = 'Extracts URLs from a given message and optionally retrieves the content of those URLs, appending it to the original message.';
|
||||
|
||||
/**
|
||||
* 工具执行函数
|
||||
* @param {Object} opt - 工具参数
|
||||
* @param {Object} ai - AI对象 (未使用)
|
||||
* @returns {Promise<{message: string, extractedContent: string}>} - 包含处理后的消息和提取内容的JSON对象
|
||||
*/
|
||||
func = async function (opt, ai) {
|
||||
let { message, appendContent = true } = opt;
|
||||
if (!message) {
|
||||
return 'The message parameter is required.';
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await processMessageWithUrls(message, appendContent);
|
||||
logger.mark(`[URL Extraction] Processed message: ${result.message}, Extracted content: ${result.extractedContent}`);
|
||||
return result;
|
||||
} catch (error) {
|
||||
logger.error(`[URL Extraction] URL extraction failed: ${error.message}`);
|
||||
return `URL extraction failed, please check the logs. ${error.message}`;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查URL是否为不需要提取内容的文件类型
|
||||
* @param {string} url URL地址
|
||||
* @returns {boolean} 是否为不需要提取的文件类型
|
||||
*/
|
||||
function isSkippedUrl(url) {
|
||||
// 使用 Set 存储扩展名以提高查找效率
|
||||
const skippedExtensions = new Set([
|
||||
// 图片
|
||||
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico', 'tiff', 'tif', 'raw', 'cr2', 'nef', 'arw', 'dng', 'heif', 'heic', 'avif', 'jfif', 'psd', 'ai',
|
||||
// 视频
|
||||
'mp4', 'webm', 'mkv', 'flv', 'avi', 'mov', 'wmv', 'rmvb', 'm4v', '3gp', 'mpeg', 'mpg', 'ts', 'mts',
|
||||
// 可执行文件和二进制文件
|
||||
'exe', 'msi', 'dll', 'sys', 'bin', 'dat', 'iso', 'img', 'dmg', 'pkg', 'deb', 'rpm', 'apk', 'ipa', 'jar', 'class', 'pyc', 'o', 'so', 'dylib',
|
||||
// 压缩文件
|
||||
'zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz', 'tgz', 'tbz', 'cab', 'ace', 'arc',
|
||||
]);
|
||||
|
||||
// 优化关键词匹配
|
||||
const skipKeywords = /\/(images?|photos?|pics?|videos?|medias?|downloads?|uploads?|binaries|assets)\//i;
|
||||
|
||||
// 从URL中提取扩展名
|
||||
const extension = url.split('.').pop().toLowerCase();
|
||||
|
||||
return skippedExtensions.has(extension) || skipKeywords.test(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文本中提取URL
|
||||
* @param {string} text 需要提取URL的文本
|
||||
* @returns {string[]} URL数组
|
||||
*/
|
||||
function extractUrls(text) {
|
||||
// 更精确的正则表达式来匹配 URL
|
||||
const urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g;
|
||||
const matches = text.match(urlRegex) || [];
|
||||
|
||||
// 简化 URL 清理逻辑
|
||||
return matches.map(url => {
|
||||
try {
|
||||
// 解码URL
|
||||
return decodeURIComponent(url);
|
||||
} catch (e) {
|
||||
// 解码失败则返回原URL
|
||||
return url;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 从URL提取内容
|
||||
* @param {string} url 需要提取内容的URL
|
||||
* @returns {Promise<Object>} 提取的内容
|
||||
*/
|
||||
async function extractUrlContent(url) {
|
||||
if (isSkippedUrl(url)) {
|
||||
logger.mark(`[URL提取]跳过不需要处理的URL类型: ${url}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
logger.mark(`[URL提取]开始从URL获取内容: ${url}`);
|
||||
const response = await fetch(`https://lbl.news/api/extract?url=${encodeURIComponent(url)}`);
|
||||
if (!response.ok) {
|
||||
// 更详细的错误处理
|
||||
const errorMsg = await response.text();
|
||||
throw new Error(`提取内容失败: ${response.status} ${response.statusText} - ${errorMsg}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
logger.mark(`[URL提取]成功获取URL内容: ${url}`);
|
||||
return data;
|
||||
} catch (error) {
|
||||
logger.error(`[URL提取]提取内容失败: ${error.message}, URL: ${url}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理消息中的URL并提取内容
|
||||
* @param {string} message 用户消息
|
||||
* @param {boolean} appendContent 是否将提取的内容附加到消息中,默认为true
|
||||
* @returns {Promise<{message: string, extractedContent: string}>} 处理后的消息和提取的内容
|
||||
*/
|
||||
async function processMessageWithUrls(message, appendContent = true) {
|
||||
const urls = extractUrls(message);
|
||||
if (urls.length === 0) {
|
||||
return { message, extractedContent: '' };
|
||||
}
|
||||
|
||||
logger.mark(`[URL处理]从消息中提取到${urls.length}个URL`);
|
||||
let processedMessage = message;
|
||||
let extractedContent = '';
|
||||
|
||||
// 使用 Promise.all 并发处理多个 URL
|
||||
const contents = await Promise.all(
|
||||
urls.map(async url => {
|
||||
if (isSkippedUrl(url)) {
|
||||
logger.mark(`[URL处理]跳过URL: ${url}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
logger.mark(`[URL处理]开始处理URL: ${url}`);
|
||||
const content = await extractUrlContent(url);
|
||||
if (content) {
|
||||
logger.mark(`[URL处理]成功提取URL内容: ${url}`);
|
||||
// 格式化提取的内容
|
||||
return { url, content: content.content };
|
||||
}
|
||||
return null;
|
||||
})
|
||||
);
|
||||
|
||||
// 组合提取的内容
|
||||
contents.forEach(item => {
|
||||
if (item) {
|
||||
const urlContent = `\n\n提取的URL内容(${item.url}):\n内容: ${item.content}`;
|
||||
extractedContent += urlContent;
|
||||
if (appendContent) {
|
||||
processedMessage += urlContent;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { message: processedMessage, extractedContent };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue