Yuxi Documentation

可私有部署的多租户知识智能体平台:统一 RAG、知识图谱、多智能体、MCP/Skills、沙盒与权限管理。Yuxi = Cloud Agents + Knowledge RAG, Self-hosted knowledge agent platform for RAG, knowledge graphs and...

7,412 stars Python Python 128 files · ~125,366 tokens #agentic-rag#ai-agents#deepagents#docker#document-ai#fastapi#harness#kbqa
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy Yuxi on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name yuxi -p 6050:6050 --restart always neo4j:5.26.29
Configured Services & Ports
⚙️ api
image: ${COMPOSE_PROJECT_NAME:-yuxi}-api:${YUXI_VERSION:-0.7.3}
🔌 Ports: ${YUXI_API_PORT:-6050}:5050
⚙️ worker
image: ${COMPOSE_PROJECT_NAME:-yuxi}-api:${YUXI_VERSION:-0.7.3}
⚙️ storage-migrator
image: ${COMPOSE_PROJECT_NAME:-yuxi}-api:${YUXI_VERSION:-0.7.3}
⚙️ sandbox-provisioner
image: ${COMPOSE_PROJECT_NAME:-yuxi}-sandbox-provisioner:${YUXI_VERSION:-0.7.3}
🔌 Ports: 127.0.0.1:${YUXI_SANDBOX_PORT:-9002}:8002
⚙️ web
image: ${COMPOSE_PROJECT_NAME:-yuxi}-web:${YUXI_VERSION:-0.7.3}
🔌 Ports: ${YUXI_WEB_PORT:-80}:80
⚙️ graph
image: neo4j:5.26.29
🔌 Ports: 127.0.0.1:${YUXI_NEO4J_HTTP_PORT:-8474}:7474, 127.0.0.1:${YUXI_NEO4J_BOLT_PORT:-8687}:7687
⚙️ etcd
image: quay.io/coreos/etcd:v3.5.5
⚙️ minio
image: ${COMPOSE_PROJECT_NAME:-yuxi}-minio:RELEASE.2023-03-20T20-16-18Z
🔌 Ports: 127.0.0.1:${YUXI_MINIO_API_PORT:-10000}:9000, 127.0.0.1:${YUXI_MINIO_CONSOLE_PORT:-10001}:9001
⚙️ milvus
image: milvusdb/milvus:v2.5.6
🔌 Ports: 127.0.0.1:${YUXI_MILVUS_PORT:-20530}:19530, 127.0.0.1:${YUXI_MILVUS_HEALTH_PORT:-10091}:9091
⚙️ postgres
image: postgres:16
🔌 Ports: 127.0.0.1:${YUXI_POSTGRES_PORT:-6432}:5432
⚙️ redis
image: redis:7.4.10-alpine
🔌 Ports: 127.0.0.1:${YUXI_REDIS_PORT:-7379}:6379
⚙️ mineru-api
image: ${COMPOSE_PROJECT_NAME:-yuxi}-mineru:latest
🔌 Ports: 127.0.0.1:${YUXI_MINERU_PORT:-30001}:30001
⚙️ paddlex
image: ${COMPOSE_PROJECT_NAME:-yuxi}-paddlex:latest
🔌 Ports: 127.0.0.1:${YUXI_PADDLEX_PORT:-8080}:8080
docker-compose.yml
yaml (docker-compose.yml)
x-api-worker-env: &api-worker-env
  HOST_IP: ${HOST_IP:-}
  RUNNING_IN_DOCKER: "true"
  YUXI_ENV: production
  PYTHONPATH: /app/package
  JWT_SECRET_KEY: ${JWT_SECRET_KEY:?Set JWT_SECRET_KEY in .env.prod}
  API_KEY_DERIVATION_SECRET: ${API_KEY_DERIVATION_SECRET:?Set API_KEY_DERIVATION_SECRET in .env.prod}
  RAPIDOCR_MODEL_DIR: /home/yuxi/.cache/rapidocr/models
  YUXI_INSTANCE_ID: ${YUXI_INSTANCE_ID:?Set YUXI_INSTANCE_ID in .env.prod}
  POSTGRES_URL: ${POSTGRES_URL:-postgresql+asyncpg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env.prod}@postgres:5432/${POSTGRES_DB:-yuxi}}
  REDIS_URL: ${REDIS_URL:-redis://redis:6379/0}
  RUN_CANCEL_KEY_TTL_SECONDS: ${RUN_CANCEL_KEY_TTL_SECONDS:-1800}
  RUN_EVENTS_STREAM_TTL_SECONDS: ${RUN_EVENTS_STREAM_TTL_SECONDS:-7200}
  READINESS_PROBE_TIMEOUT_SECONDS: ${READINESS_PROBE_TIMEOUT_SECONDS:-2}
  READINESS_CACHE_TTL_SECONDS: ${READINESS_CACHE_TTL_SECONDS:-1}
  YUXI_CORS_ORIGINS: ${YUXI_CORS_ORIGINS:-}
  NEO4J_URI: ${NEO4J_URI:-bolt://graph:7687}
  NEO4J_USERNAME: ${NEO4J_USERNAME:-neo4j}
  NEO4J_PASSWORD: ${NEO4J_PASSWORD:?Set NEO4J_PASSWORD in .env.prod}
  MILVUS_URI: ${MILVUS_URI:-http://milvus:19530}
  MILVUS_TOKEN: ${MILVUS_TOKEN:-}
  MINERU_API_URI: ${MINERU_API_URI:-http://mineru-api:30001}
  PADDLEX_URI: ${PADDLEX_URI:-http://paddlex:8080}
  MINIO_URI: ${MINIO_URI:-http://minio:9000}
  SANDBOX_PROVIDER: ${SANDBOX_PROVIDER:-provisioner}
  SANDBOX_PROVISIONER_URL: ${SANDBOX_PROVISIONER_URL:-http://sandbox-provisioner:8002}
  SANDBOX_PROVISIONER_TOKEN: ${SANDBOX_PROVISIONER_TOKEN:?Set SANDBOX_PROVISIONER_TOKEN in .env.prod}
  SANDBOX_PROVISIONER_DELETE_TIMEOUT_SECONDS: ${SANDBOX_PROVISIONER_DELETE_TIMEOUT_SECONDS:-120}
  SANDBOX_VIRTUAL_PATH_PREFIX: ${SANDBOX_VIRTUAL_PATH_PREFIX:-/home/gem/user-data}
  SANDBOX_EXEC_TIMEOUT_SECONDS: ${SANDBOX_EXEC_TIMEOUT_SECONDS:-180}
  SANDBOX_MAX_OUTPUT_BYTES: ${SANDBOX_MAX_OUTPUT_BYTES:-262144}
  TASKER_DEFAULT_TIMEOUT_SECONDS: ${TASKER_DEFAULT_TIMEOUT_SECONDS:-21600}
  YUXI_DATASET_PERSIST_BATCH_SIZE: ${YUXI_DATASET_PERSIST_BATCH_SIZE:-1}
  YUXI_SKILL_DATA_DIR: /app/skill-sources
  YUXI_SKILL_PROJECTION_DIR: /app/skill-projections
  NO_PROXY: localhost,127.0.0.1,milvus,graph,minio,etcd,mineru,paddlex,sandbox-provisioner,api.siliconflow.cn
  no_proxy: localhost,127.0.0.1,milvus,graph,minio,etcd,mineru,paddlex,sandbox-provisioner,api.siliconflow.cn

# 长驻服务的容器日志轮转上界,防止 docker json.log 无限增长占满磁盘。
x-logging: &default-logging
  driver: json-file
  options:
    max-size: "50m"
    max-file: "3"

services:
  api:
    ports:
      - "${YUXI_API_PORT:-6050}:5050"
    build:
      context: .
      dockerfile: docker/api.Dockerfile
    image: ${COMPOSE_PROJECT_NAME:-yuxi}-api:${YUXI_VERSION:-0.7.3}
    working_dir: /app
    user: "1000:1000"
    networks:
      - app-network
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/threads:/app/user-data
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/skill-sources:/app/skill-sources
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/skill-projections:/app/skill-projections
    extra_hosts:
      - "host.docker.internal:host-gateway"
    env_file:
      - ${YUXI_ENV_FILE:-.env.prod}
    # region api_envs
    environment:
      <<: *api-worker-env
      YUXI_RUNTIME_DIR: /app/runtime/api
      YUXI_USER_DATA_DIR: /app/user-data
      POSTGRES_POOL_SIZE: ${API_POSTGRES_POOL_SIZE:-120}
      POSTGRES_MAX_OVERFLOW: ${API_POSTGRES_MAX_OVERFLOW:-40}
      POSTGRES_POOL_TIMEOUT_SECONDS: ${API_POSTGRES_POOL_TIMEOUT_SECONDS:-30}
      LANGGRAPH_POSTGRES_POOL_SIZE: ${API_LANGGRAPH_POSTGRES_POOL_SIZE:-10}
      LANGGRAPH_POSTGRES_POOL_TIMEOUT_SECONDS: ${API_LANGGRAPH_POSTGRES_POOL_TIMEOUT_SECONDS:-30}
      REDIS_MAX_CONNECTIONS: ${API_REDIS_MAX_CONNECTIONS:-256}
    # endregion api_envs
    command: uv run --no-sync --no-dev uvicorn server.main:app --host 0.0.0.0 --port 5050
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:5050/api/system/ready || exit 1"]
      interval: 30s
      timeout: 15s
      retries: 8
      start_period: 180s
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      minio:
        condition: service_healthy
      sandbox-provisioner:
        condition: service_healthy
      storage-migrator:
        condition: service_completed_successfully

  worker:
    build:
      context: .
      dockerfile: docker/api.Dockerfile
    image: ${COMPOSE_PROJECT_NAME:-yuxi}-api:${YUXI_VERSION:-0.7.3}
    working_dir: /app
    user: "1000:1000"
    networks:
      - app-network
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/threads:/app/user-data
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/skill-sources:/app/skill-sources
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/skill-projections:/app/skill-projections
    extra_hosts:
      - "host.docker.internal:host-gateway"
    env_file:
      - ${YUXI_ENV_FILE:-.env.prod}
    environment:
      <<: *api-worker-env
      YUXI_RUNTIME_DIR: /app/runtime/worker
      YUXI_USER_DATA_DIR: /app/user-data
      ARQ_MAX_JOBS: ${ARQ_MAX_JOBS:-140}
      REDIS_MAX_CONNECTIONS: ${WORKER_REDIS_MAX_CONNECTIONS:-256}
      POSTGRES_POOL_SIZE: ${WORKER_POSTGRES_POOL_SIZE:-120}
      POSTGRES_MAX_OVERFLOW: ${WORKER_POSTGRES_MAX_OVERFLOW:-40}
      POSTGRES_POOL_TIMEOUT_SECONDS: ${WORKER_POSTGRES_POOL_TIMEOUT_SECONDS:-30}
      LANGGRAPH_POSTGRES_POOL_SIZE: ${WORKER_LANGGRAPH_POSTGRES_POOL_SIZE:-120}
      LANGGRAPH_POSTGRES_POOL_TIMEOUT_SECONDS: ${WORKER_LANGGRAPH_POSTGRES_POOL_TIMEOUT_SECONDS:-30}
    command: uv run --no-sync --no-dev python -m server.worker_main
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "python", "-m", "yuxi.services.worker_health"]
      interval: 10s
      timeout: 10s
      retries: 6
      start_period: 180s
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      minio:
        condition: service_healthy
      sandbox-provisioner:
        condition: service_healthy
      storage-migrator:
        condition: service_completed_successfully

  storage-migrator:
    build:
      context: .
      dockerfile: docker/api.Dockerfile
    image: ${COMPOSE_PROJECT_NAME:-yuxi}-api:${YUXI_VERSION:-0.7.3}
    working_dir: /app
    user: "0:0"
    networks:
      - app-network
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi:/app/legacy-saves
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/threads:/app/user-data
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/skill-sources:/app/skill-sources
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/skill-projections:/app/skill-projections
    env_file:
      - ${YUXI_ENV_FILE:-.env.prod}
    environment:
      <<: *api-worker-env
      YUXI_RUNTIME_DIR: /app/runtime/storage-migrator
      YUXI_LEGACY_STORAGE_DIR: /app/legacy-saves
      YUXI_USER_DATA_DIR: /app/user-data
    command: uv run --no-sync --no-dev python -m yuxi.storage_migration
    restart: "no"
    depends_on:
      postgres:
        condition: service_healthy

  sandbox-provisioner:
    ports:
      - "127.0.0.1:${YUXI_SANDBOX_PORT:-9002}:8002"
    build:
      context: ./docker/sandbox_provisioner
      dockerfile: Dockerfile
    image: ${COMPOSE_PROJECT_NAME:-yuxi}-sandbox-provisioner:${YUXI_VERSION:-0.7.3}
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/threads:/app/user-data
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/skill-projections:/app/skill-projections
      - ${SANDBOX_ENV_FILE:-./docker/sandbox_provisioner/sandbox.env}:/app/sandbox.env:ro
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      app-network:
        # 动态接入沙盒网络时保留管理端口的默认路由。
        gw_priority: 1
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      # Common provisioner settings
      - PROVISIONER_BACKEND=${SANDBOX_PROVISIONER_BACKEND:-docker}
      - PROVISIONER_PUBLIC_URL=${SANDBOX_PROVISIONER_URL:-http://sandbox-provisioner:8002}
      - SANDBOX_PROVISIONER_TOKEN=${SANDBOX_PROVISIONER_TOKEN:?Set SANDBOX_PROVISIONER_TOKEN in .env.prod}
      - SANDBOX_IMAGE=${SANDBOX_IMAGE:-enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:1.11.0}
      - SANDBOX_RUNTIME_PROFILE=${SANDBOX_RUNTIME_PROFILE:-core}
      - SANDBOX_CONTAINER_PORT=${SANDBOX_CONTAINER_PORT:-8080}
      - MEMORY_SANDBOX_URL_TEMPLATE=${MEMORY_SANDBOX_URL_TEMPLATE:-http://agent-sandbox:8000}
      - SANDBOX_HEALTH_TIMEOUT_SECONDS=${SANDBOX_HEALTH_TIMEOUT_SECONDS:-300}
      - SANDBOX_IDLE_TIMEOUT_SECONDS=${SANDBOX_IDLE_TIMEOUT_SECONDS:-120}
      - SANDBOX_IDLE_CHECK_INTERVAL_SECONDS=${SANDBOX_IDLE_CHECK_INTERVAL_SECONDS:-10}
      - SANDBOX_EXEC_TIMEOUT_SECONDS=${SANDBOX_EXEC_TIMEOUT_SECONDS:-180}
      - SANDBOX_DELETE_CONCURRENCY=${SANDBOX_DELETE_CONCURRENCY:-32}
      - SANDBOX_CONTAINER_STOP_TIMEOUT_SECONDS=${SANDBOX_CONTAINER_STOP_TIMEOUT_SECONDS:-2}
      # 单沙盒执行的资源上界
      - SANDBOX_MEM_LIMIT=${SANDBOX_MEM_LIMIT:-2g}
      - SANDBOX_CPUS=${SANDBOX_CPUS:-2}
      - SANDBOX_PIDS_LIMIT=${SANDBOX_PIDS_LIMIT:-512}
      # Optional proxy for sandbox-provisioner container
      - HTTP_PROXY=${SANDBOX_HTTP_PROXY:-}
      - HTTPS_PROXY=${SANDBOX_HTTPS_PROXY:-}
      - NO_PROXY=localhost,127.0.0.1,host.docker.internal
      # Docker backend only
      - DOCKER_NETWORK_PREFIX=${SANDBOX_DOCKER_NETWORK_PREFIX:-${COMPOSE_PROJECT_NAME:-yuxi}-sandbox}
      - DOCKER_ADDRESS_POOL=${SANDBOX_DOCKER_ADDRESS_POOL:-10.253.240.0/20}
      - DOCKER_SUBNET_PREFIX=${SANDBOX_DOCKER_SUBNET_PREFIX:-28}
      - DOCKER_USER_DATA_HOST_PATH=${SANDBOX_DOCKER_USER_DATA_HOST_PATH:-}
      - DOCKER_SKILL_PROJECTIONS_HOST_PATH=${SANDBOX_DOCKER_SKILL_PROJECTIONS_HOST_PATH:-}
      - DOCKER_SANDBOX_PREFIX=${SANDBOX_DOCKER_SANDBOX_PREFIX:-${COMPOSE_PROJECT_NAME:-yuxi}-sandbox}
      # Kubernetes backend only
      - K8S_NAMESPACE=${SANDBOX_K8S_NAMESPACE:-yuxi-know}
      - NODE_HOST=${SANDBOX_NODE_HOST:-host.docker.internal}
      - KUBECONFIG_PATH=${KUBECONFIG_PATH:-}
      - USER_DATA_PVC=${USER_DATA_PVC:-yuxi-user-data}
      - SKILLS_PVC=${SKILLS_PVC:-yuxi-skills}
    command: ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8002"]
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8002/health').read()"]
      interval: 10s
      timeout: 5s
      retries: 6
      start_period: 10s
    restart: unless-stopped
    depends_on:
      storage-migrator:
        condition: service_completed_successfully

  web:
    build:
      context: .
      dockerfile: docker/web.Dockerfile
      target: production
    image: ${COMPOSE_PROJECT_NAME:-yuxi}-web:${YUXI_VERSION:-0.7.3}
    ports:
      - "${YUXI_WEB_PORT:-80}:80"
    depends_on:
      - api
    networks:
      - app-network
    environment:
      - NODE_ENV=production
    command: nginx -g "daemon off;"
    restart: unless-stopped

  graph:
    ports:
      - "127.0.0.1:${YUXI_NEO4J_HTTP_PORT:-8474}:7474"
      - "127.0.0.1:${YUXI_NEO4J_BOLT_PORT:-8687}:7687"
    image: neo4j:5.26.29
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/neo4j/data:/data
      - ${YUXI_STATE_DIR:-./docker/volumes}/neo4j/logs:/logs
    healthcheck:
      test: ["CMD-SHELL", "cypher-shell -a bolt://localhost:7687 -u \"$${NEO4J_AUTH%%/*}\" -p \"$${NEO4J_AUTH#*/}\" 'RETURN 1;' >/dev/null || exit 1"]
      interval: 20s
      timeout: 10s
      retries: 5
      start_period: 30s
    environment:
      - NEO4J_AUTH=${NEO4J_USERNAME:-neo4j}/${NEO4J_PASSWORD:?Set NEO4J_PASSWORD in .env.prod}
      - NEO4J_server_bolt_listen__address=0.0.0.0:7687
      - NEO4J_server_http_listen__address=0.0.0.0:7474
      - ENTITY_EMBEDDING=true
    networks:
      - app-network
    restart: unless-stopped

  etcd:
    image: quay.io/coreos/etcd:v3.5.5
    logging: *default-logging
    environment:
      - ETCD_AUTO_COMPACTION_MODE=revision
      - ETCD_AUTO_COMPACTION_RETENTION=1000
      - ETCD_QUOTA_BACKEND_BYTES=4294967296
      - ETCD_SNAPSHOT_COUNT=50000
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/milvus/etcd:/etcd
    command: etcd -advertise-client-urls=http://etcd:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
    healthcheck:
      test: ["CMD", "etcdctl", "endpoint", "health"]
      interval: 60s
      timeout: 30s
      retries: 5
      start_period: 30s
    networks:
      - app-network
    restart: unless-stopped

  minio:
    build:
      context: ./docker/minio
      dockerfile: Dockerfile
    ports:
      - "127.0.0.1:${YUXI_MINIO_API_PORT:-10000}:9000"
      - "127.0.0.1:${YUXI_MINIO_CONSOLE_PORT:-10001}:9001"
    image: ${COMPOSE_PROJECT_NAME:-yuxi}-minio:RELEASE.2023-03-20T20-16-18Z
    environment:
      MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:?Set MINIO_ACCESS_KEY in .env.prod}
      MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:?Set MINIO_SECRET_KEY in .env.prod}
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/milvus/minio:/data
      - ${YUXI_STATE_DIR:-./docker/volumes}/milvus/minio_config:/root/.minio
    command: minio server /data --address 0.0.0.0:9000 --console-address 0.0.0.0:9001
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 60s
      timeout: 30s
      retries: 5
      start_period: 30s
    networks:
      - app-network
    restart: unless-stopped

  milvus:
    ports:
      - "127.0.0.1:${YUXI_MILVUS_PORT:-20530}:19530"
      - "127.0.0.1:${YUXI_MILVUS_HEALTH_PORT:-10091}:9091"
    image: milvusdb/milvus:v2.5.6
    command: ["milvus", "run", "standalone"]
    # v2.5.6 不读取 MILVUS_LOG_LEVEL;日志级别只能由镜像内 milvus.yaml 拥有,
    # 这里只对 stdout/stderr 轮转封顶。内存随知识库数据量线性增长,不设 mem_limit:
    # 硬上界终会被合法增长击穿,击穿模式是 OOM→重启→重加载→再 OOM 的循环。
    # cpus 上限同时让内置 maxprocs 按 cgroup quota 收敛 GOMAXPROCS。
    logging: *default-logging
    cpus: ${YUXI_MILVUS_CPUS:-2}
    security_opt:
      - seccomp:unconfined
    environment:
      ETCD_ENDPOINTS: etcd:2379
      MINIO_ADDRESS: minio:9000
      MINIO_ACCESS_KEY_ID: ${MINIO_ACCESS_KEY:?Set MINIO_ACCESS_KEY in .env.prod}
      MINIO_SECRET_ACCESS_KEY: ${MINIO_SECRET_KEY:?Set MINIO_SECRET_KEY in .env.prod}
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/milvus/milvus:/var/lib/milvus
      - ${YUXI_STATE_DIR:-./docker/volumes}/milvus/logs:/var/lib/milvus/logs
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
      interval: 60s
      start_period: 120s
      timeout: 30s
      retries: 5
    depends_on:
      - "etcd"
      - "minio"
    networks:
      - app-network
    restart: unless-stopped

  postgres:
    ports:
      - "127.0.0.1:${YUXI_POSTGRES_PORT:-6432}:5432"
    image: postgres:16
    command: ["postgres", "-c", "max_connections=${POSTGRES_MAX_CONNECTIONS:-600}"]
    environment:
      - POSTGRES_USER=${POSTGRES_USER:-postgres}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env.prod}
      - POSTGRES_DB=${POSTGRES_DB:-yuxi}
      - TZ=${TZ:-Asia/Shanghai}
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/postgresql:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-yuxi} || exit 1"]
      interval: 5s
      timeout: 3s
      retries: 30
    networks:
      - app-network
    restart: unless-stopped

  redis:
    ports:
      - "127.0.0.1:${YUXI_REDIS_PORT:-7379}:6379"
    image: redis:7.4.10-alpine
    command: redis-server --appendonly yes
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 10
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/redis:/data
    networks:
      - app-network
    restart: unless-stopped

  mineru-api:
    ports:
      - "127.0.0.1:${YUXI_MINERU_PORT:-30001}:30001"
    build:
      context: .
      dockerfile: docker/mineru.Dockerfile
    image: ${COMPOSE_PROJECT_NAME:-yuxi}-mineru:latest
    profiles:
      - all
    env_file:
      - ${YUXI_ENV_FILE:-.env.prod}
    environment:
      MINERU_MODEL_SOURCE: local
    entrypoint: mineru-api
    command:
      --host 0.0.0.0
      --port 30001
      # --allow-public-http-client  # Disabled by default; when binding to 0.0.0.0 or ::, this re-enables *-http-client backends and server_url. Enable only if you accept the SSRF risk.
      # parameters for vllm-engine
      # --gpu-memory-utilization 0.5  # If encountering VRAM shortage, reduce the KV cache size by this parameter; if VRAM issues persist, try lowering it further to `0.4` or below.
    ulimits:
      memlock: -1
      stack: 67108864
    ipc: host
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:30001/health || exit 1"]
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              device_ids: ["0"]  # Modify for multiple GPUs: ["0", "1"]
              capabilities: [gpu]
    networks:
      - app-network
    restart: unless-stopped

  paddlex:
    ports:
      - "127.0.0.1:${YUXI_PADDLEX_PORT:-8080}:8080"
    build:
      context: .
      dockerfile: docker/paddlex.Dockerfile
    image: ${COMPOSE_PROJECT_NAME:-yuxi}-paddlex:latest
    profiles:
      - all
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/paddlex:/paddlex
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
      interval: 20s
      timeout: 10s
      retries: 5
      start_period: 60s
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              device_ids: ['0']
              capabilities: [gpu]
    networks:
      - app-network
    restart: unless-stopped

networks:
  app-network:
    driver: bridge

File: backend/package/README.md

yuxi

Yuxi 核心后端 Python 包。

该目录用于将 yuxi 作为本地可构建、可安装的 Python package 提供给 backend 项目使用。


File: docs/develop-guides/design.md

产品体验与界面设计规范

本文档定义 Yuxi 的产品体验与界面设计规范,适用于需求设计、交互方案、视觉设计、web/src 下的新页面、新组件和现有 UI 调整。它同时面向产品、设计、人类开发者和 AI coding agent:开始实现前先说明用户目标、信息层级和交互语义,再复用现有组件、CSS 变量和交互模式。

0. 产品判断先于界面实现

界面不是接口字段的可视化投影。设计前先明确:目标用户、主要任务、需要做出的判断、成功与异常状态,以及哪些信息只是实现细节。

  • 每个可见元素必须帮助用户识别、选择、判断状态、执行操作或恢复错误;否则删除。
  • 默认使用用户语言描述用途、差异和影响。内部 ID、枚举和环境变量只在复制、对接或排障时按需展示。
  • 不同概念不能因为数据结构相同就放进同一列表。“不使用服务”“自动选择”等通常是策略,不是资源项。
  • 一个页面或分区只解决一个主要问题;高级和低频配置使用渐进披露。
  • “接口返回了”“其他后台都有”“看起来更丰富”不能成为展示理由。

1. 视觉气质

Yuxi 是知识库、知识图谱与 Agent 开发平台,界面应保持克制、清晰、工程化。设计服务于长时间阅读、配置、调试和数据管理,不做营销页式装饰。

核心原则:

  • 功能优先:视觉层级帮助用户理解任务、状态和下一步操作,不为装饰牺牲信息密度。
  • 一致优先:相似功能使用相同布局、颜色、状态和交互反馈。
  • 轻量优先:用背景、边框、字号和留白建立层级,避免重阴影、夸张渐变和非功能性动效。
  • 可维护优先:新增样式必须基于现有 token 和组件模式,不为单次需求引入新的设计体系。

2. 颜色与 Token

颜色必须优先使用 web/src/assets/css/base.css 和 web/src/assets/css/base.dark.css 中定义的 CSS 变量。不要在组件中随意新增硬编码色值;确需新增全局色值时,先补充 token 并说明用途。

主色

--main-* 和 --main-color 用于品牌主色与关键交互:

Token 使用场景
--main-color 主按钮、选中态、重点链接、关键图标
--main-700 / --main-600 / --main-500 主色文字、hover、active、强调状态
--main-50 / --main-30 / --main-10 主色浅背景、选中行背景、轻量提示背景

主色只用于表达“当前选择、主要操作、关键入口”。不要把主色当作普通装饰色铺在卡片或大面积背景上。

辅助色

--second-* 是暖金色辅助色系(基准 --second-500),刻度与 --main-* 对齐,--second-color 为语义入口。辅助色只用于少量点缀(徽标、装饰性视觉元素),不能替代主色表达交互状态,也不与语义色混用。

中性色

--gray-* 是默认界面骨架,用于背景、文本、边框和分割线:

Token 使用场景
--gray-0 页面和卡片主背景
--gray-10 / --gray-25 / --gray-50 次级背景、hover 背景、弱分区背景
--gray-100 / --gray-150 / --gray-200 分割线、输入框边框、卡片边框
--gray-900 / --gray-1000 标题和主要正文
--gray-600 / --gray-500 / --gray-400 辅助说明、占位文本、禁用文本

文本也可以使用 Ant Design 兼容语义变量:--color-text、--color-text-secondary、--color-text-tertiary。组件内优先选语义变量;需要更细层级时再使用 --gray-*。

语义色

语义色只用于状态和反馈,不用于装饰:

Token 组 使用场景
--color-success-* 成功、已完成、连接正常
--color-error-* 错误、危险操作、删除、失败
--color-warning-* 警告、待处理、需要注意但未失败
--color-info-* 信息提示、说明性状态
--color-accent-* 少量辅助强调,不能替代主色

状态标签建议使用浅背景 + 深文字,例如 background: var(--color-success-50); color: var(--color-success-700);。不要只依靠颜色传达状态,必要时配合文字或图标。

图表色

图表和统计可视化优先使用 --chart-palette-*。不要复用错误色、警告色做普通图表分类,避免与状态反馈混淆。

暗色模式

Yuxi 通过 :root.dark 覆盖同名 token。新增 UI 必须使用 CSS 变量,禁止固定浅色值,并检查浅色、暗色两套表现。

新增组件时至少检查:

  • 背景、卡片、输入框不出现纯白硬编码导致的暗色穿帮。
  • 文本和边框在暗色模式下仍有足够对比度。
  • hover、focus、disabled、selected、error 等状态在暗色模式下可辨认。
  • 图表、代码块和第三方组件需要显式传入 theme 时,使用 useThemeStore() 的现有模式。

3. 字体与文本层级

全局字体栈定义在 web/src/assets/css/main.css,新增组件不要私自引入新字体。代码、命令、路径和技术标识可使用 monospace,优先复用现有 @mono-font 或系统 monospace 栈。

建议层级:

角色 建议样式 使用场景
页面标题 20-24px,600 页面主标题、弹窗主标题
分组标题 16-18px,600 卡片标题、表单分组标题
正文 14-15px,400 常规说明、列表内容
辅助说明 12-13px,400 helper text、元信息、时间、统计说明
标签/状态 12px,500 tag、chip、状态徽标
代码/路径 12-14px,monospace 文件路径、命令、代码片段

文本规范:

  • 标题要短,优先描述对象,不写营销式口号。
  • 按钮文案使用明确动作,如“保存配置”“重新检测”“删除文件”。
  • 危险操作必须让文案直接表达后果,如“删除知识库”。
  • 不使用 placeholder 替代表单 label;placeholder 只做输入示例。
  • 不使用负字距或按视口宽度缩放字体,避免宽屏和小屏出现不可控排版。

4. 组件样式

技术栈约束

  • 包管理器:pnpm
  • 图标库:优先使用 @lucide/vue
  • 样式语言:LESS
  • 颜色变量:使用 base.css / base.dark.css 中的 CSS 变量
  • UI 基础:复用 Ant Design Vue 和项目现有组件模式,避免为单次需求封装新组件体系

按钮

按钮应按操作优先级区分:

类型 使用场景 样式规则
主按钮 页面主操作、确认提交 使用主色背景或 Ant Design primary,不在同一区域放多个主按钮
次按钮 返回、取消、普通操作 使用中性边框和浅背景,hover 只增强边框或背景
文本/链接按钮 表格行内操作、轻量入口 保持轻量,不扩大视觉权重
危险按钮 删除、撤销、清空等不可逆操作 使用 error 语义色,并配合确认弹窗或明确文案
图标按钮 工具栏、折叠、刷新、复制 使用 lucide-icon-btn 保证图标与文本居中

交互状态:

  • hover 可以改变 background、border-color、color,不要位移或放大。
  • focus 必须可见,不能移除键盘焦点样式。
  • disabled 使用弱化文本和背景,不绑定 hover 强反馈。
  • loading 应保留按钮宽度,避免布局跳动。

输入框与表单

表单用于配置和管理任务,优先保证可读性和错误可恢复:

  • 输入框背景使用 --gray-0 或 Ant Design 默认容器色。
  • 边框使用 --gray-150 / --gray-200,focus 使用主色或框架默认 focus ring。
  • label 必须稳定显示,helper text 放在输入框下方。
  • 错误信息使用 --color-error-*,并写清楚修复方式。
  • 多字段表单按逻辑分组,避免把无关设置塞进同一行。
  • 参数名称应说明业务含义和单位;非显然参数补充调整影响、推荐范围或示例。
  • 只允许用户修改真正属于业务配置的值。官方端点、内部枚举和部署参数由系统管理,不伪装成普通输入项。

卡片、列表与表格

Yuxi 的信息界面以配置卡片、列表和表格为主,默认使用轻量分层:

  • 普通卡片:background: var(--gray-0); border: 1px solid var(--gray-150); border-radius: 8px;
  • 次级区域:可使用 var(--gray-10) / var(--gray-25) 做轻背景。
  • 点击态列表行:hover 只改变背景或边框,不使用 transform。
  • 表格行内操作保持紧凑,避免每行出现多个高权重按钮。
  • 空状态要说明“当前没有什么”和“下一步可以做什么”,不要只显示图标。

阴影只用于真实浮层,如弹窗、抽屉、下拉菜单、tooltip。普通卡片和列表不要用阴影制造装饰性层级。

列表、状态与操作

  • 摘要层按“名称 → 用途或关键差异 → 需关注状态 → 操作”组织,不机械展示名称、ID、类型和全部状态。
  • 内部 ID 默认隐藏;确需展示时放入详情,使用 monospace 和明确标签或复制入口。
  • 一个状态只保留一种主要表达。开关已经表达启用状态时,不再增加绿色点和“已启用”文字。
  • 正常状态保持安静,只突出异常和需要行动的状态;颜色不能作为唯一信息载体。
  • “不启用”“自动”“沿用默认”等策略放在选择器或策略区,不伪装成普通资源项。
  • 开关默认表示立即生效。需要统一保存时,应明确显示未保存状态,不能混用两种提交模型。
  • 同类行共享稳定的展开、文本、状态和操作列。图标使用固定盒、统一 gap 和 line-height,右侧操作保持同一边界。
  • 配置列表优先使用一个列表容器和轻分割线,避免每行形成厚重卡片;展开内容与触发行保持连续。
  • 至少在真实页面的目标宽度、1024px、768px 和 375px 检查对齐、长文本和溢出。

状态标签

状态标签使用语义色浅背景 + 深文字:

状态 推荐 token
成功/正常 --color-success-50 + --color-success-700
失败/错误 --color-error-50 + --color-error-700
警告/待处理 --color-warning-50 + --color-warning-900
信息/运行中 --color-info-50 + --color-info-700
普通/未知 --gray-100 + --gray-600

标签可以使用 pill 圆角,但不要把 pill 形状扩散到所有按钮和卡片。

图标

  • 常规图标尺寸使用 16px、18px 或 20px。
  • 图标颜色默认继承文本色;需要强调时使用语义 token。
  • 图标按钮添加 lucide-icon-btn,避免图标与文本或按钮中心线错位。
  • 不为同一概念混用多个图标;相同操作在不同页面保持一致。

5. 布局与间距

间距以 4px / 8px 为基础节奏:

场景 建议值
图标与文本间距 6px / 8px
表单项内部间距 6px / 8px
卡片内部 padding 16px / 20px / 24px
列表行间距 8px / 12px
页面主要区块间距 24px / 32px
弹窗内容区间距 16px / 24px

布局原则:

  • 配置型页面保持适度信息密度,不做大面积营销式留白。
  • 相邻操作靠近对应内容,页面级操作放在标题区或工具栏。
  • 一组按钮中主操作在视觉上最明确,取消/返回等次操作弱化。
  • 宽屏下限制正文行长,避免说明文字横跨整个页面。
  • 小屏下优先纵向堆叠,避免强行压缩表格和表单字段。

6. 深度与层级

Yuxi 默认采用“背景 + 边框”的轻量层级:

层级 处理方式 使用场景
页面背景 var(--gray-0) 或布局已有背景 主页面
次级背景 var(--gray-10) / var(--gray-25) 分区、弱提示、列表 hover
卡片边界 1px solid var(--gray-150),8px 圆角 配置卡片、内容块
浮层 框架默认阴影或轻量 --shadow-* 弹窗、抽屉、dropdown、tooltip
焦点 主色 outline / Ant Design focus ring 键盘可达控件

不要在普通内容卡片上使用重阴影。只有当元素真实覆盖其他内容、需要表达浮层关系时,才允许使用阴影。

7. Do / Don't

Do

  • 使用 base.css 和 base.dark.css 中的 token。
  • 为新增交互补齐 hover、focus、disabled、loading、empty、error 等必要状态。
  • 用背景、边框、字号、间距建立层级。
  • 保持浅色和暗色模式一致可用。
  • 复用 @lucide/vue、Ant Design Vue 和项目已有组件模式。
  • 在复杂配置区保留简短说明,帮助用户理解设置影响。

Don't

  • 不要在 hover 时使用位移、放大、旋转等装饰性 transform。
  • 不要使用浓重阴影装饰普通卡片。
  • 不要使用夸张渐变或大面积高饱和背景。
  • 不要把语义色用于纯装饰。
  • 不要新增一次性 helper、样式体系或无复用价值的抽象。
  • 不要硬编码浅色模式色值导致暗色模式失效。
  • 不要在同一区域放置多个同等视觉权重的主操作。

8. 响应式行为

响应式设计以“功能不丢失、信息不挤压”为优先:

  • 小屏下表单字段纵向排列,按钮组可换行。
  • 侧栏、抽屉和弹窗要保证最小宽度内内容可读。
  • 表格在小屏下允许横向滚动;不要把关键字段压缩到不可读。
  • 图标按钮和主要操作需要保持可点击区域,移动端目标尺寸尽量不小于 40px。
  • 长文本使用省略号时,应提供 tooltip、title 或详情入口查看完整内容。
  • 图谱、图表、代码块等宽内容应保留横向滚动或自适应缩放策略。

9. 设计交付与审阅流程

UI/UX 质量不能只由构建通过和没有溢出证明。交付前按三层审阅:

  1. 产品层:每个元素是否有价值,概念是否符合用户心智,是否暴露无用实现细节。
  2. 交互层:操作是否可发现,生效时机是否明确,失败能否恢复,状态是否一致。
  3. 视觉层:层级、对齐、间距、文字、图标、颜色和响应式是否精确一致。

UI 修改必须提供真实运行页面截图,覆盖核心状态和至少一个边界状态。使用真实数据检查长名称、加载、错误、空状态、浅色/暗色和关键响应式宽度;未验证内容在交付说明中列出。

10. Agent Prompt Guide

AI agent 修改或生成 Yuxi UI 时,优先按这一节执行。

Quick Reference

  • 页面背景:var(--gray-0)
  • 次级背景:var(--gray-10) / var(--gray-25)
  • 主要文本:var(--color-text) 或 var(--gray-900)
  • 次级文本:var(--color-text-secondary) 或 var(--gray-600)
  • 边框:var(--gray-150) / var(--gray-200)
  • 主色:var(--main-color)
  • 卡片圆角:8px
  • 小控件圆角:4px / 6px
  • 状态标签圆角:999px
  • 常规图标尺寸:16px / 18px / 20px
  • 卡片 padding:16px / 20px / 24px
  • 普通 hover:只改变背景、边框或文字颜色

Example Prompts

实现配置卡片:

text
实现一个 Yuxi 风格的配置卡片:背景使用 var(--gray-0),边框 1px solid var(--gray-150),圆角 8px,不加阴影。标题使用 var(--color-text),说明文字使用 var(--color-text-secondary)。hover 只轻微改变边框或背景,不使用 transform。

实现工具栏按钮:

text
实现一个工具栏按钮:优先使用 @lucide/vue 图标,图标尺寸 16px,按钮添加 lucide-icon-btn。默认使用中性色,hover 时使用 var(--main-color) 或 var(--main-10) 强化,不位移、不放大。

实现状态标签:

text
实现状态标签:成功使用 var(--color-success-50) 背景和 var(--color-success-700) 文字;错误使用 var(--color-error-50) 背景和 var(--color-error-700) 文字;警告使用 var(--color-warning-50) 背景和 var(--color-warning-900) 文字。圆角使用 999px,文字保持 12px。

实现检查清单

  • 已说明用户任务、信息层级和提交模型。
  • 没有平铺无用字段、重复状态或伪装成资源的策略项。
  • 名称和描述使用用户语言,内部 ID 仅按需展示。
  • 图标、文字、输入框和操作列已完成视觉对齐检查。
  • 使用现有 CSS 变量,没有新增随意硬编码色值。
  • 浅色和暗色模式都检查过。
  • hover、focus、disabled、loading、empty、error 状态符合场景。
  • 没有使用 hover 位移、放大、旋转或装饰性动画。
  • 普通卡片没有使用重阴影。
  • 图标来自 @lucide/vue,尺寸和对齐符合现有模式。
  • API 接口、组件位置、样式语言符合前端开发规范。
  • 已在真实页面和关键响应式宽度截图验收,而不只依赖构建通过。

参考资料

  • web/src/assets/css/base.css:浅色模式 token
  • web/src/assets/css/base.dark.css:暗色模式 token
  • web/src/assets/css/main.css:全局字体、布局基础样式和 lucide-icon-btn
  • Awesome DESIGN.md:面向 AI agent 的 DESIGN.md 样例集合

File: docs/develop-guides/roadmap.md

开发路线图

这里记录尚未完成的方向,不代表已经实现或一定会在某个版本发布。需求和优先级会根据 Issue、讨论和维护资源调整;欢迎在 GitHub Issues 提交建议。

计划中的不兼容方向

后续 0.8 版本可能包含:

  • 升级 Milvus 3.0;
  • 进一步解除 API/worker 对共享文件系统的依赖,支持更清晰的分布式部署边界。

这两项会影响部署和数据迁移,确定方案前会单独说明升级条件和兼容范围。

知识库

  • 为知识导图增加基于文件名的关联边,并探索用聚类形成社区节点。
  • 增加按关键词排序的 query_keywords 工具。
  • 丰富检索结果中的 metadata 和标签。
  • 为个人工作区增加检索能力,但不做向量化。

智能体

  • 为子智能体增加 Steer 机制。
  • 增加子智能体向主智能体提问的 ask_for_main_agent 能力。
  • 设计子智能体之间的通信机制。

其他

  • 继续完善基于 DeepAgents 文件后端的 Memory 方案,并明确它与现有用户级 Memory 的边界。
  • 改进 ask_user_question:支持较长文本回答,并让流式输出期间的聊天区域保持跟随最新内容(#753)。

已知问题

  • 打开历史对话时自动滚动到消息末尾。

已发布的功能和修复请看版本变更记录。


File: docs/develop-guides/testing-guidelines.md

测试规范

测试的目标是证明用户可观察的结果和工程边界,而不是堆积测试数量。先选最接近风险的最小测试集,再按改动范围扩大;单元测试不能代替真实 HTTP、数据库、worker、文件或浏览器验证。

测试分层

层级 目录 适合验证什么 环境
Unit backend/test/unit 纯逻辑、边界值、状态转换和失败分支 不依赖运行中的 Docker 服务
Integration backend/test/integration 真实 HTTP、认证、事务、锁、Schema、lease 和服务副作用 依赖 Docker Compose
E2E backend/test/e2e Run、SSE、worker、文件落盘和完整用户链路 依赖完整 Compose,数量少、速度慢
Web unit web/test/unit 前端状态、组件和交互逻辑 通过 pnpm test:unit
CLI packages/yuxi-cli/tests CLI 配置、命令和客户端行为 独立 Python 包

同一个子项目只保留一个测试根目录,不要同时创建 test 和 tests。

高风险 Agent 主链路优先使用不依赖外部密钥的 deterministic assembled-path E2E;真实模型、浏览器和外部服务用于手工或周期探针。E2E 需要经过实际 API、worker、SSE 和最终持久化事实,不能用进程内 monkeypatch 代替。

如何选择目录

  • 只调用纯 Python 逻辑、fake repository 或临时目录:放 unit。
  • 要验证真实接口、认证、事务或 Redis/PostgreSQL 边界:放 integration;API 测试放 integration/api。
  • 要从入口一路验证到最终 Run、文件或对象:放 e2e。
  • 前端和 CLI 测试留在各自项目的测试根,不放到 backend。

不要因为测试文件少或执行快就把 integration 降成 unit;测试层级反映它依赖的真实边界。

命名和结构

文件名使用 test_<domain>_<target>.py,一个文件围绕一个清晰主题。测试函数使用 test_<行为>_<预期结果>,名称直接表达业务语义:

text
test_create_agent_run_commits_before_enqueue
test_viewer_download_returns_attachment_response
test_agent_bubble_sort_run_creates_expected_artifacts

测试尽量保持 Arrange → Act → Assert 三段结构:

  1. 准备数据、fixture 和外部条件;
  2. 调用真实被测行为;
  3. 断言业务结果、状态和副作用。

不要只断言 status_code == 200。根据风险回读数据库行、文件、对象、DOM、SSE 游标或协议 payload。失败信息应指出目标和实际值。

每个新 guard 都要有负向案例:恢复目标缺陷或制造非法状态后,测试必须因正确原因失败。Fixture、snapshot 和 expected output 只能显式更新并进入 diff,CI 不得一边生成 oracle 一边验证它。

Fixture 和测试数据

  • 同一文件内复用的准备逻辑优先写本地 helper;多个文件需要时再放对应层级的 conftest.py。
  • backend/test/conftest.py 只保留通用 marker,不绑定真实服务。
  • integration fixture 负责创建 test_client、测试用户和测试资源;不要依赖数据库里碰巧存在的 Agent、模型或知识库。
  • E2E fixture 负责真实入口、账号和资源清理;测试结束后删除自己创建的对话、文件、Run 和外部对象。
  • 不在测试或文档中写真实账号、密码、Token、用户数据和本地绝对路径。

skip 规则

只在以下情况使用 pytest.skip:

  1. 外部可选服务确实未提供,例如 OCR 或真实模型服务;
  2. E2E 所需的测试账号或环境变量没有配置。

“系统没有默认数据”不是 skip 理由。用 fixture 显式创建资源,或者让测试失败暴露环境问题。不要用 print、日志关键词或 if __name__ == "__main__" 判断测试结果。

修改 Bug 或既有功能

修复 Bug:

  1. 先补一个稳定复现原问题的测试;
  2. 再修改实现;
  3. 先运行最小相关测试;
  4. 再运行受影响层级的回归测试。

修改既有行为时,同时更新正向和负向断言。涉及 API、权限、持久化、队列、SSE、沙盒或恢复时,按风险升级到真实 integration 或 E2E。

常用命令

先启动开发环境:

bash
docker compose up -d
docker compose ps
docker compose logs --tail=100 api

后端:

bash
docker compose exec api uv run --group test pytest test/unit -m "not slow"
docker compose exec api uv run --group test pytest test/integration
docker compose exec api uv run --group test pytest test/e2e/test_deterministic_agent_path_e2e.py -m e2e
docker compose exec api uv run --group test pytest test

也可以从仓库根目录使用脚本:

bash
backend/test/run_tests.sh unit
backend/test/run_tests.sh integration
backend/test/run_tests.sh e2e
backend/test/run_tests.sh e2e-all  # 显式运行真实模型与外部服务探针
backend/test/run_tests.sh all

前端:

bash
docker compose exec web pnpm run lint:check
docker compose exec web pnpm run test:unit
docker compose exec web pnpm run build

CLI:

bash
cd packages/yuxi-cli
uv run pytest

工程契约、文档构建和补丁检查:

bash
python3 scripts/verify_engineering_contracts.py
python3 -m unittest scripts.test_verify_engineering_contracts
cd docs && pnpm run build
git diff --check

依赖供应链检查:

bash
make audit-dependencies
make audit-licenses

Windows 初始化脚本的安全行为需要在 Windows 或 PowerShell 7 环境中验证:

powershell
pwsh -NoProfile -File scripts/test_init_security.ps1

这些命令的实际 workflow 和 selector 由仓库 .github/workflows 与 Makefile 维护;文档不复制一份会漂移的 CI 配置。

性能评测

性能工具位于 backend/test/performance/;参数、采样和探针的单测位于 backend/test/unit/performance/,由常规后端 unit 命令执行。仓库根目录使用同一个模块入口,Python 环境需要后端依赖:

bash
python -m backend.test.performance --help
python -m backend.test.performance matrix --help
python -m backend.test.performance load --help
python -m backend.test.performance report tmp/load-tests/continuous/final-20260907/matrix.json
docker compose exec api uv run --group test pytest test/unit/performance -q

matrix 测不同用户、固定 Thread、完成后立即补位的闭环调度;load 保留通用对话与沙盒容量场景,协议不同,不能混算。两者执行采样会产生真实模型费用。默认矩阵为 3150 请求,小实验通过 --workers、--concurrency、--rounds-per-thread 显式缩减,不自动预热。认证变量、独立槽位与结果边界见并发优化决策。

矩阵只在隔离槽位运行。先导出槽位变量、测试认证变量和 MATRIX_FINE_TIMING,用 docker compose -f docker-compose.yml -f backend/test/performance/compose.yml up -d --no-deps api 装配实验 API;矩阵命令按 --workers 重建实验 Worker。采样结束或中断后,用普通 Compose 的 up -d --no-deps --force-recreate --scale worker=1 api worker 恢复普通入口。探针属于实验装配,不进入 shipping 启动。

report 默认只读已有样本,在相同目录生成 stages.json 与 report.md,不访问容器或模型,不改原始样本;仅在实验容器仍运行时显式使用 --refresh 补齐日志,并另外保存 complete.json。本地派生报告不替代决策记录中的可审阅结果。

证据和报告

测试结果必须说明:

  • 实际执行的完整命令;
  • 通过、失败或未执行;
  • 失败时的环境和影响;
  • 外部服务、凭证或浏览器未覆盖的范围;
  • 需要回读确认的最终状态、文件或协议结果。

Passed 只表示命令成功且结果已核对;Not run 必须说明原因。HTTP 200、任务完成提示、日志关键词、mock 调用次数和 Agent 自述都不能单独形成完成证据。

提交前检查

  • 测试位于正确层级,且名称表达行为。
  • 断言了业务结果和关键副作用,而不是只断言 HTTP 状态码。
  • 新增 guard 有能恢复目标缺陷的负向案例。
  • fixture 不依赖共享默认数据,测试数据会清理。
  • skip 有明确的可选外部依赖或缺失环境变量原因。
  • 真实 API、数据库、worker、文件、对象或浏览器语义已经按风险验证。
  • expected output、fixture 和 snapshot 的更新经过人工审阅。
  • PR 如实记录命令、结果和未验证范围。

相关规范:参与贡献、工程信任系统、Yuxi Spec Loop。


File: docs/agents/agents-config.md

配置智能体

本页是智能体配置参考,说明页面上的字段如何进入一次运行。新增智能体后端的代码结构见开发智能体后端;只使用现成智能体时,从快速开始开始。

配置模型

对象 负责什么
Agent 数据库中的智能体资源,保存名称、slug、共享范围和持久化配置
BaseAgent 代码中的后端类型,声明 context_schema 和 get_graph()
BaseContext 配置字段和运行时输入的 Schema
config_json.context 当前 Agent 保存的配置值
Graph / middleware 根据 Context 组合模型、工具、文件和扩展能力

内置 ChatbotAgent 用于普通对话,SubAgentBackend 用于被主智能体委派的任务。子智能体的配置入口与普通智能体相同。

配置页面从哪里来

BaseContext 的 dataclass 字段和 metadata 会生成配置项描述,前端不再维护一份独立字段清单:

text
Context 字段
  → get_configurable_items()
  → Agent 详情接口
  → AgentRuntimeConfigForm
  → config_json.context

metadata 可以定义展示名称、说明、控件类型、选项和角色权限。运行期 ID、owner 和内部派生值应隐藏,不作为用户配置。

metadata.auth 只限制修改权限:admin 字段允许管理员和超级管理员修改,superadmin 字段只允许超级管理员修改。普通用户读取有权访问的智能体时,可以读到这些已保存值,运行也使用这些值;不可修改的字段不进入其编辑表单,后端会过滤越权提交的字段并保留已保存值。auth 不提供字段保密能力,Context 配置不得存放凭据。

例如管理员将 max_execution_steps 设置为 50,普通用户运行该智能体时的 recursion_limit 也为 50。已经固化运行快照的 Run 保留自己的配置;后续新 Run 读取保存的配置。知识库、Skills 等资源仍按运行用户的访问权限筛选。

基础字段

字段 作用
system_prompt 智能体角色和行为说明
model 主模型;留空时使用系统默认模型
tool_approval_mode default 或 always_trust;仅管理员可配置
tools 可使用的内置工具
knowledges 可检索的知识库范围
mcps 直接添加到 Agent 的已启用 MCP 服务器
skills 选择共享和内置 Skill;个人 Skill 始终可用
preload_skills 从首轮请求加载完整说明和依赖的 Skill 子集
summary_threshold、summary_keep_messages 上下文压缩的唯一压力阈值和摘要后保留消息数
summary_prompt、summary_tool_result_token_limit 摘要提示词和工具结果预览上限
max_execution_steps、model_retry_times 单次运行步数和模型重试次数

资源选择语义

资源字段 tools、knowledges、skills、subagents、mcps 和 preload_skills 接受 "all" 或字符串数组,本页是这组契约的唯一 Owner。"all" 表示每次运行使用当前用户可访问的全部资源,包括之后新增的资源;[] 表示不选择;非空数组表示固定范围。新写入拒绝 null、未知字符串和非字符串数组成员。

创建时省略字段使用其默认值:工具、知识库和 Skill 为 "all",子智能体为 ["general-purpose"](通用任务),MCP 和预加载 Skill 为 []。界面的「全部(含新增)」保存 "all";逐项勾选保存固定数组,即使勾选了当前所有选项也不会自动转换成全部模式。「清空全部」保存 [],子智能体同样可以全部禁用。

各资源的附加规则:

  • MCP 选择只控制直接加载的服务器;已激活 Skill 声明的 MCP 依赖仍按需加载。
  • Skill 选择器只列共享和内置 Skill;运行时自动合并当前用户全部个人 Skill,skills=[] 只关闭共享选择。Skill 的激活时机见 Skills 管理。
  • 预加载候选项限于已选且可访问的共享 Skill;preload_skills="all" 在每次运行预加载该范围及其授权依赖的完整说明,并从首轮开放依赖工具。skills=[] 时预加载全部仍得到空列表,个人 Skill 继续按需读取正文。
  • 子智能体不能继续调用下一层子智能体。

这些字段只会缩小当前用户已经拥有的权限;运行时如何消费解析结果见 Agent 运行时上下文。

共享智能体与编辑保存

共享智能体保存完整的期望选择,每次运行再与当前操作者的可访问资源取交集,运行不会改写保存的选择。例如创建者选择 10 个 Skill,委托管理员只能访问其中 5 个,委托管理员运行时生效 5 个,保存名称或模型后仍保留原有 10 个选择。

编辑页只提交修改过的配置字段;通过 API 更新 config_json.context 时,省略字段保留原值。修改非空资源列表时,后端保留当前管理员不可访问的既有引用,并拒绝新增无权访问的引用;保留的引用维持原相对顺序,新选择追加到末尾。「清空全部」移除所有引用(包括不可访问项),切换到「全部(含新增)」则整体替换固定选择范围。界面会显示当前不在可选范围内的既有选择数量。

定时运行

智能体支持用户定时任务(Beta):为智能体配置 5 段 cron 和 IANA 时区后,worker 到期自动在独立 Conversation 中触发一次运行,也支持手动立即运行;停机错过的多个周期合并为一次,已有未完成执行时跳过并记录。任务的定义、触发与恢复边界见 Agent 运行时上下文。

自定义 Context 字段

需要让用户配置额外行为时,扩展 Context,让后端和前端沿同一 Schema 工作:

python
from dataclasses import dataclass, field
from yuxi.agents import BaseContext


@dataclass(kw_only=True)
class MyAgentContext(BaseContext):
    response_style: str = field(
        default="concise",
        metadata={
            "name": "回答风格",
            "description": "控制回答的详细程度",
            "type": "string",
            "options": ["concise", "detailed"],
        },
    )

新增字段会影响保存结构、配置表单和运行期 Context。字段默认值、权限和选项变化时,同时更新相关测试和文档。

`capabilities`

capabilities 是代码声明的静态能力,用于控制固定的前端入口,例如:

python
class MyAgent(BaseAgent):
    capabilities = ["file_upload", "files"]

它不保存待办、文件、产物或子智能体状态。运行态来自 LangGraph state 的 agent_state;能力声明只表达后端固定支持哪些 UI 入口。

运行时入口

配置如何与用户身份、权限快照、Workdir、Memory 和 PostgreSQL checkpoint 组合,见Agent 运行时上下文。工具可见性和执行授权见工具系统,上下文压缩见上下文压缩机制。

相关页面


File: docs/agents/mcp-integration.md

集成 MCP

MCP(Model Context Protocol)让智能体调用外部服务提供的工具。管理员在“扩展 → MCP”中添加远程服务器;Agent 可直接选择服务器,Skill 也可声明激活后加载的依赖。

支持的传输方式

传输方式 适用场景
streamable_http 新的远程 MCP 服务
sse 仍提供 SSE 接口的远程服务

管理接口只接受 streamable_http 和 sse。Yuxi 不支持 stdio,包括内置 MCP 和直接传入的运行时配置;历史 stdio 配置会被禁用,应迁移为远程服务。

添加远程 MCP

在“扩展 → MCP”点击“添加 MCP”,填写稳定标识、名称、传输方式和 URL。例如:

json
{
  "slug": "custom-remote-mcp",
  "name": "Example MCP",
  "transport": "streamable_http",
  "url": "https://example.com/mcp"
}

管理接口对应:

http
POST /api/system/mcp-servers
Authorization: Bearer <admin-token>
Content-Type: application/json

{
  "slug": "custom-remote-mcp",
  "name": "Example MCP",
  "transport": "streamable_http",
  "url": "https://example.com/mcp",
  "description": "提供示例查询工具"
}

需要认证的远程服务可以配置 HTTP headers、连接超时和 SSE 读取超时。凭证会随着连接请求发送,请只配置必要的 header,并把管理接口限制在可信的管理员范围。

添加后先点击“测试连接”,确认能发现工具,再把服务器状态设为“已添加”。状态关闭时,服务器记录仍保留,但不会进入运行时。

让智能体使用 MCP

在智能体配置的 MCP 字段中显式选择要直接添加的服务器。该字段默认不添加服务器:

  • 未显式配置或显式清空时,不直接加载 MCP 服务器;
  • 显式选择后,只使用所选且当前可用的服务器;
  • MCP 工具仍会在执行处使用当前用户身份和服务器配置;
  • 管理员可以在 MCP 详情页单独禁用某个工具。

共享 Skill 的 mcp_dependencies 声明所需服务器。Skill 激活后按需加载这些服务器的工具;预加载 Skill 从首轮加载,均无需在 Agent 的 MCP 字段重复选择。服务器必须由管理员启用,禁用的服务器不会提供工具。

MCP 配置从 PostgreSQL 读取,工具对象按配置哈希缓存。修改连接配置或工具禁用列表后,下一次运行会使用新的配置键。

内置远程 MCP

内置 DeepWiki 使用 deepwiki-official 标识,通过 https://mcp.deepwiki.com/mcp 提供 Streamable HTTP 服务,无需认证即可查询公开 GitHub 仓库。详见 DeepWiki 官方文档。

开发者在 builtin.py 的 BUILTIN_MCP_SERVERS 中添加固定远程定义:

python
BUILTIN_MCP_SERVERS = {
    "deepwiki-official": {
        "transport": "streamable_http",
        "url": "https://mcp.deepwiki.com/mcp",
        "description": "查询公开 GitHub 仓库的文档、架构与代码",
        "icon": "📚",
        "tags": ["内置", "代码", "文档"],
    },
}

API/worker 启动时同步固定定义到数据库;运行时直接读取代码中的连接字段。新内置 MCP 默认未添加,管理员需要启用;连接配置不可通过页面修改。添加定义无需修改服务逻辑或启动入口。

原内置 mcp-server-chart 已退役,启动时删除系统创建的记录。其他历史 stdio 记录保留并禁用,管理员可迁移为远程服务或删除。MySQL 报表技能改用 Markdown 表格,不再依赖图表 MCP;自定义角色或 Skill 中对退役 MCP 的引用需要自行调整。

常用管理接口

方法 路径 作用
GET /api/system/mcp-servers 查看服务器;普通用户只得到脱敏基础信息
POST / PUT /api/system/mcp-servers、/{slug} 添加或修改远程 MCP
PUT /api/system/mcp-servers/{slug}/status 启用或停用服务器(对应页面上的添加/移除状态)
POST /api/system/mcp-servers/{slug}/test 测试连接并发现工具
GET /api/system/mcp-servers/{slug}/tools 查看工具
PUT /api/system/mcp-servers/{slug}/tools/{tool_name}/toggle 启用或禁用单个工具

接口字段和错误响应以实例 Swagger 为准。


File: docs/agents/middleware.md

中间件

中间件把文件、Skills、子智能体、上下文压缩、审批和用量统计接到 LangGraph Agent。它们在模型调用、工具调用或 state 更新的边界运行,让不同 Agent 复用同一套能力。

内置 ChatbotAgent 和 SubAgentBackend 都在 get_graph() 中组装中间件。Graph 创建前,系统先完成用户资源和权限的归一化;中间件不应绕过这一步重新决定授权。

Graph 创建前的准备

prepare_agent_runtime_context 会根据当前用户和 Agent 配置:

  • 过滤内置工具、知识库、MCP、Skills 和子智能体;
  • 生成 _visible_knowledge_bases;
  • 展开 Skill 依赖,生成 _effective_skill_slugs 和 _runtime_skills;
  • 使用系统默认模型补齐空的模型配置。

随后,工具解析器准备可执行工具,build_prompt_with_context 生成系统提示词,Agent 再创建 Graph。工具执行时仍需检查具体目标,准备阶段的资源快照不是授权替代品。

内置中间件顺序

ChatbotAgent 的常见顺序如下(graph.py 装配);可选项只在对应能力启用时加入:

顺序 中间件 作用
1 ToolErrorGuardMiddleware 最外层隔离普通工具异常,保留取消与 interrupt 的传播
2 SteerMiddleware 在安全边界发现待接替请求
3 create_agent_filesystem_middleware 提供 Workdir、User Data、Skills 文件后端,并卸载过大的工具结果
4 SkillsMiddleware 注入 Skill 说明,按激活状态开放依赖
5 YuxiMemoryMiddleware Memory 开关开启且 MEMORY.md 有内容时,注入用户记忆并提供受限工具
6 YuxiSubAgentMiddleware 主智能体有可见子智能体时提供子智能体生命周期工具
7 YuxiSummarizationMiddleware 先确定性压缩工具结果,仍达到同一阈值时生成摘要
8 TodoListMiddleware 保存待办,供状态面板展示
9 PatchToolCallsMiddleware 修正部分工具调用消息形态
10 NetworkRetryMiddleware 网络错误按预算、其他可重试模型错误按次数重试,耗尽后抛出异常
11 ImageInputCompatibilityMiddleware 桥接工具读取图片与模型输入格式;必要时回退 OCR
12 TokenUsageMiddleware 记录近似上下文和主模型实际用量
13 工具审批 middleware 默认模式下审批 Project 外的文件写入和命令执行;当前 Project 内的写/编辑自动放行

SubAgentBackend 复用文件、Skills、Summary、待办、重试和用量等能力,同样以 ToolErrorGuardMiddleware 为最外层,但不挂载子智能体 middleware,并过滤不适合子智能体的敏感或交互工具。

模型重试耗尽后,异常进入 Run 失败通道,持久化 failed 状态与错误原因;已有部分输出保留错误元数据。

子 Run 的失败通过 subagent_await / subagent_status 返回给父智能体,由父智能体决定后续处理。最终正常回答仍须满足同 Run 的 model lifecycle 审计关联。

Skills 和知识库

Skills middleware 将 Skill 说明按模型请求注入:预加载 Skill 从首轮开放工具和 MCP 依赖,普通 Skill 在模型读取对应 SKILL.md 后激活,再开放声明的依赖。Agent 显式选择的 MCP 服务器从运行开始提供工具。

知识库能力由内置 knowledge-base Skill 提供。它的工具是否注册、模型是否可见、参数是否能访问目标知识库分别由工具组装、Skill 激活和知识库权限检查负责。完整链路见工具系统和知识库机制详解。

文件和附件

附件确认后写入当前 Project Workdir。每次 Run 会把线程历史附件的文件名和实时路径加入本轮用户消息,让模型按需调用 read_file;持久化 Message 仍保存原始文本,不会把这段模型专用路径混进用户可见消息。

普通 Agent 和子 Agent 使用根 Conversation 的同一个 runtime_scope_id 和 Workdir。子 Agent 的 child thread 只隔离 LangGraph checkpoint,不隔离共享文件。Viewer、附件和 artifact API 直接访问 UserWorkspace 的持久文件,不需要创建 file-bridge Sandbox。

子智能体

主智能体配置了可见子智能体时,middleware 提供 subagent_start、subagent_status、subagent_cancel 和 subagent_await。subagent_start 立即返回子 Run 身份,subagent_await 按需等待;查询和取消均按 run_id 执行。子智能体使用自己的 Context 和 checkpoint,但继承发起用户的权限、Workdir 和 execution runtime。

详细的调用、busy、结果和文件边界见子智能体。

Summary 上下文压缩

Summary 在文件和 Skills 等中间件之后运行。请求达到唯一压力阈值后,它先为当前模型请求精简工具结果并重新计量;仍达到同一阈值时才写入历史文件、生成摘要并更新 checkpoint。它不会删除 PostgreSQL 聊天消息,也不会把内部摘要模型调用当作用户可见回复。支持主动压缩的 Agent 复用同一摘要器,并通过 service 在空闲线程的 canonical graph state 上更新 checkpoint。

参数和状态细节见上下文压缩机制。

Token 用量

TokenUsageMiddleware 同时记录近似上下文 token(用于摘要阈值和状态面板)和主 Agent 模型返回的 usage_metadata(按最近调用、当前 Run、线程和模型分桶保存)。

siliconflow-cn 和 siliconflow 当前位于 Provider 用量黑名单:这两个供应商仍可提供近似上下文统计,但不会写入最近调用、Run 或线程的 Provider 实际用量聚合。其他能返回兼容 usage_metadata 的供应商才会进入这组实际用量统计;Summary 内部摘要模型调用不计入。

Run 终态时,worker 把与当前 run_id 匹配的 state 快照写入 AgentRun.token_usage;父 Run 和子 Run 分开保存。

新增中间件时

先说明它要改变哪一条边界:Prompt、模型调用、工具调用、文件访问、state 或观测。资源筛选和权限收敛放在 Graph 创建前;文件读写和工具结果卸载优先复用现有 filesystem middleware;新增模型可见输入或副作用时补充对应测试和失败案例。

实现入口:ChatbotAgent graph、中间件目录。


File: docs/agents/skills-management.md

管理 Skills

Skill 是一个可复用的能力包,通常包含一个 SKILL.md、提示词、参考资料和可选脚本。智能体先看到 Skill 的描述,再按需要读取 SKILL.md;本地工具和 MCP 依赖随 Skill 激活进入模型请求。

什么时候用 Skill

把一类稳定、可复用的工作方式放进 Skill,例如文档处理、研究流程、报表生成或某个外部服务的操作规范。单次任务要求写在对话中;需要跨对话复用的规则和资料再考虑 Skill 或用户工作区文件。

Skill 保存在哪里

来源 位置和事实 谁可以安装/管理
内置 随代码发布,启动时同步索引 管理员可以启用/停用,不能删除或直接编辑
共享上传/远程 Skill 持久目录 + PostgreSQL 索引 管理员按共享范围管理
个人 当前用户 UserWorkspace 的 agents/skills/<slug> 当前用户;不创建共享 Skill 数据库记录

运行时路径是:

  • 共享和内置 Skill:/home/gem/skills/<slug>/,对 Agent 只读;
  • 个人 Skill:/home/gem/user-data/agents/skills/<slug>/,位于当前用户的 UserWorkspace。

同一用户有个人 Skill 和共享 Skill 使用同一个 slug 时,个人版本覆盖该用户看到的共享版本。删除个人版本后,如果共享版本仍可访问,会恢复共享版本。

创建 Skill

标准目录至少包含根级 SKILL.md:

text
my-skill/
├── SKILL.md
├── tools/       # 可选脚本
└── prompts/     # 可选提示词或参考资料

SKILL.md 使用 YAML frontmatter:

markdown
---
name: 文档整理
slug: document-cleanup
description: 按指定结构整理文档,并保留可核对的来源。
tool_dependencies: []
mcp_dependencies: []
skill_dependencies: []
---

# 文档整理

## 何时使用

说明这个 Skill 解决什么问题,以及什么时候不该使用。

## 操作步骤

写出 Agent 应遵循的步骤、限制和验收方式。

必填字段是 name 和 description。名称和 slug 最多 128 个字符。slug 可省略,省略时直接使用 name,因此省略 slug 时 name 本身也必须是小写字母、数字和单个短横线组成的值,中文或带空格的展示名称会校验失败。

建议显式填写 slug,把自然语言名称和稳定标识分开。

依赖字段含义:

字段 作用
tool_dependencies 需要的内置工具
mcp_dependencies 需要的 MCP 服务器
skill_dependencies 需要先提供说明的其他 Skill

个人 Skill 只作为用户文件读取,不解析这些平台依赖;需要依赖工具、MCP 或其他 Skill 时,安装为共享 Skill。

安装方式

进入“扩展 → Skills”,可以选择:

  1. 推荐 Skill:从推荐列表生成安装草稿。
  2. 上传:上传 ZIP 或单个 SKILL.md,解析后生成草稿。
  3. 远程安装:从 GitHub 仓库、ModelScope Skill 或合集拉取一个或多个 Skill。
  4. 在线编辑:编辑已有且有管理权限的共享 Skill 文件和依赖。
  5. Agent 内安装:主智能体使用 install_skill 把 Skill 安装到当前用户的个人来源;子智能体不能使用该工具。

上传和远程安装都先解析为草稿,再选择个人或共享位置并确认。草稿中的可安装条目只包含已解析的临时包;远程拉取或解析失败会单独显示,不能被确认安装。确认前可以检查名称、说明、文件和依赖;取消草稿不会写入正式 Skill。

有管理权限的用户可在共享 Skill 详情页编辑文本文件。保存 SKILL.md 时,根文件的名称、描述及依赖声明会同步到数据库索引;“配置”中的依赖选择也会回写根文件。每次保存都使用打开文件时取得的修订值;如果其他人已修改该文件,页面会拒绝覆盖并保留当前草稿,用户需核对最新内容后重试。切换文件或离开页面时,未保存的修改会触发确认。共享范围和启停状态仍在配置页分别保存。新的共享内容在后续 Agent Run 准备时生效;已准备 Run 的预加载内容保留原有快照,按需读取的用户投影可随之后的编辑刷新。

远程来源限制

管理员在“设置 → 基本设置 → Skill 配置 → 远程来源白名单”中维护 remote_skill_source_policy.allowed_hosts。默认允许 github.com 和 modelscope.cn;主机名必须精确匹配,保存空列表会关闭远程安装。

示例来源:

text
anthropics/skills
https://github.com/anthropics/skills
https://modelscope.cn/skills/@anthropics/pdf
https://modelscope.cn/collections/MiniMax/MiniMax-Office-skills

除了按仓库或合集拉取,也可以在远程安装对话框切换到“全局搜索发现”,输入关键词检索 skills.sh 上的开源 Skills,再选择结果批量拉取和安装。搜索在一次性 Sandbox 中执行;选中结果后的实际拉取仍经过来源白名单和安装确认流程。

GitHub 的 owner/repo 简写会被转换为 HTTPS 地址。远程来源会在不继承全局或用户环境变量的一次性 Sandbox 中下载和提取,系统会拒绝绝对路径和路径穿越,并限制文件数、目录深度和总大小。来源白名单限制产品允许的地址,不是网络出口防火墙。

新增内置 Skill

在 backend/package/yuxi/agents/skills/buildin/<slug>/ 新增目录,至少包含 SKILL.md。启动同步按目录名排序发现直接子目录,忽略下划线或点开头的目录;无需修改 Python 注册清单。

SKILL.md frontmatter 唯一拥有名称、描述、版本和依赖。slug 必须与目录名一致,省略时使用 name;version 省略时为 1.0.0,建议使用引号包裹版本字符串。工具、MCP、Skill 依赖使用本页定义的字段。缺少根文件或元数据不合法时,启动同步明确失败。

API/worker 启动时同步文件、元数据和依赖,保留数据库中的启停状态。重启后在“扩展 → Skills”核对新增项的说明和依赖;新增脚本或资源也必须随发行包携带。源码目录与共享投影分别拥有发布内容和安装文件,编辑应落在源码目录。

安装前仍应审查 Skill 的提示词、脚本、依赖和网络行为。不要把数据库密码、云平台密钥或 SANDBOX_PROVISIONER_TOKEN 放进 Skill 或 Agent 环境。

依赖和加载时机

Agent 的 skills 配置语义("all"、固定数组与默认值)由资源选择契约拥有,本页描述激活时机。每个 Run 准备 Context 时,系统按权限解析共享选择,合并当前用户全部个人 Skill,再展开 skill_dependencies;主智能体和子智能体使用同一规则。skills=[] 时个人 Skill 仍进入模型可见的描述列表,只是没有共享选择。个人 Skill 安装不修改 Agent 配置,新增 Skill 在下一次 Run 准备时自动加入,已准备的运行保留快照。

普通渐进加载

  1. 创建 Graph 前,模型得到有效 Skill 的名称、描述和 SKILL.md 路径。
  2. 模型读取某个可见 Skill 的 SKILL.md 后,该 Skill 进入 activated_skills。
  3. 后续模型请求加入它声明的本地工具,并加载已启用的 MCP 依赖服务器提供的工具。

模型没有读取的 Skill 的依赖工具继续隐藏。未激活的 Skill 本地工具即使已注册到 ToolNode,也不能被模型调用。

预加载

Agent 配置可以用 preload_skills 指定少量需要从首轮就可用的 Skill。预加载选择器只列 skills 中当前用户可访问的共享和内置 Skill;系统会展开其依赖闭包,读取根级 SKILL.md,并从首轮模型请求开放本地工具和已启用的 MCP 依赖。

预加载的根文件缺失或不可读时,Graph 创建会明确失败,不会静默退回渐进加载。默认值为空列表;选择“全部(含新增)”时,每次运行预加载当前 Agent 已选且可访问的共享 Skill 及其授权依赖。同名个人版本覆盖共享版本时读取个人内容;其他个人 Skill 始终可发现,正文按需读取。固定列表和全部模式均遵循资源选择契约。

权限和选择

共享 Skill 使用 source_type、share_config 和 enabled 表达来源、范围和启用状态。范围使用 version 2 的 read_scope、manage_scope,可以是全局、部门或指定用户;管理范围必须包含在读取范围内。

用户 可见和使用 可管理
superadmin 全部允许的共享/内置 Skill 全部非内置 Skill,及内置 Skill 的启停
admin 命中读取范围且已启用的 Skill 命中管理范围的非内置 Skill,及内置 Skill 的启停
普通用户 命中读取范围且已启用的 Skill 自己拥有的非内置 Skill;新安装的 Skill 固定进入个人来源

普通用户安装的新 Skill 固定进入个人来源,不配置共享范围。管理员安装到共享来源时才会写入 PostgreSQL 索引,并可以配置部门或用户范围。扩展管理页展示当前用户可访问的共享和个人 Skill;Agent 配置选项只展示可访问的共享和内置 Skill。后端在保存时校验共享引用,运行时按当前用户身份加载个人目录。

Agent 配置中的 Skill 选择不能扩大用户的文件、知识库或 MCP 权限。

内置 Skill 说明

系统启动时会同步仓库内置 Skills,其中两个内置 Skill 有产品级行为约定。html-preview 用于在普通 Markdown 难以清晰表达指标、对比、流程、时间线或层级关系时,指导 Agent 输出静态 html:preview 围栏;普通 HTML 源码仍使用 html 代码块,前端把该围栏清洗后放入 sandboxed iframe 预览。未显式配置 Skills 的 Agent 按现有资源规则自动获得该 Skill;使用显式 Skills 允许列表的 Agent 需要选择 html-preview。

内置 deep-research 不依赖 html-preview。它默认在当前 Workdir 的 outputs/ 目录生成独立、响应式的 HTML 阅读文档,并通过交付物入口展示;用户明确指定其他格式时除外。宽屏报告可以提供侧栏目录,窄屏隐藏或折叠侧栏;报告可以按内容需要使用外部图片等公开资源,来源以普通链接呈现。

运行时文件行为

共享和内置 Skill 投影对 Agent 只读,但沙盒命令仍可能执行其中的脚本。脚本如需写文件,应写入当前 Project Workdir 或 User Data,而不是 Skill 目录。个人 Skill 直接从当前用户工作区读取,不会复制到共享投影。

Skill 的选择影响 Prompt 和工具激活;共享投影按用户授权集合生成,不会因为某个 Agent 选择了 Skill 就改变 Sandbox 身份。路径穿越、符号链接和跨用户访问由文件系统边界拒绝。

维护建议

  • slug 使用小写字母、数字和单个短横线,尽量短且能表达用途。
  • description 先说明能力和适用场景,再写实现细节。
  • 把“何时使用”“不要做什么”“产物放在哪里”和“如何验收”写进 SKILL.md。
  • 依赖链保持短小,避免循环依赖;只有真实需要时才声明工具或 MCP 依赖。
  • 脚本按不可信输入处理,不读取或输出运行环境中的秘密。
  • 修改共享 Skill 的依赖、范围或文件后,用一个真实 Agent Run 验证模型可见工具和最终产物。

实现入口见 包格式解析、安装草稿生命周期、共享 Skill 索引与安装、共享文件编辑、个人 Skill 安装与文件操作、用户投影、运行时解析 和 Skills middleware。


File: docs/agents/subagents-management.md

使用子智能体

子智能体是一个特殊的 Agent:它仍然是 agents 表中的一级智能体,只是标记为 is_subagent=true,并使用 SubAgentBackend。因此,子智能体和普通智能体共用创建、权限和配置入口。

在页面中配置

进入“智能体”,点击“新增智能体”,在后端类型中选择 SubAgentBackend。然后像配置普通智能体一样设置名称、提示词、模型、工具、知识库、MCP 和 Skills。

子智能体有三个限制:

  • 不会出现在普通聊天的智能体切换列表中;
  • 不能设置为默认智能体;
  • 不能继续调用其他子智能体,因此不会形成孙级调用链。

主智能体在运行配置的“子智能体”字段中选择允许调用的对象:

  • 未配置时默认仅选择通用任务(["general-purpose"]),运行时仍按当前用户权限过滤;
  • 显式选择 "all" 时,每次运行选择当前用户可见的全部子智能体;
  • 保存空列表 [] 时不启用子智能体;
  • 显式选择后,只允许调用所选项;
  • 每个子智能体使用自己的 config_json.context,不会继承主智能体的模型或工具选择;
  • 用户权限变化后,新运行会重新计算可见范围。

调用方式

主智能体通过工具调用子智能体,不要通过 Shell、curl 或 HTTP API 间接调用。

派发任务:`subagent_start`

所有子任务通过 subagent_start 派发,立即返回 run_id、thread_id 和访问链接。需要结果时调用 subagent_await(run_id);即使短任务也先派发再等待。多个独立任务先全部派发,再按需等待。

工具参数:

python
{
    "description": "整理这份文档的三条要点",
    "subagent_slug": "general-purpose",
    "thread_id": null
}

首次调用不需要 thread_id。如果要继续之前的子任务,把上一次结果中的子智能体线程 ID 传回去。

查询、等待与取消

生命周期工具统一按 Run 工作:

工具 作用
subagent_start 创建并立即返回 run_id、thread_id
subagent_status 查询运行状态和最近几条进度摘要
subagent_await 等待终态并取得最终结果;超时返回当前快照
subagent_cancel 请求取消子智能体运行

同一个子智能体线程同时只能有一个运行中的 Run。忙碌时工具返回 busy,不会隐藏地把请求排队。终态后的同一 thread_id 可以继续创建新的 Run。

所有生命周期工具按 run_id 操作,并验证该 Run 由当前父 Run 创建,不能读取或控制其他任务。

运行时边界

一次子智能体调用会创建独立的 child checkpoint thread 和 agent_runs(run_type=subagent) 记录,同时继承父运行的用户身份和根执行树:

资源 主智能体 子智能体
LangGraph checkpoint 当前 thread_id 独立 child thread_id
Sandbox runtime 根 runtime_scope_id 与根运行相同
Project Workdir 当前 Project 的 Workdir 与根 Conversation 绑定的 Project 相同
UserWorkspace 当前用户的工作区 同一用户的工作区
共享/内置 Skills 当前用户授权的只读投影 同一授权投影

父子智能体看到的是同一份 Workdir 文件字节,不会通过 checkpoint 复制或合并文件。child thread 只隔离 LangGraph 上下文;它不是文件系统隔离边界。并发写同一路径仍按真实 POSIX 文件结果处理。

子智能体可以使用自己配置的文件工具和知识库范围,但所有资源访问仍以发起用户的后端权限为最终边界。present_artifacts、ask_user_question、install_skill 等不适合子智能体直接使用的工具会被过滤;禁用状态不只在模型可见性上生效——即使模型显式传回被禁用的工具调用,执行层也会返回绑定原调用的错误结果,不会执行该工具。

API 和数据模型

子智能体沿用普通 Agent 管理 API:

  • GET /api/agent 默认返回聊天可用的普通 Agent;
  • GET /api/agent?include_subagents=true 返回包含子智能体的列表;
  • 创建或更新 SubAgentBackend 时,后端会校验 is_subagent=true;
  • 详情、更新和删除复用同一套 Agent 权限检查。

运行时,主智能体的 subagents 会先收敛为当前用户可见的允许列表。列表非空时挂载子智能体 middleware;子智能体自身不会挂载这一中间件。

查看结果

父 state 的 subagent_runs 保存子任务身份,页面加载时通过数据库父子关系补齐尚未进入 checkpoint 的记录。状态面板独立订阅各子 Run 的事件流,并从 Run 接口读取当前状态;父 graph 在 subagent_await 中等待时,先完成的子任务立即更新。切换会话会关闭订阅,重新打开时回读状态;完成后从持久化消息读取最终结果。Redis 原始事件只供运行基础设施和前端订阅,不作为主智能体的工具结果。

前端最多同时订阅三个子 Run,其余活跃子 Run 每两秒查询状态,避免 HTTP/1.1 的同源连接被长连接占满;连接无事件时每十五秒核对持久状态,断线或查询失败显示重连提示。

从 task 工具迁移

历史 task 消息仍可查看,新模型不再获得该工具。升级前完成或取消旧版本的活跃 Run,并在智能体管理中将已保存的深度研究 Agent 及自定义提示词中的 task 指令改为先 subagent_start、再 subagent_await;已有配置不会被新的默认提示词覆盖。历史 checkpoint 中尚未执行的 task 调用会明确返回未知工具错误,不会被自动重放为新的子任务。父 Run 终态仍按原有策略取消活跃后代,派发与等待分离不改变此边界。

实现入口见 子智能体 middleware、SubAgentBackend 和 AgentRun 服务。


File: docs/agents/tools-system.md

工具系统

Yuxi 的工具分成三层:内置工具、知识库工具和 MCP 工具。Graph 创建时准备可执行工具,运行时再根据用户权限、Agent 配置和 Skill 激活状态决定模型能看到什么。

注册一个内置工具

普通内置工具使用 @tool 注册:

python
from yuxi.agents.toolkits.registry import tool


@tool(category="buildin", tags=["示例"], display_name="示例工具")
def example_tool(text: str) -> str:
    """返回处理后的文本。"""
    return text
  • category 用于前端分组,常见值是 buildin、knowledge 和 debug;
  • tags 用于展示和筛选;
  • display_name 是给用户看的名称,工具 ID 是给代码和模型协议使用的稳定名称;
  • 工具模块需要被 toolkits 包导入,装饰器才会执行注册。

工具的注册不等于授权。产生文件、网络或数据库副作用的工具必须在执行边界再次校验当前用户和目标资源。

当前内置能力

常用内置工具包括:

工具 作用
ask_user_question 等待用户回答交互式问题
ocr_parse_file 把工作区中受支持的 PDF、Office 或图片转换为 Markdown
present_artifacts 展示当前用户可见的文件产物
install_skill 从允许的沙盒路径或 Git 来源安装个人 Skill;子智能体不可用
web_search 使用已配置的豆包或 Tavily 搜索网页

文件读写和命令执行由 Agent 的 Sandbox backend 提供。present_artifacts 推荐展示当前 Project 的 outputs/ 文件;large_tool_results 和会话摘要等内部文件不会作为交付物展示。

图片生成能力由内置 image-gen Skill 提供,不再作为独立的 Python 工具注册。具体依赖和文件位置由该 Skill 说明。

知识库工具

知识库工具以 @tool(category="knowledge") 注册,但不默认出现在模型工具列表。Agent 激活内置 knowledge-base Skill 后,Skills middleware 才会向模型开放:

工具 作用
list_kbs 列出当前运行可见的知识库
query_kb 按 kb_id 检索片段,返回 kb_id、file_id 和内容
find_kb_document 在指定文件中按关键词或正则定位内容
open_kb_document 按 file_id 分段读取解析后的文档
get_mindmap 读取知识导图
search_file 按文件名搜索可见知识库中的文件
download_kb_file 把有权限的原始文件下载到当前 Project 的 outputs/

工具参数中的 kb_id、file_id 和文件名都会在工具执行处重新检查,不能用模型提示词或 Agent 配置绕过知识库权限。知识库不会挂载为沙盒目录,读取方式见知识库机制详解。

需要在 Python 中直接取得知识库工具时:

python
from yuxi.agents.toolkits.kbs import get_common_kb_tools

kb_tools = get_common_kb_tools()

返回的具体顺序由函数实现维护,不要把顺序当作协议。

内置工具:网页搜索

web_search 让 Agent 从外部网页获取实时信息,当前支持豆包和 Tavily 两个供应商。搜索结果属于外部来源,不能自动替代知识库或人工核验;对重要结论应回到原网页核实。

供应商在 API/worker 环境中选择并提供凭证:

bash
WEB_SEARCH_PROVIDER=doubao
DOUBAO_SEARCH_API_KEY=<your-doubao-key>

或 WEB_SEARCH_PROVIDER=tavily 加 TAVILY_API_KEY=<your-tavily-key>。WEB_SEARCH_PROVIDER 只接受这两个值;留空时按已配置的 API Key 自动选择,两个 Key 都配置时优先豆包,没有有效 Key 时不会注册该工具。密钥只放在受保护的环境变量或密钥管理器中,不要写进 Skill、Agent 用户环境、代码或日志。

工具在 API 和 worker 进程加载时注册,修改环境变量后需要 docker compose up -d --force-recreate api worker。验证方式:在智能体详情确认工具列表出现「网页搜索」,用一个需要最新信息的问题发起真实对话,并检查工具调用返回的 URL、标题和摘要。没有该工具时检查 WEB_SEARCH_PROVIDER 拼写、对应 Key 是否存在以及容器是否已重建。

实现入口:网页搜索工具。

工具组装流程

内置 Agent 创建 Graph 时执行:

  1. prepare_agent_runtime_context 按当前用户权限过滤工具、知识库、MCP、Skills 和子智能体。
  2. resolve_configured_runtime_tools(context) 注册 Agent 配置和可见 Skill 依赖的可执行本地工具,并加载配置的 MCP 工具。
  3. SkillsMiddleware 根据当前已预加载或已激活的 Skill,向模型请求开放相应工具 schema。
  4. 工具执行器再次检查具体文件、知识库、MCP 和用户身份。
text
Agent 配置 + 用户权限
        ↓
运行时资源快照
        ↓
可执行工具注册 ──→ Skill 激活门控 ──→ 模型可见工具
        ↓
执行处的目标授权

“可执行”与“模型可见”是两件事:工具可以先注册到 ToolNode,等 Skill 激活后才对模型开放;模型看见某个工具也不代表它可以访问任意资源。

MCP 和 Skills

MCP 工具可由 Agent 直接选择的已启用服务器提供,也可由已激活 Skill 的 mcp_dependencies 按需加载;预加载 Skill 从首轮开放本地工具和 MCP 依赖,普通 Skill 在读取 SKILL.md 激活后开放这些依赖。这一节只回答“工具从哪里来、谁决定模型可见”;服务器配置和启停归 MCP 集成,Skill 的创建、依赖与激活时机归 Skills 管理,资源选择的字段语义归智能体配置。

新增工具时检查

  • 工具是否属于内置工具、知识库工具、MCP 还是 Skill;
  • 是否需要在 ToolNode 注册,是否需要 Skill 激活后才让模型看见;
  • 产生副作用时,执行处是否有用户、路径和资源权限校验;
  • 错误是否以结构化结果返回,并保留可排查信息;
  • 前端展示名称是否来自稳定元数据;
  • 是否补充纯逻辑和真实 HTTP/文件链路的相应测试。