116 lines
4.5 KiB
Python
116 lines
4.5 KiB
Python
"""常驻检索进程。标准输出只用于 JSON 协议,初始化和检索诊断写标准错误。"""
|
||
import sys
|
||
import json
|
||
import time
|
||
from contextlib import redirect_stdout
|
||
from pathlib import Path
|
||
|
||
# 从任意 cwd 启动都能找到 app 包(uvicorn 工作目录不保证是 server/)
|
||
_SERVER_ROOT = Path(__file__).resolve().parents[2]
|
||
if str(_SERVER_ROOT) not in sys.path:
|
||
sys.path.insert(0, str(_SERVER_ROOT))
|
||
|
||
|
||
def initialize():
|
||
"""每个 worker 仅初始化一次,避免每次查询重复执行迁移和历史表扫描。"""
|
||
try:
|
||
import fcntl
|
||
except ImportError:
|
||
fcntl = None
|
||
from app import db as db_mod
|
||
from app.config import DATA_DIR
|
||
# 多个首次查询同时启动进程时,不并发执行相同的数据库迁移。
|
||
with (DATA_DIR / 'rag-worker-init.lock').open('a') as lock:
|
||
if fcntl:
|
||
fcntl.flock(lock, fcntl.LOCK_EX)
|
||
db_mod.init_db()
|
||
|
||
|
||
def handle(payload: dict) -> list:
|
||
from sqlmodel import Session
|
||
from app import db as db_mod
|
||
from app.rag import pipeline as rag_pipeline
|
||
|
||
with Session(db_mod.engine) as session:
|
||
results = rag_pipeline.search_kbs(
|
||
session,
|
||
payload["kb_ids"],
|
||
payload["query"],
|
||
payload.get("k", 6),
|
||
excluded_chunk_ids={int(k): set(v) for k, v in (payload.get("excluded") or {}).items()},
|
||
campaign_id=payload.get("campaign_id", 0),
|
||
trace_id=payload.get("trace_id", ""),
|
||
user_id=payload.get("user_id", 0),
|
||
organization_id=payload.get("organization_id", 1),
|
||
file_whitelists={int(k): set(v) for k, v in (payload.get("file_whitelists") or {}).items()},
|
||
)
|
||
# 透传库名、召回来源、重排分数和各阶段耗时,不裁掉检索诊断信息。
|
||
return results
|
||
|
||
|
||
def handle_warmup() -> dict:
|
||
"""Warm the resident worker without issuing a billable retrieval."""
|
||
from app.rag import embed, store, rerank
|
||
started = time.monotonic()
|
||
embed.prewarm()
|
||
# Optional local Cross-Encoder is loaded in the resident worker so the
|
||
# first real retrieval does not pay model initialization latency.
|
||
rerank.prewarm()
|
||
# Construct the persistent vector client once. Collections remain opened
|
||
# lazily per KB, while the client and embedding runtime stay resident.
|
||
store.get_client()
|
||
return {"warm_state": "warm", "warmup_ms": round((time.monotonic() - started) * 1000, 2)}
|
||
|
||
|
||
def main():
|
||
protocol_out = sys.stdout
|
||
try:
|
||
with redirect_stdout(sys.stderr):
|
||
initialize()
|
||
except Exception as exc:
|
||
from app.system_log import redact
|
||
protocol_out.write(json.dumps({'ok': False, 'error': '检索进程初始化失败:' + redact(exc, 400)}, ensure_ascii=False) + '\n')
|
||
protocol_out.flush()
|
||
return
|
||
for line in sys.stdin:
|
||
line = line.strip()
|
||
if not line:
|
||
continue
|
||
try:
|
||
started = time.monotonic()
|
||
with redirect_stdout(sys.stderr):
|
||
payload = json.loads(line)
|
||
if payload.get("op") == "warmup":
|
||
warm = handle_warmup()
|
||
protocol_out.write(json.dumps({"ok": True, **warm}, ensure_ascii=False) + "\n")
|
||
protocol_out.flush()
|
||
continue
|
||
out = handle(payload)
|
||
from app.rag import pipeline as _pipeline
|
||
metric = _pipeline.last_retrieval_metric()
|
||
envelope = {"ok": True, "results": out,
|
||
"worker_timings": {"search_ms": round((time.monotonic() - started) * 1000, 2)},
|
||
"retrieval_metric": metric}
|
||
serialization_started = time.perf_counter()
|
||
json.dumps(envelope, ensure_ascii=False)
|
||
metric["serialization_ms"] = round((time.perf_counter() - serialization_started) * 1000, 2)
|
||
envelope["retrieval_metric"] = metric
|
||
protocol_out.write(json.dumps(envelope, ensure_ascii=False) + "\n")
|
||
except Exception as exc:
|
||
from app.system_log import redact
|
||
metric = {}
|
||
try:
|
||
from app.rag import pipeline as _pipeline
|
||
metric = _pipeline.last_retrieval_metric()
|
||
except Exception:
|
||
pass
|
||
if not metric:
|
||
metric = {"error": "retrieval_failed"}
|
||
protocol_out.write(json.dumps({"ok": False, "error": redact(exc, 400),
|
||
"retrieval_metric": metric}, ensure_ascii=False) + "\n")
|
||
protocol_out.flush()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|