判断一个产品是不是真有 Agent,我会看一个很土的细节:模型说“我要查一下订单”之后,后台有没有发生一次能追踪的工具调用。如果没有,它多半只是在聊天框里描述行动。
普通问答是一问一答。Agent 多走几步:它读当前任务,决定下一步,调用工具,再根据结果继续。听上去简单,麻烦全在“继续”这两个字里。谁保存进度,谁限制权限,什么时候算完成,都得由程序说清楚。
先看这个循环
最小版本不需要复杂框架,大概是这样:
state = initialize(user_request)
while not state.finished and state.steps < MAX_STEPS:
decision = model.decide(state, available_tools)
if decision.type == "answer":
state.finish(decision.content)
elif decision.type == "tool_call":
result = runtime.execute(decision.tool, decision.arguments)
state.observe(result)
elif decision.type == "clarify":
state.ask_user(decision.question)
else:
state.fail("invalid decision")
return state.output
代码不长,真正要花时间的是 runtime.execute 和 state。模型可以提出“调用退款接口”,运行时还要检查用户身份、金额、参数、超时和确认状态。模型的提议不能直接变成生产动作。
模型、工具和运行时
我倾向于把这三块分开。
模型读自然语言,选择工具,整理答案。它很会处理模糊表达,但不该拿数据库密码,也不该自己拼一段 Shell 命令去生产机执行。
工具把外部能力收窄成几个具体动作,比如 search_orders、get_weather、create_ticket。每个动作都有固定参数、返回结构和权限范围。名字取得直白一点也没关系,模型需要知道它究竟会做什么。
运行时负责剩下那些不太显眼、出问题却很要命的事:循环次数、状态持久化、重试、幂等、日志和人工确认。以后换模型,通常不用把这些逻辑跟着重写。
别把状态藏在聊天记录里
Agent 每走一步,都要知道刚才做了什么。把整段聊天原样塞回模型当然省事,跑到十几轮后就开始混乱。至少应该单独保存这些内容:
- 用户目标和硬性约束,例如时间范围、预算、输出格式。
- 已经确认的事实,以及它们的来源。
- 已调用的工具、参数、结果和错误。
- 待完成的子任务和当前步骤预算。
状态必须有上限。最大步数、token、工具调用次数和总耗时都交给运行时控制。预算用完就停,并把已完成的部分和缺口交代清楚。让模型自己决定“再试最后一次”,很容易变成第五个最后一次。
停下来也是一次正确动作
搜索、查询、计算这些只读动作,可以让它自己跑。创建草稿要保留撤销办法。退款、删除数据、对外发信之类的动作,到了那一步就停下来等人确认。
这会牺牲一点“全自动”的观感,我觉得值得。Agent 的权限应该跟动作风险走,跟模型说话有多像人没关系。
从一个具体任务开始
第一版可以只做一件事:“根据内部文档回答售后政策,资料不够时列出缺少的信息。”给它搜索和生成待补清单两个工具就够了。答案要有出处,找不到政策就明说,不能拿常识补。
跑十几次之后再看轨迹:它搜了什么,为什么又搜一次,引用能不能回到原文,什么时候停。如果这几个问题还答不上来,先别急着加第三个工具。