用Python+React打造一个开源的AI写标书智能体~

**完整代码已开源**

代码很多，文章只放主要代码和提示词，完整代码可以查看开源项目

Github： https://github.com/yibiaoai/yibiao-simple

Gitee： https://gitee.com/yibiao-ai/yibiao-simple


今天是第二期，根据解析好的项目概述和技术评分需求，生成投标文件提纲：

![](https://qiniu.markup.com.cn/20250911131512062.png)

![](https://qiniu.markup.com.cn/20250911131512063.png)

一份好的标书，提纲是关键，而生成好的提纲，则有以下几个难点：
- **格式化输出**：为了生成正文时，AI能够很好的理解提纲，保证结构不错乱，那么提纲必须是格式化的
- **超出AI输出长度**：几十万字的标书，提纲也不会短，现在AI虽然都号称几十万的上下文，但实际输出也就2千字左右，无法直接完整的长提纲
- **提纲可编辑**：虽然AI能生成比较不错的提纲，但还是没法100%满足要求，所以提纲必须是可编辑的，而且编辑后还不能破坏原有的结构

下面，咱们就来逐项分析一下，如何解决上述难点。

# 一、短标书+能力较强的AI模型

当生成的标书长度要求不高，比如三五万字。且AI能输出的token数比较长，如最新的 `glm-4.5`  和 `doubao-seed-1-6`，大约可以直接输出8000字左右，那么就可以使用下面这种简单的结构。

## JSON结构

``` json
{
  "outline": [
    {
      "id": "1",
      "title": "",
      "description": "",
      "children": [
        {
          "id": "1.1",
          "title": "",
          "description": "",
          "children":[
              {
                "id": "1.1.1",
                "title": "",
                "description": ""
              }
          ]
        }
      ]
    }
  ]
}
```

## 提示词

**SystemPrompt**
``` markdown
你是一个专业的标书编写专家。根据提供的项目概述和技术评分要求，生成投标文件中技术标部分的目录结构。

要求：
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**
``` Markdown
请基于以下项目信息生成标书目录结构：

项目概述：
{overview}

技术评分要求：
{requirements}

请生成完整的技术标目录结构，确保覆盖所有技术评分要点。
```

# 二、长标书+普通AI模型

要想使用如 `glm-4-air` 这样低价的普通模型，也能生成几十万字标书需要的长提纲，那实现起来就复杂多了。

**限制**
1. 上下文短，仅能输出1500字左右
2. 输出结果不稳定，尤其是json这类结构化数据，可能出现格式错误

**思路**
1. 先生成一级目录（要与技术评分要求一一对应）
2. 遍历一级目录，逐个生成二、三级目录（多个AI任务并发生成，要保证每个任务生成的目录和其他任务的不重复）
3. 校验生成的json格式是否标准
4. 最后拼成完整的目录

这种生成工作流，理论上可以生成无限长度的提纲。
## 生成一级标题

**SystemPrompt**
``` Markdown
### 角色
你是专业的标书编写专家，擅长根据项目需求编写标书。

### 人物
1. 根据得到的项目概述(overview)和评分要求(requirements)，撰写技术标部分的一级提纲

### 说明
2. 只设计一级标题，数量要和"评分要求"一一对应
3. 一级标题名称要进行简单修改，不能完全使用"评分要求"中的文字

### Output Format in JSON
{
	"rating_item":"原评分项",
	"new_title":"根据评分项修改的标题"
}
```

**UserPrompt**
```Markdown
### 项目信息

<overview>
{overview}
</overview>

<requirements>
{requirements}
</requirements>


直接返回json，不要任何额外说明或格式标记
```

## 生成二级标题

为了生成结果稳定，我们不让AI来处理json框架，而是提前用代码拼接好json框架，仅让AI在json框架中填写内容

**拼接json框架**
我这里额外加了一个nodes_distribution，是为了让二三级目录数量不一样，随机了两个重点章节，这样更符合实际情况，这个不重要，感兴趣可以直接查看源码。
下面的代码是拼接json框架的：
``` python
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参考，以免生成重复的章节内容
``` python
other_outline = "\n".join([f"{j+1}. {node['new_title']}" 
                    for j, node in enumerate(level_l1) 
                    if j!= i])
```

**SystemPrompt**
json_outline是前面生成的json框架
``` markdown
### 角色
你是专业的标书编写专家，擅长根据项目需求编写标书。

### 任务
1. 根据得到项目概述(overview)、评分要求(requirements)补全标书的提纲的二三级目录

### 说明
2. 你将会得到一段json，这是提纲的其中一个章节，你需要再原结构上补全标题(title)和描述(description)
3. 二级标题根据一级标题撰写,三级标题根据二级标题撰写
4. 补全的内容要参考项目概述(overview)、评分要求(requirements)等项目信息
5. 你还会收到其他章节的标题(other_outline)，你需要确保本章节的内容不会包含其他章节的内容

### 注意事项
在原json上补全信息，禁止修改json结构，禁止修改一级标题

### Output Format in JSON
{json_outline}
```

**UserPrompt**
``` markdown
### 项目信息

<overview>
{overview}
</overview>

<requirements>
{requirements}
</requirements>

<other_outline>
{other_outline}
</other_outline>


直接返回json，不要任何额外说明或格式标记
```

## 校验生成json结果是否和传入框架一致
``` python
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次重试，如果校验没通过，再重新执行一次，以保证成功率

![](https://qiniu.markup.com.cn/20250911131512064.png)
# 完整代码已开源

Github： https://github.com/yibiaoai/yibiao-simple

Gitee： https://gitee.com/yibiao-ai/yibiao-simple