This commit is contained in:
2025-12-04 10:11:52 +08:00
parent 90c3c0b0ba
commit 1a6b50cba7
2 changed files with 294 additions and 1 deletions

View File

@ -68,3 +68,6 @@ class DetectionResponse(BaseModel):
# 智能体相关 # 智能体相关
OLLAMA_MODEL = "alibayram/Qwen3-30B-A3B-Instruct-2507:latest" # 当前最强本地模型 OLLAMA_MODEL = "alibayram/Qwen3-30B-A3B-Instruct-2507:latest" # 当前最强本地模型
OLLAMA_BASE_URL = "http://192.168.110.5:11434" OLLAMA_BASE_URL = "http://192.168.110.5:11434"
VLLM_BASE_URL = "http://192.168.110.5:8000/v1"
VLLM_MODEL_NAME = "/home/zrway/vllm_models/LLM/new"

290
并发_vllm.py Normal file
View File

@ -0,0 +1,290 @@
# -*- coding: utf-8 -*-
import os
import re
import time
from pathlib import Path
from typing import List, Dict
from docx import Document
from openai import AsyncOpenAI
import asyncio
from datetime import datetime
from config import VLLM_BASE_URL, VLLM_MODEL_NAME
# ==================== 配置区 ====================
INPUT_WORD = r"C:\Users\YC\Desktop\1.docx"
OUTPUT_WORD = r"C:\Users\YC\Desktop\投标文件-最终版.docx"
# 统一中间文件目录(强烈建议不要改)
PROCESS_DIR = "12并发过程文件"
# ===============================================
# ==================== 12并发 ===================
client = AsyncOpenAI(base_url=VLLM_BASE_URL, api_key="EMPTY", timeout=1800)
SEM = asyncio.Semaphore(12)
# ==================== 进度条 ====================
class ProgressTracker:
def __init__(self, total: int, name: str):
self.total = total
self.name = name
self.completed = 0
self.start = time.time()
def update(self):
self.completed += 1
elapsed = time.time() - self.start
avg = elapsed / self.completed
eta = (self.total - self.completed) * avg
bar = "" * (30 * self.completed // self.total) + "" * (30 - 30 * self.completed // self.total)
print(f"\r{self.name} |{bar}| {self.completed}/{self.total} "
f"[{100*self.completed/self.total:5.1f}%] "
f"已用 {elapsed/60:4.1f}分 预计剩余 {eta/60:4.1f}", end="")
def finish(self):
total_time = time.time() - self.start
print(f"\n{self.name} 完成!总耗时 {total_time/60:.2f} 分钟")
TOTAL_START = time.time()
print("="*90)
print("12并发生成投标文件 · 字典+回调法(终极优化版)")
print(f"启动时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("="*90)
# ==================== 异步调用 ====================
async def call_llm_async(messages: List[Dict], temperature=0.3, max_tokens=56000):
async with SEM:
for attempt in range(6):
try:
start = time.time()
resp = await client.chat.completions.create(
model=VLLM_MODEL_NAME,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
top_p=0.95,
presence_penalty=1.08,
)
content = resp.choices[0].message.content.strip()
took = time.time() - start
print(f" √ 成功 | ≈{len(content)//4}千字 | 用时 {took:.1f}s")
return content
except Exception as e:
print(f" × 重试 {attempt+1}/6{e}")
if attempt < 5:
await asyncio.sleep(8)
else:
return "【请求失败,已保底】"
# ==================== 1. Word → Markdown ====================
def word_to_md(word_path: str) -> str:
print("\n开始 1. Word → Markdown 转换...")
start = time.time()
md_path = os.path.splitext(word_path)[0] + "_tender.md"
doc = Document(word_path)
text = "\n\n".join(p.text for p in doc.paragraphs if p.text.strip())
Path(md_path).write_text(text, encoding="utf-8")
print(f"1. 转换完成!用时 {time.time()-start:.1f}s → {md_path}")
return md_path
# ==================== 2. 并发四级目录 ====================
async def generate_full_outline_async(tender_md: str) -> str:
print("\n开始 2. 生成超级四级目录...")
tender_text = Path(tender_md).read_text(encoding="utf-8")
# 生成三级目录骨架prompt1 优化版)
prompt1 = f"""你现在是投标文件合规总负责人唯一目标是让投标文件100%通过形式审查和符合性审查,不被废标。
任务:输出一个【完全符合招标文件规定】的投标文件总目录(最多到四级)。
绝对铁律(任何违反都会导致废标):
1. 目录的章节顺序、层级符号(一、 二、 1. 1.1 1.1.1等)、标题文字(包括标点、空格)必须与招标文件《投标文件格式》《投标文件组成》《投标文件编制要求》或其附件一字不差。禁止任何改动、增删、合并、同义词替换。
2. 如果招标文件明确提供了目录模板或提纲,必须逐字逐句照抄,不允许任何改动。
3. 只允许在招标文件明确要求提交特定表格/清单的章节下添加对应的四级表格标题且表格标题文字必须100%来自招标文件或其附件原文。
4. 表格编号必须与招标文件一致如表1-1、表3.2-1不得自编。
5. 页码一律留空或写“(页码)”,严禁填写数字。
6. 如有正副本、分册要求必须在最前面或最后明确标明如“正本”“副本”“共X册 第X册”
7. 严禁出现任何招标文件未明确要求的章节或表格。
招标文件原文(必须严格遵守的全部目录及表格要求):
{tender_text[:60000]}
请直接输出完整的投标文件目录包括必要的四级表格标题不要任何解释、说明、引号、markdown标记。"""
outline_skeleton = await call_llm_async([{"role": "user", "content": prompt1}], temperature=0.01, max_tokens=10000)
level3_titles = [line.strip() for line in outline_skeleton.splitlines()
if re.match(r'^\d+\.\d+[、 ]', line.strip())]
print(f" 检测到 {len(level3_titles)} 个三级标题,开始并发展开四级...")
tracker = ProgressTracker((len(level3_titles) + 7) // 8, "2. 四级目录展开进度")
results = [""] * ((len(level3_titles) + 7) // 8)
task_to_idx = {}
async def on_done(fut, idx):
results[idx] = await fut
tracker.update()
for i in range(0, len(level3_titles), 8):
batch = level3_titles[i:i+8]
batch_text = "\n".join(batch)
ref_text = tender_text[:50000]
# 四级表格展开批处理提示词prompt2 优化版,更安全)
prompt2 = f"""根据招标文件要求只在明确写明“投标人应提交XX表”“填写附件X格式”“提供XX清单”的章节下插入对应的四级表格标题。
绝对禁止:
- 自创新表格或标题
- 修改招标文件原文的任何文字
- 在未要求的地方添加表格
允许的操作:
仅当招标文件原文出现类似“投标人应提交……表格式见附件X”“……一览表”“……清单”等语句时才添加对应表格标题作为四级目录。
输出要求:
- 每行格式:原章节号 + 空格 + 招标文件原文中的完整表格标题含表号如表3-1
- 用空行分隔不同章节的表格组
- 如果某个批次的三级章节下没有必须的表格,则输出空字符串即可
本批次需要处理的章节标题(保持原顺序):
{batch_text}
招标文件相关原文摘录(包含所有表格要求):
{ref_text}
直接输出四级表格标题,不要任何解释。"""
task = asyncio.create_task(call_llm_async([{"role": "user", "content": prompt2}], temperature=0.2, max_tokens=20000))
task_to_idx[task] = i // 8
task.add_done_callback(lambda t, idx=i//8: asyncio.create_task(on_done(t, idx)))
while [t for t in task_to_idx if not t.done()]:
await asyncio.sleep(0.1)
# 关键修复:只追加四级内容,不重复三级标题
full_outline = outline_skeleton + "\n\n" + "\n".join(filter(None, results)).strip() + "\n"
tracker.finish()
outline_path = f"{PROCESS_DIR}/四级目录.md"
Path(outline_path).write_text(full_outline, encoding="utf-8")
print(f"四级目录已保存 → {outline_path}")
return full_outline
# ==================== 3. 并发正文生成 ====================
async def batch_fill_content_async(outline: str, tender_text: str) -> str:
print("\n开始 3. 分批生成正文内容...")
level4_titles = [line.strip() for line in outline.splitlines()
if re.match(r'^\d+\.\d+\.\d+[、 ]', line.strip())]
batch_size = 8 # 表格减少后8个一批更稳定
total_batches = (len(level4_titles) + batch_size - 1) // batch_size
print(f"{len(level4_titles)} 个四级标题,分 {total_batches} 批处理")
tracker = ProgressTracker(total_batches, "3. 正文内容生成进度")
results = [""] * total_batches
task_to_idx = {}
async def on_done(fut, idx):
results[idx] = await fut
tracker.update()
for i in range(0, len(level4_titles), batch_size):
batch = level4_titles[i:i+batch_size]
titles_str = "\n".join(batch)
ref_text = tender_text[:60000]
prompt = f"""你现在是拥有25年投标经验的合规总师目标让投标文件100%通过初步评审(形式审查+符合性审查+资格审查),绝不废标。
请为以下章节撰写极简、极干、绝对安全的正文内容每节总字数严格控制在80-400字。
废标红线(任何一条违反都会废标,严禁触碰):
1. 只能响应招标文件明确要求的内容,多一个字、一个承诺、一个“亮点”都不允许。
2. 严禁出现“优于招标要求”“优选方案”“创新”“国内领先”“独家”等任何可能被认定为正偏离或多余承诺的字眼。
3. 所有表格必须100%使用招标文件或附件提供的原表格格式(表头、单位、列数、行数、备注文字一字不改),只允许填写数据。
4. 正文文字只能是“本公司完全响应招标文件要求”“详见后附证明材料复印件”“见下表”等最安全的套话。
5. 只能使用以下9个占位符其他一律删除不得出现具体公司名、金额等
【投标单位全称】【法定代表人姓名】【委托代理人姓名】【项目名称】【投标总价(大写)】【投标总价(小写)】【总服务期(天)】【投标保证金金额】【投标文件签署日期】
6. 严禁出现任何图片、流程图、彩色文字、组织机构图、超过两段的文字说明。
不同类型章节的唯一允许写法(必须严格选择其一):
• 投标函及附录、投标一览表类:只填招标文件提供的表格 + 一句“以上内容真实有效,完全响应招标文件所有实质性要求和条件,无任何偏差。”
• 法定代表人身份证明、授权委托书:严格按招标文件提供的格式原文填写(含“身份证号码”等栏),一字不改。
• 资质、资格证明文件类:只写“详见本节后附证明材料复印件(加盖公章)”。
• 业绩表、人员表、设备表等所有表格类:只输出完整表格,不加任何文字说明。
• 承诺书类:严格按招标文件提供的格式或范文填写,最多加一句“本公司完全响应招标文件要求”。
当前必须严格按顺序撰写的章节标题:
{titles_str}
招标文件强制要求的内容及所有表格格式原文:
{ref_text}
直接输出每个章节的正文内容,用
---
分隔,不要任何章节标题、解释、编号。"""
task = asyncio.create_task(call_llm_async([{"role": "user", "content": prompt}], temperature=0.35, max_tokens=56000))
task_to_idx[task] = i // batch_size
task.add_done_callback(lambda t, idx=i//batch_size: asyncio.create_task(on_done(t, idx)))
while [t for t in task_to_idx if not t.done()]:
await asyncio.sleep(0.1)
tracker.finish()
# 关键修复:去掉无用标题,直接拼接
final_content = "\n\n---\n\n".join(results)
content_path = f"{PROCESS_DIR}/正文内容.md"
Path(content_path).write_text(final_content, encoding="utf-8")
print(f"正文内容已保存 → {content_path}")
return final_content
# ==================== 4. Markdown → Word简单占坑 ====================
def md_to_word(md_path: str, output_word: str):
print("\n开始 4. Markdown → Word 转换...")
print("提示:请使用 Pandoc + 模板转换为精美 Word")
print(f"推荐命令pandoc \"{md_path}\" -o \"{output_word}\" --reference-doc=模板.docx")
print("暂未自动执行,需手动运行以上命令或自行补全转换逻辑")
# 如需自动,可取消下方注释(需先安装 pandoc
# import subprocess
# subprocess.run(['pandoc', md_path, '-o', output_word, '--reference-doc=模板.docx'])
# ==================== 主流程 ====================
async def main_async():
# 统一创建目录,永绝路径错误
os.makedirs(PROCESS_DIR, exist_ok=True)
os.makedirs("output", exist_ok=True)
tender_md = word_to_md(INPUT_WORD)
tender_text = Path(tender_md).read_text(encoding="utf-8")
outline = await generate_full_outline_async(tender_md)
content = await batch_fill_content_async(outline, tender_text)
final_md = f"""# 【投标单位全称】
## {Path(INPUT_WORD).stem} - 投标文件
{outline}
{content}"""
final_md_path = f"{PROCESS_DIR}/最终投标文件.md"
Path(final_md_path).write_text(final_md, encoding="utf-8")
print(f"\n最终整合完成 → {final_md_path}")
md_to_word(final_md_path, OUTPUT_WORD)
total_seconds = time.time() - TOTAL_START
print("\n" + "="*90)
print("大功告成!顶级投标文件已生成!")
print(f"总耗时:{total_seconds/60:.2f} 分钟")
print(f"最终 Word 文件路径:{OUTPUT_WORD}")
print(f"所有过程文件保存在:{PROCESS_DIR}/")
print("="*90)
if __name__ == "__main__":
asyncio.run(main_async())