From 04c08e83392201e01a82fdb3b165257087e7b37b Mon Sep 17 00:00:00 2001 From: admin Date: Mon, 7 Sep 2026 23:07:19 +0800 Subject: [PATCH] =?UTF-8?q?fix(priv):=20R1=20=E7=BC=BA=E9=99=B7=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E2=80=94=E2=80=94=E4=BF=A1=E5=B0=81=E5=8C=85=20ok=20?= =?UTF-8?q?=E9=94=AE=20+=20=E7=A9=BA=20question=20422=20+=20=E8=87=AA?= =?UTF-8?q?=E8=B0=A6=E7=AD=94=E8=AF=AD=E7=A1=AE=E5=AE=9A=E6=80=A7=E6=94=B9?= =?UTF-8?q?=E5=88=A4=20hit:false?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - private_docs.py: 命中/未命中两态响应包 ok:true(错误信封 ok:false 不变) - schemas.py: PrivateQARequest.question 加 min_length=1 - privateqa.py: LLM 自谦答语(资料中未找到相关信息+等价变体)确定性改判 {hit:false,mode:empty},不调默认阈值(底噪 0.3-0.37 与真实命中 0.42-0.73 有重叠) Co-Authored-By: Claude Fable 5 --- sdk/app/routers/private_docs.py | 7 +++++-- sdk/app/schemas.py | 2 +- sdk/app/services/privateqa.py | 25 +++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/sdk/app/routers/private_docs.py b/sdk/app/routers/private_docs.py index a7b721f..e7df754 100644 --- a/sdk/app/routers/private_docs.py +++ b/sdk/app/routers/private_docs.py @@ -43,13 +43,16 @@ def _envelope( @router.post("/private") async def private_qa(req: PrivateQARequest) -> dict[str, Any]: - """私有资料问答:命中/未命中 200;上游故障转错误信封。""" + """私有资料问答:命中/未命中 200(均带 ok:true 信封,BUG-PRIV-R1-01); + 上游故障转错误信封(ok:false)。""" try: - return await ask_private( + result = await ask_private( req.question, top_k=req.top_k, min_score=req.min_score, ) + # BUG-PRIV-R1-01(TL 裁决):命中与未命中两态响应均包 ok:true + return {"ok": True, **result} except DocgenUnavailable as exc: logger.warning("privateqa: vstore 不可达:%s", exc) return _envelope("UPSTREAM_DOWN", message="私有资料检索服务不可用,请稍后重试") diff --git a/sdk/app/schemas.py b/sdk/app/schemas.py index 8094955..2b3a3ad 100644 --- a/sdk/app/schemas.py +++ b/sdk/app/schemas.py @@ -236,7 +236,7 @@ class PrivateDocBlock(BaseModel): class PrivateQARequest(BaseModel): """POST /api/qa/private 请求体(FR-PRIV-004)。""" - question: str + question: str = Field(..., min_length=1) # BUG-PRIV-R1-02:空串 422(VALIDATION 信封) session_id: str | None = None top_k: int = 8 min_score: float = 0.30 diff --git a/sdk/app/services/privateqa.py b/sdk/app/services/privateqa.py index 54adf2e..bd5866f 100644 --- a/sdk/app/services/privateqa.py +++ b/sdk/app/services/privateqa.py @@ -35,6 +35,26 @@ SYSTEM_PROMPT = ( "300 字内简体中文 markdown。" ) +# BUG-PRIV-R1-03(TL 裁决):system prompt 固定的自谦答语。LLM 返回匹配 +# 任一即改判未命中(底噪越线时不展示低质块;不调默认阈值)。 +SELF_DEPRECATING_ANSWERS = ( + "资料中未找到相关信息", + "资料中没有相关信息", + "资料中未找到相关的内容", + "资料中没有相关的内容", + "资料中找不到相关信息", + "未找到相关信息", + "没有找到相关信息", +) + + +def _is_self_deprecating(text: str) -> bool: + """确定性自谦答语判断(子串匹配)。""" + src = (text or "").strip() + if not src: + return True # 空答案视同未命中 + return any(phrase in src for phrase in SELF_DEPRECATING_ANSWERS) + def _safe_float(value: Any) -> bool: try: @@ -116,6 +136,11 @@ async def ask_private( max_tokens=PRIVATE_QA_MAX_TOKENS, timeout=PRIVATE_QA_LLM_TIMEOUT, ) + # BUG-PRIV-R1-03(TL 裁决):LLM 自谦答语 → 改判未命中。 + # vstore 检索先行行为不变(search-chunks 仍返回 items 供内部判断), + # 只是最终响应改判,前端零痕迹。 + if _is_self_deprecating(answer): + return {"hit": False, "mode": "empty", "answer": "", "items": []} elapsed_ms = int((time.perf_counter() - started) * 1000) return { "hit": True,