Files
PolyGateway/.env.example
T
iomgaa 6d6b3cf59c docs: correct the stale throughput numbers and wiki state
独立验证发现的 3 处文档欠账:

③ 两处代码内注释还挂着已作废的吞吐估算,`.env.example`/README/
   CHANGELOG/ARCHITECTURE 四处早已改成实测口径:
   - `config.py` 的 `# 4 条 ≈ 32 行/秒(实测…)` —— "32 行/秒"正是设计
     §10 修订 #1 判定"偏乐观一倍"并作废的估算值,却挂着"实测"二字;
   - `postgres.py` 的 `pool_max` docstring 写着 `稳态吞吐 ≈ pool_max /
     RTT`,正是设计要求下游**不要**用的那个公式。
   两处统一为实测值: RTT ≈ 123ms 上 `pool_max=4` 约 15.6 行/秒
   (50 行并发批 3.2s)。设计 §8 与计划 T7 里残留的同一公式一并标注作废。

④ 文档写 `acquire(timeout=剩余预算)`,实现传的是完整预算(行为无害,
   外层 `asyncio.timeout` 才是真正上界)。**改文档不改代码**: 设计
   §3.1、计划 T3、ARCH §7.8 三处对齐,并写明为什么内层不再算剩余量。

⑤ wiki 登记页与正文状态漂移: design 登记页仍写"待人类审"(正文已是
   "已实施")、plan 登记页写"正文 326 行"(实际 380)、log.md 末条停在
   T0 之前。三处校正,T1-T8 补登记,rebuild_index。

另补一条独立验证在真实 PG 上发现的语义细节: 本地池饱和造成的丢行走
**行级丢弃**,`degraded` 保持 False,只有 `dropped_rows` 增长——只按
`degraded` 配告警的下游会完全看不见这类丢行,而它恰是 `pool_max` 配小
了的唯一信号。README / .env.example / ARCHITECTURE / CHANGELOG 各补一句。
2026-08-24 11:55:22 -04:00

164 lines
13 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PolyGateway 工程配置模板(复制为 .env 使用;.env 不提交)。
# 键名清单 = M1 设计文档 §8 定稿;缺关键配置直接报错,不做默认值兜底。
# ══ 多源配置: {SCOPE}__{PROVIDER}__{N}__{FIELD} ══
# PROVIDER 必须是注册表键(qwen/deepseek/openai,或 register_provider 注册后经 registry 传入)。
# 必填: BASE_URL / API_KEY / MODEL / TIMEOUT_S(或用平铺 LLM_TIMEOUT 作缺省)。
LLM__QWEN__1__BASE_URL=
LLM__QWEN__1__API_KEY=
LLM__QWEN__1__MODEL=
LLM__QWEN__1__TIMEOUT_S=120
# 可选(0 = 该闸不启用):
# LLM__QWEN__1__MAX_CONCURRENCY=8
# LLM__QWEN__1__RPM=60
# LLM__QWEN__1__TPM=100000
# LLM__QWEN__1__EST_TOKENS=2000 # 可选调优覆盖: TPM 入场预扣量;未填则库按 tpm//60 派生
# LLM__QWEN__1__TTFT_TIMEOUT_S=30 # 须与 INTER_TOKEN 成对;0 < inter < ttft < timeout
# LLM__QWEN__1__INTER_TOKEN_TIMEOUT_S=15
# LLM__QWEN__1__ENABLE_THINKING=true # 三态: 缺省=不注入 / true=注入开启 / false=注入关闭
# LLM__QWEN__1__MISSING_DONE=retry # SSE 缺 [DONE]: retry(默认) | salvage
# LLM__QWEN__1__TRUST_ENV=true # false = 绕过本地代理(LAN 直连)
# LLM__QWEN__1__EXTRA_BODY={"temperature":0} # 本源恒定的采样参数(JSON 对象串)
# 并入请求体,优先级低于 chat(overlay=...);受控实验固定解码用它,免得漏传
# 禁用键 model/messages/stream/stream_options(会击穿治理),配了直接报错
# OCR/EMBED scope 不消费该键: 配了会被忽略并 warning(见 issue #4 决策 G)
# ══ scope 级全局闸(跨源合计;0/缺省 = 不启用)══
# LLM__GLOBAL__MAX_CONCURRENCY=8
# LLM__GLOBAL__RPM=120
# LLM__GLOBAL__TPM=200000
# ══ 韧性参数(平铺键 = 单 scope 简写,沿用三项目习惯;scope 键优先)══
LLM_MAX_RETRIES=3 # 总尝试次数(含首次);或 LLM__RETRY__MAX_ATTEMPTS
LLM_RETRY_BASE_DELAY=2.0 # 或 LLM__RETRY__BACKOFF_BASE_S
LLM_RETRY_MAX_DELAY=30.0 # 或 LLM__RETRY__BACKOFF_MAX_S
LLM_CIRCUIT_BREAKER_THRESHOLD=5 # 连续失败通道;有效阈值取 max(此值, 源级并发×2);或 LLM__BREAKER__FAIL_THRESHOLD
LLM_CIRCUIT_BREAKER_COOLDOWN=60 # 或 LLM__BREAKER__COOLDOWN_S
# LLM_TIMEOUT=120 # 源缺 TIMEOUT_S 时的缺省
# LLM_TTFT_TIMEOUT=30 # 平铺看门狗缺省(成对生效)
# LLM_INTER_TOKEN_TIMEOUT=15
# LLM__BREAKER__PROBE_TTL_S=240 # 缺省派生: max(2×最大源超时, cooldown, 最大源超时+5);显式值须 ≥ 最大源超时+5
# LLM__BACKPRESSURE__STALL_WINDOW_S=300 # stall 双条件判死窗口;只计非生产性等待(429 退避/配额轮询/熔断冷却),与 TIMEOUT_S 无耦合,无需按 timeout×retries 放大
# LLM__BACKPRESSURE__POLL_INTERVAL_S=0.05
# LLM__SELECTOR=health_aware # health_aware(默认,M2.5) | round_robin | least_inflight
# ── M2.5 失败率熔断通道(可选,缺省即生产推荐值)──
# LLM__BREAKER__MIN_CALLS=10 # 率通道最小样本(防低流量误判)
# LLM__BREAKER__FAIL_RATE=0.6 # 窗口失败率阈值(429 不计入)
# LLM__BREAKER__WINDOW_S=60 # 失败率窗口(双 30s 桶)
# LLM__BREAKER__MAX_COOLDOWN_S=300 # 开路指数退避封顶(缺省 max(300, cooldown))
# ── AIMD 自适应并发(M2.5,库常量非 env 键): 每源初始 8,429 ×0.5,成功 +1/limit,
# ── ceiling = max(64, 源级 MAX_CONCURRENCY);禁用需构造函数注入自定义 pacer ──
# LLM__QUOTA_FULL=wait # 配额满: wait(默认) | fail_fast
# ── 熔断全拒时的处置(issue #14)。单源 scope 建议 wait: 只有一个源时
# ── "停用这个源"等于"整个 scope 停服",fail_fast 会让开路期间的每次调用
# ── 在几毫秒内死掉且 MAX_ATTEMPTS 一格用不上。wait 不削弱保护(等待期照样
# ── 不发请求),只是把最坏墙钟拉长到 BACKPRESSURE__STALL_WINDOW_S ──
# LLM__CIRCUIT_OPEN=fail_fast # 熔断开路: fail_fast(默认) | wait
# ══ 装配选择(PGW_*)══
PGW_LIMITER_BACKEND=memory # memory | redis(redis 需 REDIS_URL;多进程 worker 必须 redis)
PGW_BREAKER_BACKEND=memory # memory | redis
PGW_CACHE_BACKEND=none # redis | memory | none(必填,显式优于隐式)
PGW_TELEMETRY_BACKEND=none # sqlite | postgres | none(必填)
# PGW_TELEMETRY_SQLITE_PATH=logs/telemetry.db # sqlite 时必填
# PGW_TELEMETRY_SCHEMA_MODE=manual # auto | manual;三态: 不设 = 按后端派生(sqlite→auto、postgres→manual),
# # 显式设置则两侧都可覆盖。auto = 库给已存在的旧表自动 ALTER 补列;
# # manual = 库不发 ALTER,只 warning 点名缺列并打印可执行 SQL,
# # 按现有列裁剪 INSERT 继续写(遥测不会因缺列而全线丢失)。
# # 缺省为何不对称: postgres 是共享生产表,ALTER 取 ACCESS EXCLUSIVE 锁,
# # 会排在长事务后阻塞该表其后的所有查询,而遥测是业务路径上的内联 await;
# # 且这类部署有 DBA、有迁移工具、讲最小权限,DDL 该由他们择时执行。
# # sqlite 则是下游自己的本地文件(runs/*.db):没有 DBA、没有迁移工具、
# # 没有第二个系统碰它,ALTER 是毫秒级元数据操作,强加手工 SQL 步骤是净损失。
# PGW_TELEMETRY_PG_DSN=postgresql://user:pass@host:5432/polygateway # postgres 时必填;严禁指向在用业务库(实验室约定: 专用库 polygateway)
# PGW_TELEMETRY_PG_POOL_MAX=4 # postgres 遥测池的连接上限,须 >= 1;缺省 4。**闲时占 0 条**——
# # 池按需建连(min_size=0),不预占;这一格是忙时的天花板,不是常驻量。
# # 调参口径(以实测为准,不要按 pool_max/RTT 估算):跨内网 RTT ≈ 123ms 的
# # 实验室 PG 上,pool_max=4 实测约 **15.6 行/秒**(50 行并发批耗时 3.2s),
# # 即每条连接约 4 行/秒 —— 一次 INSERT 的实际往返比一次 `SELECT 1` 重一倍,
# # 按单次 RTT 估会乐观一倍。要放大就按这个实测值线性折算(pool_max=8 ≈ 31 行/秒)。
# # 缺省 4 在缺省 5s 预算下能吞下约 50 行的突发(余量约 1.5 倍);超预算的行被丢弃
# # 并计入 telemetry_status.dropped_rows —— 丢一条遥测好过拖垮业务调用。
# # 注意告警口径: 池饱和丢的行走**行级丢弃**,telemetry_status.degraded 保持
# # False(后端并没有挂,是本进程并发超了),只有 dropped_rows 增长。只按
# # degraded 告警会完全看不见这一类丢行 —— 对账要两个字段一起看。
# # 什么时候该调大: 单进程遥测写入并发经常超过 4(高频短调用、批量并发),
# # 或多个 client 显式共享同一个 recorder(并发在这里汇聚,应按 client 数放大)。
# PGW_TELEMETRY_PG_WRITE_TIMEOUT_S=5.0 # 一次遥测写入的硬预算(秒),须 > 0;缺省 5.0。同时用作建连、
# # acquire 与「准备 + 取连接 + 执行」整段的上界:超时即丢弃该行,
# # 绝不让遥测无界地挂在业务路径上。实测参考: 稳态写入 123ms、
# # 首次写入含建连 513ms —— 5s 对正常路径是极宽松的上限,它防的是
# # 池满排队与后端假死这类"不会自己结束"的等待。
# # 与之配套的两个不可配内部常量: 连接释放上界 1s(超时即 terminate)、
# # 环境级降级的冷却期 60s(到期自动重试一次,成功即恢复)。
# PGW_TELEMETRY_TEXT_CAP=2000 # 遥测落库正文的字符上限,须 > 0;**不设 = 不截断**(缺省,逐字节留全文)。
# # 作用于 messages 的每条文本 content、多模态 text part、response 与 thinking;
# # 超出部分头部保留、尾部换成 `…(略 N 字)`。多模态 image_url 的 sha256 摘要不受影响。
# # 缺省为何是"不截断": 遥测被下游当**审计证据**用——出了问题要回答"当时到底发了什么",
# # 也要能拿原样的请求复现与重放;截断后这两件事都做不成,而既有下游正依赖这一行为。
# # 反面同样要看清: 不截断意味着客户合同、标书全文无限期留在 llm_calls 里,
# # 多租户下还混在同一张表。真在意留存面的部署应显式设一个上限,并配保留期与访问控制。
# PGW_PRICING_PATH=config/prices.json # 可选: {"<model>": {"input_per_1m": x, "output_per_1m": y}};缺省 cost 恒 None
# # 可选第三档 "cached_input_per_1m": z —— 供应商 prompt cache 命中部分的单价;
# # 不填即命中部分也按 input 全额计(库不猜折扣率),cost 会偏高
# PGW_CACHE_NAMESPACE=<项目名或租户前缀> # 缓存启用时必填(防跨项目毒化)
# PGW_CACHE_TTL_S=604800 # 缓存启用时必填,须 > 0
# PGW_STRUCTURED_MAX_RETRIES=2 # 缺省 2(M2.5);0 = 解析失败不重问(CHS 策略)
# PGW_LEASE_TTL_S=1500 # permit 租约;须 ≥ 最大源 timeout
# ══ Redis(缓存 + 分布式限流/熔断)══
# 实验室纪律: 共享实例的 db0 有在用键,PolyGateway 一律用专用 db3(soak 会 FLUSHDB!)
# REDIS_URL=redis://:password@host:6379/3
# ══ Embedding scope(M2;EmbeddingClient.from_env 装配)══
# EMBED__QWEN__1__BASE_URL=
# EMBED__QWEN__1__API_KEY=
# EMBED__QWEN__1__MODEL=text-embedding-v3
# EMBED__QWEN__1__TIMEOUT_S=60
# EMBED__RETRY__MAX_ATTEMPTS=3
# EMBED__RETRY__BACKOFF_BASE_S=1.0
# EMBED__RETRY__BACKOFF_MAX_S=10.0
# EMBED__BREAKER__FAIL_THRESHOLD=5
# EMBED__BREAKER__COOLDOWN_S=60
# EMBED__BATCH_SIZE=64 # 必填: 每批条数(分批是行为关键,不设默认)
# EMBED__NORMALIZE=false # 可选: true = 库内 L2 归一化(VT 语义)
# EMBED__EXPECTED_DIM=768 # 可选: 维度校验,不符抛 ResultInvalid
# ══ SOAK scope(压测 harness 专用;tools/soak/run_soak.py --scope SOAK)══
# 网关保护(设计 §8.1 签字值,run_soak 强制: 必配且 ≤ 100/600,否则拒跑)
# SOAK__GLOBAL__MAX_CONCURRENCY=100
# SOAK__GLOBAL__RPM=600
# 健康源 + 故障源混编(findings §3): 坏 key 源 / 黑洞源 / 紧看门狗源 / 紧闸源
# SOAK__MINIMAX__1__BASE_URL= # 健康源(真实网关)
# SOAK__MINIMAX__1__API_KEY=
# SOAK__MINIMAX__1__MODEL=
# SOAK__MINIMAX__1__TIMEOUT_S=180
# SOAK__MINIMAX__2__BASE_URL= # 坏凭据源: 真实网关 + 错误 API_KEY → 401
# SOAK__MINIMAX__2__API_KEY=sk-wrong-key-on-purpose
# SOAK__MINIMAX__3__BASE_URL=http://10.255.255.1/v1 # 黑洞源: 防火墙 DROP → 连接超时
# SOAK__MINIMAX__4__RPM=5 # 紧闸源: 真实源 + rpm=5 / 并发 1
# SOAK__MINIMAX__4__MAX_CONCURRENCY=1
# ══ OCR scope(M3;MonkeyOCR 自建 LAN 服务,无鉴权故 API_KEY 填占位 "none")══
# CHS 单源写法:
# OCR__MONKEY__1__BASE_URL=http://10.77.0.20:7866
# OCR__MONKEY__1__API_KEY=none # 占位惯例: 服务无鉴权,SourceConfig 非空校验用
# OCR__MONKEY__1__MODEL=monkey-ocr
# OCR__MONKEY__1__TIMEOUT_S=300 # /parse 两段协议较慢,给足
# OCR__MONKEY__1__MAX_CONCURRENCY=4
# OCR__MONKEY__1__RPM=120
# VT 双实例写法(原 MONKEY_OCR_URLS 逗号列表拆多源;LAN 直连绕代理配 TRUST_ENV=false):
# OCR__MONKEY__2__BASE_URL=http://10.77.0.20:7867
# OCR__MONKEY__2__API_KEY=none
# OCR__MONKEY__2__MODEL=monkey-ocr
# OCR__MONKEY__2__TIMEOUT_S=300
# OCR__MONKEY__2__TRUST_ENV=false
# OCR__RETRY__MAX_ATTEMPTS=3 # per-scope 韧性键与 LLM scope 同一套
# ══ SOAK_OCR scope(P7 OCR 压测;tools/soak/run_soak.py --scenario P7 --scope SOAK_OCR)══
# 双真实实例 + 黑洞(连接超时)+ 坏端口(连接拒绝)故障池;
# SOAK_OCR_FAULT_SOURCES 供记分板"坏源吸流占比"不变量归因
# SOAK_OCR__GLOBAL__MAX_CONCURRENCY=16
# SOAK_OCR__GLOBAL__RPM=300
# SOAK_OCR_FAULT_SOURCES=monkey_3,monkey_4