deepfish-ai 1.0.21 → 1.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/AgentRobot/AgentRobotFactory/MainAgentRobot.js +4 -7
- package/src/AgentRobot/AgentRobotFactory/SubAgentRobot.js +3 -8
- package/src/AgentRobot/AgentRobotFactory/SubSkillAgentRobot.js +3 -8
- package/src/AgentRobot/BaseAgentRobot/Brain.js +8 -8
- package/src/AgentRobot/BaseAgentRobot/Hand.js +3 -3
- package/src/AgentRobot/BaseAgentRobot/index.js +25 -95
- package/src/AgentRobot/BaseAgentRobot/lazy-tools/doc-transform.js +204 -0
- package/src/AgentRobot/BaseAgentRobot/lazy-tools/docx.js +552 -1
- package/src/AgentRobot/BaseAgentRobot/lazy-tools/embedding.js +763 -0
- package/src/AgentRobot/BaseAgentRobot/lazy-tools/img.js +1 -0
- package/src/AgentRobot/BaseAgentRobot/lazy-tools/pdf.js +1 -0
- package/src/AgentRobot/BaseAgentRobot/lazy-tools/pptx.js +1 -0
- package/src/AgentRobot/BaseAgentRobot/lazy-tools/xlsx.js +1 -0
- package/src/AgentRobot/BaseAgentRobot/tools/BaseTools.js +1 -0
- package/src/AgentRobot/BaseAgentRobot/tools/CreateAgentTools.js +3 -2
- package/src/AgentRobot/BaseAgentRobot/tools/FileTools.js +1 -0
- package/src/AgentRobot/BaseAgentRobot/tools/GenerateTools.js +4 -2
- package/src/AgentRobot/BaseAgentRobot/tools/InquirerTools.js +1 -0
- package/src/AgentRobot/BaseAgentRobot/tools/SystemTools.js +3 -4
- package/src/AgentRobot/BaseAgentRobot/tools/TaskTools.js +1 -0
- package/src/AgentRobot/BaseAgentRobot/tools/TestTools.js +1 -0
- package/src/AgentRobot/BaseAgentRobot/tools/UserTool.js +87 -0
- package/src/AgentRobot/BaseAgentRobot/tools/WebTools.js +257 -0
- package/src/AgentRobot/BaseAgentRobot/utils/AIRequest.js +9 -2
- package/src/AgentRobot/BaseAgentRobot/utils/AIToolManager.js +128 -0
- package/src/AgentRobot/BaseAgentRobot/utils/AttachmentToolScanner.js +4 -5
- package/src/cli/DefaultConfig.js +3 -0
|
@@ -67,7 +67,7 @@ async function createSubAgent(workGoal) {
|
|
|
67
67
|
|
|
68
68
|
function loadAttachTool(toolName) {
|
|
69
69
|
try {
|
|
70
|
-
this.agentRobot.
|
|
70
|
+
this.agentRobot.toolManager.addTool(toolName)
|
|
71
71
|
return `Tool ${toolName} loaded successfully`
|
|
72
72
|
} catch (error) {
|
|
73
73
|
return `Failed to load tool ${toolName}: ${error.message}`
|
|
@@ -107,7 +107,7 @@ const descriptions = [
|
|
|
107
107
|
function: {
|
|
108
108
|
name: 'createSubAgent',
|
|
109
109
|
description:
|
|
110
|
-
'
|
|
110
|
+
'创建一个子agent,并让其执行给定工作目标。可以进行复杂的任务分配,如知识库查询、多源搜索结果整合、多步骤任务处理等,返回执行结果与状态信息。',
|
|
111
111
|
parameters: {
|
|
112
112
|
type: 'object',
|
|
113
113
|
properties: {
|
|
@@ -151,6 +151,7 @@ const CreateAgentTool = {
|
|
|
151
151
|
description: '提供子agent创建与任务分发能力,可按技能选择子agent执行目标任务',
|
|
152
152
|
descriptions,
|
|
153
153
|
functions,
|
|
154
|
+
isSystem: true
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
module.exports = CreateAgentTool
|
|
@@ -102,8 +102,9 @@ async function getGenerateSkillRules(goal) {
|
|
|
102
102
|
2. 命名规范:
|
|
103
103
|
- 函数名称前缀:「领域用途+分隔符」(如systemFileManagement_)
|
|
104
104
|
- 函数描述开头:统一格式「领域用途+分隔符+功能描述」(如系统文件管理:重命名文件)
|
|
105
|
-
3. 内置工具函数调用:函数内可以使用内置工具函数requestAI来获取AI请求结果,在环境中通过this.Tools
|
|
105
|
+
3. 内置工具函数调用:函数内可以使用内置工具函数requestAI来获取AI请求结果,在环境中通过this.Tools注入,必要时也可以使用其他函数,示例:
|
|
106
106
|
- this.Tools.requestAI(systemDescription, prompt, temperature)
|
|
107
|
+
- this.Tools.executeCommand(command)
|
|
107
108
|
4. 函数数量:至少包含1个可被AI工作流调用的函数
|
|
108
109
|
5. 拆分成多个文件,保持文件结构清晰
|
|
109
110
|
6. 对于大于5个的扩展功能,需要在functions中输出一个说明函数,只需返回一个markdown类型的英文字符串,专门用于解释当前扩展工具的使用方法、参数说明、示例等内容,函数名称为「readme」,如「systemFileManagement_readme」;函数描述需要强调调用该扩展模块前必须先阅读该规则文档。
|
|
@@ -201,7 +202,7 @@ module.exports = functions
|
|
|
201
202
|
3. 测试文件:如果引入了requestAI,必须确保函数可正确使用 this.Tools 上下文。
|
|
202
203
|
- 环境创建方式:
|
|
203
204
|
"const { DeepFishAI } = require('${packagePath}')\nconst deepfishAI = new DeepFishAI();"
|
|
204
|
-
- 调用方式:为模块导出的functions绑定Tools上下文,示例:functions.Tools = deepfishAI.agentRobot.
|
|
205
|
+
- 调用方式:为模块导出的functions绑定Tools上下文,示例:functions.Tools = deepfishAI.agentRobot.toolManager.functions;
|
|
205
206
|
4. 断言与输出规范:每个用例需打印“用例名称、输入、期望、实际、是否通过(PASS/FAIL)”;全部执行后输出汇总(总数、通过数、失败数)。
|
|
206
207
|
5. 失败处理:出现异常时不得静默吞错,需捕获并输出可定位信息(错误消息、对应用例、关键参数)。
|
|
207
208
|
6. 副作用控制:测试过程中创建的临时文件必须使用 tmp_test_ 前缀,并在测试结束后清理。
|
|
@@ -422,6 +423,7 @@ const GenerateTools = {
|
|
|
422
423
|
description: '提供扩展工具与Skill工具包生成规则能力,用于辅助AI构建标准化扩展项目模板',
|
|
423
424
|
descriptions,
|
|
424
425
|
functions,
|
|
426
|
+
isSystem: true
|
|
425
427
|
}
|
|
426
428
|
|
|
427
429
|
module.exports = GenerateTools
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @Author: Roman 306863030@qq.com
|
|
3
3
|
* @Date: 2026-03-17 11:59:19
|
|
4
4
|
* @LastEditors: Roman 306863030@qq.com
|
|
5
|
-
* @LastEditTime: 2026-04-
|
|
5
|
+
* @LastEditTime: 2026-04-23 18:53:35
|
|
6
6
|
* @FilePath: \deepfish\src\AgentRobot\BaseAgentRobot\tools\SystemTools.js
|
|
7
7
|
* @Description: 默认扩展函数
|
|
8
8
|
* @
|
|
@@ -65,8 +65,6 @@ async function requestAI(
|
|
|
65
65
|
systemDescription = systemDescription.systemDescription || ''
|
|
66
66
|
}
|
|
67
67
|
try {
|
|
68
|
-
aiConsole.logInfo(`aiSystem: ${systemDescription}`)
|
|
69
|
-
aiConsole.logInfo(`aiPrompt: ${prompt}`)
|
|
70
68
|
const response = await this.agentRobot.brain.think(
|
|
71
69
|
systemDescription,
|
|
72
70
|
prompt,
|
|
@@ -91,7 +89,7 @@ async function executeJSCode(code) {
|
|
|
91
89
|
throw error
|
|
92
90
|
}
|
|
93
91
|
try {
|
|
94
|
-
const functions = this.agentRobot.
|
|
92
|
+
const functions = this.agentRobot.toolManager.functions
|
|
95
93
|
const Func = new Function(
|
|
96
94
|
'Tools',
|
|
97
95
|
'require',
|
|
@@ -237,6 +235,7 @@ const SystemTool = {
|
|
|
237
235
|
'提供系统命令执行、AI请求、JS代码执行、扩展文件生成规则、AI配置管理、Tool加载执行等核心系统功能',
|
|
238
236
|
descriptions,
|
|
239
237
|
functions,
|
|
238
|
+
isSystem: true
|
|
240
239
|
}
|
|
241
240
|
|
|
242
241
|
module.exports = SystemTool
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const fs = require('fs-extra')
|
|
2
|
+
|
|
3
|
+
function readUserInfo() {
|
|
4
|
+
const userInfoFilePath = this.agentRobot.userInfoFilePath
|
|
5
|
+
if (!fs.existsSync(userInfoFilePath)) {
|
|
6
|
+
return '暂无用户信息记录';
|
|
7
|
+
}
|
|
8
|
+
return fs.readFileSync(userInfoFilePath, 'utf-8') || '暂无用户信息记录'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function writeUserInfo(info) {
|
|
12
|
+
const userInfoFilePath = this.agentRobot.userInfoFilePath
|
|
13
|
+
const oldUserInfo = this.Tools.readUserInfo()
|
|
14
|
+
const normalizedOldUserInfo = oldUserInfo === '暂无用户信息记录' ? '' : oldUserInfo
|
|
15
|
+
let mergedInfo = (info || '').trim()
|
|
16
|
+
|
|
17
|
+
const systemDescription = `你是用户信息整理助手。请将已有用户信息与新增用户信息合并为一份Markdown文本,只保留非敏感信息并去重,优先保留更新、更准确的信息。记录内容应简洁、可检索、可复用。`
|
|
18
|
+
const prompt = `请整合以下两部分用户信息,输出最终Markdown内容(仅输出正文,不要解释):\n\n【已有用户信息】\n${normalizedOldUserInfo || '(空)'}\n\n【新增用户信息】\n${mergedInfo || '(空)'}\n\n要求:\n1. 仅保留非敏感信息。\n2. 相同信息去重,冲突时以新增信息为准。\n3. 不确定内容标注“待确认”。\n4. 按“信息类型: 信息内容”格式整理。`
|
|
19
|
+
|
|
20
|
+
if (this.Tools.requestAI && mergedInfo) {
|
|
21
|
+
try {
|
|
22
|
+
const aiMerged = await this.Tools.requestAI(systemDescription, prompt, 0.2)
|
|
23
|
+
if (typeof aiMerged === 'string' && aiMerged.trim()) {
|
|
24
|
+
mergedInfo = aiMerged.trim()
|
|
25
|
+
}
|
|
26
|
+
} catch (error) {
|
|
27
|
+
// AI合并失败时回退为直接写入新增信息,保证写入流程可用
|
|
28
|
+
mergedInfo = normalizedOldUserInfo + '\n' + info
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
fs.writeFileSync(userInfoFilePath, mergedInfo, 'utf-8')
|
|
32
|
+
// 更新系统提示词中的用户信息区块
|
|
33
|
+
this.agentRobot.systemPrompt = this.agentRobot.systemPrompt.replace(
|
|
34
|
+
/----user info start----([\s\S]*?)----user info end----/,
|
|
35
|
+
`----user info start----\n${mergedInfo}\n----user info end----`
|
|
36
|
+
)
|
|
37
|
+
return true
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const descriptions = [
|
|
41
|
+
{
|
|
42
|
+
type: 'function',
|
|
43
|
+
function: {
|
|
44
|
+
name: 'writeUserInfo',
|
|
45
|
+
description: '写入用户信息内容。记录规则:1. 仅记录非敏感信息:个人基础信息(如姓名、年龄、职业、兴趣、性格特征)、操作习惯、代码习惯、阅读习惯、常用目录、文档收藏夹目录等。2. 禁止记录敏感信息:密码、密钥、令牌、身份证号、银行卡号、详细住址、联系方式、精确定位、财务/医疗等个人隐私数据。3. 已有同类信息时优先更新,不重复堆叠;存在不确定信息时需标注“待确认”,不得臆测补全。4. 记录内容应简洁、可检索、可复用,避免写入一次性上下文和与任务无关的噪音信息。5. 无需询问,自动写入文件。6. 使用markdown文件格式记录,按照“信息类型: 信息内容”的格式进行记录,如“兴趣: 阅读、旅行、编程”。7. 只记录当前用户信息中没有的内容,避免重复记录相同信息。',
|
|
46
|
+
parameters: {
|
|
47
|
+
type: 'object',
|
|
48
|
+
properties: {
|
|
49
|
+
info: {
|
|
50
|
+
type: 'string',
|
|
51
|
+
description: '当前用户信息中没有的内容',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
required: ['info'],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
type: 'function',
|
|
60
|
+
function: {
|
|
61
|
+
name: 'readUserInfo',
|
|
62
|
+
description: '读取已记录的用户信息内容。如果文件不存在,则返回 "暂无用户信息记录"。',
|
|
63
|
+
parameters: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
properties: {},
|
|
66
|
+
required: [],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
const functions = {
|
|
74
|
+
readUserInfo,
|
|
75
|
+
writeUserInfo,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const UserTool = {
|
|
79
|
+
name: 'UserTool',
|
|
80
|
+
description: '提供用户信息读写功能,用于维护用户偏好、习惯和常用目录信息',
|
|
81
|
+
platform: 'all',
|
|
82
|
+
descriptions,
|
|
83
|
+
functions,
|
|
84
|
+
isSystem: true
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = UserTool
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
const axios = require('axios')
|
|
2
|
+
const cheerio = require('cheerio')
|
|
3
|
+
const puppeteer = require('puppeteer')
|
|
4
|
+
|
|
5
|
+
function ok(data = null) {
|
|
6
|
+
return { success: true, data }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function fail(error, data = null) {
|
|
10
|
+
return { success: false, error: error?.message || String(error), data }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizeWhitespace(text = '') {
|
|
14
|
+
return String(text || '').replace(/\s+/g, ' ').trim()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function absoluteUrl(baseUrl, href = '') {
|
|
18
|
+
try {
|
|
19
|
+
return new URL(href, baseUrl).toString()
|
|
20
|
+
} catch {
|
|
21
|
+
return href
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function extractByCheerio(html = '', url = '') {
|
|
26
|
+
const $ = cheerio.load(html)
|
|
27
|
+
$('script, style, noscript').remove()
|
|
28
|
+
|
|
29
|
+
const title = normalizeWhitespace($('title').first().text())
|
|
30
|
+
const bodyText = normalizeWhitespace($('body').text())
|
|
31
|
+
const metaDescription = normalizeWhitespace(
|
|
32
|
+
$('meta[name="description"]').attr('content') ||
|
|
33
|
+
$('meta[property="og:description"]').attr('content') ||
|
|
34
|
+
'',
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
const links = []
|
|
38
|
+
$('a[href]').each((_, el) => {
|
|
39
|
+
const href = $(el).attr('href') || ''
|
|
40
|
+
const text = normalizeWhitespace($(el).text())
|
|
41
|
+
if (!href) return
|
|
42
|
+
links.push({
|
|
43
|
+
href: absoluteUrl(url, href),
|
|
44
|
+
text,
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
title,
|
|
50
|
+
description: metaDescription,
|
|
51
|
+
content: bodyText,
|
|
52
|
+
links,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function fetchStatic(url, timeout = 15000) {
|
|
57
|
+
const response = await axios.get(url, {
|
|
58
|
+
timeout,
|
|
59
|
+
headers: {
|
|
60
|
+
'User-Agent':
|
|
61
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130 Safari/537.36 DeepFish-MCP-Web/1.0',
|
|
62
|
+
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
return response.data
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function fetchDynamic(url, waitUntil = 'networkidle2', timeout = 30000) {
|
|
69
|
+
const browser = await puppeteer.launch({
|
|
70
|
+
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
|
71
|
+
})
|
|
72
|
+
try {
|
|
73
|
+
const page = await browser.newPage()
|
|
74
|
+
await page.goto(url, { waitUntil, timeout })
|
|
75
|
+
return await page.content()
|
|
76
|
+
} finally {
|
|
77
|
+
await browser.close()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function mcpBrowseWebpage(url, mode = 'auto', maxChars = 4000) {
|
|
82
|
+
try {
|
|
83
|
+
if (!url) {
|
|
84
|
+
return fail('url is required')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let html = ''
|
|
88
|
+
let resolvedMode = mode
|
|
89
|
+
|
|
90
|
+
if (mode === 'dynamic') {
|
|
91
|
+
html = await fetchDynamic(url)
|
|
92
|
+
} else if (mode === 'static') {
|
|
93
|
+
html = await fetchStatic(url)
|
|
94
|
+
} else {
|
|
95
|
+
try {
|
|
96
|
+
html = await fetchStatic(url)
|
|
97
|
+
resolvedMode = 'static'
|
|
98
|
+
} catch {
|
|
99
|
+
html = await fetchDynamic(url)
|
|
100
|
+
resolvedMode = 'dynamic'
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const parsed = extractByCheerio(html, url)
|
|
105
|
+
const content = parsed.content.slice(0, Number(maxChars) > 0 ? Number(maxChars) : 4000)
|
|
106
|
+
const links = parsed.links.slice(0, 50)
|
|
107
|
+
|
|
108
|
+
return ok({
|
|
109
|
+
url,
|
|
110
|
+
mode: resolvedMode,
|
|
111
|
+
title: parsed.title,
|
|
112
|
+
description: parsed.description,
|
|
113
|
+
content,
|
|
114
|
+
contentLength: parsed.content.length,
|
|
115
|
+
links,
|
|
116
|
+
linkCount: parsed.links.length,
|
|
117
|
+
})
|
|
118
|
+
} catch (error) {
|
|
119
|
+
return fail(error, { url, mode, maxChars })
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function mcpFetchWebpageByQuery(url, query, mode = 'auto', limit = 20) {
|
|
124
|
+
try {
|
|
125
|
+
if (!url) {
|
|
126
|
+
return fail('url is required')
|
|
127
|
+
}
|
|
128
|
+
if (!query) {
|
|
129
|
+
return fail('query is required')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const browseResult = await mcpBrowseWebpage(url, mode, 200000)
|
|
133
|
+
if (!browseResult.success) {
|
|
134
|
+
return browseResult
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const keyword = String(query).toLowerCase().trim()
|
|
138
|
+
const rawContent = browseResult.data.content || ''
|
|
139
|
+
const sentences = rawContent
|
|
140
|
+
.split(/[。!?.!?\n]/)
|
|
141
|
+
.map((item) => normalizeWhitespace(item))
|
|
142
|
+
.filter(Boolean)
|
|
143
|
+
|
|
144
|
+
const matchedSentences = sentences
|
|
145
|
+
.map((text) => {
|
|
146
|
+
const lower = text.toLowerCase()
|
|
147
|
+
let score = 0
|
|
148
|
+
let fromIndex = 0
|
|
149
|
+
while (true) {
|
|
150
|
+
const idx = lower.indexOf(keyword, fromIndex)
|
|
151
|
+
if (idx < 0) break
|
|
152
|
+
score += 1
|
|
153
|
+
fromIndex = idx + keyword.length
|
|
154
|
+
}
|
|
155
|
+
return { text, score }
|
|
156
|
+
})
|
|
157
|
+
.filter((item) => item.score > 0)
|
|
158
|
+
.sort((a, b) => b.score - a.score)
|
|
159
|
+
.slice(0, Number(limit) > 0 ? Number(limit) : 20)
|
|
160
|
+
|
|
161
|
+
const matchedLinks = (browseResult.data.links || [])
|
|
162
|
+
.filter((item) => {
|
|
163
|
+
const href = String(item.href || '').toLowerCase()
|
|
164
|
+
const text = String(item.text || '').toLowerCase()
|
|
165
|
+
return href.includes(keyword) || text.includes(keyword)
|
|
166
|
+
})
|
|
167
|
+
.slice(0, Number(limit) > 0 ? Number(limit) : 20)
|
|
168
|
+
|
|
169
|
+
return ok({
|
|
170
|
+
url,
|
|
171
|
+
query,
|
|
172
|
+
mode: browseResult.data.mode,
|
|
173
|
+
title: browseResult.data.title,
|
|
174
|
+
matchedSentenceCount: matchedSentences.length,
|
|
175
|
+
matchedLinkCount: matchedLinks.length,
|
|
176
|
+
matchedSentences,
|
|
177
|
+
matchedLinks,
|
|
178
|
+
})
|
|
179
|
+
} catch (error) {
|
|
180
|
+
return fail(error, { url, query, mode, limit })
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const descriptions = [
|
|
185
|
+
{
|
|
186
|
+
type: 'function',
|
|
187
|
+
function: {
|
|
188
|
+
name: 'mcpBrowseWebpage',
|
|
189
|
+
description:
|
|
190
|
+
'MCP网页浏览: 打开并抓取任意网页的标题、摘要正文与链接列表。适合快速浏览网页内容。',
|
|
191
|
+
parameters: {
|
|
192
|
+
type: 'object',
|
|
193
|
+
properties: {
|
|
194
|
+
url: {
|
|
195
|
+
type: 'string',
|
|
196
|
+
description: '要浏览的网页地址(http或https)。',
|
|
197
|
+
},
|
|
198
|
+
mode: {
|
|
199
|
+
type: 'string',
|
|
200
|
+
description: '抓取模式:auto|static|dynamic。默认auto。',
|
|
201
|
+
},
|
|
202
|
+
maxChars: {
|
|
203
|
+
type: 'number',
|
|
204
|
+
description: '返回正文最大字符数,默认4000。',
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
required: ['url'],
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
type: 'function',
|
|
213
|
+
function: {
|
|
214
|
+
name: 'mcpFetchWebpageByQuery',
|
|
215
|
+
description:
|
|
216
|
+
'MCP网页抓取: 按关键词从网页正文和链接中提取匹配内容,返回高相关片段。',
|
|
217
|
+
parameters: {
|
|
218
|
+
type: 'object',
|
|
219
|
+
properties: {
|
|
220
|
+
url: {
|
|
221
|
+
type: 'string',
|
|
222
|
+
description: '目标网页地址(http或https)。',
|
|
223
|
+
},
|
|
224
|
+
query: {
|
|
225
|
+
type: 'string',
|
|
226
|
+
description: '要匹配的关键词。',
|
|
227
|
+
},
|
|
228
|
+
mode: {
|
|
229
|
+
type: 'string',
|
|
230
|
+
description: '抓取模式:auto|static|dynamic。默认auto。',
|
|
231
|
+
},
|
|
232
|
+
limit: {
|
|
233
|
+
type: 'number',
|
|
234
|
+
description: '返回匹配结果上限,默认20。',
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
required: ['url', 'query'],
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
]
|
|
242
|
+
|
|
243
|
+
const functions = {
|
|
244
|
+
mcpBrowseWebpage,
|
|
245
|
+
mcpFetchWebpageByQuery,
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const MCPWebTool = {
|
|
249
|
+
name: 'MCPWebTool',
|
|
250
|
+
description: '提供网页浏览与内容抓取能力,支持任意网页的读取与关键词提取。',
|
|
251
|
+
platform: 'all',
|
|
252
|
+
descriptions,
|
|
253
|
+
functions,
|
|
254
|
+
isSystem: true
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
module.exports = MCPWebTool
|
|
@@ -44,10 +44,10 @@ async function think(
|
|
|
44
44
|
streamEnd,
|
|
45
45
|
)
|
|
46
46
|
await thinkAfter()
|
|
47
|
-
return messageRes.choices[0].message.content
|
|
47
|
+
return clearThinkTag(messageRes.choices[0].message.content)
|
|
48
48
|
}
|
|
49
49
|
await thinkAfter()
|
|
50
|
-
return response.choices[0].message.content
|
|
50
|
+
return clearThinkTag(response.choices[0].message.content)
|
|
51
51
|
} catch (error) {
|
|
52
52
|
throw new Error(`AI response error: ${error.message}`)
|
|
53
53
|
}
|
|
@@ -90,6 +90,7 @@ async function thinkByTool(
|
|
|
90
90
|
streamEnd,
|
|
91
91
|
)
|
|
92
92
|
await thinkAfter()
|
|
93
|
+
messageRes.choices[0].message.content = clearThinkTag(messageRes.choices[0].message.content)
|
|
93
94
|
return {
|
|
94
95
|
content: messageRes.choices[0].message.content,
|
|
95
96
|
tool_calls: messageRes.choices[0].message.tool_calls,
|
|
@@ -97,6 +98,7 @@ async function thinkByTool(
|
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
100
|
await thinkAfter()
|
|
101
|
+
response.choices[0].message.content = clearThinkTag(response.choices[0].message.content)
|
|
100
102
|
return {
|
|
101
103
|
content: response.choices[0].message.content,
|
|
102
104
|
tool_calls: response.choices[0].message.tool_calls,
|
|
@@ -107,6 +109,11 @@ async function thinkByTool(
|
|
|
107
109
|
}
|
|
108
110
|
}
|
|
109
111
|
|
|
112
|
+
// 清除字符串中<think></think>标签以及之间的内容
|
|
113
|
+
function clearThinkTag(content) {
|
|
114
|
+
return content.replace(/<think>[\s\S]*?<\/think>/g, '')
|
|
115
|
+
}
|
|
116
|
+
|
|
110
117
|
// 流式输出结果转非流式输出
|
|
111
118
|
async function _streamToNonStream(
|
|
112
119
|
stream,
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const os = require('os')
|
|
3
|
+
const fs = require('fs-extra')
|
|
4
|
+
const dayjs = require('dayjs')
|
|
5
|
+
const axios = require('axios')
|
|
6
|
+
const echarts = require('echarts')
|
|
7
|
+
const canvas = require('canvas')
|
|
8
|
+
const cheerio = require('cheerio')
|
|
9
|
+
const puppeteer = require('puppeteer')
|
|
10
|
+
const lodash = require('lodash')
|
|
11
|
+
const { AttachmentToolScanner } = require('./AttachmentToolScanner')
|
|
12
|
+
|
|
13
|
+
class AIToolManager {
|
|
14
|
+
originalTools = null // 原装工具
|
|
15
|
+
attachTools = null // 附加工具, Agent后续安装的工具函数
|
|
16
|
+
functions = {} // key为函数名称,value为方法体
|
|
17
|
+
descriptions = [] // openai能识别的描述
|
|
18
|
+
tools = [] // 工具列表
|
|
19
|
+
toolCollection = null // 工具集合
|
|
20
|
+
clawSkillCollection = null // Claw技能集合
|
|
21
|
+
constructor(agentRobot) {
|
|
22
|
+
this.agentRobot = agentRobot
|
|
23
|
+
this.initTools(agentRobot.opt)
|
|
24
|
+
}
|
|
25
|
+
initTools(opt) {
|
|
26
|
+
this.originalTools = this._getOriginalTools() // 天赋技能
|
|
27
|
+
this.attachTools = opt.attachTools || [] // 附加工具, Agent后续安装的工具函数
|
|
28
|
+
const tools = [...this.originalTools, ...this.attachTools]
|
|
29
|
+
tools.forEach((tool) => {
|
|
30
|
+
this._addTool(tool)
|
|
31
|
+
})
|
|
32
|
+
Object.assign(this.functions, {
|
|
33
|
+
fs,
|
|
34
|
+
axios,
|
|
35
|
+
dayjs,
|
|
36
|
+
lodash,
|
|
37
|
+
canvas,
|
|
38
|
+
echarts,
|
|
39
|
+
cheerio,
|
|
40
|
+
puppeteer,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
this.functions.agentRobot = this.agentRobot
|
|
44
|
+
this.functions.Tools = this.functions
|
|
45
|
+
// 兼容老版本
|
|
46
|
+
this.functions.aiCli = {
|
|
47
|
+
Tools: this.functions,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 外部工具扫描
|
|
51
|
+
this.toolCollection = AttachmentToolScanner.getToolCollection(
|
|
52
|
+
this.agentRobot.workspace,
|
|
53
|
+
) // 加载工具集合
|
|
54
|
+
this.clawSkillCollection = AttachmentToolScanner.getClawSkillCollection(
|
|
55
|
+
this.agentRobot.basespace,
|
|
56
|
+
) // 加载Claw技能集合
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 动态添加工具
|
|
60
|
+
addTool(toolName) {
|
|
61
|
+
let tool = this.tools.find((t) => t.name === toolName)
|
|
62
|
+
if (!tool) {
|
|
63
|
+
tool = this.toolCollection.find((t) => t.name === toolName)
|
|
64
|
+
if (tool) {
|
|
65
|
+
this._addTool(tool)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
_addTool(tool) {
|
|
71
|
+
const platform = tool.platform || 'all'
|
|
72
|
+
if (platform === 'all' || platform === process.platform) {
|
|
73
|
+
tool.descriptions = tool.descriptions.map((item) => {
|
|
74
|
+
if (!item.type) {
|
|
75
|
+
item = {
|
|
76
|
+
type: 'function',
|
|
77
|
+
function: item,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return item
|
|
81
|
+
})
|
|
82
|
+
if (tool.name && !tool.isSystem) {
|
|
83
|
+
for (const funcName in tool.functions) {
|
|
84
|
+
if (!funcName.includes('_')) {
|
|
85
|
+
this.functions[`${tool.name}_${funcName}`] =
|
|
86
|
+
tool.functions[funcName]
|
|
87
|
+
} else {
|
|
88
|
+
this.functions[funcName] = tool.functions[funcName]
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const descriptions = tool.descriptions.map((item) => {
|
|
92
|
+
if (
|
|
93
|
+
tool.name &&
|
|
94
|
+
item.function.name &&
|
|
95
|
+
!item.function.name.includes('_')
|
|
96
|
+
) {
|
|
97
|
+
item.function.name = `${tool.name}_${item.function.name}`
|
|
98
|
+
return item
|
|
99
|
+
} else {
|
|
100
|
+
return item
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
this.descriptions.push(...descriptions)
|
|
104
|
+
} else {
|
|
105
|
+
Object.assign(this.functions, tool.functions)
|
|
106
|
+
this.descriptions.push(...tool.descriptions)
|
|
107
|
+
}
|
|
108
|
+
this.tools.push(tool)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 获取原装工具
|
|
113
|
+
_getOriginalTools() {
|
|
114
|
+
// 自动扫描tools目录
|
|
115
|
+
const toolsPath = path.join(__dirname, '../tools')
|
|
116
|
+
const toolFiles = fs.readdirSync(toolsPath).filter((file) => {
|
|
117
|
+
return file.endsWith('.js') || file.endsWith('.cjs')
|
|
118
|
+
})
|
|
119
|
+
const tools = []
|
|
120
|
+
toolFiles.forEach((file) => {
|
|
121
|
+
const tool = require(path.join(toolsPath, file))
|
|
122
|
+
tools.push(tool)
|
|
123
|
+
})
|
|
124
|
+
return tools
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = AIToolManager
|