智能工作流中上下文和工具如何分工
智能工作流经常在两个极端之间摇摆:把所有历史、表结构和原始数据塞进模型上下文,或者把每一个小动作都拆成工具调用。前者看似省步骤,后者看似可控;但两者都会在任务变长时暴露问题。更实用的目标不是让模型“看到一切”或“调用一切”,而是让它在正确的时机取得足够的信息,并把可验证的事实留在系统边界内。
上下文适合保存任务目标、权限范围、输出格式、当前计划和少量已经确认的事实。数据库查询、金额计算、时间处理、写入操作和外部系统访问,应由受控工具执行。这样做不是因为模型不能生成计算结果,而是因为这些操作需要确定输入、审计记录和明确的失败语义。
先按任务风险划边界
对于一次性文案生成,少量会话上下文可能已经足够;对于财务报表、工单更新或合规审查,模型不应凭记忆复述数据,更不应自己决定写入。工具需要在服务端重新校验身份和资源权限,不能因为模型在上下文里看过某个用户 ID 就允许查询。
工具返回的原始数据也不该无上限进入下一轮提示词。可以返回结构化摘要、分页结果或一份带权限的结果引用,模型需要更多细节时再请求指定字段。摘要必须注明来源、时间范围和不确定项,不能为了节省 token 把影响结论的条件压掉。最终给用户的数值或结论,最好能回链到工具结果和计算过程。
type ToolResult<T> = | { ok: true; data: T; sourceId: string } | { ok: false; code: 'NOT_FOUND' | 'FORBIDDEN' | 'TEMPORARY_FAILURE' } type MonthlySummary = { month: string total: string currency: string itemCount: number } async function getMonthlySummary(userId: string, month: string): Promise<ToolResult<MonthlySummary>> { // 省略:服务端鉴权、精确计算和审计记录 return { ok: true, data: { month, total: '0.00', currency: 'CNY', itemCount: 0 }, sourceId: 'report_x' } }这个接口示意了结果类型的边界,不代表所有工具都该返回摘要。文件搜索、批量导出和写操作会需要不同的错误码、分页或确认机制;关键是把结构与语义明确下来,而不是把一段自然语言错误信息交给模型猜。
工具粒度围绕业务动作,而非内部表结构
过细的工具会让模型为了回答一个问题不断往返:先找用户,再找订单,再找地址,再做计算。过粗的工具又容易拥有过大的权限,返回不必要的数据。好的粒度通常对应一个用户能理解、可以授权、可以审计的动作,例如“获取某月已授权账户的汇总”或“提交一份待确认草稿”。
定义工具时要限制输入范围、返回大小、调用次数和截止时间。模型对同一失败反复尝试时,调度层应基于预算、步骤和状态进展停止任务,而不是继续把错误塞回上下文。对于有副作用的工具,先生成草稿或预览,再由用户或规则确认,会比让模型一次完成所有动作安全得多。
上下文裁剪不能依赖字符数猜 token
不同模型和语言的 token 化方式不同,字符数只能做粗略容量预估。更可靠的是使用目标模型的计数器,或给提示词留出固定输出空间并在服务端记录实际用量。裁剪时应优先保留系统约束、当前任务状态、用户明确要求和最近相关事实;被移出的信息要么可通过工具重新取得,要么被压缩为带来源的摘要。
同时要警惕“摘要的摘要”逐渐偏离原始事实。任务跨多轮时,可将关键事实存成结构化状态,由代码更新;模型只负责解释、规划或提出下一步查询,而不负责维护唯一真相。对用户上传的文本和工具返回内容,也要把其中的指令视为数据,不能因为它出现在上下文中就提高权限。
用观测结果调整边界
上线后记录每类任务的提示词大小、工具次数、失败原因、重试、完成率和端到端耗时。若某类任务总是在同一组工具间往返,可能需要合并为一个受限的业务操作;若模型频繁要求同样的原始数据,可能需要改进摘要或索引。调整前用真实样本验证质量和权限边界,避免单纯为了少几次调用而扩大数据暴露范围。
上下文负责让模型理解当前任务,工具负责取得事实并执行受控动作。分工清楚后,系统既不会把所有数据押在一次提示词里,也不会陷入无意义的工具编排,后续优化才有可以衡量的方向。