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.
@@ -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
@@ -0,0 +1,7 @@
1
+ const descriptions = [] // openai能识别的描述
2
+ const functions = {} // key为函数名称,value为方法体
3
+
4
+ module.exports = {
5
+ descriptions,
6
+ functions,
7
+ }