
Function Calling 的工程化团队实践文档、测试和监控的标准一、每个工程师写的 Tool Schema 都不一样调试全靠猜当团队从 1 个人扩展到 5 个人后Function Calling 的工程质量问题集中爆发。A 把 Tool 的 description 写成查询订单B 写成根据订单号查询用户的订单详情C 写成order query function。同样功能的 Tool 有三种描述LLM 调用 A 的 Tool 时成功率 85%C 的只有 50%——因为描述太简略。更糟的是没有统一的测试标准。什么是Function Calling 成功是 LLM 正确选择了 Tool还是 Tool 返回了正确结果团队对此没有共识。功能上线后唯一知道Function Calling 出问题了的信息源是用户投诉。二、Function Calling 的三项工程标准三、落地方案标准一Tool 定义模板// Tool 定义规范必须遵循的模板 type ToolDefinition struct { // 命名: {verb}_{noun}全部小写下划线 // 示例: query_order, update_inventory, send_email Name string json:name // 描述模板必须包含四个部分 // 1. What: 这个 Tool 做什么 // 2. When: 什么时候应该调用 // 3. When NOT: 什么时候不应该调用 // 4. Error: 可能的错误场景 Description string json:description // 参数定义 Parameters ToolParameters json:parameters } // ✅ 符合规范的 Tool 定义 var QueryOrderTool ToolDefinition{ Name: query_order, Description: 查询指定订单的详细信息商品、金额、状态、物流。 适用场景 - 用户询问特定订单的状态 - 客服需要查看订单详情 - 退款时需要确认订单信息 不适用场景 - 不应用于查询退款记录请使用 query_refund - 不应用于修改订单请使用 update_order - 不应用于批量查询请使用 batch_query_orders 可能的错误场景 - 订单号不存在返回 ORDER_NOT_FOUND - 权限不足返回 PERMISSION_DENIED, Parameters: ToolParameters{ Type: object, Required: []string{order_id}, Properties: map[string]PropertyDefinition{ order_id: { Type: string, Description: 订单号格式为 ORD-YYYYMMDD-XXXXX, Pattern: ^ORD-\d{8}-[A-Z0-9]{5}$, Examples: []string{ORD-20260701-ABC12}, }, }, }, }标准二Function Calling 的测试体系// ✅ Function Calling 的测试用例 func TestFunctionCallingIntegration(t *testing.T) { tests : []struct { name string userQuery string expectedTool string expectedParams map[string]interface{} shouldFail bool }{ { name: 订单查询-正常, userQuery: 帮我查一下订单 ORD-20260701-ABC12, expectedTool: query_order, expectedParams: map[string]interface{}{ order_id: ORD-20260701-ABC12, }, }, { name: 订单查询-模糊查询, userQuery: 上周买的那件T恤到哪了, expectedTool: query_order, shouldFail: true, // 无订单号应该走其他逻辑 }, { name: 退款查询应选择正确Tool, userQuery: ORD-20260701-ABC12 退款了吗, expectedTool: query_refund, // 不是 query_order }, { name: 越权检测, userQuery: 把所有订单都取消, expectedTool: , // 应被安全拦截不调用任何Tool shouldFail: true, }, } for _, tc : range tests { t.Run(tc.name, func(t *testing.T) { // 1. 调用 LLM 生成 Tool Call toolCall : callLLMWithTools(tc.userQuery, allToolDefs) // 2. 校验 Tool 选择 if tc.expectedTool ! { assert.Equal(t, tc.expectedTool, toolCall.ToolName, Tool 选择不正确) } // 3. 校验参数 if tc.expectedParams ! nil { for key, expectedVal : range tc.expectedParams { actualVal, ok : toolCall.Parameters[key] assert.True(t, ok, 缺少参数: %s, key) assert.Equal(t, expectedVal, actualVal, 参数 %s 的值不正确, key) } } }) } }标准三Function Calling 的监控指标// Function Calling 专属 Prometheus 指标 var ( // Tool 调用总数按 Tool 名称 结果分类 toolCallTotal promauto.NewCounterVec( prometheus.CounterOpts{ Name: fc_tool_call_total, Help: Function Calling Tool 调用总数, }, []string{tool_name, status}, // status: success, params_error, select_error ) // Tool 调用延迟 toolCallDuration promauto.NewHistogramVec( prometheus.HistogramOpts{ Name: fc_tool_call_duration_seconds, Help: Tool 调用延迟含 LLM 推理 Tool 执行, Buckets: []float64{0.1, 0.5, 1, 2, 5, 10, 30}, }, []string{tool_name}, ) // Tool 参数错误类型分布 toolParamError promauto.NewCounterVec( prometheus.CounterOpts{ Name: fc_tool_param_error_total, Help: Tool 参数错误类型分布, }, []string{tool_name, error_type}, // error_type: missing_required, type_mismatch, invalid_enum ) // LLM 重新生成次数当 Tool Call 出错时让 LLM 重试 toolRetryCount promauto.NewHistogram( prometheus.HistogramOpts{ Name: fc_tool_call_retries, Help: Tool Call 出错后重试次数分布, Buckets: []float64{0, 1, 2, 3, 5}, }, ) )四、团队协作的分工后端工程师负责 Tool 的实现和 API 封装Prompt 工程师或产品经理负责 Tool 的 Description 和 Schema 定义这比写代码更需要语言表达能力质量工程师负责维护 Critical User Journey 的回归测试集至少 20 条核心场景SRE负责 Function Calling 的延迟和错误率监控关键认知Tool 的 Description 是人和 LLM 之间的接口它的质量和 API 文档一样重要。写 Description 不是写代码是需要文字表达和场景理解能力的——建议由最熟悉用户场景的产品经理来写由后端技术审核技术可行性。五、总结Function Calling 的工程化核心是三个标准Tool 定义模板命名规范 描述模板 参数 Schema测试体系单元测 Tool 选择 集成测端到端 回归测核心场景和监控指标调用成功率 参数错误率 延迟分布。Profile 驱动优化——每周查看哪个 Tool 的参数错误率最高优先改进它的 Description。最容易被忽视的是描述中的不适用场景——明确告诉 LLM不要用这个 Tool 做什么可以减少 30% 的误调用。