文章链接:https://blog.csdn.net/qq_43201350/article/details/163399083
一、AgentTool Use 是什么?
通俗解释:用户用自然语言说"查一下我上个月的工单",AI 不是生成一段文字建议你去哪里查,而是 自己动手查数据库,然后把结果用自然语言告诉你 。
技术上的核心是一个循环:
这个循环里最关键的不是 AI 能力,而是中间层的 工具注册、权限控制、参数校验 ——三个纯工程化问题。
二、工具注册:12 个业务工具的元数据管理
先定义每个工具的描述,让 AI 知道什么时候该调哪个:
interface ToolDefinition {
name: string
description: string // AI 靠这段文字理解工具用途
parameters: {
type: 'object'
properties: Record<string, { type: string; description: string }>
required: string[]
}
type: 'read' | 'write' // 读操作 / 写操作
roles: string[] // 哪些角色可以调用
}
const tools: ToolDefinition[] = [
{
name: 'queryWorkOrder',
description: '根据房号查询该户主的所有维修工单。返回工单列表,包括工单号、状态、创建时间',
parameters: {
type: 'object',
properties: {
houseId: { type: 'string', description: '房号,如 3-501' }
},
required: ['houseId']
},
type: 'read',
roles: ['finance', 'customer_service', 'manager', 'supervisor']
},
{
name: 'createWorkOrder',
description: '创建一个新的维修工单。需要提供房号、工单类型和问题描述',
parameters: {
type: 'object',
properties: {
houseId: { type: 'string', description: '房号' },
type: { type: 'string', description: '工单类型:water/electric/carpentry/gas/other' },
description: { type: 'string', description: '问题描述' }
},
required: ['houseId', 'type', 'description']
},
type: 'write',
roles: ['customer_service', 'manager']
},
// ... 其余 10 个工具类似定义
]12 个工具覆盖了完整业务:
-
查询类(6个) :工单查询、工单列表、财务账单、缴费记录、住户信息、投诉进度
-
操作类(6个) :创建工单、催缴通知、派发工单、修改排班、系统配置、数据导出
为什么要区分"读"和"写"?
这是整个系统安全的最后防线。读到脏数据可以重试,写到错误数据可能造成生产事故。所以设计规则就是:
读操作 → 自动执行 → 直接返回结果
写操作 → 生成预览 → 用户确认 → 执行
三、Agent 循环:完整代码实现
async function agentLoop(
message: string,
role: string,
history: Message[]
): Promise<AgentResponse> {
// 1. 获取该角色可用的工具白名单
const availableTools = tools.filter(t => t.roles.includes(role))
// 2. 构建请求,带上工具定义
const response = await aiClient.chat({
messages: [
{ role: 'system', content: getSystemPrompt(role) },
...history,
{ role: 'user', content: message }
],
tools: availableTools,
tool_choice: 'auto' // AI 自己判断是否需要调工具
})
// 3. 判断 AI 是否要调工具
const toolCalls = response.tool_calls || []
if (toolCalls.length === 0) {
// 纯文本回复,直接返回
return { type: 'text', content: response.content }
}
// 4. 处理工具调用
const results: ToolResult[] = []
for (const toolCall of toolCalls) {
// 4a. 权限二次校验(防止 AI 绕过角色限制)
const toolDef = tools.find(t => t.name === toolCall.name)
if (!toolDef || !toolDef.roles.includes(role)) {
results.push({
tool_call_id: toolCall.id,
error: '你没有权限执行此操作'
})
continue
}
// 4b. 参数校验
const validationError = validateParams(toolCall.params, toolDef.parameters)
if (validationError) {
results.push({
tool_call_id: toolCall.id,
error: `参数错误: ${validationError}`
})
continue
}
// 4c. 写操作需要用户确认
if (toolDef.type === 'write') {
return {
type: 'confirm',
tool_name: toolCall.name,
params: toolCall.params,
message: `即将执行【${toolDef.name}】操作,请确认参数:\n${
JSON.stringify(toolCall.params, null, 2)
}`
}
}
// 4d. 读操作自动执行
const data = await executeTool(toolCall.name, toolCall.params)
results.push({
tool_call_id: toolCall.id,
output: JSON.stringify(data)
})
}
// 5. 把工具执行结果喂回 AI,让它生成最终回答
const finalResponse = await aiClient.chat({
messages: [
...response.messages,
{ role: 'tool', content: results }
],
tools: [] // 第二轮不允许再调工具,防止死循环
})
// 6. 判断是否需要渲染数据卡片
const card = buildCard(finalResponse.content, results)
return {
type: card ? 'card' : 'text',
content: finalResponse.content,
card: card
}
}有三个关键设计值得展开:
设计 1:权限二次校验
AI 模型可能绕过SystemPrompt的限制。比如财务角色 Prompt 里写了"不能创建工单",但 AI 有时候会"自作主张"。所以代码层面做二次校验:
if (!toolDef.roles.includes(role)) {
// 不管 AI 怎么想,后端不允许就是不允许
}这是大模型应用开发的黄金原则: 不要把业务安全寄托在 Prompt 上,代码层面的权限校验才是最后防线。
设计 2:读操作自动执行 vs 写操作确认
一个真实的交互场景:
用户:"帮我在 3-501 创建一个水路维修工单"
AI:返回 tool_call: createWorkOrder({ houseId: "3-501", type: "water" })
系统:→ 生成确认卡片
┌─────────────────────────────┐
│ 确认创建工单 │
│ 房号:3-501 │
│ 类型:水路维修 │
│ [确认创建] [取消] │
└─────────────────────────────┘
而查询操作不需要这个步骤——“查我上个月的物业费”,直接出结果就行。
设计 3:防止死循环
第二轮调用时 tools: [] ——AI 不能再调用新工具了。防止这种无限循环:
用户:"你好" → AI调用工具A → 结果有问题 → AI调用工具B → 又出问题 → AI调用工具A ...
生产环境中可以放宽到最多 3 轮工具调用,但必须有上限。
四、前端渲染:卡片 vs 纯文本
工具调用返回的结构化数据,前端需要根据类型渲染不同组件:
function buildCard(toolResults: ToolResult[]): CardData | null {
const lastResult = toolResults[toolResults.length - 1]
// 工单查询 → 工单卡片
if (lastResult.type === 'work_order') {
return {
component: 'WorkOrderCard',
props: { orders: lastResult.data }
}
}
// 财务查询 → 账单汇总卡片
if (lastResult.type === 'bill') {
return {
component: 'BillSummaryCard',
props: { bills: lastResult.data }
}
}
return null // 纯文本回复,不渲染卡片
}比如查物业费的结果不是一串文字,而是一张带样式的账单卡片——这比纯文本回复的体验好太多。
五、与RAG互补的两个系统
做完整套 Agent 系统后,我意识到它和 RAG(检索增强生成)是互补关系:
| Agent Tool Use | RAG | |
|---|---|---|
| 解决的问题 | AI 操作业务系统 | AI 查询知识库 |
| 数据来源 | 数据库实时查询 | 文档库向量检索 |
| 典型场景 | “查我的工单”“创建报修” | “物业管理规定是什么”“停车收费标准” |
| 系统复杂度 | 高(需要工具注册+权限+校验) | 中(需要文档处理+向量库) |
一个完整的 AI 对话系统需要两者配合:用户问操作类问题走 Agent,问知识类问题走 RAG。
六、总结
AgentToolUse 把一个"会聊天的 AI"变成了"能干活的 AI"。三个核心工程问题:
-
工具注册 :用结构化元数据描述每个工具,让 AI 理解何时调用
-
安全控制 :角色白名单 + 读写分离 + 参数校验,三道防线
-
前端体验 :结构化数据渲染卡片,比纯文本回复更专业
如果你也在做 AI 业务落地,建议先从一个工具开始(比如"工单查询"),跑通整个循环后再扩展到更多工具。一口吃不成胖子,但"工单查询→自然语言回答"这条路跑通,你就理解了 Agent 的内核。