CS146S 第二周笔记总结:The Anatomy of Coding Agents

Stanford University · Fall 2025 · 讲师:Mihail Eric 课程网站:themodernsoftware.dev


📅 课程安排

日期 主题 内容
Mon 9/29 Building a coding agent from scratch Agent 架构 + 实操构建
Fri 10/3 Building a custom MCP server MCP 协议 + 实操构建

第一讲:从零构建 Coding Agent(9/29)

1. Coding Agent 的核心架构

核心洞见:其实就是这么简单。

Coding Agent 的本质是一个循环:用户通过客户端(Cursor、Windsurf、Claude Code 等)与底层 LLM 交互,LLM 在循环中有时会发出 tool calls,客户端在 LLM 之外执行这些调用,然后将结果返回给 LLM。

用户输入 → LLM 推理 → [需要工具?] → 执行工具 → 返回结果 → LLM 继续推理 → 输出
                ↑                                              |
                └──────────────── 循环 ─────────────────────────┘

📚 延伸阅读: 第一周 Karpathy 视频中解释的 "Tool Use"(LLM 学会生成特殊 token 来触发工具调用)就是这个架构的底层原理。第一周课堂技术 ⑤(Tool Use)和 promptingguide.ai(阅读 3)的 ReAct 框架是同一个概念的不同抽象层。

2. 三种 Prompt 角色

角色 作用 示例
System Prompt 定义 LLM 的整体行为和指令 "你是一个高级 Python 开发者..."
User Prompt 用户的自定义请求 "修复这个 IndexError"
Assistant Prompt LLM 的响应 生成的代码或工具调用

📚 回顾第一周: 这三个角色在第一周第二讲已经介绍过。本周将它们放在 agent 循环的实际代码中使用。

3. 构建步骤

构建一个 coding agent 只需要以下步骤:

步骤 1:读取终端输入,持续追加到对话历史

conversation = []
while True:
    user_input = input("You: ")
    conversation.append({"role": "user", "content": user_input})

步骤 2:告诉 LLM 有哪些工具可用

LLM 会在适当的时机请求使用工具,你在 LLM 之外执行工具并返回响应。

tools = [
    {
        "name": "read_file",
        "description": "Read the contents of a file at the given path",
        "parameters": {"type": "object", "properties": {"path": {"type": "string"}}}
    },
    {
        "name": "list_dir",
        "description": "List files and directories at the given path",
        "parameters": {"type": "object", "properties": {"path": {"type": "string"}}}
    },
    {
        "name": "edit_file",
        "description": "Edit a file by replacing old content with new content",
        "parameters": {
            "type": "object",
            "properties": {
                "path": {"type": "string"},
                "old_content": {"type": "string"},
                "new_content": {"type": "string"}
            }
        }
    }
]

步骤 3:Agent 循环

while True:
    response = llm.chat(conversation, tools=tools)
    
    if response.has_tool_call:
        # LLM 请求调用工具
        tool_name = response.tool_call.name
        tool_args = response.tool_call.arguments
        
        # 在 LLM 外部执行工具
        result = execute_tool(tool_name, tool_args)
        
        # 将结果追加到对话,继续循环
        conversation.append({"role": "tool", "content": result})
    else:
        # LLM 给出最终回复
        print(response.content)
        break

📚 延伸阅读: 这个 while 循环就是 promptingguide.ai(第一周阅读 3)中 ReAct 框架的代码实现——Thought(LLM 推理)→ Action(工具调用)→ Observation(工具结果)→ 循环直到完成。

4. Claude Code 的"秘密武器"

课堂揭示了 Claude Code 在底层做的优化:

① 前置加载上下文 + 精准的微小 prompt

  • 不是给一个巨大的 prompt,而是多次用小而精准的 prompt 注入关键上下文

② 系统提醒无处不在

  • 在 system prompt、user prompt、tool calls、tool results 中都插入 <system-reminder> 标签
  • 目的:防止 LLM 在长对话中"漂移"(drift),忘记初始指令

💡 实战示例:

<!-- 在 tool result 中嵌入系统提醒 -->
<tool_result>
  File content: def calculate_total(items): ...
  <system-reminder>
  Remember: Always follow existing code patterns. 
  Do NOT introduce new dependencies without asking.
  </system-reminder>
</tool_result>

③ 命令前缀提取(Command Prefix Extraction)

  • 从用户输入中提取意图,减少歧义

④ 子 Agent 生成(Sub-agents)

  • 生成子 agent 来处理子任务
  • 避免单个 agent 上下文过载(context overloading)

📚 延伸阅读: 子 agent 的概念与第一周课堂 Best Practice #6 "任务分解" 和 promptingguide.ai 的 "Prompt Chaining" 直接对应——把复杂任务拆分给多个 agent,每个处理一个子任务。OpenAI Codex 文章(第一周阅读 5)中的 "Best-of-N" 也类似——并行生成多个方案。


第二讲:构建自定义 MCP 服务器(10/3)

1. 为什么需要 MCP?

核心问题: LLM 拥有庞大但静态的知识,只有重新训练才能更新。要构建完全自主的系统,需要可靠的方式向模型注入动态数据

需要动态数据的场景:今天的天气?谁是总统?比特币价格?Nike 最新广告的旁白是谁?

RAG 和 Tool Calling 是目前最好的答案。

📚 回顾第一周: 这正是第一周课堂技术 ⑥(RAG)和技术 ⑤(Tool Use)解决的问题。MCP 是这两个概念的工业级标准化实现

2. MCP 基础概念

Model Context Protocol(模型上下文协议):

一句话定义:让系统以跨集成通用的方式向 AI 模型提供上下文的开放协议。更直白地说——给 LLM 暴露工具的标准格式

历史背景: 在 2024 年 11 月 MCP 推出之前,每个 LLM 应用都需要为每个 API 写自定义的集成代码——就像上节课我们手动写的那个 agent,API 格式比较规范所以还行。但如果 API 文档差、格式不一致、认证复杂,那就是噩梦了。

3. 从 M×N 到 M+N 的飞跃

没有 MCP 的世界:

LLM App 1 ──┬── API 1    想象要为每个 API 写自定义连接器
LLM App 2 ──┼── API 2    M 个 LLM 应用 × N 个 API = M×N 个连接器
LLM App 3 ──┴── API 3    认证、错误处理、速率限制...全部重复实现

有 MCP 的世界:

LLM App 1 ─┐              ┌── MCP Server 1 (→ API 1)
LLM App 2 ─┤── MCP 协议 ──┤── MCP Server 2 (→ API 2)
LLM App 3 ─┘              └── MCP Server 3 (→ API 3)

M + N 个连接器,统一格式(JSON-RPC)

MCP 的核心优势:

  • 不需要为每个工具重新实现认证、错误处理、速率限制
  • 使用 JSON-RPC 强制统一输出格式
  • 从 Language Server Protocol(LSP)扩展而来,但支持主动的 agentic 工作流而不仅是被动的
  • 集成工具从 M × N → M + N 个连接器

4. MCP 架构详解

四个核心组件:

组件 角色 示例
Host 运行 AI 应用的宿主 Cursor, Claude Desktop
MCP Client 嵌入在 Host 中的库(每个 Server 一个有状态会话) SDK 客户端
MCP Server 工具前面的轻量级包装器 你自己写的 MCP 服务
Tool 可调用的函数 数据源、API 接口

完整交互流程:

1. MCP Client → MCP Server:  tools/list(你能做什么?)
2. MCP Server → MCP Client:  返回 JSON 描述每个工具(名称、摘要、JSON Schema)
3. Host 将工具 JSON 注入 LLM 的上下文
4. 用户提问 → LLM 生成结构化的 tool call
5. MCP Client → MCP Server:  tools/call(执行指定工具)
6. MCP Server 执行工具 → 返回结果
7. 对话恢复

💡 实战示例——MCP 交互流程:

用户:"帮我总结 Jack 发来的邮件"

1. [用户] → MCP Client:发送查询
2. [MCP Client] → MCP Server:tools/list(获取可用工具)
3. [MCP Server] → 返回:[{name: "search_emails", ...}, {name: "summarize", ...}]
4. [LLM] 决定调用 search_emails(from="Jack")
5. [MCP Client] → MCP Server:tools/call("search_emails", {from: "Jack"})
6. [MCP Server] → 执行 Email API 查询 → 返回邮件内容
7. [LLM] 基于返回的邮件内容生成摘要

传输层: MCP 提供 stdio(本地进程通信)和 SSE(Server-Sent Events,用于远程通信)两种传输方式。

5. MCP 的当前局限

课堂明确提出了三个关键限制:

① Agent 不擅长处理大量工具

  • Cursor 对工具数量有硬限制
  • 模型在工具过多时,tool calling 准确率显著下降

② API 会快速吃掉上下文窗口

  • 每个工具的描述和返回值都消耗 token
  • 大型 API 响应(如返回 100 条记录)可能直接撑爆上下文

③ 需要设计 AI-native 的 API,而非照搬传统 API

  • 传统 API 是为程序调用设计的
  • AI agent 需要的是更灵活、更精简的工具接口

📚 延伸阅读: "APIs don't make good MCP tools"(阅读 6)详细论证了为什么不能简单地把现有 API 转成 MCP 工具——工具太多会降低准确率、JSON 格式浪费 token、传统 API 没有利用 agent 的独特能力。第一周课堂提到的"上下文窗口限制"和"lost-in-the-middle 效应"在 MCP 场景下尤为严重。


🔗 两讲之间的联系

第一讲(构建 Agent)和第二讲(MCP)构成了完整的技术栈:

第一讲:Agent 的核心循环
  User → LLM → Tool Call → Execute → Result → LLM → Response
                   ↓
第二讲:MCP 标准化了这一层
  Tool Call → MCP Client → MCP Server → Tool Execution → Result

关键理解: MCP 没有改变 agent 的工作方式,它只是标准化了 tool call 和 tool execution 之间的通信协议。上节课手写的 read_filelist_diredit_file 工具,如果用 MCP 封装,就可以被任何 MCP 兼容的客户端使用。


📚 第二周推荐阅读

# 材料 与课堂的关系
1 MCP Introduction (Stytch) 第二讲 MCP 基础的完整技术展开,涵盖架构、OAuth 认证、代码示例
2 Sample MCP Server Implementations 第二讲"构建 MCP Server"的参考实现库
3 MCP Server Authentication (Cloudflare) MCP 安全认证的实践指南
4 MCP Server SDK 构建 MCP Server 的 TypeScript SDK
5 MCP Registry MCP 生态的中央注册系统,类比 npm registry
6 APIs don't make good MCP tools 第二讲局限性的深度反思——为什么不能简单转换 API

🛠️ 第二周作业:First Steps in the AI IDE