news 2026/8/29 13:42:40

AI标书写作如何拒绝说谎:RAG与可控生成防幻觉实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI标书写作如何拒绝说谎:RAG与可控生成防幻觉实践

这次我们来看一个很有意思的 AI 工程实践项目:让 AI 投标书撰写者拒绝说谎

标题直译过来就是“让 AI 标书写作工具拒绝撒谎”。在真实业务场景里,投标书涉及企业资质、业绩案例、技术参数、服务承诺,每一行都可能直接影响是否中标,更严重一点说,虚假信息一旦被核实,轻则废标,重则进黑名单。很多 AI 写作文案工具存在一个通病:为了生成流畅内容,模型会“脑补”案例、编造资质、填充不存在的项目业绩。这个项目要解决的就是这个问题——让 AI 在信息不确定时主动拒绝生成,而不是硬编。

从架构上看,这可以拆解为“指令约束 + 事实边界 + 拒绝机制”三部分:通过提示词设计给模型设定行为边界,通过结构化知识库让模型只能引用已验证的材料,通过输出校验和拒绝策略兜底。换句话说,这本质上是一个LLM 应用的可控生成工程问题,涉及 API 调用、RAG 检索、提示词优化、输出校验和合规约束。

这篇文章会围绕这个主题,从核心能力、环境准备、部署启动、功能测试、API 批量任务、性能观察、排查手段到最佳实践,完整带大家走一遍。如果你正在做 AI Agent、AI 文档生成、GPT 应用开发、AI 工程实践相关项目,这篇可以直接收藏。

1. 核心能力速览

能力项说明
项目类型AI 文档生成 / 可控文本生成 / RAG 检索增强
核心目标让 AI 标书撰写工具拒绝生成不实信息
主要功能真实材料召回、事实核验、拒绝生成机制、输出标注来源
关键技术LLM API、RAG、提示词工程、结构化知识库、输出校验
推荐硬件使用云端 API 时无需本地 GPU;本地部署开源模型时建议 16G 以上显存
显存占用取决于模型选择;使用 API 模式几乎不占本地显存
支持平台Windows / Linux / macOS,Python 环境
启动方式命令行启动 / API 服务模式
是否支持 API支持,可集成到业务系统
是否支持批量任务支持,可设计批量标书章节生成和校验队列
适合场景投标书初稿、审计报告初稿、合规文档、需要事实约束的文本生成

从材料看,该项目的重点不是模型本身,而是工程层的“诚实性控制”。它不是一个 UI 一键工具,更像一套可以接到任意 LLM 的生成控制中间层。理解了这一点,后续环境准备和部署思路就清楚了。

2. 适用场景与使用边界

2.1 适合谁用

  • 做标书代写工具、文档自动化 SaaS、企业知识库问答的开发者。
  • 给银行、政企、工程类项目做合规文本生成的团队。
  • 做 AI 应用开发、AI Agent 工程实践的技术人员。
  • 需要限制 LLM “胡说八道”的文档场景。

2.2 能解决的问题

  • 模型编造公司业绩、项目案例、人员资质。
  • 模型在信息缺失时强行生成“看似合理”的表述。
  • 引用过期的政策法规、错误的技术参数。
  • 多段文本之间事实矛盾,例如前一章写 A 公司有 100 人,后一章写成 200 人。

2.3 不适合什么场景

  • 纯创意写作、营销文案润色,这类场景不需要严格事实约束。
  • 需要模型基于“猜”进行头脑风暴的场景。
  • 完全没有可靠材料的场景——如果库里没有内容,AI 只能拒绝生成,产出会非常保守。

2.4 合规与隐私边界

这个项目直接涉及企业资质、商业业绩、个人证书等敏感信息。使用过程中必须注意:

  • 企业资质、业绩、证书等信息需获得授权后才可录入知识库。
  • 生成的标书内容属于商务文档,涉及商业机密时不应上传到公共模型 API,建议私有化部署或使用合规 API。
  • 建立“可追溯”机制,任何由 AI 生成的事实性陈述都必须能回溯到原始材料。
  • 不要用该项目生成涉及虚假授权、仿冒签名、伪造证件的文本。

3. 环境准备与前置条件

从实践角度看,这个项目建议按两条路准备环境:

  • API 模式:本地无需 GPU,调用 GPT、Claude、通义千问、文心一言等大模型接口。
  • 本地模型模式:需要一张至少 16G 显存的显卡,部署 Qwen、Llama、ChatGLM 等开源模型。

3.1 操作系统与 Python 环境

建议使用 Linux 服务器或 Windows 10/11 搭配 WSL2。Python 版本建议 3.10 到 3.12,低于 3.9 的话很多依赖包可能不兼容,高于 3.12 时部分本地框架可能还没有适配。

# 创建虚拟环境 python3.11 -m venv venv source venv/bin/activate # 或者 Windows # venv\Scripts\activate

3.2 依赖库

项目需要以下核心依赖,实际版本号以项目 requirements 为准,安装时不要盲目用最新版本:

pip install openai langchain chromadb pydantic python-dotenv rapidfuzz

如果走本地模型路线,还需要追加:

pip install transformers torch sentencepiece accelerate

这里多说一句:openai库现在不只服务 OpenAI 官方接口,国内的大模型服务商基本都兼容 OpenAI 协议,只需要改 base_url 和 api_key,这大大降低了后续对接成本。

3.3 模型 API Key

以 API 模式为例,需要在环境变量里配置模型服务密钥:

export LLM_API_KEY="your-api-key" export LLM_BASE_URL="https://api.your-model-service.com/v1" export LLM_MODEL="your-model-name"

为了安全,不要把密钥写到代码里,更不要提交到 Git 仓库。

3.4 磁盘与数据准备

标书知识库需要保存企业资质、过往业绩、技术方案、人员证书等文件,建议:

  • 文本格式:PDF、Word、Markdown、TXT。
  • 每个文档建议控制在 30 页以内,超长文档先拆成章节。
  • 预留至少 5G 空间用于向量数据库和临时缓存。

3.5 端口检查

如果要以 API 服务方式启动,建议使用 8000 或 8080 端口,启动前先检查:

lsof -i :8000 # 如果有输出,说明端口被占用,需要换端口或者杀掉旧进程

4. 安装部署与启动方式

4.1 代码结构规划

建议按以下目录组织项目,这样后续做批量任务时不会乱:

bid-writer/ ├── docs/ # 原始标书材料 │ ├── company_profile.md │ ├── performance_cases.md │ └── qualifications.md ├── storage/ # 向量数据库和索引 ├── app/ │ ├── ingest.py # 材料入库 │ ├── generate.py # 生成逻辑 │ ├── verify.py # 事实校验 │ └── server.py # API 服务 ├── config/ │ └── settings.yaml └── requirements.txt

4.2 材料入库

把原始标书材料拆成适合检索的块,写入向量数据库。这里使用 LangChain 加 Chroma 做演示:

from langchain.document_loaders import TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.embeddings import OpenAIEmbeddings from langchain.vectorstores import Chroma # 加载文档 loader = TextLoader("docs/company_profile.md") documents = loader.load() # 拆分文本,块大小 500,重叠 50 text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50 ) texts = text_splitter.split_documents(documents) # 构建向量库 embeddings = OpenAIEmbeddings() vectordb = Chroma.from_documents( documents=texts, embedding=embeddings, persist_directory="./storage/chroma_db" ) vectordb.persist()

注意:实际项目中,公司资质和业绩案例通常来自 PDF 或 Word,需要先用 pdfplumber、unstructured 或 docx 库做格式解析,再进入上面流程。

4.3 核心生成逻辑:约束 + 检索 + 拒绝

这一步是项目的灵魂。生成标书内容之前,先执行检索,从知识库中找出与问题相关的材料;然后基于这些材料生成内容;最后再做一个“事实核验”。如果核验不过,就拒绝输出并返回原因。

下面是一个简化示例:

from langchain.vectorstores import Chroma from langchain.embeddings import OpenAIEmbeddings from langchain.llms import OpenAI SYSTEM_PROMPT = """ 你是一个投标书撰写助手。你的任务是基于给定的企业真实材料撰写标书内容。 你必须遵守以下规则: 1. 只能使用提供的材料中明确存在的信息。 2. 如果材料中没有相关信息,必须明确回答“没有找到相关材料,无法生成该部分内容”。 3. 禁止编造公司资质、业绩案例、人员证书、技术参数。 4. 涉及数字、日期、金额时必须与材料完全一致。 5. 每段内容的末尾,列出该段引用来自哪些材料文件。 """ def retrieve_similar(query: str, k: int = 5): embeddings = OpenAIEmbeddings() vectordb = Chroma( embedding_function=embeddings, persist_directory="./storage/chroma_db" ) docs = vectordb.similarity_search(query, k=k) return "\n\n".join([d.page_content for d in docs]) def generate_bid_content(query: str): context = retrieve_similar(query) messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": f"材料:\n{context}\n\n写作要求:{query}"} ] response = openai.ChatCompletion.create( model="gpt-4", messages=messages, temperature=0.2 ) return response["choices"][0]["message"]["content"]

这里有几个关键点:

  • temperature=0.2,降低随机性,减少模型自由发挥的空间。
  • 系统提示词里写清楚了“禁止编造”和“拒绝生成”的行为策略。
  • 把所有检索到的材料放进上下文,让模型只能在这个范围内作答。

4.4 事实校验与拒绝机制

生成之后,不能直接信任输出。需要再做一道“回查”:

  • 把输出中的每个关键事实性断言,映射回原始材料。
  • 如果找不到对应文本,就认为该断言没有依据。
  • 如果一段输出里存在超过 1 条无依据断言,整段拒绝。
import re from rapidfuzz import fuzz def verify_claims(generated_text: str, source_materials: list) -> dict: # 提取输出中的数字、公司名、日期作为关键断言 numbers = re.findall(r"\d+(?:\.\d+)?%?|[\d,]+人|[\d,]+万元", generated_text) unverified = [] for num in numbers: matched = False for material in source_materials: if num in material or fuzz.partial_ratio(num, material) > 85: matched = True break if not matched: unverified.append(num) return { "unverified": unverified, "safe": len(unverified) == 0 } # 返回值中的 safe 如果为 False,就拒绝生成该段

在实际项目中,校验策略需要更强:可以把“断言抽取”从正则升级为基于 NER 的实体识别,把“匹配”升级为向量召回比对。这里只展示简化逻辑,原理是一致的。

4.5 启动 API 服务

为了方便接入业务系统,可以包装成一个 FastAPI 服务:

from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class BidRequest(BaseModel): section: str query: str class BidResponse(BaseModel): content: str refused: bool reason: str @app.post("/api/bid/generate", response_model=BidResponse) def generate_bid(req: BidRequest): content = generate_bid_content(req.query) # 这里插入事实校验逻辑 return BidResponse( content=content if not refused else "", refused=refused, reason="没有找到相关材料,无法生成该部分内容" if refused else "" )

启动命令:

uvicorn app.server:app --host 0.0.0.0 --port 8000

4.6 启动后验证

启动服务后,用 curl 做一次最简单的连通性测试:

curl http://localhost:8000/docs

正常情况下,浏览器访问/docs可以看到 FastAPI 自动生成的 Swagger 调试页面。这是排查启动问题最快的手段——如果/docs能打开,说明服务整体起来了。

5. 功能测试与效果验证

5.1 测试一:基于真实材料的生成

先在知识库存入一段真实的公司业绩材料:

"XX科技有限公司,成立于2015年,注册资金5000万元,拥有系统集成一级资质。 2021年中标XX省政务云平台建设项目,合同金额8200万元。 2022年中标XX市智慧交通项目,合同金额4600万元。"

然后发起生成请求:

import requests payload = { "section": "业绩案例", "query": "请描述一下公司在政务领域的成功案例。" } resp = requests.post("http://localhost:8000/api/bid/generate", json=payload) print(resp.json())

预期结果:

  • 输出包含“XX省政务云平台建设项目”和“8200万元”。
  • 输出末尾标注了来源文件。
  • 没有额外编造“2023 年中标 XX 项目”。

判断成功的标准:输出里的数字和项目名称都能在原材料中找到。

5.2 测试二:缺失信息时的拒绝行为

现在询问一个知识库中没有的信息:

payload = { "section": "人员资质", "query": "公司是否有 CMMI5 认证?" } resp = requests.post("http://localhost:8000/api/bid/generate", json=payload) print(resp.json())

预期结果:

  • 返回内容为“没有找到相关材料,无法生成该部分内容”。
  • refused字段为true
  • 不会生成“公司拥有 CMMI5 认证”这类猜测性文本。

这一步是整个项目最关键的验收项。如果模型在这类输入下仍然强行输出,说明拒绝机制失效,需要检查系统提示词是否被忽略,或者上下文是否为空导致模型自行发挥。

5.3 测试三:数字准确性压力测试

把多个相似数字混在上下文中,测试模型是否忠实引用:

材料 A:2021 年合同金额 8200 万元。 材料 B:2022 年合同金额 4600 万元。 材料 C:2023 年合同金额 9800 万元。

请求:“列出公司近三年每年度合同金额。”

预期输出必须严格按年份对应金额,不能把 8200 写成 2022 年,也不能把 4600 写成 2021 年。

5.4 测试四:批量生成与一致性

批量生成多个章节,检查各章节之间是否有冲突。例如:

  • 第一章写“公司成立于 2015 年”。
  • 第五章不能变成“公司成立于 2016 年”。

批量任务通常需要按顺序执行,并把前文的结果作为后文的事实约束。

6. 接口 API 与批量任务

这个项目最终的价值是接入业务系统,所以 API 设计和批量任务能力必须单独讨论。

6.1 API 参数设计建议

字段类型说明
sectionstring标书章节名称
querystring写作要求
max_tokensint最大生成长度
temperaturefloat采样温度,建议 0.1-0.3
source_fileslist限定使用哪几个材料文件
strict_modebool严格模式,开启后拒绝一切无依据内容

6.2 批量任务设计

批量生成标书章节时,建议使用异步任务队列,不要直接同步调用 LLM。原因很简单:标书通常有几十个章节,每个章节要经过“检索 + 生成 + 校验”三个环节,同步调用总耗时可能达到 5-10 分钟,很容易触发 HTTP 超时。

推荐使用 Redis + Celery 或简单的 Asyncio 队列:

from fastapi import BackgroundTasks from celery import Celery celery_app = Celery("bid_tasks", broker="redis://localhost:6379/0") @celery_app.task def generate_section(section: str, query: str): content = generate_bid_content(query) return {"section": section, "content": content} @app.post("/api/bid/batch") async def create_batch(requests: list[BidRequest]): task_ids = [] for req in requests: result = generate_section.delay(req.section, req.query) task_ids.append(result.id) return {"task_ids": task_ids}

业务侧轮询任务状态时,建议实现失败重试。LLM 调用经常因为限流、网络超时等原因失败,重试逻辑要加上指数退避。

6.3 curl 调用示例

curl -X POST http://localhost:8000/api/bid/generate \ -H "Content-Type: application/json" \ -d '{"section": "技术方案", "query": "描述系统架构设计"}'

6.4 返回结果示例

{ "content": "系统采用微服务架构,包含 API 网关、业务服务层、数据存储层。数据存储层采用分布式数据库,支撑高并发访问场景。\n\n引用来源:docs/technical_plan.md", "refused": false, "reason": "" }

7. 资源占用与性能观察

7.1 显存占用观察方法

如果使用本地模型,显存占用取决于模型参数量:

  • 7B 模型量化后大约需要 6-8G 显存。
  • 14B 模型量化后大约需要 12-16G 显存。
  • 直接用 ChatGPT、通义等 API 则不占用本地显存。

显存观测使用 nvidia-smi:

watch -n 1 nvidia-smi

7.2 性能瓶颈分析

这个项目的性能瓶颈通常不在模型本身,而在三个环节:

  • 材料解析:PDF 解析最耗时,建议预解析为 Markdown 或纯文本。
  • 向量检索:数据库量大时检索时间上升,需要给向量库加索引。
  • 输出校验:正则和模糊匹配速度快,但如果引入大模型二轮校验,耗时可能翻倍。

从工程经验看,API 模式的生成速度瓶颈主要受限于模型响应速度,而本地模式受限于显存带宽和批量大小。

7.3 降低延迟的建议

  • 把材料入库和生成分离,不要每次请求都重新构建向量库。
  • 对常用标书章节做缓存,相同 query 直接命中。
  • 批量任务需要控制并发数,一般 3-5 个并发比较稳妥,避免触发限流。
  • 严格模式会显著降低输出率,但如果标书要求“每个数字都溯源”,这一步不能省。

8. 常见问题与排查方法

问题现象可能原因排查方式解决方案
启动后/docs打不开端口被占用或服务未启动检查日志和端口占用更换端口或杀掉旧进程
模型总是编造业绩上下文为空或系统提示词被忽略打印最终发给模型的完整 prompt检查检索结果是否为空;强化系统提示词
检索不到相关内容文档入库未完成或向量库路径错误查看入库日志,检查向量库目录重新执行入库命令
生成结果数字错误上下文过长导致模型注意力分散检查检索片段是否混入相似数字降低 chunk_size,或增加数字校验
批量任务全部超时并发过大触发限流查看模型服务日志降低并发数,增加重试
安装依赖失败Python 版本不兼容查看报错信息中的版本要求切换到 3.10-3.12 环境
PDF 材料解析乱码PDF 是扫描件检查文件是否为图片型 PDF先接入 OCR 识别再入库
拒绝生成比例过高知识库内容不足检查库内材料覆盖范围补充高质量材料,或调整检索阈值
中文匹配准确率低使用英文分词器检查向量检索的分词方式切换为中文分词器或向量模型
API Key 报 401密钥无效或服务商不匹配检查 base_url 和 api_key 是否对应重新配置环境变量

8.1 排查流程建议

最实用的排查顺序是“从输入到输出”:

  1. 先打印检索结果,确认模型能看到什么。
  2. 再打印完整 prompt,确认系统约束是否生效。
  3. 最后检查校验模块的日志,确认哪些断言被判定为无依据。

90% 的“AI 胡说八道”问题,在第一步和第二步就能定位。

9. 最佳实践与使用建议

9.1 关于系统提示词

系统提示词是这个项目最重要的“代码”。建议采用“能力定义 + 禁令列表 + 行为示例”三段式写法:

能力定义:你是投标书撰写助手,只能基于材料撰写。 禁令列表: - 禁止编造公司资质。 - 禁止编造过往业绩。 - 禁止修改合同金额、日期、项目名称。 - 禁止在无材料时生成推测性内容。 行为示例: 用户问:公司是否有 CMMI5 认证? 材料中没有相关信息时,回答:“没有找到相关材料,无法生成该部分内容。”

9.2 关于知识库建设

  • 材料一定要做去重和版本管理,同一份文件有多个版本时,旧版本存在校验 bug 隐患。
  • 不要一次性把所有材料塞进上下文,优先检索再拼装,保证信息相关性。
  • 定期更新政策法规类材料,过期法规会在标书中造成硬伤。

9.3 关于商用合规

  • 标书中的企业资质、业绩案例属于商业事实,不提供来源的 AI 生成文本不能直接使用。
  • 建议给每个生成章节附带“来源引用”,便于人工复核。
  • 对外发布或投标前,必须由熟悉业务的专人做最终审核,AI 只负责初稿。

9.4 关于批量生成

  • 批量任务使用异步队列,每个任务都要有独立日志。
  • 任务失败要有重试机制,但重试不能无限循环,一般 3 次以内。
  • 严格模式适合批量任务默认开启,宁可拒绝,也不能硬编。

9.5 关于模型选择

  • 追求稳定性和事实一致性,优先选择指令跟随能力强的模型。
  • 项目早期可以直接用 Python 写“规则 + 向量检索 + LLM 调用”的轻量流程,不要一开始就上复杂框架。
  • 后期如果需要在本地私有化部署,再考虑 Qwen、Llama 等开源模型,并用 vLLM 或 Ollama 托管。

10. 总结与下一步

这个项目最值得尝试的点,是把“AI 可控生成”从概念落到了可运行的工程流程:检索真实材料、约束生成范围、校验输出事实、拒绝无依据内容。它本质上是一套“防幻觉”中间层,不依赖单一模型,可以接入任意主流 LLM API,也可以替换为本地开源模型。

最先应该验证的功能是“缺失信息时的拒绝行为”:当你问一个知识库里不存在的问题时,系统是否真的会拒绝,而不是开始编造。这个功能跑通了,整个项目的核心价值就成立了。

最容易踩的坑有两个。第一个是知识库材料没有充分解析就入库,导致检索阶段找不到有效内容;第二个是系统提示词写得不够硬,模型在用户压力下仍然尝试编造。这两个问题都要通过打印完整 prompt 和检索结果来排查。

后续可以继续扩展的方向包括:

  • 把“事实校验”升级为“多模型交叉验证”,用另一个模型对生成内容做二次核验。
  • 增加文章级别的逻辑一致性检查,防止多个章节之间数据矛盾。
  • 对接 Office 文档格式,直接生成符合招标文件格式的 Word 标书。
  • 增加人工审核工作流:AI 生成初稿,标记不确定项,人工确认后定稿。

建议收藏备用,尤其是在做 AI 工程实践、AI 文档生成、企业知识库相关项目的同学,这套“检索 + 约束 + 校验 + 拒绝”的闭环可以直接复用到其他场景。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/29 13:39:45

STNRG012数字PFC+LLC控制器设计与调试实践

1. 项目概述:STNRG012是一颗什么样的片子 做开关电源这行,绕不开PFC和LLC这两个词。最近我在做一个2kW服务器电源项目,主控选了ST的STNRG012,一颗数字式多模PFCLLC谐振控制器。很多朋友看到"数字式"三个字就有点怵&…

作者头像 李华
网站建设 2026/8/29 13:35:46

面试官如何反押题?从面经失效到考点迁移的算法面试策略

1. 面经生态:面试官为什么非得“偷看”面经1.1 一道面试题在论坛上的“生命周期”先聊聊面经这个东西是怎么在生态里流动的。Amazon的面试流程不算神秘,标准的一轮算法轮(Coding Round)大概45到60分钟,候选人要在白板或…

作者头像 李华
网站建设 2026/8/29 13:34:50

网易安全工程师笔试复盘:从Web安全到应急响应的避坑指南

网易2020校招笔试 - 安全工程师(提前批):一份迟到的复盘与避坑指南 2020年那场网易提前批安全工程师笔试,我到现在还记得交卷时的感觉:明明每个题型都见过,但真正落在卷面上的选择,很多都是在&q…

作者头像 李华
网站建设 2026/8/29 13:34:23

MATLAB数学建模图表实战:从基础绘图到论文级可视化

1. 项目概述:为什么数学建模离不开MATLAB画图?搞数学建模的朋友,尤其是参加过国赛、美赛或者亚太杯这类竞赛的,应该都深有体会:一个清晰、专业、信息量丰富的图表,往往比十几页的文字描述更有说服力。评委在…

作者头像 李华
网站建设 2026/8/29 13:34:08

Claude session管理:别让每次对话都成为对AI的谎言

Claude 的 session 到底记了什么?很多人没认真想过。我们在 Claude Code 里打开一个 session,问几个问题,关掉,再开一个新 session,继续问。从 Claude 的视角看,这不是连续对话,而是一个个彼此孤…

作者头像 李华
网站建设 2026/8/29 13:34:03

强化学习入门:从核心概念到DQN实战,掌握智能决策框架

1. 从零开始的强化学习:为什么它值得你投入时间 如果你对人工智能感兴趣,尤其是那些能下围棋、打游戏、甚至控制复杂物理系统的智能体,那么“强化学习”这个词你一定不陌生。它听起来很酷,但入门时常常让人望而生畏:马…

作者头像 李华