Audit recent Agent memory research

This commit is contained in:
wuyang
2026-07-10 18:52:00 +08:00
parent 36c631e026
commit ab758e774e
17 changed files with 42663 additions and 3 deletions
+194
View File
@@ -0,0 +1,194 @@
#!/usr/bin/env python3
"""Collect a paginated, abstract-level Agent-memory research corpus from arXiv."""
from __future__ import annotations
import argparse
import json
import re
import time
import urllib.error
import urllib.parse
import urllib.request
import xml.etree.ElementTree as ET
from collections import defaultdict
from datetime import date
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
API_URL = "https://export.arxiv.org/api/query"
NS = {
"atom": "http://www.w3.org/2005/Atom",
"opensearch": "http://a9.com/-/spec/opensearch/1.1/",
}
QUERIES = [
("agent-memory", 'all:"agent memory" OR all:"memory agent" OR all:"memory agents"'),
(
"long-term-memory",
'(all:"long-term memory" OR all:"persistent memory") AND '
'(all:"LLM agent" OR all:"language agent" OR all:"large language model agent")',
),
(
"episodic-memory",
'all:"episodic memory" AND (all:"LLM" OR all:"large language model") AND all:"agent"',
),
(
"procedural-memory",
'(all:"procedural memory" OR all:"skill memory") AND '
'(all:"LLM agent" OR all:"language agent")',
),
(
"memory-evaluation",
'(all:"memory benchmark" OR all:"memory evaluation") AND '
'(all:"LLM agent" OR all:"language agent" OR all:"agent memory")',
),
]
def normalize(value: str) -> str:
return re.sub(r"\s+", " ", value).strip()
def arxiv_id(raw: str) -> str:
return raw.rsplit("/", 1)[-1].split("v", 1)[0]
def date_query(query: str, start: str, end: str) -> str:
return (
f"({query}) AND submittedDate:[{start.replace('-', '')}0000 "
f"TO {end.replace('-', '')}2359]"
)
def fetch_page(
query: str,
start_date: str,
end_date: str,
offset: int,
page_size: int,
retries: int,
timeout: int,
) -> bytes:
params = {
"search_query": date_query(query, start_date, end_date),
"start": offset,
"max_results": page_size,
"sortBy": "submittedDate",
"sortOrder": "ascending",
}
request = urllib.request.Request(
f"{API_URL}?{urllib.parse.urlencode(params)}",
headers={"User-Agent": "agent-kb-research/0.1"},
)
for attempt in range(retries + 1):
try:
with urllib.request.urlopen(request, timeout=timeout) as response:
return response.read()
except (urllib.error.HTTPError, urllib.error.URLError, TimeoutError):
if attempt >= retries:
raise
time.sleep(2 ** attempt)
raise RuntimeError("unreachable retry state")
def parse_page(payload: bytes) -> tuple[int, list[dict]]:
root = ET.fromstring(payload)
total = int(root.findtext("opensearch:totalResults", default="0", namespaces=NS))
records = []
for entry in root.findall("atom:entry", NS):
paper_id = arxiv_id(entry.findtext("atom:id", default="", namespaces=NS))
records.append(
{
"arxiv_id": paper_id,
"title": normalize(entry.findtext("atom:title", default="", namespaces=NS)),
"abstract": normalize(entry.findtext("atom:summary", default="", namespaces=NS)),
"published": entry.findtext("atom:published", default="", namespaces=NS)[:10],
"updated": entry.findtext("atom:updated", default="", namespaces=NS)[:10],
"authors": [
normalize(author.findtext("atom:name", default="", namespaces=NS))
for author in entry.findall("atom:author", NS)
],
"categories": [
node.attrib["term"]
for node in entry.findall("atom:category", NS)
if node.attrib.get("term")
],
"url": f"https://arxiv.org/abs/{paper_id}",
"pdf_url": f"https://arxiv.org/pdf/{paper_id}",
}
)
return total, records
def write_json(path: Path, payload: dict) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
tmp = path.with_suffix(path.suffix + ".tmp")
tmp.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
tmp.replace(path)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--from-date", default="2025-01-01")
parser.add_argument("--to-date", default=date.today().isoformat())
parser.add_argument("--page-size", type=int, default=100)
parser.add_argument("--max-per-query", type=int, default=2000)
parser.add_argument("--sleep", type=float, default=1.0)
parser.add_argument("--retries", type=int, default=4)
parser.add_argument("--timeout", type=int, default=45)
parser.add_argument(
"--output",
type=Path,
default=ROOT / "data" / "research" / "agent-memory-expanded.json",
)
args = parser.parse_args()
records: dict[str, dict] = {}
matched_queries: defaultdict[str, set[str]] = defaultdict(set)
query_totals = {}
for name, query in QUERIES:
offset = 0
while offset < args.max_per_query:
payload = fetch_page(
query,
args.from_date,
args.to_date,
offset,
args.page_size,
args.retries,
args.timeout,
)
total, page = parse_page(payload)
query_totals[name] = total
for record in page:
records[record["arxiv_id"]] = record
matched_queries[record["arxiv_id"]].add(name)
offset += len(page)
print(f"{name}: {min(offset, total)}/{total}", flush=True)
if not page or offset >= total:
break
time.sleep(args.sleep)
time.sleep(args.sleep)
papers = [
{**record, "matched_queries": sorted(matched_queries[paper_id])}
for paper_id, record in records.items()
]
papers.sort(key=lambda item: (item["published"], item["arxiv_id"]))
output = {
"generated_at": date.today().isoformat(),
"from_date": args.from_date,
"to_date": args.to_date,
"query_totals": query_totals,
"unique_papers": len(papers),
"papers": papers,
}
write_json(args.output, output)
print(json.dumps({"query_totals": query_totals, "unique_papers": len(papers)}, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())