mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-09-07 08:26:06 +00:00
* feat(benchmark): 添加 LongMemEval 评估功能和内存检索器 - 实现了 LongMemEval 数据集的评估管道 - 添加了 PersonalLongmemevalRetriever 和 PersonalLongmemevalSummarizer - 创建了详细的统计分析工具 compute_stats.py - 实现了完整的答案判断和准确性计算功能 - 集成了 ReMe 内存操作和查询功能 - 添加了性能指标和时间统计功能 * feat(benchmark): 添加内存准确性和完整性评估功能 - 实现了 evaluation_for_memory_accuracy 函数用于评估提取内存的准确性 - 实现了 evaluation_for_memory_integrity 函数用于评估内存完整性 - 创建了 MemoryIntegrityEvaluator 类来评估内存点覆盖情况 - 创建了 MemoryAccuracyEvaluator 类来评估提取内存的准确性 - 添加了 compute_memory_integrity_metrics 和 compute_memory_accuracy_metrics 统计函数 - 在 MetricsAggregator 中集成内存完整性和准确性指标计算 - 更新了命令行参数默认值:top_k 改为 10,batch_size 改为 16 - 重构了个人记忆汇总器中的工具循环逻辑 - 更新了评估提示词模板以支持内存质量评估 - 添加了新的检索配置和模型设置 * feat(memory): 添加个人记忆摘要器配置文件 - 新增 personal_halumem_summarizer_adddraft.yaml 配置文件 - 新增 personal_halumem_summarizer_original_backup.yaml 备份配置文件 - 实现记忆架构师系统提示和用户消息模板 - 实现个人资料代理系统提示和用户消息模板 - 支持生物特征和行为模式记忆存储 - 实现记忆去重和合并功能 - 支持用户个人资料的动态更新和删除操作 * feat(memory): 添加个人记忆摘要器配置文件 - 新增 personal_halumem_summarizer_adddraft.yaml 配置文件 - 新增 personal_halumem_summarizer_original_backup.yaml 备份配置文件 - 实现记忆架构师系统提示和用户消息模板 - 实现个人资料代理系统提示和用户消息模板 - 支持生物特征和行为模式记忆存储 - 实现记忆去重和合并功能 - 支持用户个人资料的动态更新和删除操作 * docs(readme): 添加 ReMe Memory Agent 详细介绍文档 - 创建英文版 README.md 包含核心概念、架构设计和使用指南 - 创建中文版 README_ZH.md 提供完整的本地化文档 - 介绍 Agent 驱动的记忆管理理念和层次化检索机制 - 详述项目架构包括 ReMeSummarizer 和 ReMeRetriever 组件 - 提供快速开始示例和程序化内存操作方法 - 展示 LoCoMo、LongMemEval、HaluMem 基准测试结果 - 包含完整的项目结构说明和配置要求 * chore(config): 移除配置文件中的API密钥 - 从配置文件中删除FLOW_LLM_API_KEY环境变量设置 - 移除相关的API密钥配置项 - 更新配置文档以反映新的安全实践 - 确保敏感信息不再硬编码在配置文件中 - 添加注释说明如何通过环境变量方式配置API密钥 * fix(benchmark): 修复模型调用和配置参数问题 - 修正了reme.get_llm方法的参数传递,移除冗余的name参数 - 添加了qwen3-max模型的配置支持 - 调整了默认并发数从16降至4以提高稳定性 - 修改算法版本默认值从longmemeval和v1统一为default - 减少每类样本数量默认值从16至2以优化测试效率 * feat(benchmark): 添加记忆准确性和完整性评估功能 - 修改了 simple_request_for_json 调用以支持模型名称参数 - 新增 evaluation_for_memory_accuracy 函数用于评估记忆准确性 - 新增 evaluation_for_memory_integrity 函数用于评估记忆完整性 - 将默认模型名称从 qwen3-max 更改为 None - 更新提取记忆逻辑以过滤 time_int 和 when_to_use 字段 - 新增 MemoryIntegrityEvaluator 类用于评估记忆完整性 - 新增 MemoryAccuracyEvaluator 类用于评估记忆准确性 - 添加 compute_memory_integrity_metrics 方法计算记忆完整性指标 - 添加 compute_memory_accuracy_metrics 方法计算记忆准确性指标 - 配置多种新 LLM 模型包括 qwen-plus-t、qwen-max-t、gpt-4o-mini 等 - 初始化完整性评估器和准确性评估器实例 - 在会话数据中添加记忆完整性和准确性评估结果 - 收集记忆完整性记录和准确性记录用于统计 - 在最终结果中包含记忆完整性和准确性指标 - 更新摘要打印方法显示记忆完整性和准确性统计信息 - 更新默认评估模型为 gpt-4o-mini-2024-07-18 * docs(readme): 删除 ReMe Memory Agent 的中英文文档 - 移除英文版 README.md 中关于 ReMe Memory Agent 的详细介绍 - 删除中文版 README_ZH.md 中关于 ReMe Memory Agent 的完整文档 - 清理了包括架构图、功能特性、快速开始和实验数据在内的所有文档内容
346 lines
9.9 KiB
Python
346 lines
9.9 KiB
Python
"""
|
|
LongMemEval Evaluation Statistics Analyzer
|
|
|
|
Computes detailed statistics from evaluation results including:
|
|
- Overall accuracy
|
|
- Accuracy by question type
|
|
- Timing statistics (summary, retrieval)
|
|
- Memory extraction statistics
|
|
|
|
Usage:
|
|
python bench/longmemeval/compute_stats.py \
|
|
--results_dir bench/longmemeval/bench_results/longmemeval_reme
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
def load_results(results_dir: str) -> list[dict]:
|
|
"""Load all question result files from the directory.
|
|
|
|
Args:
|
|
results_dir: Path to the results directory
|
|
|
|
Returns:
|
|
List of result dictionaries
|
|
"""
|
|
results_path = Path(results_dir)
|
|
results = []
|
|
|
|
# Load individual question files
|
|
question_files = sorted(results_path.glob("question_*.json"))
|
|
|
|
for file_path in question_files:
|
|
try:
|
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
result = json.load(f)
|
|
results.append(result)
|
|
except Exception as e:
|
|
print(f"⚠️ Error loading {file_path}: {e}")
|
|
|
|
return results
|
|
|
|
|
|
def compute_accuracy_stats(results: list[dict]) -> dict[str, Any]:
|
|
"""Compute overall and per-type accuracy statistics.
|
|
|
|
Args:
|
|
results: List of result dictionaries
|
|
|
|
Returns:
|
|
Dictionary with accuracy statistics
|
|
"""
|
|
total = len(results)
|
|
correct = 0
|
|
incorrect = 0
|
|
error = 0
|
|
|
|
# Per question type statistics
|
|
type_stats = defaultdict(lambda: {"total": 0, "correct": 0, "incorrect": 0, "error": 0})
|
|
|
|
for r in results:
|
|
qtype = r.get("question_type", "unknown")
|
|
judgment = r.get("judgment", {})
|
|
is_correct = judgment.get("is_correct")
|
|
|
|
type_stats[qtype]["total"] += 1
|
|
|
|
if is_correct is True:
|
|
correct += 1
|
|
type_stats[qtype]["correct"] += 1
|
|
elif is_correct is False:
|
|
incorrect += 1
|
|
type_stats[qtype]["incorrect"] += 1
|
|
else:
|
|
error += 1
|
|
type_stats[qtype]["error"] += 1
|
|
|
|
# Compute accuracies
|
|
overall = {
|
|
"total": total,
|
|
"correct": correct,
|
|
"incorrect": incorrect,
|
|
"error": error,
|
|
"accuracy": correct / total if total > 0 else 0,
|
|
"accuracy_valid": correct / (correct + incorrect) if (correct + incorrect) > 0 else 0,
|
|
}
|
|
|
|
by_type = {}
|
|
for qtype, stats in type_stats.items():
|
|
valid = stats["correct"] + stats["incorrect"]
|
|
by_type[qtype] = {
|
|
**stats,
|
|
"accuracy": stats["correct"] / stats["total"] if stats["total"] > 0 else 0,
|
|
"accuracy_valid": stats["correct"] / valid if valid > 0 else 0,
|
|
}
|
|
|
|
return {
|
|
"overall": overall,
|
|
"by_question_type": by_type,
|
|
}
|
|
|
|
|
|
def compute_timing_stats(results: list[dict]) -> dict[str, Any]:
|
|
"""Compute timing statistics.
|
|
|
|
Args:
|
|
results: List of result dictionaries
|
|
|
|
Returns:
|
|
Dictionary with timing statistics
|
|
"""
|
|
summary_times = []
|
|
retrieve_times = []
|
|
|
|
for r in results:
|
|
summary_ms = r.get("summary_duration_ms", 0)
|
|
retrieve_ms = r.get("retrieve_duration_ms", 0)
|
|
|
|
if summary_ms > 0:
|
|
summary_times.append(summary_ms)
|
|
if retrieve_ms > 0:
|
|
retrieve_times.append(retrieve_ms)
|
|
|
|
def compute_stats(times: list[float]) -> dict:
|
|
if not times:
|
|
return {"count": 0, "total_ms": 0, "avg_ms": 0, "min_ms": 0, "max_ms": 0}
|
|
|
|
return {
|
|
"count": len(times),
|
|
"total_ms": sum(times),
|
|
"total_min": sum(times) / 1000 / 60,
|
|
"avg_ms": sum(times) / len(times),
|
|
"min_ms": min(times),
|
|
"max_ms": max(times),
|
|
}
|
|
|
|
return {
|
|
"summary": compute_stats(summary_times),
|
|
"retrieve": compute_stats(retrieve_times),
|
|
"total_time_min": (sum(summary_times) + sum(retrieve_times)) / 1000 / 60,
|
|
}
|
|
|
|
|
|
def compute_memory_stats(results: list[dict]) -> dict[str, Any]:
|
|
"""Compute memory extraction statistics.
|
|
|
|
Args:
|
|
results: List of result dictionaries
|
|
|
|
Returns:
|
|
Dictionary with memory statistics
|
|
"""
|
|
memory_counts = []
|
|
session_counts = []
|
|
|
|
for r in results:
|
|
memories = r.get("extracted_memories", [])
|
|
num_sessions = r.get("num_sessions", 0)
|
|
|
|
memory_counts.append(len(memories))
|
|
session_counts.append(num_sessions)
|
|
|
|
def compute_stats(counts: list[int]) -> dict:
|
|
if not counts:
|
|
return {"count": 0, "total": 0, "avg": 0, "min": 0, "max": 0}
|
|
|
|
return {
|
|
"count": len(counts),
|
|
"total": sum(counts),
|
|
"avg": sum(counts) / len(counts),
|
|
"min": min(counts),
|
|
"max": max(counts),
|
|
}
|
|
|
|
return {
|
|
"memories_per_question": compute_stats(memory_counts),
|
|
"sessions_per_question": compute_stats(session_counts),
|
|
}
|
|
|
|
|
|
def print_report(
|
|
accuracy_stats: dict,
|
|
timing_stats: dict,
|
|
memory_stats: dict,
|
|
results_dir: str,
|
|
):
|
|
"""Print formatted statistics report.
|
|
|
|
Args:
|
|
accuracy_stats: Accuracy statistics
|
|
timing_stats: Timing statistics
|
|
memory_stats: Memory statistics
|
|
results_dir: Path to results directory
|
|
"""
|
|
print("\n" + "=" * 80)
|
|
print("LONGMEMEVAL EVALUATION STATISTICS")
|
|
print(f"Results Directory: {results_dir}")
|
|
print("=" * 80)
|
|
|
|
# Overall accuracy
|
|
overall = accuracy_stats["overall"]
|
|
print("\n📊 Overall Accuracy:")
|
|
print(f" Total Questions: {overall['total']}")
|
|
print(f" ✅ Correct: {overall['correct']} ({100 * overall['accuracy']:.2f}%)")
|
|
print(
|
|
f" ❌ Incorrect: {overall['incorrect']} "
|
|
f"({100 * overall['incorrect'] / overall['total'] if overall['total'] > 0 else 0:.2f}%)",
|
|
)
|
|
if overall["error"] > 0:
|
|
print(f" ⚠️ Error: {overall['error']} ({100 * overall['error'] / overall['total']:.2f}%)")
|
|
print(f" Accuracy (valid): {100 * overall['accuracy_valid']:.2f}%")
|
|
|
|
# Accuracy by question type
|
|
print("\n📊 Accuracy by Question Type:")
|
|
print("-" * 60)
|
|
print(f"{'Question Type':<30} {'Correct':<10} {'Total':<10} {'Accuracy':<10}")
|
|
print("-" * 60)
|
|
|
|
by_type = accuracy_stats["by_question_type"]
|
|
for qtype in sorted(by_type.keys()):
|
|
stats = by_type[qtype]
|
|
print(f"{qtype:<30} {stats['correct']:<10} {stats['total']:<10} {100 * stats['accuracy']:.2f}%")
|
|
|
|
print("-" * 60)
|
|
|
|
# Timing statistics
|
|
print("\n⏱️ Timing Statistics:")
|
|
summary = timing_stats["summary"]
|
|
retrieve = timing_stats["retrieve"]
|
|
|
|
print(" Memory Summarization:")
|
|
print(f" Total Time: {summary['total_min']:.2f} min")
|
|
print(f" Avg per Q: {summary['avg_ms']:.0f} ms")
|
|
print(f" Min/Max: {summary['min_ms']:.0f} / {summary['max_ms']:.0f} ms")
|
|
|
|
print(" Memory Retrieval:")
|
|
print(f" Total Time: {retrieve['total_min']:.2f} min")
|
|
print(f" Avg per Q: {retrieve['avg_ms']:.0f} ms")
|
|
print(f" Min/Max: {retrieve['min_ms']:.0f} / {retrieve['max_ms']:.0f} ms")
|
|
|
|
print(f" Total Time: {timing_stats['total_time_min']:.2f} min")
|
|
|
|
# Memory statistics
|
|
print("\n📝 Memory Statistics:")
|
|
mem = memory_stats["memories_per_question"]
|
|
sess = memory_stats["sessions_per_question"]
|
|
|
|
print(" Extracted Memories per Question:")
|
|
print(f" Total: {mem['total']}")
|
|
print(f" Average: {mem['avg']:.1f}")
|
|
print(f" Min/Max: {mem['min']} / {mem['max']}")
|
|
|
|
print(" Sessions per Question:")
|
|
print(f" Average: {sess['avg']:.1f}")
|
|
print(f" Min/Max: {sess['min']} / {sess['max']}")
|
|
|
|
print("\n" + "=" * 80)
|
|
|
|
|
|
def save_statistics(
|
|
accuracy_stats: dict,
|
|
timing_stats: dict,
|
|
memory_stats: dict,
|
|
output_file: str,
|
|
):
|
|
"""Save statistics to JSON file.
|
|
|
|
Args:
|
|
accuracy_stats: Accuracy statistics
|
|
timing_stats: Timing statistics
|
|
memory_stats: Memory statistics
|
|
output_file: Path to output file
|
|
"""
|
|
stats = {
|
|
"accuracy": accuracy_stats,
|
|
"timing": timing_stats,
|
|
"memory": memory_stats,
|
|
}
|
|
|
|
with open(output_file, "w", encoding="utf-8") as f:
|
|
json.dump(stats, f, indent=4, ensure_ascii=False)
|
|
|
|
print(f"\n📁 Statistics saved to: {output_file}")
|
|
|
|
|
|
def main(results_dir: str, output_file: str = None):
|
|
"""Main function to compute and display statistics.
|
|
|
|
Args:
|
|
results_dir: Path to results directory
|
|
output_file: Optional path to save statistics JSON
|
|
"""
|
|
print(f"\nLoading results from: {results_dir}")
|
|
|
|
results = load_results(results_dir)
|
|
|
|
if not results:
|
|
print("❌ No results found!")
|
|
return
|
|
|
|
print(f"Loaded {len(results)} question results")
|
|
|
|
# Compute statistics
|
|
accuracy_stats = compute_accuracy_stats(results)
|
|
timing_stats = compute_timing_stats(results)
|
|
memory_stats = compute_memory_stats(results)
|
|
|
|
# Print report
|
|
print_report(accuracy_stats, timing_stats, memory_stats, results_dir)
|
|
|
|
# Save to file if specified
|
|
if output_file:
|
|
save_statistics(accuracy_stats, timing_stats, memory_stats, output_file)
|
|
else:
|
|
# Default output file in results directory
|
|
default_output = Path(results_dir) / "statistics.json"
|
|
save_statistics(accuracy_stats, timing_stats, memory_stats, str(default_output))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(
|
|
description="Compute statistics from LongMemEval evaluation results",
|
|
)
|
|
parser.add_argument(
|
|
"--results_dir",
|
|
type=str,
|
|
default="bench_results/longmemeval_reme",
|
|
help="Path to results directory containing question_*.json files",
|
|
)
|
|
parser.add_argument(
|
|
"--output_file",
|
|
type=str,
|
|
default=None,
|
|
help="Path to save statistics JSON (default: <results_dir>/statistics.json)",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
main(
|
|
results_dir=args.results_dir,
|
|
output_file=args.output_file,
|
|
)
|