From 05e5c6bff24bb50d8bd2a956bada8cbc13e7ec4e Mon Sep 17 00:00:00 2001 From: yumoqing Date: Fri, 18 Sep 2026 10:48:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(tech-split):=20tech=E8=AF=84=E4=BC=B0?= =?UTF-8?q?=E9=A1=B9=E6=8A=BD=E5=8F=96=E6=8E=A5=E6=9C=BA=E6=A2=B0=E6=8B=86?= =?UTF-8?q?=E8=A7=A3(2026-09-18=E7=94=A8=E6=88=B7=E8=A3=81=E5=AE=9A)+LENGT?= =?UTF-8?q?H=E5=AD=97=E8=8A=82=E5=8F=A3=E5=BE=84=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E2=80=94=E2=80=94=E2=91=A0=5Freconcile=5Ftech=5Fanalysis=5Fqc?= =?UTF-8?q?=E6=B4=BE=E5=8F=91=E6=AE=B5:=E6=AD=A3=E6=96=87=E8=B6=85split=5F?= =?UTF-8?q?chars=E6=8B=86N=E6=AE=B5=E5=B9=B6=E8=A1=8C=E5=AD=90=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1(char=5Frange=E4=BA=92=E4=B8=8D=E9=87=8D=E5=8F=A0,?= =?UTF-8?q?=E5=9B=9B=E7=BB=B4=E5=BA=A6A=E5=9D=97=E5=90=8C=E6=AC=BE);?= =?UTF-8?q?=E5=8D=95=E4=BD=93=E5=85=A8=E6=96=87=E6=8A=BD=E5=8F=96=E5=AE=9E?= =?UTF-8?q?=E6=B5=8B=E5=BC=8A=E7=97=85(=E7=94=98=E8=82=83r1):2.7=E5=B0=8F?= =?UTF-8?q?=E6=97=B6=E4=B8=B2=E8=A1=8C+agent=E8=87=AA=E9=80=89offset?= =?UTF-8?q?=E6=AD=A5=E8=BF=9B(700-1000=E5=AD=97=E7=AC=A6)=E7=AA=97?= =?UTF-8?q?=E5=8F=A3=E5=A4=A7=E9=87=8F=E9=87=8D=E5=8F=A0,(category,item=5F?= =?UTF-8?q?name)=E7=B2=BE=E7=A1=AE=E5=8E=BB=E9=87=8D=E5=AF=B9=E8=B7=A8?= =?UTF-8?q?=E7=AA=97=E5=8F=A3=E8=BF=91=E4=B9=89=E5=91=BD=E5=90=8D=E5=A4=B1?= =?UTF-8?q?=E6=95=88=E2=86=92733=E6=9D=A1=E6=B3=A8=E6=B0=B4QC6.8=E9=80=80?= =?UTF-8?q?=E5=9B=9E;=E4=B8=8D=E7=9B=B8=E4=BA=A4=E5=8C=BA=E9=97=B4?= =?UTF-8?q?=E4=BB=8E=E6=BA=90=E5=A4=B4=E6=B6=88=E9=99=A4=E8=B7=A8=E7=AA=97?= =?UTF-8?q?=E5=8F=A3=E9=87=8D=E5=A4=8D;=E2=91=A1=E8=BD=AE=E6=AC=A1?= =?UTF-8?q?=E5=8F=A3=E5=BE=84=E6=94=B9=5Fdim=5Fround=5Fcount(round?= =?UTF-8?q?=E5=8E=BB=E9=87=8D):=E6=97=A7COUNT=E6=9D=A1=E6=95=B0=E5=8F=A3?= =?UTF-8?q?=E5=BE=84=E6=8B=86=E8=A7=A3=E5=90=8E=E4=B8=80=E8=BD=AEN?= =?UTF-8?q?=E5=AD=90=E4=BB=BB=E5=8A=A1=E4=BC=9A=E8=99=9A=E9=AB=98N?= =?UTF-8?q?=E5=80=8D=E8=AF=AF=E8=A7=A6=E4=B8=8A=E9=99=90;=E2=91=A2=5Ftende?= =?UTF-8?q?r=5Ftext=5Flen=E4=BF=AELENGTH(=E5=AD=97=E8=8A=82)=E2=86=92CHAR?= =?UTF-8?q?=5FLENGTH(=E5=AD=97=E7=AC=A6):=E6=8A=BD=E5=8F=96=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E6=8C=89Python=E5=AD=97=E7=AC=A6=E4=B8=B2=E7=B4=A2?= =?UTF-8?q?=E5=BC=95=E5=88=87=E7=89=87,char=5Frange=E6=98=AF=E5=AD=97?= =?UTF-8?q?=E7=AC=A6=E5=9D=90=E6=A0=87;=E7=94=98=E8=82=83=E5=AE=9E?= =?UTF-8?q?=E6=B5=8BLENGTH=3D179031=20vs=20CHAR=5FLENGTH=3D67658(CJK?= =?UTF-8?q?=E2=89=882.65=E5=AD=97=E8=8A=82/=E5=AD=97),=E6=8C=89=E5=AD=97?= =?UTF-8?q?=E8=8A=82=E6=8B=86=E6=AE=B5=E7=AC=AC2=E6=AE=B5=E4=BB=85?= =?UTF-8?q?=E5=B0=BE=E9=83=A8=E6=9C=89=E6=95=88=E7=AC=AC3=E6=AE=B5?= =?UTF-8?q?=E8=B6=8A=E7=95=8C=E7=A9=BA=E4=B8=B2=E2=80=94=E2=80=942026-09-1?= =?UTF-8?q?5=E5=9B=9B=E7=BB=B4=E5=BA=A6=E6=8B=86=E8=A7=A3'=E5=AE=9E?= =?UTF-8?q?=E6=B5=8B=E9=80=9A=E8=BF=87'=E5=AE=9E=E9=99=85=E5=B8=A6?= =?UTF-8?q?=E7=97=85=E8=BF=90=E8=A1=8C(=E5=8C=BA=E9=97=B4=E9=94=99?= =?UTF-8?q?=E4=BD=8D=E6=B5=AA=E8=B4=B91/3=E5=AD=90=E4=BB=BB=E5=8A=A1),?= =?UTF-8?q?=E6=9C=AC=E6=AC=A1=E4=BF=AE=E6=AD=A3=E5=85=A8=E7=BB=B4=E5=BA=A6?= =?UTF-8?q?=E5=8F=97=E7=9B=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pipeline_bidding/bid_flow.py | 49 +++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/pipeline_bidding/bid_flow.py b/pipeline_bidding/bid_flow.py index a2c4f77..58c4bd8 100644 --- a/pipeline_bidding/bid_flow.py +++ b/pipeline_bidding/bid_flow.py @@ -176,9 +176,16 @@ async def _open_dim_tasks(sor, project_id, dim): async def _tender_text_len(sor, project_id): - """项目最新招标文件正文字符数(无文件/无正文返回 0)。拆解阈值判定用。""" + """项目最新招标文件正文字符数(无文件/无正文返回 0)。拆解阈值判定用。 + + ⚠️ 必须 CHAR_LENGTH(字符)不能 LENGTH(字节):抽取工具按 Python 字符串 + 索引切片(txt[offset:offset+length]),char_range 区间是字符坐标。甘肃实测 + LENGTH=179031 vs CHAR_LENGTH=67658(CJK utf8mb4 ≈2.65字节/字)——按字节拆 + 3 段时第 2 段仅尾部 8k 字符有效、第 3 段完全越界读空串(2026-09-15 四维度 + 拆解"实测通过"实际带病运行:区间错位浪费 1/3 子任务,2026-09-18 tech 接入时修正)。 + """ recs = await sor.sqlExe( - "SELECT LENGTH(content_text) AS n FROM bid_tender_files " + "SELECT CHAR_LENGTH(content_text) AS n FROM bid_tender_files " "WHERE project_id=${p}$ ORDER BY created_at DESC LIMIT 1", {"p": project_id}) await sor.sqlExe("COMMIT", {}) if not recs: @@ -1537,28 +1544,46 @@ async def _reconcile_tech_analysis_qc(sor, project_id, pname, n_items, latest_qc return False, False, True qc_imp = (latest_qc.get("improvement") or "") if ( latest_qc and str(latest_qc.get("passed")) != "1") else "" - attempts = await _count( - sor, "SELECT COUNT(*) AS c FROM pipeline_tasks WHERE tenant_id=${p}$ " - "AND pipeline_id='role_task' AND role=${r}$ AND state IN " - "('approved','completed','qc_rejected') AND params LIKE ${kw}$", - {"p": project_id, "r": R_ANALYST, "kw": '%bid_tech_analysis%'}) - if attempts >= MAX_ANALYST_ATTEMPT + th["qc_max_round"]: + # 轮次口径(2026-09-18 拆解配套,四维度 A 块同款):一个拆解轮派 N 个分段 + # 子任务(同 round 标签),按任务条数计轮会把一轮误算 N 轮触发上限误判。 + # 旧口径 COUNT(params LIKE '%bid_tech_analysis%') 数条数,拆解后同样虚高。 + n_first = await _dim_round_count(sor, project_id, TECH_KIND, redo=False) + n_redo = await _dim_round_count(sor, project_id, TECH_KIND, redo=True) + _over = (n_redo >= th["qc_max_round"]) if qc_imp else (n_first >= MAX_ANALYST_ATTEMPT) + if _over: ht = await find_human_task(sor, project_id, "general", status="pending") if not ht: await create_human_task( sor, project_id, "general", - "技术评估项解析多次未产出,需人工介入:%s" % pname, - ("技术评估项解析任务已执行 %d 次,产出仍为空。\n" + "技术评估项解析多轮未产出,需人工介入:%s" % pname, + ("技术评估项解析已执行 %d 轮(首跑 %d / QC重做 %d),产出仍为空或未过审。\n" "请检查招标文件技术部分是否可读(bid_tender_files.content_text)," - "或用 list_tech_items/add_tech_item 人工补录评估项。" % attempts), + "或用 list_tech_items/add_tech_item 人工补录评估项。" + % (n_first + n_redo, n_first, n_redo)), assignee_role="owner.superuser") await sor.sqlExe("COMMIT", {}) - acts.append("escalated: 技术评估项解析重试超限 → 人工介入") + acts.append("escalated: 技术评估项解析轮次超限 → 人工介入") return False, False, True tparams = {"stage": "tech_analysis", "task_kind": "bid_tech_analysis", "analysis_dim": TECH_KIND, "flow_key": flow_key} if qc_imp: tparams["qc_redo"] = 1 + # 大文件机械拆解(2026-09-18 用户要求,四维度 A 块同款机制):正文超阈值 + # → 拆 N 段并行子任务,char_range 互不重叠。单体全文抽取实测弊病(甘肃 r1): + # 2.7 小时串行 + agent 自选 offset 步进造成窗口大量重叠,(category,item_name) + # 精确去重对跨窗口近义命名失效 → 733 条注水(QC 6.8 退回,判重复条目/source_ref + # 不可回溯)。不相交区间从源头消除跨窗口重复。 + _tlen = await _tender_text_len(sor, project_id) + if _tlen > th["split_chars"]: + _ids = await _create_split_tasks( + sor, project_id, TECH_KIND, "技术评估项抽取", pname, tparams, _tlen, + th["split_chars"], ("redo%d" % (n_redo + 1)) if qc_imp else "run1", + qc_imp=["[tech_items] %s" % qc_imp[:2000]] if qc_imp else None) + await sor.sqlExe("COMMIT", {}) + acts.append("created: 技术评估项抽取拆解 %d 个分段子任务(正文 %d 字符%s)" + % (len(_ids), _tlen, ",QC退回重做" if qc_imp else "")) + return False, False, True + if qc_imp: tparams["qc_improvements"] = ["[tech_items] %s" % qc_imp[:2000]] tid = await create_role_task( sor, project_id, R_ANALYST,