# 工具包、Python 执行器与 Artifact **本文引用的文件** - [AgentToolRegistry.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/AgentToolRegistry.java) - [AgentToolPackEntity.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentToolPackEntity.java) - [AgentToolCatalogServiceImpl.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentToolCatalogServiceImpl.java) - [AgentPythonFileController.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentPythonFileController.java) - [AgentArtifactController.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentArtifactController.java) - [artifacts.ts](file://ai-electron/frontend/src/ai/utils/artifacts.ts) - [artifactApi.ts](file://ai-electron/frontend/src/ai/api/artifactApi.ts) - [index.vue(工作台)](file://ai-electron/frontend/src/ai/views/aiWorkbench/index.vue) - [ArtifactPanel.vue](file://ai-electron/frontend/src/ai/components/ArtifactPanel.vue) - [ArtifactPreviewModal.vue](file://ai-electron/frontend/src/ai/components/ArtifactPreviewModal.vue) - [ArtifactFileCard.vue](file://ai-electron/frontend/src/ai/components/ArtifactFileCard.vue) - [agent_tool_pack.sql](file://sql/agent_tool_pack.sql) ## 目录 1. [引言](#引言) 2. [项目结构定位](#项目结构定位) 3. [核心组件总览](#核心组件总览) 4. [工具目录体系:AgentTool、AgentToolPackEntity、AgentToolRegistry](#工具目录体系-agenttool-agenttoolpackentity-agenttoolregistry) 5. [工具清单加载与匹配:AgentToolCatalogService](#工具清单加载与匹配-agenttoolcatalogservice) 6. [Python 文件管理:AgentPythonFileController](#python-文件管理-agentpythonfilecontroller) 7. [产物管理:AgentArtifactController 与前端 artifacts.ts](#产物管理-agentartifactcontroller-与前端-artifactsts) 8. [示例流程](#示例流程) 9. [依赖关系分析](#依赖关系分析) 10. [性能与并发特性](#性能与并发特性) 11. [常见问题排查](#常见问题排查) 12. [结论](#结论) ## 引言 本文件聚焦三个相互关联的能力: 1. **工具目录体系**:把业务侧只读查询能力包装成 Agent 可调用工具,并通过“工具组 + 工具包”进行分组、筛选和权限控制。 2. **Python 执行器产物访问**:Python 脚本产生的图片等沙箱输出,通过受控的 HTTP 路径暴露给前端 Markdown 或结果展示使用。 3. **Agent 产物预览与下载**:工具导出 CSV 等结构化结果以 Artifact 形式持久化,并提供轻量预览与完整下载。 这些能力共同构成 Agent 从“发现问题 → 调用工具 → 生成中间结果 → 展示最终产物”的后端支撑链路。 ## 项目结构定位 相关代码主要分布在后端 `ai-server` 的前端 `ai-electron/frontend`: ```mermaid graph TB subgraph "后端 ai-server" REG["AgentToolRegistry
工具注册中心"] CATALOG["AgentToolCatalogServiceImpl
工具清单同步与查询"] PYCTL["AgentPythonFileController
Python 沙箱图片访问"] ARTCTL["AgentArtifactController
CSV 产物预览与下载"] PACK["AgentToolPackEntity
工具包实体"] end subgraph "前端 ai-electron/frontend" UTILS["artifacts.ts
产物提取与预览状态"] API["artifactApi.ts
预览与下载接口封装"] WORK["index.vue
工作台产物面板入口"] PANEL["ArtifactPanel.vue
产物列表"] MODAL["ArtifactPreviewModal.vue
产物预览弹窗"] CARD["ArtifactFileCard.vue
单条产物卡片"] end REG --> CATALOG CATALOG --> PACK ARTCTL --> UTILS UTILS --> API WORK --> PANEL PANEL --> MODAL PANEL --> CARD ``` **图表来源** - [AgentToolRegistry.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/AgentToolRegistry.java#L1-L200) - [AgentToolCatalogServiceImpl.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentToolCatalogServiceImpl.java#L1-L200) - [AgentPythonFileController.java:1-112](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentPythonFileController.java#L1-L112) - [AgentArtifactController.java:1-76](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentArtifactController.java#L1-L76) - [artifacts.ts:1-106](file://ai-electron/frontend/src/ai/utils/artifacts.ts#L1-L106) - [artifactApi.ts:1-47](file://ai-electron/frontend/src/ai/api/artifactApi.ts#L1-L47) - [index.vue:77-144](file://ai-electron/frontend/src/ai/views/aiWorkbench/index.vue#L77-L144) **章节来源** - [AgentToolRegistry.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/AgentToolRegistry.java#L1-L200) - [AgentToolCatalogServiceImpl.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentToolCatalogServiceImpl.java#L1-L200) - [AgentPythonFileController.java:1-112](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentPythonFileController.java#L1-L112) - [AgentArtifactController.java:1-76](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentArtifactController.java#L1-L76) - [artifacts.ts:1-106](file://ai-electron/frontend/src/ai/utils/artifacts.ts#L1-L106) - [artifactApi.ts:1-47](file://ai-electron/frontend/src/ai/api/artifactApi.ts#L1-L47) - [index.vue:77-144](file://ai-electron/frontend/src/ai/views/aiWorkbench/index.vue#L77-L144) ## 核心组件总览 | 组件 | 类型 | 职责 | 关键输入 | 关键输出 | |---|---|---|---|---| | `AgentToolRegistry` | Spring 组件 | 按业务域注册 Agent 工具,定义工具组名、默认激活组和工具清单 | 各业务 Service、`ArtifactService` | 注入到 `Toolkit` 的工具实例与工具组 | | `AgentToolPackEntity` | MyBatis-Plus 实体 | 表示用户或系统维护的“工具包”,即一组工具名的集合 | 数据库 `agent_tool_pack` | 工具包元数据与 `tool_names` JSON | | `AgentToolCatalogServiceImpl` | Service 实现 | 扫描注解、合并运行时 Schema、同步 `agent_tool` 表,并对外提供工具清单 | 注解扫描器、Toolkit、Mapper | 工具 VO 列表、同步计数 | | `AgentPythonFileController` | REST Controller | 安全读取 Python 沙箱产出的图片等文件 | `workspaceId`、`execId`、`filename` | 图片或二进制响应 | | `AgentArtifactController` | REST Controller | 预览 CSV 产物前 N 行、下载完整 CSV | 产物 id、可选 limit | 表格数据或文件流 | | `artifacts.ts` | 前端工具模块 | 从聊天消息中提取产物元数据、统一数值字段、管理预览弹窗状态 | `ChatMessage[]` | `SessionArtifact[]`、预览状态 | | `artifactApi.ts` | 前端 API 模块 | 调用预览与下载接口,处理 token 与 blob 下载 | 产物 id、文件名 | 预览对象或下载结果 | **章节来源** - [AgentToolRegistry.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/AgentToolRegistry.java#L1-L200) - [AgentToolPackEntity.java:1-50](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentToolPackEntity.java#L1-L50) - [AgentToolCatalogServiceImpl.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentToolCatalogServiceImpl.java#L1-L200) - [AgentPythonFileController.java:1-112](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentPythonFileController.java#L1-L112) - [AgentArtifactController.java:1-76](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentArtifactController.java#L1-L76) - [artifacts.ts:1-106](file://ai-electron/frontend/src/ai/utils/artifacts.ts#L1-L106) - [artifactApi.ts:1-47](file://ai-electron/frontend/src/ai/api/artifactApi.ts#L1-L47) ## 工具目录体系:AgentTool、AgentToolPackEntity、AgentToolRegistry ### 工具分组模型 `AgentToolRegistry` 是业务工具的注册中心。它不负责具体查询逻辑,而是把已有只读 Service 封装为 Agent 可调用工具,并按领域划分为多个工具组: - `call`:通话分析 - `trans`:交易与资金分析 - `track`:轨迹、出行与快递 - `otg`:全文检索(当前未启用) - `graph`:关系图谱 - `person`:人物画像(默认激活) - `file`:非表格材料档案 每个工具组都有: - 组名常量 - 中文标签 - 面向模型的组描述 - 工具名清单 - 是否默认激活 ```mermaid classDiagram class AgentToolRegistry { +String GROUP_CALL +String GROUP_TRANS +String GROUP_TRACK +String GROUP_OTG +String GROUP_GRAPH +String GROUP_PERSON +String GROUP_FILE +List~String~ DEFAULT_ACTIVE_GROUPS +List~String~ registeredGroupNames() +String groupLabel(groupName) +List~String~ groupToolNames(groupName) +List~String~ registeredToolNames() +registerBusinessTools(toolkit) +registerBusinessTools(toolkit, activeGroups) +registerBusinessTools(toolkit, enabledGroups, activeGroups) +registerBusinessTools(toolkit, enabledGroups, activeGroups, toolFilter) } class AgentToolPackEntity { +Long id +Long userId +Integer isBuiltin +String name +String description +String toolNames +LocalDateTime createAt +LocalDateTime updateAt } AgentToolRegistry --> "1..*" String : "工具名" AgentToolPackEntity --> "1..*" String : "tool_names 引用 agent_tool.tool_name" ``` **图表来源** - [AgentToolRegistry.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/AgentToolRegistry.java#L1-L200) - [AgentToolRegistry.java:201-449](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/AgentToolRegistry.java#L201-L449) - [AgentToolPackEntity.java:1-50](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentToolPackEntity.java#L1-L50) ### 工具包的存储语义 `AgentToolPackEntity` 对应数据库表 `agent_tool_pack`。它的核心语义是: | 字段 | 含义 | |---|---| | `id` | 雪花 ID | | `userId` | 创建者,用于用户隔离 | | `isBuiltin` | 是否为系统内置包;内置包全局可见且不可编辑删除 | | `name` | 同用户内唯一的工具包名 | | `description` | 一句话说明 | | `toolNames` | JSON 字符串数组,值来自 `agent_tool.tool_name` | | `createAt` / `updateAt` | 时间戳 | 工具包不是“新工具”,而是对现有工具名的组合。新增业务工具时,应同时更新系统内置工具包的初始化 SQL,让新建方案可以一键勾选。 **章节来源** - [AgentToolPackEntity.java:1-50](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentToolPackEntity.java#L1-L50) - [agent_tool_pack.sql:1-81](file://sql/agent_tool_pack.sql#L1-L81) ## 工具清单加载与匹配:AgentToolCatalogService ### 同步机制 `AgentToolCatalogServiceImpl.syncFromCode()` 负责把代码中的工具信息同步到 `agent_tool` 表。它合并两个数据源: 1. **反射扫描**:由 `ToolAnnotationScanner` 发现带注解的工具方法,得到工具名、类名、方法名、描述、是否只读。 2. **运行时装配**:通过 `AgentService.buildCapabilityToolkit()` 获取实际注册进 `Toolkit` 的工具,从而获得工具所属组、组描述和参数 JSON Schema。 ```mermaid flowchart TD Start(["开始同步"]) --> ReadDB["读取 agent_tool 现有数据"] ReadDB --> ScanAnno["反射扫描 @Tool 方法"] ScanAnno --> BuildToolkit["构建运行时 Toolkit"] BuildToolkit --> IndexSchema["按工具名索引 ToolSchema"] IndexSchema --> BuildGroupIndex["构建工具组索引"] BuildGroupIndex --> Merge["逐工具合并反射数据与运行时数据"] Merge --> InsertUpdate["新增或更新 agent_tool"] InsertUpdate --> SoftDelete["对已移除工具软删除"] SoftDelete --> WarnMissing{"运行时工具有但反射未扫到?"} WarnMissing -->|是| LogMissing["记录日志"] WarnMissing -->|否| Done(["完成"]) LogMissing --> Done ``` **图表来源** - [AgentToolCatalogServiceImpl.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentToolCatalogServiceImpl.java#L1-L200) ### 工具分组与排序 同步过程中,`buildGroupIndex` 会: - 按 `AgentToolRegistry.registeredGroupNames()` 的顺序建立业务组索引 - 将不属于任何业务组的工具归入“基础工具”组 - 把默认激活组标记为 `defaultActive` - 按组顺序和工具名排序后返回前端 ```mermaid flowchart LR Registry["AgentToolRegistry
registeredGroupNames()"] --> Index["GroupIndex"] Toolkit["Toolkit
getToolGroup(...)"] --> Index Index --> Base["基础工具组
BASE_GROUP_CODE"] Index --> Person["人物画像"] Index --> Call["通话分析"] Index --> Trans["交易与资金分析"] Index --> Track["轨迹、出行与快递"] Index --> Graph["关系图谱"] Index --> File["非表格材料档案"] ``` **图表来源** - [AgentToolCatalogServiceImpl.java:201-281](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentToolCatalogServiceImpl.java#L201-L281) - [AgentToolRegistry.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/AgentToolRegistry.java#L1-L200) ### 错误容错 - 如果 `agent_tool` 表不存在,同步过程记录错误日志并返回,不阻断应用启动。 - 如果工具参数 JSON 解析失败,该工具按无参数处理,不会因一条脏数据导致整页崩溃。 - 如果运行时工具容器中有工具但反射未扫到,会记录提示日志,常用于排查“注册了工具但忘记加注解”的问题。 **章节来源** - [AgentToolCatalogServiceImpl.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentToolCatalogServiceImpl.java#L1-L200) - [AgentToolCatalogServiceImpl.java:201-281](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentToolCatalogServiceImpl.java#L201-L281) ## Python 文件管理:AgentPythonFileController ### 设计目标 `AgentPythonFileController` 暴露 `/py/files/{workspaceId}/{execId}/{filename}` 路径,用于在 Markdown 或其他结果中直接显示 Python 脚本生成的图片。其关键约束是: - 只允许白名单扩展名:png、jpg、jpeg、gif、webp、svg、bmp。 - `workspaceId`、`execId`、`filename` 三段路径均做白名单校验。 - 最终路径必须落在 `workspace/{workspaceId}/py-out/` 下,防止目录穿越。 - 只能访问当前打开案件对应的 `py-out` 目录,避免仅凭 UUID 读取其他案件产物。 - 返回响应带有 1 小时公共缓存控制,适合浏览器重复渲染同一张图。 ```mermaid sequenceDiagram participant FE as "前端" participant CTRL as "AgentPythonFileController" participant FS as "文件系统" FE->>CTRL: GET /py/files/workspaceId/execId/filename CTRL->>CTRL: 校验三段路径白名单 CTRL->>CTRL: 校验当前案件 workspaceId CTRL->>FS: 计算目标路径 FS-->>CTRL: 判断是否为普通文件 alt 合法文件 CTRL-->>FE: 200 + 图片 Content-Type else 非法路径 CTRL-->>FE: 400 非法参数 else 文件不存在 CTRL-->>FE: 404 文件不存在或已被清理 end ``` **图表来源** - [AgentPythonFileController.java:1-112](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentPythonFileController.java#L1-L112) ### 安全边界 | 检查项 | 处理方式 | |---|---| | 路径包含 `/`、`\` 或 `..` | 直接拒绝 | | 字符不在字母、数字、下划线、连字符、点范围内 | 拒绝 | | 请求文件不属于当前案件 | 返回无权访问 | | 归一化后路径超出 `py-out` 根目录 | 返回非法文件路径 | | 目标不是普通文件 | 返回文件不存在 | **章节来源** - [AgentPythonFileController.java:1-112](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentPythonFileController.java#L1-L112) ## 产物管理:AgentArtifactController 与前端 artifacts.ts ### 后端产物接口 `AgentArtifactController` 提供两个端点: | 端点 | 功能 | 输入 | 输出 | |---|---|---|---| | `GET /agent/artifact/preview` | 预览 CSV 前 N 行 | `id`、可选 `limit` | `{columns, rows}` | | `GET /agent/artifact/download` | 下载完整 CSV | `id` | 带 `Content-Disposition` 的文件流 | 预览默认限制 200 行,后端不会为了统计行数而全量读取大文件;总行数由工具结果中的 `artifact.totalRows` 提供。 鉴权方面,控制器本身不进入 sa-token 白名单,而是通过 `ArtifactService.resolve` 基于当前案件解析路径;前端下载时必须携带 `x-token` 请求头。 ```mermaid sequenceDiagram participant UI as "前端产物组件" participant API as "artifactApi.ts" participant CTRL as "AgentArtifactController" participant SVC as "ArtifactService" participant FS as "文件系统" UI->>API: fetchArtifactPreview(id, limit) API->>CTRL: GET /agent/artifact/preview?id=&limit= CTRL->>SVC: preview(id, limit) SVC->>FS: 流式读取前 N 行 FS-->>SVC: columns + rows SVC-->>CTRL: Map CTRL-->>UI: {columns, rows} UI->>API: downloadArtifactFile({id, fileName}) API->>CTRL: GET /agent/artifact/download?id= CTRL->>SVC: resolve(id) SVC-->>CTRL: Path CTRL-->>API: text/csv + attachment API-->>UI: blob 触发保存 ``` **图表来源** - [AgentArtifactController.java:1-76](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentArtifactController.java#L1-L76) - [artifactApi.ts:1-47](file://ai-electron/frontend/src/ai/api/artifactApi.ts#L1-L47) ### 前端产物提取与展示 前端产物展示的核心是 `artifacts.ts`。它从聊天消息的 `toolSteps` 中恢复工具结果 JSON,并提取其中的 `artifact` 字段。这样实时流、历史会话刷新、打开历史会话三条路径共用同一套提取逻辑。 关键能力: - `extractMessageArtifacts`:从单条消息提取产物。 - `extractArtifacts`:从整段会话提取产物。 - `artifactRowsText`:格式化行数文本。 - `artifactSizeText`:格式化文件大小。 - `openArtifactPreview` / `closeArtifactPreview`:管理全局预览弹窗状态。 - `toCount`:兼容后端 Long 序列化为字符串的情况。 ```mermaid flowchart TD Msg["聊天消息 ChatMessage"] --> Steps["toolSteps"] Steps --> Parse["解析 step.result JSON"] Parse --> HasArtifact{"存在 artifact.id?"} HasArtifact -->|否| NextStep["跳过该步骤"] HasArtifact -->|是| Meta["提取 totalRows、sizeBytes、fileName"] Meta --> Normalize["数值字段归一化为 number"] Normalize --> Session["附加 messageId、toolName"] Session --> List["SessionArtifact[]"] ``` **图表来源** - [artifacts.ts:1-106](file://ai-electron/frontend/src/ai/utils/artifacts.ts#L1-L106) ### 前端展示链路 工作台入口在 `index.vue` 中: - 监听 `store.messages`。 - 调用 `extractArtifacts` 生成产物列表。 - 将产物传给 `ArtifactPanel`。 - 全局挂载 `ArtifactPreviewModal` 消费 `previewArtifact`。 ```mermaid graph LR Store["store.messages"] --> Extract["extractArtifacts"] Extract --> Panel["ArtifactPanel.vue"] Panel --> Card["ArtifactFileCard.vue"] Panel --> Modal["ArtifactPreviewModal.vue"] Card --> Modal Panel --> API["artifactApi.ts"] ``` **图表来源** - [index.vue:77-144](file://ai-electron/frontend/src/ai/views/aiWorkbench/index.vue#L77-L144) - [ArtifactPanel.vue:54-140](file://ai-electron/frontend/src/ai/components/ArtifactPanel.vue#L54-L140) - [ArtifactFileCard.vue:3-106](file://ai-electron/frontend/src/ai/components/ArtifactFileCard.vue#L3-L106) - [ArtifactPreviewModal.vue:13-138](file://ai-electron/frontend/src/ai/components/ArtifactPreviewModal.vue#L13-L138) - [artifacts.ts:1-106](file://ai-electron/frontend/src/ai/utils/artifacts.ts#L1-L106) - [artifactApi.ts:1-47](file://ai-electron/frontend/src/ai/api/artifactApi.ts#L1-L47) **章节来源** - [AgentArtifactController.java:1-76](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentArtifactController.java#L1-L76) - [artifacts.ts:1-106](file://ai-electron/frontend/src/ai/utils/artifacts.ts#L1-L106) - [artifactApi.ts:1-47](file://ai-electron/frontend/src/ai/api/artifactApi.ts#L1-L47) - [index.vue:77-144](file://ai-electron/frontend/src/ai/views/aiWorkbench/index.vue#L77-L144) - [ArtifactPanel.vue:54-140](file://ai-electron/frontend/src/ai/components/ArtifactPanel.vue#L54-L140) - [ArtifactPreviewModal.vue:13-138](file://ai-electron/frontend/src/ai/components/ArtifactPreviewModal.vue#L13-L138) - [ArtifactFileCard.vue:3-106](file://ai-electron/frontend/src/ai/components/ArtifactFileCard.vue#L3-L106) ## 示例流程 ### 示例一:注册新工具 假设要新增一个只读业务工具: 1. 在合适的业务 Service 中实现查询方法。 2. 在工具实现类中增加对应方法,并加上工具注解(由 `ToolAnnotationScanner` 扫描)。 3. 在 `AgentToolRegistry` 中: - 将该工具名加入对应工具的常量清单。 - 在 `registerBusinessTools` 中注册工具实例。 - 在 `createGroup` 中把工具名加入对应组。 4. 如果是系统内置工具包的一部分,更新 `sql/agent_tool_pack.sql` 中的内置包 `tool_names`。 5. 重启服务并执行工具清单同步,使 `agent_tool` 表生效。 注意: - 新增工具不应修改底层 Service 与 Mapper 的既有行为。 - 工具分组描述要写清楚“什么时候该激活这个组”。 - 工具名必须保持唯一,否则工具包白名单和工具清单映射会出现歧义。 **章节来源** - [AgentToolRegistry.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/AgentToolRegistry.java#L1-L200) - [AgentToolRegistry.java:201-449](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/AgentToolRegistry.java#L201-L449) - [agent_tool_pack.sql:1-81](file://sql/agent_tool_pack.sql#L1-L81) - [AgentToolCatalogServiceImpl.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentToolCatalogServiceImpl.java#L1-L200) ### 示例二:调用 Python 脚本 典型流程如下: 1. Agent 通过 Python 工具执行脚本。 2. Python 执行器将图片等产物写入工作空间下的 `workspace/{workspaceId}/py-out/{execId}/`。 3. 工具返回中包含图片访问地址。 4. 前端 Markdown 或结果展示组件请求 `/py/files/{workspaceId}/{execId}/{filename}`。 5. 后端校验当前案件、路径白名单和目标文件后返回图片。 ```mermaid sequenceDiagram participant Agent as "Agent" participant PyExec as "PythonExecutor" participant FS as "工作空间 py-out" participant FE as "前端" participant Ctrl as "AgentPythonFileController" Agent->>PyExec: 执行 Python 脚本 PyExec->>FS: 写入 execId 子目录图片 PyExec-->>Agent: 返回图片 URL FE->>Ctrl: GET /py/files/workspaceId/execId/image.png Ctrl-->>FE: 返回图片资源 ``` **图表来源** - [AgentPythonFileController.java:1-112](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentPythonFileController.java#L1-L112) 注意事项: - 如果图片被 LRU 清理,前端会收到 404。 - 如果 `workspaceId` 与当前案件不一致,会被拒绝。 - 如果文件名包含非法字符或尝试目录穿越,会返回 400。 **章节来源** - [AgentPythonFileController.java:1-112](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentPythonFileController.java#L1-L112) ### 示例三:上传数据文件 虽然本文档重点不是通用文件上传,但根据上下文,数据文件通常通过 Agent 的工作空间或附件通道进入系统,再由工具或 Python 脚本消费。对于结构化结果,推荐走 `ArtifactService` 落盘 CSV,而不是随意写入任意目录,这样可以获得统一的预览、下载、归属和安全控制。 ### 示例四:查看工具调用结果与 Artifact 预览 前端展示链路如下: 1. 工具返回 JSON 中包含 `artifact` 字段。 2. `artifacts.ts` 从 `message.toolSteps` 中提取产物。 3. `ArtifactPanel` 列出产物,显示文件名、行数、大小。 4. 点击预览时,调用 `fetchArtifactPreview` 拉取前 N 行。 5. 点击下载时,调用 `downloadArtifactFile`,携带 `x-token` 下载完整 CSV。 ```mermaid sequenceDiagram participant Message as "聊天消息" participant Utils as "artifacts.ts" participant Panel as "ArtifactPanel" participant Modal as "ArtifactPreviewModal" participant API as "artifactApi.ts" participant Server as "AgentArtifactController" Message-->>Utils: toolSteps Utils-->>Panel: SessionArtifact[] Panel->>Modal: openArtifactPreview(artifact) Modal->>API: fetchArtifactPreview(id, limit) API->>Server: GET /agent/artifact/preview Server-->>API: {columns, rows} API-->>Modal: 预览数据 Panel->>API: downloadArtifactFile({id, fileName}) API->>Server: GET /agent/artifact/download Server-->>API: CSV blob ``` **图表来源** - [artifacts.ts:1-106](file://ai-electron/frontend/src/ai/utils/artifacts.ts#L1-L106) - [ArtifactPanel.vue:54-140](file://ai-electron/frontend/src/ai/components/ArtifactPanel.vue#L54-L140) - [ArtifactPreviewModal.vue:59-138](file://ai-electron/frontend/src/ai/components/ArtifactPreviewModal.vue#L59-L138) - [artifactApi.ts:1-47](file://ai-electron/frontend/src/ai/api/artifactApi.ts#L1-L47) - [AgentArtifactController.java:1-76](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentArtifactController.java#L1-L76) **章节来源** - [artifacts.ts:1-106](file://ai-electron/frontend/src/ai/utils/artifacts.ts#L1-L106) - [ArtifactPanel.vue:54-140](file://ai-electron/frontend/src/ai/components/ArtifactPanel.vue#L54-L140) - [ArtifactPreviewModal.vue:59-138](file://ai-electron/frontend/src/ai/components/ArtifactPreviewModal.vue#L59-L138) - [artifactApi.ts:1-47](file://ai-electron/frontend/src/ai/api/artifactApi.ts#L1-L47) - [AgentArtifactController.java:1-76](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentArtifactController.java#L1-L76) ## 依赖关系分析 ```mermaid graph LR ToolAnnotationScanner["ToolAnnotationScanner"] --> Catalog["AgentToolCatalogServiceImpl"] AgentService["AgentService"] --> Catalog AgentToolRegistry["AgentToolRegistry"] --> Catalog AgentToolMapper["AgentToolMapper"] --> Catalog Catalog --> AgentToolVO["AgentToolVO"] AgentToolRegistry --> Toolkit["Toolkit"] AgentToolRegistry --> ArtifactService["ArtifactService"] AgentArtifactController --> ArtifactService Frontend["前端产物组件"] --> ArtifactsTS["artifacts.ts"] ArtifactsTS --> ArtifactApi["artifactApi.ts"] ArtifactApi --> AgentArtifactController ``` **图表来源** - [AgentToolCatalogServiceImpl.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentToolCatalogServiceImpl.java#L1-L200) - [AgentToolRegistry.java:201-449](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/AgentToolRegistry.java#L201-L449) - [AgentArtifactController.java:1-76](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentArtifactController.java#L1-L76) - [artifacts.ts:1-106](file://ai-electron/frontend/src/ai/utils/artifacts.ts#L1-L106) - [artifactApi.ts:1-47](file://ai-electron/frontend/src/ai/api/artifactApi.ts#L1-L47) 耦合关系总结: - `AgentToolRegistry` 与大量业务 Service 耦合,但这些耦合集中在注册层,业务 Service 本身保持不变。 - `AgentToolCatalogServiceImpl` 同时依赖注解扫描、运行时 Toolkit 和数据库 Mapper,属于协调型 Service。 - `AgentArtifactController` 依赖 `ArtifactService`,不直接操作文件路径细节。 - 前端通过 `artifacts.ts` 解耦消息结构与产物展示,`artifactApi.ts` 解耦 HTTP 调用细节。 **章节来源** - [AgentToolCatalogServiceImpl.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentToolCatalogServiceImpl.java#L1-L200) - [AgentToolRegistry.java:201-449](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/AgentToolRegistry.java#L201-L449) - [AgentArtifactController.java:1-76](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentArtifactController.java#L1-L76) - [artifacts.ts:1-106](file://ai-electron/frontend/src/ai/utils/artifacts.ts#L1-L106) - [artifactApi.ts:1-47](file://ai-electron/frontend/src/ai/api/artifactApi.ts#L1-L47) ## 性能与并发特性 ### 工具清单同步 - 同步过程按扫描到的工具数量线性处理。 - 对每个工具最多一次查找 Schema、一次组索引查询、一次插入或更新。 - 最后对“已存在于数据库但不再扫描到的工具”执行软删除。 - 若数据库表缺失,同步失败不会阻止应用启动。 ### Artifact 预览 - 预览接口采用流式读取前 N 行,避免整文件载入内存。 - 不统计总行数,总行数来自工具结果元数据。 - 这避免了在大 CSV 上重复 IO。 ### Python 图片缓存 - `/py/files` 返回 1 小时公共缓存。 - 适合多次渲染同一张图的场景。 - 如果文件被 LRU 清理,浏览器再次请求会拿到 404,前端需要正确处理。 ### 并发访问 - Python 文件和 Artifact 都基于文件系统路径访问。 - 多 Agent 并发执行 Python 脚本时,`execId` 作为隔离键,降低同名文件冲突风险。 - Artifact 的 id 通常来自工具结果,应避免不同任务复用相同 id。 - 若高并发产生相同 id,需要在上层保证 id 生成唯一性。 **章节来源** - [AgentToolCatalogServiceImpl.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentToolCatalogServiceImpl.java#L1-L200) - [AgentArtifactController.java:1-76](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentArtifactController.java#L1-L76) - [AgentPythonFileController.java:1-112](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentPythonFileController.java#L1-L112) ## 常见问题排查 ### Python 执行权限不足 **现象**:Python 脚本无法写入输出文件或图片。 **排查要点**: 1. 确认 Python 执行进程对工作空间目录有读写权限。 2. 确认 `workspace/{workspaceId}/py-out/` 存在且可写。 3. 检查 Python 执行器配置的执行目录、超时时间和工作目录。 4. 观察 Python 执行日志,确认异常发生在写入阶段还是命令启动阶段。 **章节来源** - [AgentPythonFileController.java:1-112](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentPythonFileController.java#L1-L112) ### 脚本超时 **现象**:Python 工具长时间无响应,最终失败。 **排查要点**: 1. 检查 Python 执行器的超时配置。 2. 确认脚本是否卡在读库、网络请求或大数据集处理。 3. 优先改为分页查询或先采样再分析。 4. 对需要较长时间的任务,考虑异步执行或分步工具。 ### 工具未注册 **现象**:Agent 看不到某个工具,或工具清单同步数量为 0。 **排查要点**: 1. 确认工具方法加了正确的工具注解。 2. 确认工具类位于注解扫描包路径下。 3. 确认 `AgentToolRegistry` 中已注册该工具实例。 4. 检查 `AgentToolCatalogServiceImpl` 日志,关注“反射未扫到任何 @Tool 方法”或“运行时工具容器里有、但反射未扫到的工具”。 5. 确认 `agent_tool` 表已创建并可读取。 **章节来源** - [AgentToolCatalogServiceImpl.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentToolCatalogServiceImpl.java#L1-L200) - [AgentToolRegistry.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/AgentToolRegistry.java#L1-L200) ### Artifact 路径不存在 **现象**:预览或下载返回 404,或前端报错。 **排查要点**: 1. 确认工具成功导出了 CSV,并设置了 `artifact.id`。 2. 确认 `ArtifactService.resolve` 能解析到有效路径。 3. 确认当前案件的 Artifact 根目录存在。 4. 检查是否有清理策略删除了产物文件。 5. 确认前端传递的 id 与工具结果中的 id 一致。 **章节来源** - [AgentArtifactController.java:1-76](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentArtifactController.java#L1-L76) - [artifactApi.ts:1-47](file://ai-electron/frontend/src/ai/api/artifactApi.ts#L1-L47) ### 并发访问冲突 **现象**:多个 Agent 或用户之间出现产物混用、路径覆盖或权限问题。 **排查要点**: 1. 确保 Python 执行 id 唯一,避免多个任务写入同一 `execId`。 2. 确保 Artifact id 全局唯一。 3. 确认 Python 图片访问限制在当前 `workspaceId`。 4. 确认 Artifact 路径按当前案件解析,不能跨案件访问。 5. 在高并发场景下,检查文件系统清理策略是否与产物生命周期匹配。 **章节来源** - [AgentPythonFileController.java:1-112](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentPythonFileController.java#L1-L112) - [AgentArtifactController.java:1-76](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentArtifactController.java#L1-L76) ### 前端无法下载 CSV **现象**:点击下载后没有文件,或下载到 JSON 错误内容。 **排查要点**: 1. 不要使用 `` 裸链接下载,因为不带 `x-token`。 2. 确认前端通过 `fetch` 携带 `x-token`。 3. 确认后端返回的是 `text/csv`,而不是登录失败的 JSON。 4. 检查 `artifactApi.ts` 中是否正确处理非 JSON 响应。 5. 检查桌面端下载管理器是否正常接收 blob。 **章节来源** - [artifactApi.ts:1-47](file://ai-electron/frontend/src/ai/api/artifactApi.ts#L1-L47) - [AgentArtifactController.java:1-76](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentArtifactController.java#L1-L76) ## 结论 工具包、Python 执行器与 Artifact 共同构成了 Agent 的可扩展能力层: - `AgentToolRegistry` 负责把业务查询能力组织成结构化、可分组、可控制的工具。 - `AgentToolCatalogServiceImpl` 负责把注解、运行时工具和数据库三者对齐,形成稳定工具清单。 - `AgentToolPackEntity` 和 `agent_tool_pack` 表提供用户级工具组合管理能力。 - `AgentPythonFileController` 提供安全的 Python 沙箱图片访问通道。 - `AgentArtifactController` 提供 CSV 产物的轻量预览和完整下载。 - 前端 `artifacts.ts` 和 `artifactApi.ts` 将产物从工具结果中抽取出来,并以直观方式展示和操作。 维护这套体系的关键原则是:**工具注册与业务逻辑分离、产物路径严格受控、前端与后端共享产物语义、错误路径给出明确反馈**。新增工具或产物类型时,应优先考虑是否需要分组、是否需要 Artifact、是否需要 Python 输出、以及并发和权限边界是否清晰。