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,9 @@
"""Test import bootstrap for the agent-cost-analysis experiment."""
from pathlib import Path
import sys
EXPERIMENT_ROOT = Path(__file__).resolve().parents[1]
if str(EXPERIMENT_ROOT) not in sys.path:
sys.path.insert(0, str(EXPERIMENT_ROOT))
@@ -0,0 +1,44 @@
"""Tracer.chat must tolerate response.usage == None (OpenAI-compatible providers)."""
from types import SimpleNamespace
import config
from tracer import Tracer
class _FakeClient:
def __init__(self, usage):
self.chat = SimpleNamespace(
completions=SimpleNamespace(create=self._create)
)
self._usage = usage
def _create(self, **_kwargs):
return SimpleNamespace(usage=self._usage)
def test_chat_tolerates_null_usage():
tr = Tracer(_FakeClient(None), pricing=config.default_pricing())
resp = tr.chat(step="turn-1", tool="query_order", model="m", messages=[])
assert resp.usage is None
assert len(tr.spans) == 1
s = tr.spans[0]
assert s.prompt_tokens == 0
assert s.completion_tokens == 0
assert s.cost_usd == 0.0
assert s.latency_s >= 0.0
def test_chat_keeps_real_usage():
usage = SimpleNamespace(
prompt_tokens=100,
completion_tokens=20,
prompt_tokens_details=SimpleNamespace(cached_tokens=10),
)
tr = Tracer(_FakeClient(usage), pricing=config.default_pricing())
tr.chat(step="turn-1", tool="query_order", model="m", messages=[])
s = tr.spans[0]
assert s.prompt_tokens == 100
assert s.completion_tokens == 20
assert s.cached_tokens == 10
assert s.cost_usd > 0
@@ -0,0 +1,96 @@
"""
Regression tests for offline trace parsing (实验 7-9 成本分析).
Covers two crash classes found in --offline mode:
- Tracer.from_records: trace JSON with explicit null token fields -> int(None) TypeError
- demo.collect_offline: scenario dict missing the optional "spans" key -> KeyError
"""
import json
import pytest
import config
import demo
from tracer import Tracer
def _span(**overrides):
span = {
"step": "turn-1",
"tool": "query_order",
"kind": "llm",
"prompt_tokens": 100,
"cached_tokens": 10,
"completion_tokens": 12,
"tool_ctx_tokens": 50,
"latency_s": 1.2,
}
span.update(overrides)
return span
def test_from_records_tolerates_null_fields():
"""Explicit JSON nulls in numeric fields are coerced, not int(None) TypeError."""
records = [_span(prompt_tokens=None, cached_tokens=None,
completion_tokens=None, tool_ctx_tokens=None, latency_s=None)]
tr = Tracer.from_records(records, pricing=config.default_pricing())
s = tr.spans[0]
assert s.prompt_tokens == 0
assert s.cached_tokens == 0
assert s.completion_tokens == 0
assert s.tool_ctx_tokens == -1 # null tool_ctx 视为「未知」
assert s.latency_s == 0.0
def test_from_records_tolerates_missing_fields():
"""Minimal span dicts (only step/tool) still parse."""
tr = Tracer.from_records([{"step": "turn-1", "tool": "query_order"}],
pricing=config.default_pricing())
assert tr.spans[0].prompt_tokens == 0
assert tr.spans[0].tool_ctx_tokens == -1
def test_from_records_keeps_real_values():
"""Normal values pass through unchanged (no coercion side effects)."""
tr = Tracer.from_records([_span()], pricing=config.default_pricing())
s = tr.spans[0]
assert (s.prompt_tokens, s.cached_tokens, s.completion_tokens) == (100, 10, 12)
assert s.tool_ctx_tokens == 50
assert s.latency_s == 1.2
def test_from_records_keeps_zero_tool_ctx_tokens():
"""Explicit 0 means known-zero tool context, not unknown (-1)."""
tr = Tracer.from_records([_span(tool_ctx_tokens=0)],
pricing=config.default_pricing())
assert tr.spans[0].tool_ctx_tokens == 0
assert tr.total_tool_ctx_tokens() == 0
def _write_trace(tmp_path, scenarios):
path = tmp_path / "trace.json"
path.write_text(json.dumps({"model": "gpt-5.6-luna", "scenarios": scenarios}),
encoding="utf-8")
return str(path)
def test_collect_offline_skips_scenario_without_spans(tmp_path, capsys):
"""A scenario missing 'spans' is skipped with a warning, not a KeyError crash."""
trace = _write_trace(tmp_path, [
{"key": "naive", "name": "A naive"}, # no spans -> skip
{"key": "both", "name": "B both", "spans": [_span()]}, # valid
])
tracers = demo.collect_offline(["naive", "both"], config.default_pricing(), trace)
assert [k for k, _ in tracers] == ["both"]
assert "缺少 spans" in capsys.readouterr().err
def test_collect_offline_exits_when_no_usable_scenario(tmp_path):
"""When every selected scenario lacks spans, exit cleanly like the empty-trace path."""
trace = _write_trace(tmp_path, [{"key": "naive", "name": "A naive"}])
with pytest.raises(SystemExit):
demo.collect_offline(["naive"], config.default_pricing(), trace)
if __name__ == "__main__":
pytest.main([__file__, "-v"])