Просмотр исходного кода

feat(mode_workflow): /api/query_score 触发+取结果(选择哈希缓存,命中短路)

刘文武 1 неделя назад
Родитель
Сommit
10e281dab8
1 измененных файлов с 38 добавлено и 0 удалено
  1. 38 0
      examples/mode_workflow/server.py

+ 38 - 0
examples/mode_workflow/server.py

@@ -73,6 +73,16 @@ def _category_tree(source_type):
     return data
     return data
 
 
 
 
+SCORE_CACHE_DIR = HERE / ".cache" / "query_score"
+
+
+def _query_sel_hash(sel):
+    """对评分选择 + 矩阵指纹算稳定短哈希,作为缓存文件名(同选择不重复付费)。"""
+    matrix_sig = hashlib.md5(MATRIX_FILE.read_bytes()).hexdigest()[:8]
+    canon = json.dumps([sel, matrix_sig], ensure_ascii=False, sort_keys=True)
+    return hashlib.md5(canon.encode("utf-8")).hexdigest()[:16]
+
+
 LOG_DIR = HERE / "runs" / "logs"
 LOG_DIR = HERE / "runs" / "logs"
 # 工序步骤 via 字段的「无具体工具」占位符,不计入工序提及工具 TOP 榜
 # 工序步骤 via 字段的「无具体工具」占位符,不计入工序提及工具 TOP 榜
 _VIA_PLACEHOLDERS = {"-", "—", "-", "--", "/", "无", "n/a", "none"}
 _VIA_PLACEHOLDERS = {"-", "—", "-", "--", "/", "无", "n/a", "none"}
@@ -461,6 +471,12 @@ class Handler(BaseHTTPRequestHandler):
                 self._json_etag(_matrix())
                 self._json_etag(_matrix())
             elif u.path == "/api/category_tree":
             elif u.path == "/api/category_tree":
                 self._json_etag(_category_tree(qs.get("source_type", "实质")))
                 self._json_etag(_category_tree(qs.get("source_type", "实质")))
+            elif u.path == "/api/query_score":
+                cache = SCORE_CACHE_DIR / f"{qs.get('sel', '')}.json"
+                if cache.is_file():
+                    self._json_etag(json.loads(cache.read_text(encoding="utf-8")))
+                else:
+                    self._json({"pending": True}, 202)
             elif u.path == "/api/dashboard":
             elif u.path == "/api/dashboard":
                 self._json_etag(_dashboard_cached())
                 self._json_etag(_dashboard_cached())
             elif u.path == "/api/queries":
             elif u.path == "/api/queries":
@@ -564,6 +580,28 @@ class Handler(BaseHTTPRequestHandler):
                     _release_cases(mode, claimed)   # 起进程失败也要释放认领,避免卡住
                     _release_cases(mode, claimed)   # 起进程失败也要释放认领,避免卡住
                     raise
                     raise
                 self._json({"task_id": task_id, "skipped": skipped})
                 self._json({"task_id": task_id, "skipped": skipped})
+            elif u.path == "/api/query_score":
+                sel = {
+                    "tool_type": payload.get("tool_type", ""),
+                    "modality": payload.get("modality", ""),
+                    "suffix": payload.get("suffix", ""),
+                    "substance_path": payload.get("substance_path") or [],
+                    "form_path": payload.get("form_path") or [],
+                    "model": payload.get("model", "anthropic/claude-sonnet-4-6"),
+                }
+                sel_hash = _query_sel_hash(sel)
+                cache = SCORE_CACHE_DIR / f"{sel_hash}.json"
+                if cache.is_file() and not payload.get("force"):
+                    return self._json({"sel": sel_hash, "cached": True})
+                cmd = [sys.executable, "stages/query_score.py", "--sel", sel_hash,
+                       "--tool-type", sel["tool_type"], "--modality", sel["modality"],
+                       "--suffix", sel["suffix"],
+                       "--substance-path", ",".join(sel["substance_path"]),
+                       "--form-path", ",".join(sel["form_path"]),
+                       "--model", sel["model"]]
+                if payload.get("force"):
+                    cmd += ["--force"]
+                self._json({"sel": sel_hash, "task_id": _spawn_task("score", cmd), "cached": False})
             elif u.path == "/api/run_search":
             elif u.path == "/api/run_search":
                 query = (payload.get("query") or "").strip()
                 query = (payload.get("query") or "").strip()
                 if not query:
                 if not query: