标书智能体测评:提纲生成代码与提示词对比
使用Python与React开发一款开源的AI标书撰写智能体,确实具备实际落地价值。
本篇为系列第二期,重点讲解如何基于已解析的项目概述与技术评分需求,生成一份切实可用的投标文件提纲。
一份优质标书,提纲就是骨架。要让AI自动生成合格的提纲,必须攻克三个核心难点:
- 结构化输出——提纲层级若模糊,后续正文生成极易偏离方向,必须预先定义清晰的嵌套结构。
- 输出长度限制——标书动辄数十万字,提纲本身也很长。即使大模型宣称支持百万级上下文,实际有效输出通常仅两千字左右,长提纲根本无法一次性生成。
- 可编辑性——AI再强大,也无法一次性满足所有定制需求。提纲必须支持手动调整,且修改后不能破坏原有的层级关系。
下面逐一拆解这些挑战。
一、短标书 + 高能AI模型
若标书篇幅较短(例如三五万字),且使用像 glm-4.5 或 doubao-seed-1-6 这类可连续输出约八千字的模型,方案只需一个递归JSON结构即可。
JSON结构
{
"outline": [
{
"id": "1",
"title": "",
"description": "",
"children": [
{
"id": "1.1",
"title": "",
"description": "",
"children":[
{
"id": "1.1.1",
"title": "",
"description": ""
}
]
}
]
}
]
}
提示词
SystemPrompt
你是一位专业的标书撰写专家。请根据提供的项目概述和技术评分要求,生成技术标部分的目录结构。
要求:
1. 目录结构需完整覆盖技术标所有必要章节
2. 章节标题需专业、精准,符合投标文件编制规范
3. 一级目录名称必须与技术评分要求中的章节名称保持一致;若评分要求中未提供章节名称,则依据其内容自行拟定
4. 目录层级设为三级
5. 输出标准JSON格式,包含章节编号、标题、描述及子章节
6. 仅输出JSON结果,禁止附带任何其他文字
JSON格式要求:
{
"outline": [
{
"id": "1",
"title": "",
"description": "",
"children": [
{
"id": "1.1",
"title": "",
"description": "",
"children":[
{
"id": "1.1.1",
"title": "",
"description": ""
}
]
}
]
}
]
}
UserPrompt
请根据以下项目信息生成标书目录结构:
项目概述:
{overview}
技术评分要求:
{requirements}
请生成完整的技术标目录结构,确保覆盖所有技术评分要点。
二、长标书 + 普通AI模型
如果只有 glm-4-air 这类价格低廉但能力有限的模型,却要生成数十万字标书所需的超长提纲,则需要换一种策略。
瓶颈很明显:
- 上下文窗口短,最多输出一千五百字左右
- 输出稳定性差,JSON这类结构化格式频繁出错
应对思路:
- 先让AI生成一级目录(必须与技术评分要求一一对应)
- 再逐条遍历一级目录,分别生成二级和三级目录(多任务并发,需保证各任务生成的目录互不重复)
- 校验返回的JSON格式是否合法
- 最终拼装为完整目录
这套流程理论上可以无限扩展提纲长度。
生成一级标题
SystemPrompt
### 角色
你是专业的标书编写专家,擅长根据项目需求编写标书。
### 任务
1. 根据给定的项目概述(overview)和评分要求(requirements),撰写技术标部分的一级提纲
### 说明
2. 只设计一级标题,数量需与"评分要求"逐项对应
3. 一级标题名称需进行适当修改,不得直接复制"评分要求"中的原文
### Output Format in JSON
{
"rating_item":"原评分项",
"new_title":"根据评分项修改的标题"
}
UserPrompt
### 项目信息
{overview}
{requirements}
直接返回json,不要任何额外说明或格式标记
生成二级标题
为了提高结果稳定性,不让AI自行构建JSON框架,而是提前用代码搭好骨架,仅让AI填充内容。
拼接JSON框架
此处额外引入 nodes_distribution 参数,目的是让二三级目录的数量差异化,随机挑选两个重点章节增加层级深度,更贴近真实标书。具体实现细节可直接查看源码。
def generate_one_outline_json_by_level1(level1_title: str, level1_index: int, nodes_distribution: Dict) -> Dict:
# 获取当前一级节点下的二级节点数量和叶子节点分配
level2_count = nodes_distribution['level2_nodes'][level1_index - 1]
leaf_distribution = nodes_distribution['leaf_per_level2'][level1_index - 1]
# 创建一级节点
level1_node = {
"id":f"{level1_index}",
"title": level1_title,
"description": "",
"children": []
}
# 创建二级节点
for j in range(level2_count):
level2_node = {
"id":f"{level1_index}.{j+1}",
"title": "", # 二级标题留空
"description": "",
"children": []
}
# 创建三级节点(叶子节点)
leaf_count = leaf_distribution[j]
for k in range(leaf_count):
level2_node["children"].append({
"id":f"{level1_index}.{j+1}.{k+1}",
"title": "", # 三级标题留空
"description": ""
})
level1_node["children"].append(level2_node)
return level1_node
在生成某个章节的二三级时,需将其他章节的标题也传给AI作为参考,避免内容重复。
other_outline = "\n".join([f"{j+1}. {node['new_title']}"
for j, node in enumerate(level_l1)
if j!= i])
SystemPrompt
(注:json_outline 为前面搭好的JSON框架)
### 角色
你是专业的标书编写专家,擅长根据项目需求编写标书。
### 任务
1. 根据提供的项目概述(overview)、评分要求(requirements)补全标书提纲的二三级目录
### 说明
2. 你会获得一段json,这是提纲的其中一个章节,请在原结构上补全标题(title)和描述(description)
3. 二级标题基于一级标题撰写,三级标题基于二级标题撰写
4. 补全内容需参考项目概述(overview)、评分要求(requirements)等信息
5. 你还会收到其他章节的标题(other_outline),需确保本章节内容不与其他章节重叠
### 注意事项
在原json上补全信息,禁止修改json结构,禁止修改一级标题
### Output Format in JSON
{json_outline}
UserPrompt
### 项目信息
{overview}
{requirements}
{other_outline}
直接返回json,不要任何额外说明或格式标记
校验生成结果与传入框架是否一致
import json
def check_json(json_str: str, schema: str | dict) -> tuple[bool, str]:
"""
根据模板 JSON 校验目标字符串的格式是否符合要求
Args:
json_str: 要校验的 JSON 字符串
schema: 模板 JSON 字符串或字典对象,用于定义预期的数据结构
Returns:
tuple[bool, str]: (是否验证通过, 错误信息)
如果验证通过返回 (True, ""),否则返回 (False, 错误原因)
"""
try:
# 解析输入的 JSON 字符串
try:
data = json.loads(json_str)
except json.JSONDecodeError as e:
return False, f"JSON 解析错误: {str(e)}"
# 处理 schema 参数
try:
if isinstance(schema, str):
schema = json.loads(schema)
elif not isinstance(schema, dict):
return False, "schema 必须是 JSON 字符串或字典对象"
except json.JSONDecodeError as e:
return False, f"schema 解析错误: {str(e)}"
def check_structure(target, template, path=""):
# 处理数字类型(int 和 float 可以互换)
if isinstance(template, (int, float)) and isinstance(target, (int, float)):
return True, ""
# 检查基本数据类型
if type(template) != type(target) and not (isinstance(template, (int, float)) and isinstance(target, (int, float))):
return False, f"路径 '{path}' 的类型不匹配: 期望 {type(template).__name__}, 实际 {type(target).__name__}"
# 如果是列表类型
if isinstance(template, list):
if not template: # 如果模板列表为空,则允许任何列表
return True, ""
if not target: # 如果目标列表为空,但模板不为空
return False, f"路径 '{path}' 的列表为空,但期望有内容"
# 检查列表中的每个元素是否符合模板中第一个元素的格式
template_item = template[0]
for i, item in enumerate(target):
is_valid, error = check_structure(item, template_item, f"{path}[{i}]")
if not is_valid:
return False, error
return True, ""
# 如果是字典类型
elif isinstance(template, dict):
# 检查所有必需的键是否存在,并且值的类型是否正确
for key in template:
if key not in target:
return False, f"路径 '{path}' 缺少必需的键 '{key}'"
is_valid, error = check_structure(target[key], template[key], f"{path}.{key}")
if not is_valid:
return False, error
return True, ""
# 对于其他基本类型,返回 True
return True, ""
is_valid, error = check_structure(data, schema)
return is_valid, error if not is_valid else ""
except Exception as e:
return False, f"未预期的错误: {str(e)}"
实际运行中,为每个生成任务设置3次重试,若校验失败则重新执行,可显著提升成功率。


