Commit 0e06821b by Archer Committed by GitHub

doc (#7174)

* doc

* fix: skill per
parent 0c1840c7
......@@ -5,7 +5,7 @@ description: 详细介绍如何通过 AI 助手辅助生成 Prompt,以及如
## 辅助生成
在“辅助生成”选项卡中,内置了针对 Agent V2 优化的 AI 协作助手。
在“辅助生成”选项卡中,内置了针对 Agent V2 优化的 AI 协作助手,目前感知的范围有限,仍处于优化阶段,仅商业版开放
![辅助生成小助手](/imgs/agent_helper_bot.png)
......
......@@ -4,7 +4,6 @@
"pages": [
"settings",
"debug",
"vm",
"startup"
"vm"
]
}
......@@ -4,7 +4,6 @@
"pages": [
"settings",
"debug",
"vm",
"startup"
"vm"
]
}
......@@ -18,7 +18,7 @@ The Configuration Panel is used to configure and bind all the core capabilities
| :---------------------------------------------------------: | :---------------------------------------------------------------: |
| ![`@` Tool Quick Binding](/imgs/agent_prompt_editor_at.png) | ![Rich Text Prompt Editor](/imgs/agent_prompt_editor_mention.png) |
- **Virtual Machine**: Once enabled, the system assigns a dedicated Linux sandbox environment for each session, supporting code execution, file operations, and startup command configuration. For architecture and debugging details, refer to [Virtual Machine](./vm). For startup script details and execution limits, see [Startup Script](./startup).
- **Virtual Machine**: Once enabled, the system assigns a dedicated Linux sandbox environment for each session, supporting code execution, file operations, and startup command configuration. For architecture and debugging details, refer to [Virtual Machine](./vm). For startup script details and execution limits, see [Virtual Machine Lifecycle](./vm#virtual-machine-lifecycle).
---
......@@ -38,10 +38,3 @@ The Configuration Panel is used to configure and bind all the core capabilities
- **Knowledge Base**: Associate specific corporate documents and adjust search settings (Hybrid Search, Re-ranking, etc.). It also supports configuring team member authorization permissions.
- **File Uploads**: Toggle file uploads for end-users, permitting images, audio, video, or custom file extensions. File upload capabilities automatically adapt based on the multimodal features of the selected LLM. For detailed configurations, see [File Input](../general/fileInput).
---
## Welcome Text & Voice Input
- **Welcome Text**: The initial welcome message sent automatically by the system before each conversation starts, supporting standard Markdown. It also supports `[Quick Question]` format, allowing users to send preset questions with a single click.
- **Voice Input**: Enables users to input voice messages which are automatically converted to text. Once enabled, you can configure **Auto Send** (automatically send the message after recording completes) and **Auto Voice Response** (the AI will reply in audio format directly, requiring voice playback to be enabled).
---
title: 配置面板
description: 详细介绍对话 Agent V2 编辑面板中 AI 核心参数、虚拟机环境与技能集成的配置指南
title: 专项配置
description: 详细介绍对话 Agent V2 中提示词、虚拟机与技能的专项配置
---
配置面板用于调整和绑定您的 Agent 所需的所有核心能力与运行环境
专项配置用于调整 Agent 的核心指令、运行环境与技能扩展能力
![配置面板](/imgs/agent_settings_panel.png)
---
## AI 配置与虚拟机
## 提示词
- **AI 模型**:选择对话大模型并配置参数,更多大模型通用配置选项请参考 [AI 配置说明](../general/ai_settings)。
- **提示词**:定义 Agent 的核心人设、工作目标和具体规则。编辑器支持富文本,且支持通过 `@` 快速唤起并绑定部分工具等。
提示词用于定义 Agent 的核心人设、工作目标和具体规则。编辑器支持富文本,并支持通过 `@` 快速唤起并绑定部分工具等上下文能力。
| | |
| :----------------------------------------------------: | :--------------------------------------------------------: |
| ![提示词 @ 快速绑定](/imgs/agent_prompt_editor_at.png) | ![提示词富文本编辑](/imgs/agent_prompt_editor_mention.png) |
| | |
| :----------------------------------------------------: | :--------------------------------------------------------: |
| ![提示词 @ 快速绑定](/imgs/agent_prompt_editor_at.png) | ![提示词富文本编辑](/imgs/agent_prompt_editor_mention.png) |
- **虚拟机**:开启后,系统会为每个独立会话在后台分配一个专属的 Linux 沙盒运行环境,支持执行代码、读写文件及配置启动脚本等。具体设计与联调操作请参考 [虚拟机](./vm),启动脚本去重与执行细节请参考 [启动脚本](./startup)。
如果需要配置对话大模型、回复长度、推理内容展示等通用模型参数,请参考 [AI 配置说明](../general/ai_settings)。
---
## 关联 SKILL 与工具
## 虚拟机
- **关联 SKILL**:可以选择已发布的 SKILL 插件包,其自带的入口脚本会随虚拟机拉起自动执行。如果未开启虚拟机但关联了 SKILL,系统会展示“虚拟机未就绪”的警告。若需了解如何自定义编写与打包 SKILL,请参考 [开发与调试](../skill/development)
虚拟机开启后,系统会为每个独立会话在后台分配一个专属的 Linux 沙盒运行环境,支持执行代码、读写文件及配置启动脚本等
![虚拟机未就绪警告](/imgs/agent_skill_vm_not_ready.png)
- **工具**:您可以选择绑定系统内置工具(如搜索引擎、图表绘制)、个人或团队创建的自定义工具(包括 HTTP/MCP 工具),或是创建的应用。
![工具](/imgs/agent_integrate_tools.png)
具体设计与联调操作请参考 [虚拟机](./vm),启动脚本去重与执行细节请参考 [虚拟机生命周期](./vm#虚拟机生命周期)。
---
## 知识库与文件上传
## 技能
- **关联知识库**:绑定企业专属知识文档,可精细调整搜索参数(混合搜索、重排等),并可配置团队成员身份验证权限。
- **文件上传配置**:控制前台用户在对话时是否可上传图片、音频、视频或特定扩展名文件。文件上传能力会根据所选大模型的多模态特性自动进行自适应调整。具体配置细节参考 [文件输入功能介绍](../general/fileInput)。
---
技能配置用于选择已发布的技能插件包。技能自带的入口脚本会随虚拟机拉起自动执行。
## 对话开场白与语音输入
如果未开启虚拟机但关联了技能,系统会展示“虚拟机未就绪”的警告。若需了解如何自定义编写与打包技能,请参考 [开发与调试](../skill/development)。
- **对话开场白**:每次新对话开始前由系统自动发送的欢迎词,支持标准 Markdown 语法。支持使用 `[快捷问题]` 特殊格式,允许用户在界面上通过点击按钮直接发送预设的提问。
- **语音输入**:支持用户在前台对话中进行语音录入并自动识别转换为文字。开启后可进一步配置**自动发送**(语音录入完成后自动发出无需手动确认)和**自动语音回复**(语音发送后,AI 的回复也自动以语音播放响应,需开启语音播报)。
![虚拟机未就绪警告](/imgs/agent_skill_vm_not_ready.png)
---
title: Startup Script
description: In-depth guide on the Agent V2 virtual machine startup script configuration, execution lifecycle, hash-based deduplication, and runtime execution limits.
---
import { Alert } from '@/components/docs/Alert';
When the **Computer** option is enabled, you can configure a **Startup Script** to automatically execute shell commands right after the sandbox environment spins up and before the AI workflow officially starts. This is commonly used for configuring environment variables, modifying software package sources, or installing Python packages (`pip`) and system-level utilities.
---
## Script Configuration & Lifecycle
Under the "Computer Configuration" section of the Agent Configuration Panel, you can write standard Shell commands directly inside the **Startup script (sh)** code editor.
![Startup Script Editor](/imgs/agent_startup_script_editor.png)
### Script Execution Sequence and Scope
When a new session starts or the virtual machine is reconstructed, the system executes the scripts sequentially in the background:
1. **Application Startup Script**: The custom Shell script configured in your Agent panel. It executes inside the virtual machine's working directory (usually `/workspace`) to prepare specific dependencies and runtime environments required by this application.
2. **Skill Entrypoint**: If your Agent is associated with skills, the [initialization entrypoint script](../skill/initialization) (e.g., `entrypoint.sh`) bundled inside the published skill package will be extracted and executed in the skill's deployment directory right after the application startup script completes.
<Alert icon="💡" context="warning">**Transactional Skill Deployment**: During skill deployment, packages are first extracted to a temporary folder (e.g., `.tmp-<versionId>-<random>`). Upon successful decompression, the folder is atomically renamed to the formal version directory to prevent corrupted partial extractions.</Alert>
### Lifecycle Flowchart
![Lifecycle Flowchart](/imgs/sandbox_lifecycle_flow_en.jpg)
---
## Status Deduplication
To prevent latency from running commands repeatedly during subsequent turns (such as reinstalling packages via `pip`), the system employs an efficient **status deduplication mechanism**:
- **Execution State Record**: The system maintains an execution state file inside the sandbox at `~/.fastgpt/agent-skill-entrypoints/state.json`.
- **Hash-based Deduplication (Application Startup Script)**: For your custom "Startup script (sh)", the system computes a **SHA-256 hash value** based on the script text and compares it with the executed hashes in `state.json`. If the script remains unmodified, the system **automatically skips execution** on subsequent requests, ensuring fast starts. The script will only run again if you edit its content or click "Clear Chat" to completely rebuild the sandbox.
- **Version ID-based Deduplication (Skill Entrypoint)**: Associated skills are deduplicated using their immutable skill **Version ID**. Since published skill versions are read-only, the entrypoint script executes only once during the sandbox's cold start as long as the bound version remains unchanged.
- **State Lifecycle**: The deduplication state is managed along with the virtual machine instance. When the virtual machine is rebuilt (due to clicking "Clear Chat" or system reclamation), a fresh environment is allocated, and all scripts will run again during the next cold start.
---
## Execution Constraints & Fault Tolerance
To ensure sandbox stability and responsiveness, the startup script is subject to the following system rules:
- **Character Length Limit**: Due to front-end validation and input constraints, the startup script supports a maximum of **16,384 characters (approx. 16KB)**. Any script exceeding this limit is truncated on save. For complex initialization logic, write it inside a separate skill entrypoint or fetch and execute remote scripts.
- **Timeout Protection**: Script execution is protected by a timeout limit controlled by the environment variable `AGENT_SANDBOX_ENTRYPOINT_TIMEOUT_SECONDS`, with a **default timeout of 30 seconds** (clamped between 1 and 600 seconds). The process is forcefully terminated if execution exceeds this duration.
- **Non-blocking Workflow**: If your startup script errors out (exits with a non-zero code), times out, or fails to read/write the state file, the system **will not block** the main chat workflow. The AI continues executing subsequent workflow nodes or tools, though it may hit runtime exceptions later if critical dependencies are missing. You can troubleshoot these errors in the preview logs or through the Computer File Manager.
- **Log Truncation**: Combined standard output (stdout) and standard error (stderr) logs for the startup script are capped at approximately **8KB**. When logged to the system, output is truncated to 4,000 characters to prevent excessive resource utilization.
---
## Technical Solution Details
To ensure virtual machine environment initialization is robust under complex concurrency, tight latency budgets, and low-code orchestrations, the platform implements the following core designs:
### VM Initialization Flowchart (Technical Details)
![VM Startup and Script Initialization Flow](/imgs/sandbox_startup_flow_en.jpg)
### 1. Redis Lease Distributed Lock
When facing concurrent chat requests or multiple processes cold-starting a virtual machine, having several processes simultaneously write files, decompress skill packages, or run startup scripts can lead to lock conflicts or container filesystem corruption.
- **Locking Mechanism**: The backend uses an exclusive Redis-based lease lock to guard initialization, with the lock key formatted as `agent-sandbox:init:${sandboxId}`.
- **Renewal and Conflict Avoidance**: The initialization lease lock has a default TTL of 3 minutes. During execution, a background timer automatically invokes a secure Lua heartbeat script every 30 seconds (1/6 of the lease duration) to renew the lease.
- **Initialization Error Handling**: If a concurrent request fails to acquire the lease lock, the backend catches the Redis lease conflict and immediately throws an `AgentSandboxInitializingError`, effectively preventing deadlocks, split-brain states, or concurrent filesystem corruptions.
### 2. State Reconcile & Delta Sync
Before a session becomes ready, the system reconciles the virtual machine state with the expected skill deployments:
- **Delta Synchronization & Cleanups**: In each run, the system scans directories inside the VM, compares them with the target skill version list, fetches only delta packages, and performs a differential `rm -rf` cleanup on decommissioned versions.
- **Deduplication State Reconciliation**: After reading the `state.json` file inside the sandbox, the system filters the executed skill ID list against the current session's active skill set. It automatically removes any obsolete skill version IDs no longer in use (marking it as dirty and rewrites the state), keeping `state.json` clean.
- **Atomic Extraction**: To protect the environment against Zip Bomb attacks or incomplete unpacking, skill packages are extracted and validated in a temporary workspace directory (e.g., `.tmp-<versionId>-<random>`). Upon complete successful extraction, it is atomically renamed to the formal version directory using a single `mv` command, hiding any intermediate corrupted state.
### 3. Deduplication, Secure Run & HOME Path
The execution state `state.json` is preserved inside the user's HOME directory (`~/.fastgpt/agent-skill-entrypoints/state.json`) inside the container.
- **Dynamic HOME Path Resolution**: The HOME path of the container is resolved dynamically. The system first executes `printf "%s" "$HOME"` to check the env variable. If that fails, it falls back to `sh -c "echo ~"`. This guarantees the state file is always generated under the correct home directory path.
- **Base64 Secure Run (Application Script)**: To prevent script escaping, line break issues, or syntax corruption caused by special characters, the custom startup script is encoded in Base64 on the server and safely decoded in the sandbox using `printf %s '<base64>' | base64 -d | /bin/bash`. If a `workDirectory` is set, it will `cd` into that workspace first.
- **Instant Step-by-Step State Saves (Skill Script)**: During execution of multiple skill entrypoints, `state.json` is **instantly updated and saved** right after each `entrypoint.sh` runs successfully. Even if a subsequent script fails or times out, the successful states of previously run scripts are locked in and skipped on the next run.
- **Bidirectional Output Truncation**: Both stdout and stderr of the scripts are piped and truncated independently to a maximum of 8KB using `> >(tail -c 8192) 2> >(tail -c 8192 >&2)`. This protects log storage and network bandwidth while keeping standard outputs separate from error traces.
### 4. Concurrency Lock Isolation
To prevent the Agent decision loop and associated external tools (ToolCall) from repeatedly reconnecting or spinning up multiple VM clients during a single turns:
- **Shared SandboxClient**: The system stores and propagates the active `SandboxClient` instance across the execution dispatch layers, ensuring all steps in a single chat turn share a single virtual machine connection context.
---
title: 启动脚本
description: 深入理解 Agent V2 虚拟机的启动脚本配置、执行生命周期、哈希去重与运行环境限制。
---
import { Alert } from '@/components/docs/Alert';
在启用虚拟机后,您可以通过配置 **启动脚本**,在沙盒环境拉起后、AI 工作流正式开始执行前,自动执行指定的 Shell 命令。这通常用于配置环境变量、更换软件源、安装 Python 依赖(pip)或系统级工具等。
---
## 脚本配置与生命周期
在 Agent 配置面板的“虚拟机配置”中,您可以直接在 **启动脚本(sh)** 的代码编辑器中编写您的 Shell 脚本。
![启动脚本编辑器](/imgs/agent_startup_script_editor.png)
### 脚本执行顺序与范围
当一个新会话启动或虚拟机重新拉起时,系统会按顺序在后台执行相应的脚本:
1. **应用启动脚本**:即您在配置面板中自定义的 Shell 脚本。该脚本会在虚拟机的工作目录(通常为 `/workspace`)下执行,用于准备当前应用所需的特定运行依赖和环境。
2. **技能入口脚本(Skill Entrypoint)**:若您的 Agent 关联了技能(Skills),技能发布包中自带的[初始化脚本](../skill/initialization)(如 `entrypoint.sh`)会在上述“应用启动脚本”执行完毕后,在每个技能包自身的部署目录下自动执行。
<Alert icon="💡" context="warning">**技能包部署的原子性保障**:技能包在解压部署时,会先在临时目录(如 `.tmp-<versionId>-<random>`)中解压,解压完全成功后,再以原子操作整体替换为正式版本目录,有效避免解压失败导致出现损坏的半截目录。</Alert>
### 生命周期流程图
![生命周期流程图](/imgs/sandbox_lifecycle_flow_zh.jpg)
---
## 状态去重
为了避免每次对话交互(热启动)时重复执行命令(例如重复通过 `pip install` 安装依赖包)带来等待延迟,系统设计了高效的 **状态去重机制**:
- **运行状态记录**:系统在虚拟机内部维护了一个状态文件:`~/.fastgpt/agent-skill-entrypoints/state.json`。
- **哈希去重(应用启动脚本)**:对于您手动编写的“应用启动脚本”,系统会计算其文本内容的 **SHA-256 哈希特征值(Hash)**,并与 `state.json` 中已执行过的哈希值进行对比。如果脚本内容没有任何修改,系统在后续交互中将 **自动跳过执行**,确保实现快速启动;仅当您修改了脚本内容,或在调试预览中点击了“重开对话”触发沙盒彻底重建时,脚本才会重新执行。
- **版本 ID 去重(技能入口脚本)**:关联技能对应的入口脚本则基于 **技能版本 ID** 进行比对去重。因为发布的技能版本是不可变的,只要绑定的技能版本未改变,其入口脚本也仅会在沙箱首次冷启动时执行一次。
- **状态的生命周期**:去重状态随虚拟机实例生命周期进行管理。当虚拟机重建(点击“重开对话”或闲置被系统回收)时,由于分配的是全新环境,所有的脚本都将在首次冷启动时重新执行。
---
## 执行限制与容错机制
为保障沙箱的稳定运行与响应时效,虚拟机启动脚本在运行时受以下系统规则约束:
- **字符长度限制**:由于前端校验与输入限制,虚拟机启动脚本最大支持 **16,384 个字符(约 16KB)**。超出此长度的脚本在保存时会被自动截断。对于复杂的初始化逻辑,建议编写在单独的技能入口脚本中,或在启动脚本中拉取远程脚本执行。
- **超时终止限制**:脚本执行存在超时保护限制,由系统环境变量 `AGENT_SANDBOX_ENTRYPOINT_TIMEOUT_SECONDS` 控制,**默认超时时间为 30 秒**(限制在 1 秒到 600 秒之间)。如果超过该时间脚本仍未执行完毕,系统将强制终止该进程。
- **非阻塞主流程**:即使您的启动脚本在执行时报错(退出状态码非 0)、超时终止,或是状态文件读写发生异常,系统也**不会阻断主对话流程**。AI 依然会继续执行后续的工作流或工具调用,但可能会因缺少特定依赖而在代码运行时抛出异常。您可以在调试预览的日志或虚拟机文件管理器中排查此类问题。
- **日志长度截断**:启动脚本标准输出(stdout)和标准错误(stderr)的最大日志输出量限制在 **8KB** 左右。在系统记录日志时,日志内容将被截断至 4,000 个字符,以防止过大的日志输出占用过多的系统与网络资源。
---
## 技术方案说明
为确保虚拟机环境初始化在复杂并发、高时效要求与低代码编排场景下的健壮性,系统在底层采用了以下核心设计:
### 虚拟机初始化流程图(技术细节)
![虚拟机启动与初始化流程](/imgs/sandbox_startup_flow_zh.jpg)
### 1. Redis Lease 分布式锁保障
当面临并发对话请求或多进程冷启动虚拟机时,如果多个进程同时触发文件注入、技能解压或运行启动脚本,可能导致文件锁交错冲突甚至容器文件损坏。
- **锁机制**:系统在后端服务端基于 Redis 独占式获取并维护租约锁,锁的 Key 格式为 `agent-sandbox:init:${sandboxId}`。
- **续期与防碰撞**:初始化租约锁的 TTL 默认为 3 分钟,在执行期间,系统通过后台定时器每 30 秒(租约周期的 1/6)自动调用专用的心跳 Lua 脚本进行安全续期。
- **初始化独占报错**:一旦并发请求获取锁失败并捕获为 Redis 租约冲突,系统将立即抛出 `AgentSandboxInitializingError`(沙箱初始化中)异常,有效防止死锁、脑裂或文件错乱冲突。
### 2. 状态调和与失效清理
在每次会话就绪前,系统会对沙箱内部的状态和部署的技能包进行“状态调和”:
- **已部署包差量同步**:系统会在虚拟机内列出已部署目录,并与当前版本关联的技能列表比对。只下载增量缺少的技能包,并用 `rm -rf` 差量清理掉已废弃的技能版本。
- **去重状态调和(Reconciliation)**:系统在读取虚拟机内部的 `state.json` 后,会将其中记录的已执行技能 ID 列表与当前会话实际使用的技能 ID 集合进行交集过滤,自动剔除所有已经不再被当前会话选用的旧技能版本 ID(标记为 Dirty 并重写),确保去重状态文件的纯净性。
- **解压与原子替换**:为了防止 Zip 炸弹攻击及解压异常损坏原本正常的部署,技能包会优先在临时目录(如 `.tmp-<versionId>-<random>`)中完成解压和完整性验证。当解压步骤完全成功后,再以原子操作 `mv` 整体重命名为正式版本目录,从而屏蔽中间不确定状态。
### 3. 去重、安全执行与 Home 路径解析
状态记录文件 `state.json` 保存在虚拟机容器的用户 HOME 目录(即 `~/.fastgpt/agent-skill-entrypoints/state.json`)中。
- **动态解析 HOME 路径**:虚拟机容器的 HOME 目录非硬编码写入,而是优先通过执行 `printf "%s" "$HOME"` 动态尝试解析;若读取失败则以 `sh -c "echo ~"` 作为备用方案,以此保证状态文件总是被正确存放在当前用户的 home 目录下。
- **Base64 防逃逸执行(应用脚本)**:为避免直接拼接 Shell 文本由于特殊字符、换行或非法字符导致语法解析错乱或注入风险,您配置的应用启动脚本会整体进行 base64 编码,并通过 `printf %s '<base64>' | base64 -d | /bin/bash` 的方式在虚拟机中安全解码运行。若配置了 `workDirectory`,则会先 `cd` 切换到指定工作区。
- **单步即时保存(技能脚本)**:在遍历执行多个关联技能的 `entrypoint.sh` 时,系统每成功完成一个技能的入口脚本执行,便会**即时更新并保存**一次 `state.json`。这样即使中途因后续某个技能超时或报错而中断,先前已成功初始化的技能状态也已被固化,下次对话将自动跳过它们,实现最大化复用。
- **日志标准输出与标准错误双向截断**:脚本执行时,系统通过 `> >(tail -c 8192) 2> >(tail -c 8192 >&2)` 管道命令,将 stdout 和 stderr 独立限制在最大 8KB。既保护了整体日志吞吐量,又完全分离了常规输出与错误堆栈。
### 4. 并发锁隔离与客户端共享
为了避免多轮对话的 Agent 决策环(AI 思考)与外部工具(ToolCall)重复建立沙盒连接或分配不同实例:
- **共享 SandboxClient**:系统将当前会话分配的 `SandboxClient` 作为局部上下文在执行调度层进行跨模块透传与隔离,保证同一会话内多轮连续交互及工具调用共享并共用同一个虚拟机客户端实例。
......@@ -3,6 +3,8 @@ title: Virtual Machine
description: Understand the core concepts, runtime design, and debugging workflow for the Agent V2 Virtual Machine (Computer Sandbox).
---
import { Alert } from '@/components/docs/Alert';
![Virtual Machine Sandbox](/imgs/agent_vm_intro.png)
In FastGPT Agent V2, the **Virtual Machine** is a dedicated, physically isolated, and secure lightweight Linux running sandbox environment provisioned for each chat session. It equips the Agent with real-world computation, code execution, and file read/write capabilities, allowing the AI to not only "think" but also execute code to solve complex tasks like a human programmer.
......@@ -19,7 +21,7 @@ With the virtual machine, the Agent can:
- **Execute Dynamic Code**: Run Python, Node.js, or Shell scripts via the code executor to perform complex calculations and data manipulations.
- **Read and Write Local Files**: Create, modify, and read files in the isolated `/workspace` directory, including generating charts or processing uploaded CSV/Excel sheets.
- **Customize the Environment & Startup Script**: Dynamically customize the runtime environment by binding SKILL packages, or configuring custom [Startup Scripts](./startup) (which automatically execute specified Shell initialization commands, such as installing dependencies or setting environment variables, after the VM spins up but before the AI workflow starts).
- **Customize the Environment & Startup Script**: Dynamically customize the runtime environment by binding SKILL packages, or configuring custom [Startup Scripts](#virtual-machine-lifecycle) (which automatically execute specified Shell initialization commands, such as installing dependencies or setting environment variables, after the VM spins up but before the AI workflow starts).
---
......@@ -78,3 +80,46 @@ During testing, the chat bubble header streams real-time VM provisioning updates
### Complete Sandbox Reconstruction upon Reset
Clicking the "Restart" button resets the test session, and the next interaction will provision and initialize a clean, brand-new VM container to prevent historical file contamination.
---
## Virtual Machine Lifecycle
When the **Computer** option is enabled, you can configure a **Startup Script** to automatically execute shell commands right after the sandbox environment spins up and before the AI workflow officially starts. This is commonly used for configuring environment variables, modifying software package sources, or installing Python packages (`pip`) and system-level utilities.
### Script Configuration & Lifecycle
Under the "Computer Configuration" section of the Agent Configuration Panel, you can write standard Shell commands directly inside the **Startup script (sh)** code editor.
![Startup Script Editor](/imgs/agent_startup_script_editor.png)
#### Script Execution Sequence and Scope
When a new session starts or the virtual machine is reconstructed, the system executes the scripts sequentially in the background:
1. **Application Startup Script**: The custom Shell script configured in your Agent panel. It executes inside the virtual machine's working directory (usually `/workspace`) to prepare specific dependencies and runtime environments required by this application.
2. **Skill Entrypoint**: If your Agent is associated with skills, the [initialization entrypoint script](../skill/initialization) (e.g., `entrypoint.sh`) bundled inside the published skill package will be extracted and executed in the skill's deployment directory right after the application startup script completes.
<Alert icon="💡" context="warning">**Transactional Skill Deployment**: During skill deployment, packages are first extracted to a temporary folder (e.g., `.tmp-<versionId>-<random>`). Upon successful decompression, the folder is atomically renamed to the formal version directory to prevent corrupted partial extractions.</Alert>
#### Lifecycle Flowchart
![Lifecycle Flowchart](/imgs/sandbox_lifecycle_flow_en.jpg)
### Status Deduplication
To prevent latency from running commands repeatedly during subsequent turns (such as reinstalling packages via `pip`), the system employs an efficient **status deduplication mechanism**:
- **Execution State Record**: The system maintains an execution state file inside the sandbox at `~/.fastgpt/agent-skill-entrypoints/state.json`.
- **Hash-based Deduplication (Application Startup Script)**: For your custom "Startup script (sh)", the system computes a **SHA-256 hash value** based on the script text and compares it with the executed hashes in `state.json`. If the script remains unmodified, the system **automatically skips execution** on subsequent requests, ensuring fast starts. The script will only run again if you edit its content or click "Clear Chat" to completely rebuild the sandbox.
- **Version ID-based Deduplication (Skill Entrypoint)**: Associated skills are deduplicated using their immutable skill **Version ID**. Since published skill versions are read-only, the entrypoint script executes only once during the sandbox's cold start as long as the bound version remains unchanged.
- **State Lifecycle**: The deduplication state is managed along with the virtual machine instance. When the virtual machine is rebuilt (due to clicking "Clear Chat" or system reclamation), a fresh environment is allocated, and all scripts will run again during the next cold start.
### Execution Constraints & Fault Tolerance
To ensure sandbox stability and responsiveness, the startup script is subject to the following system rules:
- **Character Length Limit**: Due to front-end validation and input constraints, the startup script supports a maximum of **16,384 characters (approx. 16KB)**. Any script exceeding this limit is truncated on save. For complex initialization logic, write it inside a separate skill entrypoint or fetch and execute remote scripts.
- **Timeout Protection**: Script execution is protected by a timeout limit controlled by the environment variable `AGENT_SANDBOX_ENTRYPOINT_TIMEOUT_SECONDS`, with a **default timeout of 30 seconds** (clamped between 1 and 600 seconds). The process is forcefully terminated if execution exceeds this duration.
- **Non-blocking Workflow**: If your startup script errors out (exits with a non-zero code), times out, or fails to read/write the state file, the system **will not block** the main chat workflow. The AI continues executing subsequent workflow nodes or tools, though it may hit runtime exceptions later if critical dependencies are missing. You can troubleshoot these errors in the preview logs or through the Computer File Manager.
- **Log Truncation**: Combined standard output (stdout) and standard error (stderr) logs for the startup script are capped at approximately **8KB**. When logged to the system, output is truncated to 4,000 characters to prevent excessive resource utilization.
......@@ -3,6 +3,8 @@ title: 虚拟机
description: 深入了解 Agent V2 虚拟机(沙箱)的核心概念、运行设计与联调指南。
---
import { Alert } from '@/components/docs/Alert';
![虚拟机运行环境](/imgs/agent_vm_intro.png)
在 FastGPT Agent V2 中,**虚拟机** 是专为每个会话分配的、物理隔离且安全的轻量级 Linux 运行沙盒环境。它为 Agent 提供了真实的计算、代码执行和文件读写操作能力,使得 AI 不仅仅能“思考”,还能像人类程序员一样通过实际运行代码来解决复杂任务。
......@@ -19,7 +21,7 @@ description: 深入了解 Agent V2 虚拟机(沙箱)的核心概念、运行
- **执行动态代码**:通过代码执行器运行 Python、Node.js 甚至 Shell 脚本,自主进行复杂计算或数据处理。
- **读写本地文件**:在独立的 `/workspace` 目录下创建、修改和读取文件,包括生成图表、处理上传的 CSV/Excel 电子表格等。
- **环境自定义与启动脚本**:通过关联 SKILL 包,或配置自定义的 [启动脚本](./startup)(在虚拟机拉起后且 AI 正式开始前自动在后台执行的 Shell 命令,用于安装特定软件源、Python 依赖包或系统级工具等),动态准备专属于您应用的运行环境。
- **环境自定义与启动脚本**:通过关联 SKILL 包,或配置自定义的 [启动脚本](#虚拟机生命周期)(在虚拟机拉起后且 AI 正式开始前自动在后台执行的 Shell 命令,用于安装特定软件源、Python 依赖包或系统级工具等),动态准备专属于您应用的运行环境。
---
......@@ -29,7 +31,7 @@ description: 深入了解 Agent V2 虚拟机(沙箱)的核心概念、运行
### 1. 会话级隔离与生命周期管理
- 虚拟机与用户的对话会话(Session)强绑定。不同用户、不同会话之间的运行环境完全物理隔离。
- 虚拟机与用户的对话会话(Session)强绑定。不同用户、不同会话之间的运行环境完全物理隔离。(注意,未来版本将会改至用户级别隔离,从而减少资源消耗)
- 系统通过心跳(Keepalive)机制维持活动容器的存活。当会话长时间闲置(如超过数分钟无新请求)时,系统会自动回收并销毁该虚拟机实例以释放服务器资源。
### 2. 状态存续(Session Persistence)
......@@ -47,7 +49,7 @@ description: 深入了解 Agent V2 虚拟机(沙箱)的核心概念、运行
---
## 虚拟机调试
## 虚拟机使用
当您在左侧配置面板中**启用了虚拟机**时,调试预览窗口将自动激活以下沙盒专属调试能力:
......@@ -78,3 +80,46 @@ description: 深入了解 Agent V2 虚拟机(沙箱)的核心概念、运行
### 重置对话重建沙箱
点击“重开对话”按钮重置测试时,下一次交互会重新拉起并初始化一个干净、全新的虚拟机容器,避免历史测试生成的文件污染新一轮的调试。
---
## 虚拟机生命周期
在启用虚拟机后,您可以通过配置 **启动脚本**,在沙盒环境拉起后、AI 工作流正式开始执行前,自动执行指定的 Shell 命令。这通常用于配置环境变量、更换软件源、安装 Python 依赖(pip)或系统级工具等。
### 脚本配置与生命周期
在 Agent 配置面板的“虚拟机配置”中,您可以直接在 **启动脚本(sh)** 的代码编辑器中编写您的 Shell 脚本。
![启动脚本编辑器](/imgs/agent_startup_script_editor.png)
#### 脚本执行顺序与范围
当一个新会话启动或虚拟机重新拉起时,系统会按顺序在后台执行相应的脚本:
1. **应用启动脚本**:即您在配置面板中自定义的 Shell 脚本。该脚本会在虚拟机的工作目录(通常为 `/workspace`)下执行,用于准备当前应用所需的特定运行依赖和环境。
2. **技能入口脚本(Skill Entrypoint)**:若您的 Agent 关联了技能(Skills),技能发布包中自带的[初始化脚本](../skill/initialization)(如 `entrypoint.sh`)会在上述“应用启动脚本”执行完毕后,在每个技能包自身的部署目录下自动执行。
<Alert icon="💡" context="warning">**技能包部署的原子性保障**:技能包在解压部署时,会先在临时目录(如 `.tmp-<versionId>-<random>`)中解压,解压完全成功后,再以原子操作整体替换为正式版本目录,有效避免解压失败导致出现损坏的半截目录。</Alert>
#### 生命周期流程图
![生命周期流程图](/imgs/sandbox_lifecycle_flow_zh.jpg)
### 状态去重
为了避免每次对话交互(热启动)时重复执行命令(例如重复通过 `pip install` 安装依赖包)带来等待延迟,系统设计了高效的 **状态去重机制**:
- **运行状态记录**:系统在虚拟机内部维护了一个状态文件:`~/.fastgpt/agent-skill-entrypoints/state.json`。
- **哈希去重(应用启动脚本)**:对于您手动编写的“应用启动脚本”,系统会计算其文本内容的 **SHA-256 哈希特征值(Hash)**,并与 `state.json` 中已执行过的哈希值进行对比。如果脚本内容没有任何修改,系统在后续交互中将 **自动跳过执行**,确保实现快速启动;仅当您修改了脚本内容,或在调试预览中点击了“重开对话”触发沙盒彻底重建时,脚本才会重新执行。
- **版本 ID 去重(技能入口脚本)**:关联技能对应的入口脚本则基于 **技能版本 ID** 进行比对去重。因为发布的技能版本是不可变的,只要绑定的技能版本未改变,其入口脚本也仅会在沙箱首次冷启动时执行一次。
- **状态的生命周期**:去重状态随虚拟机实例生命周期进行管理。当虚拟机重建(点击“重开对话”或闲置被系统回收)时,由于分配的是全新环境,所有的脚本都将在首次冷启动时重新执行。
### 执行限制与容错机制
为保障沙箱的稳定运行与响应时效,虚拟机启动脚本在运行时受以下系统规则约束:
- **字符长度限制**:由于前端校验与输入限制,虚拟机启动脚本最大支持 **16,384 个字符(约 16KB)**。超出此长度的脚本在保存时会被自动截断。对于复杂的初始化逻辑,建议编写在单独的技能入口脚本中,或在启动脚本中拉取远程脚本执行。
- **超时终止限制**:脚本执行存在超时保护限制,由系统环境变量 `AGENT_SANDBOX_ENTRYPOINT_TIMEOUT_SECONDS` 控制,**默认超时时间为 30 秒**(限制在 1 秒到 600 秒之间)。如果超过该时间脚本仍未执行完毕,系统将强制终止该进程。
- **非阻塞主流程**:即使您的启动脚本在执行时报错(退出状态码非 0)、超时终止,或是状态文件读写发生异常,系统也**不会阻断主对话流程**。AI 依然会继续执行后续的工作流或工具调用,但可能会因缺少特定依赖而在代码运行时抛出异常。您可以在调试预览的日志或虚拟机文件管理器中排查此类问题。
- **日志长度截断**:启动脚本标准输出(stdout)和标准错误(stderr)的最大日志输出量限制在 **8KB** 左右。在系统记录日志时,日志内容将被截断至 4,000 个字符,以防止过大的日志输出占用过多的系统与网络资源。
{
"title": "General Configuration",
"description": "Model, file upload, input guide, and basic app configuration.",
"pages": ["ai_settings", "fileInput", "chat_input_guide"]
"pages": ["ai_settings", "fileInput", "welcomeText", "voiceInput","chat_input_guide"]
}
{
"title": "通用配置",
"description": "应用构建中的模型、文件上传、输入引导和基础配置。",
"pages": ["ai_settings", "fileInput", "chat_input_guide"]
"pages": ["ai_settings", "fileInput", "welcomeText", "voiceInput", "chat_input_guide"]
}
---
title: Voice Input
description: FastGPT voice input configuration
---
Voice Input lets users record speech in the chat UI and automatically convert it to text. It is useful on mobile devices, in customer support workflows, and in scenarios where typing is inconvenient.
## Configuration Entry
In the app editor, find **Voice Input** and click the settings button on the right to open the voice input configuration dialog.
| | |
| ---------------------------------------------------------- | ------------------------------------------------------------- |
| ![Voice input entry](../../../../public/imgs/image-29.png) | ![Voice input settings](../../../../public/imgs/image-28.png) |
## Enable Voice Input
After Voice Input is enabled, the chat input box shows a voice recording entry. Users can click it to start recording, and FastGPT converts the recording to text after it finishes.
If the browser or current environment does not support voice recording, the frontend will show a voice input unsupported message.
## Auto Send
When **Auto Send** is enabled, FastGPT automatically sends the recognized text after recording finishes. Users do not need to click the send button manually.
If users should review the recognized text before sending, keep Auto Send disabled.
## Auto Voice Response
When **Auto Voice Response** is enabled, questions sent through voice input will also receive AI responses that play automatically as audio.
This requires voice playback to be enabled. If voice playback is not enabled, the AI still returns a text response, but audio will not play automatically.
## Recommendations
- Enable Voice Input for mobile or on-site scenarios to improve input efficiency.
- For scenarios that require high recognition accuracy, disable Auto Send so users can verify the recognized text first.
- For continuous voice interactions, enable both Auto Send and Auto Voice Response.
---
title: 语音输入
description: FastGPT 语音输入配置说明
---
语音输入支持用户在前台对话中进行语音录入,并自动识别转换为文字。该能力适合移动端、客服、现场记录等不方便打字的场景。
## 配置入口
在应用编辑页中,找到 **语音输入** 配置项,点击右侧的设置按钮,即可打开语音输入配置弹窗。
| | |
| ------------------------------------------------- | ------------------------------------------------- |
| ![alt text](../../../../public/imgs/image-29.png) | ![alt text](../../../../public/imgs/image-28.png) |
## 开启语音输入
开启后,前台对话输入框中会显示语音录入入口。用户点击后可以开始录音,录音完成后系统会将语音识别为文字。
如果浏览器或当前环境不支持语音录入,前台会提示浏览器不支持语音输入。
## 自动发送
开启 **自动发送** 后,用户完成语音录入并识别为文字后,系统会自动发送该内容,不需要用户再手动点击发送按钮。
如果希望用户在发送前检查识别结果,可以关闭自动发送,让用户确认文字内容后再发送。
## 自动语音回复
开启 **自动语音回复** 后,通过语音输入发送的问题,AI 的回复也会自动以语音形式播放。
该能力需要同时开启语音播报配置。若未开启语音播报,AI 仍会正常生成文字回复,但不会自动播放语音。
## 使用建议
- 面向移动端或现场场景的应用,可以开启语音输入提升输入效率。
- 对识别准确性要求较高的场景,建议关闭自动发送,让用户先确认识别文本。
- 需要连续语音交互时,可以同时开启自动发送和自动语音回复。
---
title: Welcome Text
description: FastGPT welcome text configuration
---
Welcome Text is the initial message sent automatically before each new conversation starts. Use it to introduce what the app can do, clarify the question scope, or provide common entry points.
## Configuration Entry
In the app editor, find **Welcome Text** and click the settings button on the right to edit the content.
![Welcome text settings](../../../../public/imgs/image-2.png)
## Markdown Support
Welcome Text supports standard Markdown syntax, including headings, lists, links, and bold text. This helps users quickly understand what the app can help with.
## Quick Questions
Welcome Text supports the special `[Quick Question]` format. FastGPT displays these items as clickable buttons, and users can send the preset question with one click.
Example:
```md
Hello, I can help you look up product information and support policies.
[How do I request support?]
[What scenarios does this product support?]
[Recommend a starter plan]
```
When users start a new conversation, they will see the welcome message and quick question buttons. After a user clicks a quick question, FastGPT sends that question as the user's message.
![Quick questions in chat](../../../../public/imgs/image-27.png)
## Recommendations
- Keep the welcome text short and clear, focusing on what the app can solve.
- Use quick questions for common scenarios, and avoid adding too many options.
- If the app requires a specific input format, include an example in the welcome text.
---
title: 对话开场白
description: FastGPT 对话开场白配置说明
---
对话开场白是每次新对话开始前由系统自动发送的欢迎词,适合用于介绍应用能力、说明提问范围,或提供常用入口。
## 配置入口
在应用编辑页中,找到 **对话开场白** 配置项,点击右侧的设置按钮,即可编辑开场白内容。
![alt text](../../../../public/imgs/image-2.png)
## Markdown 支持
开场白支持标准 Markdown 语法,可以使用标题、列表、链接、加粗等格式,让用户进入对话后快速理解当前应用可以处理的问题。
## 快捷问题
开场白支持使用 `[快捷问题]` 特殊格式。配置后,界面会将对应内容展示为可点击按钮,用户点击后即可直接发送预设问题。
例如:
```md
你好,我可以帮你查询产品信息和售后政策。
[如何申请售后?]
[产品支持哪些使用场景?]
[帮我推荐一个入门方案]
```
用户进入新对话后,会看到欢迎词和快捷问题按钮。点击快捷问题按钮后,系统会把该问题作为用户输入发送到对话中。
![alt text](../../../../public/imgs/image-27.png)
## 使用建议
- 开场白应简短明确,优先说明应用能解决什么问题。
- 快捷问题建议覆盖高频场景,避免一次配置过多导致用户难以选择。
- 如果应用依赖固定格式输入,可以在开场白中给出示例。
......@@ -38,5 +38,5 @@ The execution state is stored in the `~/.fastgpt/agent-skill-entrypoints/state.j
To ensure the smooth execution of the AI workflow, the skill initialization script must adhere to the same execution constraints and fault tolerance rules as the application startup script:
- **Execution Constraints and Non-blocking Fault Tolerance**: The timeout protection (default 30 seconds), non-blocking workflow (failures do not block main execution), and 8KB log truncation limits are identical to those of the application startup script. For detailed parameters, please refer to [Application Startup Script Execution Constraints](../agentv2/startup#execution-constraints--fault-tolerance).
- **Execution Constraints and Non-blocking Fault Tolerance**: The timeout protection (default 30 seconds), non-blocking workflow (failures do not block main execution), and 8KB log truncation limits are identical to those of the application startup script. For detailed parameters, please refer to [Application Startup Script Execution Constraints](../agentv2/vm#execution-constraints--fault-tolerance).
- **Debug Mode Limitation**: In the skill edit mode, the virtual machine will not automatically execute the `entrypoint.sh` script. To verify the script's behavior, the skill developer can manually execute the commands inside the Workspace Terminal.
......@@ -38,5 +38,5 @@ description: 了解如何在技能包中配置和执行初始化脚本,准备
为保障 AI 流程的流畅运行,技能初始化脚本需要遵循与应用启动脚本一致的执行限制与容错规则:
- **执行约束与非阻断容错**:技能入口脚本的超时时间限制(默认 30 秒)、非阻塞设计(执行报错或超时不阻断主流程)以及 8KB 日志输出截断规则,均与应用启动脚本保持一致。具体细节指标请参考 [应用启动脚本的执行限制](../agentv2/startup#执行限制与容错机制)。
- **执行约束与非阻断容错**:技能入口脚本的超时时间限制(默认 30 秒)、非阻塞设计(执行报错或超时不阻断主流程)以及 8KB 日志输出截断规则,均与应用启动脚本保持一致。具体细节指标请参考 [应用启动脚本的执行限制](../agentv2/vm#执行限制与容错机制)。
- **调试预览限制**:在技能的编辑模式下,虚拟机不会自动执行技能的 `entrypoint.sh` 脚本。如果需要验证脚本效果,技能开发者可以直接在侧边栏调试区的控制台终端(Workspace Terminal)中手动执行相关命令。
---
title: Getting Started with FastGPT
description: FastGPT capabilities and advantages
title: Quick Overview of FastGPT
description: FastGPT's capabilities and advantages
---
import { Alert } from '@/components/docs/Alert';
FastGPT is a knowledge base Q&A system built on LLMs, combining intelligent conversation with visual orchestration to make AI application development simple and natural. Whether you're a developer or a business user, you can easily build your own AI applications.
FastGPT is an AI Agent application development platform built on large language models. It combines Knowledge Base Q&A, visual Workflows, Agent orchestration, tool calling, and skill extensions so developers and business users can quickly build custom AI applications.
<Alert icon="🤖" context="success">
Quick Start
- International: [https://fastgpt.io](https://fastgpt.io)
- China Mainland: [https://fastgpt.cn](https://fastgpt.cn)
Quick Start - International: [https://fastgpt.io](https://fastgpt.io) - China Mainland:
[https://fastgpt.cn](https://fastgpt.cn)
</Alert>
| | |
| --------------------- | --------------------------------- |
| ![](/imgs/intro/image1.png) | ![](/imgs/intro/image2.png) |
| | |
| ---------------------------------------------- | ---------------------------------------------- |
| ![alt text](../../../public/imgs/image-30.png) | ![alt text](../../../public/imgs/image-45.png) |
| ![alt text](../../../public/imgs/image-46.png) | ![alt text](../../../public/imgs/image-47.png) |
## Why FastGPT
### 1. Simple and Flexible, Like Building Blocks 🧱
# Why FastGPT
## 1. Simple and Flexible, Like Building Blocks 🧱
Build AI applications as easily as snapping LEGO bricks together. FastGPT provides rich functional modules that let you create personalized AI apps through simple drag-and-drop — no coding required, even for complex business processes.
## 2. Make Your Data Smarter 🧠
### 2. Make Your Data Smarter 🧠
FastGPT provides a complete data intelligence solution — from data import and preprocessing to knowledge matching and intelligent Q&A — fully automated. Combined with visual workflow design, you can easily build professional-grade AI applications.
## 3. Open Source and Easy to Integrate 🔗
FastGPT is open source under the Apache 2.0 license and supports custom development. Integrate quickly through standard APIs without modifying source code. Supports mainstream models including ChatGPT, Claude, DeepSeek, and ERNIE Bot, with continuous iteration to keep the product evolving.
### 3. Open Source and Easy to Integrate 🔗
FastGPT supports custom development. Integrate quickly through standard APIs without modifying source code. It supports mainstream models including ChatGPT, Claude, DeepSeek, and ERNIE Bot, with continuous iteration to keep the product evolving.
---
# What Can FastGPT Do
## 1. Comprehensive Knowledge Base
## What Can FastGPT Do
### 1. Comprehensive Knowledge Base
Import documents and data with automatic knowledge structuring. Features intelligent Q&A with multi-turn context understanding and a continuously improving knowledge base management experience.
![](/imgs/intro/image3.png)
## 2. Visual Workflow
### 2. Visual Workflow
FastGPT's intuitive drag-and-drop interface lets you build complex business processes with zero code. Rich functional node components handle diverse business needs with flexible process orchestration.
![](/imgs/intro/image4.png)
## 3. Intelligent Data Parsing
### 3. Intelligent Data Parsing
FastGPT's knowledge base system handles imported data with great flexibility — intelligently processing complex PDF structures while preserving images, tables, and LaTeX formulas. It automatically recognizes scanned files and structures content into clean Markdown format. It also supports automatic image annotation and indexing, making visual content searchable and ensuring knowledge is presented accurately in AI Q&A.
![](/imgs/intro/image5.png)
## 4. Workflow Orchestration
### 4. Workflow Orchestration
Flow-based workflow orchestration lets you design complex Q&A processes — such as querying databases, checking inventory, or booking lab resources.
![](/imgs/intro/image6.png)
## 5. Powerful API Integration
### 5. Powerful API Integration
FastGPT is fully compatible with the OpenAI API interface, supporting one-click integration with WeCom, WeChat Official Account, Lark, DingTalk, and more — bringing AI capabilities into your business workflows.
![](/imgs/intro/image7.png)
---
# Core Features
## Core Features
- Out-of-the-box knowledge base system
- Visual low-code workflow orchestration
......@@ -63,12 +77,14 @@ FastGPT is fully compatible with the OpenAI API interface, supporting one-click
---
# Knowledge Base Core Process Diagram
## Knowledge Base Core Process Diagram
![](/imgs/intro/image8.png)
---
## Community
FastGPT is an open source project driven by users and contributors. If you have questions or suggestions, try the following support channels. Our team and community will do our best to help.
- 📱 Scan to join the Lark community group 👇
......
......@@ -5,55 +5,69 @@ description: FastGPT 的能力与优势
import { Alert } from '@/components/docs/Alert';
FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,将智能对话与可视化编排完美结合,让 AI 应用开发变得简单自然。无论您是开发者还是业务人员,都能轻松打造专属的 AI 应用。
FastGPT 是一个基于大语言模型的 AI Agent 应用开发平台,集知识库问答、可视化工作流、Agent 编排、工具调用和技能扩展于一体,让开发者和业务人员都能快速构建专属 AI 应用。
<Alert icon="🤖" context="success">
快速开始体验
- 国际版:[https://fastgpt.io](https://fastgpt.io)
- 中国大陆版:[https://fastgpt.cn](https://fastgpt.cn)
快速开始体验 - 国际版:[https://fastgpt.io](https://fastgpt.io) -
中国大陆版:[https://fastgpt.cn](https://fastgpt.cn)
</Alert>
| | |
| --------------------- | --------------------------------- |
| ![](/imgs/intro/image1.png) | ![](/imgs/intro/image2.png) |
| | |
| ---------------------------------------------- | ---------------------------------------------- |
| ![alt text](../../../public/imgs/image-30.png) | ![alt text](../../../public/imgs/image-45.png) |
| ![alt text](../../../public/imgs/image-46.png) | ![alt text](../../../public/imgs/image-47.png) |
## FastGPT 的优势
### 1. 简单灵活,像搭积木一样简单 🧱
# FastGPT 的优势
## 1. 简单灵活,像搭积木一样简单 🧱
像搭乐高一样简单有趣,FastGPT 提供丰富的功能模块,通过简单拖拽就能搭建出个性化的 AI 应用,零代码也能实现复杂的业务流程。
## 2. 让数据更智能 🧠
### 2. 让数据更智能 🧠
FastGPT 提供完整的数据智能化解决方案,从数据导入、预处理到知识匹配,再到智能问答,全流程自动化。配合可视化的工作流设计,轻松打造专业级 AI 应用。
## 3. 开源开放,易于集成 🔗
FastGPT 基于 Apache 2.0 协议开源,支持二次开发。通过标准 API 即可快速接入,无需修改源码。支持 ChatGPT、Claude、DeepSeek 和文心一言等主流模型,持续迭代优化,始终保持产品活力。
### 3. 开源开放,易于集成 🔗
FastGPT 支持二次开发。通过标准 API 即可快速接入,无需修改源码。支持 ChatGPT、Claude、DeepSeek 和文心一言等主流模型,持续迭代优化,始终保持产品活力。
---
# FastGPT 能做什么
## 1. 全能知识库
## FastGPT 能做什么
### 1. 全能知识库
可轻松导入各式各样的文档及数据,能自动对其开展知识结构化处理工作。同时,具备支持多轮上下文理解的智能问答功能,还可为用户带来持续优化的知识库管理体验。
![](/imgs/intro/image3.png)
## 2. 可视化工作流
FastGPT直观的拖拽式界面设计,可零代码搭建复杂业务流程。还拥有丰富的功能节点组件,能应对多种业务需求,有着灵活的流程编排能力,按需定制业务流程。
### 2. 可视化工作流
FastGPT 直观的拖拽式界面设计,可零代码搭建复杂业务流程。还拥有丰富的功能节点组件,能应对多种业务需求,有着灵活的流程编排能力,按需定制业务流程。
![](/imgs/intro/image4.png)
## 3. 数据智能解析
FastGPT知识库系统对导入数据的处理极为灵活,可以智能处理PDF文档的复杂结构,保留图片、表格和LaTeX公式,自动识别扫描文件,并将内容结构化为清晰的Markdown格式。同时支持图片自动标注和索引,让视觉内容可被理解和检索,确保知识在AI问答中能被完整、准确地呈现和应用。
### 3. 数据智能解析
FastGPT 知识库系统对导入数据的处理极为灵活,可以智能处理 PDF 文档的复杂结构,保留图片、表格和 LaTeX 公式,自动识别扫描文件,并将内容结构化为清晰的 Markdown 格式。同时支持图片自动标注和索引,让视觉内容可被理解和检索,确保知识在 AI 问答中能被完整、准确地呈现和应用。
![](/imgs/intro/image5.png)
## 4. 工作流编排
### 4. 工作流编排
基于 Flow 模块的工作流编排,可以帮助你设计更加复杂的问答流程。例如查询数据库、查询库存、预约实验室等。
![](/imgs/intro/image6.png)
## 5. 强大的 API 集成
### 5. 强大的 API 集成
FastGPT 完全对齐 OpenAI 官方接口,支持一键接入企业微信、公众号、飞书、钉钉等平台,让 AI 能力轻松融入您的业务场景。
![](/imgs/intro/image7.png)
---
# 核心特性
## 核心特性
- 开箱即用的知识库系统
- 可视化的低代码工作流编排
......@@ -63,12 +77,14 @@ FastGPT 完全对齐 OpenAI 官方接口,支持一键接入企业微信、公
---
# 知识库核心流程图
## 知识库核心流程图
![](/imgs/intro/image8.png)
---
## 社区交流群
FastGPT 是一个由用户和贡献者参与推动的开源项目,如果您对产品使用存在疑问和建议,可尝试以下方式寻求支持。我们的团队与社区会竭尽所能为您提供帮助。
- 📱 扫码加入飞书交流群👇
......
......@@ -8,13 +8,14 @@ description: FastGPT Toc
- [/en/guide/admin/teamMode](/en/guide/admin/teamMode)
- [/en/guide/build/agentv2/debug](/en/guide/build/agentv2/debug)
- [/en/guide/build/agentv2/settings](/en/guide/build/agentv2/settings)
- [/en/guide/build/agentv2/startup](/en/guide/build/agentv2/startup)
- [/en/guide/build/agentv2/vm](/en/guide/build/agentv2/vm)
- [/en/guide/build/evaluation](/en/guide/build/evaluation)
- [/en/guide/build/faq](/en/guide/build/faq)
- [/en/guide/build/general/ai_settings](/en/guide/build/general/ai_settings)
- [/en/guide/build/general/chat_input_guide](/en/guide/build/general/chat_input_guide)
- [/en/guide/build/general/fileInput](/en/guide/build/general/fileInput)
- [/en/guide/build/general/voiceInput](/en/guide/build/general/voiceInput)
- [/en/guide/build/general/welcomeText](/en/guide/build/general/welcomeText)
- [/en/guide/build/publish/dingtalk](/en/guide/build/publish/dingtalk)
- [/en/guide/build/publish/feishu](/en/guide/build/publish/feishu)
- [/en/guide/build/publish/link](/en/guide/build/publish/link)
......
......@@ -8,13 +8,14 @@ description: FastGPT 文档目录
- [/guide/admin/teamMode](/guide/admin/teamMode)
- [/guide/build/agentv2/debug](/guide/build/agentv2/debug)
- [/guide/build/agentv2/settings](/guide/build/agentv2/settings)
- [/guide/build/agentv2/startup](/guide/build/agentv2/startup)
- [/guide/build/agentv2/vm](/guide/build/agentv2/vm)
- [/guide/build/evaluation](/guide/build/evaluation)
- [/guide/build/faq](/guide/build/faq)
- [/guide/build/general/ai_settings](/guide/build/general/ai_settings)
- [/guide/build/general/chat_input_guide](/guide/build/general/chat_input_guide)
- [/guide/build/general/fileInput](/guide/build/general/fileInput)
- [/guide/build/general/voiceInput](/guide/build/general/voiceInput)
- [/guide/build/general/welcomeText](/guide/build/general/welcomeText)
- [/guide/build/publish/dingtalk](/guide/build/publish/dingtalk)
- [/guide/build/publish/feishu](/guide/build/publish/feishu)
- [/guide/build/publish/link](/guide/build/publish/link)
......
......@@ -7,12 +7,12 @@
"content/guide/admin/sso.mdx": "2026-06-02T16:55:40+08:00",
"content/guide/admin/teamMode.en.mdx": "2026-05-07T15:06:40+08:00",
"content/guide/admin/teamMode.mdx": "2026-05-07T15:06:40+08:00",
"content/guide/build/agentv2/debug.en.mdx": "2026-06-23T18:38:31+08:00",
"content/guide/build/agentv2/debug.mdx": "2026-06-23T18:38:31+08:00",
"content/guide/build/agentv2/settings.en.mdx": "2026-06-23T18:38:31+08:00",
"content/guide/build/agentv2/settings.mdx": "2026-06-23T18:38:31+08:00",
"content/guide/build/agentv2/startup.en.mdx": "2026-06-23T18:38:31+08:00",
"content/guide/build/agentv2/startup.mdx": "2026-06-23T18:38:31+08:00",
"content/guide/build/agentv2/debug.en.mdx": "2026-06-23T22:44:56+08:00",
"content/guide/build/agentv2/debug.mdx": "2026-06-23T22:44:56+08:00",
"content/guide/build/agentv2/settings.en.mdx": "2026-06-23T22:44:56+08:00",
"content/guide/build/agentv2/settings.mdx": "2026-06-23T22:44:56+08:00",
"content/guide/build/agentv2/vm.en.mdx": "2026-06-23T22:44:56+08:00",
"content/guide/build/agentv2/vm.mdx": "2026-06-23T22:44:56+08:00",
"content/guide/build/evaluation.en.mdx": "2026-05-07T15:06:40+08:00",
"content/guide/build/evaluation.mdx": "2026-05-07T15:06:40+08:00",
"content/guide/build/faq.en.mdx": "2026-06-04T16:10:15+08:00",
......@@ -41,8 +41,8 @@
"content/guide/build/publish/wecom.mdx": "2026-05-07T15:06:40+08:00",
"content/guide/build/skill/development.en.mdx": "2026-06-16T14:52:43+08:00",
"content/guide/build/skill/development.mdx": "2026-06-16T14:52:43+08:00",
"content/guide/build/skill/initialization.en.mdx": "2026-06-23T18:38:31+08:00",
"content/guide/build/skill/initialization.mdx": "2026-06-23T18:38:31+08:00",
"content/guide/build/skill/initialization.en.mdx": "2026-06-23T22:44:56+08:00",
"content/guide/build/skill/initialization.mdx": "2026-06-23T22:44:56+08:00",
"content/guide/build/skill/integration.en.mdx": "2026-06-16T14:52:43+08:00",
"content/guide/build/skill/integration.mdx": "2026-06-16T14:52:43+08:00",
"content/guide/build/skill/intro.en.mdx": "2026-06-16T14:52:43+08:00",
......@@ -163,8 +163,8 @@
"content/plugin/model-presets.mdx": "2026-06-04T16:10:15+08:00",
"content/plugin/system-tool-development.en.mdx": "2026-06-09T16:03:58+08:00",
"content/plugin/system-tool-development.mdx": "2026-06-09T16:03:58+08:00",
"content/self-host/config/env.en.mdx": "2026-06-23T13:54:06+08:00",
"content/self-host/config/env.mdx": "2026-06-23T13:54:06+08:00",
"content/self-host/config/env.en.mdx": "2026-06-23T21:09:39+08:00",
"content/self-host/config/env.mdx": "2026-06-23T21:09:39+08:00",
"content/self-host/config/json.en.mdx": "2026-06-22T11:01:59+08:00",
"content/self-host/config/json.mdx": "2026-06-22T11:01:59+08:00",
"content/self-host/config/model/intro.en.mdx": "2026-06-04T16:10:15+08:00",
......@@ -287,7 +287,7 @@
"content/self-host/upgrading/4-14/4149.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/4-15/41500.en.mdx": "2026-06-23T13:54:06+08:00",
"content/self-host/upgrading/4-15/41500.mdx": "2026-06-23T13:54:06+08:00",
"content/self-host/upgrading/4-15/41501.mdx": "2026-06-22T11:01:59+08:00",
"content/self-host/upgrading/4-15/41501.mdx": "2026-06-23T21:09:39+08:00",
"content/self-host/upgrading/4-15/41502.en.mdx": "2026-05-25T11:21:30+08:00",
"content/self-host/upgrading/4-15/41502.mdx": "2026-06-23T13:54:06+08:00",
"content/self-host/upgrading/4-15/41503.en.mdx": "2026-05-28T16:21:09+08:00",
......@@ -295,7 +295,7 @@
"content/self-host/upgrading/4-15/41504.en.mdx": "2026-06-10T19:02:59+08:00",
"content/self-host/upgrading/4-15/41504.mdx": "2026-06-15T23:34:43+08:00",
"content/self-host/upgrading/4-15/41505.en.mdx": "2026-06-23T13:54:06+08:00",
"content/self-host/upgrading/4-15/41505.mdx": "2026-06-23T13:54:06+08:00",
"content/self-host/upgrading/4-15/41505.mdx": "2026-06-24T13:53:05+08:00",
"content/self-host/upgrading/outdated/40.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/40.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/41.en.mdx": "2026-04-26T21:08:47+08:00",
......@@ -436,6 +436,6 @@
"content/self-host/upgrading/outdated/499.mdx": "2026-05-07T15:06:40+08:00",
"content/self-host/upgrading/upgrade-intruction.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/upgrade-intruction.mdx": "2026-04-26T21:08:47+08:00",
"content/toc.en.mdx": "2026-06-23T18:38:31+08:00",
"content/toc.mdx": "2026-06-23T18:38:31+08:00"
"content/toc.en.mdx": "2026-06-23T22:44:56+08:00",
"content/toc.mdx": "2026-06-23T22:44:56+08:00"
}
\ No newline at end of file
......@@ -23,20 +23,15 @@ function getMdxImageSrc(src: unknown): string | undefined {
}
function MdxImage(props: ComponentProps<'img'>) {
const hasWidth = props.width !== undefined && props.width !== null && props.width !== '';
const hasHeight = props.height !== undefined && props.height !== null && props.height !== '';
const src = getMdxImageSrc(props.src);
if (hasWidth && hasHeight) {
return <ImageZoom {...(props as any)} />;
}
if (!src) return null;
// 文档中的多数 Markdown 图片没有 width/height,使用自定义 img 保留现有布局,并只复用预览能力。
return (
<img
{...props}
src={getMdxImageSrc(props.src)}
alt={props.alt ?? ''}
loading={props.loading ?? 'lazy'}
/>
<ImageZoom {...(props as any)} src={src} zoomInProps={{ alt: props.alt ?? '' }}>
<img {...props} src={src} alt={props.alt ?? ''} loading={props.loading ?? 'lazy'} />
</ImageZoom>
);
}
......
......@@ -6,6 +6,9 @@ import { parseSkillMarkdown, shellQuote, joinSandboxPath, getSkillsRootPath } fr
import { getLogger, LogCategories } from '../../../../common/logger';
import type { DeployedSkillInfo, DeployedSkillVersion } from './types';
import { serviceEnv } from '../../../../env';
import { authSkillByTmbId } from '../../../../support/permission/skill/auth';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { SkillErrEnum } from '@fastgpt/global/common/error/code/skill';
export type { DeployedSkillInfo, DeployedSkillVersion } from './types';
......@@ -100,11 +103,13 @@ export const injectAgentSkillFilesToSandbox = async ({
sandbox,
skillIds,
teamId,
tmbId,
workDirectory
}: {
sandbox: ISandbox;
skillIds: string[];
teamId: string;
tmbId?: string;
workDirectory: string;
}): Promise<DeployedSkillVersion[]> => {
const skillsRootPath = getSkillsRootPath(workDirectory);
......@@ -145,17 +150,49 @@ export const injectAgentSkillFilesToSandbox = async ({
return [];
}
const skills = await MongoAgentSkills.find({
const teamSkills = await MongoAgentSkills.find({
_id: { $in: skillIds },
teamId,
deleteTime: null
});
if (skills.length === 0) {
if (teamSkills.length === 0) {
logger.warn('[Agent Skills] No valid skills found from input skillIds', { skillIds });
await cleanupStaleDirs(new Set());
return [];
}
const skills = tmbId
? (
await Promise.all(
teamSkills.map(async (skill) => {
try {
await authSkillByTmbId({
tmbId,
skillId: String(skill._id),
per: ReadPermissionVal
});
return skill;
} catch (error) {
if (error !== SkillErrEnum.unAuthSkill && error !== SkillErrEnum.unExist) {
throw error;
}
logger.warn('[Agent Skills] Skip unauthorized skill during runtime injection', {
skillId: String(skill._id),
tmbId
});
return null;
}
})
)
).filter((skill): skill is (typeof teamSkills)[number] => !!skill)
: teamSkills;
if (skills.length === 0) {
await cleanupStaleDirs(new Set());
return [];
}
const currentVersionIds = skills
.map((skill) => skill.currentVersionId)
.filter((id): id is NonNullable<typeof id> => !!id);
......
......@@ -20,7 +20,10 @@ import { MongoChatInputGuide } from '../chat/inputGuide/schema';
import { MongoChatFavouriteApp } from '../chat/favouriteApp/schema';
import { MongoChatSetting } from '../chat/setting/schema';
import { MongoResourcePermission } from '../../support/permission/schema';
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import {
PerResourceTypeEnum,
ReadPermissionVal
} from '@fastgpt/global/support/permission/constant';
import { removeImageByPath } from '../../common/file/image/controller';
import { MongoAppLogKeys } from './logs/logkeysSchema';
import { MongoChatItemResponse } from '../chat/chatItemResponseSchema';
......@@ -39,6 +42,7 @@ import {
} from '@fastgpt/global/core/app/formEdit/type';
import z from 'zod';
import { nodeInputIsReference } from '@fastgpt/global/core/workflow/utils';
import { authSkillByTmbId } from '../../support/permission/skill/auth';
const logger = getLogger(LogCategories.MODULE.APP.FOLDER);
......@@ -125,6 +129,45 @@ export const beforeUpdateAppFormat = ({ nodes }: { nodes?: StoreNodeItemType[] }
});
};
/**
* 发布应用前校验静态绑定的 Agent Skill 对当前成员可读。
* 引用输入在发布阶段没有确定值,运行时会按实际值再次过滤。
*/
export const validatePublishAppAgentSkillReadPermissions = async ({
nodes,
tmbId,
isRoot = false
}: {
nodes?: StoreNodeItemType[];
tmbId: string;
isRoot?: boolean;
}) => {
if (!nodes) return;
const skillIds = new Set<string>();
for (const node of nodes) {
for (const input of node.inputs) {
if (input.key !== NodeInputKeyEnum.skills || nodeInputIsReference(input)) continue;
const skills = z.array(SelectedAgentSkillItemTypeSchema).parse(input.value);
for (const skill of skills) {
skillIds.add(skill.skillId);
}
}
}
await Promise.all(
Array.from(skillIds).map((skillId) =>
authSkillByTmbId({
tmbId,
skillId,
per: ReadPermissionVal,
isRoot
})
)
);
};
/* Get apps */
export async function findAppAndAllChildren({
teamId,
......
/**
* 将外部文件名收敛为可写入 sandbox user_files 的单个 path segment。
* URL query 和 API body 都可能携带文件名,因此调用方不能信任原始 name。
*/
export const getSafeAgentInputFilename = (
filename: string,
index: number,
usedNames: Map<string, number>
) => {
const fallbackName = `file-${index}`;
const normalized = filename.replace(/\\/g, '/').split('/').pop()?.trim() || fallbackName;
const withoutControlChars = normalized.replace(/[\u0000-\u001F\u007F]/g, '').trim();
const baseName =
withoutControlChars && withoutControlChars !== '.' && withoutControlChars !== '..'
? withoutControlChars
: fallbackName;
const firstDotIndex = baseName.indexOf('.');
const stem = firstDotIndex > 0 ? baseName.slice(0, firstDotIndex) : baseName;
const extension = firstDotIndex > 0 ? baseName.slice(firstDotIndex) : '';
const count = usedNames.get(baseName) || 0;
usedNames.set(baseName, count + 1);
return count === 0 ? baseName : `${stem}-${count}${extension}`;
};
......@@ -12,6 +12,7 @@ import type { DeployedSkillInfo } from '../../../../../ai/skill/runtime/types';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { SubAppIds } from '@fastgpt/global/core/workflow/node/agent/constants';
import { SANDBOX_READ_FILE_TOOL_NAME } from '@fastgpt/global/core/ai/sandbox/tools';
import { getSafeAgentInputFilename } from './fileName';
export type AgentInputFile = {
id: string;
......@@ -111,6 +112,8 @@ export function parseAgentInputFiles({
.values()
);
const usedNames = new Map<string, number>();
return uniqueFiles
.slice(0, maxFiles)
.map(({ file, url }, index) => {
......@@ -119,7 +122,7 @@ export function parseAgentInputFiles({
return {
id: `${prefixId}-${index}`,
name: file.name || parsedFile.name || url,
name: getSafeAgentInputFilename(file.name || parsedFile.name || url, index, usedNames),
type: parsedFile.type,
url: parsedFile.url
};
......
......@@ -221,6 +221,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise
chatId,
sandboxId: runningAppInfo.sandboxId,
teamId: runningAppInfo.teamId,
tmbId: runningUserInfo.tmbId,
needSandboxRuntime: effectiveUseAgentSandbox,
sandboxBootstrap: agentSandboxBootstrap,
sandboxEntrypoint: effectiveSandboxEntrypoint,
......
......@@ -159,6 +159,7 @@ export const dispatchPiAgent = async (props: DispatchAgentModuleProps): Promise<
chatId,
sandboxId: runningAppInfo.sandboxId,
teamId: runningAppInfo.teamId,
tmbId: runningUserInfo.tmbId,
needSandboxRuntime: effectiveUseAgentSandbox,
sandboxBootstrap: agentSandboxBootstrap,
sandboxEntrypoint: effectiveSandboxEntrypoint,
......
......@@ -16,6 +16,7 @@ import { getSandboxClient, type SandboxClient } from '../../../../../../ai/sandb
import { pickOutboundAxios } from '../../../../../../../common/api/axios';
import { checkTeamSandboxPermission } from '../../../../../../../support/permission/teamLimit';
import { createAgentSandboxPermissionDeniedError } from '../../../../../../ai/sandbox/error';
import { getSafeAgentInputFilename } from '../../adapter/fileName';
export type AgentSandboxBootstrap = (context: {
sandboxClient: SandboxClient;
......@@ -29,6 +30,7 @@ type EnsureAgentSandboxRuntimeParams = {
chatId: string;
sandboxId?: string;
teamId: string;
tmbId: string;
needSandboxRuntime: boolean;
sandboxBootstrap?: AgentSandboxBootstrap;
sandboxEntrypoint?: string;
......@@ -49,6 +51,7 @@ type SandboxRuntimeContext = {
type InitRuntimeSandboxParams = SandboxRuntimeContext & {
teamId: string;
tmbId: string;
skillIds: string[];
sandboxBootstrap?: AgentSandboxBootstrap;
sandboxEntrypoint?: string;
......@@ -82,9 +85,11 @@ const readSandboxPwd = async (sandboxClient: SandboxClient) => {
*/
const injectInputFilesToSandbox = async (sandbox: ISandbox, files: AgentInputFile[]) => {
const writeFileTasks: Promise<FileWriteEntry>[] = [];
const usedNames = new Map<string, number>();
for (const file of files) {
const path = `${SANDBOX_USER_FILES_PATH}${file.name}`;
for (const [index, file] of files.entries()) {
const filename = getSafeAgentInputFilename(file.name, index, usedNames);
const path = `${SANDBOX_USER_FILES_PATH}${filename}`;
writeFileTasks.push(
pickOutboundAxios(file.url)
.get<ArrayBuffer>(file.url, {
......@@ -135,6 +140,7 @@ const initRuntimeSandbox = async ({
sandboxClient,
workDirectory,
teamId,
tmbId,
skillIds,
sandboxBootstrap,
sandboxEntrypoint,
......@@ -156,6 +162,7 @@ const initRuntimeSandbox = async ({
sandbox,
skillIds,
teamId,
tmbId,
workDirectory
}),
injectInputFilesToSandbox(sandbox, currentFiles),
......@@ -207,6 +214,7 @@ export async function ensureAgentSandboxRuntime({
chatId,
sandboxId,
teamId,
tmbId,
needSandboxRuntime,
sandboxBootstrap,
sandboxEntrypoint,
......@@ -255,6 +263,7 @@ export async function ensureAgentSandboxRuntime({
const { currentWorkingDirectory, skillInfos } = await initRuntimeSandbox({
...context,
teamId,
tmbId,
skillIds,
sandboxBootstrap,
sandboxEntrypoint,
......
......@@ -19,6 +19,13 @@ import { MongoAgentSkillsVersion } from '@fastgpt/service/core/ai/skill/version/
import { uploadSkillPackage } from '@fastgpt/service/core/ai/skill/package';
import { AgentSkillSourceEnum } from '@fastgpt/global/core/ai/skill/constants';
import { Types } from '@fastgpt/service/common/mongo';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { getUser } from '@test/datas/users';
import {
PerResourceTypeEnum,
ReadPermissionVal
} from '@fastgpt/global/support/permission/constant';
import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema';
const makePackage = async (entries: Array<{ path: string; name: string; description: string }>) => {
const zip = new JSZip();
......@@ -584,6 +591,127 @@ description: Latest current skill
]);
});
it('filters unauthorized runtime skills instead of injecting them', async () => {
const owner = await getUser(`runtime-skill-owner-${getNanoid(6)}`);
const runner = await getUser(`runtime-skill-runner-${getNanoid(6)}`, owner.teamId);
const [readableSkill, protectedSkill] = await MongoAgentSkills.create([
{
name: 'Readable',
description: '',
teamId: owner.teamId,
tmbId: owner.tmbId,
source: AgentSkillSourceEnum.personal
},
{
name: 'Protected',
description: '',
teamId: owner.teamId,
tmbId: owner.tmbId,
source: AgentSkillSourceEnum.personal
}
]);
await MongoResourcePermission.create({
resourceType: PerResourceTypeEnum.agentSkill,
teamId: owner.teamId,
resourceId: String(readableSkill._id),
tmbId: runner.tmbId,
permission: ReadPermissionVal
});
const readableVersionId = new Types.ObjectId();
const protectedVersionId = new Types.ObjectId();
const [readablePackage, protectedPackage] = await Promise.all([
makePackage([{ path: 'skill.md', name: 'readable', description: 'Readable skill' }]),
makePackage([{ path: 'skill.md', name: 'protected', description: 'Protected skill' }])
]);
const [readableStorage, protectedStorage] = await Promise.all([
uploadSkillPackage({
teamId: owner.teamId,
skillId: String(readableSkill._id),
packageObjectId: 'runtime-readable-version',
zipBuffer: readablePackage
}),
uploadSkillPackage({
teamId: owner.teamId,
skillId: String(protectedSkill._id),
packageObjectId: 'runtime-protected-version',
zipBuffer: protectedPackage
})
]);
await MongoAgentSkillsVersion.create([
{
_id: readableVersionId,
skillId: readableSkill._id,
tmbId: owner.tmbId,
storageKey: readableStorage.key
},
{
_id: protectedVersionId,
skillId: protectedSkill._id,
tmbId: owner.tmbId,
storageKey: protectedStorage.key
}
]);
await Promise.all([
MongoAgentSkills.updateOne(
{ _id: readableSkill._id },
{ $set: { currentVersionId: readableVersionId } }
),
MongoAgentSkills.updateOne(
{ _id: protectedSkill._id },
{ $set: { currentVersionId: protectedVersionId } }
)
]);
const readableTargetDir = `/workspace/projects/${String(readableVersionId)}`;
const protectedTargetDir = `/workspace/projects/${String(protectedVersionId)}`;
const sandbox = {
writeFiles: vi.fn(async (entries: Array<{ path: string; data: Buffer }>) =>
makeWriteResults(entries)
),
execute: vi.fn(async (command: string) => {
if (command === "mkdir -p '/workspace/projects'") {
return { exitCode: 0, stdout: '', stderr: '' };
}
if (command === LIST_VERSION_DIRS_COMMAND) {
return { exitCode: 0, stdout: '', stderr: '' };
}
if (command.includes('unzip')) {
return { exitCode: 0, stdout: '', stderr: '' };
}
if (command.startsWith('mkdir -p ')) return { exitCode: 0, stdout: '', stderr: '' };
throw new Error(`Unexpected command: ${command}`);
}),
readFiles: vi.fn()
};
const deployedVersions = await injectAgentSkillFilesToSandbox({
sandbox: sandbox as any,
skillIds: [String(readableSkill._id), String(protectedSkill._id)],
teamId: owner.teamId,
tmbId: runner.tmbId,
workDirectory: '/workspace'
});
expect(deployedVersions).toEqual([
{
versionId: String(readableVersionId),
targetDir: readableTargetDir
}
]);
expect(sandbox.writeFiles).toHaveBeenCalledTimes(1);
const writtenFilePaths = sandbox.writeFiles.mock.calls[0][0].map(
(entry: { path: string }) => entry.path
);
expect(writtenFilePaths).toEqual([
expect.stringContaining(`/workspace/projects/.tmp-${String(readableVersionId)}`)
]);
expect(writtenFilePaths.join('\n')).not.toContain(String(protectedVersionId));
expect(sandbox.execute).not.toHaveBeenCalledWith(expect.stringContaining(protectedTargetDir));
});
it('skips existing current version directories and removes unselected version directories', async () => {
const teamId = new Types.ObjectId().toHexString();
const tmbId = new Types.ObjectId().toHexString();
......
......@@ -11,6 +11,7 @@ import {
buildAgentSkillsPrompt,
buildAgentInputFilesPrompt,
buildAgentUserReminderInput,
parseAgentInputFiles,
useUserContext
} from '@fastgpt/service/core/workflow/dispatch/ai/agent/adapter/userContext';
import type { DeployedSkillInfo } from '@fastgpt/service/core/ai/skill/runtime/types';
......@@ -174,6 +175,44 @@ describe('buildAgentInputFilesPrompt', () => {
});
});
describe('parseAgentInputFiles', () => {
it('sanitizes file names into single sandbox path segments and deduplicates collisions', () => {
const files = parseAgentInputFiles({
files: [
{
name: '../report.pdf',
type: ChatFileTypeEnum.file,
url: '/api/system/file/download/a?filename=ignored.pdf'
},
{
type: ChatFileTypeEnum.file,
url: '/api/system/file/download/b?filename=%2E%2E%2Freport.pdf'
},
{
name: 'folder/image.png',
type: ChatFileTypeEnum.image,
url: '/api/system/file/download/c?filename=image.png'
},
{
name: '..',
type: ChatFileTypeEnum.file,
url: '/api/system/file/download/d?filename=..'
}
],
prefixId: 'current',
maxFiles: 10
});
expect(files.map((file) => file.name)).toEqual([
'report.pdf',
'report-1.pdf',
'image.png',
'file-3'
]);
expect(files.every((file) => !file.name.includes('/'))).toBe(true);
});
});
describe('buildAgentUserReminderInput', () => {
it('builds current turn reminder with files datasets time and original query', () => {
const result = buildAgentUserReminderInput({
......@@ -873,7 +912,7 @@ describe('useUserContext', () => {
);
});
it('falls back to url as file name when neither chat metadata nor parsed url has a filename', async () => {
it('falls back to a safe url basename when neither chat metadata nor parsed url has a filename', async () => {
await runWithContextAsync(
{
queryUrlTypeMap: {
......@@ -903,12 +942,12 @@ describe('useUserContext', () => {
});
const { text } = chatValue2RuntimePrompt(result.currentUserMessage.value);
expect(text).toContain('<name>/api/file/raw</name>');
expect(text).toContain('<name>raw</name>');
}
);
});
it('uses url as the final defensive file name fallback when parser returns an empty name', async () => {
it('uses a safe url basename as the final defensive file name fallback', async () => {
const parseUrlToFileTypeSpy = vi
.spyOn(workflowContext, 'parseUrlToFileType')
.mockReturnValueOnce({
......@@ -935,7 +974,7 @@ describe('useUserContext', () => {
});
const { text } = chatValue2RuntimePrompt(result.currentUserMessage.value);
expect(text).toContain('<name>/nameless</name>');
expect(text).toContain('<name>nameless</name>');
}
);
......
......@@ -111,6 +111,7 @@ describe('ensureAgentSandboxRuntime', () => {
userId: 'user_1',
chatId: 'chat_1',
teamId: 'team_1',
tmbId: 'tmb_1',
needSandboxRuntime: true,
sandboxBootstrap: sandboxBootstrapMock,
sandboxEntrypoint: 'pip install -r requirements.txt',
......@@ -121,6 +122,24 @@ describe('ensureAgentSandboxRuntime', () => {
name: 'current.pdf',
type: ChatFileTypeEnum.file,
url: 'https://files/current.pdf'
},
{
id: 'file_2',
name: '../current.pdf',
type: ChatFileTypeEnum.file,
url: 'https://files/unsafe-current.pdf'
},
{
id: 'file_3',
name: 'folder/report.txt',
type: ChatFileTypeEnum.file,
url: 'https://files/report.txt'
},
{
id: 'file_4',
name: '..',
type: ChatFileTypeEnum.file,
url: 'https://files/nameless'
}
]
});
......@@ -140,12 +159,25 @@ describe('ensureAgentSandboxRuntime', () => {
sandbox: expect.any(Object),
skillIds: ['skill_1'],
teamId: 'team_1',
tmbId: 'tmb_1',
workDirectory: '/workspace'
});
expect(sandboxWriteFilesMock).toHaveBeenCalledWith([
{
path: 'user_files/current.pdf',
data: expect.any(ArrayBuffer)
},
{
path: 'user_files/current-1.pdf',
data: expect.any(ArrayBuffer)
},
{
path: 'user_files/report.txt',
data: expect.any(ArrayBuffer)
},
{
path: 'user_files/file-3',
data: expect.any(ArrayBuffer)
}
]);
expect(runAgentSandboxEntrypointMock).toHaveBeenCalledWith({
......
......@@ -221,8 +221,8 @@
"permission_appCreate_tip": "可以在根目录创建应用,(文件夹下的创建权限由文件夹控制)",
"permission_datasetCreate": "创建知识库",
"permission_datasetCreate_Tip": "可以在根目录创建知识库,(文件夹下的创建权限由文件夹控制)",
"permission_skillCreate": "创建 Skill",
"permission_skillCreate_Tip": "可以在根目录创建 Skill,(文件夹下的创建权限由文件夹控制)",
"permission_skillCreate": "创建技能",
"permission_skillCreate_Tip": "可以在根目录创建技能,(文件夹下的创建权限由文件夹控制)",
"permission_manage": "管理员",
"permission_manage_tip": "可以管理成员、创建群组、管理所有群组、为群组和成员分配权限",
"please_bind_contact": "请绑定联系方式",
......
......@@ -194,15 +194,15 @@
"sandbox_status_connecting": "正在连接沙箱环境...",
"sandbox_status_creatingContainer": "正在初始化云端沙箱...",
"sandbox_status_deployingSkills": "部署技能 {{skillName}}...",
"sandbox_status_downloadingPackage": "正在下载 Skill 包...",
"sandbox_status_extractingPackage": "正在解压 Skill 包...",
"sandbox_status_downloadingPackage": "正在下载技能包...",
"sandbox_status_extractingPackage": "正在解压技能包...",
"sandbox_status_failed": "沙箱创建失败",
"sandbox_status_failed_with_message": "沙箱创建失败:{{message}}",
"sandbox_status_fetchSkills": "正在获取技能信息...",
"sandbox_status_lazyInit": "虚拟机启动中...",
"sandbox_status_ready_cold": "沙箱环境就绪",
"sandbox_status_ready_warm": "沙箱环境就绪(热启动)",
"sandbox_status_uploadingPackage": "正在上传 Skill 包到沙箱...",
"sandbox_status_uploadingPackage": "正在上传技能包到沙箱...",
"sandbox_terminal": "终端",
"sandbox_upload_failed": "上传失败",
"sandbox_upload_file": "上传文件",
......
......@@ -930,7 +930,7 @@
"plugin.Invalid URL": "URL 无效",
"plugin.Search_app": "搜索应用",
"price_over_wx_limit": "超出支付提供商限额:微信支付仅支持 6000 元以下",
"pro_modal_feature_1": "Agent与Skill的智能辅助生成",
"pro_modal_feature_1": "Agent 与技能的智能辅助生成",
"pro_modal_feature_2": "web站点同步与第三方知识库",
"pro_modal_feature_3": "多租户、组织架构与审计日志",
"pro_modal_later_button": "我再想想",
......@@ -1088,7 +1088,7 @@
"support.wallet.subscription.type.extraDatasetSize": "知识库扩容",
"support.wallet.subscription.type.extraPoints": "AI 积分套餐",
"support.wallet.subscription.type.standard": "套餐订阅",
"support.wallet.usage.Assist Generate Skill": "协助生成Skill",
"support.wallet.usage.Assist Generate Skill": "协助生成技能",
"support.wallet.usage.Audio Speech": "语音播放",
"support.wallet.usage.Code Copilot": "代码助手",
"support.wallet.usage.Optimize Prompt": "提示词优化",
......
......@@ -30,8 +30,8 @@
"qpm_tips": "每个 IP 每分钟最多提问多少次",
"request_address": "请求地址",
"show_node": "实时运行状态",
"show_skill_reference": "查看 Skill 引用",
"show_skill_reference_tips": "查看引用的所有 Skill 及加载过程",
"show_skill_reference": "查看技能引用",
"show_skill_reference_tips": "查看引用的所有技能及加载过程",
"show_share_link_modal_title": "开始使用",
"token_auth": "身份验证",
"token_auth_tips": "身份校验服务器地址",
......
......@@ -2,7 +2,7 @@
"search_skill": "搜索",
"create_skill": "新建技能",
"create_your_first_skill": "创建你的第一个技能",
"no_skills": "暂无 Skill",
"no_skills": "暂无技能",
"copy_skill": "创建副本",
"confirm_delete_title": "确定删除该技能吗?",
"confirm_delete_with_refs": "该技能当前正被<bold>{{count}}个应用</bold>引用。删除后,相关应用将<bold>无法调用此技能</bold>。建议先解除关联或备份配置。",
......@@ -12,11 +12,11 @@
"permission_settings": "权限设置",
"export_config": "导出配置",
"unnamed_skill": "未命名",
"skill_name_placeholder": "请输入 Skill 名称",
"skill_name_placeholder": "请输入技能名称",
"skill_avatar_and_name": "头像 & 名称",
"skill_intro_label": "应用介绍",
"skill_intro_placeholder": "介绍使用场景及途径",
"skill_requirement_label": "技能需求(用于智能生成SKILL.md)",
"skill_requirement_label": "技能需求(用于智能生成技能说明文件)",
"skill_requirement_tooltip_title": "示例:",
"skill_requirement_tooltip_example": "## 目标\n根据会议记录自动生成会议纪要。\n\n## 流程\n1. 识别会议主题和参与人员\n2. 提取讨论的关键要点\n3. 整理出明确的结论和决策\n4. 提取需要跟进的行动项,并标注负责人(如有)\n\n## 要求\n1. 结果以结构化格式输出\n2. 包含:会议主题、参与人、讨论要点、决策结论、行动项\n3. 内容简洁清晰,避免冗余描述",
"skill_requirement_default": "## 目标\n\n## 流程\n\n## 要求",
......@@ -41,43 +41,43 @@
"deploy_failed": "发布失败",
"copy_skill_confirm": "系统将为您创建一个相同配置技能,但权限不会进行复制,请确认!",
"history_versions": "历史版本",
"select_skill": "选择 Skill",
"associated_skills": "关联 Skill",
"select_skill": "选择技能",
"associated_skills": "关联技能",
"skill_deleted": "技能已删除",
"skill_deleted_click_remove_tip": "技能已删除,点击删除",
"skill_select_limit_tip": "已达到单个应用可关联 Skill 的上限(100 个)",
"sandbox_auto_enabled_for_skill": "skill运行依赖虚拟机环境,已为你打开虚拟机功能",
"sandbox_disable_blocked_toast": "Skill运行依赖虚拟机环境,当前 Agent 已配置 Skill,请先移除所有 Skill 再关闭虚拟机",
"skill_select_limit_tip": "已达到单个应用可关联技能的上限(100 个)",
"sandbox_auto_enabled_for_skill": "技能运行依赖虚拟机环境,已为你打开虚拟机功能",
"sandbox_disable_blocked_toast": "技能运行依赖虚拟机环境,当前 Agent 已配置技能,请先移除所有技能再关闭虚拟机",
"sandbox_system_not_configured_toast": "当前系统未配置虚拟机,暂时无法使用相关功能,请联系管理员配置。",
"sandbox_skill_system_not_configured_toast": "skill运行依赖虚拟机环境。当前系统未配置虚拟机,暂时无法使用相关功能,请联系管理员配置。",
"sandbox_skill_system_not_configured_toast": "技能运行依赖虚拟机环境。当前系统未配置虚拟机,暂时无法使用相关功能,请联系管理员配置。",
"sandbox_operation_system_not_configured_title": "未配置虚拟机",
"sandbox_operation_system_not_configured_content": "skill操作依赖虚拟机环境。当前系统未配置虚拟机,暂时无法使用相关功能,请联系管理员配置。",
"sandbox_operation_system_not_configured_content": "技能操作依赖虚拟机环境。当前系统未配置虚拟机,暂时无法使用相关功能,请联系管理员配置。",
"sandbox_plan_not_supported_title": "套餐不支持功能",
"sandbox_skill_plan_not_supported_content": "skill运行依赖虚拟机环境。当前套餐不支持虚拟机功能,请升级套餐后继续使用。",
"sandbox_operation_plan_not_supported_content": "skill操作依赖虚拟机环境。当前套餐不支持虚拟机功能,请升级套餐后继续使用。",
"sandbox_skill_plan_not_supported_content": "技能运行依赖虚拟机环境。当前套餐不支持虚拟机功能,请升级套餐后继续使用。",
"sandbox_operation_plan_not_supported_content": "技能操作依赖虚拟机环境。当前套餐不支持虚拟机功能,请升级套餐后继续使用。",
"sandbox_upgrade_action": "去升级",
"sandbox_unavailable_tag": "不可用",
"sandbox_skill_unavailable_toast": "Skill运行依赖虚拟机环境,当前 Agent 已配置 Skill,请先移除所有 Skill 再关闭虚拟机",
"sandbox_skill_unavailable_toast": "技能运行依赖虚拟机环境,当前 Agent 已配置技能,请先移除所有技能再关闭虚拟机",
"sandbox_checking": "正在检查现有沙箱环境...",
"sandbox_connecting": "正在连接沙箱环境...",
"sandbox_fetch_skills": "正在获取 Skill 配置信息...",
"sandbox_fetch_skills": "正在获取技能配置信息...",
"sandbox_creating_container": "正在初始化云端沙箱...",
"sandbox_deploying_skills": "正在部署 Skill: {{skillName}}...",
"sandbox_downloading": "正在下载 Skill 包...",
"sandbox_uploading": "正在上传 Skill 包到沙箱...",
"sandbox_extracting": "正在解压 Skill 包...",
"sandbox_deploying_skills": "正在部署技能: {{skillName}}...",
"sandbox_downloading": "正在下载技能包...",
"sandbox_uploading": "正在上传技能包到沙箱...",
"sandbox_extracting": "正在解压技能包...",
"sandbox_lazy_init": "正在初始化运行环境...",
"sandbox_ready": "沙箱环境就绪",
"sandbox_ready_warm": "沙箱环境就绪(热启动)",
"sandbox_failed": "沙箱创建失败: {{message}}",
"sandbox_retry": "重试",
"sandbox_error_title": "沙箱创建失败",
"no_current_version": "Skill 暂无可用版本,请重新创建或导入后再编辑。",
"permission.des.read": "可查看 Agent Skill",
"permission.des.write": "可编辑 Agent Skill",
"permission.des.manage": "可管理 Agent Skill 和协作者",
"empty_state_tip": "告诉 AI 如何修改 Skill,\n或让 AI 运行 Skill 查看效果",
"empty_state_community_prefix": "通过对话预览 Skill 效果,",
"no_current_version": "技能暂无可用版本,请重新创建或导入后再编辑。",
"permission.des.read": "可查看 Agent 技能",
"permission.des.write": "可编辑 Agent 技能",
"permission.des.manage": "可管理 Agent 技能和协作者",
"empty_state_tip": "告诉 AI 如何修改技能,\n或让 AI 运行技能查看效果",
"empty_state_community_prefix": "通过对话预览技能效果,",
"empty_state_community_upgrade": "升级商业版",
"empty_state_community_suffix": "可使用 AI 生成 Skill。"
"empty_state_community_suffix": "可使用 AI 生成技能。"
}
......@@ -14,8 +14,8 @@
"Variable_name": "变量名",
"add_new_input": "新增输入",
"add_new_output": "新增输出",
"agent.prompt_skill_tip": "@选择 Skill 及工具,\"/\"选择变量",
"agent.select_skill": "选择 Skill",
"agent.prompt_skill_tip": "@选择技能及工具,\"/\"选择变量",
"agent.select_skill": "选择技能",
"agent.select_tool": "选择工具",
"agent.tools": "工具",
"append_application_reply_to_history_as_new_context": "将该应用回复内容拼接到历史记录中,作为新的上下文返回",
......@@ -212,7 +212,7 @@
"template.agent": "工具调用",
"template.agent_intro": "由 AI 自主决定工具调用。",
"template.agent_module": "Agent",
"template.agent_module_intro": "关联所需工具及 Skill,可实现 AI 自主调用与规划",
"template.agent_module_intro": "关联所需工具及技能,可实现 AI 自主调用与规划",
"template.ai_chat": "AI 对话",
"template.ai_chat_intro": "AI 大模型对话",
"template.dataset_search": "知识库搜索",
......
......@@ -219,8 +219,8 @@
"permission_appCreate_tip": "可以在根目錄建立應用程式,(資料夾下的建立權限由資料夾控制)",
"permission_datasetCreate": "建立知識庫",
"permission_datasetCreate_Tip": "可以在根目錄建立知識庫,(資料夾下的建立權限由資料夾控制)",
"permission_skillCreate": "建立 Skill",
"permission_skillCreate_Tip": "可以在根目錄建立 Skill,(資料夾下的建立權限由資料夾控制)",
"permission_skillCreate": "建立技能",
"permission_skillCreate_Tip": "可以在根目錄建立技能,(資料夾下的建立權限由資料夾控制)",
"permission_manage": "管理員",
"permission_manage_tip": "可以管理成員、建立群組、管理所有群組、為群組和成員分配權限",
"please_bind_contact": "請綁定聯繫方式",
......
......@@ -191,15 +191,15 @@
"sandbox_status_connecting": "正在連接沙箱環境...",
"sandbox_status_creatingContainer": "正在初始化雲端沙箱...",
"sandbox_status_deployingSkills": "部署技能 {{skillName}}...",
"sandbox_status_downloadingPackage": "正在下載 Skill 包...",
"sandbox_status_extractingPackage": "正在解壓 Skill 包...",
"sandbox_status_downloadingPackage": "正在下載技能包...",
"sandbox_status_extractingPackage": "正在解壓技能包...",
"sandbox_status_failed": "沙箱創建失敗",
"sandbox_status_failed_with_message": "沙箱創建失敗:{{message}}",
"sandbox_status_fetchSkills": "正在獲取技能資訊...",
"sandbox_status_lazyInit": "虛擬機啟動中...",
"sandbox_status_ready_cold": "沙箱環境就緒",
"sandbox_status_ready_warm": "沙箱環境就緒(熱啟動)",
"sandbox_status_uploadingPackage": "正在上傳 Skill 包到沙箱...",
"sandbox_status_uploadingPackage": "正在上傳技能包到沙箱...",
"sandbox_terminal": "終端機",
"sandbox_upload_failed": "上傳失敗",
"sandbox_upload_file": "上傳文件",
......
......@@ -920,7 +920,7 @@
"plugin.Invalid URL": "無效的網址",
"plugin.Search_app": "搜尋應用程式",
"price_over_wx_limit": "超出支付提供商限額:微信支付僅支持 6000 元以下",
"pro_modal_feature_1": "Agent 與 Skill 的智能輔助生成",
"pro_modal_feature_1": "Agent 與技能的智能輔助生成",
"pro_modal_feature_2": "Web 站點同步與第三方知識庫",
"pro_modal_feature_3": "多租戶、組織架構與審計日誌",
"pro_modal_later_button": "我再想想",
......@@ -1077,7 +1077,7 @@
"support.wallet.subscription.type.extraDatasetSize": "知識庫擴充容量",
"support.wallet.subscription.type.extraPoints": "AI 點數方案",
"support.wallet.subscription.type.standard": "方案訂閱",
"support.wallet.usage.Assist Generate Skill": "協助生成skill",
"support.wallet.usage.Assist Generate Skill": "協助生成技能",
"support.wallet.usage.Audio Speech": "語音播放",
"support.wallet.usage.Code Copilot": "代碼助手",
"support.wallet.usage.Optimize Prompt": "提示詞優化",
......
......@@ -30,8 +30,8 @@
"qpm_tips": "每個 IP 每分鐘最高查詢次數",
"request_address": "請求網址",
"show_node": "即時執行狀態",
"show_skill_reference": "查看 Skill 引用",
"show_skill_reference_tips": "查看引用的所有 Skill 及載入過程",
"show_skill_reference": "查看技能引用",
"show_skill_reference_tips": "查看引用的所有技能及載入過程",
"show_share_link_modal_title": "開始使用",
"token_auth": "身分驗證",
"token_auth_tips": "身分驗證伺服器網址。若有提供,每次對話前將向指定伺服器傳送驗證請求。",
......
......@@ -2,7 +2,7 @@
"search_skill": "搜尋",
"create_skill": "新建技能",
"create_your_first_skill": "創建你的第一個技能",
"no_skills": "暫無 Skill",
"no_skills": "暫無技能",
"copy_skill": "建立副本",
"confirm_delete_title": "確定刪除該技能嗎?",
"confirm_delete_with_refs": "該技能當前正被<bold>{{count}}個應用</bold>引用。刪除後,相關應用將<bold>無法調用此技能</bold>。建議先解除關聯或備份配置。",
......@@ -12,11 +12,11 @@
"permission_settings": "權限設置",
"export_config": "導出配置",
"unnamed_skill": "未命名",
"skill_name_placeholder": "請輸入 Skill 名稱",
"skill_name_placeholder": "請輸入技能名稱",
"skill_avatar_and_name": "頭像 & 名稱",
"skill_intro_label": "應用介紹",
"skill_intro_placeholder": "介紹使用場景及途徑",
"skill_requirement_label": "技能需求(用於智能生成SKILL.md)",
"skill_requirement_label": "技能需求(用於智能生成技能說明文件)",
"skill_requirement_tooltip_title": "示例:",
"skill_requirement_tooltip_example": "## 目標\n根據會議記錄自動生成會議紀要。\n\n## 流程\n1. 識別會議主題和參與人員\n2. 提取討論的關鍵要點\n3. 整理出明確的結論和決策\n4. 提取需要跟進的行動項,並標注負責人(如有)\n\n## 要求\n1. 結果以結構化格式輸出\n2. 包含:會議主題、參與人、討論要點、決策結論、行動項\n3. 內容簡潔清晰,避免冗余描述",
"skill_requirement_default": "## 目標\n\n## 流程\n\n## 要求",
......@@ -26,8 +26,8 @@
"import_skill_max_size_tip": "單次最多上傳 {{maxCount}} 個文件,單個文件最大 {{maxSize}}",
"unsupported_file_format": "不支持 {{ext}} 文件格式",
"skill_info_edit": "技能資訊編輯",
"move_skill": "移動 Skill",
"move_skill_hint": "移動後,所選 Skill/文件夾將繼承新文件夾的權限設置。",
"move_skill": "移動技能",
"move_skill_hint": "移動後,所選技能/文件夾將繼承新文件夾的權限設置。",
"delete_success": "刪除成功",
"delete_failed": "刪除失敗",
"copy_success": "複製成功",
......@@ -41,43 +41,43 @@
"deploy_failed": "發布失敗",
"copy_skill_confirm": "系統將為您創建一個相同配置技能,但權限不會進行複製,請確認!",
"history_versions": "歷史版本",
"select_skill": "選擇 Skill",
"associated_skills": "關聯 Skill",
"select_skill": "選擇技能",
"associated_skills": "關聯技能",
"skill_deleted": "技能已刪除",
"skill_deleted_click_remove_tip": "技能已刪除,點擊刪除",
"skill_select_limit_tip": "已達到單個應用可關聯 Skill 的上限(100 個)",
"sandbox_auto_enabled_for_skill": "skill運行依賴虛擬機器環境,已為你開啟虛擬機器功能",
"sandbox_disable_blocked_toast": "Skill運行依賴虛擬機器環境,目前 Agent 已配置 Skill,請先移除所有 Skill 再關閉虛擬機器",
"skill_select_limit_tip": "已達到單個應用可關聯技能的上限(100 個)",
"sandbox_auto_enabled_for_skill": "技能運行依賴虛擬機器環境,已為你開啟虛擬機器功能",
"sandbox_disable_blocked_toast": "技能運行依賴虛擬機器環境,目前 Agent 已配置技能,請先移除所有技能再關閉虛擬機器",
"sandbox_system_not_configured_toast": "目前系統未配置虛擬機器,暫時無法使用相關功能,請聯絡管理員配置。",
"sandbox_skill_system_not_configured_toast": "skill運行依賴虛擬機器環境。目前系統未配置虛擬機器,暫時無法使用相關功能,請聯絡管理員配置。",
"sandbox_skill_system_not_configured_toast": "技能運行依賴虛擬機器環境。目前系統未配置虛擬機器,暫時無法使用相關功能,請聯絡管理員配置。",
"sandbox_operation_system_not_configured_title": "未配置虛擬機器",
"sandbox_operation_system_not_configured_content": "skill操作依賴虛擬機器環境。目前系統未配置虛擬機器,暫時無法使用相關功能,請聯絡管理員配置。",
"sandbox_operation_system_not_configured_content": "技能操作依賴虛擬機器環境。目前系統未配置虛擬機器,暫時無法使用相關功能,請聯絡管理員配置。",
"sandbox_plan_not_supported_title": "套餐不支援功能",
"sandbox_skill_plan_not_supported_content": "skill運行依賴虛擬機器環境。目前套餐不支援虛擬機器功能,請升級套餐後繼續使用。",
"sandbox_operation_plan_not_supported_content": "skill操作依賴虛擬機器環境。目前套餐不支援虛擬機器功能,請升級套餐後繼續使用。",
"sandbox_skill_plan_not_supported_content": "技能運行依賴虛擬機器環境。目前套餐不支援虛擬機器功能,請升級套餐後繼續使用。",
"sandbox_operation_plan_not_supported_content": "技能操作依賴虛擬機器環境。目前套餐不支援虛擬機器功能,請升級套餐後繼續使用。",
"sandbox_upgrade_action": "去升級",
"sandbox_unavailable_tag": "不可用",
"sandbox_skill_unavailable_toast": "Skill運行依賴虛擬機器環境,目前 Agent 已配置 Skill,請先移除所有 Skill 再關閉虛擬機器",
"sandbox_skill_unavailable_toast": "技能運行依賴虛擬機器環境,目前 Agent 已配置技能,請先移除所有技能再關閉虛擬機器",
"sandbox_checking": "正在檢查現有沙箱環境...",
"sandbox_connecting": "正在連接沙箱環境...",
"sandbox_fetch_skills": "正在獲取 Skill 配置資訊...",
"sandbox_fetch_skills": "正在獲取技能配置資訊...",
"sandbox_creating_container": "正在初始化雲端沙箱...",
"sandbox_deploying_skills": "正在部署 Skill: {{skillName}}...",
"sandbox_downloading": "正在下載 Skill 包...",
"sandbox_uploading": "正在上傳 Skill 包到沙箱...",
"sandbox_extracting": "正在解壓 Skill 包...",
"sandbox_deploying_skills": "正在部署技能: {{skillName}}...",
"sandbox_downloading": "正在下載技能包...",
"sandbox_uploading": "正在上傳技能包到沙箱...",
"sandbox_extracting": "正在解壓技能包...",
"sandbox_lazy_init": "正在初始化運行環境...",
"sandbox_ready": "沙箱環境就緒",
"sandbox_ready_warm": "沙箱環境就緒(熱啟動)",
"sandbox_failed": "沙箱創建失敗: {{message}}",
"sandbox_retry": "重試",
"sandbox_error_title": "沙箱創建失敗",
"no_current_version": "Skill 暫無可用版本,請重新建立或匯入後再編輯。",
"permission.des.read": "可查看 Agent Skill",
"permission.des.write": "可編輯 Agent Skill",
"permission.des.manage": "可管理 Agent Skill 和協作者",
"empty_state_tip": "告訴 AI 如何修改 Skill,\n或讓 AI 運行 Skill 查看效果",
"empty_state_community_prefix": "透過對話預覽 Skill 效果,",
"no_current_version": "技能暫無可用版本,請重新建立或匯入後再編輯。",
"permission.des.read": "可查看 Agent 技能",
"permission.des.write": "可編輯 Agent 技能",
"permission.des.manage": "可管理 Agent 技能和協作者",
"empty_state_tip": "告訴 AI 如何修改技能,\n或讓 AI 運行技能查看效果",
"empty_state_community_prefix": "透過對話預覽技能效果,",
"empty_state_community_upgrade": "升級商業版",
"empty_state_community_suffix": "可使用 AI 生成 Skill。"
"empty_state_community_suffix": "可使用 AI 生成技能。"
}
......@@ -14,8 +14,8 @@
"Variable_name": "變數名稱",
"add_new_input": "新增輸入",
"add_new_output": "新增輸出",
"agent.prompt_skill_tip": "@選擇 Skill 及工具,\"/\"選擇變數",
"agent.select_skill": "選擇 Skill",
"agent.prompt_skill_tip": "@選擇技能及工具,\"/\"選擇變數",
"agent.select_skill": "選擇技能",
"agent.select_tool": "選擇工具",
"agent.tools": "工具",
"append_application_reply_to_history_as_new_context": "將應用程式的回覆附加到歷史紀錄中,作為新的脈絡",
......@@ -212,7 +212,7 @@
"template.agent": "工具呼叫",
"template.agent_intro": "透過 AI 模型自動選擇一或多個功能區塊進行呼叫,也可以呼叫外掛程式。",
"template.agent_module": "Agent",
"template.agent_module_intro": "關聯所需工具及 Skill,可實現 AI 自主呼叫與規劃",
"template.agent_module_intro": "關聯所需工具及技能,可實現 AI 自主呼叫與規劃",
"template.ai_chat": "AI 對話",
"template.ai_chat_intro": "AI 大型語言模型對話",
"template.dataset_search": "知識庫搜尋",
......
......@@ -36,6 +36,7 @@ import { isS3ObjectKey } from '@fastgpt/service/common/s3/utils';
import { MongoAppTemplate } from '@fastgpt/service/core/app/templates/templateSchema';
import {
beforeUpdateAppFormat,
validatePublishAppAgentSkillReadPermissions,
updateParentFoldersUpdateTime
} from '@fastgpt/service/core/app/controller';
import { copyAvatarImage } from '@fastgpt/service/common/file/image/controller';
......@@ -109,7 +110,8 @@ async function handler(req: ApiRequestProps<CreateAppBodyType>) {
tmbId,
userAvatar: tmb?.avatar,
username: tmb?.user?.username,
templateId
templateId,
isRoot
});
pushTrack.createApp({
......@@ -141,6 +143,7 @@ export const onCreateApp = async ({
username,
userAvatar,
templateId,
isRoot,
session
}: {
parentId?: ParentIdType;
......@@ -157,6 +160,7 @@ export const onCreateApp = async ({
username?: string;
userAvatar?: string;
templateId?: string;
isRoot?: boolean;
session?: ClientSession;
}) => {
if (parentId) {
......@@ -173,6 +177,13 @@ export const onCreateApp = async ({
beforeUpdateAppFormat({
nodes: modules
});
if (!AppFolderTypeList.includes(type!)) {
await validatePublishAppAgentSkillReadPermissions({
nodes: modules,
tmbId,
isRoot
});
}
const create = async (session: ClientSession) => {
const resourceRefs = extractAppResourceRefsFromNodes(modules);
......
......@@ -3,7 +3,10 @@ import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller';
import {
beforeUpdateAppFormat,
validatePublishAppAgentSkillReadPermissions
} from '@fastgpt/service/core/app/controller';
import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time';
import { type PostPublishAppProps } from '@/global/core/app/api';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
......@@ -31,7 +34,7 @@ async function handler(req: ApiRequestProps<PostPublishAppProps>) {
bodySchema: PublishAppBodySchema
});
const { app, tmbId, teamId } = await authApp({
const { app, tmbId, teamId, isRoot } = await authApp({
appId,
req,
per: WritePermissionVal,
......@@ -41,6 +44,13 @@ async function handler(req: ApiRequestProps<PostPublishAppProps>) {
beforeUpdateAppFormat({
nodes
});
if (isPublish) {
await validatePublishAppAgentSkillReadPermissions({
nodes,
tmbId,
isRoot
});
}
const resourceRefs = extractAppResourceRefsFromNodes(nodes);
updateParentFoldersUpdateTime({
parentId: app.parentId
......
import { describe, expect, it } from 'vitest';
import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller';
import {
beforeUpdateAppFormat,
validatePublishAppAgentSkillReadPermissions
} from '@fastgpt/service/core/app/controller';
import {
FlowNodeInputTypeEnum,
FlowNodeTypeEnum
} from '@fastgpt/global/core/workflow/node/constant';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import { MongoAgentSkills } from '@fastgpt/service/core/ai/skill/model/schema';
import { AgentSkillSourceEnum } from '@fastgpt/global/core/ai/skill/constants';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { getUser } from '@test/datas/users';
import { SkillErrEnum } from '@fastgpt/global/common/error/code/skill';
describe('beforeUpdateAppFormat', () => {
it('保存前统一压缩知识库选择项,去掉编辑态删除标记和快照字段', () => {
......@@ -233,3 +241,47 @@ describe('beforeUpdateAppFormat', () => {
]);
});
});
describe('validatePublishAppAgentSkillReadPermissions', () => {
it('发布应用时校验静态绑定的 Agent Skill 读权限', async () => {
const owner = await getUser(`publish-skill-owner-${getNanoid(6)}`);
const member = await getUser(`publish-skill-member-${getNanoid(6)}`, owner.teamId);
const skill = await MongoAgentSkills.create({
name: 'Protected Skill',
source: AgentSkillSourceEnum.personal,
teamId: owner.teamId,
tmbId: owner.tmbId
});
const nodes = [
{
flowNodeType: FlowNodeTypeEnum.agent,
inputs: [
{
key: NodeInputKeyEnum.skills,
value: [
{
skillId: String(skill._id),
name: 'Protected Skill',
description: ''
}
]
}
]
} as StoreNodeItemType
];
await expect(
validatePublishAppAgentSkillReadPermissions({
nodes,
tmbId: member.tmbId
})
).rejects.toBe(SkillErrEnum.unAuthSkill);
await expect(
validatePublishAppAgentSkillReadPermissions({
nodes,
tmbId: owner.tmbId
})
).resolves.toBeUndefined();
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment