ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

DeepSeek-Agent-Harness-2026终极指南-第7章第33节-生产级日志与调用留痕-退出时的调用总结:minmaxavg一网打尽

DeepSeek-Agent-Harness-2026终极指南-第7章第33节-生产级日志与调用留痕-退出时的调用总结:minmaxavg一网打尽 DeepSeek Agent Harness 2026终极指南 - 第7章第33节 退出时的调用总结min/max/avg 一网打尽第 31 节做了 CallRecord第 32 节做了 trace 落盘——调用数据在积累但程序跑完就关了你什么也没看到。这节用atexit注册退出钩子在程序正常退出或sys.exit()时自动打印一份量化总结报表。min/max/avg token、耗时分布、成功率——以后每次跑完 Agent控制台末尾就是一份完整的财务报告。本文导航atexit 是什么报表设计C 语言风格的格式化输出注册到 call_tracker异常退出也能打印吗完整实录小结atexit 是什么atexit是 Python 标准库的一员干一件事注册一个函数在程序正常退出时自动调用。不需要显式写finally不需要包在try/except里注册了就生效。importatexitdefgoodbye():print(\n 程序退出再见 )atexit.register(goodbye)print(主逻辑运行中...)# 程序结束时会自动打印 程序退出再见 atexit的工作机制是正常退出sys.exit()或者main()return→ 触发os._exit()硬杀→不触发这是唯一例外SIGTERM信号→ 不直接触发需要配合signal模块监听对我们的场景来说只要你不是kill -9强杀进程atexit都会在退出前执行我们的总结函数。Agent 跑完一轮正常退出、达到预算上限触发sys.exit()、甚至 CtrlC 中断——这些情况atexit都能兜住。报表设计C 语言风格的格式化输出要做量化总结先在call_tracker.py里加一个格式化输出方法# deep_pilot/call_tracker.py —— CallTracker 追加退出报表方法defprint_exit_report(self)-None:程序退出时打印量化总结报表——min/max/avg 一网打尽sself.summary()ifs.get(call_count,0)0:return# 助手函数安全格式化数字def_nf(v,fmt.1f):returnf{v:{fmt}}ifisinstance(v,(int,float))elsestr(v)print(f\n{*70})print(f DeepPilot 本次运行调用总结)print(f{*70})print(f 调用次数:{s[call_count]})print(f{*70})print(f{指标:20}{最小:10}{最大:10}{平均:10}{合计:10})print(f{-*20}{-*10}{-*10}{-*10}{-*10})pts[prompt_tokens]cts[completion_tokens]tts[total_tokens]ets[elapsed_seconds]print(f{输入 token:20}{_nf(pt[min]):10}{_nf(pt[max]):10}{_nf(pt[avg]):10}{_nf(pt[total]):10})print(f{输出 token:20}{_nf(ct[min]):10}{_nf(ct[max]):10}{_nf(ct[avg]):10}{_nf(ct[total]):10})print(f{总 token:20}{_nf(tt[min]):10}{_nf(tt[max]):10}{_nf(tt[avg]):10}{_nf(tt[total]):10})print(f{耗时(秒):20}{_nf(et[min],.2f):10}{_nf(et[max],.2f):10}{_nf(et[avg],.2f):10}{:10})print(f{*70})print(f 终止原因分布: {s.get(finish_reasons, {})})print(f 模型使用分布: {s.get(models_used, {})})print(f{*70}\n)这个报表的设计思路C 风格对齐用20、10等字段宽度做列对齐清晰直观。比 json 美观比纯文本紧凑。最小/最大/平均/合计四列这是量化分析的标准套路。min 告诉你最乐观情况max 告诉你峰值压力avg 告诉你稳态性能合计告诉你总成本。耗时不统计合计因为合计耗时没有意义每次调用是并行的还是串行的所以那一列留空。分布统计finish_reason 分布和 model 分布放在底部一眼看出有没有异常终止、有没有用错模型。注册到 call_tracker在call_tracker.py模块级别注册 atexit 钩子# deep_pilot/call_tracker.py —— 模块末尾追加importatexitimportsysdef_on_exit():程序退出钩子——打印总结 dump trace 记录trackerCallTracker()iftracker.call_count0:returntracker.print_exit_report()# 退出前把所有记录 JSON 到文件防重启丢失try:pathtracker.dump_all()print(f[DeepPilot] 调用记录已存档:{path})exceptException:print([DeepPilot] 调用记录存档失败,filesys.stderr)# 清理超量 tracetry:deletedtracker.prune_traces(max_count1000)ifdeleted:print(f[DeepPilot] 清理超量 trace 文件:{deleted}个)exceptException:passatexit.register(_on_exit)几个设计注意点在模块 import 时就注册。atexit.register放在模块顶层这样只要from deep_pilot.call_tracker import tracker这一行执行了退出钩子就挂上了。不需要任何额外配置。dump_all()在退出时调用一次。程序跑了几天几夜内存里的_records丢了就丢了。退出前 JSON 落盘到logs/records/下次启动可以 reload虽然目前没必要但留了这个接口。异常全部吞掉。退出钩子里报异常可能会导致退出流程卡住让用户看到一个莫名其妙的 traceback。每个可能失败的步骤用try/except包住fail silently。异常退出也能打印吗atexit覆盖大部分情况但有两个边界要注意CtrlC (KeyboardInterrupt) 会触发 atexit。Python 收到 SIGINT 后抛 KeyboardInterrupt → 栈展开 → atexit 执行 → 进程退出。所以 CtrlC 停掉 Agent 后你能看到总结报表。未捕获异常也会触发 atexit。程序如果因为一个没 catch 的异常崩溃Python 会先执行 atexit 回调再打印 traceback。这意味着即使 Agent 跑崩了你也能看到崩之前的所有调用统计——这对调试非常有价值。os._exit()不触发。这是唯一的例外。所以自己写代码不要用os._exit()退出用sys.exit()就好。importatexit atexit.register(lambda:print(atexit 执行了!))# ✅ 这些会触发 atexit:sys.exit(0)# 正常退出raiseSystemExit# 等同于 sys.exit()# CtrlC 中断 # KeyboardInterrupt# ❌ 这个不会触发 atexit:# os._exit(0) # 硬杀绕过了所有清理逻辑完整实录把以上代码写入call_tracker.py完整约 180 行然后模拟几次调用 退出cddeep-pilot uv run python-c # 模拟几次调用 from deep_pilot.client import client client.chat([{role: user, content: 11等于几}]) client.chat([{role: user, content: 用 Python 写 Hello World}]) client.chat([{role: user, content: 解释一下二分查找算法}]) # 程序正常结束 → atexit 自动触发 → 打印总结报表 输出尾部2026-09-12 15:00:01 | INFO | deep_pilot.client | 调用留痕 | call_idaaa111... | ... 2026-09-12 15:00:05 | INFO | deep_pilot.client | 调用留痕 | call_idbbb222... | ... 2026-09-12 15:00:10 | INFO | deep_pilot.client | 调用留痕 | call_idccc333... | ... DeepPilot 本次运行调用总结 调用次数: 3 指标 最小 最大 平均 合计 -------------------- ---------- ---------- ---------- ---------- 输入 token 6 14 9.3 28 输出 token 4 120 49.3 148 总 token 10 128 56.7 170 耗时(秒) 0.92 4.51 2.65 终止原因分布: {stop: 3} 模型使用分布: {deepseek-flash: 3} [DeepPilot] 调用记录已存档: logs\records\calls_20260912_150010.json三行调用日志之后自动打印了总结报表。不需要手动调任何打印函数——import 了 call_tracker 的那一刻退出总结就已经挂上了。再来试一下 CtrlC 场景uv run python-c from deep_pilot.client import client import time client.chat([{role: user, content: 一些复杂的推理问题...}]) # 这时候按 CtrlC print(这行不会执行) # 按 CtrlC输出尾部2026-09-12 15:05:01 | INFO | deep_pilot.client | 调用留痕 | call_idddd444... | ... DeepPilot 本次运行调用总结 调用次数: 1 指标 最小 最大 平均 合计 -------------------- ---------- ---------- ---------- ---------- 输入 token 18 18 18 18 输出 token 56 56 56 56 总 token 74 74 74 74 耗时(秒) 3.21 3.21 3.21 终止原因分布: {stop: 1} 模型使用分布: {deepseek-flash: 1} [DeepPilot] 调用记录已存档: logs\records\calls_20260912_150510.json Traceback (most recent call last): File string, line 5, in module KeyboardInterrupt看到没——traceback 之前总结报表已经打印出来了。崩了也不怕丢数据。小结atexit是零侵入的退出通知机制import 时注册一个函数程序退出自动调用不需要 try/finally 包全场。量化报表四列min/max/avg/total覆盖输入输出 token 和耗时。分布统计finish_reason model放在底部。CtrlC 也触发 atexitKeyboardInterrupt 会走正常的栈展开流程总结报表在 traceback 之前打出。退出前dump_all()防重启丢失内存中的记录序列化到logs/records/下次启动可 reload。prune_traces()防止 trace 文件无限增长保留最近 1000 次调用的 trace。DeepPilot v0.2 日志留痕体系完整闭环控制台日志 文件日志 CallRecord 留痕 trace 三件套 退出总结。下节预告报表能看 min/max/avg 了但每次都要眼睛看——数据多了肉眼找规律太累。下一节玩个花活把结构化日志喂给 DeepSeek 自己用一句自然语言做数据分析——“分析调用平均速度、最大最小、长度与速度的关系”让模型帮你挖规律。以后看日志就是跟 Agent 聊天。如果觉得本文对你有帮助欢迎点赞、收藏、关注三连本系列持续更新中关注不迷路~
返回列表