100行代码构建智能会议纪要生成器:Qwen3-1.7B-FP8实战指南

100行代码构建智能会议纪要生成器:Qwen3-1.7B-FP8实战指南

【免费下载链接】Qwen3-1.7B-FP8 Qwen3-1.7B的 FP8 版本,具有以下功能: 类型:因果语言模型 训练阶段:训练前和训练后 参数数量:17亿 参数数量(非嵌入):1.4B 层数:28 注意力头数量(GQA):Q 为 16 个,KV 为 8 个 上下文长度:32,768 【免费下载链接】Qwen3-1.7B-FP8 项目地址: https://ai.gitcode.com/hf_mirrors/Qwen/Qwen3-1.7B-FP8

痛点与解决方案

你是否还在为冗长会议后的纪要整理而烦恼?传统人工记录效率低下、信息遗漏严重,而市场上的AI工具要么收费高昂,要么部署复杂。本文将展示如何使用Qwen3-1.7B-FP8模型,仅需100行代码构建一个功能完备的智能会议纪要生成器,彻底解决会议记录难题。

读完本文你将获得:

  • Qwen3-1.7B-FP8模型的本地化部署方案
  • 会议音频转文本的完整实现代码
  • 基于AI的会议内容自动分析与结构化输出
  • 支持多格式导出的纪要生成工具

技术选型对比

方案模型大小推理速度硬件要求中文支持部署难度
GPT-4 API-优秀
Llama3-8B8B中等8GB显存一般
Qwen3-1.7B-FP81.7B4GB显存优秀
通义千问API-优秀

项目架构设计

mermaid

环境准备

硬件要求

  • 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(' ', '&nbsp;').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
    )
)

推理速度优化

  1. 使用vllm加速推理
pip install vllm
python -m vllm.entrypoints.api_server --model Qwen3-1.7B-FP8 --port 8000
  1. 修改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部署

项目扩展方向

  1. 实时会议纪要:实现实时语音转写和实时纪要生成
  2. 多语言支持:添加多语言会议纪要生成功能
  3. 参会人识别:结合说话人分离技术识别不同参会人发言
  4. 知识库集成:连接企业知识库,自动补充会议相关背景信息
  5. 协作编辑:添加多人在线协作编辑会议纪要功能

总结

本项目展示了如何利用Qwen3-1.7B-FP8模型构建一个功能完备的智能会议纪要生成器。通过结合语音识别和大语言模型技术,我们实现了从会议录制到纪要生成的全自动化流程,大大提高了会议记录的效率和准确性。

该方案具有以下优势:

  • 本地化部署:保护企业数据隐私
  • 低硬件要求:仅需4GB显存即可运行
  • 全流程自动化:一键完成录制、转录、分析和导出
  • 高度可定制:可根据需求调整模型和参数

随着大语言模型技术的不断发展,未来我们可以期待更多功能增强,如实时翻译、情感分析和自动生成行动项等,进一步提升会议效率和决策质量。

点赞收藏关注三连

如果觉得本项目对你有帮助,请点赞、收藏并关注作者,获取更多AI应用实战教程!下期预告:《使用Qwen3构建企业级知识库问答系统》

【免费下载链接】Qwen3-1.7B-FP8 Qwen3-1.7B的 FP8 版本,具有以下功能: 类型:因果语言模型 训练阶段:训练前和训练后 参数数量:17亿 参数数量(非嵌入):1.4B 层数:28 注意力头数量(GQA):Q 为 16 个,KV 为 8 个 上下文长度:32,768 【免费下载链接】Qwen3-1.7B-FP8 项目地址: https://ai.gitcode.com/hf_mirrors/Qwen/Qwen3-1.7B-FP8

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值