fix(capabilities): S2 指代消解指令增强 + history 重写 + selfcheck 单进程化
- answer_composer: Dialogue Context 指令明确化(含指代词时必按历史确定指代对象) - sdk/services/history.py: 重写为两段式(先组轮次对、再截断),修正轮次配对逻辑 - sdk/app/test_history.py: FakeDB 模拟 SQL streaming 过滤, 7 用例全绿 - tooling/cap/cap_selfcheck.py: 单进程内完成热加载证明(diff 才有意义), GENE_FAMILY_ROOT 显式注入子进程, manifest 改字符串拼接避开 format 花括号 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -272,7 +272,8 @@ When citing a paper for the first time, you may insert a marker [[FIG:PMCID]] on
|
||||
lines = [
|
||||
"--- Dialogue Context ---",
|
||||
"",
|
||||
"以下是本会话此前的问答记录,仅供消解指代(如「它」「该基因」)使用。",
|
||||
"以下是本会话此前的问答记录。若当前问题包含指代词(如「它」「该基因」「这些成员」「上面提到的」),",
|
||||
"必须用本段记录确定指代对象,再按指代对象结合 Data Report 回答;答案开头先点明指代对象。",
|
||||
"除指代消解外,不得据此补充新事实;一切事实必须来自 Data Report / GraphChains。",
|
||||
]
|
||||
for msg in history:
|
||||
|
||||
+96
-118
@@ -1,48 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
"""tooling/cap/cap_selfcheck.py —— 机制端到端自检(假 MCP server + 热加载证明)。
|
||||
"""tooling/cap/cap_selfcheck.py —— 机制端到端自检(单进程热加载证明)。
|
||||
|
||||
流程(全程同一 QA 进程,不重启):
|
||||
1. 加载现有 capabilities.yaml → registry 工具数
|
||||
2. 临时注册假 MCP echo 工具(manifest 落盘)→ reload → 工具表出现 echo
|
||||
3. 实际调用 echo(stdio 子进程)→ 返回正确
|
||||
4. 停用(移除 manifest)→ reload → 工具表消失、MCP 连接关闭
|
||||
流程(单一子进程内完成,与生产同语义——registry 在启动时加载一次,
|
||||
之后的 yaml 变化必须靠 reload() 热加载,diff 才有意义):
|
||||
1. 初始工具表(空 yaml → 无 fake_echo)
|
||||
2. install_manager.register_manifest 落盘 → reload → diff.added 含 fake_echo
|
||||
3. 实际调用 echo(MCP stdio 子进程)→ 返回正确
|
||||
4. unregister → reload → diff.removed 含 fake_echo、工具表为空
|
||||
5. 恢复原 capabilities.yaml
|
||||
|
||||
用法:
|
||||
QA_ENV_FILE=<prod.env> python3 cap_selfcheck.py <qa-config-dir>
|
||||
QA_PYTHON=<venv python> python3 cap_selfcheck.py <qa-config-dir>
|
||||
|
||||
<qa-config-dir> = deploy 树根(含 configs/capabilities.yaml),
|
||||
即 $GENE_FAMILY_ROOT 的值。测试中的 yaml 落盘用其真实路径,因此会短暂修改
|
||||
配置文件;本脚本保证恢复。
|
||||
<qa-config-dir> = 编译树根(含 configs/capabilities.yaml),即 GENE_FAMILY_ROOT。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
TOOL_DIR = Path(__file__).parent
|
||||
FAKE_MCP = TOOL_DIR / "fake_mcp_server.py"
|
||||
|
||||
FAKE_MANIFEST = """
|
||||
capabilities:
|
||||
- name: fake_echo
|
||||
provider: mcp
|
||||
permission: read_only
|
||||
description: 自检用 echo 工具
|
||||
session_default: true
|
||||
schema:
|
||||
text: {type: string, required: true, description: 要回显的文本}
|
||||
mcp:
|
||||
command: {python}
|
||||
args: ["{fake_mcp}"]
|
||||
tool: echo
|
||||
"""
|
||||
INNER = textwrap.dedent(
|
||||
"""
|
||||
import json, sys, textwrap
|
||||
from pathlib import Path
|
||||
|
||||
root = Path(sys.argv[1])
|
||||
sys.path.insert(0, str(root))
|
||||
sys.path.insert(0, str(root / "src"))
|
||||
|
||||
from src.rag.qa_v1_1_73.capabilities.registry import get_registry
|
||||
from src.rag.qa_v1_1_73.capabilities.install_manager import InstallManager, get_install_manager
|
||||
from src.rag.qa_v1_1_73.capabilities.providers import get_dispatcher
|
||||
|
||||
caps_yaml = root / "configs" / "capabilities.yaml"
|
||||
original = caps_yaml.read_text(encoding="utf-8")
|
||||
|
||||
manifest = (
|
||||
"capabilities:\\n"
|
||||
" - name: fake_echo\\n"
|
||||
" provider: mcp\\n"
|
||||
" permission: read_only\\n"
|
||||
" description: 自检用 echo 工具\\n"
|
||||
" session_default: true\\n"
|
||||
" schema:\\n"
|
||||
" text: {type: string, required: true, description: 要回显的文本}\\n"
|
||||
" mcp:\\n"
|
||||
" command: " + sys.argv[2] + "\\n"
|
||||
" args: [\\"" + sys.argv[3] + "\\"]\\n"
|
||||
" tool: echo\\n"
|
||||
)
|
||||
|
||||
results = {}
|
||||
try:
|
||||
# 1. 初始工具表
|
||||
r = get_registry()
|
||||
results["initial_tools"] = [t.name for t in r.list_all()]
|
||||
assert "fake_echo" not in results["initial_tools"], "预置状态异常"
|
||||
|
||||
# 2. 安装 + 热加载(同进程,diff 有意义)
|
||||
manager = InstallManager(caps_yaml)
|
||||
manifests, warnings = manager.register_manifest(manifest)
|
||||
assert warnings == [] and [m.name for m in manifests] == ["fake_echo"]
|
||||
w, diff = manager.reload(registry=r)
|
||||
assert "fake_echo" in diff["added"], f"热加载未新增: {diff}"
|
||||
results["hot_reload_added"] = diff["added"]
|
||||
results["registry_after_add"] = [t.name for t in r.list_all()]
|
||||
|
||||
# 3. MCP 实际调用
|
||||
m = r.get("fake_echo").manifest
|
||||
out = get_dispatcher().execute(m, {"text": "你好机制"})
|
||||
assert "你好机制" in out, f"echo 返回异常: {out}"
|
||||
results["mcp_call_ok"] = True
|
||||
|
||||
# 4. 卸载 + 热卸载(同进程)
|
||||
assert manager.unregister("fake_echo")
|
||||
w, diff = manager.reload(registry=r)
|
||||
assert "fake_echo" in diff["removed"], f"热卸载未移除: {diff}"
|
||||
get_dispatcher().shutdown_connections(diff["removed"])
|
||||
results["hot_reload_removed"] = diff["removed"]
|
||||
results["registry_after_remove"] = [t.name for t in r.list_all()]
|
||||
|
||||
# 5. 恢复
|
||||
caps_yaml.write_text(original, encoding="utf-8")
|
||||
r.reload()
|
||||
results["restored"] = True
|
||||
|
||||
print("SELFCHECK PASS:", json.dumps(results, ensure_ascii=False, indent=2))
|
||||
except Exception as exc:
|
||||
print(f"SELFCHECK FAIL: {exc}")
|
||||
caps_yaml.write_text(original, encoding="utf-8")
|
||||
sys.exit(1)
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def _python() -> str:
|
||||
@@ -51,102 +107,24 @@ def _python() -> str:
|
||||
)
|
||||
|
||||
|
||||
def _qa_import(root: Path):
|
||||
"""在目标 QA 树环境里跑一段 python(用 deploy 树的 venv + PYTHONPATH)。"""
|
||||
env = dict(os.environ)
|
||||
env["PYTHONPATH"] = f"{root}:{root}/src" + (
|
||||
f":{env['PYTHONPATH']}" if env.get("PYTHONPATH") else ""
|
||||
)
|
||||
return env
|
||||
|
||||
|
||||
def run_step(root: Path, code: str) -> str:
|
||||
env = _qa_import(root)
|
||||
proc = subprocess.run(
|
||||
[_python(), "-c", code],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120,
|
||||
env=env,
|
||||
cwd=str(root),
|
||||
)
|
||||
if proc.returncode != 0:
|
||||
raise RuntimeError(f"step failed: {proc.stderr[-2000:]}")
|
||||
return proc.stdout.strip()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
root = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else None
|
||||
if root is None or not (root / "configs").is_dir():
|
||||
print(__doc__)
|
||||
return 2
|
||||
caps_yaml = root / "configs" / "capabilities.yaml"
|
||||
original = caps_yaml.read_text(encoding="utf-8") if caps_yaml.is_file() else "capabilities: []\n"
|
||||
|
||||
manifest = FAKE_MANIFEST.format(python=_python(), fake_mcp=FAKE_MCP)
|
||||
results = {}
|
||||
try:
|
||||
# 1. 初始工具表
|
||||
out = run_step(root, (
|
||||
"from src.rag.qa_v1_1_73.capabilities.registry import get_registry;"
|
||||
"r=get_registry();"
|
||||
"print(json.dumps({'tools':[t.name for t in r.list_all()],"
|
||||
"'warnings':r.load_warnings}, ensure_ascii=False))"
|
||||
))
|
||||
initial = json.loads(out)
|
||||
results["initial_tools"] = initial["tools"]
|
||||
assert "fake_echo" not in initial["tools"], "预置状态异常:fake_echo 已在工具表"
|
||||
|
||||
# 2. 注册 + reload(热加载)
|
||||
caps_yaml.write_text(manifest, encoding="utf-8")
|
||||
out = run_step(root, (
|
||||
"from src.rag.qa_v1_1_73.capabilities.registry import get_registry;"
|
||||
"w,d=get_registry().reload();"
|
||||
"print(json.dumps({'warnings':w,'diff':d}, ensure_ascii=False))"
|
||||
))
|
||||
reloaded = json.loads(out)
|
||||
assert "fake_echo" in reloaded["diff"]["added"], "热加载未新增 fake_echo"
|
||||
results["hot_reload_added"] = reloaded["diff"]["added"]
|
||||
|
||||
# 3. 实际调用(MCP stdio 子进程)
|
||||
out = run_step(root, (
|
||||
"from src.rag.qa_v1_1_73.capabilities.registry import get_registry;"
|
||||
"from src.rag.qa_v1_1_73.capabilities.providers import get_dispatcher;"
|
||||
"m=get_registry().get('fake_echo').manifest;"
|
||||
"r=get_dispatcher().execute(m, {'text':'你好机制'});"
|
||||
"print(r)"
|
||||
))
|
||||
assert "你好机制" in out, f"echo 返回异常: {out}"
|
||||
results["mcp_call_ok"] = True
|
||||
|
||||
# 4. 停用 + reload(热卸载)
|
||||
caps_yaml.write_text("capabilities: []\n", encoding="utf-8")
|
||||
out = run_step(root, (
|
||||
"from src.rag.qa_v1_1_73.capabilities.registry import get_registry;"
|
||||
"from src.rag.qa_v1_1_73.capabilities.providers import get_dispatcher;"
|
||||
"w,d=get_registry().reload();"
|
||||
"get_dispatcher().shutdown_connections(d['removed']);"
|
||||
"print(json.dumps({'warnings':w,'diff':d}, ensure_ascii=False))"
|
||||
))
|
||||
unloaded = json.loads(out)
|
||||
assert "fake_echo" in unloaded["diff"]["removed"], "热卸载未移除 fake_echo"
|
||||
results["hot_reload_removed"] = unloaded["diff"]["removed"]
|
||||
|
||||
# 5. 恢复原文件
|
||||
caps_yaml.write_text(original, encoding="utf-8")
|
||||
run_step(root, (
|
||||
"from src.rag.qa_v1_1_73.capabilities.registry import get_registry;"
|
||||
"get_registry().reload();print('restored')"
|
||||
))
|
||||
results["restored"] = True
|
||||
|
||||
print("SELFCHECK PASS:", json.dumps(results, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
except Exception as exc:
|
||||
print(f"SELFCHECK FAIL: {exc}")
|
||||
return 1
|
||||
finally:
|
||||
caps_yaml.write_text(original, encoding="utf-8")
|
||||
env = dict(os.environ)
|
||||
env["GENE_FAMILY_ROOT"] = str(root)
|
||||
proc = subprocess.run(
|
||||
[_python(), "-c", INNER, str(root), _python(), str(FAKE_MCP)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=180,
|
||||
env=env,
|
||||
cwd=str(root),
|
||||
)
|
||||
sys.stdout.write(proc.stdout)
|
||||
sys.stderr.write(proc.stderr[-3000:])
|
||||
return proc.returncode
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user