ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s

This commit is contained in:
2026-08-20 13:12:50 +00:00
commit b119135836
10275 changed files with 3284984 additions and 0 deletions
@@ -0,0 +1,222 @@
#!/usr/bin/env python3
"""Analyze memory, reflection, diffusion, and action logs for Experiment 10-5."""
from __future__ import annotations
import argparse
import collections
import datetime as dt
import json
import statistics
from pathlib import Path
from typing import Any, Iterable
ARMS = ("baseline", "custom_goal", "no_reflection")
EVENT_TERMS = {
"baseline": ("valentine", "party"),
"custom_goal": ("climate", "resilience", "workshop"),
"no_reflection": ("valentine", "party"),
}
ELECTION_TERMS = ("mayor", "election")
def load_json(path: Path) -> Any:
return json.loads(path.read_text(encoding="utf-8"))
def persona_nodes(sim_dir: Path) -> dict[str, list[dict[str, Any]]]:
result = {}
personas_dir = sim_dir / "personas"
for persona_dir in sorted(path for path in personas_dir.iterdir() if path.is_dir()):
path = (
persona_dir
/ "bootstrap_memory"
/ "associative_memory"
/ "nodes.json"
)
nodes = load_json(path)
result[persona_dir.name] = list(nodes.values())
return result
def contains_terms(node: dict[str, Any], terms: Iterable[str]) -> bool:
text = " ".join(
str(node.get(key, ""))
for key in ("subject", "predicate", "object", "description", "embedding_key")
).lower()
return any(term in text for term in terms)
def diffusion(nodes: dict[str, list[dict[str, Any]]], terms: tuple[str, ...]) -> dict:
aware = {}
mentions = {}
for name, rows in nodes.items():
matching = [row for row in rows if contains_terms(row, terms)]
if matching:
created = sorted(row.get("created") for row in matching if row.get("created"))
aware[name] = created[0] if created else None
mentions[name] = len(matching)
return {
"terms": list(terms),
"aware_agents": len(aware),
"first_mention_by_agent": aware,
"memory_mentions_by_agent": mentions,
}
def memory_summary(
nodes: dict[str, list[dict[str, Any]]], seed_counts: dict[str, int]
) -> dict:
totals = collections.Counter()
new_totals = collections.Counter()
new_by_persona = {}
reflection_evidence = 0
max_depth = 0
chat_edges = collections.Counter()
for name, rows in nodes.items():
cutoff = seed_counts[name]
new_rows = [row for row in rows if int(row.get("node_count", 0)) > cutoff]
counts = collections.Counter(row.get("type", "unknown") for row in rows)
new_counts = collections.Counter(row.get("type", "unknown") for row in new_rows)
totals.update(counts)
new_totals.update(new_counts)
new_by_persona[name] = dict(new_counts)
reflection_evidence += sum(
row.get("type") == "thought" and bool(row.get("filling"))
for row in new_rows
)
max_depth = max(max_depth, *(int(row.get("depth", 0)) for row in rows))
for row in new_rows:
if row.get("type") != "chat":
continue
subject = str(row.get("subject", ""))
obj = str(row.get("object", ""))
if subject and obj:
chat_edges[tuple(sorted((subject, obj)))] += 1
return {
"all_nodes_by_type": dict(totals),
"new_nodes_by_type": dict(new_totals),
"new_nodes_by_persona": new_by_persona,
"new_thoughts_with_evidence": reflection_evidence,
"maximum_thought_depth": max_depth,
"unique_chat_edges": len(chat_edges),
"chat_nodes_by_edge": {
" | ".join(edge): count for edge, count in sorted(chat_edges.items())
},
}
def action_summary(sim_dir: Path) -> dict:
meta = load_json(sim_dir / "reverie" / "meta.json")
descriptions: dict[str, list[str]] = {
name: [] for name in meta["persona_names"]
}
transitions = collections.Counter()
cafe_window: dict[str, set[str]] = collections.defaultdict(set)
sample_steps = 0
for step in range(int(meta["step"])):
path = sim_dir / "movement" / f"{step}.json"
movement = load_json(path)
current = dt.datetime.strptime(
movement["meta"]["curr_time"], "%B %d, %Y, %H:%M:%S"
)
in_event_window = (
current.date() == dt.date(2023, 2, 14)
and dt.time(17, 0) <= current.time() < dt.time(19, 0)
)
for name, row in movement["persona"].items():
description = str(row.get("description", ""))
descriptions[name].append(description)
if in_event_window and "hobbs cafe" in description.lower():
cafe_window[name].add(description)
sample_steps += 1
per_persona = {}
for name, rows in descriptions.items():
changes = sum(left != right for left, right in zip(rows, rows[1:]))
transitions[name] = changes
per_persona[name] = {
"unique_descriptions": len(set(rows)),
"description_changes": changes,
"hobbs_cafe_event_window_descriptions": sorted(cafe_window.get(name, set())),
}
unique_counts = [row["unique_descriptions"] for row in per_persona.values()]
change_counts = [row["description_changes"] for row in per_persona.values()]
return {
"steps": sample_steps,
"persona_action_summary": per_persona,
"hobbs_cafe_event_window_agents": sorted(cafe_window),
"hobbs_cafe_event_window_agent_count": len(cafe_window),
"median_unique_descriptions": statistics.median(unique_counts),
"median_description_changes": statistics.median(change_counts),
}
def provider_summary(status: dict) -> dict:
calls = errors = transport_retries = 0
usage = collections.Counter()
latency = wall = 0.0
for checkpoint in status.get("checkpoints", []):
receipt = checkpoint["receipt_summary"]
calls += int(receipt.get("calls", 0))
errors += int(receipt.get("errors", 0))
transport_retries += int(receipt.get("transport_retries", 0))
usage.update(receipt.get("usage", {}))
latency += float(receipt.get("provider_latency_seconds", 0))
wall += float(checkpoint.get("wall_seconds", 0))
return {
"calls": calls,
"errors": errors,
"transport_retries": transport_retries,
"usage": dict(usage),
"provider_latency_seconds": round(latency, 3),
"checkpoint_wall_seconds": round(wall, 3),
}
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("output", type=Path)
args = parser.parse_args()
output = args.output.resolve()
storage = output / "storage"
seed_nodes = persona_nodes(storage / "exp10_5_history_seed")
seed_counts = {name: len(rows) for name, rows in seed_nodes.items()}
result = {
"schema_version": 1,
"experiment": "10-5",
"source_commit": "fe05a71d3e4ed7d10bf68aa4eda6dd995ec070f4",
"seed_memory_nodes_by_persona": seed_counts,
"arms": {},
}
for arm in ARMS:
status = load_json(output / "status" / f"{arm}.json")
if not status.get("complete"):
raise SystemExit(f"arm is incomplete: {arm}")
sim_dir = storage / status["current_sim"]
meta = load_json(sim_dir / "reverie" / "meta.json")
nodes = persona_nodes(sim_dir)
result["arms"][arm] = {
"simulation": {
"sim_code": status["current_sim"],
"personas": len(meta["persona_names"]),
"steps": meta["step"],
"current_time": meta["curr_time"],
"sec_per_step": meta["sec_per_step"],
},
"provider": provider_summary(status),
"memory": memory_summary(nodes, seed_counts),
"seeded_event_diffusion": diffusion(nodes, EVENT_TERMS[arm]),
"election_diffusion": diffusion(nodes, ELECTION_TERMS),
"actions": action_summary(sim_dir),
}
analysis_dir = output / "analysis"
analysis_dir.mkdir(parents=True, exist_ok=True)
path = analysis_dir / "deterministic_analysis.json"
path.write_text(json.dumps(result, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
print(json.dumps(result, indent=2, ensure_ascii=False))
return 0
if __name__ == "__main__":
raise SystemExit(main())