|
| 1 | + |
| 2 | +/** |
| 3 | + * 调用 DeepSeek API |
| 4 | + * @param userContent 用户输入内容 |
| 5 | + * @param systemContent 系统提示内容 |
| 6 | + * @param outputChannel 输出通道,用于实时显示流式内容 |
| 7 | + * @param streamMode 是否启用流式模式 |
| 8 | + * @param endstring 结束字符串,用于检查输出是否包含特定字符串 |
| 9 | + * @param abortSignal 用于中断请求的信号 |
| 10 | + * @returns API 返回的完整内容 |
| 11 | + */ |
| 12 | +async function callDeepSeekApi( |
| 13 | + userContent: string, |
| 14 | + systemContent: string = 'You are a helpful assistant.', |
| 15 | + outputChannel?: vscode.OutputChannel, |
| 16 | + streamMode: boolean = true, |
| 17 | + endstring?: string, |
| 18 | + abortSignal?: AbortSignal |
| 19 | +): Promise<string | null> { |
| 20 | + const { modelName, apiBaseURL, apiKey } = getDeepSeekModelConfig(); |
| 21 | + const userStopException = 'operation stop by user'; |
| 22 | + |
| 23 | + if (!apiKey) { |
| 24 | + vscode.window.showErrorMessage('DeepSeek API Key is not configured. Please set it in the settings.'); |
| 25 | + return null; |
| 26 | + } |
| 27 | + |
| 28 | + if (!modelName || !apiBaseURL) { |
| 29 | + vscode.window.showErrorMessage('DeepSeek Model Name or API Base URL is not configured.'); |
| 30 | + return null; |
| 31 | + } |
| 32 | + |
| 33 | + try { |
| 34 | + const openai = new OpenAI({ |
| 35 | + apiKey: apiKey, |
| 36 | + baseURL: apiBaseURL, |
| 37 | + }); |
| 38 | + |
| 39 | + if (outputChannel) { |
| 40 | + outputChannel.clear(); |
| 41 | + outputChannel.show(); |
| 42 | + } |
| 43 | + |
| 44 | + const messages_body: OpenAI.ChatCompletionMessageParam[] = [ |
| 45 | + { role: 'system', content: systemContent }, |
| 46 | + { role: 'user', content: userContent }, |
| 47 | + ]; |
| 48 | + let fullResponse = ''; |
| 49 | + let maxAttempts = 5; |
| 50 | + let attempts = 0; |
| 51 | + |
| 52 | + vscode.window.showInformationMessage('开始上传DeepSeek API'); |
| 53 | + |
| 54 | + while (attempts < maxAttempts) { |
| 55 | + attempts++; |
| 56 | + const response = await openai.chat.completions.create({ |
| 57 | + model: modelName, |
| 58 | + messages: messages_body, |
| 59 | + stream: streamMode, |
| 60 | + max_tokens: 8192, |
| 61 | + temperature: 0 |
| 62 | + }); |
| 63 | + |
| 64 | + vscode.window.showInformationMessage('DeepSeek API 正在处理...'); |
| 65 | + |
| 66 | + let chunkResponse = ''; |
| 67 | + let finishReason: string | null = null; |
| 68 | + |
| 69 | + if (streamMode) { |
| 70 | + for await (const chunk of response as AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>) { |
| 71 | + if (abortSignal?.aborted) { |
| 72 | + throw new Error(userStopException); |
| 73 | + } |
| 74 | + const content = chunk.choices[0]?.delta?.content || ''; |
| 75 | + chunkResponse += content; |
| 76 | + if (outputChannel) { |
| 77 | + outputChannel.append(content); |
| 78 | + } |
| 79 | + finishReason = chunk.choices[0]?.finish_reason || null; |
| 80 | + } |
| 81 | + } else { |
| 82 | + const completion = response as OpenAI.Chat.Completions.ChatCompletion; |
| 83 | + chunkResponse = completion.choices[0].message.content || ""; |
| 84 | + finishReason = completion.choices[0].finish_reason || null; |
| 85 | + if (outputChannel) { |
| 86 | + outputChannel.append(chunkResponse); |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + // 累积完整响应 |
| 91 | + fullResponse += chunkResponse; |
| 92 | + |
| 93 | + // 检查终止条件 |
| 94 | + const shouldContinue = |
| 95 | + finishReason === 'length' || |
| 96 | + (endstring && !fullResponse.includes(endstring)); |
| 97 | + |
| 98 | + if (!shouldContinue) {break;}; |
| 99 | + |
| 100 | + if (abortSignal?.aborted) { |
| 101 | + throw new Error(userStopException); |
| 102 | + } |
| 103 | + |
| 104 | + vscode.window.showWarningMessage('超过最大Token数,正在重试...'); |
| 105 | + |
| 106 | + // 准备下一次请求 |
| 107 | + messages_body.push( |
| 108 | + { role: 'assistant', content: fullResponse }, |
| 109 | + { role: 'user', content: '你的输出被截断了,请继续输出剩余部分, 不需要```做起始,直接继续输出纯内容:' } |
| 110 | + ); |
| 111 | + } |
| 112 | + |
| 113 | + // 最终检查 |
| 114 | + if (endstring && !fullResponse.includes(endstring)) { |
| 115 | + vscode.window.showWarningMessage('响应未包含结束标记'); |
| 116 | + } |
| 117 | + |
| 118 | + messages_body.push({ role: 'assistant', content: fullResponse }); |
| 119 | + lastMessageBody = messages_body; |
| 120 | + return fullResponse; |
| 121 | + |
| 122 | + } catch (error) { |
| 123 | + if (error instanceof Error && error.message === userStopException) { |
| 124 | + vscode.window.showInformationMessage('operation stop by user'); |
| 125 | + return null; |
| 126 | + } |
| 127 | + vscode.window.showErrorMessage('API调用失败: ' + (error as Error).message); |
| 128 | + return null; |
| 129 | + } |
| 130 | +} |
| 131 | + |
| 132 | +export async function callDeepSeekFixApi( |
| 133 | + errorInfo: string, |
| 134 | + outputChannel?: vscode.OutputChannel, |
| 135 | + streamMode: boolean = true, |
| 136 | + abortSignal?: AbortSignal |
| 137 | +): Promise<string | null> { |
| 138 | + const { modelName, apiBaseURL, apiKey } = getDeepSeekModelConfig(); |
| 139 | + const userStopException = 'operation stop by user'; |
| 140 | + |
| 141 | + if (!apiKey) { |
| 142 | + vscode.window.showErrorMessage('DeepSeek API Key is not configured. Please set it in the settings.'); |
| 143 | + return null; |
| 144 | + } |
| 145 | + |
| 146 | + if (!modelName || !apiBaseURL) { |
| 147 | + vscode.window.showErrorMessage('DeepSeek Model Name or API Base URL is not configured.'); |
| 148 | + return null; |
| 149 | + } |
| 150 | + |
| 151 | + const openai = new OpenAI({ |
| 152 | + apiKey: apiKey, |
| 153 | + baseURL: apiBaseURL, |
| 154 | + }); |
| 155 | + |
| 156 | + if (outputChannel) { |
| 157 | + outputChannel.clear(); |
| 158 | + outputChannel.show(); |
| 159 | + } |
| 160 | + |
| 161 | + let messages_body = lastMessageBody; |
| 162 | + |
| 163 | + messages_body.push( |
| 164 | + { role: 'user', content:`你以上提供的数据格式存在错误: ${errorInfo}。 |
| 165 | +请你仔细检查数据,分析并找出所有错误原因,并核实错误类型。请按照下面的格式输出,要求如下: |
| 166 | + |
| 167 | +【第一步:错误原因分析】 |
| 168 | +请逐项列出所有错误原因,每项必须包括: |
| 169 | + 1. 错误类型及原因描述(详细说明为何出错) |
| 170 | + 2. 对应的文件路径(精确到文件) |
| 171 | + 3. 错误的写法(直接引用错误代码,指明具体位置) |
| 172 | + 4. 正确的写法(建议的修正代码,必须准确对应错误部分) |
| 173 | + |
| 174 | +【第二步:最小改动修正】 |
| 175 | +在保证原有正确部分完整保留的前提下,仅对错误部分做最小改动。要求: |
| 176 | + - 详细说明每处改动的理由 |
| 177 | + - 列出每个文件修改的具体位置和修改内容,确保不遗漏任何正确部分 |
| 178 | + |
| 179 | +【第三步:完整输出】 |
| 180 | +请输出最终修正后的完整数据,按照上一次要求的格式,严格输出。并注意: |
| 181 | + - 包含修正后的代码 |
| 182 | + - 不要遗漏原有正确部分(完整输出,绝对不省略任何内容) |
| 183 | + |
| 184 | +【第四步:总结说明】 |
| 185 | +在输出完完整数据后,请总结以上步骤,归纳错误原因和修改方案,并确认所有文件路径及代码位置均正确无误。 |
| 186 | + |
| 187 | +请严格按照以上步骤输出,确保先详细列出错误原因,再输出完整修正后的数据,不要只输出错误部分。`} |
| 188 | + ); |
| 189 | + |
| 190 | + let fullResponse = ''; |
| 191 | + let chunkResponse = ''; |
| 192 | + let finishReason: string | null = null; |
| 193 | + |
| 194 | + vscode.window.showInformationMessage('开始上传DeepSeek API, 进行修复'); |
| 195 | + |
| 196 | + const response = await openai.chat.completions.create({ |
| 197 | + model: modelName, |
| 198 | + messages: messages_body, |
| 199 | + stream: streamMode, |
| 200 | + max_tokens: 8192, |
| 201 | + temperature: 0 |
| 202 | + }); |
| 203 | + |
| 204 | + if (streamMode) { |
| 205 | + for await (const chunk of response as AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>) { |
| 206 | + if (abortSignal?.aborted) { |
| 207 | + throw new Error(userStopException); |
| 208 | + } |
| 209 | + const content = chunk.choices[0]?.delta?.content || ''; |
| 210 | + chunkResponse += content; |
| 211 | + if (outputChannel) { |
| 212 | + outputChannel.append(content); |
| 213 | + } |
| 214 | + finishReason = chunk.choices[0]?.finish_reason || null; |
| 215 | + } |
| 216 | + } else { |
| 217 | + const completion = response as OpenAI.Chat.Completions.ChatCompletion; |
| 218 | + chunkResponse = completion.choices[0].message.content || ""; |
| 219 | + finishReason = completion.choices[0].finish_reason || null; |
| 220 | + if (outputChannel) { |
| 221 | + outputChannel.append(chunkResponse); |
| 222 | + } |
| 223 | + } |
| 224 | + |
| 225 | + fullResponse = chunkResponse; |
| 226 | + |
| 227 | + messages_body.push({ role: 'assistant', content: fullResponse }); |
| 228 | + lastMessageBody = messages_body; |
| 229 | + |
| 230 | + return fullResponse; |
| 231 | +} |
| 232 | + |
| 233 | +/** |
| 234 | + * 应用代码重构功能 |
| 235 | + * @param cvbContent CVB 文件内容 |
| 236 | + * @param userRequest 用户输入的重构需求 |
| 237 | + * @param outputChannel 输出通道,用于实时显示流式内容 |
| 238 | + * @param abortSignal 用于中断请求的信号 |
| 239 | + * @returns API 返回的完整 CVB 内容 |
| 240 | + */ |
| 241 | +export async function queryCodeReDesign( |
| 242 | + cvbContent: string, |
| 243 | + userRequest: string, |
| 244 | + outputChannel: vscode.OutputChannel, |
| 245 | + abortSignal?: AbortSignal |
| 246 | +): Promise<string | null> { |
| 247 | + const requestContent = ` |
| 248 | +【格式说明】 |
| 249 | +- CVB 格式说明:${Cvb.getFormatDescription()} |
| 250 | +- TCVB 格式说明:${TCVB.getFormatDescription()} |
| 251 | + |
| 252 | +【任务说明】 |
| 253 | +请读取以下 CVB 格式代码,并根据需求修改代码。注意: |
| 254 | +1. 如果需求涉及“移动代码”,请务必修改原始代码,将代码重新封装到新位置,而非简单复制; |
| 255 | +2. 修改后的代码必须完整、可执行,不能有任何省略; |
| 256 | +3. 输出内容必须严格遵守 TCVB 格式(仅正文部分含 TCVB 标记,其他地方如有 TCVB 开始或结束符需转义),以确保后续合并正确; |
| 257 | +4. 注意不要将某文件的修改内容误认为是其他文件,请一条一条列出具体修改项及对应文件路径。 |
| 258 | + |
| 259 | +【输出要求】 |
| 260 | +1. 先输出你对需求及相关代码的理解,请按层级缩进列出笔记,便于整理思路; |
| 261 | +2. 再输出详细的方案大纲,格式如下: |
| 262 | + 需求理解: |
| 263 | + … |
| 264 | + 查询资料: |
| 265 | + 列出每个关键修改点所在的文件路径 |
| 266 | + 修改方案: |
| 267 | + 文件路径1: |
| 268 | + 描述修改点,避免用大块代码,注意只输出关键修改,不要太长, 不要加载无用的上下文。不要输出没有改动部分的代码 |
| 269 | + 文件路径2: |
| 270 | + 描述修改点,同上 |
| 271 | + … |
| 272 | + 最后检查: |
| 273 | + 对以上输出的方案大纲进行反思,重新阅读输入代码,结合以上方案大纲,逐条检查有没有和原文对不上的地方。检查方案是否完备、文件路径是否正确,设计思路是否无误,如有问题请提出修正意见 |
| 274 | +3. 请确保输出中既包含错误部分的修正说明,又完整保留原有正确部分,不得遗漏任何内容; |
| 275 | +4. 用最小改动实现需求目的。 |
| 276 | + |
| 277 | +【输入部分】 |
| 278 | +- 输入代码:${cvbContent} |
| 279 | +- 需求描述:${userRequest} |
| 280 | + |
| 281 | +【最终输出】 |
| 282 | +请先输出思路与方案大纲,最后汇总输出符合 TCVB 格式的精确代码。 |
| 283 | +`; |
| 284 | + |
| 285 | + return callDeepSeekApi(requestContent, undefined, outputChannel, true, '## END_TCVB', abortSignal); // 添加结束字符串 |
| 286 | +} |
| 287 | + |
| 288 | + |
| 289 | +class Cvb{ |
| 290 | + public static getFormatDescription() : string |
| 291 | + { |
| 292 | + return ` |
| 293 | +CVB 格式介绍: |
| 294 | +- 文件以 "## BEGIN_CVB" 开头,以 "## END_CVB" 结尾。 |
| 295 | +- 元数据部分以 "## META" 开头,以 "## END_META" 结尾,包含用户需求和时间戳。 |
| 296 | +- 每个文件以 "## FILE:文件路径" 开头,紧接着是 Markdown 格式的代码块,包含文件内容。 |
| 297 | +- 多个文件按顺序拼接在一起。 |
| 298 | +`; |
| 299 | + } |
| 300 | +} |
| 301 | + |
| 302 | +class TCVB{ |
| 303 | + |
| 304 | + public static getFormatDescription() : string |
| 305 | + { |
| 306 | + return ` |
| 307 | +TCVB 格式规范: |
| 308 | + |
| 309 | +## BEGIN_TCVB |
| 310 | +[文件块1] |
| 311 | +[文件块2] |
| 312 | +... |
| 313 | +## END_TCVB |
| 314 | + |
| 315 | +文件块格式: |
| 316 | +## FILE:<文件绝对路径> |
| 317 | +[操作1] |
| 318 | +[操作2] |
| 319 | +... |
| 320 | + |
| 321 | +操作类型: |
| 322 | + |
| 323 | +1. 全局替换操作(GLOBAL-REPLACE): |
| 324 | +## OPERATION:GLOBAL-REPLACE |
| 325 | +## OLD_CONTENT |
| 326 | +[markdown代码块:被全局替换的内容, 可以在需要被替换的文本前后包含一些上下文帮助精确替换,一般是上下各3行。不要太长,不要带太多不必要的上下文,因为输出越长就越可能出错导致匹配不上。总长度不要超过10行,尽量不要大块的替换代码,而是切成很多小块替换。] |
| 327 | +## NEW_CONTENT |
| 328 | +[markdown代码块:新内容] |
| 329 | + |
| 330 | +2. 创建操作(CREATE): |
| 331 | +## OPERATION:CREATE |
| 332 | +[markdown代码块:直接跟正文内容,表示新文件的全部内容] |
| 333 | + |
| 334 | +注意: |
| 335 | +1. 所有OPERATION操作以行为单位 |
| 336 | +2. 一个'## FILE'下可以有多个'## OPERATION' |
| 337 | +3. 锚点为连续的多行内容:使用至少3行唯一文本作为锚点,用来标定范围,防止混淆(如果需要可以超过3行) |
| 338 | +4. [markdown代码块], 一定要用\`\`\` ... \`\`\` 包裹,仔细检查不要漏掉。 |
| 339 | +5. 注意TCVB和CVB的区别。CVB是完整的内容,而TCVB是用来生成差量同步的,通过多个OPERATION去操作已有CVB合成新CVB |
| 340 | +6. 插入和删除操作都可以转化为替换操作 |
| 341 | +7. 用来匹配的锚点必须和原文的格式完全一致,不能有缺失,不能丢弃注释。 |
| 342 | +8. 注意不要丢失OPERATION而直接输出代码块 |
| 343 | +9. 不要私自加入不必要的空行 |
| 344 | +10.如果是在一个已有文件里插入大块代码,不应该用CREATE,而是用替换的方式插入 |
| 345 | +`; |
| 346 | + } |
| 347 | +} |
| 348 | +} |
| 349 | + |
| 350 | +读懂以上代码,帮我优化提示词 |
| 351 | +需要让模型更准确的跟随 |
| 352 | +实际使用queryCodeReDesign时发现,模型生成的代码会有以下几个问题 |
| 353 | +1.搞错了函数所在的文件 |
| 354 | +2.被替换的代码太长,其实只需要替换其中一小段,结果模型忘乎所以的几乎把一大半代码都输出当做替换串了 |
| 355 | +3.输出的被替换串不准确,比如有些可有可无的;结尾,和原文不一致导致匹配失败 |
| 356 | +4.一些原文里的缩进空格等会错误,导致匹配不准确 |
| 357 | +5.有时候提前输出的方案里包含了部分代码,正式输出的时候会错觉的把这些代码当成原文的被替换串 |
| 358 | + |
| 359 | + |
| 360 | +callDeepSeekFixApi 有以下问题: |
| 361 | +我提供给他一些错误描述,让他反思,结果他只是很敷衍的重复一些废话,比如:就是输入串不匹配。根本不去反思哪里不匹配,为什么会不匹配 |
| 362 | +然后重新输出的字符串还是错的 |
0 commit comments