Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
435c8e19f9 | ||
|
|
537beb2934 |
@@ -1,5 +1,11 @@
|
||||
# 印度占星 Skill 更新日志
|
||||
|
||||
## 2026-10-29 — 咨询回答快约三分之一
|
||||
|
||||
- 咨询里写回答那一步,模型的思考量默认改为「低」:对比测试里思考量少约一半,一轮回答从约 34 秒降到约 22 秒,回答质量逐份并排读过没有变差(issue #134,产品选 A)。
|
||||
- 只影响写回答那一步;排盘、查资料那几步不变。
|
||||
- 需要退回时,在服务器设 `CONSULT_ANSWER_REASONING_EFFORT=off` 即恢复原来的请求。
|
||||
|
||||
## 2026-10-28 — 切换人物时看得出是谁的盘
|
||||
|
||||
- 「当前星盘」按钮上写名字、出生日期时间和城市;名单每行写关系和出生时间、城市。
|
||||
|
||||
+13
-14
@@ -20463,18 +20463,17 @@
|
||||
- 复发自:无。
|
||||
- 修复版本:分支 `codex/rectification-recap-date-dup-20261028`。
|
||||
|
||||
## BUG-1498 | staging 门禁 Python 快速门偶发失败:`test_daily_starlanguage_does_not_eat_yesterday_cache` 网关调用 0 次
|
||||
## BUG-1498 | staging 门禁 Python 快速门失败:`test_daily_starlanguage_does_not_eat_yesterday_cache` 网关调用 0 次
|
||||
|
||||
- 状态:investigating(只记录已确认事实与假设;未改代码)
|
||||
- 首次发现 / 最近更新:2026-10-28(BUG-1481 验证段首次提到)/ 2026-10-28(staging 门禁 run 2017)
|
||||
- 影响面:只影响 CI 门禁,不影响线上。`tests/test_vedastro_snapshot_cache.py::test_daily_starlanguage_does_not_eat_yesterday_cache`。
|
||||
- 现象:`assert calls["gateway"] == 1` 得到 `0 == 1`;同一轮其余 1094 项通过。run 2016(同一份 Python 代码)这一项通过;本机单独连跑 5 次全部通过。
|
||||
- 触发条件:整套快速门一起跑时偶发,单独跑不出现,跟测试先后顺序或机器负载有关。
|
||||
- 已排除:BUG-1481 当时认为是测试之间共用负缓存目录;`tests/conftest.py::_isolate_vedastro_snapshot_cache` 现在每个测试都另建快照目录和负缓存目录,这次复发不是那个原因。
|
||||
- 假设(未证实):`scripts/vedastro_foreground.py` 的 `_FOREGROUND_VEDASTRO_EXECUTOR` 是模块级线程池,整个 pytest 进程共用,conftest 不清空它。前面的测试如果留下还在跑的任务占满线程,本测试的任务排在后面,前台 1.5 秒等待到期后被取消,网关一次都没调用。
|
||||
- 修复:未修。下一步:在 conftest 里每个测试后排空或重建这个线程池,或者本测试把等待时间设大,再在全套顺序下复跑确认。
|
||||
- 验证:未完成。
|
||||
- 防复发:未完成。
|
||||
- 关联:BUG-1481(第一次记录这个失败)。
|
||||
- 复发自:BUG-1481 记下的同一项失败。当时的处理(隔离负缓存目录)没拦住,因为根因很可能不在目录上。
|
||||
- 修复版本:无。
|
||||
- 状态:resolved(测试隔离 + 回归测试;以修复后第一轮 staging 门禁通过为准)
|
||||
- 首次发现 / 最近更新:2026-10-28(BUG-1481 验证段首次提到)/ 2026-10-28(staging 门禁 run 2017、2018 连续两次失败)
|
||||
- 影响面:只影响 CI 门禁,不影响线上。失败的是 `tests/test_vedastro_snapshot_cache.py::test_daily_starlanguage_does_not_eat_yesterday_cache`;同样的机制可能打到任何用前台 VedAstro 线程池的测试。
|
||||
- 现象:`assert calls["gateway"] == 1` 得到 `0 == 1`;同一轮其余 1094 项通过。run 2016 这一项通过,run 2017、2018 失败,三次 Python 代码相同;本机单独跑或整套跑都通过。
|
||||
- 触发条件:整套测试一起跑,前面某个测试留在前台线程池里的任务,在下一个测试开始之后才跑完。
|
||||
- 根因:`scripts/vedastro_foreground.py::_FOREGROUND_VEDASTRO_EXECUTOR` 是模块级线程池,整个 pytest 进程共用(`jyotish_api_server` 还按名字另外导入了一份)。任务跑完后,`_remember_gateway_outcome` 把结果写进「写的那一刻」环境变量指向的缓存目录。上一个测试留下的任务跑完时,环境变量已经指向下一个测试的隔离目录,于是一次失败被记成下一个测试「今天已经失败过」,`_failed_today` 命中,网关一次都没调用。线程池被占满时,任务排队到超时被取消,也会得到同样的 0 次。#130 给每个测试单独建目录,挡不住这种跨测试写入,所以 BUG-1481 那次的判断(共用目录)不是根因。
|
||||
- 修复:`tests/conftest.py::_isolate_foreground_vedastro_executor`。每个测试换一个新的前台线程池(两个模块里的名字都换);测试结束时丢掉排队的任务,正在跑的最多等 10 秒,所以剩下的任务一定在本测试的目录还有效时写完。生产代码没有改。
|
||||
- 验证:新增 `tests/test_vedastro_executor_isolation_20261028.py`:第一个测试留下一个 0.3 秒的任务,第二个测试断言它已经在上一个测试收尾时跑完、而且没有写进自己的目录。没有 fixture 时红(`assert 0 == 1`),加上后绿。`test_vedastro_snapshot_cache.py`、`test_vedastro_negative_day_20261028.py`、`test_vedastro_consultation_snapshot.py` 一起 19 项通过;本机完整快速门(`run_quality_gate.py --profile quick --skip-yoga-logic --skip-frontend-runtime`)通过。
|
||||
- 防复发:上面那组顺序测试固定了「任务不跨测试」。以后新增模块级线程池的,要放进同一个 fixture。
|
||||
- 关联:BUG-1481(第一次记下这个失败)、BUG-1479(#130,负缓存与每测试目录)。
|
||||
- 复发自:BUG-1481 记下的同一项失败。当时的处理(每测试单独目录)没拦住,因为根因是后台任务跨测试写入,不在目录共用上。
|
||||
- 修复版本:分支 `codex/bug-1498-executor-isolation`。
|
||||
|
||||
@@ -546,3 +546,4 @@
|
||||
| `TASK-home-crash-saved-people-20261027.md` | `PROGRESS-home-crash-saved-people-20261027.md` | **#115 首页崩溃**:存过别人的账户一进首页就「页面出错了」——人物列表的资料缺两个字段,启程引导一读就报错;入口补齐默认值(BUG-1464) | 待验收 | `codex/home-crash-saved-people-20261027` |
|
||||
| `TASK-upstream-sync9-20261028.md` | `PROGRESS-upstream-sync9-20261028.md` | **第 1 次打回(快速门 1 败:Darakaraka 旧测试锁错值,见 `TASK-upstream-sync9-fix-20261028.md`)** · **#141 上游同步第九轮**:甲批九项已做完并推 `codex/upstream-sync9-20261028`;校正 20 条和 900 条与上一份回放逐条相同;乙批仍等 #137 | 待验收 | `codex/upstream-sync9-20261028` |
|
||||
| `TASK-upstream-sync9b-20261028.md` | `PROGRESS-upstream-sync9b-20261028.md` | **第 1 次打回(冻结契约排除清单删了第八轮两条,见 `TASK-upstream-sync9b-fix-20261028.md`)** · **#159 上游同步第九轮乙批(产品选 A)**:Sarpa Drekkana 补成五段、第 22 号 Drekkana 改为 D3 第 8 宫;改冻结文件 `varga.py`,校正 20 + 900 与上一份逐条相同后按 ERR-110 重新冻结。Narayana 小运强度口径(B2)不做,另问产品 | 待验收 | `codex/upstream-sync9b-20261028` |
|
||||
| — | — | **#134 咨询答题默认低思考量(产品选 A)**:`CONSULT_ANSWER_REASONING_EFFORT` 不设时默认 `low`,设 `off` 恢复原请求体(Claude 直接执行) | 已推 staging | `codex/answer-effort-default-20261029` |
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# 真机走查 · 咨询回答默认低思考量(issue #134)
|
||||
|
||||
| # | 操作 | 应该看到 |
|
||||
| --- | --- | --- |
|
||||
| 1 | 用一张星盘问一个具体问题(如「今年事业怎么样」) | 回答正常出来,比以前快一截(以前常要半分钟以上) |
|
||||
| 2 | 连问两三个追问 | 每轮都正常回答,引用的宫位、大运和以前一样具体 |
|
||||
| 3 | 问一个时间段(如「明年上半年」) | 照常出时段分析,没有空答或中途断掉 |
|
||||
@@ -177,22 +177,26 @@ export const MAX_EVIDENCE_LOOKUPS_PER_TURN = 3;
|
||||
export const CONSULTATION_WINDOW_CALC_TOOL_ID = "run-jyotish-window-consultation";
|
||||
|
||||
/**
|
||||
* Answer-step reasoning effort. Unset or blank keeps today's request body.
|
||||
* An illegal value warns once and is treated as unset.
|
||||
* Answer-step reasoning effort. Unset or blank means "low" (issue #134: the
|
||||
* A/B halved reasoning tokens and cut answer time ~37% with no quality loss).
|
||||
* "off" sends no reasoning_effort, i.e. the pre-#134 request body.
|
||||
* An illegal value warns once and is treated as "off".
|
||||
*/
|
||||
const CONSULT_ANSWER_REASONING_EFFORTS = ["low", "medium", "high", "max"] as const;
|
||||
export const CONSULT_ANSWER_REASONING_EFFORT_DEFAULT: ProviderReasoningEffort = "low";
|
||||
let consultAnswerReasoningEffortWarned = false;
|
||||
|
||||
export function consultAnswerReasoningEffort(): ProviderReasoningEffort | undefined {
|
||||
const raw = process.env.CONSULT_ANSWER_REASONING_EFFORT;
|
||||
if (raw === undefined || raw.trim() === "") return undefined;
|
||||
if (raw === undefined || raw.trim() === "") return CONSULT_ANSWER_REASONING_EFFORT_DEFAULT;
|
||||
const value = raw.trim();
|
||||
if (value === "off") return undefined;
|
||||
if ((CONSULT_ANSWER_REASONING_EFFORTS as readonly string[]).includes(value)) {
|
||||
return value as ProviderReasoningEffort;
|
||||
}
|
||||
if (!consultAnswerReasoningEffortWarned) {
|
||||
consultAnswerReasoningEffortWarned = true;
|
||||
console.warn(`CONSULT_ANSWER_REASONING_EFFORT=${value} is not low, medium, high, or max; leaving reasoning effort unset.`);
|
||||
console.warn(`CONSULT_ANSWER_REASONING_EFFORT=${value} is not off, low, medium, high, or max; leaving reasoning effort unset.`);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -51,9 +51,12 @@ test("an illegal effort warns once and stays unset", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("unset effort keeps the pre-change request bodies", async () => {
|
||||
// 原值: 不设环境变量时请求体与改动前逐字节相同
|
||||
// 新值: 设成 "off" 时请求体与改动前逐字节相同;不设时默认 "low"(见下一条)
|
||||
// 原因: issue #134 产品选 A,答题步默认低思考量;"off" 是一行回退
|
||||
test("off keeps the pre-change request bodies", async () => {
|
||||
const previous = process.env.CONSULT_ANSWER_REASONING_EFFORT;
|
||||
delete process.env.CONSULT_ANSWER_REASONING_EFFORT;
|
||||
process.env.CONSULT_ANSWER_REASONING_EFFORT = "off";
|
||||
try {
|
||||
const natal = await captureStreamBodies({
|
||||
settings: consultationGenerationSettings(MODEL),
|
||||
@@ -76,6 +79,43 @@ test("unset effort keeps the pre-change request bodies", async () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("unset or blank effort defaults to low", () => {
|
||||
const previous = process.env.CONSULT_ANSWER_REASONING_EFFORT;
|
||||
try {
|
||||
delete process.env.CONSULT_ANSWER_REASONING_EFFORT;
|
||||
assert.equal(consultAnswerReasoningEffort(), "low");
|
||||
process.env.CONSULT_ANSWER_REASONING_EFFORT = " ";
|
||||
assert.equal(consultAnswerReasoningEffort(), "low");
|
||||
process.env.CONSULT_ANSWER_REASONING_EFFORT = "off";
|
||||
assert.equal(consultAnswerReasoningEffort(), undefined);
|
||||
} finally {
|
||||
if (previous === undefined) delete process.env.CONSULT_ANSWER_REASONING_EFFORT;
|
||||
else process.env.CONSULT_ANSWER_REASONING_EFFORT = previous;
|
||||
}
|
||||
});
|
||||
|
||||
test("unset effort puts low on the answer step only", async () => {
|
||||
const previous = process.env.CONSULT_ANSWER_REASONING_EFFORT;
|
||||
delete process.env.CONSULT_ANSWER_REASONING_EFFORT;
|
||||
try {
|
||||
const natal = await captureStreamBodies({
|
||||
settings: consultationGenerationSettings(MODEL),
|
||||
prepareStep: (input) => consultationNatalPrepareStep({
|
||||
stepNumber: input.stepNumber,
|
||||
answerReady: input.stepNumber >= 1,
|
||||
model: MODEL,
|
||||
}),
|
||||
steps: 2,
|
||||
}) as Record<string, unknown>[];
|
||||
assert.deepEqual(natal[0], fixture.natal[0]);
|
||||
assert.equal(natal[1]?.reasoning_effort, "low");
|
||||
assert.deepEqual(withoutEffort(natal[1]), fixture.natal[1]);
|
||||
} finally {
|
||||
if (previous === undefined) delete process.env.CONSULT_ANSWER_REASONING_EFFORT;
|
||||
else process.env.CONSULT_ANSWER_REASONING_EFFORT = previous;
|
||||
}
|
||||
});
|
||||
|
||||
test("low effort is only on the answer step", async () => {
|
||||
const previous = process.env.CONSULT_ANSWER_REASONING_EFFORT;
|
||||
process.env.CONSULT_ANSWER_REASONING_EFFORT = "low";
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -58,6 +59,41 @@ def _isolate_vedastro_snapshot_cache(tmp_path_factory, monkeypatch) -> None:
|
||||
clear_validate_memory()
|
||||
|
||||
|
||||
_FOREGROUND_EXECUTOR_MODULES = (
|
||||
"scripts.vedastro_foreground",
|
||||
"vedastro_foreground",
|
||||
"scripts.jyotish_api_server",
|
||||
"jyotish_api_server",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _isolate_foreground_vedastro_executor(monkeypatch):
|
||||
"""A fresh foreground pool per test, drained at teardown (BUG-1498).
|
||||
|
||||
A task left running stores its outcome under whatever cache directory the
|
||||
environment names when it finishes, which would be the next test's.
|
||||
"""
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
from scripts import vedastro_foreground
|
||||
|
||||
executor = ThreadPoolExecutor(
|
||||
max_workers=vedastro_foreground._FOREGROUND_VEDASTRO_WORKERS,
|
||||
thread_name_prefix="jyotish-vedastro-fg-test",
|
||||
)
|
||||
for name in _FOREGROUND_EXECUTOR_MODULES:
|
||||
module = sys.modules.get(name)
|
||||
if module is not None and hasattr(module, "_FOREGROUND_VEDASTRO_EXECUTOR"):
|
||||
monkeypatch.setattr(module, "_FOREGROUND_VEDASTRO_EXECUTOR", executor)
|
||||
yield
|
||||
# Queued work is dropped; running work gets a bounded wait so a stuck stub cannot hang the run.
|
||||
executor.shutdown(wait=False, cancel_futures=True)
|
||||
deadline = time.monotonic() + 10
|
||||
for thread in list(getattr(executor, "_threads", ())):
|
||||
thread.join(max(deadline - time.monotonic(), 0))
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(items) -> None:
|
||||
for item in items:
|
||||
if item.fspath.basename == "test_api_server_security.py" and item.name.startswith(SLOW_API_SECURITY_PREFIXES):
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
"""BUG-1498: a foreground VedAstro task never outlives the test that started it.
|
||||
|
||||
The two tests run in file order. The first leaves a slow task on the foreground
|
||||
executor; the second checks that the task finished before it began, so it could
|
||||
not write into the second test's cache directory.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
from scripts import vedastro_foreground as foreground
|
||||
|
||||
_SEEN: list[str] = []
|
||||
|
||||
|
||||
def _slow_task() -> None:
|
||||
time.sleep(0.3)
|
||||
_SEEN.append(os.environ.get("JYOTISH_VEDASTRO_SNAPSHOT_NEGATIVE_CACHE_DIR", ""))
|
||||
|
||||
|
||||
def test_a_leaves_slow_work_on_the_foreground_executor() -> None:
|
||||
_SEEN.clear()
|
||||
foreground._FOREGROUND_VEDASTRO_EXECUTOR.submit(_slow_task)
|
||||
|
||||
|
||||
def test_b_does_not_see_the_previous_tests_work() -> None:
|
||||
mine = os.environ["JYOTISH_VEDASTRO_SNAPSHOT_NEGATIVE_CACHE_DIR"]
|
||||
assert len(_SEEN) == 1, "the earlier test's task finished during its own teardown"
|
||||
time.sleep(0.5)
|
||||
assert mine not in _SEEN, "a leftover task wrote into this test's cache directory"
|
||||
Reference in New Issue
Block a user