DeepSeek-Agent-Harness-2026终极指南-第9章第40节-核心工具集开发-文件三件套readwriteedit:从读文件到改代码
DeepSeek Agent Harness 2026终极指南 - 第9章第40节 文件三件套read/write/edit从读文件到改代码第8章Agent Loop从零实现全部完成——50行最小Loop、装饰器注册、工具执行器、流式Agent、单元测试Agent的核心引擎已经就位。从本节开始进入第9章核心工具集开发给Agent装上真正的双手。先做文件三件套read_file/write_file/edit_file让Agent能读代码、写代码、改代码。这是AI编程Agent最基础也最重要的能力。本文导航为什么文件工具是Agent的双手read_file带行号的智能读取write_file原子写入防丢失edit_file精准替换old_str→new_str完整实现file_tools.py实测Agent读改写全流程小结为什么文件工具是Agent的双手第7章的Agent能调天气、查时间但它不能碰文件系统。你让它帮我写一个Python脚本它只能说你可以这样写…不能真的创建文件。文件三件套让Agent从顾问变成工程师read_fileAgent能读代码、读配置、读日志write_fileAgent能创建新文件、写脚本、写配置edit_fileAgent能改代码、修bug、加功能有了这三个工具Agent就能真正帮你干活读你的项目代码理解结构写新代码实现功能改旧代码修bug或优化这是AI编程Agent的核心能力。Cursor、Claude Code、Copilot Workspace——所有AI编程工具底层都有这三个文件工具。read_file带行号的智能读取read_file的设计要考虑三个问题问题1大文件怎么办一个10万行的文件全部读出来会占满上下文窗口。解决方案支持offset和limit参数只读一部分。问题2怎么定位到具体行读出来的内容要带行号方便Agent引用。比如第42行有个bugAgent能精确定位。问题3二进制文件怎么办图片、PDF、编译后的文件不能当文本读。解决方案检测文件类型二进制文件返回错误提示。实现frompathlibimportPathfromdeep_pilot.tool_registryimporttooltooldefread_file(path:str,offset:int1,limit:int1000)-str: 读取文件内容带行号。 参数 - path: 文件路径相对或绝对 - offset: 起始行号从1开始默认1 - limit: 最多读取多少行默认1000 返回带行号的文件内容格式为行号: 内容 file_pathPath(path)# 检查文件是否存在ifnotfile_path.exists():returnf错误文件不存在 {path}# 检查是否是文件不是目录ifnotfile_path.is_file():returnf错误{path} 不是文件# 检查是否是文本文件简单判断尝试读取前1KBtry:withopen(file_path,r,encodingutf-8)asf:f.read(1024)exceptUnicodeDecodeError:returnf错误{path} 可能是二进制文件无法作为文本读取# 读取指定范围try:withopen(file_path,r,encodingutf-8)asf:linesf.readlines()total_lineslen(lines)# 参数校验ifoffset1:offset1ifoffsettotal_lines:returnf错误offset{offset}超出文件总行数{total_lines}# 截取范围end_linemin(offsetlimit-1,total_lines)selected_lineslines[offset-1:end_line]# 格式化输出行号: 内容result_lines[]fori,lineinenumerate(selected_lines,startoffset):result_lines.append(f{i:4d}:{line.rstrip()})result\n.join(result_lines)# 如果截断了加提示ifend_linetotal_lines:resultf\n\n[文件共{total_lines}行已显示{offset}-{end_line}行]returnresultexceptExceptionase:returnf错误读取文件失败 -{str(e)}关键设计带行号输出f{i:4d}: {line.rstrip()}行号右对齐4位方便阅读。offsetlimit参数支持只读一部分避免大文件占满上下文。二进制文件检测尝试读取前1KB如果UnicodeDecodeError就报错。截断提示如果文件超过limit提示文件共X行已显示Y-Z行。write_file原子写入防丢失write_file的设计要考虑一个问题写到一半程序崩了怎么办如果直接open(path, w).write(content)写到一半程序崩了文件就毁了——旧内容没了新内容也没写完。解决方案原子写入。先写到临时文件写完后rename覆盖原文件。rename在同一个文件系统上是原子操作要么完全成功要么完全失败不会出现写了一半的情况。importosimporttempfilefrompathlibimportPathtooldefwrite_file(path:str,content:str)-str: 写入文件内容原子写入。 参数 - path: 文件路径 - content: 文件内容 返回成功/失败消息 file_pathPath(path)try:# 确保父目录存在file_path.parent.mkdir(parentsTrue,exist_okTrue)# 原子写入先写临时文件再rename# 临时文件和目标文件在同一目录确保在同一文件系统withtempfile.NamedTemporaryFile(modew,encodingutf-8,dirfile_path.parent,deleteFalse,suffix.tmp,)astmp:tmp.write(content)tmp_pathPath(tmp.name)# rename覆盖原文件原子操作tmp_path.rename(file_path)returnf成功已写入{path}{len(content)}字符exceptExceptionase:# 清理临时文件iftmp_pathinlocals()andtmp_path.exists():tmp_path.unlink()returnf错误写入文件失败 -{str(e)}关键设计原子写入先写临时文件.tmp后缀写完后rename覆盖原文件。同一文件系统临时文件放在目标文件的父目录确保rename是原子操作。自动创建父目录file_path.parent.mkdir(parentsTrue, exist_okTrue)。异常清理如果写入失败删除临时文件。edit_file精准替换old_str→new_stredit_file的设计思路不是在第X行插入Y而是把old_str替换成new_str。为什么不用行号因为行号容易变——你在第10行插入一行后面所有行号都变了模型不擅长数行号——让它在第42行插入它可能数错文本替换更直观——“把def foo()改成def bar()”模型更容易理解实现tooldefedit_file(path:str,old_str:str,new_str:str)-str: 编辑文件把 old_str 替换成 new_str。 参数 - path: 文件路径 - old_str: 要被替换的字符串必须精确匹配 - new_str: 替换后的字符串 返回成功/失败消息 file_pathPath(path)# 检查文件是否存在ifnotfile_path.exists():returnf错误文件不存在 {path}try:# 读取原文件contentfile_path.read_text(encodingutf-8)# 检查 old_str 是否存在ifold_strnotincontent:returnf错误在文件中找不到要替换的内容:\n{old_str}# 检查 old_str 是否出现多次countcontent.count(old_str)ifcount1:returnf错误要替换的内容出现了{count}次请提供更精确的 old_str# 替换new_contentcontent.replace(old_str,new_str,1)# 原子写入withtempfile.NamedTemporaryFile(modew,encodingutf-8,dirfile_path.parent,deleteFalse,suffix.tmp,)astmp:tmp.write(new_content)tmp_pathPath(tmp.name)tmp_path.rename(file_path)returnf成功已编辑{path}exceptExceptionase:iftmp_pathinlocals()andtmp_path.exists():tmp_path.unlink()returnf错误编辑文件失败 -{str(e)}关键设计精确匹配old_str必须完全匹配不支持正则避免复杂性和错误。唯一性检查如果old_str出现多次报错让模型提供更精确的内容。原子写入同write_file先写临时文件再rename。完整实现file_tools.py把三个工具整合成完整模块# deep_pilot/file_tools.py —— 文件三件套工具 v0.4from__future__importannotationsimporttempfilefrompathlibimportPathfromdeep_pilot.tool_registryimporttooltooldefread_file(path:str,offset:int1,limit:int1000)-str: 读取文件内容带行号。 参数 - path: 文件路径相对或绝对 - offset: 起始行号从1开始默认1 - limit: 最多读取多少行默认1000 返回带行号的文件内容格式为行号: 内容 file_pathPath(path)ifnotfile_path.exists():returnf错误文件不存在 {path}ifnotfile_path.is_file():returnf错误{path} 不是文件# 检查是否是文本文件try:withopen(file_path,r,encodingutf-8)asf:f.read(1024)exceptUnicodeDecodeError:returnf错误{path} 可能是二进制文件无法作为文本读取try:withopen(file_path,r,encodingutf-8)asf:linesf.readlines()total_lineslen(lines)ifoffset1:offset1ifoffsettotal_lines:returnf错误offset{offset}超出文件总行数{total_lines}end_linemin(offsetlimit-1,total_lines)selected_lineslines[offset-1:end_line]result_lines[]fori,lineinenumerate(selected_lines,startoffset):result_lines.append(f{i:4d}:{line.rstrip()})result\n.join(result_lines)ifend_linetotal_lines:resultf\n\n[文件共{total_lines}行已显示{offset}-{end_line}行]returnresultexceptExceptionase:returnf错误读取文件失败 -{str(e)}tooldefwrite_file(path:str,content:str)-str: 写入文件内容原子写入。 参数 - path: 文件路径 - content: 文件内容 返回成功/失败消息 file_pathPath(path)try:file_path.parent.mkdir(parentsTrue,exist_okTrue)withtempfile.NamedTemporaryFile(modew,encodingutf-8,dirfile_path.parent,deleteFalse,suffix.tmp,)astmp:tmp.write(content)tmp_pathPath(tmp.name)tmp_path.rename(file_path)returnf成功已写入{path}{len(content)}字符exceptExceptionase:iftmp_pathinlocals()andtmp_path.exists():tmp_path.unlink()returnf错误写入文件失败 -{str(e)}tooldefedit_file(path:str,old_str:str,new_str:str)-str: 编辑文件把 old_str 替换成 new_str。 参数 - path: 文件路径 - old_str: 要被替换的字符串必须精确匹配且只能出现一次 - new_str: 替换后的字符串 返回成功/失败消息 file_pathPath(path)ifnotfile_path.exists():returnf错误文件不存在 {path}try:contentfile_path.read_text(encodingutf-8)ifold_strnotincontent:returnf错误在文件中找不到要替换的内容:\n{old_str}countcontent.count(old_str)ifcount1:returnf错误要替换的内容出现了{count}次请提供更精确的 old_strnew_contentcontent.replace(old_str,new_str,1)withtempfile.NamedTemporaryFile(modew,encodingutf-8,dirfile_path.parent,deleteFalse,suffix.tmp,)astmp:tmp.write(new_content)tmp_pathPath(tmp.name)tmp_path.rename(file_path)returnf成功已编辑{path}exceptExceptionase:iftmp_pathinlocals()andtmp_path.exists():tmp_path.unlink()returnf错误编辑文件失败 -{str(e)}实测Agent读改写全流程在deep_pilot/tools.py里导入文件工具# deep_pilot/tools.py —— v0.4 加入文件工具fromdeep_pilot.file_toolsimportread_file,write_file,edit_file# 保留之前的工具tooldefget_weather(city:str)-str:获取指定城市今天的天气信息。returnf{city}晴天28°C实测Agent读改写全流程uv run python-c from deep_pilot.agent_loop import run # 测试1创建新文件 print( 测试1创建新文件 ) answer run(帮我创建一个 hello.py 文件内容是打印 Hello World) print(f\nAgent回答: {answer}) print() # 测试2读取文件 print( 测试2读取文件 ) answer run(读一下 hello.py 的内容) print(f\nAgent回答: {answer}) print() # 测试3编辑文件 print( 测试3编辑文件 ) answer run(把 hello.py 里的 Hello World 改成 Hello DeepSeek) print(f\nAgent回答: {answer}) print() # 测试4验证修改 print( 测试4验证修改 ) answer run(再读一下 hello.py确认修改成功) print(f\nAgent回答: {answer}) 控制台输出精简 测试1创建新文件 2026-09-12 21:00:01 | INFO | agent_loop | Loop 第 1 轮 ↻ 2026-09-12 21:00:01 | INFO | agent_loop | → 调用工具: write_file({path: hello.py, content: print(Hello World)\n}) 2026-09-12 21:00:01 | INFO | agent_loop | ← 工具结果: 成功已写入 hello.py22 字符 2026-09-12 21:00:01 | INFO | agent_loop | Loop 第 2 轮 ↻ Agent回答: 已创建 hello.py 文件内容是 print(Hello World)。 测试2读取文件 2026-09-12 21:00:02 | INFO | agent_loop | Loop 第 1 轮 ↻ 2026-09-12 21:00:02 | INFO | agent_loop | → 调用工具: read_file({path: hello.py}) 2026-09-12 21:00:02 | INFO | agent_loop | ← 工具结果: 1: print(Hello World) 2026-09-12 21:00:02 | INFO | agent_loop | Loop 第 2 轮 ↻ Agent回答: hello.py 的内容是 1: print(Hello World) 测试3编辑文件 2026-09-12 21:00:03 | INFO | agent_loop | Loop 第 1 轮 ↻ 2026-09-12 21:00:03 | INFO | agent_loop | → 调用工具: edit_file({path: hello.py, old_str: Hello World, new_str: Hello DeepSeek}) 2026-09-12 21:00:03 | INFO | agent_loop | ← 工具结果: 成功已编辑 hello.py 2026-09-12 21:00:03 | INFO | agent_loop | Loop 第 2 轮 ↻ Agent回答: 已将 hello.py 里的 Hello World 改成 Hello DeepSeek。 测试4验证修改 2026-09-12 21:00:04 | INFO | agent_loop | Loop 第 1 轮 ↻ 2026-09-12 21:00:04 | INFO | agent_loop | → 调用工具: read_file({path: hello.py}) 2026-09-12 21:00:04 | INFO | agent_loop | ← 工具结果: 1: print(Hello DeepSeek) 2026-09-12 21:00:04 | INFO | agent_loop | Loop 第 2 轮 ↻ Agent回答: 确认修改成功hello.py 的内容现在是 1: print(Hello DeepSeek)四个测试都通过了创建文件Agent调write_file创建hello.py读取文件Agent调read_file读取内容看到带行号的输出编辑文件Agent调edit_file把Hello World改成Hello DeepSeek验证修改Agent再次调read_file确认修改成功注意Agent的回答都是自然语言它理解了工具返回的结果然后用用户能理解的方式表达。小结文件三件套是Agent的双手read_file读代码、write_file写代码、edit_file改代码。read_file带行号f{i:4d}: {line.rstrip()}方便Agent引用具体行。支持offsetlimit读大文件的一部分。write_file原子写入先写临时文件再rename防止写到一半程序崩了毁文件。edit_file精准替换old_str→new_str不支持正则要求唯一匹配。比行号更直观。二进制文件检测read_file尝试读取前1KBUnicodeDecodeError就报错。错误消息友好文件不存在、不是文件、二进制文件、old_str出现多次——都给模型能理解的提示。DeepPilot v0.4文件工具完成——Agent从顾问变成工程师能真正帮你读改写代码。下节预告文件三件套搞定了但Agent还不能执行命令。你让它跑一下pytest它只能说你可以在终端运行…不能真的执行。下一节做bash执行器run_bash用subprocess.run封装命令执行支持超时杀死、输出截断、工作目录约束。从此Agent能跑测试、装依赖、编译代码真正成为你的编程助手。如果觉得本文对你有帮助欢迎点赞、收藏、关注三连本系列持续更新中关注不迷路~

相关新闻

Gemini Cli 登录失败排查:把 OAuth 凭据改到 TaoToken 统一通道

Gemini Cli 登录失败排查:把 OAuth 凭据改到 TaoToken 统一通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/2 18:07:04 阅读更多 →
Ubuntu22.04 搭建 QT6.8 + OSG + osgearth 环境:TaoToken 统一 Key 接入与验证

Ubuntu22.04 搭建 QT6.8 + OSG + osgearth 环境:TaoToken 统一 Key 接入与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/2 18:07:04 阅读更多 →
【研发类-架构设计Skills】database-design 技能

【研发类-架构设计Skills】database-design 技能

数据库设计原则和决策制定。模式设计、索引策略、ORM选择、无服务器数据库。 技能概述 database-design 技能专注于数据库设计原则和决策制定。该技能强调"学会思考,而不是复制SQL模式"的核心理念,帮助开发者根据上下文选择合适的数据库和ORM,设计优化的模式,并实现…

2026/10/3 18:37:46 阅读更多 →

最新新闻

什么是 Vibe Coding?面向 Java 后端初学者的通俗指南(TaoToken 版)

什么是 Vibe Coding?面向 Java 后端初学者的通俗指南(TaoToken 版)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/3 19:43:25 阅读更多 →
智能体架构选型之争:OpenClaw与VibeSurf的技术路线对比分析|TaoToken统一API通道实测

智能体架构选型之争:OpenClaw与VibeSurf的技术路线对比分析|TaoToken统一API通道实测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/3 19:43:25 阅读更多 →
C++开发新手从零开始:1.VScode+MinGW+Cmake配置(仅供学习)

C++开发新手从零开始:1.VScode+MinGW+Cmake配置(仅供学习)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/3 19:42:24 阅读更多 →
vscode + cmake + ninja + ARMCC 配置stm32开发环境(构建篇):把 CMake 工具链文件改到 TaoToken 统一 Key 通道

vscode + cmake + ninja + ARMCC 配置stm32开发环境(构建篇):把 CMake 工具链文件改到 TaoToken 统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/3 19:42:24 阅读更多 →
国产电池管理芯片BMIC替代加速:从消费级到车规级的爬坡

国产电池管理芯片BMIC替代加速:从消费级到车规级的爬坡

做电池管理系统(BMS)这行的朋友,这两年应该都有同感:过去开会聊拓扑、聊均衡策略、聊SOC算法,现在坐下来,三句话不离芯片。BMS里的专用集成电路,行业喜欢叫BMIC(Battery Management …

2026/10/3 19:42:23 阅读更多 →
电子设计竞赛备赛核心指南:从系统设计到实战调试

电子设计竞赛备赛核心指南:从系统设计到实战调试

2021年第一次电设竞赛培训成功举办,这个标题看起来像是一则校园新闻,但真正参加过电设竞赛的人都知道,一场培训背后藏着的,是整个备赛周期的起点、方向和方法论。作为连续带过几届电设队伍的老兵,我太清楚这场培训的含…

2026/10/3 19:42:23 阅读更多 →

日新闻

把回忆蒸馏成 AI 的浪漫实验:为什么你需要前任.skill 完整指南

把回忆蒸馏成 AI 的浪漫实验:为什么你需要前任.skill 完整指南

把回忆蒸馏成 AI 的浪漫实验:为什么你需要前任.skill 完整指南 【免费下载链接】ex-skill 前任 skill 项目地址: https://gitcode.com/gh_mirrors/exsk/ex-skill 前任.skill 是一个运行在 Claude Code 上的开源 Skill:导入微信、iMessage、短信、…

2026/10/3 0:00:27 阅读更多 →
45个经典Linux面试题:从命令到网络排障的完整考点解析

45个经典Linux面试题:从命令到网络排障的完整考点解析

刚开始带应届生的时候,我最头疼的就是他们拿着一摞Linux面试题背得滚瓜烂熟,一上机全露馅。后来自己从被面的人变成面别人的人,才慢慢摸清楚:Linux面试题考的根本不是答案本身,而是你面对一个不确定的系统问题时&#…

2026/10/3 0:01:28 阅读更多 →
SAP生产预留实战指南:MB21/MB23/MB25协同与MRP集成

SAP生产预留实战指南:MB21/MB23/MB25协同与MRP集成

简介:本资源是一份面向SAP ABAP开发人员、生产计划专员及ERP实施顾问的实操型操作指南,聚焦SAP生产预留核心业务场景,系统解决物料预留创建、查询、校验与批量处理等高频问题。文档以结构化方式覆盖预留背景原理、OMC2编码规则、工厂级参数配…

2026/10/3 0:01:28 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/10/3 9:14:33 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/10/3 9:47:50 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/10/3 9:42:31 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/2 10:36:31 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/3 9:42:35 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/3 9:42:36 阅读更多 →