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_file、list_dir、edit_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
- 仓库:github.com/mihail911/modern-software-dev-assignments/tree/master/week2
- 实践在 AI IDE 中使用 coding agent
- 体验 agent 的 tool calling 循环