fix(config): layer root and env compose files with expanded overrides

Load agent/cursor env overrides in config loader; pass root .env plus
environment-specific env files in env-service; align compose env_file mounts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
weichen
2026-09-13 02:55:18 +00:00
co-authored by Cursor
parent 40fdd9315c
commit fe0bb3a503
13 changed files with 203 additions and 105 deletions
+2
View File
@@ -3,6 +3,8 @@
.doc .doc
docs docs
data data
.regulations
.tmp
web/node_modules web/node_modules
web/dist web/dist
web/.vite web/.vite
+17 -14
View File
@@ -3,14 +3,12 @@
# #
# 使用说明: # 使用说明:
# 1. 复制为 .env 并填写密钥:cp .env.example .env && chmod 600 .env # 1. 复制为 .env 并填写密钥:cp .env.example .env && chmod 600 .env
# 2. 本文件供 Go 后端 (bin/server)、launch.sh 默认 compose 变量替换使用 # 2. 本文件供 Go 后端、launch.sh 与 env-service.sh 作为公共配置源
# 3. 若通过 ./scripts/env-service.sh dev start 启容器,还需同步密钥到 configs/.env.dev # 3. configs/.env.<环境> 只保存端口、数据库等环境差异,且优先级高于本文件
# (见 configs/.env.dev.example 第 6 节)
# ============================================================================= # =============================================================================
# --- 1. 运行环境与基础目录 --- # --- 1. 运行环境与基础目录 ---
ENV=dev ENV=dev
COMPOSE_PROJECT_NAME=agent-dev
DATA_DIR=./data/dev DATA_DIR=./data/dev
APP_PUBLISH_HOST=127.0.0.1 APP_PUBLISH_HOST=127.0.0.1
HTTP_PORT=8080 HTTP_PORT=8080
@@ -42,7 +40,7 @@ PI_AGENT_RUNNER_PORT=18865
# 宿主 bin/server 直连本机 pi-agent 容器时启用;容器内 app 服务由 compose 注入 http://pi-agent:18865 # 宿主 bin/server 直连本机 pi-agent 容器时启用;容器内 app 服务由 compose 注入 http://pi-agent:18865
# PI_AGENT_RUNNER_URL=http://127.0.0.1:18865 # PI_AGENT_RUNNER_URL=http://127.0.0.1:18865
# Runner 服务间认证(main 必填;dev 可留空) # Runner 服务间认证(main 必填;dev 可留空)
# RUNNER_AUTH_TOKEN= RUNNER_AUTH_TOKEN=
# --- 7. 本机 Pi CLI 回退(不使用容器 runner 时)--- # --- 7. 本机 Pi CLI 回退(不使用容器 runner 时)---
PI_AGENT_BIN=/opt/homebrew/bin/pi PI_AGENT_BIN=/opt/homebrew/bin/pi
@@ -54,17 +52,15 @@ PI_AGENT_MODEL=MiniMax-M2.7-highspeed
KB_DB_HOST=127.0.0.1 KB_DB_HOST=127.0.0.1
KB_DB_PORT=5432 KB_DB_PORT=5432
KB_DB_USER=regkb KB_DB_USER=regkb
KB_DB_PASSWORD=shared_kb_secret_pass KB_DB_PASSWORD=
KB_DB_NAME=regulations KB_DB_NAME=regulations
# --- 9. 向量 Embedding / Reranker(Go 宿主 KB 检索;容器 app 当前未透传,宿主模式必填)--- # --- 9. 向量 Embedding / Reranker(Go 宿主与容器 app 的 KB 检索)---
EMBEDDING_ENDPOINT_URL=https://www.dmxapi.cn/v1/embeddings EMBEDDING_ENDPOINT_URL=https://www.dmxapi.cn/v1/embeddings
EMBEDDING_PROVIDER=openai_compatible
EMBEDDING_MODEL=bge-m3 EMBEDDING_MODEL=bge-m3
EMBEDDING_API_KEY= EMBEDDING_API_KEY=
RERANKER_ENDPOINT_URL=https://www.dmxapi.cn/v1/rerank RERANKER_ENDPOINT_URL=https://www.dmxapi.cn/v1/rerank
RERANKER_PROVIDER=openai_compatible
RERANKER_MODEL=bge-reranker-v2-m3-free RERANKER_MODEL=bge-reranker-v2-m3-free
RERANKER_API_KEY= RERANKER_API_KEY=
@@ -87,8 +83,15 @@ ADMIN_PASSWORD=admin-secret-password-123
AUTH_REGISTRATION_ENABLED=true AUTH_REGISTRATION_ENABLED=true
AUTH_REQUIRE_APPROVAL=false AUTH_REQUIRE_APPROVAL=false
# --- 12. 可选:CORS / 内存降级 --- # --- 12. 安全、缓存与意图路由开关 ---
# dev/test 未配置时 CORS 由代码填充 localhost 默认值 # 留空时 dev/test 由代码补 localhost;生产留空代表不允许跨 Origin
# CORS_ALLOWED_ORIGINS=http://localhost:5173,http://127.0.0.1:8080 CORS_ALLOWED_ORIGINS=
# 生产必须为 false;main 启动时若 true 将拒绝加载 # 生产强制 false;main compose 不接受覆盖
# ALLOW_IN_MEMORY=false ALLOW_IN_MEMORY=false
CACHE_ENABLED=true
INTENT_V2_SHADOW=false
INTENT_V2_DECISION=false
INTENT_V2_DECISION_TIER=none
WORKFLOW_INTENTS_ENABLED=true
MULTI_INTENT_ENABLED=true
SEMANTIC_FALLBACK_ENABLED=false
+3 -19
View File
@@ -1,25 +1,18 @@
# ============================================================================= # =============================================================================
# 开发环境 Compose 专用配置模板 (configs/.env.dev) # 开发环境 Compose 专用配置模板 (configs/.env.dev)
# 作用域:./scripts/env-service.sh dev * 与 docker compose --env-file configs/.env.dev # 作用域:./scripts/env-service.sh dev * 的环境差异覆盖
# #
# 重要:本文件与根目录 .env 分工不同 # 根 .env 提供公共模型、检索、认证配置;本文件只覆盖 dev 差异。
# - 根 .env → Go bin/server、launch.sh 默认 compose
# - 本文件 → env-service.sh 启停 dev 容器组
# 密钥(MINIMAX_API_KEY / TAVILY_API_KEYS 等)须与根 .env 保持一致,否则 pi-agent 容器无 Token!
# ============================================================================= # =============================================================================
ENV=dev ENV=dev
COMPOSE_PROJECT_NAME=agent-dev
# 1. 业务 HTTP 服务 # 1. 业务 HTTP 服务
HTTP_PORT=8080 HTTP_PORT=8080
APP_PUBLISH_HOST=127.0.0.1 APP_PUBLISH_HOST=127.0.0.1
ADMIN_TOKEN=dev-admin-token-change-me
ALLOW_INSECURE_ADMIN=1
# 2. 对话数据与工作区(禁止跨环境共享) # 2. 对话数据与工作区(禁止跨环境共享)
DATA_DIR=./data/dev DATA_DIR=./data/dev
AUX_FILE_DSN=file:/var/lib/feishu/outbox_dev.db?_journal_mode=WAL&_busy_timeout=5000
# 3. 开发环境专属数据库 # 3. 开发环境专属数据库
SYSTEM_DB_USER=app SYSTEM_DB_USER=app
@@ -29,7 +22,6 @@ SYSTEM_DB_PUBLISH_PORT=5433
# 4. 共享知识库(容器 app 经 shuimu-kb-shared 访问) # 4. 共享知识库(容器 app 经 shuimu-kb-shared 访问)
KB_DB_USER=regkb KB_DB_USER=regkb
KB_DB_PASSWORD=shared_kb_secret_pass
KB_DB_NAME=regulations KB_DB_NAME=regulations
KB_DB_HOST=shuimu-kb-shared KB_DB_HOST=shuimu-kb-shared
KB_DB_PORT=5432 KB_DB_PORT=5432
@@ -40,12 +32,4 @@ PI_AGENT_RUNNER_PORT=18865
AGENT_BACKEND=pi-agent AGENT_BACKEND=pi-agent
AGENT_TIMEOUT=60m AGENT_TIMEOUT=60m
# 6. Pi-Agent / MiniMax / Tavily(pi-agent 容器必填;从根 .env 复制) # 6. Pi-Agent / MiniMax / Tavily / Runner 密钥统一读取根 .env
MINIMAX_ENDPOINT_URL=https://api.minimaxi.com/v1
MINIMAX_API_KEY=
MINIMAX_MODEL_ID=MiniMax-M2.7-highspeed
MINIMAX_API_TYPE=openai-compatible
PI_AGENT_PROVIDER=minimax
PI_AGENT_MODEL=MiniMax-M2.7-highspeed
TAVILY_API_KEYS=
# RUNNER_AUTH_TOKEN=
+8 -10
View File
@@ -1,39 +1,37 @@
# ============================================================================= # =============================================================================
# 生产/主分支环境 (main) 环境变量配置模板 (configs/.env.main) # 生产/主分支环境 (main) 环境变量配置模板 (configs/.env.main)
# 作用域:main 分支正式线上发布与生产运营 # 作用域:main 分支正式线上发布与生产运营
# 根 .env 提供公共模型与检索配置;本文件覆盖生产端口、数据库和生产密钥。
# ============================================================================= # =============================================================================
ENV=prod ENV=prod
COMPOSE_PROJECT_NAME=agent-main
# 1. 业务 HTTP 服务 # 1. 业务 HTTP 服务
HTTP_PORT=8000 HTTP_PORT=8000
APP_PUBLISH_HOST=0.0.0.0 APP_PUBLISH_HOST=0.0.0.0
ADMIN_TOKEN=main-prod-admin-token-super-secure AUTH_REGISTRATION_ENABLED=false
ALLOW_INSECURE_ADMIN=0 AUTH_REQUIRE_APPROVAL=true
# 2. 对话数据与工作区隔离目录 (禁止跨环境共享) # 2. 对话数据与工作区隔离目录 (禁止跨环境共享)
DATA_DIR=./data/main DATA_DIR=./data/main
AUX_FILE_DSN=file:/var/lib/feishu/outbox_main.db?_journal_mode=WAL&_busy_timeout=5000
# 3. 生产环境专属数据库 (禁止与 dev/test 共享) # 3. 生产环境专属数据库 (禁止与 dev/test 共享)
SYSTEM_DB_USER=app SYSTEM_DB_USER=app
SYSTEM_DB_PASSWORD=prod_highly_secure_db_pass SYSTEM_DB_PASSWORD=
SYSTEM_DB_NAME=agent_system_main SYSTEM_DB_NAME=agent_system_main
SYSTEM_DB_PUBLISH_PORT=5435 SYSTEM_DB_PUBLISH_PORT=5435
# 4. 共享知识库连接 (只读检索共享数据) # 4. 共享知识库连接 (只读检索共享数据)
KB_DB_USER=regkb KB_DB_USER=regkb
KB_DB_PASSWORD=shared_kb_secret_pass
KB_DB_NAME=regulations KB_DB_NAME=regulations
KB_DB_HOST=shuimu-kb-shared KB_DB_HOST=shuimu-kb-shared
KB_DB_PORT=5432 KB_DB_PORT=5432
# 5. Cursor / LLM Agent Runner 服务 # 5. Cursor / LLM Agent Runner 服务
CURSOR_RUNNER_PORT=18767 CURSOR_RUNNER_PORT=18767
AGENT_BACKEND=stub PI_AGENT_RUNNER_PORT=18867
AGENT_BACKEND=pi-agent
AGENT_TIMEOUT=60m AGENT_TIMEOUT=60m
# 生产必填(不可使用 shared_kb_secret_pass / admin-secret-password-123 等默认值) # 生产必填(不可使用 shared_kb_secret_pass / admin-secret-password-123 等默认值)
RUNNER_AUTH_TOKEN=change-me-runner-token RUNNER_AUTH_TOKEN=
KB_DB_PASSWORD=change-me-kb-prod-secret ADMIN_PASSWORD=
ADMIN_PASSWORD=change-me-admin-prod-secret
+1 -4
View File
@@ -1,13 +1,10 @@
# ============================================================================= # =============================================================================
# 共享基础设施环境变量配置模板 (configs/.env.shared) # 共享基础设施环境变量配置模板 (configs/.env.shared)
# 作用域:所有分支与环境共享的知识库、向量检索与底层公共网络 # 作用域:所有分支与环境共享的知识库、向量检索与底层公共网络
# KB_DB_PASSWORD 统一在根 .env 配置,本文件只覆盖共享服务差异。
# ============================================================================= # =============================================================================
COMPOSE_PROJECT_NAME=agent-infra
# 共享知识库 PostgreSQL (带 pgvector) # 共享知识库 PostgreSQL (带 pgvector)
KB_DB_USER=regkb KB_DB_USER=regkb
KB_DB_PASSWORD=shared_kb_secret_pass
KB_DB_NAME=regulations KB_DB_NAME=regulations
KB_PUBLISH_PORT=5432 KB_PUBLISH_PORT=5432
KB_DATA_DIR=./data/shared/kb
+2 -6
View File
@@ -1,20 +1,17 @@
# ============================================================================= # =============================================================================
# 测试环境 (test) 环境变量配置模板 (configs/.env.test) # 测试环境 (test) 环境变量配置模板 (configs/.env.test)
# 作用域:test 分支阶段集成、长期部署实测与运维验证 # 作用域:test 分支阶段集成、长期部署实测与运维验证
# 根 .env 提供公共模型、检索、认证配置;本文件只覆盖 test 差异。
# ============================================================================= # =============================================================================
ENV=test ENV=test
COMPOSE_PROJECT_NAME=agent-test
# 1. 业务 HTTP 服务 # 1. 业务 HTTP 服务
HTTP_PORT=8081 HTTP_PORT=8081
APP_PUBLISH_HOST=0.0.0.0 APP_PUBLISH_HOST=0.0.0.0
ADMIN_TOKEN=test-admin-token-secure-key
ALLOW_INSECURE_ADMIN=0
# 2. 对话数据与工作区隔离目录 (禁止跨环境共享) # 2. 对话数据与工作区隔离目录 (禁止跨环境共享)
DATA_DIR=./data/test DATA_DIR=./data/test
AUX_FILE_DSN=file:/var/lib/feishu/outbox_test.db?_journal_mode=WAL&_busy_timeout=5000
# 3. 测试环境专属数据库 (禁止与 dev/main 共享) # 3. 测试环境专属数据库 (禁止与 dev/main 共享)
SYSTEM_DB_USER=app SYSTEM_DB_USER=app
@@ -24,7 +21,6 @@ SYSTEM_DB_PUBLISH_PORT=5434
# 4. 共享知识库连接 (只读检索共享数据) # 4. 共享知识库连接 (只读检索共享数据)
KB_DB_USER=regkb KB_DB_USER=regkb
KB_DB_PASSWORD=shared_kb_secret_pass
KB_DB_NAME=regulations KB_DB_NAME=regulations
KB_DB_HOST=shuimu-kb-shared KB_DB_HOST=shuimu-kb-shared
KB_DB_PORT=5432 KB_DB_PORT=5432
@@ -32,7 +28,7 @@ KB_DB_PORT=5432
# 5. Cursor / LLM Agent Runner 服务 # 5. Cursor / LLM Agent Runner 服务
CURSOR_RUNNER_PORT=18766 CURSOR_RUNNER_PORT=18766
PI_AGENT_RUNNER_PORT=18866 PI_AGENT_RUNNER_PORT=18866
AGENT_BACKEND=stub AGENT_BACKEND=pi-agent
AGENT_TIMEOUT=60m AGENT_TIMEOUT=60m
# app 与 pi-agent 容器共享(test 环境建议设置非空值) # app 与 pi-agent 容器共享(test 环境建议设置非空值)
# RUNNER_AUTH_TOKEN=test-runner-shared-secret # RUNNER_AUTH_TOKEN=test-runner-shared-secret
+22 -4
View File
@@ -60,7 +60,6 @@ services:
MINIMAX_API_TYPE: ${MINIMAX_API_TYPE:-openai-compatible} MINIMAX_API_TYPE: ${MINIMAX_API_TYPE:-openai-compatible}
MINIMAX_PROVIDER: minimax MINIMAX_PROVIDER: minimax
TAVILY_API_KEYS: ${TAVILY_API_KEYS:-} TAVILY_API_KEYS: ${TAVILY_API_KEYS:-}
TAVILY_API_KEY: ${TAVILY_API_KEY:-}
RUNNER_AUTH_TOKEN: ${RUNNER_AUTH_TOKEN:-} RUNNER_AUTH_TOKEN: ${RUNNER_AUTH_TOKEN:-}
ports: ports:
- "127.0.0.1:${PI_AGENT_RUNNER_PORT:-18865}:18865" - "127.0.0.1:${PI_AGENT_RUNNER_PORT:-18865}:18865"
@@ -94,19 +93,38 @@ services:
HTTP_PORT: "${HTTP_PORT:-8080}" HTTP_PORT: "${HTTP_PORT:-8080}"
HTTP_HOST: "0.0.0.0" HTTP_HOST: "0.0.0.0"
AGENT_BACKEND: ${AGENT_BACKEND:-pi-agent} AGENT_BACKEND: ${AGENT_BACKEND:-pi-agent}
AGENT_TIMEOUT: ${AGENT_TIMEOUT:-30m}
MAX_CONCURRENT_AGENTS: ${MAX_CONCURRENT_AGENTS:-8}
DATA_DIR: /app/data DATA_DIR: /app/data
# 独占业务与对话库 # 独占业务与对话库
APP_DB_DSN: postgres://${SYSTEM_DB_USER:-app}:${SYSTEM_DB_PASSWORD:-dev_secret_db_pass}@system-db:5432/${SYSTEM_DB_NAME:-agent_system_dev}?sslmode=disable APP_DB_DSN: postgres://${SYSTEM_DB_USER:-app}:${SYSTEM_DB_PASSWORD:-dev_secret_db_pass}@system-db:5432/${SYSTEM_DB_NAME:-agent_system_dev}?sslmode=disable
# 共享知识库 # 共享知识库
POSTGRES_SHARED_KB_DSN: postgres://${KB_DB_USER:-regkb}:${KB_DB_PASSWORD:-shared_kb_secret_pass}@shuimu-kb-shared:5432/${KB_DB_NAME:-regulations}?sslmode=disable POSTGRES_SHARED_KB_DSN: postgres://${KB_DB_USER:-regkb}:${KB_DB_PASSWORD:?KB_DB_PASSWORD is required}@shuimu-kb-shared:5432/${KB_DB_NAME:-regulations}?sslmode=disable
CURSOR_RUNNER_URL: http://cursor-agent:18765 CURSOR_RUNNER_URL: http://cursor-agent:18765
PI_AGENT_RUNNER_URL: http://pi-agent:18865 PI_AGENT_RUNNER_URL: http://pi-agent:18865
PI_AGENT_RUNNER_AUTH_TOKEN: ${RUNNER_AUTH_TOKEN:-} PI_AGENT_RUNNER_AUTH_TOKEN: ${RUNNER_AUTH_TOKEN:-}
PI_AGENT_PROVIDER: ${PI_AGENT_PROVIDER:-minimax} PI_AGENT_PROVIDER: ${PI_AGENT_PROVIDER:-minimax}
PI_AGENT_MODEL: ${PI_AGENT_MODEL:-MiniMax-M2.7-highspeed} PI_AGENT_MODEL: ${PI_AGENT_MODEL:-MiniMax-M2.7-highspeed}
RUNNER_AUTH_TOKEN: ${RUNNER_AUTH_TOKEN:-} RUNNER_AUTH_TOKEN: ${RUNNER_AUTH_TOKEN:-}
ALLOW_IN_MEMORY: "false" EMBEDDING_ENDPOINT_URL: ${EMBEDDING_ENDPOINT_URL:-}
AUX_FILE_DSN: file:/var/lib/feishu/outbox_dev.db?_journal_mode=WAL&_busy_timeout=5000 EMBEDDING_MODEL: ${EMBEDDING_MODEL:-bge-m3}
EMBEDDING_API_KEY: ${EMBEDDING_API_KEY:-}
RERANKER_ENDPOINT_URL: ${RERANKER_ENDPOINT_URL:-}
RERANKER_MODEL: ${RERANKER_MODEL:-bge-reranker-v2-m3-free}
RERANKER_API_KEY: ${RERANKER_API_KEY:-}
ADMIN_USERNAME: ${ADMIN_USERNAME:-admin}
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-admin-secret-password-123}
AUTH_REGISTRATION_ENABLED: ${AUTH_REGISTRATION_ENABLED:-true}
AUTH_REQUIRE_APPROVAL: ${AUTH_REQUIRE_APPROVAL:-false}
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-}
ALLOW_IN_MEMORY: ${ALLOW_IN_MEMORY:-false}
CACHE_ENABLED: ${CACHE_ENABLED:-true}
INTENT_V2_SHADOW: ${INTENT_V2_SHADOW:-false}
INTENT_V2_DECISION: ${INTENT_V2_DECISION:-false}
INTENT_V2_DECISION_TIER: ${INTENT_V2_DECISION_TIER:-none}
WORKFLOW_INTENTS_ENABLED: ${WORKFLOW_INTENTS_ENABLED:-true}
MULTI_INTENT_ENABLED: ${MULTI_INTENT_ENABLED:-true}
SEMANTIC_FALLBACK_ENABLED: ${SEMANTIC_FALLBACK_ENABLED:-false}
ports: ports:
- "${APP_PUBLISH_HOST:-127.0.0.1}:${HTTP_PORT:-8080}:${HTTP_PORT:-8080}" - "${APP_PUBLISH_HOST:-127.0.0.1}:${HTTP_PORT:-8080}:${HTTP_PORT:-8080}"
healthcheck: healthcheck:
+24 -5
View File
@@ -14,7 +14,7 @@ services:
command: ["postgres", "-c", "shared_buffers=512MB", "-c", "max_connections=300"] command: ["postgres", "-c", "shared_buffers=512MB", "-c", "max_connections=300"]
environment: environment:
POSTGRES_USER: ${SYSTEM_DB_USER:-app} POSTGRES_USER: ${SYSTEM_DB_USER:-app}
POSTGRES_PASSWORD: ${SYSTEM_DB_PASSWORD:-prod_highly_secure_db_pass} POSTGRES_PASSWORD: ${SYSTEM_DB_PASSWORD:?SYSTEM_DB_PASSWORD is required for main/production}
POSTGRES_DB: ${SYSTEM_DB_NAME:-agent_system_main} POSTGRES_DB: ${SYSTEM_DB_NAME:-agent_system_main}
ports: ports:
- "127.0.0.1:${SYSTEM_DB_PUBLISH_PORT:-5435}:5432" - "127.0.0.1:${SYSTEM_DB_PUBLISH_PORT:-5435}:5432"
@@ -60,7 +60,6 @@ services:
MINIMAX_API_TYPE: ${MINIMAX_API_TYPE:-openai-compatible} MINIMAX_API_TYPE: ${MINIMAX_API_TYPE:-openai-compatible}
MINIMAX_PROVIDER: minimax MINIMAX_PROVIDER: minimax
TAVILY_API_KEYS: ${TAVILY_API_KEYS:-} TAVILY_API_KEYS: ${TAVILY_API_KEYS:-}
TAVILY_API_KEY: ${TAVILY_API_KEY:-}
RUNNER_AUTH_TOKEN: ${RUNNER_AUTH_TOKEN:?RUNNER_AUTH_TOKEN is required for main/production} RUNNER_AUTH_TOKEN: ${RUNNER_AUTH_TOKEN:?RUNNER_AUTH_TOKEN is required for main/production}
ports: ports:
- "127.0.0.1:${PI_AGENT_RUNNER_PORT:-18867}:18867" - "127.0.0.1:${PI_AGENT_RUNNER_PORT:-18867}:18867"
@@ -92,19 +91,39 @@ services:
HTTP_PORT: "${HTTP_PORT:-8000}" HTTP_PORT: "${HTTP_PORT:-8000}"
HTTP_HOST: "0.0.0.0" HTTP_HOST: "0.0.0.0"
AGENT_BACKEND: ${AGENT_BACKEND:-pi-agent} AGENT_BACKEND: ${AGENT_BACKEND:-pi-agent}
AGENT_TIMEOUT: ${AGENT_TIMEOUT:-30m}
MAX_CONCURRENT_AGENTS: ${MAX_CONCURRENT_AGENTS:-8}
DATA_DIR: /app/data DATA_DIR: /app/data
# 独占生产业务与对话库 # 独占生产业务与对话库
APP_DB_DSN: postgres://${SYSTEM_DB_USER:-app}:${SYSTEM_DB_PASSWORD:-prod_highly_secure_db_pass}@system-db:5432/${SYSTEM_DB_NAME:-agent_system_main}?sslmode=disable APP_DB_DSN: postgres://${SYSTEM_DB_USER:-app}:${SYSTEM_DB_PASSWORD:?SYSTEM_DB_PASSWORD is required for main/production}@system-db:5432/${SYSTEM_DB_NAME:-agent_system_main}?sslmode=disable
# 共享知识库 # 共享知识库
POSTGRES_SHARED_KB_DSN: postgres://${KB_DB_USER:-regkb}:${KB_DB_PASSWORD:-shared_kb_secret_pass}@shuimu-kb-shared:5432/${KB_DB_NAME:-regulations}?sslmode=disable POSTGRES_SHARED_KB_DSN: postgres://${KB_DB_USER:-regkb}:${KB_DB_PASSWORD:?KB_DB_PASSWORD is required for main/production}@shuimu-kb-shared:5432/${KB_DB_NAME:-regulations}?sslmode=disable
CURSOR_RUNNER_URL: http://cursor-agent:18767 CURSOR_RUNNER_URL: http://cursor-agent:18767
PI_AGENT_RUNNER_URL: http://pi-agent:18867 PI_AGENT_RUNNER_URL: http://pi-agent:18867
PI_AGENT_RUNNER_AUTH_TOKEN: ${RUNNER_AUTH_TOKEN:?RUNNER_AUTH_TOKEN is required for main/production} PI_AGENT_RUNNER_AUTH_TOKEN: ${RUNNER_AUTH_TOKEN:?RUNNER_AUTH_TOKEN is required for main/production}
PI_AGENT_PROVIDER: ${PI_AGENT_PROVIDER:-minimax}
PI_AGENT_MODEL: ${PI_AGENT_MODEL:-${MINIMAX_MODEL_ID:-MiniMax-M3}}
RUNNER_AUTH_TOKEN: ${RUNNER_AUTH_TOKEN:?RUNNER_AUTH_TOKEN is required for main/production} RUNNER_AUTH_TOKEN: ${RUNNER_AUTH_TOKEN:?RUNNER_AUTH_TOKEN is required for main/production}
KB_DB_PASSWORD: ${KB_DB_PASSWORD:?KB_DB_PASSWORD is required for main/production} KB_DB_PASSWORD: ${KB_DB_PASSWORD:?KB_DB_PASSWORD is required for main/production}
ADMIN_USERNAME: ${ADMIN_USERNAME:-admin}
ADMIN_PASSWORD: ${ADMIN_PASSWORD:?ADMIN_PASSWORD is required for main/production} ADMIN_PASSWORD: ${ADMIN_PASSWORD:?ADMIN_PASSWORD is required for main/production}
AUTH_REGISTRATION_ENABLED: ${AUTH_REGISTRATION_ENABLED:-true}
AUTH_REQUIRE_APPROVAL: ${AUTH_REQUIRE_APPROVAL:-false}
EMBEDDING_ENDPOINT_URL: ${EMBEDDING_ENDPOINT_URL:-}
EMBEDDING_MODEL: ${EMBEDDING_MODEL:-bge-m3}
EMBEDDING_API_KEY: ${EMBEDDING_API_KEY:-}
RERANKER_ENDPOINT_URL: ${RERANKER_ENDPOINT_URL:-}
RERANKER_MODEL: ${RERANKER_MODEL:-bge-reranker-v2-m3-free}
RERANKER_API_KEY: ${RERANKER_API_KEY:-}
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-}
ALLOW_IN_MEMORY: "false" ALLOW_IN_MEMORY: "false"
AUX_FILE_DSN: file:/var/lib/feishu/outbox_main.db?_journal_mode=WAL&_busy_timeout=5000 CACHE_ENABLED: ${CACHE_ENABLED:-true}
INTENT_V2_SHADOW: ${INTENT_V2_SHADOW:-false}
INTENT_V2_DECISION: ${INTENT_V2_DECISION:-false}
INTENT_V2_DECISION_TIER: ${INTENT_V2_DECISION_TIER:-none}
WORKFLOW_INTENTS_ENABLED: ${WORKFLOW_INTENTS_ENABLED:-true}
MULTI_INTENT_ENABLED: ${MULTI_INTENT_ENABLED:-true}
SEMANTIC_FALLBACK_ENABLED: ${SEMANTIC_FALLBACK_ENABLED:-false}
ports: ports:
- "${APP_PUBLISH_HOST:-0.0.0.0}:${HTTP_PORT:-8000}:${HTTP_PORT:-8000}" - "${APP_PUBLISH_HOST:-0.0.0.0}:${HTTP_PORT:-8000}:${HTTP_PORT:-8000}"
healthcheck: healthcheck:
+2 -2
View File
@@ -10,7 +10,7 @@ services:
container_name: shuimu-kb-shared container_name: shuimu-kb-shared
environment: environment:
POSTGRES_USER: ${KB_DB_USER:-regkb} POSTGRES_USER: ${KB_DB_USER:-regkb}
POSTGRES_PASSWORD: ${KB_DB_PASSWORD:-shared_kb_secret_pass} POSTGRES_PASSWORD: ${KB_DB_PASSWORD:?KB_DB_PASSWORD is required}
POSTGRES_DB: ${KB_DB_NAME:-regulations} POSTGRES_DB: ${KB_DB_NAME:-regulations}
ports: ports:
- "127.0.0.1:${KB_PUBLISH_PORT:-5432}:5432" - "127.0.0.1:${KB_PUBLISH_PORT:-5432}:5432"
@@ -29,7 +29,7 @@ services:
networks: networks:
shared-net: shared-net:
name: chat-agent-shared-net name: chat-agent-shared-net
driver: bridge external: true
volumes: volumes:
shared-kb-pgdata: shared-kb-pgdata:
+24 -4
View File
@@ -60,7 +60,6 @@ services:
MINIMAX_API_TYPE: ${MINIMAX_API_TYPE:-openai-compatible} MINIMAX_API_TYPE: ${MINIMAX_API_TYPE:-openai-compatible}
MINIMAX_PROVIDER: minimax MINIMAX_PROVIDER: minimax
TAVILY_API_KEYS: ${TAVILY_API_KEYS:-} TAVILY_API_KEYS: ${TAVILY_API_KEYS:-}
TAVILY_API_KEY: ${TAVILY_API_KEY:-}
RUNNER_AUTH_TOKEN: ${RUNNER_AUTH_TOKEN:-} RUNNER_AUTH_TOKEN: ${RUNNER_AUTH_TOKEN:-}
ports: ports:
- "127.0.0.1:${PI_AGENT_RUNNER_PORT:-18866}:18866" - "127.0.0.1:${PI_AGENT_RUNNER_PORT:-18866}:18866"
@@ -92,17 +91,38 @@ services:
HTTP_PORT: "${HTTP_PORT:-8081}" HTTP_PORT: "${HTTP_PORT:-8081}"
HTTP_HOST: "0.0.0.0" HTTP_HOST: "0.0.0.0"
AGENT_BACKEND: ${AGENT_BACKEND:-pi-agent} AGENT_BACKEND: ${AGENT_BACKEND:-pi-agent}
AGENT_TIMEOUT: ${AGENT_TIMEOUT:-30m}
MAX_CONCURRENT_AGENTS: ${MAX_CONCURRENT_AGENTS:-8}
DATA_DIR: /app/data DATA_DIR: /app/data
# 独占测试业务与对话库 # 独占测试业务与对话库
APP_DB_DSN: postgres://${SYSTEM_DB_USER:-app}:${SYSTEM_DB_PASSWORD:-test_secret_db_pass}@system-db:5432/${SYSTEM_DB_NAME:-agent_system_test}?sslmode=disable APP_DB_DSN: postgres://${SYSTEM_DB_USER:-app}:${SYSTEM_DB_PASSWORD:-test_secret_db_pass}@system-db:5432/${SYSTEM_DB_NAME:-agent_system_test}?sslmode=disable
# 共享知识库 # 共享知识库
POSTGRES_SHARED_KB_DSN: postgres://${KB_DB_USER:-regkb}:${KB_DB_PASSWORD:-shared_kb_secret_pass}@shuimu-kb-shared:5432/${KB_DB_NAME:-regulations}?sslmode=disable POSTGRES_SHARED_KB_DSN: postgres://${KB_DB_USER:-regkb}:${KB_DB_PASSWORD:?KB_DB_PASSWORD is required}@shuimu-kb-shared:5432/${KB_DB_NAME:-regulations}?sslmode=disable
CURSOR_RUNNER_URL: http://cursor-agent:18766 CURSOR_RUNNER_URL: http://cursor-agent:18766
PI_AGENT_RUNNER_URL: http://pi-agent:18866 PI_AGENT_RUNNER_URL: http://pi-agent:18866
PI_AGENT_RUNNER_AUTH_TOKEN: ${RUNNER_AUTH_TOKEN:-} PI_AGENT_RUNNER_AUTH_TOKEN: ${RUNNER_AUTH_TOKEN:-}
PI_AGENT_PROVIDER: ${PI_AGENT_PROVIDER:-minimax}
PI_AGENT_MODEL: ${PI_AGENT_MODEL:-${MINIMAX_MODEL_ID:-MiniMax-M3}}
RUNNER_AUTH_TOKEN: ${RUNNER_AUTH_TOKEN:-} RUNNER_AUTH_TOKEN: ${RUNNER_AUTH_TOKEN:-}
ALLOW_IN_MEMORY: "false" EMBEDDING_ENDPOINT_URL: ${EMBEDDING_ENDPOINT_URL:-}
AUX_FILE_DSN: file:/var/lib/feishu/outbox_test.db?_journal_mode=WAL&_busy_timeout=5000 EMBEDDING_MODEL: ${EMBEDDING_MODEL:-bge-m3}
EMBEDDING_API_KEY: ${EMBEDDING_API_KEY:-}
RERANKER_ENDPOINT_URL: ${RERANKER_ENDPOINT_URL:-}
RERANKER_MODEL: ${RERANKER_MODEL:-bge-reranker-v2-m3-free}
RERANKER_API_KEY: ${RERANKER_API_KEY:-}
ADMIN_USERNAME: ${ADMIN_USERNAME:-admin}
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-admin-secret-password-123}
AUTH_REGISTRATION_ENABLED: ${AUTH_REGISTRATION_ENABLED:-true}
AUTH_REQUIRE_APPROVAL: ${AUTH_REQUIRE_APPROVAL:-false}
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-}
ALLOW_IN_MEMORY: ${ALLOW_IN_MEMORY:-false}
CACHE_ENABLED: ${CACHE_ENABLED:-true}
INTENT_V2_SHADOW: ${INTENT_V2_SHADOW:-false}
INTENT_V2_DECISION: ${INTENT_V2_DECISION:-false}
INTENT_V2_DECISION_TIER: ${INTENT_V2_DECISION_TIER:-none}
WORKFLOW_INTENTS_ENABLED: ${WORKFLOW_INTENTS_ENABLED:-true}
MULTI_INTENT_ENABLED: ${MULTI_INTENT_ENABLED:-true}
SEMANTIC_FALLBACK_ENABLED: ${SEMANTIC_FALLBACK_ENABLED:-false}
ports: ports:
- "${APP_PUBLISH_HOST:-0.0.0.0}:${HTTP_PORT:-8081}:${HTTP_PORT:-8081}" - "${APP_PUBLISH_HOST:-0.0.0.0}:${HTTP_PORT:-8081}:${HTTP_PORT:-8081}"
healthcheck: healthcheck:
+21 -2
View File
@@ -252,9 +252,29 @@ func Load(path string) (*Config, error) {
if v := os.Getenv("AGENT_BACKEND"); v != "" { if v := os.Getenv("AGENT_BACKEND"); v != "" {
cfg.AgentBackend = v cfg.AgentBackend = v
} }
if v := os.Getenv("AGENT_TIMEOUT"); v != "" {
if d, err := time.ParseDuration(v); err == nil {
cfg.AgentTimeout = d
}
}
if v := os.Getenv("CURSOR_API_KEY"); v != "" {
cfg.CursorAPIKey = v
}
if v := os.Getenv("CURSOR_MODEL"); v != "" {
cfg.CursorModel = v
}
if v := os.Getenv("CURSOR_BIN"); v != "" {
cfg.CursorBin = v
}
if v := os.Getenv("CURSOR_BRIDGE_BIN"); v != "" {
cfg.CursorBridgeBin = v
}
if v := os.Getenv("PI_AGENT_BIN"); v != "" { if v := os.Getenv("PI_AGENT_BIN"); v != "" {
cfg.PiAgentBin = v cfg.PiAgentBin = v
} }
if v := os.Getenv("PI_AGENT_BRIDGE_BIN"); v != "" {
cfg.PiAgentBridgeBin = v
}
if v := os.Getenv("PI_AGENT_PROVIDER"); v != "" { if v := os.Getenv("PI_AGENT_PROVIDER"); v != "" {
cfg.PiAgentProvider = v cfg.PiAgentProvider = v
} }
@@ -398,7 +418,7 @@ func validateProductionSecrets(cfg *Config) error {
"CONTEXT_DB_PASSWORD": cfg.ContextDbPassword, "CONTEXT_DB_PASSWORD": cfg.ContextDbPassword,
"ADMIN_PASSWORD": cfg.AdminPassword, "ADMIN_PASSWORD": cfg.AdminPassword,
} }
defaults := []string{"shared_kb_secret_pass", "dev_secret_db_pass", "admin-secret-password-123"} defaults := []string{"shared_kb_secret_pass", "dev_secret_db_pass", "prod_highly_secure_db_pass", "admin-secret-password-123"}
for envKey, val := range weak { for envKey, val := range weak {
if strings.TrimSpace(val) == "" { if strings.TrimSpace(val) == "" {
return fmt.Errorf("%s must be set in %s environment", envKey, cfg.Env) return fmt.Errorf("%s must be set in %s environment", envKey, cfg.Env)
@@ -482,4 +502,3 @@ func (c *Config) KbDSN() string {
func (c *Config) ContextDSN() string { func (c *Config) ContextDSN() string {
return "postgres://" + c.ContextDbUser + ":" + c.ContextDbPassword + "@" + c.ContextDbHost + ":" + strconv.Itoa(c.ContextDbPort) + "/" + c.ContextDbName + "?sslmode=disable" return "postgres://" + c.ContextDbUser + ":" + c.ContextDbPassword + "@" + c.ContextDbHost + ":" + strconv.Itoa(c.ContextDbPort) + "/" + c.ContextDbName + "?sslmode=disable"
} }
+41
View File
@@ -69,6 +69,35 @@ func TestConfigLoadDSNOverrides(t *testing.T) {
} }
} }
func TestConfigLoadRuntimeEnvironmentOverrides(t *testing.T) {
t.Setenv("ENV", "dev")
t.Setenv("AGENT_TIMEOUT", "47m")
t.Setenv("CURSOR_API_KEY", "cursor-test-key")
t.Setenv("CURSOR_MODEL", "cursor-test-model")
t.Setenv("CURSOR_BIN", "/test/cursor")
t.Setenv("CURSOR_BRIDGE_BIN", "/test/cursor-bridge.mjs")
t.Setenv("PI_AGENT_BRIDGE_BIN", "/test/pi-bridge.mjs")
t.Setenv("EMBEDDING_MODEL", "embedding-test-model")
t.Setenv("RERANKER_MODEL", "reranker-test-model")
cfg, err := config.Load("/dev/null")
if err != nil {
t.Fatalf("unexpected error loading config: %v", err)
}
if cfg.AgentTimeout != 47*time.Minute {
t.Fatalf("expected environment AgentTimeout 47m, got %v", cfg.AgentTimeout)
}
if cfg.CursorAPIKey != "cursor-test-key" || cfg.CursorModel != "cursor-test-model" || cfg.CursorBin != "/test/cursor" || cfg.CursorBridgeBin != "/test/cursor-bridge.mjs" {
t.Fatalf("cursor environment overrides were not applied")
}
if cfg.PiAgentBridgeBin != "/test/pi-bridge.mjs" {
t.Fatalf("pi bridge environment override was not applied")
}
if cfg.EmbeddingModel != "embedding-test-model" || cfg.RerankerModel != "reranker-test-model" {
t.Fatalf("retrieval model environment overrides were not applied")
}
}
func TestConfigLoadCORSDefaultsForDev(t *testing.T) { func TestConfigLoadCORSDefaultsForDev(t *testing.T) {
cfg, err := config.Load("/dev/null") cfg, err := config.Load("/dev/null")
if err != nil { if err != nil {
@@ -103,3 +132,15 @@ func TestConfigLoadRejectsWeakSecretsOutsideDev(t *testing.T) {
t.Fatal("expected config load to fail for missing secrets in main env") t.Fatal("expected config load to fail for missing secrets in main env")
} }
} }
func TestConfigLoadRejectsProductionDatabasePlaceholder(t *testing.T) {
t.Setenv("ENV", "prod")
t.Setenv("KB_DB_PASSWORD", "kb-prod-secret-xyz")
t.Setenv("CONTEXT_DB_PASSWORD", "prod_highly_secure_db_pass")
t.Setenv("ADMIN_PASSWORD", "admin-prod-secret-xyz")
_, err := config.Load("/dev/null")
if err == nil {
t.Fatal("expected config load to reject the documented production database placeholder")
}
}
+13 -12
View File
@@ -90,44 +90,45 @@ run_compose() {
prepare_env_file "${target}" prepare_env_file "${target}"
fi fi
local env_arg="" local env_args=()
# 根 .env 提供跨环境共用的模型、检索与认证配置;环境文件仅覆盖端口、
# 数据库和其他环境差异。后声明的 env-file 优先级更高。
if [[ -f ".env" ]]; then
env_args+=(--env-file ".env")
fi
if [[ -f "${env_file}" ]]; then if [[ -f "${env_file}" ]]; then
env_arg="--env-file ${env_file}" env_args+=(--env-file "${env_file}")
fi fi
case "${act}" in case "${act}" in
start) start)
if [[ "${target}" != "shared" ]]; then
ensure_shared_network ensure_shared_network
fi
echo "==> [${target}] 启动服务 (${compose_file})..." echo "==> [${target}] 启动服务 (${compose_file})..."
docker compose -f "${compose_file}" ${env_arg} up -d docker compose -f "${compose_file}" "${env_args[@]}" up -d
echo "==> [${target}] 服务已启动" echo "==> [${target}] 服务已启动"
;; ;;
stop) stop)
echo "==> [${target}] 停止服务..." echo "==> [${target}] 停止服务..."
docker compose -f "${compose_file}" ${env_arg} stop docker compose -f "${compose_file}" "${env_args[@]}" stop
;; ;;
restart) restart)
if [[ "${target}" != "shared" ]]; then
ensure_shared_network ensure_shared_network
fi
echo "==> [${target}] 重启服务..." echo "==> [${target}] 重启服务..."
docker compose -f "${compose_file}" ${env_arg} restart docker compose -f "${compose_file}" "${env_args[@]}" restart
;; ;;
status|ps) status|ps)
echo "------------------------------------------------------------" echo "------------------------------------------------------------"
echo "环境: [${target}] (编排: ${compose_file})" echo "环境: [${target}] (编排: ${compose_file})"
echo "------------------------------------------------------------" echo "------------------------------------------------------------"
docker compose -f "${compose_file}" ${env_arg} ps docker compose -f "${compose_file}" "${env_args[@]}" ps
echo "" echo ""
;; ;;
logs) logs)
docker compose -f "${compose_file}" ${env_arg} logs -f --tail=100 docker compose -f "${compose_file}" "${env_args[@]}" logs -f --tail=100
;; ;;
down) down)
echo "==> [${target}] 清理并下线容器..." echo "==> [${target}] 清理并下线容器..."
docker compose -f "${compose_file}" ${env_arg} down docker compose -f "${compose_file}" "${env_args[@]}" down
;; ;;
*) *)
echo "[错误] 未知操作: ${act}" echo "[错误] 未知操作: ${act}"