vllm
This commit is contained in:
@ -67,4 +67,7 @@ class DetectionResponse(BaseModel):
|
||||
|
||||
# 智能体相关
|
||||
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