"""人工检验队列(ReviewQueue)—— 第三个协作者(纯标准库 sqlite3)。 复用同一"交流文本"协议:异步队列,不阻塞响应路径。系统交付后按抽样率或 safety 标签强制规则入队,由人工审核给出 verdict(approve/edit/reject)并可 回写修正数据(correction),用于后续质量分析(论文 E 实验的人工地基)。 对齐《实现方案_v2》5.1 T8: - SQLite 存储(data/review.sqlite3),零第三方依赖(sqlite3 为标准库)。 - enqueue / get / list / submit(verdict, correction)。 - 抽样规则:brief.tags 命中 force_tags(如 safety)强制入队,否则按 sample_rate 随机抽样。 """ from __future__ import annotations import random import sqlite3 import threading import uuid from datetime import datetime, timezone from pathlib import Path from typing import Any, Dict, List, Optional def _now_iso() -> str: return datetime.now(timezone.utc).isoformat(timespec="seconds") # 抽样用系统级随机源(CSPRNG) _SYSTEM_RANDOM = random.SystemRandom() _SCHEMA = """ CREATE TABLE IF NOT EXISTS reviews ( id INTEGER PRIMARY KEY AUTOINCREMENT, request_id TEXT NOT NULL, query TEXT NOT NULL, response TEXT NOT NULL, tags TEXT NOT NULL DEFAULT '[]', status TEXT NOT NULL DEFAULT 'pending', -- pending | reviewed verdict TEXT, -- approve | edit | reject correction TEXT, reviewer TEXT, reason TEXT, workspace_path TEXT, created_at TEXT NOT NULL, reviewed_at TEXT ); CREATE INDEX IF NOT EXISTS idx_reviews_status ON reviews(status); """ class ReviewQueue: """人工检验队列(sqlite 后端)。方法为同步;调用方按需自行放入线程池。""" def __init__(self, db_path: str = "data/review.sqlite3"): self.db_path = Path(db_path) self.db_path.parent.mkdir(parents=True, exist_ok=True) self._lock = threading.Lock() self._init_db() def _connect(self) -> sqlite3.Connection: conn = sqlite3.connect(str(self.db_path)) conn.row_factory = sqlite3.Row return conn def _init_db(self) -> None: with self._lock, self._connect() as conn: conn.executescript(_SCHEMA) # --------------------------------------------------------------- # 抽样策略 # --------------------------------------------------------------- @staticmethod def should_enqueue(tags: List[str], sample_rate: float = 0.10, force_tags: Optional[List[str]] = None, rng: Optional[random.Random] = None) -> bool: """是否应入队:tags 命中 force_tags 强制;否则按 sample_rate 抽样。 缺省用系统级 CSPRNG(不可预测,不可被时间种子影响抽样公平性)。 """ force = force_tags or [] if any(t in force for t in tags): return True rng = rng or _SYSTEM_RANDOM return rng.random() < sample_rate # --------------------------------------------------------------- # 入队 # --------------------------------------------------------------- def enqueue(self, request_id: str, query: str, response: str, tags: Optional[List[str]] = None, reason: str = "sample", workspace_path: Optional[str] = None) -> int: """入队一条待审记录,返回 review id。""" tags_json = _json_dumps(tags or []) with self._lock, self._connect() as conn: cur = conn.execute( "INSERT INTO reviews (request_id, query, response, tags, reason, workspace_path, created_at)" " VALUES (?,?,?,?,?,?,?)", (request_id, query, response, tags_json, reason, workspace_path, _now_iso()), ) return int(cur.lastrowid) # --------------------------------------------------------------- # 查询 # --------------------------------------------------------------- def get(self, review_id: int) -> Optional[Dict[str, Any]]: with self._lock, self._connect() as conn: row = conn.execute("SELECT * FROM reviews WHERE id=?", (review_id,)).fetchone() return _row_to_dict(row) if row else None def list(self, status: Optional[str] = None, limit: int = 50) -> List[Dict[str, Any]]: with self._lock, self._connect() as conn: if status: rows = conn.execute( "SELECT * FROM reviews WHERE status=? ORDER BY id DESC LIMIT ?", (status, limit)).fetchall() else: rows = conn.execute( "SELECT * FROM reviews ORDER BY id DESC LIMIT ?", (limit,)).fetchall() return [_row_to_dict(r) for r in rows] def count(self, status: Optional[str] = None) -> int: with self._lock, self._connect() as conn: if status: row = conn.execute("SELECT COUNT(*) AS c FROM reviews WHERE status=?", (status,)).fetchone() else: row = conn.execute("SELECT COUNT(*) AS c FROM reviews").fetchone() return int(row["c"]) if row else 0 # --------------------------------------------------------------- # 审核提交 # --------------------------------------------------------------- def submit(self, review_id: int, verdict: str, correction: Optional[str] = None, reviewer: Optional[str] = None) -> bool: """提交审核结论。verdict: approve | edit | reject。返回是否更新成功。""" if verdict not in ("approve", "edit", "reject"): raise ValueError(f"非法 verdict: {verdict}(支持 approve|edit|reject)") with self._lock, self._connect() as conn: cur = conn.execute( "UPDATE reviews SET status='reviewed', verdict=?, correction=?, reviewed_at=?, reviewer=?" " WHERE id=? AND status='pending'", (verdict, correction, _now_iso(), reviewer, review_id), ) return cur.rowcount > 0 def _row_to_dict(row: Optional[sqlite3.Row]) -> Optional[Dict[str, Any]]: if row is None: return None d = dict(row) try: import json d["tags"] = json.loads(d.get("tags") or "[]") except Exception: d["tags"] = [] return d def _json_dumps(obj) -> str: import json return json.dumps(obj, ensure_ascii=False) def build_review_queue(cfg: Dict[str, Any]) -> ReviewQueue: """cfg 为 config.review 段。""" return ReviewQueue(db_path=cfg.get("queue_db", "data/review.sqlite3"))