Files
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

152 lines
5.4 KiB
Python

import asyncio
from types import SimpleNamespace
import pytest
from agents import Coordinator
from agents import WorkerAgent
from message_bus import MessageBus
from sources import DEFAULT_SITES, load_sites
@pytest.mark.asyncio
async def test_near_simultaneous_hits_settle_and_broadcast_once():
bus = MessageBus(verbose=False)
coordinator = Coordinator(bus, "target")
await asyncio.gather(
coordinator._settle("agent-a", {"name": "target"}),
coordinator._settle("agent-b", {"name": "target"}),
)
assert coordinator.winner in {"agent-a", "agent-b"}
assert len(coordinator.duplicate_hits) == 1
assert sum(m.type == "terminate" for m in bus.history) == 1
def test_default_dataset_is_ten_real_http_university_pages():
sites = load_sites(None)
assert len(sites) == 10
assert all(s.url.startswith("https://") for s in sites)
assert all(not hasattr(s, "content") and not hasattr(s, "latency") for s in sites)
class StubWorker:
def __init__(self, worker_id, bus, events):
self.id = worker_id
self.site = SimpleNamespace(
name=f"source-{worker_id}", url=f"https://example.test/{worker_id}"
)
self.bus = bus
self.events = events
self.timeout = 0.1
self.sub = bus.subscribe(worker_id, types=["task_assigned", "terminate"])
async def run(self):
assigned = await self.sub.get()
assert assigned.type == "task_assigned"
for event_type, payload in self.events:
if event_type == "status_update":
payload = {"source": self.site.name, **payload}
else:
payload = {**payload, "source": self.site.name}
await self.bus.send(self.id, "coordinator", event_type, payload)
@pytest.mark.asyncio
async def test_all_not_found_has_no_cascade_and_returns_reason_and_status_aggregation():
bus = MessageBus(verbose=False)
coordinator = Coordinator(bus, "missing")
for worker_id in ("agent-a", "agent-b"):
coordinator.add_worker(StubWorker(worker_id, bus, [
("status_update", {"state": "执行中", "note": "reading"}),
("not_found", {"reason": "target absent"}),
("status_update", {"state": "已完成", "note": "未找到目标"}),
("resource_closed", {"browser_context_closed": True}),
]))
result = await coordinator.run()
assert result["outcome"] == "not_found"
assert result["winner"] is None
assert result["terminate_broadcasts"] == 0
assert result["not_found_reasons"] == {
"agent-a": "target absent", "agent-b": "target absent",
}
assert all(row["state"] == "已完成" for row in result["status_table"].values())
assert result["failure_summary"] == {"count": 0, "by_type": {}}
@pytest.mark.asyncio
async def test_worker_failure_is_isolated_and_summarized_while_peer_completes():
bus = MessageBus(verbose=False)
coordinator = Coordinator(bus, "missing")
coordinator.add_worker(StubWorker("bad", bus, [
("worker_error", {"error": "TimeoutError: deadline"}),
("status_update", {"state": "失败", "note": "timeout"}),
("resource_closed", {"browser_context_closed": True}),
]))
coordinator.add_worker(StubWorker("good", bus, [
("not_found", {"reason": "target absent"}),
("status_update", {"state": "已完成", "note": "peer completed"}),
("resource_closed", {"browser_context_closed": True}),
]))
result = await coordinator.run()
assert result["outcome"] == "not_found"
assert result["errors"] == {"bad": "TimeoutError: deadline"}
assert result["not_found_reasons"] == {"good": "target absent"}
assert result["failure_summary"] == {"count": 1, "by_type": {"TimeoutError": 1}}
assert result["status_table"]["good"]["state"] == "已完成"
@pytest.mark.asyncio
async def test_timeout_cancellation_closes_real_worker_context():
class BlockingPage:
async def goto(self, *args, **kwargs):
return None
def locator(self, _selector):
return self
async def inner_text(self, **kwargs):
await asyncio.Future()
class Context:
def __init__(self):
self.closed = False
async def new_page(self):
return BlockingPage()
async def close(self):
self.closed = True
class Pool:
def __init__(self):
self.context = Context()
self.closed = 0
async def new_context(self):
return self.context
async def mark_closed(self):
self.closed += 1
bus = MessageBus(verbose=False)
pool = Pool()
site = SimpleNamespace(name="blocking", url="https://example.test")
worker = WorkerAgent("agent-timeout", site, bus, "target", pool, timeout=0.01)
coordinator_sub = bus.subscribe("coordinator", types=None)
await bus.send("coordinator", worker.id, "task_assigned", {})
# The outer Manager deadline cancels the worker while body text is pending.
await asyncio.wait_for(worker.run(), timeout=0.05)
assert pool.context.closed is True
assert pool.closed == 1
messages = []
while not coordinator_sub.inbox.empty():
messages.append(coordinator_sub.inbox.get_nowait())
assert any(m.type == "worker_error" and "TimeoutError" in m.payload["error"] for m in messages)
assert any(m.type == "resource_closed" and m.payload["browser_context_closed"] for m in messages)