
1. 项目概述从“聊完就忘”到“过目不忘”的会议助手在团队协作中会议是决策和同步的核心但“会议后遗症”几乎人人都有谁负责什么上次那个关键数据是多少我们不是讨论过这个方案吗这些信息往往散落在不同人的笔记、聊天记录或邮件里随着时间推移上下文丢失得一干二净。传统的笔记软件或聊天机器人只能被动记录或进行单次问答缺乏将历史信息串联起来、形成持续记忆的能力。这正是我动手开发“AI会议记忆助手”的初衷——打造一个能真正记住每次会议内容并能基于这些记忆智能回答问题的数字同事。简单来说这个项目是一个结合了持久化记忆与大型语言模型推理能力的智能体。它不是一个简单的记事本而是一个会学习、会回忆的AI伙伴。你可以向它录入会议纪要也可以随时向它提问比如“上次会议谁负责前端联调”或“关于用户登录流程我们达成了哪些共识”。它会从所有存储的记忆中检索相关信息并生成一个结合上下文的、准确的回答。整个系统基于Python构建前端使用Streamlit快速搭建交互界面核心的“大脑”则接入了Groq提供的超高速LLM API确保了响应的实时性。无论你是项目经理、团队负责人还是需要频繁参与跨部门会议的开发者这个工具都能帮你把散落的会议智慧沉淀下来随用随取。2. 核心设计思路如何让AI“记住”并“思考”构建一个有记忆的AI助手关键在于解决两个核心问题信息如何被有效地存储和检索以及如何让AI基于这些信息进行有逻辑的推理。这区别于简单的关键词匹配搜索也不同于没有上下文记忆的聊天机器人。2.1 记忆系统的设计从临时缓存到持久化存储最初我考虑过使用内存中的列表或字典来临时存储对话。但这显然不实用一旦程序重启所有记忆都会消失。因此持久化存储是必须的。选择本地JSON文件作为存储介质是基于以下几个考量轻量且无需外部依赖对于个人或小团队使用的工具引入数据库如SQLite、PostgreSQL会增加部署和管理的复杂度。JSON文件无需安装任何额外服务Python原生支持读写非常轻便。结构化与可读性JSON格式本身是结构化的易于程序解析。同时它也是人类可读的在调试或手动检查记忆内容时直接用文本编辑器打开即可这对开发阶段的排查非常友好。灵活性每条记忆可以作为一个包含多个字段如时间戳、内容、类型、可能的标签的对象存储。未来如果需要扩展比如增加对会议录音转文字的支持只需在JSON结构中添加新字段即可而无需改动数据库表结构。当然JSON方案也有其局限性例如当记忆条目非常多时全文检索的效率会下降。但在项目初期或中小规模使用场景下其简单可靠的优势非常明显。一个典型的内存条目设计如下{ “id”: “20240415_103002”, “timestamp”: “2024-04-15T10:30:02”, “content”: “Shiv将负责下周的产品演示会议筹备包括材料准备和场地协调。”, “type”: “note”, “source”: “user_input” }2.2 智能检索与推理链连接记忆与答案仅有存储还不够如何从海量记忆中精准找到与问题相关的片段是第二个挑战。这里采用了“检索-增强生成”的策略但进行了一定简化。意图识别当用户输入一段文本时系统首先需要判断这是一个需要存储的新笔记还是一个需要回答的问题。我采用了一个基于规则轻量级语义判断的混合方法。例如检查输入是否以“问”、“请问”、“谁”、“什么”、“何时”等疑问词开头或者句子末尾是否有问号。同时也会用一个小型的文本分类模型或简单的关键词列表来辅助判断避免将“请记录明天开会”这样的指令误判为问题。记忆检索如果判定为问题下一步就是从JSON文件中检索相关记忆。这里没有使用复杂的向量数据库而是采用了基于TF-IDF或BM25算法的关键词相似度匹配。具体流程是将用户的问题进行分词处理提取关键实体和意图关键词。遍历所有存储的记忆条目计算每个条目内容与问题关键词的相似度得分。选取得分最高的前K条例如前3条或前5条作为“相关记忆上下文”。这个过程虽然不如基于嵌入向量的语义搜索精准但对于会议纪要这类主题相对集中、语言较为规范的文本效果已经足够好且实现成本极低。上下文构建与LLM调用检索到的相关记忆片段不会直接作为答案输出因为它们是零散的原始文本。我们需要LLM来扮演“理解者”和“总结者”的角色。系统会构建一个这样的提示词模板发送给Groq LLM你是一个专业的会议记忆助手。请基于以下背景信息回答用户的问题。 背景信息历史会议记录 1. [记忆片段1的内容] 2. [记忆片段2的内容] ... 用户问题[用户输入的问题] 请直接给出准确、简洁的答案。如果背景信息中无法找到明确答案请如实告知“根据现有记录无法确定该信息”。通过这种方式LLM的推理能力被严格限制在提供的“记忆”范围内有效避免了幻觉确保答案有据可依。2.3 技术栈选型背后的逻辑Python作为AI和数据处理领域的事实标准语言拥有从Web框架到机器学习库最丰富的生态系统快速原型开发的首选。Streamlit核心需求是一个能让非技术同事也能轻松使用的界面。Streamlit允许我用纯Python脚本快速构建出带有输入框、按钮、表格和数据展示的Web应用无需接触HTML/CSS/JavaScript极大地缩短了从后端逻辑到前端交互的开发路径。Groq API这是本项目的“加速器”。传统的云LLM API如OpenAI有时响应延迟较高。Groq以其极低的推理延迟著称这对于一个需要实时问答、追求流畅交互体验的工具来说至关重要。其免费层额度也足够用于项目原型验证和小规模使用。JSON dotenvJSON负责数据持久化dotenv则负责管理敏感信息如Groq API密钥。将API密钥等配置写在环境变量或.env文件中而不是硬编码在脚本里是保障安全性和便于跨环境部署的基本实践。3. 分步实现与核心代码解析下面我将拆解整个系统的构建步骤并附上关键代码的说明。你可以跟随这些步骤在自己的环境中复现这个项目。3.1 环境准备与依赖安装首先确保你的电脑上安装了Python建议3.8及以上版本。创建一个新的项目目录并在其中初始化虚拟环境这能隔离项目依赖避免包版本冲突。# 创建项目目录并进入 mkdir ai_meeting_assistant cd ai_meeting_assistant # 创建虚拟环境以venv为例 python -m venv venv # 激活虚拟环境 # 在Windows上 venv\Scripts\activate # 在macOS/Linux上 source venv/bin/activate激活虚拟环境后命令行提示符前通常会显示(venv)。接下来创建requirements.txt文件列出所有需要的库streamlit1.28.0 groq0.3.0 python-dotenv1.0.0然后使用pip安装pip install -r requirements.txt3.2 记忆存储模块的实现我们创建一个名为memory_manager.py的文件专门负责记忆的读写和检索逻辑。import json import os from datetime import datetime from typing import List, Dict, Any import hashlib class MemoryManager: def __init__(self, memory_file: str “meeting_memory.json”): self.memory_file memory_file self.memories self._load_memories() def _load_memories(self) - List[Dict[str, Any]]: 从JSON文件加载现有记忆。如果文件不存在则返回空列表。 if os.path.exists(self.memory_file): with open(self.memory_file, ‘r’, encoding‘utf-8’) as f: try: return json.load(f) except json.JSONDecodeError: # 文件内容损坏返回空列表 return [] return [] def _save_memories(self): 将当前记忆列表保存到JSON文件。 with open(self.memory_file, ‘w’, encoding‘utf-8’) as f: json.dump(self.memories, f, ensure_asciiFalse, indent2) def add_memory(self, content: str, memory_type: str “note”) - str: 添加一条新记忆。 Args: content: 记忆内容文本。 memory_type: 记忆类型如 ‘note‘, ‘action_item‘, ‘decision‘。 Returns: 生成的内存ID。 memory_id hashlib.md5(f“{datetime.now().isoformat()}{content}”.encode()).hexdigest()[:8] new_memory { “id”: memory_id, “timestamp”: datetime.now().isoformat(), “content”: content, “type”: memory_type, “source”: “user_input” } self.memories.append(new_memory) self._save_memories() return memory_id def search_memories(self, query: str, top_k: int 3) - List[Dict[str, Any]]: 基于简单的内容相关性搜索记忆。 这是一个简化版的检索。在实际生产中可替换为更复杂的语义搜索如使用SentenceTransformers。 Args: query: 搜索查询字符串。 top_k: 返回最相关的记忆条数。 Returns: 按相关性排序的记忆列表。 if not query: return [] query_words set(query.lower().split()) scored_memories [] for memory in self.memories: content memory[“content”].lower() # 简单的词频匹配得分 score sum(1 for word in query_words if word in content) if score 0: scored_memories.append((score, memory)) # 按得分降序排序 scored_memories.sort(keylambda x: x[0], reverseTrue) # 返回前top_k条记忆的内容字典 return [memory for _, memory in scored_memories[:top_k]] def clear_all_memories(self): 清空所有记忆。 self.memories [] self._save_memories() print(“所有记忆已清空。”)注意这里的search_memories函数使用了非常基础的关键词匹配。对于更精准的语义搜索可以考虑集成sentence-transformers库将文本转换为向量后计算余弦相似度。但对于会议纪要这类场景关键词匹配在大多数情况下已经足够有效且响应更快。3.3 集成Groq LLM与问答逻辑接下来创建ai_engine.py处理与Groq API的交互和答案生成。import os from groq import Groq from dotenv import load_dotenv from memory_manager import MemoryManager # 加载.env文件中的环境变量 load_dotenv() class AIEngine: def __init__(self, memory_manager: MemoryManager): self.client Groq(api_keyos.getenv(“GROQ_API_KEY”)) self.memory_manager memory_manager # 可以在这里指定模型例如 ‘mixtral-8x7b-32768‘ self.model “mixtral-8x7b-32768” def generate_answer(self, question: str) - str: 基于记忆生成答案。 # 1. 检索相关记忆 relevant_memories self.memory_manager.search_memories(question) if not relevant_memories: return “当前记忆库中没有找到与您问题相关的信息。” # 2. 构建上下文提示 context_text “\n”.join([f“{i1}. {mem[‘content’]}” for i, mem in enumerate(relevant_memories)]) prompt f“”” 你是一个专业的会议记忆助手负责根据团队的历史会议记录回答问题。 请严格根据以下提供的背景信息来回答用户的问题。如果信息中明确没有答案请说“根据现有记录无法确定该信息”。 背景信息历史会议记录 {context_text} 用户问题{question} 请直接给出准确、简洁的答案 “”” # 3. 调用Groq API try: chat_completion self.client.chat.completions.create( messages[ { “role”: “system”, “content”: “你是一个准确、简洁的会议信息助手。” }, { “role”: “user”, “content”: prompt } ], modelself.model, temperature0.1, # 低温度值使输出更确定、更少创造性 max_tokens500 ) answer chat_completion.choices[0].message.content return answer.strip() except Exception as e: return f“调用AI服务时出错{str(e)}” def is_question(self, text: str) - bool: 一个简单的启发式方法判断输入是否是问题。 text_lower text.strip().lower() question_words [“谁”, “什么”, “何时”, “哪里”, “为什么”, “怎么”, “如何”, “是否”, “?”] # 如果包含疑问词或以问号结尾则认为是问题 if any(word in text_lower for word in question_words) or text_lower.endswith(‘?’): return True # 另外如果句子很短且看起来像提问这里规则可以更复杂 if len(text_lower.split()) 10 and text_lower.startswith(‘问’): return True return False实操心得在调用LLM API时设置temperature参数很重要。对于这种事实性问答我通常将其设为较低的值如0.1-0.3这能减少答案的随机性使其更倾向于从提供的上下文中提取确定信息避免“胡编乱造”。3.4 构建Streamlit交互界面最后创建主应用文件app.py使用Streamlit将前后端连接起来。import streamlit as st from memory_manager import MemoryManager from ai_engine import AIEngine import pandas as pd # 页面配置 st.set_page_config(page_title“AI会议记忆助手”, page_icon“”, layout“wide”) # 初始化核心组件 st.cache_resource def init_memory_manager(): return MemoryManager() st.cache_resource def init_ai_engine(_mm): return AIEngine(_mm) mm init_memory_manager() ai_engine init_ai_engine(mm) st.title(“ AI会议记忆助手”) st.markdown(“记录会议要点随时智能问答。让AI成为你的团队记忆中枢。”) # 创建两列布局 col1, col2 st.columns([2, 1]) with col1: st.subheader(“ 输入”) user_input st.text_area(“在此输入会议记录或您的问题”, height150, placeholder“例如‘张三负责下周的UI评审’ 或 ‘问谁负责UI评审’”) col1_btn1, col1_btn2 st.columns(2) with col1_btn1: if st.button(“提交为笔记”, use_container_widthTrue): if user_input: with st.spinner(“正在保存笔记...”): mem_id mm.add_memory(user_input) st.success(f“笔记已保存(ID: {mem_id})”) st.rerun() else: st.warning(“请输入内容。”) with col1_btn2: if st.button(“提问”, use_container_widthTrue): if user_input: with st.spinner(“正在思考...”): answer ai_engine.generate_answer(user_input) st.info(“**助手回答**”) st.write(answer) else: st.warning(“请输入问题。”) with col2: st.subheader(“⚙️ 控制面板”) if st.button(“ 查看所有记忆”, use_container_widthTrue): st.session_state[‘show_memory’] True if st.button(“️ 清空所有记忆”, use_container_widthTrue): if st.warning(“此操作不可逆确定清空吗”): mm.clear_all_memories() st.success(“所有记忆已清空。”) st.rerun() # 显示记忆库 if st.session_state.get(‘show_memory’, False): st.subheader(“ 记忆库”) if mm.memories: # 将记忆转换为DataFrame以便更好展示 df pd.DataFrame(mm.memories) # 格式化时间戳 df[‘timestamp’] pd.to_datetime(df[‘timestamp’]).dt.strftime(‘%Y-%m-%d %H:%M’) st.dataframe(df[[“timestamp”, “content”, “type”]], use_container_widthTrue, hide_indexTrue) else: st.info(“记忆库为空。”) # 侧边栏可以放一些说明或设置 with st.sidebar: st.header(“关于”) st.markdown(“”” **如何使用** 1. 在左侧输入框记录会议决定、任务分配等信息。 2. 需要查询时直接以提问形式输入如‘谁负责XX’。 3. 助手会从所有历史记录中寻找答案。 **当前特性** - 持久化记忆存储本地JSON - 基于关键词的智能检索 - 集成Groq高速LLM推理 - 简洁的Web界面 ) st.divider() st.caption(f“当前记忆库中共有 **{len(mm.memories)}** 条记录。”)运行这个应用只需要在项目根目录下执行streamlit run app.pyStreamlit会自动在浏览器中打开本地应用通常是http://localhost:8501。4. 开发中遇到的挑战与解决方案在实际开发过程中我遇到了一些典型问题以下是排查和解决这些问题的记录。4.1 API密钥管理与环境变量加载失败问题在ai_engine.py中os.getenv(“GROQ_API_KEY”)返回None导致Groq客户端初始化失败。排查检查是否在项目根目录创建了.env文件。确认.env文件中的变量名是否正确应为GROQ_API_KEYyour_actual_key_here。检查.env文件是否被.gitignore忽略应该忽略以免密钥泄露。确认python-dotenv库已正确安装。解决方案确保.env文件与app.py在同一级目录。在代码开头显式调用load_dotenv()。有时需要指定路径load_dotenv(dotenv_path‘.env’)。作为备选方案可以在Streamlit的Secrets管理st.secrets中配置API密钥这对于部署到Streamlit Community Cloud特别有用。4.2 记忆检索效果不佳或返回无关内容问题当提问“谁负责后端”系统可能返回一条提到“后端”但无关人员的记录或者因为表述不同如“后端开发由…负责”而检索不到。排查检查search_memories函数的匹配逻辑。简单的关键词匹配对同义词、近义词不敏感。解决方案方案A快速提升在检索前对查询和记忆内容进行简单的文本预处理如去除停用词“的”、“了”、“是”、统一小写。这能提升基础匹配的准确度。方案B进阶引入轻量级语义搜索。可以使用sentence-transformers库中的预训练模型如all-MiniLM-L6-v2它体积小、速度快。将每条记忆和查询都转换为向量然后计算余弦相似度。这能极大提升基于语义的检索能力。# 示例使用sentence-transformers进行语义搜索需额外安装 from sentence_transformers import SentenceTransformer, util model SentenceTransformer(‘all-MiniLM-L6-v2’) def semantic_search(query, memory_texts, top_k3): query_embedding model.encode(query, convert_to_tensorTrue) memory_embeddings model.encode(memory_texts, convert_to_tensorTrue) cos_scores util.cos_sim(query_embedding, memory_embeddings)[0] top_results cos_scores.topk(ktop_k) return top_results4.3 Streamlit应用状态管理与页面刷新问题问题点击按钮后整个页面刷新导致输入框内容被清空或者某些临时状态丢失。排查Streamlit的脚本是自上而下重新执行的。任何按钮触发都会导致脚本重跑。解决方案利用Streamlit的Session State来在重跑之间保存状态。对于输入框内容可以使用st.text_input的key参数或将其值存入st.session_state。对于控制UI显示的标志如是否显示记忆库也使用st.session_state。# 在app.py中 if ‘show_memory’ not in st.session_state: st.session_state[‘show_memory’] False # 按钮点击时修改状态 if st.button(“查看记忆”): st.session_state[‘show_memory’] True # 根据状态决定是否显示 if st.session_state[‘show_memory’]: display_memory_table()4.4 Groq模型响应慢或超时问题在提问时Streamlit界面卡在“正在思考...”长时间无响应或报超时错误。排查网络连接问题。Groq API服务暂时不稳定。请求的上下文提示词记忆过长超过了模型令牌限制或处理时间变长。解决方案为API调用设置合理的超时参数。优化提示词使其更简洁。限制检索并送入LLM的记忆条数top_k避免上下文过长。对于超长记忆可以尝试进行摘要后再存储。在UI上给用户明确的等待提示比如使用st.spinner。考虑加入重试机制对于偶发的网络错误进行有限次数的重试。5. 功能扩展与优化方向这个基础版本已经可以工作但还有很大的改进空间使其更强大、更智能。5.1 增强记忆的结构化与分类目前的记忆条目只有一个type字段。可以进一步结构化自动提取实体使用NER模型或简单的规则从笔记中自动提取人名、任务名、时间点、项目名等实体并作为独立字段存储便于更精确的检索。智能分类利用LLM对输入的笔记进行自动分类例如“决策”、“待办事项”、“风险点”、“信息同步”。这样在检索和展示时可以按类别过滤。5.2 实现更强大的混合检索结合关键词检索和语义向量检索的优点构建混合检索系统关键词检索快速筛选出包含明确实体词的记忆如“张三”、“Q2预算”。语义检索从关键词检索的结果中再用向量相似度进行精排序找出语义上最相关的。 这种方式既能保证召回率又能提升准确率。5.3 增加多轮对话与记忆关联当前系统是单轮问答。可以扩展为支持多轮对话让助手能理解对话上下文。实现思路将短暂的对话历史最近几轮QA也作为上下文的一部分与长期记忆一起送入LLM。同时可以将一轮对话中产生的新信息答案或澄清选择性地存入长期记忆。5.4 部署与团队共享目前是本地运行。要让团队使用需要考虑部署本地服务器使用Docker容器化应用在内部服务器上部署。云服务部署到Streamlit Community Cloud、Hugging Face Spaces或Railway等平台。注意妥善管理云端的API密钥和环境变量。权限与多用户为不同用户或团队创建独立的记忆文件或数据库表实现数据隔离。5.5 集成外部工具让助手的能力不止于记忆和问答日历集成自动从笔记中提取会议时间并添加到Google Calendar或Outlook。任务管理识别出“待办事项”自动创建Trello卡片或Jira工单。文档生成每周自动生成会议纪要摘要并通过邮件发送给团队成员。这个项目的魅力在于它从一个非常具体的痛点出发用相对简单的技术组合构建了一个真正有用的工具。它证明了不需要掌握最前沿的AGI技术通过巧妙地组合持久化存储、信息检索和现有的大语言模型API我们就能创造出能显著提升工作效率的智能应用。