feat(bidding): 移除立项+配人步骤,收到招标书直接启动项目

- import_tender_file 无项目时自动立项(项目名缺省取文件名),导入即解析
- 主 agent 下线 assign_members/list_members 工具(能力原语保留)
- BID_PROMPT/典型场景同步:不再先问建项目/配人
This commit is contained in:
yumoqing 2026-09-01 05:15:53 +08:00
parent aa5eb26d37
commit 4e1386f2a1
3 changed files with 66 additions and 58 deletions

View File

@ -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,

View File

@ -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):

View File

@ -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",