news 2026/9/18 22:40:56

双智能体会议纪要助手 MeetingActionAgent:基于 HelloAgents 的提取-审核协作实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
双智能体会议纪要助手 MeetingActionAgent:基于 HelloAgents 的提取-审核协作实战

双智能体会议纪要助手 MeetingActionAgent:基于 HelloAgents 的提取-审核协作实战

【免费下载链接】hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程项目地址: https://gitcode.com/datawhalechina/hello-agents

导读

本文深入剖析 Datawhale Hello-Agents 共创项目中 Henry2513-MeetingActionAgent 的完整实现:一个输入中文会议文字记录、输出可追溯可执行会议纪要的双 Agent 生产力工具。它用两个SimpleAgent顺序协作——MinutesAgent负责结构化提取,ReviewAgent负责对照原文审核,配合 Pydantic 数据校验与有限次重试机制。读完本文,你将掌握「生成 Agent + 审核 Agent」的协作范式、结构化输出的健壮解析技巧,以及如何用最少模型调用构建可落地的文档处理流程。

项目定位与核心设计思想

这是一个入门规模但流程完整的生产力工具项目,核心约束是「原文没有的信息不猜」:未知负责人、未知会议日期或截止日期一律显示为「未提供」,绝不靠常识补全。整个流程围绕以下四步展开:

  1. MinutesAgent提取会议日期、摘要、决策、行动项和待确认问题;
  2. ReviewAgent对照原文检查遗漏、编造和日期冲突;
  3. 审核未通过时,系统最多修正一次;
  4. 最终生成结构化 JSON 和易读的 Markdown 会议纪要。

从源码结构看,第一版刻意不使用任何工具调用(没有搜索、数据库、RAG、MCP 或长期记忆),普通 Python 代码负责读取、校验和保存文件,Agent 只承担语言理解与审核职责,这让整个项目的推理边界非常清晰。

双 Agent 顺序协作架构

工作流程

会议文字记录 → MinutesAgent:只提取原文支持的信息 → ReviewAgent:检查遗漏、编造和冲突 → 必要时修正并复核一次 → JSON + Markdown

两条提示词在 main.ipynb 中完整给出,职责边界非常鲜明:

MinutesAgent(提取专家)

MINUTES_SYSTEM_PROMPT = """你是严谨的中文会议纪要提取专家。 只使用会议原文明确支持的信息,不得补充常识或猜测。 严格区分讨论、建议和已确认决策。 每个行动项必须保留一段原文证据;未知负责人、会议日期或截止日期必须为 null。 只返回符合用户给定 Schema 的 JSON,不要返回 Markdown 或额外解释。"""

ReviewAgent(独立审核员)

REVIEW_SYSTEM_PROMPT = """你是独立的会议纪要审核员。 必须逐项对照会议原文和纪要草稿,检查遗漏、编造、模糊行动项和日期冲突。 不能因为文字通顺就判定通过,也不能使用外部信息。 只返回符合用户给定 Schema 的 JSON,不要返回 Markdown 或额外解释。"""

审核 Agent 的提示词刻意强调「不能因为文字通顺就判定通过」——这是对抗 LLM 过度自信的关键设计,要求审核必须回到会议原文逐项核对,而不是泛泛阅读。

两个 Agent 由build_agents()工厂函数创建,共享同一个 LLM 实例:

def build_agents(): llm = HelloAgentsLLM() minutes_agent = SimpleAgent(name="MinutesAgent", llm=llm, system_prompt=MINUTES_SYSTEM_PROMPT) review_agent = SimpleAgent(name="ReviewAgent", llm=llm, system_prompt=REVIEW_SYSTEM_PROMPT) return minutes_agent, review_agent

其中HelloAgentsLLM在 code/chapter4/llm_client.py 中有完整实现:它封装 OpenAI Python SDK,自动从环境变量LLM_MODEL_IDLLM_API_KEYLLM_BASE_URL读取配置,支持任意 OpenAI-compatible 服务,默认超时 60 秒,并通过流式方式收集模型输出。

用 Pydantic 定义结构化数据契约

结构化输出是整个流程的地基。项目定义了三个 Pydantic 模型,逐字段约束模型返回的内容:

# 表示从会议原文中提取的一条行动项。 class ActionItem(BaseModel): task: str = Field(min_length=1) owner: str | None = None due_date_raw: str | None = None priority: Literal["高", "中", "低", "未说明"] = "未说明" evidence: str = Field(min_length=1) # 表示最终输出的完整会议纪要及其审核状态。 class MeetingResult(BaseModel): title: str = Field(min_length=1) meeting_date: str | None = None participants: list[str] = Field(default_factory=list) summary: str = Field(min_length=1) decisions: list[str] = Field(default_factory=list) action_items: list[ActionItem] = Field(default_factory=list) open_questions: list[str] = Field(default_factory=list) review_status: Literal["pending", "passed", "needs_manual_review"] = "pending" review_issues: list[str] = Field(default_factory=list) # 表示 ReviewAgent 对纪要草稿的审核结论和问题。 class ReviewResult(BaseModel): passed: bool issues: list[str] = Field(default_factory=list) missing_items: list[str] = Field(default_factory=list) unsupported_items: list[str] = Field(default_factory=list) revision_advice: list[str] = Field(default_factory=list)

几个值得注意的设计细节:

  • 可空字段默认Noneownerdue_date_rawmeeting_date都允许为空,配合「不猜」原则,未知值在最终输出中呈现为「未提供」;
  • 优先级用Literal枚举:只允许「高/中/低/未说明」四个取值,模型输出「紧急」等非法值会直接被 Pydantic 拒绝并触发校验失败;
  • evidence字段必填:强制每个行动项必须携带原文证据,这是「可追溯」的硬性契约;
  • review_status三态流转pending(草稿)→passed(通过)或needs_manual_review(需人工复核)。

在 Notebook 的自检中专门验证了非法优先级会被拒绝(ActionItem(task="测试", ..., priority="紧急", ...)触发ValidationError),说明枚举约束在实际运行中是生效的防线。

健壮解析:应对模型输出的「不老实」

大模型经常不按指令只输出 JSON——可能把 JSON 包进 Markdown 代码围栏,也可能在前后加上一句解释。项目提供了两层防护:

# 从模型响应中截取并解析 JSON 对象。 def extract_json_object(text: str) -> dict: start = text.find("{") end = text.rfind("}") if start == -1 or end == -1 or end < start: raise ValueError("模型响应中没有完整的 JSON 对象") return json.loads(text[start : end + 1]) # 将模型响应解析并验证为指定的 Pydantic 模型。 def parse_model_response(text: str, model_type: type[BaseModel]) -> BaseModel: return model_type.model_validate(extract_json_object(text))

extract_json_object通过「取第一个{到最后}之间内容」的方式剥离多余文字;parse_model_response再交给 Pydantic 做类型与约束校验。即使响应被包在```json ... ```代码围栏中也能正确解析——自检第一组用例就是专门验证这一场景。

有限调用预算:最多四次模型调用

为了防止失控重试产生无限成本,项目实现了一个CallBudget计数器,并规定整个流程共享四次模型调用预算

class CallBudget: # 初始化模型调用次数上限。 def __init__(self, maximum: int = 4) -> None: self.maximum = maximum self.used = 0 # 计算剩余的模型调用次数。 @property def remaining(self) -> int: return self.maximum - self.used # 在次数限制内执行一次 Agent 调用。 def run(self, agent, prompt: str) -> str: if self.remaining <= 0: raise RuntimeError("已达到四次模型调用上限") self.used += 1 return agent.run(prompt)

run_structured是统一的「调用 + 解析 + 格式修复」入口:它先把 Pydantic 模型序列化为 JSON Schema 拼进提示词,要求模型按 Schema 输出;首次解析失败时允许请求一次格式修复(把校验错误和原响应一并回传给模型,只修格式不改含义),但修复同样计入调用预算

def run_structured( agent, prompt: str, model_type: type[BaseModel], budget: CallBudget, ) -> BaseModel: schema = json.dumps(model_type.model_json_schema(), ensure_ascii=False) full_prompt = f"{prompt}\n\n必须遵循以下 JSON Schema:\n{schema}" raw_response = budget.run(agent, full_prompt) try: return parse_model_response(raw_response, model_type) except ValueError as error: if budget.remaining <= 0: raise RuntimeError(f"JSON 校验失败且没有剩余调用次数:{error}") from error repair_prompt = ( "上一次响应无法通过 JSON 校验。不要改变内容含义,只修复格式。\n" f"校验错误:{error}\n" f"原响应:\n{raw_response}\n" f"目标 Schema:\n{schema}\n" "只返回修复后的 JSON。" ) repaired_response = budget.run(agent, repair_prompt) return parse_model_response(repaired_response, model_type)

这里用到 Pydantic 的model_json_schema()能力——把 Python 数据结构自动翻译成模型能直接遵循的 JSON Schema,省去手写提示词格式约束的繁琐工作。

主流程编排:提取 → 审核 → 一次修正

analyze_meeting是整条流水线的心脏,它把输入校验、双 Agent 协作、预算控制和状态流转串在一起:

def analyze_meeting(transcript: str) -> tuple[MeetingResult, ReviewResult, int]: transcript = validate_transcript(transcript) minutes_agent, review_agent = build_agents() budget = CallBudget(maximum=4) draft_prompt = f"请从以下会议原文提取结构化纪要:\n\n{transcript}" draft = run_structured(minutes_agent, draft_prompt, MeetingResult, budget) review = run_structured(review_agent, make_review_prompt(transcript, draft), ReviewResult, budget) if review.passed: final_result = draft.model_copy(update={"review_status": "passed", "review_issues": []}) return final_result, review, budget.used issues = review.issues + review.missing_items + review.unsupported_items if budget.remaining < 2: final_result = draft.model_copy( update={"review_status": "needs_manual_review", "review_issues": issues} ) return final_result, review, budget.used revision_prompt = ( "请根据审核意见修正纪要。仍然只能使用会议原文支持的信息。\n\n" f"【会议原文】\n{transcript}\n\n" f"【原草稿】\n{draft.model_dump_json(indent=2)}\n\n" f"【审核意见】\n{review.model_dump_json(indent=2)}" ) revised = run_structured(minutes_agent, revision_prompt, MeetingResult, budget) final_review = run_structured(review_agent, make_review_prompt(transcript, revised), ReviewResult, budget) final_issues = final_review.issues + final_review.missing_items + final_review.unsupported_items status = "passed" if final_review.passed else "needs_manual_review" final_result = revised.model_copy(update={"review_status": status, "review_issues": final_issues}) return final_result, final_review, budget.used

分支逻辑可以归纳为一个状态机:

场景模型调用次数结果状态
首次审核通过2 次(提取 + 审核)passed
首次未通过且剩余预算 ≥ 24 次(提取 + 审核 + 修正 + 复核)按复核结果定
首次未通过且剩余预算 < 2≤ 3 次needs_manual_review

配套的输入校验函数要求会议记录至少 20 个字符,避免无意义的输入浪费调用预算:

def validate_transcript(transcript: str) -> str: cleaned = transcript.strip() if len(cleaned) < 20: raise ValueError("会议记录过短,请至少提供 20 个字符") return cleaned

此外,analyze_meeting每次调用都会通过build_agents()创建全新的 Agent 实例,多份会议连续分析时不会发生历史对话污染。

Markdown 渲染:交给确定性的代码

一个容易被忽视但很聪明的决定是:Markdown 由普通 Python 生成,而不是让模型二次改写。既然数据已经通过 Pydantic 校验为结构化对象,就无需再消耗一次模型调用来「美化排版」,也避免了重复改写引入新错误。

def markdown_cell(value: str | None) -> str: if not value: return "未提供" return value.replace("|", "\\|").replace("\n", " ")

markdown_cell处理了两个 Markdown 表格陷阱:空值统一渲染为「未提供」,而文本中的|和换行会被转义或替换,防止破坏表格结构。to_markdown最终拼出包含会议摘要、已确认决策、行动项表格(任务/负责人/截止日期(原文)/优先级/原文证据五列)、待确认问题和审核状态的完整纪要,行动项为空时输出占位行「| 无 | 未提供 | ... |」。

结果落盘由save_result完成,同时写出 JSON 与 Markdown 两份文件:

def save_result(result: MeetingResult, stem: str = "meeting_result") -> tuple[Path, Path]: output_dir = PROJECT_ROOT / "outputs" json_path = output_dir / f"{stem}.json" markdown_path = output_dir / f"{stem}.md" json_path.write_text(result.model_dump_json(indent=2), encoding="utf-8") markdown_path.write_text(to_markdown(result), encoding="utf-8") return json_path, markdown_path

离线自检:不调用模型的六组测试

Notebook 第 8 节内置了 6 组不调用模型的离线自检,覆盖最容易出错的边界场景,可在无 API 密钥时独立验证代码逻辑:

  1. JSON 代码围栏解析:把outputs/example_result.json的内容包进```json围栏后,parse_model_response仍能正确解析;
  2. 关键字段核对:验证示例结果的meeting_date == "2026-07-27"、最后一个行动项owner is None(对应「负责人会后确认」);
  3. 空行动项处理:构造无行动项的MeetingResult,验证to_markdown输出「| 无 |」占位行;
  4. 空输入拒绝validate_transcript("太短")应抛出ValueError
  5. 非法优先级拒绝priority="紧急"应触发 PydanticValidationError
  6. Markdown 一致性to_markdown(expected_result)的输出必须与outputs/example_minutes.md逐字节一致。

全部通过后打印「自检通过:6 组」。这组自检的意义在于:把「不依赖模型的纯逻辑」和「依赖模型的生成逻辑」彻底分离,让代码重构有回归保障。

输入数据与示例输出

正常会议样例

data/sample_meeting.txt 是一段正常的中文会议记录:3 位参会者,含明确任务分工、一个已确认决策(验证码有效期 5 分钟)、一个负责人待定的行动项。

边界会议样例

data/edge_case_meeting.txt 专门为考验审核 Agent 而设计,包含三类陷阱:

  1. 「可以考虑下个月上线,但这只是一个初步想法」——建议不是决定
  2. 「测试环境需要有人确认,目前还没有负责人」——缺失负责人
  3. 「会议前记录写产品文档周三完成,但今天讨论时又有人说周五完成」——日期冲突

Notebook 附带了人工预测脚手架:confirmed_launch_decision: Falsetest_environment_owner: Nonedocument_date_conflict: True。学习者可以先人工预测再运行模型对比,直观感受审核 Agent 是否抓住了这些语义细节。

示例产物

outputs/example_result.json 展示了正常会议的结构化结果:提取了 4 个行动项和 1 条已确认决策,4 条行动项证据都能在会议原文中找到;due_date_raw缺失时保持null,对应 Markdown 中的「未提供」。outputs/example_minutes.md 则是渲染后的人类可读版本,行动项表格中的原文证据列让任何人都能反向核对每一项结论。

快速上手:环境搭建与运行

环境要求

  • Python 3.11+
  • 一个可用的 OpenAI-compatible LLM API
  • 依赖清单见 requirements.txt:hello-agents==0.2.9openai>=1.109,<2pydantic>=2.12,<3python-dotenv>=1.2,<2,以及 hello-agents 0.2.9 导入所需的huggingface-hub>=1.20,<2和 JupyterLab 环境依赖。

步骤一:创建虚拟环境

python -m venv .venv .\.venv\Scripts\Activate.ps1 python -m pip install -r requirements.txt

步骤二:配置模型

复制.env.example.env,填写 OpenAI-compatible 模型服务的三个关键变量(.env已被 Git 忽略,禁止提交真实密钥):

LLM_MODEL_ID=your-model-id LLM_API_KEY=your-api-key LLM_BASE_URL=https://your-provider.example/v1

这三个变量的读取逻辑封装在HelloAgentsLLM中:优先使用构造参数,缺省时从环境变量加载,三者缺一即抛出ValueError(详见 code/chapter4/llm_client.py)。

步骤三:运行 Notebook

jupyter lab

打开main.ipynb,确认内核使用当前项目的.venv,然后从上到下运行。注意:请从Henry2513-MeetingActionAgent项目目录启动 Jupyter,Notebook 不兼容其他工作目录——因为代码使用PROJECT_ROOT = Path.cwd()定位data/outputs/目录。

Notebook 会直接调用真实模型分析data/sample_meeting.txt,运行后打印模型调用次数、审核状态与保存的文件名。若预算耗尽会抛出RuntimeError("已达到四次模型调用上限")或 JSON 校验失败异常。

性能评估与当前限制

根据项目作者在 README 中记录的提交前实测(对data/sample_meeting.txt的一次真实运行):共调用模型 2 次,最终审核状态为passed;提取 4 个行动项和 1 个已确认决策;4 条行动项证据都能在原文找到;6 组离线自检全部通过。作者明确说明这是入门规模的流程验证,没有进行大规模准确率基准测试,实际响应时间受所选模型与 API 服务状态影响,因此不给出固定数值。

当前限制同样需要正视:

  • 只处理文字记录,不处理录音或实时会议;
  • 不搜索互联网,也不使用数据库、RAG、MCP 或长期记忆;
  • LLM 输出存在不确定性,needs_manual_review的结果必须人工确认;
  • 示例数据均为虚构内容,不应输入敏感会议数据。

常见问题与下一步演进

项目 README 与 Notebook 的 FAQ 部分总结了最典型的四个坑:

  • 把建议写成决定:Reviewer 必须检查「考虑、建议、可能」等措辞,这是edge_case_meeting.txt的核心考点;
  • 编造负责人或日期:未知值保持null,最终 Markdown 显示「未提供」,用数据契约强制「不猜」;
  • JSON 不稳定:允许一次格式修复,但仍受四次调用预算限制;
  • 多份会议连续分析:每次调用analyze_meeting都创建新 Agent,避免历史记录互相污染。

项目也明确了第二版的演进方向:增加日期标准化工具(同时保留原文日期用于核对)、支持连续处理多份会议记录并分别保存结果。而第一版坚持「双 Agent、无工具调用」的最小化设计——这种先跑通主链路、再逐步叠加能力的思路,恰好是构建可靠 Agent 应用的推荐路径。

结语

MeetingActionAgent 是一个小而完整的双 Agent 协作范本:MinutesAgentReviewAgent职责分离形成天然的质量闭环,Pydantic 数据契约保证结构可靠,四次调用预算约束成本,证据字段支撑结果追溯。对希望入门 HelloAgents 与多 Agent 协作的开发者来说,它的价值不仅在于能产出可用的会议纪要,更在于示范了一种「有限调用、结构约束、人工兜底」的工程化 Agent 构建方法论。完整的提示词、流程编排与测试用例都可在 main.ipynb 中直接阅读与复现。

【免费下载链接】hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程项目地址: https://gitcode.com/datawhalechina/hello-agents

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 22:34:50

A2UI 快速上手完整指南:让 AI Agent 自动生成界面的开源协议

A2UI 快速上手完整指南&#xff1a;让 AI Agent 自动生成界面的开源协议 【免费下载链接】a2ui 项目地址: https://gitcode.com/GitHub_Trending/a2/a2ui 你想让 AI 应用拥有真正的交互界面&#xff0c;却不想为前端开发再雇一个人、再排一个季度的期&#xff1f;A2UI&…

作者头像 李华
网站建设 2026/9/18 22:33:32

【ComfyUI】SDXL + ControlNet 姿态搭配深度融合动漫转真人

今天给大家演示一个 动漫转真人 + 摄影风格光影映射 ComfyUI 工作流。这个流程能自动将二维风格的动漫图像转化为具备真实光影、细节纹理和摄影风格的逼真人像。通过融合多种 ControlNet 类型(如 OpenPose、Tile、Depth)、风格配准处理器与文生图大模型,最终生成具有真实感且…

作者头像 李华