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
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:
@@ -0,0 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from supervise_campaigns import live_receipt_has_error
|
||||
|
||||
|
||||
def test_runner_creates_movement_directory_after_fork():
|
||||
source = Path(__file__).resolve().parents[1] / "run_campaign.py"
|
||||
text = source.read_text(encoding="utf-8")
|
||||
constructor = 'server = ReverieServer(status["current_sim"], sim_code)'
|
||||
mkdir = '(target_dir / "movement").mkdir(exist_ok=True)'
|
||||
assert constructor in text
|
||||
assert mkdir in text
|
||||
assert text.index(constructor) < text.index(mkdir)
|
||||
|
||||
|
||||
def test_packager_retains_action_arena_compatibility_receipts():
|
||||
source = Path(__file__).resolve().parents[1] / "package_evidence.py"
|
||||
text = source.read_text(encoding="utf-8")
|
||||
assert 'compatibility = output / "compatibility"' in text
|
||||
assert 'shutil.copytree(compatibility, destination / "compatibility")' in text
|
||||
|
||||
|
||||
def test_supervisor_detects_provider_error_in_live_checkpoint(tmp_path):
|
||||
status = tmp_path / "status" / "baseline.json"
|
||||
status.parent.mkdir()
|
||||
status.write_text(json.dumps({"completed_steps": 360}), encoding="utf-8")
|
||||
receipt = tmp_path / "receipts" / "baseline" / "steps_00360_00720.jsonl"
|
||||
receipt.parent.mkdir(parents=True)
|
||||
receipt.write_text(
|
||||
json.dumps({"success": True})
|
||||
+ "\n"
|
||||
+ json.dumps({"success": False})
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
assert live_receipt_has_error(tmp_path, "baseline", 17_280, 360) is True
|
||||
receipt.write_text(json.dumps({"success": True}) + "\n", encoding="utf-8")
|
||||
assert live_receipt_has_error(tmp_path, "baseline", 17_280, 360) is False
|
||||
@@ -0,0 +1,47 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from judge_plausibility import (
|
||||
DIMENSIONS,
|
||||
evenly_sample,
|
||||
load_canonical_judgments,
|
||||
parse_json_object,
|
||||
)
|
||||
|
||||
|
||||
def test_evenly_sample_keeps_endpoints():
|
||||
assert evenly_sample(list(range(10)), 4) == [0, 3, 6, 9]
|
||||
assert evenly_sample([1, 2], 4) == [1, 2]
|
||||
|
||||
|
||||
def test_parse_json_object_validates_all_scores():
|
||||
value = {
|
||||
"A": {dimension: 4 for dimension in DIMENSIONS},
|
||||
"B": {dimension: 3 for dimension in DIMENSIONS},
|
||||
"preferred": "A",
|
||||
"evidence": {},
|
||||
"confidence": "medium",
|
||||
}
|
||||
assert parse_json_object(f"```json\n{json.dumps(value)}\n```") == value
|
||||
value["A"][DIMENSIONS[0]] = 6
|
||||
with pytest.raises(ValueError, match="invalid A"):
|
||||
parse_json_object(json.dumps(value))
|
||||
|
||||
|
||||
def test_load_canonical_judgments_quarantines_failed_rows(tmp_path):
|
||||
receipts = tmp_path / "plausibility_judgments.jsonl"
|
||||
successful = {"persona": "A", "success": True}
|
||||
failed = {"persona": "B", "success": False, "error": {"type": "Timeout"}}
|
||||
receipts.write_text(
|
||||
json.dumps(successful) + "\n" + json.dumps(failed) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
assert load_canonical_judgments(receipts) == [successful]
|
||||
assert receipts.read_text(encoding="utf-8") == json.dumps(successful) + "\n"
|
||||
quarantined = list(tmp_path.glob("plausibility_judgments.failed-*.jsonl"))
|
||||
assert len(quarantined) == 1
|
||||
assert json.loads(quarantined[0].read_text(encoding="utf-8")) == failed
|
||||
@@ -0,0 +1,154 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from types import SimpleNamespace
|
||||
|
||||
from provider_adapter import ReceiptRecorder, install
|
||||
|
||||
|
||||
class Response(dict):
|
||||
def to_dict_recursive(self):
|
||||
return dict(self)
|
||||
|
||||
|
||||
def test_recorder_materializes_zero_call_checkpoint(tmp_path):
|
||||
receipt = tmp_path / "nested" / "empty.jsonl"
|
||||
recorder = ReceiptRecorder()
|
||||
recorder.set_path(receipt)
|
||||
assert receipt.is_file()
|
||||
assert receipt.read_bytes() == b""
|
||||
|
||||
|
||||
def test_adapter_overrides_legacy_models_and_compacts_embeddings(tmp_path, monkeypatch):
|
||||
calls = []
|
||||
|
||||
class ChatCompletion:
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
calls.append(("chat", kwargs))
|
||||
return Response(
|
||||
id="chat-id",
|
||||
model=kwargs["model"],
|
||||
choices=[{"message": {"content": "ok"}}],
|
||||
usage={"prompt_tokens": 2, "completion_tokens": 1, "total_tokens": 3},
|
||||
)
|
||||
|
||||
class Completion:
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
raise AssertionError("legacy completion endpoint should not be called")
|
||||
|
||||
class Embedding:
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
calls.append(("embedding", kwargs))
|
||||
return Response(
|
||||
id="embedding-id",
|
||||
model=kwargs["model"],
|
||||
data=[{"index": 0, "object": "embedding", "embedding": [0.1, 0.2]}],
|
||||
usage={"prompt_tokens": 1, "total_tokens": 1},
|
||||
)
|
||||
|
||||
fake_openai = SimpleNamespace(
|
||||
api_key=None,
|
||||
api_base=None,
|
||||
ChatCompletion=ChatCompletion,
|
||||
Completion=Completion,
|
||||
Embedding=Embedding,
|
||||
)
|
||||
monkeypatch.setitem(sys.modules, "openai", fake_openai)
|
||||
receipt = tmp_path / "calls.jsonl"
|
||||
install(
|
||||
api_key="test-key-not-retained",
|
||||
api_base="https://example.invalid/v1",
|
||||
chat_model="current-chat",
|
||||
embedding_model="current-embedding",
|
||||
receipt_path=receipt,
|
||||
)
|
||||
|
||||
chat = fake_openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=[])
|
||||
completion = fake_openai.Completion.create(model="text-davinci-003", prompt="hello")
|
||||
embedding = fake_openai.Embedding.create(model="text-embedding-ada-002", input=["x"])
|
||||
|
||||
assert chat["id"] == "chat-id"
|
||||
assert completion.choices[0].text == "ok"
|
||||
assert embedding["data"][0]["embedding"] == [0.1, 0.2]
|
||||
assert [call[1]["model"] for call in calls] == [
|
||||
"current-chat",
|
||||
"current-chat",
|
||||
"current-embedding",
|
||||
]
|
||||
assert all(call[1]["request_timeout"] == 90 for call in calls)
|
||||
rows = [json.loads(line) for line in receipt.read_text().splitlines()]
|
||||
assert len(rows) == 3
|
||||
assert all(row["success"] for row in rows)
|
||||
compact = rows[-1]["response"]["data"][0]
|
||||
assert compact["embedding_dimensions"] == 2
|
||||
assert "embedding" not in compact
|
||||
assert "test-key-not-retained" not in receipt.read_text()
|
||||
|
||||
|
||||
def test_adapter_retries_transient_connection_and_records_one_logical_call(
|
||||
tmp_path, monkeypatch
|
||||
):
|
||||
attempts = 0
|
||||
|
||||
class APIConnectionError(Exception):
|
||||
pass
|
||||
|
||||
class ChatCompletion:
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
nonlocal attempts
|
||||
attempts += 1
|
||||
if attempts == 1:
|
||||
raise APIConnectionError("connection closed")
|
||||
return Response(
|
||||
id="retry-success",
|
||||
model=kwargs["model"],
|
||||
choices=[{"message": {"content": "ok"}}],
|
||||
usage={"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
|
||||
)
|
||||
|
||||
class Completion:
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
raise AssertionError("legacy completion endpoint should not be called")
|
||||
|
||||
class Embedding:
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
raise AssertionError("embedding endpoint should not be called")
|
||||
|
||||
fake_openai = SimpleNamespace(
|
||||
api_key=None,
|
||||
api_base=None,
|
||||
ChatCompletion=ChatCompletion,
|
||||
Completion=Completion,
|
||||
Embedding=Embedding,
|
||||
)
|
||||
monkeypatch.setitem(sys.modules, "openai", fake_openai)
|
||||
monkeypatch.setattr("provider_adapter.time.sleep", lambda _: None)
|
||||
receipt = tmp_path / "retry.jsonl"
|
||||
install(
|
||||
api_key="test-key-not-retained",
|
||||
api_base="https://example.invalid/v1",
|
||||
chat_model="current-chat",
|
||||
embedding_model="current-embedding",
|
||||
receipt_path=receipt,
|
||||
)
|
||||
|
||||
response = fake_openai.ChatCompletion.create(model="legacy", messages=[])
|
||||
rows = [json.loads(line) for line in receipt.read_text().splitlines()]
|
||||
assert response["id"] == "retry-success"
|
||||
assert attempts == 2
|
||||
assert len(rows) == 1
|
||||
assert rows[0]["success"] is True
|
||||
assert rows[0]["transport_retries"] == [
|
||||
{
|
||||
"attempt": 1,
|
||||
"type": "APIConnectionError",
|
||||
"message": "connection closed",
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,193 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import gzip
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from action_arena_compat import normalize_action_arena
|
||||
from run_campaign import (
|
||||
CUSTOM_CURRENTLY,
|
||||
ValidatedZero,
|
||||
normalize_task_decomp_response,
|
||||
quarantine_artifact,
|
||||
receipt_summary,
|
||||
safe_task_decomp_generate,
|
||||
validated_receipt_summary,
|
||||
)
|
||||
|
||||
|
||||
def test_receipt_summary_counts_calls_usage_and_errors(tmp_path):
|
||||
path = tmp_path / "receipts.jsonl.gz"
|
||||
rows = [
|
||||
{
|
||||
"kind": "chat",
|
||||
"success": True,
|
||||
"latency_seconds": 1.25,
|
||||
"response": {"usage": {"prompt_tokens": 4, "completion_tokens": 2, "total_tokens": 6}},
|
||||
},
|
||||
{
|
||||
"kind": "embedding",
|
||||
"success": False,
|
||||
"latency_seconds": 0.5,
|
||||
"response": None,
|
||||
},
|
||||
]
|
||||
with gzip.open(path, "wt", encoding="utf-8") as handle:
|
||||
for row in rows:
|
||||
handle.write(json.dumps(row) + "\n")
|
||||
assert receipt_summary(path) == {
|
||||
"calls": 2,
|
||||
"by_kind": {"chat": 1, "embedding": 1},
|
||||
"errors": 1,
|
||||
"transport_retries": 0,
|
||||
"usage": {"prompt_tokens": 4, "completion_tokens": 2, "total_tokens": 6},
|
||||
"provider_latency_seconds": 1.75,
|
||||
}
|
||||
|
||||
|
||||
def test_custom_goal_is_specific_and_time_bounded():
|
||||
assert "climate-resilience workshop" in CUSTOM_CURRENTLY
|
||||
assert "February 14th, 2023" in CUSTOM_CURRENTLY
|
||||
assert "5pm to 7pm" in CUSTOM_CURRENTLY
|
||||
|
||||
|
||||
def test_validated_zero_is_numeric_but_not_the_false_sentinel():
|
||||
value = ValidatedZero()
|
||||
|
||||
assert value == 0
|
||||
assert value != False # noqa: E712 - verifies the upstream comparison exactly
|
||||
assert int(value) == 0
|
||||
assert json.dumps({"poignancy": value}) == '{"poignancy": 0}'
|
||||
|
||||
|
||||
def test_task_decomp_normalization_discards_prose_and_bounds_duration():
|
||||
prompt = "Describe subtasks in 5 min increments. (total duration in minutes 10):"
|
||||
response = """The prompt is contradictory.
|
||||
1) Wolfgang is resting. (duration in minutes: 5, minutes left: 5)
|
||||
2) Wolfgang is resting. (duration in minutes: 5, minutes left: 0)
|
||||
Here is an alternative.
|
||||
1) Wolfgang is studying. (duration in minutes: 10, minutes left: 0)"""
|
||||
|
||||
assert normalize_task_decomp_response(response, prompt) == (
|
||||
"1) Wolfgang is resting. (duration in minutes: 5, minutes left: 5)\n"
|
||||
"2) Wolfgang is resting. (duration in minutes: 5, minutes left: 0)"
|
||||
)
|
||||
|
||||
|
||||
def test_task_decomp_generation_cleans_malformed_response_without_requery():
|
||||
calls = 0
|
||||
prompt = "Describe subtasks in 5 min increments. (total duration in minutes 10):"
|
||||
response = """Commentary.
|
||||
1) Wolfgang is resting. (duration in minutes: 5, minutes left: 5)
|
||||
2) Wolfgang is resting. (duration in minutes: 5, minutes left: 0)"""
|
||||
|
||||
def request(prompt, parameters):
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
return response
|
||||
|
||||
def clean_up(value, prompt):
|
||||
if value.startswith("Commentary"):
|
||||
raise IndexError("missing duration")
|
||||
return value.splitlines()
|
||||
|
||||
result = safe_task_decomp_generate(
|
||||
request, prompt, {}, 5, ["asleep"], lambda value, prompt: value, clean_up
|
||||
)
|
||||
|
||||
assert len(result) == 2
|
||||
assert calls == 1
|
||||
|
||||
|
||||
def test_task_decomp_generation_raises_after_five_unparseable_responses():
|
||||
calls = 0
|
||||
|
||||
def request(prompt, parameters):
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
return "unstructured prose"
|
||||
|
||||
def clean_up(value, prompt):
|
||||
raise ValueError("invalid duration")
|
||||
|
||||
with pytest.raises(ValueError, match="invalid duration"):
|
||||
safe_task_decomp_generate(
|
||||
request,
|
||||
"Describe subtasks in 5 min increments. (total duration in minutes 60):",
|
||||
{},
|
||||
5,
|
||||
["asleep"],
|
||||
lambda value, prompt: value,
|
||||
clean_up,
|
||||
)
|
||||
|
||||
assert calls == 5
|
||||
|
||||
|
||||
def test_action_arena_strips_legacy_leading_brace():
|
||||
allowed = ["common room", "Tom and Jane Moreno's bedroom", "kitchen"]
|
||||
result = normalize_action_arena(
|
||||
"{Tom and Jane Moreno's bedroom}", allowed, "common room"
|
||||
)
|
||||
assert result.value == "Tom and Jane Moreno's bedroom"
|
||||
assert result.reason == "stripped_response_wrappers"
|
||||
assert result.fallback is False
|
||||
|
||||
|
||||
def test_action_arena_matches_case_insensitively_to_exact_allowed_value():
|
||||
allowed = ["common room", "Tom and Jane Moreno's bedroom", "kitchen"]
|
||||
result = normalize_action_arena(
|
||||
" {TOM AND JANE MORENO'S BEDROOM} ", allowed, "common room"
|
||||
)
|
||||
assert result.value == "Tom and Jane Moreno's bedroom"
|
||||
assert result.reason == "case_insensitive_exact_match"
|
||||
assert result.fallback is False
|
||||
|
||||
|
||||
def test_action_arena_invalid_output_falls_back_only_within_accessible_arenas():
|
||||
allowed = ["common room", "kitchen"]
|
||||
current_result = normalize_action_arena("private vault", allowed, "kitchen")
|
||||
assert current_result.value == "kitchen"
|
||||
assert current_result.value in allowed
|
||||
assert current_result.fallback is True
|
||||
|
||||
first_result = normalize_action_arena("private vault", allowed, "bedroom")
|
||||
assert first_result.value == "common room"
|
||||
assert first_result.value in allowed
|
||||
assert first_result.fallback is True
|
||||
|
||||
|
||||
def test_provider_error_checkpoint_is_quarantined_with_compatibility_receipt(tmp_path):
|
||||
receipt = tmp_path / "steps_00000_00360.jsonl.gz"
|
||||
with gzip.open(receipt, "wt", encoding="utf-8") as handle:
|
||||
handle.write(
|
||||
json.dumps(
|
||||
{
|
||||
"kind": "chat",
|
||||
"success": False,
|
||||
"latency_seconds": 1,
|
||||
"response": None,
|
||||
}
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
compatibility = tmp_path / "steps_00000_00360.jsonl"
|
||||
compatibility.write_text("{}\n", encoding="utf-8")
|
||||
|
||||
with pytest.raises(RuntimeError, match="provider errors make checkpoint"):
|
||||
validated_receipt_summary(receipt, compatibility)
|
||||
|
||||
assert not receipt.exists()
|
||||
assert not compatibility.exists()
|
||||
assert len(list(tmp_path.glob("steps_00000_00360.failed-*.jsonl.gz"))) == 1
|
||||
assert len(list(tmp_path.glob("steps_00000_00360.failed-*.jsonl"))) == 1
|
||||
|
||||
|
||||
def test_quarantine_preserves_non_receipt_suffix(tmp_path):
|
||||
artifact = tmp_path / "state.bin"
|
||||
artifact.write_bytes(b"state")
|
||||
target = quarantine_artifact(artifact)
|
||||
assert target is not None
|
||||
assert target.read_bytes() == b"state"
|
||||
assert target.name.startswith("state.bin.failed-")
|
||||
@@ -0,0 +1,41 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from validate_campaign import (
|
||||
canonical_provider_receipt,
|
||||
compatibility_correction_valid,
|
||||
positive_provider_usage,
|
||||
)
|
||||
|
||||
|
||||
def test_positive_provider_usage_ignores_nested_token_details():
|
||||
row = {
|
||||
"response": {
|
||||
"usage": {
|
||||
"prompt_tokens": 49,
|
||||
"prompt_tokens_details": {"cached_tokens": 0},
|
||||
"total_tokens": 69,
|
||||
}
|
||||
}
|
||||
}
|
||||
assert positive_provider_usage(row) is True
|
||||
|
||||
|
||||
def test_compatibility_correction_must_resolve_to_accessible_arena():
|
||||
row = {
|
||||
"kind": "action_arena_compatibility_correction",
|
||||
"raw_output": "{Tom and Jane Moreno's bedroom",
|
||||
"normalized_output": "Tom and Jane Moreno's bedroom",
|
||||
"accessible_arenas": ["common room", "Tom and Jane Moreno's bedroom"],
|
||||
"reason": "stripped_response_wrappers",
|
||||
"fallback": False,
|
||||
}
|
||||
assert compatibility_correction_valid(row) is True
|
||||
row["normalized_output"] = "private vault"
|
||||
assert compatibility_correction_valid(row) is False
|
||||
|
||||
|
||||
def test_failed_compressed_receipts_are_not_canonical(tmp_path):
|
||||
assert canonical_provider_receipt(tmp_path / "steps_00000_00360.jsonl.gz")
|
||||
assert not canonical_provider_receipt(
|
||||
tmp_path / "steps_00000_00360.failed-123.jsonl.gz"
|
||||
)
|
||||
Reference in New Issue
Block a user