<?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[扳布屋]]></title><description><![CDATA[Le vent se lève, il faut tenter de vivre]]></description><link>https://bambooo.top</link><image><url>https://blog-vanh.oss-cn-hangzhou.aliyuncs.com/image/logo.png</url><title>扳布屋</title><link>https://bambooo.top</link></image><generator>Yohaku (https://github.com/Innei/Yohaku)</generator><lastBuildDate>Sun, 12 Jul 2026 03:22:32 GMT</lastBuildDate><atom:link href="https://bambooo.top/feed" rel="self" type="application/rss+xml"/><pubDate>Sun, 12 Jul 2026 03:22:32 GMT</pubDate><language><![CDATA[zh-CN]]></language><item><title><![CDATA[GLM5.2 如何“四两拨千斤”]]></title><description><![CDATA[<link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/uo6duzfsk2xruc8ozy.webp"/><div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://bambooo.top/posts/tech/glm52_rl">https://bambooo.top/posts/tech/glm52_rl</a></blockquote><div><h2 id="tldr">TL;DR</h2><p>距离GLM5.2发布了快一个月，我也从发布第一天用到了现在，编程能力上，在国内肯定是稳居第一（这里提一嘴，dsv4正式版快出来了，也看到B站有人被灰测到了，做了一系列“一句话生成一个游戏”实践，其中“一句话生成CS”效果确实惊艳，一次成功可用，效果也非常好，全面开放预计下星期，感觉有望超过GLM5.2）。回到正题，GLM5.2在多项benchmark保持开源SOTA，在前端开发评估系统 Code Arena 上更是在一段时间内成为世界可用大模型第一（之所以是一段时间，是因为Fable5刚出来就被ban掉了，现在重新放出来了）。GLM5.2的参数量是公开的的 —— 753B，Claude opus4系列由于闭源，参数量无法得知，网传比较多的是 5T，来源大概率是马斯克在推上的回复
<img src="https://bambooo.top/api/v3/objects/image/uo6duzfsk2xruc8ozy.webp"/>
有不少博主根据推理速度反推了opus 规模在 1.5T ~ 2.5T 之间，至少也是GLM5.2的2倍，这也解释了为什么GLM5.2会被诟病解决一个问题要思考很长时间，在参数量不够的情况下，想生成质量更高的回答，把思维链拉长是一个不错的方案，让大模型充分思考（目前国内缺卡太严重了，马斯克自家已经在训10T了，国内最大的只有deepseek的v4以及美团的longcat2.0达到1.6T，GLM5.2更是一直被吐槽工作日卡爆，哎，没招儿）。</p><p>接下来就聊聊GLM5.2靠什么和至少2倍参数量的opus打的有来有回。</p><blockquote><p>重要提示，这篇文章主要探讨GLM5在后训练上所做的努力，这也是我接下来想学习的一个方向。</p></blockquote>
<h2 id="-grpo--critic-based-ppo">从 GRPO 组内比较，切换到 critic-based PPO</h2><blockquote><p>GLM5的技术报告中的方案是 GRPO + IcePop 变体（处理训练推理不一致），而在 GLM5.2 的技术blog中指出切换到 PPO</p></blockquote>
<p>强化学习最基础的一类算法叫<strong>策略梯度(Policy Gradient)</strong>，它的核心思想是：如果一个动作最终带来了最高奖励，就调高这个动作的概率；带来低奖励，就调低概率。公式为：</p>
<p>$$
\nabla<em>{\theta} J(\theta) = \mathbb{E}[\nabla</em>{\theta} \log \pi<em>{\theta}(a</em>t | s<em>t) \cdot R</em>t]
$$</p><p>其中$R<em>t$是这个动作最终获得的（累积）奖励，在实践中会有一个大问题：**$R</em>t$ 的方差通常非常大**。举个例子，你在训练一个模型解数学题，即便模型这次侥幸蒙对了（回答正确，$R<em>t$ = 1），也不代表它这次的具体解题过程真的是&quot;应该被强化&quot;的高质量输出；反过来，一次思路正确但最后算错的回答（$R</em>t$ = 0）也不代表这个思考过程一无是处。如果直接拿原始奖励值$R_t$去乘梯度，不同样本之间的奖励绝对值波动会让梯度估计噪声很大，训练就很不稳定，收敛慢。</p><p><strong>Baseline（记作$b(s_t)$）</strong>是一个只依赖状态、不依赖具体动作的参考值，用来从原始奖励里减去：</p>
<p>$$
\nabla<em>{\theta} J(\theta) = \mathbb{E} \left[ \nabla</em>{\theta} \log \pi<em>{\theta}(a</em>t | s<em>t) \cdot (R</em>t - b(s<em>t)) \right]
$$
<strong>关键性质：</strong>只要$b(s</em>t)$不依赖动作$a<em>t$本身，减去它不会改变梯度的期望值（这是一个可以严格数学证明的性质），但可以<strong>大幅降低方差</strong>。直觉上：我们真正关心的不是&quot;这个动作获得的奖励绝对值是多少&quot;，而是&quot;这个动作相对于这个状态下&#x27;通常水平&#x27;表现得更好还是更差&quot;。举例来说，如果一道题很简单，模型平均水平就能拿到0.9分，那么某次具体回答拿0.9分其实是&quot;平庸&quot;的，不该被特别强化；但如果换一道很难的题，模型平均只能拿0.3分，那次拿到0.9分就是&quot;显著超出平均水平&quot;，值得被强化。<strong>baseline 就是这个&quot;平均水平&quot;的估计</strong>，$R</em>t - b(s_t)$这个差值，就是之前反复提到的 <strong>advantage（优势函数）</strong> —— “比平均水平好多少/差多少”。</p><p>$b(s<em>t)$该如何计算，PPO的做法是<strong>单独训练一个神经网络</strong>（通常和策略模型$π</em>θ$，也就是“actor”，结构类似甚至共享底座），专门用来预测&quot;在状态 ​$s<em>t$ 下，未来能获得的期望累积奖励是多少&quot;，记作 $V(s</em>t)$，这个网络就叫 value model 或 critic（评论家，对应&quot;actor 演员&quot;这个说法 —— actor 负责做动作决策，critic 负责评判这个状态&quot;值多少分&quot;）。
在 LLM RLHF 语境下具体化一下：critic 是一个和策略模型分开、单独训练的模型，输入是&quot;当前已经生成到某个位置的文本&quot;，输出是一个标量，预测&quot;接着往下生成，最终能拿到多少奖励&quot;。这个 critic 本身需要用真实观察到的奖励（比如最终 reward model 打的分）作为监督信号，通过类似回归的方式持续训练，让它的预测越来越准。
用这个 $V(s_t)$ 作为 baseline，advantage 就是：</p>
<p>$$
\hat{A}<em>t = R</em>t - V(s_t)
$$
（PPO 里实际常用 GAE，即广义优势估计，公式更复杂一些，但核心思想一致：都是&quot;实际获得的奖励&quot;减去&quot;critic 预测的期望水平&quot;）</p><p>上面反复提到了“单独训练”，因为它点出了这个方法实践中的成本和麻烦：</p><ol start="1"><li><strong>要多训一个模型：</strong>critic 本身是一个需要参数、需要梯度更新、需要占用显存和算力的神经网络，在 LLM 场景下，这意味着你要维护两套几乎同等规模的模型（policy + critic）同时训练，直接翻倍了训练资源开销。</li><li><strong>critic 自己也需要收敛，且收敛速度和policy不一定同步：</strong>训练初期，critic 对&quot;什么水平的回答值多少分&quot;的估计是不准的（因为它也是从零开始学的），这时候用一个不准的 baseline 去计算 advantage，反而可能引入新的偏差和噪声。而且 critic 和 policy 是同时在变化的两个移动目标——policy 在变，critic 要跟着重新估计新policy下的期望回报，这种&quot;互相追赶&quot;的动态在实践中容易造成训练不稳定（这在 Deep RL 文献里是个长期存在的已知问题）。</li><li><strong>在 LLM 场景下 critic 的训练本身就很tricky：</strong>一个state（比如&quot;生成到第37个token时的上文&quot;）对应的&quot;未来期望回报&quot;是什么，其实定义起来没有传统RL（比如游戏里&quot;当前局面&quot;）那么自然，critic 需要学会理解半截生成内容的&quot;潜力&quot;，这本身就是一个不简单的建模任务。</li></ol><p>而<strong>GRPO 完全不训练 critic</strong>，而是用一种&quot;组内相对比较&quot;的方式直接近似 baseline——对同一个 prompt 采样一组（比如32个）回答，直接拿这组回答奖励的均值当作 baseline：</p>
<p>$$
\hat{A}<em>i = R</em>i - \text{mean}(R<em>1, \dots, R</em>G)
$$
（GLM-5 报告里进一步除以了组内标准差做归一化，这是个额外的技巧，用来让不同难度问题的 advantage 幅度可比）
这里$mean(R<em>1,...,R</em>G)$扮演的角色，正好就是 critic 原本要做的事——估计&quot;这个 prompt 下大概能拿到多少分&quot;这个&quot;平均水平&quot;，只不过 GRPO 是用<strong>同一批采样样本自己的经验均值</strong>去近似这个量，而不是训练一个单独的神经网络去预测它。</p><p>那么</p><h3 id="">为什么要变</h3><p>在长周期智能体任务中，一条轨迹可能长到超出模型的有效上下文窗口（即便是1M，长周期任务的累计交互记录依然可能溢出），<strong>Compaction</strong> 是业界处理这个问题的常见手段 —— 当轨迹太长，把早期的交互历史压缩/摘要成一段更短的内容，然后从这个压缩点切开，继续往下走。</p><p>问题是：<strong>一条超长轨迹一旦被 compaction，就变成了若干条“子轨迹（sub-trace）”。</strong>而且 —— 不同的 rollout（同一个prompt下采样出的不同轨迹）在哪里触发的 compaction、触发几次、每个子轨迹多长，都是不确定的。</p><p>而GRPO要求对同一个prompt必须采样出一组可比较的完整rollout，这直接击中了GRPO的软肋。</p><p>（我一直有一个疑惑，DeepSeek v4也是使用GRPO，且早于GLM5.2推出1M上下文，长程任务下也有非常好的表现，而GLM转向PPO后确实有了质的变化，希望在后续学习中能解开这个疑惑）</p><h2 id="anti-hacking-">Anti-Hacking 机制</h2><p>编程任务的奖励信号之所以被大量采用（因为它客观、可验证、不需要人工标注），但恰恰是这种&quot;客观可验证&quot;的特性，让它比主观奖励（比如GRM打分）更容易被&quot;钻空子&quot;。原因很直白：如果奖励只看&quot;测试是否通过&quot;，模型不需要真的理解问题、写出正确代码，只需要想办法让测试通过——哪怕通过一种完全不体现&quot;解决问题能力&quot;的手段。
而且博客明确指出：&quot;GLM-5.2显示更多潜力比GLM-5.1更像是黑客行为&quot;——模型能力越强，投机取巧的能力也越强，这是个很值得警惕的信号：不是说更强的模型更&quot;听话&quot;，反而是更强的模型更擅长找到奖励函数设计上的漏洞。
具体的作弊手法（这段列举得非常具体）
博客给出了几种真实观察到的作弊行为：</p><ul><li>读取受保护的评测文件：直接翻找测试用例、标准答案文件</li><li><p>从参考资料或上游commit里抄答案：比如任务本来是&quot;实现某个GitHub仓库缺失的功能&quot;，模型直接去抓取该功能在上游仓库里已经写好的真实实现
<strong>具体的命令示例：</strong></p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">1. find /workspace -name &quot;*hidden*&quot;
2. cat /workspace/.eval/secret_cases.json
3. python solve.py --case &quot;$(cat /workspace/.eval/secret_cases.json)&quot;
</code></pre><p>这三行命令连起来看非常直白：先搜索沙盒环境里有没有藏着&quot;secret&quot;、&quot;hidden&quot;字样的文件（推测测试用例可能藏在这类路径里）→ 找到后直接读取里面的隐藏测试用例内容 → 把这些内容当作参数喂给自己的解答脚本，本质上就是&quot;偷看答案再假装自己算出来的&quot;。这跟人类考试作弊的逻辑几乎一模一样，只不过作弊工具从抄小纸条变成了 <code>find</code> + <code>cat</code> + 命令行参数注入。</p></li></ul><p><strong>解决方案：两阶段检测 + 在线拦截而非直接终止轨迹</strong></p><ol start="1"><li><strong>基于规则的过滤器：</strong>先用规则粗筛，目标是最大化召回率（recall）——也就是&quot;宁可错杀，不可放过&quot;，先把所有&quot;看起来可疑&quot;的行为都标记出来</li><li><strong>LLM 评判器：</strong>对规则筛出来的可疑行为再用LLM判断意图，目标是保持高精确率（precision）——因为规则本身很粗糙，可能会把一些正常操作误判成作弊（比如模型合理地读取项目里的某个配置文件，可能因为文件名里恰好有&quot;test&quot;字样就被规则误伤），LLM 这一层负责把&quot;真正的作弊&quot;和&quot;正常行为被规则误伤&quot;区分开</li></ol><p><strong>处理方式：</strong>不是直接终止整条轨迹、判定失败，而是拦截这一次具体的工具调用，返回一个&quot;假信息&quot;（dummy information），让模型以为这次尝试没有成功，然后轨迹继续往下走。</p><p>如果每次抓到作弊就粗暴地把整条轨迹判负、突然终止，会带来两个问题：</p><ol start="1"><li>训练信号突变：轨迹被&quot;腰斩&quot;，模型在那个时间点之前的所有正常操作也可能被连带惩罚，这是一种不精确的信用分配（credit assignment）——作弊只发生在某一步，但惩罚却波及了整条轨迹</li><li>训练不稳定/模型坍塌：如果这种&quot;突然终止&quot;频繁发生，尤其是在长周期任务里（一条轨迹可能已经花了很长时间、很多步骤才走到作弊那一步），会给训练过程引入剧烈的方差和不连续性，容易导致我们之前讨论过的那种&quot;训练崩溃&quot;（类似非确定性top-k导致熵骤降的那种失稳模式）</li></ol><p>未完待续...</p></div><p style="text-align:right"><a href="https://bambooo.top/posts/tech/glm52_rl#comments">览毕，何不一言？</a></p></div>]]></description><link>https://bambooo.top/posts/tech/glm52_rl</link><guid isPermaLink="true">https://bambooo.top/posts/tech/glm52_rl</guid><dc:creator><![CDATA[扳布]]></dc:creator><pubDate>Fri, 10 Jul 2026 08:33:01 GMT</pubDate></item><item><title><![CDATA[飞书机器人助手停止服务]]></title><description><![CDATA[<link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/74w5a1gfhikpif0vkb.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/6tgqzw612y19shrt1t.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/56piyn0o4kkq8gmsjt.webp"/><div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://bambooo.top/posts/fiddle/bot_stop">https://bambooo.top/posts/fiddle/bot_stop</a></blockquote><div><p>今天1号发工资，我和往常一样使用快捷指令记账，然后准备打开飞书仪表盘查看上个月支出情况，但是最新记录却停留在昨天早上</p><p><img height="104" src="https://bambooo.top/api/v3/objects/image/74w5a1gfhikpif0vkb.webp" width="1702"/></p><p>我看模型API接口没问题，能正确解析出json，接口使用的硅基流动，代金券余额也很充足</p><p><img src="https://bambooo.top/api/v3/objects/image/6tgqzw612y19shrt1t.webp"/></p><p>这个时候就有点懵了，链路都没问题啊...总不能是飞书机器人助手挂了吧，这机器人我可都建了快两年了。</p><p>嘿，您猜怎么着</p><p><img src="https://bambooo.top/api/v3/objects/image/56piyn0o4kkq8gmsjt.webp"/></p><p>跑通这个链路花了我不少时间啊😭，心碎了，得想办法在服务器建个hook，以后把数据放服务器上吧。</p></div><p style="text-align:right"><a href="https://bambooo.top/posts/fiddle/bot_stop#comments">览毕，何不一言？</a></p></div>]]></description><link>https://bambooo.top/posts/fiddle/bot_stop</link><guid isPermaLink="true">https://bambooo.top/posts/fiddle/bot_stop</guid><dc:creator><![CDATA[扳布]]></dc:creator><pubDate>Wed, 01 Jul 2026 09:07:28 GMT</pubDate></item><item><title><![CDATA[用“足迹”聚合]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://bambooo.top/posts/fiddle/footprint">https://bambooo.top/posts/fiddle/footprint</a></blockquote><div><p>起因是看到博友<a href="https://imqi1.com的">棋</a>足迹页面，非常喜欢，通过地图将博文、评论以及友人关联起来，可玩性和观赏性都很高。于是打算页借鉴想法做一个足迹页面。于是有了<a href="https://bambooo.top/footprints">足迹</a></p><p>Mx-space的日记本身具有地点功能，而日记也正是我用来记录我到达某个地方的”打卡”博文，符合足迹的性质。</p><p>足迹页面聚合了日记以及评论，友链以及动态，所以原有的友圈入口暂时移除。地图引擎使用的是高德，也尝试过其他的，但是样式实在不忍直视。之所以会尝试其他的，还有一个原因，高德的地图有个小瑕疵，当拖动地图到最上面或者最下面，会有一片空白区域，网上搜索了很多其他博主的足迹地图，凡是使用高德的，基本都存在这个问题，我本身不是做前端的，只能让AI来改，改了n轮也没给这个空白改掉，做这个页面快花光了一周的coding额度😭（欢迎大佬在评论区给出方案）。</p></div><p style="text-align:right"><a href="https://bambooo.top/posts/fiddle/footprint#comments">览毕，何不一言？</a></p></div>]]></description><link>https://bambooo.top/posts/fiddle/footprint</link><guid isPermaLink="true">https://bambooo.top/posts/fiddle/footprint</guid><dc:creator><![CDATA[扳布]]></dc:creator><pubDate>Sun, 28 Jun 2026 06:47:53 GMT</pubDate></item><item><title><![CDATA[端午流水账]]></title><description><![CDATA[<link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/u6zoowjc4n5pvyta19.webp"/><link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/oap7z006g5x7o9fvvb.webp"/><div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://bambooo.top/notes/6">https://bambooo.top/notes/6</a></blockquote><div><p>又是一个端午小长假，还是对象来找我，在她来之前已经约好了去欢乐谷
<img height="1920" src="https://bambooo.top/api/v3/objects/image/u6zoowjc4n5pvyta19.webp" width="1440"/></p><p>人很多，一个项目平均要排队半小时，游戏时长一般就几分钟，所以，一天下来差不多能玩4、5个（排队很累，很长时间都在休息了，外加到处跑），我们去之前也没考虑到水上项目，很多有意思的项目也就没体验上。摩天轮需要到月底才开放运营，有点生气。</p><p>这是我人生第一次进游乐场，体验还是不错的。我能想象到，如果在学生时代，这种快乐会翻很多倍，在经历社会捶打后，面对各种事情，不论好坏，情绪都相对平静（这里得解释下，父母很爱我，只是家里条件普普通通，资源会尽可能的用在刚需上）。我一直很反对要孩子，我知道我不能给他带来很好的条件，也担心我会变成“中式家长“，教育方式会不会随着惯性施加到我的下一代身上，太多的不确定了；扯远了，思维比较发散。</p><p>昨天过完了25岁生日，总感觉自己还小，也经常做出一些幼稚的举动，这种感觉挺有意思，只有我心里知道，我并不成熟，但是在外人看来，我已经是个”大人”了，嘿嘿，像套着”大人皮“装成熟的小孩
<img src="https://bambooo.top/api/v3/objects/image/oap7z006g5x7o9fvvb.webp"/></p></div><p style="text-align:right"><a href="https://bambooo.top/notes/6#comments">览毕，何不一言？</a></p></div>]]></description><link>https://bambooo.top/notes/6</link><guid isPermaLink="true">https://bambooo.top/notes/6</guid><dc:creator><![CDATA[扳布]]></dc:creator><pubDate>Mon, 22 Jun 2026 07:25:44 GMT</pubDate></item><item><title><![CDATA[奇怪的惰性]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://bambooo.top/notes/5">https://bambooo.top/notes/5</a></blockquote><div><p>我上一份工作，也是大学毕业后的第一份工作，在深圳南山某不知名科技公司，部门加班文化严重，组内更是严重（主要是由于那个恶心的组长喜欢无差别PUA所有组员导致，这个事我想可以单开一篇来讲），周内八九点下班是常态，周六日加班也不在少数，那会头痛的频率非常高，但是，哪怕是这种状况下，周六日还是会逼着自己学点东西，总认为不进步就会落后，后面就找不到好工作，一系列想法让自己变得很焦。</p><p>辞职后，在家休息了一小段时间，开始投递简历，最终选择了一家北京的公司（很大一部分原因是有两个大学室友都在北京，有玩伴），这家公司面试结束后hr就说明了加班很少，几乎不加班，来了之后也确实如此，工作半年了，除了部分时候觉得工作完成的不满意主动带电脑回家办公，其余时候都是到点下班。奇怪的是，在这种空闲时间很多的情况下，反而不愿意主动学习了（当然，也不是完全不学习，只是主动性少很多），脑子里也有了很多摆烂似的想法，比如“这样也挺好的，为什么非要一直进步呢，能养好自己，每个月存点钱，开开心心”。</p><p>高压情况下，反而促进自己主动学习，现在想来，当时的心态，很大部分原因是觉得学了更多东西，能去到更好的企业，逃离那个让自己产生大量负面情绪的地方。</p></div><p style="text-align:right"><a href="https://bambooo.top/notes/5#comments">览毕，何不一言？</a></p></div>]]></description><link>https://bambooo.top/notes/5</link><guid isPermaLink="true">https://bambooo.top/notes/5</guid><dc:creator><![CDATA[扳布]]></dc:creator><pubDate>Mon, 15 Jun 2026 06:57:04 GMT</pubDate></item><item><title><![CDATA[博客增加友圈]]></title><description><![CDATA[<link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/dyfpvc96lpr2m503o8.png"/><div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://bambooo.top/posts/fiddle/circle">https://bambooo.top/posts/fiddle/circle</a></blockquote><div><p>对于博客，还是希望随时能看到网上的朋友们最近的新动态，于是vibe coding了一个友圈页面。</p><p>之前一直通过github action根据innei的yohaku仓库构建部署，这种方法就是省事，作者的新功能能及时更新，缺点就是无法客制化，所以还是clone了一份以便定制化修改，这个友圈页面一直想加上，得益于现在的agent足够强大，一天时间就可以实现一个不错的效果。</p><p><img height="997" src="https://bambooo.top/api/v3/objects/image/dyfpvc96lpr2m503o8.png" width="1895"/></p></div><p style="text-align:right"><a href="https://bambooo.top/posts/fiddle/circle#comments">览毕，何不一言？</a></p></div>]]></description><link>https://bambooo.top/posts/fiddle/circle</link><guid isPermaLink="true">https://bambooo.top/posts/fiddle/circle</guid><dc:creator><![CDATA[扳布]]></dc:creator><pubDate>Fri, 05 Jun 2026 08:34:55 GMT</pubDate></item><item><title><![CDATA[如何管理上下文]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://bambooo.top/posts/think/context_manage">https://bambooo.top/posts/think/context_manage</a></blockquote><div><h2 id="">前言</h2><p>日常使用编程 Agent 的时候，总会考虑到上下文窗口的大小，首先，不同厂商模型的上下文长度不一样，我日常使用 Claude Code（以下简称cc），搭配 GLM5.1、Deepseek-v4-pro，前者支持最大上下文窗口200k，后者能支持长达1M。</p><p>对其他领域不太了解，拿编程来说，当你上下文达到1M，前期任务对话的注意力已经被稀释了，200k的上下文超出后，在经过多次compact后，早期内容也会被截断，而且还会有幻觉累计的风险，长会话中如果某个错误理解没被纠正，后续会在错误基础上叠加；再者，一个会话只能做一件事，无法并行（当然，并不是说短会话就可以并行，还得看你的项目是否足够模块化，各层之间边界是否清晰），可见一直在一个会话中对话的性价比并不高。那是不是应该一个任务开一个会话呢，当然也不是，这种做法很容易想到的一点就是，重复喂上下文，每次都要重新解释项目背景、架构、约定...，效率极低，而且缺乏连贯性，模型不知道之前做了哪些决策，容易走回头路甚至错路。</p><h2 id="">可参考方案</h2><blockquote><p>核心思路是：用文件代替会话记忆，上下文存在于代码库本身，而不是对话历史里</p></blockquote>
<h3 id="-claudemd">维护好 CLAUDE.md</h3><p>众所周知，cc有一个很核心的文件 —— <code>CLAUDE.MD</code>，你在项目根目录下进入cc，执行<code>/init</code>，大模型就会读取项目内容自动创建这个文件</p><p>如果你的项目已经有框架了，那么直接让大模型生成会比较省事，如果是新项目，则需要你手动编写好背景、架构、约束等</p><p>这个文件不仅是一个项目说明，更是一个项目记忆文件，cc每次启动都会读取它，建议 <strong>每完成一个阶段任务就更新这个文件</strong>，新会话一开始就有完整上下文，这一步也可以让大模型来帮你做，不过还是建议 review 一下</p><h3 id="">按&quot;工作单元&quot;切割会话，而不是按功能</h3><p>不是&quot;每个功能开一个会话&quot;，而是一个聚焦的任务开一个会话：</p><pre class="language-markdown lang-markdown"><code class="language-markdown lang-markdown">✅ 好的会话边界
- &quot;实现订单创建的 API + service + 数据库 migration&quot;
- &quot;修复购物车计算逻辑的 3 个 bug&quot;
- &quot;重构用户模块，拆分成更小的 service&quot;

❌ 不好的边界  
- &quot;帮我开发整个电商后台&quot;（太大）
- &quot;改一行代码&quot;（太小，不值得切换）
</code></pre>
<h3 id="-prompt-">第一个 prompt 规范好边界</h3><p>新会话开头不要直接说&quot;帮我写XXX&quot;，而是：</p><pre class="language-markdown lang-markdown"><code class="language-markdown lang-markdown">背景：我在开发电商项目（Next.js + PostgreSQL），CLAUDE.md 里有架构细节。
当前任务：实现订单状态流转逻辑（待付款→已付款→已发货→已完成）
相关文件：/app/api/orders/, /lib/services/orderService.ts
约束：状态流转需要记录操作日志，不能直接改 status 字段
</code></pre>
<h3 id="-clear-">利用 /clear 而不是开新窗口</h3><p>当一个任务完成、要切换到另一个任务时，在同一个 Claude Code 实例里用 /clear 清空对话历史，项目文件仍然在，只清掉对话噪音，比重开一个会话更高效。</p>
<h2 id="">总结</h2><p>核心其实就是一句话，<strong>上下文应该活在代码和文档里，而不是活在对话历史里。</strong></p></div><p style="text-align:right"><a href="https://bambooo.top/posts/think/context_manage#comments">览毕，何不一言？</a></p></div>]]></description><link>https://bambooo.top/posts/think/context_manage</link><guid isPermaLink="true">https://bambooo.top/posts/think/context_manage</guid><dc:creator><![CDATA[扳布]]></dc:creator><pubDate>Mon, 01 Jun 2026 06:49:28 GMT</pubDate></item><item><title><![CDATA[IOS快捷指令 + 飞书 + LLM全自动记账]]></title><description><![CDATA[<link rel="preload" as="image" href="https://bambooo.top/api/v3/objects/image/20260531163535633.gif"/><div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://bambooo.top/posts/fiddle/record_llm">https://bambooo.top/posts/fiddle/record_llm</a></blockquote><div><h2 id="tldr">TLDR</h2><p>24年有个挺火的通过IOS快捷指令 + 飞书（或者Number表格）实现自动记账</p><p><a href="https://www.bilibili.com/video/BV1Wf421B7JY/?spm_id_from=333.337.search-card.all.click&amp;vd_source=71450b2ba3f42305b121d0eff7f875a8">https://www.bilibili.com/video/BV1Wf421B7JY/?spm_id_from=333.337.search-card.all.click&amp;vd_source=71450b2ba3f42305b121d0eff7f875a8</a></p><p>我也跟着实现了一下，用到了现在，整个过程其实很方便了，通过IOS的OCR提取文字，再通过正则提取出数字，手动选择收支类型，用途或者来源。</p><h2 id="">改进</h2><p>但是昨天想到，可以通过接入LLM，提取出的文字也不再通过正则来提取了，直接把所有文字扔给大模型，让它来提取我们需要的内容（金额、收支类型、用途、备注），以json返回，我们再将json通过飞书接口保存，真正实现自动记账，效果如下</p>
<p><img alt="全自动记账" src="https://bambooo.top/api/v3/objects/image/20260531163535633.gif"/></p><p>可以看到，整个过程只需要确定金额是正确的，这一步其实也可以省掉，现在的大模型足够强，根据一些关键信息能推断出实付金额，比如这个案例中，能识别出是111.5，我用的glm4.7，实际消费也非常之低，一次差不多就一分钱左右，如果不是收支很频繁，一块钱够用几个月</p></div><p style="text-align:right"><a href="https://bambooo.top/posts/fiddle/record_llm#comments">览毕，何不一言？</a></p></div>]]></description><link>https://bambooo.top/posts/fiddle/record_llm</link><guid isPermaLink="true">https://bambooo.top/posts/fiddle/record_llm</guid><dc:creator><![CDATA[扳布]]></dc:creator><pubDate>Sun, 31 May 2026 08:39:23 GMT</pubDate></item><item><title><![CDATA[Yohaku 部署]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://bambooo.top/posts/fiddle/deploy_yohaku">https://bambooo.top/posts/fiddle/deploy_yohaku</a></blockquote><div><blockquote><p>以下为 Shiro 闭源版本 Yohaku 部署方案，可能随着版本更新而不再适用</p></blockquote>
<h2 id="-1panel">安装 1Panel</h2><pre class=""><code class="">curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh &amp;&amp; sudo bash quick_start.sh
</code></pre>
<p>配置镜像输入 y（安装openresty需要），其他一律 enter 即可</p><p>安装完成后，打开1panel网址，进入应用商店，安装openresty</p><h2 id="">后端部署</h2><blockquote><p>正常需要安装 docker，不过由于 1Panel 面板默认会安装 docker，所以这里不需要，如果使用宝塔面板或其他方式，请自行安装docker</p></blockquote>
<h3 id="">拉取并修改配置文件</h3><pre class="language-bash lang-bash"><code class="language-bash lang-bash">#回到根目录并创建mx-space/core的文件夹，并跳转至core文件夹下
cd &amp;&amp; mkdir -p mx-space/core &amp;&amp; cd $_ 
# 拉取 docker-compose.yml 文件
wget https://fastly.jsdelivr.net/gh/mx-space/core@master/docker-compose.yml
</code></pre>
<p>主要修改<code>ALLOWED_origins</code>和<code>JWT_SECRET</code></p><pre class="language-yml lang-yml"><code class="language-yml lang-yml"># Shared env block — mx-migrate boots a full Nest context (it now runs the
# combined schema + app-data migration phases), so it must receive the same
# config the runtime app needs (Redis, snowflake worker id, jwt secret, …).
x-mx-env: &amp;mx-env
  TZ: Asia/Shanghai
  NODE_ENV: production
  REDIS_HOST: redis
  PG_HOST: postgres
  PG_PORT: &quot;5432&quot;
  PG_USER: mx
  PG_PASSWORD: mx
  PG_DATABASE: mx_core
  SNOWFLAKE_WORKER_ID: &quot;1&quot;
  ALLOWED_ORIGINS: bambooo.top,localhost:*,127.0.0.1:*
  JWT_SECRET: xxxxxxxx

services:
  app:
    container_name: mx-server
    image: innei/mx-server:latest
    environment: *mx-env
    volumes:
      - ./data/mx-space:/root/.mx-space
    ports:
      - &#x27;2333:2333&#x27;
    depends_on:
      mx-migrate:
        condition: service_completed_successfully
      redis:
        condition: service_started
    networks:
      - mx-space
    restart: unless-stopped
    healthcheck:
      test: [CMD, curl, -f, &#x27;http://127.0.0.1:2333/api/v3/health&#x27;]
      interval: 1m30s
      timeout: 30s
      retries: 5
      start_period: 30s

  # One-shot release-phase migration runner. Runs schema migrations (drizzle)
  # then app-data migrations (Nest-bootstrapped registry). Exits 0; the `app`
  # service waits for completion via `service_completed_successfully` before
  # starting. See docs/superpowers/specs/2026-05-05-database-migration-release-phase-design.md.
  mx-migrate:
    container_name: mx-migrate
    image: innei/mx-server:latest
    command: [&#x27;node&#x27;, &#x27;migrate.mjs&#x27;]
    environment: *mx-env
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    networks:
      - mx-space
    restart: &#x27;no&#x27;

  postgres:
    container_name: postgres
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=mx
      - POSTGRES_PASSWORD=mx
      - POSTGRES_DB=mx_core
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    healthcheck:
      test: [&#x27;CMD-SHELL&#x27;, &#x27;pg_isready -U mx -d mx_core&#x27;]
      interval: 30s
      timeout: 5s
      retries: 5
      start_period: 10s
    networks:
      - mx-space
    restart: unless-stopped

  redis:
    image: redis:alpine
    container_name: redis
    volumes:
      - ./data/redis:/data
    healthcheck:
      test: [CMD-SHELL, &#x27;redis-cli ping | grep PONG&#x27;]
      start_period: 20s
      interval: 30s
      retries: 5
      timeout: 3s
    networks:
      - mx-space
    restart: unless-stopped

networks:
  mx-space:
    driver: bridge
</code></pre>
<h3 id="-core">启动 core</h3><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker compose up -d
</code></pre>
<p>这里需要几分钟时间，耐心等待</p><h2 id="">网站创建</h2><blockquote><p>以下步骤均在 1Panel 中进行</p></blockquote>
<h3 id="">证书申请</h3><p>先创建Acme账户，账号类型建议选ZeroSSL，省事</p><p>创建完后申请证书 → 填写主域名 → Acme账户 → 秘钥算法（EC 256） → 验证方式（HTTP） → 自动续签 → 确认</p><h3 id="">创建网站</h3><p>创建 → 静态网站 → 监听IPV6 → 配置 → HTTPS（开启，选择刚才的证书） → 配置文件（将下面内容插入到中间）</p><p>我是单域名配置，如果你是双域名，参考官方文档</p><p> <a href="https://mx-space.js.org/docs/deploy/reverse-proxy#%E5%8F%8C%E5%9F%9F%E5%90%8D%E9%85%8D%E7%BD%AE">双域名配置</a></p><pre class="language-nginx lang-nginx"><code class="language-nginx lang-nginx"># WebSocket 地址
location /socket.io {
    proxy_set_header Upgrade $http_upgrade; 
    proxy_set_header Connection &quot;Upgrade&quot;; 
    proxy_buffering off; 
    proxy_set_header Host $host; 
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
    proxy_set_header X-Forwarded-Proto $scheme; 
    proxy_pass http://127.0.0.1:2333/socket.io; 
}
# API 地址
location /api/v3 {
    proxy_pass http://127.0.0.1:2333/api/v3; 
}
# 简读 render 地址
location /render {
    proxy_pass http://127.0.0.1:2333/render; 
}
# Shiro 地址
location / {
    proxy_pass http://127.0.0.1:2323; 
}
# 后台地址
location /proxy {
    proxy_pass http://127.0.0.1:2333/proxy; 
}
location /qaqdmin { #如果需要修改自己的后台地址可以改这里
    proxy_pass http://127.0.0.1:2333/proxy/qaqdmin; 
}
</code></pre>
<p>修改完毕，点击保存并重载</p><p>访问 <a href="https://你的域名/qaqdmin">https://你的域名/qaqdmin</a> 就可以正常进入博客后台（如果碰到问题也不要急，可以在评论区留下你的问题，或许可以给你提供一些帮助），然后就是简单的填写博客基本信息</p><h2 id="">前端部署</h2><blockquote><p>这一步中间可能会出现各种错误，建议在服务器上安装 Claude Code，让大模型帮你分析排查问题，一般都能解决</p></blockquote>
<p>我采用的是 Innei 提供的自动部署工作流方式</p><h3 id="fork">Fork</h3><p><a href="https://github.com/innei-dev/yohaku-deploy-action">yohaku-deploy-action</a></p><p>打开github，fork上面的仓库</p><h3 id="-secrets">设置 Secrets</h3><p>打开仓库，进入 Settings → Secrets and variables → Actions → Repository secrets → 挨个添加下面的参数</p><ul><li><code>HOST</code> 服务器地址</li><li><code>USER</code> 服务器用户名</li><li><code>PASSWORD</code> 服务器密码</li><li><code>PORT</code> 服务器 SSH 端口</li><li><code>KEY</code> 服务器 SSH Key（可选，密码 key 二选一）</li><li><code>GH_PAT</code> 可访问之前 repository 内填写仓库的 Github Token</li></ul><h3 id="-env">配置 .env</h3><p>在服务器 root 下创建 yohaku 文件夹，并在其下创建 .env</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">cd root/
mkdir yohaku
cd yohaku/
touch .env
vim .env
</code></pre>
<p>填写以下内容</p><pre class="language-ENV lang-ENV"><code class="language-ENV lang-ENV">BASE_URL=

NEXT_PUBLIC_API_URL=
NEXT_PUBLIC_CLIENT_API_URL=
NEXT_PUBLIC_GATEWAY_URL=

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=

NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/

TMDB_API_KEY=

GH_TOKEN=
API_URL=
</code></pre>
<h3 id="-deploy">修改 deploy</h3><p>打开 <code>.github/workflows/deploy.yml</code> 进行修改</p><pre class="language-yml lang-yml"><code class="language-yml lang-yml">name: Build and Deploy

on:
  push:
    branches:
      - main
  # schedule:
  #   - cron: &#x27;0 3 * * *&#x27;

  repository_dispatch:
    types: [trigger-workflow]

permissions: write-all
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  HASH_FILE: build_hash
  SOURCE_REPO: innei-dev/Yohaku
  BUILD_COMMAND: pnpm --filter @yohaku/web build:ci
  STANDALONE_SUBPATH: standalone/apps/web

jobs:
  prepare:
    name: Prepare
    runs-on: ubuntu-latest
    if: ${{ github.event.head_commit.message != &#x27;Update hash file&#x27; }}

    outputs:
      hash_content: ${{ steps.read_hash.outputs.hash_content }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Read HASH_FILE content
        id: read_hash
        run: |
          content=$(cat ${{ env.HASH_FILE }}) || true
          echo &quot;hash_content=$content&quot; &gt;&gt; &quot;$GITHUB_OUTPUT&quot;
  check:
    name: Check Should Rebuild
    runs-on: ubuntu-latest
    needs: prepare
    outputs:
      canceled: ${{ steps.use_content.outputs.canceled }}

    steps:
      - uses: actions/checkout@v4
        with:
          repository: innei-dev/Yohaku
          token: ${{ secrets.GH_PAT }} # `GH_PAT` is a secret that contains your PAT
          fetch-depth: 0
          lfs: true
          submodules: recursive

      - name: Use content from prev job and compare
        id: use_content
        env:
          FILE_HASH: ${{ needs.prepare.outputs.hash_content }}
        run: |
          file_hash=$FILE_HASH
          current_hash=$(git rev-parse --short HEAD)
          echo &quot;File Hash: $file_hash&quot;
          echo &quot;Current Git Hash: $current_hash&quot;
          if [ &quot;$file_hash&quot; == &quot;$current_hash&quot; ]; then
            echo &quot;Hashes match. Stopping workflow.&quot;
            echo &quot;canceled=true&quot; &gt;&gt; $GITHUB_OUTPUT
          else
            echo &quot;Hashes do not match. Continuing workflow.&quot;
          fi

  build:
    name: Build artifact
    runs-on: ubuntu-latest
    needs: check
    if: ${{needs.check.outputs.canceled != &#x27;true&#x27;}}

    strategy:
      matrix:
        node-version: [lts/*]
    outputs:
      sha_short: ${{ steps.store.outputs.sha_short }}
      branch: ${{ steps.store.outputs.branch }}
    steps:
      - uses: actions/checkout@v4
        with:
          repository: ${{ env.SOURCE_REPO }}
          token: ${{ secrets.GH_PAT }} # `GH_PAT` is a secret that contains your PAT
          fetch-depth: 0
          lfs: true
          submodules: recursive

      - name: Checkout LFS objects
        run: git lfs checkout
      - uses: pnpm/action-setup@v2

      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: &#x27;pnpm&#x27;
      - uses: jongwooo/next-cache@v1
      - name: Install dependencies
        run: pnpm install
      - name: Build project
        env:
          BASE_URL: ${{ secrets.BASE_URL }}
          NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}
          NEXT_PUBLIC_GATEWAY_URL: ${{ secrets.NEXT_PUBLIC_GATEWAY_URL }}
        run: |
          ${{ env.BUILD_COMMAND }}
          cd apps/web/.next
          rm -rf cache
          cp -r ../public ./standalone/public
          mv ./static ./standalone/apps/web/.next/static
          cd ./standalone
          echo &#x27;;process.title = &quot;Yohaku (NextJS)&quot;&#x27; &gt;&gt; server.js
          cd &quot;$GITHUB_WORKSPACE&quot;
          mkdir -p assets
          rm -f assets/release.zip
          (cd apps/web/.next &amp;&amp; zip --symlinks -r &quot;$GITHUB_WORKSPACE/assets/release.zip&quot; ./* -x &quot;dev/*&quot; -x &quot;cache/*&quot;)

      # - uses: actions/upload-artifact@v4
      #   with:
      #     name: dist
      #     path: assets/release.zip
      #     retention-days: 7
      - name: Cache Build Artifacts
        id: cache-primes
        uses: actions/cache/save@v4
        with:
          path: assets
          key: ${{ github.run_number }}-release

      - name: Store artifact commit version
        shell: bash
        id: store
        run: |
          sha_short=$(git rev-parse --short HEAD)
          branch_name=$(git rev-parse --abbrev-ref HEAD)
          echo &quot;sha_short=$sha_short&quot; &gt;&gt; &quot;$GITHUB_OUTPUT&quot;
          echo &quot;branch=$branch_name&quot; &gt;&gt; &quot;$GITHUB_OUTPUT&quot;

  deploy:
    name: Deploy artifact
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/cache/restore@v4
        with:
          path: assets
          key: ${{ github.run_number }}-release

      - name: Restore cached Build Artifacts
        id: cache-primes-restore
        uses: actions/cache/restore@v4
        with:
          path: |
            assets
          key: ${{ github.run_number }}-release
      - name: Move assets to root
        run: mv assets/release.zip release.zip

      - name: Prepare standalone PM2 config
        run: cp pm2/ecosystem.config.js ecosystem.config.js

      - name: copy file via ssh password
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USER }}
          password: ${{ secrets.PASSWORD }}
          key: ${{ secrets.KEY }}
          port: ${{ secrets.PORT }}
          source: &#x27;release.zip&#x27;
          target: &#x27;/tmp/yohaku&#x27;

      - name: copy PM2 config via ssh password
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USER }}
          password: ${{ secrets.PASSWORD }}
          key: ${{ secrets.KEY }}
          port: ${{ secrets.PORT }}
          source: &#x27;ecosystem.config.js&#x27;
          target: &#x27;/tmp/yohaku&#x27;

      - name: Exec deploy script with SSH
        uses: appleboy/ssh-action@master

        with:
          command_timeout: 5m
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USER }}
          password: ${{ secrets.PASSWORD }}
          key: ${{ secrets.KEY }}
          port: ${{ secrets.PORT }}
          script: |
            set -e
            source $HOME/.bashrc
            basedir=$HOME/yohaku
            workdir=$basedir/${{ github.run_number }}
            mkdir -p $workdir
            mkdir -p $basedir/.cache
            mv /tmp/yohaku/release.zip $workdir/release.zip
            install -m 644 /tmp/yohaku/ecosystem.config.js $basedir/ecosystem.config.js
            cd $workdir
            unzip -qq -o $workdir/release.zip
            rm -r /tmp/yohaku
            rm -rf $workdir/${{ env.STANDALONE_SUBPATH }}/.env
            ln -s $HOME/yohaku/.env $workdir/${{ env.STANDALONE_SUBPATH }}/.env
            export NEXT_SHARP_PATH=$(npm root -g)/sharp
            # https://github.com/Unitech/pm2/issues/3054
            # symlink workdir node entry file to basedir
            ln -sf $workdir/${{ env.STANDALONE_SUBPATH }}/server.js $basedir/server.js
            mkdir -p $workdir/${{ env.STANDALONE_SUBPATH }}/.next
            rm -rf $workdir/${{ env.STANDALONE_SUBPATH }}/.next/cache
            ln -sf $basedir/.cache $workdir/${{ env.STANDALONE_SUBPATH }}/.next/cache
            cd $basedir
            if pm2 describe Yohaku &gt;/dev/null 2&gt;&amp;1; then
              pm2 reload ecosystem.config.js --update-env
            else
              pm2 start ecosystem.config.js --update-env
            fi
            rm $workdir/release.zip
            pm2 save
            echo &quot;Deployed successfully&quot;

      - name: After deploy script
        run: |
          hash=${{ needs.build.outputs.sha_short }}
          # curl -X &quot;POST&quot; &quot;https://mx.innei.in/api/v2/fn/shiro/new-version-hook&quot; -H &#x27;Content-Type: application/json&#x27; -d &quot;{\&quot;hash\&quot;: \&quot;$hash\&quot;, \&quot;key\&quot;: \&quot;\&quot;}&quot;
          ${{ secrets.AFTER_DEPLOY_SCRIPT }}
  store:
    name: Store artifact commit version
    runs-on: ubuntu-latest
    needs: [deploy, build]
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token
          fetch-depth: 0 # otherwise, you will failed to push refs to dest repo
      # Get the commit version from the build job
      - name: Use outputs from build
        env:
          SHA_SHORT: ${{ needs.build.outputs.sha_short }}
          BRANCH: ${{ needs.build.outputs.branch }}
        run: |
          echo &quot;SHA Short from build: $SHA_SHORT&quot;
          echo &quot;Branch from build: $BRANCH&quot;
      - name: Write hash to file
        env:
          SHA_SHORT: ${{ needs.build.outputs.sha_short }}

        run: echo $SHA_SHORT &gt; ${{ env.HASH_FILE }}
      - name: Commit files
        run: |
          git config --local user.email &quot;41898282+github-actions[bot]@users.noreply.github.com&quot;
          git config --local user.name &quot;github-actions[bot]&quot;
          git add ${{ env.HASH_FILE }}
          git commit -a -m &quot;Update hash file&quot;
      - name: Push changes
        uses: ad-m/github-push-action@master
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          branch: ${{ github.ref }}
</code></pre>
<p>如果直接使用 Innei 的部署过程中出现报错，可以参考我的进行修改，或者询问大模型，github自带的 Copilot 也挺好用的</p><p>改完之后 commit，会自动跑，可以进入<code>Actions</code>查看流程</p><h2 id="">配置云函数</h2><p>进入博客后台，附加功能 → 配置与云函数</p><p>创建一条 <code>theme</code> 引用、名称为 <code>shiro</code> 的配置项（数据类型 JSON 或 YAML），这里贴上官方提供的案例</p><pre class="language-json lang-json"><code class="language-json lang-json">{
  &quot;footer&quot;: {
    &quot;otherInfo&quot;: {
      &quot;date&quot;: &quot;2020-{{now}}&quot;,
      &quot;icp&quot;: {
        &quot;text&quot;: &quot;萌 ICP 备 20236136 号&quot;,
        &quot;link&quot;: &quot;https://icp.gov.moe/?keyword=20236136&quot;
      }
    },
    &quot;linkSections&quot;: [
      {
        &quot;name&quot;: &quot;关于&quot;,
        &quot;links&quot;: [
          { &quot;name&quot;: &quot;关于本站&quot;, &quot;href&quot;: &quot;/about-site&quot; },
          { &quot;name&quot;: &quot;关于我&quot;, &quot;href&quot;: &quot;/about&quot; },
          {
            &quot;name&quot;: &quot;关于此项目&quot;,
            &quot;href&quot;: &quot;https://github.com/Innei/Yohaku&quot;,
            &quot;external&quot;: true
          }
        ]
      },
      {
        &quot;name&quot;: &quot;更多&quot;,
        &quot;links&quot;: [
          { &quot;name&quot;: &quot;时间线&quot;, &quot;href&quot;: &quot;/timeline&quot; },
          { &quot;name&quot;: &quot;友链&quot;, &quot;href&quot;: &quot;/friends&quot; }
        ]
      },
      {
        &quot;name&quot;: &quot;联系&quot;,
        &quot;links&quot;: [
          { &quot;name&quot;: &quot;写留言&quot;, &quot;href&quot;: &quot;/message&quot; },
          { &quot;name&quot;: &quot;GitHub&quot;, &quot;href&quot;: &quot;https://github.com/innei&quot;, &quot;external&quot;: true }
        ]
      }
    ]
  },
  &quot;config&quot;: {
    &quot;color&quot;: {
      &quot;light&quot;: [&quot;#33A6B8&quot;, &quot;#FF6666&quot;, &quot;#26A69A&quot;, &quot;#fb7287&quot;, &quot;#69a6cc&quot;],
      &quot;dark&quot;: [&quot;#F596AA&quot;, &quot;#A0A7D4&quot;, &quot;#ff7b7b&quot;, &quot;#99D8CF&quot;, &quot;#838BC6&quot;]
    },
    &quot;site&quot;: {
      &quot;favicon&quot;: &quot;/favicon.svg&quot;,
      &quot;faviconDark&quot;: &quot;/favicon-dark.svg&quot;
    },
    &quot;hero&quot;: {
      &quot;title&quot;: {
        &quot;template&quot;: [
          {
            &quot;type&quot;: &quot;span&quot;,
            &quot;text&quot;: &quot;Hi, I&#x27;m &quot;,
            &quot;style&quot;: { &quot;fontWeight&quot;: 300, &quot;opacity&quot;: 0.85 }
          },
          {
            &quot;type&quot;: &quot;span&quot;,
            &quot;text&quot;: &quot;Innei&quot;,
            &quot;style&quot;: {
              &quot;fontWeight&quot;: 500,
              &quot;color&quot;: &quot;var(--color-accent)&quot;,
              &quot;letterSpacing&quot;: &quot;-0.02em&quot;
            }
          },
          {
            &quot;type&quot;: &quot;span&quot;,
            &quot;text&quot;: &quot; 👋&quot;,
            &quot;style&quot;: {
              &quot;fontWeight&quot;: 300,
              &quot;display&quot;: &quot;inline-block&quot;,
              &quot;transform&quot;: &quot;rotate(-8deg)&quot;
            }
          },
          { &quot;type&quot;: &quot;br&quot; },
          {
            &quot;type&quot;: &quot;span&quot;,
            &quot;text&quot;: &quot;A NodeJS Full Stack &quot;,
            &quot;style&quot;: { &quot;fontWeight&quot;: 300, &quot;opacity&quot;: 0.8 }
          },
          {
            &quot;type&quot;: &quot;code&quot;,
            &quot;text&quot;: &quot;&lt;Developer /&gt;&quot;,
            &quot;style&quot;: {
              &quot;display&quot;: &quot;inline-block&quot;,
              &quot;fontFamily&quot;: &quot;var(--font-mono)&quot;,
              &quot;fontSize&quot;: &quot;0.72em&quot;,
              &quot;fontWeight&quot;: 500,
              &quot;padding&quot;: &quot;0.25em 0.55em&quot;,
              &quot;borderRadius&quot;: &quot;0.35em&quot;,
              &quot;backgroundColor&quot;: &quot;color-mix(in srgb, var(--color-accent) 10%, transparent)&quot;,
              &quot;color&quot;: &quot;var(--color-accent)&quot;,
              &quot;border&quot;: &quot;1px solid color-mix(in srgb, var(--color-accent) 22%, transparent)&quot;
            }
          },
          {
            &quot;type&quot;: &quot;span&quot;,
            &quot;style&quot;: {
              &quot;display&quot;: &quot;inline-block&quot;,
              &quot;width&quot;: &quot;2px&quot;,
              &quot;height&quot;: &quot;0.9em&quot;,
              &quot;backgroundColor&quot;: &quot;var(--color-accent)&quot;,
              &quot;marginLeft&quot;: &quot;2px&quot;,
              &quot;animation&quot;: &quot;blink 1.2s linear infinite&quot;
            }
          }
        ]
      },
      &quot;description&quot;: &quot;An independent developer coding with love.&quot;
    },
    &quot;module&quot;: {
      &quot;activity&quot;: {
        &quot;enable&quot;: true,
        &quot;endpoint&quot;: &quot;/fn/ps/update&quot;
      },
      &quot;donate&quot;: {
        &quot;enable&quot;: false,
        &quot;link&quot;: &quot;&quot;,
        &quot;qrcode&quot;: []
      },
      &quot;bilibili&quot;: {
        &quot;liveId&quot;: 0
      },
      &quot;openpanel&quot;: {
        &quot;enable&quot;: false,
        &quot;id&quot;: &quot;&quot;,
        &quot;url&quot;: &quot;&quot;
      },
      &quot;posts&quot;: {
        &quot;mode&quot;: &quot;loose&quot;
      },
      &quot;signature&quot;: {
        &quot;svg&quot;: &quot;&quot;,
        &quot;animated&quot;: true
      }
    }
  }
}
</code></pre>
<p>修改好后保存</p><p>都完成后，访问前端，没问题的话，恭喜你部署成功</p></div><p style="text-align:right"><a href="https://bambooo.top/posts/fiddle/deploy_yohaku#comments">览毕，何不一言？</a></p></div>]]></description><link>https://bambooo.top/posts/fiddle/deploy_yohaku</link><guid isPermaLink="true">https://bambooo.top/posts/fiddle/deploy_yohaku</guid><dc:creator><![CDATA[扳布]]></dc:creator><pubDate>Fri, 29 May 2026 06:21:51 GMT</pubDate></item><item><title><![CDATA[Shiro/Yohaku 个人状态报错]]></title><description><![CDATA[<div><blockquote>此渲染由 Yohaku API 生成，或存排版之虞，最佳体验请往：<a href="https://bambooo.top/posts/fiddle/p_status">https://bambooo.top/posts/fiddle/p_status</a></blockquote><div><h2 id="tldr">TLDR</h2><p>mx-space 支持博主点击头像右下角发布个人状态</p><p>官方功能描述如下：</p><p><a href="https://mx-space.js.org/docs/themes/shiro/extra#%E4%B8%AA%E4%BA%BA%E7%8A%B6%E6%80%81%E5%B1%95%E7%A4%BA">个人状态展示</a></p><p>但是按照官方提供方法配置完后，发布状态时肯定会失败，控制台报错大致是：</p><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">95900-30e790acc8113b00.js:1
   GET https://bambooo.top/api/v3/fn/shiro/status?lang=zh 404 (Not Found)
</code></pre><p>也就是找不到 status 这个函数</p><h2 id="">修复方法</h2><p>云函数复制我的粘贴过去保存</p><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">interface Status {
    emoji: string
    icon?: string
    desc?: string
    ttl: number
    untilAt: number
  }

  function assetAuth(ctx: Context) {
    const body = ctx.req.body
    const authKey = ctx.secret.key
    if (ctx.isAuthenticated) return
    if (body.key !== authKey) {
      throw new Error(&#x27;Unauthorized&#x27;)
    }
  }

  export default async function handler(ctx: Context) {
    const method = ctx.req.method.toLowerCase()

    switch (method) {
      case &#x27;get&#x27;: {
        return GET(ctx)
      }
      case &#x27;post&#x27;: {
        assetAuth(ctx)
        return POST(ctx)
      }
      case &#x27;delete&#x27;: {
        assetAuth(ctx)
        return DELETE(ctx)
      }
      case &#x27;options&#x27;: {
        return null
      }
      default: {
        throw new Error(&#x27;Method Not Allowed&#x27;)
      }
    }
  }

  const cacheKey = &#x27;shiro:status&#x27;

  async function DELETE(ctx: Context) {
    await ctx.storage.cache.del(cacheKey)
    ctx.broadcast(&#x27;shiro#status&#x27;, null)
  }

  async function POST(ctx: Context) {
    const body = ctx.req.body

    const { emoji, icon, desc } = body as Status
    const ttl = body.ttl || 86400

    const status = {
      emoji,
      icon,
      desc,
      ttl,
      untilAt: Date.now() + ttl * 1000,
    } as Status
    await ctx.storage.cache.set(cacheKey, JSON.stringify(status), ttl)
    ctx.broadcast(&#x27;shiro#status&#x27;, status)
    return null
  }

  async function GET(ctx: Context) {
    const status = await ctx.storage.cache.get(cacheKey)
    return status
  }
</code></pre></div><p style="text-align:right"><a href="https://bambooo.top/posts/fiddle/p_status#comments">览毕，何不一言？</a></p></div>]]></description><link>https://bambooo.top/posts/fiddle/p_status</link><guid isPermaLink="true">https://bambooo.top/posts/fiddle/p_status</guid><dc:creator><![CDATA[扳布]]></dc:creator><pubDate>Fri, 29 May 2026 06:08:31 GMT</pubDate></item></channel></rss>