perf(consultation): 健康探测加 TTL、yoga 表达式只编译一次
BUG-734: probe_official_rest_health 不是 ping —— 它把一份虚构 smoke 排盘 POST 给官方 HoroscopePredictions(实测单次 1,034 ms),还经 call() 吃掉对方 每分钟 5 个限流令牌之一,而结论零 TTL。连续 6 轮前台请求下,第 6 次探测会被 本地限流器挡下、反过来报出假的 official_blocked,真正的业务调用还要和它抢令牌。 加进程级 TTL 缓存(成功 60 s / 失败 10 s,配置变即失效,缓存键只记 key 配没配 的布尔)。force_refresh 默认 True:诊断端点调的是裸 gateway_status() 且该文件 本轮不得改,默认实时才不会让运维看到旧结论;run_gateway_packet 显式走 False。 探测在锁外执行。6 轮实测:探测 6→1、握手 5→1、限流令牌 6→1。 连接复用(任务书 6.2)按让步顺序砍掉:实测 urllib.request 没有连接池,模块级 共享 opener 在 6 次请求下仍开 6 条 TCP 连接,按任务书写法只能骗过「同一个 opener 实例」的断言而省不掉任何握手。另立单。 BUG-735: yoga_engine._eval_custom 对静态规则表的表达式每请求重新编译。实测每次 检测 386 次源码编译(182 次 eval 字符串 + 204 次 ast.parse/compile)。拆出 _build_custom_exec_globals(每次新建,绑定当前盘 ctx)与 lru_cache 的 _compiled_eval_code / _compiled_exec_code,只缓存 code object,绝不缓存求值 结果。求值语义逐条对齐改前。第二次检测起编译 0 次,单次 27.4 ms → 3.3 ms。 等价用同进程差分(BUG-733 的做法):192 条去重表达式 × 3 张公开虚构示例盘, 与逐字复刻的改前实现同值,另有反向验证证明差分非恒真。 未改 scripts/jyotish_api_server.py。既有断言一条未改。新增 23 条测试。 快速门唯一红的 test_chat_page_uses_authenticated_cloud_persistence 经干净 origin/staging 检出复跑确认为基线红,记入 BLOCKED.md BLK-002,不顺手修。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
This commit is contained in:
co-authored by
Claude Opus 5
parent
10afdcddeb
commit
63c419f375
+107
-51
@@ -11,6 +11,7 @@ Yoga 规则引擎 v1.0 (数据驱动架构)
|
||||
"""
|
||||
|
||||
import ast
|
||||
import functools
|
||||
import json
|
||||
import os
|
||||
from typing import Dict, List, Any, Optional
|
||||
@@ -544,6 +545,94 @@ class YogaContext:
|
||||
return lst[0] if lst else None
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# custom 规则表达式的编译缓存(BUG-735)
|
||||
# ============================================================================
|
||||
# `expr` 只来自仓库内的静态规则表 references/yoga_rules.json,进程生命周期内不变,
|
||||
# 改前却每个请求重新编译一遍(本机实测 386 次/次检测:182 次 eval 字符串编译 +
|
||||
# 204 次 ast.parse/compile)。这里只缓存 code object。
|
||||
#
|
||||
# 硬约束:求值命名空间 exec_globals 依赖每次不同的盘上下文(ctx),必须每次新建;
|
||||
# 绝不缓存求值结果——缓存结果会让所有人拿到同一张盘的 yoga 判定。
|
||||
#
|
||||
# maxsize 512:规则表去重后 192 条表达式,留出增长余量,同时给缓存一个硬上界。
|
||||
_CUSTOM_EXPR_CACHE_MAXSIZE = 512
|
||||
|
||||
|
||||
def _capture_tail_expr(block):
|
||||
"""把代码块末尾表达式改写为 __result__ = <expr>;递归处理 if/for/while 分支。"""
|
||||
if not block:
|
||||
return block
|
||||
last = block[-1]
|
||||
if isinstance(last, ast.Expr):
|
||||
block[-1] = ast.Assign(targets=[ast.Name(id="__result__", ctx=ast.Store())], value=last.value)
|
||||
elif isinstance(last, ast.If):
|
||||
last.body = _capture_tail_expr(last.body)
|
||||
last.orelse = _capture_tail_expr(last.orelse)
|
||||
elif isinstance(last, (ast.For, ast.While)):
|
||||
last.body = _capture_tail_expr(last.body)
|
||||
last.orelse = _capture_tail_expr(last.orelse)
|
||||
elif isinstance(last, ast.Try):
|
||||
last.body = _capture_tail_expr(last.body)
|
||||
last.orelse = _capture_tail_expr(last.orelse)
|
||||
last.finalbody = _capture_tail_expr(last.finalbody)
|
||||
for handler in last.handlers:
|
||||
handler.body = _capture_tail_expr(handler.body)
|
||||
return block
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=_CUSTOM_EXPR_CACHE_MAXSIZE)
|
||||
def _compiled_eval_code(expr):
|
||||
"""表达式模式的 code object;不是单个 expression 时返回 None(改前由 SyntaxError 表达)。"""
|
||||
try:
|
||||
return compile(expr, "<string>", "eval")
|
||||
except SyntaxError:
|
||||
return None
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=_CUSTOM_EXPR_CACHE_MAXSIZE)
|
||||
def _compiled_exec_code(expr):
|
||||
"""多语句模式的 code object:先按 AST 捕获末尾表达式,再编译。
|
||||
|
||||
v6.0.32: 支持多行语句(if/else/for 等)+ 末尾表达式求值模式。
|
||||
不能简单 split 最后一行:多行 if/else 的最后一行经常只是 else 分支内部表达式,
|
||||
直接 exec 前半段会产生缩进不完整。这里用 AST 捕获“实际执行分支”的末尾表达式。
|
||||
"""
|
||||
try:
|
||||
tree = ast.parse(expr.strip(), mode="exec")
|
||||
tree.body = _capture_tail_expr(tree.body)
|
||||
ast.fix_missing_locations(tree)
|
||||
return compile(tree, "<yoga_custom>", "exec")
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _run_custom_expr(expr, exec_globals):
|
||||
"""按改前语义求值 custom 表达式:先表达式模式,SyntaxError 退到多语句 exec 模式。
|
||||
|
||||
只有 code object 来自缓存;`exec_globals` 由调用方每次新建。
|
||||
"""
|
||||
result = None
|
||||
code = _compiled_eval_code(expr)
|
||||
if code is not None:
|
||||
try:
|
||||
# 同一命名空间同时作为 globals/locals,保证 list/dict comprehension 能访问 BENEFICS 等名称。
|
||||
result = eval(code, exec_globals, exec_globals)
|
||||
except SyntaxError:
|
||||
code = None # 运行期 SyntaxError:与改前一样退到多语句分支
|
||||
except Exception:
|
||||
return None
|
||||
if code is None:
|
||||
exec_code = _compiled_exec_code(expr)
|
||||
if exec_code is not None:
|
||||
try:
|
||||
exec(exec_code, exec_globals, exec_globals)
|
||||
result = exec_globals.get("__result__")
|
||||
except Exception:
|
||||
return None
|
||||
return result
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# YogaEngine: 规则加载与检测
|
||||
# ============================================================================
|
||||
@@ -1238,6 +1327,23 @@ class YogaEngine:
|
||||
if not expr:
|
||||
return []
|
||||
|
||||
# BUG-735: exec_globals 每次新建(依赖当前盘 ctx),只有 code object 走进程级缓存。
|
||||
exec_globals = self._build_custom_exec_globals(ctx, rule, bindings)
|
||||
result = _run_custom_expr(expr, exec_globals)
|
||||
|
||||
if result:
|
||||
combo = cond.get("combo_template", "自定义条件满足")
|
||||
if callable(combo):
|
||||
combo = combo(ctx, bindings)
|
||||
strength = cond.get("strength", rule.get("strength", "中"))
|
||||
if callable(strength):
|
||||
strength = strength(ctx, bindings)
|
||||
return [{"combination": combo, "strength": strength}]
|
||||
return []
|
||||
|
||||
def _build_custom_exec_globals(self, ctx, rule, bindings):
|
||||
"""构造 custom 表达式的求值命名空间。每次调用都新建,绑定当前这张盘的 ctx。"""
|
||||
|
||||
def house_of(p): return ctx.house_of(p)
|
||||
def sign_of(p): return ctx.sign_of(p)
|
||||
def deg_of(p): return ctx.degree_of(p)
|
||||
@@ -1542,57 +1648,7 @@ class YogaEngine:
|
||||
"kendra_lords_list": _kendra_lords_list,
|
||||
"trikona_lords_list": _trikona_lords_list,
|
||||
}
|
||||
# v6.0.32: 支持多行语句(if/else/for等)+ 末尾表达式求值模式。
|
||||
# 不能简单 split 最后一行:多行 if/else 的最后一行经常只是 else 分支内部表达式,
|
||||
# 直接 exec 前半段会产生缩进不完整。这里用 AST 捕获“实际执行分支”的末尾表达式。
|
||||
result = None
|
||||
exec_globals = {"__builtins__": {}, **safe_globals}
|
||||
|
||||
def _capture_tail_expr(block):
|
||||
"""把代码块末尾表达式改写为 __result__ = <expr>;递归处理 if/for/while 分支。"""
|
||||
if not block:
|
||||
return block
|
||||
last = block[-1]
|
||||
if isinstance(last, ast.Expr):
|
||||
block[-1] = ast.Assign(targets=[ast.Name(id="__result__", ctx=ast.Store())], value=last.value)
|
||||
elif isinstance(last, ast.If):
|
||||
last.body = _capture_tail_expr(last.body)
|
||||
last.orelse = _capture_tail_expr(last.orelse)
|
||||
elif isinstance(last, (ast.For, ast.While)):
|
||||
last.body = _capture_tail_expr(last.body)
|
||||
last.orelse = _capture_tail_expr(last.orelse)
|
||||
elif isinstance(last, ast.Try):
|
||||
last.body = _capture_tail_expr(last.body)
|
||||
last.orelse = _capture_tail_expr(last.orelse)
|
||||
last.finalbody = _capture_tail_expr(last.finalbody)
|
||||
for handler in last.handlers:
|
||||
handler.body = _capture_tail_expr(handler.body)
|
||||
return block
|
||||
|
||||
try:
|
||||
# 同一命名空间同时作为 globals/locals,保证 list/dict comprehension 能访问 BENEFICS 等名称。
|
||||
result = eval(expr, exec_globals, exec_globals)
|
||||
except SyntaxError:
|
||||
try:
|
||||
tree = ast.parse(expr.strip(), mode="exec")
|
||||
tree.body = _capture_tail_expr(tree.body)
|
||||
ast.fix_missing_locations(tree)
|
||||
exec(compile(tree, "<yoga_custom>", "exec"), exec_globals, exec_globals)
|
||||
result = exec_globals.get("__result__")
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if result:
|
||||
combo = cond.get("combo_template", "自定义条件满足")
|
||||
if callable(combo):
|
||||
combo = combo(ctx, bindings)
|
||||
strength = cond.get("strength", rule.get("strength", "中"))
|
||||
if callable(strength):
|
||||
strength = strength(ctx, bindings)
|
||||
return [{"combination": combo, "strength": strength}]
|
||||
return []
|
||||
return {"__builtins__": {}, **safe_globals}
|
||||
|
||||
def _eval_semantic_condition(self, ctype, cond, ctx, rule, bindings):
|
||||
"""兼容批量抽取规则中的语义型条件。返回保守布尔匹配。"""
|
||||
|
||||
Reference in New Issue
Block a user