<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>成本优化 — mizore / notes</title>
    <link>https://mizore.blog/tags/成本优化/</link>
    <link xmlns="http://www.w3.org/2005/Atom" href="https://mizore.blog/tags/成本优化/feed.xml" rel="self" type="application/rss+xml"></link>
    <description>1 篇</description>
    <language>zh-CN</language>
    <lastBuildDate>Mon, 03 Aug 2026 00:00:00 +0000</lastBuildDate>
    <item>
      <title>给 Memoh 动了一场省钱手术：Telegram 特化改造记录</title>
      <link>https://mizore.blog/posts/making-memoh-cheaper-on-telegram/</link>
      <description>&lt;p&gt;前段时间我把 &lt;a href=&#34;https://github.com/memohai/memoh&#34;&gt;Memoh&lt;/a&gt; 部署成了几个 Telegram 群聊机器人,主要是为了消耗我用不完的ollama的(有一个蠢蛋一下订了一年的ollama).&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://mizore.blog/images/2baf5985a2608e7d7729c6df9ab9a320191edd3be43f59a8f37e8ed387fb0307.webp&#34; alt=&#34;Telegram 群聊里的 Memoh 机器人「小雪」正在和群友聊天，并回复了一张猫娘贴纸&#34; decoding=&#34;async&#34; fetchpriority=&#34;high&#34; width=&#34;1256&#34; height=&#34;874&#34; srcset=&#34;https://mizore.blog/images/ddf84c9704ee90683624988b2e2c49058a8693b40b103bf58f70831278c12e72.webp 720w, https://mizore.blog/images/2baf5985a2608e7d7729c6df9ab9a320191edd3be43f59a8f37e8ed387fb0307.webp 1256w&#34; sizes=&#34;(max-width: 760px) calc(100vw - 64px), 704px&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;跑了一阵子之后,发现在群里,十分钟就能把5hr limit烧一半,一天周限额就没了一半.于是我思考: 如何在聊天这个场景下,把 LLM 调用次数和 token 消耗压下去? 于是我针对群聊这个场景,做了一些改变,现在我的成本至少减少到了原来的 1/5.&lt;/p&gt;&#xA;&lt;p&gt;当然,这篇文章不是来挑上游毛病的。Memoh 本身是个设计得很认真的通用 agent 框架,多步骤工具循环、完整的工具集、结构化消息元数据,这些对&amp;quot;帮你干活&amp;quot;的 agent 场景都是合理甚至必要的。我做的事情是把它&lt;strong&gt;特化&lt;/strong&gt;成聊天机器人:群聊里的 agent 大部分时候只是在说话,不是在干活,而&amp;quot;说话&amp;quot;这个动作在通用 agent 循环里,成本结构是非常不划算的。&lt;/p&gt;&#xA;&lt;h2 id=&#34;钱到底花在哪&#34;&gt;钱到底花在哪&lt;/h2&gt;&#xA;&lt;p&gt;先看我优化前抓到的一个真实请求构成:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr&gt;&#xA;&lt;th&gt;部分&lt;/th&gt;&#xA;&lt;th style=&#34;text-align:right&#34;&gt;大小&lt;/th&gt;&#xA;&lt;th style=&#34;text-align:right&#34;&gt;占比&lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/thead&gt;&#xA;&lt;tbody&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;Messages(历史消息)&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;~157.7 KB&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;68.0%&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;Tools(工具 schema)&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;~49.5 KB&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;21.3%&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;System(system prompt)&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;~24.7 KB&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;10.7%&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;一个请求两百多 KB,折成 token 大约 5 万。问题在于,通用 agent 的工具循环意味着:&lt;strong&gt;每多一次 tool call,这 5 万 token 就要原样再读一遍&lt;/strong&gt;。而聊天场景里 tool call 特别多:发消息是一次,回复是一次,发表情包又是一次,发完还有个状态返回让模型再确认一次。一条群聊消息发出去,背后是 2 到 3 次完整上下文的重复读取。&lt;/p&gt;&#xA;&lt;p&gt;所以我的改造就沿着三个方向走:少调用几次、每次少读一点、读过的东西尽量命中缓存。下面每一项都附上代码,全部来自我的 fork(&lt;a href=&#34;https://github.com/mizorewww/Memoh&#34;&gt;mizorewww/Memoh&lt;/a&gt;,对比基线是上游 &lt;code&gt;d0c5c735^&lt;/code&gt;)。&lt;/p&gt;&#xA;&lt;h2 id=&#34;改动一发完消息就下班&#34;&gt;改动一:发完消息就下班&lt;/h2&gt;&#xA;&lt;p&gt;上游的循环是这样的:模型决定发消息 → 调用 send → 框架把投递状态(消息 ID、平台、目标会话等)作为 tool result 返回 → 模型再被调用一次,读完整个上下文,然后说&amp;quot;好的发完了&amp;quot;并结束。最后这一次调用,对群聊机器人来说几乎纯浪费:消息已经发出去了,模型除了复读一遍确认什么也做不了.真正的豆包模型的时候还出过一个更糟的状况:模型看着返回的成功状态,一遍遍继续发,陷入无限循环。&lt;/p&gt;&#xA;&lt;p&gt;我的做法是给 provider 包了一层装饰器(&lt;code&gt;internal/agent/runtime/native/terminal_send.go&lt;/code&gt;):send 成功投递到当前会话后,模型&amp;quot;下一次&amp;quot;被调用时,装饰器直接在本地返回一个 &lt;code&gt;FinishReasonStop&lt;/code&gt;,根本不发网络请求:&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;figcaption&gt;&lt;span class=&#34;code-path&#34;&gt;&lt;b&gt;internal/agent/runtime/native/terminal_send.go&lt;/b&gt;&lt;span&gt;投递成功后本地结束本轮&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;a href=&#34;https://github.com/mizorewww/Memoh/blob/ad8012c1a0926822307f56454bd26be9ef9dd2dc/internal/agent/runtime/native/terminal_send.go#L76-L103&#34;&gt;L76–103 · ad8012c ↗&lt;/a&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;ln&#34;&gt;76&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;terminalSendProvider&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;77&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Provider&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;78&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;state&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;terminalSendState&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;79&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;80&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;81&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;terminalSendProvider&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;DoGenerate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;params&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;GenerateParams&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;GenerateResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;82&lt;/span&gt;&#x9;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;state&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;isDelivered&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;83&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;GenerateResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;84&lt;/span&gt;&#x9;&#x9;&#x9;&lt;span class=&#34;nx&#34;&gt;FinishReason&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;FinishReasonStop&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;85&lt;/span&gt;&#x9;&#x9;&#x9;&lt;span class=&#34;nx&#34;&gt;Response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;     &lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ResponseMetadata&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ID&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;terminalSendResponseID&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;86&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;p&#34;&gt;},&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;87&lt;/span&gt;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;88&lt;/span&gt;&#x9;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Provider&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;DoGenerate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;params&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;89&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;90&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;91&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;terminalSendProvider&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;DoStream&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;params&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;GenerateParams&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;StreamResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;92&lt;/span&gt;&#x9;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;state&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;isDelivered&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;93&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Provider&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;DoStream&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;params&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;94&lt;/span&gt;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;95&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;parts&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;make&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;chan&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;StreamPart&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;96&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;parts&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;FinishStepPart&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;97&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;nx&#34;&gt;FinishReason&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;FinishReasonStop&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;98&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;nx&#34;&gt;Response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;     &lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ResponseMetadata&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ID&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;terminalSendResponseID&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;99&lt;/span&gt;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;100&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;parts&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;FinishPart&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;FinishReason&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;FinishReasonStop&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;101&lt;/span&gt;&#x9;&lt;span class=&#34;nb&#34;&gt;close&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;parts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;102&lt;/span&gt;&#x9;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;StreamResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Stream&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;parts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;},&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;103&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&#xA;&lt;p&gt;当然,tool call 和 tool result 在协议上必须配对,所以不能真的&amp;quot;不返回状态&amp;quot;,否则历史里会出现悬空的工具调用。实际做的是把 result 压缩到极致:当前会话发送成功只回 &lt;code&gt;{&amp;quot;ok&amp;quot;: true}&lt;/code&gt;,跨会话发送才附带 target 信息;失败则返回结构化的可重试错误(&lt;code&gt;error_code&lt;/code&gt; + &lt;code&gt;retryable&lt;/code&gt; + &lt;code&gt;guidance&lt;/code&gt;),让模型有机会自己修正,而不是直接抛 Go error 断掉循环:&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;figcaption&gt;&lt;span class=&#34;code-path&#34;&gt;&lt;b&gt;internal/agent/tool/message.go&lt;/b&gt;&lt;span&gt;极简的 send 结果&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;a href=&#34;https://github.com/mizorewww/Memoh/blob/ad8012c1a0926822307f56454bd26be9ef9dd2dc/internal/agent/tool/message.go#L311-L324&#34;&gt;L311–324 · ad8012c ↗&lt;/a&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;ln&#34;&gt;311&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;MessageProvider&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;execSend&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;session&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;SessionContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;toolCallID&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;args&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;any&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;any&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;312&lt;/span&gt;&#x9;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;session&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;SessionType&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sessionmode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Discuss&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;313&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;nx&#34;&gt;sendResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;exec&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;SendDirectForTool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;toMessagingSession&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;session&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;toolCallID&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;314&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;315&lt;/span&gt;&#x9;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;messageSendErrorResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;316&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;317&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;nx&#34;&gt;resp&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;any&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;ok&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;318&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;session&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;IsSameConversation&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sendResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Platform&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sendResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Target&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;319&lt;/span&gt;&#x9;&#x9;&#x9;&lt;span class=&#34;nx&#34;&gt;resp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;platform&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sendResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Platform&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;320&lt;/span&gt;&#x9;&#x9;&#x9;&lt;span class=&#34;nx&#34;&gt;resp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;target&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sendResult&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Target&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;321&lt;/span&gt;&#x9;&#x9;&#x9;&lt;span class=&#34;nx&#34;&gt;resp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;delivered&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;target&amp;#34;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;322&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;323&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;resp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;324&lt;/span&gt;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&#xA;&lt;p&gt;还有一层在历史投影里。未来的轮次再读历史时,成功的 send 调用被折叠成只含 &lt;code&gt;text / reply_to / sticker_id / attachments / message&lt;/code&gt; 五个观众可见字段的最小 call/result 对,投递细节(bot_id、platform、message_id 之类)不再出现在上下文里。注意这是投影层的折叠,持久化的 canonical 历史并不改写,排障时完整数据还在:&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;figcaption&gt;&lt;span class=&#34;code-path&#34;&gt;&lt;b&gt;internal/agent/application/service_messages.go&lt;/b&gt;&lt;span&gt;历史投影里的最小 send 调用&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;a href=&#34;https://github.com/mizorewww/Memoh/blob/ad8012c1a0926822307f56454bd26be9ef9dd2dc/internal/agent/application/service_messages.go#L232-L246&#34;&gt;L232–246 · ad8012c ↗&lt;/a&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;ln&#34;&gt;232&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;minimalDiscussSendCall&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;call&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ToolCallPart&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ToolCallPart&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;233&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;input&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;normalizeToolInputMap&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;call&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Input&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;234&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;minimal&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;make&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;any&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;235&lt;/span&gt;&#x9;&lt;span class=&#34;c1&#34;&gt;// Current-conversation routing is implicit. Keep only audience-visible&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;236&lt;/span&gt;&#x9;&lt;span class=&#34;c1&#34;&gt;// payload fields and the reply reference needed to understand the exchange.&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;237&lt;/span&gt;&#x9;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;key&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;range&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;reply_to&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;sticker_id&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;attachments&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;message&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;238&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;ok&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;input&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;];&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;ok&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;239&lt;/span&gt;&#x9;&#x9;&#x9;&lt;span class=&#34;nx&#34;&gt;minimal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;240&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;241&lt;/span&gt;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;242&lt;/span&gt;&#x9;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ToolCallPart&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;243&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;nx&#34;&gt;ToolCallID&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;strings&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;TrimSpace&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;call&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ToolCallID&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;244&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;nx&#34;&gt;ToolName&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;   &lt;span class=&#34;s&#34;&gt;&amp;#34;send&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;245&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;nx&#34;&gt;Input&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;      &lt;span class=&#34;nx&#34;&gt;minimal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;246&lt;/span&gt;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&#xA;&lt;p&gt;效果:纯发消息的轮次,LLM 调用从 2 次变 1 次,&lt;strong&gt;省 50%&lt;/strong&gt;。需要查资料、调工具的多步骤流程完全不受影响,查到了照样返回回答,只是把&amp;quot;发消息&amp;quot;当成了收尾动作。&lt;/p&gt;&#xA;&lt;h2 id=&#34;改动二发消息回复表情包一次调用做完&#34;&gt;改动二:发消息、回复、表情包,一次调用做完&lt;/h2&gt;&#xA;&lt;p&gt;聊天里一条消息经常同时是回复、又带表情包。原来这要拆成多次 tool call,每次都要重读全部上下文。我把它们合并成了一个 send 工具:&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;send(text[optional], reply_to[optional], sticker_id[optional])&lt;/code&gt;&lt;/pre&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figure&gt;&#xA;&lt;p&gt;具体做了两件事。一是上游本来就有顶层 &lt;code&gt;reply_to&lt;/code&gt;,但还留着一套冗余的嵌套 &lt;code&gt;message.reply{message_id}&lt;/code&gt; 表单,我把嵌套表单删了,只留顶层参数,并且只允许引用本轮可见的消息 ID(不可见会返回可重试的错误,而不是发错地方)。&lt;/p&gt;&#xA;&lt;p&gt;二是我之前为了发sticker写了个MCP,但发现调用MCP会大幅增加LLM读的次数,现在折进 send 的 &lt;code&gt;sticker_id&lt;/code&gt; 参数。&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;figcaption&gt;&lt;span class=&#34;code-path&#34;&gt;&lt;b&gt;internal/agent/runtime/native/telegram_sticker.go&lt;/b&gt;&lt;span&gt;sticker MCP 变成 send 的内部后端&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;a href=&#34;https://github.com/mizorewww/Memoh/blob/ad8012c1a0926822307f56454bd26be9ef9dd2dc/internal/agent/runtime/native/telegram_sticker.go#L14-L31&#34;&gt;L14–31 · ad8012c ↗&lt;/a&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;ln&#34;&gt;14&lt;/span&gt;&#x9;&lt;span class=&#34;s&#34;&gt;&amp;#34;github.com/memohai/memoh/internal/delivery&amp;#34;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;15&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;16&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;17&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;18&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;telegramStickerSendGuidance&lt;/span&gt;     &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34; For Telegram, normally pair every coherent public message with at least one context-appropriate Sticker by setting `sticker_id` in the same send call; omit it only when the catalog has no suitable Sticker. Choose from the full visual descriptions, not from the original emoji alone, and avoid repetitive duplicates.&amp;#34;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;19&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;telegramStickerCatalogSchemaKey&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;x-memoh-sticker-catalog&amp;#34;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;20&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;21&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;22&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;telegramStickerSchemaEntry&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;23&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;ID&lt;/span&gt;          &lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;`json:&amp;#34;id&amp;#34;`&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;24&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;Description&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;`json:&amp;#34;description&amp;#34;`&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;25&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;Emoji&lt;/span&gt;       &lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;`json:&amp;#34;emoji&amp;#34;`&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;26&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;Status&lt;/span&gt;      &lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;`json:&amp;#34;status&amp;#34;`&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;27&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;28&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;29&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// mergeTelegramStickerSendTools converts the Sticker MCP into an internal&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;30&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// backend for the first-party send tool. The model sees one send call whose&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;31&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// sticker_id enum contains the complete, stable catalog; it never sees a&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&#xA;&lt;p&gt;一次调用内先发文本再发贴纸;贴纸失败而文本已发时会返回部分成功并立即终止,避免模型重试导致文本发两遍。&lt;/p&gt;&#xA;&lt;p&gt;我让视觉模型预先给每个贴纸生成一段不超过 50 字的视觉描述,结果和贴纸集元数据一起存进 SQLite。这些描述按 ID 确定性排序后,写进 send 工具 schema 的 description 里。合并之后,发sitcker基本上不会有额外的成本.&lt;/p&gt;&#xA;&lt;h2 id=&#34;改动三把会动的东西全部挪到前缀尾巴上&#34;&gt;改动三:把会动的东西全部挪到前缀尾巴上&lt;/h2&gt;&#xA;&lt;p&gt;这是缓存命中率的关键。各家的 prompt caching 机制(Anthropic、OpenAI、DeepSeek、Kimi 都一样)都是&lt;strong&gt;前缀匹配&lt;/strong&gt;:缓存要求请求的前缀逐字节一致,前缀中任何地方变了,从那个位置往后的缓存全部作废。&lt;/p&gt;&#xA;&lt;p&gt;公平地说,上游的默认行为在日常轮次里缓存并不算糟:消息历史永远追加在末尾,DeepSeek 这类自动前缀缓存能兜住大部分。真正的漏洞是 system prompt 中间埋了一个频繁重写的东西:上游把 MEMORY.md 放在 AGENTS.md 和 PROFILES.md 之间,而 MEMORY.md 是记忆系统动不动就重写的内容,它一动,排在它后面的一切缓存全废。我的修改只有一个函数:&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;figcaption&gt;&lt;span class=&#34;code-path&#34;&gt;&lt;b&gt;internal/agent/runtime/native/fs.go&lt;/b&gt;&lt;span&gt;MEMORY.md 挪到 system prompt 尾部&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;a href=&#34;https://github.com/mizorewww/Memoh/blob/ad8012c1a0926822307f56454bd26be9ef9dd2dc/internal/agent/runtime/native/fs.go#L66-L71&#34;&gt;L66–71 · ad8012c ↗&lt;/a&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;ln&#34;&gt;66&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// systemFileLoadOrder keeps the frequently changing memory summary at the&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;67&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// tail of the system prompt. Stable instruction/profile content therefore&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;68&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// remains in the reusable prefix when MEMORY.md changes between turns.&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;69&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;systemFileLoadOrder&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;70&lt;/span&gt;&#x9;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;AGENTS.md&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;PROFILES.md&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;MEMORY.md&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;71&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&#xA;&lt;p&gt;加载顺序从 &lt;code&gt;AGENTS.md → MEMORY.md → PROFILES.md&lt;/code&gt; 改成 &lt;code&gt;AGENTS.md → PROFILES.md → MEMORY.md&lt;/code&gt;,最稳定的内容占据前缀,最易变的内容待在整个 system prompt 的最尾部。顺着这个思路又处理了一批类似的&amp;quot;前缀污染&amp;quot;:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;记忆写作手册(约 2.6 KB)改成只在配置了 memory provider 时才注入,选 None 就整段不出现:&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;figcaption&gt;&lt;span class=&#34;code-path&#34;&gt;&lt;b&gt;internal/agent/application/service.go&lt;/b&gt;&lt;span&gt;没配 memory provider 就不注入手册&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;a href=&#34;https://github.com/mizorewww/Memoh/blob/ad8012c1a0926822307f56454bd26be9ef9dd2dc/internal/agent/application/service.go#L710&#34;&gt;L710 · ad8012c ↗&lt;/a&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;ln&#34;&gt;710&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;MemoryInstructions&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;    &lt;span class=&#34;nx&#34;&gt;strings&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;TrimSpace&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;botSettings&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;MemoryProviderID&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;平台身份只注入当前平台的字段,顺便把含 bot token 的 &lt;code&gt;avatar_url&lt;/code&gt; 从注入内容里剔掉了。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;效果是可以实测的:旧的&amp;quot;动态改 system&amp;quot;方案下,一次强制回复调用的 cache read 只有 5.1%(这是那条路径的单次实测,不是日常基线);改完之后,真实群聊里连续两步请求的输入分别是 52,259 和 52,633 token,其中 cache read 52,096 和 52,480,&lt;strong&gt;命中率 99.7%&lt;/strong&gt;,每轮真正新增计费的未命中输入只有 160 来个 token。&lt;/p&gt;&#xA;&lt;h2 id=&#34;改动四看不见的请求瘦身&#34;&gt;改动四:看不见的请求瘦身&lt;/h2&gt;&#xA;&lt;p&gt;请求小一点,每次读的成本就低一点。这块是几个小动作叠加,全部做成了 Telegram 渠道级的可配置策略(存在 bot metadata 里):&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;figcaption&gt;&lt;span class=&#34;code-path&#34;&gt;&lt;b&gt;internal/agent/channelpolicy/policy.go&lt;/b&gt;&lt;span&gt;四个 Telegram 策略键&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;a href=&#34;https://github.com/mizorewww/Memoh/blob/ad8012c1a0926822307f56454bd26be9ef9dd2dc/internal/agent/channelpolicy/policy.go#L21-L24&#34;&gt;L21–24 · ad8012c ↗&lt;/a&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;ln&#34;&gt;21&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;TelegramToolCallsEnabledMetadataKey&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;telegram_tool_calls_enabled&amp;#34;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;22&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;TelegramEnabledToolsMetadataKey&lt;/span&gt;     &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;telegram_enabled_tools&amp;#34;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;23&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;TelegramSkillsEnabledMetadataKey&lt;/span&gt;    &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;telegram_skills_enabled&amp;#34;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;24&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;TelegramMessageMetadataModeKey&lt;/span&gt;      &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;telegram_message_metadata_mode&amp;#34;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;telegram_tool_calls_enabled&lt;/code&gt; 是工具总开关,&lt;code&gt;telegram_enabled_tools&lt;/code&gt; 是逐项白名单(显式空数组等于全关),&lt;code&gt;telegram_skills_enabled&lt;/code&gt; 控制 skills。聊天机器人不需要 shell、不需要文件编辑,我线上两个 bot 只开了 &lt;code&gt;send&lt;/code&gt;,&lt;strong&gt;关掉了 37 个工具&lt;/strong&gt;。关键在过滤发生的时机:白名单在 schema 生成之前就生效,关掉的工具连一个字符都不会进请求:&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;figcaption&gt;&lt;span class=&#34;code-path&#34;&gt;&lt;b&gt;internal/agent/runtime/native/agent.go&lt;/b&gt;&lt;span&gt;schema 生成前过滤工具&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;a href=&#34;https://github.com/mizorewww/Memoh/blob/ad8012c1a0926822307f56454bd26be9ef9dd2dc/internal/agent/runtime/native/agent.go#L990-L997&#34;&gt;L990–997 · ad8012c ↗&lt;/a&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;ln&#34;&gt;990&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;991&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;filtered&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;make&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;([]&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Tool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;providerTools&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;992&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;tool&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;range&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;providerTools&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;993&lt;/span&gt;&#x9;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;cfg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ChannelPolicy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;AllowsTool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;cfg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ChannelPolicy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;AllowsBackendTool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;994&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;nx&#34;&gt;filtered&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;append&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;filtered&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;tool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;995&lt;/span&gt;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;996&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;997&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;providerTools&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;filtered&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&#xA;&lt;p&gt;优化前 Tools 段占请求的 21.3%(约 49.5 KB,粗估 1.2 万 token),这部分基本整个消失了。策略读取失败时是 fail-closed 的,宁可没工具可用也不把不该给的工具漏出去。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;telegram_message_metadata_mode&lt;/code&gt; 控制消息元数据投影,Telegram 默认 &lt;code&gt;compact&lt;/code&gt;。上游发给模型的每条消息都带完整元数据头:&lt;code&gt;&amp;lt;message id=&amp;quot;...&amp;quot; sender=&amp;quot;...&amp;quot; t=&amp;quot;...&amp;quot; channel=&amp;quot;telegram&amp;quot; conversation=&amp;quot;...&amp;quot; type=&amp;quot;...&amp;quot; target=&amp;quot;...&amp;quot;&amp;gt;&lt;/code&gt;。库存储的 canonical 格式不变,但发给模型的瞬时投影会去掉逐条重复的时间戳、平台、会话名,群聊保留发言人归属:&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;figcaption&gt;&lt;span class=&#34;code-path&#34;&gt;&lt;b&gt;internal/agent/turn/user_header.go&lt;/b&gt;&lt;span&gt;compact 模式的消息头投影&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;a href=&#34;https://github.com/mizorewww/Memoh/blob/ad8012c1a0926822307f56454bd26be9ef9dd2dc/internal/agent/turn/user_header.go#L165-L183&#34;&gt;L165–183 · ad8012c ↗&lt;/a&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;ln&#34;&gt;165&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;ProjectUserMessageHeader&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;mode&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;166&lt;/span&gt;&#x9;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;strings&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;ToLower&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;strings&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;TrimSpace&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;compact&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;strings&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;HasPrefix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;strings&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;TrimSpace&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;&amp;lt;message&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;167&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;content&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;168&lt;/span&gt;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;169&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;start&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;strings&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Index&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;&amp;lt;message&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;170&lt;/span&gt;&#x9;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;start&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;171&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;content&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;172&lt;/span&gt;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;173&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;endRel&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;strings&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Index&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;start&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:],&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;174&lt;/span&gt;&#x9;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;endRel&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;175&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;content&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;176&lt;/span&gt;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;177&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;end&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;start&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;endRel&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;178&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;compactOpening&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;ok&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;projectMessageOpeningTag&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;start&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;end&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;179&lt;/span&gt;&#x9;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ok&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;180&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;content&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;181&lt;/span&gt;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;182&lt;/span&gt;&#x9;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[:&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;start&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;compactOpening&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;end&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:]&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;183&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&#xA;&lt;p&gt;每条消息省 85~120 字符,&lt;code&gt;channel=&amp;quot;telegram&amp;quot;&lt;/code&gt; 在请求里出现次数从 148 次降到 2 次,大历史下整个请求从 221.0 KB 缩到 208.7 KB。&lt;/p&gt;&#xA;&lt;h2 id=&#34;改动五给文字模型配个眼睛&#34;&gt;改动五:给文字模型配个&amp;quot;眼睛&amp;quot;&lt;/h2&gt;&#xA;&lt;p&gt;我跑的主模型里有不带视觉能力的(比如 DeepSeek V4 Flash),但群聊里天天有人发图。我给 agent 加了一个辅助视觉模型机制:主模型声明不支持图片、而本轮确实有图片附件时,先用一个便宜的小视觉模型把图片描述出来:&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;figcaption&gt;&lt;span class=&#34;code-path&#34;&gt;&lt;b&gt;internal/agent/application/auxiliary_vision.go&lt;/b&gt;&lt;span&gt;主模型无视觉能力时才触发&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;a href=&#34;https://github.com/mizorewww/Memoh/blob/ad8012c1a0926822307f56454bd26be9ef9dd2dc/internal/agent/application/auxiliary_vision.go#L183-L196&#34;&gt;L183–196 · ad8012c ↗&lt;/a&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;ln&#34;&gt;183&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Service&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;describeImagesWithAuxiliaryVision&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;184&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;185&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;req&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;ChatRequest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;186&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;primarySupportsVision&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;187&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;attachments&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;gatewayAttachment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;188&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;189&lt;/span&gt;&#x9;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;primarySupportsVision&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;190&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;191&lt;/span&gt;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;192&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;images&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;extractNativeImageParts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;attachmentsToAny&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;attachments&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;193&lt;/span&gt;&#x9;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;describeImagePartsWithAuxiliaryVision&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;req&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;primarySupportsVision&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;images&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;194&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;195&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;196&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Service&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;describeImagePartsWithAuxiliaryVision&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&#xA;&lt;p&gt;描述文本以 &lt;code&gt;&amp;lt;auxiliary_vision_description&amp;gt;&lt;/code&gt; 块追加到&lt;strong&gt;最后一条 user message 的尾部&lt;/strong&gt;,还是不碰前缀的思路:&lt;/p&gt;&#xA;&lt;figure class=&#34;code&#34;&gt;&lt;figcaption&gt;&lt;span class=&#34;code-path&#34;&gt;&lt;b&gt;internal/agent/application/auxiliary_vision.go&lt;/b&gt;&lt;span&gt;描述追加到最后一条用户消息尾部&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;code-tools&#34;&gt;&lt;a href=&#34;https://github.com/mizorewww/Memoh/blob/ad8012c1a0926822307f56454bd26be9ef9dd2dc/internal/agent/application/auxiliary_vision.go#L454-L467&#34;&gt;L454–467 · ad8012c ↗&lt;/a&gt;&lt;button class=&#34;copy&#34; type=&#34;button&#34;&gt;copy&lt;/button&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;ln&#34;&gt;454&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;appendAuxiliaryVisionToLastUserMessage&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;messages&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Message&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;visionContext&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Message&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;455&lt;/span&gt;&#x9;&lt;span class=&#34;nx&#34;&gt;visionContext&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;strings&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;TrimSpace&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;visionContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;456&lt;/span&gt;&#x9;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;visionContext&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;457&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;messages&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;458&lt;/span&gt;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;459&lt;/span&gt;&#x9;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;messages&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;--&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;460&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;messages&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Role&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;MessageRoleUser&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;461&lt;/span&gt;&#x9;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;continue&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;462&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;463&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;nx&#34;&gt;messages&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Content&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;append&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;messages&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;].&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Content&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;TextPart&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Text&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;\n\n&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;visionContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;464&lt;/span&gt;&#x9;&#x9;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;messages&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;465&lt;/span&gt;&#x9;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;466&lt;/span&gt;&#x9;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;append&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;messages&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;UserMessage&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;visionContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;span class=&#34;ln&#34;&gt;467&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&#xA;&lt;h2 id=&#34;顺手的-telegram-特化&#34;&gt;顺手的 Telegram 特化&lt;/h2&gt;&#xA;&lt;p&gt;还有一批纯粹为了让机器人在 Telegram 里表现更好的改动,顺带也和成本有关:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;被选中回复的 turn 期间持续刷新 typing 状态,群友能看到&amp;quot;正在输入&amp;quot;,而不是死等三十秒突然弹出一大段;&lt;/li&gt;&#xA;&lt;li&gt;压缩(compaction)改成滚动摘要模式,摘要 prompt 明确禁止复述原始工具参数 JSON;上下文预算取模型窗口和 bot 压缩阈值的较小者,截断点保证不落在 user/tool 边界中间,避免截出悬空调用。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;到底省了多少&#34;&gt;到底省了多少&lt;/h2&gt;&#xA;&lt;p&gt;建模其实不复杂。单次调用的输入成本可以写成:&lt;/p&gt;&#xA;&lt;div class=&#34;math&#34; tabindex=&#34;0&#34;&gt;&lt;math display=&#34;block&#34; style=&#34;font-feature-settings: &#39;dtls&#39; off;&#34; xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;C&lt;/mi&gt;&lt;mpadded lspace=&#34;0&#34;&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;c&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;a&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;l&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;l&lt;/mi&gt;&lt;/mpadded&gt;&lt;/msub&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mi&gt;T&lt;/mi&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mrow&gt;&lt;mo fence=&#34;true&#34; form=&#34;prefix&#34; stretchy=&#34;true&#34;&gt;[&lt;/mo&gt;&lt;mo form=&#34;prefix&#34; stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mi&gt;h&lt;/mi&gt;&lt;mo form=&#34;postfix&#34; stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;P&lt;/mi&gt;&lt;mpadded lspace=&#34;0&#34;&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;i&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;n&lt;/mi&gt;&lt;/mpadded&gt;&lt;/msub&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mi&gt;h&lt;/mi&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;P&lt;/mi&gt;&lt;mpadded lspace=&#34;0&#34;&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;c&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;a&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;c&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;h&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;e&lt;/mi&gt;&lt;/mpadded&gt;&lt;/msub&gt;&lt;mo fence=&#34;true&#34; form=&#34;postfix&#34; stretchy=&#34;true&#34;&gt;]&lt;/mo&gt;&lt;/mrow&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mi&gt;T&lt;/mi&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;P&lt;/mi&gt;&lt;mpadded lspace=&#34;0&#34;&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;i&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;n&lt;/mi&gt;&lt;/mpadded&gt;&lt;/msub&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mrow&gt;&lt;mo fence=&#34;true&#34; form=&#34;prefix&#34; stretchy=&#34;true&#34;&gt;[&lt;/mo&gt;&lt;mo form=&#34;prefix&#34; stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mi&gt;h&lt;/mi&gt;&lt;mo form=&#34;postfix&#34; stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mi&gt;h&lt;/mi&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;mo fence=&#34;true&#34; form=&#34;postfix&#34; stretchy=&#34;true&#34;&gt;]&lt;/mo&gt;&lt;/mrow&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;C_{\mathrm{call}} = T \times \left[(1-h) \times P_{\mathrm{in}} + h \times P_{\mathrm{cache}}\right] = T \times P_{\mathrm{in}} \times \left[(1-h) + h \times r\right]&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/div&gt;&#xA;&lt;p&gt;&lt;code&gt;T&lt;/code&gt; 是输入 token 数,&lt;code&gt;h&lt;/code&gt; 是缓存命中率,&lt;code&gt;r = P_cache / P_in&lt;/code&gt; 是缓存命中价与原价的比值。总成本再乘调用次数 &lt;code&gt;N&lt;/code&gt;,改造前后的差异就收进三个相互独立的因子:&lt;/p&gt;&#xA;&lt;div class=&#34;math&#34; tabindex=&#34;0&#34;&gt;&lt;math display=&#34;block&#34; style=&#34;font-feature-settings: &#39;dtls&#39; off;&#34; xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;C&lt;/mi&gt;&lt;mpadded lspace=&#34;0&#34;&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;b&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;e&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;f&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;o&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;r&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;e&lt;/mi&gt;&lt;/mpadded&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;C&lt;/mi&gt;&lt;mpadded lspace=&#34;0&#34;&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;a&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;f&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;t&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;e&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;r&lt;/mi&gt;&lt;/mpadded&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;mpadded lspace=&#34;0&#34;&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;o&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;l&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;d&lt;/mi&gt;&lt;/mpadded&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;mpadded lspace=&#34;0&#34;&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;n&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;e&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;w&lt;/mi&gt;&lt;/mpadded&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;T&lt;/mi&gt;&lt;mpadded lspace=&#34;0&#34;&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;o&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;l&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;d&lt;/mi&gt;&lt;/mpadded&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;T&lt;/mi&gt;&lt;mpadded lspace=&#34;0&#34;&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;n&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;e&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;w&lt;/mi&gt;&lt;/mpadded&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mi&gt;f&lt;/mi&gt;&lt;mo form=&#34;prefix&#34; stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;h&lt;/mi&gt;&lt;mpadded lspace=&#34;0&#34;&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;o&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;l&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;d&lt;/mi&gt;&lt;/mpadded&gt;&lt;/msub&gt;&lt;mo form=&#34;postfix&#34; stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;mi&gt;f&lt;/mi&gt;&lt;mo form=&#34;prefix&#34; stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;h&lt;/mi&gt;&lt;mpadded lspace=&#34;0&#34;&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;n&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;e&lt;/mi&gt;&lt;mi mathvariant=&#34;normal&#34;&gt;w&lt;/mi&gt;&lt;/mpadded&gt;&lt;/msub&gt;&lt;mo form=&#34;postfix&#34; stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;\frac{C_{\mathrm{before}}}{C_{\mathrm{after}}} = \frac{N_{\mathrm{old}}}{N_{\mathrm{new}}} \times \frac{T_{\mathrm{old}}}{T_{\mathrm{new}}} \times \frac{f(h_{\mathrm{old}})}{f(h_{\mathrm{new}})}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/div&gt;&#xA;&lt;p&gt;其中 &lt;span class=&#34;math-inline&#34;&gt;&lt;math display=&#34;inline&#34; style=&#34;font-feature-settings: &#39;dtls&#39; off;&#34; xmlns=&#34;http://www.w3.org/1998/Math/MathML&#34;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;f&lt;/mi&gt;&lt;mo form=&#34;prefix&#34; stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mi&gt;h&lt;/mi&gt;&lt;mo form=&#34;postfix&#34; stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mo form=&#34;prefix&#34; stretchy=&#34;false&#34;&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mi&gt;h&lt;/mi&gt;&lt;mo form=&#34;postfix&#34; stretchy=&#34;false&#34;&gt;)&lt;/mo&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mi&gt;h&lt;/mi&gt;&lt;mo&gt;×&lt;/mo&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&#34;application/x-tex&#34;&gt;f(h) = (1-h) + h \times r&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;。三个因子的取值都有依据:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;N_old / N_new = 2~3&lt;/strong&gt;:纯发消息 2 次调用变 1 次,带回复/表情 3 次变 1 次。这是机制决定的,不含任何估算;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;T_old / T_new ≈ 70K / 52K ≈ 1.35&lt;/strong&gt;:请求里砍掉的工具 schema(49.5 KB)、动态贴纸目录(18.3 KB)和重复元数据,合计约 2 万 token;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;h_new = 99.7%&lt;/strong&gt; 是实测;h_old 取 95%,因为上游默认有自动前缀缓存,日常并不差,失分只在 memory 重写这类击穿前缀的事件上。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;code&gt;r&lt;/code&gt; 来自各家官方定价(2026-08-03):DeepSeek V4 Flash 是 $0.0028/$0.14 = &lt;strong&gt;1/50&lt;/strong&gt;,Claude Sonnet 4.6 是 1/10,Kimi K2.6 约 1/6,豆包 seed-1.6 是 1/5。代入保守取值(N 倍率 2、h_old 95%):&lt;/p&gt;&#xA;&lt;table&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr&gt;&#xA;&lt;th&gt;模型&lt;/th&gt;&#xA;&lt;th style=&#34;text-align:right&#34;&gt;r&lt;/th&gt;&#xA;&lt;th style=&#34;text-align:right&#34;&gt;f(95%) / f(99.7%)&lt;/th&gt;&#xA;&lt;th style=&#34;text-align:right&#34;&gt;总倍率 2 × 1.35 × f 比&lt;/th&gt;&#xA;&lt;th style=&#34;text-align:right&#34;&gt;省下&lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/thead&gt;&#xA;&lt;tbody&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;DeepSeek V4 Flash&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;1/50&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;3.0&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;≈ 8.1×&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;≈ 88%&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;Claude Sonnet 4.6&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;1/10&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;1.4&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;≈ 3.8×&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;≈ 74%&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;Kimi K2.6&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;≈1/6&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;1.2&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;≈ 3.3×&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;≈ 70%&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;规律一眼可见:缓存价差越大,99.7% 命中率的杠杆越长,DeepSeek 的 50 倍价差让省幅冲到接近九成。而 N 那个因子不依赖任何缓存假设,是白捡的。这和我实际的账单感受一致:成本至少降到了原来的 1/5,模型在保守取值下给出 1/8,因为我给 h_old 留足了面子,也没算少发的输出 token。&lt;/p&gt;&#xA;&lt;h2 id=&#34;最后&#34;&gt;最后&lt;/h2&gt;&#xA;&lt;p&gt;我只是在问自己一个问题,&lt;strong&gt;通用框架的默认行为,放到高频聊天的场景里,每一处都要问一句&amp;quot;这次读取/这次调用真的必要吗&amp;quot;&lt;/strong&gt;。发完消息不用再确认、三个动作合并成一次调用、易变内容挪到前缀尾部、用不到的工具一个字符都不发、图片让便宜的小模型先看。&lt;/p&gt;&#xA;&lt;p&gt;也得再说一次,上游的工作值得尊重:工具循环、历史投影、channel 抽象这些基础设施都做得很扎实,我能在一个多星期里完成这种程度的特化,恰恰是因为它的边界划得清楚:terminal send 能包装 provider 而不动核心循环,工具策略能在 schema 生成前统一过滤,都是架构上留了口子。如果你的场景是&amp;quot;让 agent 干活&amp;quot;,上游的默认值大概率就是对的;如果你和我一样只是想要一个住群里、话多、还不能太烧钱的家伙,希望这篇记录有点用。&lt;/p&gt;&#xA;</description>
      <category>技术</category>
      <pubDate>Mon, 03 Aug 2026 00:00:00 +0000</pubDate>
      <guid>https://mizore.blog/zh/%E6%8A%80%E6%9C%AF/making-memoh-cheaper-on-telegram/</guid>
    </item>
  </channel>
</rss>