2026-09-02 13:00:35 +08:00
|
|
|
|
#!/usr/bin/env python3
|
2026-09-02 14:42:12 +08:00
|
|
|
|
"""tooling/cap/cap_selfcheck.py —— 机制端到端自检(单进程热加载证明)。
|
|
|
|
|
|
|
|
|
|
|
|
流程(单一子进程内完成,与生产同语义——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、工具表为空
|
2026-09-02 13:00:35 +08:00
|
|
|
|
5. 恢复原 capabilities.yaml
|
|
|
|
|
|
|
|
|
|
|
|
用法:
|
2026-09-02 14:42:12 +08:00
|
|
|
|
QA_PYTHON=<venv python> python3 cap_selfcheck.py <qa-config-dir>
|
2026-09-02 13:00:35 +08:00
|
|
|
|
|
2026-09-02 14:42:12 +08:00
|
|
|
|
<qa-config-dir> = 编译树根(含 configs/capabilities.yaml),即 GENE_FAMILY_ROOT。
|
2026-09-02 13:00:35 +08:00
|
|
|
|
"""
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
|
import os
|
|
|
|
|
|
import subprocess
|
|
|
|
|
|
import sys
|
2026-09-02 14:42:12 +08:00
|
|
|
|
import textwrap
|
2026-09-02 13:00:35 +08:00
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
TOOL_DIR = Path(__file__).parent
|
|
|
|
|
|
FAKE_MCP = TOOL_DIR / "fake_mcp_server.py"
|
|
|
|
|
|
|
2026-09-02 14:42:12 +08:00
|
|
|
|
INNER = textwrap.dedent(
|
|
|
|
|
|
"""
|
|
|
|
|
|
import json, sys, textwrap
|
|
|
|
|
|
from pathlib import Path
|
2026-09-02 13:00:35 +08:00
|
|
|
|
|
2026-09-02 14:42:12 +08:00
|
|
|
|
root = Path(sys.argv[1])
|
|
|
|
|
|
sys.path.insert(0, str(root))
|
|
|
|
|
|
sys.path.insert(0, str(root / "src"))
|
2026-09-02 13:00:35 +08:00
|
|
|
|
|
2026-09-02 14:42:12 +08:00
|
|
|
|
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
|
2026-09-02 13:00:35 +08:00
|
|
|
|
|
|
|
|
|
|
caps_yaml = root / "configs" / "capabilities.yaml"
|
2026-09-02 14:42:12 +08:00
|
|
|
|
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"
|
|
|
|
|
|
)
|
2026-09-02 13:00:35 +08:00
|
|
|
|
|
|
|
|
|
|
results = {}
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 1. 初始工具表
|
2026-09-02 14:42:12 +08:00
|
|
|
|
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": "你好机制"})
|
2026-09-02 13:00:35 +08:00
|
|
|
|
assert "你好机制" in out, f"echo 返回异常: {out}"
|
|
|
|
|
|
results["mcp_call_ok"] = True
|
|
|
|
|
|
|
2026-09-02 14:42:12 +08:00
|
|
|
|
# 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. 恢复
|
2026-09-02 13:00:35 +08:00
|
|
|
|
caps_yaml.write_text(original, encoding="utf-8")
|
2026-09-02 14:42:12 +08:00
|
|
|
|
r.reload()
|
2026-09-02 13:00:35 +08:00
|
|
|
|
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")
|
2026-09-02 14:42:12 +08:00
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
"""
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _python() -> str:
|
|
|
|
|
|
return os.environ.get(
|
|
|
|
|
|
"QA_PYTHON", "/home/xzq/5090/teamWorkspace/zh/plantai-rag-new-venv/bin/python"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
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
|
2026-09-02 13:00:35 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
raise SystemExit(main())
|