Files
ai-agent-book/chapter10/parallel-web-research/demo.py
T
liqiang b119135836
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
ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
2026-08-20 13:12:50 +00:00

94 lines
3.9 KiB
Python

#!/usr/bin/env python3
"""Experiment 10-4: parallel real-browser faculty search."""
from __future__ import annotations
import argparse
import asyncio
import json
import time
from pathlib import Path
try:
from dotenv import load_dotenv
load_dotenv()
except Exception:
pass
from agents import BrowserPool, Coordinator, WorkerAgent, run_sequential
from message_bus import MessageBus
from sources import DEFAULT_SITES, TARGET, load_sites
def parse_args():
p = argparse.ArgumentParser(description="实验 10-4:N 个独立真实浏览器会话并行搜索教师")
p.add_argument("--target", default=TARGET, help="要查找的教师姓名")
p.add_argument("--sites-json", help="网站数组 JSON,每项包含 name/college/url")
p.add_argument("--agents", type=int, default=len(DEFAULT_SITES), help="使用前 N 个网站/Agent")
p.add_argument("--timeout", type=float, default=120, help="每网站超时秒数")
p.add_argument("--headed", action="store_true", help="显示每个真实浏览器页面")
p.add_argument("--quiet", action="store_true", help="不打印逐条总线消息")
p.add_argument("--no-compare", action="store_true", help="跳过串行基线(默认真实运行并对比)")
p.add_argument("--output", default="artifacts/latest.json", help="保存实测结果 JSON")
return p.parse_args()
async def main(args) -> int:
sites = load_sites(args.sites_json, args.agents)
print(f"真实目标:{args.target}; 真实网站/独立 browser context 数:{len(sites)}")
parallel_pool = BrowserPool(headless=not args.headed)
await parallel_pool.start()
try:
bus = MessageBus(verbose=not args.quiet)
coordinator = Coordinator(bus, args.target)
for i, site in enumerate(sites):
coordinator.add_worker(WorkerAgent(
f"agent-{i:02d}", site, bus, args.target, parallel_pool, args.timeout
))
parallel = await coordinator.run()
finally:
await parallel_pool.close()
serial = None
serial_pool = None
if not args.no_compare:
serial_pool = BrowserPool(headless=not args.headed)
await serial_pool.start()
try:
serial = await run_sequential(sites, args.target, serial_pool, args.timeout)
finally:
await serial_pool.close()
evidence = {
"target": args.target,
"sites": [site.__dict__ for site in sites],
"parallel": parallel,
"serial": serial,
"resource_audit": {
"parallel_contexts_created": parallel_pool.contexts_created,
"parallel_contexts_closed": parallel_pool.contexts_closed,
"serial_contexts_created": serial_pool.contexts_created if serial_pool else 0,
"serial_contexts_closed": serial_pool.contexts_closed if serial_pool else 0,
},
"measured_at": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
}
if serial and parallel["parallel_seconds"]:
evidence["measured_speedup"] = round(serial["seconds"] / parallel["parallel_seconds"], 3)
output = Path(args.output)
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(evidence, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(evidence, ensure_ascii=False, indent=2))
resources_ok = parallel_pool.contexts_created == parallel_pool.contexts_closed
single_cascade = parallel["winner"] is None or parallel["terminate_broadcasts"] == 1
acknowledgements_ok = not parallel["missing_loser_acks"]
print(f"资源清理:{'PASS' if resources_ok else 'FAIL'}; 单次级联广播:{'PASS' if single_cascade else 'FAIL'}")
if parallel["winner"] is None:
print(f"搜索完成:未找到目标教师;失败统计={parallel['failure_summary']}")
return 0 if resources_ok and single_cascade and acknowledgements_ok else 1
if __name__ == "__main__":
raise SystemExit(asyncio.run(main(parse_args())))