项目实践笔记:打卡 / 日报 Agent 的代码实现,把「输入边界」落到 Function Calling
前言:
上一篇写了输入分类 × Tool 决策对照表,把哪些输入允许触发 Function、哪些必须兜底先定下来。
这一篇只写实现层:
怎么把那张表,翻译成可跑的代码结构。
一、拆成两个文件:main.py + tools.py
没有一上来就引入框架、消息队列、数据库之类的东西,原因很简单:
v1.0 的目标不是“做大”,而是“可控 + 可回放 + 可测试”。
所以只保留两个角色。
1)main.py:对话调度员(串流程,不做业务)
main.py 只负责三件事:
-
接收用户输入
-
调一次模型,让模型决定 要不要调用工具
-
如果模型调用了工具,就执行工具,再把结果回灌给模型,让它回一句“简短确认”
也就是说 main.py 的核心价值不是“聪明”,而是:
把流程写死,把行为收敛。
2)tools.py:工具箱(能做什么 + 怎么做)
tools.py 负责两块:
-
工具定义(JSON Schema):告诉模型“你能调用哪些工具、参数是什么”
-
工具实现(确定性动作):真正落到文件存储/状态更新
我把工具限定为 5 个(对齐第一篇的分类):
-
record_fragment:写入干净事实碎片
-
get_fragments_by_date:查询某天碎片
-
confirm_clock_event:记录一次打卡确认
-
mark_clock_timeout:记录一次打卡超时
-
get_clock_status:查询打卡状态
关键的一点是:
tools.py 里没有“写日报/写用例”这种生成类工具。
v1.0 宁可不做,也不让系统越权。
二、代码结构背后的一个核心取舍:AI 不做决定
很多人做 Agent 喜欢把 AI 放在最核心位置:
让模型自己决定“记什么、怎么记、写成什么样”。
但我在 v1.0 选择另外的方式:
AI 只负责“识别输入类型”和“触发哪个工具”,
真正写入/查询动作必须走工具。
原因也很现实:
-
真实输入里有情绪、有吐槽、有半句话
-
AI 很容易把“像工作内容”的句子当事实写入
-
一旦脏数据进了碎片层,后面总结全都变脏
所以我更看重“可控性”:
允许 AI 参与决策,但不允许 AI 直接动数据。
三、闭环怎么跑:一次输入的一整条链路
v1.0 的主链路非常固定:
-
用户说一句话
-
模型决定是否要调用工具(tool_choice=auto)
-
如果调用:执行工具
-
把工具结果回灌给模型
-
模型再回一句“确认/查询结果”,而不是长篇总结
这条链路的好处是:
-
你能清楚地看到:模型“为什么会触发这个工具”
-
工具执行是确定的:写入就是写入,查询就是查询
-
系统出错也好定位:是模型误判?还是工具参数?还是兜底没挡住?
四、 reminders:做的三条“从严限制”
这三条限制其实比代码更重要,它们决定了 v1.0 的稳定性。
1)只收“干净事实”,情绪/吐槽一律不入库
像下面这种输入,在真实工作里非常常见:
-
写用例了,烦死了
-
今天好像没干啥
-
开发接口不行
它们“真实”,但不“干净”。
v1.0 我选择 宁可不记,也不污染事实层。
2)查询 ≠ 生成
“我今天都干了啥” → 只触发查询工具,把事实拿出来
“帮我写日报/写用例” → 直接兜底,不给生成能力
3)main.py 不做业务判断
不在 main.py 里堆“规则 if else”,保持它只管流程。
业务动作都收敛到工具上,这样后面测试也更清晰。
五、这一步的收获:代码很少,但行为边界很清楚
到这里,v1.0 其实已经能用:
-
白天随口记一句“工作事实”
-
下班前做“确认/查询”
-
需要回顾时再查当天碎片
现在主要的不是“功能多”,而是:
系统不会因为真实输入的复杂性而乱触发工具。
下一篇我会写怎么做“冒烟测试”:
不是写复杂测试框架,而是用一组真实工作输入去撞系统,看看它会不会失控。
更多推荐


所有评论(0)