通过`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 回传结果 → 服务端封装结果喂给模型 → 模型生成回复 → 前端展示。

相关推荐
一只小bit11 小时前
Qt 绘图核心教程:从基础绘制到图像操作全解析
前端·c++·qt·gui
还在忙碌的吴小二12 小时前
Go-View 数据可视化大屏使用手册
开发语言·后端·信息可视化·golang
哪里不会点哪里.12 小时前
什么是 Spring Cloud?
后端·spring·spring cloud
乾元12 小时前
绕过艺术:使用 GANs 对抗 Web 防火墙(WAF)
前端·网络·人工智能·深度学习·安全·架构
HWL567912 小时前
一个CSS属性will-change: transform
前端·css
Y淑滢潇潇12 小时前
WEB 作业 即时内容发布前端交互案例
前端·javascript·交互
比特森林探险记12 小时前
后端开发者快速入门react
开发语言·前端·javascript
树码小子12 小时前
Spring框架:Spring程序快速上手
java·后端·spring
李松桃12 小时前
python第三次作业
java·前端·python
农场主John12 小时前
Accelerate_deepspeed使用
pytorch·llm·deepspeed