Function Calling 如何安全调用业务 API
1. 对应简历段落
这篇文章对应简历中“在内部 AI 客服和业务辅助系统中,通过 Function Calling 将用户自然语言意图转换为结构化业务工具调用,安全接入保单、客户、产品、工单、保全等传统业务 API,并实现权限校验、参数校验、审批确认、脱敏审计和风险分级”的经历。
面试时这部分不要只说“模型可以调用函数”。真正有含金量的是:模型如何知道能调用哪些工具,工具参数如何校验,用户有没有权限,模型能不能越权查别人的保单,执行类接口如何二次确认,接口异常如何回到对话,以及调用记录如何审计追责。
可以把简历里的项目描述成:
在 AI 客服系统中设计工具调用编排层,将传统 Java 业务 API 封装为受控工具,供大模型通过 Function Calling 发起查询或辅助操作。工具层统一处理 schema 注册、参数绑定、身份透传、权限校验、风控拦截、结果脱敏、调用审计和人工确认,确保大模型只能在业务授权范围内调用接口。
这段经历的重点是把 Function Calling 从“模型能力”落成“企业可控的业务集成能力”。
2. 业务背景
内部客服系统里的很多问题都不能只靠知识库回答。比如用户问:“这个客户名下有几张有效保单?”“这张保单为什么续保失败?”“当前工单应该转哪个队列?”“能不能帮我生成保全变更材料清单?”这些问题需要访问实时业务系统。
传统做法是客服坐席在多个系统里手工查询。AI 接入后,希望坐席用自然语言提问,系统自动判断要查哪个接口,聚合结果后生成回答。这就是 Function Calling 的典型价值:模型负责理解意图和组织参数,后端负责执行确定性 API。
但是保险业务 API 很敏感。保单、客户、健康告知、银行卡、联系方式都属于敏感信息。一个普通坐席不能查全公司的客户,一个渠道人员不能看其他渠道保单,一个模型也不能因为用户说“帮我查张三身份证号”就绕过权限。
更复杂的是,有些接口是只读查询,有些接口会改变业务状态。查询保单摘要风险较低,创建工单风险中等,提交退保、变更银行卡、发起保全则风险很高。Function Calling 如果没有风险分级和确认机制,就会把自然语言入口变成一个危险的万能后门。
所以项目目标不是“让模型可以调用业务接口”,而是“让模型只能以受控、可审计、可回滚或可确认的方式调用允许的业务工具”。
3. 核心原理
Function Calling 的核心原理是把可调用能力描述给模型,让模型在对话中输出结构化调用请求。工具描述通常包含 name、description、parameters schema 和必填字段。模型根据用户问题选择工具,并生成参数 JSON。
但生产系统里不能让模型输出什么就执行什么。安全调用链路至少要分成五步。
第一,工具注册。每个业务 API 被封装成一个工具定义,包含工具名称、参数 schema、风险等级、权限点、是否需要确认、超时时间、限流策略、结果脱敏规则和审计类型。工具注册信息由服务端维护,不由前端或用户动态传入。
第二,模型选择。编排服务把当前用户可见的工具子集提供给模型。比如普通客服只能看到查询类工具,高权限主管才能看到部分工单处理工具。模型输出 toolName 和 arguments,但这只是“调用建议”。
第三,调用前校验。后端根据工具定义做 JSON schema 校验、业务参数校验、权限校验、数据范围校验、幂等校验和风控校验。比如 policyNo 必须属于当前机构或当前工单绑定客户;身份证号不能作为任意查询入口;批量查询要限量。
第四,执行与结果治理。工具执行时使用当前登录用户或系统服务账号加用户上下文调用传统 API。返回结果经过字段裁剪、脱敏、摘要化后再给模型。模型不应该拿到完整数据库对象。
第五,审计与反馈。记录谁在什么会话里、基于什么问题、调用了什么工具、传入什么参数、返回什么摘要、是否成功、是否确认。高风险操作还要记录审批人或确认动作。
可以把大模型理解为“意图解析器和对话编排器”,而不是“业务权限主体”。真正的权限主体仍然是登录用户和企业 IAM 体系。
4. 项目落地
在 Java 项目中,建议把工具调用层独立成一个 Tool Orchestrator 服务,位于 AI 编排服务和传统业务 API 之间。
工具定义可以用注解、配置文件或数据库注册。对于研发维护的内部工具,注解方式比较直观。例如在 Spring Bean 方法上标记 @AiTool,声明工具名称、描述、风险等级和权限点。系统启动时扫描注解生成工具 registry,再转换成模型需要的 function schema。
工具执行器要实现统一接口,例如 ToolExecutor.execute(ToolCall call, UserContext user)。执行器内部按工具名找到 ToolDefinition,再执行参数校验、权限校验、调用业务服务、结果脱敏和审计记录。
风险分级非常重要。可以把工具分成四类:
- READ_PUBLIC:只读公共信息,如产品配置、条款查询。
- READ_PRIVATE:只读客户或保单信息,需要数据权限。
- WRITE_DRAFT:生成草稿、创建待办、补充工单备注,需要确认但不直接改变核心交易。
- WRITE_TRANSACTION:提交保全、退保、银行卡变更等高风险交易,通常只允许生成办理建议或草稿,不允许模型自动提交。
对于查询类工具,模型调用后可以直接基于结果生成回答。对于写入类工具,应该进入“计划确认”流程:模型先生成操作计划,后端展示给用户确认,用户点击确认后由确定性代码执行。不要让模型在一次回答里既决定又执行高风险操作。
项目中还要处理接口风格不统一的问题。传统系统可能有 Dubbo、REST、SOAP、老 Spring MVC、存储过程代理。工具层应该屏蔽这些差异,对上只暴露统一 schema,对下适配不同调用方式。这样模型侧不会感知“这是保单系统还是工单系统”,只看到稳定工具。
5. 流程或伪代码
public ToolResult handleToolCall(ToolCall call, UserContext user, ConversationContext ctx) {
ToolDefinition def = registry.get(call.name())
.orElseThrow(() -> new ToolNotFoundException(call.name()));
if (!def.visibleTo(user)) {
audit.warn("tool_invisible", user, call);
throw new AccessDeniedException("当前用户无权使用该工具");
}
JsonNode args = schemaValidator.validate(def.schema(), call.arguments());
businessValidator.validate(def.name(), args, ctx);
PermissionRequest permission = PermissionRequest.of(
user.userId(),
def.permissionCode(),
args,
ctx.boundCustomerId(),
ctx.boundPolicyNo()
);
permissionService.check(permission);
RiskDecision decision = riskEngine.evaluate(def, args, user, ctx);
if (decision.needConfirm()) {
return ToolResult.confirmRequired(def.name(), args, decision.message());
}
if (decision.blocked()) {
return ToolResult.blocked(decision.reason());
}
Object raw = def.executor().execute(args, user, ctx);
Object safe = resultSanitizer.sanitize(def.resultPolicy(), raw, user);
audit.success(user, ctx, def, args, safe);
return ToolResult.success(safe);
}
模型侧拿到的不是原始 raw,而是 safe。比如保单查询接口返回身份证、手机号、银行卡、地址、健康告知,给模型的结果可能只保留保单状态、产品名称、缴费状态、续保提示,并把手机号变成 138****1234。
一次完整对话流程可以是:
- 用户问:“帮我看一下 P20260001 为什么续保失败。”
- 编排服务提供 queryRenewalFailure 工具给模型。
- 模型输出工具调用:policyNo=P20260001。
- 工具层校验用户是否有该保单数据权限。
- 调用续保系统查询失败原因。
- 返回脱敏后的原因码、失败描述和下一步建议。
- 模型生成客服可读回答。
- 审计日志记录调用链路。
6. 安全边界
第一,工具白名单边界。模型只能调用服务端注册的工具,不能构造任意 URL、SQL、RPC 方法名。工具名也不能从用户输入直接拼接。
第二,用户权限边界。工具执行必须绑定当前登录用户。即使模型认为需要查某保单,后端也要判断该用户是否有该保单的数据权限。
第三,参数边界。所有参数必须做 schema 校验和业务校验。比如保单号格式、日期范围、批量数量、客户 ID 与当前工单关系都要验证。
第四,结果边界。业务 API 返回的原始对象不能完整交给模型。需要按字段白名单裁剪,敏感字段脱敏,必要时只给摘要。
第五,动作边界。高风险写操作不能由模型自动完成。模型可以生成计划、草稿、材料清单,但最终提交要用户确认、业务规则校验或人工审批。
第六,审计边界。所有工具调用必须可追踪。出现争议时要能回答:是谁发起的,模型为什么调用,参数是什么,返回了什么,用户是否确认。
第七,模型边界。不要让模型掌握内部服务凭证。API key、数据库账号、RPC token 都应该只在后端执行环境中使用。
7. 常见坑
第一个坑是把 Function Calling 当成权限系统。模型会选择工具,但它不是可信主体,不能替代 IAM 和业务权限校验。
第二个坑是工具描述过宽。比如定义一个 executeSql 或 callApi 工具,看似灵活,实际等于给模型一个通用后门。工具应该细粒度、意图明确、参数有限。
第三个坑是把业务对象全量返回给模型。很多 Java DTO 里字段非常多,直接序列化会泄露敏感信息,也会增加 token 成本。
第四个坑是没有确认机制。写操作、外呼、短信、工单流转都可能产生真实影响,必须有人确认或有确定性审批规则。
第五个坑是异常处理粗糙。业务接口失败时不能把堆栈、SQL、内部错误码直接返回给模型或前端。应该转换成可解释的业务失败原因。
第六个坑是工具过多。一次给模型几十上百个工具,会降低选择准确率。应该按场景、权限和意图动态提供工具子集。
第七个坑是没有幂等。模型可能重试,前端也可能断线重连。如果创建工单、发送通知没有幂等键,就可能重复执行。
8. 面试追问
面试官可能问:Function Calling 和普通后端接口调用有什么区别?可以回答:普通接口由前端明确触发,Function Calling 由模型根据语义生成调用意图,但执行仍由后端确定性控制。
可能问:怎么防止模型越权?要强调工具可见性过滤、服务端权限校验、数据范围校验和审计,而不是只靠 Prompt。
可能问:高风险操作怎么处理?可以说按风险分级,查询可直接执行,写操作生成计划并要求确认,交易类接口通常不允许模型直提。
可能问:工具 schema 怎么设计?回答应包括参数类型、必填字段、枚举值、描述、示例、默认值限制和业务校验。
可能问:如何减少模型选错工具?可以讲工具命名清晰、描述聚焦、按意图动态裁剪工具、few-shot 示例、工具调用评测集。
可能问:工具调用失败怎么办?可以说把技术异常转成业务可读结果,让模型基于失败原因回答,必要时建议人工处理。
9. 推荐回答
推荐回答如下:
我们把 Function Calling 看成模型提出的“工具调用建议”,不是直接执行权限。所有工具都在服务端注册,包含 schema、风险等级、权限点、脱敏策略和审计策略。模型只能看到当前用户、当前场景下允许使用的工具子集。
当模型输出工具调用后,后端会做 JSON schema 校验、业务参数校验、用户权限校验、数据范围校验和风控判断。只读查询通过后可以执行,结果会按字段白名单脱敏后再给模型。写操作会先生成操作计划,要求用户确认或走审批,高风险交易不会让模型自动提交。
所以这个系统的关键不是让模型“能调接口”,而是把传统业务 API 封装成有权限、有边界、有审计的企业工具。模型负责理解意图,业务系统负责事实和动作,工具编排层负责安全。
这段回答能体现你对 AI 工程化边界的理解,也能让面试官看到你不是只会调 SDK。
10. 延伸学习路线
第一阶段学习 OpenAI 或主流模型的 Function Calling / Tool Calling 机制,理解 schema、tool choice、parallel calls、streaming tool call 等基本概念。
第二阶段学习后端安全:认证、鉴权、数据权限、字段脱敏、审计日志、幂等、限流、风控。这些能力决定 Function Calling 能否进入生产。
第三阶段学习 API 设计:如何把粗粒度内部接口封装成模型友好的工具,如何设计参数 schema,如何返回摘要化结果。
第四阶段学习工作流编排:对于写操作和长流程,要结合审批、确认、补偿、状态机,而不是让模型一次性完成。
第五阶段做一个 Java 项目实践:用 Spring Boot 封装几个业务工具,比如保单查询、产品查询、工单创建,接入模型 tool calling,并实现权限校验、脱敏和审计。这个项目非常适合面试展示。