GPU_GUARD_MONOREPO/docs/superpowers/specs/2026-04-26-multimodal-tool-design.md
2026-05-20 21:39:12 +08:00

315 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 多模态图片识别工具 & 工具模型分类 & 对话附件上传 设计
> 日期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,
}));
}
}
// 若未配置或渠道不可用,工具不进入 runtimeToolsLLM 不会看到该工具)
}
```
**关键决策:模型未配置时在 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` 参数URLhttp/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": "<promptHint + prompt>" },
{ "type": "image_url", "image_url": { "url": "<url或data:image/...;base64,...>" } }
]
}]
}
```
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 验证)