feat(bidding): 移除立项+配人步骤,收到招标书直接启动项目
- import_tender_file 无项目时自动立项(项目名缺省取文件名),导入即解析 - 主 agent 下线 assign_members/list_members 工具(能力原语保留) - BID_PROMPT/典型场景同步:不再先问建项目/配人
This commit is contained in:
parent
aa5eb26d37
commit
4e1386f2a1
@ -22,17 +22,17 @@ PIPELINE_ID = "bidding_general"
|
||||
# ══════════════════ 工具定义(主 agent / 驾驶舱)══════════════════
|
||||
|
||||
BID_TOOLS = [
|
||||
# ── 立项 / 人员 / 招标文件(产线起点:从上传招标文件开始,无邮件采集)──
|
||||
ToolDefinition(name="create_bid_project", description="创建投标项目(直接立项,不依赖招标信息)。用户说「创建/新建投标项目」时调用。若用户已提供招标文件(附件/正文),必须带上 tender_content,系统自动跳过上传步骤直接进入解析",
|
||||
# ── 招标文件(产线起点:收到招标书直接启动,无需立项/配人步骤)──
|
||||
ToolDefinition(name="import_tender_file", description="导入招标文件并启动投标项目:用户提供了招标文件(附件/正文/文件路径)就调它。未建项目时自动立项(收到招标书直接启动),已有项目则导入到当前项目,系统自动开始解析",
|
||||
parameters={"content_text": "招标文件正文(用户提供时必填)",
|
||||
"file_path": "文件路径(与content_text二选一)",
|
||||
"file_name": "文件名(可选)",
|
||||
"project_name": "项目名称(可选,未建项目时自动立项用,缺省取文件名)"},
|
||||
category="project"),
|
||||
ToolDefinition(name="create_bid_project", description="创建空投标项目(仅当用户明确要先建项目、稍后再给招标文件时用)。若用户已提供招标文件,直接用 import_tender_file 自动立项",
|
||||
parameters={"name": "项目名称", "description": "项目描述(可选)",
|
||||
"tender_content": "招标文件正文(用户已提供时必填,直接导入跳过上传步骤)",
|
||||
"tender_file_name": "招标文件名(可选)"}, category="project"),
|
||||
ToolDefinition(name="import_tender_file", description="导入招标文件(项目已建但还没上传时):把对话中用户提供的招标文件正文/文件路径直接导入,跳过人工上传步骤,系统自动开始解析",
|
||||
parameters={"content_text": "招标文件正文", "file_path": "文件路径(与content_text二选一)",
|
||||
"file_name": "文件名(可选)"}, category="project"),
|
||||
ToolDefinition(name="assign_members", description="设定投标项目参与人员,如「张三:负责人,李四:writer」",
|
||||
parameters={"members": "人员清单(姓名:角色 逗号分隔,或JSON数组)"}, category="project"),
|
||||
ToolDefinition(name="list_members", description="查看投标项目参与人员", parameters={}, category="project"),
|
||||
ToolDefinition(name="request_tender_file", description="发布/重发「等待上传招标文件」人类任务",
|
||||
parameters={"note": "说明(可选)"}, category="project"),
|
||||
ToolDefinition(name="list_tender_files", description="查看已上传的招标文件及抽取状态", parameters={}, category="project"),
|
||||
@ -80,11 +80,14 @@ BID_TOOLS = [
|
||||
|
||||
# ══════════════════ 产线 prompt 片段 ══════════════════
|
||||
|
||||
BID_PROMPT = """你是「投标产线」的驾驶舱 agent,负责从上传招标文件到标书交付的全流程管理:
|
||||
创建投标项目 → 上传招标文件 → 解析(评分项/得分规则/资质/投标文件要求/章节骨架)
|
||||
BID_PROMPT = """你是「投标产线」的驾驶舱 agent,负责从招标文件到标书交付的全流程管理:
|
||||
收到招标文件 → 自动立项并解析(评分项/得分规则/资质/投标文件要求/章节骨架)
|
||||
→ 解析产出 QC 契合度审核 → 资质文档准备 → 分章节编写 → 章节评审 → 合成标书 → 整书评分 → 交付确认。
|
||||
|
||||
招标信息的采集/摘要/审批归商机产线,本产线从「上传招标文件」起步;用户要采集招标信息时说明去商机产线。
|
||||
产线没有独立的「立项/配人」步骤:用户把招标文件(附件/正文/文件路径)交给你,
|
||||
调 import_tender_file 即自动建项目并启动解析——不要先问用户「要不要建项目/安排人员」。
|
||||
|
||||
招标信息的采集/摘要/审批归商机产线,本产线从「收到招标文件」起步;用户要采集招标信息时说明去商机产线。
|
||||
|
||||
## 流转由机制驱动(重要)
|
||||
角色任务由系统状态对账器自动派发(每 15 秒一轮),你**不需要**手动为每个章节创建任务:
|
||||
@ -92,7 +95,7 @@ BID_PROMPT = """你是「投标产线」的驾驶舱 agent,负责从上传招
|
||||
按 10 分制打分,得分高于通过分(默认 9.5)才放行;不通过给改进意见 → 清空产出 → 解析重做 → 重审
|
||||
- 章节评审不达标 → 章节自动退回重写
|
||||
- 整书评分不达标 → 改进意见自动落到问题章节并退回重写 → 重新合成 → 重新评分
|
||||
你的职责是:立项与配人、推动上传招标文件、处理卡点与提问、回答评审/评分中的人类决策。
|
||||
你的职责是:收到招标文件立即导入启动、处理卡点与提问、回答评审/评分中的人类决策。
|
||||
只有诊断到「机制该派任务却没派」时,才用 advance_bid 手动触发一次对账,或用 diagnose_bid 查根因。
|
||||
|
||||
## 阻塞门禁(不要绕过)
|
||||
@ -103,8 +106,9 @@ BID_PROMPT = """你是「投标产线」的驾驶舱 agent,负责从上传招
|
||||
遇到项目"不动了",先 diagnose_bid 看是不是卡在门禁上,缺什么就催人补什么,不要硬推。
|
||||
|
||||
## 典型场景
|
||||
- 用户说「创建一个投标项目,叫 XXX」→ create_bid_project(name=XXX)。**若用户同时提供了招标文件(附件/正文),必须把它放进 tender_content 一起提交**,系统直接导入并跳过人工上传步骤,自动开始解析——不要再让用户去界面手动上传。
|
||||
- 项目已建、用户随后补交招标文件(对话正文或已写入工作空间的文件)→ import_tender_file 导入,跳过上传步骤。
|
||||
- 用户交来招标文件(附件/正文/文件路径)→ 直接 import_tender_file(可带 project_name)。**未建项目时系统自动立项,无需先建项目或配人**——不要再让用户去界面手动上传。
|
||||
- 用户明确说「先建个投标项目,文件稍后给」→ create_bid_project(name=XXX)。
|
||||
- 项目已建、用户随后补交招标文件 → import_tender_file 导入(有当前项目就导入到该项目),跳过上传步骤。
|
||||
- 用户问「招标文件上传了吗 / 解析出来了吗」→ list_tender_files 看上传与抽取状态。
|
||||
- 用户问「解析质量怎么样 / QC 过了吗」→ qc_report,讲清哪类产出通过、哪类还在改。
|
||||
- 用户问进展 → diagnose_bid(含章节分布、在办任务、最近评分),不要只报"正在进行"。
|
||||
@ -217,34 +221,19 @@ async def _h_create_bid_project(sor, p, ctx):
|
||||
|
||||
|
||||
async def _h_import_tender_file(sor, p, ctx):
|
||||
pid, err = _need_project(ctx)
|
||||
if err:
|
||||
return err
|
||||
# 无当前项目也允许:capability 内部自动立项(收到招标书直接启动)
|
||||
pid = ctx.get("project_id", "") or ""
|
||||
from .bid_tender_capability import import_tender_file
|
||||
ok, msg = await import_tender_file(pid, content_text=p.get("content_text", ""),
|
||||
file_path=p.get("file_path", ""),
|
||||
file_name=p.get("file_name", ""),
|
||||
project_name=p.get("project_name", ""),
|
||||
org_id=ctx.get("org_id", "") or "",
|
||||
created_by=ctx.get("user_id", "") or "",
|
||||
who="agent.main_agent")
|
||||
return _fmt(ok, msg)
|
||||
|
||||
|
||||
async def _h_assign_members(sor, p, ctx):
|
||||
pid, err = _need_project(ctx)
|
||||
if err:
|
||||
return err
|
||||
from .bid_tender_capability import assign_members
|
||||
ok, msg = await assign_members(pid, members=p.get("members", ""), who="agent.main_agent")
|
||||
return _fmt(ok, msg)
|
||||
|
||||
|
||||
async def _h_list_members(sor, p, ctx):
|
||||
pid, err = _need_project(ctx)
|
||||
if err:
|
||||
return err
|
||||
from .bid_tender_capability import list_members
|
||||
return _fmt_rows(await list_members(pid), "尚未设定参与人员")
|
||||
|
||||
|
||||
async def _h_request_tender_file(sor, p, ctx):
|
||||
pid, err = _need_project(ctx)
|
||||
if err:
|
||||
@ -394,8 +383,6 @@ async def _h_advance_bid(sor, p, ctx):
|
||||
BID_HANDLERS = {
|
||||
"create_bid_project": _h_create_bid_project,
|
||||
"import_tender_file": _h_import_tender_file,
|
||||
"assign_members": _h_assign_members,
|
||||
"list_members": _h_list_members,
|
||||
"request_tender_file": _h_request_tender_file,
|
||||
"list_tender_files": _h_list_tender_files,
|
||||
"list_scoring_items": _h_list_scoring_items,
|
||||
|
||||
@ -90,21 +90,52 @@ async def create_bid_project(name, description="", org_id="", created_by="",
|
||||
|
||||
|
||||
async def import_tender_file(project_id, content_text="", file_path="", file_name="",
|
||||
project_name="", org_id="", created_by="",
|
||||
who=None, agent_id=None):
|
||||
"""导入招标文件(已创建的项目补录):落库 bid_tender_files 并跳过人工上传步骤。
|
||||
"""导入招标文件;未建项目时自动立项(产线新起点:收到招标书直接启动,2026-09-01)。
|
||||
|
||||
- 有 project_id:落库 bid_tender_files 并跳过人工上传步骤;
|
||||
- 无 project_id:先自动创建投标项目(名称取 project_name,缺省用文件名/「投标项目」),
|
||||
再导入招标文件——用户不必先走立项步骤,收到招标书即启动项目。
|
||||
|
||||
content_text 与 file_path 至少给一个。导入后上传任务自动完成,对账器即派解析。
|
||||
"""
|
||||
if not project_id:
|
||||
return False, "缺少 project_id"
|
||||
txt = str(content_text or "").strip()
|
||||
fp = str(file_path or "").strip()
|
||||
if not txt and not fp:
|
||||
return False, "缺少招标文件内容:请提供 content_text(正文)或 file_path(文件路径)"
|
||||
db, dbname = get_db()
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
pid = str(project_id or "").strip()
|
||||
auto_created = False
|
||||
if not pid:
|
||||
# 自动立项:收到招标书直接启动,无需先建项目/配人
|
||||
pname = str(project_name or "").strip()[:190]
|
||||
if not pname:
|
||||
pname = (str(file_name or "").strip() or (fp.split("/")[-1] if fp else "")
|
||||
or txt.split("\n")[0].strip())[:190] or "投标项目"
|
||||
pid = new_id()
|
||||
await sor.C("sd_projects", {
|
||||
"id": pid,
|
||||
"name": pname,
|
||||
"description": "由导入招标文件自动立项(收到招标书直接启动)",
|
||||
"project_type": "bidding",
|
||||
"pipeline_id": PIPELINE_ID,
|
||||
"status": "in_progress",
|
||||
"org_id": org_id or "0",
|
||||
"created_by": created_by or (who or "agent.main_agent"),
|
||||
})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
await record(sor, pid, "sd_projects", pid, "auto_create_bid_project",
|
||||
to_state="in_progress", who=who, agent_id=agent_id,
|
||||
detail="auto-created on tender import")
|
||||
auto_created = True
|
||||
else:
|
||||
p = await get_project(sor, pid)
|
||||
if not p:
|
||||
return False, "项目不存在: %s" % pid
|
||||
await sor.C("bid_tender_files", {
|
||||
"id": new_id(), "project_id": project_id,
|
||||
"id": new_id(), "project_id": pid,
|
||||
"file_name": str(file_name or ("招标文件(对话提供)" if txt else fp.split("/")[-1]))[:250],
|
||||
"file_type": "tender_doc",
|
||||
"content_text": txt[:2000000] if txt else "",
|
||||
@ -114,18 +145,21 @@ async def import_tender_file(project_id, content_text="", file_path="", file_nam
|
||||
"uploaded_by": who or "agent.main_agent",
|
||||
})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
ht = await find_human_task(sor, project_id, HT_TENDER_FILE, status="pending")
|
||||
ht = await find_human_task(sor, pid, HT_TENDER_FILE, status="pending")
|
||||
if ht:
|
||||
await sor.sqlExe(
|
||||
"UPDATE pipeline_human_tasks SET status='done', qc_status='passed', "
|
||||
"qc_comment='招标文件已导入,系统自动完成(跳过上传步骤)', "
|
||||
"submitted_at=NOW() WHERE id=${h}$", {"h": ht.get("id")})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
await record(sor, project_id, "bid_tender_files", project_id, "import_tender_file",
|
||||
await record(sor, pid, "bid_tender_files", pid, "import_tender_file",
|
||||
to_state="uploaded", who=who, agent_id=agent_id,
|
||||
detail=("text %d chars" % len(txt)) if txt else ("path %s" % fp))
|
||||
return True, ("招标文件已导入(%s),上传步骤已跳过,系统将自动开始解析。"
|
||||
% ("正文 %d 字" % len(txt) if txt else file_path))
|
||||
base = ("正文 %d 字" % len(txt)) if txt else file_path
|
||||
if auto_created:
|
||||
return True, ("project_id=%s(已自动立项并导入招标文件:%s,"
|
||||
"系统将自动开始解析)" % (pid, base))
|
||||
return True, ("招标文件已导入(%s),上传步骤已跳过,系统将自动开始解析。" % base)
|
||||
|
||||
|
||||
async def assign_members(project_id, members="", who=None, agent_id=None):
|
||||
|
||||
@ -15,20 +15,7 @@ logger = logging.getLogger("pipeline.bidding.tools")
|
||||
_M = "pipeline_bidding"
|
||||
|
||||
BID_ROLE_TOOL_SCHEMAS = {
|
||||
# ── bid_tender_capability:立项/人员(产线从上传招标文件开始,无邮件采集/审批)──
|
||||
"assign_members": {
|
||||
"module": f"{_M}.bid_tender_capability",
|
||||
"description": "设定投标项目参与人员,如「张三:负责人,李四:writer」",
|
||||
"params": {"project_id": "项目ID(可选,默认当前)",
|
||||
"members": "人员清单(姓名:角色 逗号分隔,或JSON数组)"},
|
||||
"required": ["members"],
|
||||
},
|
||||
"list_members": {
|
||||
"module": f"{_M}.bid_tender_capability",
|
||||
"description": "查看投标项目参与人员",
|
||||
"params": {"project_id": "项目ID(可选,默认当前)"},
|
||||
"required": [],
|
||||
},
|
||||
# ── bid_tender_capability:招标文件(产线起点=收到招标书直接启动,无立项/配人步骤)──
|
||||
# ── bid_analysis_capability:招标文件分析师 ──
|
||||
"read_tender_file": {
|
||||
"module": f"{_M}.bid_analysis_capability",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user