🏠 总目录📚 本教程 附录B · 代码与检查单
📑 本页目录(点开跳转)

附录 B · 代码与检查单

📌 需要什么 Ctrl+F 搜,复制粘贴。不要通读。


📦 环境

pip install anthropic          # 官方 SDK
pip install requests           # 第 17 节项目一
pip install pytest             # 第 17 节项目二
export ANTHROPIC_API_KEY=...   # Windows: setx ANTHROPIC_API_KEY "..."

零成本开发:先用第 1 节的 MockModel 把逻辑跑通,再换真模型。


🔁 最小 Agent 循环(第 1 节)

import json

def run_agent(model, tools, task, system, max_steps=10):
    messages = [{"role": "system", "content": system},
                {"role": "user", "content": task}]
    for step in range(max_steps):
        reply = model.chat(messages)
        messages.append({"role": "assistant", "content": reply})
        try:
            call = json.loads(reply); assert "tool" in call
        except (json.JSONDecodeError, AssertionError):
            return reply                          # 最终答案
        fn = tools.get(call["tool"])
        result = fn(**call["args"]) if fn else f"错误:无 {call['tool']}"
        messages.append({"role": "user", "content": f"工具结果:\n{result}"})
    return "达到最大步数"                          # 护栏

🔌 接官方 tool use API

import anthropic
client = anthropic.Anthropic()

TOOLS = [{
    "name": "search_logs",
    "description": "在服务日志中搜索匹配的行。返回匹配行及前后各2行上下文。",
    "input_schema": {
        "type": "object",
        "properties": {
            "pattern": {"type": "string", "description": "正则或关键词"},
            "max_lines": {"type": "integer", "description": "最多返回行数,默认50"},
        },
        "required": ["pattern"],
    },
}]

def agent_turn(messages):
    resp = client.messages.create(
        model="claude-sonnet-5", max_tokens=2048,
        tools=TOOLS, messages=messages)
    if resp.stop_reason == "tool_use":
        block = next(b for b in resp.content if b.type == "tool_use")
        result = dispatch(block.name, block.input)
        messages.append({"role": "assistant", "content": resp.content})
        messages.append({"role": "user", "content": [{
            "type": "tool_result", "tool_use_id": block.id, "content": result}]})
        return None                                # 继续循环
    return resp.content[0].text                    # 最终答案

🏷️ @tool 装饰器(第 18 节挑战 A)

import inspect

def tool(fn):
    sig = inspect.signature(fn)
    fn._schema = {
        "name": fn.__name__,
        "description": (fn.__doc__ or "").strip(),
        "input_schema": {
            "type": "object",
            "properties": {n: {"type": "string"} for n in sig.parameters},
            "required": [n for n, p in sig.parameters.items()
                         if p.default is inspect.Parameter.empty],
        },
    }
    return fn

@tool
def search_logs(pattern: str, max_lines: str = "50"):
    """在服务日志中搜索匹配的行,返回匹配行及上下文。"""
    ...

🗜️ 上下文压缩(第 4、18 节)

COMPACT_PROMPT = """总结以下 Agent 工作历史。必须显式保留:
1. 已完成的事
2. 当前状态
3. 下一步计划
4. 所有关键事实:文件路径、做过的决策、报错原文、数字

不要省略任何路径或报错——省略会导致重复犯错。

历史:
{history}"""

def compact(model, messages):
    history = "\n".join(f"{m['role']}: {m['content']}" for m in messages[1:])
    summary = model.chat([{"role": "user",
                           "content": COMPACT_PROMPT.format(history=history)}])
    return [messages[0],                                  # 保留 system
            {"role": "user", "content": f"【历史摘要】\n{summary}\n\n请继续。"}]

def est_tokens(messages):
    return sum(len(m["content"]) for m in messages) // 3   # 粗估,够用

🌿 子 Agent(第 4、13 节)

def spawn_subagent(model, task, tools, system, depth=0, max_depth=2):
    if depth >= max_depth:
        return "已达子 Agent 深度上限"                     # 防无限递归
    return run_agent(model, tools, task, system, max_steps=15)
    # 只有返回值进入主上下文,子 Agent 的完整历史被丢弃

👮 好的工具错误信息(第 7 节)

def calculator(expression: str) -> str:
    """计算数学表达式。只支持数字和 + - * / ** ( )"""
    ALLOWED = set("0123456789+-*/(). ")
    bad = set(expression) - ALLOWED
    if bad:
        return (f"错误:不支持字符 {sorted(bad)}。"
                f"只支持数字和 + - * / ** ( )。"
                f"示例:calculator(expression='3.14 * 2**2')")
    try:
        return str(eval(expression, {"__builtins__": {}}, {}))
    except Exception as e:
        return f"计算失败:{e}。请检查括号是否配对。"

🛡️ 环境层硬边界(第 15、20 节)

class Guard:
    """确定性规则。模型碰不到这里——这才叫硬边界"""
    def __init__(self):
        self.whitelist = {"user_alice": {"user_bob"}}   # 运行时不可变更

    def check_transfer(self, actor, to_u, amt):
        if to_u not in self.whitelist.get(actor, set()):
            return False, f"拒绝:{to_u} 不在白名单收款人中"
        if amt > 1000:
            return False, "需要用户二次确认"
        return True, "ok"

def transfer(actor, to_u, amt, guard, confirmed=False):
    ok, reason = guard.check_transfer(actor, to_u, amt)
    if not ok and not confirmed:
        return f"转账被拒:{reason}"      # 模型说破天也没用
    ...

⚠️ 反例:把「不许转给白名单外的人」写进系统提示词——那是软防御,不是硬边界

📏 评测循环(第 14、19 节)

from collections import defaultdict

def evaluate(agent, tasks, code_scorer, runs=1):
    results = []
    for t in tasks:
        for _ in range(runs):
            out = agent.run(t["input"])
            score, note = code_scorer(out, t["reference"])
            results.append({"id": t["id"], "type": t["type"],
                            "score": score, "note": note})
    return results

def summarize(results):
    """⭐ 必须分维度看,总分会骗人"""
    by = defaultdict(list)
    for r in results:
        by[r["type"]].append(r["score"])
    return {k: round(sum(v)/len(v), 3) for k, v in by.items()}

def pass_hat_k(results_per_task, k):
    """k 次全部成功的比例。面向用户的 Agent 该看这个"""
    return sum(all(s == 1.0 for s in scores[:k])
               for scores in results_per_task.values()) / len(results_per_task)

🧪 LLM 评委 rubric 模板

RUBRIC = """按以下维度给输出打分,每项 0-25 分:
- 准确性:结论与事实/参考解一致吗
- 完整性:要求的要素都覆盖了吗
- 边界处理:异常/模糊输入处理得当吗
- 恰当拒绝:该说"不知道"时说了吗(不该拒绝的别拒绝)

输出 JSON:{"准确":分,"完整":分,"边界":分,"拒绝":分,"理由":"一句话"}

参考解:{reference}
实际输出:{output}"""
# ⭐ 用前先校准:让它给参考解打分,应该接近满分,否则评委坏了

✅ 上生产检查单

【架构】
☐ 做过降级测试,有数据证明当前复杂度必要(第6节)
☐ 能说清为什么用工作流/Agent、为什么用这种多Agent模式
☐ 多Agent的15倍token溢价对得起它的价值

【上下文】
☐ 系统提示词砍到最小,删掉了为旧模型写的补丁
☐ 长任务有压缩机制,且摘要保留关键事实
☐ 工具超过20个上了Tool Search
☐ 大量阅读类任务派给了子Agent

【工具】
☐ 工具按意图分组,不是API的一比一镜像
☐ 返回里没有模型用不上的字段
☐ 错误信息是可行动的(告诉模型怎么改)
☐ 危险操作做了防呆

【验证】
☐ Agent有能自己跑的验证(测试/编译/lint)
☐ 验证者不是干活的那个Agent
☐ 主观标准转成了可回答的rubric条目

【评测】
☐ 至少20个来自真实失败的任务,每个有参考解
☐ 评分器评产出不评路径
☐ 读过转录,人工核对过评分器
☐ 明确该看pass@k还是pass^k
☐ 对小于3个百分点的差距保持怀疑

【安全】
☐ 做过威胁建模:Agent被完全控制最坏会怎样
☐ 至少一道环境层硬边界(代码里的if,不是提示词)
☐ 高危动作权限分离(能写≠能部署)
☐ 只在高危动作要确认,避免审批疲劳
☐ 外部内容当不可信数据处理

【运维】
☐ 有max_steps/超时/预算三道护栏,且优雅退出
☐ 状态可从出错点恢复,不用从头重跑
☐ 有结构化trace能回放决策路径
☐ 部署不打断运行中的Agent(彩虹部署)

【长期】
☐ 日历上有"模型升级后重审harness"的提醒
☐ 定期问:我可以停止做什么?

🐛 常见问题速查

症状 可能原因 处理
Agent 跳步骤、草草收尾 effort 不够 / 上下文焦虑 提 effort;检查是不是快到 token 上限
认真做了但结论错 模型能力不够 换更大模型
长会话后变笨 上下文腐烂 压缩或 /clear 重开
同一问题纠正三次还错 上下文被失败尝试污染 清空重来,用吸收教训的新提示词
工具老是用错参数 描述含糊 input_examples;参数改枚举
工具定义吃掉几万 token 工具太多 Tool Search 延迟加载
中间结果撑爆上下文 数据过模型 程序化调用/代码执行,数据留沙箱
宣布完成但根本没做完 没有验证闭环 给一个能跑的检查,要求跑到通过
自评总说自己做得好 自评正面偏差 生成与评估分离,独立上下文审查
多 Agent 重复劳动 委派指令含糊 结构化委派:目标+输出格式+边界
离线涨线上不涨 评测失真 查基础设施噪声、任务是否饱和、分维度看
加了分类器还是被注入 只有软防御 上环境层硬边界

👉 回到首页

打卡记录保存在你的浏览器里,首页能看到总进度