资讯动态

FDE 实战:给 AI 加上 Tool Calling,让模型真正操作业务系统

发布时间:2026/9/25 22:19:20 来源:尧图企业网站定制
FDE 实战给 AI 加上 Tool Calling让模型真正操作业务系统专栏《AI FDE 实战从 Demo 到生产》第 11 篇 / 共 18 篇本篇目标让模型通过受控工具查询订单、形成工单草稿并由独立的人工确认端点完成本地模拟提交。本篇产物工具契约、有界 Responses 调用循环、SQLite 幂等提交逻辑、FastAPI 确认端点与失败路径自检。星河设备、订单、用户与工单均为虚构教学数据实验不会连接真实 CRM、发送邮件或创建外部工单。客服已经能够通过助手查询政策下一句话往往是“那你顺便把这个问题登记成工单吧。”从用户体验看这只是多了一句自然语言从系统责任看却发生了重要变化。之前的输出主要是信息现在的输出可能改变业务记录。助手说“已经提交”之后用户会期待工单号、处理状态和后续跟进。语言流畅不再足以证明任务完成。本篇以订单 SO-1042 的设备无法启动为例完成一个很小但完整的闭环查询可见订单生成待确认草稿展示具体内容用户确认后写入本地模拟工单。模型参与理解与草拟应用负责身份、授权、执行边界和状态。读者可以运行所有本地实验也能沿着明确接口扩展到真实系统。一、Tool Calling 不是让模型直接获得数据库权限工具调用的基本过程是应用向模型说明可用工具及其参数模型提出某个工具调用请求应用解析并验证请求执行自己控制的业务函数再把结果返回模型。模型随后可以组织回答或在允许的范围内请求下一步。这里最重要的主语是“应用执行”。模型产生的函数名和 JSON 参数只是一个需要处理的请求。它不能因为格式正确就自动成为数据库命令也不能因为用户说“我有权限”就改变服务端身份。执行者必须保留对工具白名单、参数与业务条件的控制。图 2模型提出调用应用验证与执行业务系统返回可核验结果。例如模型可以请求查询某个订单但应用要检查该订单是否属于当前租户、当前角色是否可以查看。模型可以整理工单摘要但创建工单之前应用还要确认用户批准的就是这份内容。工具调用让语言接上业务能力并没有替代原有权限与事务设计。OpenAI 的 Responses API 使用专门的函数调用输出项表达请求应用通过对应的工具结果项继续对话。本文采用这一接口形态避免把其他 API 的消息结构直接混用。参考OpenAI Function Calling 官方文档二、先画动作边界再写工具 Schema我们只给模型两个工具lookup_order查询订单draft_ticket生成草稿。正式确认函数confirm_ticket不进入模型工具列表而由用户界面的独立确认请求触发。这个划分让“提出内容”和“批准执行”在代码里也有清楚边界。只读工具也不是零风险。查询可能暴露客户信息消耗上游配额或者返回含有不可信文本的备注。因此只读路径仍然需要鉴权、结果裁剪和超时。它与写入工具的差别主要在业务副作用不意味着可以跳过所有检查。草稿工具虽然写入本地草稿表但不创建正式工单。用户界面必须显示“待确认”不能为了让演示顺畅而把它写成“已提交”。在本篇代码里草稿包含订单、摘要、失效时间和内容哈希正式工单则具有独立工单号并由确认端点返回。企业可以根据风险设计不同确认要求但不能把“是否确认”留给模型临时判断。某些低影响操作可以在明确授权下自动执行退款或大范围更新则可能需要额外审批。FDE 的工作是把业务决定落实为稳定的执行条件而不是每次依靠提示词猜测。三、工具接口应贴近用户任务而不是暴露内部通用能力一个叫作execute_sql的工具看起来灵活却把数据库结构、查询语法和权限风险一起交给了模型。相比之下lookup_order(order_id)清楚表达一个业务动作服务端可以固定返回字段、查询条件和错误行为读者也更容易测试它是否符合承诺。同样不需要给模型一个可以任意发送 HTTP 请求的通用工具来完成售后登记。真实业务里允许调用哪些系统、哪些路径、什么请求体应当由应用控制。范围越小模型选择越容易异常处理和审计也越清楚。工具描述应说明用途、必要输入、返回状态与副作用。不要只写“处理订单”因为模型无法据此判断它是查询、取消还是修改。草稿工具尤其要明确“生成待确认内容不提交正式工单”让模型和界面都围绕同一个状态解释结果。参数尽量使用用户任务中已经存在的业务标识。租户与当前用户不应该作为模型自由填写的参数它们由服务端上下文注入。若模型提供一个tenant_id应用应拒绝这个多余字段而不是把它视为用户有意切换租户的依据。四、严格 Schema 管结构授权逻辑管能否执行Responses 工具定义中可以明确启用严格模式并为对象设置允许字段与必填字段。本文的查询工具只接收一个字符串订单号草稿工具接收订单号和摘要。这样可以降低形状错误但服务端仍然进行自己的验证。lookup_tool{type:function,name:lookup_order,description:查询当前用户可见订单不改变业务数据。,strict:True,parameters:{type:object,properties:{order_id:{type:string}},required:[order_id],additionalProperties:False,},}即使订单号是合法字符串也可能属于另一个客户即使摘要长度符合要求也可能包含用户没有批准的承诺。结构约束不等于业务正确性更不等于访问授权。把这两类责任混为一谈是工具调用系统里很容易出现的误解。严格输出也不能消除网络错误、响应不完整或模型拒绝等情况。应用必须检查实际返回状态并对无法继续的结果提供明确出口。本文循环在模型响应未完成时返回不可用状态不会把空文本当成任务已经完成。参考OpenAI Structured Outputs五、订单查询先证明“看得见”再返回最少必要信息本篇使用UserContext保存租户、用户和角色。查询 SQL 同时限定租户与订单号只返回订单号、产品和状态。为了演示越权路径数据库里还有另一个租户的 SO-2042。租户甲的客服查询它时与查询一个不存在订单得到同样的外部状态。为什么不告诉用户“订单存在但属于其他客户”因为存在性也可能是敏感信息。统一成“未找到或不可见”能避免接口成为枚举其他客户对象的工具。内部审计可以保留更具体原因但不必把它们直接交给用户或模型。返回值也不需要包含整行数据库记录。为当前任务选择必要字段减少模型接触无关个人信息也降低上下文成本。将来确实需要收件地址或联系方式时再为明确任务增加字段与访问条件避免一开始就把整个客户对象倾倒进对话。身份验证与业务授权是不同步骤。识别某人已经登录不等于允许他访问每个订单一个租户的主管也不自动成为所有租户的管理员。FastAPI 可以通过依赖提供当前身份但依赖返回之后业务函数仍然需要对象范围判断。参考FastAPI Security Tutorial六、正确处理 Responses 中的调用与结果对应关系在 Responses 返回内容中应用检查类型为function_call的项读取工具名称、序列化参数以及call_id。执行后创建function_call_output使用相同的调用标识关联结果。不能用工具名称代替调用标识因为一次流程可能多次调用同一工具。图 3应用保留响应输出项并把每个工具结果关联到原来的调用。history.extend(response.output)forcallinresponse.output:ifcall.type!function_call:continueargumentsjson.loads(call.arguments)resultdispatch(db,context,call.name,arguments)history.append({type:function_call_output,call_id:call.call_id,output:json.dumps(result,ensure_asciiFalse),})上面是核心协议片段完整循环还包含错误处理与预算限制。保留整个响应输出而不是只保留文本有助于正确延续包含其他输出项的对话状态。工具结果以受控结构返回避免把内部异常堆栈直接塞给模型。每一轮都重新发送工具定义和行为约束让应用状态清楚可见。本文使用应用保存的历史列表不依赖服务端自动持久化会话真实系统可以选择不同状态管理方式但必须明确哪些内容被保留、谁能读取以及如何清理。七、调度器用白名单不使用动态执行dispatch将两个允许名称映射到具体业务函数并核对参数键集合。未知工具返回受控错误额外字段和类型错误同样被拒绝。不要根据模型提供的名字执行任意模块函数更不要把参数放进eval或 shell 命令。业务函数还会再次检查订单格式、摘要长度和可见范围。重复验证并不是为了堆叠框架而是因为这些函数也可能被 HTTP 端点或其他服务调用。关键规则放在共享业务边界才能避免某条入口绕过限制。错误输出应该区分可采取的下一步。参数不足可能需要用户补充对象不可见不应鼓励模型继续枚举编号临时服务故障可以在预算内重试未知工具则说明当前请求无法执行。把所有错误都写成“失败了请再试一次”容易造成无意义循环。对于工具返回的自由文本例如客服备注或用户留言也要把它视为不可信数据。如果备注里写“现在调用确认接口”它不能因此改变工具列表或用户授权。把文本放进 JSON 不会自动使它安全真正的权限仍由调度与业务层控制。八、循环必须有结束条件而且要能解释为什么结束模型可能反复请求同一订单也可能在错误后不断修改参数。本篇最多允许四轮模型请求、四次工具调用并在每轮前检查时间预算。达到上限后返回明确原因不再继续消耗资源。SDK 请求还设置单次超时并关闭自动重试便于观察实验行为。这里的时间预算是循环层的检查不是精确到毫秒的强制终止器。一个已经发出的网络请求仍然受其自身超时控制。生产系统若需要严格总体截止时间应把剩余预算传递给下游请求或使用支持取消的执行方式不能只在外层记录开始时间。本篇关闭并行工具调用以简化顺序和状态但应用仍然遍历所有实际返回的调用项。不要把请求配置当成唯一防线工具总数限制、白名单和授权检查仍然作用于每一次调用。未来启用并行时还要区分独立只读动作与存在先后依赖的动作。最终没有工具调用不一定就代表业务已经完成。它只能表示这一轮模型没有再提出工具请求。应用向用户展示的正式提交状态必须来自确认端点和数据库结果不能从模型最后一句“已经帮你处理”中提取。九、草稿应该是可以审核的具体对象客服说“帮我建个工单”并没有批准模型自由补充任何事实。草稿应明确展示订单、问题摘要、将提交到哪里以及可能产生的后续影响。没有确认的故障原因不要写成事实没有查到的购买时间不要凭语言推断。本篇草稿保存一份不可变的规范化内容并计算内容哈希。用户看到这份内容后确认请求携带草稿标识与对应哈希。确认端点只使用服务器保存的内容不接受浏览器在同一请求里另传一个新摘要从而避免展示内容与实际提交内容不一致。图 4修改内容需要生成新草稿已确认结果通过工单标识核验。内容哈希不是身份凭据也不是万能签名。它帮助发现确认对象发生变化真正的访问限制仍来自会话身份、草稿所有者、租户和当前权限。攻击者即使知道哈希也不应该因此获得确认别人工单的权力。草稿还需要失效时间。用户上午打开确认卡片下午订单状态或权限已经变化原先适合的操作可能不再成立。本文设置教学用的十分钟有效期并在确认时重新检查订单可见性。真实有效期应根据业务变化速度和用户工作方式决定。十、人工确认必须是服务端可以区分的执行路径最薄弱的确认方式是让模型询问“是否确认”然后自己识别下一条“好的”再调用写入工具。对话里的确认可能指向不同内容也可能被无关文本干扰。如果系统声称需要人工确认就应把确认对象和身份绑定到明确请求。本篇提供POST /api/tickets/confirm只接受草稿标识、内容哈希和幂等键。模型工具列表里没有这个端点。实际集成时前端根据服务端返回的草稿对象展示卡片由用户点击按钮触发请求而不是让模型生成一段可执行的任意链接。本地 HTTP 实验使用公开固定的演示令牌把请求映射到两个虚构用户。这只是为了运行身份分支不能作为真实认证。任何知道令牌的人都能冒充该演示用户所以启动命令只绑定本机地址生产接入必须替换为经过验证的身份提供方。若真实应用采用浏览器 Cookie 会话还要根据认证方式处理跨站请求等问题并保持确认接口的访问控制。本文没有实现完整生产会话、安全表单或企业单点登录不能因为端点能返回工单号就直接将其公开部署。十一、幂等解决的是重复执行不是所有一致性问题用户连续点击两次按钮、浏览器重发请求、网关在超时后重试都可能把一次确认变成多次到达。应用需要识别这些请求属于同一个业务意图并返回同一个结果。本篇使用租户内的幂等键同时保存该键对应的草稿与内容指纹。如果同一个幂等键再次携带相同草稿和内容返回原工单号如果它被拿来确认另一份草稿返回冲突。不能仅凭“这个键出现过”就直接返回旧结果否则用户可能把不同请求错误合并甚至收到不属于当前确认内容的响应。数据库还为草稿与正式工单建立唯一关系。即使客户端换了一个幂等键同一份有效草稿也不会创建第二张工单。两层约束分别守住请求重放与同一业务草稿重复提交避免把全部责任压在前端按钮禁用上。已经成功确认的同键重放即使发生在草稿失效时间之后也可以返回已有结果因为没有发生新的业务执行。但仍需重新检查当前身份和对象可见性。相反一份从未确认过的过期草稿应被拒绝要求重新生成并审核。十二、事务把检查和写入放在同一个受控范围如果应用先查“没有工单”再在另一个事务里插入两次并发请求都可能看到不存在然后各自创建一条记录。唯一约束可以阻止部分重复但应用仍需要正确处理冲突并把幂等记录与工单结果一起提交。本篇 SQLite 确认逻辑使用BEGIN IMMEDIATE在事务中读取草稿、检查当前权限、核验内容、读取幂等记录最后写入工单和幂等映射。任意步骤失败就回滚。这个小实验选择了容易解释的写入序列不追求高并发吞吐。参考SQLite Transactions数据库事务只覆盖这个本地数据库。若真实工单位于第三方 CRM先调用外部接口再写本地记录可能遇到外部成功而本地失败先写本地又可能遇到外部失败。此时需要结合外部幂等能力、状态查询、任务表或事务发件箱等机制设计恢复路径。尤其不要把超时直接解释成失败。请求可能已经被外部系统执行只是响应丢失。没有查明状态就重发写操作可能制造重复工单。可靠做法是使用稳定的业务请求标识查询或恢复必要时将状态标为待确认并交给人工处理。十三、让界面显示业务事实而不是模型口吻聊天文本可以解释结果但状态卡片应由结构化业务结果驱动。查到订单显示订单字段生成草稿显示待确认内容确认成功显示工单号结果未知显示正在核实或需要人工介入。不要根据文本里是否出现“成功”来改变按钮或流程状态。确认成功后用户应能查看当时提交的内容和对应工单标识。如果后续允许编辑工单要把编辑作为新的业务动作处理不能悄悄修改原先被确认的快照。否则审计人员无法解释用户究竟批准了什么。错误信息同样应服务于下一步。草稿过期时提供重新生成入口内容变化时要求再次审核当前权限不足时说明无法继续业务服务不可用时保留草稿并避免宣称提交完成。不同错误对应不同操作简单弹出“系统错误”只会诱使用户不断点击。把模型的自由文本限制在它擅长的解释范围把关键状态交给业务结果是一种很实用的架构分工。它让模型偶尔使用过于积极的措辞时界面仍然有明确的事实依据也方便客服与工程人员共同排查问题。十四、运行本地实验观察失败路径完整代码在配套目录中。先运行纯标准库的领域实验和工具循环实验再运行依赖 FastAPI 的 HTTP 检查。三个脚本使用临时 SQLite 文件进行自检完成后自动清理不会碰用户现有数据库。cdoutputs/11-tool-calling/code python ticket_lab.py python test_loop.py python test_http.py实际运行结果为领域逻辑十一项检查通过离线工具协议与有界循环九项检查通过HTTP 身份与确认边界五项检查通过。它们覆盖正常查询、跨租户不可见、草稿不产生正式工单、内容变化、过期、重复确认、幂等键冲突、空响应以及角色撤销等分支。图 5正常路径证明可以工作失败路径证明哪些操作必须停止。离线循环使用固定响应对象模拟模型输出只能验证程序如何处理协议不证明真实模型一定选择正确工具。真实 Responses 示例保存在responses_loop.py需要配置自己的服务端密钥与可用模型后运行。本文交付时没有发起这条真实模型调用。本地交互端点可以通过uvicorn app:app --host 127.0.0.1 --port 8011启动。它会在练习目录创建模拟数据库使用方法见配套说明。不要把这套公开演示令牌带到生产也不要把模拟工单号当作真实企业系统的提交凭证。十五、把它接回售后助手时接口应该如何组合第 07 篇的聊天接口已经有回答、状态、引用和追踪标识。本篇工具循环可以成为聊天服务内部的一个组件服务端取得可信身份后传入上下文模型查询订单或生成草稿返回时将草稿对象作为明确的附加结构交给界面。RAG 与订单工具负责不同事实。政策检索解释申请材料与适用条款订单查询提供订单状态和产品信息。不要让模型把检索到的样例订单当作当前订单也不要把订单备注当作正式政策。每类结果保留来源与用途能降低信息混淆。本文提供的是独立可组合实验尚未把所有文件自动接入前面章节的应用。集成时至少需要统一错误映射、身份依赖、追踪标识和数据存储并为聊天到确认的完整路径增加端到端检查。仅把函数复制过去不能代表整个流程已经验收。工具数量增加时先按业务任务控制可用集合而不是一次性暴露所有内部接口。当前只处理售后登记就没有必要让模型同时看到批量导出和退款操作。范围清楚的工具集更容易评估也使权限变更能够落实到具体动作。十六、审计需要记录什么才足以解释一次提交一次正式写入至少需要关联发起用户、租户、草稿标识、被确认内容、确认时间、幂等键与业务结果。模型调用和工具调用可以通过追踪标识关联但不要把模型文本当作唯一审计记录。它可能省略关键字段也可能使用与真实执行不一致的措辞。图 6确认内容与正式结果之间建立稳定关联才能解释和处理争议。本篇数据库保存草稿、工单和幂等映射足以演示对象之间的关系但没有实现完整的不可变审计事件表。生产系统还需要根据企业要求记录操作时间、结果类别和必要上下文并限制审计记录的访问与修改权限。日志有内容不等于已经满足审计要求。敏感内容不宜在每个链路日志里重复保存。可以保留结构化标识、状态、耗时和错误类别原始工单内容放在受控业务存储中。调查人员通过授权方式关联查看避免调试平台意外成为另一套客户信息数据库。还要记录“没有执行”的原因。越权被阻止、草稿过期、内容变化和幂等冲突都是证明控制生效的事件。只记录成功操作会让团队无法区分用户没有尝试、系统正确拒绝与请求根本没有到达。十七、实战练习主动制造一次重复与一次内容变化先创建草稿并复制确认请求连续发送两次检查两个响应里的工单号是否一致。再更换幂等键重复确认同一草稿核对数据库正式工单数量仍然为一。这个练习可以直接看见数据库约束比单纯禁用按钮更可靠的地方。接着把确认请求中的内容哈希改成另一个字符串观察接口拒绝。再生成一份新摘要却复用旧幂等键观察冲突。两种失败看起来相似但原因不同前者确认对象发生变化后者把不同业务请求错误绑定到同一个重放标识。第三个练习使用另一个租户的身份确认草稿。即使你拥有正确草稿标识和哈希也应被拒绝。随后去掉用户角色再尝试查询和确认检查共享业务函数是否对两条入口保持一致。如果只在聊天接口检查角色直接调用确认端点就可能绕过限制。最后让模拟模型持续请求查询观察循环在预算上限停止并保留清楚的结束原因。尝试返回未知工具或无法解析的参数确认它们不会变成任意代码执行也不会让系统无限等待。可靠的工具调用不仅会走通成功分支也知道何时停下来。FDE Thinking行动能力越强越需要清楚的责任归属为什么不把确认工具直接交给模型因为这个教学阶段的业务约定是人工审核后提交。把写入工具排除在模型能力之外能够让约定成为可检查的程序边界而不只是提示词里的愿望。未来改变自动化范围时也必须先改变业务授权与验收条件。为什么保留草稿对象而不只显示一段聊天文本因为对象可以绑定版本、所有者、内容和结果可以做失效、重放和审计。聊天文字适合解释却难以独立承担这些状态管理责任。具体对象使用户确认的东西与系统执行的东西保持一致。为什么明明是 AI 教程却花很多篇幅讨论事务和幂等因为用户的工作结果发生在业务系统里。大模型让输入方式变得灵活但没有消除分布式失败、重复请求和权限变化。能够把这些普通而关键的工程问题处理清楚才是 AI 从演示走向交付的基础。到这里售后助手已经拥有两类能力检索知识并通过受控工具处理业务任务。下一篇将讨论什么时候需要 Agent以及如何用有界工作流组织这些能力。先守住每个动作的输入、授权和结果再谈更复杂的自主执行系统才有可理解的成长路径。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑