# HYD 完整公开语料 姓名:HYD 简介:青岛工学院 2024 级数据科学与大数据技术专业在读。比起把知识停在笔记里,我更习惯把它推到能跑起来的那一步——从脚本、小工具,到真正能用的产品。 平时在写代码、读论文和做小工具之间来回切换,关注 AI Agent 与 AI Native 的产品形态;喜欢开源、音乐和动漫,想到什么就写进随笔。 --- # 本站的搜索和提问是怎么工作的? 作者:HYD 来源:https://hydblog.xyz/answers/live-ask/ 摘要:先匹配预生成的策展答案,没命中就在文章、随笔与问答里做全文检索;仍未命中则交给实时问答服务,检索公开内容并生成带来源的回答,单轮、有配额、不长期记忆。 **站内提问不依赖模型也能用。** 常见的几个问题都有预生成的策展答案,命中时直接呈现;没有命中的问题就在已发布内容里做全文检索,返回相关的策展问答与文章。 再没有命中的问题会交给实时问答服务:它检索本站公开内容,把检索到的片段交给模型生成有来源依据的回答,浏览器入口带人机验证与配额。回答只基于已发布的内容,单轮对话。 无论哪一层,回答都不会长期记忆,也不会代替作者做承诺;有疑问时以链接到的公开页面为准。 --- # 本站是什么? 作者:HYD 来源:https://hydblog.xyz/answers/what-is-this-site/ 摘要:HYD 的个人站点,记录 AI 全栈与 LLM 方向的学习过程,发布文章、随笔、项目和常见问题;用 Astro、Starlight 和 Refined-X 模板搭建,一份内容同时面向读者、搜索引擎和 AI Agent。 **这是 HYD 的个人站点。** 内容以学习过程为主:AI 全栈、LLM 与 Agent 相关的笔记和文章,加上做出来的项目和随手写下的随笔。 站点用 Astro 和 Starlight 搭建,基于 Refined-X 发布模板:文章、随笔、问答和项目都写在 Markdown 与 YAML 里,构建时除了生成给人读的页面,还会生成同一份内容的 Markdown 镜像、llms.txt 和 JSON 接口,方便搜索引擎和 AI Agent 直接读取。 想了解人,看[关于页面](/about/);想了解做过什么,看[项目页](/projects/);想找具体答案,用站内提问,或者直接翻[常见问题](/answers/)。 --- # 如何与 HYD 合作? 作者:HYD 来源:https://hydblog.xyz/answers/collaborate/ 摘要:发邮件到 HYDhyd0505@gmail.com,或者在 GitHub 上找 HYDtomako 开 Issue、提 PR;聊 AI Agent 实践、开源协作和站点发布都欢迎。 **最快的方式是发邮件:HYDhyd0505@gmail.com。** 也可以在 GitHub 上找到 HYDtomako 开 Issue、提 PR,QQ 是 2804956879。 适合一起聊的方向:AI Agent 的工程实践,比如工具调用、长期记忆、多 Agent 协作;开源项目上的分工与代码评审;以及个人站点和内容发布。做过的东西都列在[项目页](/projects/)上,觉得哪个能一起做,直接说就行。 目前还在读书,回复可能不快,但每封都会认真看。 --- # 以前的文章发在哪里? 作者:HYD 来源:https://hydblog.xyz/answers/where-articles-were/ 摘要:2025 年夏天起写在 CSDN(blog.csdn.net/2401_87876529),前期是 Python 与数据分析,后期转向 Transformer、LLM 与 Agent;2026 年 9 月起新内容写在这里,旧文保留不再更新。 **2025 年夏天到 2026 年 9 月,文章都写在 CSDN 上,前后二十来篇:** https://blog.csdn.net/2401_87876529 前半段补基础:Python 的数据结构、Numpy 和 Pandas,接着是数据分析与可视化,中间夹着几个比赛和小项目;后半段转向 AI,从 Transformer 写到工具调用、Agent Loop、MultiAgent、Claude Code,还有提示词注入防御和 Agent checkpoint 这些工程问题。 现在新内容都写在这个站点上,CSDN 的旧文保留但不再更新。搬过来之后顺手把旧笔记重排了一遍,[Transformer 学习笔记](/2026/04/04/transformer-learning-notes/)就是重新捋过顺序的那一篇;搬家这件事本身写在随笔[从 CSDN 到自己的站点](/notes/from-csdn/)里。 --- # 有哪些开源项目? 作者:HYD 来源:https://hydblog.xyz/answers/open-source-projects/ 摘要:目前六个:EnCoder、QQbot、Minimind-notes、Simple_CNN、interview-skill、homework-web_blog,集中在 Agent 工具和深度学习复现上。 **公开的项目集中在两件事上:Agent 工具,和深度学习的基础复现。** 仓库都在 GitHub:https://github.com/HYDtomako **EnCoder** 是 CoreCoder 的扩展版,在最小 Agent 实现上补了定时调度、长期记忆、Agent 团队与任务管理。**QQbot** 用 pi 当大脑、NapCatQQ 做连接,把知识问答、课表查询、群管理、定时任务、长期记忆和模型热切换放进一个机器人里。 **Minimind-notes** 梳理大模型结构与训练方法,**Simple_CNN** 从卷积、池化一路复现到完整训练流程。另外两个偏练习:**interview-skill** 把 JD 和简历放在一起做针对性面试练习,**homework-web_blog** 是 Python Web 课的期末博客站点。 每个项目的状态、标签和仓库地址都在[项目页](/projects/)。 --- # 站点记录了哪些 AI 实践? 作者:HYD 来源:https://hydblog.xyz/answers/ai-practice/ 摘要:分三层:Transformer 与深度学习的学习笔记,Agent 工程机制的源码阅读笔记(Agent Loop、工具、上下文压缩、多 Agent),以及把这些机制用起来的小工具,比如 EnCoder 和 QQbot。 **AI 是这里的主线,写下来的内容大致分三层,从机制到能跑起来的东西。** 第一层是基础机制:[Transformer 学习笔记](/2026/04/04/transformer-learning-notes/)把 Token、位置编码、Q/K/V 注意力、多头注意力和层归一化按数据流拆开讲;Simple_CNN 记录了从卷积、池化到完整训练的复现过程。 第二层是 Agent 工程:[三个 Claude Code 实现](/2026/08/23/agent-engineering-from-claude-code/)放在一起读,Agent Loop、工具定义、上下文注入与压缩、并行工具与子 Agent、多 Agent 协作和安全边界串成了一条线;Minimind-notes 则是大模型结构与训练方法的学习记录。 第三层是能跑起来的东西:EnCoder 在最小 Agent 实现上补了定时调度、长期记忆、Agent 团队与任务管理;QQbot 用 pi 当大脑、NapCatQQ 做连接,覆盖知识问答、课表查询、群管理、长期记忆和模型热切换。项目清单都在[项目页](/projects/)。 --- # 作者是谁? 作者:HYD 来源:https://hydblog.xyz/answers/who-is-author/ 摘要:HYD 是青岛工学院 2024 级数据科学与大数据技术专业的学生,方向是 AI Agent 与 AI Native 产品,习惯把学到的东西推到能跑起来的那一步,再开源出来。 **HYD** 是青岛工学院 2024 级数据科学与大数据技术专业的学生,方向是 AI Agent 与 AI Native 产品,平时在写代码、读论文和做小工具之间来回切换。 比起把知识停在笔记里,他更习惯把它推到能跑起来的那一步:源码读到能复述,想法做成能用的工具,再回到站点上复盘。公开的项目在 GitHub(https://github.com/HYDtomako),从 Agent 应用到深度学习的基础复现都有。 代码之外的那一面写在[关于页面](/about/)里:动漫、音乐,还有出门随手拍的风景。 --- # Agent 或 AI 怎么读取本站内容? 作者:HYD 来源:https://hydblog.xyz/answers/agent-ready/ 摘要:从同一份内容生成 llms.txt、llms-full.txt、Markdown 镜像、JSON 接口和 OpenAPI 描述,Agent 不用解析页面布局就能拿到可引用的材料。 **同一份内容,既有给人看的页面,也有给机器读的接口。** 入口目录是 [llms.txt](/llms.txt),完整语料在 [llms-full.txt](/llms-full.txt)。 文章、随笔和问答都有对应的 Markdown 镜像与 JSON 接口,例如 /api/articles.json、/api/profile.json、/api/search-index.json;接口字段说明在 [openapi.json](/openapi.json),站点地图在 /sitemap-index.xml。Markdown 镜像就是发布时用的原文,引用时不会和页面渲染产生出入。 站内提问走三层:策展答案、全文检索,以及实时 AI 问答(见「本站的搜索和提问是怎么工作的?」)。实时回答由同源的 Worker 提供——它先检索本站公开内容,再生成带来源引用的答案,保持单轮、不做长期记忆。 --- # 三个 Claude Code 实现:一份 Agent 工程笔记 作者:HYD 来源:https://hydblog.xyz/2026/08/23/agent-engineering-from-claude-code/ 发布日期:2026-08-23T13:00:00.000Z 更新时间:2026-08-23T13:00:00.000Z 摘要:一份来自三个实现的 Agent 工程笔记。CoreCoder 是 Python 手写的最小实现,Claude Code 是生产实现,learn-claude-code 是教学实现。内容覆盖 Agent Loop 的流转条件与中断修复、工具定义与文件编辑策略、上下文注入与三档压缩、QueryEngine 的长期状态、并行工具与子 Agent、Hook 与 Task 扩展点、多 Agent 协作、记忆 / MCP / 后台任务 / 定时任务,以及路径与命令的安全防线。 读 agent 源码容易陷在两个地方:要么被 CLI 那一层交互细节带跑,要么一直停在"LLM 会调用工具"这个结论上。把三个实现摆在一起看反而清爽——哪些零件是省不掉的,哪些只是实现风格,一比就出来了。 - **[CoreCoder](https://github.com/he-yufeng/CoreCoder)**:Python 手写的最小 agent。loop、tool、context 都从零写一遍,能看清骨架。 - **[Claude Code](https://www.xuanyuancode.com/learn-claude-code)**:生产实现,源码拆解在 xuanyuancode 上有完整连载。值得看的是启动装配、长期存在的会话对象、上下文工程,以及 BashTool 这类工具是怎么被"治理"起来的。 - **[learn-claude-code](https://github.com/shareAI-lab/learn-claude-code)**:教学实现。hook、task、多 agent、memory、MCP 被拆成小块逐个讲。 下面按机制整理,不按项目。 ## Agent Loop:整个流程的心脏 CoreCoder 用一个 `for` 循环撑起全部流程,`max_rounds = 50`,**流转条件只有一个:这一轮还有没有 tool call**。 工具调用遵循 OpenAI 的 function calling 范式:assistant 消息里带着 `tool_call`,每个调用有 id,执行结果按同样的 id 回到历史里,配对关系是 `tool → tool_call_id → tool_content`。骨架就这么点东西,真正麻烦的是循环里两个"不干净"的时刻。 **第一,工具报错要能归因。** 同一个 error,得先分清是参数传进 tool 函数时就错了,还是 tool 执行过程中才错的。前者是模型的锅(schema 或参数不对),后者是环境的锅(命令、路径、权限)。不分开,反馈给模型的提示就是错的,下一轮还会错。 **第二,中断之后历史必须重新合法。** 用户按下 Ctrl+C 时,如果恰好截断在"模型已经发出 tool_call、tool 还没执行完"的缝隙里,历史里就出现了一个没有结果的 tool_call。对模型来说这是非法状态,而且会影响整个会话。CoreCoder 的做法是给这个缺口补上 `content: ["interrupt"]`,先把历史修回合法,再把异常往上抛交给上层处理。 这套"补一条合法内容"的思路后面还会再出现一次——上下文压缩的时候。 ## 工具层:定义、编辑与执行 工具的定义很直白:继承 `Tool` 基类,持有 `name`、`description`、`parameters`,实现 `execute()`,再由 `schema()` 拼成 OpenAI function calling 的格式,注册进 `tool_list`。 真正要下功夫的是编辑文件的工具,因为它是 agent 最常用的那只手。三条路都试过: 1. **让模型整体重写文件**——太浪费 token。 2. **让模型给行号去定位**——模型对行号很模糊,而且行数改动不能有偏差。 3. **让模型生成 unified diff**(带 `@@ -42,7 +42,8 @@` 那种行号头)——错误率高得让人头疼,那套上下文行数和偏移量它算不准。 于是 CoreCoder 选了第四条路:**定位唯一修改内容**。要求待替换的片段在文件里恰好出现一次;如果出现多次,就让它多带一段上下文,直到唯一;改完之后,tool 把这次修改的 diff 返回给模型,让模型自己判断改对没有。 Bash 这边是另一套纪律:每个命令执行前先过一遍 `rm` 之类的危险命令检测表;`cd a && cd b` 这种链式命令要按顺序理解,因为 `b` 是相对 `a` 的。 Claude Code 的 BashTool 则把这件事做成了系统能力。它的关键不在于"能执行命令",而在于把命令执行构建成了一个拥有**语义分类、权限约束、任务管理和 UI 呈现**的正式能力: - **它提供了验证代码所需的执行能力。** 没有 BashTool,Agent 只能做静态修改;有了它才能跑测试(`pytest`、`cargo test`)、看构建(`npm run build`)、调项目脚本(lint、格式化),以及和 Git、包管理器这些工具链打通,拿到完整的项目状态。从"看懂代码"到"验证代码",中间隔着的就是这一层。 - **它尝试理解命令的语义,而不是黑盒执行。** 区分一条命令是搜索、读取还是其他行为,而不是一股脑扔给 Shell。好处是三重:UI 呈现更合理(按命令类型给不同界面)、安全策略更精细(只读命令可以更放心地自动放行)、结果处理更好(搜索和读取的结果可以折叠显示)。 - **它有严格的安全控制。** 一条命令进来,它会额外关心:是不是只读操作、操作路径是否合法、是否该放进沙箱执行、是否需要请求用户权限确认、是否适合放到后台跑。 补一句:CLI 里 agent 的实际输出和 UI 展示是两回事。展示层可以裁剪、折叠、上色,但那是展示层的事。 ## 上下文工程:注入什么,砍掉什么 上下文分两件事:往里放什么,和往里砍什么。 **注入。** `CLAUDE.md` 这类文件是核心——它是对仓库信息、项目分支、记忆信息等做过滤和去重之后得到的。它把项目经验从"靠人临时口述"变成了"可注入的系统知识",这直接影响三件事:输出风格更稳定、修改更符合仓库约定、不容易反复犯同样的错误。除了它,git 状态也会一起进上下文。 **压缩。** CoreCoder 准备了三档保障,一档不够用下一档: 1. `tool_call` 的返回值只在当时那个任务上有用,后续任务里没什么可保留的,所以**保留首尾、删掉中间**。 2. 把历史消息压成摘要,但**保留结构化内容**,而不是变成一坨背景文本。 3. 如果前两档还不够,就只保留最前面几轮加摘要,**大幅删除**。 这里有个坑,和开头那个中断问题同源:**如果切分点恰好落在 tool_call 上,`tool_id` 和它的 `tool_content` 就被劈开了**,历史又变得非法。所以删的时候要移动分割边界,不能从中间一刀切。 Claude Code 的顺序是同一个思路的延伸:**优先保留结构化上下文,对低价值内容做压缩和裁剪,不急着把它变成一段摘要**;不够就投影一个折叠后的上下文视图、让模型自主压缩;再不够才是兜底压缩。摘要不是第一步,是最后一步。 ## 会话与状态:为什么需要 QueryEngine `main.tsx` 是整个应用的"装配根",它要抢在 REPL 起来之前把一切都准备好: 1. 用 profile checkpoint 计算各阶段的初始化耗时,抢启动时间(加载配置、API)。 2. 拉取 context、command、skill、tool,初始化外部能力(MCP、LSP),并搞清当前 session 的状态:是不是交互模式、有没有远程会话或桥接模式、要不要恢复旧会话、模型 / 权限 / 提示风格 / 工作目录分别是什么。 3. 这些都在 REPL 启动前完成——用户看到界面的时候,能用的东西已经就位。 `QueryEngine` 是一个**围绕会话长期存在的对象**,这就是它能跨多轮次的原因。它长期持有的状态,也正好就是"一次会话结束之后还留下来"的东西: - 消息历史 - 已知的权限拒绝信息 - 文件读取缓存 - usage 统计 - 某些 memory / skill 的发现状态 `submitMessage()` 本质上就是"启动一轮 agent run":接收用户输入 → 设置目录和 session → 过滤 tool → 配置 prompt 和上下文 → 开启 query 与模型交互 → 工具调用输出并追加 history → 统计 usage 和状态。串起来就是这些,没有别的魔法。 **为什么它有反思和纠错能力?** 因为不是简单地"模型调用工具",而是**工具结果的回流**:模型根据 system_prompt / history 做决策 → 决策里可能包含 tools → tools 走权限判断 → 模型调用 tool 拿到结果 → 结果注入 history → 进入下一轮。模型每轮看到的都是上一轮的真实结果,纠错能力是从这个回路里长出来的,不是额外加的功能。 ## 并发:并行工具与子 Agent Claude Code 在解析用户输入的时候,tools 已经在执行了,不必等整段响应结束,所以回复快;CoreCoder 则是老老实实按顺序执行。差别不在"谁更先进",而在要不要为这点延迟付出复杂度。 CoreCoder 的取舍是:一个任务如果有多个 tool 响应,单个就直接跑,多个就交给 `_exec_tools_parallel`,也就是一个线程池。但并行不是肆无忌惮的—— > 线程 1 正在保存 A 目录,线程 2 并行保存 B,回头再看 A 的内容就被覆盖了。 所以线程之间必须隔离。Python 里有现成的工具:`threading.local()`,它给每个线程一份独立的副本,线程之间互不可见。 **子 Agent 的处理更值得看。** CoreCoder 里没有显式定义"子 agent"这个实体,而是通过 `AgentTool` 定义:AgentTool 指回主 agent,子 agent 与主 agent 共享资源;同时给子 agent 单独开一份 context,让主 agent 的窗口保持干净——主 agent 不需要知道子 agent 中间干了什么,只要它的执行结果。唯一的约束是:**子 agent 不能再调用 AgentTool**,否则就是无限套娃。 learn-claude-code 把这条边界画得更清楚: ![子 Agent 的上下文边界:父 Agent 只收到一段任务描述,子 Agent 只回传最终文本](/asset/subagent-architecture.png) - 子 Agent 拿到的是**全新的 `messages[]`**,`fresh`,不继承父对话。 - 它跑自己的 `while` 循环,最多 30 轮,工具表是 bash / read / write / edit / glob 这些基础工具,**没有 task**——只允许一层委派。 - 它内部的工具调用和结果不复制回父 `messages[]`,只有最终文本通过类似 `extract_text()` 的方式,作为父 Agent 的 `tool_result` 回去。 来回一算:父 Agent 的窗口里只多了"一小段任务描述"和"一段结论"。 ## 扩展点:Hook 与任务系统 **Hook 写在 loop 外面。** 如果扩展逻辑一行行塞进循环,它很快就会变得认不出来: ```python def agent_loop(messages): while True: # ... LLM call ... for block in response.content: if block.type != "tool_use": continue log_to_file(block) # 加一行 check_permission(block) # 加一行 notify_slack(block) # 又加一行 output = execute(block) auto_git_add(block) # 再加一行 # ... 很快循环就认不出来了 ``` 于是有了 HOOK 表:`UserPromptSubmit`(上下文注入)、`PreToolUse`(权限、日志)、`PostToolUse`(大输出处理)、`Stop`…… 覆盖三类诉求——对 tool 的权限(用户许可、高危命令)、对 tool 工作日志的打印、对 tool 输出的判断(是不是大文件)。 关键在于:**执行顺序本来可以一样,Hook 的价值不是改变顺序,而是把具体扩展逻辑从 Agent Loop 里解耦出来**,让扩展可以独立注册、增加、删除,而不用动核心 loop。 **Todo 和 Task 是两件事。** 如果说 `todo_write` 是告诉你 agent"要做什么",`task_system` 就是告诉你**任务之间的依赖**。 Todo 是一个列表:它让 Agent 具备规划能力,但不是所有的任务都需要 todo。状态用 `[ ] pending`、`[>] in progress`、`[x] completed` 表示。 Task 是持久化文件。像"创建 db → 创建表 → 测试"这种任务,整体有先后依赖: ```python # 创建任务依赖,通过 blockedBy 去连接 # 一个任务一个文件,跨会话保存 / 可恢复 @dataclass class Task: id: str subject: str description: str status: str # pending | in_progress | completed owner: str | None # 负责当前任务的 Agent blockedBy: list[str] # 依赖的任务 ID 列表 ``` 规则有两条:一个 task 一个文件(`.task/.json`);**只有当 `blockedBy` 里的任务(first 除外)全部 `status=complete`,下一个才能开始**。状态从 `pending` 走到 `in_progress`,再到 `completed`。 这也顺带回答了"如果退出 CC,任务还存在吗"——在,因为它是磁盘上的文件,不是内存里的变量。 ## 多 Agent:Agent Team Subagent 像 tool 一样,执行完就结束;**Agent_team 则要保持持续状态**(work、IDLE、shutdown)。这部分是三个实现里最"工程"的一块,值得逐条记: 1. **要不要用多 agent,由用户判断。** lead 会提出方案,用户决策。 2. **分配靠文件不靠抢。** 一个 task 一个文件(也有 `lead.json`),里面 `owner = agent`,任务可以直接指派,不用争抢。 3. **任务分配是双向的。** 初期由 lead 分配 agent_task;但 agent 执行完之后还有剩余任务(现实中 task 数远大于 agent 数),加上 lead 自己的回顾任务,于是空闲的 agent 会去接新任务(`new_task > lead_task`)。 4. **消息走持久化 mailbox。** agent 之间、lead 之间通过 `.mailboxes/.json` 传递信息。 ```python # Mailbox 为 agent 之间通信,.mailboxes/ # 一个 agent 一个 .json 文件,记录的是发送给 agent 的消息 # agent 之间有独立的 context / loop / tool / messages class MessageBus: def send(self, from_agent, to_agent, content, msg_type="message", metadata=None): msg = { "from": from_agent, "to": to_agent, "content": content, "type": msg_type, "metadata": metadata or {}, } ... ``` 5. **反馈要清理。** agent 执行完把 result / status 反馈给 lead,同时 `clear_lead`;不清理的话,下一次还会把 agent_team 的旧信息重复注入一遍。 6. **并行要隔离文件。** agent_team 是并行执行的,为防止相互干扰地改同一份内容,会在目录下建 `worktree` 分支(`.git/worktree`)。 7. **可观测,才谈得上可执行。** Runtime 通过暴露 args 展示 agent 是不是真的在"做",而不只是接受了请求。 8. **一个 agent 一个线程。** 创建 thread,每个 agent 一条自己的 loop,执行 `(task, name)`。 ## 长期能力:记忆、MCP、后台任务、定时任务 ### memory 记忆要解决四件事,每件都有对应的机制: **存储——怎么持久化?** 一个记忆一个文件,放在 `.memory/` 下分门别类(`personal_prefer`、`project`……),字段包含 `name` / `description` / `type` / `content`;另外放一份 `.memory/MEMORY.md` 记录每个记忆的摘要,一行一个,充当索引。 **召回——怎么放进对话?** 分四步递进:先拿最近几轮对话做反馈;再用一个小模型对用户输入和 `MEMORY.md` 做语义匹配,拿到 index;匹配失败就把用户输入正则化拆成关键词,去匹配 memory 的 `name`、`description`;最后,记忆是作为**背景知识**加入的——当前请求优先于历史记忆,是"优先考虑",不是覆盖。 **提取——怎么从上下文里提取?** 让 LLM 把用户输入解析成 json 去匹配 memory,只有 `scope=persistent` 才存入;兜底用 temporary memory 词表判断;字段缺失同样不允许入库。 **整理——怎么优化记忆?** 记忆数量到达阈值时重新整理知识;处理重复记忆时对比 `name`、`description`、`body` 等字段;**改动前先存原文件快照**,改或删失败还能恢复(先 snapshot,再改)。 ### MCP 链路是三层:**mcp server(拿到 tool 的地方)→ mcp client(用 handler 执行 tool)→ harness**。 - **tool_pool 是动态的。** 第一轮只有基础 tool;连接 mcp server 拿到相应 tool 之后,第二轮用 `assemble_tool_pool()` 组装,`Tool = 基础 tool + mcp_client_tool`。 - **为什么命名成 `mcp__{server}__{tool}`?** 因为不同的 server 可能有同名 tool,加上 server 前缀才不会撞。 - **权限要收口。** 如果某个 mcp server 提供了 `delete_database`、`trigger_deploy` 这种本质很危险的能力,必须由 harness 或用户做最终确认。 ### 后台任务 思路一句话:让 Agent 启动一个耗时任务之后不要傻等——把任务扔到后台,Agent 继续思考和干活,等任务完成再把结果告诉它。 - 目前只对 bash 开放:tool_call 的返回里带有 `{"name": …, "run_in_background": …}`,只有当 `tool_name == "bash"` 且 `run_in_background` 为真时才走后台执行,这个判断依赖 LLM 的语义理解。 - 因此一个 tool 会有**两个 tool_call**:主线程 `bash → bg_id`,后台 `bash → command`。后台结果会被格式化成一个 ``,告诉 LLM 这是一件新的独立事件。 - 加锁(`threading.Lock`)之外,还有个细节:任务完成后**不会立刻插进 agent loop**,而是先放进 `result{}` / `_ready[]`,等下一个 loop 再消费。 ### Cron scheduler 定时任务,比如让 agent 每天推送一次。机制比想象的土:**每秒更新时间,和任务时间比较**,到点就把任务放进 `cron.queue`,等 agent 空闲时再交付到 message 里;用锁避免线程争用。有 first 触发:first 放入队列,后期不再重复存入。 ```python # durable=True 写成 json,可跨会话保留 / 任务重启保存 @dataclass class CronJob: id: str cron: str prompt: str recurring: bool durable: bool pending_delivery: bool = False last_fired: str | None = None # cron 决定何时触发,prompt 是触发后交给 Agent 的任务。 # pending_delivery 表示任务已经到期但尚未被模型接收, # last_fired 防止同一分钟重复入队。 ``` ## 安全边界 前面零散提过几处安全设计,这里集中收一下。CoreCoder 的 `/save` 是个好例子:保存会话要让用户输入会话名,但在命令行上就是敲任意字符,什么都不防。 **第一道防线是清洗。** 直接取末尾的名字,防止它跳去别的目录: ```text 输入: session_id = "../../etc/passwd" 清洗: replace("\\", "/") -> "../../etc/passwd" split("/")[-1] -> "passwd" 正则替换 -> "passwd" strip("._") -> "passwd" 返回 "passwd" 拼接: /home/user/myapp/sessions/passwd.json 检查: 父目录是 /home/user/myapp/sessions,通过 ``` **第二道防线是校验。** 万一绕过了清洗,`resolve()` 之后还要比一次父目录: ```text 拼接: Path("/home/user/myapp/sessions/../../../etc/passwd.json") 解析: resolve() -> /etc/passwd.json 检查: path.parent 是 /etc,不等于 root(/home/user/myapp/sessions) 结果: 立刻抛出 ValueError("Invalid session id"),阻止访问 ``` 这两道加上前面的 Bash 危险命令检测表、MCP 危险工具的最终确认,原则是一致的:**不要指望模型自觉,把危险收敛在边界上,让越界在代码层面直接不可能。** ## 读完这三点 把三个实现放一起,能落下来的判断大概三条: **Loop 本身最简单,难的是循环里那些"不干净"的时刻。** 中断、报错、压缩、并发——每一个都会让历史或状态变得不合法,而处理它们的套路出奇一致:先让数据重新合法,再谈别的。`content: ["interrupt"]`、移动压缩的切分边界、线程各持一份副本,都是同一个动作。 **上下文是资源,压缩不是事后补救。** 它决定了压缩时该砍哪里、该留什么。所以"保留结构化内容"排在"变成摘要"之前——摘要不可逆,裁剪可逆。 **Agent 的能力边界靠外部系统撑起来。** task 文件、mailbox、worktree、memory 目录、cron 队列,这些东西放在一起看挺土的,但正是它们决定了 agent 能不能跨会话、能不能协作、能不能被观测。模型之外的这一层,才是工程真正要写的地方。 --- # Transformer 学习笔记:从整体架构到多头注意力 作者:HYD 来源:https://hydblog.xyz/2026/04/04/transformer-learning-notes/ 发布日期:2026-04-04T13:00:00.000Z 更新时间:2026-04-04T13:00:00.000Z 摘要:一份 Transformer 的学习笔记,按先整体再拆件的顺序整理:编码器与解码器的数据流;学大模型前需要回答的三个问题(神经网络、注意力机制、PyTorch 组成);Token、词嵌入矩阵与位置编码各自解决什么问题;Q、K、V 的含义,Q·K 点积为什么代表相关性,softmax 前为什么要除以维度的平方根;自注意力与交叉注意力的区别,以及交叉注意力为什么不需要掩码;训练时并行预测多个 token 而推理时逐 token 生成,因此需要因果掩码防止偷看,做法是把后文置为负无穷再经 softmax 变成 0;前馈网络、多头注意力与层归一化的定义,包括层归一化与批量归一化在归一化维度上的差别。 这份笔记是我学 Transformer 时记下来的,最早发在 CSDN 上。现在搬回自己的站点,顺手把顺序和数据流重新捋了一遍——结论没变,讲法顺了一些。 它不算教程,更像一份学习记录:先看整体,再把零件一个一个拆开。 ## 先看整体:数据是怎么流过的 输入先经过词嵌入矩阵,与位置编码融合,然后进入多头注意力机制(Q、K、V),再经过层归一化与残差连接,到前馈神经网络,之后再一次层归一化与残差连接。到这里就是输入加上编码器(Encoder)的部分——编码器可以按同样的结构堆叠 N 层。 解码器这一侧,输入从 `` 开始,同样先做词嵌入与位置编码,然后依次经过: ```text 因果掩码注意力 → 层归一化 + 残差 → 交叉注意力 → 层归一化 + 残差 → 前馈神经网络 → 层归一化 + 残差 → 线性层 → softmax → output ``` 所以整个架构无非就是这几个模块,我们逐一搞懂即可。 ![Transformer 整体架构:左侧是编码器,右侧是解码器,每个子层后面都跟着层归一化与残差连接](/asset/transformer-architecture.webp) ## 大模型的认识 我们都用过很多大模型,会发现提问之后,它的输出并不是把整段话一次性给出来,而是像词语接龙一样,一个词一个词往外蹦。把大模型的基础学完,再回头看这个过程,对 AI 的理解会具体很多。 学大模型、学 Agent,我给自己列了三个要先回答的问题。 **1. 神经网络是什么?** 最基础的答案,它是学习的地基:`x -> f(x) -> y`。 **2. 注意力机制是什么?** 对你的输入,我需要分配不同的注意力(也就是权重参数),让输出朝向你希望的样子。换成空间的角度看:一个词(token)会有很多向量方向,注意力就是把这个词转成符合当前语义的那个向量方向。 ![词向量空间的示意:语义关系被编码成方向](/asset/transformer-embedding-space.webp) **3. PyTorch 的架构?** 记住这几个零件就够了:Tensor(张量)、Parameter(可优化参数,也就是初始化参数)、Model(模型)、Autograd(计算梯度)、Optimizer(优化参数)。 现在可以进入 Transformer 了。 ## 模型组成 ### 1. Token 我们需要先把语句拆成很多个词,这个词就叫 token。比如 `I love you`,这里是 3 个 token,先把它当成单词理解。 计算机不认识这些单词,所以还要转成计算机能识别的语言——向量。每个 token 里包含很多数值,这些数值我们很难直接看懂,但大致能表示词的语义、词性、位置等等复杂信息。同一个单词在不同语句里的向量是不同的。 ![同一个词在不同语境下的向量方向不同:mole 在两种含义下指向不同的语义方向](/asset/transformer-polysemy-embedding.webp) ### 2. 词嵌入矩阵 最初处理 token 时,用的是独热编码(0/1)。比如「我是一只狗」: ```text 我 -> [1, 0, 0, 0, 0, …] 是 -> [0, 1, 0, 0, 0, …] 一 -> [0, 0, 1, 0, 0, …] ``` 问题很明显:维度很大,而每个向量只有一个位置是 1。所以引入词嵌入:通过词嵌入矩阵(一张训练得到的权重表),让语义相近的词聚在一起、差异大的词分散开,同时完成降维——用数值把自然语言区分开。 - 词向量维度 `d` 一般为 512,包含 token 的基本语义信息,是模型训练中可调节的权重表。 - `V` 是词表数量,也就是 token 的数量。 - 词嵌入矩阵是 `d × V`;单个 token 的独热向量是 `V × 1`,相乘得到 `d × 1`。整个句子的 token 组合起来就是 `V × d`。 ![独热编码、词嵌入矩阵与词向量降维到二维的示意图](/asset/transformer-word-embedding.webp) ### 3. 位置编码 到这里数据其实已经处理好了,为什么还要再处理一次?因为我们面对的不是表格、图片这类数据,而是带有顺序、有语义逻辑的信息。比如「狗咬人」和「人咬狗」,词的位置不同,含义完全不同,所以需要一份信息来衡量这种性质。 Transformer 里的位置编码是一个和三角函数有关的公式,参数是词嵌入的维度 `d` 和当前 token 在序列中的位置 `pos`,最后与词嵌入矩阵逐项相加。 ### 4. 注意力机制:Q、K、V 初始时 token 里只有自己的向量,但进入 Transformer 之后,在上下文的语义下,attention 会把向量移动到对应的语义位置上。 当我们预测下一个 token 时,依据是上一个 token 的嵌入向量;由于它已经包含了上下文的语义,它的参数量远大于单个 token 本身。 接下来是最重要的三个参数:Q、K、V。 **Q(Query)**:比如在一句英文里,名词会询问自己前面是否有形容词,这种询问被编码成另一种向量,就是 query。相当于每个词去提问自己的上下文信息。`Q = Wq · E`,其中 `Wq` 是模型学习的参数,Q 的维度远小于 E 的维度,相当于从高维降到低维。 ![Query 的示意:每个词向上下文提问,我前面有形容词吗](/asset/transformer-query.webp) **K(Key)**:如果前面确实有形容词,那么形容词的回答就成为 key。`Wk` 同样也是可学习的参数,`K = Wk · E`。所以当名词对形容词的查询真的存在时,相关性就大:Q 与 K 的点积大,查询与回答就对齐。 ![Q 与 K 点积得到的注意力矩阵:句子中每两个 token 之间的相关性得分](/asset/transformer-qk-dot-product.webp) 之后要得到概率,就经过 softmax;为了数值稳定,先除以维度的平方根。 ![softmax(QKᵀ / √d_k):点积之后除以维度的平方根,再取 softmax 得到概率](/asset/transformer-softmax-formula.webp) **V(Value)**:如果要改变某一个词的含义,你需要加入的那个向量。 ![自注意力机制的矩阵表示:X 分别乘 Wq、Wk、Wv 得到 Q、K、V](/asset/transformer-self-attention-matrix.webp) 这里我可能解释得不太好,建议大家再从其他地方补充看看。上面讲的是自注意力机制;在机器翻译任务里还存在交叉注意力机制:一种语言的词对另一种语言的词提问、回答,这里不会用到掩码(后面会讲),因为不存在「偷看」问题。 ![交叉注意力机制的矩阵表示:Q 来自解码器,K 和 V 来自编码器](/asset/transformer-cross-attention-matrix.webp) ### 5. 训练和推理 以翻译任务为例,输入是「我是一条狗」,labels 是 `I am a dog`。 先讲推理过程:输入中文,经过编码器得到编码信息;解码器从 `` 开始,把神经网络的运算与编码信息结合,得到概率最高的英文 `I`;再从 ` I` 出发得到 `am`;再从 ` I am` 出发得到 `a`……一直到 `` 结束符。 所以它是把每一个输出和之前的输出一起作为输入,去推理得到下一个输出。 训练过程不太一样。首先模型会充分利用数据:同时预测多个 token,一个样本训练多次,并并行计算 loss,效率大大加快。 同时,我们需要让模型朝正确的方向训练。还是前面的翻译任务:如果模型第一步就翻译错了,之后的语义只会更错。所以我们会把「正确答案」告诉模型,保证它以正确的姿态去学习。到这里问题也来了——答案都告诉模型了,它直接照着看就行了,那还训练什么?所以我们在注意力中引入掩码,防止模型「偷看」,防止后文影响前文的预测。 ![训练时同时预测多个位置,每个位置只能看到它前面的词](/asset/transformer-masked-prediction.webp) ### 6. 因果掩码注意力机制 我们希望这个「询问」不要被后文影响;同时因为最后要输出概率,做法是先把后文置为负无穷,再经过 softmax 变成 0。 ![因果掩码的矩阵表示:被遮住的上三角区域在 softmax 前被置为负无穷](/asset/transformer-causal-mask.webp) ![掩码处理之后的注意力矩阵:上三角区域被强制变为 0](/asset/transformer-attention-pattern.webp) ### 7. 前馈神经网络 一种网络结构:无循环、单向流动、多层,以全连接层为主。(RNN 循环神经网络不是;AlexNet 是。)这部分相对简单。 ### 8. 多头注意力机制 针对一组 token,对每个 token 做多组独立的注意力计算。不同的注意力权重 W 关注的角度不同——比如句子中的意思、标点、句法等等。 ![多头注意力机制:多组独立的 Wq、Wk、Wv 并行计算,再拼接起来](/asset/transformer-multi-head.webp) ### 9. 层归一化 层归一化:对一个样本,把它在所有神经元上的输出一起归一化(y1, y2, y3, y4, …)。 批量归一化(ResNet 用的那种):在同一层里,对不同样本在同一个神经元上的输出做归一化(y1, y1, y1, y1, …)。 | 归一化方式 | 归一化的范围 | 典型场景 | | --- | --- | --- | | 层归一化 LayerNorm | 一个样本,在全部神经元维度上的输出 | Transformer 的每个子层之后 | | 批量归一化 BatchNorm | 同一神经元,在一个 batch 内不同样本上的输出 | ResNet 这类卷积网络 | ![LayerNorm 公式:减去当前样本的均值、除以标准差,再乘缩放因子、加偏置](/asset/transformer-layernorm-formula.webp) ## 写在最后 我也是学习者,当时跟着 B 站的炮哥、3BB 的课程学。这篇文章是我对 Transformer 的学习理解,只是为了记录自己的学习过程和学习输出,可能存在片面和错误的地方,欢迎大家多多指正,共同学习。 文中的架构图、公式图和矩阵笔记截图来自课程课件,词向量空间的示意来自 炮哥带你学和3Blue1Brown 的注意力系列。