# 多模态图片识别工具 & 工具模型分类 & 对话附件上传 设计 > 日期:2026-04-26 > 状态:待实施 ## 概述 在工具管理系统中引入三个能力: 1. **工具模型分类**:区分"需要大模型配置"和"不需要大模型配置"的工具,支持工具级别的模型渠道绑定 2. **图片识别工具**:新增 `image_recognize` 多模态工具,支持 URL 和 base64 两种图片输入,通过配置的多模态模型进行图片分析 3. **对话附件上传**:Agent 对话页面支持上传图片/视频/PDF 等附件,支持多文件、首尾帧,为后续文生图/图生图/视频生成预留扩展 ## 一、数据层变更 ### netaclaw_tool 表新增字段 | 字段 | 类型 | 默认值 | 说明 | |------|------|--------|------| | `requiresModel` | int | 0 | 是否需要大模型配置(0否 1是) | | `modelChannelId` | int | null | 关联模型渠道ID(指向 netaclaw_model_channel.id) | | `modelId` | varchar(100) | null | 关联模型ID(渠道内具体模型名) | 在 `NetaClawToolEntity` 中新增对应字段。 ### Catalog Schema 扩展 `registerSchema()` 的 schema 对象新增 `requiresModel?: boolean` 字段,工具注册时声明是否需要模型。 `syncCatalogToDb()` 同步时将此字段写入 DB。 ## 二、图片识别工具实现 ### 文件位置 `packages/backend/src/modules/netaclaw/tools/builtin/image_recognize.ts` ### 工具元信息 - name: `image_recognize` - label: `图片识别` - toolset: `vision` - capability: `multimodal` - requiresModel: `true` - visibility: `tool` - isCore: false - canDisable: true ### 依赖注入模式 遵循项目现有的**工厂函数 + 闭包**模式。现有工厂函数接收的是**运行时数据**(provider 实例、凭证),不是 service 对象。image_recognize 也应如此: ```typescript // image_recognize.ts — 工厂函数接收已解析的运行时凭证 export function createImageRecognizeTool(credentials: { baseUrl: string; apiKey: string; supplier: string; modelId: string; promptHint: string | null; }): AnyAgentTool { return { name: 'image_recognize', async execute(id, params) { // 通过闭包直接访问已解析的凭证,无需再查 DB const provider = getProvider(supplierToProvider[credentials.supplier]); // ... 调用 provider.chat() }, }; } ``` 在 `tool_resolver.ts` 的 `resolve()` 方法中,先查询 DB 获取凭证,再传给工厂函数: ```typescript // tool_resolver.ts resolve() 方法中 if (filteredNames.includes('image_recognize')) { const toolConfig = await this.toolRegistry.getToolModelConfig('image_recognize'); if (toolConfig) { const channel = await this.modelChannelService.resolveForAgent(toolConfig.modelChannelId); if (channel) { runtimeTools.push(createImageRecognizeTool({ ...channel, modelId: toolConfig.modelId, promptHint: toolConfig.promptHint, })); } } // 若未配置或渠道不可用,工具不进入 runtimeTools(LLM 不会看到该工具) } ``` **关键决策:模型未配置时在 resolve 阶段排除工具**,避免 LLM 尝试调用必然失败的工具。 ### 参数 Schema (TypeBox) ```typescript { image: Type.String({ description: '图片URL或base64编码字符串' }), prompt: Type.Optional(Type.String({ description: '分析提示词,不传则使用工具默认提示词' })) } ``` ### 默认系统提示词 工具内置一个高质量默认提示词,存储在 `promptHint` 字段中,前端可编辑覆写: ``` 你是一个专业的图像分析助手。请按以下步骤分析图片: 1. **图像分类**:首先识别图片类型(如:身份证、驾驶证、行驶证、营业执照、发票、商品图片、截图、照片、表格、图表、手写文字、印刷文字等)。 2. **结构化提取**:根据图片类型,提取关键信息: - 证件类:提取所有字段(姓名、证件号、有效期、地址等) - 票据类:提取金额、日期、项目明细等 - 商品类:提取品名、规格、价格、品牌等 - 表格/图表类:提取数据结构和关键数值 - 其他类:详细描述画面内容 3. **详细描述**:对图片内容进行全面、详细的文字描述,不遗漏任何可见信息。包括文字内容、图形元素、颜色、布局等。 4. **质量评估**:简要说明图片清晰度、是否有遮挡或模糊区域。 请以结构化格式输出分析结果。 ``` ### 执行流程 1. 工厂函数通过闭包直接访问已解析的凭证(baseUrl、apiKey、supplier、modelId、promptHint) 2. 判断 `image` 参数:URL(http/https 开头)或 base64 3. 读取闭包中的 `promptHint` 作为系统提示词,若用户传了 `prompt` 参数则追加 4. 通过项目现有的 LLM provider 层调用模型(`getProvider(supplierToProvider[supplier])` → `provider.chat()`),不直接用 fetch 5. 构造 OpenAI 兼容的 multimodal messages(火山引擎 volcengine supplier 映射到 openai provider): ```json { "model": "doubao-seed-2-0-pro-260215", "messages": [{ "role": "user", "content": [ { "type": "text", "text": "" }, { "type": "image_url", "image_url": { "url": "" } } ] }] } ``` 6. 返回 `textResult(response.choices[0].message.content)` ## 三、后端接口变更 ### tool controller 变更 - `page` 接口 `pageQueryOp` 新增 `requiresModel` 到 `fieldEq` - `update` 接口自动支持(CoolController 的 update 会更新所有传入字段) ### tool_registry service 变更 - `syncCatalogToDb()` 同步时处理 `requiresModel` 字段 - `page()` 方法支持 `requiresModel` 过滤 - 新增 `getToolModelConfig(toolName)` 方法: ```typescript async getToolModelConfig(toolName: string): Promise<{ modelChannelId: number; modelId: string } | null> { const tool = await this.toolEntity.findOneBy({ name: toolName }); if (!tool?.modelChannelId || !tool?.modelId) return null; return { modelChannelId: tool.modelChannelId, modelId: tool.modelId }; } ``` ### tool_resolver.ts 变更 在 `resolve()` 方法中,为 `image_recognize` 工具创建实例时注入依赖(与 memory/delegate 工具同模式)。 ## 四、前端工具管理页改造 ### 筛选栏 新增"模型依赖"下拉筛选:全部 / 需要模型 / 不需要模型 ### 表格列 新增"模型配置"列: - `requiresModel === 0`:显示 `-` - `requiresModel === 1` 且已配置:显示模型名称(如 `doubao-seed-2-0-pro-260215`) - `requiresModel === 1` 且未配置:显示橙色 `el-tag type="warning"` "未配置" ### 编辑抽屉 当 `requiresModel === 1` 时,在编辑抽屉中显示"模型配置"区域(`el-divider` 分隔): - 模型渠道下拉(调用 `model_channel` 的 `allModels` 接口,筛选含 multimodal 能力模型的渠道) - 模型选择下拉(联动,显示所选渠道下 capability 为 multimodal 的模型) - 默认提示词文本框(`el-input type="textarea" :rows="8"`,编辑 `promptHint` 字段) ## 五、Agent 对话附件上传 ### 架构决策:主 Agent LLM 如何感知附件 主 Agent LLM 可能是纯文本模型(如 DeepSeek),无法直接"看到"图片。采用 **metadata + prompt_builder 注入方案**: 1. 用户消息 `content` 保持纯净,不污染原始文本 2. 附件信息存储在 message 的 `metadata.attachments` 中 3. 在 `prompt_builder` 层构造 LLM messages 时,检查 metadata.attachments,将附件信息作为**独立的 system/user message** 注入到 messages 数组中: ``` [系统注入的附件提示] 用户上传了以下附件: - 图片: photo.jpg (URL: https://xxx/photo.jpg) - PDF: report.pdf (URL: https://xxx/report.pdf) 如需分析图片内容,请使用 image_recognize 工具,传入图片URL。 ``` 这样避免了"后端注入 + 前端过滤"的脆弱双向耦合,content 字段始终是用户原始输入。 ### 协议层扩展 `gateway/protocol.ts` 中 ClientChatMessage 新增 `attachments` 字段: ```typescript interface ChatAttachment { id: string; type: 'image' | 'video' | 'pdf' | 'document' | 'other'; url: string; name: string; size: number; mimeType: string; role?: 'start_frame' | 'end_frame'; } ``` ### 消息存储 `netaclaw_message.metadata` JSON 中存储: ```json { "attachments": [...] } ``` ### 前端组件化设计 将附件功能拆分为独立组件,不膨胀现有 ChatComposer: ``` components/chat/ ├── ChatComposer.vue (现有,slim 协调器,新增 attachments prop 传递) ├── ChatAttachmentButton.vue (新增,附件按钮 + 文件选择器) ├── ChatAttachmentPreview.vue (新增,附件预览区,缩略图/删除/首尾帧标记) ├── MessageAttachments.vue (新增,消息气泡中的附件展示) └── ... ``` #### ChatAttachmentButton.vue - 回形针图标按钮,点击打开文件选择器 - 接受类型:图片(jpg/png/gif/webp/bmp)、视频(mp4/mov/avi)、PDF、文档(doc/docx/xls/xlsx) - 支持多选 - emit: `@select(files: File[])` #### ChatAttachmentPreview.vue - 横向滚动的附件预览条,位于输入框上方 - 每个附件显示:缩略图(图片)/ 文件图标(其他)+ 文件名 + 大小 + 删除按钮 - 图片:显示缩略图(前端 `URL.createObjectURL` 或上传后的 URL) - 视频:显示视频图标 + 文件名 - 右键菜单:标记为"首帧" / "尾帧"(设置 `role` 字段) - 上传进度条(每个文件独立进度) - props: `attachments: ChatAttachment[]` - emit: `@remove(id)`, `@update-role(id, role)` #### ChatComposer.vue 改造 - 在 textarea 上方条件渲染 `ChatAttachmentPreview` - 在 textarea 左侧添加 `ChatAttachmentButton` - 支持拖拽上传(`@dragover` + `@drop` 事件) - 支持粘贴上传(`@paste` 事件,检测 `clipboardData.files`) - 新增 `attachments` ref,发送时随消息一起 emit #### MessageAttachments.vue - 在 `message-item.vue` 中,当 `metadata.attachments` 存在时渲染 - 图片:网格布局缩略图,点击 `el-image-viewer` 放大 - 视频:视频缩略图 + 播放图标 - PDF/文档:文件图标 + 文件名 + 大小,点击新窗口打开 ### 上传流程 1. 用户选择/拖拽/粘贴文件 2. 前端调用 `/admin/base/comm/upload` 上传到 Space(复用现有上传基础设施) 3. 上传成功后获得文件 URL,构造 `ChatAttachment` 对象 4. 添加到 `attachments` 数组,显示预览 5. 用户点击发送 → `attachments` 随消息通过 WebSocket 发送(只携带 URL,不携带 base64 数据) 6. 后端将附件信息存入 message metadata ### ConversationList 消息展示 在 `message-item.vue` 的用户消息气泡中: - 检查 `metadata.attachments`,若存在则渲染 `MessageAttachments` 组件 - content 字段始终是用户原始输入,无需过滤 ## 六、影响范围 ### 新增文件 - `packages/backend/src/modules/netaclaw/tools/builtin/image_recognize.ts` — 图片识别工具 - `packages/frontend/src/modules/agent/components/chat/ChatAttachmentButton.vue` — 附件按钮 - `packages/frontend/src/modules/agent/components/chat/ChatAttachmentPreview.vue` — 附件预览 - `packages/frontend/src/modules/agent/components/chat/MessageAttachments.vue` — 消息附件展示 ### 修改文件 - `packages/backend/src/modules/netaclaw/entity/tool.ts` — 新增 3 个字段 - `packages/backend/src/modules/netaclaw/tools/catalog.ts` — schema 类型扩展 + import 新工具 - `packages/backend/src/modules/netaclaw/service/tool_registry.ts` — 同步、查询、模型配置方法 - `packages/backend/src/modules/netaclaw/service/tool_resolver.ts` — 注入 image_recognize 工具依赖 - `packages/backend/src/modules/netaclaw/controller/admin/tool.ts` — 筛选参数 - `packages/frontend/src/modules/agent/views/tools.vue` — 筛选、表格列、编辑抽屉 - `packages/backend/src/modules/netaclaw/gateway/protocol.ts` — ChatAttachment 类型 + ClientChatMessage 扩展 - `packages/backend/src/modules/netaclaw/gateway/server.ts` — 附件消息处理(存 metadata,不改 content) - `packages/backend/src/modules/netaclaw/service/prompt_builder.ts` — 附件信息注入到 LLM messages - `packages/frontend/src/modules/agent/components/chat/ChatComposer.vue` — 集成附件按钮/预览/拖拽/粘贴 - `packages/frontend/src/modules/agent/components/message-item.vue` — 渲染 MessageAttachments - `packages/frontend/src/modules/agent/types/index.d.ts` — ChatAttachment 类型 + WSClientMessage 扩展 - `packages/frontend/src/modules/agent/hooks/websocket.ts` — 消息发送携带 attachments ### 不需要修改 - model_channel 相关代码(复用现有 resolveForAgent + allModels 接口) - tool_resolver.ts 的 capability 匹配逻辑(已存在,无需改动) - Agent entity(工具模型配置在工具级别) ### 安全注意事项 - 后端文件上传接口需验证 MIME 白名单和文件大小限制 - API Key 不得出现在工具执行日志或前端返回的 tool_result 事件中 - 附件 URL 应有访问控制(签名 URL 或 token 验证)