mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-08-28 05:25:04 +00:00
* feat(benchmark): add golden answer validation and session review for LongMemEval - Introduce GoldenCheckStep to validate LongMemEval golden answers using structured verdicts - Add SessionReviewStep to extract query/answer-relevant evidence from all sessions - Implement concurrent session processing with configurable concurrency limits - Create check_golden job configuration with lme_review and lme_judge agent wrappers - Add Qwen3.7-plus model configuration for enhanced processing capabilities - Include python_execute tool integration for agent-based reasoning and date validation - Generate comprehensive JSON output with session summaries and validation verdicts - Add run_check_golden.py script for batch processing across all LongMemEval samples - Configure proper logging initialization with console and file output options - Update component registry and file I/O modules to support new benchmark features * feat(scripts): add script to summarize LongMemEval check_golden verdicts - Parse check_golden.json files across all LongMemEval samples - Calculate accuracy metrics for golden answers and session IDs - Provide breakdown by question type with percentage calculations - Add command line options for listing bad samples and JSON output - Include progress tracking showing completed vs pending samples - Display confidence scores and date sanity checks statistics * refactor(benchmark): move golden check scripts to longmemeval directory - Moved run_check_golden.py from scripts/ to benchmark/longmemeval/ - Moved stats_check_golden.py from scripts/ to benchmark/longmemeval/ - Updated path resolution to use parents[2] instead of parent.parent - Added new --list-run-failed option to stats script - Added logging directory constant and functions for tracking launched samples - Enhanced stats output with launched count and run failure information - Improved error reporting with run failure details and log file paths * feat(benchmark): add LongMemEval agentic answer workflow with session extraction - Add LmeAgenticAnswerStep, LmeAutoMemoryStep, and LmeExtractSessionStep to __init__.py - Create shared helper render_with_source for displaying search results with session_id - Implement agentic_answer step with vector_search, bm25_search, and extract_session_by_id tools - Add auto_memory step to convert each session into search-friendly daily notes - Create extract_session step to retrieve and analyze raw session content by session_id - Update jinli_lme.yaml with auto_memory, vector_search, bm25_search, and agentic_answer jobs - Configure lme_memory, lme_extract, and lme_agentic_answer agent wrappers - Enhance search steps with include_source option to show session_id metadata - Add proper session_id tracking and collision handling in daily note generation * feat(benchmark): add LongMemEval agentic answer evaluation pipeline - Add session_id tracking to agentic_answer.py result metadata - Introduce run_agentic_answer.py driver for complete pipeline execution - Implement auto_memory, update_index, and agentic_answer job orchestration - Add concurrent execution with configurable limits and staggering - Create aggregation script for collecting tool-call trails and results - Add stats_agentic_answer.py for comprehensive result analysis - Implement resume capability with existing output detection - Generate aggregate.json with per-sample breakdown and tool call summaries * feat(steps): add ClearPathsStep for cleaning workspace outputs before rebuild - Introduce ClearPathsStep to remove stale workspace files/directories - Add support for specifying paths and config_keys as targets to clear - Implement safety checks to prevent deletion of files outside workspace - Add logging for cleared paths and warnings for invalid paths - Configure clear_paths_step in jinli_lme.yaml to clean daily_dir - Add clear_paths_step to clean mem_answer.json before rebuilds * feat(benchmark): add resume functionality to agentic answer runner - Replace --force flag with --resume flag for controlling job execution - By default every job reruns with clean rebuild behavior using config clear steps - Add --resume option to skip samples whose output already exists and continue interrupted batches - Update documentation to reflect new default clean rebuild behavior - Modify job skipping logic to honor resume flag instead of force flag - Update dry-run output to show correct todo jobs based on resume status - Change default example command to use --resume for continuing interrupted runs * feat(benchmark): generate JSONL output for check golden records - Add write_check_golden_list function to create JSONL file - Write all readable check_golden records as JSONL format - Include check_golden_list path in stats output - Display generated JSONL file path in summary report - Maintain UTF-8 encoding with non-ASCII character support * refactor(benchmark): rename answer judge step and integrate LME LLM judge - Rename AnswerJudgeStep to LmeLlmJudgeStep and update imports - Add new llm_judge configuration in jinli_lme.yaml - Update run_agentic_answer.py to include llm_judge in pipeline - Modify LmeLlmJudgeStep to read from query.json and answer.json - Write LLM judgement results back to mem_answer.json - Add command line options for start/end sample range selection - Update aggregate.json generation to include LLM judgement data - Add resume capability for llm_judge job based on judgement presence * refactor(benchmark): rename answer judge step and integrate LME LLM judge - Rename AnswerJudgeStep to LmeLlmJudgeStep and update imports - Add new llm_judge configuration in jinli_lme.yaml - Update run_agentic_answer.py to include llm_judge in pipeline - Modify LmeLlmJudgeStep to read from query.json and answer.json - Write LLM judgement results back to mem_answer.json - Add command line options for start/end sample range selection - Update aggregate.json generation to include LLM judgement data - Add resume capability for llm_judge job based on judgement presence * feat(steps): add wait_for_paths_step to block until workspace files exist - Introduce WaitForPathsStep class that polls for required workspace-relative paths - Add step registration with 'wait_for_paths_step' backend identifier - Implement path validation to ensure targets are within workspace boundaries - Add polling mechanism with configurable intervals via poll_seconds parameter - Include logging functionality with log_every_seconds parameter for status updates - Add metadata tracking of waited paths and duration in response object - Register step in index module and expose in public API - Configure step in jinli_lme.yaml to wait for session_review.json before golden check - Add script rename from run_check_golden.py to run_golden_check.py with enhanced options * feat(benchmark): enhance longmemeval benchmarking with concurrency and progress tracking - Add benchmark extra dependency group with portalocker requirement - Introduce concurrent execution support for golden_check and session_review workflows - Add progress reporting interval option with real-time status updates - Implement global throttling mechanism for session review requests using file locks - Enhance golden check validation with current schema verification - Add active task tracking and graceful shutdown handling - Rename check_golden scripts to golden_check for consistency - Update statistics reporting with correct/incorrect terminology instead of reasonable - Add stale format detection and compatibility handling for verdict fields - Include both_correct rate calculation in accuracy metrics - Add concurrency and staggering options for better resource management * ci(workflow): add Windows smoke test workflow - Create new workflow file .github/workflows/windows-smoke.yml - Configure workflow to trigger on push and pull request events - Set up Python environment with version 3.11 - Install package dependencies using pip - Run version job as smoke test for CLI functionality - Enable concurrency control to prevent duplicate runs - Use matrix strategy for Python version testing * feat(benchmark): add retry mechanism and health check for session review - Added retry configuration options (retry_initial_seconds, retry_max_seconds, retry_max_attempts) to jinli_lme.yaml - Implemented exponential backoff retry logic with configurable parameters in session_review step - Added output_is_healthy function to verify session_review.json integrity and absence of failed reviews - Updated resume functionality to skip only healthy outputs instead of all existing files - Integrated JSON parsing and validation to check for failed reviews in output files - Enhanced error handling and logging for retry attempts and recovery scenarios * feat(benchmark): add LongMemEval session review statistics script - Create stats_session_review.py to summarize session_review.json artifacts - Add command line options for listing failed, missing, and run failed samples - Implement JSON output mode for programmatic consumption - Calculate and display health statistics including total samples, healthy outputs, failed sessions - Provide detailed failure information with session IDs and error messages - Generate re-run commands for samples with failed reviews - Add percentage calculations for better statistical overview - Include support for multiple output formats and detailed logging * feat(benchmark): add LongMemEval output cleanup script and enhance golden check retry logic - Added clean_sample_outputs.py script to remove generated LongMemEval files while preserving source inputs - Implemented configurable retry mechanism in golden_check.py with exponential backoff strategy - Added retry parameters (initial/max seconds and max attempts) to control failure recovery behavior - Integrated asyncio support for asynchronous sleep during retry intervals - Configured default retry settings in jinli_lme.yaml with 5s initial and 300s maximum intervals - Preserved core files (query.json, answer.json, session/) while cleaning generated artifacts * feat(benchmark): add AppleDouble file cleanup to sample output cleaner - Remove AppleDouble files starting with '._' recursively including under session/ - Add is_under helper function to check if path is inside parent directory - Track targets in set to avoid duplicate processing - Include AppleDouble files in cleanup targets when not already covered by existing targets - Maintain dry-run mode as default behavior with --apply flag for actual deletion * refactor(benchmark): update LongMemEval sample output cleaning script - Add time and Iterator imports for enhanced functionality - Add --progress-every argument to control progress reporting frequency - Replace is_under function with iter_sample_targets generator - Implement detailed progress tracking with timing measurements - Add sample-by-sample processing with elapsed time reporting - Include AppleDouble file detection within session directory - Update target counting and deletion statistics display - Add conditional progress updates based on progress-every setting - Improve dry-run mode with would-delete indication * chore(benchmark): increase initial interval for session review step - Changed START_INTERVAL_SECONDS from 1.0 to 3.0 seconds - Adjusted timing parameters for better benchmark stability * refactor(benchmark): implement coordinated retry mechanism for session reviews - Add retry gate condition to coordinate concurrent review attempts - Implement wait_for_healthy_start_slot to handle sequential retries - Create mark_retrying and mark_recovered functions to track retry states - Update reply_with_retry to accept index parameter for coordination - Add has_prior_retry logic to prevent race conditions during recovery - Ensure proper cleanup of retry state on success or failure - Maintain backward compatibility while adding coordination features * chore(benchmark): adjust session review start interval timeout - Changed START_INTERVAL_SECONDS from 3.0 to 5.0 seconds - Increased initial delay for session review benchmark step - Updated timeout configuration for improved stability * refactor(benchmark): update session review concurrency and throttling mechanism - Replace global throttle with per-process concurrency control - Add concurrency parameter with default value of 30 in config - Add start_interval_seconds parameter with default value of 2 seconds - Change default concurrency from 3 to 1 in command line interface - Update documentation to reflect new throttling behavior - Implement semaphore-based concurrency limiting for review tasks - Modify retry mechanism to use local locking instead of global files - Remove portalocker dependency for cross-process throttling * refactor(config): update session review configuration and concurrency settings - Removed deprecated retry configuration parameters from jinli_lme.yaml - Increased MAX_CONCURRENCY from 30 to 60 in session_review.py - Reduced START_INTERVAL_SECONDS from 2.0 to 1.0 in session_review.py - Cleaned up redundant backend specifications in configuration file - Simplified agent wrapper configurations by removing obsolete retry settings * feat(benchmark): enhance LME auto memory step with advanced scheduling and error handling - Add datetime parsing functionality for LongMemEval timestamps with regex pattern - Implement configurable concurrency limits with MAX_CONCURRENCY of 60 - Introduce retry mechanism with exponential backoff for agent interactions - Add session filtering based on date comparison with question_date validation - Create rate limiting with start interval control between requests - Implement sophisticated retry coordination using asyncio conditions - Add comprehensive error tracking for failed and filtered session extracts - Remove deprecated concurrency parameter from jinli_lme.yaml configuration - Add structured output validation in session review step - Include detailed metadata reporting with session statistics and errors * fix(benchmark): adjust default concurrency for auto_memory job - Changed default concurrency from 3 to 1 for auto_memory job to prevent API overload - Updated help text to reflect new default value of 1 for concurrency parameter - Modified documentation to clarify concurrency behavior varies by job type * refactor(search): replace hardcoded candidate multiplier with constant - Introduced _CANDIDATE_MULTIPLIER constant set to 10 - Replaced hardcoded factor of 5 with _CANDIDATE_MULTIPLIER in BM25 search - Replaced hardcoded factor of 5 with _CANDIDATE_MULTIPLIER in vector search - Updated test to verify both search steps use ten times limit for candidates - Imported VectorSearchStep and Bm25SearchStep in test module - Added comprehensive test case for candidate count calculation logic * feat(lme): add data inspection error handling with fallback mechanism - Implemented non-retryable data inspection error markers detection - Added _is_data_inspection_error method to identify inspection failures - Created fallback handling for data inspection errors in auto memory extraction - Added fallback handling for data inspection errors in session review - Extended failed extracts tracking with non-retryable and fallback flags - Separated fallback extracts from regular failed extracts in reporting - Enhanced error logging with specific data inspection failure messages - Updated metrics to track fallback extractions and reviews separately - Maintained existing retry logic for other exception types * feat(benchmark): enhance session review statistics with fallback tracking - Add support for identifying and listing non-retryable fallback reviews - Introduce --list-fallback argument to display fallback review details - Separate retryable failures from non-retryable fallbacks in reporting - Track fallback samples and sessions separately from failed ones - Update console output to show both retryable and non-retryable categories - Include fallback details in JSON output with reasons and session info - Modify failure counting logic to distinguish between retryable and fallback reviews * feat(benchmark): add question_id tracking and enhanced fallback reporting - Add question_id function to extract query.question_id from data - Initialize question_id_by_id dictionary to store question IDs by index - Store question_id for each sample during data processing - Enhance fallback output to include question IDs and session information - Format sample labels with question IDs when available - Display session IDs associated with each fallback case * feat(benchmark): add question_id support and improve bad sample reporting - Add question_id_for function to extract question_id from multiple sources - Add sample_label function to format samples as idx(question_id) when available - Store question_id in data dictionary during processing - Change bad_golden and bad_sessions to store full records instead of just indices - Update list_bad output to show formatted labels with question_id information - Improve error reporting with more detailed sample identification * feat(benchmark): enhance golden check stats with structured output - Add related_session_ids function to extract session IDs from verdict records - Create grouped_records function to group records by question type - Replace flat list output with JSON-formatted grouped records in list_bad option - Replace flat list output with JSON-formatted grouped records in list_bad_sessions option - Maintain Chinese labels while adding structured data presentation - Improve readability of bad verdict record display with hierarchical grouping * feat(benchmark): update data structure for question indexing - Replace sample_label with _idx field for index tracking - Add question_id field to store _question_id values - Maintain backward compatibility with empty string defaults - Preserve existing session_id functionality - Update data mapping to include new fields in grouped results * refactor(benchmark): streamline golden answer verification process - Replace relevance filtering with comprehensive information extraction - Remove is_relevant field and simplify session summary structure - Change relevant_info to extracted_info for clarity - Update golden check logic to work with full extractions instead of filtered summaries - Simplify prompt instructions to focus on complete information extraction - Remove redundant schema validation and structured output requirements - Adjust statistics calculation to match new extraction approach - Update metadata field names to reflect extraction rather than relevance checking * feat(benchmark): add selective file deletion option to clean_sample_outputs - Add --filename argument to delete only specific root-level files - Modify iter_sample_targets function to accept optional filenames filter - Implement validation for root-level filename constraints - Update function calls to pass filenames parameter - Add example usage for selective file deletion in documentation * feat(benchmark): add error count metrics to golden check statistics - Added golden_bad, session_bad, and both_bad calculation fields - Updated console output format to include error counts per question type - Modified table display to show both accuracy rates and error numbers - Enhanced statistical summary with additional error breakdown metrics * test(search): update search step tests with include_source parameter - Added include_source=False parameter to VectorSearchStep initialization - Added include_source=False parameter to Bm25SearchStep initialization - Maintained existing RuntimeContext parameters for both search steps - Updated test calls to match new constructor signature with include_source option
202 lines
7.6 KiB
Python
202 lines
7.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Summarise the ``agentic_answer`` results across all LongMemEval samples.
|
|
|
|
Reports progress (how many of the 500 samples produced ``mem_answer.json``) and a
|
|
breakdown of answer *status*:
|
|
- answered — a non-empty answer that is not "not provided";
|
|
- not_provided — the agent gave up ("not provided");
|
|
- empty — ``mem_answer.json`` exists but the answer is blank;
|
|
- missing — no ``mem_answer.json`` yet.
|
|
|
|
Everything is broken down by ``question_type``. This script does NOT judge answer
|
|
correctness (there is no grader for ``mem_answer`` yet) — it only tracks progress
|
|
and collects predicted-vs-golden pairs. Tool-call statistics are read from the
|
|
aggregate written by ``run_agentic_answer.py`` when it is present.
|
|
|
|
Examples:
|
|
python benchmark/longmemeval/stats_agentic_answer.py
|
|
python benchmark/longmemeval/stats_agentic_answer.py --list-run-failed
|
|
python benchmark/longmemeval/stats_agentic_answer.py --list-unanswered
|
|
python benchmark/longmemeval/stats_agentic_answer.py --json
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
|
|
REPO = Path(__file__).resolve().parents[2]
|
|
DATA = REPO / "datasets" / "longmemeval"
|
|
LOGBASE = REPO / "logs" / "agentic_answer"
|
|
AGGREGATE = LOGBASE / "aggregate.json"
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
"""Parse command-line arguments."""
|
|
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
p.add_argument("--list-unanswered", action="store_true", help="list samples answered 'not provided' or empty")
|
|
p.add_argument("--list-run-failed", action="store_true", help="list launched samples with no readable output")
|
|
p.add_argument("--json", action="store_true", help="emit the summary as JSON")
|
|
return p.parse_args()
|
|
|
|
|
|
def sample_ids() -> list[str]:
|
|
"""List all sample IDs (numeric workspace dirs), numerically sorted."""
|
|
ids = [p.name for p in DATA.iterdir() if p.is_dir() and p.name.isdigit()]
|
|
return sorted(ids, key=int)
|
|
|
|
|
|
def pct(num: int, den: int) -> str:
|
|
"""Format a percentage."""
|
|
return f"{(100.0 * num / den):.1f}%" if den else "n/a"
|
|
|
|
|
|
def logged_sample_ids() -> list[str]:
|
|
"""List sample IDs that have an agentic_answer launch log."""
|
|
logdir = LOGBASE / "agentic_answer"
|
|
if not logdir.exists():
|
|
return []
|
|
ids = [p.stem for p in logdir.glob("*.log") if p.stem.isdigit()]
|
|
return sorted(ids, key=int)
|
|
|
|
|
|
def answer_status(pred: str, has_file: bool) -> str:
|
|
"""Classify an answer into answered / not_provided / empty / missing."""
|
|
if not has_file:
|
|
return "missing"
|
|
if not pred:
|
|
return "empty"
|
|
if "not provided" in pred.lower():
|
|
return "not_provided"
|
|
return "answered"
|
|
|
|
|
|
def load_tool_calls() -> dict[str, int]:
|
|
"""Map idx -> num_tool_calls from the aggregate, if it exists."""
|
|
if not AGGREGATE.exists():
|
|
return {}
|
|
try:
|
|
with AGGREGATE.open(encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
except (OSError, json.JSONDecodeError):
|
|
return {}
|
|
return {s["idx"]: s.get("num_tool_calls", 0) for s in data.get("samples", []) if "idx" in s}
|
|
|
|
|
|
def main() -> int:
|
|
"""Main entry point."""
|
|
args = parse_args()
|
|
ids = sample_ids()
|
|
total = len(ids)
|
|
tool_calls = load_tool_calls()
|
|
|
|
rows, unreadable = [], []
|
|
finished_ids = set()
|
|
for idx in ids:
|
|
query_path = DATA / idx / "query.json"
|
|
mem_path = DATA / idx / "mem_answer.json"
|
|
qtype = "(unknown)"
|
|
try:
|
|
with query_path.open(encoding="utf-8") as f:
|
|
qtype = json.load(f).get("question_type") or "(unknown)"
|
|
except (OSError, json.JSONDecodeError):
|
|
pass
|
|
|
|
has_file = mem_path.exists()
|
|
pred = ""
|
|
if has_file:
|
|
try:
|
|
with mem_path.open(encoding="utf-8") as f:
|
|
pred = str(json.load(f).get("answer") or "").strip()
|
|
finished_ids.add(idx)
|
|
except (OSError, json.JSONDecodeError):
|
|
unreadable.append(idx)
|
|
has_file = False
|
|
|
|
rows.append({"idx": idx, "type": qtype, "status": answer_status(pred, has_file)})
|
|
|
|
finished = [r for r in rows if r["status"] != "missing"]
|
|
n = len(finished)
|
|
launched = logged_sample_ids()
|
|
run_failed = [idx for idx in launched if idx not in finished_ids]
|
|
|
|
# Overall status tallies.
|
|
status_counts: dict[str, int] = defaultdict(int)
|
|
for r in rows:
|
|
status_counts[r["status"]] += 1
|
|
answered = status_counts["answered"]
|
|
unanswered = [r["idx"] for r in rows if r["status"] in ("not_provided", "empty")]
|
|
|
|
calls_vals = [tool_calls[i] for i in finished_ids if i in tool_calls]
|
|
avg_calls = sum(calls_vals) / len(calls_vals) if calls_vals else 0.0
|
|
|
|
# Per question_type breakdown.
|
|
by_type: dict[str, dict[str, int]] = defaultdict(lambda: {"n": 0, "answered": 0})
|
|
for r in finished:
|
|
by_type[r["type"]]["n"] += 1
|
|
by_type[r["type"]]["answered"] += 1 if r["status"] == "answered" else 0
|
|
|
|
if args.json:
|
|
print(
|
|
json.dumps(
|
|
{
|
|
"total": total,
|
|
"finished": n,
|
|
"pending": total - n - len(unreadable),
|
|
"unreadable": unreadable,
|
|
"launched": len(launched),
|
|
"run_failed": run_failed,
|
|
"status_counts": dict(status_counts),
|
|
"answered_rate": round(answered / n, 4) if n else None,
|
|
"avg_tool_calls": round(avg_calls, 2) if calls_vals else None,
|
|
"by_type": {
|
|
t: {**c, "answered_rate": round(c["answered"] / c["n"], 4)} for t, c in by_type.items()
|
|
},
|
|
"unanswered": unanswered,
|
|
"aggregate": str(AGGREGATE) if AGGREGATE.exists() else None,
|
|
},
|
|
ensure_ascii=False,
|
|
indent=2,
|
|
),
|
|
)
|
|
return 0
|
|
|
|
print("=" * 60)
|
|
print("LongMemEval agentic_answer 统计")
|
|
print("=" * 60)
|
|
print(f"样例总数 : {total}")
|
|
print(f"已完成 (有产出) : {n} ({pct(n, total)})")
|
|
print(f"未完成 : {total - n - len(unreadable)}")
|
|
if unreadable:
|
|
print(f"损坏/无法解析 : {len(unreadable)} {unreadable}")
|
|
print(f"已启动过 (有 log) : {len(launched)}")
|
|
print(f"运行失败/无可读产出 : {len(run_failed)}")
|
|
print("-" * 60)
|
|
print(f"已作答 (非 not provided): {answered} ({pct(answered, n)} of finished)")
|
|
print(f" 其中 not provided : {status_counts['not_provided']}")
|
|
print(f" 其中 空答案 : {status_counts['empty']}")
|
|
if calls_vals:
|
|
print(f"平均工具调用次数 : {avg_calls:.1f} (来自 {AGGREGATE.name})")
|
|
else:
|
|
print("平均工具调用次数 : n/a (先跑 run_agentic_answer.py 生成 aggregate.json)")
|
|
print("-" * 60)
|
|
print("按 question_type:")
|
|
print(f" {'type':<24} {'n':>4} {'已作答率':>12}")
|
|
for t in sorted(by_type):
|
|
c = by_type[t]
|
|
print(f" {t:<24} {c['n']:>4} {pct(c['answered'], c['n']):>12}")
|
|
|
|
if args.list_unanswered:
|
|
print("-" * 60)
|
|
print(f"not provided / 空答案的样例 ({len(unanswered)}): {unanswered}")
|
|
if args.list_run_failed:
|
|
print("-" * 60)
|
|
print(f"运行失败/无可读 mem_answer.json 的样例 ({len(run_failed)}): {run_failed}")
|
|
for idx in run_failed:
|
|
print(f" {idx}: {LOGBASE / 'agentic_answer' / f'{idx}.log'}")
|
|
print("=" * 60)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|