可私有部署的多租户知识智能体平台:统一 RAG、知识图谱、多智能体、MCP/Skills、沙盒与权限管理。Yuxi = Cloud Agents + Knowledge RAG, Self-hosted knowledge agent platform for RAG, knowledge graphs and...
docker pull library/neo4j
docker run -d --name yuxi -p 6050:6050 --restart always neo4j:5.26.29
podman run -d --name yuxi -p 6050:6050 --restart always neo4j:5.26.29
skopeo inspect docker://docker.io/library/neo4j:5.26.29
oras pull neo4j:5.26.29
docker compose up -d
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