Agent 开发(四)—— 扩展篇:功能扩展
2026/7/1 4:36:07 网站建设 项目流程

一、从终端命令到自然语言

1.1 传统 CLI 的痛点

在扩展之前,我们只有一条命令:

commit-agent--stage

这个命令只做一件事:生成 commit message 并提交。如果你想做其他操作——切换分支、查看日志、合并代码——你需要记住另一套命令:

gitswitch maingitlog--oneline-5gitmerge feature-xgitreset--softHEAD~1gitstash push-m"wip"

这里有几个问题:

  1. 记忆成本高:每个操作有独立的命令、参数、标志,你需要记住它们的具体写法
  2. 组合操作麻烦:想"先 stash 当前工作,再切换到 main 分支,合并 feature,再切回来 pop"——需要 4 条命令串起来
  3. 错误成本高git reset --hard敲错了就是数据丢失
  4. 上下文割裂:每条命令独立执行,没有"状态",没有"之前发生了什么"

1.2 自然语言交互的优势

把 Git Agent 从"单条命令"升级为"对话式助手"后,上述问题全部消失:

对比维度传统 CLI自然语言 Agent
学习成本需记忆命令和参数直接说话就行
组合操作多条命令手动串联一句话 = 多步操作
错误防护敲回车即执行,无回滚LLM 会先检查状态,危险操作需确认
上下文无状态Agent 记住对话历史,知道"之前做了什么"
模糊匹配参数写错就失败“切到 main” 和 “切换到 main 分支” 都理解

举个例子:

CLI 方式:

gitstash push-m"temp work"gitcheckout maingitpull origin maingitcheckout featuregitstash pop

Agent 方式:

> 帮我暂存当前工作,切到 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 Loop

2.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.pyagent.py中分离出来。

2.2 Agent Loop:思考→行动→观察

Agent Loop 是这个架构的灵魂。每次用户输入,Agent 进入一个循环:

用户输入 │ ▼ ┌─────────────────────────────┐ │ LLM 决策 │ │ (调用工具 or 回复用户) │ └──────────┬──────────────────┘ │ ┌────┴────┐ │ │ 调工具 回复文本 │ │ ▼ ▼ 执行函数 显示给用户 │ │ ▼ │ 结果回传──────┘ (继续循环)

关键实现(agent.py):

defrun_agent_turn(client,user_input):client.add_message("user",user_input)tools=get_tool_definitions()for_inrange(MAX_TURNS):# MAX_TURNS = 10 防止无限循环response=client.send_with_tools(tools)msg=response.choices[0].messageifmsg.tool_calls:# 执行每个工具调用fortool_callinmsg.tool_calls:result=execute_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_TURNS=10确保单轮用户输入最多自动执行 10 步操作,超时后提示用户重新说明需求。

2.3 Conversation History:让 Agent 记住上下文

Agent 需要"记忆"才能进行多轮对话。LLMClient内部维护一个messages列表:

classLLMClient:def__init__(self,api_key):self.client=OpenAI(api_key=api_key,base_url="https://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):"""添加工具执行结果(role='tool')"""self.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列表都发给 LLM,LLM 因此知道"之前说过什么、做过什么"。

2.4 tools.py:工具注册中心

tools.py是新增模块,负责两件事:

  1. 工具定义:返回 OpenAI Function Calling 格式的工具列表
  2. 工具调度:根据工具名找到对应的 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="",model="deepseek-chat"):handlers={"current_branch":lambda:f"当前分支:{current_branch()}","create_branch":lambda:create_branch(arguments["name"]),"switch_branch":lambda:switch_branch(arguments["name"]),# ...}handler=handlers.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 + untracked)
generate_commit_message根据 diff 生成 Conventional Commit message
commit_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 <hash>soft 模式需无未提交变更;hard 直接执行(会丢失变更)
force_reset(hash)git reset --hard <hash>无安全检查,执行前必须用户确认
discard_changes(path)git checkout -- <path>不可恢复,需用户确认
show_commit_log(count)git log --oneline -<count>只读操作,无安全风险

为什么分开 soft 和 hard?

rollback_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 --branch
merge_branch(target, allow_uncommitted)合并分支,默认检查未提交变更

四、REPL 对话式交互的实现

4.1 REPL 循环设计

CLI 入口从 argparse 单命令改为主循环:

defmain():api_key=os.environ.get("DEEPSEEK_API_KEY")ifnotapi_key:print("[ERROR] 请设置 DEEPSEEK_API_KEY")sys.exit(1)client=LLMClient(api_key=api_key,model=args.model)client.add_message("system",AGENT_SYSTEM_PROMPT)print("Git Agent 已启动(输入 /exit 退出,/help 查看帮助)")whileTrue:user_input=input("\n> ").strip()ifuser_input=="/exit":breakelifuser_input=="/help":show_help()continueelifnotuser_input:continueelifuser_input=="/clear":# 清空历史,但保留 system promptclient.messages=[client.messages[0]]print("对话历史已清空")continueresponse=run_agent_turn(client,user_input)print(f"\n{response}")

为什么用/开头做命令?避免与自然语言冲突。/exit/help/clear都是元操作,不走 Agent Loop。其中/clear在长时间的对话后特别有用——上下文窗口满了会导致模型忘记早期对话。

4.2 对话历史管理

随着对话进行,messages列表不断增长。两个问题:

  1. Token 消耗增大:每次 API 调用都发送全部历史
  2. 上下文窗口溢出: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 Key(Windows PowerShell)$env:DEEPSEEK_API_KEY="sk-xxxx"# 启动 REPLcommit-agent

6.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] type=feat title=feat: 添加用户登录功能 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 列表持续增长,导致:

  1. API 调用变慢(token 增加)
  2. LLM 注意力分散(历史过长)

解决方法:

  • /clear手动清理
  • 自动摘要历史(高级功能,需额外 LLM 调用)

7.3 Agent 陷入循环

LLM 连续多次调用工具而不返回回复。MAX_TURNS=10防止无限循环。

7.4 Windows 编码

运行前设置环境变量避免终端编码问题:

$env:PYTHONIOENCODING='utf-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_diffgenerate_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
安全机制三层防护 + 状态检查

为什么自然语言比终端命令更好?

  1. 降低认知负荷:不需要记住命令和参数,直接说话就行
  2. 组合操作一步到位:一句话 = 多条 git 命令
  3. 容错性强:LLM 理解同义表达,"切到 main"和"切换到 main 分支"都行
  4. 有上下文:Agent 记住"之前做了什么",不需要重复说明
  5. 安全:危险操作有确认机制,不会像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 就能解决的。它们是工程问题,需要一整套架构和持续的维护。因为,还是有必须继续学习的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询