06|眼睛:LLM 的「视野」怎么拼出来,又怎么不爆

作者:浪遏日期:2026/8/2

这是《Agent全栈实战》的第 6 篇。整个系列以 catbuddy(一个本地优先的 AI 编程助手,约 3.6 万行 TypeScript)为案例,由浅入深拆解 harness 设计。前面我们讲了心脏(Agent Loop)和手脚(工具系统)。这一篇聊「眼睛」——模型每次开口之前,它到底看到了什么。你给 LLM 的那一长串 messages,不是随手拼的,而是一条五层流水线装出来的;而对话一长,这串东西就会撑爆上下文窗口,撞上窗口红线直接 context_length_exceeded。所以这篇是一条完整的线:先看怎么拼出来,再看为什么会爆,最后看 catbuddy 怎么治理让它不爆。一篇看完,你能把「上下文工程」从装配到自愈讲给同事听。


  1. 先问一个你可能没细想的问题

你调 LLM 的时候,传进去的是个 messages 数组,对吧?

1await llm.chat({ system: "...", messages: [...] })
2

那个 system 里到底是什么?那个 messages 数组又是谁拼的?

大多数人写 demo 的时候,system 是一句写死的 "You are a helpful assistant",messages 就是把历史 push 进去完事。能跑。但你把它放进一个真正干活、连续聊几百轮的 Agent 里,两个问题立刻冒出来:

  1. 拼的问题:这个 Agent 的人格、它能用哪些工具、它记得用户什么事、当前在哪个项目——这些信息从哪来,按什么顺序塞进去?拼错了顺序、漏了一块,模型的「视野」就是残缺的。
  2. 爆的问题:聊到第 200 轮,历史 messages 累计的 token 早就超过窗口了。下一次调 LLM,provider 直接给你甩一个 context_length_exceeded——任务当场崩。

catbuddy 的「眼睛」就是为这两件事生的。它由两半组成:一半负责装配(ContextBuilder,把视野拼出来),一半负责治理(AgentRunner._governContext,让视野不爆)。我们一个一个看。


  1. 装配侧:ContextBuilder 的五层流水线

先看「拼」。

打开 catbuddy 的日志,你会看到发给 LLM 的 system 大概长这样:

1You are catbuddy 🐱, a smart and caring cat-spirit AI assistant.
2OS: darwin / Node.js v20.14.0    Work root: /Users/.../my-project
3---
4## AGENTS.md     ...这个项目里 Agent 该怎么表现...
5## SOUL.md       ...Agent 的性格设定...
6## USER.md       ...用户画像:名字、偏好、时区...
7## TOOLS.md      ...自定义工具使用说明...
8---
9## Long-Term Memory (You)   ...关于你的长期记忆...
10## Project Memory           ...关于这个项目的长期记忆...
11---
12## Always-On Skills         ...memory / my 技能的完整正文...
13## Available Skills         - drawio: 生成架构图  - github: 搜索仓库 ...
14

这不是一个写死的字符串。它是 ContextBuilder(源码在 context/prompt-builder.ts)按固定五层顺序动态拼出来的。每一层管一类信息,层与层之间用 Markdown 分隔线 --- 隔开——对 LLM 来说,这是一道清晰的视觉边界。

为什么是这个顺序? 因为它遵循「最重要 → 可忽略」的降级原则:先立住「我是谁」,再加「怎么干活」,再补「记得什么」,最后挂上「能用什么」。万一哪天 token 预算真的紧张,从后往前砍也不伤核心。下面逐层快速过一遍。

① SOUL:人格层(一句话带过)

第一层是 Agent 的人格声明——You are catbuddy 🐱...。它不是写死的字符串,而是一个 Handlebars 模板(identity.md)。Handlebars 是 JS 生态里一个轻量模板引擎,{{变量}} 会被替换成实际值。所以同一个模板,在 Telegram bot 里渲染成「简洁回复」,在桌面端渲染成「完整工作区上下文」——一个模板,多副面孔。

人格工程本身是个大话题(怎么写 SOUL.md 才能让 Agent 既有性格又不胡来),但那是另一篇的事。这里你只需要记住:人格是五层里的第一层,先立人设。

② 引导文件:四个用户能改的 .md

人设立完,加载四个用户可以随手编辑的引导文件,定制这个 Agent 的具体行为:

文件装的是什么类比
AGENTS.md这个项目里 Agent 该怎么表现相当于业界常说的 CLAUDE.md
SOUL.md说话风格、回复偏好人格的可调旋钮
USER.md用户画像:名字、技术栈偏好、时区「我是谁」
TOOLS.md自定义工具的使用说明工具的本地备注

代码就是老老实实地遍历这四个文件名,读到内容就拼一段 ## 文件名\n\n内容:

1for (const name of BOOTSTRAP_FILES) {            // ['AGENTS.md','SOUL.md','USER.md','TOOLS.md']
2  const content = this.fs.readWorkspaceFile(name)
3  if (content) parts.push(`## ${name}\n\n${content}`)
4}
5

注意 USER.md 有个特殊待遇:如果开了「全局画像」,它从 ~/.catbuddy/workspace 读——这样你在所有项目里共享同一份用户画像,不用在每个项目里都写一遍「我叫张三,喜欢 TypeScript」。

③ 分层记忆:「关于你」+「关于这个项目」

第三层注入长期记忆,而且分两层:

  • Long-Term Memory (You) — 从全局 MEMORY.md 读,是跨项目的用户级记忆(你的技术栈偏好、沟通风格)。
  • Project Memory — 从当前项目的 MEMORY.md 读,是项目级记忆(这个项目的架构决策、正在进行的任务)。

这里有个很省 token 的小聪明——isDefaultMemory() 检查:如果 MEMORY.md 还是模板默认内容(说明还没攒下任何记忆),就不注入,免得白白浪费几十个 token 塞一段占位符。

这些记忆是从哪来的?谁往 MEMORY.md 里写的?——这是「记忆」那一篇(第 07 篇)的活。这里你只需要知道:眼睛从记忆里读,但不负责写。 记忆的提炼、巩固、后台学习,全部留到下一篇。

④ 技能:全文 vs 目录摘要

第四层挂技能。catbuddy 把技能分两类,注入策略完全不同:

  • 始终在线技能(memory、my 这两个)——完整 SKILL.md 正文直接塞进系统提示词。因为这两个太核心,每轮都得在场。
  • 按需技能(drawio、github……其他所有)——只放一行摘要:- drawio: 生成架构图。

为什么按需技能只放摘要不放全文?因为一个技能的全文可能很长(drawio 的全文包含整套画图 DSL 指令)。系统提示词里只需要一份目录,告诉模型「你有这些本事」;真正的全文,等模型实际调用那个技能时再通过 Skill 系统注入。这是典型的「目录常驻、正文懒加载」——省 token 的关键。

⑤ 运行时上下文:工具清单按需注入

最后一层是运行时信息——MCP 工具清单按需注入到这一轮该有的位置。MCP(Model Context Protocol)是接外部工具的标准协议,这块第 05 篇详细讲过,这里不展开。


1.5 装配侧最值得抄的两个工程细节

五层拼法你看懂了,但真正体现「工程功力」的是下面两点。它们解决的都是同一个矛盾:系统提示词好几千 token,每次请求都重拼一遍太亏,但又不能拼一次就永远缓存——文件改了得能感知到。

细节一:指纹缓存——用 mtime 当「内容变没变」的探针

ContextBuilder 不会每次 build() 都重新读文件、跑模板、拼字符串。它先算一个指纹,拿指纹当缓存 key:

1buildSystemPrompt(opts): string {
2  const key = `${channel}:${this._fingerprint()}`
3  const cached = this.cache.get(key)
4  if (cached !== undefined) return cached          // 命中 → 直接返回,零拼接
5  const prompt = this._assembleSystemPrompt(channel)
6  this.cache.set(key, prompt)
7  return prompt
8}
9

关键是 _fingerprint() 怎么算的——它把所有可能影响系统提示词内容的文件的 statSync().mtimeMs(修改时间戳)串成一个字符串:四个引导文件、全局记忆、项目记忆、技能目录指纹,再加上工作区路径,用 \0 拼起来。

逻辑朴素到优雅:任何一个文件被编辑 → mtime 变 → 指纹变 → key 变 → 缓存自然失效,下次重建。 不需要文件监听器,不需要事件通知。指纹只做 6-8 个文件的 statSync,每次 < 1ms;省下的是几千 token 的字符串拼接 + 模板渲染。这笔账,太划算。

所以当你改了 SOUL.md、或者后台往 MEMORY.md 写了新记忆、或者装了个新 Skill——下一次调 LLM,眼睛自动看到的就是新的视野,你什么都不用手动刷新。

细节二:运行时信息为什么不放系统提示词

你可能注意到了:当前时间、 channel 这些运行时信息,catbuddy 没有放进系统提示词,而是拼在第一条 user 消息的末尾:

1const textWithCtx = runtimeCtx
2  ? `${opts.currentMessage}\n\n[runtime: ${runtimeCtx}]`    // 时间/channel 拼这儿
3  : opts.currentMessage
4messages.push({ role: 'user', content: textWithCtx })
5

为什么?因为系统提示词被指纹缓存了,但当前时间是每 毫秒 都在变的。如果你把 Time: 2026-06-28T10:30:01Z 写进系统提示词,那这个时间戳每秒都不一样,指纹每秒都失效,缓存形同虚设——你等于亲手把缓存砸了。

放进 user 消息就完全合理:每条 user 消息本来就互不相同,不存在缓存可言,时间放这儿不破坏任何东西。这是一个很小但很见功底的取舍——把「易变的东西」和「可缓存的东西」分开放。


  1. 为什么会爆:token 撞上窗口红线

视野拼好了,现在看「爆」。

模型的上下文窗口是有上限的——常见的 128K,大的 200K。听起来很大?我们算笔账:

1一条用户消息              ~50 tokens
2一条 Agent 回复(含思考)  ~500-2000 tokens
3一次工具调用结果          ~1000-8000 tokens(read_file 读一整个文件 / grep 命中 50 行)
4─────────────────────────────────────
5一轮完整交互             ~2000-10000 tokens
6

20 轮 × 平均 5000 = 100K tokens。再来几轮,窗口就满了。

更阴险的是:token 不是按「消息条数」线性涨的。 一次 grep 命中 50 行代码,一条 tool result 就能吃进去 2000 token。而模型对这些原始 grep 输出,其实只需要一个摘要就够了——它正占着大量预算,性价比极低。

于是历史像吹气球一样涨,直到某一轮,累计 token 撞上窗口红线:

这就是所有 AI 助手都躲不掉的坎。你有没有对着某个聊天机器人聊了俩小时,它突然开始胡说八道?不是模型变笨了,是窗口满了、开头被截断了,它丢了「我们之前聊到哪」的记忆——而界面上还显示着完整记录,你根本不知道它已经看不到开头了。

catbuddy 的解法不是粗暴地「从头删」,而是一套先精确测量、再分级治理的机制。


  1. 治理侧之一:先把 token 数得准

要治理,先得知道「现在到底用了多少 token」。这一步不能靠估算。

最常见的偷懒办法是 text.length / 4。但这个估算误差大到离谱,因为中英文、代码的 token 密度天差地别:

1"Hello world"        → 2 tokens   (英文约 4 字符/token)
2"你好世界"            → 8 tokens   (中文约 1.5 字符/token)
3"print('hello')"     → ~5 tokens  (代码更密)
4

catbuddy 用 js-tiktoken(token-counter.ts)——OpenAI 那套 BPE 分词器的纯 JS 移植。BPE(Byte Pair Encoding,字节对编码)的思路是:从字节开始,反复合并最高频的相邻对,形成子词词表。"unbelievable" 可能被切成 ["un", "believ", "able"],每个汉字往往单独成一个 token。这是 GPT 系列真正在用的分词方式,所以数出来是精确值,不是猜。

数一条消息的 token,遵循 OpenAI Cookbook 的规范——content、tool_calls 的参数、name 字段都要算,再加每条消息固定的格式开销:

1const MESSAGE_OVERHEAD = 4                          // role + ChatML 分隔符的固定开销
2let total = MESSAGE_OVERHEAD
3total += encoder.encode(msg.content).length         // 正文
4if (msg.toolCalls) for (const tc of msg.toolCalls) {
5  total += encoder.encode(tc.name).length           // 工具名
6  total += encoder.encode(JSON.stringify(tc.arguments)).length  // 参数 JSON
7}
8

那个 MESSAGE_OVERHEAD = 4 是 ChatML 格式里 <|im_start|>role\n...<|im_end|> 这套标记的开销。catbuddy 还有个立场叫**「宁可高估,绝不低估」 ——高估只会让你提前一点开始压缩(功能轻微降级),低估却会真的超窗**(API 直接报错)。两害相权,当然选前者。另外它还留了一手 safeCountTokens:万一 js-tiktoken 在某些受限环境初始化失败,回退到 length / 3 的粗估——功能有边界,但系统不崩。这种优雅降级在 catbuddy 里到处都是。

精确 计数 到底值多少? 在一个真实会话上实测过:

计数方式结果
粗估(字符 ÷ 4)85K tokens
js-tiktoken 精确112K tokens

差了 27K。对 128K 的窗口来说,这 27K 可能正好是「能正常回答」和「开始胡言乱语」之间的那条线。估算误差 30%,在大窗口里就是「能答」和「胡说」的差距。 数得准,你才能在撞线之前就动手,而不是等 API 甩错误回来才慌。


  1. 治理侧之二:每轮调 LLM 前的四步自愈

数准了,接下来是真正的治理。

catbuddy 在 AgentRunner.run() 的主循环里,每一轮调 LLM 之前,都先跑一遍 _governContext()——一条四步流水线,纯规则、零 LLM 调用、微秒级完成。它把那个会越长越脏的 messages 数组,原地整理到「干净、合法、不超预算」:

1private _governContext(messages: LLMMessage[], spec: RunSpec) {
2  this._dropOrphanToolResults(messages)       // ① 删孤儿
3  this._backfillMissingToolResults(messages)  // ② 补缺失
4  this._microcompact(messages)                // ③ 微压缩
5  this._snipHistory(messages, spec)           // ④ 断头截断
6}
7

这个顺序不是随意的:清理 → 修复 → 压缩 → 截断,每一步给下一步准备好干净的数据。

① _dropOrphanToolResults:删「无主」的工具结果

会话恢复、或者 /stop 中断了一个工具序列时,可能留下「有 tool result 但没有对应 assistant tool_call」的孤儿。LLM API 对消息格式有严格的配对要求——一条孤立的 tool result 会直接破坏格式、触发 API 报错。

做法很直白:先扫一遍所有 tool_call 的 id 收集成集合,再倒序删掉那些 id 不在集合里的 tool 消息。倒序是关键——splice 会让后面的索引偏移,正序删会跳过元素。

② _backfillMissingToolResults:给「无尾」的调用打补丁

反过来:有 tool_call,但对应的结果丢了(history.jsonl 断尾、中断恢复时常见)。这时候 catbuddy 不是留空,而是补一条占位 result:

1messages.splice(idx, 0, {
2  role: 'tool', toolCallId: tc.id, name: tc.name,
3  content: '[Tool result unavailable — call was interrupted or lost]',
4})
5

为什么要补而不是留空?因为 tool_call / tool_result 是配对协议,模型看到一个调用却没有结果,可能会困惑、幻觉、甚至重复调用。补一条「结果丢了」的明牌,比让它对着残缺对话瞎猜好得多——至少它知道发生了什么。

③ _microcompact:最聪明的一步,纯规则零 LLM

这是我最喜欢的一步。核心洞察:真正吃 token 的,是少数几种工具的旧输出。

1const MICROCOMPACT_KEEP_RECENT = 10
2const COMPACTABLE_TOOLS = new Set(['read_file','exec','grep','web_search','web_fetch','list_dir'])
3

逻辑就三句话:扫出所有这些「大体积工具」的结果 → 保留最近 10 条原文 → 更早的统统替换成 [read_file result: 前 80 字符...]。一个原本 5000 token 的 read_file 结果,瞬间瘦成几十 token 的摘要。

为什么不压缩所有工具?因为 write_file、edit_file 的结果通常就一句「文件已写入」,压它没收益。真正的油水在 read_file 读一整个文件、grep 命中 50 行、web_fetch 拉一整页 HTML 上。而且对话越往后,这些详细内容越不重要——模型只要知道「这个文件读过了,大概长这样」就够了。

最关键的是:这一步纯字符串截断,零 LLM 调用。 它发生在每一轮迭代里,要是还得调一次 LLM 去摘要,那开销根本扛不住。确定性、零延迟,这才配得上「每轮都跑」。

④ _snipHistory:最后一道防线,断头

如果前面都做了还是超预算,就只能「断头」——从最早的消息开始删,直到 token 落进预算。预算算法是:

1预算 = 上下文窗口 − 本次最大生成 token − 1024(安全边界)
2

但断头不是简单地从索引 0 删 N 条。它有两条铁律保护 LLM 协议的合法性:

  1. 截断点必须落在一条 user 消息上——ChatML 要求 system/user/assistant 交替,从一条 tool 或 assistant 开头会破坏 provider 的角色校验。
  2. 保证当前的 user 消息(也就是用户刚发的这条问题)绝不被删——从后往前累加 token,最新的消息最重要,永远留住。
1let total = 0
2for (let i = messages.length - 1; i >= 0; i--) {   // 从后往前累加
3  total += countMessageTokens(messages[i], encoding)
4  if (total > budget) {
5    // 找到截断点,并向后挪到最近的一条 user 消息,保持角色交替
6    ...
7    messages.splice(0, start)                       // 从头删到截断点
8    return
9  }
10}
11

这一步会真的丢信息,你不会百分百满意它。但它保证 Agent 绝不会因为窗口溢出而崩溃。它只在 _microcompact 都救不回来时才触发,是名副其实的最后手段。

这四步合起来是什么

把它放回主循环看就清楚了——前几轮上下文很干净,第④步根本不触发;随着对话变长,第③步开始压旧工具输出;还不够就第④步断头;万一历史加载得有问题,第①②步自愈格式。 这是一套渐进式、纯算法、原位自愈的系统:不靠外部干预,每次调 LLM 前自动整理一遍,让 Agent 在超长对话里持续活着。


  1. 还有一道更重的防线:COMPACT 让 LLM 自己总结

四步自愈是「轻量级、每轮跑、纯规则」的。但它有个天花板:_microcompact 只会字符串截断,不懂语义——一个大文件被砍掉 99% 内容后,万一里面有关键信息,模型可能就丢了。_snipHistory 的断头更是不可逆,切掉的早期上下文永久消失。

所以 catbuddy 还有一道更重的防线兜底:当历史消息数超过阈值(默认 50 条),外层状态机(AgentLoop)会进入 COMPACT 状态,调一次 LLM,把旧对话总结成一段结构化摘要:

总结出来的摘要,作为第一条 user 消息([Previous conversation summary]: ...)注入下一次的上下文,替换掉那一大坨旧对话。于是:最近 25 条保留原文细节,更早的以「高信息密度的摘要」形式存在。 这本质是一种滚动摘要(rolling summary)——不是粗暴扔掉旧消息,而是把它们压成信息密度更高的形式留下来。

COMPACT 这个状态机本身(它在状态流转里的位置、怎么被触发)第 03 篇讲过,这里只聚焦它触发的那个「摘要压缩」动作。

它和四步自愈是互补,不是替代:

四步自愈 _governContextCOMPACT 摘要
时机每轮调 LLM 前历史超 50 条时
方式纯算法,零 LLM调一次 LLM 总结
特点在线、同步、确定性离线感、语义压缩
代价微秒级一次 LLM 调用

一个负责「每一轮都不爆」,一个负责「把长历史压成精华」,各司其职。


  1. 还有第三层,但那是下一篇的事

讲到这你可能会问:摘要替换了旧对话,可那些被压掉的事实呢?比如「我们第 30 轮定下来用 useReducer」这个决策,下次新开会话,模型还记得吗?

答案是:catbuddy 还有第三层防线——后台记忆(Dream)。它在 Agent 睡着的时候,把对话里的事实悄悄提炼进 MEMORY.md,下次直接从记忆里读,而不是从历史里翻。这也正好接上了第 1 节那个「眼睛只从记忆读、不负责写」的伏笔——写记忆的人,就是 Dream。

但这层的细节(怎么提炼、怎么去重、怎么不阻塞对话)全部留给下一篇。这里你只要在脑子里留一个位置:上下文治理是三层——四步自愈(每轮)、COMPACT 摘要(超阈值)、后台记忆(睡着时)——一层比一层重,一层比一层「记得久」。


这篇讲了什么?

  1. 装配侧:ContextBuilder 按固定五层(人格 → 引导文件 → 分层记忆 → 技能 → 运行时上下文)拼出每次调 LLM 的视野;两个见功底的细节是指纹缓存(6-8 个文件的 mtime 当探针,文件一改自动失效重建,热路径零 I/O)和把易变的运行时信息放 user 消息而非系统提示词,避免砸掉缓存。
  2. 为什么会爆:token 不按消息条数线性涨,一次 grep/read_file 就能吃几千 token;聊到几十轮撞上窗口红线,下次调 LLM 直接 context_length_exceeded。治理的前提是用 js-tiktoken 精确 BPE 计数——比字符÷4 估算准 30%,在大窗口里就是「能答」和「胡说」的差距。
  3. 治理侧:每轮调 LLM 前跑四步自愈(删孤儿 → 补缺失 → 微压缩 → 断头,纯规则零 LLM);历史超 50 条时 COMPACT 状态调一次 LLM 把旧对话总结成结构化摘要替换掉。一轻一重,互补兜底。

下一篇预告:眼睛只从 MEMORY.md 读、不负责写——那写记忆的是谁?第 07 篇聊「记忆」:从 JSONL 持久化(为什么不用 SQLite)一路讲到 Dream 后台学习——猫睡着的时候,怎么把对话提炼成长期记忆。


《06|眼睛:LLM 的「视野」怎么拼出来,又怎么不爆》 是转载文章,点击查看原文。


相关推荐


Java Jetty 实战详解:从嵌入式 HTTP 服务到 Spring Boot 容器替换
唐青枫2026/7/25

简介 Jetty 是 Eclipse 基金会维护的 Java Web 服务器,也是一个 Servlet 容器。 它主要能做这些事: 监听 HTTP 端口 处理 HTTP 请求和响应 运行 Servlet / Filter / Listener 部署 WAR 包 支持 WebSocket、HTTP/2、HTTP/3 作为库嵌入到 Java 程序里启动 作为 Spring Boot 的内嵌容器 一句话概括: Jetty 既能像 Tomcat 一样单独部署 Web 应用,也能像普通 Java 库一样


学习 OpenMontage 的工具发现与打分选择器
日习一技2026/7/17

在前面的几篇文章里,我们已经体验了 OpenMontage 的零成本玩法:用 Piper 配音、用免费素材剪纪录片、用 Remotion 把图片做成动画,全程不花一分钱 API 费用。我们也试过贴一个参考视频,让 agent 帮我们拆解出差异化的制作方案。 不过零成本路径终究有上限。想要 FLUX 的图、Veo 或 Kling 的真实运动镜头、ElevenLabs 的高质量配音,就得接入对应的 provider。OpenMontage 支持的 provider 有几十个,同一个能力往往有好几个、


LeetCode 28. 找出字符串中第一个匹配项的下标
Best_Jerry2026/7/9

leetcode.cn/problems/fi… programmercarl.com/0028.%E5%AE… 给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回  -1 ****。   示例 1: 输入: haystack = "sadbutsad", needle = "sad" 输出: 0 解释: "sad" 在下标 0 和


图解 MongoDB 22|读写关注:持久性与一致性的档位选择
十三Tech2026/7/1

前面几篇多次提到 w: "majority",这篇把它彻底讲清楚。读写关注(read/write concern)是 MongoDB 控制持久性和一致性的核心参数——它们决定了「一个写入要被几个节点确认才算成功」「一个读取从哪个节点读、读到什么程度的一致」。理解了它们,才能在不同业务场景下精准调出「够用且不浪费」的持久性/一致性档位。 先把机制边界说清楚 读写关注是三个相关但独立的参数: writeConcern(写关注):写操作要被几个节点确认才算成功。控制持久性。 readPreferen


图解 MongoDB 05|文档模型设计:内嵌 vs 引用,反范式不是免费午餐
十三Tech2026/6/22

刚从 MySQL 迁到 MongoDB 的人,最容易把关系建模那一套照搬过来:每个实体建一个集合,用 userId、orderId 这种字段做关联,查询时再 $lookup 拼。这种写法能跑,但它把 MongoDB 用成了「没有外键约束的关系库」,丢掉了文档模型最大的优势——访问局部性。 文档模型真正的价值,不是「字段随便加」,而是把一个业务实体的相关信息内嵌成一个文档,应用读一次就能拿到全部信息。但内嵌也不是免费午餐:它换来访问效率的同时,要承担冗余、一致性维护和文档膨胀的成本。这一篇讲清楚内


从 WWDC 26 空间重构(Spatial Reframing)再看端侧 2D 转 3D 的技术演进
Layer2026/6/14

2026 年 6 月 8 日,WWDC26 上苹果发布了空间重构(Spatial Reframing):照片拍完之后,拖动画面重新选择机位,AI 实时补全新视角缺失的内容: 头部玩家在两年内相继入场,这背后是三项能力趋于成熟: 单目深度估计沉淀为基础模型:无需双摄或激光雷达,仅凭一张普通照片推断每个像素的远近;过去这类模型更像“专用工具”,换个场景就容易失准;2024 年前后,香港大学与字节跳动的 Depth Anything V2 的 25M 参数的 Small


Python 迭代器与生成器
copyer_xyf2026/6/7

本文面向已有前端开发基础、正在学习 Python 的开发者。 迭代器和生成器解决的是同一个问题:数据不一定要一次性全部准备好,可以在需要的时候一个一个取出来。前端里最接近的经验是 for...of、Symbol.iterator、生成器函数 function* 和 yield。 这几个概念可以先合在一起记: 可迭代对象表示“可以被遍历的数据源” 迭代器表示“真正负责一步一步取值的对象” 生成器表示“用 yield 快速创建出来的迭代器” 后面的 for 循环,本质上就是先从可迭代对象拿到迭代器


【架构实战】ElasticSearch搜索集群:全文检索的艺术
heimeiyingwang2026/5/31

【架构实战】ElasticSearchæœç´¢é›†ç¾¤ï¼šå ¨æ–‡æ£€ç´¢çš„è‰ºæœ¯ 倒排索引、分片副本、搜索优化、实战案例 ä¸€ã€ä»Žä¸€ä¸ªçœŸå®žçš„æ• äº‹è¯´èµ· 2024年双十一,某电商平台搜索系统在流量洪峰到来的那一刻,突


豆包收费了:3.45亿用户,一个“豆包型人格“的道歉经济学
倔强的石头_2026/5/9

5月4号,两个微博热搜几乎同时炸了——#豆包错误率# 和 #豆包笨还收费#。 前一天,豆包刚刚在App Store页面更新了付费订阅声明:标准版68元/月,加强版200元/月,专业版500元/月。作为目前国内月活超过3.45亿的AI助手——这个数字意味着大约每四个中国人里就有一个人在用豆包——这是字节跳动第一次正式给豆包贴上价格标签。 但市场的反应不是期待,而是愤怒。 “又笨又收费,说平时用免费版,经常答非所问,信息出错,逻辑也不严谨,有时候还一本正经地胡说八道,基础功能都没做好。” “免费的


解锁AI编程密码:程序员常用的10个AI提示词
小码哥_常2026/4/30

解锁AI编程密码:程序员常用的10个AI提示词 引言:AI 时代的编程利器 在当今数字化浪潮中,编程领域正经历着前所未有的变革,AI 的加入让程序员们如虎添翼。有这样一个真实的故事,程序员小李在开发一个电商项目的订单管理模块时,遇到了性能瓶颈。原本处理大量订单数据时需要耗费很长时间,导致用户在下单和查询订单状态时响应迟缓。小李尝试了各种常规优化手段,但效果甚微,他陷入了困境,项目进度也因此受阻。 后来,小李了解到可以借助 AI 来解决问题。他在 AI 编程助手的输入框中输入了这样一个提示词:“A

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读