deepfish-ai 1.0.8
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/LICENSE +21 -0
- package/README.md +344 -0
- package/README_CN.md +344 -0
- package/package.json +56 -0
- package/src/cli.js +650 -0
- package/src/core/AICLI.js +93 -0
- package/src/core/DefaultConfig.js +14 -0
- package/src/core/GlobalVariable.js +10 -0
- package/src/core/ai-services/AIService.js +25 -0
- package/src/core/ai-services/AiWorker/AIMessageManager.js +155 -0
- package/src/core/ai-services/AiWorker/AiAgent.js +151 -0
- package/src/core/ai-services/AiWorker/AiPrompt.js +37 -0
- package/src/core/ai-services/AiWorker/AiRecorder.js +120 -0
- package/src/core/ai-services/AiWorker/AiTools.js +219 -0
- package/src/core/ai-services/AiWorker/index.js +88 -0
- package/src/core/extension/BaseExtension.js +7 -0
- package/src/core/extension/DefaultExtension.js +696 -0
- package/src/core/extension/ExtensionManager.js +172 -0
- package/src/core/utils.js +261 -0
- package/src/index.js +7 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Author: Roman 306863030@qq.com
|
|
3
|
+
* @Date: 2026-03-17 09:12:22
|
|
4
|
+
* @LastEditors: Roman 306863030@qq.com
|
|
5
|
+
* @LastEditTime: 2026-03-17 10:03:21
|
|
6
|
+
* @FilePath: \src\core\ai-services\AiWorker\AiTools.js
|
|
7
|
+
* @Description: 对话初始化、对话请求
|
|
8
|
+
* @
|
|
9
|
+
*/
|
|
10
|
+
const { OpenAI } = require('openai')
|
|
11
|
+
const { AiAgentSystemPrompt } = require('./AiPrompt')
|
|
12
|
+
const { streamOutput, streamLineBreak, objStrToObj } = require('../../utils')
|
|
13
|
+
|
|
14
|
+
// 创建client
|
|
15
|
+
function createOpenAiClient(aiConfig) {
|
|
16
|
+
return new OpenAI({
|
|
17
|
+
baseURL: aiConfig.baseUrl,
|
|
18
|
+
apiKey: aiConfig.apiKey || '',
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 获取初始的message
|
|
23
|
+
function getInitialMessages(goal) {
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
role: 'system',
|
|
27
|
+
content: AiAgentSystemPrompt,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
role: 'user',
|
|
31
|
+
content: goal,
|
|
32
|
+
},
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Ai单轮问答
|
|
38
|
+
* @param {*} openAiClient OpenAI客户端
|
|
39
|
+
* @param {*} aiConfig {model, temperature, maxTokens, stream}
|
|
40
|
+
* @param {*} systemDescription
|
|
41
|
+
* @param {*} prompt
|
|
42
|
+
* @param {*} temperature
|
|
43
|
+
* @returns
|
|
44
|
+
*/
|
|
45
|
+
async function aiRequestSingle(
|
|
46
|
+
openAiClient,
|
|
47
|
+
aiConfig,
|
|
48
|
+
systemDescription,
|
|
49
|
+
prompt,
|
|
50
|
+
) {
|
|
51
|
+
const messages = []
|
|
52
|
+
messages.push({
|
|
53
|
+
role: 'system',
|
|
54
|
+
content: systemDescription,
|
|
55
|
+
})
|
|
56
|
+
messages.push({
|
|
57
|
+
role: 'user',
|
|
58
|
+
content: prompt,
|
|
59
|
+
})
|
|
60
|
+
const response = await openAiClient.chat.completions.create({
|
|
61
|
+
messages: messages,
|
|
62
|
+
...aiConfig,
|
|
63
|
+
stream: false
|
|
64
|
+
})
|
|
65
|
+
return response.choices[0].message.content
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Ai携带工具请求
|
|
69
|
+
* @param {*} openAiClient
|
|
70
|
+
* @param {*} aiConfig {model, temperature, maxTokens}
|
|
71
|
+
* @param {*} messages
|
|
72
|
+
* @param {*} functionDescriptions
|
|
73
|
+
* @returns
|
|
74
|
+
*/
|
|
75
|
+
async function aiRequestByTools(
|
|
76
|
+
openAiClient,
|
|
77
|
+
aiConfig,
|
|
78
|
+
messages,
|
|
79
|
+
functionDescriptions,
|
|
80
|
+
) {
|
|
81
|
+
const response = await openAiClient.chat.completions.create({
|
|
82
|
+
messages: messages,
|
|
83
|
+
tools: functionDescriptions,
|
|
84
|
+
tool_choice: 'auto',
|
|
85
|
+
...aiConfig,
|
|
86
|
+
})
|
|
87
|
+
if (aiConfig.stream) {
|
|
88
|
+
const messageRes = await _streamToNonStream(response)
|
|
89
|
+
return {
|
|
90
|
+
content: messageRes.choices[0].message.content,
|
|
91
|
+
tool_calls: messageRes.choices[0].message.tool_calls,
|
|
92
|
+
message: messageRes.choices[0].message,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
content: response.choices[0].message.content,
|
|
97
|
+
tool_calls: response.choices[0].message.tool_calls,
|
|
98
|
+
message: response.choices[0].message,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 流式输出结果转非流式输出
|
|
103
|
+
async function _streamToNonStream(stream) {
|
|
104
|
+
// 初始化最终响应结构(对齐 OpenAI 非流式响应格式)
|
|
105
|
+
const finalResponse = {
|
|
106
|
+
id: '',
|
|
107
|
+
object: 'chat.completion',
|
|
108
|
+
created: Math.floor(Date.now() / 1000), // 生成时间戳
|
|
109
|
+
model: '',
|
|
110
|
+
choices: [
|
|
111
|
+
{
|
|
112
|
+
index: 0,
|
|
113
|
+
message: {
|
|
114
|
+
role: 'assistant',
|
|
115
|
+
content: '',
|
|
116
|
+
reasoning_content: '',
|
|
117
|
+
tool_calls: [], // 存储完整的工具调用列表
|
|
118
|
+
},
|
|
119
|
+
finish_reason: null,
|
|
120
|
+
logprobs: null,
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
usage: {
|
|
124
|
+
prompt_tokens: 0,
|
|
125
|
+
completion_tokens: 0,
|
|
126
|
+
total_tokens: 0,
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 工具调用缓冲区:处理多工具调用 + 分段参数拼接
|
|
131
|
+
const toolCallBuffers = new Map() // key: tool_call_id, value: toolCall object
|
|
132
|
+
const toolCallIndexMap = new Map() // key: index, value: tool_call_id
|
|
133
|
+
try {
|
|
134
|
+
// 遍历所有流式数据块
|
|
135
|
+
for await (const chunk of stream) {
|
|
136
|
+
// 1. 填充全局信息(仅首次获取)
|
|
137
|
+
if (!finalResponse.id) {
|
|
138
|
+
finalResponse.id = chunk.id || `chatcmpl-${Date.now()}`
|
|
139
|
+
}
|
|
140
|
+
if (!finalResponse.model) {
|
|
141
|
+
finalResponse.model = chunk.model || 'deepseek-reasoner'
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const choice = chunk.choices[0]
|
|
145
|
+
const delta = choice.delta
|
|
146
|
+
if (!delta) {
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
149
|
+
// 2. 处理普通文本内容
|
|
150
|
+
const reasoning_content = delta.reasoning_content
|
|
151
|
+
if (reasoning_content) {
|
|
152
|
+
finalResponse.choices[0].message.reasoning_content += reasoning_content
|
|
153
|
+
// 流式输出
|
|
154
|
+
streamOutput(reasoning_content)
|
|
155
|
+
}
|
|
156
|
+
const content = delta.content
|
|
157
|
+
if (content) {
|
|
158
|
+
finalResponse.choices[0].message.content += content
|
|
159
|
+
// 流式输出
|
|
160
|
+
streamOutput(content)
|
|
161
|
+
}
|
|
162
|
+
// 3. 处理工具调用(核心逻辑)
|
|
163
|
+
if (delta.tool_calls && delta.tool_calls.length > 0) {
|
|
164
|
+
delta.tool_calls.forEach((toolCallChunk) => {
|
|
165
|
+
const index = toolCallChunk.index
|
|
166
|
+
if (toolCallChunk.id) {
|
|
167
|
+
const id = toolCallChunk.id
|
|
168
|
+
toolCallIndexMap.set(index, id)
|
|
169
|
+
let toolCall = toolCallBuffers.get(id)
|
|
170
|
+
if (!toolCall) {
|
|
171
|
+
toolCall = {
|
|
172
|
+
id: id,
|
|
173
|
+
type: toolCallChunk.type || 'function',
|
|
174
|
+
function: {
|
|
175
|
+
name: toolCallChunk.function.name,
|
|
176
|
+
arguments: '',
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
toolCallBuffers.set(id, toolCall)
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
const id = toolCallIndexMap.get(index)
|
|
183
|
+
const toolCall = toolCallBuffers.get(id)
|
|
184
|
+
if (toolCall && toolCallChunk.function?.arguments) {
|
|
185
|
+
toolCall.function.arguments += toolCallChunk.function.arguments
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 4. 处理结束标记
|
|
192
|
+
if (choice.finish_reason) {
|
|
193
|
+
finalResponse.choices[0].finish_reason = choice.finish_reason
|
|
194
|
+
|
|
195
|
+
// 工具调用结束:将缓冲区数据写入最终响应
|
|
196
|
+
if (choice.finish_reason === 'tool_calls' && toolCallBuffers.size > 0) {
|
|
197
|
+
finalResponse.choices[0].message.content = "" // 工具调用时 content 为 null
|
|
198
|
+
finalResponse.choices[0].message.tool_calls = Array.from(
|
|
199
|
+
toolCallBuffers.values(),
|
|
200
|
+
)
|
|
201
|
+
} else {
|
|
202
|
+
finalResponse.choices[0].message.tool_calls = undefined
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
streamLineBreak()
|
|
207
|
+
return finalResponse
|
|
208
|
+
} catch (error) {
|
|
209
|
+
console.error('流式数据转换失败:', error.message)
|
|
210
|
+
throw error // 抛出错误让上层处理
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = {
|
|
215
|
+
createOpenAiClient,
|
|
216
|
+
aiRequestSingle,
|
|
217
|
+
aiRequestByTools,
|
|
218
|
+
getInitialMessages,
|
|
219
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Author: Roman 306863030@qq.com
|
|
3
|
+
* @Date: 2026-03-16 09:18:05
|
|
4
|
+
* @LastEditors: Roman 306863030@qq.com
|
|
5
|
+
* @LastEditTime: 2026-03-17 10:22:46
|
|
6
|
+
* @FilePath: \cmd\src\core\ai-services\AiWorker\index.js
|
|
7
|
+
* @Description: 工作流类
|
|
8
|
+
* @
|
|
9
|
+
*/
|
|
10
|
+
const { logInfo } = require('../../utils')
|
|
11
|
+
const AiAgent = require('./AiAgent')
|
|
12
|
+
const { getInitialMessages } = require('./AiTools')
|
|
13
|
+
|
|
14
|
+
class AiWorker {
|
|
15
|
+
constructor(aiCli, client) {
|
|
16
|
+
this.aiCli = aiCli
|
|
17
|
+
this.client = client
|
|
18
|
+
this.aiRecorder = this.aiCli.aiRecorder
|
|
19
|
+
this.messages = []
|
|
20
|
+
this.aiAgent = new AiAgent(
|
|
21
|
+
this.client,
|
|
22
|
+
this.aiCli.config,
|
|
23
|
+
this.aiCli.aiConfig,
|
|
24
|
+
this.aiCli.extensionManager.extensions,
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async main(goal) {
|
|
29
|
+
// 判断是否回复会话
|
|
30
|
+
const isRecover = await this.aiRecorder.recover()
|
|
31
|
+
if (isRecover) {
|
|
32
|
+
const { messages } = isRecover
|
|
33
|
+
// 判断是否已经完成
|
|
34
|
+
const lastMessage = messages[messages.length - 1]
|
|
35
|
+
if (lastMessage.role === 'assistant' && !lastMessage.tool_calls) {
|
|
36
|
+
// 说明已经执行完毕,直接返回
|
|
37
|
+
this.messages = messages
|
|
38
|
+
logInfo(lastMessage.content)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
this.messages = messages
|
|
42
|
+
this.aiAgent.aiMessageManager.reLinkMsgs(this.messages)
|
|
43
|
+
this._recoverHistory(goal, this.messages)
|
|
44
|
+
} else {
|
|
45
|
+
if (!this.messages.length) {
|
|
46
|
+
this.messages = getInitialMessages(goal)
|
|
47
|
+
this.aiAgent.work(this.messages)
|
|
48
|
+
} else {
|
|
49
|
+
this.aiAgent.aiMessageManager.reLinkMsgs(this.messages)
|
|
50
|
+
this.aiAgent.aiMessageManager.addMsg({
|
|
51
|
+
role: 'user',
|
|
52
|
+
content: goal,
|
|
53
|
+
})
|
|
54
|
+
this.aiAgent.work(this.messages)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// this.aiRecorder.clear()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async _recoverHistory(goal, messages) {
|
|
61
|
+
logInfo('Recovering from previous conversation...')
|
|
62
|
+
let lastMessage = messages[messages.length - 1]
|
|
63
|
+
if (lastMessage.role === 'tool') {
|
|
64
|
+
// 删除最后一项
|
|
65
|
+
messages.pop()
|
|
66
|
+
}
|
|
67
|
+
lastMessage = messages[messages.length - 1]
|
|
68
|
+
if (lastMessage.role === 'assistant' && lastMessage.tool_calls) {
|
|
69
|
+
// 最后一项正在执行工具,则重新执行
|
|
70
|
+
await this.aiAgent.execTools(lastMessage.tool_calls)
|
|
71
|
+
this.aiAgent.work(this.messages)
|
|
72
|
+
} else if (lastMessage.role === 'assistant' && lastMessage.content) {
|
|
73
|
+
return lastMessage.content || ''
|
|
74
|
+
} else if (lastMessage.role === 'user') {
|
|
75
|
+
// 最后一项是用户输入,说明是新的一轮对话
|
|
76
|
+
this.aiAgent.work(this.messages)
|
|
77
|
+
} else if (lastMessage.role === 'system') {
|
|
78
|
+
this.aiAgent.aiMessageManager.addMsg({
|
|
79
|
+
role: 'user',
|
|
80
|
+
content: goal,
|
|
81
|
+
})
|
|
82
|
+
this.aiAgent.work(this.messages)
|
|
83
|
+
}
|
|
84
|
+
return ''
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = AiWorker
|