fix: 配置接线审查修复(push数值哨兵化/恒真测试修正/哨兵防漂移/种子映射消双路径)

Important-1: push 数值字段(timeout/max_attempts/retry_backoff_base)
出厂默认改 None 哨兵(与 ai.model 同款),resolve_push_settings 对 None
回退 env——根治 truthy 出厂默认永久遮住 env 覆盖;恒真测试修正(env 数值
patch 成 7.5/5/2.0 与出厂区分)+租户显式数值优先断言;ai.provider 遮
env 的边界记入 docstring。Minor-2: 出厂 model 哨兵与 DEFAULT_CONFIG
一致性测试 + defaults.py 双源注释(model/rate_limit_per_hour)。
Minor-4: allowed_seed_keys → context_seed_map(返回映射,单一取值路径)。
Minor-6/7/3/8: WS 配置冻结时机注释/name_to_region 查表序注释改准确/
chat_rest limit 0 语义注释/resolver None 路径仅供测试注释。404 全绿。

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-10-12 21:57:57 +08:00
parent 32b582aac8
commit 49e2b476f5
7 changed files with 74 additions and 29 deletions
+3
View File
@@ -114,6 +114,9 @@ def resolve_ai_settings(config_data: dict | None) -> dict:
api_key/base_url 空槽(None/空串)→ env 智谱凭据(平台默认集团 key,
spec §5.5 "用平台默认"状态下租户侧不存密钥);model 空或等于出厂默认
→ env AI_MODEL。
已知边界: provider 出厂 "glm" 同样遮 env AI_PROVIDER——当前单 provider
无实害,多 provider 接入时再改哨兵结构(与 push 数值同款纪律)。
config_data=None 路径仅供测试直调,生产调用一律传 runtime.config.data。
"""
ai = ((config_data or {}).get("ai")) or {}
model = ai.get("model")
+3 -1
View File
@@ -298,7 +298,9 @@ def name_to_region(name: str | None,
容错:去首尾空白、大小写不敏感、弯撇号(U+2019)归一为 ASCII。
Task 9: extras = misc.name_to_region_extras(租户扩表),可加键也可
覆盖基础表同名键(租户纠偏)——查表序 extras 优先。
覆盖基础表同名键(租户纠偏)。查表分两轮:先精确匹配(extras→基础
表)、后 casefold 折叠(extras→基础表)——基础表的精确命中优先于
extras 的大小写折叠命中(精确 > 折叠,非"extras 整体优先")。
"""
if not name:
return None
+11 -8
View File
@@ -50,14 +50,16 @@ def resolve_contact_flags(config_data: dict | None) -> dict:
}
def allowed_seed_keys(config_data: dict | None) -> set[str]:
"""context 种子白名单 = config.engine.context_seed_keys 的键集合。
def context_seed_map(config_data: dict | None) -> dict:
"""context 种子映射 = config.engine.context_seed_keys(ctx_key → raw_key)。
Task 9 前白名单(product_name/product_id)硬编码在本模块——两租户差异
(auto 是 vehicle_*)由此配置化;create_conversation 按映射值写 raw_info。
(auto 是 vehicle_*)由此配置化;create_conversation 是唯一消费口(按
映射值写 raw_info,白名单 = 本映射的键集合——审查 Minor-4: 单一取值
路径,不留第二条内联读法)。
"""
engine = ((config_data or {}).get("engine")) or {}
return {str(k) for k in (engine.get("context_seed_keys") or {})}
return dict(engine.get("context_seed_keys") or {})
# 职能边界触发词(Task 13.2, 评审#1/#4 修订): 命中即注入红线提醒。精修说明:
# - 裸 "how long"/"多久" 不收(规格参数类问题常用),英文要求 take/arrive/deliver/ship 邻接,
@@ -372,7 +374,9 @@ class ConversationManager:
wall=runtime.compiled_wall,
config_data=runtime.config.data)
# Task 9: contact 行为开关(cc 补全/邮箱轻补)与国名映射扩表——
# manager 生命周期内配置不变(reload 换 runtime 即换 manager 装配线)
# manager 生命周期内配置不变(reload 换 runtime 即换 manager 装配线;
# 生效时机: WS 长连接内冻结至断连,REST 路径下一请求生效——Phase 2
# 联调改配置后按此预期,勿误判"没生效")
self._contact_flags = resolve_contact_flags(runtime.config.data)
self._region_extras = (((runtime.config.data.get("misc")) or {})
.get("name_to_region_extras") or None)
@@ -408,9 +412,8 @@ class ConversationManager:
# WS path passes no context -> raw_info stays {}.
raw_info: dict = {}
if context:
engine_cfg = self.runtime.config.data.get("engine") or {}
seed_map = engine_cfg.get("context_seed_keys") or {}
for ctx_key, raw_key in seed_map.items():
for ctx_key, raw_key in context_seed_map(
self.runtime.config.data).items():
if context.get(ctx_key):
raw_info[raw_key] = context[ctx_key]
profile = CustomerProfile(conversation_id=conv.id, raw_info=raw_info)
+16 -5
View File
@@ -37,8 +37,14 @@ logger = logging.getLogger(__name__)
def resolve_push_settings(config_data: dict | None) -> dict:
"""租户 config.push → 推送参数(Task 9 接线; env settings 回退)。
出厂默认的空槽(False/空串)视同"未设置"→ 回退 env PUSH_*——部署级
.env 开关是平台默认集的一部分,租户未配置时继承,不静默关停。
空槽回退哨兵(审查 Important-1, 与 ai.model 同款纪律):
- 布尔/字符串: falsy 出厂默认(False/空串)视同"未设置"→ 回退 env
PUSH_*——部署级 .env 开关是平台默认集的一部分,租户未配置时继承,
不静默关停;
- 数值字段(timeout/max_attempts/retry_backoff_base): 出厂 None =
"未设置"→ 回退 env(settings 自带默认 10.0/3/1.0,最终行为与出厂
truthy 默认时一致),租户显式数值(含显式 0)优先于 env。
config_data=None 路径仅供测试直调,生产调用一律传 runtime.config.data。
已知边界: env 开 + 租户显式 false 无法区分于未配置(布尔空槽只有
False),Phase 2 admin 落地时以"平台清 env、配置显式写 false"解决。
"""
@@ -48,10 +54,15 @@ def resolve_push_settings(config_data: dict | None) -> dict:
"url": p.get("url") or settings.PUSH_API_URL,
"api_key": p.get("api_key") or settings.PUSH_API_KEY,
"prefix": p.get("prefix") or settings.PUSH_EXTERNAL_KEY_PREFIX,
"timeout": p.get("timeout") or settings.PUSH_TIMEOUT_SECONDS,
"max_attempts": p.get("max_attempts") or settings.PUSH_MAX_ATTEMPTS,
# 数值哨兵: None(出厂/显式清空)→ env;显式数值优先
"timeout": (p.get("timeout") if p.get("timeout") is not None
else settings.PUSH_TIMEOUT_SECONDS),
"max_attempts": (p.get("max_attempts")
if p.get("max_attempts") is not None
else settings.PUSH_MAX_ATTEMPTS),
"retry_backoff_base": (p.get("retry_backoff_base")
or settings.PUSH_RETRY_BACKOFF_BASE),
if p.get("retry_backoff_base") is not None
else settings.PUSH_RETRY_BACKOFF_BASE),
}
# 对方契约(docs/AI_INQUIRY_PUSH_API.md): phone/whatsapp/email 至少一个有效