在 AI Agent 应用日益普及的今天,会议纪要生成是最常见的落地场景之一。然而,如何科学地评估一个会议纪要 Agent 的性能,却是许多开发者面临的难题。本文将详细介绍如何构建一个完整的 benchmark 体系,包括评估维度设计、数据集准备、指标计算和自动化测试流程。

为什么需要 Benchmark 会议纪要 Agent?

会议纪要 Agent 看似简单,实则涉及多个复杂的 NLP 任务:

  • 语音识别准确性(如果从音频开始)
  • 说话人识别与分离
  • 关键信息提取
  • 内容摘要生成
  • 行动项(Action Items)识别
  • 决策点(Decision Points)标注
  • 时间线重构

没有系统的 benchmark,你无法回答:

  • 模型升级后性能是否真正提升?
  • 不同场景下(技术会议 vs 商务会议)表现差异有多大?
  • 相比竞品的优势在哪里?
  • 哪些改进方向能带来最大收益?

Benchmark 设计的核心维度

1. 评估维度分层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 评估维度定义
EVALUATION_DIMENSIONS = {
"content_quality": {
"completeness": 0.25, # 信息完整性
"accuracy": 0.35, # 信息准确性
"conciseness": 0.20, # 简洁度
"coherence": 0.20 # 连贯性
},
"structure_quality": {
"organization": 0.40, # 组织结构
"formatting": 0.30, # 格式规范
"hierarchy": 0.30 # 层级清晰度
},
"functional_quality": {
"action_items": 0.35, # 行动项识别
"decisions": 0.35, # 决策点识别
"key_points": 0.30 # 关键要点提取
},
"user_experience": {
"readability": 0.50, # 可读性
"usability": 0.50 # 易用性
}
}

# generated by hugo's coding agent

2. 测试数据集构建

一个完整的测试数据集应包含:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
benchmark_dataset/
├── audio_recordings/ # 原始音频(如适用)
│ ├── tech_standup_01.mp3
│ ├── sales_review_01.mp3
│ └── board_meeting_01.mp3
├── transcripts/ # 转录文本
│ ├── tech_standup_01.txt
│ └── ...
├── ground_truth/ # 人工标注的标准答案
│ ├── tech_standup_01.json
│ └── ...
└── metadata/ # 元数据
├── meeting_types.json
└── difficulty_levels.json

Step-by-Step 实施指南

Step 1: 准备标注数据集

首先创建标准答案格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# ground_truth_schema.py
from pydantic import BaseModel
from typing import List, Optional
from datetime import datetime

class ActionItem(BaseModel):
description: str
assignee: Optional[str]
deadline: Optional[datetime]
priority: str # "high", "medium", "low"

class Decision(BaseModel):
description: str
rationale: Optional[str]
participants: List[str]

class GroundTruth(BaseModel):
meeting_id: str
meeting_type: str # "standup", "planning", "review", etc.

# 核心内容
summary: str
key_points: List[str]
action_items: List[ActionItem]
decisions: List[Decision]

# 元信息
participants: List[str]
duration_minutes: int
topics_covered: List[str]

# 质量标签
difficulty_level: str # "easy", "medium", "hard"
audio_quality: str # "clear", "moderate", "poor"

class Config:
json_schema_extra = {
"example": {
"meeting_id": "tech_standup_2026_01_07",
"meeting_type": "standup",
"summary": "团队进行每日站会,讨论当前进度和阻塞问题",
"key_points": [
"后端API性能优化已完成,响应时间降低40%",
"前端组件库升级遇到兼容性问题",
"产品需求文档将在明天完成"
],
"action_items": [
{
"description": "调研前端组件库兼容性解决方案",
"assignee": "张三",
"deadline": "2026-01-08T18:00:00",
"priority": "high"
}
],
"decisions": [
{
"description": "推迟新功能发布至下周",
"rationale": "等待组件库问题解决",
"participants": ["张三", "李四", "王五"]
}
],
"participants": ["张三", "李四", "王五"],
"duration_minutes": 15,
"topics_covered": ["进度更新", "技术问题", "发布计划"],
"difficulty_level": "easy",
"audio_quality": "clear"
}
}

# generated by hugo's coding agent

Step 2: 实现自动化评估指标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# evaluator.py
import numpy as np
from rouge_score import rouge_scorer
from bert_score import score as bert_score
from typing import Dict, List, Tuple
import difflib

class MeetingMinutesEvaluator:
def __init__(self):
self.rouge_scorer = rouge_scorer.RougeScorer(
['rouge1', 'rouge2', 'rougeL'],
use_stemmer=True
)

def evaluate_summary(
self,
predicted: str,
reference: str
) -> Dict[str, float]:
"""评估摘要质量"""

# ROUGE scores (词汇重叠)
rouge_scores = self.rouge_scorer.score(reference, predicted)

# BERTScore (语义相似度)
P, R, F1 = bert_score(
[predicted],
[reference],
lang="zh",
verbose=False
)

return {
"rouge1_f1": rouge_scores['rouge1'].fmeasure,
"rouge2_f1": rouge_scores['rouge2'].fmeasure,
"rougeL_f1": rouge_scores['rougeL'].fmeasure,
"bert_score_f1": F1.item(),
"combined_score": (
rouge_scores['rougeL'].fmeasure * 0.4 +
F1.item() * 0.6
)
}

def evaluate_key_points(
self,
predicted: List[str],
reference: List[str]
) -> Dict[str, float]:
"""评估关键要点提取"""

# 使用最佳匹配策略
matched_count = 0
total_similarity = 0.0

for ref_point in reference:
best_match_score = 0.0
for pred_point in predicted:
similarity = difflib.SequenceMatcher(
None,
ref_point,
pred_point
).ratio()
best_match_score = max(best_match_score, similarity)

if best_match_score > 0.6: # 阈值
matched_count += 1
total_similarity += best_match_score

recall = matched_count / len(reference) if reference else 0
precision = matched_count / len(predicted) if predicted else 0
f1 = (
2 * precision * recall / (precision + recall)
if (precision + recall) > 0 else 0
)

return {
"precision": precision,
"recall": recall,
"f1_score": f1,
"avg_similarity": total_similarity / len(reference) if reference else 0
}

def evaluate_action_items(
self,
predicted: List[ActionItem],
reference: List[ActionItem]
) -> Dict[str, float]:
"""评估行动项识别"""

# 提取描述文本
pred_descriptions = [item.description for item in predicted]
ref_descriptions = [item.description for item in reference]

# 基础匹配评分
base_score = self.evaluate_key_points(
pred_descriptions,
ref_descriptions
)

# 检查结构化信息准确性
structure_scores = []
for pred_item in predicted:
best_match = None
best_similarity = 0

for ref_item in reference:
sim = difflib.SequenceMatcher(
None,
pred_item.description,
ref_item.description
).ratio()
if sim > best_similarity:
best_similarity = sim
best_match = ref_item

if best_match and best_similarity > 0.6:
# 检查assignee和priority是否正确
assignee_match = pred_item.assignee == best_match.assignee
priority_match = pred_item.priority == best_match.priority
structure_scores.append(
(assignee_match + priority_match) / 2
)

structure_accuracy = (
np.mean(structure_scores) if structure_scores else 0
)

return {
**base_score,
"structure_accuracy": structure_accuracy,
"weighted_f1": base_score['f1_score'] * 0.7 + structure_accuracy * 0.3
}

def evaluate_full_output(
self,
predicted: GroundTruth,
reference: GroundTruth
) -> Dict[str, any]:
"""完整评估"""

results = {
"meeting_id": reference.meeting_id,
"summary_scores": self.evaluate_summary(
predicted.summary,
reference.summary
),
"key_points_scores": self.evaluate_key_points(
predicted.key_points,
reference.key_points
),
"action_items_scores": self.evaluate_action_items(
predicted.action_items,
reference.action_items
)
}

# 计算综合得分
results["overall_score"] = (
results["summary_scores"]["combined_score"] * 0.30 +
results["key_points_scores"]["f1_score"] * 0.35 +
results["action_items_scores"]["weighted_f1"] * 0.35
)

return results

# generated by hugo's coding agent

Step 3: 构建测试套件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# test_suite.py
import json
from pathlib import Path
from typing import List, Dict
import pandas as pd
from tqdm import tqdm

class BenchmarkSuite:
def __init__(
self,
dataset_path: str,
agent_callable
):
self.dataset_path = Path(dataset_path)
self.agent = agent_callable
self.evaluator = MeetingMinutesEvaluator()

def load_test_cases(self) -> List[Dict]:
"""加载所有测试用例"""
test_cases = []
ground_truth_dir = self.dataset_path / "ground_truth"

for gt_file in ground_truth_dir.glob("*.json"):
with open(gt_file, 'r', encoding='utf-8') as f:
reference = GroundTruth(**json.load(f))

# 加载对应的转录文本
transcript_file = (
self.dataset_path / "transcripts" /
f"{reference.meeting_id}.txt"
)
with open(transcript_file, 'r', encoding='utf-8') as f:
transcript = f.read()

test_cases.append({
"reference": reference,
"transcript": transcript
})

return test_cases

def run_benchmark(
self,
output_path: str = "benchmark_results.json"
) -> pd.DataFrame:
"""运行完整benchmark"""

test_cases = self.load_test_cases()
results = []

for case in tqdm(test_cases, desc="Running benchmark"):
# 调用Agent生成纪要
predicted = self.agent(case["transcript"])

# 评估结果
scores = self.evaluator.evaluate_full_output(
predicted,
case["reference"]
)

# 添加元数据
scores["meeting_type"] = case["reference"].meeting_type
scores["difficulty"] = case["reference"].difficulty_level
scores["duration"] = case["reference"].duration_minutes

results.append(scores)

# 保存详细结果
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(results, f, ensure_ascii=False, indent=2)

# 生成汇总报告
return self._generate_report(results)

def _generate_report(self, results: List[Dict]) -> pd.DataFrame:
"""生成汇总报告"""

df = pd.DataFrame([
{
"meeting_id": r["meeting_id"],
"meeting_type": r["meeting_type"],
"difficulty": r["difficulty"],
"overall_score": r["overall_score"],
"summary_score": r["summary_scores"]["combined_score"],
"key_points_f1": r["key_points_scores"]["f1_score"],
"action_items_f1": r["action_items_scores"]["weighted_f1"]
}
for r in results
])

# 按会议类型分组统计
summary = df.groupby("meeting_type").agg({
"overall_score": ["mean", "std"],
"summary_score": "mean",
"key_points_f1": "mean",
"action_items_f1": "mean"
}).round(4)

print("\n=== Benchmark Summary ===")
print(summary)

# 按难度分组
difficulty_summary = df.groupby("difficulty")["overall_score"].agg(
["mean", "std", "count"]
).round(4)
print("\n=== Performance by Difficulty ===")
print(difficulty_summary)

return df

# generated by hugo's coding agent

Step 4: 实际运行示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# run_benchmark.py
from your_agent import MeetingMinutesAgent

# 初始化你的Agent
agent = MeetingMinutesAgent(
model="gpt-4-turbo",
temperature=0.3
)

def agent_wrapper(transcript: str) -> GroundTruth:
"""包装Agent输出为标准格式"""
raw_output = agent.generate_minutes(transcript)

# 解析Agent输出为GroundTruth格式
return GroundTruth(
meeting_id=raw_output.get("id", "unknown"),
meeting_type=raw_output.get("type", "unknown"),
summary=raw_output["summary"],
key_points=raw_output["key_points"],
action_items=[
ActionItem(**item) for item in raw_output["action_items"]
],
decisions=[
Decision(**dec) for dec in raw_output.get("decisions", [])
],
participants=raw_output.get("participants", []),
duration_minutes=raw_output.get("duration", 0),
topics_covered=raw_output.get("topics", []),
difficulty_level="unknown",
audio_quality="unknown"
)

# 运行benchmark
suite = BenchmarkSuite(
dataset_path="./benchmark_dataset",
agent_callable=agent_wrapper
)

results_df = suite.run_benchmark(
output_path="results_v1.0.json"
)

# 可视化结果
import matplotlib.pyplot as plt
import seaborn as sns

plt.figure(figsize=(12, 6))

# 各维度得分对比
metrics = ['summary_score', 'key_points_f1', 'action_items_f1', 'overall_score']
scores = results_df[metrics].mean()

sns.barplot(x=metrics, y=scores.values)
plt.title("Average Scores by Metric")
plt.ylabel("Score")
plt.ylim(0, 1)
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig("benchmark_scores.png")

print(f"\n✅ Benchmark completed! Results saved to results_v1.0.json")
print(f"📊 Overall average score: {results_df['overall_score'].mean():.4f}")

# generated by hugo's coding agent

进阶优化建议

1. 添加人类评估

自动化指标无法完全替代人类判断:

class HumanEvaluator:
    def __init__(self):
        self.criteria = {
            "usefulness": "纪要对会后行动的实用性(1-5分)",
            "clarity": "表达清晰度(1-5分)",
            "completeness": "信息完整性(1-5分)",
            "professionalism": "专业度(1-5分)"
        }

    def collect_ratings(
        self,
        meeting_id: str,
        generated_minutes: str
    ) -> Dict[str
---
原文链接: [如何对会议纪要 Agent 进行 Benchmark?完整指南与实践](https://hugozhu.site/post/2026/117-how-to-benchmark-meeting-minutes-agent/)