vllm
This commit is contained in:
@ -67,4 +67,7 @@ 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
290
并发_vllm.py
Normal 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())
|
||||||
Reference in New Issue
Block a user