通过`ai.js`与`@ai-sdk`实现前后端tool注入与交互

示例代码摘取自开源项目:next-ai-draw-io

1. 总述

ai.js 中,工具(Tools)通过 streamText 配置的 tools 参数注入,分为两类:

  • 服务端工具 :配置包含 execute 异步函数,可在服务端自动执行并返回结果;
  • 客户端工具 :仅配置元信息(description/inputSchema),无 execute 函数,需前端执行后手动回传结果。

2. 工具注入(streamText 核心配置)

javascript 复制代码
// 服务端 streamText 调用(核心代码)
const result = await streamText({
  model: openai('gpt-4o'),
  messages: allMessages, // 对话历史
  // 注入工具
  tools: {
    // 1. 服务端工具:带 execute 函数
    get_shape_library: {
      description: "获取draw.io形状库文档", // 模型识别工具的描述
      inputSchema: z.object({ // 入参校验Schema(zod)
        library: z.string().describe("形状库名称,如aws4")
      }),
      // 服务端自动执行的函数,返回值直接传给模型
      execute: async ({ library }) => {
        // 服务端业务逻辑(文件读取/API调用等)
        const content = await fs.readFile(`./docs/${library}.md`, 'utf-8');
        return content; // 结果自动返回给模型,无需手动处理
      }
    },
    // 2. 客户端工具:仅元信息,无 execute
    display_diagram: {
      description: "在draw.io渲染图形(客户端执行)",
      inputSchema: z.object({
        xml: z.string().describe("draw.io的mxCell XML字符串")
      })
      // 无 execute 函数,需前端处理
    }
  },
  experimental_repairToolCall: async ({ toolCall, error }) => {
    // 修复模型返回的工具入参格式错误(如截断的JSON)
    if (error.name === 'InvalidToolInputError') {
      return { ...toolCall, input: jsonrepair(toolCall.input) };
    }
    return null;
  },
  onFinish: ({ totalUsage }) => {
    // 对话结束回调(记录token/日志)
  }
});

// 转换为前端可识别的流式响应
return result.toUIMessageStreamResponse({
  sendReasoning: true, // 返回模型推理过程
});

注解

  • tools 对象中,每个键为工具名称,值为工具配置;
  • inputSchema(zod 格式)用于校验模型传入的工具参数;
  • toUIMessageStreamResponse 是服务端与前端 useChat 通信的关键,格式化流式响应为前端可解析的结构(包含 toolCall/text 等字段)。

3. 前端工具交互(useChat 核心配置)

tsx 复制代码
// 前端 useChat 调用
const { messages, sendMessage, addToolOutput } = useChat({
  // 配置传输层,对接服务端 /api/chat 接口
  transport: new DefaultChatTransport({
    api: '/api/chat',
  }),
  // 模型触发工具调用时的回调
  onToolCall: async ({ toolCall }) => {
    const { id: toolCallId, toolName, input: toolInput } = toolCall;
    
    try {
      // 区分工具类型处理
      switch (toolName) {
        // 客户端工具:前端执行逻辑 + 手动回传结果
        case 'display_diagram': {
          const { xml } = toolInput as { xml: string };
          // 前端业务逻辑(如调用draw.io API渲染图形)
          await renderDiagram(xml);
          
          // 手动回传结果给模型
          addToolOutput({
            toolCallId: toolCallId, // 必须匹配模型返回的ID(关键)
            toolName: toolName,
            output: JSON.stringify({ // 结果需序列化为字符串
              success: true,
              message: '图形渲染成功'
            })
          });
          break;
        }
        // 服务端工具:前端无需处理,等待服务端自动执行
        case 'get_shape_library': {
          console.log('等待服务端返回形状库结果');
          break;
        }
        default:
          // 未知工具返回错误
          addToolOutput({
            toolCallId,
            toolName,
            output: '',
            error: `未知工具:${toolName}`
          });
      }
    } catch (error) {
      // 工具执行失败,回传错误信息
      addToolOutput({
        toolCallId,
        toolName,
        output: '',
        error: (error as Error).message
      });
    }
  },
  // 工具结果回传后自动触发模型继续回复
  sendAutomaticallyWhen: ({ messages }) => true,
  onError: (error) => console.error('聊天错误:', error)
});

注解

  • DefaultChatTransport:封装前端与服务端的通信逻辑,自动解析服务端返回的流式响应;
  • onToolCall:前端捕获模型工具调用指令的唯一入口;
  • addToolOutput:客户端工具结果回传的核心方法,toolCallId 必须与模型返回的 ID 一致(否则模型无法关联结果);
  • sendAutomaticallyWhen: () => true:确保工具结果回传后,模型自动生成最终回复,完成闭环。

二、服务端工具 vs 客户端工具:交互核心区别

维度 服务端工具(带 execute) 客户端工具(无 execute)
执行主体 服务端(Node.js) 前端(浏览器 / 客户端)
核心触发逻辑 AI.js 自动触发 execute 函数 前端 onToolCall 回调手动触发
结果返回方式 execute 返回值由 AI.js 自动封装并传给模型 前端调用 addToolOutput 手动回传结果
关键依赖 服务端环境(文件 / 数据库 / 后端 API) 前端环境(DOM / 客户端 SDK / 本地存储)
通信成本 无额外网络请求(服务端内部完成) 需 2 次网络请求(指令下发 + 结果回传)
核心约束 execute 需自行捕获异常,返回友好提示 toolCallId 必须与模型返回的 ID 严格匹配
典型场景 数据查询、文件读取、后端 API 调用 前端 UI 交互、本地操作、客户端 SDK 调用

三、核心流程梳理

1. 服务端工具流程

用户指令 → 服务端 streamText → 模型调用服务端工具 → AI.js 自动执行 execute → 结果自动传给模型 → 模型生成回复 → 前端展示。

2. 客户端工具流程

用户指令 → 服务端 streamText → 模型调用客户端工具 → 服务端通过流式响应下发 toolCall 指令 → 前端 onToolCall 触发 → 执行客户端逻辑 → addToolOutput 回传结果 → 服务端封装结果喂给模型 → 模型生成回复 → 前端展示。

相关推荐
想用offer打牌4 小时前
MCP (Model Context Protocol) 技术理解 - 第二篇
后端·aigc·mcp
崔庆才丨静觅5 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
KYGALYX6 小时前
服务异步通信
开发语言·后端·微服务·ruby
掘了6 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅6 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
爬山算法6 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端