找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

969

积分

0

好友

123

主题
发表于 3 天前 | 查看: 8| 回复: 0

在构建企业内部AI工具生态的过程中,过去一年我们进行了大量的探索与迭代。

回顾整个演进路径,它清晰地呈现为一条从“编写代码”到“提供标准化能力”的链路:从编写简单的JS函数,到将其封装为Agent可调用的工具,再到通过MCP(Model Context Protocol)协议将其升级为跨平台通用能力。本文将沿着这条演进路线,详细拆解其背后的核心逻辑、遇到的挑战以及最终的工程化解决方案。

一、系统演进概述

我们打造了一个“在线JS云函数平台”,其核心目标是将编写一个JS函数的过程,转化为为AI赋予一项新工具能力的过程。该系统主要经历了三个阶段:

第一阶段: 最初仅是作为一个定制化组件,集成在Flowise的LangChain StructuredTool 框架中,供工作流内的Agent调用。

Flowise 是一款开源的可视化AI工作流构建工具,允许用户通过拖拽节点来快速搭建LLM应用或智能体。它底层基于LangChain的TypeScript实现,天然具备模型调用、工具调用、链式编排等能力。早期选择它作为AI工作流平台,主要看中其原生支持TypeScript,并且可视化界面能极大加速AI应用原型的验证。

第二阶段: 演进为一个独立的浏览器端云函数IDE,支持在线编写代码、调试、沙箱执行,并能安全地调用内网的gRPC、HTTP接口及MySQL等数据源。

第三阶段: 接入MCP协议,使平台上的工具能够被任何支持MCP的AI Agent或开发工作室(如Claude Desktop、Cursor等)跨平台调用,并支持SSE与Streamable HTTP两种流式传输协议。

二、构建“AI工具能力层”的必要性

在内部开发客服、直播运营助手等AI应用时,我们遇到了几个典型痛点:

  1. 工具重复开发:每次对接一个新的AI应用,都需要重新编写调用后端已有能力(如查询开播状态、发送通知)的代码,包括参数结构定义、鉴权逻辑等,导致团队间重复劳动。
  2. 自然语言与接口参数的语义鸿沟:业务接口通常是 GET /room/info?roomid=123 的形式,但用户会自然地说“查一下imzerooo的开播状态”。这中间的语义映射无法靠简单规则完成,直接暴露原始接口给AI效果很差。
  3. JSON格式对AI不友好:内部接口返回的嵌套复杂JSON,虽然LLM能够解析,但容易导致信息提取不全、回答冗长或产生“幻觉”。AI更擅长处理提炼过的、人类可读的信息,如Markdown表格或简洁的自然语言摘要。
  4. 工具资产难以复用:工具代码通常与特定AI工作流、项目或脚本强耦合,无法作为企业级资产被统一管理和复用。

核心问题在于:缺乏一个能够统一编写、管理、发布和复用工具的能力层。我们的目标就是让创建一项AI工具,简化为只需编写一段JS函数

三、第一阶段:基于LangChain StructuredTool的探索

最初,我们在Flowise中创建了大量的自定义StructuredTool组件。

export class QueryLiveRoomTool extends StructuredTool {
  name = 'QueryLiveRoomTool'
  description = '根据 uid 查询主播信息'
  schema = z.object({
    roomid: z.string().describe('直播间id')
  })
  async _call({ roomid }) {
    const res = await axios.post({...})
    return formatForAI(res)
  }
}

这一版的优点是工具能被Agent直接调用,且有明确的参数模式(Schema)。但其局限性也很明显:工具生命周期与Flowise项目绑定;需要完整的TypeScript/Node.js项目开发经验,效率不高;无法跨平台使用;且JSON格式处理逻辑仍需开发者手动编写。

四、第二阶段:在线JS云函数平台(NodeVM运行时)

为了解决上述问题,我们构建了全新的在线JS云函数平台。开发者无需了解底层的Flowise或LangChain,只需在Web IDE中编写函数。

4.1 基于vm2的安全JS沙箱(NodeVM)

我们基于vm2库的NodeVM模块,创建了一个安全、可控的JS执行沙箱环境。

class DynamicTool extends StructuredTool {
  constructor({ name, description, schema, code }) {
    super({ name, description, schema })
    this.code = code // 用户在在线编辑器里写的 JS 代码
  }
  async _call(args, runManager, flowContext) {
    // 1. 构建沙箱环境,注入统一能力
    const sandbox = {
      // 用户参数转换为 $ 前缀变量
      ...Object.fromEntries(Object.entries(args).map(([k, v]) => [`$${k}`, v])),
      // 内部上下文(cookie/env/session)
      $flow: flowContext,
      $cookie: flowContext.cookie,
      // 内网能力封装 SDK(gRPC/HTTP)
      $yuumi: yuumi,
      // 结果转换工具(JSON -> Markdown表格)
      $json2MarkdownTable: json2MarkdownTable,
      // 内部LLM客户端
      $biliLLM: biliLLMClient
    }
    // 2. 创建NodeVM沙箱实例
    const vm = new NodeVM({
      sandbox,
      console: "inherit",
      require: {
        builtin: allowedBuiltinDeps, // 白名单内置模块
        external: allowedExternalDeps // 白名单外部模块
      }
    })
    // 3. 执行开发者编写的云函数代码
    return await vm.run(
      `module.exports = async () => { ${this.code} }()`,
      __dirname
    )
  }
}

该沙箱实现了安全隔离与能力注入的平衡:

  • 安全:禁止访问文件系统、限制require权限、仅开放白名单依赖。
  • 赋能:内置了内网调用SDK($yuumi)、结果格式化工具($json2MarkdownTable)以及会话上下文($cookie, $flow)等。

于是,开发者只需编写如下清晰的业务逻辑:

const res = await $yuumi.grpc({
  appId: 'live.service',
  path: '/room/info',
  params: { roomid: $roomid },
  cookie: $cookie,
})
return $json2MarkdownTable(res.data.list)

4.2 在线调试与版本管理

编辑器基于Monaco Editor(VSCode核心),提供良好的TS/JS开发体验。平台提供了参数Mock面板,支持一键运行调试,实时展示控制台日志、函数返回结果及异常。每次保存生成一个版本,支持发布、回滚,保障了变更的可控性。

4.3 可视化工具参数(Tool Arguments)配置

我们提供了可视化界面,让开发者能以更贴近自然语言的方式定义工具参数及其描述。平台据此统一生成MCP Tool的JSON Schema、Zod Schema以及NodeVM内的变量映射,实现“一次配置,多处复用”。

4.4 企业内部工具市场

平台集成了工具市场功能,允许各部门将开发好的工具上架。其他团队可以直接查找、复用,使工具从“项目私有资产”转变为“企业公共能力”,极大避免了重复建设。

五、第三阶段:接入MCP协议,实现跨平台通用性

第二版虽已好用,但工具仍与平台绑定。我们希望同一工具定义,能被任何支持MCP协议的客户端使用。

5.1 MCP协议简介与认知误区

MCP(Model Context Protocol)为“大模型+工具调用”定义了一套统一的通信标准。它解决了工具跨环境(本机、远程服务器)调用的标准化问题。

一个常见的误区是:直接将现有HTTP接口包装成MCP工具。这忽略了两个关键点:

  1. 自然语言到参数的映射:接口参数(如status=1)需要与自然语言描述(如“未完成的任务”)建立映射,这需要在工具定义阶段精心设计参数描述。
  2. 返回格式的友好性:原始JSON对AI并不友好。我们的云函数层强制要求返回人类可读的文本或Markdown,这正是$json2MarkdownTable等内置函数的价值所在。

5.2 实现MCP服务网关

我们在Express应用中添加了一层轻量的MCP网关。

export function createMCPServer({ app, AppDataSource }: App){
  // 单个云函数工具端点
  app.post('/api/mcp/function-tool/:toolId', singleToolCreateStreamableHTTPServer)
  // 多个工具组合的MCP端点
  app.post('/api/mcp/:mcpId', multipleToolCreateStreamableHTTPServer)
  // 会话复用处理
  app.get('/api/mcp/function-tool/:id', handleSessionRequest)
  app.delete('/api/mcp/function-tool/:id', handleSessionRequest)
  // ... 其他路由
}

5.3 核心:将云函数工具注册为MCP Tool

关键步骤是利用官方McpServer,将平台工具库中的元信息注册为标准的MCP工具。

function buildMcpServer(config: BuildMcpServerConfig): McpServer {
  const { name, tools, cookie, env, id, type, username, transport } = config
  const server = new McpServer({ name, version: '1.0.0' })

  for (const tool of tools) {
    // 1. 转换元数据,创建动态StructuredTool
    const obj = {
      name: tool.name,
      description: tool.description,
      schema: z.object(convertSchemaToZod(tool.schema as string | object)),
      code: tool.func as string
    }
    const dynamicStructuredTool = new DynamicStructuredTool(obj)
    dynamicStructuredTool.setFlowObject({ cookie, env })

    // 2. 将UI配置的Schema转换为Zod定义
    const schemaParser = (tool.schema ? JSON.parse(tool.schema) : []) as Schema[]
    const paramsSchema = schemaParser.reduce((res, cur) => {
      if (cur.required) {
        res[cur.property] = z[cur.type]({ required_error: `${cur.property} required` })
          .describe(cur.description) as z.ZodTypeAny
      } else {
        res[cur.property] = z[cur.type]()
          .describe(cur.description)
          .optional() as z.ZodTypeAny
      }
      return res
    }, {} as any)

    // 3. 使用server.tool()注册MCP工具
    server.tool(tool.name, tool.description, paramsSchema, async (args, _extra) => {
      // 执行逻辑最终会路由到DynamicStructuredTool的_call,即NodeVM沙箱执行
      const runnerResult = await dynamicStructuredTool.call({ ...args })
      // 返回MCP标准格式
      return {
        content: [{ type: 'text', text: runnerResult }]
      }
    })
  }
  return server
}

至此,对于任何MCP客户端而言,它只是调用了一个名为QueryLiveRoomInfo的工具并获得了文本结果。而平台内部则完成了一次从MCP协议到NodeVM沙箱,再到内网服务的完整调用链。

5.4 MCP市场与调试

平台提供了MCP市场,方便用户发现和共享工具。同时提供一键调试功能,无需连接真实大模型,即可在浏览器内模拟MCP调用,验证工具逻辑和返回格式。

六、安全鉴权设计

MCP作为对外的服务入口,安全至关重要。我们的设计如下:

  1. 注册鉴权:每个MCP工具的注册URL必须携带基于内部规范生成的签名(sign),无合法签名则拒绝注册。
  2. 调用代理:所有MCP调用首先到达平台的代理层。该层负责校验签名,并将合法的身份信息(如$cookie)注入执行上下文。
  3. 业务隔离:业务后端接收到的是经过代理层处理的、统一的内部协议请求,无需感知外部MCP协议细节,保持了架构的清晰和安全。

七、总结

从AI工作流内嵌工具,到独立的云函数平台,再到支持MCP协议的跨平台能力层,我们最终构建了一个统一的AI工具开发生态:

  • 统一定义:基于元信息的工具描述。
  • 统一执行:基于NodeVM的安全沙箱运行时。
  • 统一协议:基于MCP的跨平台调用标准。
  • 统一共享:企业内部工具市场。
  • 统一安全:基于签名的鉴权体系。

这使得业务开发者无需再关心“如何对接AI”,而只需聚焦于“我能为AI提供什么能力”。将编写一个JS函数,转化为赋予AI一项新能力,极大地提升了AI应用的构建效率与工具复用的规范性。




上一篇:VS Code JavaScript/TypeScript Modernizer:AI驱动的老旧项目自动化升级指南
下一篇:WPF边缘网关开发实战:低代码配置PLC数据采集与HTTP上报方案
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-17 13:10 , Processed in 0.123810 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表