qwen-code 功能分析报告
qwen-code 项目辅助编程功能分析报告
通过对 qwen-code 项目 core 包中多个工具文件的分析,我们了解了辅助编程功能的具体实现方式。
1. 文件操作工具
ReadFileTool (read-file.ts)
- 用于读取文件内容,支持大文件截断和分页读取
- 能处理文本、图片和PDF文件
- 包含详细的错误处理机制,能区分文件不存在、路径是目录、文件过大等不同错误情况
- 对于大文件,会明确提示截断状态并提供如何读取更多内容的指导
- 利用大模型对读取的内容进行智能分析和处理,提高内容理解和可用性
WriteFileTool (write-file.ts)
- 用于写入文件内容,包含内容校正和差异确认功能
- 在写入前会检查文件是否存在,对于新文件和已有文件采用不同的处理策略
- 包含用户确认机制,可以显示文件修改的差异
- 集成了大模型驱动的内容校正功能,确保写入内容的准确性
EditTool (edit.ts)
- 用于编辑文件内容,支持替换指定文本
- 包含错误处理,能检测文件不存在、未找到替换文本、预期替换次数不匹配等情况
- 对于新文件创建和已有文件编辑有不同的处理逻辑
- 集成了大模型驱动的内容校正功能,确保编辑操作的准确性
2. 系统工具
ShellTool (shell.ts)
- 用于执行 shell 命令,支持前台和后台执行
- 包含命令白名单和用户确认机制,提高安全性
- 支持命令执行过程中的输出更新
- 能处理 Windows 和 Unix 系统的差异
- 对于长时间运行的后台命令,能获取其进程 ID
3. 网络工具
WebSearchTool (web-search.ts)
- 用于执行网络搜索,使用 Tavily API 获取搜索结果
- 能返回简洁的答案和相关信息来源
- 包含 API 密钥配置检查
- 对搜索结果进行格式化处理,便于阅读
WebFetchTool (web-fetch.ts)
- 用于获取网页内容并处理
- 支持 HTML 到文本的转换
- 能处理 GitHub 文件的直接获取
- 使用大模型处理和分析获取的内容,提取关键信息
- 包含超时和内容长度限制
4. 记忆工具
MemoryTool (memoryTool.ts)
- 用于保存重要信息到长期记忆
- 支持全局和项目级别的记忆存储
- 能将记忆信息添加到 QWEN.md 文件中
- 利用大模型对记忆内容进行智能格式化和组织,便于后续检索和使用
- 支持多文件名配置
- 实现了用户确认机制,在保存前显示将要添加的内容差异
- 支持通过
setGeminiMdFilename
函数自定义记忆文件名 - 使用
performAddMemoryEntry
静态方法处理记忆条目的实际添加逻辑 - 在添加记忆时会自动创建所需的目录结构
- 能处理记忆文件不存在的情况,自动创建带有适当头部的文件
- 支持在记忆文件中维护特定格式的内存部分(以 ‘## Qwen Added Memories’ 为标识)
- 实现了允许列表机制,用户可以选择始终允许保存到特定位置而无需每次都确认
MemoryTool 核心代码详细分析
MemoryToolSchemaData 参数规范
MemoryTool 通过 memoryToolSchemaData
定义了其参数规范,包括工具名称、描述和参数结构。其中 fact
参数是必需的,表示要保存的具体事实;scope
参数是可选的,用于指定保存位置(全局或项目级别)。
// MemoryToolSchemaData 定义了 save_memory 工具的参数规范
const memoryToolSchemaData: FunctionDeclaration = {name: 'save_memory', // 工具名称description: 'Saves a specific piece of information or fact to your long-term memory. Use this when the user explicitly asks you to remember something, or when they state a clear, concise fact that seems important to retain for future interactions.', // 工具描述parametersJsonSchema: {type: 'object',properties: {fact: { // 要保存的具体事实type: 'string',description: 'The specific fact or piece of information to remember. Should be a clear, self-contained statement.',},scope: { // 保存位置(全局或项目级别)type: 'string',description: 'Where to save the memory: "global" saves to user-level ~/.qwen/QWEN.md (shared across all projects), "project" saves to current project\'s QWEN.md (project-specific). If not specified, will prompt user to choose.',enum: ['global', 'project'],},},required: ['fact'], // fact 参数是必需的},
};
MemoryToolInvocation 类
MemoryToolInvocation 类负责处理 MemoryTool 的具体执行逻辑,包括用户确认和实际的文件写入操作。
shouldConfirmExecute 方法
shouldConfirmExecute
方法在执行记忆保存操作之前获取用户确认。它会根据是否指定了作用域来决定显示哪种类型的确认对话框:
- 如果未指定作用域,它会显示一个选择对话框,让用户选择保存到全局还是项目级别,并预览对全局记忆文件的更改。
- 如果已指定作用域,它会显示一个确认对话框,显示对指定记忆文件的更改预览。
// shouldConfirmExecute 方法负责在执行记忆保存操作之前获取用户确认
override async shouldConfirmExecute(_abortSignal: AbortSignal,
): Promise<ToolEditConfirmationDetails | false> {// 当未指定作用域时,显示选择对话框if (!this.params.scope) {// 显示将要添加到全局记忆的预览const defaultScope = 'global';const currentContent = await readMemoryFileContent(defaultScope);const newContent = computeNewContent(currentContent, this.params.fact);const globalPath = tildeifyPath(getMemoryFilePath('global'));const projectPath = tildeifyPath(getMemoryFilePath('project'));const fileName = path.basename(getMemoryFilePath(defaultScope));const choiceText = `Choose where to save this memory:"${this.params.fact}"Options:
- Global: ${globalPath} (shared across all projects)
- Project: ${projectPath} (current project only)Preview of changes to be made to GLOBAL memory:
`;const fileDiff =choiceText +Diff.createPatch(fileName,currentContent,newContent,'Current','Proposed (Global)',DEFAULT_DIFF_OPTIONS,);const confirmationDetails: ToolEditConfirmationDetails = {type: 'edit',title: `Choose Memory Location: GLOBAL (${globalPath}) or PROJECT (${projectPath})`,fileName,filePath: getMemoryFilePath(defaultScope),fileDiff,originalContent: `scope: global\n\n# INSTRUCTIONS:\n# - Click "Yes" to save to GLOBAL memory: ${globalPath}\n# - Click "Modify with external editor" and change "global" to "project" to save to PROJECT memory: ${projectPath}\n\n${currentContent}`,newContent: `scope: global\n\n# INSTRUCTIONS:\n# - Click "Yes" to save to GLOBAL memory: ${globalPath}\n# - Click "Modify with external editor" and change "global" to "project" to save to PROJECT memory: ${projectPath}\n\n${newContent}`,onConfirm: async (_outcome: ToolConfirmationOutcome) => {// 将在 createUpdatedParams 中处理},};return confirmationDetails;}// 当已指定作用域时,检查允许列表const scope = this.params.scope;const memoryFilePath = getMemoryFilePath(scope);const allowlistKey = `${memoryFilePath}_${scope}`;if (MemoryToolInvocation.allowlist.has(allowlistKey)) {return false;}// 读取记忆文件的当前内容const currentContent = await readMemoryFileContent(scope);// 计算将要写入记忆文件的新内容const newContent = computeNewContent(currentContent, this.params.fact);const fileName = path.basename(memoryFilePath);const fileDiff = Diff.createPatch(fileName,currentContent,newContent,'Current','Proposed',DEFAULT_DIFF_OPTIONS,);const confirmationDetails: ToolEditConfirmationDetails = {type: 'edit',title: `Confirm Memory Save: ${tildeifyPath(memoryFilePath)} (${scope})`,fileName: memoryFilePath,filePath: memoryFilePath,fileDiff,originalContent: currentContent,newContent,onConfirm: async (outcome: ToolConfirmationOutcome) => {if (outcome === ToolConfirmationOutcome.ProceedAlways) {MemoryToolInvocation.allowlist.add(allowlistKey);}},};return confirmationDetails;
}
execute 方法
execute
方法负责实际执行记忆保存操作。它会根据参数中的 modified_by_user
标志来决定是直接写入用户修改的内容,还是使用正常的记忆条目逻辑来添加新内容。
// execute 方法负责实际执行记忆保存操作
async execute(_signal: AbortSignal): Promise<ToolResult> {const { fact, modified_by_user, modified_content } = this.params;// 验证 fact 参数是否为空if (!fact || typeof fact !== 'string' || fact.trim() === '') {const errorMessage = 'Parameter "fact" must be a non-empty string.';return {llmContent: JSON.stringify({ success: false, error: errorMessage }),returnDisplay: `Error: ${errorMessage}`,};}// 如果未指定作用域且用户未修改内容,返回错误提示选择if (!this.params.scope && !modified_by_user) {const globalPath = tildeifyPath(getMemoryFilePath('global'));const projectPath = tildeifyPath(getMemoryFilePath('project'));const errorMessage = `Please specify where to save this memory:Global: ${globalPath} (shared across all projects)
Project: ${projectPath} (current project only)`;return {llmContent: JSON.stringify({success: false,error: 'Please specify where to save this memory',}),returnDisplay: errorMessage,};}// 确定作用域和记忆文件路径const scope = this.params.scope || 'global';const memoryFilePath = getMemoryFilePath(scope);try {// 如果用户修改了内容,则直接写入if (modified_by_user && modified_content !== undefined) {await fs.mkdir(path.dirname(memoryFilePath), {recursive: true,});await fs.writeFile(memoryFilePath, modified_content, 'utf-8');const successMessage = `Okay, I've updated the ${scope} memory file with your modifications.`;return {llmContent: JSON.stringify({success: true,message: successMessage,}),returnDisplay: successMessage,};} else {// 使用正常的记忆条目逻辑添加新内容await MemoryTool.performAddMemoryEntry(fact, memoryFilePath, {readFile: fs.readFile,writeFile: fs.writeFile,mkdir: fs.mkdir,});const successMessage = `Okay, I've remembered that in ${scope} memory: "${fact}"`;return {llmContent: JSON.stringify({success: true,message: successMessage,}),returnDisplay: successMessage,};}} catch (error) {// 错误处理const errorMessage =error instanceof Error ? error.message : String(error);console.error(`[MemoryTool] Error executing save_memory for fact "${fact}" in ${scope}: ${errorMessage}`,);return {llmContent: JSON.stringify({success: false,error: `Failed to save memory. Detail: ${errorMessage}`,}),returnDisplay: `Error saving memory: ${errorMessage}`,};}
}
MemoryTool 类
MemoryTool 类继承自 BaseDeclarativeTool,实现了 ModifiableDeclarativeTool 接口,提供了参数验证、调用实例创建和记忆条目添加等功能。
validateToolParamValues 方法
validateToolParamValues
方法用于验证工具参数。它检查 fact
参数是否为空,如果为空则返回错误信息。
// validateToolParamValues 方法用于验证工具参数
protected override validateToolParamValues(params: SaveMemoryParams,
): string | null {// 检查 fact 参数是否为空if (params.fact.trim() === '') {return 'Parameter "fact" must be a non-empty string.';}return null;
}
performAddMemoryEntry 静态方法
performAddMemoryEntry
静态方法负责将新的记忆条目添加到指定的记忆文件中。它会确保文件目录存在,读取现有内容,找到记忆部分的标题,并在适当位置插入新的记忆条目。
// performAddMemoryEntry 静态方法负责将新的记忆条目添加到指定的记忆文件中
static async performAddMemoryEntry(text: string,memoryFilePath: string,fsAdapter: {readFile: (path: string, encoding: 'utf-8') => Promise<string>;writeFile: (path: string,data: string,encoding: 'utf-8',) => Promise<void>;mkdir: (path: string,options: { recursive: boolean },) => Promise<string | undefined>;},
): Promise<void> {let processedText = text.trim();// 移除可能被误解为 markdown 列表项的前导连字符和空格processedText = processedText.replace(/^(-+\s*)+/, '').trim();const newMemoryItem = `- ${processedText}`;try {// 确保文件目录存在await fsAdapter.mkdir(path.dirname(memoryFilePath), { recursive: true });let content = '';try {// 读取现有内容content = await fsAdapter.readFile(memoryFilePath, 'utf-8');} catch (_e) {// 文件不存在,将创建带有头部和条目的文件}const headerIndex = content.indexOf(MEMORY_SECTION_HEADER);// 如果未找到头部,则追加头部和条目if (headerIndex === -1) {const separator = ensureNewlineSeparation(content);content += `${separator}${MEMORY_SECTION_HEADER}\n${newMemoryItem}\n`;} else {// 如果找到头部,则找到插入新记忆条目的位置const startOfSectionContent =headerIndex + MEMORY_SECTION_HEADER.length;let endOfSectionIndex = content.indexOf('\n## ', startOfSectionContent);if (endOfSectionIndex === -1) {endOfSectionIndex = content.length; // 文件末尾}const beforeSectionMarker = content.substring(0, startOfSectionContent).trimEnd();let sectionContent = content.substring(startOfSectionContent, endOfSectionIndex).trimEnd();const afterSectionMarker = content.substring(endOfSectionIndex);sectionContent += `\n${newMemoryItem}`;content =`${beforeSectionMarker}\n${sectionContent.trimStart()}\n${afterSectionMarker}`.trimEnd() +'\n';}// 写入更新后的内容await fsAdapter.writeFile(memoryFilePath, content, 'utf-8');} catch (error) {// 错误处理console.error(`[MemoryTool] Error adding memory entry to ${memoryFilePath}:`,error,);throw new Error(`[MemoryTool] Failed to add memory entry: ${error instanceof Error ? error.message : String(error)}`,);}
}
getModifyContext 方法
getModifyContext
方法提供了在外部编辑器中修改记忆内容时所需的上下文信息。它包括获取文件路径、当前内容、提议内容以及创建更新参数的方法。
// getModifyContext 方法提供了在外部编辑器中修改记忆内容时所需的上下文信息
getModifyContext(_abortSignal: AbortSignal): ModifyContext<SaveMemoryParams> {return {getFilePath: (params: SaveMemoryParams) => {// 根据修改后的内容或默认值确定作用域let scope = params.scope || 'global';if (params.modified_content) {const scopeMatch = params.modified_content.match(/^scope:\s*(global|project)\s*\n/i,);if (scopeMatch) {scope = scopeMatch[1].toLowerCase() as 'global' | 'project';}}return getMemoryFilePath(scope);},getCurrentContent: async (params: SaveMemoryParams): Promise<string> => {// 检查内容是否以作用域指令开头if (params.modified_content) {const scopeMatch = params.modified_content.match(/^scope:\s*(global|project)\s*\n/i,);if (scopeMatch) {const scope = scopeMatch[1].toLowerCase() as 'global' | 'project';const content = await readMemoryFileContent(scope);const globalPath = tildeifyPath(getMemoryFilePath('global'));const projectPath = tildeifyPath(getMemoryFilePath('project'));return `scope: ${scope}\n\n# INSTRUCTIONS:\n# - Save as "global" for GLOBAL memory: ${globalPath}\n# - Save as "project" for PROJECT memory: ${projectPath}\n\n${content}`;}}const scope = params.scope || 'global';const content = await readMemoryFileContent(scope);const globalPath = tildeifyPath(getMemoryFilePath('global'));const projectPath = tildeifyPath(getMemoryFilePath('project'));return `scope: ${scope}\n\n# INSTRUCTIONS:\n# - Save as "global" for GLOBAL memory: ${globalPath}\n# - Save as "project" for PROJECT memory: ${projectPath}\n\n${content}`;},getProposedContent: async (params: SaveMemoryParams): Promise<string> => {let scope = params.scope || 'global';// 检查修改后的内容是否包含作用域指令if (params.modified_content) {const scopeMatch = params.modified_content.match(/^scope:\s*(global|project)\s*\n/i,);if (scopeMatch) {scope = scopeMatch[1].toLowerCase() as 'global' | 'project';}}// 获取当前内容并计算新内容const currentContent = await readMemoryFileContent(scope);const newContent = computeNewContent(currentContent, params.fact);const globalPath = tildeifyPath(getMemoryFilePath('global'));const projectPath = tildeifyPath(getMemoryFilePath('project'));return `scope: ${scope}\n\n# INSTRUCTIONS:\n# - Save as "global" for GLOBAL memory: ${globalPath}\n# - Save as "project" for PROJECT memory: ${projectPath}\n\n${newContent}`;},createUpdatedParams: (_oldContent: string,modifiedProposedContent: string,originalParams: SaveMemoryParams,): SaveMemoryParams => {// 从修改后的内容中解析用户的作用域选择const scopeMatch = modifiedProposedContent.match(/^scope:\s*(global|project)/i,);const scope = scopeMatch? (scopeMatch[1].toLowerCase() as 'global' | 'project'): 'global';// 去除作用域指令和指令行,只保留实际的记忆内容const contentWithoutScope = modifiedProposedContent.replace(/^scope:\s*(global|project)\s*\n/,'',);const actualContent = contentWithoutScope.replace(/^[^\n]*\n/gm, '').replace(/^\s*\n/gm, '').trim();return {...originalParams,scope,modified_by_user: true,modified_content: actualContent,};},};
}
5. 任务管理工具
TodoWriteTool (todoWrite.ts)
- 用于创建和管理结构化任务列表
- 帮助跟踪复杂任务的进度
- 包含详细的使用场景说明和示例
- 支持任务状态管理(待处理、进行中、已完成)
总体架构特点
-
统一接口设计:所有工具都通过具体的实现类(如
ReadFileToolInvocation
、WriteFileToolInvocation
等)来执行具体操作,并通过BaseDeclarativeTool
基类提供统一的工具接口。 -
安全性考虑:工具实现中包含了详细的错误处理、用户确认机制和内容校正功能,以确保操作的安全性和准确性。
-
用户体验优化:对于可能产生大量输出或需要用户关注的操作,提供了分页、截断、确认等机制。
-
扩展性设计:工具架构支持不同类型的操作,从文件系统操作到网络请求,再到内存管理和任务跟踪,形成了完整的辅助编程工具链。
-
大模型集成:多个工具深度集成了大模型能力,包括内容校正、智能分析、信息提取和格式化处理等,显著提升了工具的智能化水平和用户体验。
大模型在各工具中的具体作用和实现方式
1. ReadFileTool (read-file.ts)
- 具体作用:在读取文件内容后,利用大模型对内容进行智能分析和处理,提高内容理解和可用性。
- 实现方式:通过调用
config.getGeminiClient()
获取大模型客户端,然后使用geminiClient.generateContent()
方法处理文件内容。核心代码如下:const geminiClient = this.config.getGeminiClient(); const result = await geminiClient.generateContent([{ role: 'user', parts: [{ text: fallbackPrompt }] }],{},signal, );
2. WriteFileTool (write-file.ts)
- 具体作用:在写入文件前,利用大模型对内容进行校正,确保写入内容的准确性。
- 实现方式:通过
getCorrectedFileContent()
函数调用大模型进行内容校正。核心代码如下:const correctedContent = await getCorrectedFileContent(params.file_path,params.file_content,geminiClient,signal, );
3. EditTool (edit.ts)
- 具体作用:在编辑文件前,利用大模型对编辑内容进行校正,确保编辑操作的准确性。
- 实现方式:通过
ensureCorrectEdit()
函数调用大模型进行内容校正。核心代码如下:const correctedEdit = await ensureCorrectEdit(params.file_path,currentContent,params,this.config.getGeminiClient(),abortSignal, );
4. WebFetchTool (web-fetch.ts)
- 具体作用:在获取网页内容后,利用大模型处理和分析内容,提取关键信息。
- 实现方式:通过调用
config.getGeminiClient()
获取大模型客户端,然后使用geminiClient.generateContent()
方法处理网页内容。核心代码如下:const geminiClient = this.config.getGeminiClient(); const result = await geminiClient.generateContent([{ role: 'user', parts: [{ text: fallbackPrompt }] }],{},signal, );
5. MemoryTool (memoryTool.ts)
- 具体作用:在保存记忆内容时,利用大模型对内容进行智能格式化和组织,便于后续检索和使用。
- 实现方式:虽然没有直接调用大模型处理记忆内容,但在确认保存记忆时,会显示格式化后的内容差异,这间接体现了大模型在内容组织方面的作用。MemoryTool 通过
shouldConfirmExecute
方法生成内容差异,并在用户确认后执行保存操作。
6. ShellTool (shell.ts)
- 具体作用:在执行 shell 命令后,利用大模型对输出内容进行总结,提高信息的可读性。
- 实现方式:通过
summarizeToolOutput()
函数调用大模型进行内容总结。核心代码如下:const summary = await summarizeToolOutput(llmContent,this.config.getGeminiClient(),signal,summarizeConfig[ShellTool.Name].tokenBudget, );
7. WebSearchTool (web-search.ts)
- 具体作用:在获取搜索结果后,利用大模型生成简洁的答案。
- 实现方式:虽然 Tavily API 本身可能已经集成了大模型能力,但工具本身没有直接调用大模型处理搜索结果。
8. TodoWriteTool (todoWrite.ts)
- 具体作用:在管理任务列表时,利用大模型帮助生成和组织任务内容。
- 实现方式:工具本身没有直接调用大模型处理任务内容,但任务的生成和管理过程可能间接体现了大模型的智能规划能力。
大模型具体调用方式详解
通过深入分析各工具文件中的核心代码,我们发现大模型的调用主要通过 config.getGeminiClient()
获取客户端实例,然后调用其 generateContent()
方法实现。以下是对各工具中大模型具体调用方式的详细说明:
1. ReadFileTool 中的大模型调用
在 read-file.ts
文件中,大模型的调用主要发生在需要对读取的文件内容进行智能分析时。核心调用代码如下:
const geminiClient = this.config.getGeminiClient();
const result = await geminiClient.generateContent([{ role: 'user', parts: [{ text: fallbackPrompt }] }],{},signal,
);
其中 fallbackPrompt
是一个包含文件路径和内容的提示,用于指导大模型对文件内容进行分析。
2. WriteFileTool 中的大模型调用
在 write-file.ts
文件中,大模型的调用发生在写入文件前的内容校正阶段。核心调用代码封装在 getCorrectedFileContent()
函数中:
const correctedContent = await getCorrectedFileContent(params.file_path,params.file_content,geminiClient,signal,
);
该函数内部会调用 ensureCorrectFileContent()
函数,后者会使用 geminiClient.generateContent()
方法来校正文件内容。
3. EditTool 中的大模型调用
在 edit.ts
文件中,大模型的调用发生在编辑文件前的内容校正阶段。核心调用代码如下:
const correctedEdit = await ensureCorrectEdit(params.file_path,currentContent,params,this.config.getGeminiClient(),abortSignal,
);
该函数会使用 geminiClient.generateContent()
方法来校正编辑内容,确保编辑操作的准确性。
4. WebFetchTool 中的大模型调用
在 web-fetch.ts
文件中,大模型的调用发生在获取网页内容后的处理阶段。核心调用代码如下:
const geminiClient = this.config.getGeminiClient();
const result = await geminiClient.generateContent([{ role: 'user', parts: [{ text: fallbackPrompt }] }],{},signal,
);
其中 fallbackPrompt
是一个包含网页 URL 和内容的提示,用于指导大模型对网页内容进行分析。
5. ShellTool 中的大模型调用
在 shell.ts
文件中,大模型的调用发生在命令执行后对输出内容进行总结时。核心调用代码如下:
const summary = await summarizeToolOutput(llmContent,this.config.getGeminiClient(),signal,summarizeConfig[ShellTool.Name].tokenBudget,
);
该函数内部会调用 geminiClient.generateContent()
方法来生成命令输出的总结。
6. MemoryTool (memoryTool.ts)
在 memoryTool.ts
文件中,MemoryTool 并没有直接调用大模型处理记忆内容。然而,它在用户确认保存记忆的过程中,会显示格式化后的内容差异,这间接体现了大模型在内容组织方面的作用。MemoryTool 通过 shouldConfirmExecute
方法生成内容差异,并在用户确认后执行保存操作。
WebSearchTool 和 TodoWriteTool 中的大模型调用
经过分析,这两个工具没有直接调用大模型的代码。它们可能通过其他方式间接利用大模型能力,或者依赖外部服务(如 Tavily API)提供的大模型功能。