
简介Cluelessly 是一款开源的 AI 会议纪要助手面向开发者、远程协作团队及效率工具爱好者解决会议记录耗时、关键信息遗漏、行动项追踪困难等实际痛点。资源包含347个文件以155个 Vue 组件前端交互与界面、77个 PHP 文件Laravel 后端逻辑、41个 TypeScript 类型定义与业务逻辑为主干辅以 Markdown 文档、Shell 脚本、SQLite 数据库及 Electron 桌面集成相关文件完整覆盖从实时语音转录、AI 分析到本地存储与桌面部署的全链路实现。压缩包大小为65.05MB结构清晰含 .env.example、artisan 命令行工具、Tailwind 样式配置及 OpenAI Realtime API 集成示例便于二次开发与本地调试。目前已有111人学习下载读者可直接获取可运行的跨平台会议辅助系统源码掌握 LaravelVueElectron 多端协同架构、AI 实时流处理实践及 SQLite 本地会话持久化方案。1. 项目缘起从“会议纪要”这个痛点说起如果你和我一样经常需要参加各种线上会议无论是内部项目同步、客户需求沟通还是技术方案评审那你一定对“会后整理纪要”这件事深恶痛绝。会议开完人已经筋疲力尽但更痛苦的任务才刚刚开始你需要回放一两个小时的录音反复拖动进度条试图从一堆“嗯”、“啊”、“这个嘛”的口水话里提炼出关键决策、待办事项和核心结论。这个过程不仅耗时而且极其枯燥常常一拖再拖最后纪要变成了“回忆录”细节早已模糊不清。更糟糕的是当团队规模扩大会议频率增加这种手动整理的方式完全不可持续。我曾经尝试过市面上的一些“智能”工具但它们要么是简单的语音转文字把口语化的混乱对话原封不动地扔给你让你自己去“淘金”要么就是功能臃肿、价格昂贵还涉及到数据隐私的担忧。我需要的是一个能真正理解会议内容、能自动结构化输出、并且完全由我自己掌控的工具。这就是我动手开发Cluelessly的初衷——一个轻量、开源、可定制的 AI 会议纪要助手。它不只是一个转录工具而是一个能帮你“理解”会议并生成可直接分发的行动清单和总结报告的智能助手。“Cluelessly”这个名字本身带有“一无所知”或“漫不经心”的戏谑意味恰恰反衬了它的目标让你无需再为会议纪要的琐事费心be clueless about meeting notes从而能更专注于会议本身的内容和讨论。这个项目完全开源意味着你可以查看每一行代码根据自己的需求进行修改甚至部署在自己的服务器上确保所有会议数据都在你的掌控之中。2. 核心架构设计如何让AI“听懂”并“总结”会议一个高效的会议纪要助手绝不仅仅是语音转文字ASR那么简单。它需要完成一个从“物理信号”到“结构化知识”的完整认知链条。Cluelessly 的架构就是围绕这个链条设计的主要分为四个核心环节。2.1 音频处理与高精度语音转写这是所有工作的基础。如果转写的文字错误百出后续的分析就是空中楼阁。我们的首要目标是获取一份尽可能准确的会议文字稿。技术选型与理由市面上开源的ASR方案很多如 Whisper、Vosk 等。我最终选择了OpenAI 的 Whisper模型。原因有三第一它的准确率在开源模型中公认最高尤其是在中英文混合、带口音或背景噪音的场景下表现非常稳健。第二它支持“大模型本地部署”虽然模型文件较大例如large-v3模型约3GB但一旦部署所有计算都在本地完成无需调用任何外部API彻底解决了数据隐私和网络延迟的问题。第三Whisper 不仅输出文字还能带时间戳Timestamp这为后续的“谁在什么时候说了什么”的发言者区分提供了可能。实操细节与避坑在本地部署 Whisper 时第一个坑就是性能。直接用CPU跑一个两小时的会议音频可能会让你等到怀疑人生。必须使用 GPU 加速。在配置环境时确保你的 PyTorch 版本是支持 CUDA 的。安装命令大概是这样的pip install openai-whisper pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whisper/cu118使用起来非常简单import whisper model whisper.load_model(“large-v3”) # 根据你的硬件选择模型大小如 base, small, medium result model.transcribe(“meeting_audio.mp3”) text result[“text”] segments result[“segments”] # 包含时间戳的段落列表这里的关键是segments。它返回一个列表每个元素包含start,end,text。这就是我们后续进行发言者分离和内容分析的原材料。注意Whisper 的large-v3模型效果最好但对显存要求也高约10GB。如果硬件受限可以降级到medium或small模型在准确率和速度之间取得平衡。实测中medium模型在大多数场景下已经足够可用。2.2 发言者分离区分“谁说了什么”只有一份完整的文字稿还不够。一份合格的会议纪要需要知道“这句话是谁说的”特别是当需要明确任务负责人时。这就是发言者分离Speaker Diarization要解决的问题。方案选择纯粹的声纹聚类如 pyannote.audio效果很好但配置复杂且对音频质量如每人有独立麦克风要求较高。对于更常见的、单声道混合了所有人声音的会议录音比如腾讯会议导出的音频一个更简单实用的方法是基于时间戳和文本内容的启发式规则。我们的实现逻辑静默检测分割首先我们利用segments中的时间戳。Whisper 本身会在说话间隙进行分割。我们可以设定一个阈值比如静默超过1.5秒认为这是一个话轮turn的结束。文本内容聚类然后对每个话轮的文本使用嵌入模型例如 Sentence-BERT将其转换为向量。简单聚类最后对这些向量进行简单的聚类如 K-Means但K值需要预估说话人数。聚类后同一类的段落就被认为是同一个发言者。这种方法虽然不是100%准确但在3-5人的会议中结合上下文正确率可以接受。代码逻辑示例如下from sentence_transformers import SentenceTransformer from sklearn.cluster import KMeans import numpy as np # 假设 turn_texts 是分割后的话轮文本列表 embedder SentenceTransformer(‘paraphrase-MiniLM-L6-v2’) embeddings embedder.encode(turn_texts) # 预估说话人数量这里假设为3 num_speakers 3 kmeans KMeans(n_clustersnum_speakers) kmeans.fit(embeddings) labels kmeans.labels_ # 每个话轮对应的说话人标签现在我们就有了一个结构化的列表[(start_time, end_time, speaker_id, text), …]。2.3 语义理解与关键信息抽取这是AI发挥核心价值的阶段。我们需要让模型理解这些文字并提取出我们关心的要素。传统的关键词提取或规则匹配在这里力不从心我们需要大语言模型LLM的语义理解能力。为什么用LLM而不是规则会议讨论是自由且多样的。待办事项可能以“小明你下周把方案发出来”的形式出现也可能以“我们需要解决登录超时的问题”这种集体任务的形式出现。决策点可能隐藏在漫长的讨论中。LLM的优势在于它能理解上下文和意图。Prompt工程是关键我们不是简单地把全文扔给LLM说“总结一下”而是通过精心设计的提示词Prompt引导它进行结构化输出。Cluelessly 的核心Prompt大致如下你是一个专业的会议纪要助手。请分析以下会议转录文本并严格按JSON格式输出。 会议参与人[从发言者分离结果中提取的姓名或代号列表] 会议文本[按时间顺序排列的带发言人的文本] 请从文本中提取并总结以下信息 1. **会议主题** 用一句话概括。 2. **核心讨论点** 列出3-5个主要讨论的话题。 3. **关键决策** 列出会议中做出的所有明确决定。 4. **待办事项Action Items** 列出所有明确的任务。每一项必须包含任务内容、负责人从参与人列表中匹配、截止时间如果提及。 5. **后续计划** 下一步的整体安排或会议安排。 请确保所有信息均来源于提供的文本不要捏造。对于负责人不明确的任务负责人字段设为“待定”。本地大模型部署为了隐私和成本我们同样在本地部署LLM。可以选择像ChatGLM3-6B、Qwen-7B或Llama 3这类开源模型并使用Ollama或vLLM等框架进行本地服务化部署。调用方式就变成了向本地API发送上述Prompt。import requests import json def summarize_with_llm(structured_text): prompt f”…“ # 将上面的提示词模板化填入具体内容 url “http://localhost:11434/api/generate” # Ollama 默认地址 payload { “model”: “qwen:7b”, # 你本地部署的模型名 “prompt”: prompt, “stream”: False } response requests.post(url, jsonpayload) result json.loads(response.text)[“response”] # 解析 result 中的 JSON 部分 summary json.loads(result) # 这里需要处理LLM返回的不稳定格式 return summary实操心得LLM的返回格式可能不稳定有时会在JSON外包裹额外解释文字。一个稳健的做法是在Prompt中要求它首先输出“JSON”结束时输出“/JSON”然后在代码中用正则表达式提取这两个标签之间的内容进行解析。2.4 结构化输出与集成最后一步将LLM输出的JSON数据渲染成人类可读的、格式优美的会议纪要。我们可以选择输出为Markdown文件它轻便、易读并且能很好地兼容任务列表等格式。Markdown模板示例# 会议纪要 - **时间** 2023-10-27 14:00-15:30 - **主题** {{会议主题}} - **参会人** 张三李四王五 ## 核心讨论点 1. {{讨论点1}} 2. {{讨论点2}} ## 关键决策 - [ ] 决策一{{决策内容}} - [ ] 决策二{{决策内容}} ## 待办事项 (Action Items) | 任务内容 | 负责人 | 截止时间 | 状态 | | :--- | :--- | :--- | :--- | | {{任务1}} | 张三 | 2023-11-03 | 待开始 | | {{任务2}} | 李四 | 2023-11-01 | 进行中 | ## 后续计划 - {{计划内容}}我们可以使用像Jinja2这样的模板引擎将JSON数据填充到模板中生成最终的meeting_summary.md文件。这个文件可以直接分享到团队群或者导入到Notion、飞书等协作平台。3. 从源码到可运行应用工程化实践有了核心模块我们需要一个“胶水”把它们粘合起来并处理各种边缘情况形成一个用户可以方便使用的应用。3.1 项目结构与依赖管理一个清晰的项目结构是长期维护的基础。Cluelessly 的目录结构大致如下cluelessly/ ├── cli.py # 命令行入口点 ├── config.yaml # 配置文件模型路径、API地址、参数等 ├── requirements.txt # Python依赖 ├── src/ │ ├── audio/ │ │ ├── transcribe.py # Whisper 转写模块 │ │ └── diarization.py # 发言者分离模块 │ ├── analysis/ │ │ └── summarizer.py # LLM 总结模块 │ ├── output/ │ │ └── render.py # 纪要渲染模块Markdown │ └── utils/ │ └── helpers.py # 通用工具函数 └── templates/ └── meeting_template.md.j2 # Jinja2 纪要模板使用requirements.txt管理依赖是 Python 项目的标准做法。我们的依赖可能包括openai-whisper,sentence-transformers,scikit-learn,ollama(或openai库如果后期切换API),jinja2,pyyaml等。3.2 配置化与参数调优不是所有会议都一样。有的会议技术性强需要更多技术术语识别有的会议是头脑风暴需要更关注创意点。因此将关键参数配置化非常重要。config.yaml示例whisper: model_size: “medium” # small, medium, large-v3 device: “cuda” # or “cpu” language: “zh” # 可设为 None 以自动检测 diarization: silence_threshold_sec: 1.5 embedding_model: “paraphrase-MiniLM-L6-v2” num_speakers: null # 设为 null 可尝试自动估计 summarizer: llm_provider: “ollama” # 可选 “openai”, “anthropic” 等 ollama_base_url: “http://localhost:11434” ollama_model: “qwen:7b” prompt_template_path: “./prompts/summary_v1.txt” output: template_path: “./templates/meeting_template.md.j2” date_format: “%Y-%m-%d”这样用户无需修改代码只需调整配置文件就能适应不同的场景。例如在内存较小的机器上可以将model_size改为small如果会议主要是中文明确指定language: “zh”可以提高转写准确率。3.3 构建命令行界面 (CLI)对于开发者或技术型用户一个命令行工具是最直接高效的交互方式。我们使用 Python 的argparse或更现代的typer库来构建CLI。一个简单的cli.py示例import typer from src.audio.transcribe import transcribe_audio from src.audio.diarization import separate_speakers from src.analysis.summarizer import generate_summary from src.output.render import render_markdown app typer.Typer() app.command() def process(audio_path: str, output_path: str “./meeting_summary.md”): “””处理音频文件并生成会议纪要。””” typer.echo(f”开始处理音频文件: {audio_path}“) # 1. 转写 text_segments transcribe_audio(audio_path) typer.echo(“✓ 语音转写完成”) # 2. 发言者分离 turns separate_speakers(text_segments) typer.echo(“✓ 发言者分离完成”) # 3. AI总结 summary_json generate_summary(turns) typer.echo(“✓ AI内容分析与总结完成”) # 4. 渲染输出 render_markdown(summary_json, output_path) typer.echo(f”✓ 会议纪要已生成: {output_path}“) if __name__ “__main__”: app()用户只需要在终端执行python cli.py process meeting.mp3就可以一键获得会议纪要Markdown文件。3.4 错误处理与日志记录一个健壮的应用必须能妥善处理异常。比如音频文件损坏、Whisper模型下载失败、本地LLM服务未启动、网络超时等等。关键的错误处理点文件检查在处理前验证音频文件是否存在、格式是否支持。模型加载捕获模型加载时的异常给出清晰的指引如“未找到Whisper模型请运行whisper —-help查看下载指令”。LLM调用设置请求超时并重试机制。如果LLM返回了非JSON内容要有降级方案如尝试用正则提取或提示用户重试。日志系统使用 Python 的logging模块将关键步骤、警告和错误记录到文件方便后期排查问题。日志级别可以设置为INFO用于正常流程跟踪WARNING和ERROR用于问题定位。4. 进阶优化与踩坑实录在实际开发和使用的过程中我遇到了不少预料之外的问题也做了一些优化这些经验可能比基础功能更有价值。4.1 处理长音频与内存优化Whisper 处理超长音频如4小时以上的研讨会时可能会遇到内存不足OOM的问题。因为默认它会尝试将整个音频加载到内存中进行处理。解决方案分块处理。Whisper 本身就支持分块转录。我们可以将长音频按固定时长如30分钟分割分别转写最后合并结果。但这里有个坑直接合并会导致上下文断裂影响发言者分离和LLM总结的连贯性。我们的策略在分块时设置重叠区Overlap。例如每30分钟一段相邻两段重叠5分钟。这样在合并文本时可以通过重叠部分的内容进行对齐和去重尽可能保证语义的连贯性。在调用Whisper时可以使用transcribe()方法的segment参数进行更细粒度的控制。4.2 提升发言者分离的准确性基于文本嵌入的聚类方法在说话人风格差异大时效果不错但如果参会人说话风格相似或者会议中穿插了大量“对”、“是的”等短句聚类就容易出错。优化方法一结合声学特征。如果条件允许可以集成pyannote.audio这样的专业声纹工具。它不依赖文本纯粹通过声音特征进行聚类与我们的文本聚类方法可以形成互补。我们可以设计一个投票机制当两种方法结果不一致时优先采用声学方法的结果或者交给LLM结合上下文判断。优化方法二人名预注册。如果会议开始前就知道参会人名单可以做一个简单的“声纹注册”。让每个人说一两句固定的话如自我介绍提取其声音嵌入向量。在正式会议中将每个话轮的声音片段与这些预注册的向量进行比对找到最相似的人。这能极大提升准确率特别适用于定期召开的固定团队会议。4.3 驯服不稳定的LLM输出LLM的“幻觉”和格式不稳定性是最大的挑战之一。你要求输出JSON它可能给你一段前面带解释的文本或者JSON的键名用了中文引号。Prompt工程的迭代经过多次测试我发现以下技巧很有效角色扮演与格式强调在Prompt开头强烈明确其角色和输出格式要求。例如“你是一个严格遵守指令的JSON生成器。你必须只输出一个合法的JSON对象不要有任何额外的解释、前缀或后缀。”提供输出示例在Prompt中直接给出一个期望的JSON结构示例。LLM的模仿能力很强看到例子后输出格式的稳定性会大幅提高。分步指令对于复杂抽取可以要求LLM先逐步思考。例如“首先找出所有包含任务分配的句子。然后为每个任务提取负责人和截止时间。最后将结果组织成如下JSON格式...”后处理校验在代码中对LLM的返回结果一定要用try…except json.JSONDecodeError包裹。如果解析失败可以尝试用正则清洗文本或者给用户一个友好提示并附上原始文本让用户手动处理。4.4 从CLI到Web界面提升易用性对于非技术背景的团队成员命令行工具门槛太高。一个简单的Web界面可以极大提升工具的可用性。轻量级实现方案使用Gradio或Streamlit这类Python框架可以在极少的代码量下构建一个交互式Web应用。核心逻辑复用只是将CLI的输入文件上传和输出结果显示和下载换成Web组件。一个用Gradio实现的极简界面可能只需要几十行代码import gradio as gr from cli import process # 导入我们之前写好的处理函数 def process_audio_interface(audio_file): # 临时保存上传的文件 temp_path “/tmp/uploaded_audio.mp3” # … 保存文件逻辑 output_path “/tmp/summary.md” process(temp_path, output_path) # 调用核心处理流程 # 读取生成的Markdown内容返回 with open(output_path, ‘r’, encoding‘utf-8’) as f: content f.read() return content demo gr.Interface( fnprocess_audio_interface, inputsgr.Audio(type“filepath”, label“上传会议录音”), outputsgr.Markdown(label“生成的会议纪要”), title“Cluelessly - AI会议纪要助手” ) demo.launch()这样团队成员只需打开浏览器上传录音文件稍等片刻就能看到并下载整理好的纪要体验瞬间提升。开发 Cluelessly 的过程是一个典型的“用技术解决具体生活痛点”的实践。它涉及了音频处理、机器学习、自然语言处理和软件工程等多个领域。开源出来是希望这个思路和实现能给大家提供一个起点。你可以直接使用它更可以基于它进行改造比如集成到你的钉钉/飞书机器人里自动处理群里的会议录音或者增加对视频文件提取音频的支持甚至训练一个微调模型专门用于你所在行业如法律、医疗的术语和纪要格式。技术的价值最终在于它能否实实在在地提升效率解放人力。希望 Cluelessly 能成为你会议效率工具箱里的一件得力工具。本文还有配套的精品资源点击获取