feat(bidding v1.2): 编写分商务/技术角色+篇幅门禁+样板标书规范+并发编写
- 章节按section分流: business→agent.bid_biz_writer(新角色), technical/price→agent.bid_writer - 骨架生成自动推断section(_infer_section标题关键词) - 篇幅门禁机制层强制: review_chapter字数低于门限必退+自动附意见 (参数bid_chapter_min_words=800/bid_chapter_min_words_biz=500) - 并发编写: 对账器单轮最多派bid_write_concurrency(4)个编写任务 - 样板标书: bid_kb_docs doc_type=sample_bid码值; extract_writing_spec工具 (PM抽章节结构/篇幅口径/表格图示/商务要素, 落bid_doc_requirements writing_spec) - DESIGN.md v1.2
This commit is contained in:
parent
76ef3e4f7d
commit
b40f54d63f
@ -2,7 +2,7 @@
|
||||
|
||||
> 对照开发产线(`sdlc_general`)设计,复用平台通用内核(项目/会话/任务链/人类任务/交付件/问答冒泡/技能分层),
|
||||
> 只增量定义投标产线自己的 **阶段、角色、工具、技能、任务流转规则、交付件**。
|
||||
> 版本:v1.1.0 创建:2026-08-26 修订:2026-08-31(产线起点改为上传招标文件;解析产出加 QC 契合度审核门禁)
|
||||
> 版本:v1.2.0 创建:2026-08-26 修订:2026-08-31(v1.1 上传招标文件起点+QC 门禁;v1.2 编写分商务/技术角色+篇幅门禁+样板标书规范+并发编写)
|
||||
|
||||
---
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
| 7 | **解析产出 QC 契合度审核(四类逐类,10 分制 >9.5 放行,否则改进意见退回重做)** | agent.qc | bid_qc_reviews |
|
||||
| 8 | 从公司投标知识库获取所需资质文档 | agent.bid_prep | bid_qualifications.kb_doc_id ← bid_kb_docs |
|
||||
| 9 | 发布人类应提供的文件清单 | agent.bid_prep(阻塞门禁) | pipeline_human_tasks(human_docs_request) |
|
||||
| 10 | 按投标文件要求分章节编写投标文件 | agent.bid_writer | bid_chapters |
|
||||
| 10 | 按投标文件要求分章节编写投标文件(商务/技术分角色,并发) | agent.bid_writer / agent.bid_biz_writer | bid_chapters |
|
||||
| 11 | 每章节完成后对照招标/投标文件要求评审,分数不够给改进意见重做 | agent.bid_reviewer | bid_reviews(scope=chapter) → 章节循环 |
|
||||
| 12 | 合成标书 | agent.bid_compositor | bid_documents(docx) |
|
||||
| 13 | 对整个标书评审 + 按评分项和得分规则打分 | agent.bid_scorer | bid_reviews(scope=whole) + bid_scores |
|
||||
@ -48,7 +48,7 @@
|
||||
| 招标文件解析 | `agent.tender_analyst` | 抽取评分项+得分规则、所需资质、投标文件要求、商务要素,产出章节骨架 |
|
||||
| 解析产出 QC | `agent.qc` | 四类产出对照招标文件原文按 10 分制审核契合度,>9.5 放行,否则改进意见退回 |
|
||||
| 资料准备 | `agent.bid_prep` | 知识库资质匹配、缺失项发布人类文件清单 |
|
||||
| 章节编写 | `agent.bid_writer` | 按章节骨架逐章写作(一章一任务) |
|
||||
| 章节编写 | `agent.bid_writer`(技术标)/ `agent.bid_biz_writer`(商务标) | 按章节骨架逐章写作(一章一任务、并发派发);篇幅与要素达标,搜不到冒泡 |
|
||||
| 章节评审 | `agent.bid_reviewer` | 对照招标要求+得分规则逐章打分,不达标退回重写 |
|
||||
| 标书合成 | `agent.bid_compositor` | 合成 docx(封面/目录/页码),产出交付件 |
|
||||
| 全书评分 | `agent.bid_scorer` | 按评分项+得分规则给整书打分、生成改进意见并映射章节 |
|
||||
@ -78,6 +78,7 @@ agent.qc(A2 QC 门禁)→ 四类产出逐类契合度审核(10 分制)
|
||||
agent.bid_prep → 知识库资质匹配 + [人类任务:应提供文件清单](阻塞)
|
||||
↓
|
||||
agent.bid_writer → 逐章写作(每章一个任务,可并行)
|
||||
(section=business 的章节派 agent.bid_biz_writer,其余派 agent.bid_writer)
|
||||
↓ 章节 written
|
||||
agent.bid_reviewer → 章节评审打分
|
||||
↓ 全部章节 approved
|
||||
@ -140,7 +141,28 @@ agent.bid_scorer → 整书评分
|
||||
> 这是角色任务派发机制的约定,不是引擎通用规则 —— 引擎层 SQL 不得引用该等价关系,
|
||||
> 引擎对投标产线零特判;投标的阻塞语义全部收敛在投标产线自己的代码(bid_flow)里。
|
||||
|
||||
### 3.6 硬规则
|
||||
### 3.6 编写分角色、篇幅门禁与并发(v1.2 新增)
|
||||
|
||||
**分角色**:章节按 `bid_chapters.section` 分流——
|
||||
- `business`(投标函/报价/资质证明/承诺/业绩,骨架生成时按标题关键词自动推断)→ `agent.bid_biz_writer`;
|
||||
- `technical`/`price` → `agent.bid_writer`。
|
||||
- 两类内容不混写;capability 共用 `bid_write_capability`(工具白名单相同,角色技能不同)。
|
||||
|
||||
**篇幅门禁(机制层强制,review_chapter 内)**:
|
||||
- 技术标章节字数 < `bid_chapter_min_words`(默认 800)或商务标 < `bid_chapter_min_words_biz`(默认 500)
|
||||
→ 直接不通过 + 自动附「篇幅不达标」意见;与得分率门限取并集。
|
||||
- 内容完整性由评审员按 bid-doc-spec 查(评分子维度漏应答/商务要素缺失 = 退)。
|
||||
|
||||
**搜不到 → 冒泡**:写者检索知识库无所需素材/案例/参数时 ask_question 冒泡人类,禁编造凑字数。
|
||||
|
||||
**样板标书规范**:`bid_kb_docs.doc_type=sample_bid`;PM 调 `extract_writing_spec` 抽取
|
||||
章节结构惯例/篇幅口径/表格图表示例/商务要素清单,落 `bid_doc_requirements(req_type=writing_spec)`,
|
||||
写者与评审参照。无样板时提示人工录入。
|
||||
|
||||
**并发编写**:对账器 C 块单轮最多派 `bid_write_concurrency`(默认 4)个编写任务;
|
||||
引擎 agent poller 另有每项目并发上限 `max_agents_per_project`(默认 4)兜底,任务间 asyncio 并发执行。
|
||||
|
||||
### 3.7 硬规则
|
||||
|
||||
- 章节写作任务与章节一对一(`params.chapter_id`),禁止一个任务写多章。
|
||||
- 评审/评分只由 `agent.bid_reviewer` / `agent.bid_scorer` 执行,写作角色不得自评;
|
||||
@ -251,6 +273,9 @@ agent.bid_scorer → 整书评分
|
||||
| `bid_max_round` | 3 | 整书最大评分轮次 |
|
||||
| `bid_qc_pass_score` | 9.5 | 解析产出 QC 契合度通过分(10 分制,严格大于才放行) |
|
||||
| `bid_qc_max_round` | 3 | 单类产出 QC 最大审核轮次 / 解析重做轮次上限 |
|
||||
| `bid_chapter_min_words` | 800 | 技术标章节最低字数(低于评审必退) |
|
||||
| `bid_chapter_min_words_biz` | 500 | 商务标章节最低字数 |
|
||||
| `bid_write_concurrency` | 4 | 章节编写单轮并发派发上限 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -116,7 +116,8 @@
|
||||
{"k": "case", "v": "业绩案例"},
|
||||
{"k": "template", "v": "模板"},
|
||||
{"k": "finance", "v": "财务资料"},
|
||||
{"k": "personnel", "v": "人员资料"}
|
||||
{"k": "personnel", "v": "人员资料"},
|
||||
{"k": "sample_bid", "v": "样板标书"}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@ -444,6 +444,17 @@ async def list_doc_requirements(project_id, req_type=""):
|
||||
|
||||
# ══════════════ 章节骨架 ══════════════
|
||||
|
||||
def _infer_section(title):
|
||||
"""按章节标题关键词推断商务/技术分类(商务标:报价/商务/资质证明/承诺函等;其余技术标)。"""
|
||||
t = title or ""
|
||||
biz_kw = ("报价", "投标函", "商务", "资格证明", "资质证明", "承诺书", "声明", "保证金",
|
||||
"法定代表人", "授权委托", "财务状况", "业绩一览", "合同格式")
|
||||
for kw in biz_kw:
|
||||
if kw in t:
|
||||
return "business"
|
||||
return "technical"
|
||||
|
||||
|
||||
async def create_chapter_outline(project_id, chapters="", who=None, agent_id=None):
|
||||
"""生成章节骨架 → bid_chapters(status=pending)。
|
||||
|
||||
@ -471,6 +482,7 @@ async def create_chapter_outline(project_id, chapters="", who=None, agent_id=Non
|
||||
"chapter_no": d.get("chapter_no") or "",
|
||||
"title": d.get("chapter_title") or "",
|
||||
"outline": (d.get("requirement") or "")[:4000],
|
||||
"section": _infer_section(d.get("chapter_title") or ""),
|
||||
})
|
||||
if not items:
|
||||
return False, ("无章节来源:请先抽取 req_type=structure 的投标文件要求,"
|
||||
|
||||
@ -67,6 +67,9 @@ DEFAULT_PARAMS = {
|
||||
"bid_max_round": "3", # 整书最大评分轮次
|
||||
"bid_qc_pass_score": "9.5", # 解析产出 QC 契合度通过分(10 分制,高于此分才放行)
|
||||
"bid_qc_max_round": "3", # 单类产出 QC 最大审核轮次(超限抛人工)
|
||||
"bid_chapter_min_words": "800", # 技术标章节最低字数(低于即评审必退)
|
||||
"bid_chapter_min_words_biz": "500", # 商务标章节最低字数
|
||||
"bid_write_concurrency": "4", # 章节编写并发上限(单轮最多同时派发的编写任务数)
|
||||
}
|
||||
|
||||
|
||||
@ -187,6 +190,9 @@ async def get_thresholds(sor):
|
||||
"max_round": to_int(await get_param(sor, "bid_max_round"), 3),
|
||||
"qc_pass_score": to_float(await get_param(sor, "bid_qc_pass_score"), 9.5),
|
||||
"qc_max_round": to_int(await get_param(sor, "bid_qc_max_round"), 3),
|
||||
"chapter_min_words": to_int(await get_param(sor, "bid_chapter_min_words"), 800),
|
||||
"chapter_min_words_biz": to_int(await get_param(sor, "bid_chapter_min_words_biz"), 500),
|
||||
"write_concurrency": to_int(await get_param(sor, "bid_write_concurrency"), 4),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -33,6 +33,7 @@ logger = logging.getLogger("pipeline.bidding")
|
||||
R_ANALYST = "agent.tender_analyst"
|
||||
R_PREP = "agent.bid_prep"
|
||||
R_WRITER = "agent.bid_writer"
|
||||
R_BIZ_WRITER = "agent.bid_biz_writer"
|
||||
R_REVIEWER = "agent.bid_reviewer"
|
||||
R_COMPOSITOR = "agent.bid_compositor"
|
||||
R_SCORER = "agent.bid_scorer"
|
||||
@ -354,24 +355,31 @@ async def reconcile_project(sor, project_id, project_name=""):
|
||||
if not chapters:
|
||||
return acts + ["idle: 无章节,等待解析产出章节骨架"]
|
||||
|
||||
# ── C. 章节编写(一章一任务;pending/rejected 且无在办任务)──
|
||||
# ── C. 章节编写(一章一任务、并发派发;pending/rejected 且无在办任务)──
|
||||
# 商务/技术分角色:section=business → 商务标写者;其余(technical/price)→ 技术标写者。
|
||||
# 并发:单轮最多派 write_concurrency 个编写任务(引擎另有每项目并发上限兜底)。
|
||||
created_w = 0
|
||||
for c in chapters:
|
||||
if created_w >= th["write_concurrency"]:
|
||||
break
|
||||
if c.get("status") not in (CH_PENDING, CH_REJECTED):
|
||||
continue
|
||||
if to_int(c.get("revise_count"), 0) > th["max_revise"]:
|
||||
continue # 已超重做上限,等人工介入(review 阶段已抛任务)
|
||||
if await count_open_tasks(sor, project_id, role=R_WRITER, chapter_id=c["id"]) > 0:
|
||||
role = R_BIZ_WRITER if (c.get("section") or "technical") == "business" else R_WRITER
|
||||
if await count_open_tasks(sor, project_id, role=role, chapter_id=c["id"]) > 0:
|
||||
continue
|
||||
stage = "revise" if c.get("status") == CH_REJECTED else "write"
|
||||
tid = await create_role_task(
|
||||
sor, project_id, R_WRITER,
|
||||
sor, project_id, role,
|
||||
"%s 第%s章《%s》%s" % (pname, c.get("chapter_no"), c.get("title"),
|
||||
"修改重写" if stage == "revise" else "编写"),
|
||||
"修改重写" if stage == "revise" else
|
||||
("编写(商务标)" if role == R_BIZ_WRITER else "编写(技术标)")),
|
||||
{"stage": stage, "task_kind": "bid_write", "chapter_id": c["id"],
|
||||
"chapter_no": c.get("chapter_no"), "revise_count": c.get("revise_count")})
|
||||
"chapter_no": c.get("chapter_no"), "section": c.get("section") or "technical",
|
||||
"revise_count": c.get("revise_count")})
|
||||
created_w += 1
|
||||
acts.append("created: 章节编写任务 %s (%s)" % (tid, c.get("chapter_no")))
|
||||
acts.append("created: 章节编写任务 %s (%s/%s)" % (tid, c.get("chapter_no"), role))
|
||||
if created_w:
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
|
||||
|
||||
@ -261,3 +261,54 @@ async def auto_match_qualifications(project_id, org_id="", who=None, agent_id=No
|
||||
missing += 1
|
||||
return True, ("资质自动匹配完成:命中知识库 %d 项,需人工提供 %d 项。"
|
||||
"请对需人工提供的项调用 request_human_docs 发布清单。" % (matched, missing))
|
||||
|
||||
|
||||
async def extract_writing_spec(project_id, kb_doc_id="", note="", who=None, agent_id=None):
|
||||
"""PM 从样板标书(bid_kb_docs doc_type=sample_bid)抽取编写规范并落库。
|
||||
|
||||
抽取结果写入项目级「编写规范」记录(bid_doc_requirements req_type=writing_spec),
|
||||
供写者章节编写与评审参照:章节结构惯例/篇幅口径/表格图表示例/商务标要素清单。
|
||||
本工具只负责落库框架,具体规范条目由 PM(LLM)读样板标书内容后逐条给出。
|
||||
"""
|
||||
try:
|
||||
project_id = await resolve_project_id(project_id)
|
||||
except ValueError as e:
|
||||
return False, str(e)
|
||||
db, dbname = get_db()
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
if kb_doc_id:
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT id, doc_name, doc_type, LEFT(content_text, 8000) AS content_text "
|
||||
"FROM bid_kb_docs WHERE id=${i}$", {"i": kb_doc_id})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
if not recs:
|
||||
return False, "样板标书不存在: %s" % kb_doc_id
|
||||
doc = rec_to_dict(recs[0])
|
||||
if (doc.get("doc_type") or "") != "sample_bid":
|
||||
return False, ("该文档类型是 %s 不是 sample_bid;"
|
||||
"请先用 add_kb_doc(doc_type=sample_bid) 录入样板标书" % doc.get("doc_type"))
|
||||
else:
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT id, doc_name, doc_type, LEFT(content_text, 8000) AS content_text "
|
||||
"FROM bid_kb_docs WHERE doc_type='sample_bid' ORDER BY created_at DESC LIMIT 1", {})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
if not recs:
|
||||
return False, ("知识库无样板标书(doc_type=sample_bid)。"
|
||||
"请人工提供一份中标样板标书,用 add_kb_doc(doc_type=sample_bid) 录入后重试。")
|
||||
doc = rec_to_dict(recs[0])
|
||||
# 落一条 writing_spec 记录(幂等:同项目同样板只留最新)
|
||||
await sor.sqlExe(
|
||||
"DELETE FROM bid_doc_requirements WHERE project_id=${pid}$ AND req_type='writing_spec'",
|
||||
{"pid": project_id})
|
||||
await sor.C("bid_doc_requirements", {
|
||||
"id": new_id(), "project_id": project_id, "req_type": "writing_spec",
|
||||
"chapter_no": "0", "chapter_title": "编写规范(样板抽取:%s)" % (doc.get("doc_name") or "")[:60],
|
||||
"requirement": (note or "")[:20000] or "(PM 抽取规范见任务交付说明)",
|
||||
"source_ref": doc.get("id") or "",
|
||||
})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
return True, ("已绑定样板标书《%s》并初始化编写规范记录。请通读其内容,"
|
||||
"抽取:①章节结构惯例 ②篇幅口径(每类章节字数/页数)③表格图表示例 "
|
||||
"④商务标要素清单(投标函/报价/资质证明/承诺),写入本任务交付说明,"
|
||||
"供写者与评审参照。样板正文预览:\n%s"
|
||||
% (doc.get("doc_name"), (doc.get("content_text") or "")[:3000]))
|
||||
|
||||
@ -49,6 +49,17 @@ async def review_chapter(chapter_id, score, max_score="", comments="", detail=""
|
||||
ms = 100.0 # 章节未挂评分项时按百分制评审
|
||||
ratio = sc / ms if ms > 0 else 0.0
|
||||
passed = ratio >= th["chapter_pass_ratio"]
|
||||
|
||||
# ── 篇幅门禁:字数低于门限必退(防内容过短/缺失),意见自动附带 ──
|
||||
wc = to_int(c.get("word_count"), 0)
|
||||
min_w = th["chapter_min_words_biz"] if (c.get("section") or "technical") == "business" \
|
||||
else th["chapter_min_words"]
|
||||
if wc < min_w:
|
||||
passed = False
|
||||
short_note = ("【篇幅不达标】正文 %d 字 < 门限 %d 字(%s标),内容疑似过短或缺失,"
|
||||
"请按评分规则子维度补全到操作层。" % (
|
||||
wc, min_w, "商务" if (c.get("section") or "") == "business" else "技术"))
|
||||
comments = (short_note + "\n" + (comments or "")).strip()
|
||||
if not passed and not (comments or "").strip():
|
||||
return False, ("评审不通过必须给出改进意见(comments),"
|
||||
"写清缺哪些评分维度、应补什么内容、对照哪条得分规则")
|
||||
|
||||
@ -220,6 +220,13 @@ BID_ROLE_TOOL_SCHEMAS = {
|
||||
"params": {"project_id": "项目ID(可选,默认当前)"},
|
||||
"required": [],
|
||||
},
|
||||
# ── bid_spec_capability:样板标书编写规范抽取(PM)──
|
||||
"extract_writing_spec": {
|
||||
"module": f"{_M}.bid_kb_capability",
|
||||
"description": "从样板标书(doc_type=sample_bid)抽取编写规范并落库:章节结构惯例/篇幅口径/表格图表示例/商务标要素清单",
|
||||
"params": {"kb_doc_id": "样板标书ID(可选,默认取最新一份)", "note": "PM抽取的规范条目(可选)"},
|
||||
"required": [],
|
||||
},
|
||||
# ── bid_compose_capability:标书合成工程师 ──
|
||||
"compose_bid": {
|
||||
"module": f"{_M}.bid_compose_capability",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user