Electron + Vue 3 + Vite 通用 AI Agent 功能调用架构


Electron + Vue 3 + Vite 通用 AI Agent 功能调用架构

在桌面应用中,AI Agent 最有价值的地方,不是”和用户聊几句”,而是把自然语言变成真实的操作能力。

例如用户输入一句话,应用不必把这句话硬编码成某个分支,而是让 AI 明确判断:这对应哪几个功能动作,以及各自的参数是什么。之后由 Electron 主进程去执行真正的本地能力。

这是一种可复用的架构思路:

自然语言 → 函数调用 → 本地执行

本文提供一套与具体业务无关的通用实现,你只需要替换”工具清单”和”主进程能力”,就能把它接入任意桌面功能,包括但不限于音视频处理、文件管理、日志分析、系统配置等。


1. 设计目标

这类架构的核心目标是:

  • 把用户输入的自然语言转成结构化的函数调用
  • 让 AI 只负责”判断调用哪个能力”
  • 让 Electron 主进程负责真正执行本地功能
  • 让渲染层只负责展示和交互

这样可以把职责边界拉清楚:

  • 渲染进程:UI、输入、反馈
  • 主进程:本地能力、系统接口、IPC
  • AI:语义理解和能力选择

2. 核心技术原理

2.1 AI Function Calling

Function Calling 的基本过程通常是:

  1. 开发者给模型提供一组可调用函数定义
  2. 用户输入自然语言
  3. 模型判断需要调用哪些函数,以及参数是什么
  4. 应用接收到函数调用请求后,去执行对应的本地功能
  5. 执行结果再回传给模型或前端展示

这使得 AI 具备了”调用工具”的能力,而不是只会生成文本。

2.2 Electron IPC

Electron 里,主进程和渲染进程是不同的上下文,因此不能直接访问对方的对象和方法。这时就需要 IPC(进程间通信)来进行桥接。

常见做法是:

  • 渲染进程调用 ipcRenderer.invoke(...)
  • 主进程通过 ipcMain.handle(...) 处理
  • 通过 contextBridge 在 preload 中暴露最小 API

这样既能保持安全,也能让渲染层调用主进程能力。

2.3 Vue 3 + Vite 的适配方式

Vue 3 负责渲染层,Vite 负责构建。前端侧不需要直接访问底层 Node 能力,应该通过 preload 暴露的 API 做桥接。

这样可以满足以下条件:

  • 渲染层保持浏览器友好
  • 主进程保留真实权限
  • 前端代码不直接依赖 Node 原生 API

3. 整体架构

graph TD
    A[用户] --> B[Vue 3 前端]
    B -->|自然语言指令| C[preload / IPC]
    C --> D[Electron 主进程]
    D --> E[AI 模型]
    E -->|函数调用 JSON| D
    D --> F[本地功能模块]
    F --> D
    D -->|返回执行结果| B
    B -->|展示结果| A

图中的关键点是:

  • 前端不直接执行本地任务
  • 主进程扮演”工具执行器”角色
  • AI 只负责解释指令并挑选函数

通用实现的关键E → F 这一段里,F 是一组可以随时替换的注册表,本文后面会把它做成与业务解耦的模块,而不是写死某个场景。


4. 功能定义:让 AI 知道有哪些工具可调用

主进程里,首先要定义一组”可供 AI 调用的能力”,也就是工具注册表。以通用桌面助手为例:

const tools = [
    {
        name: "openFileDialog",
        description: "打开系统文件选择器,返回用户选中的文件或目录路径。",
        parameters: {
            type: "object",
            properties: {
                filter: {
                    type: "string",
                    description: "可选的文件类型描述,例如 'all'、'images'、'docs'。",
                },
            },
            required: [],
        },
    },
    {
        name: "readFile",
        description: "读取指定路径文件的文本内容。",
        parameters: {
            type: "object",
            properties: {
                filePath: { type: "string", description: "文件的完整路径。" },
                encoding: {
                    type: "string",
                    description: "可选,编码格式,默认 utf-8。",
                },
            },
            required: ["filePath"],
        },
    },
    {
        name: "readSettings",
        description: "读取应用的配置项。",
        parameters: {
            type: "object",
            properties: {
                key: { type: "string", description: "配置键名,留空则返回全部配置。" },
            },
            required: [],
        },
    },
    {
        name: "runTask",
        description: "触发一个内部处理流程,例如导出、解析、批量操作。",
        parameters: {
            type: "object",
            properties: {
                taskName: { type: "string", description: "要执行的内部任务名称。" },
            },
            required: ["taskName"],
        },
    },
];

重点在于:

  • 每个工具都有清晰的名称
  • 每个工具有明确的用途说明
  • 参数类型和描述要足够清晰,帮助模型生成正确的调用
  • 工具清单与业务解耦:加一个场景只需新增一个对象,无需改动 AI 调用链路

AI 的工作不是”想当然地执行”,而是”按工具定义来调用”。


5. 主进程中的能力封装

主进程是最适合放真实操作逻辑的地方。这里用对象式封装的 capabilities,作为可扩展的能力注入点:

const { app, BrowserWindow, ipcMain, dialog } = require("electron");
const fs = require("fs/promises");

// 通用能力库:每个能力是一个 async 函数
const capabilities = {
    async openFile(filter = "all") {
        const filters =
            filter === "all"
                ? [{ name: "All Files", extensions: ["*"] }]
                : [{ name: filter, extensions: ["*"] }];

        const { canceled, filePaths } = await dialog.showOpenDialog({
            properties: ["openFile"],
            filters,
        });

        if (canceled) {
            return { success: false, message: "未选择文件" };
        }

        return { success: true, result: filePaths[0] };
    },

    async readFile(filePath, encoding = "utf-8") {
        const content = await fs.readFile(filePath, encoding);
        return { success: true, result: content };
    },

    async readSettings(key) {
        // 从本地配置模块读取
        const settings = await configStore.get(key);
        return { success: true, result: settings };
    },

    async runTask(taskName) {
        // 触发器:根据名称分发到对应的内部流程
        return taskRunner.dispatch(taskName);
    },
};

这些能力可以按需扩展,比如:

  • 读取并解析某个文件
  • 打开某个本地目录
  • 修改应用配置
  • 触发导出、解析、转换等内部流程
  • 与外部设备或系统接口交互

只要遵循”输入参数 → 返回 { success, result | message }“这个约定,就能无脑加入能力库。


6. 通过 IPC 暴露能力

渲染进程需要调用主进程能力,因此要在 ipcMain.handle 里注册。为了让注册通用化,可以对能力库做一次遍历注册

// 把能力库自动注册为 IPC 通道
function registerCapabilitiesAsIpc() {
    for (const [name, fn] of Object.entries(capabilities)) {
        ipcMain.handle(`capability:${name}`, (_, ...args) => fn(...args));
    }
}

当然也可以按需显式注册:

ipcMain.handle("capability:openFile", async (_, filter) => {
    return await capabilities.openFile(filter);
});

ipcMain.handle("capability:readFile", async (_, filePath, encoding) => {
    return await capabilities.readFile(filePath, encoding);
});

然后在 preload 中通过 contextBridge 暴露:

const { contextBridge, ipcRenderer } = require("electron");

contextBridge.exposeInMainWorld("electronAPI", {
    callCapability: (name, ...args) => ipcRenderer.invoke(`capability:${name}`, ...args),
    callAiAgent: prompt => ipcRenderer.invoke("call-ai-agent", prompt),
});

这样渲染层只接触一个非常有限的 API(两个方法),而不是直接进入 Node 环境。


7. Vue 组件如何发起命令

前端很简单:用户输入自然语言,调用一个统一的入口即可。




8. AI 调用主进程工具的实际实现

真正的关键在于主进程里接收 AI 的工具调用请求,并执行对应能力。这里把”工具定义”与”能力实现”用桥梁 toolRegistry 统一起来,从而做到扩展新工具时改动最小:

const OpenAI = require("openai");

const openai = new OpenAI({
    apiKey: process.env.OPENAI_API_KEY,
});

// 统一工具注册表:name -> 能力函数
const toolRegistry = {
    openFileDialog: capabilities.openFile,
    readFile: capabilities.readFile,
    readSettings: capabilities.readSettings,
    runTask: capabilities.runTask,
};

async function callAiAgent(prompt) {
    const tools = Object.entries(toolRegistry).map(([name, fn]) => ({
        type: "function",
        function: {
            name,
            description: fn.__description ?? name,
            parameters: { type: "object", properties: {}, required: [] },
        },
    }));

    const completion = await openai.chat.completions.create({
        model: "gpt-4o-mini",
        messages: [
            {
                role: "system",
                content: "你是桌面应用助手,能根据用户需求调用本地功能。",
            },
            { role: "user", content: prompt },
        ],
        tools,
        tool_choice: "auto",
    });

    const message = completion.choices[0].message;

    if (!message.tool_calls || message.tool_calls.length === 0) {
        return { success: true, message: message.content };
    }

    const firstCall = message.tool_calls[0];
    const toolName = firstCall.function.name;
    const args = JSON.parse(firstCall.function.arguments);

    const executor = toolRegistry[toolName];
    if (!executor) {
        return { success: false, message: `未知工具:${toolName}` };
    }

    return await executor(args);
}

这里为了让示例聚焦,工具描述与参数使用了简化写法。实践中,建议把工具的定义(描述、参数 JSON Schema)与实现分开管理,并在 toolRegistry 里显式登记完整定义,这样既能生成精确的 tools 入参,也便于维护。

关键点是:

  • 模型做的是”工具选择”
  • 主进程才真正执行逻辑
  • 前端不能直接挥舞 Node 权限
  • 扩展新能力 = 在能力库加函数 + 在注册表登记,链路本身零改动

9. 一个典型的用户流程

用户输入:

帮我选择一份文件,然后读取它的内容

流程大致如下:

  1. 前端把 prompt 发给主进程
  2. 主进程调用 AI 模型
  3. 模型决定调用 openFileDialog,参数为空
  4. 主进程打开系统文件选择器
  5. 用户选择文件
  6. 模型或调用链决定要执行 readFile,参数为所选路径
  7. 主进程读取并返回文件内容
  8. 前端展示结果

这样一个流程里,前端始终不接触真实文件路径的处理逻辑,只负责展示,职责非常干净。


10. 最佳实践

10.1 让前端保持无权状态

渲染进程不应该直接访问底层系统能力,最好是:

  • 只渲染界面
  • 只收集用户输入
  • 通过 IPC 调用主进程能力

10.2 工具定义与实现分离

为了真正通用,建议把三块内容分开维护:

  • 工具定义:给 AI 看的描述、参数 Schema
  • 能力实现:主进程中真正执行逻辑的函数
  • 注册表/桥接:把两者一一对应起来

这样新增一个功能时,你只改”定义 + 实现”两个地方,AI 调用链路、IPC 通道、前端全部保持不变。

10.3 工具定义必须清晰

工具定义越清晰,模型越容易正确选择对应能力。好的定义包含:

  • 名称
  • 说明
  • 参数类型
  • 参数描述
  • 可能的错误场景

10.4 所有敏感配置放在主进程

例如:

  • API Key
  • 服务密钥
  • 内网地址
  • 本地目录

都应该放在主进程中读取,并通过环境变量或安全配置管理,避免被前端直接访问。

10.5 对工具调用做权限校验

即便 AI 选择了某个工具,也必须保证:

  • 用户有权限执行该功能
  • 当前状态允许执行该功能
  • 参数必须来自安全校验

这能避免”AI 直接发出危险操作”的问题。

10.6 支持多工具串联

真实场景下,一句话往往对应多个连续动作(如上文的”选文件 + 读内容”)。建议:

  • 主进程收集 message.tool_calls 中全部调用,而不是只处理第一个
  • 把前一个工具的返回作为上下文,回传给模型继续决策
  • 最终把整条工具链的结果回传前端

这样通用架构才能承载复杂指令,而不是只回应单个动作。


11. 总结

AI Agent 在 Electron 里的价值,不在于”聊天”,而在于:

把自然语言转换成可执行的能力调用。

一套通用的合理架构是:

  • 前端负责交互
  • 主进程负责能力执行
  • AI 负责语义理解和工具选择
  • IPC 负责跨进程协作
  • 能力注册表让业务与链路解耦,做到”加功能不改链路”

这套模式与具体业务无关。无论你的应用是做音视频处理、文件管理、日志分析、系统配置,还是智能客服,都可以沿用同一个骨架,只需替换掉”工具清单”和”能力的实现”。这也是它比传统菜单式软件更灵活、更值得推广的根本原因。


文章作者: 弈心
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 弈心 !
评论
 本篇
Electron + Vue 3 + Vite 通用 AI Agent 功能调用架构 Electron + Vue 3 + Vite 通用 AI Agent 功能调用架构
一套与具体业务无关的通用 AI Agent 功能调用架构:在 Electron + Vue 3 + Vite 应用中, 把自然语言指令转成结构化函数调用,通过 IPC 让主进程执行本地能力。不绑定音视频场景, 可无缝对接到任意桌面功能,适合桌面助手、智能客服、自动化工具等。
2026-09-19
下一篇 
Vite 环境变量前缀与密钥边界:为什么 `VITE_` 不是加密 Vite 环境变量前缀与密钥边界:为什么 `VITE_` 不是加密
解释 Vite 中 `VITE_` 前缀的真实含义、为什么它并不等于“安全”,以及如何正确区分公开配置和服务端密钥。
2026-09-19
  目录