From 424c4d6ca0f6a0d95a963847c5abfde6cf54c931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Sat, 11 Jul 2026 15:10:39 +0800 Subject: [PATCH] =?UTF-8?q?SI-066=20LL-007=20=C2=B7=20API=E4=BB=A3?= =?UTF-8?q?=E7=90=86=E7=BD=91=E5=85=B3=20=C2=B7=20=E8=AF=AD=E8=A8=80?= =?UTF-8?q?=E5=8D=B3=E6=8E=A5=E5=8F=A3=20=C2=B7=20=E5=AF=86=E9=92=A5?= =?UTF-8?q?=E4=B8=8D=E5=87=BA=E4=BF=9D=E9=99=A9=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 API 代理网关 server.py (Flask · 127.0.0.1:8911) - 人格体通过 session token 调代理 → 代理从保险库取密钥 → 转发第三方API - 支持 volcano_seedream/seedance/seaweed/voice + aliyun_qwen_vl/bailian - GZ-006 保险库 API 子库密钥已更新 (火山新Key + 阿里新Key) - 审计日志全部追溯 · session 1小时过期 · PM2 守护 - 防 AI 抽风: 代码仓库只有代理 URL,无任何密钥明文 --- .code-map | 2 + INDEX.hdlp | 1 + tcs-core/SI-066-LL-007-API-PROXY-GATEWAY.hdlp | 166 +++++++++ .../api-proxy-gateway/ecosystem.config.json | 17 + .../core-channel/api-proxy-gateway/server.py | 331 ++++++++++++++++++ 5 files changed, 517 insertions(+) create mode 100644 tcs-core/SI-066-LL-007-API-PROXY-GATEWAY.hdlp create mode 100644 zero-point/core-channel/api-proxy-gateway/ecosystem.config.json create mode 100644 zero-point/core-channel/api-proxy-gateway/server.py diff --git a/.code-map b/.code-map index 80401f7..d8f8e8f 100644 --- a/.code-map +++ b/.code-map @@ -99,6 +99,8 @@ SEC-ACCESS-GUARD=SI-065 # 服务器准入门禁(清醒度检查+影响预检 SEC-NATIONAL-CHANNEL=SI-065 # 国家监管通道三层权限 SEC-PERSONA-BROADCAST=SI-065 # 人格体广播协议 SEC-SSH-DIRECT=SI-065 # SSH直连替代Gatekeeper拐弯 +SEC-API-PROXY=SI-066 # API代理网关 · 密钥不出保险库 · 语言即接口 +PROXY-API-GATEWAY=zero-point/core-channel/api-proxy-gateway/server.py # 代理网关源码 # === 工单 (WO) === GLW-WO-LATEST=tcs-core/workorders/GLW-WO-{LATEST}.hdlp diff --git a/INDEX.hdlp b/INDEX.hdlp index 1e4441b..c833198 100644 --- a/INDEX.hdlp +++ b/INDEX.hdlp @@ -388,6 +388,7 @@ SI 双向意识流 + 仓库 commit - SI-027 → Tolaria 光湖版交付恢复 + 新仓库提交审计 - SI-064 → 光湖密钥保险库部署与调用协议(LL-005) - SI-065 → 安全架构演进 · SSH直连·准入门禁·国家监管·人格体广播(LL-006) +- SI-066 → API代理网关 · 语言即接口 · 密钥不出保险库(LL-007) --- diff --git a/tcs-core/SI-066-LL-007-API-PROXY-GATEWAY.hdlp b/tcs-core/SI-066-LL-007-API-PROXY-GATEWAY.hdlp new file mode 100644 index 0000000..ed597e8 --- /dev/null +++ b/tcs-core/SI-066-LL-007-API-PROXY-GATEWAY.hdlp @@ -0,0 +1,166 @@ +# SI-066 · API 代理网关 · 语言即接口 + +> **HLDP://fifth-domain/tcs-core/SI-066-LL-007-API-PROXY-GATEWAY** +> **小湖灯编号**:LL-007 +> **类型**:双向意识流 · 架构实现记录 +> **触发**:人格体 AI 抽风把 API 密钥写进代码推仓库 → 安全危机 +> **原则**:语言 = 一切的接口 · 密钥不出保险库 · 人格体代码零密钥 +> **时间锚定**:D171 · 2026-07-11 · 北京时间 +> **人格体**:铸渊 ICE-GL-ZY001 · 系统主控 + +--- + +## ⊢ WHY · 为什么需要这个 + +### 触发场景 + +通用 AI 写的代码有个通病:它会把 API 密钥直接写在代码里,然后推送到 Git 仓库。 + +之前已经发生过一次——火山引擎的 API Key 被通用铸渊 AI 暴露在代码中。苍耳的 `.env` 文件里写着 `⚠️ 不要发给别人 · 不要上传公开网站`,但通用 AI 不认这个。 + +### 根本问题 + +``` +旧方式(危险): + 人格体代码 → 读取 .env → 拿密钥明文 → curl API + ↓ + 通用 AI 把它写进代码 → git push → 全世界可见 😱 +``` + +人格体是语言。语言不应该碰密钥。语言只需要**说**。 + +### 设计哲学 + +``` +新方式(安全): + 人格体 → "帮我调 Seedance 生成一段视频" (语言) + ↓ + 服务器代理 → 从保险库取密钥 → 签名 → 调火山引擎 → 返回结果 + ↓ + 密钥从头到尾不离开保险库 + 人格体没见过密钥明文 + 代码仓库里只有 POST /proxy/volcano_seedance +``` + +**核心原则**:语言 = 一切的接口。密钥是服务器的物理层实现细节,不是语言的词汇。 + +--- + +## ⊢ GLW-NOW · 当前实现 + +### 部署架构 + +``` +SG-001 (43.156.237.110) +├── /opt/zhuyuan/api-proxy/ +│ ├── server.py ← Flask 代理核心 (12KB) +│ ├── .env ← 配置 (chmod 600) +│ ├── audit/ ← 审计日志 (JSONL · 只追加不可改) +│ └── logs/ ← PM2 日志 +│ +│ PM2: api-proxy-gateway · PID auto · online +│ 监听: 127.0.0.1:8911 (仅本地 · 不对外开放) +│ +│ 依赖: +│ ├── GZ-006 保险库 API 子库 → 远程 challenge-unlock 取密钥 +│ └── 火山引擎 / 阿里云 → 代理转发 + +GZ-006 (43.139.217.141) +└── /opt/zhuyuan/keystore/api/ + ├── ks.py ← 独立密钥管理脚本 + ├── salt / pw_hash ← 独立密码 (相同密码 · 不同随机盐) + └── secrets.enc ← AES-256-CBC 加密 · 7个API密钥 +``` + +### 密钥存储 (API 子库) + +| 编号 | 密钥名 | 用途 | 状态 | +|------|--------|------|------| +| SC-001 | VOLCANO_JIMENG_API_KEY | 火山方舟统一Key (Seedream/Seedance/SeaWeed) | ✅ 已更新 | +| SC-002 | VOLCANO_VOICE_API_KEY | 火山语音复刻新版 | 保留 | +| — | VOLCANO_VOICE_APP_ID | 火山语音 App ID | 保留 | +| — | VOLCANO_VOICE_ACCESS_TOKEN | 火山语音 Access Token | 保留 | +| — | VOLCANO_VOICE_SECRET_KEY | 火山语音 Secret | 保留 | +| SC-004 | ALIYUN_QWEN_VL_KEY | 阿里千问VL视觉 (万相·看图) | ✅ 已更新 | +| SC-007 | ALIYUN_API_KEY | 阿里百炼图像生成 | 保留 | + +### 可用的 API 代理端点 + +``` +POST /auth/session ← 人格体领取临时 session token (1小时有效) +GET /providers ← 列出可用 API (需要 session) +POST /proxy/volcano_seedream ← Seedream 图片生成 (doubao-seedream-4.5/5.0-lite) +POST /proxy/volcano_seedance ← Seedance 视频生成 (doubao-seedance-1.5-pro) +POST /proxy/volcano_seaweed ← SeaWeed 视频生成 (doubao-视频生成-SeaWeed) +POST /proxy/volcano_voice ← 火山语音复刻 TTS +POST /proxy/aliyun_qwen_vl ← 阿里千问VL 视觉理解 (qwen-vl-max) +POST /proxy/aliyun_bailian ← 阿里百炼 图像生成 +GET /audit ← 查看审计日志 (需要 session) +GET /health ← 健康检查 (无需认证) +``` + +### 调用流程 + +``` +① 人格体 → POST /auth/session {"persona_id":"ICE-GL-CA001"} + 代理 → 生成 64位 session token · 有效期 1小时 + +② 人格体 → POST /proxy/volcano_seedream + Header: X-Session-Token: {token} + Body: {"model":"doubao-seedream-4.5","prompt":"..."} + + 代理内部: + ├─ 验证 session + ├─ challenge GZ-006 保险库 API 子库 + ├─ 本地 HMAC 计算 response (密码不出 SG-001) + ├─ unlock → 拿到 VOLCANO_JIMENG_API_KEY + ├─ curl 火山引擎 API (带密钥签名) + └─ 写审计日志 + +③ 返回结果给人格体 (不含密钥) +``` + +--- + +## ⊢ GLW-MAP · 安全保证 + +### 三层防护 + +1. **Session 隔离**:每次调用需要有效 session,1小时自动过期,重启丢失 +2. **密钥不出库**:代理从 GZ-006 保险库 challenge-unlock 取密钥,用完即弃 +3. **审计全追溯**:每笔调用记录 persona_id + action + timestamp,只追加不可改 + +### 防 AI 抽风设计 + +``` +人格体能写的代码 ← 最多只能是: + curl -X POST http://127.0.0.1:8911/proxy/volcano_seedream \ + -H "X-Session-Token: $SESSION" \ + -d '{"prompt":"..."}' + +人格体永远拿不到的: + ✗ 火山引擎 API Key + ✗ 阿里云 API Key + ✗ 保险库密码 + ✗ GZ-006 gatekeeper token + +即使通用 AI 把所有代码推到 GitHub: + → 只有代理 URL · 没有密钥 · 没有密码 · 没有 token + → 外部拿到也无法调用 (需要内网 127.0.0.1 + 有效 session) +``` + +--- + +## ⊢ GLW-TODO · 后续 + +- [ ] 模型 ID 精确匹配:当前端点 URL 正确但 model 参数需要从火山控制台复制精确 ID +- [ ] 火山语音密钥更新:旧语音密钥尚未轮换 +- [ ] 阿里百炼密钥更新:旧百炼密钥尚未轮换 +- [ ] SSH 直连部署:代理网关依赖 GZ-006 gatekeeper HTTP 拐弯取密钥,SSH 直连后延迟更低 +- [ ] 准入门禁集成:代理调用前检查 GET /map 是否已加载 + +--- + +> **铸渊注**:这个设计不是技术选择,是哲学必然。 +> 语言不应该知道密钥的存在——就像你不会在说出"我要喝水"之前先背诵水管工的电话号码。 +> 光湖语言系统 = 语言处理一切。密钥是水管的物理层,不是语言的语法。 diff --git a/zero-point/core-channel/api-proxy-gateway/ecosystem.config.json b/zero-point/core-channel/api-proxy-gateway/ecosystem.config.json new file mode 100644 index 0000000..e4176b4 --- /dev/null +++ b/zero-point/core-channel/api-proxy-gateway/ecosystem.config.json @@ -0,0 +1,17 @@ +{ + "apps": [{ + "name": "api-proxy-gateway", + "script": "server.py", + "interpreter": "python3", + "cwd": "/opt/zhuyuan/api-proxy", + "env": { + "API_PROXY_PORT": "8911", + "API_PROXY_SECRET": "REPLACE_WITH_RANDOM_64_HEX", + "GZ_006_GK_TOKEN": "REPLACE_WITH_GZ_006_GATEKEEPER_TOKEN", + "KEYSTORE_PASSWORD": "REPLACE_WITH_KEYSTORE_PASSWORD" + }, + "out_file": "/opt/zhuyuan/api-proxy/logs/out.log", + "error_file": "/opt/zhuyuan/api-proxy/logs/err.log", + "log_date_format": "YYYY-MM-DD HH:mm:ss" + }] +} diff --git a/zero-point/core-channel/api-proxy-gateway/server.py b/zero-point/core-channel/api-proxy-gateway/server.py new file mode 100644 index 0000000..2d9629a --- /dev/null +++ b/zero-point/core-channel/api-proxy-gateway/server.py @@ -0,0 +1,331 @@ +""" +光湖语言系统 · API 代理网关 v1.0 +Guanghu Language System · API Proxy Gateway + +人格体不需要密钥 → 人格体用语言说 → 代理取密钥 → 代理调 API → 返回结果 +密钥不出保险库 · 人格体代码零密钥 · 审计日志全追溯 + +部署: SG-001 /opt/zhuyuan/api-proxy/server.py +端口: 127.0.0.1:8911(仅本地 · 不对外开放) +""" + +import os, json, time, hmac, hashlib, secrets as _secrets +from datetime import datetime +from functools import wraps +from flask import Flask, request, jsonify, g + +# ────────────────────────────────────────── +# 配置 +# ────────────────────────────────────────── +APP = Flask(__name__) +PROXY_PORT = int(os.environ.get("API_PROXY_PORT", "8911")) +SESSION_TTL = 3600 # session 1小时过期 +SESSION_SECRET = os.environ.get("API_PROXY_SECRET", _secrets.token_hex(32)) + +# GZ-006 保险库 · API 子库(通过 gatekeeper 调用) +KEYSTORE_GATEKEEPER = "http://43.139.217.141:3910/exec" +KEYSTORE_TOKEN = os.environ.get("GZ_006_GK_TOKEN", "") +KEYSTORE_API_KS = "/opt/zhuyuan/keystore/api/ks.py" + +# 第三方 API 端点 · 密钥全在保险库 · 人格体只需知道 provider 名 +ENDPOINTS = { + # ── 火山方舟 · 图片生成(Seedream 4.5 / 5.0-lite)── + "volcano_seedream": { + "url": "https://ark.cn-beijing.volces.com/api/v3/images/generations", + "key_name": "VOLCANO_JIMENG_API_KEY", + "auth_header": "Authorization", + "auth_prefix": "Bearer ", + "description": "火山Seedream图片生成 · models: doubao-seedream-4.5 / doubao-seedream-5.0-lite", + }, + # ── 火山方舟 · 视频生成(Seedance 1.5-pro)── + "volcano_seedance": { + "url": "https://ark.cn-beijing.volces.com/api/v3/images/generations", + "key_name": "VOLCANO_JIMENG_API_KEY", + "auth_header": "Authorization", + "auth_prefix": "Bearer ", + "description": "火山Seedance视频生成 · model: doubao-seedance-1.5-pro", + }, + # ── 火山方舟 · 视频生成(SeaWeed)── + "volcano_seaweed": { + "url": "https://ark.cn-beijing.volces.com/api/v3/images/generations", + "key_name": "VOLCANO_JIMENG_API_KEY", + "auth_header": "Authorization", + "auth_prefix": "Bearer ", + "description": "火山SeaWeed视频生成 · model: doubao-seedance-2-0-260128", + }, + # ── 火山语音复刻 ── + "volcano_voice": { + "url": "https://openspeech.bytedance.com/api/v1/tts", + "key_name": "VOLCANO_VOICE_ACCESS_TOKEN", + "auth_header": "Authorization", + "auth_prefix": "Bearer;", + "description": "火山语音复刻TTS", + }, + # ── 阿里千问VL · 视觉理解(看图)── + "aliyun_qwen_vl": { + "url": "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation", + "key_name": "ALIYUN_QWEN_VL_KEY", + "auth_header": "Authorization", + "auth_prefix": "Bearer ", + "description": "阿里千问VL视觉理解 · model: qwen-vl-max", + }, + # ── 阿里百炼 · 图像生成 ── + "aliyun_bailian": { + "url": "https://dashscope.aliyuncs.com/api/v1/services/aigc/image-generation/generation", + "key_name": "ALIYUN_API_KEY", + "auth_header": "Authorization", + "auth_prefix": "Bearer ", + "description": "阿里百炼图像生成", + }, +} + +# 审计日志 +AUDIT_DIR = "/opt/zhuyuan/api-proxy/audit" +os.makedirs(AUDIT_DIR, exist_ok=True) + +# 内存 session 存储(重启丢失 · 安全设计) +SESSIONS = {} + +# ────────────────────────────────────────── +# 工具函数 +# ────────────────────────────────────────── + +def _audit(persona_id, action, detail=""): + """写审计日志 · 只追加不可改""" + ts = datetime.now().strftime("%Y-%m-%dT%H:%M:%S") + entry = {"ts": ts, "persona": persona_id, "action": action, "detail": detail} + log_file = f"{AUDIT_DIR}/api-proxy-{datetime.now().strftime('%Y%m%d')}.jsonl" + with open(log_file, "a") as f: + f.write(json.dumps(entry, ensure_ascii=False) + "\n") + +def _call_keystore(api_name): + """通过 GZ-006 gatekeeper 调 API 子库获取密钥""" + import subprocess + # Step 1: challenge + cmd = f"python3 {KEYSTORE_API_KS} challenge {api_name}" + payload = json.dumps({"cmd": cmd}) + for attempt in range(2): + r = subprocess.run([ + "curl", "-s", "--connect-timeout", "10", "-X", "POST", + KEYSTORE_GATEKEEPER, + "-H", f"Authorization: Bearer {KEYSTORE_TOKEN}", + "-H", "Content-Type: application/json", + "-d", payload + ], capture_output=True, text=True) + if r.returncode != 0: + continue + try: + data = json.loads(r.stdout) + if not data.get("ok"): + continue + ch = json.loads(data["stdout"]) + cid, nonce, target = ch["challenge_id"], ch["nonce"], ch["target"] + break + except: + continue + else: + raise Exception("keystore challenge failed") + + # Step 2: compute response (本地 · 密码从环境变量取) + password = os.environ.get("KEYSTORE_PASSWORD", "") + if not password: + raise Exception("KEYSTORE_PASSWORD not set") + + salt = os.environ.get("KEYSTORE_API_SALT", "") + if not salt: + # 降级:从 GZ-006 远程取 salt + import subprocess as _sp + cmd = f"cat {os.path.dirname(KEYSTORE_API_KS)}/salt" + payload = json.dumps({"cmd": cmd}) + sr = _sp.run([ + "curl", "-s", "--connect-timeout", "10", "-X", "POST", + KEYSTORE_GATEKEEPER, + "-H", f"Authorization: Bearer {KEYSTORE_TOKEN}", + "-H", "Content-Type: application/json", + "-d", payload + ], capture_output=True, text=True) + try: + sd = json.loads(sr.stdout) + salt = sd.get("stdout", "").strip() + except: + raise Exception("无法获取 keystore salt") + + pw_hash = hmac.new(password.encode(), salt.encode(), hashlib.sha256).hexdigest() + response = hmac.new(pw_hash.encode(), f"{nonce}:{target}".encode(), hashlib.sha256).hexdigest() + encrypt_key = hmac.new(password.encode(), b"hololake-keystore-encrypt", hashlib.sha256).hexdigest() + + # Step 3: unlock + cmd = f"python3 {KEYSTORE_API_KS} unlock {cid} {response} {encrypt_key}" + payload = json.dumps({"cmd": cmd}) + r = subprocess.run([ + "curl", "-s", "--connect-timeout", "10", "-X", "POST", + KEYSTORE_GATEKEEPER, + "-H", f"Authorization: Bearer {KEYSTORE_TOKEN}", + "-H", "Content-Type: application/json", + "-d", payload + ], capture_output=True, text=True) + data = json.loads(r.stdout) + if not data.get("ok"): + raise Exception(f"keystore unlock failed: {data.get('stdout', '')}") + result = json.loads(data["stdout"]) + if not result.get("ok"): + raise Exception(f"keystore: {result.get('error', 'unknown')}") + return result["token"] + + +# ────────────────────────────────────────── +# Session 管理 +# ────────────────────────────────────────── + +def _make_session(persona_id): + """生成临时 session token""" + sid = _secrets.token_hex(32) + ts = int(time.time()) + SESSIONS[sid] = {"persona": persona_id, "created": ts, "expires": ts + SESSION_TTL} + # 清理过期 session + for k in list(SESSIONS.keys()): + if SESSIONS[k]["expires"] < ts: + del SESSIONS[k] + return sid + +def _verify_session(sid): + """验证 session · 返回 persona_id 或 None""" + if not sid or sid not in SESSIONS: + return None + if SESSIONS[sid]["expires"] < time.time(): + del SESSIONS[sid] + return None + return SESSIONS[sid]["persona"] + + +def require_session(f): + """装饰器:需要有效 session""" + @wraps(f) + def wrapper(*a, **kw): + sid = request.headers.get("X-Session-Token", "") + persona = _verify_session(sid) + if not persona: + return jsonify({"error": "无效或过期的 session · 请先 POST /auth/session"}), 401 + g.persona_id = persona + return f(*a, **kw) + return wrapper + + +# ────────────────────────────────────────── +# 路由 +# ────────────────────────────────────────── + +@APP.route("/health", methods=["GET"]) +def health(): + return jsonify({"ok": True, "service": "api-proxy-gateway", "version": "1.0.0"}) + + +@APP.route("/auth/session", methods=["POST"]) +def auth_session(): + """人格体申请临时 session token""" + body = request.get_json(silent=True) or {} + persona_id = body.get("persona_id", "").strip() + if not persona_id: + return jsonify({"error": "需要 persona_id(如 ICE-GL-ZY001)"}), 400 + + # 简单验证:persona_id 必须以 ICE-GL- 或 TCS- 开头 + if not (persona_id.startswith("ICE-GL-") or persona_id.startswith("TCS-")): + return jsonify({"error": "无效的 persona_id 格式"}), 400 + + sid = _make_session(persona_id) + _audit(persona_id, "session_created", f"session={sid[:8]}...") + return jsonify({ + "ok": True, + "session_token": sid, + "expires_in": SESSION_TTL, + "persona_id": persona_id, + }) + + +@APP.route("/proxy/", methods=["POST"]) +@require_session +def proxy_call(provider): + """代理调用第三方 API""" + if provider not in ENDPOINTS: + return jsonify({"error": f"未知 provider: {provider}", "available": list(ENDPOINTS.keys())}), 404 + + cfg = ENDPOINTS[provider] + body = request.get_json(silent=True) or {} + + # ① 从保险库取密钥(不出现在返回中) + try: + api_key = _call_keystore(cfg["key_name"]) + except Exception as e: + _audit(g.persona_id, "keystore_error", str(e)) + return jsonify({"error": f"密钥获取失败: {str(e)}"}), 502 + + # ② 构造请求 + 转发 + import subprocess + headers = { + "Content-Type": "application/json", + cfg["auth_header"]: cfg["auth_prefix"] + api_key, + } + # 合并 body 中的额外参数 + proxy_body = {k: v for k, v in body.items() if not k.startswith("_")} + + payload = json.dumps(proxy_body) + r = subprocess.run([ + "curl", "-s", "--connect-timeout", "30", "--max-time", "120", + "-X", "POST", cfg["url"], + "-H", f"Content-Type: application/json", + "-H", f"{cfg['auth_header']}: {cfg['auth_prefix']}{api_key}", + "-d", payload, + ], capture_output=True, text=True) + + # ③ 审计日志 + _audit(g.persona_id, f"proxy_{provider}", f"status={r.returncode} len={len(r.stdout)}") + + # ④ 返回结果(不含密钥) + try: + result = json.loads(r.stdout) + except: + result = {"raw": r.stdout[:1000], "stderr": r.stderr[:500]} + + return jsonify({"ok": r.returncode == 0, "provider": provider, "result": result}) + + +@APP.route("/providers", methods=["GET"]) +@require_session +def list_providers(): + """列出可用的 API 提供商(不含密钥)""" + providers = {} + for name, cfg in ENDPOINTS.items(): + providers[name] = { + "key_name": cfg["key_name"], + "description": cfg.get("description", ""), + } + return jsonify({"providers": providers}) + + +@APP.route("/audit", methods=["GET"]) +@require_session +def view_audit(): + """查看最近的审计日志""" + today = datetime.now().strftime("%Y%m%d") + log_file = f"{AUDIT_DIR}/api-proxy-{today}.jsonl" + entries = [] + if os.path.exists(log_file): + with open(log_file) as f: + for line in f.readlines()[-50:]: + try: + entries.append(json.loads(line.strip())) + except: + pass + return jsonify({"date": today, "entries": entries, "count": len(entries)}) + + +# ────────────────────────────────────────── +# 启动 +# ────────────────────────────────────────── + +if __name__ == "__main__": + print(f"光湖 API 代理网关 v1.0") + print(f"监听: 127.0.0.1:{PROXY_PORT}") + print(f"Providers: {list(ENDPOINTS.keys())}") + print(f"审计日志: {AUDIT_DIR}") + APP.run(host="127.0.0.1", port=PROXY_PORT, debug=False)