Agent 开发(四)—— 扩展篇:功能扩展 一、从终端命令到自然语言1.1 传统 CLI 的痛点在扩展之前我们只有一条命令commit-agent--stage这个命令只做一件事生成 commit message 并提交。如果你想做其他操作——切换分支、查看日志、合并代码——你需要记住另一套命令gitswitch maingitlog--oneline-5gitmerge feature-xgitreset--softHEAD~1gitstash push-mwip这里有几个问题记忆成本高每个操作有独立的命令、参数、标志你需要记住它们的具体写法组合操作麻烦想先 stash 当前工作再切换到 main 分支合并 feature再切回来 pop——需要 4 条命令串起来错误成本高git reset --hard敲错了就是数据丢失上下文割裂每条命令独立执行没有状态没有之前发生了什么1.2 自然语言交互的优势把 Git Agent 从单条命令升级为对话式助手后上述问题全部消失对比维度传统 CLI自然语言 Agent学习成本需记忆命令和参数直接说话就行组合操作多条命令手动串联一句话 多步操作错误防护敲回车即执行无回滚LLM 会先检查状态危险操作需确认上下文无状态Agent 记住对话历史知道之前做了什么模糊匹配参数写错就失败“切到 main” 和 “切换到 main 分支” 都理解举个例子CLI 方式gitstash push-mtemp workgitcheckout maingitpull origin maingitcheckout featuregitstash popAgent 方式 帮我暂存当前工作切到 main 拉取最新再切回来恢复Agent 自动决定调用的工具序列stash_push → switch_branch → ...。1.3 交互模式的演进v1实战篇 v2扩展篇 单向流程 对话式 REPL CLI 参数 → 执行 你当前在哪个分支 Agent当前在 main 分支 你切到 feature-x Agent已切换到 feature-x 你审查代码变更 Agent调 get_working_diff → 给你分析结果v1 是问一次答一次v2 是持续对话Agent 自主决策。二、架构演进从单向流程到 Agent Loop2.1 架构变化v1实战篇 v2扩展篇 cli.py → agent.py cli.py → agent.py → tools.py → llm_client.py → llm_client.py (对话历史版) → git_utils.py → git_utils.py (17 个函数) → prompts.py → tools.py (NEW: 工具定义 调度) → prompts.py (多工具提示词) 数据流 用户输入 → LLM → 文本 → CLI 输出 用户输入 → LLM → 工具调用 → 执行 → 结果回 LLM → 最终回复 ↑_____________________________________________↓ Agent Loop核心变化引入tools.py作为工具注册中心将工具定义和调度逻辑从llm_client.py和agent.py中分离出来。2.2 Agent Loop思考→行动→观察Agent Loop 是这个架构的灵魂。每次用户输入Agent 进入一个循环用户输入 │ ▼ ┌─────────────────────────────┐ │ LLM 决策 │ │ (调用工具 or 回复用户) │ └──────────┬──────────────────┘ │ ┌────┴────┐ │ │ 调工具 回复文本 │ │ ▼ ▼ 执行函数 显示给用户 │ │ ▼ │ 结果回传──────┘ (继续循环)关键实现agent.pydefrun_agent_turn(client,user_input):client.add_message(user,user_input)toolsget_tool_definitions()for_inrange(MAX_TURNS):# MAX_TURNS 10 防止无限循环responseclient.send_with_tools(tools)msgresponse.choices[0].messageifmsg.tool_calls:# 执行每个工具调用fortool_callinmsg.tool_calls:resultexecute_tool(tool_call.function.name,args)client.add_tool_result(tool_call.id,result)# → 继续循环LLM 基于工具结果再次决策else:# LLM 返回文本本轮结束returnmsg.content为什么需要 MAX_TURNS没有上限的话LLM 可能陷入无限循环——比如连续调用get_working_diff10 次而不返回文本。MAX_TURNS10确保单轮用户输入最多自动执行 10 步操作超时后提示用户重新说明需求。2.3 Conversation History让 Agent 记住上下文Agent 需要记忆才能进行多轮对话。LLMClient内部维护一个messages列表classLLMClient:def__init__(self,api_key):self.clientOpenAI(api_keyapi_key,base_urlhttps://api.deepseek.com)self.messages[]# 整个会话的消息历史defadd_message(self,role,content):添加用户/助手/系统消息self.messages.append({role:role,content:content})defadd_tool_result(self,tool_call_id,content):添加工具执行结果roletoolself.messages.append({role:tool,tool_call_id:tool_call_id,content:content,})会话历史的结构messages [ {role: system, content: AGENT_SYSTEM_PROMPT}, {role: user, content: 当前在哪个分支}, {role: assistant, content: None, tool_calls: [...]}, # LLM 决定调工具 {role: tool, tool_call_id: ..., content: main}, # 工具结果 {role: assistant, content: 当前在 main 分支}, # 最终回复 {role: user, content: 帮我创建一个新分支 test}, ... # 持续增长 ]每次调用 API 时整个messages列表都发给 LLMLLM 因此知道之前说过什么、做过什么。2.4 tools.py工具注册中心tools.py是新增模块负责两件事工具定义返回 OpenAI Function Calling 格式的工具列表工具调度根据工具名找到对应的 handler 执行# 工具定义OpenAI Function Calling 格式defget_tool_definitions()-list:return[{type:function,function:{name:current_branch,description:查看当前所在的分支名,parameters:{type:object,properties:{},required:[]}}},{type:function,function:{name:create_branch,description:创建并切换到新分支,parameters:{type:object,properties:{name:{type:string,description:新分支的名称}},required:[name]}}},# ... 共 17 个工具]# 工具调度映射到实际函数defexecute_tool(tool_name,arguments,api_key,modeldeepseek-chat):handlers{current_branch:lambda:f当前分支{current_branch()},create_branch:lambda:create_branch(arguments[name]),switch_branch:lambda:switch_branch(arguments[name]),# ...}handlerhandlers.get(tool_name)ifnothandler:returnf[ERROR] unknown tool:{tool_name}returnhandler()这个调度模式的关键在于handler 返回的是字符串这个字符串会通过add_tool_result()回传给 LLM。LLM 看到结果后决定下一步做什么——是继续调工具还是回复用户。三、新增工具详解3.1 分支管理工具对应 Git 命令说明current_branchgit rev-parse --abbrev-ref HEAD查看当前所在分支list_branchesgit branch -a列出所有分支create_branch(name)git checkout -b name创建并切换到新分支switch_branch(name)git switch name切换到已有分支delete_branch(name, force)git branch -d/-D name删除分支区分 create 和 switch实战篇中create_branch同时做创建 切换。扩展篇新增switch_branch用于仅切换git switch更符合 Git 2.23 的推荐用法。3.2 代码审查与提交工具说明get_working_diff获取所有变更staged unstaged untrackedgenerate_commit_message根据 diff 生成 Conventional Commit messagecommit_changes(message)暂存所有变更并提交branch_diff(branch)查看某分支与当前分支的差异提交流程Agent 自动执行用户提交代码 Agent→ 调 get_working_diff → 看到修改了哪些文件 → 调 generate_commit_message → 得到 commit message → 回复你 变更内容 README.md | 10 生成的 commit message feat: 添加用户注册接口 是否提交(y/n) 用户确认 Agent→ 调 commit_changes → 提交成功这个流程体现了 Agent Loop 的核心价值一次用户输入触发 LLM 多次工具调用中间不需要用户干预。只有最终的确认步骤才需要用户参与。3.3 回溯与变更管理工具对应 Git 命令安全机制rollback_to_commit(hash, hard)git reset --soft/--hard hashsoft 模式需无未提交变更hard 直接执行会丢失变更force_reset(hash)git reset --hard hash无安全检查执行前必须用户确认discard_changes(path)git checkout -- path不可恢复需用户确认show_commit_log(count)git log --oneline -count只读操作无安全风险为什么分开 soft 和 hardrollback_to_commit的 soft 模式会检查是否有未提交的变更——因为 soft reset 保留工作区内容如果已有未提交变更会导致冲突。而 hard 模式--hard正是用来丢弃变更的所以即使有未提交变更也应该能执行。force_reset则完全不检查是一个我说了算的逃生舱。这在 LLM 的系统提示词里明确写了对于 destructive 操作force_reset、discard_changes、hard reset、force delete 必须告知用户后果并获得明确确认后再调用工具。3.4 Stash 暂存管理工具对应 Git 命令说明stash_push(message)git stash push -m message暂存当前变更工作区变干净stash_popgit stash pop恢复最近一次暂存stash_listgit stash list查看所有暂存记录Stash 是一个典型的多工具协作场景。用户说我临时切一下分支时Agent 应自动判断是否需要先 stash 帮我切到 main 看一下东西再回来 Agent当前分支有未提交的变更我先 stash 一下 → stash_push(temp before switching to main) → switch_branch(main) → ... 用户看完 ... 你好了切回来 Agent→ switch_branch(feature) → stash_pop()3.5 其他工具工具说明git_status查看仓库状态git status --short --branchmerge_branch(target, allow_uncommitted)合并分支默认检查未提交变更四、REPL 对话式交互的实现4.1 REPL 循环设计CLI 入口从 argparse 单命令改为主循环defmain():api_keyos.environ.get(DEEPSEEK_API_KEY)ifnotapi_key:print([ERROR] 请设置 DEEPSEEK_API_KEY)sys.exit(1)clientLLMClient(api_keyapi_key,modelargs.model)client.add_message(system,AGENT_SYSTEM_PROMPT)print(Git Agent 已启动输入 /exit 退出/help 查看帮助)whileTrue:user_inputinput(\n ).strip()ifuser_input/exit:breakelifuser_input/help:show_help()continueelifnotuser_input:continueelifuser_input/clear:# 清空历史但保留 system promptclient.messages[client.messages[0]]print(对话历史已清空)continueresponserun_agent_turn(client,user_input)print(f\n{response})为什么用/开头做命令避免与自然语言冲突。/exit、/help、/clear都是元操作不走 Agent Loop。其中/clear在长时间的对话后特别有用——上下文窗口满了会导致模型忘记早期对话。4.2 对话历史管理随着对话进行messages列表不断增长。两个问题Token 消耗增大每次 API 调用都发送全部历史上下文窗口溢出DeepSeek 是 64K 上下文窗口目前通过/clear手动清理。未来可以自动截断保留最近 N 轮对话Token 计数超过阈值时自动 summarize 历史4.3 保留 CLI 模式扩展篇保留了--no-repl参数可以用单条命令模式commit-agent --no-repl查看当前分支commit-agent --no-repl创建一个分支叫 test这在脚本化和集成场景下有用REPL 和 CLI 只是交互方式不同后端 Agent 逻辑完全复用。五、安全设计5.1 三层安全机制层级机制说明L1工具定义中的描述在 tool.description 中注明危险操作L2System Prompt 规则明确要求 LLM 在调用危险工具前先问用户L3函数内部安全检查合并/soft 回溯前检查未提交变更5.2 危险操作清单以下操作在 System Prompt 中被标记为需用户确认force_reset— 丢弃所有未提交变更discard_changes— 丢弃本地修改不可恢复rollback_to_commit的 hard 模式 — 回溯到历史版本delete_branch的 force 模式 — 删除未合并的分支5.3 状态检查以下操作在函数层面有安全检查操作检查条件通过后拒绝后merge_branchhas_uncommitted_changes()执行 merge返回错误提示rollback_to_commit(soft)has_uncommitted_changes()执行 reset --soft返回错误提示rollback_to_commit(hard)不检查直接执行 reset --hard—六、运行示例6.1 启动# 设置 API KeyWindows PowerShell$env:DEEPSEEK_API_KEYsk-xxxx# 启动 REPLcommit-agent6.2 会话示例Git Agent 已启动输入 /exit 退出/help 查看帮助 当前在哪个分支 当前分支main 创建一个分支叫 feature/login 已创建并切换到分支feature/login 查看最近的提交 提交历史 abc1234 initial commit 审查我的代码变更 Agent 调 get_working_diff 变更内容 login.py | 45 新增登录页面 api.py | 20 新增登录接口 生成 commit message Agent 调 generate_commit_message [Commit Message] typefeat titlefeat: 添加用户登录功能 body: - 新增登录页面邮箱密码 - 新增登录 API 接口 - 使用 JWT 进行身份认证 是否提交(y/n) y Agent 调 commit_changes 提交成功 /clear 对话历史已清空 切到 main 分支 已切换到分支main 合并 feature/login 合并成功 /help Git Agent 可用工具列表 - get_working_diff: 获取代码变更 - current_branch: 查看当前分支 - create_branch: 创建分支 - switch_branch: 切换分支 - merge_branch: 合并分支 - show_commit_log: 查看提交历史 - ...共 17 个工具 /exit 再见七、常见问题7.1 LLM 不调用正确的工具这是最常见的问题。可能的原因和对策原因对策工具描述不够清晰让 description 更具体比如加上先让用户确认后再调用工具名称不直观工具名应该让 LLM 一看就懂避免缩写重名或相似工具有歧义区分度低的工具合并或改名7.2 对话历史越来越长随着对话进行messages 列表持续增长导致API 调用变慢token 增加LLM 注意力分散历史过长解决方法/clear手动清理自动摘要历史高级功能需额外 LLM 调用7.3 Agent 陷入循环LLM 连续多次调用工具而不返回回复。MAX_TURNS10防止无限循环。7.4 Windows 编码运行前设置环境变量避免终端编码问题$env:PYTHONIOENCODINGutf-8八、与通用 AI 助手的对比读完本文你可能会想既然 Claude、DeepSeek、ChatGPT 这些通用 AI 助手也能读懂并执行 git 命令为什么还要专门写一个 Agent两种方案有本质差异适用不同场景。以下以本文的 Git Agent 与 Claude Code命令行 AI 助手为例对比8.1 优势对比对比维度Git Agent通用 AI 助手如 Claude Code工具编排内置 17 个 Git 工具LLM 自动选择、链式调用。一条「提交代码」可以触发get_working_diff→generate_commit_message→ 用户确认 →commit_changes共 4 步自动化流程需要每步都输出终端命令让你确认工具链不连续交互效率「帮我暂存、切分支、合并、再回来」—一句话触发 6 步操作中间不打断你通常每执行一个命令就问你要不要继续高频操作体验割裂确定性工具是硬编码的什么参数、什么返回值LLM 只能调这些不会跑偏可以做任何事包括不该做的需要你全程盯着专注度只做 Git 相关操作不会被带偏去写代码、查资料功能太多容易分心。你说「切到 main」它可能顺便分析起代码来无外部依赖只要有 DeepSeek API Key 就能运行离线可用需要联网且依赖特定平台可定制直接改 Python 代码加工具 10 分钟搞定你无法修改它的行为逻辑8.2 通用 AI 助手的优势对比维度Git Agent通用 AI 助手理解深度识别指令靠关键词匹配工具名 参数逻辑固定的场景没问题理解复杂语义「把上周三之后那个改了登录页面的提交回退掉」——能解析时间 范围 操作意图文件级操作只能执行 git 命令无法查看或修改文件内容不只能git diff还能直接读文件、改代码、查引用——「这个 commit 改了哪些函数帮我检查有没有漏调用的地方」上下文理解只能看到 git 命令的输出diff、status、log 等文本能看到整个项目的文件结构、多个文件的内容、git 历史给出综合分析能力边界只有 17 个工具超出就报unknown tool可以执行任何终端命令没有预设上限零配置需要自己部署、配 API Key、装依赖开箱即用不需要任何配置8.3 选择建议你的 Agent 遥控器一键开电视、调音量、换台快且准 通用 AI 助手 管家能分析「今晚看什么好」但调频道你得说一声什么时候用你的 Agent高频重复操作每天提交代码、切分支、合并形成肌肉记忆标准化流程团队统一的提交流程必须先 lint → 再测试 → 再提交有固定规则commit 必须符合 Conventional Commits、分支命名规范等团队共享写好一个 Agent团队所有人都能用行为一致什么时候用通用 AI 助手复杂一次性操作回滚到某个特定提交前先检查影响范围需要判断的任务分析多个分支的差异、审查代码质量、定位 bug跨领域操作不只改 Git还要改代码、查文档、调配置探索性工作不确定该做什么需要 AI 给建议8.4 也可以组合使用两者不是二选一的关系。实际工作流中完全可以结合日常开发 → 用你的 Git Agent 快速提交、切分支 遇到复杂问题 → 用通用 AI 助手分析影响范围、给建议 有了方案 → 回到 Git Agent 执行具体操作打个比方你用遥控器Agent换台看节目但不确定看什么时叫管家通用 AI过来推荐一下。两种工具各司其职。九、总结从实战篇到扩展篇发生了什么变化维度实战篇v1扩展篇v2工具数量1 个17 个交互方式commit-agent --stage对话式 REPL调用模式单次调用Agent Loop最多 10 轮对话历史无messages列表维护上下文架构文件5 个模块新增tools.py安全机制无三层防护 状态检查为什么自然语言比终端命令更好降低认知负荷不需要记住命令和参数直接说话就行组合操作一步到位一句话 多条 git 命令容错性强LLM 理解同义表达切到 main和切换到 main 分支都行有上下文Agent 记住之前做了什么不需要重复说明安全危险操作有确认机制不会像git reset --hard那样不可挽回项目的完整代码所有代码在git-commit-agent/目录下commit_agent/ ├── __init__.py ├── cli.py # REPL 入口 命令解析 ├── agent.py # Agent Loop 核心 ├── git_utils.py # 17 个 Git 操作封装 ├── llm_client.py # DeepSeek API 对话历史 ├── tools.py # 工具定义 调度 (NEW) └── prompts.py # 多工具系统提示词 tests/ ├── test_commit_agent.py # 46 个测试 quick_test.py # 快速测试脚本 (9 个测试)讨论完成该项目后读者可能会有疑问尽管我在文中写了当前的Agent与通用AI 助手的对比但实际上通用AI助手如Claude Code可以轻松完成当前Agent的开发这就让目前的工作显得非常无意义也让Agent开发显得有些没有价值但是真正的agent开发不是做这个层面的工作写几个tool definition 一个 system prompt就能跑的agent只能作为Agent的Hello World后续的内容还有很多包括基础设施的搭建安全与治理Agent能力的测试与评估此外还需要集成入现有的系统。这些东西不是写一个 system prompt 就能解决的。它们是工程问题需要一整套架构和持续的维护。因为还是有必须继续学习的。