Claude Code 源码导读:Agent 设计详解

Claude Code 的 agent 到底是怎么被设计成一个能长时间工作、能分派子 agent、能记忆、能压缩上下文、也能可靠停下来的系统的? Claude Code 最值得研究的地方,不是它“能调用工具”,而是它把模型、工具、记忆、上下文、后台任务、hook 和权限系统编成了一个稳定的 agent harness。 一句话概括:Claude Code 的 agent 不是一个模型循环,而是三层循环叠在一起:主 query 循环负责推理和工具回灌;工具循环负责受控执行和并发;任务循环负责子 agent、后台 agent、记忆整理和长任务生命周期。 flowchart TD User["用户输入 / 队列消息"] --> Query["query.ts 主循环"] Query --> Model["流式模型调用"] Model -->|没有 tool_use| StopPath["停止路径stop hooks / token budget / completed"] Model -->|产生 tool_use| ToolLoop["工具执行循环"] ToolLoop --> Attach["附件注入记忆 / 变更文件 / 任务通知 / 技能"] Attach --> Query Query --> Compact["上下文治理tool budget / microcompact / autocompact / reactive compact"] Query --> Memory["记忆系统CLAUDE.md / AutoMem / Session Memory"] Query --> AgentTool["AgentTool"] AgentTool --> SubAgent["runAgent 子循环"] SubAgent --> LocalTask["LocalAgentTask前台 / 后台 / 可恢复"] LocalTask --> Attach 一、核心源码地图 先把关键文件放在桌面上: ...

June 21, 2026

3D球场项目 (四)

项目终于来到了落地环节。 在3D球场项目 (三)里,我们用一段离线 PBP 数据 + 一次 LLM 调用,把"导演 Agent"的雏形跑了起来。Demo 阶段的脚本是离线生成、整段 导入的;那时的 LLM 既负责"想象画面",又顺手把动作、镜头、音效、解说词都打包了。 但项目一旦从 Demo 推到线上,导演 Agent 立刻面对三件事: Opta 数据是按 Point 持续推过来的,不会一次性把一场比赛喂给大模型; 大模型 会失败、会延迟,一旦卡住,球员就只能停在 idle; 客户端会断流、会重连,需要一个"刚才那一拍发生了什么"的回放接口。 上一章末尾提到的三个坑——player 卡 idle、两人一直来回发球、长链断了客户端不知道 怎么补——本质都来自这三件事。线上版本的 livecast 服务(基于 tRPC-Go)做了一次完整的 工程化:导演 Agent 不再是单一 LLM,而是 确定性脚本层 + LLM 解说层 + 双重下发保险 的组合。本篇就讲这套组合怎么搭起来。 一、网球数据是怎么进来的 NBA 那边的 PBP 是 单条 投递:上游每发生一个篮球事件,就经 Kafka 推一条消息过来。 但网球数据天然是 按回合聚合 的——Opta 在一个 Point(一个回合)结束之后才会一次性 把这一拍涉及的所有事件下发,所以服务端也用了批量消费通道: Kafka topic: pbp_flow └─ FlowConsumer.BatchHandle(msgs []*sarama.ConsumerMessage) └─ livecastSvc.ProcessBatchPBPFlow(payloads [][]byte) 进入 ProcessBatchPBPFlow 之后,先按 mid(match id)做二级分组、过滤掉非网球的批次, 再交给真正的 pipeline。两道闸门决定一场比赛的事件能不能走 3D 流水线: ...

May 30, 2026

3D球场项目 (三)

在3D球场项目 (二)中,我们梳理了视频弹幕引擎的整体架构、引擎初始化流程。现在,3D 球场部分基本已经探索完毕,我们接下来就是要解决这个项目的数据驱动部分了。 我们现在研究一下下发数据的来源:Opta,它是全球顶级的体育数据公司,1996 年创立于英国伦敦,现隶属于 Stats Perform,是行业公认的足球数据金标准。 覆盖:20+ 运动、3900+ 赛事、每年 6 万 + 场比赛 规模:7.2PB 历史数据、年采集超 10 亿数据点 精度:单场足球2 万 + 事件点,事件识别准确率 99.8% 腾讯体育采买了 Opta WTA 网球比赛的数据,我们可以通过 Opta 的 API 来获取数据。 根据他们官网的介绍,一个 MA3 网球赛事数据中 typeId 对应的意义如下: 点击展开 / 折叠 Event TypeId 完整对照表 Event TypeId Event Name Description 20 STOP_GAME Indicates that the game has finished. 41 START_SET_1 Start 1st Set 42 START_SET_2 Start 2nd Set 43 START_SET_3 Start 3rd Set 44 START_SET_4 Start 4th Set 45 START_SET_5 Start 5th Set 46 STOP_SET Stop Set 47 T_WO1 w.o. Player 1 48 T_WO2 w.o. Player 2 128 SAFE Safe (No longer used) 129 DANGER Danger (Technical/connection issue at venue) 132 INJ_BREAK Game suspended - Player injured 133 PLAYERS_COMING_OUT Players coming out 141 GAME_ABOUT_TO_START Game about to start 149 GAME_SUSPENDED Game suspended 160 PLAYERS_WARMING_UP Players warming up 162 SHAKE_HANDS_WITH_REFEREE Players shake hands with referee 163 COIN_FLIPPING_FOR_FIRST_SERVER Coin flip to choose first server 164 PLAYERS_TALKING_TO_REFEREE Player(s) talking to the referee 165 REFEREE_CHECKING_POINTMARK Referee checking pointmark 166 BOTH_PLAYERS_SEATED Both players seated 167 PLAYER1_RECEIVING_TREATMENT Player 1 receiving treatment 168 PLAYER2_RECEIVING_TREATMENT Player 2 receiving treatment 169 PLAYERS_GOING_TO_COURT Players going back to court 170 NEXT_GAME_ABOUT_TO_START Next game is about to start 180 T_MORE_THAN_5 Rally, over five shots 181 T_MORE_THAN_10 Rally over ten shots 182 T_MORE_THAN_15 Rally over fifteen shots 187 T_POINT_UNDER_INVESTIGATION Point under investigation 188 T_START_TIE_BREAK Start tie break 191 T_POINT_STOPPED_BY_REF Point stopped by referee 197 UPDATE_SCORE_START Start score update 198 UPDATE_SCORE_FINISHED Score update finished 200 SIDE_CHANGE Side change 206 SHOT_CNT Shot count 216 STATISTIC_VERIFICATION Statistics confirmation 240 NEW_BALLS New balls 241 UMPIRE_ON_COURT UMPIRE ON COURT 242 WARMING_UP_VOLLEY Warming up - volley 243 WARMING_UP_SERVICE Warming up - service 244 ONE_MINUTE One minute 245 TIME Time 246 SHOT_DETAILS Shot details 256 CLS Cancel last sent event 257 CLR Clear events 258 GCC Game conditions changed. 261 SCORER Event details for %RELATED_EVENT% in the %MIN%. minute changed 262 BP Ball position event 264 ODD Odds event (deprecated) 266 TIME_CORRECTION_EVENT Correct timestamp for a missed event 276 START_GAME_CLOCK The (stopped) game clock is (re)started the game is running 277 STOP_GAME_CLOCK The game clock is stopped. Time will not change until Start Game Clock is sent again 278 ADJUST_GAME_CLOCK Game clock value is adjusted manually 279 CSTAT A statistical value is cleared and an additional event with the correct action is sent (e.g. an invalid Ace Home event is replaced by a Service Winner Home) 280 CONF_PERIOD_SCORE Period score confirmed 282 TIME_ADAPTION Time for event %RELATED_EVENT% was adapted by %SEC% seconds 285 PLAYER_DATA_CONFIRMED Player data for %RELATED_EVENT% in the %MIN% confirmed 513 SYS_MSG System Message 514 SCOUT_IN_STADION Scout in Stadium 515 CONNECTION_PROBLEMSSCOUT_OFFLINE Connection problems Scout offline 516 CONNECTION_PROBLEMS Connection problems 517 TRANSMISSION_ONLINE Transmission online 520 LINEUP_CHANGED Line-up changed 524 JERSEY_CHANGED Jersey colors updated 782 GAME CANCELLED Automatic “Game cancelled” event after first “cancellation” System message is sent 1152 T_SERVE1 Service Player 1 1153 T_1ST_SERVICE1 1st service Player 1 1154 T_2ND_SERVICE1 2nd service Player 1 1155 T_NA1 Net approach player 1 1156 T_W_FH1 Winner (forehand) Player 1 1157 T_W_BH1 Winner (backhand) Player 1 1158 T_FE_FH1 Forced error (forehand) Player 1 1159 T_FE_BH1 Forced error (backhand) Player 1 1160 T_UE_FH1 Unforced error (forehand) Player 1 1161 T_UE_BH1 Unforced error (backhand) Player 1 1162 T_HE1 Hawkeye Player 1 1163 T_WBR1 Warning by referee Player 1 1164 T_CBR1 Cautioned by referee Player 1 1165 T_SRV_NET1 Net Player 1 1166 T_SRV_OUT1 Out Player 1 1167 T_SRV_FF1 Foot fault Player 1 1168 T_SRV_DF1 Double fault Player 1 1169 T_SRV_A1 Ace Player 1 1170 T_SRV_SW1 Service winner Player 1 1171 T_SRV_RW_FH1 Return winner (forehand) Player 1 1172 T_SRV_IN1 Serve in Player 1 1173 T_POINT1 Point Player 1 1174 T_CONF1 Confirm Point Player 1 1175 T_GAME1 Game Player 1 1176 T_SET1 Set Player 1 1177 T_BL1 Baseline Player 1 1178 T_SRV_NR1 Net/Retake Player 1 1179 T_W1 Winner Player 1 1180 T_FE1 Forced error Player 1 1181 T_UE1 Unforced error Player 1 1182 T_SRV_RW_BH1 Return winner (backhand) Player 1 1183 T_V_FH1 Volley (forehand) Player 1 1184 T_V_BH1 Volley (backhand) Player 1 1185 T_BB1 Break ball Player 1 1186 T_B1 Break Player 1 1187 T_HES1 Hawk eye successful Player 1 1188 T_ITO1 Injury Timeout Player 1 1189 T_START_SRV1 Start Service Player 1 1190 T_WARNING1 1st offence - warning Player 1 1191 T_PENALTY_POINT1 2nd offence - penalty point Player 1 1192 T_PENALTY_GAME1 3rd offence - penalty game Player 1 1193 T_DISQUALIFICATION1 Disqualification Player 1 1194 T_UE_NET1 Unforced error net Player 1 1195 T_FE_NET1 Forced error net Player 1 1196 T_SUCCESSFUL_NET_APPROACH1 Successful net approach Player 1 1197 T_TIME_VIOLATION1 Time violation Player 1 1198 T_PENALTY_POINT1 T_UNSUCCESSFUL_NET_APPROACH1 Unsuccessful net approach Player 1 1199 T_AT_NET1 At net Player 1 1200 T_AT_BASELINE1 At baseline Player 1 1201 T_SET_BALL1 Set ball Player 1 1202 T_MATCH_BALL1 Match ball Player 1 2176 T_SERVE2 Service Player 2 2177 T_1ST_SERVICE2 1st service Player 2 2178 T_2ND_SERVICE2 2nd service Player 2 2179 T_NA2 Net approach player 2 2180 T_W_FH2 Winner (forehand) Player 2 2181 T_W_BH2 Winner (backhand) Player 2 2182 T_FE_FH2 Forced error (forehand) Player 2 2183 T_FE_BH2 Forced error (backhand) Player 2 2184 T_UE_FH2 Unforced error (forehand) Player 2 2185 T_UE_BH2 Unforced error (backhand) Player 2 2186 T_HE2 Hawkeye Player 2 2187 T_WBR2 Warning by referee Player 2 2188 T_CBR2 Cautioned by referee Player 2 2189 T_SRV_NET2 Net Player 2 2190 T_SRV_OUT2 Out Player 2 2191 T_SRV_FF2 Foot fault Player 2 2192 T_SRV_DF2 Double fault Player 2 2193 T_SRV_A2 Ace Player 2 2194 T_SRV_SW2 Service winner Player 2 2195 T_SRV_RW_FH2 Return winner (forehand) Player 2 2196 T_SRV_IN2 Serve in Player 2 2197 T_POINT2 Point Player 2 2198 T_CONF2 Confirm Point Player 2 2199 T_GAME2 Game Player 2 2200 T_SET2 Set Player 2 2201 T_BL2 Baseline Player 2 2202 T_SRV_NR2 Net/Retake Player 2 2203 T_W2 Winner Player 2 2204 T_FE2 Forced error Player 2 2205 T_UE2 Unforced error Player 2 2206 T_SRV_RW_BH2 Return winner (backhand) Player 2 2207 T_V_FH2 Volley (forehand) Player 2 2208 T_V_BH2 Volley (backhand) Player 2 2209 T_BB2 Break ball Player 2 2210 T_B2 Break Player 2 2211 T_HES2 Hawk eye successful Player 2 2212 T_ITO Injury Timeout Player 2 2213 T_START_SRV2 Start Service Player 2 2214 T_WARNING2 1st offence - warning Player 2 2215 T_PENALTY_POINT2 2nd offence - penalty point Player 2 2216 T_PENALTY_GAME2 3rd offence - penalty game Player 2 2217 T_DISQUALIFICATION2 Disqualification Player 2 2218 T_UE_NET2 Unforced error net Player 2 2219 T_FE_NET2 Forced error net Player 2 2220 T_SUCCESSFUL_NET_APPROACH2 Successful net approach Player 2 2221 T_TIME_VIOLATION2 Time violation Player 2 2222 T_AT_NET2 Unsuccessful net approach Player 2 2223 At net Player 2 2224 T_AT_BASELINE2 At baseline Player 2 2225 T_SET_BALL2 Set ball Player 2 2226 T_MATCH_BALL2 Match ball Player 2 PBP = Play-by-Play(逐回合 / 逐事件数据),就是把一场比赛里每一次事件都按时间线秒级记录下来的最细粒度原始数据流。口语里也叫 “逐帧数据 / 流水账数据”。网球赛事的数据,都是以这个形式下发的,一段数据可能下发的数据是这样的: ...

May 30, 2026

OpenClaw 源码导读(一):架构总览 — 为"单个主人"而设计的 AI Gateway

2025 年 11 月,Peter Steinberger(PSPDFKit 创始人、前著名 iOS 社区人物)把自己折腾出来的个人 AI 助手 Molty 开源为 OpenClaw。短短几个月,仓库吃到 35 万+ star、93 个 release、360 多个 contributor,核心代码量在 TypeScript/Swift/Kotlin/Go/Python 之间横跨 40 多万行。它和 Claude Code、Codex CLI 乍看都是"跑在本地的 CLI Agent",但设计哲学完全不同——Claude Code 是一个编程助手,OpenClaw 是一个长在你设备上的私人秘书:它挂在 WhatsApp/Telegram/iMessage/微信 的 IM 客户端后面,能自己发消息、自己开浏览器、自己调用 iOS/Android 上的摄像头和麦克风。 本文是 OpenClaw 源码导读系列的第一篇,目标是把整个项目的"地图"摊开。先讲清楚它要解决什么问题、在怎样的信任模型下运行,再把仓库 100 多个顶层模块拎出来分类,最后给出后续系列文章的导航。 一、它到底是什么 1. 一句话定义 OpenClaw 官方给自己的定位是 “Personal AI Assistant. Any OS. Any Platform. The lobster way.” 翻译过来就是:一个在你自己设备上运行、从你自己现有的聊天工具里和你对话的个人 AI 助手。 这个定位里藏着三个关键差异: Personal:它不是多租户 SaaS,也不是团队协作工具,而是单一主人的私人助手。整个信任模型就是为"只有一个 operator"优化的。 Any OS / Any Platform:Gateway 是 Node 进程,可以跑在 macOS/Linux/Windows(WSL2)、甚至 Fly.io/Docker/NAS 上;客户端包含 iOS/Android/macOS 原生 App,还有 Web Dashboard。 The lobster way:作者把它拟人化成一只太空龙虾 Molty,这是一个品牌/吉祥物层面的设计,但也反映了项目的"玩心"。 2. 和 Claude Code 对比:两个相似却完全不同的 CLI Agent 由于作者 Steipete 本身是 Claude Code 的重度用户和 Anthropic 的合作者,社区最常问的问题就是"和 Claude Code 有啥区别"。这里先画一张对比表: ...

May 2, 2026

OpenClaw 源码导读(二):Gateway 控制平面 — 一条 WebSocket 连上所有人

在系列第一篇里我们看到,OpenClaw 的核心隐喻是 “Gateway 是控制平面”。它不存转储消息、不排队、不持久化事件流,而是一个长在 127.0.0.1:18789 上的状态化 RPC 服务器,所有客户端(CLI、macOS.app、iOS/Android Node、WebChat 浏览器、Canvas iframe)都靠一条 WebSocket 接入。 这一篇拆开控制平面的五个层面: 进程入口:CLI 怎么从 openclaw gateway 走到 Node 进程常驻 WebSocket 协议:握手帧、RPC 帧、Event 帧、状态快照与版本号 鉴权矩阵:Token / Password / Device Token / Bootstrap Token / Tailscale Whois / Trusted Proxy Pairing:陌生客户端怎么合法加入(setup code、pairing store、allowFrom) Session 模型:Session Key 组成规则、多 Agent 路由、Session ID 模糊匹配 注:下文所有 src/ 路径都指 openclaw/openclaw 仓库根下。 一、CLI 到 Gateway 的六段路径 第一篇概览过 src/entry.ts 和 src/cli/run-main.ts,这里按调用序重新画一遍,把每个关键函数在 graph 上标出来: flowchart TD A0["openclaw gateway --port 18789"] --> A1 A1["entry.tsmain guard + process.title"] --> A2 A2["normalizeEnv + enableCompileCache()"] --> A3 A3["ensureCliRespawnReady()按需 spawn 子 Node"] --> A4 A4["parseCliContainerArgsparseCliProfileArgs"] --> A5 A5["tryHandleRootVersionFastPathtryHandleRootHelpFastPath"] -->|miss| A6 A6["runMainOrRootHelp → runCli(argv)"] --> B0 B0["run-main.tscontainer? container in progress"] --> B1 B1["loadCliDotEnv (~/.openclaw/.env)"] --> B2 B2["assertSupportedRuntime (Node≥22.16)"] --> B3 B3["tryRouteCli(fast-path 子命令路由)"] -->|miss| B4 B4["buildProgram() + commander 注册"] --> B5 B5["installUnhandledRejectionHandler"] --> B6 B6["registerPluginCliCommandsFromValidatedConfig (lazy)"] --> B7 B7["program.parseAsync"] --> C0 C0["gateway 命令匹配 → gatewayCommandAction"] --> C1 C1["lock 文件 + 端口 acquire"] --> C2 C2["HTTP/WS server 启动"] --> C3 C3["channels/plugins/cron/canvas-host 启动"] --> C4["Ready"] 1. 为什么要自己 spawn 自己 ensureCliRespawnReady() 是 src/entry.ts 里最容易被忽视但非常重要的段落: ...

May 2, 2026

OpenClaw 源码导读(四):Channels、Nodes 与扩展生态

前三篇我们看清了 OpenClaw 的三副骨架:架构全景、Gateway 控制平面、Agent Harness 执行管线。这一篇是压轴篇,我们看它的"血肉"——channels/、node-host/、canvas-host/、plugins/、skills/、sandbox/。OpenClaw 真正"好用"的感觉,是从这一层来的:一条消息从 WhatsApp 进来、触发 iOS 上的摄像头、在 Android 上渲染一张 Canvas、最后把结果发回 Slack 线程——整个过程中 Gateway 本身一行代码都不需要改。 本文涵盖 118 个 channel/provider extension、53 个 skill、iOS/macOS/Android node 客户端、A2UI Canvas 协议、以及 Docker sandbox 的整套隔离模型。 一、Channels:118 个 extension 是怎么"插"进来的 1. ChannelPlugin 接口 OpenClaw 所有 channel(微信、Telegram、Slack、iMessage、IRC、Discord……)本质上都是实现同一个接口 ChannelPlugin: export type ChannelPlugin<ResolvedAccount = any, Probe = unknown, Audit = unknown> = { id: ChannelId; meta: ChannelMeta; capabilities: ChannelCapabilities; defaults?: { queue?: { debounceMs?: number; }; }; reload?: { configPrefixes: string[]; noopPrefixes?: string[] }; setupWizard?: ChannelPluginSetupWizard; config: ChannelConfigAdapter<ResolvedAccount>; configSchema?: ChannelConfigSchema; setup?: ChannelSetupAdapter; pairing?: ChannelPairingAdapter; security?: ChannelSecurityAdapter<ResolvedAccount>; groups?: ChannelGroupAdapter; mentions?: ChannelMentionAdapter; outbound?: ChannelOutboundAdapter; status?: ChannelStatusAdapter<ResolvedAccount, Probe, Audit>; gatewayMethods?: string[]; gateway?: ChannelGatewayAdapter<ResolvedAccount>; auth?: ChannelAuthAdapter; approvalCapability?: ChannelApprovalCapability; elevated?: ChannelElevatedAdapter; commands?: ChannelCommandAdapter; lifecycle?: ChannelLifecycleAdapter; secrets?: ChannelSecretsAdapter; allowlist?: ChannelAllowlistAdapter; doctor?: ChannelDoctorAdapter; bindings?: ChannelConfiguredBindingProvider; conversationBindings?: ChannelConversationBindingSupport; streaming?: ChannelStreamingAdapter; threading?: ChannelThreadingAdapter; messaging?: ChannelMessagingAdapter; agentPrompt?: ChannelAgentPromptAdapter; directory?: ChannelDirectoryAdapter; resolver?: ChannelResolverAdapter; actions?: ChannelMessageActionAdapter; heartbeat?: ChannelHeartbeatAdapter; agentTools?: ChannelAgentToolFactory | ChannelAgentTool[]; }; 这个接口一共有 40 多个可选的 adapter,每个对应一种能力: ...

May 2, 2026

OpenClaw 源码导读(三):Agent Harness — 为"每一家 LLM 都能兜住"而生的执行管线

系列第一篇把 OpenClaw 的架构地图摊开,第二篇深入 Gateway 控制平面。这一篇,我们钻进整个项目的心脏——src/agents/,看 OpenClaw 怎么处理最复杂的一件事:当 Gateway 接到一条用户消息之后,怎么把它变成一轮真正的 LLM turn + Tool 循环。 这一层代码的体量和复杂度非常惊人: src/agents/pi-embedded-runner/run.ts 单文件 2160 行,是整个 agent 循环的驱动器。 src/agents/pi-embedded-runner/compact.ts 1148 行,专门处理上下文压缩。 整个 src/agents/ 目录有 830+ 个文件,光 anthropic-*.ts 就有近 20 个(为了 Messages API 的各种 edge case)。 同时支持 Anthropic、OpenAI、Google Gemini、Bedrock、Vertex、OpenRouter、Z.AI/GLM、MiniMax、Qwen、Ollama、Kilocode 等十多个 provider。 但这些"量"的背后,是一个只有一个接口的核心抽象:AgentHarness。本文就从这个抽象出发,一路看到 2160 行循环如何处置真实世界里十几类失败。 一、Harness 抽象:只有一个 runAttempt 1. 为什么不用 LangChain 风格的"Agent Framework" 读过 LangChain 或 CrewAI 的人第一眼会期望看到 AgentExecutor、AgentChain、Memory、Callback、Tool 这些类。OpenClaw 里一个都没有。 它的选择是:把整个 agent 循环当成一个黑盒,对外只暴露一个接口: export type AgentHarness = { id: string; label: string; pluginId?: string; supports(ctx: AgentHarnessSupportContext): AgentHarnessSupport; runAttempt(params: AgentHarnessAttemptParams): Promise<AgentHarnessAttemptResult>; compact?(params: AgentHarnessCompactParams): Promise<AgentHarnessCompactResult | undefined>; reset?(params: AgentHarnessResetParams): Promise<void> | void; dispose?(): Promise<void> | void; }; 五个方法,其中三个可选: ...

May 2, 2026

神经网络原理

想象你手写了一个数字"3",然后拿给计算机看——它是怎么"认出"这是3而不是8的?这个看似简单的问题,背后隐藏着整个深度学习的核心思想。让我们从最直觉的方式开始,一层一层揭开神经网络的面纱。 一、从像素到数字:问题的本质 1.1 手写数字识别 假设我们有一张28x28像素的灰度手写数字图片。每个像素是一个0到1之间的数字——0代表全黑,1代表全白。这意味着一张图片其实就是一个包含784个数字的列表。 图片 → [0.0, 0.0, 0.12, 0.85, 0.93, ..., 0.0] (784个数字) 我们的目标是设计一个函数,输入这784个数字,输出10个数字,分别代表这张图是0~9每个数字的概率: f(784个像素值) → [P(0), P(1), P(2), ..., P(9)] 比如输入一张手写的"3",我们期望输出类似: [0.01, 0.02, 0.05, 0.89, 0.01, 0.01, 0.00, 0.01, 0.00, 0.00] ^^^^ "3"的概率最高 这就是神经网络要解决的问题。但关键问题是:这个函数 f 长什么样? 1.2 为什么传统编程行不通 你可能会想:能不能写一堆if-else规则?比如"如果上方有一个圆弧,下方也有一个圆弧,那就是3"。 问题在于:人类写字的方式千变万化。同一个"3",有人写得圆润,有人写得尖锐,有人偏左,有人偏右。你几乎不可能用明确的规则覆盖所有情况。 我们需要的是一种从数据中自动学习规则的方法——这就是神经网络。 二、神经网络的结构 2.1 神经元:最基本的单元 让我们从最简单的单元开始。一个神经元本质上就是一个持有数字的容器。这个数字叫做激活值(activation),取值范围通常在0到1之间。 你可以把它想象成一个灯泡:0表示完全熄灭,1表示完全点亮,中间的值表示不同亮度。 2.2 层:神经元的组织方式 神经网络把神经元组织成层(layer): graph LR subgraph 输入层 [输入层 - 784个神经元] I1[像素1] I2[像素2] I3[...] I784[像素784] end subgraph 隐藏层1 [隐藏层1 - 16个神经元] H1_1[h1] H1_2[h2] H1_3[...] H1_16[h16] end subgraph 隐藏层2 [隐藏层2 - 16个神经元] H2_1[h1] H2_2[h2] H2_3[...] H2_16[h16] end subgraph 输出层 [输出层 - 10个神经元] O0[0] O1[1] O2[...] O9[9] end I1 --> H1_1 I1 --> H1_2 I784 --> H1_16 H1_1 --> H2_1 H1_16 --> H2_16 H2_1 --> O0 H2_16 --> O9 输入层:784个神经元,每个对应一个像素值 隐藏层:中间的层(这里用2层,每层16个神经元) 输出层:10个神经元,对应数字0~9 为什么叫"隐藏层"?因为在训练数据中,我们只知道输入(像素)和输出(标签),中间层在"学什么"我们并不直接指定——它们是被自动学出来的。 ...

May 2, 2026

反向传播算法

在上一篇文章中,我们构建了一个神经网络,了解了前向传播是如何产生预测的,也知道了损失函数可以衡量预测有多差。但我们遗留了最核心的问题:那13,002个参数到底该怎么调整,才能让网络变得更好? 这就是反向传播算法(Backpropagation)要回答的问题。它是神经网络学习的核心引擎,几乎所有现代深度学习的训练都建立在它之上。 一、梯度下降:从直觉开始 1.1 一个简单的类比 想象你站在一片浓雾中的山地上,看不到全貌,但你能感受到脚下地面的倾斜方向。你的目标是找到最低的山谷。最自然的策略是什么? 顺着脚下最陡的下坡方向走一小步。 这就是**梯度下降(Gradient Descent)**的核心思想。 1.2 数学表述 假设损失函数 $L$ 取决于所有参数 $\theta = (w_1, w_2, ..., w_n, b_1, b_2, ..., b_m)$。 梯度是一个向量,指向损失增长最快的方向: $$\nabla L = \left(\frac{\partial L}{\partial w_1}, \frac{\partial L}{\partial w_2}, \cdots, \frac{\partial L}{\partial b_m}\right)$$梯度的每个分量告诉我们:如果微微增大某个参数,损失会增加多少。 要降低损失,我们朝梯度的反方向迈步: $$\theta_{\text{new}} = \theta_{\text{old}} - \eta \cdot \nabla L$$其中 $\eta$ 是学习率(learning rate),控制每一步的步长。 1.3 学习率的微妙之处 学习率 下降行为 结果 太小 每步挪动极小,到达局部最小值后就停住了,无法跳出 收敛极慢,可能困在局部最小值 太大 一步跨过了最低点,又跨回来,反复来回 在最低点附近震荡,甚至发散 合适 每步大小适中,沿着曲面稳步走向谷底 稳步下降,逐渐逼近最优解 1.4 梯度下降的关键问题 现在整个问题归结为一件事:怎么高效地计算 $\nabla L$,即损失对每一个参数的偏导数? 你可能会想:对每个参数微微扰动一下,看看损失变化了多少。但如果有13,002个参数,每个参数都扰动一次,就需要做13,002次前向传播——太慢了。 ...

May 2, 2026

iOS中AI Chat实战

做一个能跑的AI聊天Demo只需要几十行代码:拼一个HTTP请求、把模型返回的文本显示出来即可。但要做一个达到生产级体验的AI Chat,就会面对一堆并不"AI"的工程问题:字怎么一个个"蹦"出来?Markdown怎么边流边渲染?模型想让前端弹一个卡片甚至一个表单,该怎么办?用户点击卡片里的按钮怎么回传给模型? 本文以iOS为实现目标,系统梳理AI Chat的工程架构,重点讲清楚两件事:流式输出与A2UI(Agent to UI),同时覆盖工具调用、取消、多模态、端侧推理等常见高级能力。 一、整体架构 一个完整的iOS AI Chat客户端,通常可以拆成如下几层: graph TB subgraph UI ["展示层"] LIST["消息列表UICollectionView / List"] CELL["消息Cell文本 / Markdown / A2UI Surface"] INPUT["输入区文本 / 图片 / 语音"] end subgraph VM ["ViewModel层"] STATE["会话状态机"] STREAM["流式拼装器"] TOOL["工具执行器"] end subgraph NET ["网络层"] SSE["SSE / HTTP 分块"] PARSER["协议解析OpenAI / Claude / A2UI"] end subgraph DATA ["数据层"] DB["消息持久化GRDB / WCDB / CoreData"] CTX["上下文裁剪Token预算"] end INPUT --> STATE STATE --> NET NET --> PARSER PARSER --> STREAM STREAM --> CELL STREAM --> TOOL TOOL --> NET STATE --> DB DB --> CTX CTX --> NET 几个关键特征: ...

May 2, 2026