100行代码构建智能会议纪要生成器:Qwen3-1.7B-FP8实战指南
痛点与解决方案
你是否还在为冗长会议后的纪要整理而烦恼?传统人工记录效率低下、信息遗漏严重,而市场上的AI工具要么收费高昂,要么部署复杂。本文将展示如何使用Qwen3-1.7B-FP8模型,仅需100行代码构建一个功能完备的智能会议纪要生成器,彻底解决会议记录难题。
读完本文你将获得:
- Qwen3-1.7B-FP8模型的本地化部署方案
- 会议音频转文本的完整实现代码
- 基于AI的会议内容自动分析与结构化输出
- 支持多格式导出的纪要生成工具
技术选型对比
| 方案 | 模型大小 | 推理速度 | 硬件要求 | 中文支持 | 部署难度 |
|---|---|---|---|---|---|
| GPT-4 API | - | 快 | 无 | 优秀 | 低 |
| Llama3-8B | 8B | 中等 | 8GB显存 | 一般 | 中 |
| Qwen3-1.7B-FP8 | 1.7B | 快 | 4GB显存 | 优秀 | 低 |
| 通义千问API | - | 快 | 无 | 优秀 | 低 |
项目架构设计
环境准备
硬件要求
- CPU: 4核及以上
- 内存: 8GB及以上
- GPU: 4GB显存(推荐NVIDIA显卡)
软件依赖
# 创建虚拟环境
python -m venv qwen-venv
source qwen-venv/bin/activate # Linux/Mac
# qwen-venv\Scripts\activate # Windows
# 安装依赖包
pip install torch==2.3.1 transformers==4.51.0 sentencepiece==0.2.0 pyaudio==0.2.14 wave==0.0.2
pip install faster-whisper==1.0.3 python-docx==1.1.0 python-pptx==0.6.23
模型下载
git clone https://gitcode.com/hf_mirrors/Qwen/Qwen3-1.7B-FP8
cd Qwen3-1.7B-FP8
核心功能实现
1. 语音录制模块
import pyaudio
import wave
import threading
import time
from datetime import datetime
class AudioRecorder:
def __init__(self, output_dir="recordings"):
self.CHUNK = 1024
self.FORMAT = pyaudio.paInt16
self.CHANNELS = 1
self.RATE = 44100
self.output_dir = output_dir
self.is_recording = False
self.audio = pyaudio.PyAudio()
self.frames = []
# 创建输出目录
import os
os.makedirs(self.output_dir, exist_ok=True)
def start_recording(self):
self.is_recording = True
self.frames = []
self.stream = self.audio.open(
format=self.FORMAT,
channels=self.CHANNELS,
rate=self.RATE,
input=True,
frames_per_buffer=self.CHUNK
)
print("开始录音... (按 Ctrl+C 停止)")
while self.is_recording:
data = self.stream.read(self.CHUNK)
self.frames.append(data)
def stop_recording(self):
self.is_recording = False
if hasattr(self, 'stream'):
self.stream.stop_stream()
self.stream.close()
# 生成带时间戳的文件名
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{self.output_dir}/meeting_{timestamp}.wav"
# 保存录音
wf = wave.open(filename, 'wb')
wf.setnchannels(self.CHANNELS)
wf.setsampwidth(self.audio.get_sample_size(self.FORMAT))
wf.setframerate(self.RATE)
wf.writeframes(b''.join(self.frames))
wf.close()
print(f"录音已保存至: {filename}")
return filename
def __del__(self):
self.audio.terminate()
# 使用示例
if __name__ == "__main__":
recorder = AudioRecorder()
try:
threading.Thread(target=recorder.start_recording).start()
while True:
time.sleep(1)
except KeyboardInterrupt:
audio_file = recorder.stop_recording()
2. 语音转文本模块
from faster_whisper import WhisperModel
class SpeechToText:
def __init__(self, model_size="base", device="auto", compute_type="int8"):
"""
初始化语音转文本模型
Args:
model_size: 模型大小,可选 "tiny", "base", "small", "medium", "large"
device: 运行设备,"auto" 自动选择
compute_type: 计算类型,"int8" 适合CPU,"float16" 适合GPU
"""
self.model = WhisperModel(model_size, device=device, compute_type=compute_type)
def transcribe(self, audio_path, language="zh"):
"""
将音频文件转录为文本
Args:
audio_path: 音频文件路径
language: 语言代码,"zh" 中文,"en" 英文
Returns:
转录后的文本字符串
"""
segments, info = self.model.transcribe(audio_path, language=language)
# 打印识别信息
print(f"检测到语言: {info.language},置信度: {info.language_probability:.2f}")
# 拼接所有片段文本
full_text = ""
for segment in segments:
full_text += segment.text + " "
return full_text.strip()
# 使用示例
if __name__ == "__main__":
stt = SpeechToText(model_size="base")
text = stt.transcribe("recordings/meeting_20250916_100000.wav")
print("转录文本:")
print(text)
# 保存转录结果
with open("transcripts/meeting_transcript.txt", "w", encoding="utf-8") as f:
f.write(text)
3. Qwen3-1.7B-FP8模型调用模块
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
class Qwen3Model:
def __init__(self, model_path="Qwen3-1.7B-FP8", device=None):
"""
初始化Qwen3-1.7B-FP8模型
Args:
model_path: 模型路径
device: 运行设备,None 自动选择
"""
self.model_path = model_path
self.tokenizer = AutoTokenizer.from_pretrained(model_path)
# 自动选择设备
self.device = device if device else ("cuda" if torch.cuda.is_available() else "cpu")
# 加载模型
self.model = AutoModelForCausalLM.from_pretrained(
model_path,
torch_dtype="auto",
device_map="auto" if self.device == "cuda" else None
)
# 设置生成参数
self.generation_config = {
"max_new_tokens": 2048,
"temperature": 0.6,
"top_p": 0.95,
"top_k": 20,
"do_sample": True,
"pad_token_id": self.tokenizer.pad_token_id,
"eos_token_id": self.tokenizer.eos_token_id
}
def process_text(self, text, task_type="summary"):
"""
使用Qwen3处理文本
Args:
text: 输入文本
task_type: 任务类型,"summary" 摘要,"action_items" 行动项提取,"decision" 决策提取
Returns:
处理结果
"""
# 根据任务类型构建提示词
prompts = {
"summary": f"""请对以下会议内容进行总结,包括主要讨论话题、关键观点和结论:
{text}
会议总结:""",
"action_items": f"""请从以下会议内容中提取行动项,包括负责人和截止时间(如提及):
{text}
行动项列表:""",
"decision": f"""请从以下会议内容中提取决策事项,包括决策内容和相关责任人:
{text}
决策记录:""",
"full_minutes": f"""请根据以下会议内容生成完整会议纪要,包括会议主题、参会人员(如提及)、时间(如提及)、议程、讨论内容、行动项和决策:
{text}
会议纪要:"""
}
# 检查任务类型是否有效
if task_type not in prompts:
raise ValueError(f"无效的任务类型: {task_type},可选类型: {list(prompts.keys())}")
prompt = prompts[task_type]
# 应用聊天模板
messages = [{"role": "user", "content": prompt}]
input_text = self.tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=True
)
# 编码输入
inputs = self.tokenizer([input_text], return_tensors="pt").to(self.device)
# 生成输出
with torch.no_grad():
generated_ids = self.model.generate(
**inputs,
**self.generation_config
)
# 解码输出
output_ids = generated_ids[0][len(inputs.input_ids[0]):].tolist()
# 解析思考内容和最终结果
try:
# 查找思考结束标记
index = len(output_ids) - output_ids[::-1].index(151668) # 151668 是思考结束标记ID
except ValueError:
index = 0
# 提取并返回最终结果
result = self.tokenizer.decode(output_ids[index:], skip_special_tokens=True).strip()
return result
# 使用示例
if __name__ == "__main__":
qwen = Qwen3Model()
# 读取转录文本
with open("transcripts/meeting_transcript.txt", "r", encoding="utf-8") as f:
meeting_text = f.read()
# 生成完整会议纪要
minutes = qwen.process_text(meeting_text, task_type="full_minutes")
print("生成的会议纪要:")
print(minutes)
# 保存会议纪要
with open("minutes/meeting_minutes.txt", "w", encoding="utf-8") as f:
f.write(minutes)
4. 会议纪要生成主程序
import os
import time
from datetime import datetime
from AudioRecorder import AudioRecorder
from SpeechToText import SpeechToText
from Qwen3Model import Qwen3Model
class MeetingMinuteGenerator:
def __init__(self):
"""初始化会议纪要生成器"""
# 创建必要目录
self.directories = ["recordings", "transcripts", "minutes", "exports"]
for dir in self.directories:
os.makedirs(dir, exist_ok=True)
# 初始化组件
self.recorder = AudioRecorder()
self.stt = SpeechToText(model_size="base")
self.qwen = Qwen3Model()
# 生成时间戳
self.timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
def record_meeting(self):
"""录制会议音频"""
print("\n=== 开始会议录制 ===")
try:
import threading
recording_thread = threading.Thread(target=self.recorder.start_recording)
recording_thread.start()
print("会议录制中... 按 Enter 键停止录制")
input() # 等待用户按Enter停止
self.audio_path = self.recorder.stop_recording()
recording_thread.join()
return self.audio_path
except Exception as e:
print(f"录制出错: {str(e)}")
self.recorder.stop_recording()
return None
def transcribe_meeting(self, audio_path=None):
"""转录会议音频为文本"""
print("\n=== 开始语音转文本 ===")
if not audio_path:
audio_path = self.audio_path
if not audio_path or not os.path.exists(audio_path):
raise FileNotFoundError(f"音频文件不存在: {audio_path}")
self.transcript = self.stt.transcribe(audio_path)
# 保存转录文本
self.transcript_path = f"transcripts/transcript_{self.timestamp}.txt"
with open(self.transcript_path, "w", encoding="utf-8") as f:
f.write(self.transcript)
print(f"转录完成,已保存至: {self.transcript_path}")
return self.transcript
def generate_minutes(self, task_type="full_minutes"):
"""生成会议纪要"""
print("\n=== 开始生成会议纪要 ===")
if not hasattr(self, 'transcript'):
if hasattr(self, 'transcript_path') and os.path.exists(self.transcript_path):
with open(self.transcript_path, "r", encoding="utf-8") as f:
self.transcript = f.read()
else:
raise ValueError("没有找到转录文本,请先进行转录")
self.minutes = self.qwen.process_text(self.transcript, task_type=task_type)
# 保存会议纪要
self.minutes_path = f"minutes/minutes_{self.timestamp}.txt"
with open(self.minutes_path, "w", encoding="utf-8") as f:
f.write(self.minutes)
print(f"会议纪要生成完成,已保存至: {self.minutes_path}")
return self.minutes
def export_minutes(self, format="docx"):
"""导出会议纪要为不同格式"""
print(f"\n=== 导出会议纪要为 {format} 格式 ===")
if not hasattr(self, 'minutes'):
if hasattr(self, 'minutes_path') and os.path.exists(self.minutes_path):
with open(self.minutes_path, "r", encoding="utf-8") as f:
self.minutes = f.read()
else:
raise ValueError("没有找到会议纪要,请先生成会议纪要")
# 根据格式导出
if format == "docx":
return self._export_to_docx()
elif format == "pdf":
return self._export_to_pdf()
elif format == "pptx":
return self._export_to_pptx()
else:
raise ValueError(f"不支持的导出格式: {format}")
def _export_to_docx(self):
"""导出为Word文档"""
from docx import Document
from docx.shared import Inches
doc = Document()
doc.add_heading(f"会议纪要 - {self.timestamp}", level=1)
doc.add_paragraph(self.minutes)
export_path = f"exports/minutes_{self.timestamp}.docx"
doc.save(export_path)
print(f"Word文档已保存至: {export_path}")
return export_path
def _export_to_pdf(self):
"""导出为PDF文档(需要安装pdfkit和wkhtmltopdf)"""
try:
import pdfkit
html_content = f"<html><head><meta charset='UTF-8'><title>会议纪要 - {self.timestamp}</title></head><body><h1>会议纪要 - {self.timestamp}</h1><p>{self.minutes.replace(' ', ' ').replace('\n', '<br>')}</p></body></html>"
export_path = f"exports/minutes_{self.timestamp}.pdf"
pdfkit.from_string(html_content, export_path)
print(f"PDF文档已保存至: {export_path}")
return export_path
except ImportError:
raise ImportError("导出PDF需要安装pdfkit: pip install pdfkit")
except Exception as e:
raise Exception(f"PDF导出失败: {str(e)},可能需要安装wkhtmltopdf")
def _export_to_pptx(self):
"""导出为PPT演示文稿"""
from pptx import Presentation
from pptx.util import Inches, Pt
prs = Presentation()
# 添加标题页
slide_layout = prs.slide_layouts[0]
slide = prs.slides.add_slide(slide_layout)
title = slide.shapes.title
subtitle = slide.placeholders[1]
title.text = "会议纪要"
subtitle.text = f"生成时间: {self.timestamp}"
# 拆分内容为多个幻灯片
content = self.minutes.split('\n\n')
for i, section in enumerate(content):
if i == 0: # 第一部分作为概述
slide_layout = prs.slide_layouts[1]
slide = prs.slides.add_slide(slide_layout)
title = slide.shapes.title
content_placeholder = slide.placeholders[1]
title.text = "会议概述"
content_placeholder.text = section
elif section.strip(): # 跳过空段落
slide_layout = prs.slide_layouts[1]
slide = prs.slides.add_slide(slide_layout)
title = slide.shapes.title
content_placeholder = slide.placeholders[1]
# 尝试提取标题行
lines = section.split('\n', 1)
if len(lines) > 1 and len(lines[0]) < 50: # 如果第一行较短,作为标题
title.text = lines[0]
content_placeholder.text = lines[1]
else:
title.text = f"会议内容 {i}"
content_placeholder.text = section
export_path = f"exports/minutes_{self.timestamp}.pptx"
prs.save(export_path)
print(f"PPT演示文稿已保存至: {export_path}")
return export_path
def run_full_pipeline(self):
"""运行完整流程:录制 -> 转录 -> 生成纪要 -> 导出"""
print("\n=== 会议纪要生成器 ===")
# 1. 录制会议
audio_path = self.record_meeting()
if not audio_path:
print("录制失败,程序退出")
return
# 2. 转录会议
try:
self.transcribe_meeting(audio_path)
except Exception as e:
print(f"转录失败: {str(e)}")
return
# 3. 生成会议纪要
try:
self.generate_minutes()
print("\n=== 会议纪要预览 ===")
print(self.minutes[:500] + "...") # 打印前500字符预览
except Exception as e:
print(f"生成纪要失败: {str(e)}")
return
# 4. 导出为多种格式
try:
self.export_minutes("docx")
self.export_minutes("pptx")
print("\n=== 所有流程完成 ===")
except Exception as e:
print(f"导出失败: {str(e)}")
# 主程序入口
if __name__ == "__main__":
generator = MeetingMinuteGenerator()
generator.run_full_pipeline()
完整项目结构
meeting_minutes_generator/
├── AudioRecorder.py # 音频录制模块
├── SpeechToText.py # 语音转文本模块
├── Qwen3Model.py # Qwen3模型调用模块
├── main.py # 主程序
├── requirements.txt # 依赖列表
├── recordings/ # 录音文件
├── transcripts/ # 转录文本
├── minutes/ # 会议纪要
└── exports/ # 导出文件
性能优化建议
模型加载优化
- 使用4-bit或8-bit量化进一步减少内存占用
# 安装量化依赖
pip install bitsandbytes
# 修改Qwen3Model初始化代码
self.model = AutoModelForCausalLM.from_pretrained(
model_path,
load_in_4bit=True, # 启用4-bit量化
device_map="auto",
quantization_config=BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16
)
)
推理速度优化
- 使用vllm加速推理
pip install vllm
python -m vllm.entrypoints.api_server --model Qwen3-1.7B-FP8 --port 8000
- 修改Qwen3Model以使用vllm API
import requests
import json
class Qwen3ModelVLLM:
def __init__(self, api_url="http://localhost:8000/v1/completions"):
self.api_url = api_url
self.headers = {"Content-Type": "application/json"}
def process_text(self, text, task_type="summary"):
# 构建提示词(同上)
# ...
# 调用vllm API
payload = {
"model": "Qwen3-1.7B-FP8",
"prompt": prompt,
"max_tokens": 2048,
"temperature": 0.6,
"top_p": 0.95,
"top_k": 20
}
response = requests.post(self.api_url, headers=self.headers, json=payload)
result = response.json()["choices"][0]["text"]
return result
常见问题解决
模型加载失败
- 确保模型文件完整下载
- 检查PyTorch版本是否兼容
- 尝试降低模型精度:
torch_dtype=torch.float16
语音识别准确率低
- 使用更大的Whisper模型:
model_size="medium" - 确保录音环境安静
- 尽量使用麦克风近距离录音
生成速度慢
- 确保使用GPU加速
- 减少max_new_tokens参数
- 使用vllm或SGLang部署
项目扩展方向
- 实时会议纪要:实现实时语音转写和实时纪要生成
- 多语言支持:添加多语言会议纪要生成功能
- 参会人识别:结合说话人分离技术识别不同参会人发言
- 知识库集成:连接企业知识库,自动补充会议相关背景信息
- 协作编辑:添加多人在线协作编辑会议纪要功能
总结
本项目展示了如何利用Qwen3-1.7B-FP8模型构建一个功能完备的智能会议纪要生成器。通过结合语音识别和大语言模型技术,我们实现了从会议录制到纪要生成的全自动化流程,大大提高了会议记录的效率和准确性。
该方案具有以下优势:
- 本地化部署:保护企业数据隐私
- 低硬件要求:仅需4GB显存即可运行
- 全流程自动化:一键完成录制、转录、分析和导出
- 高度可定制:可根据需求调整模型和参数
随着大语言模型技术的不断发展,未来我们可以期待更多功能增强,如实时翻译、情感分析和自动生成行动项等,进一步提升会议效率和决策质量。
点赞收藏关注三连
如果觉得本项目对你有帮助,请点赞、收藏并关注作者,获取更多AI应用实战教程!下期预告:《使用Qwen3构建企业级知识库问答系统》
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



