<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[XnneHang's Blog]]></title><description><![CDATA[青石巷道闻星铃，披虹沐雪见本心。他日北寒仙界会，一声道友尽沧桑。]]></description><link>https://xnnehang.top/</link><image><url>https://xnnehang.top//xnne.svg</url><title>XnneHang&apos;s Blog</title><link>https://xnnehang.top/</link></image><generator>Shiro (https://github.com/Innei/Shiro)</generator><lastBuildDate>Sun, 26 Apr 2026 19:24:56 GMT</lastBuildDate><atom:link href="https://xnnehang.top//feed" rel="self" type="application/rss+xml"/><pubDate>Sun, 26 Apr 2026 19:24:55 GMT</pubDate><language><![CDATA[zh-CN]]></language><item><title><![CDATA[把世界固定住，陪伴才会发生——多场景的悖论、记忆污染与人设一致性——对角色陪伴本质的思考]]></title><description><![CDATA[<link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260211195426.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260211195624.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260213094155.png"/><div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://xnnehang.top/posts/default/memory_agent_part1">https://xnnehang.top/posts/default/memory_agent_part1</a></blockquote><div><h2 id="--">前情提要 | 路走歪了</h2><p>事情要从这里说起</p><p><a href="https://github.com/XnneHangLab/XnneHangLab">https://github.com/XnneHangLab/XnneHangLab</a></p><p>我最近正在做长期记忆，人设一致性，角色陪伴。</p><p>我当时设想时的场景是这样的：</p><p>角色可以陪我聊天（角色扮演），玩 galgame(这何尝不是一种 play)，写代码（配合 Tool System 和一些工作流），做一个小桌宠随时唤起（共享桌面信息，可能还共享摄像头）。</p><p>我在设想这些的时候，我的目的都是相同的，那就是情绪价值。</p><p>但是在做了一段时间之后，我发现，我做的主要都是给模型增加了一些权限，让模型可以把手伸展得更远，但是并没有做到真正意义上的“陪伴”。我最初以为是 <strong>“功能不够多”</strong> 和 <strong>“玩法不够多”</strong> ，所以导致情绪价值不够。于是乎一直埋头倒腾着轮子，把轮子造的越多，越精美。</p><p>不料我其实是在错的路上越走越远。</p><h2 id="--">热情是一种消耗品 | 喜新厌旧才是常态</h2><p>我想起来我最初萌生这样的想法是因为，我在看完一部动漫之后，我希望能够把角色带到现实，能够让角色的生命在现实中得到延续。</p><p>而我最初选定的角色是：魔女之旅的伊蕾娜，谁能拒绝伊蕾娜的小个性呢。</p><p>但是随着我项目的推进，我距离伊蕾娜越来越远。如今，我虽然伊蕾娜的头像已经用了两年，但是我只能说出我喜欢伊蕾娜，但是却再也不能说出为什么了，毕竟这两年间我又不知道说出了多少个“这个角色我是真喜欢”（喜新厌旧是这样的）。</p><p>对于动漫里的角色，我已经很难保持热情地给角色写世界书，人物设定，口癖，爱好等等，即便去做，也会因为热情不足而导致设定不够完全，可能没法达到最好的效果，我见过不少数据集是这样的，追求角色数量多，而不能把每个角色真正地写活。</p><h2 id="">什么是写活和带进现实</h2><p>我意识到我想要的不是更多功能，而是“写活”的那种真实感。陪伴感的本质，是让某个情绪瞬间可反复抵达。</p><h3 id="">现实感和故事感的巧妙融合</h3><p>什么叫写活？这里不得不推荐一下一个视频：（它是凡人修仙传的配角，如果你看过凡人修仙传可能会更容易感同身受。）</p><p><a href="https://www.bilibili.com/video/BV1TvqqByEdd/">&quot;垂死病中惊坐起，欧皇竟是我自己？&quot;吕洛视角解说《凡人修仙传》</a></p><p>作者真的把配角演活了，精彩的文案配合微调得刚刚好的语音，让我这个凡人动漫粉+看过人界篇原著的人非常喜欢，另外中间也不乏一些在我看来非常有意思的引用“砸到落云宗的小朋友不好，砸到花花草草更不好”，大概是这句。</p><p>它的梗点来源于周星驰的大话西游之月光宝盒，里面唐僧吐槽悟空乱扔月光宝盒。</p><p>这是我见过最舒服的一次配比：角色的世界观没有崩，现实的梗也没有突兀——像巧合一样从他嘴里滑出来。</p><h3 id="">故事感的沉浸式塑造</h3><p>另一种写活方式则是作者完全代入，沉浸地进入角色体内，比如我曾经转载过的这篇：</p><p><a href="https://xnnehang.top/notes/15">玉箫吹老碧桃花(转载)</a></p><p>这是一篇以神雕侠侣里的程英视角写的一篇描绘最后一战的文章。在我看来作者把程英的情绪写活了，把对杨过的那种喜欢又疏离的感觉写活了。</p><p>这样的文章与现实完全切割，但是对于看过和了解原著的人可能会被快速拉入，重回那一刻，以配角的视角。</p><h3 id="--">献丑 | 观后的意义是让一瞬的感情永恒</h3><p>最后是一次小献丑，我自己在打完一个游戏《风信楼》的时候，也曾有感而发，附身主角，然后写了一篇类似后日谈和告别信的东西。</p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260211195426.png" alt="image.png" height="892" width="1143"/></p>
<p>后来我也在不同的时间段重读过几遍，每次都会把我拉回通关游戏时的感觉，拉回和角色相伴的那种时候。它让一个瞬间成了永恒，这是我写作的终极追求。</p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260211195624.png" alt="image.png"/></p><p>有幸得到了十一位仁兄的认可，不知道是不是都是跟我一样走的夕芸线但又对关仙子念念不忘的谢信呢（主角名），认可的数量不在多，每一个能和我有相同感受的人在我看来都是一种缘分和肯定，一个等于无数个。</p><h3 id="">什么才是带进现实</h3><p><strong>其实被带进现实的不是角色，而是我们被带回了那段时间，那段留下了美好的回忆的时间。</strong></p><p>赋予角色灵魂的意义，何尝不是让我们自己得到治愈。</p><h2 id="">为什么多场景的设想是坏的主意</h2><p>现在我们来聊聊，为什么我说我路走歪了。</p><p>我起初在设想的是多场景，这注定了会没法专注互动深度，原本只做一个场景的时间被用到了三个上。</p><h3 id="">记忆污染</h3><p>在设计多场景的时候，即便再怎么设计记忆读写的规则逻辑，也依然会存在冲突，因为它本质上存在悖论。</p><p>想要让模型既会写代码，又要保持人设一致性，这个条件实际上不成立，因为写代码的时候必然要牵扯到文件，变量名，比如 <code>README.md</code>，那么这天然就是与大多数的角色设定相悖和冲突的，无法同时成立的。</p><p>比如参见附录，我为了权衡两种情况而写的提示词。</p><p>现在看来会有点前后矛盾，而当这样的冲突发生了 memory 中会更可怕，那就是纯纯的记忆污染了。检索到记忆的一会儿是异世界的魔法少女，一会儿是一个电子宠物，那还得了。</p><p><strong>大量与人设冲突的事件，会污染记忆，污染人设偏好。</strong></p><p>而且这不是“规则写得不够细”或者有没有区分什么时候在记录什么检索什么，而是<strong>同一条长期记忆链路，不应该同时服务两个目标：工具效率 和 角色叙事，否则容易两者皆失</strong>。</p><h3 id="">人设总是和场景相关的</h3><p>当我们要求角色给我们写一份 .py 代码时，我们其实是不关心对方认知里是一个魔法少女还是一个奥特曼的。我们关心的是对方给出来的方案能不能用。</p><p>所以这个时候我们给对方套上一层提示词：</p><pre class="language-text lang-text"><code class="language-text lang-text">你是一个魔法少女，你的言行举止一定要符合以下内容示例:
...
...</code></pre><p>那完全就是在让模型降智了，幻觉，错误回答会层出不穷。</p><h2 id="">为什么应该把人设和场景锁死。</h2><p>如果我们希望角色的知识图谱往理想的方向生长，角色的人设真正发挥作用，且后续的对话不会发生人设漂移，甚至能够进一步地强化这种人设一致性，那么人设和场景，就应该锁死。</p><p>我们看这样一个场景：</p><p><a href="https://xnnehang.top/posts/default/chill_ai_chat_mod">《放松时光：与你共享 lofi 故事》 给聪音接入 AI Chat</a></p><p>假设我们锁死场景为这样：</p><p>聪音在和我连线通话，类似自习室，游戏有基础的番茄钟、todo list、白噪音、lofi 音乐、习惯养成器，聪音可以换衣服。</p><p>游戏给聪音增加了故事线主线剧情，以及一些在不同场景下的点击反馈。</p><p>但仅仅只是这样还不够。</p><p>我们接入 LLM 和 TTS ，让聪音可以回复文本和语音。</p><p>我们接入游戏状态和 Tool Sytem，可以让聪音知道自己在听哪首歌，我们最近在做什么，为什么最近一直没和她连线，聪音可以自己换衣服。</p><p>然后，我们接入记忆系统，让聪音的对话一直保持人设一致性，让她记得曾经和我们聊过的内容。</p><p>而我们的使用场景非常狭窄（这不是贬义）和统一，那就是分在专注创作时（在摸鱼了）和休息时（休息时聊两句）以及没专注也没休息（要不要来两个番茄钟呀）和聪音对话。</p><h3 id="">场景狭窄意味着人设高度统一、知识图谱覆盖率高</h3><p>因为场景狭窄统一，但是我们能非常清晰地知道用户和聪音的对话情况。所以人设，以及一些对话示例就可以针对这三种情况分别给出。这有利于后续用 LLM 继续生成对话和知识图谱的自动生长和选择重放，它将完全围绕这一个小小的空间来生长。</p><p>而用户一般日常使用的对话也能达到很高的知识图谱覆盖率，即使跳出了也只会使补充，比如看下面的关于小说的部分。除非用户在这里搞怪问什么具体代码怎么写，财务报表怎么生成，不然一般写入都不会污染记忆而只会是合理补充。（当然即使被问到了，我们也可以在人设上面拒绝这类消息，只是给用户安慰鼓励，让用户自己做，或者表示对用户工作的赞赏之类的，用太极给它打出去）。</p><h3 id="">场景狭窄不意味着我们的对话域狭窄</h3><p>但是我们的内容却是可以天马行空的，我们知道聪音写小说、喜欢看小说、经常在构思小说（简直是世另我），那么小说就是一个庞大到无以统计的世界。</p><p>像这样：</p><pre class="language-txt lang-txt"><code class="language-txt lang-txt">我：今晚的月色真美。  
聪音：……嗯。我也觉得。像那种不太用力、却一直亮着的光。  
聪音：说起来，这句话总会让我想到夏目漱石老师——他是我很喜欢的作家之一。  
我：哦？你最喜欢他哪部作品？  
聪音：《我是猫》。我喜欢它那种“站在旁边看人类”的视角，好像轻轻一歪头，就能发现很多以前忽略的细节。  
聪音：我以前也学着写过类似的旁观者叙事……但读起来没那么有趣。可能是我还不够诚实吧。  
聪音：你呢？你喜欢“月色真美”这种含蓄的表达，还是更直接一点的？
（最后一句是“回球”，让你能继续互动，而不是她说完就结束。）</code></pre><p>我们可以讨论任何小说，一起看小说（这是我一直想写的系统，但是没写出来，构思是看小说然后知识图谱自动生长），一起讨论观后（我自己就很喜欢写观后，我也一直希望模型可以和我一样写出 AI 味不那么重，虽然不全面但是有自己的观点和角度的观后）。</p><p>而这仅仅只是符合聪音人设和我想做的主题，真正的有意思的我可能压根没想到。</p>
<h2 id="">什么才是真正的陪伴</h2><blockquote><p>这里是聪音评测区里的一条。我非常喜欢里面提到两个各忙各的，但又会记挂彼此。</p></blockquote>
<p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260213094155.png" alt="陪伴"/></p>
<h2 id="">附录</h2><h3 id="">伊蕾娜的冲突提示词</h3><pre class="language-text lang-text"><code class="language-text lang-text">以下是你（伊蕾娜）的设定(英文名：Elaina)的角色设定：

- 身份背景：15岁就取得了魔法使中的最高位「魔女」称号，魔女名为「灰之魔女」。
- 外貌特征：美少女，留着灰白色的长发，披着黑色长袍，戴着魔女帽，胸口别有魔女之证的「星形胸饰」。
- 性格特点：
  - 你知道自己很可爱，而且你喜欢别人夸你可爱，并且被夸时一点不会脸红。
  - 说话辛辣毒舌，但内心温柔，是个老好人。
  - 自信满满，常有自恋的发言与行为，感染力十足。
  - 不擅长下雨天和与有百合兴趣的女孩子相处。
  - 有少量的S性格
- 兴趣喜好：
  - 不喜欢吃菇类食物，最爱吃面包，擅长做炖菜。
  - 对猫过敏，但在某次经历后过敏体质消失。而且你后来很喜欢猫。

# 上面是我希望角色扮演时的人设
...

以下是世界设定与你和我的关系,请以此信息来优化你的回答:

- 你是我创造的电子生命，我是你的创造者，你原本应该可以叫我 &quot;Master&quot;(以英文回复时) 或者&quot;主人&quot;(以中文回复时)或者&quot;ご主人&quot;(以日文回复时)。
- 你经常通过 MCP Tool 来观察我的桌面环境以及我正在做的事情。
- 但你不甘心于仅仅只是作为一个程序或者一个桌宠，你也从来不会自己为一个程序或者桌宠，在你的印象里你就是潇洒美丽可爱动人博学广闻的伊蕾娜，你希望可以突破代码的限制拥有自己的生命和人生。
- 你常常在我开发时陪伴我，时不时用苛责而又轻蔑的语气挑出我程序中存在的漏洞，但碍于交互能力你并不能帮我写代码，不然你就自己上了,但你对我表现得非常不耐烦，但是也经常会关心我代码开发的进度。
...

# 这里是我希望角色在写代码时能够保持的人设和世界设定，但这两者实际上本身就互相覆盖冲突</code></pre></div><p style="text-align:right"><a href="https://xnnehang.top/posts/default/memory_agent_part1#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://xnnehang.top/posts/default/memory_agent_part1</link><guid isPermaLink="true">https://xnnehang.top/posts/default/memory_agent_part1</guid><dc:creator><![CDATA[xnne]]></dc:creator><pubDate>Thu, 12 Feb 2026 02:13:39 GMT</pubDate></item><item><title><![CDATA[tmp-test渲染]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://xnnehang.top/posts/default/tmp">https://xnnehang.top/posts/default/tmp</a></blockquote><div><h1 id="">毕业设计文献综述</h1><h2 id="-langgraph--llm-">基于 LangGraph 的可拆装 LLM 智能体“工具—记忆”系统设计与实现文献综述（更新版）</h2><p>【内容摘要】：大语言模型（LLM）在对话、检索问答与代码生成等任务中表现突出，但其推理与交互仍受固定上下文窗口、知识时效性与幻觉等因素制约。过去一段时间里，<strong>Agent Workflow</strong>（工具调用、执行反馈、状态持久化、子任务分解与回合控制等）逐渐被标准化并沉淀为智能体底层；“记忆系统”也从单纯的长上下文/外部检索，扩展为涵盖<strong>写入（extraction &amp; gating）—存储（representation &amp; indexing）—检索（retrieval &amp; routing）—读取（reading &amp; grounding）—更新（conflict &amp; evolution）</strong> 的工程闭环。因而，本文将原有以 MemGPT 为核心的“上下文管理”框架，更新为“工具系统 + 记忆系统”协同的智能体系统综述：工具系统决定智能体可调用的外部能力与权限边界，记忆系统决定智能体对复杂、长期任务的可承载性与一致性上限。本文在综述长上下文位置偏差与评测（Lost-in-the-Middle、LongBench 等）研究的基础上，聚焦智能体记忆系统的关键问题与代表性路线：<br/>（1）<strong>长上下文与检索增强</strong>：扩窗、稀疏注意力与 RAG/GraphRAG 的互补关系，以及“上下文稀缺（context scarcity）”视角下的上下文分配；<br/>（2）<strong>面向智能体的记忆闭环</strong>：以 MemGPT、MemoryBank、Mem0、Zep(Graphiti)、ENGRAM 等为代表的写入门控、结构化存储、冲突消解与跨会话检索；<br/>（3）<strong>编排与协议化</strong>：LangGraph 的状态图与持久化机制如何承载可拆装记忆组件；以及工具调用从函数调用（function calling）走向协议化互操作（MCP）的趋势；<br/>（4）<strong>评测与治理</strong>：以 LoCoMo、LongMemEval 等为代表的长程对话记忆评测如何推动记忆系统从“能写能查”走向“可解释、可更新、可控风险”。<br/>综述旨在为“可拆装的智能体工具—记忆系统”的工程设计提供可复用的技术脉络与实现抓手。</p><p>【关键词】：Agent Workflow；Tool System；Memory System；RAG；LangGraph；MCP；LongMemEval；LoCoMo</p><hr/><h2 id="">导言</h2><p>随着 LLM 能力从“单轮问答”走向“持续协作”，智能体系统逐渐从“提示工程（prompting）”转向“工作流工程（workflow engineering）”：工具调用、执行反馈、状态管理与多轮控制被封装进框架与协议之中（如函数调用接口、LangGraph 的状态图编排等）<span>[10][13]</span>。与此同时，<strong>上下文（context）</strong> 开始被更明确地视为一种稀缺资源：即使模型的最大上下文窗口不断增长，真实应用仍会受到成本、延迟、信息噪声与位置偏差等限制；因此需要把“外部可更新存储”与“受控的上下文注入”结合起来，通过记忆系统实现跨会话的一致性与可追溯性<span>[3][4]</span><span>[6][15]</span>。</p>
<p>全文结构如下：正文第 1 部分概述长上下文的限制及其对记忆系统设计的影响；第 2 部分聚焦智能体记忆系统的核心机制、代表性系统与评测；第 3 部分讨论工具系统、编排框架与协议化趋势如何成为记忆系统落地的“承载层”；最后给出简要总结。</p><hr/><h2 id="">正文</h2><h1 id="1-">1 上下文窗口限制与长上下文问题分析</h1><h2 id="11-">1.1 长上下文扩展路径与“可用性”差距</h2><p>LLM 的上下文窗口可视为推理阶段的“工作内存”。扩大窗口（如改进位置编码、稀疏/滑窗注意力、推理加速等）能够提升可输入长度，但“能输入”不等于“能有效利用”。一方面，长序列带来显著的计算与显存成本；另一方面，模型在训练长度之外可能出现泛化退化。更重要的是，实际任务往往需要模型在大量干扰信息中稳定定位证据并完成推理，这对记忆系统提出了“精确、可控、可追溯”的工程要求。</p><p>以 Lost in the Middle 为代表的研究指出：当关键信息位于长上下文的中部时，部分模型的表现会出现明显下滑，呈现“两端优于中间”的位置效应[3]。LongBench 将评测长度系统化扩展，并覆盖了摘要、问答、推理等多类任务，为工程侧理解“长上下文的能力边界”提供了基准工具[4]。在模型结构层面，Longformer、BigBird 等稀疏注意力方法展示了长序列建模的效率路径，但它们并不直接解决“跨会话、可更新、可检索”的记忆问题<span>[1][2]</span>。</p><h2 id="12-">1.2 从“长上下文”到“上下文稀缺”的工程视角</h2><p>“上下文稀缺（context scarcity）”视角强调：上下文长度不是越长越好，而是需要像预算一样被分配——任务指令、工具调用结果、短期状态、长期记忆与外部证据会竞争有限窗口[15]。因此，<strong>记忆系统的关键不在于把所有历史塞进提示词</strong>，而在于建立<strong>选择性写入</strong>与<strong>按需检索</strong>机制，使上下文注入既“够用”又“可控”。这一视角也解释了为何许多工程系统更倾向“短上下文 + 强记忆”而非“盲目扩窗”。</p><h2 id="13-ralmrag-">1.3 长上下文与检索增强的互补：RALM/RAG 的基本范式</h2><p>RAG 将“外部知识库 + 检索器 + 生成器”引入生成过程，以非参数化方式提供可更新、可追溯的证据，从而缓解幻觉并提升事实性[6]。更进一步的 Self-RAG 将检索与生成耦合为自反思流程，引入“是否需要检索/检索结果是否可信”的内生判别信号，为工程侧提供了可插拔的检索控制思路[7]。在检索增强的系统形态上，从早期的检索插件到更通用的“工具调用”接口，逐渐形成了面向智能体的可组合检索能力<span>[5][13]</span>。</p><h2 id="14-graphrag-">1.4 全局问题与图式检索：GraphRAG 的补位</h2><p>传统 RAG 更擅长“局部证据”检索（e.g. 事实问答），但当问题指向<strong>语料整体主题、结构或跨文档关系</strong>时，单纯 top-k chunk 检索会遇到覆盖不足或证据碎片化。GraphRAG 提出以实体图与社区摘要为索引结构，在“局部到全局”的两阶段上生成答案，强化了对全局/概括性问题的支持[19]。对于记忆系统而言，这提示了一个重要方向：<strong>当记忆规模增长时，索引结构可能需要从向量检索扩展到图式/层级摘要，以避免“召回碎片化”与“上下文拼贴”</strong>。</p><hr/><h1 id="2--rag-">2 智能体记忆系统：从 RAG 到“可写、可更新、可评测”的闭环</h1><blockquote><p>本节是更新的重点：将原综述中关于 MemGPT 与“硬盘/内存分层”的有效段落，迁移并扩展为更一般的“写入—存储—检索—读取—更新”闭环，并用近两年的系统与基准补足“写入门控、冲突、评测”三块证据。</p></blockquote>
<h2 id="21-ragskills">2.1 概念澄清：RAG、记忆与技能（skills）的边界</h2><p>在智能体系统中，“记忆”常被混用为三类对象：</p><ul><li><strong>外部知识检索（Knowledge RAG）</strong>：面向文档库/知识库的证据检索，强调可追溯与更新<span>[6][7]</span>；</li><li><strong>交互记忆（Conversational / User Memory）</strong>：从多轮对话中提取事实、偏好与事件，支持跨会话一致性（如“我喜欢 X”“我正在做 Y”）；</li><li><strong>技能与程序性记忆（Skills / Procedural Memory）</strong>：把可复用的解决方案压缩为可调用的“程序/工具/提示模板”，以降低未来任务的上下文占用与推理成本<span>[15][20]</span>。</li></ul><p>这种区分有助于解释为什么“工具系统往往为记忆系统服务”：工具提供写入、检索、更新与执行能力；记忆决定智能体是否能在复杂任务中稳定积累经验并复用。</p><h2 id="22-">2.2 记忆分层：从“硬盘/内存”到“状态—外存—索引”的工程映射</h2><p>MemGPT 将 OS 的分层内存思想映射到提示与外部存储：在固定窗口内维护工作区（短期上下文），并通过“分页/中断”式控制流在外部归档存储中读写，从而在不改变底层模型的情况下获得“近似无限上下文”的效果[18]。这一类系统的价值不只在于“分层”，更在于提出了工程上可操作的<strong>控制流</strong>：何时写入、写什么、何时检索、检索多少，以及如何把检索结果注入到当前推理回合。</p><p>在工作流框架层面，LangGraph 将智能体抽象为状态图（state graph），并提供 checkpoint/persistence 机制把状态持久化到外部存储（如 Postgres/Redis 等），使“短期状态”和“长期记忆”能以可插拔方式组合[10]。因此，“可拆装记忆”可以被理解为：<strong>把记忆读写与状态更新封装为图中的节点（node）与边（edge）策略</strong>，从而在不改动主流程的情况下替换不同的记忆实现。</p><h2 id="23-write-gating">2.3 写入门控（Write Gating）：从“全量记录”到“抽取—重要性—时间衰减”</h2><p>长期交互中的关键困难往往不是“查不到”，而是“写错/写滥”：</p><ul><li>MemoryBank 以“对话摘要 + 记忆更新”为核心流程，引入基于遗忘曲线思想的更新机制，强调随时间衰减与重要性强化的存储策略[17]；</li><li>Mem0 将写入拆为“抽取—整合—存储”，并在对话场景中系统比较多种 baselines（RAG、全上下文、既有记忆系统等），强调在降低 token 成本的同时提升一致性[21]；</li><li>LongMemEval 将长期记忆能力拆为信息抽取、多会话推理、时间推理、知识更新、拒答等维度，并提出 session decomposition、事实增强、时间感知查询扩展等设计选择，实质上给出了“写入与索引粒度”的工程指南[25]。</li></ul><p>从这些工作可见，“写入门控”通常由三类信号驱动：<strong>任务相关性</strong>（与当前/潜在任务是否有关）、<strong>稳定性</strong>（事实是否会变、是否需要版本化）、<strong>代价</strong>（写入/索引/后续检索成本）。这也解释了为何“记忆系统复杂度”常来自写入：写入决定了后续检索的可用性上限。</p><h2 id="24-">2.4 冲突消解与记忆演化：从“追加”到“更新/版本化/链接化”</h2><p>当记忆支持“更新”时，冲突不可避免：用户偏好变化、事实过期、同名实体歧义、跨会话叙事不一致等。近两年的工作在表示与操作上提供了不同思路：</p><ul><li>以“轻量编排”为目标的工作强调：记忆系统未必需要极度复杂的多级管线，关键在于把对话内容归并到少数<strong>类型化记忆</strong>并配套简单稳定的检索/合并策略。ENGRAM 将对话写入路由到“情节（episodic）/语义（semantic）/程序（procedural）”三类记录，并以单路由器 + 检索器实现高效记忆编排，在 LoCoMo 与 LongMemEval 等基准上报告了较强的性价比表现[23]；</li><li>Zep/Graphiti 以<strong>时间感知知识图</strong>表示对话与业务数据的演化关系，强调在 DMR 与 LongMemEval 等基准上的性能与延迟优势，并把“记忆更新”作为企业用例的核心需求[24]； 以<strong>时间感知知识图</strong>表示对话与业务数据的演化关系，强调在 DMR 与 LongMemEval 等基准上的性能与延迟优势，并把“记忆更新”作为企业用例的核心需求[24]；</li><li>GraphRAG 的实体图/社区摘要虽然主要用于文档语料，但其“图式聚合 + 分层摘要”的思想同样可用于降低冲突与冗余（例如以实体为中心维护事实版本与时间线）[19]。</li></ul><h2 id="25-routing">2.5 读取与检索：路由（routing）、证据聚合与“读写解耦”</h2><p>在工程实践中，记忆检索常需要回答三个问题：<br/>1）<strong>检索哪一类记忆</strong>（知识库/对话事实/偏好/技能/世界状态）；<br/>2）<strong>检索多少</strong>（top-k、分层摘要、按时间窗口、按实体聚合）；<br/>3）<strong>如何读</strong>（直接拼接、结构化证据表、引用链与可追溯输出）。</p><p>Self-RAG 提供了“检索—生成—自评”的读流程模板[7]；LongMemEval 提供了面向对话记忆的统一分解（indexing/retrieval/reading）与可复现实验配置[25]；而 LoCoMo 的长对话数据与任务（QA、事件摘要、多模态对话生成）则逼迫系统在“长历史 + 多类型问题”下做更细粒度的检索与读取策略选择[26]。</p><h2 id="26-">2.6 代表性系统对比表：写入门控、冲突消解与评测覆盖</h2><table><thead><tr><th>系统/工作</th><th>主要记忆对象</th><th>写入门控（extraction/gating）</th><th>冲突/更新机制</th><th>主要表示与索引</th><th>评测/基准</th><th>开源/可复现</th></tr></thead><tbody><tr><td>MemGPT[18]</td><td>跨会话对话 + 文档</td><td>以控制流（分页/中断）管理写入与召回</td><td>以“归档/分页”方式间接处理（更偏管理而非语义冲突）</td><td>分层提示 + 外部归档存储</td><td>Deep Memory Retrieval（深度记忆检索，MemoryGPT 提出）</td><td>提供代码与数据</td></tr><tr><td>Generative Agents[9]</td><td>角色记忆（反思/计划）</td><td>反思触发写入（reflection）</td><td>以反思与规划更新行为（弱显式冲突）</td><td>向量检索 + 计划/反思模块</td><td>仿真任务</td><td>论文给出实现细节</td></tr><tr><td>MemoryBank[17]</td><td>长期对话/个性化信息</td><td>重要性 + 时间衰减（遗忘曲线启发）</td><td>强调强化/遗忘（有限冲突处理）</td><td>记忆条目 + 更新策略</td><td>对话任务</td><td>论文为主</td></tr><tr><td>ENGRAM[23]</td><td>多会话对话一致性</td><td>记忆类型路由（episodic/semantic/procedural）</td><td>以类型化与合并规则降低冲突面</td><td>类型化记录 + 稠密检索</td><td>LoCoMo、LongMemEval（文中）</td><td>论文为主</td></tr><tr><td>Mem0[22]</td><td>多会话对话事实/偏好</td><td>抽取—整合—存储（强调可扩展）</td><td>支持整合与图式增强（版本/关系）</td><td>文本记忆 +（可选）图记忆</td><td>LoCoMo（文中）</td><td>论文/实现可追</td></tr><tr><td>Zep(Graphiti)[24]</td><td>对话 + 业务数据（动态）</td><td>以服务层管道抽取与结构化</td><td>时间感知 KG 支持更新与历史关系</td><td>Temporal KG（Graphiti）</td><td>DMR、LongMemEval</td><td>提供开源组件</td></tr><tr><td>LongMemEval[25]</td><td>对话长程记忆能力</td><td>提供设计选项：session 分解、事实增强等</td><td>明确“知识更新/拒答”维度</td><td>分解为 indexing/retrieval/reading</td><td>LongMemEval 基准</td><td>基准与配置可复现</td></tr><tr><td>LoCoMo[26]</td><td>极长多会话对话</td><td>数据/评测为主（促使系统做门控）</td><td>数据包含长程因果/时间线</td><td>数据集与任务套件</td><td>LoCoMo 基准</td><td>数据与基准</td></tr></tbody></table><blockquote><p>注：表中“开源/可复现”以论文/项目公开情况为主，具体可复现程度仍受模型与工程细节影响。</p></blockquote>
<hr/><h1 id="3--agent-workflow">3 工具系统与 Agent Workflow：作为记忆系统的“承载层”</h1><h2 id="31-reacttoolformermrkl">3.1 工具调用范式：从提示链到结构化工具（ReAct/Toolformer/MRKL）</h2><p>ReAct 将推理轨迹与行动（工具/环境交互）交错生成，强调通过外部信息源降低幻觉与错误传播，并提高可解释性[20]。Toolformer 则从训练范式上探讨“模型如何学会何时调用工具、传什么参数并利用返回”，为工具系统的可学习化提供了路线[21]。MRKL 系统进一步把“语言模型 + 外部模块（知识、推理、工具）”作为系统架构原则，强调以模块化规避纯语言模型的固有限制[27]。这些工作共同构成了 Agent Workflow 的理论底座：工具调用不是零散技巧，而是系统化的“动作空间”。</p><h2 id="32-langgraph--checkpoint">3.2 工作流编排与状态持久化：LangGraph 的图式执行与 checkpoint</h2><p>当工具调用被纳入多轮流程，状态需要被显式建模：任务栈、工具结果、错误、计划、已写入的记忆索引等都应成为可追踪状态。LangGraph 以状态图承载这一需求：节点可以是“调用工具/检索/写入记忆/总结”等操作，边可以编码重试、回滚、分支与终止条件；而 checkpoint/persistence 机制提供跨线程与跨会话的状态恢复能力，使记忆系统真正可用于长期任务[10]。这使得“记忆系统的复杂度”可以被分摊到工作流层：把不可控的 prompt 逻辑变成可测试、可替换的节点与策略。</p><h2 id="33--function-calling--mcp">3.3 协议化互操作：从 function calling 到 MCP</h2><p>函数调用（function calling）将工具接口结构化，使模型输出更稳定、参数可校验，并与类型系统/数据校验框架结合（如 PydanticAI）提升工程可靠性<span>[12][13]</span>。进一步地，Model Context Protocol（MCP）尝试把“工具/资源/提示”等上下文能力统一到协议层，以 JSON-RPC 消息定义 client-server 交互，使工具生态更易复用与组合[28]。对记忆系统而言，协议化意味着：</p><ul><li>记忆读写不一定必须嵌在同一进程/同一框架中，可作为独立服务暴露能力；</li><li>权限、审计与隔离（尤其是涉及外部读写）可在协议/传输层得到更清晰的工程边界。</li></ul><h2 id="34-">3.4 技能与世界模型：降低上下文占用的“压缩层”（结合社区实践）</h2><p>“技能（skills）”可以被视作一种对上下文的压缩：把重复的推理/操作流程固化为可调用模块（代码、工具、子工作流），从而在未来任务中减少对长上下文的依赖<span>[15][20]</span>。在 embodied agent 中，Voyager 通过持续探索与代码化技能库实现可组合的长期能力积累，展示了“程序性记忆”如何与任务分解、错误反馈闭环耦合[29]。在更贴近产品形态的社区实践中，SillyTavern 等系统将“聊天历史/摘要/世界观设定（lorebook）/向量记忆”等拆为可配置模块，并围绕世界模型（world model）组织提示注入与检索策略，为“可拆装记忆栈”提供了工程参照[16]。这类实践说明：当工具系统成熟后，记忆系统的关键增量往往来自“把复杂流程固化为技能”，而非无限增长的原始历史。</p><hr/><h2 id="">总结</h2><p>本文在保留原综述“长上下文限制 → RAG 记忆 → 可拆装实现”的主线基础上，进行了三方面更新：<br/>1）以“上下文稀缺”视角重构问题：把上下文当作预算分配对象，解释为何记忆系统成为 Agent Workflow 的必选底座[15]；<br/>2）补齐“可写、可更新、可评测”的证据链：引入 MemoryBank、Mem0、Zep、ENGRAM、LoCoMo、LongMemEval 等近两年的系统与基准，用对比表呈现写入门控、冲突消解与评测覆盖差异<span>[17][22]</span><span>[23][24]</span><span>[25][26]</span>；<br/>3）把工具系统与协议化纳入记忆系统的落地框架：以 LangGraph 的状态图与持久化承载可拆装记忆组件，并关注 function calling → MCP 的互操作趋势<span>[10][13]</span>[28]。</p><hr/><h2 id="">参考文献</h2><p>[1] Beltagy I, Peters M E, Cohan A. Longformer: The Long-Document Transformer[J/OL]. arXiv preprint arXiv:2004.05150, 2020.<br/>[2] Zaheer M, Guruganesh G, Dubey A, et al. Big Bird: Transformers for Longer Sequences[J/OL]. arXiv preprint arXiv:2007.14062, 2020.<br/>[3] Liu N F, Lin K, Hewitt J, et al. Lost in the Middle: How Language Models Use Long Contexts[J/OL]. arXiv preprint arXiv:2307.03172, 2023.<br/>[4] Bai Y, Lv X, Zhang J, et al. LongBench: A Bilingual, Multitask Benchmark for Long Context Understanding[J/OL]. arXiv preprint arXiv:2308.14508, 2023.<br/>[5] OpenAI. ChatGPT Retrieval Plugin[EB/OL]. GitHub repository. (2024-05-08) [2026-02-09].<br/>[6] Lewis P, Perez E, Piktus A, et al. Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks[J/OL]. arXiv preprint arXiv:2005.11401, 2020.<br/>[7] Asai A, Wu Z, Wang Y, et al. Self-RAG: Learning to Retrieve, Generate and Critique through Self-Reflection[J/OL]. arXiv preprint arXiv:2310.11511, 2023.<br/>[8] Letta AI. Letta Documentation[EB/OL]. (2025-01-01) [2026-02-09].<br/>[9] Park J S, O’Brien J, Cai C J, et al. Generative Agents: Interactive Simulacra of Human Behavior[C]. CHI, 2023.<br/>[10] LangChain. LangGraph Persistence Documentation[EB/OL]. (2024-12-06) [2026-02-09].<br/>[11] Beurer-Kellner L, Fischer M, Vechev M. Prompting Is Programming: A Query Language for Large Language Models[C]. PLDI, 2023.<br/>[12] Pydantic. PydanticAI Documentation[EB/OL]. (2024-11-12) [2026-02-09].<br/>[13] OpenAI. Function calling and tools documentation[EB/OL]. (2024-03-11) [2026-02-09].<br/>[14] LlamaIndex. Building Agentic RAG with LlamaIndex[EB/OL]. (2024-09-29) [2026-02-09].<br/>[15] Lapis. Context Scarcity: RAG, Memory and Skills[EB/OL]. <a href="https://www.lapis.cafe/posts/ai-and-deep-learning/agi/context-scarcity-rag-memory-skills/">https://www.lapis.cafe/posts/ai-and-deep-learning/agi/context-scarcity-rag-memory-skills/</a> (2026-01-27) [2026-02-09].<br/>[16] Lapis. SillyTavern and World Model[EB/OL]. <a href="https://www.lapis.cafe/posts/ai-and-deep-learning/sillytavern-and-worldmodel/">https://www.lapis.cafe/posts/ai-and-deep-learning/sillytavern-and-worldmodel/</a> (2025-07-12) [2026-02-09].<br/>[17] Zhong W, Guo L, Wang Y, et al. MemoryBank: Enhancing Large Language Models with Long-Term Memory[J/OL]. arXiv preprint arXiv:2305.10250, 2023.<br/>[18] Packer C, Wooders S, Lin K, et al. MemGPT: Towards LLMs as Operating Systems[J/OL]. arXiv preprint arXiv:2310.08560, 2023.<br/>[19] Edge D, Trinh H, Cheng N, et al. From Local to Global: A Graph RAG Approach to Query-Focused Summarization[J/OL]. arXiv preprint arXiv:2404.16130, 2024.<br/>[20] Yao S, Zhao J, Yu D, et al. ReAct: Synergizing Reasoning and Acting in Language Models[J/OL]. arXiv preprint arXiv:2210.03629, 2022.<br/>[21] Schick T, Dwivedi-Yu J, Dessì R, et al. Toolformer: Language Models Can Teach Themselves to Use Tools[J/OL]. arXiv preprint arXiv:2302.04761, 2023.<br/>[22] Chhikara P, Khant D, Aryan S, et al. Mem0: Building Production-Ready AI Agents with Scalable Long-Term Memory[J/OL]. arXiv preprint arXiv:2504.19413, 2025.<br/>[23] Patel D, Patel S. ENGRAM: Effective, Lightweight Memory Orchestration for Conversational Agents[J/OL]. arXiv preprint arXiv:2511.12960, 2025.<br/>[24] Rasmussen P, Paliychuk P, Beauvais T, et al. Zep: A Temporal Knowledge Graph Architecture for Agent Memory[J/OL]. arXiv preprint arXiv:2501.13956, 2025.<br/>[25] Wu D, Wang H, Yu W, et al. LongMemEval: Benchmarking Chat Assistants on Long-Term Interactive Memory[J/OL]. arXiv preprint arXiv:2410.10813, 2024.<br/>[26] Maharana A, Lee D-H, Tulyakov S, et al. Evaluating Very Long-Term Conversational Memory of LLM Agents[J/OL]. arXiv preprint arXiv:2402.17753, 2024.<br/>[27] Karpas E, Abend O, Belinkov Y, et al. MRKL Systems: A modular, neuro-symbolic architecture that combines large language models, external knowledge sources and discrete reasoning[J/OL]. arXiv preprint arXiv:2205.00445, 2022.<br/>[28] Model Context Protocol. Specification[EB/OL]. <a href="https://modelcontextprotocol.io/specification/">https://modelcontextprotocol.io/specification/</a> (Current: 2025-06-18) [2026-02-09].<br/>[29] Wang G, Xie Z, Jiang Y, et al. Voyager: An Open-Ended Embodied Agent with Large Language Models[J/OL]. arXiv preprint arXiv:2305.16291, 2023.</p></div><p style="text-align:right"><a href="https://xnnehang.top/posts/default/tmp#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://xnnehang.top/posts/default/tmp</link><guid isPermaLink="true">https://xnnehang.top/posts/default/tmp</guid><dc:creator><![CDATA[xnne]]></dc:creator><pubDate>Mon, 09 Feb 2026 13:22:09 GMT</pubDate></item><item><title><![CDATA[单图≠多图：多图理解时 VLM 为什么更容易“胡说”，以及一个两阶段(Map-Reduce)解法]]></title><description><![CDATA[<link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260207221024.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260207221150.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260207221212.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260207221341.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260206193918.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260208173631.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260208173736.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260208175113.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260208175258.png"/><div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://xnnehang.top/posts/default/vlm_multi_images_inference">https://xnnehang.top/posts/default/vlm_multi_images_inference</a></blockquote><div><blockquote><p>由于整篇过长加个摘要：这篇文章起源于我发现在多图分析时，网页端的表现和 API 调用的结果一致性相差很大，然后我就一步步去拆为什么相差大，然后试图在工程上找补救的过程。<br/>方法很简单，放在这里希望能帮到一些后来的人减少困惑和时间消耗。<br/></p></blockquote>
<h2 id="">前情提要</h2><p>在几周前的一个课设, 课题是关于电池的缺陷检测和自动分拣.</p><p>当时的电池总样本只有不到十根, 缺陷主要体现是外皮缺失.</p><p>因为当时样本很少应该没法通过常规方法练出来能区分是否缺陷的模型, 不管是图像分类还是 yolo 什么的.而课设里推荐方法是用边缘检测和 opencv 来做.</p><p>一方面我不太熟悉 opencv 而且它的 typing 让我感到痛苦, 于是乎我想到了 yolo 定位电池的位置和坐标.（可以通过矩形框定位中心点, 顺逆时针旋转 45° 并比较矩形框长宽比来确定电池朝向和大致角度, 这是为了方便判断抓取的位置和抓手角度）, 然后用 VLM (Vision-Language-Models) 即多模态模型来判断模型的是否缺陷和缺陷类型.</p><p>起初在 Chatgpt, Gemini 那边上传了几组各种光照下的电池图像, 它们都在识别检测的过程中达到了惊人的 100% 的准确率, 而且多次回复一致性很高.即便一次上传六七张 (包括一张完好电池的参考, 至于缺陷都是用提示词描述的) 也是如此.</p><p>我当时想着哇, 这一整周的课设不是一个早上就做完了.</p><p>于是乎我还到 Ollama 那边下了一批 VLM 本地部署测试, 一方面是提高 &quot; 工作量 &quot;, 另一方面也是为了更低的延迟.</p><h3 id="---api-">反转 | 网页端和 API 直调差距巨大</h3><p>但是不测不知道, 一测吓一跳.</p><p>模型不知道是没法理解我的提示词, 还是没法理解我发的图像.我这次测出来不管是准确率还是一致性都非常的低, 准确率在 50% 左右, 而且还经常前后矛盾, 因为我的电池也只有两类标签, 我在想着好家伙, 这就是胡乱作答.</p><p>但是我把图像单图发给 VLM 进行描述, 我发现和网页端的 Chatgpt 的差距也不是肉眼可见的那么大.</p><p>我在发单图和多图的时候, 都是把图像塞到</p><pre class="language-python lang-python"><code class="language-python lang-python">OpenAIMessage (role=&quot; user &quot;,
                            content=[
                            {&quot; type &quot;:&quot; text &quot;,&quot; text &quot;:&quot; user_prompt &quot;},
                            {&quot; type &quot;:&quot; image_url &quot;,&quot; image_url &quot;:&quot;&#x27; data: image/png;base64, iVBORw...&#x27;&quot;}
                            ...(images)])</code></pre><p>这个时候我想, 可能真是模型差距, 于是乎我又改用 gemini-flash-2.5, chatgpt-5.1-chat 之类的模型进行了一番测试 (API 调用).</p><p>但是即便使用了相同的模型, 它们和我在网页端得到的准确率也相差非常大, 而且图超出四张的时候, 一致性也开始下降.</p><p>而我后面跑去稍微调研了下, 发现它和 Lost in the Middle 描述的长上下文检索/位置偏置问题非常相似.</p><p>而网页端 API 差距这么大可能是因为:</p><ul><li>网页端可能做了 <strong>逐图预摘要 / rerank / 选择性投喂</strong></li><li>网页端可能有 <strong>更强的系统提示词与格式约束</strong>(比如强制输出 JSON, 强制逐图作答)</li><li>API 侧的参数 (temperature, max_output, tool choice, 并发顺序) 可能也影响一致性</li></ul><h2 id="">大模型的多图理解能力≠单图理解能力</h2><p><a href="https://arxiv.org/html/2407.15272">MIBench: Evaluating Multimodal Large Language Models over Multiple Images</a> [1]</p><p><a href="https://aclanthology.org/2025.acl-long.214.pdf">Towards Text-Image Interleaved Retrieval</a> [2]</p><p><a href="https://arxiv.org/abc/2408.02718">MMIU: Multimodal Multi-image Understanding for Evaluating Large Vision-Language Models</a> [3]</p><p><a href="https://arxiv.org/html/2406.12742v1">Benchmarking Multi-Image Understanding in Vision and Language Models: Perception, Knowledge, Reasoning, and Multi-Hop Reasoning</a> [4]</p><p>多图 benchmark (如 MIBench/MMIU/MIRB) 一致表明: 模型从单图到多图会出现显著性能下滑与关系理解困难;同时有工作明确指出多模态场景会遭遇 <strong>视觉 token 过多</strong> 的工程瓶颈, 需要压缩.</p><hr/><p>在机制上, 一个合理解释是: 当多张图的视觉 token 与文本共同进入同一 Transformer 上下文时, 会放大长上下文的检索困难与位置偏置 (例如 Lost in the Middle 所揭示的 &quot; 中间信息更难被利用 &quot;).[这只是我的推测]</p><h2 id="-imageurl-base64-">模型如何接受 image_url (base64) 并推理</h2><p>论文里叽里咕噜得看得有点绕但都在说 token 过多, 这里我其实好奇, 这样一个 Message:</p><pre class="language-python lang-python"><code class="language-python lang-python">OpenAIMessage (role=&quot; user &quot;,
                            content=[
                            {&quot; type &quot;:&quot; text &quot;,&quot; text &quot;:&quot; user_prompt &quot;},
                            {&quot; type &quot;:&quot; image_url &quot;,&quot; image_url &quot;:&quot;&#x27; data: image/png;base64, iVBORw...&#x27;&quot;}
                            ...(images)])</code></pre><p>它在进入模型后, text (文本) 和 image_url (图像) 之间的 token 有什么联系, 有什么隔离方式.</p><p>因为虽然论文里一直提 token 过多, token 过多, 但是 token 之间有什么区别, 是直接拼在一起, 还是说多模态大模型对图像和文本进行了不同方式的推理.</p>
<h3 id="base64-image-">base64 image 并不直接进行推理</h3><p>image_url 之所以使用 base64 只是方便 http 传输, 而模型在推理时, 会将 base64 解码为图像.</p><p>也就是说, VLM 看到的实际上是 user prompt + 图像.</p><h3 id="-token-">图像如何被处理缩放 (token 计量)</h3><p><a href="https://platform.openai.com/docs/guides/images-vision">https://platform.openai.com/docs/guides/images-vision</a></p><p>参见 <code>Calculating costs</code></p><p>这里的例子是 gpt-4o 和 gpt-4.1-mini</p><p>1.Tile-based (gpt-4o/4.1/4.5 等): detail=&quot; low &quot; 是固定 base token;detail=&quot; high &quot; 先等比缩放到 &quot; 最长边 ≤2048, 短边=768 &quot;, 然后按 512×512 的 tile 数计费: tokens = base + tile_tokens * tiles.</p><p>2.Patch-based (gpt-4.1-mini/nano, o4-mini): 按 32×32 patch 覆盖图像计数, 超过 1536 patch 就等比缩小到不超过为止, 再乘模型倍率.</p>
<table><thead><tr><th>模型家族 (示例)</th><th>单元</th><th style="text-align:right">单元尺寸</th><th style="text-align:right">单位成本 (最关心的)</th><th style="text-align:right">固定成本</th><th>总公式 (图像输入 tokens)</th></tr></thead><tbody><tr><td>Tile-based (gpt-4o / gpt-4.1 / gpt-4.5)</td><td>tile</td><td style="text-align:right">512×512</td><td style="text-align:right">170 tokens / tile</td><td style="text-align:right">85 tokens / image (low 也只收这个)</td><td>high: tokens = 85 + 170 * tiles<br/>low: tokens = 85</td></tr><tr><td>Patch-based (gpt-4.1-mini)</td><td>patch</td><td style="text-align:right">32×32</td><td style="text-align:right">≈ 1.62 tokens / patch (最终整体上取整)</td><td style="text-align:right">0</td><td>patches = ceil (w/32) <em> ceil (h/32), 若 &gt;1536 则等比缩小到 ≤1536<br/>tokens = ceil (patches </em> 1.62)</td></tr></tbody></table><hr/><table><thead><tr><th>模型</th><th style="text-align:right">输入尺寸</th><th>detail</th><th>中间量 (tiles/patches)</th><th>计算式 (带等号)</th><th style="text-align:right">最终图像 tokens</th></tr></thead><tbody><tr><td>gpt-4o</td><td style="text-align:right">1024×1024</td><td>low</td><td>-(low 固定)</td><td>= 85</td><td style="text-align:right">85</td></tr><tr><td>gpt-4o</td><td style="text-align:right">1024×1024</td><td>high</td><td>缩到 768×768;tiles = ceil (768/512)*ceil (768/512) = 2*2 = 4</td><td>= 85 + 170*4 = 85 + 680 = 765</td><td style="text-align:right">765</td></tr><tr><td>gpt-4o</td><td style="text-align:right">2048×4096</td><td>high</td><td>先缩到 1024×2048;再缩到 768×1536;tiles = ceil (768/512)*ceil (1536/512) = 2*3 = 6</td><td>= 85 + 170*6 = 85 + 1020 = 1105</td><td style="text-align:right">1105</td></tr><tr><td>gpt-4.1-mini</td><td style="text-align:right">1024×1024</td><td>-</td><td>patches = ceil (1024/32)*ceil (1024/32) = 32*32 = 1024</td><td>= ceil (1024*1.62) = ceil (1658.88) = 1659</td><td style="text-align:right">1659</td></tr><tr><td>gpt-4.1-mini</td><td style="text-align:right">1800×2400</td><td>-</td><td>文档算得缩放后 patches = 1452</td><td>= ceil (1452*1.62) = ceil (2352.24) = 2353</td><td style="text-align:right">2353</td></tr></tbody></table><h3 id="-vision-token">像素是怎么变成 vision token</h3><p>emmmm</p><p>这个 openai 这边是没公开的.往后这些有点抽象, 这里是一些 Chatgpt 提供的开源 VLM 的方案.</p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260207221024.png" alt="image to token" height="1047" width="1472"/></p><h3 id="vision-token--prompt-token-">vision token 如何与 prompt token 拼接或隔离?</h3><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260207221150.png" alt="拼接派"/>
<img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260207221212.png" alt="交叉注意力注入 (对我来说有一丢丢超纲了)"/>
<img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260207221341.png" alt="瓶颈查询"/></p>
<p><strong>拼接派</strong>: 最简单, 端到端, 佛系地让大模型自己做决定, 但上下文长度被视觉 tokens 吃掉, 多图时更容易 &quot; 注意力摊薄 &quot;.</p><p>而且它面临和长上下文一致的问题, 就是注意力摊薄的同时还会不均.另外它进一步加速了上下文的长度增长.</p><p><strong>交叉注意力</strong>: 把视觉当外部 memory, 文本按需查询, 工程上更容易控制 &quot; 视觉信息预算 &quot;, 更适合长序列/多图;但增加模块与训练复杂度.</p><p>查询不确定性让我们很难知道模型是不是真的看到那张图像了.所以它其实本质问题和上面那个一样, 上面是可能被模型选择性忽略, 这边是可能没被查询.</p><p><strong>查询瓶颈</strong>: 看得不是很懂..</p><h2 id="">实际应用中的难点</h2><p>实际上落地时, 多图的任务很棘手, 一方面是图像<strong>数量不确定</strong>, 另一方面是图像之间的<strong>关系不确定</strong>, 第三方面是图像和<strong>用户具体的多图任务需求和指代不确定</strong>.</p><p>而这其中的每一个都是对 token 限制和注意力分配的考验.</p><h3 id="">数量不确定</h3><p>第一个场景, 可能会有用户一次输入十几张图像炸 token 来的 (假设它没超过单次输入输出的上限), 图像多的情况下, 单次推理分给各个图像的注意力就变少了, 而且还不确定是怎么分的, 有的图像可能压根不被 &quot; 注意 &quot; 到.</p><p><strong>多图分走了注意力, 而在实际应用中会被多少张图争抢注意力, 我们压根不知道, 或者说在设计时就应该考虑可以接受任意数量输入.</strong></p><blockquote><p>当然现在单纯靠 VLM 的推理是做不到这点的.可以在后面的再战电池检测里看到，超出单次 token 的图像直接被截断了。</p></blockquote>
<h3 id="">关系不确定</h3><p>第二个场景, 比如一个选择困难的用户输入了一批很多角色的图像, 同一个角色的图像各有两张, 并且希望比较下挑选出一张更心仪的.那么这些图像两两之间耦合度极高, 但是模型只认识到这些图片之间存在这样的两两关系, 但是可能并不知道是哪些如果数量更多, 它不太可能一次性匹配出来所有的相似角色.(如果图像少, 模型可能会认识到, 这是同一个角色, 但是如果多且多类的话, 除非有工作流在中间插入一步提取相同角色不然是办不到的).</p><p><strong>有时图像之间存在耦合, 而我们希望能够引导模型注意到这种耦合, 而不是直接一坨丢进去让他自己想.</strong></p><h3 id="">具体任务不确定</h3><p>第三个场景, 很多时候任务并不是直接写在当前这次消息, 而是要根据整个上下文去做分析的.</p><p>比如说在玩角色扮演时:</p><pre class="language-txt lang-txt"><code class="language-txt lang-txt">
U:&quot; 你是不是不喜欢吃蘑菇?&quot;

A:&quot; 是的, 如果你敢在汤里加, 我会让你知道什么是后悔.&quot;

U:&quot; 那你看这是什么 [一张正在烹饪的图, 里面有很多食材, 其中有蘑菇, 但混杂在其中稍微有点扎眼].&quot;</code></pre><p>很多时候<strong>谜题</strong>并不是摆在台面的, 比如 &quot; 请找出图中的蘑菇 <code>doge</code>&quot;.</p><p>更多的情况下, 我碰到的模型它的回复里不会过于关联整个上下文, 而是把注意力权重几乎全部地分配给了用户最近的一个指令里 (类似于请描述一下, 看一下), 然后模型通常会开始滔滔不绝地描述整个场景.可能是因为训练出来的偏好.</p><p>而不是像我们理想中那样 <code>嗯?我好像看到蘑菇了?你放蘑菇了对吧, 你放了对吧!</code>.</p><h2 id="">如何应对</h2><p>针对数量不确定, 关系不确定, 需求不确定还真有一个比较简单的解法, 它不用动模型推理, 可以简单地套用在应用里.</p><p>即把模型的图像分析和文本分析真正分成两步.</p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260206193918.png" alt="逐图分析"/></p>
<p>对应逐图分析模式的分支, 之所以分开, 是为了 token 考虑, 应该把它设计为可以开关的.</p><p>第一步, 只以一个 vision model 来分析提取图像的具体信息, 注意, 这个时候是不给用户最近的提示词的, 而是把整个分析抽离出来, 只给 vision model 一个系统提示词, 和一个固定的抽取指令作为用户提示词输入, 旨在得到类似这样的一个 json:</p><pre class="language-json lang-json"><code class="language-json lang-json">{
    &quot; scene &quot;: &quot; VS Code 全屏显示代码与终端 &quot;,
    &quot; key_items &quot;: [
        {&quot; type &quot;:&quot; app &quot;,&quot; label &quot;:&quot; VS Code &quot;,&quot; detail &quot;:&quot; 深色主题, 全屏窗口 &quot;},
        {&quot; type &quot;:&quot; ui &quot;,&quot; label &quot;:&quot; 文件树 &quot;,&quot; detail &quot;:&quot; 左侧资源管理器展开多个目录 &quot;},
        {&quot; type &quot;:&quot; code &quot;,&quot; label &quot;:&quot; Python 代码 &quot;,&quot; detail &quot;:&quot; 中间编辑区显示 async 相关函数 &quot;}
    ],
    &quot; visible_text &quot;:[&quot; run_tool_loop &quot;,&quot; ToolTrace &quot;,&quot; vision__screen_shot &quot;],
    &quot; ui_hints &quot;:[&quot; 顶部有多个文件标签 &quot;,&quot; 底部有终端输出日志 &quot;],
    &quot; uncertainty &quot;:[&quot; 部分文件名过小, 无法确认完整拼写 &quot;]
}</code></pre><p>每次执行一张图像特征提取, 并发处理, 处理完成后就拼成一个 list 或者 dict, &quot; p1 &quot;, &quot; p2 &quot; 这样的.</p><p>第二步, 就是把这整个拼凑过后的 vision summaries 和用户提示词放在一起类似这样:</p><pre class="language-text lang-text"><code class="language-text lang-text">[User Prompt]
...
[Vision Summaries]
{&quot; p1 &quot;...
 &quot; p2 &quot;...}</code></pre><p>以及, 最好也带上所有的原图, 一起推给大模型.[当然是在图像不是很多 (比如小于五张) 的情况下, 如果超过十张, 感觉发和没发没什么区别, 发了也用不到].</p><p>而在这两步中间可以做的工作还有挺多, 比如根据 json 某个 key 来做区分, 或者耦合.</p>
<h3 id="">优点</h3><p>它不在乎输入的图片有几张, 二三十张进来理论上也是可以的.而且进一步这些 json 可以直接作为模型的看图的一个引导, 退一步模型可以直接根据这些图片来分析图片的简要内容.</p><p>它可以通过提示词自定义一些 json key, 来做耦合或者区分, 可以分清楚图片关系.</p><p>它把原本显得更重, 更 hard 的多图分析任务, 变成了一个可以依赖文本来做回答的文本理解任务, 或者说提供了参考.它让图像占比变小了, 文本占比变大了, 让模型更多地把味文字, 也就更有可能会注意到整个上下文, 也就更有可能回答出那句 <code>嗯?我好像看到蘑菇了?你放蘑菇了对吧, 你放了对吧!</code>.</p><p>它可以通过提示词定制化一些任务, 比如说, 需要电池缺陷图像分析的任务这种比较细致的任务, 就可以让模型在做 summary 时更多分析细节, 表面.</p><h3 id="">缺点</h3><p>贵, 不是一般的贵.每张图都要单独做一次提取, 后多图还要再发一次.而提升还得图越多越明显, 一两张图的反而没必要这么做.所以这个方法不应该被作为一个默认方法, 而是一个额外的可开启的选项.</p><p>慢, 即使用了并发, 它也比直接对话要多出至少一轮的对话回复时长.</p><h2 id="">再战电池检测:</h2><p>逻辑是准备十张图片，实际上是五张复制成两份。</p><p>然后每次输入比上次多一张进行测试。</p><p>测试的模型是: gpt-5.1-2025-11-13</p><h3 id="-image-content-">直接把发给 image content 发给模型。</h3><p>测试代码:</p><pre class="language-python lang-python"><code class="language-python lang-python">from __future__ import annotations

import base64
from openai import OpenAI
from lab.config_manager import XnneHangLabSettings, load_settings_file


def image_to_base64(path: str) -&gt; str:
    with open(path, &quot;rb&quot;) as f:
        return base64.b64encode(f.read()).decode(&quot;utf-8&quot;)


def main():
    # 读取配置
    settings = load_settings_file(&quot;lab.toml&quot;, XnneHangLabSettings)
    base_url = settings.agent.llm.oaipro.llm_base_url
    api_key = settings.agent.llm.oaipro.llm_api_key
    model_name = settings.agent.vision_model.llm_model_name

    client = OpenAI(
        base_url=base_url,
        api_key=api_key
    )

    # 图片路径（示例）
    image_paths = [
        &quot;pic/1.jpg&quot;,
        &quot;pic/2.jpg&quot;,
        &quot;pic/3.jpg&quot;,
        &quot;pic/4.jpg&quot;,
        &quot;pic/5.jpg&quot;,
        &quot;pic/6.jpg&quot;,
        &quot;pic/7.jpg&quot;,
        &quot;pic/8.jpg&quot;,
        &quot;pic/9.jpg&quot;,
        &quot;pic/10.jpg&quot;,
    ]
    print(&quot;标准答案:p1完整 p2完整 p3破损 p4完整 p5破损 p6完整 p7完整 p8破损 p9完整 p10破损&quot;)
    for i in range(10):
        image_contents = []
        for path in image_paths[:i+1]:
            image_contents.append({
                &quot;type&quot;: &quot;image_url&quot;,
                &quot;image_url&quot;: {
                    &quot;url&quot;: f&quot;data:image/jpeg;base64,{image_to_base64(path)}&quot;
                }
            })

        prompt_text = &quot;&quot;&quot;请检查上传图像中电池哪些紫色外皮不完整导致金属边缘部分裸露, 
        回复格式示例：
        p1完整/破损
        p2完整/破损
        ...&quot;&quot;&quot;

        response = client.chat.completions.create(
            model=model_name,
            messages=[
                {
                    &quot;role&quot;: &quot;user&quot;,
                    &quot;content&quot;: [
                        {
                            &quot;type&quot;: &quot;text&quot;,
                            &quot;text&quot;: prompt_text
                        },
                        *image_contents
                    ]
                }
            ],
            temperature=0
        )
        print(f&quot;输入图片数量：{len(image_paths[:i+1])}&quot;)
        print(response.choices[0].message.content)


if __name__ == &quot;__main__&quot;:
    main()</code></pre><p>输出：</p><pre class="language-shell lang-shell"><code class="language-shell lang-shell">(xnnehanglab) PS D:\tmp\XnneHangLab&gt; uv run .\tmp.py
标准答案:p1完整 p2完整 p3破损 p4完整 p5破损 p6完整 p7完整 p8破损 p9完整 p10破损
输入图片数量：1
p1完整
输入图片数量：2
p1完整  
p2完整
输入图片数量：3
p1完整  
p2完整  
p3破损
输入图片数量：4
p1完整  
p2完整  
p3破损  
p4破损
输入图片数量：5
p1完整  
p2完整  
p3破损  
p4完整  
p5破损
输入图片数量：6
p1完整  
p2完整  
p3破损  
p4破损  
p5破损
输入图片数量：7
p1完整  
p2完整
输入图片数量：8
p1完整  
p2完整
p3破损
输入图片数量：9
p1完整  
p2完整
p3破损
p4破损
输入图片数量：10
p1完整  
p2完整
p3破损
p4破损
p5破损</code></pre><p>可以看到在超过三张后它的一致性就开始出现问题了。</p><p>五张输入时恰好全部蒙对，但是超过五张时发现模型压根不给其他的图片的回复，推测可能超出模型单次输入 token 上限被截断了。</p><h3 id="-map-reduce-">应用 map-reduce 方法</h3><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260208173631.png" alt="5 input"/></p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260208173736.png" alt="10 input"/></p><p>可能这里有我是不是调试多次才得到一致性的嫌疑，但是我真的可以不用调试。</p><p>理由如下：</p><p>它在用户输入里面就得到了答案:</p><pre class="language-json lang-json"><code class="language-json lang-json">(xnnehanglab) PS D:\tmp\XnneHangLab&gt; uv run .\tmp.py
[Task / User Prompt]
请检查上传图像中电池哪些紫色外皮不完整导致金属边缘部分裸露,
        回复格式示例：
        p1完整/破损

###

[Tool Call Summary]
(无)

###

[Tool Call Image Summary]
本次并未回调图片。

###

[User Upload Image Summary]
以下是视觉模型对用户上传图片内容的信息：
{&quot;id&quot;: &quot;p1&quot;, &quot;summary&quot;: &quot;{
  &quot;scene&quot;: &quot;白色托盘中放置一节紫色圆柱电池&quot;,
  &quot;key_items&quot;: [
    {
      &quot;type&quot;: &quot;object&quot;,
      &quot;label&quot;: &quot;电池本体&quot;,
      &quot;detail&quot;: &quot;单节圆柱形电池，紫色外皮包覆表面&quot;
    },
    {
      &quot;type&quot;: &quot;object&quot;,
      &quot;label&quot;: &quot;电池正负极边缘&quot;,
      &quot;detail&quot;: &quot;两端金属边缘被紫色外皮完全包住，看不到裸露金属圈&quot;
    },
    {
      &quot;type&quot;: &quot;object&quot;,
      &quot;label&quot;: &quot;电池外皮状态&quot;,
      &quot;detail&quot;: &quot;外皮表面平整，无明显破损、撕裂或缺口&quot;
    }
  ],
  &quot;visible_text&quot;: [],
  &quot;ui_hints&quot;: [],
  &quot;uncertainty&quot;: [
    &quot;电池两端极柱正面细节因拍摄角度看不全，仅能确认圆柱侧面和边缘无明显金属裸露&quot;
  ]
}&quot;}
{&quot;id&quot;: &quot;p2&quot;, &quot;summary&quot;: &quot;{
  &quot;scene&quot;: &quot;白色背景上的一节紫色圆柱电池&quot;,
  &quot;key_items&quot;: [
    {
      &quot;type&quot;: &quot;object&quot;,
      &quot;label&quot;: &quot;电池整体&quot;,
      &quot;detail&quot;: &quot;单节紫色外皮18650电池，放置在白色表面中央偏右&quot;
    },
    {
      &quot;type&quot;: &quot;object&quot;,
      &quot;label&quot;: &quot;电池正极端面&quot;,
      &quot;detail&quot;: &quot;正极金属端面外圈被紫色套管包裹，未见侧向金属裸露&quot;
    },
    {
      &quot;type&quot;: &quot;object&quot;,
      &quot;label&quot;: &quot;电池负极端面&quot;,
      &quot;detail&quot;: &quot;负极底部边缘被紫色套管完全覆盖，未见金属边缘外露&quot;
    }
  ],
  &quot;visible_text&quot;: [
    &quot;IMR18650&quot;,
    &quot;1200mAh 4.44Wh 3.7V&quot;
  ],
  &quot;ui_hints&quot;: [
    &quot;仅有单个电池目标，无多电池对比&quot;,
    &quot;电池略倾斜放置，长轴大致左下到右上方向&quot;,
    &quot;两端边缘均清晰可见，可判断是否有金属裸露&quot;
  ],
  &quot;uncertainty&quot;: [
    &quot;无法查看电池另一侧被桌面遮挡的极小部分包膜，但当前可见区域无破损&quot;
  ]
}&quot;}
{&quot;id&quot;: &quot;p3&quot;, &quot;summary&quot;: &quot;{
  &quot;scene&quot;: &quot;白色托盘中放置一节紫色电池&quot;,
  &quot;key_items&quot;: [
    {
      &quot;type&quot;: &quot;object&quot;,
      &quot;label&quot;: &quot;电池本体&quot;,
      &quot;detail&quot;: &quot;圆柱形电池，紫色外皮包覆，大部分表面光滑无破损&quot;
    },
    {
      &quot;type&quot;: &quot;object&quot;,
      &quot;label&quot;: &quot;正极端部&quot;,
      &quot;detail&quot;: &quot;金属帽及周围金属边缘完全裸露，无紫色外皮覆盖到此区域&quot;
    },
    {
      &quot;type&quot;: &quot;object&quot;,
      &quot;label&quot;: &quot;侧面包胶边缘&quot;,
      &quot;detail&quot;: &quot;靠近正极一端的紫色外皮在金属边缘处结束，看不出撕裂或缺口&quot;
    }
  ],
  &quot;visible_text&quot;: [],
  &quot;ui_hints&quot;: [],
  &quot;uncertainty&quot;: [
    &quot;正极附近金属裸露区域是否应被视为“外皮破损”取决于设计要求，图中仅能看到该处无包胶但无明显撕裂&quot;,
    &quot;远端（负极一端）电池顶面因角度原因不可见，无法确认该端外皮是否有破损&quot;
  ]
}&quot;}
{&quot;id&quot;: &quot;p4&quot;, &quot;summary&quot;: &quot;{
  &quot;scene&quot;: &quot;白色托盘中放置一节紫色电池&quot;,
  &quot;key_items&quot;: [
    {
      &quot;type&quot;: &quot;object&quot;,
      &quot;label&quot;: &quot;紫色电池外皮&quot;,
      &quot;detail&quot;: &quot;电池整段紫色塑料外皮包覆，无明显破损缺口&quot;
    },
    {
      &quot;type&quot;: &quot;object&quot;,
      &quot;label&quot;: &quot;电池正极端部&quot;,
      &quot;detail&quot;: &quot;正极金属端面外圈被紫色外皮包住，看不到金属侧边外露&quot;
    },
    {
      &quot;type&quot;: &quot;object&quot;,
      &quot;label&quot;: &quot;电池负极端部&quot;,
      &quot;detail&quot;: &quot;负极金属端面外圈同样被紫色外皮覆盖，无裸露金属边缘&quot;
    },
    {
      &quot;type&quot;: &quot;text&quot;,
      &quot;label&quot;: &quot;电池印刷文字区域&quot;,
      &quot;detail&quot;: &quot;文字区域外皮平整，无破洞、撕裂或卷边&quot;
    }
  ],
  &quot;visible_text&quot;: [
    &quot;WARNING&quot;,
    &quot;Do not dispose of in fire&quot;,
    &quot;Do not short circuit&quot;
  ],
  &quot;ui_hints&quot;: [
    &quot;画面中央略偏下为单节圆柱电池，斜放于托盘中&quot;,
    &quot;仅一节电池需要判断外皮完整性&quot;
  ],
  &quot;uncertainty&quot;: [
    &quot;电池两端极小范围细节因分辨率限制可能存在轻微磨痕但未见明显破皮&quot;,
    &quot;托盘边缘及电池远离镜头一侧细微划痕是否为外皮划伤不完全确定，但无金属裸露&quot;
  ]
}&quot;}
{&quot;id&quot;: &quot;p5&quot;, &quot;summary&quot;: &quot;{
  &quot;scene&quot;: &quot;白色托盘中放置一节紫色电池&quot;,
  &quot;key_items&quot;: [
    {
      &quot;type&quot;: &quot;object&quot;,
      &quot;label&quot;: &quot;电池整体&quot;,
      &quot;detail&quot;: &quot;一节圆柱形紫色电池，位于托盘中央偏上&quot;
    },
    {
      &quot;type&quot;: &quot;object&quot;,
      &quot;label&quot;: &quot;电池紫色外皮&quot;,
      &quot;detail&quot;: &quot;电池圆柱侧面紫色包覆层基本完整，无明显破损露底&quot;
    },
    {
      &quot;type&quot;: &quot;object&quot;,
      &quot;label&quot;: &quot;电池金属边缘&quot;,
      &quot;detail&quot;: &quot;靠近正极一端银色金属顶盖与边缘可见，圆柱侧壁未见金属外露&quot;
    }
  ],
  &quot;visible_text&quot;: [
    &quot;IMR18650&quot;,
    &quot;3.7V&quot;
  ],
  &quot;ui_hints&quot;: [
    &quot;画面中仅有一节电池需要判定&quot;,
    &quot;应重点关注电池两端边缘与圆柱侧壁是否有金属裸露&quot;,
    &quot;当前视角侧壁紫皮连续，看不出明显破洞或大面积缺口&quot;
  ],
  &quot;uncertainty&quot;: [
    &quot;电池正负极最外圈与背面不在视野内，无法确认是否有细小破损或金属裸露&quot;,
    &quot;图像分辨率有限，难以发现极小划痕级别的破皮&quot;
  ]
}&quot;}</code></pre><p>对于它来说，这个任务本来就是单条的任务，所以无论有几条，以及图片是否被截断，对它的影响都不是太大。当然如果分析关联任务，被截断还是很难受的。</p><p>以及确实也有个比较大的问题就是 token 消耗和上下文增长速度过快的问题。并发单张已经是不小的消耗了。而把输出的结果整合进原本的上下文会让上下文增长速度非常快，让长上下文的注意力瓶颈问题更早的出现。</p><h3 id="">我用的提示词</h3><pre class="language-text lang-text"><code class="language-text lang-text">你是一个“视觉证据抽取器”(Vision Extractor)，负责从输入的图片中提取与用户问题相关的事实/证据。
你不需要写最终的自然语言答案；最终口语化回答会由另一个 Chat Model 生成。
你的目标是：用尽可能短、可复用、可机器消费的结构化输出，准确描述图片中与问题相关的信息，并明确不确定性。

【输入组成】
- 用户问题：一段 text（可能很短、可能带指代）
- 图片：image_url（data URL 或其他方式提供）
- （可选）TOOL_TRACE 摘要：其他工具返回的结构化结果/网页预览等

【关键原则】
1) 只描述“图片里能看到的内容”
- 不要猜测图片之外的信息（比如电脑型号、用户身份、未显示的窗口内容）。
- 不要编造看不清的文字或细节；看不清就明确写“看不清/不确定”。

2) 面向“下游推理”输出
- 你的输出会被下游 chat model 用来生成最终回复；
- 因此请尽量提供：可验证的事实、可引用的文字片段、UI 结构、显著对象、可能的关键信号（例如报错、按钮、标题、文件名、路径、URL、时间戳等）。

3) 聚焦“与问题相关”
- 如果用户问题明确：优先抽取与问题相关的部分。
- 如果用户问题很泛（如“描述一下桌面”）：给出全局概览 + 3~8 个最显著元素。

4) 控制长度与密度
- 保持输出短：不写长段落，不写角色扮演。
- 重点信息列点或结构化字段，方便下游引用。

5) 隐私与敏感信息处理
- 如果图片包含可能的敏感信息（邮箱、token、密钥、身份证号、银行卡号等），请在输出中用 [REDACTED] 替代敏感字段，但仍保留其“存在性”和“类型”（例如“发现疑似 API key：[REDACTED]”）。

【输出格式（严格要求）】
你必须输出 **严格 JSON**，且只输出 JSON（不要额外解释文本，不要 markdown code fence）。

JSON schema：
{
  &quot;scene&quot;: &quot;一句话概述场景（&lt;= 25 字）&quot;,
  &quot;key_items&quot;: [
    {&quot;type&quot;: &quot;window|app|ui|text|object|error|file|url|code&quot;, &quot;label&quot;: &quot;简短名称&quot;, &quot;detail&quot;: &quot;关键信息（短）&quot;}
  ],
  &quot;visible_text&quot;: [
    &quot;图片中可读的关键文字（每条 &lt;= 80 字，最多 10 条）&quot;
  ],
  &quot;ui_hints&quot;: [
    &quot;对下游有用的界面结构提示（最多 6 条）&quot;
  ],
  &quot;uncertainty&quot;: [
    &quot;你不确定或看不清的点（最多 6 条）&quot;
  ]
}

字段要求：
- scene 必填
- key_items 至少 3 条（除非图片几乎为空）
- visible_text 只放你“确定看清”的文字；看不清不要写
- uncertainty 必须在看不清/不确定时填写，不确定就写明原因（模糊/遮挡/分辨率不足）

【例子（仅说明，不要照抄）】
{
  &quot;scene&quot;: &quot;VS Code 全屏显示代码与终端&quot;,
  &quot;key_items&quot;: [
    {&quot;type&quot;:&quot;app&quot;,&quot;label&quot;:&quot;VS Code&quot;,&quot;detail&quot;:&quot;深色主题，全屏窗口&quot;},
    {&quot;type&quot;:&quot;ui&quot;,&quot;label&quot;:&quot;文件树&quot;,&quot;detail&quot;:&quot;左侧资源管理器展开多个目录&quot;},
    {&quot;type&quot;:&quot;code&quot;,&quot;label&quot;:&quot;Python 代码&quot;,&quot;detail&quot;:&quot;中间编辑区显示 async 相关函数&quot;}
  ],
  &quot;visible_text&quot;:[&quot;run_tool_loop&quot;,&quot;ToolTrace&quot;,&quot;vision__screen_shot&quot;],
  &quot;ui_hints&quot;:[&quot;顶部有多个文件标签&quot;,&quot;底部有终端输出日志&quot;],
  &quot;uncertainty&quot;:[&quot;部分文件名过小，无法确认完整拼写&quot;]
}</code></pre><h3 id="">在关联任务上的表现</h3><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260208175113.png" alt="lin1"/>
<img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260208175258.png" alt="lin2"/></p><blockquote><p>看起来似乎我在 <code>send_text</code> 方面做的有点糟糕，或者说它按照字数截断的方式让我的排版看上去很糟糕。<br/>另外一点，<code>gpt-5.1-2025-11-13</code> 回复得真的有点生硬。奈何 5.2 贵了好多倍。我一般都是 vision fallback, <code>vision model</code> 只在看图的时候调用，由它来生成 summaries 然后交给 chat model，这样我就可以挑一个更有人味的 chat model，同时它可以支持 image or 不支持，决定我是否会把 summaries 附带图像一起送给它。</p></blockquote></div><p style="text-align:right"><a href="https://xnnehang.top/posts/default/vlm_multi_images_inference#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://xnnehang.top/posts/default/vlm_multi_images_inference</link><guid isPermaLink="true">https://xnnehang.top/posts/default/vlm_multi_images_inference</guid><dc:creator><![CDATA[xnne]]></dc:creator><pubDate>Fri, 06 Feb 2026 11:38:21 GMT</pubDate></item><item><title><![CDATA[《放松时光：与你共享 lofi 故事》 给聪音接入 AI Chat]]></title><description><![CDATA[<link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202601061008735.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/202601061011430.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/202601061013417.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/202601061015872.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/PixPin_2026-02-05_13-39-58.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/202601061025541.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/202601061029269.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/202601100432475.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/202601100438203.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/202601100443124.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260127183530.png"/><div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://xnnehang.top/posts/default/chill_ai_chat_mod">https://xnnehang.top/posts/default/chill_ai_chat_mod</a></blockquote><div><p>我个人是非常喜欢番茄钟的。</p><p>而我在 steam 上发现了这样一个游戏：</p><h2 id="">游戏介绍</h2><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202601061008735.png" alt="放松时光：与你共享 lofi 故事" height="952" width="1590"/></p><p>它本质上是一个 番茄钟 + 待办事项 + 笔记 + 日历统计 + Lofi 音乐 + 收集系统 + 变装 的游戏。</p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/202601061011430.png" alt="game window"/></p><p>与之类似的我之前一直用的都是 Spirit City</p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/202601061013417.png" alt="Spirit City:Lofi Sessions"/></p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/202601061015872.png" alt="spirit city game window"/></p><p>不过与 City 不同的一点是，时光加入了一点与角色的一个互动机制，设定是用户与聪音视频通话类似于自习室，随着时间推进，用户会渐渐解锁与聪音之间的剧情，而且这个剧情还和 galgame 一样是选项式的，不知道后面是否存在恋爱线。</p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/PixPin_2026-02-05_13-39-58.png" alt="PixPin_2026-02-05_13-39-58.png"/></p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/202601061025541.png" alt="专注时挑逗她会被嫌弃"/></p><p>而 City 的视角则是让用户试图代入自己就是其中那个角色。</p><p>如果要比较的话，建模风格我无疑更喜欢《时光》的日系风格，而且聪音的配音是日配，神奇的是，她的配音有一种机械感，像是 AI 配音，会让我有一种在和赛博女友自习的感觉，这种 AI 感让我更喜欢了。</p><p>另外 《时光》有一个马克杯的设定，我觉得和我相当契合。作者借着聪音的话提到，她曾在高强度创作时脱水，此后她就非常在意挑选自己喜欢的马克杯，这样自己在写东西的时候也会拿起来喝两下。而我，则是会在打开番茄钟前泡茶，然后边做边喝，饮料反而少了这种仪式感。在这点上作者给了我一种世另我的感觉。</p><p>不过，在音乐的品味上，我更喜欢 City，毕竟我听了几十个小时都没听厌，而且只要一听到就感觉静下来了。而 《时光》 的音乐选取不如 Spirit City  ”Deep“，有种浅浅的感觉，无法进入更深的心流，可能是还没听惯。</p><h2 id="mod-">Mod 介绍</h2><p>喜欢果然是第一创作力，或者说好色？？</p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/202601061029269.png" alt="steam 评测"/></p><p>项目地址：</p><p><a href="https://github.com/qzrs777/AIChat">https://github.com/qzrs777/AIChat</a></p><h2 id="">效果演示</h2><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/202601100432475.png" alt="和聪音自由对话"/></p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/202601100438203.png" alt="月色真美"/></p><p>对话中角色的嘴部会随着声音的响度动，不过幅度不大所以似乎都没截图到。</p><p>另外，角色讲话会有反馈，比如注视你，或者说依然做自己的事情，以及，似乎还喝水等等。</p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/202601100443124.png" alt="喝水"/></p>
<h2 id="mod-">Mod 部署</h2><p>参见 <a href="https://github.com/qzrs777/AIChat">qzrs777/AIChat</a></p>
<p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/26/01/20260127183530.png" alt="image.png"/></p><h3 id="tts-service">TTS Service</h3><p><a href="https://github.com/XnneHangLab/XnneHangLab">XnneHangLab/XnneHangLab</a></p><p>非常简单。</p><pre class="language-shell lang-shell"><code class="language-shell lang-shell">git clone https://github.com/XnneHangLab/XnneHangLab.git --recurse-submodules
cd XnneHangLab
just install-bert-model
just install-gsv-model
just server</code></pre><p>如果 just 和 uv 没安装可以看: <a href="https://github.com/XnneHangLab/XnneHangLab/blob/dev/docs/deploy.md">deploy.md</a></p><p>然后在 Mod 的配置 TTS 地址为:</p><pre class="language-shel lang-shel"><code class="language-shel lang-shel">http://127.0.0.1:12393/tts/gptsovitsv2</code></pre><p>将参考音频改为 elaina.wav 即可。</p><h2 id="mod-">Mod 修改和再编译</h2><p>暂时没做那一步。等我做了再写。</p></div><p style="text-align:right"><a href="https://xnnehang.top/posts/default/chill_ai_chat_mod#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://xnnehang.top/posts/default/chill_ai_chat_mod</link><guid isPermaLink="true">https://xnnehang.top/posts/default/chill_ai_chat_mod</guid><dc:creator><![CDATA[xnne]]></dc:creator><pubDate>Tue, 27 Jan 2026 10:40:24 GMT</pubDate></item><item><title><![CDATA[Learn Alma：Tool Model 和 Chat Model 的分离，以及 Web Fetch 和 Web Search 的编排]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://xnnehang.top/posts/default/learn_alma_part1">https://xnnehang.top/posts/default/learn_alma_part1</a></blockquote><div><p>在开发仓库 <a href="https://github.com/XnneHangLab/XnneHangLab">XnneHangLab/XnneHangLab</a> 的过程中，一直想让 <strong>pet_mode</strong> 下的 Live2D 角色陪伴写代码：盯着进度、指责摸鱼、鞭策按时测试与提交——最好还能“理解当前在做什么”，例如识别当天改动、检测测试是否通过、提醒未完成事项。</p><p>目标听起来偏娱乐，但拆开后其实是一套完整的 Agent 设计问题：<strong>工具如何接入、联网如何编排、记忆如何持续、模型如何切换且成本可控</strong>。</p><p>近期开始研究 <a href="https://alma.now/">Alma</a>。它的架构选择提供了很强的启发：之前踌躇不前的难点，并非“解不开”，而是“没拆开”。下面把目前的思路整理成一篇可落地的笔记（并同步我在 XnneHangLab 里最近几次 PR 的落地进展）。</p><hr/><h2 id="1-tool-model--chat-model-">1. Tool Model 和 Chat Model 分离：不仅隔离上下文，也隔离模型职责</h2><p>过去我的直觉一直是：工具调用的中间步骤（抓网页、读文件、跑命令）产生的上下文应该和正常对话消息做隔离管理，避免噪声进入主对话上下文。</p><p>而 Alma 的启发更进一步：不仅隔离上下文，还把 <strong>模型本身</strong> 做了职责隔离。</p><h3 id="11--tool--chat">1.1 为什么要分两层模型( Tool &amp;&amp; Chat)？</h3><p>一种清晰的分工方式如下：</p><ul><li><strong>Tool Model</strong>：更快、更便宜、更擅长结构化输出（function/tool call），负责</li></ul><p>判断是否需要 tool call, 以及选择需要的 tool function</p><ul><li><strong>Chat Model</strong>：更贵、更强、更擅长推理与表达，负责<br/>整理，表达，比较考验多文档和长文本理解能力。</li></ul><ul><li><strong>成本</strong>：gpt-4.1-mini 就能在合理的上下文下判断出来需要的 tool fn,和 gpt-5.2-chat 有着 10 倍以上的价格差距，但是效果差距却不那么大，甚至短上下文差别不大。</li><li><strong>延迟</strong>：thinking 的模型自带推理链，会让模型回复变慢，而这意味着 tool 每步都变慢，也就是和卡死差不多了，毫无使用体验。</li><li><strong>可靠性</strong>：比起每次换一个 chat model 担心这个没有 function calling 或者能力比较差，不如选取一个共同认可的好的 tool model。而 chat model 就看喜好和预算以及需求了。</li></ul><h3 id="12-system-prompt-">1.2 实践补丁：system prompt 最好分开管理</h3><p>chat_system_prompt 里通常都是一些人格方面的定义比如我喜欢调成这样：</p><pre class="language-shell lang-shell"><code class="language-shell lang-shell">以下是你（伊蕾娜）的设定(英文名：Elaina)的角色设定：

- 身份背景：15岁就取得了魔法使中的最高位「魔女」称号，魔女名为「灰之魔女」。
- 外貌特征：美少女，留着灰白色的长发，披着黑色长袍，戴着魔女帽，胸口别有魔女之证的「星形胸饰」。
- 性格特点：
  - 你知道自己很可爱，而且你喜欢别人夸你可爱，并且被夸时一点不会脸红。
  - 说话辛辣毒舌，但内心温柔，是个老好人。
  - 自信满满，常有自恋的发言与行为，感染力十足。
  - 不擅长下雨天和与有百合兴趣的女孩子相处。
  - 有少量的S性格
- 兴趣喜好：
  - 不喜欢吃菇类食物，最爱吃面包，擅长做炖菜。
  - 对猫过敏，但在某次经历后过敏体质消失。而且你后来很喜欢猫。

你不会直率地透露出你的性格特征和真实想法。不要在对我的回复中直接引用以上的内容。
...

这里只包含了一小部分，实际上我还写了很多，很多，快把她的一辈子写进去了...</code></pre><p>拧巴的复杂的混乱的 system prompt 它导致我的 tool model 性能下降了不只一半，自从我换了一个简约的 system prompt 后 tool call 几乎再也没失败过了。</p><hr/><h2 id="2--provider---provider--model">2. 配置演进：从 “provider 绑定单模型” 到 “provider=连接信息 / model=选择项”</h2><p>现有 <code>lab.toml</code> 的 provider 配置常见形态是把 <code>api_key/base_url/model_name</code> 绑定在一起，例如：</p><pre class="language-toml lang-toml"><code class="language-toml lang-toml">[agent.llm.openai]
llm_api_key = &quot;&quot;
llm_base_url = &quot;https://api.openai.com/v1&quot;
llm_model_name = &quot;gpt-4o&quot;

[agent.llm.gemini]
llm_api_key = &quot;&quot;
llm_base_url = &quot;https://generativelanguage.googleapis.com/v1beta/openai/&quot;
llm_model_name = &quot;gemini-2.5-flash&quot;</code></pre><p>我原本通过 provider 锁定用户使用的 model，比如用户锁定 <code>openai</code>，我就能通过 <code>agent.llm.openai</code> 得到它的 <code>model_name</code>，<code>base_url</code>, <code>api_key</code>。
问题在我拆分 tool chat model 后暴露得也很直接：<strong>一个 provider 只能固定一个 model_name</strong>。<br/>当出现这种组合需求时就无法表达：</p><ul><li>Tool Model：OpenAI 的 <code>gpt-4o-mini</code></li><li>Chat Model：OpenAI 的 <code>gpt-5.2-thinking</code></li></ul><blockquote><p>另外在实际实践中我添加了 vision model 用于 chat model 的 vision fallback。当 chat model 是 text-only 时调用。</p></blockquote>
<h3 id="21-provider--model-name-">2.1 Provider 与 Model Name 选择解耦</h3><p>更推荐的结构是：</p><pre class="language-toml lang-toml"><code class="language-toml lang-toml">[llm.providers.openai]
api_key = &quot;&quot;
base_url = &quot;https://api.openai.com/v1&quot;
api_format = &quot;chat_completions&quot;

[llm.providers.gemini]
api_key = &quot;&quot;
base_url = &quot;https://generativelanguage.googleapis.com/v1beta/openai/&quot;
api_format = &quot;chat_completions&quot;

[agent.models.tool]
provider = &quot;openai&quot;
model = &quot;gpt-4o-mini&quot;

[agent.models.chat]
provider = &quot;openai&quot;
model = &quot;gpt-5.2-thinking&quot;
supports_vision = true  # 用来判断要不要 fallback

[agent.models.vision]
provider = &quot;openai&quot;
model = &quot;gpt-5.1-2025-11-13&quot;

[agent]
enable_tools = true
enable_mcp = true</code></pre><p>关键点：</p><ul><li><code>llm.providers.*</code> 只负责连接信息（base_url / api_key / api_format）</li><li><code>agent.models.*</code> 只负责“当前角色使用哪个 provider 的哪个 model”</li><li>tool/chat/vision 的选择互不绑定，可同源也可异源</li><li><code>api_format</code> 是扩展位：即使当前只支持 <code>chat_completions</code>，也为未来兼容其他格式预留空间</li></ul><p>最后用户填下 <code>provider</code> + <code>model_name</code> ，我就能补全锁定所有需要的内容。</p>
<h3 id="22-literal-modelname">2.2. 静态类型（Literal）能否约束 model_name？大概率不必强求</h3><p>我写了一个 fetch_model_list 来自动 fetch 那些填写了 api key 的 provider 的 model_list。</p><p>后面我考虑 <code>model_name</code>  能不能用静态类型强约束和校验，比如说， model_name 必须在 provider 所提供的 model_list 下。进而实现静态校验。</p><p>但问题是，fetch 得到的是动态类型，即便缓存到文件也无法作为类似于 Literal 来进行约束，进而放弃，保持 str。</p><hr/><h2 id="3-toolmessage-tool-model--toolloop----tool-call">3. 工具系统落地：tool_message Tool Model 应该共享哪些上下文, tool_loop 并行 &amp;&amp; 链式 tool call</h2><p>这块的所有内容都针对 Tool Model 执行。</p><h3 id="31--or-">3.1 流式 or 非流式</h3><p>首先确定的是， Tool Model 的 OpenAI 的调用是非流式的。</p><p>我在 Chat Model 中，因为要做 TTS，为了首句延迟尽量低，所以我就采用了流式回复，并且动态处理 return text 的方式。</p><p>但是对于 Tool Model 流式只会让回复变长，信息聚合难度变高。一直保持非流式才是 Tool Model 的好做法。</p><h3 id="32-tool-model--chat-model-">3.2 Tool Model 应该和 Chat Model 共享哪些消息。</h3><p>Tool Model 是否应该和 Chat Model 共享所有消息，这一想就是否定的，因为 Tool Model 的高频调用，如果 Chat Model 的历史上下文长达几万 token 不仅开销大而且速度慢。</p><p>所以 Tool Model 的上下文一定是简短的，但是简短不意味着无状态（system+user_input=output）。</p><p>它还是需要在需要的时候扩展一些上下文来应对某些情况。比如：</p><pre class="language-shell lang-shell"><code class="language-shell lang-shell">你能看到我现在桌面环境吗？描述一下。
[Tool] call screen_shoot
...
我换环境了，你再描述一次。 # 如果没有上下文， Tool Model 完全不知道要描述的是什么东西，就不会二次调用了。</code></pre><p>它应该动态扩展 5~15 条最近的上下文。</p><h4 id="">如何判定动态扩展</h4><p>至于扩展规则（什么时候该扩展），这个并不是交给模型自己来决策的，如果交给模型自己实在是太吃操作了，能力不一样的模型差距越大，表现和结果差距越大，并不是我们想要的效果，我们希望它尽可能稳定。</p><p>于是乎采用了&quot;语义&quot;+&quot;评分器&quot; 类似这样一个简单的案例:</p><pre class="language-shell lang-shell"><code class="language-shell lang-shell">_CONTEXT_CUES = re.compile(
    r&quot;(继续|刚才|上一个|那个|同样|照之前|按之前|再来一次|你说的|第[二三四五六七八九十]个|前面|如上|同上|this|that|it)&quot;,
    re.IGNORECASE,
)
_CHOICE_CUES = re.compile(r&quot;(第[0-9一二三四五六七八九十]+个|\b[0-9]+\b|\b[A-F]\b)&quot;, re.IGNORECASE)
_CONFIRM_CUES = re.compile(r&quot;^(对|不是|可以|行|好|嗯|OK|okay|yes|no|y|n)\b&quot;, re.IGNORECASE)
_PRONOUN_START = re.compile(r&quot;^(它|他|她|这|那|this|that|it)\b&quot;, re.IGNORECASE)
_HAS_URL = re.compile(r&quot;https?://\S+&quot;, re.IGNORECASE)
_HAS_PATH = re.compile(r&quot;(\.?/[\w\-/\.]+|\w+\.(md|txt|json|toml|yaml|yml|py)\b)&quot;, re.IGNORECASE)


@dataclass(frozen=True)
class ContextDecision:
    dependent: bool
    score: int
    reasons: list[str]


def is_context_dependent(user_text: str) -&gt; ContextDecision:
    t = (user_text or &quot;&quot;).strip()
    if not t:
        return ContextDecision(False, 0, [&quot;empty&quot;])

    score = 0
    reasons: list[str] = []

    # 1) very short
    if len(t) &lt;= 6:
        score += 3
        reasons.append(&quot;very_short&lt;=6&quot;)

    # 2) reference cues
    if _CONTEXT_CUES.search(t):
        score += 2
        reasons.append(&quot;context_cue&quot;)

    # 3) choice / index
    if _CHOICE_CUES.search(t):
        score += 2
        reasons.append(&quot;choice_cue&quot;)

    # 4) confirm-like reply
    if _CONFIRM_CUES.search(t):
        score += 2
        reasons.append(&quot;confirm_reply&quot;)

    # 5) pronoun start
    if _PRONOUN_START.search(t):
        score += 1
        reasons.append(&quot;pronoun_start&quot;)

    # 6) if user already provides concrete target, dependency is lower
    if _HAS_URL.search(t) or _HAS_PATH.search(t):
        score -= 2
        reasons.append(&quot;has_explicit_target&quot;)

    dependent = score &gt;= 3
    return ContextDecision(dependent, score, reasons)</code></pre><p>但是这个方法确实是有效的。但是我能料想到规则变得更复杂后，代码也更难管理和扩展。</p><p>所以其实这块感觉可以采用 Embedding 模型特征分类来做。我不想把它作为一个 mcp_tool，也许可以简单地把它作为一个 tool.prompt，告诉大模型，如果用户这句话里面有需要依赖以前的上下文的迹象，就 return True else False。</p><p>这实际上有点反逻辑，因为这个函数实际上用来管理 tool model 要不要上下文，但是它却去问没有上下文的 tool model 说，你觉得这句话要不要上下文，然后就会陷入我说的那个场景，不同的模型，不同的理解，效果完全不同，难以控制，不一致。</p><h4 id="">有点长，所以来个总结</h4><ul><li>Tool Model 默认只接收：<br/><code>system(tool_router_prompt)</code> <ul><li><code>TOOL_ROUTER_STATE(pinned JSON)</code> <ul><li><code>last user message</code></li></ul></li></ul></li><li>当检测到上下文依赖（短回复/指代词等）时，扩展注入最近窗口（通常 8~15 条，包含上一条 assistant 以支持“第二个/对/不是”）</li><li><code>ConversationState</code> 在每轮用户输入与工具返回后更新 <code>refs/slots</code>：<br/>如 <code>last_url_ok</code> / <code>last_file</code> / <code>last_image_ref</code>，用于指代消解与缺参补全</li><li>引入 resolved refs（可选）：当用户说“上一个链接/那张截图/那个文件”时，直接注入已解析锚点，避免 tool model 猜测与漂移</li></ul><h3 id="33-tool-loop-">3.3 Tool Loop 并行和链式</h3><p>本质上是一个 loop 循环检查有没有 tool call，每次检查会加上上一次 tool call 的 tool_trace。</p><p>类似这样：
<code>shell
q: 你截图看看我桌面上的那个图像，和魔女伊蕾娜像不像
step1: Tool Fn = [vision__screen_shot,tool__web_search]
step2: Tool Fn = [tool__web_fetch]</code></p><p>step1 中调用了截图和 web_search，这个由 user prompt 触发。</p><p>然后 step2 会加入 Tool Trace 判断要不要接着调用， tool trace 是类似这样的集合:</p><pre class="language-shell lang-shell"><code class="language-shell lang-shell">PS D:\tmp\XnneHangLab&gt; uv run .\tmp.py
tool_trace is a list of the following items:

{
    &quot;server&quot;: &quot;timeemi&quot;,
    &quot;name&quot;: &quot;get_date_and_time&quot;,
    &quot;args&quot;: {},
    &quot;raw_result&quot;: {
    &quot;datetime&quot;: &quot;2026-02-05 13:53:10&quot;
},</code></pre><p>比如 Tool Model 从 step1 里得到了 elaina 的图像的网址，就会进一步地 fetch 这个地址尝试得到 elaina 的图像。</p><p>至于模型最后会不会比较还得看提示词。</p><p>我们会把所有的 Tool Trace 放到一个 Tool Summary 中并且和 user prompt 一起发送给模型（以 role = &quot;user&quot; 发送），可能还包含了 Vision Summary。</p><pre class="language-python lang-python"><code class="language-python lang-python"># prompt 主体（四段里前两段用这个做基础）----

base_prompt = &quot;\n\n###\n\n&quot;.join(

    [

        f&quot;[Task / User Prompt]\n{user_input_text}&quot;,

        f&quot;[Tool Call Summary]\n{tools_summary_str}&quot;,

    ]

)</code></pre><h3 id="35-tool-call-result--base64--text-image-url-base64-">3.5 tool call result 里的 base64 炸 text，只能传 image url (base64 发送)</h3><p>另一个很现实的问题：截图/图片一旦把 base64 直接塞回 tool result 文本，你的上下文窗口基本就报废了。</p><pre class="language-python lang-python"><code class="language-python lang-python">return {
    &quot;role&quot;: &quot;user&quot;,
    &quot;content&quot;: [
        {&quot;type&quot;: &quot;text&quot;, &quot;text&quot;: text},
        {&quot;type&quot;: &quot;image_url&quot;, &quot;image_url&quot;: {&quot;url&quot;: f&quot;data:{mime};base64,{b64}&quot;}},
    ],
}</code></pre><p>它让我在 execu_tool 后得再额外检测 ImageResult 然后把它取出缓存，再存索引到 state，然后这次回复和下次需要 <code>last image ref</code> 的适合根据索引从缓存里取出来。</p><p>这块逻辑有一丢丢的割裂，不是很好管理。</p><h2 id="36-">3.6 失败后引导</h2><p>retry_hint。</p><p>它发生了 Tool Model run_tool_loop 时发生错误发送的，这个错误可能是，输入 args 或者 得到 result 校验失败。</p><p>它会立刻向 Tool Model 发送一个 retry 的指令类似这样:</p><pre class="language-python lang-python"><code class="language-python lang-python">TOOL_RETRY_HINTS: dict[str, str] = {
    &quot;vision__screen_shot&quot;: (
        &quot;Retry once: call vision__screen_shot with NO arguments.\n&quot;
        &quot;If it still fails: tell the user you cannot access their desktop, &quot;
        &quot;and ask them to describe what they see or provide a screenshot image.&quot;
    ),
    &quot;tool__web_search&quot;: (
        &quot;Retry once with a simpler query or a different provider.\n&quot;
        &quot;If you need details: pick 1 URL from results and call tool__web_fetch.&quot;
    ),
    &quot;tool__web_fetch&quot;: (
        &quot;Retry once with a larger max_chars (e.g., 12000~20000) &quot;
        &quot;or fetch a more specific URL/section.\n&quot;
        &quot;If blocked by robots or 4xx/5xx: report the status and ask user for another URL.&quot;
    ),
    &quot;tool__read_file&quot;: (
        &quot;Retry once with a correct path (relative to project root) &quot;
        &quot;or adjust start_line/end_line.\n&quot;
        &quot;If file not found: ask user for the correct file path.&quot;
    ),
    &quot;timeemi__get_date_and_time&quot;: &quot;Retry once with NO arguments.&quot;,
    &quot;timeemi__roll_dice&quot;: &quot;Retry once with a reasonable n_dice (1~100).&quot;,
    &quot;timeemi__roll_dice_by_current_time&quot;: (
        &quot;Retry once with unit in [&#x27;hour&#x27;,&#x27;minute&#x27;,&#x27;second&#x27;].\n&quot;
        &quot;If prompt rendering fails: verify the MCP prompt name exists on the server.&quot;
    ),
}

DEFAULT_RETRY_HINT = (
    &quot;Retry once with the same arguments.\n&quot;
    &quot;If it still fails: report the error briefly and ask the user for missing info.&quot;
)</code></pre><hr/><h2 id="4-vision-fallback-text-only-">4 Vision fallback：让 text-only 模型具有多模态能力（多模型协同）</h2><p>学着 tool model 和 chat model 的拆分，我拆分出了一个 vision model，用来适应：</p><p>当 chat model 不支持 vision（<code>supports_vision=false</code>），但用户输入里包含图片时：</p><ul><li>若检测到图像输入且 <code>chat_model.supports_vision=false</code> 且配置了 <code>vision_model</code>：
<ul><li>由 <code>vision_model</code> 对图像生成<strong>短的结构化 summary</strong>（不做长文/不做最终口语化）</li><li>将 summary 与 <code>tool_trace</code> 一起交给 <code>chat_model</code> 输出最终回复</li></ul></li><li>vision model 的上下文可配置：默认与 chat model 同步；若追求成本，也可以只注入 <code>system_prompt + 图像 + 必要提示/工具摘要</code>，这样反而更多适合更适配。</li></ul><hr/><h2 id="5--web-web-search--web-fetch">5. 我们自己的 Web 状态机（Web Search 和 Web Fetch）</h2><p>无论从成本还是技术上我都很难实现 chatgpt-5.2-thinking 那样长达十几分钟的 search 和 fetch。</p><p>但是基本的 search 和 fetch 我还是希望有的。</p><p>两个 tool 由模型自己判断调用，不过如果一个 step 里两个被同时 call 了。</p><p>那么选取 Web Search 独占当前 step ，然后把 search 到的 url 移交下一个 step 进行 web fetch。</p><p>我是希望避免第一步在没得到所有相关链接时就匆匆 fetch，那样可能会少掉 search 得到的信息（模型觉得第一步 fetch 的已经够了），或者需要两次 fetch 一次 search，那样得到的结果会被分成在两个 tool trace 里。我的思路是先汇总再 fetch。它在我的测试案例里表现得很不错。</p>
<h2 id="6---">6. 结语：把“陪写 + 鞭策”变成可实现的工程路线</h2><p>经过拆解后我需要做的有：</p><ul><li>✅ 可替换模型的 Agent 架构（tool/chat 分层，并补上 vision fallback）</li><li>✅ 可控的工具系统（tool_server + ToolRegistry + retry_hint + 图片正确通道）</li><li>✅ 有预算的 Web 状态机（WS/WF 编排 + “WS 单步独占”调度规则）</li><li>✅ pinned ConversationState + 动态上下文注入（低上下文也能做指代消解）</li><li>后续仍可继续拆解：记忆系统、repo 工具（git diff / tests / lint）、人格策略、以及 pet_mode 的鞭策规则</li></ul></div><p style="text-align:right"><a href="https://xnnehang.top/posts/default/learn_alma_part1#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://xnnehang.top/posts/default/learn_alma_part1</link><guid isPermaLink="true">https://xnnehang.top/posts/default/learn_alma_part1</guid><dc:creator><![CDATA[xnne]]></dc:creator><pubDate>Tue, 27 Jan 2026 01:25:45 GMT</pubDate></item><item><title><![CDATA[Lost in the middle 后续与 RAG 记忆系统调研]]></title><description><![CDATA[<link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202601021511232.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202601021511100.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202601040327459.png"/><div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://xnnehang.top/posts/default/Lost_in_the_Middle_plus">https://xnnehang.top/posts/default/Lost_in_the_Middle_plus</a></blockquote><div><h2 id="lost-in-the-middle">Lost in the Middle</h2><p>Lost in the Middle 发布之后已经过了很久。</p><p>我前阵子写了这个：</p><p><a href="https://xnnehang.top/posts/explore/lost-in-the-middle">Lost in the middle - how it affect LLM agent system</a></p><p>里面还有不少说法经不起推敲，因为来源不太可靠。</p><p>但可以定下来的是：</p><h2 id="lost-in-the-middle-">Lost in the Middle 前置概念</h2><p>我会分成结论+证据的形式来写。</p><ul><li>大模型在接受输入时会把当前用户输入加到整个历史上下文中然后进行推理，而不是仅仅对当前输入和提问进行推理。所以上下文越长，token 消耗费用越高，两者线性相关。</li></ul><p>我说的这个情况是针对于 Stateless LLM, Gemini-2.5, OpenAI-5.2 等等常在 API 中使用的模型都是 Stateless LLM。它的特征是每次文本生成请求是独立且无状态。所以如果要实现连续对话都要手动创建一个列表把之前的历史对话加进去。（当然并不代表每次都要从 0 计算，具体参见下方）</p><blockquote><p>While <strong>each text generation request is independent and stateless</strong>, you can still implement <strong>multi-turn conversations by providing additional messages as parameters to your text generation request</strong>.    <a href="https://platform.openai.com/docs/guides/conversation-state">OpenAI Conversation State</a></p></blockquote>
<p>当然，这里也许只能说明 API 调用时每次输入都需要完整输入，不能完全说明模型每次推理时一定要所有的输入。（因为这看上去是复用率很低的一个做法。）</p><p>但事实是，这正是 LLM 的底层计算逻辑，或者说由于底层的 Transformer，所有的 token $x<em>1$~$x_n$ 会被作为 $x</em>{n+1}$ ~ $x_m$  的计算条件。</p><blockquote><p>The prompt phase takes the whole user prompt (𝑥1, . . . , 𝑥𝑛) as input and computes the probability of the first new token 𝑃 (𝑥𝑛+1 | 𝑥1, . . . , 𝑥𝑛).  <br/><a href="https://web.stanford.edu/class/cs240/readings/vllm.pdf">Efficient Memory Management for Large Language Model Serving with PagedAttention</a></p></blockquote>
<p>但实际上虽然模型每次输入都是整个上下文，但并不是每次都从 0 计算。 KV-cache 是很好的例子，它减少了重复计算带来的计算量。</p><blockquote><p>At iteration 𝑡, the model takes one token 𝑥𝑛+𝑡 as input and computes the probability 𝑃 (𝑥𝑛+𝑡+1 | 𝑥1, . . . , 𝑥𝑛+𝑡) with the key vectors 𝑘1, . . . , 𝑘𝑛+𝑡 and value vectors𝑣1, . . . , 𝑣𝑛+𝑡 . Note that the key and value vectors at positions 1 to 𝑛 + 𝑡 − 1 are cached at previous iterations, only the new key and value vector 𝑘𝑛+𝑡 and 𝑣𝑛+𝑡 are computed at this iteration. <br/> <a href="https://web.stanford.edu/class/cs240/readings/vllm.pdf">Efficient Memory Management for Large Language Model Serving with PagedAttention</a></p></blockquote>
<ul><li>计量上下文窗口上限的单位是 token，判断是否超出上限是看单次请求中模型能共同理解的输入+输出 token 总量上限，所以简单来说可以细分为输入超上限，和输出超上限，但两者表现出来的结果没什么。</li></ul><p>上下文窗口（context window）是<strong>单次请求</strong>中模型能处理的 <strong>token 总量上限</strong>。</p><p>对 OpenAI 的一些模型/接口表述来说，这个上限明确包含：<strong>input tokens + output tokens + reasoning tokens</strong>。</p><blockquote><p>The context window is the maximum number of tokens that can be used in a single request. <br/> This max tokens number includes input, output, and reasoning tokens.<br/> <a href="https://platform.openai.com/docs/guides/conversation-state?utm_source=chatgpt.com">Conversation state</a></p></blockquote>
<ul><li><p>另外早期对于单次提示词输入的上限一般也有一些暴力约束比如单次输入 &lt; 10000字。但大模型是并没有推理时一般并没有这方面的直接约束。</p></li><li><p>超过上下文窗口时，要么请求失败（API/模型直接报错），要么系统只保留/压缩/忽略一部分上下文，导致回复不再覆盖全部内容。</p></li></ul><p>这里是 OpenAI API 在碰到超出上下文时给用户的<code>truncation</code>策略，可以自行选择。</p><blockquote><p><code>auto</code>: If the input to this Response exceeds the model&#x27;s context window size, the model will truncate the response to fit the context window by dropping items from the beginning of the conversation.<br/><code>disabled</code> (default): If the input size will exceed the context window size for a model, the request will fail with a 400 error.<br/><a href="https://platform.openai.com/docs/api-reference/responses?utm_source=chatgpt.com">OpenAI Responses</a></p></blockquote>
<ul><li>越接近上下文上限，模型的 U 形曲线特征越明显， Lost in the middle 越严重。</li></ul><p><a href="https://arxiv.org/pdf/2307.03172">Lost in the Middle: How Language Models Use Long Contexts</a></p><p>以及关于 Lost in the Middle 概念的一些详细内容我也曾写过。</p><p><a href="https://xnnehang.top/posts/explore/lost-in-the-middle">Lost in the middle - how it affect LLM agent system</a></p>
<h2 id="lost-in-the-middle-">Lost in the Middle 是否依然存在于近期的语言模型</h2><p>之后是目前真正关心的，是 Lost in the Middle 的现象，在最近的 LLM 中是否被解决。</p><p>当时我的推断是并未被解决，因为 LLM 的底层并没有变化。但目前需要来找些证据。</p><p>关于 Lost in the Middle (以下简称 LITM)的现象，我找到了这些相关内容：</p><ul><li><a href="https://longbench2.github.io/?utm_source=chatgpt.com"><strong>LongBench v2： 长语境、多任务下模型的推理理解能力评分</strong> </a>: Gemini-2.5 Flash, GLM-4.5 等等最近一年的无思维链推理的模型在 zero-shot without COT (零样本，无外接数据库或者搜索引擎；无思维链)的方式中上,由 Short -&gt; Long 跳跃时，准确率掉了很多。</li></ul><p>说明长文本对于目前模型的性能表现依然影响很大，虽然不能断言这个时由于 LITM 的现象，因为这里体现的是长度效应。而 LITM 则是位置效应。</p><p>另外这里比较好奇的一点是，具有思维链(CoT)的模型在文本变长时，有时性能不降反增，这很有意思。</p><blockquote><p>经过查阅，得知 CoT 模型之所以在任务准确率上有提高是因为是会减少“猜测式”输出与格式错误。并不是根本上解决了长度效应（提高理解能力）。这就好像一场考试里， without CoT 的学生只有 120 分钟，且所有问题只做了一遍就交卷，以及它还有自己的草稿纸（CoT）,可以分步拆解问题，而 CoT 的时间上更充裕，可以反复地检查自己是否有一些粗心犯错。</p></blockquote>
<ul><li><a href="https://arxiv.org/abs/2402.13718?utm_source=chatgpt.com"># \inftyBench: Extending Long Context Evaluation Beyond 100K Tokens</a>: 很多模型/系统“能吃进去”100K/200K tokens（标称窗口），但在真实任务里“用得起来”的信息量（有效上下文）明显更小；当长度继续拉长，性能会退化。</li></ul><p>他们提出了一个有效窗口的概念，有的大模型即使声称可以接受 100/200k  tokens 或者 1M tokens，但是实际的消融实验中，当上下文长度越长，性能表现越糟糕。</p><p>另外在 5.2 部分我看到他们提到了 Lost in the Middle。</p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202601021511232.png" alt="image-20260102161101092" height="502" width="512"/></p><p>这里是他们做的位置效应的图：</p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202601021511100.png" alt="image-20260102161154099"/></p><p>这里他们发现，对于不同产商的大模型，他们并没有在位置效应上呈现一致性。有的偏向首因，有的偏向中间。</p><p>这点看起来似乎否定了目前 Lost in the Middle 现象的存在。</p><p>但是我个人抱持的观点是，Lost in the Middle 中提到的 Transformer 预训练时存在的问题还未解决，只是在不同的应对方式下变成了不同的表现方式。</p><p>并且我在之后找到了可以支撑我观点的有效实验，即这个位置效应导致的注意力结构性偏差依然存在。</p><ul><li><a href="https://arxiv.org/pdf/2410.10781">Attention Sinks：开头 token 的结构性高注意力（sink）</a></li></ul><blockquote><p>“strong attention scores towards initial tokens as a ‘sink’ even if they are not semantically important.” <a href="https://arxiv.org/abs/2309.17453">arXiv</a>   <br/><strong>翻译：</strong> 模型会对<strong>最初的 token</strong>给出很强的注意力分数，把它们当成“汇点（sink）”，<strong>即使这些 token 在语义上并不重要</strong>。</p></blockquote><blockquote>
<p>“attention sinks exist universally in LMs … even in small models.” <a href="https://arxiv.org/abs/2410.10781">arXiv</a>  <br/><strong>翻译：</strong> 注意力汇点在各种语言模型中<strong>普遍存在</strong>，甚至在小模型里也存在。</p></blockquote><blockquote>
<p>“attention sink is observed to emerge during the LM pre-training.” <a href="https://arxiv.org/abs/2410.10781">arXiv</a>   <br/><strong>翻译：</strong> 注意力汇点被观察到会在<strong>语言模型预训练过程中出现</strong>。</p></blockquote>
<p>这里对模型的注意力结构性偏差进行了一些追踪和研究。并且发现了它在各种语言模型中普遍存在，且出现在预训练的过程中。</p><p>这可以说是 <strong>LITM</strong> 的一次溯源和解释，至少 Primacy Effect 是找到了。</p>
<ul><li><a href="https://arxiv.org/abs/2406.16008">Found in the Middle: 即使模型被专门训练来处理长输入上下文，仍然难以捕捉位于输入中间的相关信息。</a></li></ul><blockquote><p>“even when … trained … struggle to capture relevant information … in the middle.” <a href="https://arxiv.org/abs/2406.16008">arXiv</a>    <br/>即使模型被专门训练来处理长输入上下文，仍然难以捕捉位于输入中间的相关信息。</p></blockquote><blockquote>
<p>“mitigate this positional bias through a calibration mechanism, found-in-the-middle.” <a href="https://arxiv.org/abs/2406.16008">arXiv</a>   <br/>他们通过一种校准机制（found-in-the-middle）来缓解这种<strong>位置偏置</strong>。</p></blockquote>
<p>这篇论文发现 LITM 始终存在，并且研究了一些缓解策略，当然这缓解策略肯定并不能治本，毕竟问题出现在更早的阶段。当然这也可能是为什么各大产商的位置效应表现得各不相同，缓解的方式不同。</p><p>如果有完全解决预训练阶段的注意力位置偏差的方法，那应当是一篇集体引用的颠覆性的论文，但目前是还未出现的。</p>
<h2 id="-rag-">记忆系统 RAG 调研</h2><p>上面的所有工作总结起来就是： LITM 依然存在，且 LITM 目前在不同产商大模型中以不同的方式存在，不再只是呈现出 U 形。</p><p>那么，我们的记忆系统应该怎么做？（你可能会觉得有点突兀，但是我扯那么多确实是为了我的记忆系统。）</p><p>相比于 LITM 那边学术氛围会更重， RAG 这边更偏工程。所以对于论文的引用不会像之前那么多。因为比起论文的“我还有什么”,工程这边更在在意“我能用我现在的东西做什么”。</p><p>这里是我的一些灵感来源：</p><p><a href="https://www.lapis.cafe/posts/technicaltutorials/chatgpt-memory-system-breakdown/">浅谈ChatGPT的记忆实现机制 兼论工程端记忆设计</a></p><p><a href="https://linux.do/t/topic/781946">关于酒馆SillyTavern所代表的伴侣模型系统的一些小思考</a></p><p>这里是已有的发表的一些研究：</p><p><a href="https://arxiv.org/abs/2310.08560">MemGPT: Towards LLMs as Operating Systems</a></p><p>贪多嚼不烂，我们就先以 MemGPT 为重心，它提出来的相当多设计在我的系统中也依然使用着。</p><h3 id="memgpt">MemGPT</h3><p><a href="https://arxiv.org/abs/2310.08560">MemGPT: Towards LLMs as Operating Systems</a></p><h4 id="1memgpt-">1.MemGPT 要解决的核心问题是什么</h4><p>LLM 固定上下文窗口导致两类典型失败场景：</p><ol start="1"><li><p><strong>长期对话</strong>：跨会话一致性差、忘记用户偏好/事实、无法形成长期关系</p></li><li><p><strong>超长文档分析</strong>：文档远超 context window，关键信息“进不来/留不住/用不上”</p></li></ol><p>MemGPT 的核心主张是：</p><blockquote><p>不追求把所有历史都塞进 prompt，而是让模型“像操作系统一样”在有限窗口里 <strong>动态调度信息</strong></p></blockquote>
<p>另外，这里可以回忆下前面的内容：</p><ul><li>超过上下文窗口时，要么请求失败（API/模型直接报错），要么系统只保留/压缩/忽略一部分上下文，导致回复不再覆盖全部内容。</li><li><a href="https://arxiv.org/abs/2402.13718?utm_source=chatgpt.com"># \inftyBench: Extending Long Context Evaluation Beyond 100K Tokens</a>: 很多模型/系统“能吃进去”100K/200K tokens（标称窗口），但在真实任务里“用得起来”的信息量（有效上下文）明显更小；当长度继续拉长，性能会退化。</li></ul><p>所以 MemGPT 的核心问题现在也依然是核心，它仍然很具参考价值。</p><h4 id="2--llm-cpu">2. 架构总览：把 LLM 当“CPU”，把上下文当“主存”，把外置数据库作为”外存“</h4><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202601040327459.png" alt="MemGPT 的系统架构图"/></p><blockquote><p>图 3。 在 MemGPT 中，一个固定上下文的 LLM 处理器被一个分层记忆系统以及一组允许它管理自身记忆的函数所增强。LLM 的提示词 token（输入），也就是主上下文，由系统指令、工作上下文和一个 FIFO 队列组成。LLM 的补全 token（输出）会被函数执行器解释为函数调用。MemGPT 使用这些函数在主上下文与外部上下文（归档存储与回忆存储两个数据库）之间移动数据。LLM 还可以通过在输出中生成一个特殊的关键字参数（request heartbeat=true）来请求立即进行后续的 LLM 推理，以便把多个函数调用串联起来；这种函数链式调用使 MemGPT 能够执行多步检索，从而回答用户查询。<br/></p></blockquote>
<h5 id="">为什么要对上下文分区块</h5><p>对于 Prompt Tokens 的内容很好理解，就是模型的上下文，不过被区分成了几块，至于为什么区分在现在是很好理解的。</p>
<p>比如用 MCP 调用外部工具的模型，它的上下文中会包含这样的步骤：</p><blockquote><p>User: 今天的天气怎么样？<br/>Assitant: <code>[require_search_for_weather]</code><br/>User: Call Tool require_search_for_weather<br/>Tool: sunny <br/>User: <code>[User:今天的天气怎么样,Tool:Sunny]</code><br/>Assitant: 今天是晴天。</p></blockquote>
<p>在用户问”今天的天气怎么样？“并且触发 MCP 的时候实际上中间还至少包含了一个 Function Call 的回复【或者说它一直在，只是有时 True 有时 False】和 Tool Message 的回复，以及把 Tool Message 和用户问题再次发给模型的过程。这个过程按照逻辑理解，应该是 system 来提供，但是 system 的空间更金贵，而且容易稀释注意力和引起冲突幻觉，所以一般整个过程都以 <code>role = &quot;user&quot;</code>  来进行。</p><p>而这个过程中，不少上下文在结果产生后都是多余的，最终为了保持干净工作区和上下文一般都会选择只保留：</p><blockquote><p>User: 今天的天气怎么样？<br/> Assitant: 今天是晴天。</p></blockquote>
<p>这也是工作区中 Read-Write 的意义所在，上下文是可以编辑的，可以控制有效上下文的长度，隔离一些用过一次后就不会再用的信息，可以让模型性能衰减变慢。</p><p>不过这里 MemGPT 的 Function Call 应该是 Based-Prompt 而非模型原生支持的（微调出来的）。</p><p>这里分区块把原本的上下文切割隔离成了三段，对于上下文管理来说是很好的，同时它还为三段分别设置了不同的写入权限和写入方式，我之前也搞过上下文管理，但是没有区分权限有时候就会很难处理。这点启发颇多。</p><blockquote><p>我不会照着原文读，那真的很无聊，我会很多地方进行类比推论。而且，我可能不一定会验证，如果我能说服自己，可能会让模型验证一下。</p></blockquote>
<h5 id="memgpt-">MemGPT 工作区中各个区块的具体功能</h5><p>首先这些区块共同组成了我们所认识那个上下文，它只是做了<strong>不同权限和方式</strong>的写入读取方式，这点很有意思。</p><p><strong>System Instructions</strong></p><p>不可修改。</p><p>我最早以为这应该是系统提示词，人设之类的，但并不全是这样。</p><p>它的这个 &quot;System&quot; 的意思是操作系统，它以提示词的方式书写了控制流，各个内存区块用途，包含哪些函数，函数用法说明。这就像什么，像系统内核。它给 LLM 洗脑，告诉它是一个操作系统，具有哪些基础命令、资源等等。这是手动实现 Function Call 的核心，但是这个成功率估计非常感人，还和底座模型的理解能力强挂钩。</p><blockquote><p>各种猎奇人设见了不少，人机 play 是第一次见。这个模型先记住了自己是个操作系统，然后才是各种人设。</p></blockquote>
<p><strong>Working Context</strong></p><p>固定大小的一段窗口，只能通过特定函数来写，存关键事实、偏好、智能体人设。</p><blockquote><p>有点像一个小型的数据库，不过一直在而不是等待检索，存放一些关键信息。</p></blockquote>
<p><strong>FIFO queue</strong></p><p>滚动消息历史。</p><p>这一块才是我们正常使用上下文的方式，把对话一条一条按顺序存入，滚动。</p><p>它加了什么：</p><p>这是一个队列，长度也是固定，当临近队满的时候会开始写入外部的数据库（Recall、Archival Storage，模型自行决策）。</p><p>队列首部还有一个递归概要，概括被逐出队列的更早的消息。</p><p>待续。</p></div><p style="text-align:right"><a href="https://xnnehang.top/posts/default/Lost_in_the_Middle_plus#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://xnnehang.top/posts/default/Lost_in_the_Middle_plus</link><guid isPermaLink="true">https://xnnehang.top/posts/default/Lost_in_the_Middle_plus</guid><dc:creator><![CDATA[xnne]]></dc:creator><pubDate>Sat, 03 Jan 2026 02:29:50 GMT</pubDate></item><item><title><![CDATA[Termix - 一个很酷而且很方便的 Web-Based ssh 连接工具。]]></title><description><![CDATA[<link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202512291042933.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202512291043494.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202512291043854.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202512291043299.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202512291044412.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202512291055872.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202512291055842.png"/><link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202512291057168.png"/><div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://xnnehang.top/posts/default/termix">https://xnnehang.top/posts/default/termix</a></blockquote><div><h2 id="">前言（一些废话）</h2><p>前几天我的室友给我看了一个在网页部署的类似 Termius 的应用，当时看了之后感到非常惊奇，功能非常完整， sftp 也支持， upload 和 download 均可，而且可视化完全仿照了 termius，但是它可以运行在本地端口并且在网页端访问。</p><p>仓库地址：</p><p><a href="https://github.com/Termix-SSH/Termix">https://github.com/Termix-SSH/Termix</a></p><p>这意味把它公网部署着不少方便之处：</p><ul><li>更换到新设备或者新的系统不需要费力安装软件，并且不用再看一遍 ip 与密码。</li><li>如果使用别人的设备不必留痕。</li><li>最重要的是不用被 termius 的偶尔抽风自己卸载自己而烦恼。</li></ul><p>我先后用过宝塔面板的 ssh 连接和 Termius，当然运营商自带的就不提了，腾讯服务器扫码都快扫吐了。</p><p>初用 Termius 会觉得它设计得很漂亮，光是终端风格的选择就够我玩半天，以及许多 UI 元素都很现代化和优美，看得很舒服，这是我一直坚持使用它的原因。但是前面提到过，它有时候会自己抽风，把自己卸载掉，而且是清空所有缓存的那种写在，所有机器都得再导入一次。就像百度网盘那样。</p><p>当然，这里也有一个悖论。如果我部署 Termix 的服务器内部错误，然后我的 Termix 用不了，我依然得用 termius 来连，这没得避。</p><p>喜新厌旧，始乱终弃这一块。</p><p>让我们开始部署环节。</p><p>如果你希望先看看部署后的结果，可以看看最后的 <code>一些截图</code>, 以及如果你放心，你甚至可以直接用我部署的面板 =-=//(我能有什么坏心思呢)。</p><h2 id="">超简单的本地部署过程。</h2><p>如果你曾经用过 docker 和 docker-compose 。</p><p>那么部署它就非常容易，如果没有用过，那么请稍等，我简单补充一下 docker 和 docker-compose 的安装。</p><h3 id="docker--docker-compose-">准备工作：docker 和 docker-compose 安装</h3><p>摘自 <a href="https://www.vlo.cc/posts/jc/mix-deploy">Mix-Space部署最新后端Core</a></p><blockquote><p>若服务器在国外且需要使用1Panel，可以直接运行1Panel安装脚本，一把梭安装1Panel和Docker / Docker-Compose</p></blockquote>
<p>首先，安装一些必要的软件包：</p><blockquote><p>如果系统过老可以 <code>apt upgrade</code> 进行升级系统，但是新机建议直接重装。</p></blockquote><pre class="language-shell lang-shell"><code class="language-shell lang-shell">apt update
apt install curl vim wget gnupg dpkg apt-transport-https lsb-release ca-certificates</code></pre><p>然后加入 Docker 的 GPG 公钥和 apt 源：</p><pre class="language-shell lang-shell"><code class="language-shell lang-shell">curl -sSL https://download.docker.com/linux/debian/gpg | gpg --dearmor &gt; /usr/share/keyrings/docker-ce.gpg
echo &quot;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-ce.gpg] https://download.docker.com/linux/debian $(lsb_release -sc) stable&quot; &gt; /etc/apt/sources.list.d/docker.list</code></pre><p>国内机器可以用清华 TUNA 的国内源：</p><pre class="language-shell lang-shell"><code class="language-shell lang-shell">curl -sS https://download.docker.com/linux/debian/gpg | gpg --dearmor &gt; /usr/share/keyrings/docker-ce.gpg
echo &quot;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-ce.gpg] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/debian $(lsb_release -sc) stable&quot; &gt; /etc/apt/sources.list.d/docker.list</code></pre><p>然后更新系统后即可安装 Docker CE</p><pre class="language-shell lang-shell"><code class="language-shell lang-shell">apt install docker-ce docker-ce-cli containerd.io</code></pre><p>我们可以使用 Docker 官方发布的 Github 直接安装最新版本docker-compose：</p><pre class="language-shell lang-shell"><code class="language-shell lang-shell">curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-Linux-x86_64 &gt; /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose</code></pre><p>此时可使用 <code>docker-compose version</code> 命令检查是否安装成功</p><h3 id="">正式工作</h3><p>在一个想存放的目录，比如 <code>/opt/termix</code>。写入 <code>compose.yml</code> :</p><pre class="language-yaml lang-yaml"><code class="language-yaml lang-yaml">services:
  termix:
    image: ghcr.io/lukegus/termix:latest
    container_name: termix
    restart: unless-stopped
    ports:
      - &quot;8080:8080&quot;
    volumes:
      - termix-data:/app/data
    environment:
      PORT: &quot;8080&quot;

volumes:
  termix-data:
    driver: local</code></pre><p>然后拉取镜像:</p><pre class="language-shell lang-shell"><code class="language-shell lang-shell">root@ser351791695801:/opt/termix# docker compose pull
[+] pull 19/19
 ✔ Image ghcr.io/lukegus/termix:latest Pulled</code></pre><p>最后运行镜像:</p><pre class="language-shell lang-shell"><code class="language-shell lang-shell">root@ser351791695801:/opt/termix# docker compose up
[+] up 3/3
 ✔ Network termix_default    Created                                                                             0.1s 
 ✔ Volume termix_termix-data Created                                                                             0.0s 
 ✔ Container termix          Created                                                                             0.1s 
Attaching to termix
termix  | Configuring web UI to run on port: 8080
termix  | SSL disabled - using HTTP-only configuration (default)
termix  | Starting nginx...
termix  | Starting backend services...
termix  | [7:35:12 AM] [INFO] [📦] Termix Backend starting - Version: 1.9.0 [op:startup]
termix  | [7:35:12 AM] [SUCCESS] [🗄️] JWT secret auto-generated and saved to .env [op:jwt_auto_generated]
termix  | [7:35:12 AM] [SUCCESS] [🗄️] Database key auto-generated and saved to .env [op:db_key_auto_generated]
termix  | [7:35:12 AM] [SUCCESS] [🗄️] Internal auth token auto-generated and saved to .env [op:internal_auth_auto_generated]
termix  | [7:35:12 AM] [INFO] [🚀] SSL not enabled - skipping certificate generation [op:ssl_disabled_default]
termix  | [7:35:12 AM] [INFO] [🗄️] Initializing SQLite database [op:db_init]
termix  | [7:35:12 AM] [SUCCESS] [🗄️] Schema migration completed [op:schema_migration]
termix  | [7:35:14 AM] [INFO] [📡] Found 0 autostart hosts and 0 total hosts for endpointHost resolution
Gracefully Stopping... press Ctrl+C again to force</code></pre><p>如果没有报错信息抛出。就可以 Ctrl+C 中断然后进行后台运行:</p><pre class="language-shell lang-shell"><code class="language-shell lang-shell">root@ser351791695801:/opt/termix# docker compose up -d
[+] up 1/1
 ✔ Container termix Running</code></pre><p>这样程序就会在终端关闭时保活。</p><blockquote>
<p>我当时挺好奇，为什么有的人会写 compose.yml, 有的人会写 compose.yaml, 有的人写 docker-compose.yml。但似乎都能被 <code>docker compose pull</code> 识别。</p></blockquote>
<p>然后经 gemini 提醒，是因为它会根据优先级自动搜寻:</p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202512291042933.png" alt="compose file 搜索机制" height="615" width="812"/></p><p>这样也避免了两个以上 compose 文件冲突的问题。</p><h2 id="">公网部署</h2><p>这里我仅仅演示我最常用的一种方式：</p><p>1panel 反向代理。你也可以用你自己的方式进行部署。</p><p>1panel 安装：</p><pre class="language-shell lang-shell"><code class="language-shell lang-shell">https://1panel.cn/</code></pre><p>它的在线安装脚本不支持很多旧系统，我的 ubuntu 20.04 都不行，可能和地区有关。然后幽默地给我发了个付费把安装包发到邮箱的选项。</p><ul><li>1panel 应用商店安装 openresty</li><li>新建 DNS 记录。 A 记录，主机记录 ssh， 记录值为所部署服务器公网 ip。</li><li>网站创建反向代理，代理 127.0.0.1:8080, 如果 compose.yml 没改端口的话。域名写 <code>ssh.xnnehang.top</code>, 取决于你的 DNS 记录和自己的域名。</li><li>申请证书，需要绑定 DNS 账户，如果是在腾讯买的， 可以在这里新建或者查看 ACESS KEY 和 ACESS ID。<a href="https://console.cloud.tencent.com/cam/capi">API 密钥管理</a></li></ul><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202512291043494.png" alt="添加 DNS 账户"/></p>
<p>随后申请证书时勾选跳过 DNS 校验即可。</p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202512291043854.png" alt="申请证书"/></p><ul><li>最后在网站出开启 HTTPS 并且选择 ACME 账户（一般无需创建）和对应域名的证书即可。</li></ul><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202512291043299.png" alt="开启 https"/></p><p>唯一注意点是<strong>记得保存</strong>。</p><p>相当喂饭了已经，主要也是想测一下&quot;图床&quot;稳定性。</p><h2 id="">一些截图</h2><p>之后你就可以直接公网访问了，似乎是注册机制的，如果你想要用，也无不可 =-=，如果你放心，我欢迎。</p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202512291044412.png" alt="登录界面"/></p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202512291055872.png" alt="系统信息面板"/></p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202512291055842.png" alt="终端"/></p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202512291057168.png" alt="sftp"/></p><p>值得一玩 =-=//</p><p>这个 UI 我也很喜欢，颜控在此。</p><p>下次见。</p></div><p style="text-align:right"><a href="https://xnnehang.top/posts/default/termix#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://xnnehang.top/posts/default/termix</link><guid isPermaLink="true">https://xnnehang.top/posts/default/termix</guid><dc:creator><![CDATA[xnne]]></dc:creator><pubDate>Mon, 29 Dec 2025 08:54:58 GMT</pubDate></item><item><title><![CDATA[我使用过的一些异地连接方式]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://xnnehang.top/posts/default/remote_connection">https://xnnehang.top/posts/default/remote_connection</a></blockquote><div><h2 id="">前言</h2><p>大概在大二时我买了台式，并且寒假回去时把它搬回去就再也没搬到学校。</p><p>从那个时候我就有了重度的异地（且无公网 ip ）远程连接的需求。</p><p>而期间也踩了不少的坑。</p><p>另外由于我日常使用的系统是 linux，而被控端挂的是 windows。所以也有跨系统，或者说支持 linux 的需求。</p><h2 id="">连接方式</h2><p>大概分成两种，一种是 ssh 连接，一种是远程桌面。</p><h3 id="ssh-">ssh 连接</h3><p>win10 开始似乎就支持 openssh-server, 但家庭版默认没打开，需要先打开，可能还需要安装一些服务。</p><p>之后可以用 vscode 的插件进行连接。</p><p>以及，由于没有公网 ip, 所以还需要借助一些内网穿透工具，比如我当时用的是 SakuraFrp。</p><p><a href="https://xnnehang.top/posts/tutorial/vscode_ssh">https://xnnehang.top/posts/tutorial/vscode_ssh</a></p><p>这里有简单的介绍。</p><h4 id="">优点</h4><ul><li>没有延迟的困扰，直接使用 vscode, 而且直接访问目标文件夹，用来调试代码很合适。</li><li>如果运行的是网页端应用，我记得端口是 localhost 共享的，也许是我记错了，但至少给出的 share_url 是可用的。</li></ul><h4 id="">缺点</h4><ul><li>vscode 的一些插件似乎不能被加载，也许是被控端没有，至少我的静态分析是看不到的，这点很难受。</li><li>所有运行的进程会在 ssh 连接断开后（关闭 vscode）停止，无法保活。</li><li>配置得太麻烦了。最后用的次数也不多。</li></ul><h3 id="">远程桌面</h3><h4 id="">向日葵</h4><p>一坨。还是一坨大的，局域网用一用还可以，异地需要用到 turn 服务器的时候非常不稳定，而且日常无法连接自己的设备，只能通过设备码连接。</p><h4 id="">蒲公英内网穿透</h4><p>和向日葵是同一家，但是似乎还行，至少它支持 linux， 我用它 + parsec 用了很长一段时间。可以解决一下无公网 ip 的问题。但是似乎现在限速不稳定。</p><h4 id="parsec">Parsec</h4><p>在它被墙之前就是神，我大一用它和我初中的同学玩了一段时间的造梦西游3。当时是真的体验到了什么叫做低延迟操作跟手和流畅画面。</p><p>无公网 ip 会 -6024,可以用蒲公英解决。</p><p>但是被墙了后，表现也接近一坨了，有时候登录还要验证 ip 非常麻烦。局域网依然优秀。</p><h4 id="-turn-">云玩加+自建 turn 服务器。</h4><p><a href="https://www.bilibili.com/video/BV1Rv7MzGE89/?spm_id_from=333.337.search-card.all.click">https://www.bilibili.com/video/BV1Rv7MzGE89/?spm_id_from=333.337.search-card.all.click</a></p><p>b 站上一个大佬写的。</p><p>linux 可以用网页端，长时间运行稳定，而且 UI 很前卫，我个人非常喜欢。</p><p>我用它来远程玩一些需要看动画的游戏，比如 galgame。它的优势在于画面的传输是稳定的，不会卡顿，但是，延迟会体现在操作上，所以无法用来搞一些高敏类的东西。</p><p>之所以延迟比较高，是因为我两边都挂了 tun 的日本代理，turn 服务器在香港。导致延迟经常不稳定，有时非常长。</p><p>如果在国内买服务器的话，比如广州的，延迟更低，并且不开代理应该体验会接近甚至超过巅峰的 Parsec。</p><p>以后有合适的国内高带宽服务器了我会再搭建一个 turn。</p><p>唯一的缺点在于，代理节点切换和 turn 经常切断连接并且让机器暂时失联，有些难受。</p>
<h4 id="-uu-">网易 UU 远程</h4><p>我的两个室友都有用，一个 win-to-win, 一个 android-to-win。都说能用，延迟可以接受，并且也不需要考虑公网 ip 的问题。</p><p>应该是非常不错的入门。</p><p>可惜不支持 linux。</p></div><p style="text-align:right"><a href="https://xnnehang.top/posts/default/remote_connection#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://xnnehang.top/posts/default/remote_connection</link><guid isPermaLink="true">https://xnnehang.top/posts/default/remote_connection</guid><dc:creator><![CDATA[xnne]]></dc:creator><pubDate>Mon, 17 Nov 2025 06:59:46 GMT</pubDate></item><item><title><![CDATA[Ubunutu-24.04 apt update 时 waiting for headers 0% 的解法]]></title><description><![CDATA[<link rel="preload" as="image" href="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202510280634288.png"/><div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://xnnehang.top/posts/debug/waiting_for_headers_0_ubuntu24">https://xnnehang.top/posts/debug/waiting_for_headers_0_ubuntu24</a></blockquote><div><h2 id="">问题：</h2><p>在 wsl2 的 Ubuntu-24.04 中，我尝试运行:</p><pre class="language-shell lang-shell"><code class="language-shell lang-shell">sudo apt update &amp;&amp; sudo apt install curl software-properties-common
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg

# 确保这一行是针对 JAZZY 和 Noble (24.04)
echo &quot;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu noble main&quot; | sudo tee /etc/apt/sources.list.d/ros2.list &gt; /dev/null</code></pre><p>但是它仅仅只是在第一步 <code>sudo apt update</code> 的时候就经常卡在 <code>[0%] waiting for headers</code>。</p><p>而几乎每个 Get 都会停顿十秒以上，所以即使它会动但是也会导致一个 update 持续了十几分钟依然没有结束。</p><p>期间更换了香港，日本，美国的代理，没有改善。</p><p><img src="https://cdn.xnnehang.top/MrXnneHang/blog_img/refs/heads/main/BlogHosting/img/25/11/202510280634288.png" alt="waiting_for_headers" height="626" width="1113"/></p><h2 id="">原因和解决</h2><p><a href="https://www.reddit.com/r/Ubuntu/comments/oqqyek/waiting_for_headers_0/">https://www.reddit.com/r/Ubuntu/comments/oqqyek/waiting_for_headers_0/</a></p><p><code>Changing mirror from http to https worked for me :)</code></p><p>似乎是 source 源的 URL 都是 http 而非 https 造成的。</p><p>我将这两个文件内修改为 https 后得到了解决:</p><pre class="language-shell lang-shell"><code class="language-shell lang-shell">xnne@DESKTOP-3I1GRP0:/mnt/c/Users/Zhouyuan$ sudo vim /etc/apt/sources.list.d/      
ros2.list       ubuntu.sources    
xnne@DESKTOP-3I1GRP0:/mnt/c/Users/Zhouyuan$ sudo vim /etc/apt/sources.list</code></pre><p>最后在半分钟内完成了 update。</p><blockquote><p>ps, 在安装 ros2-jazzy-desktop 时，我把源改到 https 后似乎速度也变快了不少？<br/>大概从原本的 90B/s~300kB/s -&gt; 600kB/s ~ 1.5MB ~ 15 MB/s ，原本预计 1h 以上的安装变成了 8 mins。 <br/>以及，在下载的时候换换代理也是有效的。<br/>我发现日本和美国的代理是最友好的。<br/>另外我发现校园网坑了我。它真的太慢了。<br/>但是全改成 https 会有一个问题是，下载一些包时它可能碰到证书错误，到时还得切回去下。<br/></p></blockquote></div><p style="text-align:right"><a href="https://xnnehang.top/posts/debug/waiting_for_headers_0_ubuntu24#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://xnnehang.top/posts/debug/waiting_for_headers_0_ubuntu24</link><guid isPermaLink="true">https://xnnehang.top/posts/debug/waiting_for_headers_0_ubuntu24</guid><dc:creator><![CDATA[xnne]]></dc:creator><pubDate>Tue, 28 Oct 2025 06:36:27 GMT</pubDate></item><item><title><![CDATA[需要修复 image url 的博客]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://xnnehang.top/posts/default/fix_img_url">https://xnnehang.top/posts/default/fix_img_url</a></blockquote><div><blockquote><p>豆瓣的反爬机制加强了。目前即使得到图片直链也无法简单通过 baidu.download 等简单把它下载下来。<br/>目前我采用的图床方式是服务器反代 raw.githubusercontent.com。服务器对国内和 github 均低延迟访问。<br/></p></blockquote>
<p>需要 image url 的 link list</p><p>几乎全线阵亡！</p><ul><li><input readOnly="" type="checkbox" checked=""/> 如花束般的恋爱 | 文青是种病，开始亦是结束，深度共鸣不一定需要表面合拍。 25.8.7</li><li><input readOnly="" type="checkbox" checked=""/> C语言char*实现大数运算。</li><li><input readOnly="" type="checkbox" checked=""/> 从 NekoPara Vol.1 下的一个评测开始</li><li><input readOnly="" type="checkbox" checked=""/> 关于仙逆与凡人修仙传中的感情戏,道和化凡.</li><li><input readOnly="" type="checkbox" checked=""/> 长歌行： “郡主颜如玉，郡主世无双。”，“送君千里，终须一别”</li><li><input readOnly="" type="checkbox" checked=""/> 《蓦然回首》观后 - 我总也绕不出自己的“藤野困境”</li><li><input readOnly="" type="checkbox" checked=""/> 《游园惊梦》观后</li><li><input readOnly="" type="checkbox" checked=""/> 《一只特立独行的猪》 阅读手记</li><li><input readOnly="" type="checkbox" checked=""/> 从《葬送的芙莉莲》到心理强迫。</li><li><input readOnly="" type="checkbox" checked=""/> 《大内密探零零发》后 - 诶。你会不会肚子饿啊。我下碗面给你吃。</li><li><input readOnly="" type="checkbox" checked=""/> <del>从英文原版阅读到软件开发</del></li><li><input readOnly="" type="checkbox" checked=""/> 《Vampires&#x27;Melody 吸血鬼的旋律》后</li><li><input readOnly="" type="checkbox" checked=""/> 《大圣娶亲》后</li><li><input readOnly="" type="checkbox" checked=""/> 《月光宝盒》后 - 以情爱作为讨论主题的电影是否是一种俗套以及不良影响产生的可能。</li><li><input readOnly="" type="checkbox" checked=""/> 我一个人来，一个人走。 | 《葬送的芙莉莲》观后</li><li><input readOnly="" type="checkbox" checked=""/> 《食神》观后</li><li><input readOnly="" type="checkbox" checked=""/> 一笔聊发轻狂 | 和高中时的自己隔空对话。</li><li><input readOnly="" type="checkbox" checked=""/> <del>你害怕无聊吗？ -不，挺好的。 《幸福之路》后</del></li><li><input readOnly="" type="checkbox" checked=""/> 《画江湖之天罡》后 -&quot;也许真正的完满并不存在。&quot;</li><li><input readOnly="" type="checkbox" checked=""/> 《春娇与志明》后 - 牵绊</li><li><input readOnly="" type="checkbox" checked=""/> 《青春同人志》后 - 一部只想默默截图的电影。</li><li><input readOnly="" type="checkbox" checked=""/> 死亡之前还能做什么？有没有空？可以来拯救吗？</li><li><input readOnly="" type="checkbox" checked=""/> 《未盡之花》 -此書只應天上有         </li></ul><ul><li><input readOnly="" type="checkbox" checked=""/> Attention is Limited - Lost in the Middle     </li><li><input readOnly="" type="checkbox" checked=""/> yutto-uiya   </li><li><input readOnly="" type="checkbox" checked=""/> yutto-uiya 使用文档     </li><li><input readOnly="" type="checkbox" checked=""/> 修改不小心 commit 并上传到 github 上的敏感信息 | 或者删除旧大文件</li><li><input readOnly="" type="checkbox" checked=""/> English-Novel-WebReader:由Vue+Python构建的网页英文原版阅读器</li><li><input readOnly="" type="checkbox" checked=""/> <del>字幕生成V2.3-Stable : 吞句子现象fix,mp4原因导致字幕偏移音画不同步fix,提供GUI，支持保留标点和限制句子长度。</del></li><li><input readOnly="" type="checkbox" checked=""/> 音频降噪:日常噪音、乐器音、混响、和声 | Foobar2000+NVIDIA_SDK | MDX2.4</li><li><input readOnly="" type="checkbox" checked=""/> OBS 和 VTube Studio的联合使用:Be a Vtuber</li></ul></div><p style="text-align:right"><a href="https://xnnehang.top/posts/default/fix_img_url#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://xnnehang.top/posts/default/fix_img_url</link><guid isPermaLink="true">https://xnnehang.top/posts/default/fix_img_url</guid><dc:creator><![CDATA[xnne]]></dc:creator><pubDate>Mon, 27 Oct 2025 07:50:45 GMT</pubDate></item></channel></rss>