2026-08-23 22:35:59 +08:00
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
"""全局配置:端口、数据目录、令牌有效期、认证开关。
|
|
|
|
|
|
|
|
|
|
|
|
所有路径/开关均可通过环境变量覆盖,便于部署和测试:
|
|
|
|
|
|
|
|
|
|
|
|
PINEAGENTS_DEMO_HOST / PINEAGENTS_DEMO_PORT 监听地址(默认 127.0.0.1:8090)
|
|
|
|
|
|
PINEAGENTS_DEMO_DATA_DIR JSON 数据目录(默认 <项目根>/data)
|
|
|
|
|
|
PINEAGENTS_DEMO_AUTH_ENABLED 认证开关(默认 true)
|
|
|
|
|
|
"""
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
2026-08-25 12:08:14 +08:00
|
|
|
|
# 启动第一步即加载项目根 .env(含 PINEAGENTS_INTERNAL_TOKEN / DATABASE_URL 等),
|
|
|
|
|
|
# 否则后续 os.environ.get 读到空串(.env 后续才会被 app.park/app.training 各自加载)。
|
|
|
|
|
|
# 必须在下方任何 os.environ.get 之前执行。
|
|
|
|
|
|
try: # python-dotenv 为可选依赖;缺失时仍可用系统环境变量
|
|
|
|
|
|
from dotenv import load_dotenv
|
|
|
|
|
|
load_dotenv(Path(__file__).resolve().parent.parent / ".env")
|
|
|
|
|
|
except Exception: # noqa: BLE001
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
2026-08-23 22:35:59 +08:00
|
|
|
|
# 项目根目录 = 本文件的上上级(app/config.py -> <根>)
|
|
|
|
|
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
2026-08-24 00:42:35 +08:00
|
|
|
|
# 数据目录(serverdata/ 统一承载所有资源:data/logs/files/keys/prompts/uploads)
|
2026-08-23 22:35:59 +08:00
|
|
|
|
# ---------------------------------------------------------------------------
|
2026-08-24 00:42:35 +08:00
|
|
|
|
SERVERDATA_DIR = Path(
|
|
|
|
|
|
os.environ.get("PINEAGENTS_SERVERDATA_DIR", str(BASE_DIR / "serverdata")),
|
2026-08-23 22:35:59 +08:00
|
|
|
|
).resolve()
|
|
|
|
|
|
|
2026-08-24 00:42:35 +08:00
|
|
|
|
# 数据子目录(缺失自动创建,start.sh 亦负责初始化)
|
|
|
|
|
|
DATA_DIR = SERVERDATA_DIR / "data"
|
|
|
|
|
|
LOG_DIR = SERVERDATA_DIR / "logs"
|
|
|
|
|
|
FILES_DIR = SERVERDATA_DIR / "files"
|
|
|
|
|
|
KEYS_DIR = SERVERDATA_DIR / "keys"
|
|
|
|
|
|
PROMPTS_DIR = SERVERDATA_DIR / "prompts"
|
|
|
|
|
|
UPLOADS_DIR = SERVERDATA_DIR / "uploads"
|
|
|
|
|
|
|
|
|
|
|
|
for _d in (DATA_DIR, LOG_DIR, FILES_DIR, KEYS_DIR, PROMPTS_DIR, UPLOADS_DIR):
|
|
|
|
|
|
_d.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
|
2026-08-23 23:52:58 +08:00
|
|
|
|
# 数据库连接(异步)。开发默认 SQLite+aiosqlite;生产用 MySQL+asyncmy(环境变量覆盖)。
|
|
|
|
|
|
# 示例 MySQL:PINEAGENTS_DEMO_DATABASE_URL=mysql+asyncmy://user:pass@127.0.0.1:3306/opc?charset=utf8mb4
|
2026-08-23 22:35:59 +08:00
|
|
|
|
DATABASE_URL = os.environ.get(
|
|
|
|
|
|
"PINEAGENTS_DEMO_DATABASE_URL",
|
2026-08-23 23:52:58 +08:00
|
|
|
|
f"sqlite+aiosqlite:///{DATA_DIR / 'app.db'}",
|
2026-08-23 22:35:59 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-08-23 23:52:58 +08:00
|
|
|
|
# Redis(缓存/会话/限流/分布式锁),空则降级内存。
|
|
|
|
|
|
REDIS_URL = os.environ.get("REDIS_URL", "redis://127.0.0.1:6379/0")
|
|
|
|
|
|
|
2026-08-24 00:42:35 +08:00
|
|
|
|
# OSS 对象存储(aioboto3,兼容阿里云 OSS / MinIO);空则文件落 serverdata/uploads。
|
2026-08-23 23:52:58 +08:00
|
|
|
|
OSS_ENDPOINT = os.environ.get("OSS_ENDPOINT", "")
|
|
|
|
|
|
OSS_ACCESS_KEY = os.environ.get("OSS_ACCESS_KEY", "")
|
|
|
|
|
|
OSS_SECRET_KEY = os.environ.get("OSS_SECRET_KEY", "")
|
|
|
|
|
|
OSS_BUCKET = os.environ.get("OSS_BUCKET", "opc-files")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-23 22:35:59 +08:00
|
|
|
|
USERS_FILE = DATA_DIR / "users.json"
|
|
|
|
|
|
TOKENS_FILE = DATA_DIR / "tokens.json"
|
|
|
|
|
|
AGENTS_FILE = DATA_DIR / "agents.json"
|
|
|
|
|
|
|
|
|
|
|
|
# 智能体初始化 md 模板(镜像本地 md_files 结构;qa 已统一为 PineAgents 品牌)。
|
2026-08-24 00:42:35 +08:00
|
|
|
|
# 目录:<PROMPTS_DIR>/<lang>/*.md(通用)、<PROMPTS_DIR>/qa/<lang>/*.md、
|
|
|
|
|
|
# <PROMPTS_DIR>/local/<lang>/*.md
|
|
|
|
|
|
TEMPLATES_DIR = PROMPTS_DIR
|
2026-08-23 22:35:59 +08:00
|
|
|
|
|
|
|
|
|
|
# 首次启动时用于初始化 data/ 的种子数据(提交到仓库,数据可读)。
|
|
|
|
|
|
SEED_DIR = BASE_DIR / "data" / "seed"
|
|
|
|
|
|
SEED_USERS_FILE = SEED_DIR / "demo_users.json"
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# 服务监听
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
HOST = os.environ.get("PINEAGENTS_DEMO_HOST", "127.0.0.1")
|
|
|
|
|
|
PORT = int(os.environ.get("PINEAGENTS_DEMO_PORT", "8090"))
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# 认证与令牌
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# 演示服务默认开启认证(PineAgents 主后端把 /auth/* 转发到本服务)。
|
|
|
|
|
|
AUTH_ENABLED = (
|
|
|
|
|
|
os.environ.get("PINEAGENTS_DEMO_AUTH_ENABLED", "true").strip().lower()
|
|
|
|
|
|
in ("true", "1", "yes")
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 令牌有效期(秒):默认 7 天;-1/0 表示永久(按 100 年封顶)。
|
|
|
|
|
|
DEFAULT_TOKEN_EXPIRY_SECONDS = 7 * 24 * 3600
|
|
|
|
|
|
PERMANENT_TOKEN_SECONDS = 100 * 365 * 24 * 3600
|
|
|
|
|
|
TOKEN_EXPIRY_MAX = PERMANENT_TOKEN_SECONDS
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# JWT
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
JWT_SECRET = os.environ.get(
|
|
|
|
|
|
"PINEAGENTS_DEMO_JWT_SECRET",
|
|
|
|
|
|
"dev-insecure-change-me-0123456789abcdef",
|
|
|
|
|
|
)
|
|
|
|
|
|
JWT_ALGORITHM = os.environ.get("PINEAGENTS_DEMO_JWT_ALGORITHM", "HS256")
|
|
|
|
|
|
JWT_ACCESS_EXPIRY_SECONDS = DEFAULT_TOKEN_EXPIRY_SECONDS
|
|
|
|
|
|
|
2026-08-24 14:16:34 +08:00
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# 算力引擎(compute-engine / new-api)管理接入(loopback,仅服务)
|
|
|
|
|
|
# 模型调用与额度/令牌/流水均经此引擎计量;前端不直连引擎。
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
COMPUTE_BASE_URL = os.environ.get(
|
|
|
|
|
|
"PINEAGENTS_COMPUTE_BASE_URL", "http://127.0.0.1:3000",
|
|
|
|
|
|
)
|
2026-08-25 12:08:14 +08:00
|
|
|
|
# 受信任内部令牌:server-core 作为唯一入口,无权限限制访问引擎管理/root 端点。
|
|
|
|
|
|
# 与引擎侧 PINEAGENTS_INTERNAL_TOKEN 保持一致;回退兼容旧名 PINEAGENTS_COMPUTE_ADMIN_TOKEN。
|
|
|
|
|
|
COMPUTE_ADMIN_TOKEN = os.environ.get("PINEAGENTS_INTERNAL_TOKEN") or os.environ.get("PINEAGENTS_COMPUTE_ADMIN_TOKEN", "")
|
|
|
|
|
|
# /v1 模型中继转发(relay.py)用的引擎消费令牌:需是引擎 /v1 能识别的真实用户/渠道令牌,
|
|
|
|
|
|
# 与聚合 root 的 COMPUTE_ADMIN_TOKEN 区分开(后者仅用于管理/root 端点)。回退 COMPUTE_ADMIN_TOKEN。
|
|
|
|
|
|
COMPUTE_RELAY_TOKEN = os.environ.get("PINEAGENTS_COMPUTE_RELAY_TOKEN", "")
|
2026-08-25 14:08:26 +08:00
|
|
|
|
# 用户端算力中心「基础 API 地址」:对外公网地址(如 https://opc.pinesound.cn),供用户其它工具接入。
|
|
|
|
|
|
# 未设置时回落请求的 base_url。经代理时 request.base_url 可能为内部地址,故用此配置给公网地址。
|
|
|
|
|
|
COMPUTE_PUBLIC_BASE = os.environ.get("PINEAGENTS_COMPUTE_PUBLIC_BASE", "").rstrip("/")
|
2026-08-24 14:16:34 +08:00
|
|
|
|
COMPUTE_TIMEOUT = float(os.environ.get("PINEAGENTS_COMPUTE_TIMEOUT", "10"))
|
|
|
|
|
|
COMPUTE_RETRIES = int(os.environ.get("PINEAGENTS_COMPUTE_RETRIES", "2"))
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# 短信验证码(统一登录)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
# stub = 仅打印验证码不真正发送(默认,便于联调);生产接阿里云/腾讯云短信。
|
|
|
|
|
|
SMS_PROVIDER = os.environ.get("PINEAGENTS_SMS_PROVIDER", "stub").strip().lower()
|
|
|
|
|
|
SMS_CODE_TTL_SECONDS = int(os.environ.get("PINEAGENTS_SMS_CODE_TTL_SECONDS", "300"))
|
|
|
|
|
|
SMS_RATE_LIMIT = int(os.environ.get("PINEAGENTS_SMS_RATE_LIMIT", "5"))
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
2026-08-25 22:47:27 +08:00
|
|
|
|
# 微信登录(小程序 code2session + 开放平台网站应用扫码登录)
|
|
|
|
|
|
# 未配置 appid/secret 时走「测试直通」:扫码登录把 code 当 openid 返回,便于无真实凭据联调。
|
|
|
|
|
|
# WECHAT_APPID/SECRET 为小程序;WECHAT_OPEN_APPID/SECRET 为开放平台「网站应用」(扫码登录),缺省回退前者。
|
2026-08-24 14:16:34 +08:00
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
WECHAT_APPID = os.environ.get("PINEAGENTS_WECHAT_APPID", "")
|
|
|
|
|
|
WECHAT_SECRET = os.environ.get("PINEAGENTS_WECHAT_SECRET", "")
|
2026-08-25 22:47:27 +08:00
|
|
|
|
WECHAT_OPEN_APPID = os.environ.get("PINEAGENTS_WECHAT_OPEN_APPID", "") or WECHAT_APPID
|
|
|
|
|
|
WECHAT_OPEN_SECRET = os.environ.get("PINEAGENTS_WECHAT_OPEN_SECRET", "") or WECHAT_SECRET
|
|
|
|
|
|
# 扫码登录授权后的回跳地址(须在开放平台后台配置为该 URL):默认服务端 /auth/wx-callback。
|
|
|
|
|
|
# 未设置时由前端 request.base_url 推导。
|
|
|
|
|
|
WECHAT_QR_REDIRECT_URI = os.environ.get("PINEAGENTS_WECHAT_QR_REDIRECT_URI", "")
|
|
|
|
|
|
# 扫码登录开关(true 才返回可用的 qr_start;false 时前端隐藏「微信扫码」入口)
|
|
|
|
|
|
AUTH_WECHAT_QR = os.environ.get("PINEAGENTS_WECHAT_QR", "true").strip().lower() in ("true", "1", "yes")
|
2026-08-25 16:25:50 +08:00
|
|
|
|
# 本期统一登录仅两模式(账号密码/手机号验证码);微信扫码预留后期,未开放(默认 False)。
|
|
|
|
|
|
AUTH_WECHAT_LOGIN = os.environ.get("PINEAGENTS_WECHAT_LOGIN", "false").strip().lower() in ("true", "1", "yes")
|
2026-08-24 14:16:34 +08:00
|
|
|
|
|
2026-08-23 22:35:59 +08:00
|
|
|
|
|
|
|
|
|
|
def resolve_token_expiry(expires_in: int | None) -> int:
|
|
|
|
|
|
"""把前端传来的 ``expires_in`` 归一化为秒数。
|
|
|
|
|
|
|
|
|
|
|
|
- None → 默认 7 天
|
|
|
|
|
|
- <= 0(含 -1/0)→ 永久(100 年)
|
|
|
|
|
|
- 其他 → 按最大值封顶
|
|
|
|
|
|
"""
|
|
|
|
|
|
if expires_in is None:
|
|
|
|
|
|
return DEFAULT_TOKEN_EXPIRY_SECONDS
|
|
|
|
|
|
if expires_in <= 0:
|
|
|
|
|
|
return PERMANENT_TOKEN_SECONDS
|
|
|
|
|
|
return min(expires_in, TOKEN_EXPIRY_MAX)
|