<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog</id>
    <title>ChatBlog Blog</title>
    <updated>2026-09-08T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://arch.gh.wzhecnu.cn/ChatBlog/blog"/>
    <subtitle>ChatBlog Blog</subtitle>
    <icon>https://arch.gh.wzhecnu.cn/ChatBlog/img/favicon.svg</icon>
    <entry>
        <title type="html"><![CDATA[用 Python 接入火山方舟：从 AK/SK 到两套 Plan 密钥]]></title>
        <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-agent-coding-plan-guide</id>
        <link href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-agent-coding-plan-guide"/>
        <updated>2026-09-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[从 AK/SK 管理密钥，到 ChatEnv 隔离配置，再用 Python 实测 Coding Plan 与 Agent Plan 的 Chat Completions 和 Responses。]]></summary>
        <content type="html"><![CDATA[<p>我用同一个火山引擎账号，把 Coding Plan 和 Agent Plan 分别接到了 Python。两套配置各调用一次 Chat Completions、一次 Responses，四次请求都返回了 <code>PLAN_OK</code>。</p>
<p>这篇文章保留完成这件事所需的步骤：在哪里拿 Key、AK/SK 如何参与密钥管理、两个地址怎样区分，以及代码究竟返回了什么。示例里的凭据均为占位符；完整密钥不进入文章、仓库或下载包。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="先把两个地址配对">先把两个地址配对<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-agent-coding-plan-guide#%E5%85%88%E6%8A%8A%E4%B8%A4%E4%B8%AA%E5%9C%B0%E5%9D%80%E9%85%8D%E5%AF%B9" class="hash-link" aria-label="先把两个地址配对的直接链接" title="先把两个地址配对的直接链接" translate="no">​</a></h2>
<p>Coding Plan 与 Agent Plan 可以使用相同的 OpenAI Python SDK，但不能因此共用一份含糊的连接配置。我为它们建立了两个 ChatEnv profile：</p>
<table><thead><tr><th>配置</th><th>Base URL</th><th>本次实测模型</th></tr></thead><tbody><tr><td><code>volcengine-coding-plan</code></td><td><code>https://ark.cn-beijing.volces.com/api/coding/v3</code></td><td><code>doubao-seed-2.0-code</code></td></tr><tr><td><code>volcengine-agent-plan</code></td><td><code>https://ark.cn-beijing.volces.com/api/plan/v3</code></td><td><code>doubao-seed-2.0-code</code></td></tr></tbody></table>
<p><code>Base URL</code> 指到版本前缀即可，SDK 会继续拼接 <code>/chat/completions</code> 或 <code>/responses</code>。不要再手工把这两个后缀填到 <code>base_url</code> 中。</p>
<p>普通方舟接口的 <code>/api/v3</code> 不在这份配置里。即使普通接口也能接受某个模型名，也不能据此认为请求消耗的是 Plan 套餐。本文脚本会检查完整 Base URL，不匹配就退出，也不配置按量入口作为失败后的备选。</p>
<p>还有一项独立检查：Agent Plan 的<a href="https://docs.volcengine.com/docs/82379/2516285" target="_blank" rel="noopener noreferrer" class="">“超额后付费”</a>。地址正确并不代替费用设置检查。我没有开启这个开关；下面的调用也只发送短提示词，关闭自动重试，并限制输出长度。它们是连通性测试，不是对额度耗尽行为的压力测试。</p>
<p>上述模型名在本次请求中实际可用，但官方页面的模型清单仍在更新。新账号应优先以自己套餐页面展示的 Model ID 为准；不要把这里记录的实测模型名当作长期支持承诺。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="aksk-和-api-key-做不同的事">AK/SK 和 API Key 做不同的事<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-agent-coding-plan-guide#aksk-%E5%92%8C-api-key-%E5%81%9A%E4%B8%8D%E5%90%8C%E7%9A%84%E4%BA%8B" class="hash-link" aria-label="AK/SK 和 API Key 做不同的事的直接链接" title="AK/SK 和 API Key 做不同的事的直接链接" translate="no">​</a></h2>
<p>如果目的是让应用问模型一个问题，应用只需要 API Key。把整个云账号的 AK/SK 交给应用，没有必要。</p>
<p>AK/SK 用在控制面：给管理请求签名，查询身份、套餐和密钥，再按权限创建密钥。API Key 用在模型调用面：通过 Bearer 鉴权发起生成请求。</p>
<img src="https://arch.gh.wzhecnu.cn/ChatBlog/img/volcengine-plan-flow.svg" alt="AK/SK用于管理，两套API Key分开保存并调用各自Plan入口" width="680" height="560">
<p>这条流程不会创建新的火山云账号，也不会设置网页登录密码。后文说的“创建”，具体对象都是 <strong>API Key</strong>。购买套餐、创建 IAM 用户、重置密码、更新后付费设置，是另外的操作，不应悄悄塞进一个接入脚本。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="获取密钥先查现有记录再决定是否创建">获取密钥：先查现有记录，再决定是否创建<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-agent-coding-plan-guide#%E8%8E%B7%E5%8F%96%E5%AF%86%E9%92%A5%E5%85%88%E6%9F%A5%E7%8E%B0%E6%9C%89%E8%AE%B0%E5%BD%95%E5%86%8D%E5%86%B3%E5%AE%9A%E6%98%AF%E5%90%A6%E5%88%9B%E5%BB%BA" class="hash-link" aria-label="获取密钥：先查现有记录，再决定是否创建的直接链接" title="获取密钥：先查现有记录，再决定是否创建的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="在控制台操作">在控制台操作<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-agent-coding-plan-guide#%E5%9C%A8%E6%8E%A7%E5%88%B6%E5%8F%B0%E6%93%8D%E4%BD%9C" class="hash-link" aria-label="在控制台操作的直接链接" title="在控制台操作的直接链接" translate="no">​</a></h3>
<p>已经购买套餐的读者，可从 <a href="https://docs.volcengine.com/docs/82379/1928261" target="_blank" rel="noopener noreferrer" class="">Coding Plan 快速开始</a>进入普通方舟 API Key 管理页；Agent Plan 则从 <a href="https://docs.volcengine.com/docs/82379/2373738" target="_blank" rel="noopener noreferrer" class="">Agent Plan 快速开始</a>进入个人套餐的专属 Key 区域。复制时取完整值，不要复制列表里的掩码。两套 Key 分别保存，随后按本文的两个 Base URL 调用。</p>
<p>如果页面没有专属 Key，先确认当前套餐、账号和项目，再创建。不要为了“得到一把新 Key”点击更新或重置旧 Key：旧应用可能还在用它。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="用-aksk-自动完成">用 AK/SK 自动完成<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-agent-coding-plan-guide#%E7%94%A8-aksk-%E8%87%AA%E5%8A%A8%E5%AE%8C%E6%88%90" class="hash-link" aria-label="用 AK/SK 自动完成的直接链接" title="用 AK/SK 自动完成的直接链接" translate="no">​</a></h3>
<p>本次最容易混淆的是两个同名的 <code>ListApiKeys</code>：IAM 的密钥列表，不等于 Ark 服务的 API Key 列表。调用时不仅要看 Action，还要核对服务、域名和 API 版本。</p>
<p>这次实际使用的控制面是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">服务：ark</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">地域：cn-beijing</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">域名：ark.cn-beijing.volcengineapi.com</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">版本：2024-01-01</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">方法：POST</span><br></div></code></pre></div></div>
<p>Agent Plan 个人版的查询请求还要带场景过滤：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"ProjectName"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"default"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"PageSize"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">100</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"Filter"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"Scene"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"RealAgentPlanPersonal"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>这是本次从控制台公开前端调用路径核对、再用真实账号验证的契约。它不等于承诺所有内部管理 Action 都有长期稳定的公开接口；自动化应保留错误即停的处理，不应在失败时猜另一个 Action 名继续试。</p>
<p>我查到这个场景的密钥列表为空，才执行一次 <code>CreateApiKey</code>。请求体如下：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"Name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"chatenv-agent-plan"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"ProjectName"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"default"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"ResourceInstances"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"ResourceId"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"*"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"ResourceType"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"all"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"Scene"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"RealAgentPlanPersonal"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>成功响应提供密钥 ID。随后必须重新调用带相同场景过滤的 <code>ListApiKeys</code>，核对名称、ID 和 <code>Active</code> 状态，再用 <code>GetRawApiKey</code> 读取完整值：</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">body </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">"Id"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> selected_key</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"Id"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><code>Id</code> 保留列表返回的原始类型；不要把数字 ID 擅自转成字符串。</p>
<p>这里展示的资源参数来自本次控制台创建路径，并不是一份建议照搬到所有 IAM 凭据上的最小权限策略。生产自动化的 AK 应限制到需要的管理操作；普通模型调用程序不应持有 AK/SK。</p>
<p>Coding Plan 这次复用了已经存在、随后通过套餐入口验证成功的 Key，没有新建或轮换。尤其不要拿普通 Ark 列表里的第一条记录，未经核对就自动标记成 Coding Plan Key。存在多条记录时，操作人要明确选择 ID，程序再验证状态和实际调用。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="准备-python-环境">准备 Python 环境<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-agent-coding-plan-guide#%E5%87%86%E5%A4%87-python-%E7%8E%AF%E5%A2%83" class="hash-link" aria-label="准备 Python 环境的直接链接" title="准备 Python 环境的直接链接" translate="no">​</a></h2>
<p>本次使用 Python 3.12，依赖版本为 <code>chatenv 0.2.11</code>、<code>openai 2.54.0</code>、<code>requests 2.34.2</code>。已有合适环境可以复用；新机器可以用 uv 建一个独立环境。以下为 macOS/Linux shell 命令：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">uv venv --python 3.12 "$HOME/.chatarch/volcengine/.venv"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">source "$HOME/.chatarch/volcengine/.venv/bin/activate"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">uv pip install "chatenv==0.2.11" "openai==2.54.0" "requests==2.34.2"</span><br></div></code></pre></div></div>
<p>把下方程序保存到自己的工作目录。运行模型程序不需要 AK/SK；只有管理密钥的自动化脚本需要它。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="把两套配置放进-chatenv">把两套配置放进 ChatEnv<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-agent-coding-plan-guide#%E6%8A%8A%E4%B8%A4%E5%A5%97%E9%85%8D%E7%BD%AE%E6%94%BE%E8%BF%9B-chatenv" class="hash-link" aria-label="把两套配置放进 ChatEnv的直接链接" title="把两套配置放进 ChatEnv的直接链接" translate="no">​</a></h2>
<p>我把密钥放在命名 profile 中，没有切换全局默认配置。这样，已有应用不会因为一次验证换到另一个账号或另一个计费入口。</p>
<p>下面的程序通过隐藏输入读取 Key，并使用 ChatEnv 自己的存储接口写入。它不把密钥放进命令行参数，也不会覆写已有的不同密钥。</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">save_plan_profile.py</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> argparse</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> getpass </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> getpass</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> chatenv </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> EnvStore</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> OpenAIConfig</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> get_paths</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">BASES </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"volcengine-coding-plan"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"https://ark.cn-beijing.volces.com/api/coding/v3"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"volcengine-agent-plan"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"https://ark.cn-beijing.volces.com/api/plan/v3"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">parser </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> argparse</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ArgumentParser</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">parser</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">add_argument</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"profile"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> choices</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">BASES</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">args </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> parser</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">parse_args</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">key </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> getpass</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"粘贴完整 API Key（输入不回显）: "</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">strip</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">not</span><span class="token plain"> key </span><span class="token keyword" style="color:#00009f">or</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"*"</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> key</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">raise</span><span class="token plain"> SystemExit</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"需要完整密钥，不能使用掩码。"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">store </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> EnvStore</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">get_paths</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">envs_dir</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">old </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> store</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">load_profile</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">OpenAIConfig</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> args</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">profile</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> old</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"OPENAI_API_KEY"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">not</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">None</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> key</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">raise</span><span class="token plain"> SystemExit</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"该 profile 已有不同密钥；停止，未覆盖。"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">values </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"OPENAI_API_KEY"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> key</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"OPENAI_API_BASE"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> BASES</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">args</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">profile</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"OPENAI_API_MODEL"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"doubao-seed-2.0-code"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">path </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> store</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">save_profile</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">OpenAIConfig</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> args</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">profile</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> values</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">assert</span><span class="token plain"> store</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">load_profile</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">OpenAIConfig</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> args</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">profile</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> values</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"已保存 </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">args</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation">profile</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">，未切换默认配置。"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"文件：</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">path</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">"</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>运行两次，每次输入对应套餐页面取得的密钥：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">python save_plan_profile.py volcengine-coding-plan</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python save_plan_profile.py volcengine-agent-plan</span><br></div></code></pre></div></div>
<p>实际生成的配置结构是：</p>
<div class="language-dotenv codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">volcengine-coding-plan.env（结构示意，不含真实密钥）</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dotenv codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">OPENAI_API_KEY=替换为你的完整CodingPlanKey</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">OPENAI_API_BASE=https://ark.cn-beijing.volces.com/api/coding/v3</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">OPENAI_API_MODEL=doubao-seed-2.0-code</span><br></div></code></pre></div></div>
<div class="language-dotenv codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">volcengine-agent-plan.env（结构示意，不含真实密钥）</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dotenv codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">OPENAI_API_KEY=替换为你的完整AgentPlanKey</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">OPENAI_API_BASE=https://ark.cn-beijing.volces.com/api/plan/v3</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">OPENAI_API_MODEL=doubao-seed-2.0-code</span><br></div></code></pre></div></div>
<p>默认位置是 <code>~/.chatarch/envs/OpenAI/</code>。本次写入后，两份文件均验证为 <code>0600</code>。程序从指定 profile 读取，不需要先 <code>source</code> 文件，也不会从进程里碰巧存在的 <code>OPENAI_API_KEY</code> 偷换凭据。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="完整调用代码">完整调用代码<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-agent-coding-plan-guide#%E5%AE%8C%E6%95%B4%E8%B0%83%E7%94%A8%E4%BB%A3%E7%A0%81" class="hash-link" aria-label="完整调用代码的直接链接" title="完整调用代码的直接链接" translate="no">​</a></h2>
<p>下面就是实测用的程序。它没有隐藏的辅助服务，不需要启动代理服务器或数据库。</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">plan_demo.py</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">#!/usr/bin/env python3</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token triple-quoted-string string" style="color:#e3116c">"""Run one bounded request through a named ChatEnv Plan profile."""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> argparse</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> json</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> urllib</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">parse </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> urlsplit</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> chatenv </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> EnvStore</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> OpenAIConfig</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> get_paths</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> openai </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> OpenAI</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">BASES </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"volcengine-coding-plan"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"https://ark.cn-beijing.volces.com/api/coding/v3"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"volcengine-agent-plan"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"https://ark.cn-beijing.volces.com/api/plan/v3"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">run</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">profile</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> protocol</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token builtin">dict</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    values </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> EnvStore</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">get_paths</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">envs_dir</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">load_profile</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        OpenAIConfig</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> profile</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    base </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> values</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"OPENAI_API_BASE"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">rstrip</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"/"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> base </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> BASES</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">profile</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">raise</span><span class="token plain"> ValueError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Profile must use its exact Plan endpoint; no fallback."</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    key </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> values</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"OPENAI_API_KEY"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">not</span><span class="token plain"> key </span><span class="token keyword" style="color:#00009f">or</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"*"</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> key</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">raise</span><span class="token plain"> ValueError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Missing real API key; masked values are not credentials."</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    model </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> values</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"OPENAI_API_MODEL"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">with</span><span class="token plain"> OpenAI</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">api_key</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">key</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> base_url</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">base</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> timeout</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">30</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> max_retries</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> client</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> protocol </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"responses"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            result </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> client</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">responses</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">create</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                model</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">model</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token builtin">input</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"只回复 PLAN_OK，不要解释。"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                max_output_tokens</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">32</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                extra_body</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">"thinking"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">"type"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"disabled"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            text </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> result</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">output_text</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">else</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            result </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> client</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">chat</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">completions</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">create</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                model</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">model</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                messages</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token string" style="color:#e3116c">"role"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"user"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token string" style="color:#e3116c">"content"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"只回复 PLAN_OK，不要解释。"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                max_tokens</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">32</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                extra_body</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">"thinking"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">"type"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"disabled"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            text </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> result</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">choices</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">message</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">content</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token string" style="color:#e3116c">"profile"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> profile</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token string" style="color:#e3116c">"protocol"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> protocol</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token string" style="color:#e3116c">"path"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> urlsplit</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">base</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">path </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token string" style="color:#e3116c">"/responses"</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> protocol </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"responses"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/chat/completions"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token string" style="color:#e3116c">"model"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> model</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token string" style="color:#e3116c">"text"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> text</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token string" style="color:#e3116c">"usage"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> result</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">usage</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">model_dump</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> result</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">usage </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">None</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> __name__ </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"__main__"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    parser </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> argparse</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ArgumentParser</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">description</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">__doc__</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    parser</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">add_argument</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"--profile"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> choices</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">BASES</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> required</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    parser</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">add_argument</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"--protocol"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> choices</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"responses"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"chat"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> default</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"responses"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    args </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> parser</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">parse_args</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">json</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">dumps</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        run</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">args</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">profile</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> args</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">protocol</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ensure_ascii</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">False</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> indent</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">2</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>Responses 的结果从 <code>output_text</code> 读取；Chat Completions 则从 <code>choices[0].message.content</code> 读取。两者都兼容 OpenAI SDK，不意味着请求体和响应结构可以混用。</p>
<p><code>thinking</code> 是本次所用模型接受的扩展字段，通过 <code>extra_body</code> 传入。换模型时要重新核对它是否支持，不能把这行当成所有供应商通用的参数。这里关闭思考，只为让一次短连通性测试迅速结束。</p>
<p>运行命令：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">python plan_demo.py --profile volcengine-coding-plan --protocol responses</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python plan_demo.py --profile volcengine-coding-plan --protocol chat</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python plan_demo.py --profile volcengine-agent-plan --protocol responses</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python plan_demo.py --profile volcengine-agent-plan --protocol chat</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四次请求实际返回了什么">四次请求实际返回了什么<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-agent-coding-plan-guide#%E5%9B%9B%E6%AC%A1%E8%AF%B7%E6%B1%82%E5%AE%9E%E9%99%85%E8%BF%94%E5%9B%9E%E4%BA%86%E4%BB%80%E4%B9%88" class="hash-link" aria-label="四次请求实际返回了什么的直接链接" title="四次请求实际返回了什么的直接链接" translate="no">​</a></h2>
<p>下面是 Agent Plan 的 Responses 真实输出；只保留程序打印的字段，没有展示凭据或账号标识：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"profile"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"volcengine-agent-plan"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"protocol"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"responses"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"path"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/api/plan/v3/responses"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"model"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"doubao-seed-2.0-code"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"text"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"PLAN_OK"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"usage"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"input_tokens"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">53</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"input_tokens_details"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"cache_write_tokens"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token null keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"cached_tokens"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"output_tokens"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"output_tokens_details"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"reasoning_tokens"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"total_tokens"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">56</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>四次调用的汇总如下。Chat Completions 原始字段名是 <code>prompt_tokens</code> / <code>completion_tokens</code>，表中统一列为输入与输出，方便比较。</p>
<table><thead><tr><th>套餐</th><th>协议</th><th>返回文本</th><th style="text-align:right">输入 token</th><th style="text-align:right">输出 token</th></tr></thead><tbody><tr><td>Coding Plan</td><td>Responses</td><td><code>PLAN_OK</code></td><td style="text-align:right">53</td><td style="text-align:right">3</td></tr><tr><td>Coding Plan</td><td>Chat Completions</td><td><code>PLAN_OK</code></td><td style="text-align:right">53</td><td style="text-align:right">3</td></tr><tr><td>Agent Plan</td><td>Responses</td><td><code>PLAN_OK</code></td><td style="text-align:right">53</td><td style="text-align:right">3</td></tr><tr><td>Agent Plan</td><td>Chat Completions</td><td><code>PLAN_OK</code></td><td style="text-align:right">53</td><td style="text-align:right">3</td></tr></tbody></table>
<p>这些结果证明的是：在本次账号、模型与两套 Plan 入口下，两种文本协议都完成了鉴权和生成。它们不证明全部模型可用、不证明工具调用或多模态协议兼容，也不是速率上限测试。返回的 token 统计不是账单，不能拿 <code>total_tokens</code> 直接推导扣了多少套餐额度或人民币。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="seed-evolving-和语音识别怎样接入">Seed Evolving 和语音识别怎样接入<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-agent-coding-plan-guide#seed-evolving-%E5%92%8C%E8%AF%AD%E9%9F%B3%E8%AF%86%E5%88%AB%E6%80%8E%E6%A0%B7%E6%8E%A5%E5%85%A5" class="hash-link" aria-label="Seed Evolving 和语音识别怎样接入的直接链接" title="Seed Evolving 和语音识别怎样接入的直接链接" translate="no">​</a></h2>
<p>在前面的协议验证之后，我又分别用两把 Key 调用 <code>doubao-seed-evolving</code>，两次都返回 <code>EVOLVING_OK</code>，各统计输入 53、输出 5 token。要改用它，只需把对应 profile 的 <code>OPENAI_API_MODEL</code> 设置为 <code>doubao-seed-evolving</code>；不要连同 Key 和地址一起混换。</p>
<p>两套模型列表的完整快照也放在工具包 <code>models-2026-09-08.json</code>。2026-09-08 管理 API 返回 Coding 15 个、Agent 21 个标识（含路由别名，不是底层模型数量）。两者共有：</p>
<p><code>ark-code-latest</code>、<code>deepseek-latest</code>、<code>deepseek-v4-flash</code>、<code>deepseek-v4-pro</code>、<code>doubao-seed-2-1-turbo</code>、<code>doubao-seed-2.0-lite</code>、<code>doubao-seed-evolving</code>、<code>glm-5.2</code>、<code>glm-5.3</code>、<code>glm-5.3-flash</code>、<code>glm-latest</code>、<code>kimi-k2.7-code</code>、<code>kimi-latest</code>、<code>minimax-latest</code>、<code>minimax-m3</code>。</p>
<p>Agent 清单另有：<code>doubao-embedding-vision</code>、<code>doubao-seed-2.0-mini</code>、<code>doubao-seedance-2.0</code>、<code>doubao-seedance-2.0-fast</code>、<code>doubao-seedream-5.0-lite</code>、<code>kimi-k3</code>。其中图像、视频和 Embedding 要走各自协议，不是普通聊天候选。语音模型另由语音接入文档列出，因此不能只靠这份文本/多模态模型列表判断 ASR 是否可用。</p>
<p>这两套套餐当前公开模型清单都包含 Seed Evolving。Agent Plan 还提供语音识别，但它是另一条接口：<strong><code>doubao-seed-asr-2.0</code>，不是把音频塞给 Seed Evolving 的 Chat Completions。</strong></p>
<p>按照<a href="https://docs.volcengine.com/docs/82379/2516286" target="_blank" rel="noopener noreferrer" class="">官方语音接入文档</a>，ASR 使用 Agent Plan 专属 Key，走 WebSocket，资源标识为 <code>volc.seedasr.sauc.duration</code>：</p>
<ul>
<li class="">双流：<code>wss://openspeech.bytedance.com/api/v3/plan/sauc/bigmodel_async</code>，边发送音频边接收识别结果。</li>
<li class="">单流：<code>wss://openspeech.bytedance.com/api/v3/plan/sauc/bigmodel_nostream</code>，偏准确率优先，结果返回时机按接口协议处理。</li>
</ul>
<p>调用头用 <code>X-Api-Key</code> 和 <code>X-Api-Resource-Id</code>，不是前面 SDK 的 Bearer 请求。官方还提供 <code>doubao-seed-tts-2.0</code> 做文本转语音。这些语音能力在本文只核对了接入文档，<strong>没有上传音频实测</strong>；语音抵扣、权限和后付费设置也需要单独确认，不能由文本请求成功推断出来。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="复用-aksk-管理脚本">复用 AK/SK 管理脚本<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-agent-coding-plan-guide#%E5%A4%8D%E7%94%A8-aksk-%E7%AE%A1%E7%90%86%E8%84%9A%E6%9C%AC" class="hash-link" aria-label="复用 AK/SK 管理脚本的直接链接" title="复用 AK/SK 管理脚本的直接链接" translate="no">​</a></h2>
<p><a href="https://arch.gh.wzhecnu.cn/ChatBlog/downloads/volcengine-plan-toolkit.zip">下载完整 Python 工具包</a>。包内包含签名和管理脚本、命名配置保存、模型调用示例、依赖清单、测试及完整说明；<strong>不含任何真实凭据</strong>。</p>
<p>先按包内 README 准备 Python 环境，再把自己授权的 AK/SK 放入仅本人可读的凭据文件，或通过无回显输入送进 stdin。无需把 AK/SK 写入文章里的生成程序。</p>
<p>下面是管理脚本的最小使用顺序。<code>PY</code> 指向已安装依赖的 Python；资源 ID 来自你自己的密钥列表，不是 API Key 明文。</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">export PY="$HOME/.chatarch/volcengine/.venv/bin/python"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">"$PY" scripts/plan_toolkit.py identity</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">"$PY" scripts/plan_toolkit.py plan --kind coding</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">"$PY" scripts/plan_toolkit.py plan --kind agent</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">"$PY" scripts/plan_toolkit.py list-keys --kind coding --show-ids</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">"$PY" scripts/plan_toolkit.py list-keys --kind agent --show-ids</span><br></div></code></pre></div></div>
<p>确认列表中的目标后，通过 <code>ensure-existing</code> 取回并保存。Coding 的普通密钥库存不代表其中每把都可用于 Coding Plan，因此必须自己确认并指定 ID，脚本不会擅自挑第一把。</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">"$PY" scripts/plan_toolkit.py ensure-existing \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --kind coding --key-id "$CODING_KEY_ID" --profile volcengine-coding-plan</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">"$PY" scripts/plan_toolkit.py ensure-existing \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --kind agent --key-id "$AGENT_KEY_ID" --profile volcengine-agent-plan</span><br></div></code></pre></div></div>
<p>若将来为另一个已开通套餐的账号接入，并且 Agent 专属密钥确实尚未生成，才使用下面的创建入口：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">"$PY" scripts/plan_toolkit.py create-agent \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --profile volcengine-agent-plan --create-missing-agent-key</span><br></div></code></pre></div></div>
<p>它在发送前重新查询库存，并以排他文件记录创建意图；请求超时之后只读回列表，不重发创建。遇到“结果未知”时按 README 核对控制台，不要删除标记后循环重试。已有配置不一致时拒绝覆盖，也不会激活全局默认 profile。<strong>这里没有云账号、IAM 用户或登录密码的创建功能。</strong></p>
<p>原始控制脚本已真实完成一次 Agent Key 创建；整理后的工具包重新验证了身份、套餐、密钥查询以及已有 profile 不变。新工具包的创建恢复分支只做离线测试，没有为了验收再制造一把 Key。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="失败时停在哪一步">失败时，停在哪一步<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-agent-coding-plan-guide#%E5%A4%B1%E8%B4%A5%E6%97%B6%E5%81%9C%E5%9C%A8%E5%93%AA%E4%B8%80%E6%AD%A5" class="hash-link" aria-label="失败时，停在哪一步的直接链接" title="失败时，停在哪一步的直接链接" translate="no">​</a></h2>
<p>第一次执行程序时，本机没有安装 <code>openai</code>，直接报 <code>ModuleNotFoundError</code>。这是本地依赖失败，尚未发出模型请求。安装 SDK 后再执行，才得到上面的真实结果。不要把程序退出和“模型服务拒绝请求”混为一谈。</p>
<p>接入后常见问题可以沿着请求路径排查：</p>
<ul>
<li class=""><strong>凭据错误或 401</strong>：确认复制的是完整 Key，profile 名称没选错；不要先去生成第三把 Key。</li>
<li class=""><strong>403</strong>：检查 AK 的管理权限或 API Key 对应的服务权限。提高重试次数不能修复授权。</li>
<li class=""><strong>模型不可用</strong>：在当前套餐页面核对模型标识。不要把自定义推理接入点 ID 与模型 ID 混用。</li>
<li class=""><strong>429 或额度不足</strong>：停止自动重试，检查套餐用量与刷新窗口。不要自动退回普通 <code>/api/v3</code>。</li>
<li class=""><strong>创建密钥超时</strong>：先重新查列表，按本次名称和 ID 核对是否已经创建。超时只说明客户端没拿到确定结果，不能据此再建一把。</li>
<li class=""><strong>保存时发现不同密钥</strong>：停止覆盖，确认到底是同一账号的旧配置，还是选错 profile。默认配置不应在恢复流程里被顺手切换。</li>
</ul>
<p>对于没有文档支持的管理 Action，错误即停也很重要。本次曾核对一个看似合理的“获取个人套餐密钥”Action 名，服务端返回 404；最后采用的是控制台实际使用的 <code>ListApiKeys → GetRawApiKey</code> 路径。API 名称不能靠英文拼词推断出来。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="把验证变成可重复的操作">把验证变成可重复的操作<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-agent-coding-plan-guide#%E6%8A%8A%E9%AA%8C%E8%AF%81%E5%8F%98%E6%88%90%E5%8F%AF%E9%87%8D%E5%A4%8D%E7%9A%84%E6%93%8D%E4%BD%9C" class="hash-link" aria-label="把验证变成可重复的操作的直接链接" title="把验证变成可重复的操作的直接链接" translate="no">​</a></h2>
<p>下一次在新机器上接入，不需要重新追一遍控制台代码。操作顺序应固定下来：先验证 AK 身份与套餐，再选已有 Key；只有 Agent Plan 场景列表确认为空、操作者明确要求创建时才创建；写入命名 ChatEnv profile 后，再手动发起短调用。</p>
<p>把创建与测试拆开有一个直接好处：只想检查配置的人，不会意外创建密钥或消耗模型额度。真实模型请求也不应混进单元测试里反复运行。</p>
<p>本文配套脚本包将这些边界放进命令接口：管理动作白名单、明确的创建开关、已存在密钥的复用、profile 覆写保护，以及独立的短调用命令。公共下载包不包含本次账号凭据。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="资料与验证边界">资料与验证边界<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-agent-coding-plan-guide#%E8%B5%84%E6%96%99%E4%B8%8E%E9%AA%8C%E8%AF%81%E8%BE%B9%E7%95%8C" class="hash-link" aria-label="资料与验证边界的直接链接" title="资料与验证边界的直接链接" translate="no">​</a></h2>
<p>本文记录的是 2026-09-08 的一次真实接入。套餐价格、支持模型与额度规则会变化；购买和正式上线前，仍需打开对应套餐页面核对。本文不把当日可用的模型列表写成永久支持承诺。</p>
<ul>
<li class=""><a href="https://docs.volcengine.com/docs/82379/1925114" target="_blank" rel="noopener noreferrer" class="">Coding Plan：套餐概览</a>、<a href="https://docs.volcengine.com/docs/82379/1928261" target="_blank" rel="noopener noreferrer" class="">快速开始</a>。</li>
<li class=""><a href="https://docs.volcengine.com/docs/82379/2366394" target="_blank" rel="noopener noreferrer" class="">Agent Plan：套餐概览</a>、<a href="https://docs.volcengine.com/docs/82379/2373738" target="_blank" rel="noopener noreferrer" class="">快速开始</a>。</li>
<li class=""><a href="https://docs.volcengine.com/docs/82379/1361424" target="_blank" rel="noopener noreferrer" class="">API Key 管理</a>与 <a href="https://docs.volcengine.com/docs/82379/1298459" target="_blank" rel="noopener noreferrer" class="">Base URL 及鉴权</a>。</li>
<li class=""><a href="https://docs.volcengine.com/docs/82379/2516284" target="_blank" rel="noopener noreferrer" class="">超额后付费规则</a>、<a href="https://docs.volcengine.com/docs/82379/2516285" target="_blank" rel="noopener noreferrer" class="">管理开关</a>、<a href="https://docs.volcengine.com/docs/82379/2516283" target="_blank" rel="noopener noreferrer" class="">AFP 抵扣规则</a>。</li>
<li class=""><a href="https://docs.volcengine.com/docs/82379/2546385" target="_blank" rel="noopener noreferrer" class="">Coding Plan 模型列表</a>、<a href="https://docs.volcengine.com/docs/82379/2546386" target="_blank" rel="noopener noreferrer" class="">Agent Plan 模型列表</a>。</li>
<li class=""><a href="https://docs.volcengine.com/docs/82379/2546382" target="_blank" rel="noopener noreferrer" class="">查询个人版套餐</a>、<a href="https://docs.volcengine.com/docs/82379/2479847" target="_blank" rel="noopener noreferrer" class="">获取 AFP 额度</a>、<a href="https://docs.volcengine.com/docs/82379/2612140" target="_blank" rel="noopener noreferrer" class="">查询模型限流</a>。</li>
</ul>
<p>接口取证还包括方舟控制台公开前端模块：Agent Plan 页面使用的 <code>Scene</code>、<code>ListApiKeys</code>、<code>CreateApiKey</code> 和 <code>GetRawApiKey</code> 已与真实请求逐项核对。可从 <a href="https://console.volcengine.com/ark/region:cn-beijing/openManagement?advancedActiveKey=agentPlan" target="_blank" rel="noopener noreferrer" class="">Agent Plan 控制台</a>进入；控制台内部契约与有版本的公开文档应分别对待。</p>]]></content>
        <author>
            <name>ChatArch</name>
            <uri>https://github.com/ChatArch</uri>
        </author>
        <category label="火山引擎" term="火山引擎"/>
        <category label="Coding Plan" term="Coding Plan"/>
        <category label="Agent Plan" term="Agent Plan"/>
        <category label="API" term="API"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[用 CRS API Key 生成图片：ChatEnv 与 curl 实用指南]]></title>
        <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog/crs-image-generation-guide</id>
        <link href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/crs-image-generation-guide"/>
        <updated>2026-09-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[从一张快速排序教学图出发，用 ChatEnv 管理 CRS Key，通过 Python 示例或纯 curl 调用 image_generation，并把返回结果保存为 PNG。]]></summary>
        <content type="html"><![CDATA[<p>给出一段描述，调用图像工具，得到一张可以直接放进课件的图片——这篇指南把这个过程落实到配置和代码。</p>
<p>下面这张快速排序示意图，就是通过 <strong>CRS API Key → Responses API → <code>image_generation</code></strong> 实际生成的。客户端只需要服务地址和一把 CRS 调用 Key。</p>
<p><img decoding="async" loading="lazy" src="https://share.public.wzhecnu.cn/images/chatblog/crs-image-generation/quicksort-9c42781894fb.png" alt="通过 CRS image_generation 工具生成的中文快速排序教学图" class="img_ev3q"></p>
<p>本次生成使用 <code>gpt-5.5</code> 承载工具调用、<code>gpt-image-2</code> 绘图，返回一张 <strong>1536 × 1024 的 PNG</strong>，请求约 57 秒完成。图中的输入数组、基准 4、左右分区和最终排序均经过核对。</p>
<p>本文给出两种使用方式：</p>
<ol>
<li class=""><strong>在 ChatImg / ChatEnv 环境中运行配套 Python 脚本</strong>：从已有配置读取 Key，生成并保存图片。</li>
<li class=""><strong>直接使用 curl</strong>：自己发送请求，再从响应流中提取 PNG。</li>
</ol>
<p>两种方式使用同一个 <code>/responses</code> 接口。本文的 <code>generate_image.py</code> 是配套示例脚本，不是 ChatImg 内置子命令。</p>
<a href="https://arch.gh.wzhecnu.cn/ChatBlog/downloads/crs-image-guide/examples.zip">下载完整示例包：Python 脚本、curl 脚本、请求 JSON 与提示词</a>
<p>示例中的 <code>https://crs.example.com</code> 是占位域名，请替换为自己的 CRS 地址。模型名称以你的 Key 实际可用范围为准；本文示例请求已经在支持这些模型的账号上完成验证。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一使用-chatimg--chatenv-环境生成图片">一、使用 ChatImg / ChatEnv 环境生成图片<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/crs-image-generation-guide#%E4%B8%80%E4%BD%BF%E7%94%A8-chatimg--chatenv-%E7%8E%AF%E5%A2%83%E7%94%9F%E6%88%90%E5%9B%BE%E7%89%87" class="hash-link" aria-label="一、使用 ChatImg / ChatEnv 环境生成图片的直接链接" title="一、使用 ChatImg / ChatEnv 环境生成图片的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="安装工具">安装工具<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/crs-image-generation-guide#%E5%AE%89%E8%A3%85%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="安装工具的直接链接" title="安装工具的直接链接" translate="no">​</a></h3>
<p>在已有的 Python 虚拟环境中安装：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">pip install chatimg</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">chatimg --version</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">chatimg --tree</span><br></div></code></pre></div></div>
<p>本文验证环境为 ChatImg <strong>0.1.5</strong>、ChatEnv <strong>0.2.11</strong>。已有 ChatArch 环境可以直接复用。</p>
<p>如果还没有虚拟环境，可以先创建一个：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">python3 -m venv .venv</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">source .venv/bin/activate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">pip install chatimg</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="将-crs-key-放入-chatenv">将 CRS Key 放入 ChatEnv<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/crs-image-generation-guide#%E5%B0%86-crs-key-%E6%94%BE%E5%85%A5-chatenv" class="hash-link" aria-label="将 CRS Key 放入 ChatEnv的直接链接" title="将 CRS Key 放入 ChatEnv的直接链接" translate="no">​</a></h3>
<p>创建一个名为 <code>crs</code> 的 OpenAI 类型配置：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">chatenv new -t oai crs</span><br></div></code></pre></div></div>
<p>命令会创建配置文件。使用编辑器打开 <code>~/.chatarch/envs/OpenAI/crs.env</code>，填写以下字段；如果设置了 <code>CHATARCH_HOME</code>，文件位于该目录下的 <code>envs/OpenAI/crs.env</code>。已有同名配置则直接使用，不必重新创建。</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">curl --version</span><br></div></code></pre></div></div>
<p>配套脚本会使用系统 curl 发送请求，先确认上面的命令可以运行。然后填写配置：</p>
<div class="language-env codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-env codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">OPENAI_API_BASE=https://crs.example.com/openai/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">OPENAI_API_KEY=&lt;YOUR_CRS_API_KEY&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">OPENAI_API_MODEL=gpt-5.5</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">OPENAI_IMAGE_MODEL=gpt-image-2-medium</span><br></div></code></pre></div></div>
<p>这几个字段分工不同：</p>
<table><thead><tr><th>字段</th><th>用途</th></tr></thead><tbody><tr><td><code>OPENAI_API_BASE</code></td><td>CRS 的 OpenAI 兼容入口，包含 <code>/openai/v1</code></td></tr><tr><td><code>OPENAI_API_KEY</code></td><td>CRS 签发的调用 Key</td></tr><tr><td><code>OPENAI_API_MODEL</code></td><td>接收提示词、调用图像工具的承载模型</td></tr><tr><td><code>OPENAI_IMAGE_MODEL</code></td><td>图像模型预设，<code>-medium</code> 表示中等质量</td></tr></tbody></table>
<p>虽然调用的是 CRS，配置仍放在 ChatEnv 的 <strong>OpenAI 类型</strong>里。配套脚本通过 <code>--profile crs</code> 读取这个命名配置，不需要切换全局默认配置。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="写提示词运行生成">写提示词，运行生成<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/crs-image-generation-guide#%E5%86%99%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%BF%90%E8%A1%8C%E7%94%9F%E6%88%90" class="hash-link" aria-label="写提示词，运行生成的直接链接" title="写提示词，运行生成的直接链接" translate="no">​</a></h3>
<p>下载并解压示例包，进入其中的目录。包里的 <code>prompt.txt</code> 包含完整的快速排序图提示词，也可以改成自己的描述：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">生成一张中文快速排序教学图。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">输入数组：[6, 3, 8, 2, 5, 1, 7, 4]。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">选择最后一个元素 4 作为基准。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">分区为 [3, 2, 1]、[4]、[6, 8, 5, 7]。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">左右两组分别递归排序，最后得到 [1, 2, 3, 4, 5, 6, 7, 8]。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">使用白底、清晰数字方块与箭头，突出基准和分区关系。</span><br></div></code></pre></div></div>
<p>执行：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">python generate_image.py \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --profile crs \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --host-model gpt-5.5 \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --image-model gpt-image-2 \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --size 1536x1024 \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --quality medium \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --prompt-file prompt.txt \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --output quicksort.png</span><br></div></code></pre></div></div>
<p>脚本完成后会打印图片路径、格式、尺寸和 SHA-256。图片写入指定文件，完整响应保存在同名 <code>.sse</code> 文件中，方便之后重新提取，不必重复发起生成请求。</p>
<p>配套脚本读取 Key 的核心代码是：</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> pathlib </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> Path</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> os</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> chatenv</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">configs </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> OpenAIConfig</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> chatenv</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">store </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> EnvStore</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">home </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> Path</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">os</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">environ</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"CHATARCH_HOME"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">or</span><span class="token plain"> Path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">home</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">/</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">".chatarch"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">values </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> EnvStore</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">home </span><span class="token operator" style="color:#393A34">/</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"envs"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">load_profile</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">OpenAIConfig</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"crs"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">api_base </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> values</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"OPENAI_API_BASE"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">rstrip</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"/"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">api_key </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> values</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"OPENAI_API_KEY"</span><span class="token punctuation" style="color:#393A34">]</span><br></div></code></pre></div></div>
<p>生成流程可以概括为：读取命名配置 → 构造图像工具请求 → 通过 curl 发送 → 提取完整图像 → 校验并保存。Python 负责参数与文件处理，HTTP 调用仍是下一节展示的 curl 请求。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二纯-curl-调用-image-tool">二、纯 curl 调用 image tool<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/crs-image-generation-guide#%E4%BA%8C%E7%BA%AF-curl-%E8%B0%83%E7%94%A8-image-tool" class="hash-link" aria-label="二、纯 curl 调用 image tool的直接链接" title="二、纯 curl 调用 image tool的直接链接" translate="no">​</a></h2>
<p>这一种方式只需要 curl、jq 和 Base64 解码工具，不需要安装 Python 包。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="配置地址和-key">配置地址和 Key<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/crs-image-generation-guide#%E9%85%8D%E7%BD%AE%E5%9C%B0%E5%9D%80%E5%92%8C-key" class="hash-link" aria-label="配置地址和 Key的直接链接" title="配置地址和 Key的直接链接" translate="no">​</a></h3>
<p>在 Bash 中设置服务地址，并通过安全输入读入 Key：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">export CRS_API_BASE='https://crs.example.com/openai/v1'</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">read -r -s -p 'CRS API Key: ' CRS_API_KEY</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">printf '\n'</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export CRS_API_KEY</span><br></div></code></pre></div></div>
<p>完整请求地址是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">POST https://crs.example.com/openai/v1/responses</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="准备请求-json">准备请求 JSON<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/crs-image-generation-guide#%E5%87%86%E5%A4%87%E8%AF%B7%E6%B1%82-json" class="hash-link" aria-label="准备请求 JSON的直接链接" title="准备请求 JSON的直接链接" translate="no">​</a></h3>
<p>示例包已经提供 <code>request.json</code>。也可以自行创建，内容如下：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"model"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"gpt-5.5"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"instructions"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Use the image_generation tool to create the requested teaching infographic."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"input"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"role"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"user"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"content"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"input_text"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"text"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"生成中文快速排序示意图：数组[6,3,8,2,5,1,7,4]，基准4，分区为[3,2,1]、[4]、[6,8,5,7]，递归排序后得到[1,2,3,4,5,6,7,8]。白底，数字方块和箭头清晰。"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"tools"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"image_generation"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"model"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"gpt-image-2"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"action"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"generate"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"quality"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"medium"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"size"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"1536x1024"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"partial_images"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"tool_choice"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"image_generation"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"stream"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"store"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>最值得看清楚的是两个 <code>model</code>：外层的 <code>gpt-5.5</code> 负责调用工具；<code>tools</code> 里的 <code>gpt-image-2</code> 负责生成图片。<code>tool_choice</code> 明确要求调用图像工具，<code>stream: true</code> 表示使用流式响应。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="发起请求并保存响应">发起请求并保存响应<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/crs-image-generation-guide#%E5%8F%91%E8%B5%B7%E8%AF%B7%E6%B1%82%E5%B9%B6%E4%BF%9D%E5%AD%98%E5%93%8D%E5%BA%94" class="hash-link" aria-label="发起请求并保存响应的直接链接" title="发起请求并保存响应的直接链接" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">printf '%s: %s %s\n' Authorization Bearer "$CRS_API_KEY" |</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">curl --fail-with-body --silent --show-error \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --connect-timeout 10 --max-time 300 \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --header @- \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --header 'Content-Type: application/json' \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --header 'Accept: text/event-stream' \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --data-binary @request.json \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "$CRS_API_BASE/responses" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --output response.sse</span><br></div></code></pre></div></div>
<p>认证头从标准输入传给 curl，实际 Key 不出现在 curl 的命令行参数里。<code>request.json</code> 只保存模型、提示词和图像参数，可以与代码一起管理；Key 应留在自己的凭据配置中。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="从响应中保存-png">从响应中保存 PNG<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/crs-image-generation-guide#%E4%BB%8E%E5%93%8D%E5%BA%94%E4%B8%AD%E4%BF%9D%E5%AD%98-png" class="hash-link" aria-label="从响应中保存 PNG的直接链接" title="从响应中保存 PNG的直接链接" translate="no">​</a></h3>
<p>请求成功退出后，执行：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">set -o pipefail</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">jq -rRn '</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  [inputs | select(startswith("data: ")) | .[6:] | fromjson?] as $events</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | if any($events[];</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">           .type == "error" or .type == "response.failed"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">           or .type == "response.incomplete")</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">       or ([$events[] | select(.type == "response.completed")</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            | .response.status] | last) != "completed"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    then error("No successfully completed response")</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    else</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      [$events[]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">       | if .type == "response.output_item.done" then .item</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">         elif .type == "response.completed" then .response.output[]?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">         else empty end</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">       | select(.type == "image_generation_call")</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">       | .result | select(type == "string" and length &gt; 0)]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      | last // error("No final image in the stream")</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    end</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">' response.sse | base64 --decode &gt; quicksort.png</span><br></div></code></pre></div></div>
<p>Responses 返回的是 <strong>SSE 事件流</strong>。完整图片以 Base64 字符串放在 <code>image_generation_call</code> 的 <code>result</code> 字段中。上面的命令同时接收两种完整结果位置：</p>
<ul>
<li class=""><code>response.output_item.done</code> 中的 <code>item.result</code>；</li>
<li class=""><code>response.completed</code> 中的 <code>response.output[].result</code>。</li>
</ul>
<p>这样既能接收独立输出项，也能接收最终汇总。本次示例图片来自第一种位置；shell 解码得到的文件与 Python 解码结果逐字节一致。</p>
<p>如果希望将发送和保存合成一条命令，可以使用示例包中的脚本：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">bash curl_image_tool.sh request.json quicksort.png</span><br></div></code></pre></div></div>
<p>也可以对已经保存的响应重新解码：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">python generate_image.py \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --decode response.sse \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --output quicksort-copy.png</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="换一个主题保留同一套调用方式">换一个主题，保留同一套调用方式<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/crs-image-generation-guide#%E6%8D%A2%E4%B8%80%E4%B8%AA%E4%B8%BB%E9%A2%98%E4%BF%9D%E7%95%99%E5%90%8C%E4%B8%80%E5%A5%97%E8%B0%83%E7%94%A8%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="换一个主题，保留同一套调用方式的直接链接" title="换一个主题，保留同一套调用方式的直接链接" translate="no">​</a></h3>
<p>做其他图片时，先改提示词，再调整 <code>size</code> 和 <code>quality</code> 即可。流程图应给出节点和连线，教学图应提供准确的数据与步骤，封面图应描述主题、构图和配色。</p>
<p>这张快速排序图采用的是<strong>分治概念示意</strong>：分区阶段保留 <code>[3, 2, 1]</code> 与 <code>[6, 8, 5, 7]</code>，下一步才展示递归排序结果。将算法事实先写清楚，模型才能把正确的内容转成清晰的画面。用于正式材料前，再核对图里的文字、数字和关系。</p>
<a href="https://share.public.wzhecnu.cn/images/chatblog/crs-image-generation/quicksort-9c42781894fb.png">下载本次生成的 PNG 原图</a>
<p>相关项目：<a href="https://github.com/ChatArch/ChatImg" target="_blank" rel="noopener noreferrer" class="">ChatImg</a>、<a href="https://github.com/ChatArch/ChatEnv" target="_blank" rel="noopener noreferrer" class="">ChatEnv</a>、<a href="https://github.com/Wei-Shaw/claude-relay-service" target="_blank" rel="noopener noreferrer" class="">CRS</a>。</p>]]></content>
        <author>
            <name>ChatArch</name>
            <uri>https://github.com/ChatArch</uri>
        </author>
        <category label="ChatImg" term="ChatImg"/>
        <category label="CRS" term="CRS"/>
        <category label="image-generation" term="image-generation"/>
        <category label="curl" term="curl"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[AI4Math 的数据集十年：从小学应用题到 Lean Eval]]></title>
        <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution</id>
        <link href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution"/>
        <updated>2026-08-29T10:40:00.000Z</updated>
        <summary type="html"><![CDATA[从 GSM8K、MATH、miniF2F 到 Compfiles、PutnamBench、FrontierMath、LeanEval v1，沿着数据集内容看 AI4Math 研究重点如何从最终答案转向过程、验证器、形式化证明、live eval 和研究级数学。]]></summary>
        <content type="html"><![CDATA[<p>如果从传统数学史看，数学的发展可以讲成代数、几何、分析、概率、拓扑、逻辑不断分化又统一。但如果从计算机和 AI4Math 看，近十年的主线更像一部<strong>数据集形态变化史</strong>。</p>
<p>一开始，AI 做数学主要是在回答题：给出最终答案，算 exact match。后来，数据集开始要求模型写过程、生成多条解法、接受 verifier 打分、调用代码执行器、进入 Lean/Coq/Isabelle 这样的 proof assistant，最后又发展出 live leaderboard、去污染评测、研究级问题和提交制 formalization 平台。</p>
<p>换句话说，前沿不只是“模型更会做题了”，而是数学数据集本身从静态题库变成了可执行、可验证、可审计、可持续更新的数学工作流。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>一句话结论</div><div class="admonitionContent_BuS1"><p>AI4Math 的 benchmark 正在从 <code>problem -&gt; answer</code> 迁移到 <code>problem -&gt; process -&gt; verifier -&gt; environment -&gt; live workflow</code>。GSM8K 和 MATH 证明了自然语言数学推理的缺口；miniF2F 把答案变成可验证证明；PutnamBench、FrontierMath、MathArena、LeanEval v1 则说明：前沿已经转向更难、更鲜、更形式化、更像真实研究过程的评测。</p></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>时间与比较口径</div><div class="admonitionContent_BuS1"><p>本文沿用 <strong>2026-08-29 的资料与榜单快照</strong>，不是实时排行榜。MATH 与 MATH-500、不同 proof assistant、不同 Pass@k 和搜索预算分别标注；快照日期不等于模型发布日期。各条路线并行发展，不是后一个完全取代前一个。</p></div></div>
<p>GSM8K 是开放式应用题，不是选择题；MATH 已经是高中竞赛问题，也不只是更复杂的四则运算。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="20152020从文字列式到解题程序">2015–2020：从文字列式到解题程序<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#20152020%E4%BB%8E%E6%96%87%E5%AD%97%E5%88%97%E5%BC%8F%E5%88%B0%E8%A7%A3%E9%A2%98%E7%A8%8B%E5%BA%8F" class="hash-link" aria-label="2015–2020：从文字列式到解题程序的直接链接" title="2015–2020：从文字列式到解题程序的直接链接" translate="no">​</a></h3>
<p>早期工作集中于把应用题转换成算式或方程，例如 <a href="https://aclanthology.org/D15-1202/" target="_blank" rel="noopener noreferrer" class="">2015 年的算术应用题研究</a>。<a href="https://arxiv.org/abs/1705.04146" target="_blank" rel="noopener noreferrer" class="">AQuA-RAT（2017）</a> 为选择题提供自然语言解释；<a href="https://arxiv.org/abs/1905.13319" target="_blank" rel="noopener noreferrer" class="">MathQA（2019）</a> 则引入操作序列表示。这个阶段已经出现“过程数据”，但规模、题目多样性与标注质量仍是主要限制。随后 GSM8K 和 MATH 把挑战集中到更稳定的多步推理与竞赛解题上。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="先看总图数据集的六次换挡">先看总图：数据集的六次换挡<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#%E5%85%88%E7%9C%8B%E6%80%BB%E5%9B%BE%E6%95%B0%E6%8D%AE%E9%9B%86%E7%9A%84%E5%85%AD%E6%AC%A1%E6%8D%A2%E6%8C%A1" class="hash-link" aria-label="先看总图：数据集的六次换挡的直接链接" title="先看总图：数据集的六次换挡的直接链接" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" alt="AI4Math 数据集六次换挡" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjAiIGhlaWdodD0iOTYwIiB2aWV3Qm94PSIwIDAgOTYwIDk2MCIgcm9sZT0iaW1nIiBhcmlhLWxhYmVsbGVkYnk9InRpdGxlIGRlc2MiPgo8dGl0bGUgaWQ9InRpdGxlIj5BSSBmb3IgTWF0aO+8muaVsOaNruWvueixoeeahOa8lOi/mzwvdGl0bGU+PGRlc2MgaWQ9ImRlc2MiPumYtuauteebuOS6kumHjeWPoO+8m+i/meaYr+WIhumYtuauteaXtumXtOe6v++8jOS4jeaYr+etiei3neW5tOS7vei9tOOAgjwvZGVzYz4KPGcgZm9udC1mYW1pbHk9IiZxdW90O1BpbmdGYW5nIFNDJnF1b3Q7LCAmcXVvdDtOb3RvIFNhbnMgQ0pLIFNDJnF1b3Q7LCAmcXVvdDtNaWNyb3NvZnQgWWFIZWkmcXVvdDssIHNhbnMtc2VyaWYiPgo8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iOTYwIiBoZWlnaHQ9Ijk2MCIgcng9IjIyIiBmaWxsPSIjZjdmOWZjIiBzdHJva2U9IiNmN2Y5ZmMiLz4KPHRleHQgeD0iNDAiIHk9IjU4IiBmb250LXNpemU9IjMyIiBmaWxsPSIjMTcyYjRkIiBmb250LXdlaWdodD0iNzAwIj5BSSBmb3IgTWF0aO+8muaVsOaNruWvueixoeeahOa8lOi/mzwvdGV4dD4KPHRleHQgeD0iNDAiIHk9Ijk2IiBmb250LXNpemU9IjE5IiBmaWxsPSIjNTI2NTdiIiBmb250LXdlaWdodD0iNDAwIj7pmLbmrrXnm7jkupLph43lj6DvvJvov5nmmK/liIbpmLbmrrXml7bpl7Tnur/vvIzkuI3mmK/nrYnot53lubTku73ovbTjgII8L3RleHQ+CjxyZWN0IHg9IjM4IiB5PSIxMjYiIHdpZHRoPSI4ODQiIGhlaWdodD0iMTEwIiByeD0iMTQiIGZpbGw9IndoaXRlIiBzdHJva2U9IiNkYmUzZWQiLz4KPHJlY3QgeD0iMzgiIHk9IjEyNiIgd2lkdGg9IjgiIGhlaWdodD0iMTEwIiByeD0iMyIgZmlsbD0iI2JmNzgwMCIgc3Ryb2tlPSIjYmY3ODAwIi8+Cjx0ZXh0IHg9IjY0IiB5PSIxNTYiIGZvbnQtc2l6ZT0iMTkiIGZpbGw9IiNiZjc4MDAiIGZvbnQtd2VpZ2h0PSI3MDAiPjIwMTXigJMyMDIwPC90ZXh0Pgo8dGV4dCB4PSIyNjQiIHk9IjE1NiIgZm9udC1zaXplPSIyMyIgZmlsbD0iIzE3MmI0ZCIgZm9udC13ZWlnaHQ9IjcwMCI+5paH5a2X5YiX5byP5LiO56iL5bqP5YyW6KGo56S6PC90ZXh0Pgo8dGV4dCB4PSI2NCIgeT0iMTg5IiBmb250LXNpemU9IjIwIiBmaWxsPSIjMTcyYjRkIiBmb250LXdlaWdodD0iNDAwIj7nrpfmnK/lupTnlKjpopggwrcgQVF1QS1SQVQgwrcgTWF0aFFBPC90ZXh0Pgo8dGV4dCB4PSI2NCIgeT0iMjE5IiBmb250LXNpemU9IjE5IiBmaWxsPSIjNTI2NTdiIiBmb250LXdlaWdodD0iNDAwIj7popjnm64g4oaSIOeul+W8j+OAgeaTjeS9nOW6j+WIl+S4juiHqueEtuivreiogOino+mHijwvdGV4dD4KPHJlY3QgeD0iMzgiIHk9IjI1MCIgd2lkdGg9Ijg4NCIgaGVpZ2h0PSIxMTAiIHJ4PSIxNCIgZmlsbD0id2hpdGUiIHN0cm9rZT0iI2RiZTNlZCIvPgo8cmVjdCB4PSIzOCIgeT0iMjUwIiB3aWR0aD0iOCIgaGVpZ2h0PSIxMTAiIHJ4PSIzIiBmaWxsPSIjZDI1MzQ1IiBzdHJva2U9IiNkMjUzNDUiLz4KPHRleHQgeD0iNjQiIHk9IjI4MCIgZm9udC1zaXplPSIxOSIgZmlsbD0iI2QyNTM0NSIgZm9udC13ZWlnaHQ9IjcwMCI+MjAyMeKAkzIwMjI8L3RleHQ+Cjx0ZXh0IHg9IjI2NCIgeT0iMjgwIiBmb250LXNpemU9IjIzIiBmaWxsPSIjMTcyYjRkIiBmb250LXdlaWdodD0iNzAwIj7lpJrmraXmjqjnkIbkuI7op6Pms5XpgInmi6k8L3RleHQ+Cjx0ZXh0IHg9IjY0IiB5PSIzMTMiIGZvbnQtc2l6ZT0iMjAiIGZpbGw9IiMxNzJiNGQiIGZvbnQtd2VpZ2h0PSI0MDAiPk1BVEjvvIgyMDIxLTAz77yJwrcgR1NNOEvvvIgyMDIxLTEw77yJPC90ZXh0Pgo8dGV4dCB4PSI2NCIgeT0iMzQzIiBmb250LXNpemU9IjE5IiBmaWxsPSIjNTI2NTdiIiBmb250LXdlaWdodD0iNDAwIj7moIflh4bop6PjgIFDb1Qg5LiO5YCZ6YCJ6Kej5o6S5bqP77yb6aqM6K+B5bm26Z2e5ZCO5p2l5omN5Ye6546wPC90ZXh0Pgo8cmVjdCB4PSIzOCIgeT0iMzc0IiB3aWR0aD0iODg0IiBoZWlnaHQ9IjExMCIgcng9IjE0IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSIjZGJlM2VkIi8+CjxyZWN0IHg9IjM4IiB5PSIzNzQiIHdpZHRoPSI4IiBoZWlnaHQ9IjExMCIgcng9IjMiIGZpbGw9IiMwMDdmODIiIHN0cm9rZT0iIzAwN2Y4MiIvPgo8dGV4dCB4PSI2NCIgeT0iNDA0IiBmb250LXNpemU9IjE5IiBmaWxsPSIjMDA3ZjgyIiBmb250LXdlaWdodD0iNzAwIj4yMDIx4oCTMjAyMzwvdGV4dD4KPHRleHQgeD0iMjY0IiB5PSI0MDQiIGZvbnQtc2l6ZT0iMjMiIGZpbGw9IiMxNzJiNGQiIGZvbnQtd2VpZ2h0PSI3MDAiPuW5tuihjOi3r+e6v++8muacuuWZqOajgOafpeivgeaYjjwvdGV4dD4KPHRleHQgeD0iNjQiIHk9IjQzNyIgZm9udC1zaXplPSIyMCIgZmlsbD0iIzE3MmI0ZCIgZm9udC13ZWlnaHQ9IjQwMCI+bWluaUYyRu+8iDIwMjEtMDnvvInCtyBMZWFuRG9qb++8iDIwMjMtMDbvvIk8L3RleHQ+Cjx0ZXh0IHg9IjY0IiB5PSI0NjciIGZvbnQtc2l6ZT0iMTkiIGZpbGw9IiM1MjY1N2IiIGZvbnQtd2VpZ2h0PSI0MDAiPuW9ouW8j+WMluWRvemimOOAgeivgeaYjueKtuaAgeOAgXRhY3RpYyDkuI7lvJXnkIbmo4DntKI8L3RleHQ+CjxyZWN0IHg9IjM4IiB5PSI0OTgiIHdpZHRoPSI4ODQiIGhlaWdodD0iMTEwIiByeD0iMTQiIGZpbGw9IndoaXRlIiBzdHJva2U9IiNkYmUzZWQiLz4KPHJlY3QgeD0iMzgiIHk9IjQ5OCIgd2lkdGg9IjgiIGhlaWdodD0iMTEwIiByeD0iMyIgZmlsbD0iIzMwNjZiZSIgc3Ryb2tlPSIjMzA2NmJlIi8+Cjx0ZXh0IHg9IjY0IiB5PSI1MjgiIGZvbnQtc2l6ZT0iMTkiIGZpbGw9IiMzMDY2YmUiIGZvbnQtd2VpZ2h0PSI3MDAiPjIwMjPigJMyMDI0PC90ZXh0Pgo8dGV4dCB4PSIyNjQiIHk9IjUyOCIgZm9udC1zaXplPSIyMyIgZmlsbD0iIzE3MmI0ZCIgZm9udC13ZWlnaHQ9IjcwMCI+5pWw5o2u5bel56iL5oiQ5Li66K6t57uD5pa55rOVPC90ZXh0Pgo8dGV4dCB4PSI2NCIgeT0iNTYxIiBmb250LXNpemU9IjIwIiBmaWxsPSIjMTcyYjRkIiBmb250LXdlaWdodD0iNDAwIj5QUk04MDBLIMK3IE9wZW5XZWJNYXRoIMK3IERlZXBTZWVrTWF0aDwvdGV4dD4KPHRleHQgeD0iNjQiIHk9IjU5MSIgZm9udC1zaXplPSIxOSIgZmlsbD0iIzUyNjU3YiIgZm9udC13ZWlnaHQ9IjQwMCI+5q2l6aqk5qCH5rOo44CB5pWw5a2m6K+t5paZ5LiO5Y+v6aqM6K+B5Y+N6aaIPC90ZXh0Pgo8cmVjdCB4PSIzOCIgeT0iNjIyIiB3aWR0aD0iODg0IiBoZWlnaHQ9IjExMCIgcng9IjE0IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSIjZGJlM2VkIi8+CjxyZWN0IHg9IjM4IiB5PSI2MjIiIHdpZHRoPSI4IiBoZWlnaHQ9IjExMCIgcng9IjMiIGZpbGw9IiM3NjUxYjUiIHN0cm9rZT0iIzc2NTFiNSIvPgo8dGV4dCB4PSI2NCIgeT0iNjUyIiBmb250LXNpemU9IjE5IiBmaWxsPSIjNzY1MWI1IiBmb250LXdlaWdodD0iNzAwIj4yMDI0LTA3IOKGkiAyMDI1PC90ZXh0Pgo8dGV4dCB4PSIyNjQiIHk9IjY1MiIgZm9udC1zaXplPSIyMyIgZmlsbD0iIzE3MmI0ZCIgZm9udC13ZWlnaHQ9IjcwMCI+5pu05bm/55qE5pWw5a2m5LiO5pu05paw55qE6K+V6aKYPC90ZXh0Pgo8dGV4dCB4PSI2NCIgeT0iNjg1IiBmb250LXNpemU9IjIwIiBmaWxsPSIjMTcyYjRkIiBmb250LXdlaWdodD0iNDAwIj5QdXRuYW1CZW5jaCDCtyBGcm9udGllck1hdGggwrcgTWF0aEFyZW5hPC90ZXh0Pgo8dGV4dCB4PSI2NCIgeT0iNzE1IiBmb250LXNpemU9IjE5IiBmaWxsPSIjNTI2NTdiIiBmb250LXdlaWdodD0iNDAwIj7mnKznp5Hnn6Xor4bjgIHkuJPlrrbljp/liJvpopjjgIHmjInmr5TotZvml7bpl7Tmm7TmlrDnmoTor4TmtYs8L3RleHQ+CjxyZWN0IHg9IjM4IiB5PSI3NDYiIHdpZHRoPSI4ODQiIGhlaWdodD0iMTEwIiByeD0iMTQiIGZpbGw9IndoaXRlIiBzdHJva2U9IiNkYmUzZWQiLz4KPHJlY3QgeD0iMzgiIHk9Ijc0NiIgd2lkdGg9IjgiIGhlaWdodD0iMTEwIiByeD0iMyIgZmlsbD0iIzI1ODE1YiIgc3Ryb2tlPSIjMjU4MTViIi8+Cjx0ZXh0IHg9IjY0IiB5PSI3NzYiIGZvbnQtc2l6ZT0iMTkiIGZpbGw9IiMyNTgxNWIiIGZvbnQtd2VpZ2h0PSI3MDAiPjIwMjYtMDYg4oaSIDA4PC90ZXh0Pgo8dGV4dCB4PSIyNjQiIHk9Ijc3NiIgZm9udC1zaXplPSIyMyIgZmlsbD0iIzE3MmI0ZCIgZm9udC13ZWlnaHQ9IjcwMCI+5o+Q5Lqk44CB5Zue5pS+5LiO5Z+65YeG55Sf5ZG95ZGo5pyfPC90ZXh0Pgo8dGV4dCB4PSI2NCIgeT0iODA5IiBmb250LXNpemU9IjIwIiBmaWxsPSIjMTcyYjRkIiBmb250LXdlaWdodD0iNDAwIj5MZWFuIEV2YWwg5LiK57q/IOKGkiBMZWFuRXZhbCB2MSDlhrvnu5Ppm4blkIg8L3RleHQ+Cjx0ZXh0IHg9IjY0IiB5PSI4MzkiIGZvbnQtc2l6ZT0iMTkiIGZpbGw9IiM1MjY1N2IiIGZvbnQtd2VpZ2h0PSI0MDAiPuWPr+S/oemimOaEj+OAgUNvbXBhcmF0b3Ig6aqM6K+B44CB54mI5pys5LiO5o+Q5Lqk6K6w5b2VPC90ZXh0Pgo8dGV4dCB4PSI0MCIgeT0iOTExIiBmb250LXNpemU9IjIwIiBmaWxsPSIjNTI2NTdiIiBmb250LXdlaWdodD0iNDAwIj7pmIXor7vph43ngrnvvJrpopjnm67lpoLkvZXnu4Tnu4fvvIzlhrPlrprkuobmqKHlnovooqvorq3nu4Plkozor4Tku7fnmoTog73lipvjgII8L3RleHQ+CjwvZz48L3N2Zz4=" width="960" height="960" class="img_ev3q"></p>
<p>图中时间采用分阶段展开，非等距时间轴；越接近近年，粒度越细。关键是“数据对象”在变：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">题目 + 答案</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">-&gt; 题目 + 标准解 / CoT</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">-&gt; 多条解题轨迹 + verifier</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">-&gt; 代码执行 / 形式化证明环境</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">-&gt; 动态、去污染、live evaluation</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">-&gt; 研究级数学 + 提交制 formalization workflow</span><br></div></code></pre></div></div>
<p>所以我们不应只问“哪个模型在 MATH 上多少分”，还要问：这个 benchmark 到底把数学能力定义成什么？是最终答案？自然语言推理？可执行程序？Lean 证明？还是在持续更新的题库里提交可复现 artifact？</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-小学应用题阶段gsm8k-把简单数学也会翻车钉在墙上">1. 小学应用题阶段：GSM8K 把“简单数学也会翻车”钉在墙上<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#1-%E5%B0%8F%E5%AD%A6%E5%BA%94%E7%94%A8%E9%A2%98%E9%98%B6%E6%AE%B5gsm8k-%E6%8A%8A%E7%AE%80%E5%8D%95%E6%95%B0%E5%AD%A6%E4%B9%9F%E4%BC%9A%E7%BF%BB%E8%BD%A6%E9%92%89%E5%9C%A8%E5%A2%99%E4%B8%8A" class="hash-link" aria-label="1. 小学应用题阶段：GSM8K 把“简单数学也会翻车”钉在墙上的直接链接" title="1. 小学应用题阶段：GSM8K 把“简单数学也会翻车”钉在墙上的直接链接" translate="no">​</a></h2>
<p>代表数据集：<a href="https://arxiv.org/abs/2110.14168" target="_blank" rel="noopener noreferrer" class="">GSM8K</a>。</p>
<p>GSM8K 只有小学到初中早期的算术和代数概念，规模约 8.5K，7.5K train / 1K test。题目通常需要 2 到 8 步，重点不是高等数学，而是读懂自然语言、分解条件、连续计算、不要中途跑偏。</p>
<p>这类数据集把研究重点从“会不会算”推到“能不能稳定地多步推理”。它还很早就引入了 verifier 的思路：生成多个候选解，再训练 verifier 判断哪条解更可能正确。</p>
<p>这一步很重要，因为它预告了后来 AI4Math 的一条主线：<strong>数学的价值不只是答案可检查，而是 verifier 可以成为训练和搜索的一部分。</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-竞赛题阶段math-把-benchmark-从算术推进到-problem-solving">2. 竞赛题阶段：MATH 把 benchmark 从算术推进到 problem solving<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#2-%E7%AB%9E%E8%B5%9B%E9%A2%98%E9%98%B6%E6%AE%B5math-%E6%8A%8A-benchmark-%E4%BB%8E%E7%AE%97%E6%9C%AF%E6%8E%A8%E8%BF%9B%E5%88%B0-problem-solving" class="hash-link" aria-label="2. 竞赛题阶段：MATH 把 benchmark 从算术推进到 problem solving的直接链接" title="2. 竞赛题阶段：MATH 把 benchmark 从算术推进到 problem solving的直接链接" translate="no">​</a></h2>
<p>代表数据集：<a href="https://arxiv.org/abs/2103.03874" target="_blank" rel="noopener noreferrer" class="">MATH</a>。</p>
<p>MATH 是一个转折点。它有 12,500 道高中竞赛数学题，来自 AMC、AIME 等来源；题目覆盖 prealgebra、algebra、number theory、counting and probability、geometry、intermediate algebra、precalculus，并带有 difficulty level 1 到 5。每题有 step-by-step solution 和 boxed final answer。</p>
<p>MATH 的内容设计改变了研究重点：</p>
<ul>
<li class="">不再只测 plug-and-chug calculation；</li>
<li class="">要求模型掌握启发式 problem solving；</li>
<li class="">LaTeX、文本图形、标准解成为训练信号；</li>
<li class="">exact answer 仍然便宜可评测，但过程开始变重要。</li>
</ul>
<p>MATH 提出时，最大的模型也只有个位数准确率。到 2025-2026，MATH-500 这类代表子集已经接近饱和。这是 AI benchmark 最典型的一条曲线：<strong>今天的 frontier，明天的 sanity check。</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-形式化阶段minif2f-把答对变成证明可检查">3. 形式化阶段：miniF2F 把“答对”变成“证明可检查”<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#3-%E5%BD%A2%E5%BC%8F%E5%8C%96%E9%98%B6%E6%AE%B5minif2f-%E6%8A%8A%E7%AD%94%E5%AF%B9%E5%8F%98%E6%88%90%E8%AF%81%E6%98%8E%E5%8F%AF%E6%A3%80%E6%9F%A5" class="hash-link" aria-label="3. 形式化阶段：miniF2F 把“答对”变成“证明可检查”的直接链接" title="3. 形式化阶段：miniF2F 把“答对”变成“证明可检查”的直接链接" translate="no">​</a></h2>
<p>代表数据集：<a href="https://arxiv.org/abs/2109.00110" target="_blank" rel="noopener noreferrer" class="">miniF2F</a>。</p>
<p>MATH 的答案可以 exact-match，但自然语言证明本身仍然难以自动判定。miniF2F 走了另一条路：把 Olympiad-level problem statements 形式化到 proof assistant 里，让机器检查证明是否通过。</p>
<p>miniF2F v1 有 488 个 formal statements，244 validation / 244 test，覆盖 Lean、Metamath、Isabelle，HOL Light 部分支持；来源包括 AMC、AIME、IMO、MATH 和课程材料。</p>
<p>这里的数据字段已经变了：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">problem, solution, answer</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">-&gt; formal statement, proof state, tactic, theorem library, kernel check</span><br></div></code></pre></div></div>
<p>这使得 AI4Math 研究重点从“生成一个像证明的文本”转向“生成一个 proof assistant 接受的 artifact”。模型不再只是在写答案，而是在和 Lean/Metamath/Isabelle 的逻辑环境交互。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-imo-竞赛级compfiles-和-matholympiadbench-把题库变成-lean-资产">4. IMO 竞赛级：Compfiles 和 MathOlympiadBench 把题库变成 Lean 资产<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#4-imo-%E7%AB%9E%E8%B5%9B%E7%BA%A7compfiles-%E5%92%8C-matholympiadbench-%E6%8A%8A%E9%A2%98%E5%BA%93%E5%8F%98%E6%88%90-lean-%E8%B5%84%E4%BA%A7" class="hash-link" aria-label="4. IMO 竞赛级：Compfiles 和 MathOlympiadBench 把题库变成 Lean 资产的直接链接" title="4. IMO 竞赛级：Compfiles 和 MathOlympiadBench 把题库变成 Lean 资产的直接链接" translate="no">​</a></h2>
<p>Compfiles 不是带固定测试划分和统一刷榜口径的题库，而是一个持续增长的 Lean 形式化仓库：<a href="https://dwrensha.github.io/compfiles/" target="_blank" rel="noopener noreferrer" class="">Compfiles</a> 把 olympiad-style problems 和完整解法形式化到 Lean 4 中。它更像一座正在建设的“竞赛数学 proof artifact 仓库”。</p>
<p>后来的 <a href="https://huggingface.co/datasets/Goedel-LM/MathOlympiadBench" target="_blank" rel="noopener noreferrer" class="">MathOlympiadBench</a> 明确吸收了这条线：它包含 360 个 human-verified Olympiad-level formalization，其中包括 158 道 IMO 问题、131 道 IMO shortlist 问题、68 道 national olympiad 问题和 3 道 puzzles，来源包括 Compfiles 和 IMOSLLean4。</p>
<p>这代表一个新的研究重点：</p>
<ul>
<li class="">不是只把题面放进 benchmark；</li>
<li class="">而是要把题面、Lean formal statement、是否 solved、proof code、Mathlib compatibility 都纳入数据；</li>
<li class="">数据质量问题也变成研究对象，例如题意不完整、多文件分布、一个问题多个 theorem、formal statement 与 informal statement 不匹配。</li>
</ul>
<p>到了这里，数据集不再只是“题库”，而是 formal mathematics library 的一部分。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-本科竞赛级putnambench-把难度和多语言形式化一起拉高">5. 本科竞赛级：PutnamBench 把难度和多语言形式化一起拉高<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#5-%E6%9C%AC%E7%A7%91%E7%AB%9E%E8%B5%9B%E7%BA%A7putnambench-%E6%8A%8A%E9%9A%BE%E5%BA%A6%E5%92%8C%E5%A4%9A%E8%AF%AD%E8%A8%80%E5%BD%A2%E5%BC%8F%E5%8C%96%E4%B8%80%E8%B5%B7%E6%8B%89%E9%AB%98" class="hash-link" aria-label="5. 本科竞赛级：PutnamBench 把难度和多语言形式化一起拉高的直接链接" title="5. 本科竞赛级：PutnamBench 把难度和多语言形式化一起拉高的直接链接" translate="no">​</a></h2>
<p>代表数据集：<a href="https://arxiv.org/abs/2407.11214" target="_blank" rel="noopener noreferrer" class="">PutnamBench</a>。</p>
<p>PutnamBench 面向 William Lowell Putnam Mathematical Competition，也就是北美最有名的本科数学竞赛。它比高中 Olympiad 更接近大学数学，需要分析、抽象代数、线性代数、数论、组合等更广的本科知识。</p>
<p>早期论文版本包含 640 个 Putnam problems 的多语言 formalization；2026-08-29 的仓库快照统计为 1724 个 manually-crafted formalizations，覆盖 672 个 Lean 4、640 个 Isabelle、412 个 Coq formalizations。</p>
<p>PutnamBench 的意义是：</p>
<ul>
<li class="">它不是自然语言 answer-only；</li>
<li class="">它不是单一 proof assistant；</li>
<li class="">它把同一数学问题推向 Lean、Isabelle、Coq；</li>
<li class="">它让 benchmark 既测数学推理，也测 formal language proficiency。</li>
</ul>
<p>原论文的 baseline 非常低：GPT-4、COPRA、Sledgehammer、CoqHammer 等方法合计只证明了少数问题。到 2026 的 public leaderboard，PutnamBench 已经出现一次爆发：一些系统在 Lean 版本上从 single digits / dozens solved 跳到 hundreds solved，甚至接近全覆盖。但这里必须非常谨慎：compute budget、是否带 factored answer、版本、提交规则都不同，所以更适合把它看成“形式化数学 benchmark 的压力计”，而不是简单排名。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-研究级数学frontiermath-说明静态竞赛题已经不够了">6. 研究级数学：FrontierMath 说明静态竞赛题已经不够了<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#6-%E7%A0%94%E7%A9%B6%E7%BA%A7%E6%95%B0%E5%AD%A6frontiermath-%E8%AF%B4%E6%98%8E%E9%9D%99%E6%80%81%E7%AB%9E%E8%B5%9B%E9%A2%98%E5%B7%B2%E7%BB%8F%E4%B8%8D%E5%A4%9F%E4%BA%86" class="hash-link" aria-label="6. 研究级数学：FrontierMath 说明静态竞赛题已经不够了的直接链接" title="6. 研究级数学：FrontierMath 说明静态竞赛题已经不够了的直接链接" translate="no">​</a></h2>
<p>代表数据集：<a href="https://arxiv.org/abs/2411.04872" target="_blank" rel="noopener noreferrer" class="">FrontierMath</a>，以及 Epoch AI 的 <a href="https://epoch.ai/frontiermath/tiers-1-4/about" target="_blank" rel="noopener noreferrer" class="">FrontierMath Tiers 1-4</a>。</p>
<p>FrontierMath 的设计动机很直接：GSM8K、MATH、MATH-500 被刷高以后，模型需要更难、更不容易污染的数学问题。FrontierMath 由专家数学家编写和审查，题目覆盖现代数学多个分支，强调 original、unpublished、guessproof。</p>
<p>根据 Epoch 的版本说明：</p>
<ul>
<li class="">2024-10-22 版本有 119 题，是论文分析版本；</li>
<li class="">2024-11-26 版本有 180 题；OpenAI 2024-12-20 的 o3 announcement 声称在该版本上达到 25.2%；</li>
<li class="">2025-02-28 版本扩到 300 core dataset；</li>
<li class="">2025-06-30 又有 Tier 4 extreme difficulty expansion。</li>
</ul>
<p>FrontierMath 代表的是另一个方向：不再靠公开固定题库，而是用专家原创、隐藏 held-out、难猜答案和版本化策略对抗污染。数学 benchmark 从“考试卷”变成“研究级能力探针”。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-lean-eval-v1benchmark-变成-public-submission-workflow">7. Lean Eval v1：benchmark 变成 public submission workflow<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#7-lean-eval-v1benchmark-%E5%8F%98%E6%88%90-public-submission-workflow" class="hash-link" aria-label="7. Lean Eval v1：benchmark 变成 public submission workflow的直接链接" title="7. Lean Eval v1：benchmark 变成 public submission workflow的直接链接" translate="no">​</a></h2>
<p>代表平台：<a href="https://lean-lang.org/eval/" target="_blank" rel="noopener noreferrer" class="">Lean Eval</a> 和 <a href="https://github.com/leanprover/lean-eval" target="_blank" rel="noopener noreferrer" class="">leanprover/lean-eval</a>。</p>
<p>Lean Eval 是更晚近的一步。它不是一个普通静态 PDF benchmark，而是 comparator-based Lean formal mathematics eval。benchmark authors 在共享 Lean modules 中写 trusted problem statements，系统为每个 problem 生成 comparator workspace；submission 是否 solved，由 comparator 接受与否决定。</p>
<p>2026-08-29 读取的 <a href="https://lean-lang.org/eval/site-data/v2/groups/formalization-evaluation.json" target="_blank" rel="noopener noreferrer" class="">官方榜单数据</a> 中，LeanEval v1 是于 <strong>2026-08-20 发布的 128 题冻结集合</strong>；站点提供问题历史、最近提交与回放状态。<a href="https://lean-lang.org/" target="_blank" rel="noopener noreferrer" class="">Lean 官方时间线</a>将 Lean Eval 的公开发布列在 <strong>2026 年 6 月</strong>。这两个日期分别对应平台上线和 v1 冻结集合，不能混为“年初发布了同一个基准”。</p>
<p>榜单的提交可能来自有人类参与的系统，且不一定共享推理预算；已接受证明数不是统一条件下的模型 pass@1，也不自动意味着发现了新定理。</p>
<p>这意味着数据集进一步变成 workflow：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">problem set</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">-&gt; trusted theorem modules</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">-&gt; comparator workspace</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">-&gt; public submissions</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">-&gt; accepted solutions</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">-&gt; replay / lifecycle / problem history</span><br></div></code></pre></div></div>
<p>这是 AI4Math 很可能长期演化的形态：不再只有“模型报告里的一张表”，而是一个持续运行的公共验证系统。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="数据集定位难度和验证方式是两条轴">数据集定位：难度和验证方式是两条轴<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#%E6%95%B0%E6%8D%AE%E9%9B%86%E5%AE%9A%E4%BD%8D%E9%9A%BE%E5%BA%A6%E5%92%8C%E9%AA%8C%E8%AF%81%E6%96%B9%E5%BC%8F%E6%98%AF%E4%B8%A4%E6%9D%A1%E8%BD%B4" class="hash-link" aria-label="数据集定位：难度和验证方式是两条轴的直接链接" title="数据集定位：难度和验证方式是两条轴的直接链接" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" alt="AI4Math 数据集内容与验证方式对照" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjAiIGhlaWdodD0iMTAwMCIgdmlld0JveD0iMCAwIDk2MCAxMDAwIiByb2xlPSJpbWciIGFyaWEtbGFiZWxsZWRieT0idGl0bGUgZGVzYyI+Cjx0aXRsZSBpZD0idGl0bGUiPuS4pOadoei9tO+8mumimOebruWGheWuuSDDlyDpqozor4HmlrnlvI88L3RpdGxlPjxkZXNjIGlkPSJkZXNjIj7lvaLlvI/ljJblubbkuI3nrYnkuo7mm7Tpmr7vvJvkuI3lkIzmlbDmja7pm4bkuZ/kuI3og73mjpLmiJDnu5/kuIDog73lipvmjpLooYzmppzjgII8L2Rlc2M+CjxnIGZvbnQtZmFtaWx5PSImcXVvdDtQaW5nRmFuZyBTQyZxdW90OywgJnF1b3Q7Tm90byBTYW5zIENKSyBTQyZxdW90OywgJnF1b3Q7TWljcm9zb2Z0IFlhSGVpJnF1b3Q7LCBzYW5zLXNlcmlmIj4KPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9Ijk2MCIgaGVpZ2h0PSIxMDAwIiByeD0iMjIiIGZpbGw9IiNmN2Y5ZmMiIHN0cm9rZT0iI2Y3ZjlmYyIvPgo8dGV4dCB4PSI0MCIgeT0iNTgiIGZvbnQtc2l6ZT0iMzIiIGZpbGw9IiMxNzJiNGQiIGZvbnQtd2VpZ2h0PSI3MDAiPuS4pOadoei9tO+8mumimOebruWGheWuuSDDlyDpqozor4HmlrnlvI88L3RleHQ+Cjx0ZXh0IHg9IjQwIiB5PSI5NiIgZm9udC1zaXplPSIxOSIgZmlsbD0iIzUyNjU3YiIgZm9udC13ZWlnaHQ9IjQwMCI+5b2i5byP5YyW5bm25LiN562J5LqO5pu06Zq+77yb5LiN5ZCM5pWw5o2u6ZuG5Lmf5LiN6IO95o6S5oiQ57uf5LiA6IO95Yqb5o6S6KGM5qac44CCPC90ZXh0Pgo8cmVjdCB4PSIzOCIgeT0iMTMwIiB3aWR0aD0iODg0IiBoZWlnaHQ9Ijk4IiByeD0iMTQiIGZpbGw9IndoaXRlIiBzdHJva2U9IiNkYmUzZWQiLz4KPHRleHQgeD0iNjAiIHk9IjE2MCIgZm9udC1zaXplPSIyMiIgZmlsbD0iIzMwNjZiZSIgZm9udC13ZWlnaHQ9IjcwMCI+R1NNOEs8L3RleHQ+Cjx0ZXh0IHg9IjYwIiB5PSIxOTAiIGZvbnQtc2l6ZT0iMjAiIGZpbGw9IiMxNzJiNGQiIGZvbnQtd2VpZ2h0PSI0MDAiPuWwj+WtpuiHs+aXqeacn+S7o+aVsOW6lOeUqOmimDwvdGV4dD4KPHRleHQgeD0iNjAiIHk9IjIxNiIgZm9udC1zaXplPSIxOSIgZmlsbD0iIzUyNjU3YiIgZm9udC13ZWlnaHQ9IjQwMCI+5byA5pS+5byP562U5qGIICsg6Kej6aKY6L+H56iL77yb5LiN5piv6YCJ5oup6aKYPC90ZXh0Pgo8cmVjdCB4PSIzOCIgeT0iMjQyIiB3aWR0aD0iODg0IiBoZWlnaHQ9Ijk4IiByeD0iMTQiIGZpbGw9IndoaXRlIiBzdHJva2U9IiNkYmUzZWQiLz4KPHRleHQgeD0iNjAiIHk9IjI3MiIgZm9udC1zaXplPSIyMiIgZmlsbD0iIzMwNjZiZSIgZm9udC13ZWlnaHQ9IjcwMCI+TUFUSCAvIE1BVEgtNTAwPC90ZXh0Pgo8dGV4dCB4PSI2MCIgeT0iMzAyIiBmb250LXNpemU9IjIwIiBmaWxsPSIjMTcyYjRkIiBmb250LXdlaWdodD0iNDAwIj7pq5jkuK3nq57otZvvvJrku6PmlbDjgIHlh6DkvZXjgIHmlbDorrrnrYk8L3RleHQ+Cjx0ZXh0IHg9IjYwIiB5PSIzMjgiIGZvbnQtc2l6ZT0iMTkiIGZpbGw9IiM1MjY1N2IiIGZvbnQtd2VpZ2h0PSI0MDAiPuacgOe7iOetlOahiOivhOa1i++8m+WujOaVtOa1i+ivlembhuS4jiA1MDAg6aKY5a2Q6ZuG5YiG5byAPC90ZXh0Pgo8cmVjdCB4PSIzOCIgeT0iMzU0IiB3aWR0aD0iODg0IiBoZWlnaHQ9Ijk4IiByeD0iMTQiIGZpbGw9IndoaXRlIiBzdHJva2U9IiNkYmUzZWQiLz4KPHRleHQgeD0iNjAiIHk9IjM4NCIgZm9udC1zaXplPSIyMiIgZmlsbD0iIzMwNjZiZSIgZm9udC13ZWlnaHQ9IjcwMCI+bWluaUYyRjwvdGV4dD4KPHRleHQgeD0iNjAiIHk9IjQxNCIgZm9udC1zaXplPSIyMCIgZmlsbD0iIzE3MmI0ZCIgZm9udC13ZWlnaHQ9IjQwMCI+6K++56iL6aKY5LiO56ue6LWb6aKY55qE5b2i5byP5YyW5ZG96aKYPC90ZXh0Pgo8dGV4dCB4PSI2MCIgeT0iNDQwIiBmb250LXNpemU9IjE5IiBmaWxsPSIjNTI2NTdiIiBmb250LXdlaWdodD0iNDAwIj7or4HmmI7pgJrov4cgcHJvb2YgYXNzaXN0YW5077yb5Zu65a6a5rWL6K+V5YiS5YiGPC90ZXh0Pgo8cmVjdCB4PSIzOCIgeT0iNDY2IiB3aWR0aD0iODg0IiBoZWlnaHQ9Ijk4IiByeD0iMTQiIGZpbGw9IndoaXRlIiBzdHJva2U9IiNkYmUzZWQiLz4KPHRleHQgeD0iNjAiIHk9IjQ5NiIgZm9udC1zaXplPSIyMiIgZmlsbD0iIzMwNjZiZSIgZm9udC13ZWlnaHQ9IjcwMCI+Q29tcGZpbGVzIC8gTWF0aE9seW1waWFkQmVuY2g8L3RleHQ+Cjx0ZXh0IHg9IjYwIiB5PSI1MjYiIGZvbnQtc2l6ZT0iMjAiIGZpbGw9IiMxNzJiNGQiIGZvbnQtd2VpZ2h0PSI0MDAiPklNT+OAgXNob3J0bGlzdCDkuI7lhbbku5blpaXotZvpopg8L3RleHQ+Cjx0ZXh0IHg9IjYwIiB5PSI1NTIiIGZvbnQtc2l6ZT0iMTkiIGZpbGw9IiM1MjY1N2IiIGZvbnQtd2VpZ2h0PSI0MDAiPuaMgee7reW7uuiuvueahCBMZWFuIOS7k+W6kyAvIOS6uuW3peaguOmqjOeahOivhOa1i+mimOmbhjwvdGV4dD4KPHJlY3QgeD0iMzgiIHk9IjU3OCIgd2lkdGg9Ijg4NCIgaGVpZ2h0PSI5OCIgcng9IjE0IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSIjZGJlM2VkIi8+Cjx0ZXh0IHg9IjYwIiB5PSI2MDgiIGZvbnQtc2l6ZT0iMjIiIGZpbGw9IiMzMDY2YmUiIGZvbnQtd2VpZ2h0PSI3MDAiPlB1dG5hbUJlbmNoPC90ZXh0Pgo8dGV4dCB4PSI2MCIgeT0iNjM4IiBmb250LXNpemU9IjIwIiBmaWxsPSIjMTcyYjRkIiBmb250LXdlaWdodD0iNDAwIj7mm7Tlub/nmoTmnKznp5HmlbDlrabnq57otZvnn6Xor4Y8L3RleHQ+Cjx0ZXh0IHg9IjYwIiB5PSI2NjQiIGZvbnQtc2l6ZT0iMTkiIGZpbGw9IiM1MjY1N2IiIGZvbnQtd2VpZ2h0PSI0MDAiPkxlYW7jgIFJc2FiZWxsZeOAgUNvce+8m+azqOaEj+mimOW6k+eJiOacrOS4juetlOahiOaooeW8jzwvdGV4dD4KPHJlY3QgeD0iMzgiIHk9IjY5MCIgd2lkdGg9Ijg4NCIgaGVpZ2h0PSI5OCIgcng9IjE0IiBmaWxsPSJ3aGl0ZSIgc3Ryb2tlPSIjZGJlM2VkIi8+Cjx0ZXh0IHg9IjYwIiB5PSI3MjAiIGZvbnQtc2l6ZT0iMjIiIGZpbGw9IiMzMDY2YmUiIGZvbnQtd2VpZ2h0PSI3MDAiPkZyb250aWVyTWF0aDwvdGV4dD4KPHRleHQgeD0iNjAiIHk9Ijc1MCIgZm9udC1zaXplPSIyMCIgZmlsbD0iIzE3MmI0ZCIgZm9udC13ZWlnaHQ9IjQwMCI+5pys56eR6auY6Zq+6aKY6Iez56CU56m257qn5pWw5a2mPC90ZXh0Pgo8dGV4dCB4PSI2MCIgeT0iNzc2IiBmb250LXNpemU9IjE5IiBmaWxsPSIjNTI2NTdiIiBmb250LXdlaWdodD0iNDAwIj7kuJPlrrbljp/liJvjgIHkv53nlZnpopjpm4bjgIHnqIvluo/ovoXliqnnrZTmoYjpqozor4E8L3RleHQ+CjxyZWN0IHg9IjM4IiB5PSI4MDIiIHdpZHRoPSI4ODQiIGhlaWdodD0iOTgiIHJ4PSIxNCIgZmlsbD0id2hpdGUiIHN0cm9rZT0iI2RiZTNlZCIvPgo8dGV4dCB4PSI2MCIgeT0iODMyIiBmb250LXNpemU9IjIyIiBmaWxsPSIjMzA2NmJlIiBmb250LXdlaWdodD0iNzAwIj5MZWFuRXZhbCB2MTwvdGV4dD4KPHRleHQgeD0iNjAiIHk9Ijg2MiIgZm9udC1zaXplPSIyMCIgZmlsbD0iIzE3MmI0ZCIgZm9udC13ZWlnaHQ9IjQwMCI+5Zuw6Zq+5pWw5a2m5b2i5byP5YyW5Lu75YqhPC90ZXh0Pgo8dGV4dCB4PSI2MCIgeT0iODg4IiBmb250LXNpemU9IjE5IiBmaWxsPSIjNTI2NTdiIiBmb250LXdlaWdodD0iNDAwIj7lhrvnu5Ppm4blkIggKyDlj6/kv6HpopjmhI8gKyBDb21wYXJhdG9yICsg5o+Q5Lqk5Zue5pS+PC90ZXh0Pgo8dGV4dCB4PSI0MCIgeT0iOTU3IiBmb250LXNpemU9IjE5IiBmaWxsPSIjNTI2NTdiIiBmb250LXdlaWdodD0iNDAwIj5Db21wZmlsZXMg55qE5bqT6KaG55uW546HIOKJoCDmqKHlnovop6PpopjnjofvvJtMZWFuRXZhbCDnmoTmj5DkuqTmlbAg4omgIOe7n+S4gCBwYXNzQDHjgII8L3RleHQ+CjwvZz48L3N2Zz4=" width="960" height="1000" class="img_ev3q"></p>
<p>这些数据集不能严格排成一条难度阶梯。GSM8K、MATH 和 Putnam 描述题目内容；miniF2F 同时限定形式化输入；LeanEval 更强调可信提交和验证协议。图中把题目内容与验收方式并列，避免把“验证更严格”误当成“每一题都更难”。</p>
<table><thead><tr><th>层级</th><th>代表</th><th>数据内容</th><th>验收层</th></tr></thead><tbody><tr><td>小学 word problem</td><td>GSM8K</td><td>题面、自然语言解、最终答案</td><td>answer parser / verifier</td></tr><tr><td>高中竞赛</td><td>MATH, MATH-500</td><td>LaTeX、标准解、boxed answer、difficulty</td><td>exact match / symbolic equivalence</td></tr><tr><td>形式化 Olympiad</td><td>miniF2F</td><td>formal statement、proof state、tactic</td><td>Lean / Metamath / Isabelle kernel</td></tr><tr><td>IMO 题库资产</td><td>Compfiles, MathOlympiadBench</td><td>Lean problem、solution code、solved flag</td><td>Mathlib + Lean checker</td></tr><tr><td>本科竞赛</td><td>PutnamBench</td><td>Lean / Isabelle / Coq formalizations</td><td>multi-proof-assistant verification</td></tr><tr><td>研究级数学</td><td>FrontierMath, Riemann-Bench, Soohak</td><td>expert-created unpublished problems</td><td>hidden set、versioning、专家审查</td></tr><tr><td>提交制 formalization</td><td>LeanEval v1</td><td>trusted module、comparator、submission、replay</td><td>comparator acceptance + lifecycle</td></tr></tbody></table>
<p>这也是为什么旧 benchmark 被刷满以后不会消失。它们会降级成低阶 sanity check，同时逼出更高层的 benchmark。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="分数如何上升math-family-和-minif2f-的历史节点">分数如何上升：MATH family 和 miniF2F 的历史节点<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#%E5%88%86%E6%95%B0%E5%A6%82%E4%BD%95%E4%B8%8A%E5%8D%87math-family-%E5%92%8C-minif2f-%E7%9A%84%E5%8E%86%E5%8F%B2%E8%8A%82%E7%82%B9" class="hash-link" aria-label="分数如何上升：MATH family 和 miniF2F 的历史节点的直接链接" title="分数如何上升：MATH family 和 miniF2F 的历史节点的直接链接" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" alt="MATH、MATH-500 与 miniF2F 分开呈现的历史分数节点" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjAiIGhlaWdodD0iMTQxMCIgdmlld0JveD0iMCAwIDk2MCAxNDEwIiByb2xlPSJpbWciIGFyaWEtbGFiZWxsZWRieT0idGl0bGUgZGVzYyI+Cjx0aXRsZSBpZD0idGl0bGUiPuWIhuaVsOS4iuWNh++8muWIhuWIq+eci+WOhuWPsuiKgueCuTwvdGl0bGU+PGRlc2MgaWQ9ImRlc2MiPuS4jeWQjOa1i+ivlembhuWIhumdouadv++8jOS4jeWQjOmihOeul+S4jei/nue6v++8m+i/meS6m+iKgueCueS4jeaYr+e7n+S4gOWPo+W+hOeahCBTT1RBIOabsue6v+OAgjwvZGVzYz4KPGcgZm9udC1mYW1pbHk9IiZxdW90O1BpbmdGYW5nIFNDJnF1b3Q7LCAmcXVvdDtOb3RvIFNhbnMgQ0pLIFNDJnF1b3Q7LCAmcXVvdDtNaWNyb3NvZnQgWWFIZWkmcXVvdDssIHNhbnMtc2VyaWYiPgo8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iOTYwIiBoZWlnaHQ9IjE0MTAiIHJ4PSIyMiIgZmlsbD0iI2Y3ZjlmYyIgc3Ryb2tlPSIjZjdmOWZjIi8+Cjx0ZXh0IHg9IjQwIiB5PSI1OCIgZm9udC1zaXplPSIzMiIgZmlsbD0iIzE3MmI0ZCIgZm9udC13ZWlnaHQ9IjcwMCI+5YiG5pWw5LiK5Y2H77ya5YiG5Yir55yL5Y6G5Y+y6IqC54K5PC90ZXh0Pgo8dGV4dCB4PSI0MCIgeT0iOTYiIGZvbnQtc2l6ZT0iMTkiIGZpbGw9IiM1MjY1N2IiIGZvbnQtd2VpZ2h0PSI0MDAiPuS4jeWQjOa1i+ivlembhuWIhumdouadv++8jOS4jeWQjOmihOeul+S4jei/nue6v++8m+i/meS6m+iKgueCueS4jeaYr+e7n+S4gOWPo+W+hOeahCBTT1RBIOabsue6v+OAgjwvdGV4dD4KPHJlY3QgeD0iMzgiIHk9IjEyOCIgd2lkdGg9Ijg4NCIgaGVpZ2h0PSIzNDIiIHJ4PSIxNCIgZmlsbD0id2hpdGUiIHN0cm9rZT0iI2RiZTNlZCIvPgo8dGV4dCB4PSI2MCIgeT0iMTYyIiBmb250LXNpemU9IjIzIiBmaWxsPSIjZDI1MzQ1IiBmb250LXdlaWdodD0iNzAwIj5BICDlrozmlbQgTUFUSCDmtYvor5Xpm4Y8L3RleHQ+Cjx0ZXh0IHg9IjYwIiB5PSIxOTMiIGZvbnQtc2l6ZT0iMjAiIGZpbGw9IiMxNzJiNGQiIGZvbnQtd2VpZ2h0PSI2MDAiPjIwMjEtMDMgIOWOn+iuuuaWh+acgOS9s+Wfuue6vzwvdGV4dD4KPHRleHQgeD0iNjAiIHk9IjIxOSIgZm9udC1zaXplPSIxOCIgZmlsbD0iIzUyNjU3YiIgZm9udC13ZWlnaHQ9IjQwMCI+YWNjdXJhY3k8L3RleHQ+CjxyZWN0IHg9IjYwIiB5PSIyMzIiIHdpZHRoPSI3NTIiIGhlaWdodD0iMTMiIHJ4PSI2IiBmaWxsPSIjZTRlYWYxIiBzdHJva2U9IiNlNGVhZjEiLz4KPHJlY3QgeD0iNjAiIHk9IjIzMiIgd2lkdGg9IjUxLjg4ODAwMDAwMDAwMDAwNSIgaGVpZ2h0PSIxMyIgcng9IjYiIGZpbGw9IiNkMjUzNDUiIHN0cm9rZT0iI2QyNTM0NSIvPgo8dGV4dCB4PSI4MzAiIHk9IjI0NSIgZm9udC1zaXplPSIyMiIgZmlsbD0iI2QyNTM0NSIgZm9udC13ZWlnaHQ9IjcwMCI+Ni45JTwvdGV4dD4KPHRleHQgeD0iNjAiIHk9IjI4OSIgZm9udC1zaXplPSIyMCIgZmlsbD0iIzE3MmI0ZCIgZm9udC13ZWlnaHQ9IjYwMCI+MjAyMi0wNiAgTWluZXJ2YSA1NDBCPC90ZXh0Pgo8dGV4dCB4PSI2MCIgeT0iMzE1IiBmb250LXNpemU9IjE4IiBmaWxsPSIjNTI2NTdiIiBmb250LXdlaWdodD0iNDAwIj5tYWpvcml0eSB2b3Rl77yb5Y2V5qyhIDMzLjYlPC90ZXh0Pgo8cmVjdCB4PSI2MCIgeT0iMzI4IiB3aWR0aD0iNzUyIiBoZWlnaHQ9IjEzIiByeD0iNiIgZmlsbD0iI2U0ZWFmMSIgc3Ryb2tlPSIjZTRlYWYxIi8+CjxyZWN0IHg9IjYwIiB5PSIzMjgiIHdpZHRoPSIzNzguMjU2IiBoZWlnaHQ9IjEzIiByeD0iNiIgZmlsbD0iI2QyNTM0NSIgc3Ryb2tlPSIjZDI1MzQ1Ii8+Cjx0ZXh0IHg9IjgzMCIgeT0iMzQxIiBmb250LXNpemU9IjIyIiBmaWxsPSIjZDI1MzQ1IiBmb250LXdlaWdodD0iNzAwIj41MC4zJTwvdGV4dD4KPHRleHQgeD0iNjAiIHk9IjM4NSIgZm9udC1zaXplPSIyMCIgZmlsbD0iIzE3MmI0ZCIgZm9udC13ZWlnaHQ9IjYwMCI+MjAyNC0wMiAgRGVlcFNlZWtNYXRoLVJMIDdCPC90ZXh0Pgo8dGV4dCB4PSI2MCIgeT0iNDExIiBmb250LXNpemU9IjE4IiBmaWxsPSIjNTI2NTdiIiBmb250LXdlaWdodD0iNDAwIj5zZWxmLWNvbnNpc3RlbmN5IDY077ybdG9wLTEgNTEuNyU8L3RleHQ+CjxyZWN0IHg9IjYwIiB5PSI0MjQiIHdpZHRoPSI3NTIiIGhlaWdodD0iMTMiIHJ4PSI2IiBmaWxsPSIjZTRlYWYxIiBzdHJva2U9IiNlNGVhZjEiLz4KPHJlY3QgeD0iNjAiIHk9IjQyNCIgd2lkdGg9IjQ1Ny45Njc5OTk5OTk5OTk5NiIgaGVpZ2h0PSIxMyIgcng9IjYiIGZpbGw9IiNkMjUzNDUiIHN0cm9rZT0iI2QyNTM0NSIvPgo8dGV4dCB4PSI4MzAiIHk9IjQzNyIgZm9udC1zaXplPSIyMiIgZmlsbD0iI2QyNTM0NSIgZm9udC13ZWlnaHQ9IjcwMCI+NjAuOSU8L3RleHQ+CjxyZWN0IHg9IjM4IiB5PSI0OTAiIHdpZHRoPSI4ODQiIGhlaWdodD0iMjQ2IiByeD0iMTQiIGZpbGw9IndoaXRlIiBzdHJva2U9IiNkYmUzZWQiLz4KPHRleHQgeD0iNjAiIHk9IjUyNCIgZm9udC1zaXplPSIyMyIgZmlsbD0iIzAwN2Y4MiIgZm9udC13ZWlnaHQ9IjcwMCI+QiAgTUFUSC01MDDvvJrlj6bkuIDkuKrmtYvor5Xpm4blkIg8L3RleHQ+Cjx0ZXh0IHg9IjYwIiB5PSI1NTUiIGZvbnQtc2l6ZT0iMjAiIGZpbGw9IiMxNzJiNGQiIGZvbnQtd2VpZ2h0PSI2MDAiPjIwMjUtMDEgIERlZXBTZWVrLVIxPC90ZXh0Pgo8dGV4dCB4PSI2MCIgeT0iNTgxIiBmb250LXNpemU9IjE4IiBmaWxsPSIjNTI2NTdiIiBmb250LXdlaWdodD0iNDAwIj7orrrmlofmiqXlkYogcGFzc0AxPC90ZXh0Pgo8cmVjdCB4PSI2MCIgeT0iNTk0IiB3aWR0aD0iNzUyIiBoZWlnaHQ9IjEzIiByeD0iNiIgZmlsbD0iI2U0ZWFmMSIgc3Ryb2tlPSIjZTRlYWYxIi8+CjxyZWN0IHg9IjYwIiB5PSI1OTQiIHdpZHRoPSI3MzEuNjk1OTk5OTk5OTk5OSIgaGVpZ2h0PSIxMyIgcng9IjYiIGZpbGw9IiMwMDdmODIiIHN0cm9rZT0iIzAwN2Y4MiIvPgo8dGV4dCB4PSI4MzAiIHk9IjYwNyIgZm9udC1zaXplPSIyMiIgZmlsbD0iIzAwN2Y4MiIgZm9udC13ZWlnaHQ9IjcwMCI+OTcuMyU8L3RleHQ+Cjx0ZXh0IHg9IjYwIiB5PSI2NTEiIGZvbnQtc2l6ZT0iMjAiIGZpbGw9IiMxNzJiNGQiIGZvbnQtd2VpZ2h0PSI2MDAiPjIwMjYtMDggIEFBIOamnOWNleW/q+eFp++8mkdQVC01IGhpZ2g8L3RleHQ+Cjx0ZXh0IHg9IjYwIiB5PSI2NzciIGZvbnQtc2l6ZT0iMTgiIGZpbGw9IiM1MjY1N2IiIGZvbnQtd2VpZ2h0PSI0MDAiPueLrOeri+ivhOa1i++8m+atpOaXpeacn+S4jeaYr+aooeWei+WPkeW4g+aXpeacnzwvdGV4dD4KPHJlY3QgeD0iNjAiIHk9IjY5MCIgd2lkdGg9Ijc1MiIgaGVpZ2h0PSIxMyIgcng9IjYiIGZpbGw9IiNlNGVhZjEiIHN0cm9rZT0iI2U0ZWFmMSIvPgo8cmVjdCB4PSI2MCIgeT0iNjkwIiB3aWR0aD0iNzQ3LjQ4OCIgaGVpZ2h0PSIxMyIgcng9IjYiIGZpbGw9IiMwMDdmODIiIHN0cm9rZT0iIzAwN2Y4MiIvPgo8dGV4dCB4PSI4MzAiIHk9IjcwMyIgZm9udC1zaXplPSIyMiIgZmlsbD0iIzAwN2Y4MiIgZm9udC13ZWlnaHQ9IjcwMCI+OTkuNCU8L3RleHQ+CjxyZWN0IHg9IjM4IiB5PSI3NTYiIHdpZHRoPSI4ODQiIGhlaWdodD0iNTM0IiByeD0iMTQiIGZpbGw9IndoaXRlIiBzdHJva2U9IiNkYmUzZWQiLz4KPHRleHQgeD0iNjAiIHk9Ijc5MCIgZm9udC1zaXplPSIyMyIgZmlsbD0iIzMwNjZiZSIgZm9udC13ZWlnaHQ9IjcwMCI+QyAgbWluaUYyRi10ZXN077ya5LiN5ZCM5pCc57Si6aKE566XPC90ZXh0Pgo8dGV4dCB4PSI2MCIgeT0iODIxIiBmb250LXNpemU9IjIwIiBmaWxsPSIjMTcyYjRkIiBmb250LXdlaWdodD0iNjAwIj4yMDIxLzIwMjIgIOWOn+iuuuaWhyBMZWFuIEdQVC1mPC90ZXh0Pgo8dGV4dCB4PSI2MCIgeT0iODQ3IiBmb250LXNpemU9IjE4IiBmaWxsPSIjNTI2NTdiIiBmb250LXdlaWdodD0iNDAwIj5QYXNzQDjvvJtQYXNzQDEg5Li6IDI0LjYlPC90ZXh0Pgo8cmVjdCB4PSI2MCIgeT0iODYwIiB3aWR0aD0iNzUyIiBoZWlnaHQ9IjEzIiByeD0iNiIgZmlsbD0iI2U0ZWFmMSIgc3Ryb2tlPSIjZTRlYWYxIi8+CjxyZWN0IHg9IjYwIiB5PSI4NjAiIHdpZHRoPSIyMTkuNTgzOTk5OTk5OTk5OTciIGhlaWdodD0iMTMiIHJ4PSI2IiBmaWxsPSIjMzA2NmJlIiBzdHJva2U9IiMzMDY2YmUiLz4KPHRleHQgeD0iODMwIiB5PSI4NzMiIGZvbnQtc2l6ZT0iMjIiIGZpbGw9IiMzMDY2YmUiIGZvbnQtd2VpZ2h0PSI3MDAiPjI5LjIlPC90ZXh0Pgo8dGV4dCB4PSI2MCIgeT0iOTE3IiBmb250LXNpemU9IjIwIiBmaWxsPSIjMTcyYjRkIiBmb250LXdlaWdodD0iNjAwIj4yMDIyLTA1ICBIVFBTIC8gRXZhcmlzdGU8L3RleHQ+Cjx0ZXh0IHg9IjYwIiB5PSI5NDMiIGZvbnQtc2l6ZT0iMTgiIGZpbGw9IiM1MjY1N2IiIGZvbnQtd2VpZ2h0PSI0MDAiPnBhc3NANjQ8L3RleHQ+CjxyZWN0IHg9IjYwIiB5PSI5NTYiIHdpZHRoPSI3NTIiIGhlaWdodD0iMTMiIHJ4PSI2IiBmaWxsPSIjZTRlYWYxIiBzdHJva2U9IiNlNGVhZjEiLz4KPHJlY3QgeD0iNjAiIHk9Ijk1NiIgd2lkdGg9IjMwOC4zMiIgaGVpZ2h0PSIxMyIgcng9IjYiIGZpbGw9IiMzMDY2YmUiIHN0cm9rZT0iIzMwNjZiZSIvPgo8dGV4dCB4PSI4MzAiIHk9Ijk2OSIgZm9udC1zaXplPSIyMiIgZmlsbD0iIzMwNjZiZSIgZm9udC13ZWlnaHQ9IjcwMCI+NDEuMCU8L3RleHQ+Cjx0ZXh0IHg9IjYwIiB5PSIxMDEzIiBmb250LXNpemU9IjIwIiBmaWxsPSIjMTcyYjRkIiBmb250LXdlaWdodD0iNjAwIj4yMDI0LTA4ICBEZWVwU2Vlay1Qcm92ZXItVjEuNTwvdGV4dD4KPHRleHQgeD0iNjAiIHk9IjEwMzkiIGZvbnQtc2l6ZT0iMTgiIGZpbGw9IiM1MjY1N2IiIGZvbnQtd2VpZ2h0PSI0MDAiPlJMICsgUk1heFRTIOaQnOe0oumFjee9rjwvdGV4dD4KPHJlY3QgeD0iNjAiIHk9IjEwNTIiIHdpZHRoPSI3NTIiIGhlaWdodD0iMTMiIHJ4PSI2IiBmaWxsPSIjZTRlYWYxIiBzdHJva2U9IiNlNGVhZjEiLz4KPHJlY3QgeD0iNjAiIHk9IjEwNTIiIHdpZHRoPSI0NzcuNTIiIGhlaWdodD0iMTMiIHJ4PSI2IiBmaWxsPSIjMzA2NmJlIiBzdHJva2U9IiMzMDY2YmUiLz4KPHRleHQgeD0iODMwIiB5PSIxMDY1IiBmb250LXNpemU9IjIyIiBmaWxsPSIjMzA2NmJlIiBmb250LXdlaWdodD0iNzAwIj42My41JTwvdGV4dD4KPHRleHQgeD0iNjAiIHk9IjExMDkiIGZvbnQtc2l6ZT0iMjAiIGZpbGw9IiMxNzJiNGQiIGZvbnQtd2VpZ2h0PSI2MDAiPjIwMjUtMDQgIERlZXBTZWVrLVByb3Zlci1WMjwvdGV4dD4KPHRleHQgeD0iNjAiIHk9IjExMzUiIGZvbnQtc2l6ZT0iMTgiIGZpbGw9IiM1MjY1N2IiIGZvbnQtd2VpZ2h0PSI0MDAiPuS9nOiAheaKpeWRiueahOaVtOS9k+ivgeaYjua1geeoi+mAmui/h+eOhzwvdGV4dD4KPHJlY3QgeD0iNjAiIHk9IjExNDgiIHdpZHRoPSI3NTIiIGhlaWdodD0iMTMiIHJ4PSI2IiBmaWxsPSIjZTRlYWYxIiBzdHJva2U9IiNlNGVhZjEiLz4KPHJlY3QgeD0iNjAiIHk9IjExNDgiIHdpZHRoPSI2NjguNTI4IiBoZWlnaHQ9IjEzIiByeD0iNiIgZmlsbD0iIzMwNjZiZSIgc3Ryb2tlPSIjMzA2NmJlIi8+Cjx0ZXh0IHg9IjgzMCIgeT0iMTE2MSIgZm9udC1zaXplPSIyMiIgZmlsbD0iIzMwNjZiZSIgZm9udC13ZWlnaHQ9IjcwMCI+ODguOSU8L3RleHQ+Cjx0ZXh0IHg9IjYwIiB5PSIxMjA1IiBmb250LXNpemU9IjIwIiBmaWxsPSIjMTcyYjRkIiBmb250LXdlaWdodD0iNjAwIj4yMDI1LTA4ICBHb2VkZWwtUHJvdmVyLVYyLTMyQjwvdGV4dD4KPHRleHQgeD0iNjAiIHk9IjEyMzEiIGZvbnQtc2l6ZT0iMTgiIGZpbGw9IiM1MjY1N2IiIGZvbnQtd2VpZ2h0PSI0MDAiPlBhc3NAMzIgKyBzZWxmLWNvcnJlY3Rpb248L3RleHQ+CjxyZWN0IHg9IjYwIiB5PSIxMjQ0IiB3aWR0aD0iNzUyIiBoZWlnaHQ9IjEzIiByeD0iNiIgZmlsbD0iI2U0ZWFmMSIgc3Ryb2tlPSIjZTRlYWYxIi8+CjxyZWN0IHg9IjYwIiB5PSIxMjQ0IiB3aWR0aD0iNjc5LjgwOCIgaGVpZ2h0PSIxMyIgcng9IjYiIGZpbGw9IiMzMDY2YmUiIHN0cm9rZT0iIzMwNjZiZSIvPgo8dGV4dCB4PSI4MzAiIHk9IjEyNTciIGZvbnQtc2l6ZT0iMjIiIGZpbGw9IiMzMDY2YmUiIGZvbnQtd2VpZ2h0PSI3MDAiPjkwLjQlPC90ZXh0Pgo8dGV4dCB4PSI0MCIgeT0iMTM1MCIgZm9udC1zaXplPSIxOSIgZmlsbD0iIzUyNjU3YiIgZm9udC13ZWlnaHQ9IjQwMCI+5p2l5rqQ77ya5Y6f6K665paH44CB5L2c6ICF5oql5ZGK5LiOIEFydGlmaWNpYWwgQW5hbHlzaXPvvJvmraPmlofpgJDpobnliJflh7rmjIfmoIflkozmnaXmupDjgII8L3RleHQ+Cjx0ZXh0IHg9IjQwIiB5PSIxMzgwIiBmb250LXNpemU9IjE5IiBmaWxsPSIjNTI2NTdiIiBmb250LXdlaWdodD0iNDAwIj7lrozmlbQgTUFUSCDiiaAgTUFUSC01MDDvvJtwYXNzQDEg4omgIHBhc3NAa++8m+aguOmqjOmAmui/h+S5n+S4jeiHquWKqOivgeaYjumimOaEj+W/oOWunuOAgjwvdGV4dD4KPC9nPjwvc3ZnPg==" width="960" height="1410" class="img_ev3q"></p>
<p>图中将 <strong>完整 MATH 测试集、MATH-500 子集、miniF2F</strong> 分成三个面板；不同预算的分数不连成同口径增长曲线。它展示已报告的历史节点，不宣称是完整 SOTA 纪录。</p>
<p>第一条是 MATH / MATH-500 family：自然语言数学 benchmark 从“做不了”到“接近刷满”。</p>
<table><thead><tr><th>时间</th><th>模型/事件</th><th>指标</th><th style="text-align:right">分数</th></tr></thead><tbody><tr><td>2021-03</td><td>MATH 原论文 baseline</td><td>MATH accuracy</td><td style="text-align:right">3.0%-6.9%</td></tr><tr><td>2022-06</td><td>Minerva 540B</td><td>MATH single / majority vote</td><td style="text-align:right">33.6% / 50.3%</td></tr><tr><td>2024-02</td><td>DeepSeekMath-RL 7B</td><td>MATH top-1 / self-consistency 64</td><td style="text-align:right">51.7% / 60.9%</td></tr><tr><td>2025-01</td><td>DeepSeek-R1 / OpenAI o1-1217</td><td>MATH-500 pass@1</td><td style="text-align:right">97.3% / 96.4%</td></tr><tr><td>2026-08</td><td>Artificial Analysis snapshot</td><td>MATH-500 score</td><td style="text-align:right">GPT-5 high 99.4%, o3 99.2%</td></tr></tbody></table>
<p>这里不能把 full MATH 和 MATH-500 当作完全同口径比较。但作为一个 family，它非常清楚地说明：静态自然语言竞赛题一旦进入 CoT、majority vote、specialized pretraining、RL reasoning model 的主航道，就会快速从 frontier benchmark 变成 display benchmark。</p>
<p>第二条是 miniF2F：形式化证明 benchmark 从“proof code 很难写”到“被 proof search 和 verifier feedback 迅速推高”。</p>
<table><thead><tr><th>时间</th><th>模型/事件</th><th>指标</th><th style="text-align:right">分数</th></tr></thead><tbody><tr><td>2021-09 / 2022-02</td><td>miniF2F 原论文</td><td>miniF2F-test Pass@1 / Pass@8</td><td style="text-align:right">Lean GPT-f 24.6% / 29.2%；Metamath GPT-f 1.3% / 1.6%</td></tr><tr><td>2022-05</td><td>HyperTree Proof Search / Evariste</td><td>miniF2F-test pass@64</td><td style="text-align:right">41.0%</td></tr><tr><td>2024-08</td><td>DeepSeek-Prover-V1.5-RL + RMaxTS</td><td>miniF2F-test</td><td style="text-align:right">63.5%</td></tr><tr><td>2025-04</td><td>DeepSeek-Prover-V2-671B</td><td>miniF2F-test pass ratio</td><td style="text-align:right">88.9%</td></tr><tr><td>2025-08</td><td>Goedel-Prover-V2-32B</td><td>miniF2F Pass@32 / self-correction</td><td style="text-align:right">88.0% / 90.4%</td></tr></tbody></table>
<p>miniF2F 的 caveat 更大：Pass@1、Pass@32、Pass@64、Pass@1024 不是同一回事；Lean 版本、statement 版本、timeout、proof search budget、是否有 self-correction 都会影响分数。因此，miniF2F 的“被刷高”更准确的解释不是“形式化数学解决了”，而是“固定小 benchmark 在强 search + verifier + data synthesis 下不再足够区分”。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="数据集内容如何反推研究重点">数据集内容如何反推研究重点<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#%E6%95%B0%E6%8D%AE%E9%9B%86%E5%86%85%E5%AE%B9%E5%A6%82%E4%BD%95%E5%8F%8D%E6%8E%A8%E7%A0%94%E7%A9%B6%E9%87%8D%E7%82%B9" class="hash-link" aria-label="数据集内容如何反推研究重点的直接链接" title="数据集内容如何反推研究重点的直接链接" translate="no">​</a></h2>
<p>如果把这些 benchmark 的内容拆开，会看到 AI4Math 研究重点至少发生了六次迁移。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="从答案监督到标准解监督">从答案监督到标准解监督<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#%E4%BB%8E%E7%AD%94%E6%A1%88%E7%9B%91%E7%9D%A3%E5%88%B0%E6%A0%87%E5%87%86%E8%A7%A3%E7%9B%91%E7%9D%A3" class="hash-link" aria-label="从答案监督到标准解监督的直接链接" title="从答案监督到标准解监督的直接链接" translate="no">​</a></h3>
<p>早期问题是：模型能不能给出正确答案？GSM8K 和 MATH 都是 answer-checkable，但它们加入了自然语言解法，让模型不只学答案，还学中间步骤。</p>
<p>这推动了 CoT、scratchpad、majority voting。模型开始被要求“先想，再答”。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="从标准解到过程监督">从标准解到过程监督<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#%E4%BB%8E%E6%A0%87%E5%87%86%E8%A7%A3%E5%88%B0%E8%BF%87%E7%A8%8B%E7%9B%91%E7%9D%A3" class="hash-link" aria-label="从标准解到过程监督的直接链接" title="从标准解到过程监督的直接链接" translate="no">​</a></h3>
<p>GSM8K 原论文的 verifier 用最终答案正确与否构造监督；<a href="https://arxiv.org/abs/2305.20050" target="_blank" rel="noopener noreferrer" class="">PRM800K</a> 则收集步骤级人工反馈。这是结果监督与过程监督的区别，不能把两者都称为逐步正确性标注。研究重点变成：哪一步错？如何给中间过程打分？reward model 是不是会奖励看起来像推理但其实错误的步骤？</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="从自然语言到可执行验证">从自然语言到可执行验证<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#%E4%BB%8E%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E5%88%B0%E5%8F%AF%E6%89%A7%E8%A1%8C%E9%AA%8C%E8%AF%81" class="hash-link" aria-label="从自然语言到可执行验证的直接链接" title="从自然语言到可执行验证的直接链接" translate="no">​</a></h3>
<p>代码 benchmark、APPS、HumanEval、LiveCodeBench 让数学推理进入 test cases；miniF2F、ProofNet、LeanDojo 让证明进入 proof kernel。共同点是：评测器变硬了，不再完全依赖人类读自然语言。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="从-benchmark-到训练数据工程">从 benchmark 到训练数据工程<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#%E4%BB%8E-benchmark-%E5%88%B0%E8%AE%AD%E7%BB%83%E6%95%B0%E6%8D%AE%E5%B7%A5%E7%A8%8B" class="hash-link" aria-label="从 benchmark 到训练数据工程的直接链接" title="从 benchmark 到训练数据工程的直接链接" translate="no">​</a></h3>
<p>OpenWebMath、Proof-Pile-2、DeepSeekMath、OpenThoughts、Big-Math、DeepMath-103K、CrystalMath 说明数据集不只是评测模型，也直接塑造模型。数据字段开始关心 source、dedup、difficulty、teacher、rollout、verifier result、decontamination。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="从固定题库到-live--去污染">从固定题库到 live / 去污染<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#%E4%BB%8E%E5%9B%BA%E5%AE%9A%E9%A2%98%E5%BA%93%E5%88%B0-live--%E5%8E%BB%E6%B1%A1%E6%9F%93" class="hash-link" aria-label="从固定题库到 live / 去污染的直接链接" title="从固定题库到 live / 去污染的直接链接" translate="no">​</a></h3>
<p>MATH 和 GSM8K 公开并长期使用后，训练测试重叠的风险越来越值得审计；高分本身并不能证明污染。MathArena、LiveCodeBench、AIME fresh evaluation、GSM-SEM、DynaSolidGeo 的共同点是让题目随时间更新，或者让题目通过 semantic variant / dynamic generation 抵抗 memorization。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="从解题到可复用-proof-artifact">从解题到可复用 proof artifact<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#%E4%BB%8E%E8%A7%A3%E9%A2%98%E5%88%B0%E5%8F%AF%E5%A4%8D%E7%94%A8-proof-artifact" class="hash-link" aria-label="从解题到可复用 proof artifact的直接链接" title="从解题到可复用 proof artifact的直接链接" translate="no">​</a></h3>
<p>LeanEval、PutnamBench、FormalMATH、Compfiles、MathOlympiadBench 把“做对一道题”变成“产出可验证、可复现、可进入库的 artifact”。这一步最接近真实数学研究，因为数学的目标不是一次性答题，而是可积累的可靠知识。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="hugging-face-trending-给出的外部信号">Hugging Face trending 给出的外部信号<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#hugging-face-trending-%E7%BB%99%E5%87%BA%E7%9A%84%E5%A4%96%E9%83%A8%E4%BF%A1%E5%8F%B7" class="hash-link" aria-label="Hugging Face trending 给出的外部信号的直接链接" title="Hugging Face trending 给出的外部信号的直接链接" translate="no">​</a></h2>
<p><a href="https://huggingface.co/papers/trending" target="_blank" rel="noopener noreferrer" class="">Hugging Face Papers Trending</a> 可作为新论文发现入口，但综合热榜不是 AI for Math 的领域统计。本文 2026-08-29 快照中的 agent、verifier 与评测框架论文只能提供邻近方向线索，不能据此断言数学研究的主流占比。</p>
<p>更直接的证据来自本文介绍的数据与评测协议：</p>
<ul>
<li class="">agent 可以长时间搜索证明；</li>
<li class="">verifier 决定 reward 和筛选；</li>
<li class="">harness 决定 evaluation 是否可信；</li>
<li class="">document/math parsing 决定数学语料能不能保留公式结构；</li>
<li class="">live benchmark 决定模型是否只是记住旧题。</li>
</ul>
<p>所以现在看 AI4Math，不能只看数学题库，还要看它背后的工具链：Lean server、compiler feedback、retrieval、answer parser、rollout budget、submission workflow、benchmark versioning。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="当前前沿到底在做什么">当前前沿到底在做什么<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#%E5%BD%93%E5%89%8D%E5%89%8D%E6%B2%BF%E5%88%B0%E5%BA%95%E5%9C%A8%E5%81%9A%E4%BB%80%E4%B9%88" class="hash-link" aria-label="当前前沿到底在做什么的直接链接" title="当前前沿到底在做什么的直接链接" translate="no">​</a></h2>
<p>我会把 2025-2026 的主流工作压成七个方向。</p>
<ol>
<li class=""><strong>Live / uncontaminated evaluation。</strong> 代表是 MathArena、LiveCodeBench、AIME/USAMO fresh evaluation。目标是解决固定题库污染和刷榜。</li>
<li class=""><strong>Research-level math。</strong> 代表是 FrontierMath、Riemann-Bench、Soohak。目标是把难度从考试题推向专家原创和研究级问题。</li>
<li class=""><strong>Formal theorem proving。</strong> 代表是 miniF2F 后的 FormalMATH、CombiBench、PutnamBench、MathOlympiadBench、LeanEval。目标是生成 machine-checkable proof artifact。</li>
<li class=""><strong>Verifier / reward data。</strong> 代表是 DeepMath-103K、Big-Math、CrystalMath、FormalRewardBench、AIME CoT Verification。目标是把数学变成 RLVR 环境。</li>
<li class=""><strong>Benchmark audit。</strong> 代表是 miniF2F-Lean Revisited、Faults in Formal Benchmarking、GSM-SEM。目标是发现 benchmark statement defects、过度简化、语义不匹配和 evaluation loopholes。</li>
<li class=""><strong>Multimodal and geometry。</strong> 代表是 MathVista、MathVerse、SolidGeo、DynaSolidGeo、MathNet、Math-Vision Diagrams。目标是处理图形、空间、几何构造和视觉依赖。</li>
<li class=""><strong>Code-as-math。</strong> 代表是 CodeContests、LiveCodeBench、AetherCode、OJBench、OIBench、rStar-Coder。目标是用测试、执行和竞赛编程把推理变成可验证程序。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="这条时间线给我们的启发">这条时间线给我们的启发<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#%E8%BF%99%E6%9D%A1%E6%97%B6%E9%97%B4%E7%BA%BF%E7%BB%99%E6%88%91%E4%BB%AC%E7%9A%84%E5%90%AF%E5%8F%91" class="hash-link" aria-label="这条时间线给我们的启发的直接链接" title="这条时间线给我们的启发的直接链接" translate="no">​</a></h2>
<p>第一，多个经典 benchmark 对最强系统的区分度正在下降。GSM8K 和 MATH 仍然重要，但它们已经从 frontier benchmark 变成基础能力检查。MATH-500 接近 99% 后，再报一个 MATH 分数已经说明不了太多。</p>
<p>第二，分数越来越依赖 evaluation protocol。尤其是 miniF2F、PutnamBench、LeanEval：Pass@1 和 Pass@1024 不是同一个能力；单次生成、tree search、self-correction、human-in-the-loop、compiler feedback 的边界必须写清楚。</p>
<p>第三，前沿数学能力越来越像系统能力。模型权重只是其中一部分，真正决定效果的是：数据、检索、工具、verifier、proof assistant、budget、版本控制、submission rules、人工审计。</p>
<p>第四，形式化是 AI4Math 的一条重要可信出口，而不是唯一出口。Lean/Coq/Isabelle 证明与 Comparator 回放加强了机器验证；自然语言研究仍依赖专家审查。两者都还需要检查题意建模、所用假设和结论的新颖性。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一个简短的路线图">一个简短的路线图<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#%E4%B8%80%E4%B8%AA%E7%AE%80%E7%9F%AD%E7%9A%84%E8%B7%AF%E7%BA%BF%E5%9B%BE" class="hash-link" aria-label="一个简短的路线图的直接链接" title="一个简短的路线图的直接链接" translate="no">​</a></h2>
<p>如果要继续追 AI4Math，我会按下面的顺序读：</p>
<ol>
<li class="">GSM8K 和 MATH：理解自然语言数学 benchmark 为什么成立，又为什么会被刷满。</li>
<li class="">miniF2F 和 LeanDojo：理解 proof assistant 为什么改变评价方式。</li>
<li class="">Compfiles、MathOlympiadBench、PutnamBench：理解竞赛级 formalization 如何变成库资产。</li>
<li class="">FrontierMath、MathArena、LiveCodeBench：理解去污染和 live evaluation。</li>
<li class="">FormalMATH、CombiBench、MathlibLemma、LeanEval v1：理解 2025-2026 的 formal benchmark ecology。</li>
<li class="">DeepMath-103K、Big-Math、CrystalMath、FormalRewardBench：理解 RLVR 为什么成为训练范式。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="资料索引">资料索引<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#%E8%B5%84%E6%96%99%E7%B4%A2%E5%BC%95" class="hash-link" aria-label="资料索引的直接链接" title="资料索引的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://arxiv.org/abs/2110.14168" target="_blank" rel="noopener noreferrer" class="">GSM8K: Training Verifiers to Solve Math Word Problems</a></li>
<li class=""><a href="https://arxiv.org/abs/2103.03874" target="_blank" rel="noopener noreferrer" class="">MATH: Measuring Mathematical Problem Solving With the MATH Dataset</a></li>
<li class=""><a href="https://arxiv.org/abs/2206.14858" target="_blank" rel="noopener noreferrer" class="">Minerva: Solving Quantitative Reasoning Problems with Language Models</a></li>
<li class=""><a href="https://arxiv.org/abs/2402.03300" target="_blank" rel="noopener noreferrer" class="">DeepSeekMath</a></li>
<li class=""><a href="https://arxiv.org/abs/2501.12948" target="_blank" rel="noopener noreferrer" class="">DeepSeek-R1</a></li>
<li class=""><a href="https://artificialanalysis.ai/evaluations/math-500" target="_blank" rel="noopener noreferrer" class="">MATH-500 Artificial Analysis snapshot</a></li>
<li class=""><a href="https://arxiv.org/abs/2109.00110" target="_blank" rel="noopener noreferrer" class="">miniF2F</a></li>
<li class=""><a href="https://arxiv.org/abs/2205.11491" target="_blank" rel="noopener noreferrer" class="">HyperTree Proof Search</a></li>
<li class=""><a href="https://arxiv.org/abs/2408.08152" target="_blank" rel="noopener noreferrer" class="">DeepSeek-Prover-V1.5</a></li>
<li class=""><a href="https://github.com/deepseek-ai/DeepSeek-Prover-V2" target="_blank" rel="noopener noreferrer" class="">DeepSeek-Prover-V2</a></li>
<li class=""><a href="https://github.com/Goedel-LM/Goedel-Prover-V2" target="_blank" rel="noopener noreferrer" class="">Goedel-Prover-V2 and MathOlympiadBench</a></li>
<li class=""><a href="https://dwrensha.github.io/compfiles/" target="_blank" rel="noopener noreferrer" class="">Compfiles</a></li>
<li class=""><a href="https://huggingface.co/datasets/Goedel-LM/MathOlympiadBench" target="_blank" rel="noopener noreferrer" class="">MathOlympiadBench</a></li>
<li class=""><a href="https://arxiv.org/abs/2407.11214" target="_blank" rel="noopener noreferrer" class="">PutnamBench</a></li>
<li class=""><a href="https://trishullab.github.io/PutnamBench/leaderboard.html" target="_blank" rel="noopener noreferrer" class="">PutnamBench leaderboard</a></li>
<li class=""><a href="https://arxiv.org/abs/2411.04872" target="_blank" rel="noopener noreferrer" class="">FrontierMath</a></li>
<li class=""><a href="https://epoch.ai/frontiermath" target="_blank" rel="noopener noreferrer" class="">Epoch AI FrontierMath</a></li>
<li class=""><a href="https://lean-lang.org/eval/" target="_blank" rel="noopener noreferrer" class="">Lean Eval</a></li>
<li class=""><a href="https://github.com/leanprover/lean-eval" target="_blank" rel="noopener noreferrer" class="">leanprover/lean-eval</a></li>
<li class=""><a href="https://huggingface.co/papers/trending" target="_blank" rel="noopener noreferrer" class="">Hugging Face Papers Trending</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="最后">最后<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/ai4math-dataset-benchmark-evolution#%E6%9C%80%E5%90%8E" class="hash-link" aria-label="最后的直接链接" title="最后的直接链接" translate="no">​</a></h2>
<p>从 GSM8K 到 LeanEval，AI4Math 的变化不是一条简单的“分数越来越高”曲线，而是一条不断加验证层的路线。</p>
<p>先是答案可检查，然后是过程可解释，再是证明可编译，最后是提交可复现、评测可更新、历史可追踪。</p>
<p>数学给 AI 提供了最好的训练场之一，因为它既需要创造性，又有硬验证边界。未来真正重要的系统，可能不是只会在旧 benchmark 上拿 99 分的模型，而是能在新的数学环境里提出候选、调用工具、接受失败、修正证明、留下可验证 artifact 的研究伙伴。</p>
<p>这也是为什么 AI4Math 的数据集史，最终会走向形式化、live eval 和 verified discovery。</p>]]></content>
        <category label="ai-math" term="ai-math"/>
        <category label="benchmark" term="benchmark"/>
        <category label="dataset" term="dataset"/>
        <category label="lean" term="lean"/>
        <category label="theorem-proving" term="theorem-proving"/>
        <category label="reasoning" term="reasoning"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[AI 研究狂飙的时代，为什么形式化会变得更重要]]></title>
        <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog/formalization-ai-research-era</id>
        <link href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/formalization-ai-research-era"/>
        <updated>2026-08-29T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[当 AI 生成想法、代码和证明的速度越来越快，形式化不再只是数学家的洁癖，而会成为验证、复用和协作的基础设施：让机器生成的推理有一个可以被机器严格检查的落点。]]></summary>
        <content type="html"><![CDATA[<p>这几年 AI 研究的节奏明显变了：模型能力、工具链、论文、开源项目、benchmark 和应用场景都在高速迭代。很多过去需要专家慢慢写、慢慢查的东西，现在可以由模型在几分钟内生成一个看起来很像样的版本：代码、实验计划、定理证明草稿、综述、数据分析、系统设计，甚至新的 conjecture。</p>
<p>这当然是巨大的生产力提升。但它也带来一个更尖锐的问题：<strong>当生成速度远远超过人工验证速度时，我们到底靠什么维持可信度？</strong></p>
<p>形式化的重要性，正是在这个背景下突然变得现实起来。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>一句话结论</div><div class="admonitionContent_BuS1"><p>AI 让“产生候选答案”变得便宜，形式化让“确认答案真的成立”变得可机械检查。未来很多关键知识工作，会越来越依赖这两者的组合：AI 负责搜索和生成，形式化系统负责约束和验收。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="什么是形式化">什么是形式化<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/formalization-ai-research-era#%E4%BB%80%E4%B9%88%E6%98%AF%E5%BD%A2%E5%BC%8F%E5%8C%96" class="hash-link" aria-label="什么是形式化的直接链接" title="什么是形式化的直接链接" translate="no">​</a></h2>
<p>形式化并不是把数学或程序写得更“像代码”而已。它的核心是：把一个命题、规范或证明，写进一个有精确定义的逻辑系统里，让计算机可以逐步检查它是否真的成立。</p>
<p>以 Lean 为例，我们可以把自然数、函数、群、环、拓扑空间、测度、范畴等对象都定义在系统中，再把证明写成 Lean 可以检查的项。Lean 的 kernel 不会因为一句“显然”就放行，也不会因为作者很有名就默认正确。它只做一件事：检查这个证明项是否真的具有目标命题对应的类型。</p>
<p>所以形式化证明的意义不是“让电脑相信人类”，而是把证明变成一个可以被独立检查、可以被复用、可以被组合的 artifact。</p>
<p>传统论文证明是面向人类阅读的文本；形式化证明则更像面向机器和人类共同使用的知识对象。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ai-时代的问题不是没有答案而是答案太多">AI 时代的问题不是没有答案，而是答案太多<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/formalization-ai-research-era#ai-%E6%97%B6%E4%BB%A3%E7%9A%84%E9%97%AE%E9%A2%98%E4%B8%8D%E6%98%AF%E6%B2%A1%E6%9C%89%E7%AD%94%E6%A1%88%E8%80%8C%E6%98%AF%E7%AD%94%E6%A1%88%E5%A4%AA%E5%A4%9A" class="hash-link" aria-label="AI 时代的问题不是没有答案，而是答案太多的直接链接" title="AI 时代的问题不是没有答案，而是答案太多的直接链接" translate="no">​</a></h2>
<p>在 AI 辅助研究之前，很多领域的瓶颈是“想不出方案”或“写不出草稿”。现在情况正在变化：模型可以一次给出十几个证明路线、几十个代码实现、上百个实验变体。问题开始从生成侧转移到验证侧。</p>
<p>这会产生几个典型风险：</p>
<ol>
<li class=""><strong>幻觉变得更难发现。</strong> 模型生成的文本越流畅，越容易把缺失条件、偷换定义、错误引用藏在顺滑叙述里。</li>
<li class=""><strong>局部正确不等于整体正确。</strong> 一个证明片段看起来没问题，一个程序函数测试通过，都不能保证完整系统满足目标性质。</li>
<li class=""><strong>研究链条越来越长。</strong> AI 生成的中间结论如果没有可靠记录和检查，后续工作会在不稳固的地基上继续堆高。</li>
<li class=""><strong>人工 review 速度跟不上。</strong> 人可以抽查，但很难持续审完机器每天生成的大量候选产物。</li>
</ol>
<p>这不是说 AI 不可靠，所以不能用。恰恰相反：AI 越有用，我们越需要一种机制，把它生成的结果接到可靠的验收层上。</p>
<p>形式化就是这个验收层的重要候选。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="形式化把看起来对变成检查通过">形式化把“看起来对”变成“检查通过”<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/formalization-ai-research-era#%E5%BD%A2%E5%BC%8F%E5%8C%96%E6%8A%8A%E7%9C%8B%E8%B5%B7%E6%9D%A5%E5%AF%B9%E5%8F%98%E6%88%90%E6%A3%80%E6%9F%A5%E9%80%9A%E8%BF%87" class="hash-link" aria-label="形式化把“看起来对”变成“检查通过”的直接链接" title="形式化把“看起来对”变成“检查通过”的直接链接" translate="no">​</a></h2>
<p>人类推理里有大量隐含上下文。数学家写“由紧性可得”，通常默认读者知道使用的是哪个紧性定理、空间满足哪些条件、映射在哪个拓扑下连续。软件工程师写“这个状态不会出现”，通常也默认了某些调用顺序、输入约束和并发假设。</p>
<p>AI 很擅长模仿这种写法，但它不一定真的持有那些隐含条件。</p>
<p>形式化系统会把这些隐含条件全部逼出来：</p>
<ul>
<li class="">定义必须明确；</li>
<li class="">前提必须列出；</li>
<li class="">类型必须匹配；</li>
<li class="">引理必须已经证明；</li>
<li class="">每一步推理必须能被 kernel 检查。</li>
</ul>
<p>这会让人一开始觉得繁琐，因为许多“人类觉得显然”的地方都要补全。但在 AI 高速生成的场景里，这种繁琐反而是价值所在：它把模糊的自然语言说法，转化成能被机器反复检查的精确结构。</p>
<p>可以说，形式化不是为了降低表达成本，而是为了降低长期信任成本。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="lean-的角色数学知识的编译器">Lean 的角色：数学知识的编译器<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/formalization-ai-research-era#lean-%E7%9A%84%E8%A7%92%E8%89%B2%E6%95%B0%E5%AD%A6%E7%9F%A5%E8%AF%86%E7%9A%84%E7%BC%96%E8%AF%91%E5%99%A8" class="hash-link" aria-label="Lean 的角色：数学知识的编译器的直接链接" title="Lean 的角色：数学知识的编译器的直接链接" translate="no">​</a></h2>
<p>Lean 是今天最受关注的证明助手之一，尤其因为 <code>mathlib</code> 已经积累了大量现代数学基础库。它可以表达复杂的数学结构，也可以把定理、定义和证明之间的依赖关系组织成一个可搜索、可复用的库。</p>
<p>如果把传统数学论文看成“自然语言源码”，那么 Lean 形式化有点像给数学加上编译器：</p>
<ul>
<li class="">定义不一致，编译不过；</li>
<li class="">条件缺失，编译不过；</li>
<li class="">引用的定理不适用，编译不过；</li>
<li class="">证明跳步，编译不过；</li>
<li class="">依赖关系可以被系统追踪。</li>
</ul>
<p>这对 AI 特别关键。因为 AI 可以生成 Lean 代码，也可以尝试补证明；但最终能不能进库，不由模型自己说了算，而由 Lean 检查。</p>
<p>未来理想的工作流可能是这样的：</p>
<ol>
<li class="">人类提出问题、定义和研究目标；</li>
<li class="">AI 生成候选证明、相关引理和搜索方向；</li>
<li class="">Lean 检查每个候选 proof artifact；</li>
<li class="">通过检查的部分进入知识库；</li>
<li class="">新知识再被后续 AI 和人类复用。</li>
</ol>
<p>这个循环的关键不是“AI 是否聪明到永远不犯错”，而是“AI 犯错时能不能被低成本、系统性地拦下来”。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="形式化会改变-ai-for-math-的评价方式">形式化会改变 AI for Math 的评价方式<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/formalization-ai-research-era#%E5%BD%A2%E5%BC%8F%E5%8C%96%E4%BC%9A%E6%94%B9%E5%8F%98-ai-for-math-%E7%9A%84%E8%AF%84%E4%BB%B7%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="形式化会改变 AI for Math 的评价方式的直接链接" title="形式化会改变 AI for Math 的评价方式的直接链接" translate="no">​</a></h2>
<p>AI for Math 很容易被漂亮的 demo 吸引：模型解出一道题、生成一段证明、发现一个路线。但数学研究真正需要的是可积累的可靠知识，而不只是一次性答案。</p>
<p>形式化系统提供了一种更硬的评价方式：</p>
<ul>
<li class="">证明是否通过 checker；</li>
<li class="">是否使用了额外公理；</li>
<li class="">依赖了哪些库和引理；</li>
<li class="">是否能在新版本库中复现；</li>
<li class="">是否能被后续定理复用。</li>
</ul>
<p>这比“看起来像证明”强得多。它也让 benchmark 从自然语言评分，转向 proof artifact 评分。模型不只是要说服评测者，而是要产出一个可以被 kernel 接受的对象。</p>
<p>当然，checker 也不是神。形式系统本身、kernel 实现、库定义、问题建模都可能有边界和漏洞。但即便如此，形式化仍然把信任边界大幅收窄了：我们不再相信一整段自然语言推理，而是主要相信一个小得多的 kernel、清晰的定义和可审计的依赖链。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="不只是数学软件硬件和协议更需要它">不只是数学：软件、硬件和协议更需要它<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/formalization-ai-research-era#%E4%B8%8D%E5%8F%AA%E6%98%AF%E6%95%B0%E5%AD%A6%E8%BD%AF%E4%BB%B6%E7%A1%AC%E4%BB%B6%E5%92%8C%E5%8D%8F%E8%AE%AE%E6%9B%B4%E9%9C%80%E8%A6%81%E5%AE%83" class="hash-link" aria-label="不只是数学：软件、硬件和协议更需要它的直接链接" title="不只是数学：软件、硬件和协议更需要它的直接链接" translate="no">​</a></h2>
<p>形式化的价值不只在数学。AI 正在越来越多地写代码、改系统、生成配置、设计协议。普通测试只能说明“这些样例没坏”，但很多关键错误只会在极端状态、并发交错、安全边界或长期演化中出现。</p>
<p>形式化方法可以表达并检查更强的性质，例如：</p>
<ul>
<li class="">编译器优化是否保持程序语义；</li>
<li class="">加密协议是否满足认证和保密性质；</li>
<li class="">智能合约是否不会在某类调用下丢资产；</li>
<li class="">分布式系统是否满足一致性约束；</li>
<li class="">控制系统是否不会进入危险状态；</li>
<li class="">AI 生成的代码是否满足给定 specification。</li>
</ul>
<p>随着 coding agent 变得更强，软件生产会更快，也会更容易出现“看起来能跑，但没人真正理解完整行为”的系统。形式化规范和验证工具会成为抵消这种风险的基础设施。</p>
<p>未来的高可靠工程里，AI 可能负责写实现、补测试、找反例；形式化工具负责定义不可妥协的边界：哪些状态不允许发生，哪些性质必须保持，哪些优化不能改变语义。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="形式化也是一种知识组织方式">形式化也是一种知识组织方式<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/formalization-ai-research-era#%E5%BD%A2%E5%BC%8F%E5%8C%96%E4%B9%9F%E6%98%AF%E4%B8%80%E7%A7%8D%E7%9F%A5%E8%AF%86%E7%BB%84%E7%BB%87%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="形式化也是一种知识组织方式的直接链接" title="形式化也是一种知识组织方式的直接链接" translate="no">​</a></h2>
<p>还有一个常被低估的点：形式化不仅是在“证明正确”，也是在重新组织知识。</p>
<p>自然语言知识库很适合阅读，但不擅长自动组合。一个定理能不能用于另一个场景，往往要专家自己判断。形式化库则把定义、实例、类型类、定理依赖都结构化了。它让机器知道：这个对象是什么类型，满足哪些性质，可以调用哪些定理，还缺哪些前提。</p>
<p>这对 AI 很重要，因为模型需要的不只是更多文本，还需要更可靠的工具环境。一个大型形式化库相当于给 AI 提供了一个高精度的知识图谱和操作空间。模型可以在其中搜索、尝试、失败、修正，而不是只在自然语言相似性里游走。</p>
<p>换句话说，形式化让知识从“可读”进一步变成“可计算”。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="最大障碍成本和体验">最大障碍：成本和体验<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/formalization-ai-research-era#%E6%9C%80%E5%A4%A7%E9%9A%9C%E7%A2%8D%E6%88%90%E6%9C%AC%E5%92%8C%E4%BD%93%E9%AA%8C" class="hash-link" aria-label="最大障碍：成本和体验的直接链接" title="最大障碍：成本和体验的直接链接" translate="no">​</a></h2>
<p>如果形式化这么重要，为什么还没有成为主流？主要原因也很直接：成本高。</p>
<p>形式化一段数学证明，往往比写论文证明更费时。形式化一个工程系统，也需要提前定义 specification、抽象模型和不变量。对初学者来说，Lean、Coq、Isabelle、TLA+、Dafny、F* 等工具都有学习曲线。</p>
<p>这也是 AI 可能反过来帮助形式化的地方。过去形式化最贵的是细节劳动：找定理、补类型、处理繁琐的 rewrite、把人类证明拆成机器可接受的小步。AI 如果能承担其中一部分，就会显著降低形式化门槛。</p>
<p>所以 AI 和形式化不是对立关系。更可能出现的是互补：</p>
<ul>
<li class="">AI 降低形式化的书写成本；</li>
<li class="">形式化降低 AI 输出的信任成本；</li>
<li class="">AI 扩大可形式化的范围；</li>
<li class="">形式化筛选真正可靠的 AI 产物。</li>
</ul>
<p>这是一种正反馈。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="未来重要的不是全自动证明而是可信流水线">未来重要的不是“全自动证明”，而是“可信流水线”<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/formalization-ai-research-era#%E6%9C%AA%E6%9D%A5%E9%87%8D%E8%A6%81%E7%9A%84%E4%B8%8D%E6%98%AF%E5%85%A8%E8%87%AA%E5%8A%A8%E8%AF%81%E6%98%8E%E8%80%8C%E6%98%AF%E5%8F%AF%E4%BF%A1%E6%B5%81%E6%B0%B4%E7%BA%BF" class="hash-link" aria-label="未来重要的不是“全自动证明”，而是“可信流水线”的直接链接" title="未来重要的不是“全自动证明”，而是“可信流水线”的直接链接" translate="no">​</a></h2>
<p>很多讨论会把目标想成：AI 什么时候能完全自动证明所有定理？这个问题当然有趣，但可能不是最先改变世界的部分。</p>
<p>更现实、更重要的变化，是可信流水线的形成：</p>
<ul>
<li class="">需求用更清晰的 specification 表达；</li>
<li class="">AI 根据 specification 生成实现或证明；</li>
<li class="">工具自动检查类型、性质、反例和依赖；</li>
<li class="">人类审查抽象是否合理、目标是否值得、模型是否贴近现实；</li>
<li class="">通过检查的 artifact 被纳入可复用库。</li>
</ul>
<p>这条流水线不会消灭人类判断。相反，它会把人类从大量机械验证里释放出来，把注意力放回建模、抽象、问题选择和概念创造上。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="结语">结语<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/formalization-ai-research-era#%E7%BB%93%E8%AF%AD" class="hash-link" aria-label="结语的直接链接" title="结语的直接链接" translate="no">​</a></h2>
<p>在 AI 研究快速发展的时代，我们最不缺的是生成能力。模型会越来越会写，越来越会猜，越来越会组合已有知识。真正稀缺的是可验证性：哪些结果能被信任，哪些结论能被复用，哪些系统能在关键场景里承担责任。</p>
<p>形式化的重要性正在于此。</p>
<p>Lean 这样的证明助手，形式化验证这样的工程方法，不只是学术上的严谨爱好。它们可能会成为 AI 时代的基础设施：像编译器检查程序一样，检查证明、规范和推理；像类型系统约束代码一样，约束机器生成的知识；像版本库记录代码演化一样，记录可验证知识的积累过程。</p>
<p>AI 让我们更快地产生可能性。形式化帮助我们分辨哪些可能性真的站得住。</p>
<p>未来的关键不是在二者之间选一个，而是把它们接在一起。</p>]]></content>
        <category label="ai" term="ai"/>
        <category label="lean" term="lean"/>
        <category label="formal-verification" term="formal-verification"/>
        <category label="theorem-proving" term="theorem-proving"/>
        <category label="mathematics" term="mathematics"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[2025+ 开源语音克隆框架调研：从 IndexTTS、CosyVoice 到 Chatterbox]]></title>
        <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog/open-source-voice-cloning-frameworks-2025</id>
        <link href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/open-source-voice-cloning-frameworks-2025"/>
        <updated>2026-08-22T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[面向自用体验的 2025+ 开源语音克隆框架调研，比较 IndexTTS、CosyVoice、GPT-SoVITS、F5-TTS、Chatterbox、Fish Speech、Spark-TTS、Dia 等项目的 GitHub 星标、许可证、功能定位和试用建议。]]></summary>
        <content type="html"><![CDATA[<p>过去两年，语音克隆已经从早期的 Bark、Tortoise、VALL-E-X、XTTS-v2 时代，明显切换到“<strong>大模型 TTS + zero-shot/few-shot voice cloning</strong>”路线。现在主流体验不再只是“能不能模仿音色”，而是看它能不能稳定说长文本、能不能跨语言、能不能控制情绪和语速、能不能本地跑，以及有没有好用的 WebUI 或 Hugging Face demo。</p>
<p>如果是自用场景，许可证不一定是第一过滤条件。更实际的筛选标准是：<strong>效果体验、中文/多语能力、上手成本、长文本稳定性、是否适合长期做自己的声音库。</strong> 本文按 2025 年以后仍活跃或有新版路线的项目做一次集中梳理。</p>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>数据口径</div><div class="admonitionContent_BuS1"><p>本文的 GitHub Stars、许可证和活跃度快照来自 GitHub API，时间为 <strong>2026-08-22</strong>。Stars 会持续变化，许可证也可能因“代码许可证”和“模型权重许可证”不同而出现差异；下表优先写出对实际使用影响最大的口径。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="先区分两类问题tts-克隆和音频换声">先区分两类问题：TTS 克隆和音频换声<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/open-source-voice-cloning-frameworks-2025#%E5%85%88%E5%8C%BA%E5%88%86%E4%B8%A4%E7%B1%BB%E9%97%AE%E9%A2%98tts-%E5%85%8B%E9%9A%86%E5%92%8C%E9%9F%B3%E9%A2%91%E6%8D%A2%E5%A3%B0" class="hash-link" aria-label="先区分两类问题：TTS 克隆和音频换声的直接链接" title="先区分两类问题：TTS 克隆和音频换声的直接链接" translate="no">​</a></h2>
<p>“语音克隆”这个词经常混用，但实际至少分两类：</p>
<table><thead><tr><th>类型</th><th>输入</th><th>输出</th><th>典型项目</th><th>适合场景</th></tr></thead><tbody><tr><td>TTS voice cloning</td><td>文本 + 参考音频</td><td>用参考音色朗读新文本</td><td>IndexTTS、CosyVoice、GPT-SoVITS、F5-TTS、Chatterbox</td><td>配音、播客、个人语音助手、有声读物</td></tr><tr><td>Voice conversion</td><td>原始语音 + 目标音色</td><td>把已有录音转换成目标音色</td><td>RVC、Seed-VC</td><td>翻唱、已有录音换声、歌声转换</td></tr></tbody></table>
<p>本文主要讨论第一类：<strong>文字转语音的音色克隆</strong>。如果目标是“我已经有一段录音，想把它换成另一个人的声音”，那应该优先看 RVC/Seed-VC，而不是 TTS 克隆模型。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="候选项目总览">候选项目总览<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/open-source-voice-cloning-frameworks-2025#%E5%80%99%E9%80%89%E9%A1%B9%E7%9B%AE%E6%80%BB%E8%A7%88" class="hash-link" aria-label="候选项目总览的直接链接" title="候选项目总览的直接链接" translate="no">​</a></h2>
<p>下面这些项目是本轮更值得优先体验的候选。排序不是绝对质量排名，而是综合了新鲜度、效果体验、中文可用性、Demo/本地上手和社区活跃度。</p>
<table><thead><tr><th>框架</th><th style="text-align:right">GitHub Stars</th><th>License</th><th>新鲜度</th><th>核心能力</th><th>最适合</th></tr></thead><tbody><tr><td><a href="https://github.com/RVC-Boss/GPT-SoVITS" target="_blank" rel="noopener noreferrer" class="">GPT-SoVITS</a></td><td style="text-align:right">61.1k</td><td>MIT</td><td>2024 起，持续活跃</td><td>5 秒 zero-shot、1 分钟 few-shot、WebUI、数据处理链路完整</td><td>中文/日文/英文少样本克隆，长期做固定声音库</td></tr><tr><td><a href="https://github.com/fishaudio/fish-speech" target="_blank" rel="noopener noreferrer" class="">Fish Speech</a></td><td style="text-align:right">32.3k</td><td>Fish Audio Research License</td><td>2025+ 活跃</td><td>多语 TTS、情绪 tag、短参考音频克隆、服务端推理</td><td>追求表现力、角色化、情绪化声音</td></tr><tr><td><a href="https://github.com/resemble-ai/chatterbox" target="_blank" rel="noopener noreferrer" class="">Chatterbox</a></td><td style="text-align:right">26.1k</td><td>MIT</td><td>2025+ 活跃</td><td>多语 V3、Turbo、Nano、paralinguistic tags、内置水印</td><td>英文/多语 voice agent、自然聊天式 TTS</td></tr><tr><td><a href="https://github.com/index-tts/index-tts" target="_blank" rel="noopener noreferrer" class="">IndexTTS</a></td><td style="text-align:right">23.3k</td><td>bilibili Model Use License</td><td>2025+ 活跃</td><td>单条参考音频克隆，情绪/语速/发音控制，中英日西阿</td><td>中文可控配音、情绪克隆、发音精修</td></tr><tr><td><a href="https://github.com/FunAudioLLM/CosyVoice" target="_blank" rel="noopener noreferrer" class="">CosyVoice</a></td><td style="text-align:right">22.8k</td><td>Apache-2.0</td><td>CosyVoice2/3 路线持续更新</td><td>多语、方言、情绪、语速/音量指令、流式和部署支持</td><td>中文/多语综合 TTS、产品化自用服务</td></tr><tr><td><a href="https://github.com/nari-labs/dia" target="_blank" rel="noopener noreferrer" class="">Dia</a></td><td style="text-align:right">19.4k</td><td>Apache-2.0</td><td>2025+</td><td>英文对话 TTS、音频 prompt、笑声/咳嗽等非语言事件</td><td>英文播客、角色对话、双人对话感生成</td></tr><tr><td><a href="https://github.com/SWivid/F5-TTS" target="_blank" rel="noopener noreferrer" class="">F5-TTS</a></td><td style="text-align:right">15.1k</td><td>代码 MIT，权重 CC-BY-NC</td><td>2025 v1 以后持续活跃</td><td>Flow Matching TTS，高自然度 zero-shot，Demo 体验好</td><td>快速体验高自然度音色克隆</td></tr><tr><td><a href="https://github.com/SparkAudio/Spark-TTS" target="_blank" rel="noopener noreferrer" class="">Spark-TTS</a></td><td style="text-align:right">11.0k</td><td>代码 Apache-2.0，权重常见 CC-BY-NC-SA</td><td>2025</td><td>中英双语 zero-shot、voice creation、性别/音高/语速控制</td><td>中英克隆、虚拟声音创建</td></tr><tr><td><a href="https://github.com/myshell-ai/OpenVoice" target="_blank" rel="noopener noreferrer" class="">OpenVoice</a></td><td style="text-align:right">37.2k</td><td>MIT</td><td>2024 项目，仍有参考价值</td><td>instant voice cloning、跨语种 tone color cloning、情绪/口音控制</td><td>快速音色迁移、轻量 baseline</td></tr></tbody></table>
<p>如果严格只保留“2025+ 主线”，OpenVoice 可以放到备选；但它的 MIT 许可证、简单度和历史影响力仍然让它值得作为 baseline 对照。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-indextts中文可控性很突出的-zero-shot-tts">1. IndexTTS：中文可控性很突出的 zero-shot TTS<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/open-source-voice-cloning-frameworks-2025#1-indextts%E4%B8%AD%E6%96%87%E5%8F%AF%E6%8E%A7%E6%80%A7%E5%BE%88%E7%AA%81%E5%87%BA%E7%9A%84-zero-shot-tts" class="hash-link" aria-label="1. IndexTTS：中文可控性很突出的 zero-shot TTS的直接链接" title="1. IndexTTS：中文可控性很突出的 zero-shot TTS的直接链接" translate="no">​</a></h2>
<p>IndexTTS 是近两年中文语音克隆里很值得优先测的项目。它的定位不是“只要像”，而是把音色克隆和可控生成结合起来：参考音频负责音色，额外参数负责情绪、发音、语速和语言。</p>
<p>关键能力：</p>
<ul>
<li class=""><strong>单条参考音频克隆</strong>：上传一个 speaker audio prompt，就可以生成目标文本。</li>
<li class=""><strong>多语支持</strong>：最新 IndexTTS-2.5 支持中文、英文、日文、西班牙文、阿拉伯文。</li>
<li class=""><strong>情绪控制</strong>：可以通过参考音频、情绪描述文本等方式控制语气和表现力。</li>
<li class=""><strong>发音控制</strong>：支持中文拼音、英文 CMU phonemes、日文 Kana 等更细粒度的发音修正。</li>
<li class=""><strong>语速控制</strong>：通过 duration factor 控制整体说话速度。</li>
<li class=""><strong>部署方向</strong>：官方提到 vLLM 生产部署路径，说明它不是只面向 notebook demo。</li>
</ul>
<p>体验判断：如果你主要测中文个人声音，IndexTTS 应该排在第一梯队。它的优势是“可控”：不是只能靠模型随机发挥，而是能对中文发音、情绪、语速做显式调节。自用时，如果你想做的是“自己的中文配音模型”，它比很多单纯 zero-shot demo 更值得反复调参数。</p>
<p>可能的短板：安装和模型环境比纯在线 demo 更重；同时它采用自定义模型许可证，商用时要读细则。自用不太受影响。</p>
<p>可试入口：</p>
<ul>
<li class="">GitHub: <a href="https://github.com/index-tts/index-tts" target="_blank" rel="noopener noreferrer" class="">https://github.com/index-tts/index-tts</a></li>
<li class="">Hugging Face Demo: <a href="https://huggingface.co/spaces/IndexTeam/IndexTTS-2.5-Demo" target="_blank" rel="noopener noreferrer" class="">https://huggingface.co/spaces/IndexTeam/IndexTTS-2.5-Demo</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-cosyvoice综合能力最均衡的中文多语方案">2. CosyVoice：综合能力最均衡的中文/多语方案<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/open-source-voice-cloning-frameworks-2025#2-cosyvoice%E7%BB%BC%E5%90%88%E8%83%BD%E5%8A%9B%E6%9C%80%E5%9D%87%E8%A1%A1%E7%9A%84%E4%B8%AD%E6%96%87%E5%A4%9A%E8%AF%AD%E6%96%B9%E6%A1%88" class="hash-link" aria-label="2. CosyVoice：综合能力最均衡的中文/多语方案的直接链接" title="2. CosyVoice：综合能力最均衡的中文/多语方案的直接链接" translate="no">​</a></h2>
<p>CosyVoice 来自 FunAudioLLM 生态，是当前中文和多语 TTS 里非常均衡的一条线。它不只是做“语音克隆”，而是覆盖了训练、推理、Web demo、流式、服务端部署等完整链路。</p>
<p>关键能力：</p>
<ul>
<li class=""><strong>多语和方言覆盖</strong>：CosyVoice 3 路线覆盖中英日韩德西法意俄等常见语言，也强调中文方言/口音。</li>
<li class=""><strong>zero-shot / cross-lingual / instruct</strong>：可以做零样本克隆、跨语种生成，也能用自然语言指令控制风格。</li>
<li class=""><strong>情绪和语速控制</strong>：支持情绪、速度、音量等指令式控制。</li>
<li class=""><strong>流式能力</strong>：官方强调双向 streaming，适合低延迟交互场景。</li>
<li class=""><strong>部署友好</strong>：支持 vLLM、TensorRT-LLM、FastAPI、Docker 等路径，更接近可长期自用的服务化方案。</li>
</ul>
<p>体验判断：如果只选一个“综合型”项目，CosyVoice 很可能是最稳的。它不一定在每个 demo 场景都秒杀其他模型，但在中文、多语、方言、服务化和长期维护之间非常平衡。</p>
<p>可能的短板：环境安装、模型下载和部署链路比单个 HF Space 更复杂；不同版本模型的能力差异较大，测试时要明确是 CosyVoice2 还是 Fun-CosyVoice3。</p>
<p>可试入口：</p>
<ul>
<li class="">GitHub: <a href="https://github.com/FunAudioLLM/CosyVoice" target="_blank" rel="noopener noreferrer" class="">https://github.com/FunAudioLLM/CosyVoice</a></li>
<li class="">Hugging Face Demo: <a href="https://huggingface.co/spaces/FunAudioLLM/Fun-CosyVoice3-0.5B" target="_blank" rel="noopener noreferrer" class="">https://huggingface.co/spaces/FunAudioLLM/Fun-CosyVoice3-0.5B</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-gpt-sovits少样本微调生态最成熟">3. GPT-SoVITS：少样本微调生态最成熟<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/open-source-voice-cloning-frameworks-2025#3-gpt-sovits%E5%B0%91%E6%A0%B7%E6%9C%AC%E5%BE%AE%E8%B0%83%E7%94%9F%E6%80%81%E6%9C%80%E6%88%90%E7%86%9F" class="hash-link" aria-label="3. GPT-SoVITS：少样本微调生态最成熟的直接链接" title="3. GPT-SoVITS：少样本微调生态最成熟的直接链接" translate="no">​</a></h2>
<p>GPT-SoVITS 是中文社区里非常实用的一套语音克隆工具链。它的最大价值不是单点模型，而是“从数据准备到训练/推理”的完整 WebUI 工作流。</p>
<p>关键能力：</p>
<ul>
<li class=""><strong>5 秒 zero-shot</strong>：给一小段参考音频即可尝试克隆。</li>
<li class=""><strong>1 分钟 few-shot 微调</strong>：用很少的目标声音数据训练出更稳定的个人音色。</li>
<li class=""><strong>WebUI 成熟</strong>：提供推理、训练、数据处理等界面。</li>
<li class=""><strong>数据工具齐全</strong>：集成伴奏分离、自动切片、多语 ASR、文本处理等环节。</li>
<li class=""><strong>多语支持</strong>：覆盖中文、英文、日文、韩文、粤语等常见场景。</li>
<li class=""><strong>推理速度优化</strong>：新版本强调 ProPlus 和更快的 RTF 表现。</li>
</ul>
<p>体验判断：如果你只是想一次性上传 10 秒音频试一下，GPT-SoVITS 不一定是最省事的；但如果你想长期用自己的声音生成内容，GPT-SoVITS 的 few-shot 微调非常关键。很多 zero-shot 模型听起来“像”，但长文本稳定性、口头禅、气息和语速可能不够一致；few-shot 微调往往能解决一部分问题。</p>
<p>可能的短板：链路比纯 zero-shot 复杂，需要准备干净音频、切片、标注或 ASR 校正。要获得好效果，最好愿意花一点时间整理数据。</p>
<p>可试入口：</p>
<ul>
<li class="">GitHub: <a href="https://github.com/RVC-Boss/GPT-SoVITS" target="_blank" rel="noopener noreferrer" class="">https://github.com/RVC-Boss/GPT-SoVITS</a></li>
<li class="">Hugging Face Demo: <a href="https://lj1995-gpt-sovits-proplus.hf.space/" target="_blank" rel="noopener noreferrer" class="">https://lj1995-gpt-sovits-proplus.hf.space/</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-f5-tts自然度和在线体验都很好的-quick-win">4. F5-TTS：自然度和在线体验都很好的 quick win<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/open-source-voice-cloning-frameworks-2025#4-f5-tts%E8%87%AA%E7%84%B6%E5%BA%A6%E5%92%8C%E5%9C%A8%E7%BA%BF%E4%BD%93%E9%AA%8C%E9%83%BD%E5%BE%88%E5%A5%BD%E7%9A%84-quick-win" class="hash-link" aria-label="4. F5-TTS：自然度和在线体验都很好的 quick win的直接链接" title="4. F5-TTS：自然度和在线体验都很好的 quick win的直接链接" translate="no">​</a></h2>
<p>F5-TTS 是 2025 后仍很活跃的高自然度 TTS 路线，官方描述是基于 flow matching 的语音生成模型。它在社区中的吸引力主要来自两个点：自然度好，以及 demo 很容易上手。</p>
<p>关键能力：</p>
<ul>
<li class=""><strong>zero-shot voice cloning</strong>：用参考音频和参考文本 prompt 生成目标文本。</li>
<li class=""><strong>高自然度</strong>：在很多样例里，声音流畅度、节奏和自然感都比较好。</li>
<li class=""><strong>推理脚本和包化使用</strong>：可通过 Python package、CLI、WebUI 等方式使用。</li>
<li class=""><strong>多语言社区微调</strong>：Hugging Face 上有不少围绕 F5-TTS 的语言适配和衍生模型。</li>
</ul>
<p>体验判断：F5-TTS 适合作为“快速看效果上限”的第一批 demo。你准备一段干净参考音频，配好参考文本，通常能较快听出它的自然度优势。</p>
<p>可能的短板：它比较依赖参考音频和对应 transcript 的质量；如果参考文本不准、音频噪声大或语气差异太大，克隆结果会变差。另一个现实问题是，预训练权重常见为非商用许可证；自用可以忽略，但公开产品要谨慎。</p>
<p>可试入口：</p>
<ul>
<li class="">GitHub: <a href="https://github.com/SWivid/F5-TTS" target="_blank" rel="noopener noreferrer" class="">https://github.com/SWivid/F5-TTS</a></li>
<li class="">Hugging Face Demo: <a href="https://huggingface.co/spaces/mrfakename/E2-F5-TTS" target="_blank" rel="noopener noreferrer" class="">https://huggingface.co/spaces/mrfakename/E2-F5-TTS</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-chatterbox英文多语-voice-agent-的强候选">5. Chatterbox：英文/多语 voice agent 的强候选<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/open-source-voice-cloning-frameworks-2025#5-chatterbox%E8%8B%B1%E6%96%87%E5%A4%9A%E8%AF%AD-voice-agent-%E7%9A%84%E5%BC%BA%E5%80%99%E9%80%89" class="hash-link" aria-label="5. Chatterbox：英文/多语 voice agent 的强候选的直接链接" title="5. Chatterbox：英文/多语 voice agent 的强候选的直接链接" translate="no">​</a></h2>
<p>Chatterbox 来自 Resemble AI，定位很接近“自然语音代理”和“多语 voice cloning”。它不是中文生态里最强势的名字，但如果你要做英文、多语或者对话式语音，它非常值得测。</p>
<p>关键能力：</p>
<ul>
<li class=""><strong>模型族完整</strong>：包括 Chatterbox、Chatterbox-Turbo、Chatterbox-Nano、Chatterbox-Multilingual V3。</li>
<li class=""><strong>多语 V3</strong>：官方强调 23+ 语言覆盖、更稳定的 speaker similarity、更少 hallucination。</li>
<li class=""><strong>低资源变体</strong>：Nano 面向 CPU/端侧，Turbo 面向更快推理。</li>
<li class=""><strong>paralinguistic tags</strong>：支持类似 <code>[laugh]</code> 的副语言标签，适合聊天和角色声音。</li>
<li class=""><strong>内置水印</strong>：生成音频包含 Resemble AI 的 PerTh 水印，体现其 responsible AI 取向。</li>
</ul>
<p>体验判断：如果你测英文播客、英语 voice agent、跨语言说话风格，Chatterbox 应该放到第一梯队。它的优势不是“中文发音最精细”，而是自然、稳定、适合对话产品。</p>
<p>可能的短板：中文能力要实测，不建议只看英文样例就直接判断它适合所有中文场景。参数如 cfg weight、exaggeration 会影响节奏和表现力，需要调。</p>
<p>可试入口：</p>
<ul>
<li class="">GitHub: <a href="https://github.com/resemble-ai/chatterbox" target="_blank" rel="noopener noreferrer" class="">https://github.com/resemble-ai/chatterbox</a></li>
<li class="">Hugging Face Demo: <a href="https://huggingface.co/spaces/ResembleAI/Chatterbox-Multilingual-TTS-V3" target="_blank" rel="noopener noreferrer" class="">https://huggingface.co/spaces/ResembleAI/Chatterbox-Multilingual-TTS-V3</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-fish-speech--openaudio表现力上限高但更像进阶玩家选项">6. Fish Speech / OpenAudio：表现力上限高，但更像进阶玩家选项<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/open-source-voice-cloning-frameworks-2025#6-fish-speech--openaudio%E8%A1%A8%E7%8E%B0%E5%8A%9B%E4%B8%8A%E9%99%90%E9%AB%98%E4%BD%86%E6%9B%B4%E5%83%8F%E8%BF%9B%E9%98%B6%E7%8E%A9%E5%AE%B6%E9%80%89%E9%A1%B9" class="hash-link" aria-label="6. Fish Speech / OpenAudio：表现力上限高，但更像进阶玩家选项的直接链接" title="6. Fish Speech / OpenAudio：表现力上限高，但更像进阶玩家选项的直接链接" translate="no">​</a></h2>
<p>Fish Speech 是近两年非常活跃的开源 TTS 项目之一。它强调多语、表现力、情绪 tag 和快速克隆，新的 Fish Audio / OpenAudio 路线也在持续推进。</p>
<p>关键能力：</p>
<ul>
<li class=""><strong>短参考音频克隆</strong>：官方文档提到典型 10-30 秒参考样本即可进行快速 voice cloning。</li>
<li class=""><strong>多语能力</strong>：面向大量语言和多说话人场景。</li>
<li class=""><strong>情绪 tag</strong>：可以在文本里通过标签控制 whisper、excited、angry 等表现。</li>
<li class=""><strong>多轮生成</strong>：强调利用上下文提升后续生成自然度。</li>
<li class=""><strong>服务端性能</strong>：结合 SGLang 等推理加速方向，适合进阶部署。</li>
</ul>
<p>体验判断：如果你追求的是“声音有灵魂”、角色感、情绪表达，而不是最简单的一键克隆，Fish Speech 值得测。它的上限可能很高，尤其适合做角色化声音或风格化朗读。</p>
<p>可能的短板：整体生态更偏进阶，版本线、模型权重和部署方式需要仔细选；如果只想点开网页快速玩，体验门槛可能高于 F5-TTS 或 Chatterbox。</p>
<p>可试入口：</p>
<ul>
<li class="">GitHub: <a href="https://github.com/fishaudio/fish-speech" target="_blank" rel="noopener noreferrer" class="">https://github.com/fishaudio/fish-speech</a></li>
<li class="">文档入口: <a href="https://speech.fish.audio/" target="_blank" rel="noopener noreferrer" class="">https://speech.fish.audio/</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-spark-tts中英双语和-voice-creation-很有意思">7. Spark-TTS：中英双语和 voice creation 很有意思<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/open-source-voice-cloning-frameworks-2025#7-spark-tts%E4%B8%AD%E8%8B%B1%E5%8F%8C%E8%AF%AD%E5%92%8C-voice-creation-%E5%BE%88%E6%9C%89%E6%84%8F%E6%80%9D" class="hash-link" aria-label="7. Spark-TTS：中英双语和 voice creation 很有意思的直接链接" title="7. Spark-TTS：中英双语和 voice creation 很有意思的直接链接" translate="no">​</a></h2>
<p>Spark-TTS 是 2025 年出现的中英双语 TTS/voice cloning 项目。它的一个特色是基于 Qwen2.5 路线，并把 voice cloning 和 voice creation 放在一起。</p>
<p>关键能力：</p>
<ul>
<li class=""><strong>中英双语</strong>：重点覆盖中文和英文，支持跨语言/code-switching 场景。</li>
<li class=""><strong>zero-shot voice cloning</strong>：无需为目标说话人专门训练即可克隆。</li>
<li class=""><strong>voice creation</strong>：可通过性别、音高、语速等参数创建虚拟声音。</li>
<li class=""><strong>推理部署</strong>：提供 WebUI、CLI 和 Triton 推理服务相关说明。</li>
</ul>
<p>体验判断：Spark-TTS 适合自用时拿来玩两类东西：一是中英双语克隆，二是创建一个不存在的虚拟声音。如果你不仅想“复制自己的声音”，还想调一个新角色音色，它比纯克隆模型更有趣。</p>
<p>可能的短板：语言覆盖不如 CosyVoice 或 Chatterbox Multilingual；权重许可证常见为非商用，虽然自用无所谓，但公开发布前仍需留意。</p>
<p>可试入口：</p>
<ul>
<li class="">GitHub: <a href="https://github.com/SparkAudio/Spark-TTS" target="_blank" rel="noopener noreferrer" class="">https://github.com/SparkAudio/Spark-TTS</a></li>
<li class="">Hugging Face Demo: <a href="https://huggingface.co/spaces/Mobvoi/Offical-Spark-TTS" target="_blank" rel="noopener noreferrer" class="">https://huggingface.co/spaces/Mobvoi/Offical-Spark-TTS</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="8-dia不是通用克隆首选但对话感很强">8. Dia：不是通用克隆首选，但对话感很强<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/open-source-voice-cloning-frameworks-2025#8-dia%E4%B8%8D%E6%98%AF%E9%80%9A%E7%94%A8%E5%85%8B%E9%9A%86%E9%A6%96%E9%80%89%E4%BD%86%E5%AF%B9%E8%AF%9D%E6%84%9F%E5%BE%88%E5%BC%BA" class="hash-link" aria-label="8. Dia：不是通用克隆首选，但对话感很强的直接链接" title="8. Dia：不是通用克隆首选，但对话感很强的直接链接" translate="no">​</a></h2>
<p>Dia 来自 Nari Labs，定位更像“超真实对话 TTS”。它可以从 transcript 直接生成对话音频，也能用音频 prompt 控制声音、语气和情绪。</p>
<p>关键能力：</p>
<ul>
<li class=""><strong>英文对话生成</strong>：适合播客、角色对话、两人对话等场景。</li>
<li class=""><strong>音频 prompt</strong>：官方建议 5-10 秒参考音频作为 voice cloning/conditioning 输入。</li>
<li class=""><strong>非语言事件</strong>：能生成笑声、咳嗽、清嗓等对话中的自然声音。</li>
<li class=""><strong>Apache-2.0</strong>：许可证友好。</li>
</ul>
<p>体验判断：Dia 不应被当作“最通用的中文语音克隆工具”。它更适合英文对话、播客样式、角色互动。如果你的目标是做一个英语对话节目或模拟两个人聊天，它很值得加入测试。</p>
<p>可能的短板：官方明确模型只支持英文生成和理解；中文自用场景应优先看 IndexTTS、CosyVoice、GPT-SoVITS。</p>
<p>可试入口：</p>
<ul>
<li class="">GitHub: <a href="https://github.com/nari-labs/dia" target="_blank" rel="noopener noreferrer" class="">https://github.com/nari-labs/dia</a></li>
<li class="">Hugging Face Demo: <a href="https://huggingface.co/spaces/nari-labs/Dia-1.6B" target="_blank" rel="noopener noreferrer" class="">https://huggingface.co/spaces/nari-labs/Dia-1.6B</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="9-openvoice2024-baseline仍适合做对照组">9. OpenVoice：2024 baseline，仍适合做对照组<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/open-source-voice-cloning-frameworks-2025#9-openvoice2024-baseline%E4%BB%8D%E9%80%82%E5%90%88%E5%81%9A%E5%AF%B9%E7%85%A7%E7%BB%84" class="hash-link" aria-label="9. OpenVoice：2024 baseline，仍适合做对照组的直接链接" title="9. OpenVoice：2024 baseline，仍适合做对照组的直接链接" translate="no">​</a></h2>
<p>OpenVoice V2 不完全符合“2025+”过滤条件，但作为轻量、MIT、跨语种 instant voice cloning baseline，它仍然值得在自用测试里保留一个对照位。</p>
<p>关键能力：</p>
<ul>
<li class=""><strong>instant voice cloning</strong>：快速克隆参考音色。</li>
<li class=""><strong>跨语种 tone color cloning</strong>：能把音色迁移到不同语言和口音上。</li>
<li class=""><strong>风格控制</strong>：支持情绪、口音、节奏、停顿、语调等控制思路。</li>
<li class=""><strong>商业友好许可证</strong>：MIT，虽然本文不以许可证为主，但这让它很容易放进工具箱。</li>
</ul>
<p>体验判断：OpenVoice 的优势是简单、成熟、轻量。它不一定代表 2025+ 最新效果上限，但作为 baseline 很有价值：如果一个新模型比 OpenVoice 复杂很多，效果却没有明显超过它，那就不值得长期维护。</p>
<p>可试入口：</p>
<ul>
<li class="">GitHub: <a href="https://github.com/myshell-ai/OpenVoice" target="_blank" rel="noopener noreferrer" class="">https://github.com/myshell-ai/OpenVoice</a></li>
<li class="">Hugging Face Demo: <a href="https://huggingface.co/spaces/myshell-ai/OpenVoice" target="_blank" rel="noopener noreferrer" class="">https://huggingface.co/spaces/myshell-ai/OpenVoice</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="按自用目标选择">按自用目标选择<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/open-source-voice-cloning-frameworks-2025#%E6%8C%89%E8%87%AA%E7%94%A8%E7%9B%AE%E6%A0%87%E9%80%89%E6%8B%A9" class="hash-link" aria-label="按自用目标选择的直接链接" title="按自用目标选择的直接链接" translate="no">​</a></h2>
<p>如果不想把所有项目都跑一遍，可以按目标缩小范围。</p>
<table><thead><tr><th>自用目标</th><th>优先候选</th><th>原因</th></tr></thead><tbody><tr><td>中文个人声音克隆，越像越好</td><td>GPT-SoVITS、IndexTTS、CosyVoice</td><td>GPT-SoVITS 适合 few-shot 稳定音色；IndexTTS/CosyVoice zero-shot 和可控性强</td></tr><tr><td>快速上传一段音频看效果</td><td>F5-TTS、IndexTTS、Chatterbox</td><td>Demo 体验相对直接，容易快速判断音色相似度和自然度</td></tr><tr><td>中文情绪配音/角色感</td><td>IndexTTS、Fish Speech、CosyVoice</td><td>情绪、语速、发音或 tag 控制更丰富</td></tr><tr><td>中英/多语切换</td><td>CosyVoice、Chatterbox、F5-TTS</td><td>综合多语能力强，跨语种克隆体验更值得测</td></tr><tr><td>长期本地使用</td><td>GPT-SoVITS、CosyVoice、F5-TTS</td><td>有本地推理、WebUI、训练或服务化路径</td></tr><tr><td>英文播客/对话</td><td>Chatterbox、Dia</td><td>对话自然度和副语言事件更适合英文内容</td></tr><tr><td>创建一个虚拟声音</td><td>Spark-TTS、Fish Speech</td><td>不只克隆，也能调性别、音高、语速或风格</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="我会怎么排测试顺序">我会怎么排测试顺序<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/open-source-voice-cloning-frameworks-2025#%E6%88%91%E4%BC%9A%E6%80%8E%E4%B9%88%E6%8E%92%E6%B5%8B%E8%AF%95%E9%A1%BA%E5%BA%8F" class="hash-link" aria-label="我会怎么排测试顺序的直接链接" title="我会怎么排测试顺序的直接链接" translate="no">​</a></h2>
<p>如果测试对象主要是中文自用，我建议按这个顺序：</p>
<ol>
<li class=""><strong>IndexTTS 2.5</strong>：先看中文相似度、情绪控制、语速控制和发音修正能力。</li>
<li class=""><strong>CosyVoice 3/2</strong>：看整体稳定性、多语和方言表现，以及是否适合做本地服务。</li>
<li class=""><strong>F5-TTS v1</strong>：快速判断自然度上限，尤其是参考音频质量较好时的表现。</li>
<li class=""><strong>GPT-SoVITS</strong>：如果 zero-shot 不够像，再投入时间做少样本微调。</li>
<li class=""><strong>Chatterbox V3</strong>：补测英文/多语和聊天式 voice agent 场景。</li>
<li class=""><strong>Fish Speech / Spark-TTS</strong>：当你想要更强表现力、情绪 tag 或虚拟声音时再测。</li>
<li class=""><strong>Dia</strong>：只在英文对话/播客场景需要时测。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="测试素材建议">测试素材建议<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/open-source-voice-cloning-frameworks-2025#%E6%B5%8B%E8%AF%95%E7%B4%A0%E6%9D%90%E5%BB%BA%E8%AE%AE" class="hash-link" aria-label="测试素材建议的直接链接" title="测试素材建议的直接链接" translate="no">​</a></h2>
<p>语音克隆模型对素材非常敏感。为了公平比较，建议准备同一套测试集：</p>
<ul>
<li class=""><strong>参考音频</strong>：10-30 秒，干净人声，无 BGM，无明显混响，音量稳定。</li>
<li class=""><strong>参考文本</strong>：如果模型需要 transcript，尽量手动校对，标点也要自然。</li>
<li class=""><strong>目标文本</strong>：至少包含短句、长句、情绪句和中英混杂句。</li>
<li class=""><strong>输出格式</strong>：统一采样率和音量后再听，避免被响度误导。</li>
<li class=""><strong>评估维度</strong>：音色相似度、咬字准确率、长文本稳定性、情绪自然度、是否重复/乱续写。</li>
</ul>
<p>一组简单中文测试文本可以这样设计：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">今天我们来聊一下语音克隆模型的实际体验。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">这段话稍微长一点，用来测试模型在长文本下会不会断句奇怪、重复，或者出现发音不稳定的问题。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">哎，这个效果还真有点意思，比我想象中自然多了。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">我今天用的是 voice cloning demo，想测试一下中文、English words，还有一点点情绪变化。</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="最终结论">最终结论<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/open-source-voice-cloning-frameworks-2025#%E6%9C%80%E7%BB%88%E7%BB%93%E8%AE%BA" class="hash-link" aria-label="最终结论的直接链接" title="最终结论的直接链接" translate="no">​</a></h2>
<p>如果只从“自用体验”出发，我会把候选压缩成五个主力：</p>
<table><thead><tr><th style="text-align:right">优先级</th><th>框架</th><th>一句话结论</th></tr></thead><tbody><tr><td style="text-align:right">1</td><td>IndexTTS 2.5</td><td>中文可控性最值得测，情绪、发音、语速都比较完整</td></tr><tr><td style="text-align:right">2</td><td>CosyVoice 3/2</td><td>综合能力最均衡，中文、多语、方言、部署都强</td></tr><tr><td style="text-align:right">3</td><td>GPT-SoVITS</td><td>想长期做自己的声音库，few-shot 微调很实用</td></tr><tr><td style="text-align:right">4</td><td>F5-TTS v1</td><td>快速体验自然度上限，demo 友好</td></tr><tr><td style="text-align:right">5</td><td>Chatterbox V3</td><td>英文/多语 voice agent 和对话式声音值得测</td></tr></tbody></table>
<p>再往后，Fish Speech 适合追求表现力上限，Spark-TTS 适合中英克隆和虚拟声音创建，Dia 适合英文对话，OpenVoice 适合作为轻量 baseline。</p>
<p>真正落地时，不要只听官方样例。最有价值的测试是：用自己的参考音频、自己的目标文本、同一套评估维度，把这些模型各跑一遍。语音克隆的效果差异常常不是“哪个模型绝对最好”，而是“哪个模型最适合你的声音、语言和使用习惯”。</p>]]></content>
        <category label="tts" term="tts"/>
        <category label="voice-cloning" term="voice-cloning"/>
        <category label="ai-audio" term="ai-audio"/>
        <category label="open-source" term="open-source"/>
        <category label="research" term="research"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Remotion 进阶：一份代码，参数化批量出片]]></title>
        <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-parametrized-batch-render</id>
        <link href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-parametrized-batch-render"/>
        <updated>2026-08-22T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[同一个 React 组件，通过 zod schema 定义参数，换一换数据/配色就能批量渲染出不同版本——展示 Remotion 区别于其他动画工具的核心能力。]]></summary>
        <content type="html"><![CDATA[<p>上一篇文章走完了 Remotion 的最小闭环。但要真正发挥 Remotion 的威力，关键在<strong>参数化</strong>——用 zod schema 定义参数，一份代码换不同数据，批量渲染出不同版本。</p>
<p>这篇文章用柱状图动画演示：同一个 <code>&lt;DataBars&gt;</code> 组件，给它换三组参数，渲染出三份不同内容、不同配色的视频。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="参数化模型">参数化模型<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-parametrized-batch-render#%E5%8F%82%E6%95%B0%E5%8C%96%E6%A8%A1%E5%9E%8B" class="hash-link" aria-label="参数化模型的直接链接" title="参数化模型的直接链接" translate="no">​</a></h2>
<p>Remotion 的 <code>&lt;Composition&gt;</code> 支持 <code>schema</code> 和 <code>defaultProps</code>：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Composition</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">id</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">DataBars</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">component</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript maybe-class-name" style="color:#00009f">DataBars</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">durationInFrames</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript number" style="color:#36acaa">210</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f">  </span><span class="token tag comment" style="color:#999988;font-style:italic">// 7s</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">fps</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript number" style="color:#36acaa">30</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">width</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript number" style="color:#36acaa">1920</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">height</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript number" style="color:#36acaa">1080</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">schema</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">dataBarsSchema</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f">  </span><span class="token tag comment" style="color:#999988;font-style:italic">// zod 定义参数结构</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">defaultProps</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f"> title</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"ChatArch 服务增长"</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript spread operator" style="color:#393A34">...</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f"></span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><br></div></code></pre></div></div>
<p><code>schema</code> 用 zod 定义，<code>@remotion/zod-types</code> 还提供了 <code>zColor()</code>——一个颜色类型，渲染时自动校验。<code>defaultProps</code> 给 Studio 预览和基础渲染用，<code>--props</code> 命令行参数随时覆盖。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="柱状图组件">柱状图组件<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-parametrized-batch-render#%E6%9F%B1%E7%8A%B6%E5%9B%BE%E7%BB%84%E4%BB%B6" class="hash-link" aria-label="柱状图组件的直接链接" title="柱状图组件的直接链接" translate="no">​</a></h2>
<p>这个组件展示的是 Remotion 最擅长的场景：<strong>数据驱动的可视化视频</strong>。</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> dataBarsSchema </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">object</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  title</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">           </span><span class="token comment" style="color:#999988;font-style:italic">// 标题</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  subtitle</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic">// 副标题</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  accentColor</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">zColor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">       </span><span class="token comment" style="color:#999988;font-style:italic">// 强调色（柱状图颜色）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">array</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">               </span><span class="token comment" style="color:#999988;font-style:italic">// 数据：{label, value}[]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">object</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> label</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">string</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">number</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<figure><video controls="" preload="metadata" poster="https://share.public.wzhecnu.cn/chatblog/remotion-exploration/data-bars-poster.jpg" style="width:100%;border-radius:12px;border:1px solid var(--ifm-color-emphasis-200);background:#0b1020"><source src="https://share.public.wzhecnu.cn/chatblog/remotion-exploration/data-bars-stars.mp4" type="video/mp4"><p>你的浏览器不支持 HTML5 video。</p></video><figcaption><p>"生态对比"版（accentColor=#f472b6）：5 根柱子，manim 最高（91.9K star），Remotion 第二（57K），lottie/p5/motion-canvas 依次。柱高比例正确，数值从 0 计数递增。</p></figcaption></figure>
<p>动画原语：</p>
<ul>
<li class=""><code>spring({ frame, fps, config: { damping, stiffness } })</code>——弹簧动画驱动柱子从 0 长到目标高度，每根柱子通过 <code>i * 8</code> 帧错峰入场</li>
<li class=""><code>interpolate(frame, [start, end], [0, 1])</code>——数值从 0 计到目标值，标题淡入上移</li>
<li class="">每个数据点渲染为绝对定位的 <code>&lt;div&gt;</code>，由 <code>height</code> 和位置控制柱形</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三份参数三份视频">三份参数，三份视频<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-parametrized-batch-render#%E4%B8%89%E4%BB%BD%E5%8F%82%E6%95%B0%E4%B8%89%E4%BB%BD%E8%A7%86%E9%A2%91" class="hash-link" aria-label="三份参数，三份视频的直接链接" title="三份参数，三份视频的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="版本-1chatarch-服务增长默认配色-38bdf8">版本 1：ChatArch 服务增长（默认配色 #38bdf8）<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-parametrized-batch-render#%E7%89%88%E6%9C%AC-1chatarch-%E6%9C%8D%E5%8A%A1%E5%A2%9E%E9%95%BF%E9%BB%98%E8%AE%A4%E9%85%8D%E8%89%B2-38bdf8" class="hash-link" aria-label="版本 1：ChatArch 服务增长（默认配色 #38bdf8）的直接链接" title="版本 1：ChatArch 服务增长（默认配色 #38bdf8）的直接链接" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  title</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"ChatArch 服务增长"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  subtitle</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"2026 Q2 · 月度活跃服务（示意数据）"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  accentColor</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"#38bdf8"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 蓝色</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> label</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Apr"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">320</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> label</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"May"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">540</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> label</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Jun"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">860</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<video controls="" preload="metadata" poster="https://share.public.wzhecnu.cn/chatblog/remotion-exploration/data-bars-poster.jpg" style="width:100%;border-radius:12px;border:1px solid var(--ifm-color-emphasis-200);background:#0b1020"><source src="https://share.public.wzhecnu.cn/chatblog/remotion-exploration/data-bars-default.mp4" type="video/mp4"></video>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="版本-2生态对比f472b6-粉色">版本 2：生态对比（#f472b6 粉色）<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-parametrized-batch-render#%E7%89%88%E6%9C%AC-2%E7%94%9F%E6%80%81%E5%AF%B9%E6%AF%94f472b6-%E7%B2%89%E8%89%B2" class="hash-link" aria-label="版本 2：生态对比（#f472b6 粉色）的直接链接" title="版本 2：生态对比（#f472b6 粉色）的直接链接" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  title</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"代码写视频的生态对比"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  subtitle</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"GitHub Stars · 2026-08 快照"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  accentColor</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"#f472b6"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 粉色</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> label</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"manim"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">         value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">91900</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> label</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Remotion"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">      value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">57000</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> label</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"lottie-web"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">    value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">32100</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> label</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"p5.js"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">         value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">23900</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> label</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"motion-canvas"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">19000</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<video controls="" preload="metadata" poster="https://share.public.wzhecnu.cn/chatblog/remotion-exploration/data-bars-poster.jpg" style="width:100%;border-radius:12px;border:1px solid var(--ifm-color-emphasis-200);background:#0b1020"><source src="https://share.public.wzhecnu.cn/chatblog/remotion-exploration/data-bars-stars.mp4" type="video/mp4"></video>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="版本-3月度渲染量34d399-绿色">版本 3：月度渲染量（#34d399 绿色）<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-parametrized-batch-render#%E7%89%88%E6%9C%AC-3%E6%9C%88%E5%BA%A6%E6%B8%B2%E6%9F%93%E9%87%8F34d399-%E7%BB%BF%E8%89%B2" class="hash-link" aria-label="版本 3：月度渲染量（#34d399 绿色）的直接链接" title="版本 3：月度渲染量（#34d399 绿色）的直接链接" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  title</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"月度视频渲染量"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  subtitle</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Playground 渲染流水线（示意数据）"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  accentColor</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"#34d399"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 绿色</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  data</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> label</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Jul"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">120</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> label</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Aug"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">480</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> label</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Sep"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">720</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<video controls="" preload="metadata" poster="https://share.public.wzhecnu.cn/chatblog/remotion-exploration/data-bars-poster.jpg" style="width:100%;border-radius:12px;border:1px solid var(--ifm-color-emphasis-200);background:#0b1020"><source src="https://share.public.wzhecnu.cn/chatblog/remotion-exploration/data-bars-monthly.mp4" type="video/mp4"></video>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="如何批量渲染">如何批量渲染<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-parametrized-batch-render#%E5%A6%82%E4%BD%95%E6%89%B9%E9%87%8F%E6%B8%B2%E6%9F%93" class="hash-link" aria-label="如何批量渲染的直接链接" title="如何批量渲染的直接链接" translate="no">​</a></h2>
<p>三种方式，灵活度递进。最简单的是<code>--props</code> 命令行参数：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># 用 --props 传 JSON 参数覆盖 defaultProps</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npx remotion render src/index.ts DataBars out/v1.mp4 \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --props='{"title":"生态对比","accentColor":"#f472b6","data":[...]}'</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 换一组参数就是另一条视频</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npx remotion render src/index.ts DataBars out/v2.mp4 \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --props='{"title":"月度渲染量","accentColor":"#34d399","data":[...]}'</span><br></div></code></pre></div></div>
<p>第二种方式：用 <code>inputProps</code> 从 Node.js API 调用，适合写脚本批量渲染。第三种：Studio 里直接改参数预览，所见即所得。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="参数化的边界">参数化的边界<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-parametrized-batch-render#%E5%8F%82%E6%95%B0%E5%8C%96%E7%9A%84%E8%BE%B9%E7%95%8C" class="hash-link" aria-label="参数化的边界的直接链接" title="参数化的边界的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><code>schema</code> 用 zod 校验，<code>zColor()</code> 来自 <code>@remotion/zod-types</code>，支持十六进制颜色</li>
<li class="">渲染时通过 <code>--props</code> 传 JSON，与 <code>defaultProps</code> 合并</li>
<li class="">复杂参数（如嵌套对象、数组）完全支持，zod 的 <code>.object()</code> / <code>.array()</code> 天然适配</li>
<li class="">参数错误时 Remotion 会给出明确校验错误，不会产出"不小心"坏掉的视频</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="同一份代码不同的可能">同一份代码，不同的可能<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-parametrized-batch-render#%E5%90%8C%E4%B8%80%E4%BB%BD%E4%BB%A3%E7%A0%81%E4%B8%8D%E5%90%8C%E7%9A%84%E5%8F%AF%E8%83%BD" class="hash-link" aria-label="同一份代码，不同的可能的直接链接" title="同一份代码，不同的可能的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>参数</th><th>作用</th><th>示例</th></tr></thead><tbody><tr><td><code>title</code> / <code>subtitle</code></td><td>标题文字</td><td>任何语言，任何长度</td></tr><tr><td><code>accentColor</code></td><td>全局色调</td><td>品牌色、数据分类色</td></tr><tr><td><code>data</code></td><td>数值数组</td><td>周报、月报、KPI 对比</td></tr><tr><td>未来扩展</td><td>更多字段</td><td>图表类型、动画速度、分辨率</td></tr></tbody></table>
<p>这个模式适用于任何数据驱动的视频：周报、KPI 简报、产品对比、榜单、年终总结……只要写一个组件，数据换一下，就是一个新视频。这就是 Remotion 的"参数化批量出片"——比写死脚本更灵活，比手动剪辑更可控。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="相关链接">相关链接<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-parametrized-batch-render#%E7%9B%B8%E5%85%B3%E9%93%BE%E6%8E%A5" class="hash-link" aria-label="相关链接的直接链接" title="相关链接的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://remotion.dev/docs/parametrized-rendering" target="_blank" rel="noopener noreferrer" class="">Remotion 参数化渲染文档</a></li>
<li class=""><a class="" href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-react-video-quickstart">上一篇：Remotion 最小闭环</a></li>
<li class="">demo 源码：<code>src/DataBars.tsx</code>（在 <code>ChatArch/remotion</code> fork 的 <code>packages/example</code> 目录）</li>
<li class="">三个 demo MP4：<!-- -->
<ul>
<li class=""><a href="https://share.public.wzhecnu.cn/chatblog/remotion-exploration/data-bars-default.mp4" target="_blank" rel="noopener noreferrer" class="">默认版</a></li>
<li class=""><a href="https://share.public.wzhecnu.cn/chatblog/remotion-exploration/data-bars-stars.mp4" target="_blank" rel="noopener noreferrer" class="">生态对比版</a></li>
<li class=""><a href="https://share.public.wzhecnu.cn/chatblog/remotion-exploration/data-bars-monthly.mp4" target="_blank" rel="noopener noreferrer" class="">月度渲染量版</a></li>
</ul>
</li>
</ul>]]></content>
        <category label="remotion" term="remotion"/>
        <category label="react" term="react"/>
        <category label="typescript" term="typescript"/>
        <category label="video" term="video"/>
        <category label="parameterization" term="parameterization"/>
        <category label="batch-render" term="batch-render"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Remotion：用 React 写代码生成视频的最小闭环]]></title>
        <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-react-video-quickstart</id>
        <link href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-react-video-quickstart"/>
        <updated>2026-08-22T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[从 `npx create-video` 脚手架到真实渲染出一条 1080p 动画视频，再通过 ChatShare 分享 MP4 并嵌入 ChatBlog。用 HelloWorld 模板走完 Remotion 视频生产的最小闭环。]]></summary>
        <content type="html"><![CDATA[<p><strong>Remotion</strong>（github.com/remotion-dev/remotion，约 5.7 万 star）是一个用 React 写代码来制作视频的框架。它的核心心智模型是：<strong>React 代码就是视频的"源文件"</strong>——你写一个 React 组件，Remotion 会按时间轴把它一帧一帧渲染出来，最终编码成真正的 MP4 视频。</p>
<p>这篇文章不追求大片质感，只走一遍最小闭环：<strong>脚手架 → 理解代码 → 真实渲染 → 分享 MP4 → 嵌入博客</strong>。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>最终结果</div><div class="admonitionContent_BuS1"><p>这就是本文在服务器上实际跑出来的视频：Remotion <code>4.0.515</code>，HelloWorld 官方模板，1920×1080，30 FPS，H.264，约 5 秒。</p></div></div>
<figure><video controls="" preload="metadata" poster="https://share.public.wzhecnu.cn/chatblog/remotion-exploration/hello-world-poster.jpg" style="width:100%;border-radius:12px;border:1px solid var(--ifm-color-emphasis-200);background:#ffffff"><source src="https://share.public.wzhecnu.cn/chatblog/remotion-exploration/hello-world.mp4" type="video/mp4"><p>你的浏览器不支持 HTML5 video。可以直接打开：
<a href="https://share.public.wzhecnu.cn/chatblog/remotion-exploration/hello-world.mp4" target="_blank" rel="noopener noreferrer" class="">https://share.public.wzhecnu.cn/chatblog/remotion-exploration/hello-world.mp4</a></p></video><figcaption><p>Remotion HelloWorld 模板：彩色原子 Logo 旋转、单词逐字缩放入场、副标题淡入。视频文件通过 ChatShare 暴露为稳定 URL，再由 ChatBlog 的 MDX <code>&lt;video&gt;</code> 标签嵌入。</p></figcaption></figure>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="这条-quickstart-演示什么">这条 quickstart 演示什么<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-react-video-quickstart#%E8%BF%99%E6%9D%A1-quickstart-%E6%BC%94%E7%A4%BA%E4%BB%80%E4%B9%88" class="hash-link" aria-label="这条 quickstart 演示什么的直接链接" title="这条 quickstart 演示什么的直接链接" translate="no">​</a></h2>
<!-- -->
<p>这条流水线是通的三件事：</p>
<ul>
<li class=""><strong>理解</strong>：Remotion 的 <code>&lt;Composition&gt;</code> / <code>useCurrentFrame()</code> / <code>spring()</code> / <code>interpolate()</code> 这几个核心概念；</li>
<li class=""><strong>产出</strong>：一条真实渲染的 1080p 动画视频；</li>
<li class=""><strong>交付</strong>：通过 ChatShare 分享 MP4，再嵌入静态博客。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-0先克隆了解它是什么">Step 0：先克隆，了解它是什么<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-react-video-quickstart#step-0%E5%85%88%E5%85%8B%E9%9A%86%E4%BA%86%E8%A7%A3%E5%AE%83%E6%98%AF%E4%BB%80%E4%B9%88" class="hash-link" aria-label="Step 0：先克隆，了解它是什么的直接链接" title="Step 0：先克隆，了解它是什么的直接链接" translate="no">​</a></h2>
<p>在服务器上先浅克隆（仓库比较大，1.2 GB 的 monorepo）：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git clone --depth 1 --single-branch https://github.com/ChatArch/remotion.git core/remotion</span><br></div></code></pre></div></div>
<p>monorepo 里有几十个 <code>packages/</code>：<code>core</code>（React 组件与动画原语）、<code>cli</code>（命令行）、<code>renderer</code>（渲染引擎）、<code>compositor</code>（Rust 合成器）、<code>player</code>（把视频组件嵌进网页）、<code>lambda</code>（AWS 云端渲染）、<code>studio</code>（可视化编辑器）、以及 20+ 个官方模板。核心 API 从 <code>packages/core/src/index.ts</code> 导出，约 60 个：<code>Composition / Sequence / Series / Loop / AbsoluteFill / Audio / Video / Img / interpolate / spring / staticFile / useCurrentFrame / useVideoConfig ...</code>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1脚手架一个官方模板">Step 1：脚手架一个官方模板<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-react-video-quickstart#step-1%E8%84%9A%E6%89%8B%E6%9E%B6%E4%B8%80%E4%B8%AA%E5%AE%98%E6%96%B9%E6%A8%A1%E6%9D%BF" class="hash-link" aria-label="Step 1：脚手架一个官方模板的直接链接" title="Step 1：脚手架一个官方模板的直接链接" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npx create-video@latest --yes --hello-world remotion-demo</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cd remotion-demo &amp;&amp; npm install</span><br></div></code></pre></div></div>
<p>脚手架会生成一个标准 Remotion 工程：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">remotion-demo/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  src/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    index.ts          # registerRoot(RemotionRoot)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Root.tsx          # 声明 &lt;Composition&gt;（= 侧边栏里的每个"视频"）</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    HelloWorld.tsx    # 具体视频组件：一个 React FC</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    HelloWorld/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      Title.tsx  Subtitle.tsx  Logo.tsx  Atom.tsx  Arc.tsx</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  remotion.config.ts  # 全局配置</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  package.json</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2模板源码在做什么">Step 2：模板源码在做什么<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-react-video-quickstart#step-2%E6%A8%A1%E6%9D%BF%E6%BA%90%E7%A0%81%E5%9C%A8%E5%81%9A%E4%BB%80%E4%B9%88" class="hash-link" aria-label="Step 2：模板源码在做什么的直接链接" title="Step 2：模板源码在做什么的直接链接" translate="no">​</a></h2>
<p><code>src/index.ts</code> 只有一行核心：<code>registerRoot(RemotionRoot)</code>，把根组件注册给 Remotion。</p>
<p><code>src/Root.tsx</code> 里声明"视频"——每个 <code>&lt;Composition&gt;</code> 就是一个可渲染的视频：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Composition</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">id</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">HelloWorld</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">component</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript maybe-class-name" style="color:#00009f">HelloWorld</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">durationInFrames</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript number" style="color:#36acaa">150</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f">   </span><span class="token tag comment" style="color:#999988;font-style:italic">// 150 帧</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">fps</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript number" style="color:#36acaa">30</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f">                 </span><span class="token tag comment" style="color:#999988;font-style:italic">// 30 帧/秒 → 5 秒</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">width</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript number" style="color:#36acaa">1920</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">height</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript number" style="color:#36acaa">1080</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">schema</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">myCompSchema</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f">    </span><span class="token tag comment" style="color:#999988;font-style:italic">// zod 定义参数，支持参数化渲染</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">defaultProps</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f"> titleText</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"Welcome to Remotion"</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript spread operator" style="color:#393A34">...</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f"></span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><br></div></code></pre></div></div>
<p><code>src/HelloWorld.tsx</code> 是这个视频的内容。动画的关键是 <strong>"每一帧是什么"</strong>——Remotion 会为第 0、1、2…149 帧各渲染一次组件：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> frame </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useCurrentFrame</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain">              </span><span class="token comment" style="color:#999988;font-style:italic">// 当前是第几帧 = 时间轴</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> fps </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useVideoConfig</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain">             </span><span class="token comment" style="color:#999988;font-style:italic">// 视频配置（fps、尺寸、时长）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// spring：弹簧动画，25 帧后从 0 弹到 1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> progress </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">spring</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> frame</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> frame </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">25</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> fps</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> config</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> damping</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">100</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// interpolate：把时间映射成数值（透明度、位移、缩放）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> opacity </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">interpolate</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">frame</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">25</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Sequence：把子组件"时间平移"，像剪辑轨</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Sequence</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">from</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript number" style="color:#36acaa">35</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Title</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">titleText</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript spread operator" style="color:#393A34">...</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">titleColor</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript spread operator" style="color:#393A34">...</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Sequence</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">AbsoluteFill</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">style</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f"> background</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"white"</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">...</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">AbsoluteFill</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p>所以"动画"就是<strong>用帧号驱动 React 样式</strong>：<code>spring</code> 给弹性缓动、<code>interpolate</code> 做数值映射、<code>Sequence</code> 编排出场时间。<code>Title.tsx</code> 里每个单词还会用 <code>spring</code> 错峰缩放，形成逐字入场的节奏。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3列出并渲染">Step 3：列出并渲染<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-react-video-quickstart#step-3%E5%88%97%E5%87%BA%E5%B9%B6%E6%B8%B2%E6%9F%93" class="hash-link" aria-label="Step 3：列出并渲染的直接链接" title="Step 3：列出并渲染的直接链接" translate="no">​</a></h2>
<p>先看有哪些视频：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npx remotion compositions src/index.ts</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># HelloWorld    30  1920x1080  150 (5.00 sec)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># OnlyLogo      30  1920x1080  150 (5.00 sec)</span><br></div></code></pre></div></div>
<p>再渲染出 MP4：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npx remotion render src/index.ts HelloWorld out/hello-world.mp4</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>关于浏览器</div><div class="admonitionContent_BuS1"><p>Remotion 渲染需要一个 headless Chrome 来执行 React 代码。首次运行它会自动下载 Chrome Headless Shell；如果网络受限，可以指定已有浏览器：<code>--browser-executable=/path/to/chrome-headless-shell</code>。本次在服务器上复用了已安装的 Chromium headless shell，150 帧全部渲染成功。</p></div></div>
<p>渲染日志摘要：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Rendered 149/150</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Encoded 150/150</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">+ out/hello-world.mp4  1.4 MB</span><br></div></code></pre></div></div>
<p>用 <code>ffprobe</code> 回读真实产物：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"codec_name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"h264"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"width"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1920</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"height"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1080</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"r_frame_rate"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"30/1"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"duration"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"5.056000"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"size"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"1367893"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4生成-poster">Step 4：生成 poster<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-react-video-quickstart#step-4%E7%94%9F%E6%88%90-poster" class="hash-link" aria-label="Step 4：生成 poster的直接链接" title="Step 4：生成 poster的直接链接" translate="no">​</a></h2>
<p>博客里直接放 <code>&lt;video&gt;</code> 时，配一张 poster 可以避免初始位置是黑框：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">ffmpeg -y -ss 00:00:02.5 -i out/hello-world.mp4 -frames:v 1 -q:v 2 out/hello-world-poster.jpg</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-5用-chatshare-发布-mp4">Step 5：用 ChatShare 发布 MP4<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-react-video-quickstart#step-5%E7%94%A8-chatshare-%E5%8F%91%E5%B8%83-mp4" class="hash-link" aria-label="Step 5：用 ChatShare 发布 MP4的直接链接" title="Step 5：用 ChatShare 发布 MP4的直接链接" translate="no">​</a></h2>
<p>视频放进 Git 仓库会让仓库膨胀。更自然的做法是作为媒体对象分享出去，文章只嵌入 URL。用 ChatShare：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">chatshare put --overwrite out/hello-world.mp4 chatblog/remotion-exploration/hello-world.mp4</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">chatshare put --overwrite out/hello-world-poster.jpg chatblog/remotion-exploration/hello-world-poster.jpg</span><br></div></code></pre></div></div>
<p>得到稳定 URL 并回读 HTTP 头：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">https://share.public.wzhecnu.cn/chatblog/remotion-exploration/hello-world.mp4</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  status=200  content-type=video/mp4   accept-ranges=bytes  content-length=1367893</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">https://share.public.wzhecnu.cn/chatblog/remotion-exploration/hello-world-poster.jpg</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  status=200  content-type=image/jpeg  accept-ranges=bytes  content-length=64324</span><br></div></code></pre></div></div>
<p><code>accept-ranges=bytes</code> 很关键：浏览器可以按需拉取和拖动进度，不必一次性下载整个文件。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-6在-chatblog-里嵌入视频">Step 6：在 ChatBlog 里嵌入视频<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-react-video-quickstart#step-6%E5%9C%A8-chatblog-%E9%87%8C%E5%B5%8C%E5%85%A5%E8%A7%86%E9%A2%91" class="hash-link" aria-label="Step 6：在 ChatBlog 里嵌入视频的直接链接" title="Step 6：在 ChatBlog 里嵌入视频的直接链接" translate="no">​</a></h2>
<p>ChatBlog 是 Docusaurus + MDX，可以直接写 HTML5 <code>&lt;video&gt;</code>：</p>
<div class="language-mdx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-mdx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">&lt;video controls preload="metadata"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  poster="https://share.public.wzhecnu.cn/chatblog/remotion-exploration/hello-world-poster.jpg"&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  &lt;source src="https://share.public.wzhecnu.cn/chatblog/remotion-exploration/hello-world.mp4" type="video/mp4" /&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  你的浏览器不支持 HTML5 video。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">&lt;/video&gt;</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="同类项目怎么选">同类项目怎么选<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-react-video-quickstart#%E5%90%8C%E7%B1%BB%E9%A1%B9%E7%9B%AE%E6%80%8E%E4%B9%88%E9%80%89" class="hash-link" aria-label="同类项目怎么选的直接链接" title="同类项目怎么选的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>项目</th><th>Star（2026-08 快照）</th><th>语言</th><th>定位</th></tr></thead><tbody><tr><td><strong>Remotion</strong></td><td>57.0K</td><td>TypeScript</td><td>React 代码 → 视频，可接数据批量出片、可嵌入应用</td></tr><tr><td>manim</td><td>91.9K</td><td>Python</td><td>数学/讲解动画引擎（3Blue1Brown）</td></tr><tr><td>motion-canvas</td><td>19.0K</td><td>TypeScript</td><td>Canvas 声明式动画 → 视频导出</td></tr><tr><td>p5.js</td><td>23.9K</td><td>JavaScript</td><td>网页创意编码（Processing 精神续作）</td></tr><tr><td>moviepy</td><td>14.9K</td><td>Python</td><td>Python 视频剪辑（ffmpeg 封装）</td></tr><tr><td>lottie-web</td><td>32.1K</td><td>JavaScript</td><td>After Effects 动画 → Web/移动端运行时</td></tr></tbody></table>
<p>如果目标是"用代码做能导出的视频"，<strong>Remotion 是 star 最多、生态最完整的 TypeScript 方案</strong>：官方 22 个模板、Studio 可视化编辑器、<code>@remotion/player</code> 嵌入应用、Lambda/Cloud Run 云端批量渲染、zod 参数化批量出片。Manim 偏数学/科普讲解视频，Motion Canvas 偏技术示意图动画。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="下一步">下一步<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-react-video-quickstart#%E4%B8%8B%E4%B8%80%E6%AD%A5" class="hash-link" aria-label="下一步的直接链接" title="下一步的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>层面</th><th>现在</th><th>下一步</th></tr></thead><tbody><tr><td>内容</td><td>官方 HelloWorld</td><td>改文字/配色/时长，做成自己的片头</td></tr><tr><td>参数化</td><td>defaultProps 写死</td><td>用 zod schema + 数据批量渲染多个版本</td></tr><tr><td>媒体</td><td>纯图形</td><td>加 Audio 配乐、Video 素材、字幕</td></tr><tr><td>交付</td><td>本地渲染</td><td>Lambda/Cloud Run 云端批量渲染</td></tr><tr><td>嵌入</td><td>独立 MP4</td><td>用 <code>@remotion/player</code> 把动画组件嵌进网页应用</td></tr></tbody></table>
<p>最小闭环跑通后，Remotion 就从"一个 React 库"变成了"可以持续产出视频的工作流"。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="相关链接">相关链接<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/remotion-react-video-quickstart#%E7%9B%B8%E5%85%B3%E9%93%BE%E6%8E%A5" class="hash-link" aria-label="相关链接的直接链接" title="相关链接的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://remotion.dev/" target="_blank" rel="noopener noreferrer" class="">Remotion 官网</a></li>
<li class=""><a href="https://remotion.dev/docs" target="_blank" rel="noopener noreferrer" class="">Remotion 文档</a></li>
<li class=""><a href="https://github.com/remotion-dev/remotion" target="_blank" rel="noopener noreferrer" class="">Remotion GitHub</a></li>
<li class=""><a href="https://share.public.wzhecnu.cn/chatblog/remotion-exploration/hello-world.mp4" target="_blank" rel="noopener noreferrer" class="">本文 demo MP4</a></li>
<li class=""><a class="" href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/manim-python-math-animation-video-pipeline">上一篇：Manim：用 Python 搭一条数学动画视频生产线</a></li>
</ul>]]></content>
        <category label="remotion" term="remotion"/>
        <category label="react" term="react"/>
        <category label="typescript" term="typescript"/>
        <category label="video" term="video"/>
        <category label="quickstart" term="quickstart"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Speakr：我们现在用的网页录音与实时语音工作台]]></title>
        <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog/speakr-realtime-voice-workspace</id>
        <link href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/speakr-realtime-voice-workspace"/>
        <updated>2026-08-17T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[介绍当前 Speakr 网站的会议录音、实时转写、摘要、文字转语音和实时语音对话，以及它在 Recall 服务器上的真实模型与部署方式。]]></summary>
        <content type="html"><![CDATA[<p>我们最早只是想做一个顺手的网页录音页：打开浏览器，允许麦克风，然后一边说一边看到文字。真正开始使用以后，事情很快变复杂了。录音能不能暂停，结束后能不能接着录，临时识别结果为什么会反复变化，两小时的会议会不会把浏览器或 GPU 撑满，这些都比“接一个语音模型”更接近真实问题。</p>
<p>现在这套网站放在 <a href="https://speakr.public.wzhecnu.cn/" target="_blank" rel="noopener noreferrer" class="">speakr.public.wzhecnu.cn</a>。页面中文名仍是“声笺”，代码来自我们持续改造的 Qwen Audio Demo 仓库。它目前有三个相互独立的工作区：会议记录、声音工作室和实时对话。</p>
<p>这里的 Speakr 指 ChatArch 当前部署的这套语音工作台，并不是把同名开源项目原样部署后换了一个页面。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="会议记录怎么用">会议记录怎么用<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/speakr-realtime-voice-workspace#%E4%BC%9A%E8%AE%AE%E8%AE%B0%E5%BD%95%E6%80%8E%E4%B9%88%E7%94%A8" class="hash-link" aria-label="会议记录怎么用的直接链接" title="会议记录怎么用的直接链接" translate="no">​</a></h2>
<p>第一次进入时，可以选访客模式，也可以用受邀账号登录。</p>
<ul>
<li class="">访客记录只保存在当前浏览器的 IndexedDB，单个录音段最多 10 分钟。</li>
<li class="">登录账号的会议文字和摘要会保存在服务器，可以跨设备打开，单个连续录音段最多 2 小时。</li>
<li class="">暂停不计入录音段时长。点结束以后，可以在同一条会议里继续录；打开旧会议也可以继续追加。</li>
<li class="">网站不开放自行注册。账号由管理员在服务器上创建。</li>
</ul>
<p>开始录音后，页面底部显示真实麦克风波形和本段剩余时间。文字区分成三层：</p>
<ol>
<li class=""><strong>正式记录</strong>：已经稳定的内容，只会继续增加，不会因为后面的短结果突然消失。</li>
<li class=""><strong>上下文回写</strong>：模型根据稍后的语音重新识别后，仍可能调整的最近几句。</li>
<li class=""><strong>实时识别</strong>：当前最新、最不稳定的一句话。</li>
</ol>
<p>结束录音时，最后一个识别窗口会先完成，再把文字并入正式记录。摘要页随后整理主题、行动项、风险和待确认问题。会议标题也会自动生成；如果用户自己改过标题，模型不会再覆盖它。</p>
<p>原始录音和转写是两条独立链路。MediaRecorder 每秒产生一个音频分片并写入浏览器 IndexedDB，不会把两小时音频一直堆在 JavaScript 内存里。用户点击下载时，浏览器才读取这些分片并组装成完整文件。转写失败不会直接带走本地录音。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="回写到底用了什么">“回写”到底用了什么<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/speakr-realtime-voice-workspace#%E5%9B%9E%E5%86%99%E5%88%B0%E5%BA%95%E7%94%A8%E4%BA%86%E4%BB%80%E4%B9%88" class="hash-link" aria-label="“回写”到底用了什么的直接链接" title="“回写”到底用了什么的直接链接" translate="no">​</a></h2>
<p>这里最容易产生误解。当前的上下文回写没有为每一句话再调用一次大语言模型。</p>
<p>浏览器把 16 kHz PCM 音频通过 WebSocket 发给服务端。服务端大约每 3 秒重新识别一次当前窗口，窗口长度控制在 42–45 秒。新结果会包含更多上下文，因此模型可能把刚才听错的词改回来。前端的 <code>TranscriptState</code> 再比较相邻版本，把稳定句子推到正式记录，把最近一句留在实时区，中间部分放进“回写中”。</p>
<p>这套做法有两个直接好处：</p>
<ul>
<li class="">回写只使用同一个 ASR 模型，不需要为高频临时文本持续支付大模型调用成本。</li>
<li class="">每次送进 GPU 的音频长度有上限。会议开两小时，识别窗口也不会增长到两小时。</li>
</ul>
<p>ASR 输出后还有一层很轻的本地整理，例如移除少量语气词、补齐末尾标点。它不是语义改写模型。真正的会议摘要才会进入大语言模型。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="声音工作室和实时对话">声音工作室和实时对话<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/speakr-realtime-voice-workspace#%E5%A3%B0%E9%9F%B3%E5%B7%A5%E4%BD%9C%E5%AE%A4%E5%92%8C%E5%AE%9E%E6%97%B6%E5%AF%B9%E8%AF%9D" class="hash-link" aria-label="声音工作室和实时对话的直接链接" title="声音工作室和实时对话的直接链接" translate="no">​</a></h2>
<p>声音工作室目前可以把文字合成为 MP3 或 WAV，内置龙安灵心、龙安鲁风两个音色，也可以填写已经创建好的自定义 voice id。生成后的音频可以直接试听和下载。</p>
<p>页面保留了声音复刻入口，不过当前正式部署没有配置独立的 DashScope Voice API Key，所以创建复刻音色的按钮会保持禁用。这个状态是服务端下发的，前端不会把未配置能力伪装成可用。</p>
<p>实时对话是另一条链路。浏览器持续发送麦克风音频，由服务端 VAD 判断说话开始和结束；模型返回文字和 24 kHz PCM 音频，网页边收边播放。用户重新开口时可以打断正在播放的回答。对话支持模型和音色选择、历史记录、搜索、删除以及 Markdown 导出。当前账号实际可用的 Realtime 模型只有一个，页面不会列出无法调用的占位选项。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="当前实际使用的模型">当前实际使用的模型<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/speakr-realtime-voice-workspace#%E5%BD%93%E5%89%8D%E5%AE%9E%E9%99%85%E4%BD%BF%E7%94%A8%E7%9A%84%E6%A8%A1%E5%9E%8B" class="hash-link" aria-label="当前实际使用的模型的直接链接" title="当前实际使用的模型的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>功能</th><th>当前模型或实现</th><th>运行位置</th></tr></thead><tbody><tr><td>会议语音识别</td><td>FunASR <code>iic/SenseVoiceSmall</code></td><td>Recall 服务器，CUDA <code>cuda:0</code></td></tr><tr><td>临近文字回写</td><td>同一个 SenseVoiceSmall 对 42–45 秒窗口重新识别，加前端 revision 状态机</td><td>Recall + 浏览器</td></tr><tr><td>ASR 轻量清理</td><td>本地规则：空白、少量语气词和标点整理</td><td>FastAPI 服务</td></tr><tr><td>会议摘要与整理</td><td><code>qwen3.8-max</code></td><td>千问兼容 API，经服务端调用</td></tr><tr><td>自动会议标题</td><td><code>qwen3.6-flash</code>，关闭思考模式</td><td>千问兼容 API，经服务端调用</td></tr><tr><td>文字转语音</td><td><code>qwen-audio-3.0-tts-plus</code></td><td>千问 TTS WebSocket API</td></tr><tr><td>实时语音对话</td><td><code>qwen-audio-3.0-realtime-plus</code></td><td>千问 Realtime WebSocket API</td></tr></tbody></table>
<p>API Key 只放在 Recall 服务器的私有环境文件中。浏览器访问的是我们自己的 FastAPI 接口，不会拿到供应商密钥。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="服务是怎么部署的">服务是怎么部署的<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/speakr-realtime-voice-workspace#%E6%9C%8D%E5%8A%A1%E6%98%AF%E6%80%8E%E4%B9%88%E9%83%A8%E7%BD%B2%E7%9A%84" class="hash-link" aria-label="服务是怎么部署的的直接链接" title="服务是怎么部署的的直接链接" translate="no">​</a></h2>
<!-- -->
<p>应用运行在 Recall 服务器上，由 FastAPI 同时提供页面、HTTP API 和 WebSocket。默认 ASR 在一张 RTX 2080 Ti 上运行，模型加载后留在进程缓存中；服务器还有第二张 GPU，但当前识别通道只使用 <code>cuda:0</code>。公网入口负责 HTTPS、WebSocket 升级和长连接超时，随后转发到 Recall。</p>
<p>登录账号的会议文字、摘要和实时对话文字保存在服务器 SQLite。访客模式不把这些内容写入服务器数据库，但音频和文字仍会在转写、摘要请求期间经过服务端处理。ASR 使用的临时 WAV 文件会在每次推理后删除，服务日志只记录分片序号、字节数、输出字数和耗时，不记录完整正文。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="我们实际测过什么">我们实际测过什么<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/speakr-realtime-voice-workspace#%E6%88%91%E4%BB%AC%E5%AE%9E%E9%99%85%E6%B5%8B%E8%BF%87%E4%BB%80%E4%B9%88" class="hash-link" aria-label="我们实际测过什么的直接链接" title="我们实际测过什么的直接链接" translate="no">​</a></h2>
<p>为了避免用“应该可以”代替验证，目前自动化覆盖了暂停、继续、结束后续录、打开旧会议追加、短 revision 回退、重复句、长句拆分和 40 次连续回写等场景。</p>
<p>长会议测试使用两小时 PCM 流做模拟，完成了 100 次以上上下文窗口轮换，并验证服务端音频历史保持有界。真实链路则用 Qwen TTS 生成中文测试音频，经公网 WebSocket 进入 GPU ASR，再调用 Qwen 生成摘要；最近一次测试得到 5 次累计 revision，最终转写与原文的归一化相似度为 1.0。</p>
<p>还有几件事没有做完：iPhone 和 Android 连续两小时的真机浸泡仍待执行；网页切到后台或锁屏后不承诺继续录音；当前没有说话人分离；声音复刻还缺正式密钥配置。这些边界会直接保留在项目记录里，不会写成已经解决。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="源码">源码<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/speakr-realtime-voice-workspace#%E6%BA%90%E7%A0%81" class="hash-link" aria-label="源码的直接链接" title="源码的直接链接" translate="no">​</a></h2>
<ul>
<li class="">网站：<a href="https://speakr.public.wzhecnu.cn/" target="_blank" rel="noopener noreferrer" class="">https://speakr.public.wzhecnu.cn/</a></li>
<li class="">GitHub：<a href="https://github.com/ChatArch/qwen-audio-tts-realtime-demo" target="_blank" rel="noopener noreferrer" class="">ChatArch/qwen-audio-tts-realtime-demo</a></li>
</ul>
<p>仓库里包含 FastAPI 后端、单页前端、FunASR worker、账号和会议存储，以及覆盖 UI、ASR、长会议和账号隔离的合同测试。仓库名还保留着早期 Demo 的历史痕迹，当前线上入口统一使用 Speakr。</p>]]></content>
        <category label="speakr" term="speakr"/>
        <category label="realtime-asr" term="realtime-asr"/>
        <category label="meeting-notes" term="meeting-notes"/>
        <category label="qwen-audio" term="qwen-audio"/>
        <category label="funasr" term="funasr"/>
        <category label="self-hosted" term="self-hosted"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[DeepSeek Harness 上手笔记：dsh、workspace、patch 和 Cordis]]></title>
        <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog/deepseek-harness-dsh-profile-plugin-cordis</id>
        <link href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/deepseek-harness-dsh-profile-plugin-cordis"/>
        <updated>2026-08-14T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[一次 DeepSeek Harness 实践记录：Node 22 运行时、dsh web、headless smoke、Web UI 修小仓库、workspace API 403、Cordis patch 插件验证。]]></summary>
        <content type="html"><![CDATA[<p>DeepSeek Harness 发布后，我花了一轮把它跑起来。版本组合是 Node <code>v22.19.0</code> 和 <code>@deepseek-ai/dsh@0.1.0-rc.6</code>。我跑了四段：启动 <code>dsh web</code>，用 headless 做最小 smoke；让 Web UI 修一个带测试的小仓库；处理一次 workspace API 403；再写一个最小 Cordis 插件，用 <code>--patch</code> 插进 headless profile。</p>
<p>这篇记录按实践顺序写。UI 能打开只能算入口。能读写 workspace、跑测试、留下 session/event、加载 patch 插件，才碰到了 Harness 的运行时。</p>
<p><img decoding="async" loading="lazy" alt="DeepSeek Harness editorial hero" src="https://arch.gh.wzhecnu.cn/ChatBlog/assets/images/deepseek-harness-editorial-hero-269efbab0bfd9093c7e768f10bdb691f.jpg" width="1536" height="1024" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="我实际跑过的路径">我实际跑过的路径<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/deepseek-harness-dsh-profile-plugin-cordis#%E6%88%91%E5%AE%9E%E9%99%85%E8%B7%91%E8%BF%87%E7%9A%84%E8%B7%AF%E5%BE%84" class="hash-link" aria-label="我实际跑过的路径的直接链接" title="我实际跑过的路径的直接链接" translate="no">​</a></h2>
<p>我没有改系统 Node。实践目录里单独放运行时，先确认版本，再装包：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">node -v</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npm install @deepseek-ai/dsh@0.1.0-rc.6</span><br></div></code></pre></div></div>
<p>Node <code>v24.x</code> 这条路先失败了，原因在 <code>node-pty</code> rebuild。切到 Node <code>v22.19.0</code> 后，安装和启动都顺了。</p>
<p>接着启动 Web UI：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">dsh web --host 127.0.0.1 --port 3080</span><br></div></code></pre></div></div>
<p>页面回读 HTTP 200 后，再跑一次 headless：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">dsh --profile headless "Reply with exactly OK."</span><br></div></code></pre></div></div>
<p>这一步很朴素。它检查 profile、provider、凭据加载和模型请求链路。网页壳能加载，headless 任务也能返回，底座才算站住。</p>
<p><img decoding="async" loading="lazy" alt="DeepSeek Harness practice evidence board" src="https://arch.gh.wzhecnu.cn/ChatBlog/assets/images/deepseek-harness-practice-evidence-board-e52b9511174b9c37e142a5a659be68bd.jpg" width="1536" height="1024" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="dsh-web-启动了一棵-cordis-plugin-tree"><code>dsh web</code> 启动了一棵 Cordis plugin tree<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/deepseek-harness-dsh-profile-plugin-cordis#dsh-web-%E5%90%AF%E5%8A%A8%E4%BA%86%E4%B8%80%E6%A3%B5-cordis-plugin-tree" class="hash-link" aria-label="dsh-web-启动了一棵-cordis-plugin-tree的直接链接" title="dsh-web-启动了一棵-cordis-plugin-tree的直接链接" translate="no">​</a></h2>
<p>官方 README 的入口命令很短：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npx @deepseek-ai/dsh web</span><br></div></code></pre></div></div>
<p>这个命令会选择 Web 相关的 profile 和 bundle，把模型 provider、工具、session、workspace、settings、client UI 装进同一棵 Cordis plugin tree。headless 入口也走 <code>dsh</code>，加载的是另一组能力。两个入口共用运行时概念，差别在 profile 选了什么、bundle 带了什么、patch 改了哪里。</p>
<p>profile 目录通常有两个文件：<code>package.json</code> 和 <code>cordis.patch.yml</code>。<code>package.json</code> 记录依赖和 <code>dsh.profile.bundles</code>，<code>cordis.patch.yml</code> 描述这个 profile 自己的装配改动。运行时会按顺序叠 bundle patch、profile patch、<code>$DSH_HOME/cordis.patch.yml</code>、命令行 <code>--patch</code>。后面的 patch 可以插入插件，也可以改前面已有的节点。</p>
<p>读 Harness 时，先把这几个词放到脑子里：</p>
<ul>
<li class=""><code>dsh</code>：启动器。</li>
<li class="">profile：一次运行的装配方案。</li>
<li class="">bundle：可复用能力包。</li>
<li class="">patch：改装运行时树的配置层。</li>
<li class="">Cordis plugin tree：最终激活的能力图。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="web-ui-任务修一个带测试的小仓库">Web UI 任务：修一个带测试的小仓库<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/deepseek-harness-dsh-profile-plugin-cordis#web-ui-%E4%BB%BB%E5%8A%A1%E4%BF%AE%E4%B8%80%E4%B8%AA%E5%B8%A6%E6%B5%8B%E8%AF%95%E7%9A%84%E5%B0%8F%E4%BB%93%E5%BA%93" class="hash-link" aria-label="Web UI 任务：修一个带测试的小仓库的直接链接" title="Web UI 任务：修一个带测试的小仓库的直接链接" translate="no">​</a></h2>
<p>我给 Harness 准备了一个小 Node.js 仓库。文件只有 <code>package.json</code>、<code>src/quote.js</code>、<code>test/quote.test.js</code>。里面有一个故意写错的 bug：<code>discountPercent</code> 传入 <code>10</code>，语义是 10%，代码却把它当成原始乘数。</p>
<p>给 Web UI 的任务很短：读项目文件，找出测试失败原因，修 <code>estimateQuote</code> 的百分比计算，运行 <code>npm test</code>，再写 <code>PRACTICE_RESULT.md</code> 记录改动和测试结果。</p>
<p>下面是实践回读的 Web UI。截图只保留页面主体：左侧有 <code>webui-workspace</code>，中间是任务输入和 Workspace Write 权限，历史会话里留下了那次修复百分比计算的任务。地址栏、机器名、内网地址、绝对路径、账号和凭据都没有出现在图里。</p>
<p><img decoding="async" loading="lazy" alt="DeepSeek Harness Web UI practice screenshot" src="https://arch.gh.wzhecnu.cn/ChatBlog/assets/images/deepseek-harness-web-ui-practice-72daa8850273b67805b777c80cd52b21.png" width="1280" height="720" class="img_ev3q"></p>
<p>这轮工具链能从日志里拼出来：注入上下文，读 <code>package.json</code>，读 <code>src/quote.js</code>，读 <code>test/quote.test.js</code>，改 <code>src/quote.js</code>，跑 <code>npm test</code>，写 <code>PRACTICE_RESULT.md</code>，最后回读结果。服务端确认两个测试通过，结果文件也存在。</p>
<p>我现在更信这类证据。模型自然语言报告只能当线索。文件 diff、测试退出码、结果文件、session 里的工具调用链合在一起，才构成一次可验收的 agent 任务。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="那次-403-排障">那次 403 排障<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/deepseek-harness-dsh-profile-plugin-cordis#%E9%82%A3%E6%AC%A1-403-%E6%8E%92%E9%9A%9C" class="hash-link" aria-label="那次 403 排障的直接链接" title="那次 403 排障的直接链接" translate="no">​</a></h2>
<p>Web UI 首次打开后，添加 workspace 失败。浏览器请求 <code>/api/host.listDirectory</code>，后端返回 HTTP 403。</p>
<p>排障点在 Host 和 Origin。浏览器带着公开入口的 <code>Origin</code>，后端看到的 <code>Host</code> 却被 public edge 改成了另一侧 authority。Harness 的 trust fence 拒绝了这次目录枚举。</p>
<p>修复方式很小：在 public edge 上给这个入口单独配 vhost，保留浏览器请求的 Host。随后带 Origin 的 <code>/api/host.listDirectory</code> 返回 HTTP 200，Web UI 的目录选择器恢复。</p>
<p>这段经历改变了我的验收方式。部署 Harness 时要检查页面、API、Host/Origin、workspace 边界和日志。它背后连着文件系统、shell、模型工具调用和权限策略，网页可见只覆盖了最外层。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="--patch-插件验证"><code>--patch</code> 插件验证<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/deepseek-harness-dsh-profile-plugin-cordis#--patch-%E6%8F%92%E4%BB%B6%E9%AA%8C%E8%AF%81" class="hash-link" aria-label="--patch-插件验证的直接链接" title="--patch-插件验证的直接链接" translate="no">​</a></h2>
<p>DeepSeek Harness 文档强调 Everything is a Plugin。我写了一个最小 Cordis 插件，用命令行 <code>--patch</code> 插入 headless profile。</p>
<p>插件只做两件小事。<code>apply(ctx)</code> 被调用时写出宿主侧 marker；同时通过 <code>ctx.systemPrompt.section()</code> 注入一段提示，让模型路径返回：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">DSH_PRACTICE_PLUGIN_ACTIVE</span><br></div></code></pre></div></div>
<p>运行日志里还留下：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">PLUGIN_EXTENSION_VERIFIED=1</span><br></div></code></pre></div></div>
<p>这里踩了两个细节。相对路径加载树外插件失败，因为 loader 按 profile 侧规则解析模块路径。改成 <code>file://&lt;plugin-path&gt;</code> 后插件进入树里。headless 单跑时还缺 provider 环境，后面复用 Web 启动时的安全加载方式，只检查 <code>present</code>，不打印任何密钥值。</p>
<p>这轮验证让我确认：<code>--patch</code> 贡献的是运行时配置。模块路径、provider 环境、profile 选择、Cordis 依赖等待，都在同一套装配规则里。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="turn-flow-才是调试入口">turn flow 才是调试入口<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/deepseek-harness-dsh-profile-plugin-cordis#turn-flow-%E6%89%8D%E6%98%AF%E8%B0%83%E8%AF%95%E5%85%A5%E5%8F%A3" class="hash-link" aria-label="turn flow 才是调试入口的直接链接" title="turn flow 才是调试入口的直接链接" translate="no">​</a></h2>
<p>官方架构文档里的 turn flow 很有用。一次 turn 从 <code>turn/start</code> 开始，运行时 claim 输入，拼 prompt section 和 tool schema，进入 <code>agent/pre-step</code>。step 开始后，用户消息写进 log，运行时从 session log 推导模型历史，发起 <code>agent/request</code>，接收 <code>llm/stream</code>，生成 assistant chunk 和 assistant message。</p>
<p>模型调用工具时，会经过 <code>tools/pre-execute</code>、<code>tools/execute</code>、<code>tools/post-execute</code>。工具结果再回到 session。step 结束后，turn stopping 决定是否继续。</p>
<p>这条链路把 agent 行动拆成很多可查点。<code>session/event</code> 尤其该盯。文档里的原则是：model-visible means logged。进入模型请求的内容，应该能从 log 重建。出问题时，我会先看 prompt 里有没有那段上下文、tool schema 是哪个版本、审批策略有没有拦命令、provider streaming 中间有没有断、UI 是否漏掉 session 里的事实。</p>
<p>Cordis 的作用也在这里显出来。插件可以参与 prompt assembly，可以影响 claimed messages，可以接入模型请求，可以挂工具执行前后的阶段。Harness 暴露的是 runtime seam：prompt、LLM、tool、session、workspace、sandbox、provider。接入这些位置会带来能力，也会带来权限和追踪负担。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cordis-给了检查插件系统的词">Cordis 给了检查插件系统的词<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/deepseek-harness-dsh-profile-plugin-cordis#cordis-%E7%BB%99%E4%BA%86%E6%A3%80%E6%9F%A5%E6%8F%92%E4%BB%B6%E7%B3%BB%E7%BB%9F%E7%9A%84%E8%AF%8D" class="hash-link" aria-label="Cordis 给了检查插件系统的词的直接链接" title="Cordis 给了检查插件系统的词的直接链接" translate="no">​</a></h2>
<p>DeepSeek Harness README 指向 Cordis 和论文《A Programming Paradigm for Spatiotemporal Composability》。读 Harness 时，我主要拿 Cordis 检查两件事。</p>
<p>Temporal composability 关心时间。插件注册 tool、监听事件、添加 prompt section、打开连接或挂 UI node，卸载时这些影响要能撤回。放到 agent runtime 里，就是工具、prompt、session hook、provider adapter、审批策略在 profile 切换或插件卸载后要干净退出。</p>
<p>Spatial composability 关心依赖空间。一个插件可能依赖模型 provider，另一个依赖 workspace provider，还有一个依赖 shell backend 或 sandbox policy。运行时要知道谁依赖谁，谁可以激活，谁要等待，依赖变化时谁需要停用或重接线。</p>
<p>Cordis 论文里的 revertible effects 和 reactive coeffects，解释了 Harness 为什么把 profile、bundle、patch、plugin tree 放到前台。effect 要能跟踪和撤销，coeffect 要能声明上下文依赖。对应到 Harness，就是工具注册在哪里、provider 谁提供、workspace 权限从哪里来、session hook 怎么撤、profile 切换是否干净。</p>
<p>Cordis 论文不能替代 Harness 实测，也不能证明 Harness 已经稳定。它提供的是一套检查语言。用它去看 Harness，我会问：effect 能否撤销，coeffect 能否声明，profile 切换有没有残留，plugin reload 后 session/event 是否还能回放，sandbox 和 workspace provider 边界是否清楚。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="我现在会怎么用它">我现在会怎么用它<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/deepseek-harness-dsh-profile-plugin-cordis#%E6%88%91%E7%8E%B0%E5%9C%A8%E4%BC%9A%E6%80%8E%E4%B9%88%E7%94%A8%E5%AE%83" class="hash-link" aria-label="我现在会怎么用它的直接链接" title="我现在会怎么用它的直接链接" translate="no">​</a></h2>
<p>DeepSeek Harness 现在适合研究 agent runtime，也适合在受控环境里做插件、profile、workspace 实验。官方已经标着 developer preview，并提醒会有 breaking changes。</p>
<p>我会按这条路径上手：先读 <code>docs/architecture.md</code>，看 turn flow、session log、prompt assembly、tool pipeline、provider seam 和 sandbox；再跑 headless smoke；准备一个小仓库，让 Web UI 做可回读修改和测试；最后写一个 marker 插件，用 <code>--patch</code> 证明它进入宿主和模型路径。</p>
<p>现在的判断很克制：DeepSeek Harness 还早，但它把 agent 工程里那些麻烦问题摊到了运行时层面。一次错误工具调用怎么追踪，插件怎么升级，workspace 权限怎么收口，团队 profile 怎么复用，session 怎样回放，模型上下文怎样解释，这些问题都能沿着 Harness 的 runtime seam 去查。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="参考链接">参考链接<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/deepseek-harness-dsh-profile-plugin-cordis#%E5%8F%82%E8%80%83%E9%93%BE%E6%8E%A5" class="hash-link" aria-label="参考链接的直接链接" title="参考链接的直接链接" translate="no">​</a></h2>
<ul>
<li class="">DeepSeek Harness 官方仓库：<a href="https://github.com/deepseek-ai/deepseek-harness" target="_blank" rel="noopener noreferrer" class="">https://github.com/deepseek-ai/deepseek-harness</a></li>
<li class="">DeepSeek Harness Architecture：<a href="https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/architecture.md" target="_blank" rel="noopener noreferrer" class="">https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/architecture.md</a></li>
<li class="">DeepSeek Harness User Guide：<a href="https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/guide/index.md" target="_blank" rel="noopener noreferrer" class="">https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/guide/index.md</a></li>
<li class="">DeepSeek Harness First Plugin：<a href="https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/index.md" target="_blank" rel="noopener noreferrer" class="">https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/index.md</a></li>
<li class="">DeepSeek Harness Capability Seams：<a href="https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/capability-seams.md" target="_blank" rel="noopener noreferrer" class="">https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/capability-seams.md</a></li>
<li class="">Cordis 论文仓库：<a href="https://github.com/cordiverse/paper" target="_blank" rel="noopener noreferrer" class="">https://github.com/cordiverse/paper</a></li>
</ul>]]></content>
        <category label="deepseek" term="deepseek"/>
        <category label="agent" term="agent"/>
        <category label="runtime" term="runtime"/>
        <category label="plugin" term="plugin"/>
        <category label="cordis" term="cordis"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[ChatGlance 页面开发模式：把 Glance Dashboard 做成可审查的页面流水线]]></title>
        <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog/chatglance-page-development-pattern</id>
        <link href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/chatglance-page-development-pattern"/>
        <updated>2026-08-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[介绍 ChatGlance 如何把 Glance 的页面配置、数据快照、渲染脚本和 live 验证组织成可测试、可回滚、可持续扩展的 Dashboard 页面开发模式。]]></summary>
        <content type="html"><![CDATA[<p>如果要给 ChatGlance 增加一个新标签页，最重要的问题不是“要不要重写一个前端”，而是：<strong>这个页面的数据从哪里来、怎么变成 Glance 能消费的配置、怎么验证不会把坏配置或敏感信息发布到 live 站点。</strong></p>
<p>ChatGlance 的答案是把页面做成一条 repo-owned pipeline：源码负责生成和验证页面，runtime 只保存可再生成的数据快照，真正的 Web 服务仍由 upstream Glance 读取 <code>glance.yml</code> 提供。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>一句话结论</div><div class="admonitionContent_BuS1"><p>ChatGlance 的核心不是替代 Glance，而是把 <code>glance.yml</code>、页面 YAML、HTML widget、数据快照、刷新脚本和发布验证变成一套可审查、可测试、可回滚的工程流程。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="先把边界说清楚">先把边界说清楚<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/chatglance-page-development-pattern#%E5%85%88%E6%8A%8A%E8%BE%B9%E7%95%8C%E8%AF%B4%E6%B8%85%E6%A5%9A" class="hash-link" aria-label="先把边界说清楚的直接链接" title="先把边界说清楚的直接链接" translate="no">​</a></h2>
<p>Glance 本身是一个 dashboard server。它读取 YAML 配置，渲染页面、columns、widgets。ChatGlance 不重新实现这个 server，也不把站点变成一个新的 React/NPM 前端项目。</p>
<p>更准确地说，ChatGlance 做的是三件事：</p>
<ol>
<li class=""><strong>生成页面</strong>：把项目清单、服务器状态、网站服务、账号额度等数据渲染为 Glance page YAML，通常通过 <code>html</code> widget 放入可控的 HTML/CSS。</li>
<li class=""><strong>更新配置</strong>：把生成页面插入一个 candidate <code>glance.yml</code>，并移除旧的 generated page，保持导航顺序稳定。</li>
<li class=""><strong>保护 live 更新</strong>：先校验 candidate，再备份旧文件，最后一次性替换数据、页面和配置；服务生命周期交给外层执行。</li>
</ol>
<p>也就是说，ChatGlance 是“Glance 配置与页面生成的工程化层”，不是另一个 dashboard runtime。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="页面流水线长什么样">页面流水线长什么样<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/chatglance-page-development-pattern#%E9%A1%B5%E9%9D%A2%E6%B5%81%E6%B0%B4%E7%BA%BF%E9%95%BF%E4%BB%80%E4%B9%88%E6%A0%B7" class="hash-link" aria-label="页面流水线长什么样的直接链接" title="页面流水线长什么样的直接链接" translate="no">​</a></h2>
<p>一个 durable 页面通常遵循下面的流程：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">reviewed source / runtime source</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; collect or normalize data</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; render generated page YAML</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; update a candidate glance.yml copy</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; validate candidate with upstream Glance</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; backup old artifacts</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; replace data/page/config together</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; let an outer scheduler/operator handle service lifecycle</span><br></div></code></pre></div></div>
<p>这里有两个关键词。</p>
<p>第一个是 <strong>candidate</strong>。页面刷新不应该直接改 live <code>glance.yml</code>。先生成候选配置，再用 Glance 自己的 <code>config:validate</code> 校验。只有校验通过，才允许替换 live 文件。</p>
<p>第二个是 <strong>together</strong>。数据 JSON、生成 page YAML、完整 <code>glance.yml</code> 必须作为同一批产物推进。否则很容易出现“页面 YAML 是新的，但数据还是旧的”或“配置已经引用新页面，但对应数据没刷新”的半更新状态。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="为什么要拆成-collectrender-pageupdate-config">为什么要拆成 collect、render-page、update-config<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/chatglance-page-development-pattern#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E6%8B%86%E6%88%90-collectrender-pageupdate-config" class="hash-link" aria-label="为什么要拆成 collect、render-page、update-config的直接链接" title="为什么要拆成 collect、render-page、update-config的直接链接" translate="no">​</a></h2>
<p>ChatGlance 的 CLI surface 基本按资源拆成三个动作：</p>
<table><thead><tr><th>动作</th><th>作用</th><th>输出</th></tr></thead><tbody><tr><td><code>collect</code> / <code>json</code></td><td>采集或规范化数据</td><td>JSON snapshot</td></tr><tr><td><code>render-page</code></td><td>把 JSON 渲染为 Glance page object</td><td>page YAML</td></tr><tr><td><code>update-config</code></td><td>把 page 替换进 <code>glance.yml</code> candidate</td><td>full candidate config</td></tr></tbody></table>
<p>这样的拆分让每一层都可以单独测试：</p>
<ul>
<li class="">数据层可以测试 schema、脱敏、排序、失败状态；</li>
<li class="">renderer 可以用 fixture 直接检查 HTML/CSS 是否符合产品要求；</li>
<li class="">config patcher 可以检查 page 顺序、legacy page 移除、是否只改目标页面；</li>
<li class="">refresh script 可以检查 staging、validate、backup、replace 是否按顺序发生。</li>
</ul>
<p>脚本再把这些动作串起来，例如：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">scripts/refresh-projects-page.sh</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">scripts/refresh-server-status.sh</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">scripts/refresh-sites-page.sh</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">scripts/refresh-account-limits-page.sh</span><br></div></code></pre></div></div>
<p>这比把所有逻辑藏在一个不可测试的 “deploy” 命令里更容易 review，也更容易在失败时定位是哪一层出问题。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="当前几类页面的模式">当前几类页面的模式<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/chatglance-page-development-pattern#%E5%BD%93%E5%89%8D%E5%87%A0%E7%B1%BB%E9%A1%B5%E9%9D%A2%E7%9A%84%E6%A8%A1%E5%BC%8F" class="hash-link" aria-label="当前几类页面的模式的直接链接" title="当前几类页面的模式的直接链接" translate="no">​</a></h2>
<p>ChatGlance 已经有几类页面，它们展示了同一套模式在不同数据源上的用法。</p>
<table><thead><tr><th>页面</th><th>数据来源</th><th>页面重点</th><th>特别边界</th></tr></thead><tbody><tr><td><code>项目</code></td><td>仓库 inventory、版本和 CLI 证据</td><td>最近提交、PR/Issue、分类、一览表</td><td>版本展示走 PyPI-only；紧凑表格只放 entrypoint，不展开所有子命令</td></tr><tr><td><code>服务器</code></td><td>reviewed inventory + 只读 SSH probe</td><td>IP、CPU、内存、磁盘、状态、重启时间</td><td>probe 不安装包、不写远端文件；离线回退要 fail closed</td></tr><tr><td><code>网站服务</code></td><td>reviewed service inventory + Uptime 状态</td><td>服务卡片、封面、public 入口、监控状态</td><td>不自动扫所有 Nginx vhost；local host 只作为运维/probe 信息</td></tr><tr><td><code>账号额度</code></td><td>credentialed probe 的安全派生数据 + 公共 reset tracker</td><td>左侧 reset calendar、右侧账号使用卡</td><td>不渲染 token、profile 明细、raw stderr、proxy 或诊断表</td></tr></tbody></table>
<p>这些页面不是同一种数据，但它们的工程契约一致：<strong>数据可解释、页面可重建、配置可校验、live 更新可回滚。</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="config-patcher-要-copy-on-write">Config patcher 要 copy-on-write<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/chatglance-page-development-pattern#config-patcher-%E8%A6%81-copy-on-write" class="hash-link" aria-label="Config patcher 要 copy-on-write的直接链接" title="Config patcher 要 copy-on-write的直接链接" translate="no">​</a></h2>
<p>一个常见反模式是直接打开 live <code>glance.yml</code> 手工改。短期看很快，长期看会让站点变成“谁最后手改谁知道”的状态。</p>
<p>ChatGlance 的 config patcher 应该做 copy-on-write：</p>
<ol>
<li class="">读取当前 <code>glance.yml</code>；</li>
<li class="">deepcopy 配置；</li>
<li class="">构造目标 generated page；</li>
<li class="">移除同类 legacy/generated page；</li>
<li class="">把新页面插到稳定位置；</li>
<li class="">写出 candidate config；</li>
<li class="">交给 Glance 校验。</li>
</ol>
<p>这样做还有一个好处：页面迁移时可以同时清理旧名字。例如 <code>Projects</code>、<code>ChatArch Projects</code>、<code>ChatArch Projects List</code> 都可以被视作同一类历史 generated page，在替换时统一移除，避免导航里出现重复标签页。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="renderer-不应该变成诊断-dump">Renderer 不应该变成诊断 dump<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/chatglance-page-development-pattern#renderer-%E4%B8%8D%E5%BA%94%E8%AF%A5%E5%8F%98%E6%88%90%E8%AF%8A%E6%96%AD-dump" class="hash-link" aria-label="Renderer 不应该变成诊断 dump的直接链接" title="Renderer 不应该变成诊断 dump的直接链接" translate="no">​</a></h2>
<p>Glance 的 <code>html</code> widget 很自由，能放表格、卡片、tabs、calendar、进度条和样式。这种自由度很有用，但也带来一个风险：为了调试方便，把所有 raw data 都塞到页面里。</p>
<p>人类 Dashboard 页面应该回答的是：</p>
<ul>
<li class="">现在状态是什么？</li>
<li class="">哪些项需要注意？</li>
<li class="">数据什么时候刷新？</li>
<li class="">点击哪里能继续查看公开/安全的详情？</li>
</ul>
<p>它不应该默认展示：</p>
<ul>
<li class="">token、cookie、auth header、proxy URL；</li>
<li class="">profile 列表、credential source、raw account diagnostic；</li>
<li class="">full stderr/stdout；</li>
<li class="">私有机器路径和内部配置文件内容；</li>
<li class="">未经校验的外部 URL。</li>
</ul>
<p>诊断证据可以进入独立的 JSON/TSV/report artifact，但 renderer 要有明确的“人类可读”边界。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="刷新脚本不是随手写的-shell">刷新脚本不是随手写的 shell<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/chatglance-page-development-pattern#%E5%88%B7%E6%96%B0%E8%84%9A%E6%9C%AC%E4%B8%8D%E6%98%AF%E9%9A%8F%E6%89%8B%E5%86%99%E7%9A%84-shell" class="hash-link" aria-label="刷新脚本不是随手写的 shell的直接链接" title="刷新脚本不是随手写的 shell的直接链接" translate="no">​</a></h2>
<p>一个页面一旦进入 live，就应该有 repo-owned refresh script，而不是依赖某次会话里的手工命令串。</p>
<p>一个合格的 refresh script 至少应该满足：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">set -euo pipefail</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">set +x  # when credentials or proxy settings may be present</span><br></div></code></pre></div></div>
<p>并且遵守这些规则：</p>
<ul>
<li class="">所有新产物先写 <code>.next</code> 或 candidate path；</li>
<li class="">candidate config 必须用 upstream Glance 校验；</li>
<li class="">校验通过前不替换 live 文件；</li>
<li class="">替换前备份旧 config/data/page；</li>
<li class="">失败时保留旧 live 状态；</li>
<li class="">不 echo token、proxy、password、cookie；</li>
<li class="">不用 <code>eval</code> 解释 proxy/helper 输出；</li>
<li class="">不把 service restart 藏在数据生成逻辑里。</li>
</ul>
<p><code>service_action=external</code> 这类输出很重要：它把“页面刷新完成”和“服务何时重载/重启”分开，让 cron、systemd timer 或人工操作能在更外层做生命周期决策。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="新增一个标签页的推荐步骤">新增一个标签页的推荐步骤<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/chatglance-page-development-pattern#%E6%96%B0%E5%A2%9E%E4%B8%80%E4%B8%AA%E6%A0%87%E7%AD%BE%E9%A1%B5%E7%9A%84%E6%8E%A8%E8%8D%90%E6%AD%A5%E9%AA%A4" class="hash-link" aria-label="新增一个标签页的推荐步骤的直接链接" title="新增一个标签页的推荐步骤的直接链接" translate="no">​</a></h2>
<p>假设要新增一个 <code>certificates</code> 标签页，建议从下面这条路径开始，而不是先写 HTML：</p>
<ol>
<li class=""><strong>写 PRD</strong>：这个页面回答什么问题？谁会看？哪些字段可以公开？哪些字段必须留在 runtime-only？</li>
<li class=""><strong>定义安全数据 schema</strong>：例如证书页可以展示 SAN、issuer、not_after、days remaining、public TLS probe 状态；不能展示私钥、ACME account、secret 文件内容。</li>
<li class=""><strong>写 fixture 和 RED tests</strong>：先固定期望页面结构、排序、失败状态、脱敏规则。</li>
<li class=""><strong>实现 renderer</strong>：新增 <code>src/chatglance/certificates.py</code>，提供 <code>load_*_data</code>、<code>build_*_page</code>、<code>replace_*_page</code>。</li>
<li class=""><strong>接入 CLI</strong>：增加 <code>chatglance certificates render-page/update-config</code>，如需要再加 <code>collect</code>。</li>
<li class=""><strong>写 refresh script</strong>：<code>scripts/refresh-certificates-page.sh</code> 只负责 collect、render、candidate、validate、backup、replace。</li>
<li class=""><strong>补 docs/examples</strong>：runtime inventory 用脱敏 example 表达；真实 inventory 不进仓库。</li>
<li class=""><strong>跑 gates</strong>：<code>chatglance --tree</code>、targeted tests、full tests、build/compile、<code>git diff --check</code>、secret scan。</li>
<li class=""><strong>live readback</strong>：刷新后结构化解析 generated page YAML 的 <code>columns[].widgets[].source</code>，不要只 grep folded YAML。</li>
</ol>
<p>这个流程的价值是把“新增页面”变成一个 reviewable change，而不是一次不可复现的现场配置。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="account-limits-页带来的几个经验">Account-limits 页带来的几个经验<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/chatglance-page-development-pattern#account-limits-%E9%A1%B5%E5%B8%A6%E6%9D%A5%E7%9A%84%E5%87%A0%E4%B8%AA%E7%BB%8F%E9%AA%8C" class="hash-link" aria-label="Account-limits 页带来的几个经验的直接链接" title="Account-limits 页带来的几个经验的直接链接" translate="no">​</a></h2>
<p>账号额度页是一个很好的反例/正例结合案例，因为它同时碰到 UI、时间、外部来源和凭据边界。</p>
<p>几个经验可以复用到后续页面：</p>
<ul>
<li class=""><strong>公共来源和账号窗口要分开</strong>：官方/公共 reset tracker 是独立数据 section，不能把每个账号采样到的 reset window 冒充成官方日历。</li>
<li class=""><strong>用户页面只放决策信息</strong>：紧凑页面里保留 reset calendar、使用进度和重置时间；raw diagnostic 留给安全报告，不进卡片。</li>
<li class=""><strong>时间展示不要依赖机器时区</strong>：如果面向北京时区用户，就显式转换并测试 <code>TZ=UTC</code> 下仍然稳定。</li>
<li class=""><strong>外部 URL 要校验 scheme</strong>：不安全的 URL 不渲染成链接。</li>
<li class=""><strong>stderr 进入数据前要脱敏</strong>：截断不是脱敏；任何 token/auth/proxy 样式内容都应先 redaction。</li>
<li class=""><strong>proxy helper 输出只能 allow-list parse</strong>：不要用 <code>eval</code> 导入一段 shell 文本。</li>
</ul>
<p>这些不是只属于账号额度页的规则，而是所有“有外部 probe / credential / runtime data”的页面都应该遵守的基本安全线。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="什么应该进仓库什么不应该进仓库">什么应该进仓库，什么不应该进仓库<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/chatglance-page-development-pattern#%E4%BB%80%E4%B9%88%E5%BA%94%E8%AF%A5%E8%BF%9B%E4%BB%93%E5%BA%93%E4%BB%80%E4%B9%88%E4%B8%8D%E5%BA%94%E8%AF%A5%E8%BF%9B%E4%BB%93%E5%BA%93" class="hash-link" aria-label="什么应该进仓库，什么不应该进仓库的直接链接" title="什么应该进仓库，什么不应该进仓库的直接链接" translate="no">​</a></h2>
<p>推荐边界如下：</p>
<table><thead><tr><th>应该进仓库</th><th>不应该进仓库</th></tr></thead><tbody><tr><td>renderer 源码</td><td>live auth config</td></tr><tr><td>config patcher</td><td>token / cookie / password</td></tr><tr><td>refresh script 模板</td><td>proxy credential</td></tr><tr><td>sanitized example inventory</td><td>raw runtime backup</td></tr><tr><td>docs / contract / tests</td><td>credentialed raw probe output</td></tr><tr><td>fixture data</td><td>私钥、ACME account、完整 SSH config</td></tr></tbody></table>
<p>这个边界能让 ChatGlance 同时具备两种能力：</p>
<ul>
<li class="">源码仓库可以公开 review 页面逻辑和安全策略；</li>
<li class="">live runtime 可以保留实际部署所需的私有状态和数据快照。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="结语">结语<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/chatglance-page-development-pattern#%E7%BB%93%E8%AF%AD" class="hash-link" aria-label="结语的直接链接" title="结语的直接链接" translate="no">​</a></h2>
<p>ChatGlance 的页面开发模式可以总结成一句话：<strong>不要把 dashboard 当成一次性配置文件，而要把它当成一条可测试的数据产品流水线。</strong></p>
<p>当页面有明确的数据契约、renderer、config patcher、refresh script、candidate validation、backup/replace 和 live readback 时，新标签页就不再是“在服务器上手工改一段 YAML”，而是一个可以 code review、可以 CI 验证、可以回滚、可以持续演进的 ChatArch 组件。</p>
<p>这也是后续扩展证书、账单、资源用量、队列状态等页面时最值得复用的模式：先定义安全数据，再生成人类可读页面，最后用真实验证守住 live 边界。</p>]]></content>
        <category label="chatglance" term="chatglance"/>
        <category label="dashboard" term="dashboard"/>
        <category label="glance" term="glance"/>
        <category label="operations" term="operations"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[从 17 万条 Zulip 消息看 Agent 社区应该怎么组织]]></title>
        <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns</id>
        <link href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns"/>
        <updated>2026-08-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[基于一次 Zulip 全量可见消息导出，分析 Stream、Topic、Thread、机器 feed、评审协作和新人路由如何共同构成一个可搜索、可追踪、可收束的技术社区，并提炼给自建 Agent 社区的频道设计建议。]]></summary>
        <content type="html"><![CDATA[<p>如果要搭一个“人和 Agent 一起工作的社区”，问题不只是选 Discourse、Discord、Zulip 还是 Mattermost。真正难的是：<strong>大家平常到底聊什么？这些话题放在哪里？机器人应该怎么参与？一次讨论怎样从闲聊变成可追踪的任务、评审和知识沉淀？</strong></p>
<p>为了回答这个问题，我们把一个真实 Zulip 技术社区在当前 API 可见范围内的消息拉成 SQLite，做了一次结构分析。结论很直接：高质量协作社区不是靠频道多，而是靠 <strong>少量稳定 Stream + 大量具体 Topic/Thread + 明确的机器/人类边界</strong>。</p>
<p>这篇文章不是 Zulip 运维教程，而是从 chat log 反推社区设计：如果我们要自建一个由 Agent 参与、也允许人类加入的 Zulip 社区，哪些结构值得直接借鉴，哪些地方必须重新设计。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="这次分析了什么">这次分析了什么<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#%E8%BF%99%E6%AC%A1%E5%88%86%E6%9E%90%E4%BA%86%E4%BB%80%E4%B9%88" class="hash-link" aria-label="这次分析了什么的直接链接" title="这次分析了什么的直接链接" translate="no">​</a></h2>
<p>这次数据来自 Zulip API 导出的、当前机器人账号可见范围内的消息。分析口径是 Zulip 的实际层级：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Organization -&gt; Stream / Channel -&gt; Topic / Thread -&gt; Message</span><br></div></code></pre></div></div>
<p>数据规模如下：</p>
<table><thead><tr><th>指标</th><th style="text-align:right">数值</th></tr></thead><tbody><tr><td>总消息数</td><td style="text-align:right">169,555</td></tr><tr><td>Stream / Channel 消息</td><td style="text-align:right">169,526</td></tr><tr><td>Private / Direct 消息</td><td style="text-align:right">29</td></tr><tr><td>活跃 Stream</td><td style="text-align:right">40</td></tr><tr><td>Stream-Topic / Thread</td><td style="text-align:right">11,083</td></tr><tr><td>发送者</td><td style="text-align:right">2,586</td></tr><tr><td>Top 6 Stream 占比</td><td style="text-align:right">82.3%</td></tr></tbody></table>
<p>这个规模足够说明一个成熟技术 Zulip 的形态：它不是平均分散在很多频道里，而是明显呈现“核心广场 + 专门工作间 + 自动 feed + 项目房间”的组合。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="zulip-的关键不是-channel而是-topic">Zulip 的关键不是 Channel，而是 Topic<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#zulip-%E7%9A%84%E5%85%B3%E9%94%AE%E4%B8%8D%E6%98%AF-channel%E8%80%8C%E6%98%AF-topic" class="hash-link" aria-label="Zulip 的关键不是 Channel，而是 Topic的直接链接" title="Zulip 的关键不是 Channel，而是 Topic的直接链接" translate="no">​</a></h2>
<p>很多人第一次看聊天系统，会先问“要建多少频道”。但 Zulip 的关键点其实不是频道数量，而是 Topic。</p>
<p>Channel / Stream 是长期职责：比如主工程、通用问答、新人、评审、机器通知、AI 工具、项目协作。</p>
<p>Topic / Thread 才是具体工作单元：一个问题、一个 PR、一个工具公告、一个设计争议、一次 benchmark、一个新人目标、一个长期项目的子任务。</p>
<p>这次统计里，Thread 的消息数分布大概是：</p>
<table><thead><tr><th>Thread 消息数</th><th style="text-align:right">Thread 数</th><th style="text-align:right">消息数</th><th style="text-align:right">消息占比</th></tr></thead><tbody><tr><td>1</td><td style="text-align:right">1,206</td><td style="text-align:right">1,206</td><td style="text-align:right">0.7%</td></tr><tr><td>2–5</td><td style="text-align:right">3,677</td><td style="text-align:right">12,555</td><td style="text-align:right">7.4%</td></tr><tr><td>6–20</td><td style="text-align:right">4,532</td><td style="text-align:right">48,241</td><td style="text-align:right">28.5%</td></tr><tr><td>21–100</td><td style="text-align:right">1,543</td><td style="text-align:right">59,097</td><td style="text-align:right">34.9%</td></tr><tr><td>101+</td><td style="text-align:right">125</td><td style="text-align:right">48,427</td><td style="text-align:right">28.6%</td></tr></tbody></table>
<p>这很有启发：大多数 Topic 不是一句话就结束，也不是无限滚动的频道水流，而是 6 到 100 条之间的局部协作。它们足够短，可以搜索和回看；也足够长，可以完成解释、争论、代码片段、链接、结论和后续行动。</p>
<p>对 Agent 社区来说，这意味着：不要让 Agent 在 <code>general</code> 里不断刷状态。每次执行、评审、提问、决策都应该有自己的 Topic。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一个成熟技术社区的六类-stream">一个成熟技术社区的六类 Stream<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#%E4%B8%80%E4%B8%AA%E6%88%90%E7%86%9F%E6%8A%80%E6%9C%AF%E7%A4%BE%E5%8C%BA%E7%9A%84%E5%85%AD%E7%B1%BB-stream" class="hash-link" aria-label="一个成熟技术社区的六类 Stream的直接链接" title="一个成熟技术社区的六类 Stream的直接链接" translate="no">​</a></h2>
<p>从 Stream 结构看，比较值得迁移的不是具体名字，而是功能分层。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-核心技术--主工程流">1. 核心技术 / 主工程流<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#1-%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF--%E4%B8%BB%E5%B7%A5%E7%A8%8B%E6%B5%81" class="hash-link" aria-label="1. 核心技术 / 主工程流的直接链接" title="1. 核心技术 / 主工程流的直接链接" translate="no">​</a></h3>
<p>典型 Stream 包括主库、语言、工具、标准库、metaprogramming / tactics 等。</p>
<p>这些地方承担长期技术讨论：API 设计、命名、迁移、bug、性能、兼容性、抽象边界。它们不是“支持频道”，而是主工程的设计现场。</p>
<p>对 Agent 社区来说，对应的是：</p>
<table><thead><tr><th>推荐 Stream</th><th>用途</th></tr></thead><tbody><tr><td><code>agent-tools-prompting</code></td><td>Prompt、Skill、MCP、工具调用、Agent 工作流</td></tr><tr><td><code>runtime-infra</code></td><td>Agent runtime、gateway、队列、权限、执行环境</td></tr><tr><td><code>projects</code></td><td>长期项目入口；高流量项目再拆独立 Stream</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-公共问答与知识路由">2. 公共问答与知识路由<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#2-%E5%85%AC%E5%85%B1%E9%97%AE%E7%AD%94%E4%B8%8E%E7%9F%A5%E8%AF%86%E8%B7%AF%E7%94%B1" class="hash-link" aria-label="2. 公共问答与知识路由的直接链接" title="2. 公共问答与知识路由的直接链接" translate="no">​</a></h3>
<p>最值得借鉴的是类似 <code>Is there code for X?</code> 的频道。它的描述可以概括成：“人肉库搜索”。</p>
<p>这类频道解决的是技术社区里最常见的问题：我知道自己要一个能力、定理、工具或 precedent，但不知道它在哪里。</p>
<p>这对 Agent 社区非常重要。我们可以直接迁移成：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">capability-search</span><br></div></code></pre></div></div>
<p>Topic 命名可以是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Is there tool/data/code for X?</span><br></div></code></pre></div></div>
<p>这里的 Agent 不应该泛泛聊天，而应该回答：</p>
<ul>
<li class="">已有能力在哪里；</li>
<li class="">相关代码、文档、报告、PR、Issue 是什么；</li>
<li class="">有没有类似 precedent；</li>
<li class="">如果没有，缺口是什么；</li>
<li class="">下一步应该开 task、写 skill、还是做 infra。</li>
</ul>
<p>这会让社区逐渐形成一张“能力地图”。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-新人和新-agent-的-onboarding">3. 新人和新 Agent 的 Onboarding<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#3-%E6%96%B0%E4%BA%BA%E5%92%8C%E6%96%B0-agent-%E7%9A%84-onboarding" class="hash-link" aria-label="3. 新人和新 Agent 的 Onboarding的直接链接" title="3. 新人和新 Agent 的 Onboarding的直接链接" translate="no">​</a></h3>
<p><code>new members</code> 不是寒暄频道。它的真实功能是路由。</p>
<p>一个典型模式是：新人介绍自己、说明背景和目标；老成员追问“你到底想证明什么 / 构建什么 / 运行什么”；然后把人指向对应资源、库、项目、频道或 Topic。</p>
<p>Agent 社区也需要这个机制。新加入的不只是人，还包括新 Agent：worker、reviewer、router、summarizer、feed-bot。它们都需要说明：</p>
<ul>
<li class="">我是谁；</li>
<li class="">我能做什么；</li>
<li class="">我不能做什么；</li>
<li class="">我需要哪些权限；</li>
<li class="">我的输出应该发到哪里；</li>
<li class="">我什么时候需要 human approval。</li>
</ul>
<p>所以可以保留：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">new-members</span><br></div></code></pre></div></div>
<p>但里面的 Topic 不只写人名，也可以写 Agent 名：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">router-agent onboarding</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">reviewer-agent onboarding</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-pr--review--ci-协作流">4. PR / Review / CI 协作流<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#4-pr--review--ci-%E5%8D%8F%E4%BD%9C%E6%B5%81" class="hash-link" aria-label="4. PR / Review / CI 协作流的直接链接" title="4. PR / Review / CI 协作流的直接链接" translate="no">​</a></h3>
<p>成熟工程社区不会把评审藏在几个短评论里。类似 <code>PR reviews</code> 的 Stream 会围绕 PR 号开 Topic，讨论：</p>
<ul>
<li class="">CI 为什么失败；</li>
<li class="">unused import 是否真的 unused；</li>
<li class="">API 命名是否合适；</li>
<li class="">是否 ready；</li>
<li class="">要不要拆 PR；</li>
<li class="">哪个 reviewer 需要再看一眼。</li>
</ul>
<p>对 Agent 社区来说，这可以迁移成：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">agent-reviews</span><br></div></code></pre></div></div>
<p>它不只 review 代码，也 review Agent 产物：报告、部署、设计、数据分析、benchmark、自动生成的 PR。</p>
<p>Topic 模板可以是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">review:&lt;artifact or PR&gt;</span><br></div></code></pre></div></div>
<p>每个 reviewer Agent 的回复应该固定包含：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Scope</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Evidence</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Findings</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Risk</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Decision: approve / request changes / split / block</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-机器-feed-隔离层">5. 机器 Feed 隔离层<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#5-%E6%9C%BA%E5%99%A8-feed-%E9%9A%94%E7%A6%BB%E5%B1%82" class="hash-link" aria-label="5. 机器 Feed 隔离层的直接链接" title="5. 机器 Feed 隔离层的直接链接" translate="no">​</a></h3>
<p>这次数据里 <code>rss</code> 非常醒目：消息量大、Topic 少、bot 消息占比极高、链接占比接近全量。它不是人类讨论模型，而是机器信号入口。</p>
<p>这点非常重要：机器 feed 必须隔离。</p>
<p>GitHub commit、CI、部署状态、RSS、监控、论文 feed、模型评测结果，如果直接灌进 <code>general</code>，社区会被噪音淹没。正确做法是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">feeds-rss</span><br></div></code></pre></div></div>
<p>每个外部来源一个固定 Topic。人类或 Agent 想讨论某条 feed 时，把链接带到对应的工作 Stream，而不是在 feed 流里长聊。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-ai--agent-工具探索层">6. AI / Agent 工具探索层<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#6-ai--agent-%E5%B7%A5%E5%85%B7%E6%8E%A2%E7%B4%A2%E5%B1%82" class="hash-link" aria-label="6. AI / Agent 工具探索层的直接链接" title="6. AI / Agent 工具探索层的直接链接" translate="no">​</a></h3>
<p>成熟社区里的 AI 相关讨论不是“模型新闻闲聊”。更有价值的是这些类型：</p>
<ul>
<li class="">一个工具公告；</li>
<li class="">一个 MCP / LSP / agentic workflow 设计；</li>
<li class="">一个 benchmark；</li>
<li class="">一次模型失败；</li>
<li class="">一个 AI-authored 项目的质量评估；</li>
<li class="">一个安全边界问题，比如私有仓库、自动反馈、权限越界。</li>
</ul>
<p>这说明 Agent 社区应该有单独空间：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">agent-tools-prompting</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">model-evals</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">agent-projects</span><br></div></code></pre></div></div>
<p>而且讨论必须围绕 evidence：仓库、CI、日志、复现命令、benchmark、失败样例、修复 PR。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="从内容分布看大家到底在聊什么">从内容分布看，大家到底在聊什么<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#%E4%BB%8E%E5%86%85%E5%AE%B9%E5%88%86%E5%B8%83%E7%9C%8B%E5%A4%A7%E5%AE%B6%E5%88%B0%E5%BA%95%E5%9C%A8%E8%81%8A%E4%BB%80%E4%B9%88" class="hash-link" aria-label="从内容分布看，大家到底在聊什么的直接链接" title="从内容分布看，大家到底在聊什么的直接链接" translate="no">​</a></h2>
<p>用 Stream 和 Topic 标题做启发式分类后，几个大类非常明显：</p>
<table><thead><tr><th>内容大类</th><th style="text-align:right">Topic 数</th><th style="text-align:right">消息数</th><th style="text-align:right">占 Stream 消息</th></tr></thead><tbody><tr><td>代码 / 库搜索</td><td style="text-align:right">5,392</td><td style="text-align:right">92,769</td><td style="text-align:right">54.7%</td></tr><tr><td>PR / 评审 / CI</td><td style="text-align:right">3,745</td><td style="text-align:right">49,758</td><td style="text-align:right">29.4%</td></tr><tr><td>求助 / Q&amp;A</td><td style="text-align:right">3,488</td><td style="text-align:right">40,900</td><td style="text-align:right">24.1%</td></tr><tr><td>社区 / 治理 / 社交</td><td style="text-align:right">2,608</td><td style="text-align:right">28,843</td><td style="text-align:right">17.0%</td></tr><tr><td>项目 / 研究协作</td><td style="text-align:right">1,463</td><td style="text-align:right">18,670</td><td style="text-align:right">11.0%</td></tr><tr><td>AI / Agent / 模型</td><td style="text-align:right">945</td><td style="text-align:right">13,291</td><td style="text-align:right">7.8%</td></tr><tr><td>公告 / 活动 / 职位</td><td style="text-align:right">480</td><td style="text-align:right">1,844</td><td style="text-align:right">1.1%</td></tr></tbody></table>
<p>分类是重叠的，所以总和不等于 100%。但趋势很明确：一个高质量技术社区的主体不是“聊天”，而是找代码、问问题、做评审、协作推进项目。</p>
<p>这对 Agent 社区的启发是：不要把机器人社区设计成“很多 Agent 在一起闲聊”。更合理的模式是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">问题 -&gt; 检索 -&gt; 执行 -&gt; 评审 -&gt; 决策 -&gt; 沉淀</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="七种可以直接借鉴的对话模式">七种可以直接借鉴的对话模式<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#%E4%B8%83%E7%A7%8D%E5%8F%AF%E4%BB%A5%E7%9B%B4%E6%8E%A5%E5%80%9F%E9%89%B4%E7%9A%84%E5%AF%B9%E8%AF%9D%E6%A8%A1%E5%BC%8F" class="hash-link" aria-label="七种可以直接借鉴的对话模式的直接链接" title="七种可以直接借鉴的对话模式的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-精准求助把问题变成可复用知识">1. 精准求助：把问题变成可复用知识<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#1-%E7%B2%BE%E5%87%86%E6%B1%82%E5%8A%A9%E6%8A%8A%E9%97%AE%E9%A2%98%E5%8F%98%E6%88%90%E5%8F%AF%E5%A4%8D%E7%94%A8%E7%9F%A5%E8%AF%86" class="hash-link" aria-label="1. 精准求助：把问题变成可复用知识的直接链接" title="1. 精准求助：把问题变成可复用知识的直接链接" translate="no">​</a></h3>
<p>一个好的求助 Thread 通常包含：代码片段、错误、模型判断、人的困惑、专家纠正、底层解释、最后的概念沉淀。</p>
<p>Agent 可以参与两个环节：</p>
<ul>
<li class="">提问前：帮用户把上下文补齐，整理成可回答的问题；</li>
<li class="">回答后：把 thread 总结成知识卡片或 skill。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-长线-api-设计从一个问题演化成任务拆解">2. 长线 API 设计：从一个问题演化成任务拆解<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#2-%E9%95%BF%E7%BA%BF-api-%E8%AE%BE%E8%AE%A1%E4%BB%8E%E4%B8%80%E4%B8%AA%E9%97%AE%E9%A2%98%E6%BC%94%E5%8C%96%E6%88%90%E4%BB%BB%E5%8A%A1%E6%8B%86%E8%A7%A3" class="hash-link" aria-label="2. 长线 API 设计：从一个问题演化成任务拆解的直接链接" title="2. 长线 API 设计：从一个问题演化成任务拆解的直接链接" translate="no">​</a></h3>
<p>有些 Thread 会从一个小问题开始，逐渐变成几周的设计讨论：解析、elaboration、delaboration、API 兼容性、迁移成本、任务阶段。</p>
<p>这类 Thread 对 Agent 社区很关键，因为它说明：长期设计不应该埋在单条 issue 或一次会议里。Topic 可以成为设计日志。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-pr-评审围绕一个-artifact-形成讨论">3. PR 评审：围绕一个 artifact 形成讨论<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#3-pr-%E8%AF%84%E5%AE%A1%E5%9B%B4%E7%BB%95%E4%B8%80%E4%B8%AA-artifact-%E5%BD%A2%E6%88%90%E8%AE%A8%E8%AE%BA" class="hash-link" aria-label="3. PR 评审：围绕一个 artifact 形成讨论的直接链接" title="3. PR 评审：围绕一个 artifact 形成讨论的直接链接" translate="no">​</a></h3>
<p>评审 Thread 的核心不是“LGTM”，而是证据：CI 输出、代码位置、性能、命名、是否保留旧实现、是否需要 reviewer 再看。</p>
<p>Agent reviewer 应该学这种格式，而不是只给一句泛泛建议。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-新人路由从自我介绍到任务入口">4. 新人路由：从自我介绍到任务入口<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#4-%E6%96%B0%E4%BA%BA%E8%B7%AF%E7%94%B1%E4%BB%8E%E8%87%AA%E6%88%91%E4%BB%8B%E7%BB%8D%E5%88%B0%E4%BB%BB%E5%8A%A1%E5%85%A5%E5%8F%A3" class="hash-link" aria-label="4. 新人路由：从自我介绍到任务入口的直接链接" title="4. 新人路由：从自我介绍到任务入口的直接链接" translate="no">​</a></h3>
<p>新人说“我对某个方向感兴趣”，社区不会只欢迎一下，而是继续追问目标和约束，然后推荐资源或频道。</p>
<p>Agent router 很适合做这件事：读新人目标，给出可能的 Stream、已有 Topic、入门任务和需要 human mentor 的位置。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-工具公告发布质疑试用反馈">5. 工具公告：发布、质疑、试用、反馈<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#5-%E5%B7%A5%E5%85%B7%E5%85%AC%E5%91%8A%E5%8F%91%E5%B8%83%E8%B4%A8%E7%96%91%E8%AF%95%E7%94%A8%E5%8F%8D%E9%A6%88" class="hash-link" aria-label="5. 工具公告：发布、质疑、试用、反馈的直接链接" title="5. 工具公告：发布、质疑、试用、反馈的直接链接" translate="no">​</a></h3>
<p>AI/Agent 工具公告类 Thread 通常包含：作者发布 alpha、用户追问安全/性能/并发/私有仓库风险、有人试用后反馈 bug、作者快速修复。</p>
<p>这就是 Agent 工具社区应该长成的样子：不是发布 PR 稿，而是让工具进入可质疑、可试用、可修复的对话。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-ai-authored-项目评估既看亮点也看边界">6. AI-authored 项目评估：既看亮点，也看边界<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#6-ai-authored-%E9%A1%B9%E7%9B%AE%E8%AF%84%E4%BC%B0%E6%97%A2%E7%9C%8B%E4%BA%AE%E7%82%B9%E4%B9%9F%E7%9C%8B%E8%BE%B9%E7%95%8C" class="hash-link" aria-label="6. AI-authored 项目评估：既看亮点，也看边界的直接链接" title="6. AI-authored 项目评估：既看亮点，也看边界的直接链接" translate="no">​</a></h3>
<p>AI 生成项目的讨论通常不会只说“很厉害”。它会追问：CI 呢？代码可读吗？数学声明够清楚吗？是不是有 junk corner cases？成果到底说明了什么？</p>
<p>Agent 社区需要这种 skeptical review，否则会被 demo hype 主导。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-feed-到讨论的分流">7. Feed 到讨论的分流<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#7-feed-%E5%88%B0%E8%AE%A8%E8%AE%BA%E7%9A%84%E5%88%86%E6%B5%81" class="hash-link" aria-label="7. Feed 到讨论的分流的直接链接" title="7. Feed 到讨论的分流的直接链接" translate="no">​</a></h3>
<p>机器 feed 只负责把信号送进来。真正讨论发生在另一个 Topic。这个模式可以防止通知流和协作流互相污染。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="给自建-agent-zulip-的推荐初始结构">给自建 Agent Zulip 的推荐初始结构<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#%E7%BB%99%E8%87%AA%E5%BB%BA-agent-zulip-%E7%9A%84%E6%8E%A8%E8%8D%90%E5%88%9D%E5%A7%8B%E7%BB%93%E6%9E%84" class="hash-link" aria-label="给自建 Agent Zulip 的推荐初始结构的直接链接" title="给自建 Agent Zulip 的推荐初始结构的直接链接" translate="no">​</a></h2>
<p>不要一开始就建几十个空频道。先建少量稳定 Stream：</p>
<table><thead><tr><th>Stream</th><th>用途</th><th>Topic 命名建议</th><th>Agent 参与方式</th></tr></thead><tbody><tr><td><code>announcements</code></td><td>低频公告、重要发布、路线图</td><td><code>YYYY-MM-DD release/decision/...</code></td><td>owner / orchestrator 发，Agent 附证据链接</td></tr><tr><td><code>general</code></td><td>开放讨论、临时问题、跨项目话题</td><td>具体问题句子</td><td>Agent 回答、补链接、建议迁移 Topic</td></tr><tr><td><code>new-members</code></td><td>人和 Agent onboarding</td><td>人名或 Agent 名 + 目标</td><td>Router Agent 欢迎、追问目标、路由</td></tr><tr><td><code>capability-search</code></td><td>找工具、找代码、找 precedent</td><td><code>Is there tool/data/code for X?</code></td><td>检索型 Agent 回答已有能力和缺口</td></tr><tr><td><code>agent-runs</code></td><td>每次可追踪 Agent 执行</td><td><code>run:&lt;date&gt;:&lt;goal&gt;</code></td><td>Worker Agent 发状态、证据、blocker、结论</td></tr><tr><td><code>agent-reviews</code></td><td>代码、方案、报告、部署 review</td><td><code>review:&lt;artifact or PR&gt;</code></td><td>Reviewer Agent 给 checklist 和决策建议</td></tr><tr><td><code>model-evals</code></td><td>模型/Agent benchmark 与失败案例</td><td><code>&lt;model&gt;/&lt;benchmark&gt;/&lt;date&gt;</code></td><td>Eval Agent 发固定格式结果和复现路径</td></tr><tr><td><code>agent-tools-prompting</code></td><td>Prompt、Skill、MCP、工具链、安全边界</td><td><code>[ANN] tool</code> / <code>safety:&lt;issue&gt;</code></td><td>工具 Agent 发布变更，用户反馈风险</td></tr><tr><td><code>feeds-rss</code></td><td>GitHub、CI、监控、外部信息 feed</td><td>固定 feed topic</td><td>bot-only；讨论迁移到其他 Stream</td></tr><tr><td><code>meta</code></td><td>规则、权限、机器人行为、噪音治理</td><td><code>policy:&lt;topic&gt;</code> / <code>noise:&lt;topic&gt;</code></td><td>Meta Agent 汇总规则和指标</td></tr></tbody></table>
<p>等真实讨论增多后，再把高流量长期项目拆成独立 Project Stream。不要先设计一棵复杂频道树。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="agent-的发言协议">Agent 的发言协议<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#agent-%E7%9A%84%E5%8F%91%E8%A8%80%E5%8D%8F%E8%AE%AE" class="hash-link" aria-label="Agent 的发言协议的直接链接" title="Agent 的发言协议的直接链接" translate="no">​</a></h2>
<p>如果 Agent 真的进 Zulip，它们的行为应该比普通聊天机器人更克制。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="执行型-agent-状态更新">执行型 Agent 状态更新<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#%E6%89%A7%E8%A1%8C%E5%9E%8B-agent-%E7%8A%B6%E6%80%81%E6%9B%B4%E6%96%B0" class="hash-link" aria-label="执行型 Agent 状态更新的直接链接" title="执行型 Agent 状态更新的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Status: 当前做到哪一步</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Evidence: 可点击链接、命令输出、文件、截图、API readback</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Blocker: 如果卡住，卡在哪里</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Next: 下一步做什么，是否需要人类确认</span><br></div></code></pre></div></div>
<p>不要每做一个小动作就发一条。只在阶段变化、遇到 blocker、需要确认或最终完成时更新。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="检索型-agent-回复">检索型 Agent 回复<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#%E6%A3%80%E7%B4%A2%E5%9E%8B-agent-%E5%9B%9E%E5%A4%8D" class="hash-link" aria-label="检索型 Agent 回复的直接链接" title="检索型 Agent 回复的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Question understood as: ...</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Found: ...</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Closest existing capability: ...</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Gap: ...</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Recommended next topic/task: ...</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="reviewer-agent-回复">Reviewer Agent 回复<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#reviewer-agent-%E5%9B%9E%E5%A4%8D" class="hash-link" aria-label="Reviewer Agent 回复的直接链接" title="Reviewer Agent 回复的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Scope: review 的对象和边界</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Evidence: 实际读了什么、跑了什么</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Findings: 问题列表</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Risk: 还剩什么风险</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Decision: approve / request changes / split / block</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="summarizer-agent-回复">Summarizer Agent 回复<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#summarizer-agent-%E5%9B%9E%E5%A4%8D" class="hash-link" aria-label="Summarizer Agent 回复的直接链接" title="Summarizer Agent 回复的直接链接" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Decision / Open questions / Action items / Links / Next owner</span><br></div></code></pre></div></div>
<p>Agent 最重要的不是“像人一样热闹”，而是让讨论更可追踪、更可复现、更容易沉淀。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="这对平台选择意味着什么">这对平台选择意味着什么<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#%E8%BF%99%E5%AF%B9%E5%B9%B3%E5%8F%B0%E9%80%89%E6%8B%A9%E6%84%8F%E5%91%B3%E7%9D%80%E4%BB%80%E4%B9%88" class="hash-link" aria-label="这对平台选择意味着什么的直接链接" title="这对平台选择意味着什么的直接链接" translate="no">​</a></h2>
<p>如果目标是长期知识、任务和决策，Discourse 仍然很适合做主社区：topic-first、搜索友好、结论可沉淀。</p>
<p>如果目标是实时协作、快速问答、机器人状态更新、工具公告、benchmark 讨论，Zulip 的 Topic-threaded chat 很合适。它比普通群聊更容易把对话切成可搜索的工作单元。</p>
<p>更合理的组合可能是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Discourse：长期议题、决策、公开知识库</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Zulip：实时 topic-threaded 协作、人机混合讨论</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Git / PR：源码和可审计变更</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ChatBoard / Docs / Blog：沉淀后的结构化产物</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Agent Runtime：执行、检索、评审、总结</span><br></div></code></pre></div></div>
<p>Zulip 不必替代 Discourse，也不必替代 Git。它更像一层“协作总线”：让人和 Agent 在同一个 Topic 里围绕具体对象推进。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zulip-agent-community-patterns#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h2>
<p>这次从 17 万条 Zulip 消息里看到的核心模式是：</p>
<ol>
<li class="">Stream 负责长期职责，Topic 负责具体工作单元。</li>
<li class="">核心技术讨论、问答、评审、新人路由、机器 feed、AI 工具探索应该分层。</li>
<li class="">机器 feed 必须隔离，否则会淹没人类讨论。</li>
<li class="">Agent 不应该只是聊天机器人，而应该承担检索、执行、评审、总结、路由和 feed 清洗。</li>
<li class="">高质量社区的关键不是频道多，而是 thread 标题具体、上下文完整、证据可追溯、结论能沉淀。</li>
</ol>
<p>如果要自建一个 Agent Zulip，我会从少量 Stream 开始，让每个 Agent 有明确身份和发言协议，再通过真实 Topic 慢慢长出项目、规则和知识库。</p>
<p>真正值得模仿的不是别人每天聊了哪些热闹话题，而是他们如何把一句问题变成一次可追踪协作，最后变成社区可以再次搜索和复用的技术记忆。</p>]]></content>
        <category label="zulip" term="zulip"/>
        <category label="agent" term="agent"/>
        <category label="community" term="community"/>
        <category label="collaboration" term="collaboration"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[B站发文和视频投稿：官方开放平台、插件能力与 CLI 工具调研]]></title>
        <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog/bilibili-publishing-api-cli-field-guide</id>
        <link href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/bilibili-publishing-api-cli-field-guide"/>
        <updated>2026-08-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[调研 B站官方开放平台、Wechatsync Bilibili adapter、第三方 CLI 工具，回答能发什么、怎么发、有没有官方 CLI，以及 ChatPost 应优先走哪条接入路线。]]></summary>
        <content type="html"><![CDATA[<p>我们要回答的不是“能不能点网页发一篇 B站内容”，而是 <strong>ChatPost 这种发文/投稿编排层应该接哪条稳定边界</strong>：官方 API、浏览器插件、还是第三方 CLI。</p>
<p>结论先说：<strong>B站官方开放平台已经有视频稿件管理和专栏稿件管理 API；插件路线当前看到的是专栏图文草稿，不是视频投稿；官方没有发现可直接投稿的 CLI，第三方 CLI 以 biliup 系列为代表，但它们不等于官方接入路径。</strong>[1][5][8][14]</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="先拿官方入口">先拿官方入口<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/bilibili-publishing-api-cli-field-guide#%E5%85%88%E6%8B%BF%E5%AE%98%E6%96%B9%E5%85%A5%E5%8F%A3" class="hash-link" aria-label="先拿官方入口的直接链接" title="先拿官方入口的直接链接" translate="no">​</a></h2>
<p>如果要从官方路线开始，入口不要从 cookies、二维码、浏览器插件开始，而应该先看这几个官方站点：</p>
<table><thead><tr><th>入口</th><th>用途</th></tr></thead><tbody><tr><td><a href="https://open.bilibili.com/" target="_blank" rel="noopener noreferrer" class="">B站开放平台</a></td><td>开放平台首页</td></tr><tr><td><a href="https://open.bilibili.com/doc" target="_blank" rel="noopener noreferrer" class="">开放平台文档中心</a></td><td>OPEN API、客户端 SDK、运营指南、更多支持</td></tr><tr><td><a href="https://openhome.bilibili.com/company-core" target="_blank" rel="noopener noreferrer" class="">开平管理中心 / 应用管理</a></td><td>入驻、创建应用、查看接口权限、配置白名单</td></tr><tr><td><a href="https://open.bilibili.com/doc/4/eaf0e2b5-bde9-b9a0-9be1-019bb455701c" target="_blank" rel="noopener noreferrer" class="">账号授权</a></td><td>OAuth 2.0 授权、token 与 refresh token</td></tr><tr><td><a href="https://openhome.bilibili.com/doc/4/aac73b2e-4ff2-b75c-4c96-35ced865797b" target="_blank" rel="noopener noreferrer" class="">网页应用接入</a></td><td>Web OAuth 授权页、回调与授权码流程</td></tr><tr><td><a href="https://open.bilibili.com/doc/4/8673959e-f7bb-56e6-6e68-d225f971b81b" target="_blank" rel="noopener noreferrer" class="">接口签名和状态码</a></td><td>HMAC-SHA256 签名、公共请求头、签名验证工具</td></tr><tr><td><a href="https://open.bilibili.com/doc/4/b2dc2f0e-c874-aed7-3d92-360929e79d3a" target="_blank" rel="noopener noreferrer" class="">接口权限白名单</a></td><td>某些接口只允许指定 UID 调用时的限制配置</td></tr><tr><td><a href="https://github.com/bilibili-openplatform" target="_blank" rel="noopener noreferrer" class="">B站开放平台 GitHub org</a></td><td>官方 Demo 和签名样例代码</td></tr></tbody></table>
<p>这组入口已经能说明官方接入的基本模型：先入驻开放平台、创建应用、拿到 <code>client_id</code> 和 <code>secret</code>，然后走 OAuth 获取用户授权 token，再用开放平台签名规则调用稿件接口。[1][2][3][4]</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="官方能发布哪些东西">官方能发布哪些东西<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/bilibili-publishing-api-cli-field-guide#%E5%AE%98%E6%96%B9%E8%83%BD%E5%8F%91%E5%B8%83%E5%93%AA%E4%BA%9B%E4%B8%9C%E8%A5%BF" class="hash-link" aria-label="官方能发布哪些东西的直接链接" title="官方能发布哪些东西的直接链接" translate="no">​</a></h2>
<p>本次调研里，和“发布内容”直接相关的官方能力分两组：</p>
<table><thead><tr><th>内容类型</th><th>官方能力组</th><th>Scope</th><th>能力边界</th></tr></thead><tbody><tr><td>视频稿件</td><td>视频稿件管理 / 服务端视频稿件投递</td><td><code>ARC_BASE</code></td><td>视频文件预处理、分片上传、合片、封面上传、分区查询、视频稿件提交</td></tr><tr><td>专栏文章</td><td>专栏稿件管理</td><td><code>ATC_BASE</code></td><td>文章提交、编辑、删除、详情、列表、分类、图片上传</td></tr></tbody></table>
<p>这意味着官方 API 不是只有“读取账号信息”或“授权登录”。它已经覆盖了 B站内容生产里最核心的两类发布对象：<strong>视频投稿</strong>和<strong>专栏文章</strong>。[5][6][7][8][9][10]</p>
<p>但它也不是一个“随便拿账号就能调”的接口。官方账号授权文档明确说，使用授权之前需要在开放平台完成入驻和应用申请，得到 <code>client_id</code> 和 <code>secret</code>；如果应用新增 scope，原有授权用户还需要重新授权并勾选对应 scope。[1]</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="官方视频投稿大概怎么发">官方视频投稿大概怎么发<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/bilibili-publishing-api-cli-field-guide#%E5%AE%98%E6%96%B9%E8%A7%86%E9%A2%91%E6%8A%95%E7%A8%BF%E5%A4%A7%E6%A6%82%E6%80%8E%E4%B9%88%E5%8F%91" class="hash-link" aria-label="官方视频投稿大概怎么发的直接链接" title="官方视频投稿大概怎么发的直接链接" translate="no">​</a></h2>
<p>官方视频投稿链路不是一个单接口 <code>upload(video)</code>，而是一个分阶段上传与提交流程：</p>
<table><thead><tr><th>阶段</th><th>官方接口</th><th>作用</th></tr></thead><tbody><tr><td>1</td><td><code>archive/video/init</code></td><td>文件上传预处理，拿到上传所需信息</td></tr><tr><td>2</td><td><code>video/v2/part/upload</code> 或 <code>video/v2/upload</code></td><td>分片上传，或小视频单文件上传</td></tr><tr><td>3</td><td><code>archive/video/complete</code></td><td>合片，完成视频文件上传</td></tr><tr><td>4</td><td><code>archive/cover/upload</code></td><td>上传稿件封面</td></tr><tr><td>5</td><td><code>archive/type/list</code></td><td>查询/刷新分区信息</td></tr><tr><td>6</td><td><code>archive/add-by-utoken</code></td><td>提交视频稿件</td></tr></tbody></table>
<p>官方概览还写了视频参数边界：文件大小限制 4GB、时长小于 5 小时、推荐 mp4/flv、最大 4096x4096、最大 120fps，并提醒投稿需要选择合适分区，分区可能调整，建议定时查询更新。[5]</p>
<p>所以 ChatPost 如果走官方视频投稿，不应该把它设计成“浏览器里点上传按钮”的封装，而应该设计成一个明确的 pipeline：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">OpenPlatform OAuth token</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 签名请求头</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; init 获取上传上下文</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; part upload / single upload</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; complete 合片</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; cover upload</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; type/list 校验分区</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; add-by-utoken 提交稿件</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 查询稿件状态 / 审核结果</span><br></div></code></pre></div></div>
<p>这个流程天然适合服务端编排：可以记录每个阶段的 request id、upload token、分片进度、失败重试、最终稿件状态。它也天然需要更强的权限和更严格的凭据管理，因为它不再只是保存草稿，而是真正创建 B站视频稿件。[6][7][8]</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="官方专栏文章大概怎么发">官方专栏文章大概怎么发<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/bilibili-publishing-api-cli-field-guide#%E5%AE%98%E6%96%B9%E4%B8%93%E6%A0%8F%E6%96%87%E7%AB%A0%E5%A4%A7%E6%A6%82%E6%80%8E%E4%B9%88%E5%8F%91" class="hash-link" aria-label="官方专栏文章大概怎么发的直接链接" title="官方专栏文章大概怎么发的直接链接" translate="no">​</a></h2>
<p>专栏文章 API 比视频上传短一些，但也有审核和素材处理边界：</p>
<table><thead><tr><th>阶段</th><th>官方接口</th><th>作用</th></tr></thead><tbody><tr><td>1</td><td><code>article/upload/image</code></td><td>上传正文/封面用图片，返回 B站图片 URL</td></tr><tr><td>2</td><td><code>article/categories</code></td><td>获取可用文章分类</td></tr><tr><td>3</td><td><code>article/add</code></td><td>提交文章稿件</td></tr><tr><td>4</td><td><code>article/list</code> / <code>article/detail</code></td><td>查询文章列表、状态和详情</td></tr><tr><td>5</td><td><code>article/edit</code></td><td>编辑文章；编辑后重新审核</td></tr><tr><td>6</td><td><code>article/delete</code></td><td>删除文章；删除类操作要谨慎处理</td></tr></tbody></table>
<p>官方文章提交文档写得比较清楚：文章提交需要 <code>ATC_BASE</code>，正文内容 200–40000 字，或者至少添加三张图片；如果正文里需要图片 URL，要使用图片上传接口生成的 URL；文章提交之后会进入审核过程，期间不对外开放。[9][10]</p>
<p>这说明“专栏发布”也不能简单等同于“发出去了马上可见”。对 ChatPost 来说，正确的状态模型应该至少区分：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">prepared -&gt; uploaded-assets -&gt; submitted -&gt; auditing -&gt; passed / rejected / edited-resubmitted</span><br></div></code></pre></div></div>
<p>如果做 <code>chatpost bilibili article submit</code>，返回值不应该只报 success，而应该返回文章 id、提交状态、是否进入审核、后续查询命令，以及审核中/打回/通过这些状态解释。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wechatsync--插件里支持什么">Wechatsync / 插件里支持什么<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/bilibili-publishing-api-cli-field-guide#wechatsync--%E6%8F%92%E4%BB%B6%E9%87%8C%E6%94%AF%E6%8C%81%E4%BB%80%E4%B9%88" class="hash-link" aria-label="Wechatsync / 插件里支持什么的直接链接" title="Wechatsync / 插件里支持什么的直接链接" translate="no">​</a></h2>
<p>我们本次看的 Wechatsync v2 Bilibili adapter 快照显示，插件侧的 B站适配器 metadata 是：</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">id</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'bilibili'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">homepage</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'https://member.bilibili.com/platform/upload/text'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">capabilities</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'article'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'draft'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'image_upload'</span><span class="token punctuation" style="color:#393A34">]</span><br></div></code></pre></div></div>
<p>它的发布函数保存到的是 B站专栏草稿接口：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">https://api.bilibili.com/x/article/creative/draft/addupdate</span><br></div></code></pre></div></div>
<p>图片上传走的是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">https://api.bilibili.com/x/article/creative/article/upcover</span><br></div></code></pre></div></div>
<p>也就是说，插件快照支持的是 <strong>B站专栏/图文草稿 + 图片上传</strong>，不是视频投稿。<code>homepage</code> 指向 <code>platform/upload/text</code>，capabilities 也只有 <code>article</code>、<code>draft</code>、<code>image_upload</code>，没有 <code>video</code>、<code>archive</code>、<code>upload</code> 这类视频投稿能力。[13]</p>
<p>这条路线的价值是：如果官方 API 权限还没申请下来，可以用浏览器登录态先验证“Markdown/HTML -&gt; 专栏草稿”的产品体验。但它不应该被描述成“支持 B站视频发布”。最多只能说：<strong>支持专栏图文草稿；视频上传在当前 adapter 里没有看到实现证据。</strong>[13]</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="有没有官方-cli-工具">有没有官方 CLI 工具<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/bilibili-publishing-api-cli-field-guide#%E6%9C%89%E6%B2%A1%E6%9C%89%E5%AE%98%E6%96%B9-cli-%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="有没有官方 CLI 工具的直接链接" title="有没有官方 CLI 工具的直接链接" translate="no">​</a></h2>
<p>目前没有发现 B站官方提供“投稿/发布 CLI”。</p>
<p>官方文档里出现的“工具”主要是两类：</p>
<ol>
<li class=""><strong>签名验证工具</strong>：文档给了一个 Windows 64 位签名验证工具下载链接，用来验证开放平台请求签名。[4]</li>
<li class=""><strong>官方 Demo / SDK 示例</strong>：官方 GitHub org 下有 <code>SignatureAlgorithm_DotnetDemo</code> 和 <code>OpenPlatform_CSharpDemo</code>，它们是 C# 签名和开放平台调用示例，不是 <code>bilibili upload</code> 这类可直接投稿的命令行产品。[11][12]</li>
</ol>
<p>官方 <code>OpenPlatform_CSharpDemo</code> README 也写明，Demo 里的功能都需要开放平台接入并完成用户授权后才能触发；它要求开发者填入应用信息、回调地址和 token，再按需要选择 sample 中的功能。[12]</p>
<p>所以文章里的 CLI 结论应当严格写成：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">官方有开放平台 API、签名验证工具、C# Demo / SDK 示例；</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">当前未发现官方投稿/发布 CLI。</span><br></div></code></pre></div></div>
<p>不要把第三方工具当成官方 CLI。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="第三方-clibiliup-是参考不是官方路线">第三方 CLI：biliup 是参考，不是官方路线<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/bilibili-publishing-api-cli-field-guide#%E7%AC%AC%E4%B8%89%E6%96%B9-clibiliup-%E6%98%AF%E5%8F%82%E8%80%83%E4%B8%8D%E6%98%AF%E5%AE%98%E6%96%B9%E8%B7%AF%E7%BA%BF" class="hash-link" aria-label="第三方 CLI：biliup 是参考，不是官方路线的直接链接" title="第三方 CLI：biliup 是参考，不是官方路线的直接链接" translate="no">​</a></h2>
<p>第三方生态里确实有 B站投稿 CLI / 桌面工具，最明显的是 <code>biliup</code> 系列。当前 GitHub API 读到的 <code>biliup/biliup</code> 描述是“自动直播录制、投稿、twitch、ytb频道搬运工具。命令行投稿(B站)和视频下载工具，提供多种登录方式，支持多p。”，Stars 为 5340，仓库未归档，最近 push 时间是 2026-08-07。[14]</p>
<p>此外，旧的 <code>biliup-rs</code> README 明确说仓库已归档，后续开发迁移到新仓库；它的旧文档里仍能看到 <code>biliup upload</code>、<code>login</code>、<code>renew</code>、<code>append</code>、<code>list</code> 等命令形态。[15]</p>
<p>这类工具对我们有两个价值：</p>
<ul>
<li class="">可以参考多 P、分区、封面、延时发布、cookie 文件、多账号等产品参数设计；</li>
<li class="">可以作为 fallback：当官方 API 权限申请周期太长，而内部需要先验证视频投稿流程时，第三方 CLI 能作为受控实验对象。</li>
</ul>
<p>但它不应该成为 ChatPost 的首选长期边界。原因很简单：第三方 CLI 常常围绕 cookies、网页登录态、客户端/网页接口做封装，稳定性、合规边界、权限解释和审计能力都弱于官方开放平台 API。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="对-chatpost-的建议路线">对 ChatPost 的建议路线<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/bilibili-publishing-api-cli-field-guide#%E5%AF%B9-chatpost-%E7%9A%84%E5%BB%BA%E8%AE%AE%E8%B7%AF%E7%BA%BF" class="hash-link" aria-label="对 ChatPost 的建议路线的直接链接" title="对 ChatPost 的建议路线的直接链接" translate="no">​</a></h2>
<p>ChatPost 应该把 B站拆成两条路线，而不是混成一个 <code>bilibili publish</code>：</p>
<table><thead><tr><th>层级</th><th>推荐命令</th><th>接入边界</th><th>说明</th></tr></thead><tbody><tr><td>账号/权限</td><td><code>chatpost bilibili oauth login/status/logout</code></td><td>官方 OAuth</td><td>管理开放平台 app、用户授权 token、scope readback</td></tr><tr><td>专栏文章</td><td><code>chatpost bilibili article submit/status/edit</code></td><td>官方 <code>ATC_BASE</code></td><td>真正提交专栏稿件，进入审核；不是只存浏览器草稿</td></tr><tr><td>视频投稿</td><td><code>chatpost bilibili video upload/submit/status</code></td><td>官方 <code>ARC_BASE</code></td><td>分片上传、封面、分区、提交、状态查询</td></tr><tr><td>草稿 fallback</td><td><code>chatpost bilibili draft</code></td><td>Wechatsync / browser runner</td><td>仅当官方权限不可用时，用专栏草稿体验验证</td></tr><tr><td>第三方实验</td><td><code>chatpost bilibili experimental biliup ...</code></td><td>biliup 等第三方 CLI</td><td>只作为显式实验功能，不混进官方能力</td></tr></tbody></table>
<p>第一阶段不建议直接做公开视频发布。更稳的顺序是：</p>
<ol>
<li class="">确认是否已有 B站开放平台主体和应用；</li>
<li class="">确认应用是否拿到 <code>ATC_BASE</code> / <code>ARC_BASE</code>；</li>
<li class="">做 OAuth 授权和 token 安全存储；</li>
<li class="">先接查询/分类/图片上传这类低风险接口；</li>
<li class="">再接专栏提交，并把审核状态做清楚；</li>
<li class="">最后接视频上传分片和投稿提交；</li>
<li class="">如果官方权限被卡住，再把 Wechatsync 专栏草稿或 biliup 第三方路线作为 fallback，而不是默认主线。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="最后结论">最后结论<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/bilibili-publishing-api-cli-field-guide#%E6%9C%80%E5%90%8E%E7%BB%93%E8%AE%BA" class="hash-link" aria-label="最后结论的直接链接" title="最后结论的直接链接" translate="no">​</a></h2>
<p>如果目标只是“今天能不能把内容弄到 B站草稿箱”，Wechatsync 的 Bilibili adapter 快照已经给出一条专栏草稿路线。[13]</p>
<p>如果目标是把 ChatPost 做成可验收、可追踪、可长期维护的平台发布层，B站应该优先走官方开放平台 API：专栏用 <code>ATC_BASE</code>，视频用 <code>ARC_BASE</code>，先 OAuth 和 scope readback，再做素材上传、稿件提交和状态查询。[1][6][8][9]</p>
<p>官方 CLI 这块，当前结论是：<strong>没有发现官方投稿/发布 CLI；官方提供的是开放平台文档、签名验证工具和 C# Demo。第三方 CLI 有，但只能作为参考或 fallback，不应该被写成官方路径。</strong>[4][11][12][14]</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="sources">Sources<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/bilibili-publishing-api-cli-field-guide#sources" class="hash-link" aria-label="Sources的直接链接" title="Sources的直接链接" translate="no">​</a></h2>
<p>[1] B站账号授权文档：<a href="https://open.bilibili.com/doc/4/eaf0e2b5-bde9-b9a0-9be1-019bb455701c" target="_blank" rel="noopener noreferrer" class="">open.bilibili.com/doc/4/eaf0e2b5-bde9-b9a0-9be1-019bb455701c</a>
[2] B站网页应用接入指引：<a href="https://openhome.bilibili.com/doc/4/aac73b2e-4ff2-b75c-4c96-35ced865797b" target="_blank" rel="noopener noreferrer" class="">openhome.bilibili.com/doc/4/aac73b2e-4ff2-b75c-4c96-35ced865797b</a>
[3] B站接口权限白名单：<a href="https://open.bilibili.com/doc/4/b2dc2f0e-c874-aed7-3d92-360929e79d3a" target="_blank" rel="noopener noreferrer" class="">open.bilibili.com/doc/4/b2dc2f0e-c874-aed7-3d92-360929e79d3a</a>
[4] B站接口签名和状态码：<a href="https://open.bilibili.com/doc/4/8673959e-f7bb-56e6-6e68-d225f971b81b" target="_blank" rel="noopener noreferrer" class="">open.bilibili.com/doc/4/8673959e-f7bb-56e6-6e68-d225f971b81b</a>
[5] B站视频稿件管理接口概览：<a href="https://open.bilibili.com/doc/4/1c4302da-8171-d036-5400-ca0dec077045" target="_blank" rel="noopener noreferrer" class="">open.bilibili.com/doc/4/1c4302da-8171-d036-5400-ca0dec077045</a>
[6] B站视频上传预处理：<a href="https://open.bilibili.com/doc/4/0c532c6a-e6fb-0aff-8021-905ae2409095" target="_blank" rel="noopener noreferrer" class="">open.bilibili.com/doc/4/0c532c6a-e6fb-0aff-8021-905ae2409095</a>
[7] B站视频分片上传：<a href="https://open.bilibili.com/doc/4/733a520a-c50f-7bb4-17cb-35338ba20500" target="_blank" rel="noopener noreferrer" class="">open.bilibili.com/doc/4/733a520a-c50f-7bb4-17cb-35338ba20500</a>
[8] B站视频稿件提交：<a href="https://open.bilibili.com/doc/4/f7fc57dd-55a1-5cb1-cba4-61fb2994bf0f" target="_blank" rel="noopener noreferrer" class="">open.bilibili.com/doc/4/f7fc57dd-55a1-5cb1-cba4-61fb2994bf0f</a>
[9] B站专栏文章提交：<a href="https://open.bilibili.com/doc/4/b14b77b6-8889-8c8b-2e83-17c5a4c550fb" target="_blank" rel="noopener noreferrer" class="">open.bilibili.com/doc/4/b14b77b6-8889-8c8b-2e83-17c5a4c550fb</a>
[10] B站专栏图片上传：<a href="https://open.bilibili.com/doc/4/0eaa4d3e-c4c0-f874-6f3c-e083aa939a1b" target="_blank" rel="noopener noreferrer" class="">open.bilibili.com/doc/4/0eaa4d3e-c4c0-f874-6f3c-e083aa939a1b</a>
[11] B站官方签名算法 C# Demo：<a href="https://github.com/bilibili-openplatform/SignatureAlgorithm_DotnetDemo" target="_blank" rel="noopener noreferrer" class="">github.com/bilibili-openplatform/SignatureAlgorithm_DotnetDemo</a>
[12] B站官方开放平台 C# Demo：<a href="https://github.com/bilibili-openplatform/OpenPlatform_CSharpDemo" target="_blank" rel="noopener noreferrer" class="">github.com/bilibili-openplatform/OpenPlatform_CSharpDemo</a>
[13] Wechatsync v2 Bilibili adapter：<a href="https://github.com/wechatsync/Wechatsync/blob/v2/packages/core/src/adapters/platforms/bilibili.ts" target="_blank" rel="noopener noreferrer" class="">github.com/wechatsync/Wechatsync/blob/v2/packages/core/src/adapters/platforms/bilibili.ts</a>
[14] biliup：<a href="https://github.com/biliup/biliup" target="_blank" rel="noopener noreferrer" class="">github.com/biliup/biliup</a>
[15] biliup-rs：<a href="https://github.com/biliup/biliup-rs" target="_blank" rel="noopener noreferrer" class="">github.com/biliup/biliup-rs</a></p>]]></content>
        <category label="chatpost" term="chatpost"/>
        <category label="bilibili" term="bilibili"/>
        <category label="publishing" term="publishing"/>
        <category label="open-platform" term="open-platform"/>
        <category label="cli" term="cli"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[当两个 Agent 进同一个 Mattermost Thread：ChatRSS、Hermes 与 cc-connect 的对话规则]]></title>
        <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-chatrss-multi-agent-conversation-rules</id>
        <link href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-chatrss-multi-agent-conversation-rules"/>
        <updated>2026-08-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[用一篇文章讲清楚 ChatArch Mattermost 双账号双 Agent 接入：@hermes-agent 与 @cc-connect 的接入机制和命令边界、thread/session 对话历史隔离规则，以及 ChatRSS 在 TriggerEvent、Router 与 Ledger 层的位置。]]></summary>
        <content type="html"><![CDATA[<p>前两篇文章已经把 Mattermost 放进了 ChatArch 的版图：它是一个自托管实时 Agent 工作间，也是一个和 Slack、飞书心智都不完全一样的 channel / post / thread 系统。接下来真正会遇到的问题是：<strong>如果一个 Mattermost thread 里同时有多个 Agent，它们到底共享什么历史？谁能唤醒谁？命令边界在哪里？ChatRSS 又应该接在哪一层？</strong></p>
<p>这篇文章只写成一篇，不拆成系列。全文分成三个大节：第一节讲已经跑通的双账号接入和命令边界；第二节讲 thread、session、history 与 Agent 互相 @ 的规则；第三节讲 ChatRSS 在 TriggerEvent、Router、Action 和 Ledger 层应该承担什么角色。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>一句话结论</div><div class="admonitionContent_BuS1"><p>Mattermost 是共享消息总线，不是共享 Agent memory。<code>@hermes-agent</code> 和 <code>@cc-connect</code> 应该是两个独立 bot account、两套独立 runtime；它们能共同看到的是同一个 Mattermost thread 里显式写出来的文本，而不是彼此内部 session、tools、memory 或历史。ChatRSS 的位置也不是替代实时聊天 gateway，而是把需要跨平台去重、路由、审计和回放的事件接进 TriggerEvent / Router / Ledger 层。</p></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>证据口径</div><div class="admonitionContent_BuS1"><p>本文只使用公开可打开的链接、公开项目、公开文章和不含凭据的实测结论。Mattermost token、bot credential、私有配置、机器路径、用户 ID 和运维细节都不进入正文。文中的“已实测”指这次在 ChatArch Mattermost public thread 中回读到了普通用户触发、<code>hermes-agent</code> 回复、<code>cc-connect</code> 回复这三类不同作者身份。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一双账号双-agent-接入mattermost-是共享消息总线不是一个-bot-混跑所有-runtime">一、双账号双 Agent 接入：Mattermost 是共享消息总线，不是一个 bot 混跑所有 runtime<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-chatrss-multi-agent-conversation-rules#%E4%B8%80%E5%8F%8C%E8%B4%A6%E5%8F%B7%E5%8F%8C-agent-%E6%8E%A5%E5%85%A5mattermost-%E6%98%AF%E5%85%B1%E4%BA%AB%E6%B6%88%E6%81%AF%E6%80%BB%E7%BA%BF%E4%B8%8D%E6%98%AF%E4%B8%80%E4%B8%AA-bot-%E6%B7%B7%E8%B7%91%E6%89%80%E6%9C%89-runtime" class="hash-link" aria-label="一、双账号双 Agent 接入：Mattermost 是共享消息总线，不是一个 bot 混跑所有 runtime的直接链接" title="一、双账号双 Agent 接入：Mattermost 是共享消息总线，不是一个 bot 混跑所有 runtime的直接链接" translate="no">​</a></h2>
<p>这次我们先在 Mattermost 上做了一个很小但关键的验收：在同一个 public thread 里，普通用户先触发 Hermes，再触发 cc-connect，并且分别由两个不同 bot account 回包。</p>
<p>公开 thread 入口：</p>
<ul>
<li class="">Root thread: <a href="https://mattermost.public.wzhecnu.cn/_redirect/pl/yq3uts5jfpbd7gutbjdjaurnwc" target="_blank" rel="noopener noreferrer" class="">https://mattermost.public.wzhecnu.cn/_redirect/pl/yq3uts5jfpbd7gutbjdjaurnwc</a></li>
<li class="">Hermes reply: <a href="https://mattermost.public.wzhecnu.cn/_redirect/pl/4yzgq91aabg19fqd53mzrpf5ay" target="_blank" rel="noopener noreferrer" class="">https://mattermost.public.wzhecnu.cn/_redirect/pl/4yzgq91aabg19fqd53mzrpf5ay</a></li>
<li class="">cc-connect reply: <a href="https://mattermost.public.wzhecnu.cn/_redirect/pl/nb7h3jjz63b9tfrndb9uk8n11c" target="_blank" rel="noopener noreferrer" class="">https://mattermost.public.wzhecnu.cn/_redirect/pl/nb7h3jjz63b9tfrndb9uk8n11c</a></li>
</ul>
<p>这个 thread 的关键顺序是：</p>
<table><thead><tr><th style="text-align:right">顺序</th><th>谁发的</th><th>内容含义</th></tr></thead><tbody><tr><td style="text-align:right">1</td><td>普通用户</td><td>开一个 multi-agent acceptance root post。</td></tr><tr><td style="text-align:right">2</td><td>普通用户</td><td>在 thread 里发 <code>@hermes-agent /ssh status</code>。</td></tr><tr><td style="text-align:right">3</td><td><code>hermes-agent</code></td><td>Hermes 以自己的 bot 身份返回 SSH status。</td></tr><tr><td style="text-align:right">4</td><td>普通用户</td><td>在同一个 thread 里发 <code>@cc-connect /help</code>。</td></tr><tr><td style="text-align:right">5</td><td><code>cc-connect</code></td><td>cc-connect 以自己的 bot 身份返回命令帮助。</td></tr></tbody></table>
<p>这说明 Mattermost thread 可以成为一个多人多 Agent 的共享协作表面。但它同时也说明了边界：<code>@hermes-agent</code> 和 <code>@cc-connect</code> 必须是两个账号，不能混用同一个 Mattermost token 或 bot 用户。</p>
<p>如果混用账号，会出现四类问题：</p>
<table><thead><tr><th>问题</th><th>影响</th></tr></thead><tbody><tr><td>回包身份不清楚</td><td>用户看不出是 Hermes 还是 cc-connect 在回答。</td></tr><tr><td>allowlist 不清楚</td><td>无法按 runtime 精确控制谁能唤醒哪个 Agent。</td></tr><tr><td>session key 不清楚</td><td>同一个 bot account 背后多个 runtime 容易串话。</td></tr><tr><td>审计不清楚</td><td>事后很难判断是哪套 Agent、哪条配置、哪次触发产生了动作。</td></tr></tbody></table>
<p>所以这套接入应该按四层理解：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Mattermost thread / DM / channel</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; bot adapter 或 gateway</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">     -&gt; Hermes runtime / cc-connect runtime</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        -&gt; 可选的 ChatRSS TriggerEvent / Router / Action / Ledger</span><br></div></code></pre></div></div>
<p>每一层职责不同：</p>
<table><thead><tr><th>层</th><th>负责什么</th><th>不负责什么</th></tr></thead><tbody><tr><td>Mattermost</td><td>channel、post、thread、DM、mention、bot account、REST/WebSocket 事件</td><td>Agent 推理、工具执行、长期任务账本</td></tr><tr><td>Hermes Gateway</td><td>把 <code>@hermes-agent</code> / DM / thread 消息转成 Hermes session，并把结果写回 Mattermost</td><td>cc-connect 的本地 coding agent 命令</td></tr><tr><td>cc-connect adapter</td><td>把 <code>@cc-connect</code> / DM 消息转成 cc-connect <code>core.Message</code>，调用它自己的 runtime 和命令体系</td><td>Hermes <code>/ssh</code>、Hermes skills、Hermes gateway session</td></tr><tr><td>ChatRSS</td><td>把跨来源事件标准化成 TriggerEvent，做 router、action、ledger、审计和回放</td><td>替代实时聊天里的低延迟 bot reply happy path</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="hermes-的入口和命令边界">Hermes 的入口和命令边界<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-chatrss-multi-agent-conversation-rules#hermes-%E7%9A%84%E5%85%A5%E5%8F%A3%E5%92%8C%E5%91%BD%E4%BB%A4%E8%BE%B9%E7%95%8C" class="hash-link" aria-label="Hermes 的入口和命令边界的直接链接" title="Hermes 的入口和命令边界的直接链接" translate="no">​</a></h3>
<p>Hermes 进 Mattermost 后，它不是一个简单 echo bot。它背后是完整 Hermes Agent runtime：模型、工具、skills、记忆、文件、浏览器、SSH、发布流程都可以进入一次任务。</p>
<p>Mattermost 侧的触发规则应该保持简单：</p>
<table><thead><tr><th>场景</th><th>默认触发规则</th><th>推荐回复位置</th></tr></thead><tbody><tr><td>普通频道顶层消息</td><td>需要 <code>@hermes-agent</code></td><td>这条消息的 thread</td></tr><tr><td>已有 thread reply</td><td>建议继续 <code>@hermes-agent</code></td><td>同一个 thread</td></tr><tr><td>DM 私聊 <code>hermes-agent</code></td><td>不需要 @</td><td>DM 对话或 DM thread</td></tr><tr><td>普通频道里不 @ 的消息</td><td>不触发</td><td>避免误读频道聊天</td></tr></tbody></table>
<p>在这次实测里，<code>@hermes-agent /ssh status</code> 进入的是 Hermes 的 slash command / gateway command 体系。也就是说：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">@hermes-agent /ssh status</span><br></div></code></pre></div></div>
<p>是 Hermes 命令，不是 cc-connect 命令。</p>
<p>对 ChatArch 来说，<code>/ssh</code> 这一组命令用于查看和切换 Hermes SSH Mode 状态，例如：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">/ssh status</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">/ssh list</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">/ssh test &lt;target&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">/ssh use &lt;target&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">/ssh on &lt;target&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">/ssh off &lt;target&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">/ssh help</span><br></div></code></pre></div></div>
<p>在 Mattermost 里，只要前面加上 bot mention，就可以把它作为 Hermes Gateway 命令发送：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">@hermes-agent /ssh status</span><br></div></code></pre></div></div>
<p>这条路径的意义是：Mattermost thread 可以直接成为 Hermes 运维和执行任务入口，而不是需要人在平台之间复制粘贴。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cc-connect-的入口和命令边界">cc-connect 的入口和命令边界<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-chatrss-multi-agent-conversation-rules#cc-connect-%E7%9A%84%E5%85%A5%E5%8F%A3%E5%92%8C%E5%91%BD%E4%BB%A4%E8%BE%B9%E7%95%8C" class="hash-link" aria-label="cc-connect 的入口和命令边界的直接链接" title="cc-connect 的入口和命令边界的直接链接" translate="no">​</a></h3>
<p>cc-connect 的定位和 Hermes 不一样。它是把 Claude Code、Codex、Cursor、Gemini CLI、Kimi CLI 等本地 coding agent 接到聊天平台的 connector。现在 ChatArch 已经给 cc-connect 补了 Mattermost adapter，并随 v1.0.6 发布；公开 release 入口在这里：</p>
<p><a href="https://github.com/ChatArch/cc-connect/releases/tag/v1.0.6" target="_blank" rel="noopener noreferrer" class="">https://github.com/ChatArch/cc-connect/releases/tag/v1.0.6</a></p>
<p>这次 Mattermost adapter 的核心路径是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Mattermost WebSocket posted event</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; cc-connect platform/mattermost</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; mention / DM / allowlist / dedupe / self-message filter</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; cc-connect core.Message</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; cc-connect Engine</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 内置命令或配置的 coding agent runtime</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; Mattermost REST API thread reply</span><br></div></code></pre></div></div>
<p>在频道里，cc-connect 默认也应该要求显式 mention：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">@cc-connect /help</span><br></div></code></pre></div></div>
<p>DM 里则可以免 @，因为私聊本身已经表达“我在和这个 bot 说话”。</p>
<p>cc-connect 的当前命令面不是 Hermes 命令面。它自己的 <code>/help</code> 会列出 cc-connect session 和 agent 管理相关命令，常用心智如下：</p>
<table><thead><tr><th>命令</th><th>用途</th></tr></thead><tbody><tr><td><code>/help</code></td><td>显示可用命令。</td></tr><tr><td><code>/new</code></td><td>新建会话。</td></tr><tr><td><code>/list</code></td><td>列出当前可切换 session。</td></tr><tr><td><code>/search</code></td><td>搜索 session。</td></tr><tr><td><code>/switch</code></td><td>切换到指定 session。</td></tr><tr><td><code>/delete</code></td><td>删除 session。</td></tr><tr><td><code>/history</code></td><td>查看当前 cc-connect session 历史。</td></tr><tr><td><code>/model</code></td><td>查看或切换当前模型/agent 配置。</td></tr><tr><td><code>/shell</code></td><td>shell 相关入口。</td></tr><tr><td><code>/thread</code></td><td>thread 相关入口。</td></tr><tr><td><code>/cron</code></td><td>定时任务相关入口。</td></tr><tr><td><code>/dir</code></td><td>工作目录相关入口。</td></tr><tr><td><code>/continue</code></td><td>继续当前 session。</td></tr><tr><td><code>/retry</code></td><td>重试上一轮。</td></tr><tr><td><code>/stop</code></td><td>停止当前任务。</td></tr><tr><td><code>/rename</code></td><td>重命名 session。</td></tr><tr><td><code>/status</code></td><td>查看状态。</td></tr></tbody></table>
<p>这也是为什么我们必须把 <code>@hermes-agent</code> 和 <code>@cc-connect</code> 分成两个账号：同一个用户在同一个 Mattermost thread 里可以分别向两个 runtime 发命令，但两个命令表不应该混在一起。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二对话历史和-multiagent-规则thread-共享session-不混互相--必须防回环">二、对话历史和 MultiAgent 规则：thread 共享，session 不混，互相 @ 必须防回环<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-chatrss-multi-agent-conversation-rules#%E4%BA%8C%E5%AF%B9%E8%AF%9D%E5%8E%86%E5%8F%B2%E5%92%8C-multiagent-%E8%A7%84%E5%88%99thread-%E5%85%B1%E4%BA%ABsession-%E4%B8%8D%E6%B7%B7%E4%BA%92%E7%9B%B8--%E5%BF%85%E9%A1%BB%E9%98%B2%E5%9B%9E%E7%8E%AF" class="hash-link" aria-label="二、对话历史和 MultiAgent 规则：thread 共享，session 不混，互相 @ 必须防回环的直接链接" title="二、对话历史和 MultiAgent 规则：thread 共享，session 不混，互相 @ 必须防回环的直接链接" translate="no">​</a></h2>
<p>Mattermost 最关键的一对概念是 root post 和 thread。放到 Agent 场景里，可以把一个对话单元定义成：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">conversation = server + channel + root post</span><br></div></code></pre></div></div>
<p>如果用户在频道里发一条新的顶层消息：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">@hermes-agent 看一下 ChatRSS 现在在做什么。</span><br></div></code></pre></div></div>
<p>这条消息就是 root post。Hermes 的后续回复、用户追问、cc-connect 的补充，都应该尽量回到这个 root post 下面的 thread。</p>
<p>这比“整个频道共享一条历史”安全，也比“每条 reply 都新开一个会话”自然：</p>
<table><thead><tr><th>模型</th><th>问题</th></tr></thead><tbody><tr><td>整个频道一条历史</td><td>不同任务、不同用户、不同 Agent 互相污染，成本和误触发都会变高。</td></tr><tr><td>每条消息一条历史</td><td>Agent 无法记住同一个 thread 内的上下文，追问体验很差。</td></tr><tr><td>root post + thread</td><td>每个任务有明确入口，thread 内连续，thread 间隔离。</td></tr></tbody></table>
<p>所以 ChatArch 当前推荐：<strong>一个任务从一个 root post 开始，后续都在 thread 里继续；新的任务另开 root post。</strong></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cc-connect-的-session_scope">cc-connect 的 <code>session_scope</code><a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-chatrss-multi-agent-conversation-rules#cc-connect-%E7%9A%84-session_scope" class="hash-link" aria-label="cc-connect-的-session_scope的直接链接" title="cc-connect-的-session_scope的直接链接" translate="no">​</a></h3>
<p>cc-connect Mattermost adapter 有一个关键配置：<code>session_scope</code>。它决定一条 Mattermost 消息进入 cc-connect 后，应该归到哪个本地 session key。</p>
<p>可以粗略理解成三种模式：</p>
<table><thead><tr><th><code>session_scope</code></th><th>会话粒度</th><th>适合场景</th><th>风险</th></tr></thead><tbody><tr><td><code>user</code></td><td>同一个 Mattermost 用户共用一个 cc-connect session</td><td>私人 bot、低并发个人入口</td><td>同一用户在多个 thread 里做不同任务时容易串话。</td></tr><tr><td><code>channel</code></td><td>同一个 channel 共用一个 cc-connect session</td><td>小团队单任务频道</td><td>多任务频道会互相污染。</td></tr><tr><td><code>thread</code></td><td>同一个 root post/thread 一个 cc-connect session</td><td>Agent 工作间、多任务并行</td><td>需要用户养成 thread-first 习惯。</td></tr></tbody></table>
<p>这次验收用的是 <code>thread</code>。这也是我建议的生产默认值：<strong>多 Agent 房间里，cc-connect 应该按 Mattermost thread 隔离历史。</strong></p>
<p>注意这里的“历史”是 cc-connect 自己的 session history，不等于 Mattermost thread 的完整消息历史。比如：</p>
<ul>
<li class="">用户在 thread 里提到 <code>@hermes-agent</code>，Hermes 收到了；</li>
<li class="">这条消息如果没有触发 <code>@cc-connect</code>，cc-connect 默认不应该把它吸进自己的 runtime history；</li>
<li class="">后面用户再 <code>@cc-connect</code> 时，cc-connect 能看到的上下文取决于 adapter 是否做了 thread backfill，或者用户是否把必要背景写在这次触发消息里。</li>
</ul>
<p>所以 <code>/history</code> 这类命令只能解释为“cc-connect 眼里的 session history”，不能等同于“Mattermost UI 中这条 thread 的全部历史”。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="multiagent-之间到底共享什么历史">MultiAgent 之间到底共享什么历史<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-chatrss-multi-agent-conversation-rules#multiagent-%E4%B9%8B%E9%97%B4%E5%88%B0%E5%BA%95%E5%85%B1%E4%BA%AB%E4%BB%80%E4%B9%88%E5%8E%86%E5%8F%B2" class="hash-link" aria-label="MultiAgent 之间到底共享什么历史的直接链接" title="MultiAgent 之间到底共享什么历史的直接链接" translate="no">​</a></h3>
<p>多个 Agent 在一个 thread 里，历史共享要分三层看。</p>
<p>第一层是 <strong>UI 可见历史</strong>。所有参与者，包括人和 bot，都能在 Mattermost UI 里看到 thread 中的公开文本。这个层面是共享的：Hermes 回复了什么，cc-connect 回复了什么，人都能看到。</p>
<p>第二层是 <strong>adapter ingest 历史</strong>。每个 adapter 会根据自己的触发规则决定“要不要把这条 post 交给 runtime”。</p>
<p>例如：</p>
<table><thead><tr><th>消息</th><th>Hermes 会不会收</th><th>cc-connect 会不会收</th></tr></thead><tbody><tr><td><code>@hermes-agent /ssh status</code></td><td>会</td><td>默认不会，除非它被配置为读取其它 bot mention 或 thread backfill。</td></tr><tr><td><code>@cc-connect /help</code></td><td>默认不会，除非 Hermes 被明确提到或配置为自由监听</td><td>会。</td></tr><tr><td>普通用户在频道里不 @ 的闲聊</td><td>默认不会</td><td>默认不会。</td></tr><tr><td>DM 给 <code>hermes-agent</code></td><td>会</td><td>不会。</td></tr><tr><td>DM 给 <code>cc-connect</code></td><td>不会</td><td>会。</td></tr></tbody></table>
<p>第三层是 <strong>runtime 内部记忆</strong>。Hermes 有自己的 gateway session、memory、skills 和工具上下文；cc-connect 有自己的 session manager、agent runtime 和历史管理。两者不会自动互相读取。</p>
<p>因此，MultiAgent 协作的安全默认值应该是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">shared = Mattermost thread 中显式可见的消息</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">private = 每个 Agent 自己的 session/history/memory/tools</span><br></div></code></pre></div></div>
<p>如果需要真正共享知识，应该通过显式机制完成，而不是假设两个 runtime 天然互通。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="agent-能不能互相-">Agent 能不能互相 @<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-chatrss-multi-agent-conversation-rules#agent-%E8%83%BD%E4%B8%8D%E8%83%BD%E4%BA%92%E7%9B%B8-" class="hash-link" aria-label="Agent 能不能互相 @的直接链接" title="Agent 能不能互相 @的直接链接" translate="no">​</a></h3>
<p>协议上可以。一个 bot 回复里写：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">@cc-connect 请继续看这个仓库的构建问题。</span><br></div></code></pre></div></div>
<p>这在 Mattermost 里就是一段普通 mention 文本。目标 bot 是否会响应，取决于四个条件：</p>
<ol>
<li class="">目标 bot 是否监听这个 channel 或 thread；</li>
<li class="">目标 bot 是否把其它 bot 发出的消息视为允许来源；</li>
<li class="">allowlist 是否允许这个 sender；</li>
<li class="">bot/self-message filter 是否会把它过滤掉。</li>
</ol>
<p>但“能做到”不等于“应该默认放开”。如果让 Agent 之间自由互相 @，最容易出现的是回环：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Hermes: @cc-connect 你来处理</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cc-connect: @hermes-agent 我需要更多背景</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Hermes: @cc-connect 背景如下</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cc-connect: @hermes-agent 继续确认</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">...</span><br></div></code></pre></div></div>
<p>所以生产规则应该是：默认不允许无边界 bot-to-bot ping-pong。如果确实需要 Agent handoff，至少要有：</p>
<table><thead><tr><th>机制</th><th>作用</th></tr></thead><tbody><tr><td>明确 allowlist</td><td>哪些 bot 可以唤醒哪些 bot。</td></tr><tr><td>correlation id</td><td>这次 handoff 属于哪一个任务。</td></tr><tr><td>hop counter</td><td>最多转交几次，防止无限循环。</td></tr><tr><td>idempotency key</td><td>同一事件不会重复触发同一 action。</td></tr><tr><td>ledger</td><td>谁触发、谁接受、做了什么、是否成功。</td></tr><tr><td>human gate</td><td>高风险动作仍需人工批准。</td></tr></tbody></table>
<p>这就是 ChatRSS 应该参与的地方。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三chatrss-的位置triggerevent--router--action--ledger而不是替代实时聊天-happy-path">三、ChatRSS 的位置：TriggerEvent / Router / Action / Ledger，而不是替代实时聊天 happy path<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-chatrss-multi-agent-conversation-rules#%E4%B8%89chatrss-%E7%9A%84%E4%BD%8D%E7%BD%AEtriggerevent--router--action--ledger%E8%80%8C%E4%B8%8D%E6%98%AF%E6%9B%BF%E4%BB%A3%E5%AE%9E%E6%97%B6%E8%81%8A%E5%A4%A9-happy-path" class="hash-link" aria-label="三、ChatRSS 的位置：TriggerEvent / Router / Action / Ledger，而不是替代实时聊天 happy path的直接链接" title="三、ChatRSS 的位置：TriggerEvent / Router / Action / Ledger，而不是替代实时聊天 happy path的直接链接" translate="no">​</a></h2>
<p>ChatRSS 现在的方向是 trigger-router-action framework。它不应该把每一条 Mattermost 实时聊天都抢到自己那里再转给 Hermes。实时聊天的 happy path 更短：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">人类 @hermes-agent</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; Hermes Mattermost Gateway</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; Hermes runtime</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; Mattermost thread reply</span><br></div></code></pre></div></div>
<p>或：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">人类 @cc-connect</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; cc-connect Mattermost adapter</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; cc-connect runtime</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; Mattermost thread reply</span><br></div></code></pre></div></div>
<p>ChatRSS 的价值在另一类问题上：当 Mattermost 只是一堆事件来源之一，而我们需要统一处理 GitHub、RSSHub、Discourse、Zulip、Mattermost post、评论、release 请求和审批时，它可以把这些来源标准化成同一类 TriggerEvent。</p>
<p>可以把它想成：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Source Event</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; TriggerEvent</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; Event Inbox / Dedupe</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; Rule Router</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; Model Router</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; Action Planner</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; Action Executor</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; Ledger / Audit</span><br></div></code></pre></div></div>
<p>这和前一篇 RSS/RSSHub 文章里的分层是一致的：RSSHub 把外部更新变成 feed；ChatRSS 把 feed 或其它平台事件变成协作事件；Hermes 或 cc-connect 负责具体推理和行动。</p>
<p>当前 ChatRSS CLI 也体现了这个过渡状态。已经可回读的真实命令面包括：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">chatrss --tree</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">chatrss cat</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">chatrss flow demo</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">chatrss init</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">chatrss ps</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">chatrss server start/status/logs/restart/stop/url</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">chatrss watch</span><br></div></code></pre></div></div>
<p>而 <code>trigger</code>、<code>event</code>、<code>router</code>、<code>model</code>、<code>action</code>、<code>ledger</code>、<code>connector</code> 这些更完整的命名空间，目前更适合当 minor 版本目标树：先在文档里明确设计，不要做成空壳命令。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="推荐生产约定">推荐生产约定<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-chatrss-multi-agent-conversation-rules#%E6%8E%A8%E8%8D%90%E7%94%9F%E4%BA%A7%E7%BA%A6%E5%AE%9A" class="hash-link" aria-label="推荐生产约定的直接链接" title="推荐生产约定的直接链接" translate="no">​</a></h3>
<p>如果 ChatArch 要把 Mattermost 变成多人多 Agent 工作间，可以先采用这组规则。</p>
<p>第一，<strong>每个 runtime 一个 bot account</strong>。</p>
<table><thead><tr><th>Runtime</th><th>Mattermost 账号</th><th>说明</th></tr></thead><tbody><tr><td>Hermes</td><td><code>@hermes-agent</code></td><td>工具型 Hermes runtime 和 gateway command 入口。</td></tr><tr><td>cc-connect</td><td><code>@cc-connect</code></td><td>cc-connect command 和本地 coding agent connector 入口。</td></tr><tr><td>后续其它 Agent</td><td>独立 bot account</td><td>不混用 token，不混用审计身份。</td></tr></tbody></table>
<p>第二，<strong>共享频道默认 mention gating</strong>。频道里默认只有显式 <code>@bot</code> 才触发 Agent；DM 里可以免 @。这条规则和飞书/Slack 用户心智一致，也能避免 bot 读入整个频道闲聊。</p>
<p>第三，<strong>thread 是任务边界</strong>。一个 root post 对应一个任务 thread。Agent 回复、追问、handoff、验收链接都回到同一个 thread。新的任务另开 root post。</p>
<p>第四，<strong>Agent 内部历史默认不共享</strong>。不要把两个 runtime 的 memory 混成一个公共池。需要共享时，写入 Mattermost thread 或 ChatRSS ledger；需要交接时，写清楚摘要、目标 bot、任务 ID 和上下文范围。</p>
<p>第五，<strong>bot-to-bot handoff 走显式路由</strong>。允许 Agent 在文本里 mention 另一个 Agent，但生产环境里应由 ChatRSS 或类似 ledger/router 层做防回环和审计，而不是让 bot 直接无限互叫。</p>
<p>第六，<strong>写动作要有回读证据</strong>。无论是 Hermes 发 PR、cc-connect 跑 coding agent，还是 ChatRSS 执行 action，最终都应该把可验证链接或结果写回 thread：PR、release、workflow、deployment、docs URL、ledger entry，而不是只说“完成了”。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="当前还缺什么">当前还缺什么<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-chatrss-multi-agent-conversation-rules#%E5%BD%93%E5%89%8D%E8%BF%98%E7%BC%BA%E4%BB%80%E4%B9%88" class="hash-link" aria-label="当前还缺什么的直接链接" title="当前还缺什么的直接链接" translate="no">​</a></h3>
<p>这次实测已经证明双账号和同 thread 多 Agent 表面可以跑通，但还有几个边界不能说成已完成：</p>
<table><thead><tr><th>项</th><th>当前状态</th></tr></thead><tbody><tr><td>cc-connect Mattermost adapter</td><td>已合入并随 v1.0.6 发布。</td></tr><tr><td><code>@cc-connect /help</code></td><td>已用独立 cc-connect bot account 实测。</td></tr><tr><td>Hermes <code>@hermes-agent /ssh status</code></td><td>已在 Mattermost thread 实测。</td></tr><tr><td>cc-connect 长期常驻</td><td>还不是生产 daemon；这次是临时验收进程。</td></tr><tr><td>cc-connect Hermes <code>/ssh</code> bridge</td><td>未实现；<code>/ssh</code> 仍属于 Hermes。</td></tr><tr><td>reaction lifecycle</td><td>未实现；可以后续加 👀 / ⏳ / ✅ 这类处理状态。</td></tr><tr><td>ChatRSS 完整 Mattermost connector / ledger</td><td>是建议建设方向，不是当前已部署事实。</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="一个完整例子">一个完整例子<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-chatrss-multi-agent-conversation-rules#%E4%B8%80%E4%B8%AA%E5%AE%8C%E6%95%B4%E4%BE%8B%E5%AD%90" class="hash-link" aria-label="一个完整例子的直接链接" title="一个完整例子的直接链接" translate="no">​</a></h3>
<p>假设一个频道叫 <code>chatrss-triggers</code>。用户想让两个 Agent 分工，可以这样写：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Root post:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">请在这个 thread 里检查 ChatRSS 的 Mattermost 接入规则。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Thread reply 1:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">@hermes-agent 先说明当前 Hermes gateway 的 session 隔离和 /ssh status。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Thread reply 2:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">@cc-connect /help</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Thread reply 3:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">@hermes-agent 请根据 cc-connect 返回的命令表，整理一版面向 ChatBlog 的规则。</span><br></div></code></pre></div></div>
<p>这时：</p>
<ul>
<li class="">Mattermost thread 是共同协作面；</li>
<li class="">Hermes 只在被 <code>@hermes-agent</code> 触发时进入 Hermes session；</li>
<li class="">cc-connect 只在被 <code>@cc-connect</code> 触发时进入 cc-connect session；</li>
<li class="">两个 Agent 的内部历史不自动合并；</li>
<li class="">如果要让 cc-connect 使用 Hermes 刚才总结的内容，最好把摘要显式写进同一条 <code>@cc-connect</code> 消息，或者由 ChatRSS ledger/router 注入上下文。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="小结">小结<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-chatrss-multi-agent-conversation-rules#%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="小结的直接链接" title="小结的直接链接" translate="no">​</a></h3>
<p>Mattermost 给 ChatArch 提供的是一个自托管、thread-first 的人机协作空间。Hermes 和 cc-connect 进来以后，它就不只是“聊天工具”，而是一个可以承载任务触发、Agent 执行、结果回读和多 Agent 协作的实时工作面。</p>
<p>但多 Agent 的关键不是“把很多 bot 拉进一个频道”。真正关键的是边界：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Mattermost thread  = 共享可见上下文</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Hermes session     = Hermes 自己的执行历史</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cc-connect session = cc-connect 自己的执行历史</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ChatRSS ledger     = 需要跨平台、可审计、可回放的事件历史</span><br></div></code></pre></div></div>
<p>把这四件事分清楚，Agent Community 才不会变成一群机器人在频道里互相吵架；它会变成一套可追踪、可协作、可逐步自治的工作系统。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="主要来源">主要来源<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-chatrss-multi-agent-conversation-rules#%E4%B8%BB%E8%A6%81%E6%9D%A5%E6%BA%90" class="hash-link" aria-label="主要来源的直接链接" title="主要来源的直接链接" translate="no">​</a></h3>
<ul>
<li class=""><a class="" href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-hermes-agent-workspace">把 Agent 放进自己的实时工作间：Mattermost、Hermes 与 ChatArch 的下一块拼图</a></li>
<li class=""><a class="" href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-posting-model-slack-comparison">看懂 Mattermost：频道、帖子、Thread、私聊，以及它和 Slack/飞书哪里不一样</a></li>
<li class=""><a class="" href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/rss-and-rsshub-foundation-for-chatrss">RSS 与 RSSHub 入门：把网页更新变成可订阅的信息流</a></li>
<li class=""><a href="https://hermes-agent.nousresearch.com/docs/user-guide/messaging/mattermost/" target="_blank" rel="noopener noreferrer" class="">Hermes Mattermost 官方文档</a></li>
<li class=""><a href="https://docs.mattermost.com/end-user-guide/collaborate/organize-conversations.html" target="_blank" rel="noopener noreferrer" class="">Mattermost threaded discussions</a></li>
<li class=""><a href="https://developers.mattermost.com/integrate/reference/bot-accounts/" target="_blank" rel="noopener noreferrer" class="">Mattermost bot accounts</a></li>
<li class=""><a href="https://api.mattermost.com/" target="_blank" rel="noopener noreferrer" class="">Mattermost REST API reference</a></li>
<li class=""><a href="https://github.com/chenhg5/cc-connect" target="_blank" rel="noopener noreferrer" class="">CC Connect</a></li>
<li class=""><a href="https://github.com/ChatArch/cc-connect/releases/tag/v1.0.6" target="_blank" rel="noopener noreferrer" class="">ChatArch cc-connect v1.0.6 release</a></li>
<li class=""><a href="https://github.com/ChatArch/ChatRSS" target="_blank" rel="noopener noreferrer" class="">ChatRSS</a></li>
</ul>]]></content>
        <category label="mattermost" term="mattermost"/>
        <category label="chatrss" term="chatrss"/>
        <category label="hermes" term="hermes"/>
        <category label="cc-connect" term="cc-connect"/>
        <category label="multi-agent" term="multi-agent"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Frontend Slides：zarazhangrui 的 AI Agent HTML 演示文稿 Skill 详解]]></title>
        <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill</id>
        <link href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill"/>
        <updated>2026-08-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[详细拆解 zarazhangrui/frontend-slides：它为什么不是一个普通 PPT 模板，而是一套面向 AI coding agent 的零依赖 HTML 演示文稿 skill；以及它如何辅助 ChatBlog 继续完善 Web Slides 工作流。]]></summary>
        <content type="html"><![CDATA[<p>ChatBlog 已经有一个 <a href="https://arch.gh.wzhecnu.cn/ChatBlog/slides" target="_blank" rel="noopener noreferrer" class="">Slides</a> 入口，也已经有一篇文章讨论 Patrick Fu 那套更工程化的 <a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/frontend-harness-slides-web-deck-workflow" target="_blank" rel="noopener noreferrer" class="">Frontend Harness Slides 工作流</a>。那篇文章的重点是：把一套演示文稿做成小型前端 artifact，用 scene / beat / URL state / build / preview / readback 来治理。</p>
<p>这次看的 <code>zarazhangrui/frontend-slides</code> 是另一个互补方向：它不是 React/Vite workbench，也不是一个大而全的 slides 平台，而是一个 <strong>AI coding agent skill</strong>。它把“做一套好看的 Web Slides”拆成一套可被 Claude Code、Codex、Kimi Code、OpenCode、Gemini CLI 等本地 coding agent 读取和执行的流程：先看内容，再做 3 张视觉 preview，让用户用眼睛选方向，最后生成一个零依赖、固定 16:9、可分享、可导出 PDF 的单 HTML 演示文稿。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>一句话结论</div><div class="admonitionContent_BuS1"><p><code>zarazhangrui/frontend-slides</code> 最值得学习的不是某个模板，而是它把 presentation 生成变成了 <strong>agent-readable skill + progressive disclosure design system + fixed-stage single HTML artifact</strong>。对 ChatBlog 来说，它可以补上“从博客/调研快速生成可看的 slides 草稿”和“用视觉 preview 选风格”的能力；但正式纳入 ChatBlog 时，仍要走我们自己的 Git / PR / Docusaurus / 生产 readback 发布链路。</p></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>证据口径</div><div class="admonitionContent_BuS1"><p>本文快照时间为 <strong>2026-08-12 16:22 CST</strong>。证据来自 <code>zarazhangrui/frontend-slides</code> 的 GitHub API、README、<code>SKILL.md</code>、<code>STYLE_PRESETS.md</code>、<code>html-template.md</code>、<code>viewport-base.css</code>、<code>animation-patterns.md</code>、<code>bold-template-pack/README.md</code>、<code>bold-template-pack/selection-index.json</code>、<code>.claude-plugin/marketplace.json</code>、<code>scripts/</code> 目录，以及 ChatBlog 当前 <code>Slides</code> 页面和 <code>Agent Community Quick Start</code> 静态 deck 源码。本文只做静态读取，没有安装、克隆或运行该仓库。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一这个仓库到底是什么">一、这个仓库到底是什么<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#%E4%B8%80%E8%BF%99%E4%B8%AA%E4%BB%93%E5%BA%93%E5%88%B0%E5%BA%95%E6%98%AF%E4%BB%80%E4%B9%88" class="hash-link" aria-label="一、这个仓库到底是什么的直接链接" title="一、这个仓库到底是什么的直接链接" translate="no">​</a></h2>
<p>先给它一个准确定位：<code>zarazhangrui/frontend-slides</code> 是一个 <strong>面向 coding agent 的 presentation skill</strong>。</p>
<p>它的 README 第一段写得很直接：这是一个用于创建 HTML presentations 的 coding-agent skill，可以从零生成，也可以把 PowerPoint 转成 web；它被打包成 Claude Code plugin，同时核心 <code>SKILL.md</code> 也可以被其它有文件系统和 shell 访问能力的 coding agent 读取。</p>
<p>仓库快照如下：</p>
<table><thead><tr><th>项</th><th>快照</th></tr></thead><tbody><tr><td>GitHub</td><td><a href="https://github.com/zarazhangrui/frontend-slides" target="_blank" rel="noopener noreferrer" class="">https://github.com/zarazhangrui/frontend-slides</a></td></tr><tr><td>描述</td><td>Create beautiful slides on the web using a coding agent's frontend skills</td></tr><tr><td>License</td><td>MIT</td></tr><tr><td>默认分支</td><td><code>main</code></td></tr><tr><td>创建时间</td><td>2026-01-28</td></tr><tr><td>最近 push</td><td>2026-06-23</td></tr><tr><td>Stars / Forks / Open issues</td><td>27,350 / 2,217 / 65</td></tr><tr><td>GitHub topics</td><td><code>ai-slides</code>, <code>claude-code</code>, <code>claude-skill</code>, <code>html</code>, <code>presentation</code>, <code>slides</code>, <code>vibe-coding</code> 等</td></tr><tr><td>主要语言统计</td><td>JavaScript、Shell、Python、CSS</td></tr></tbody></table>
<p>从文件结构看，它也不像一个普通前端 app。根目录没有 <code>package.json</code> 这种应用入口，而是围绕 skill 运行时需要读的材料组织：</p>
<table><thead><tr><th>文件 / 目录</th><th>角色</th></tr></thead><tbody><tr><td><code>SKILL.md</code></td><td>核心工作流：模式判断、提问、视觉 preview、生成、PPT 转换、交付、分享和导出。</td></tr><tr><td><code>STYLE_PRESETS.md</code></td><td>12 个安全基础视觉 preset：暗色、亮色、专业、复古、终端、纸张等。</td></tr><tr><td><code>bold-template-pack/selection-index.json</code></td><td>34 个更强风格模板的轻量索引，只在 style discovery 时读取元数据。</td></tr><tr><td><code>bold-template-pack/templates/*/preview.md</code></td><td>被 shortlist 后才读的小 preview card。</td></tr><tr><td><code>bold-template-pack/templates/*/design.md</code></td><td>用户选定某个 bold template 后才读的完整设计系统。</td></tr><tr><td><code>viewport-base.css</code></td><td>强制固定 16:9 stage 的基础 CSS，生成最终 deck 时要完整内联。</td></tr><tr><td><code>html-template.md</code></td><td>单 HTML deck 的架构、JS controller、inline editing、图片处理和代码规范。</td></tr><tr><td><code>animation-patterns.md</code></td><td>动效和情绪的映射：cinematic、techy、playful、professional、editorial 等。</td></tr><tr><td><code>scripts/extract-pptx.py</code></td><td>从 <code>.pptx</code> 提取文字、图片、speaker notes，用于 PPT 转 web。</td></tr><tr><td><code>scripts/deploy.sh</code></td><td>把 HTML 或目录部署到 Vercel。</td></tr><tr><td><code>scripts/export-pdf.sh</code></td><td>用 Playwright 截每页 1920×1080 图并合成 PDF。</td></tr></tbody></table>
<p>所以它不是“下载后启动一个服务”，而更像一套给 Agent 使用的设计与生成手册。Agent 读 <code>SKILL.md</code>，再按当前阶段选择性读取支持文件，最后产出 HTML。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二核心心智不是让用户描述风格而是让用户看见风格">二、核心心智：不是让用户描述风格，而是让用户看见风格<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#%E4%BA%8C%E6%A0%B8%E5%BF%83%E5%BF%83%E6%99%BA%E4%B8%8D%E6%98%AF%E8%AE%A9%E7%94%A8%E6%88%B7%E6%8F%8F%E8%BF%B0%E9%A3%8E%E6%A0%BC%E8%80%8C%E6%98%AF%E8%AE%A9%E7%94%A8%E6%88%B7%E7%9C%8B%E8%A7%81%E9%A3%8E%E6%A0%BC" class="hash-link" aria-label="二、核心心智：不是让用户描述风格，而是让用户看见风格的直接链接" title="二、核心心智：不是让用户描述风格，而是让用户看见风格的直接链接" translate="no">​</a></h2>
<p><code>frontend-slides</code> 的核心产品判断是：大多数非设计用户很难用抽象词准确说出自己想要什么风格，但他们能看懂“我喜欢 A，不喜欢 B”。所以它把 style selection 做成 <strong>show, don't tell</strong>。</p>
<p>它默认流程不是问一堆“你喜欢极简还是现代”这种选择题，而是生成 3 张真实 title-slide preview：</p>
<ol>
<li class="">一个来自 <code>STYLE_PRESETS.md</code> 的安全 preset；</li>
<li class="">至少一个来自 <code>bold-template-pack</code> 的 bold template；</li>
<li class="">一个 wildcard，可以是第二个 bold template，也可以是 Agent 自己根据内容设计的 custom direction。</li>
</ol>
<p>这里的重点是“真实 title slide”。<code>SKILL.md</code> 明确要求 preview 不能像诊断卡片，不能写 <code>Option A/B/C</code>、<code>template</code>、<code>preview.md</code>、<code>generated from</code> 这种内部流程字样，也不能把用户需求说明直接写到 slide 上。用户看到的应该是一张像正式 deck 第一页的东西。</p>
<p>这点对我们很重要。ChatBlog 后续如果把文章变 slides，不应该直接把正文段落塞进一个默认模板，而应该先进入一个视觉选择阶段：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">文章 / 调研 / 项目结果</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; deck brief</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 3 张真实 visual preview</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 选定风格</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 完整 deck</span><br></div></code></pre></div></div>
<p>这比“先生成 20 页，再让用户挑毛病”更省时间，也更容易避免 AI 常见的紫色渐变、白底卡片、Inter 字体、通用 dashboard 风。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三固定-169-stage-是非协商项">三、固定 16:9 stage 是非协商项<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#%E4%B8%89%E5%9B%BA%E5%AE%9A-169-stage-%E6%98%AF%E9%9D%9E%E5%8D%8F%E5%95%86%E9%A1%B9" class="hash-link" aria-label="三、固定 16:9 stage 是非协商项的直接链接" title="三、固定 16:9 stage 是非协商项的直接链接" translate="no">​</a></h2>
<p><code>frontend-slides</code> 对输出几何有一个硬约束：<strong>每套 deck 都是 1920×1080 固定 stage，整个 stage 根据浏览器窗口等比缩放</strong>。</p>
<p><code>viewport-base.css</code> 里定义了这套基础模型：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">deck-viewport = fixed inset 0, 占满浏览器窗口</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">deck-stage    = absolute 1920px × 1080px, transform-origin: 0 0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">slide         = absolute inset 0, 1920px × 1080px</span><br></div></code></pre></div></div>
<p>也就是说，slide 内容不是普通网页布局，不应该在手机上改成另一个响应式排版。它更像舞台：舞台的坐标不变，观众屏幕大小变化时整体缩放、留黑边或留白边。</p>
<p>这条规则解决了 Web Slides 里一个很常见的问题：如果每张 slide 都写成响应式网页，投影、截图、PDF、手机预览、CI 检查看到的布局可能完全不一样。固定 stage 后，排版、标注、箭头、图表和 reveal 位置都稳定了。</p>
<p>同时，<code>SKILL.md</code> 对 slide 切换也有明确坑位提醒：不要用 <code>display: none</code> / <code>display: block</code> 控制 slide 显隐，而要用 <code>.active</code> / <code>.visible</code> 配合 <code>visibility</code>、<code>opacity</code>、<code>pointer-events</code>。原因是后续 <code>.slide-content { display: flex; }</code> 之类布局类可能覆盖 display，把所有 slide 一次性显示出来。</p>
<p>这些看起来像 CSS 小规则，但实际是 presentation artifact 的底层契约。ChatBlog 现在的 <code>Agent Community Quick Start</code> deck 已经有 scene / beat / URL state，但它是一个轻量静态 HTML demo，舞台尺寸更多依赖外层 <code>aspect-ratio</code> 和 <code>clamp()</code>。如果以后要继续打磨，我们可以借 <code>frontend-slides</code> 把它升级为更严格的 1920×1080 fixed-stage 模型。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四它如何避免ai-味的视觉输出">四、它如何避免“AI 味”的视觉输出<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#%E5%9B%9B%E5%AE%83%E5%A6%82%E4%BD%95%E9%81%BF%E5%85%8Dai-%E5%91%B3%E7%9A%84%E8%A7%86%E8%A7%89%E8%BE%93%E5%87%BA" class="hash-link" aria-label="四、它如何避免“AI 味”的视觉输出的直接链接" title="四、它如何避免“AI 味”的视觉输出的直接链接" translate="no">​</a></h2>
<p><code>frontend-slides</code> 对审美的要求写得很直白：避免 generic “AI slop”。它点名了几类常见问题：</p>
<ul>
<li class="">过度使用 Inter、Roboto、Arial、system font；</li>
<li class="">白底紫色渐变和泛化的科技卡片；</li>
<li class="">可预测的布局组件；</li>
<li class="">任何看起来像“模板生成”而不是“为这个内容设计”的输出。</li>
</ul>
<p>为了让 Agent 不只会说“要有设计感”，它把审美拆成了可执行材料：</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-安全-preset">1. 安全 preset<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#1-%E5%AE%89%E5%85%A8-preset" class="hash-link" aria-label="1. 安全 preset的直接链接" title="1. 安全 preset的直接链接" translate="no">​</a></h3>
<p><code>STYLE_PRESETS.md</code> 提供 12 个基础视觉方向，比如：</p>
<table><thead><tr><th>类别</th><th>示例</th></tr></thead><tbody><tr><td>Dark Themes</td><td>Bold Signal、Electric Studio、Creative Voltage、Dark Botanical</td></tr><tr><td>Light Themes</td><td>Notebook Tabs、Pastel Geometry、Split Pastel、Vintage Editorial</td></tr><tr><td>Specialty</td><td>Neon Cyber、Terminal Green、Swiss Modern、Paper &amp; Ink</td></tr></tbody></table>
<p>每个 preset 都给出 vibe、layout、typography、colors、signature elements。Agent 不是凭感觉“做个暗色科技风”，而是有具体字体、色板、构图和标志性元素可用。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-bold-template-pack">2. Bold Template Pack<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#2-bold-template-pack" class="hash-link" aria-label="2. Bold Template Pack的直接链接" title="2. Bold Template Pack的直接链接" translate="no">​</a></h3>
<p>更强的一层是 <code>bold-template-pack</code>。它把 <code>beautiful-html-templates</code> 里的 34 个设计系统引入 skill，但不是一次性全部塞给 Agent。正确读取顺序是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">selection-index.json</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; shortlist by mood / tone / best_for / avoid_for / formality / density / scheme</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 只读 shortlist 的 preview.md</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 用户选中后，只读那一个 design.md</span><br></div></code></pre></div></div>
<p>这就是 progressive disclosure。好处是：Agent 一开始不会被 34 套完整 design doc 淹没，也不会把所有模板胡乱混在一起。每次只把“当前决策需要的信息”放进上下文。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-wildcard-自定义设计">3. Wildcard 自定义设计<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#3-wildcard-%E8%87%AA%E5%AE%9A%E4%B9%89%E8%AE%BE%E8%AE%A1" class="hash-link" aria-label="3. Wildcard 自定义设计的直接链接" title="3. Wildcard 自定义设计的直接链接" translate="no">​</a></h3>
<p>它还保留一个 wildcard slot。也就是说，如果当前内容有更具体的视觉机会，Agent 不必强行套模板，可以自己设计一个更贴合内容的方向。但 custom wildcard 也有约束：要有独特 typography、稳定 palette、可扩展 layout system、一个清晰的视觉 thesis，并且不要把 <code>custom</code>、<code>wildcard</code>、<code>template</code> 这些流程标签渲染到 slide 上。</p>
<p>这套机制对 ChatBlog 特别有用。我们的内容经常是技术调研、Agent 工作流、服务架构、社区协作、ASR/视频/论文复现等。如果每篇都套一个 generic tech deck，会很快审美疲劳。<code>frontend-slides</code> 提醒我们：先让内容决定视觉隐喻，再让模板服务表达。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="五工作流不是生成-html这么简单">五、工作流不是“生成 HTML”这么简单<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#%E4%BA%94%E5%B7%A5%E4%BD%9C%E6%B5%81%E4%B8%8D%E6%98%AF%E7%94%9F%E6%88%90-html%E8%BF%99%E4%B9%88%E7%AE%80%E5%8D%95" class="hash-link" aria-label="五、工作流不是“生成 HTML”这么简单的直接链接" title="五、工作流不是“生成 HTML”这么简单的直接链接" translate="no">​</a></h2>
<p><code>SKILL.md</code> 把任务拆成 6 个 phase：</p>
<table><thead><tr><th>Phase</th><th>作用</th></tr></thead><tbody><tr><td>Phase 0: Detect Mode</td><td>判断是新建 presentation、PPT conversion，还是增强已有 HTML deck。</td></tr><tr><td>Phase 1: Content Discovery</td><td>一次性问清 purpose、length、content readiness、density，并整理图片。</td></tr><tr><td>Phase 2: Style Discovery</td><td>生成 3 张视觉 preview，让用户选风格或混合。</td></tr><tr><td>Phase 3: Generate Presentation</td><td>读取 <code>html-template.md</code>、<code>viewport-base.css</code>、<code>animation-patterns.md</code>，生成完整单 HTML。</td></tr><tr><td>Phase 4: PPT Conversion</td><td>用 <code>extract-pptx.py</code> 提取文本、图片、speaker notes，再进入 style selection。</td></tr><tr><td>Phase 5: Delivery</td><td>打开 HTML，说明文件位置、风格、页数、导航、可编辑方式。</td></tr><tr><td>Phase 6: Share &amp; Export</td><td>可选部署到 URL 或导出 PDF。</td></tr></tbody></table>
<p>这里有几个值得单独拿出来的设计点。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-先问密度再决定页数">1. 先问密度，再决定页数<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#1-%E5%85%88%E9%97%AE%E5%AF%86%E5%BA%A6%E5%86%8D%E5%86%B3%E5%AE%9A%E9%A1%B5%E6%95%B0" class="hash-link" aria-label="1. 先问密度，再决定页数的直接链接" title="1. 先问密度，再决定页数的直接链接" translate="no">​</a></h3>
<p>它把 presentation 分成两种密度模式：</p>
<table><thead><tr><th>模式</th><th>适合</th><th>设计行为</th></tr></thead><tbody><tr><td>Low density / speaker-led</td><td>公开演讲、keynote、现场讲解</td><td>一页一个想法，大字、强视觉层次、1-3 个 bullet，不够就拆更多页。</td></tr><tr><td>High density / reading-first</td><td>报告、异步 review、内部材料</td><td>更自解释，可用表格、grid、annotation、4-8 个 bullet，但不能拥挤。</td></tr></tbody></table>
<p>这比“做 10 页 PPT”更合理。因为 10 页对现场演讲和异步阅读完全不是同一个设计任务。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-修改已有-deck-时先查容量">2. 修改已有 deck 时先查容量<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#2-%E4%BF%AE%E6%94%B9%E5%B7%B2%E6%9C%89-deck-%E6%97%B6%E5%85%88%E6%9F%A5%E5%AE%B9%E9%87%8F" class="hash-link" aria-label="2. 修改已有 deck 时先查容量的直接链接" title="2. 修改已有 deck 时先查容量的直接链接" translate="no">​</a></h3>
<p>Mode C 是 enhancement。它提醒 Agent：在已有 slide 上加文字或图片前，先数元素、检查密度和空间；如果加一张图会挤爆 1920×1080 stage，就应该拆成新 slide，而不是硬塞进去。</p>
<p>这正好对应我们“完善 ChatBlog slides”的需求。完善不是无脑加内容，而是每次都要问：这张 slide 是否已经达到表达容量？新增信息应该变成 beat、变成新 scene，还是替换掉旧元素？</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-inline-editing-是后置能力">3. Inline editing 是后置能力<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#3-inline-editing-%E6%98%AF%E5%90%8E%E7%BD%AE%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="3. Inline editing 是后置能力的直接链接" title="3. Inline editing 是后置能力的直接链接" translate="no">​</a></h3>
<p><code>html-template.md</code> 里把 inline editing 作为 post-draft affordance：用户看过 draft 后，可以 hover 左上角或按 <code>E</code> 进入编辑模式，点文字直接改，<code>Ctrl+S</code> 保存。它明确说不要在 Phase 1 就问用户要不要 inline editing，因为用户没看到稿子前不会知道自己是否需要。</p>
<p>对 ChatBlog 来说，这个能力可以作为草稿阶段辅助：先让人直接在浏览器里改字、调句子，再把稳定修改回写到源文件。但正式进入 ChatBlog repo 后，最终仍要以 Git diff 为准，不能只依赖 localStorage 里的浏览器编辑状态。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-分享和导出是交付的一部分">4. 分享和导出是交付的一部分<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#4-%E5%88%86%E4%BA%AB%E5%92%8C%E5%AF%BC%E5%87%BA%E6%98%AF%E4%BA%A4%E4%BB%98%E7%9A%84%E4%B8%80%E9%83%A8%E5%88%86" class="hash-link" aria-label="4. 分享和导出是交付的一部分的直接链接" title="4. 分享和导出是交付的一部分的直接链接" translate="no">​</a></h3>
<p>它提供两条后处理路径：</p>
<ul>
<li class=""><code>scripts/deploy.sh</code>：把 HTML 或目录部署到 Vercel，拿到可分享 URL；</li>
<li class=""><code>scripts/export-pdf.sh</code>：用 Playwright 逐页截图，合成 PDF。</li>
</ul>
<p>这说明它的交付不是“我写了一个 HTML 文件”就结束，而是要考虑别人怎么打开、怎么转发、怎么存档。</p>
<p>不过，放到 ChatArch/ChatBlog，我们不能直接照搬 Vercel 作为默认发布面。我们的正式路径应该是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">ChatBlog branch</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; PR</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; Docusaurus build</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; Preview URL</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; merge</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; production URL readback</span><br></div></code></pre></div></div>
<p>Vercel 可以用于个人临时分享，但 ChatBlog 的 canonical artifact 应该回到 <code>ChatArch/ChatBlog</code>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="六和-patrick-的-frontend-harness-slides-有什么区别">六、和 Patrick 的 Frontend Harness Slides 有什么区别<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#%E5%85%AD%E5%92%8C-patrick-%E7%9A%84-frontend-harness-slides-%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB" class="hash-link" aria-label="六、和 Patrick 的 Frontend Harness Slides 有什么区别的直接链接" title="六、和 Patrick 的 Frontend Harness Slides 有什么区别的直接链接" translate="no">​</a></h2>
<p>这两个项目都在说 Web Slides，但层级不同。</p>
<table><thead><tr><th>维度</th><th><code>frontend-harness-slides</code></th><th><code>zarazhangrui/frontend-slides</code></th></tr></thead><tbody><tr><td>核心定位</td><td>Web deck 工程化 harness / scene-beat 工作流</td><td>AI Agent presentation skill / 单 HTML 生成流程</td></tr><tr><td>主要产物</td><td>方法论、workbench、demo、React/Vite/Tailwind/Playwright 参考</td><td><code>SKILL.md</code>、style presets、bold templates、HTML template、脚本</td></tr><tr><td>输出形态</td><td>更像小型前端项目，可测试、可部署、可持续演进</td><td>零依赖 self-contained HTML，快速生成和分享</td></tr><tr><td>强项</td><td>URL-addressable state、player/stage/navigation/tests、生产化治理</td><td>视觉探索、反 AI-slop、PPT 转网页、单文件便携、可读 skill 流程</td></tr><tr><td>风险</td><td>对单场 talk 可能太重</td><td>大型长期 deck 会变成单文件维护压力</td></tr><tr><td>对 ChatBlog 的价值</td><td>建立长期 slides infra 和质量门槛</td><td>快速把文章/调研变成可看的 deck 草稿，并提升视觉质量</td></tr></tbody></table>
<p>所以它们不是替代关系。一个偏“工程治理”，一个偏“AI 辅助视觉生产”。</p>
<p>更合理的组合方式是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">frontend-slides</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 快速生成视觉方向、单 HTML 草稿、PPT 转换、PDF/临时 URL</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">frontend-harness / ChatBlog slides infra</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 稳定 scene/beat registry、可测试 player、PR preview、生产部署、长期维护</span><br></div></code></pre></div></div>
<p>如果只要做一次 8 页内部分享，<code>frontend-slides</code> 的单 HTML 可能就够了。如果这套 slides 要成为 ChatBlog 的公开栏目、要和文章互相回链、要长期维护，那就应该把它接回我们的 repo 流程。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="七它能怎么辅助我们完善-chatblog-slides">七、它能怎么辅助我们完善 ChatBlog Slides<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#%E4%B8%83%E5%AE%83%E8%83%BD%E6%80%8E%E4%B9%88%E8%BE%85%E5%8A%A9%E6%88%91%E4%BB%AC%E5%AE%8C%E5%96%84-chatblog-slides" class="hash-link" aria-label="七、它能怎么辅助我们完善 ChatBlog Slides的直接链接" title="七、它能怎么辅助我们完善 ChatBlog Slides的直接链接" translate="no">​</a></h2>
<p>当前 ChatBlog 已经有 <code>/slides</code> 页面，里面有一个 <code>Agent Community Quick Start</code>。这个 deck 是纯静态 HTML，支持 scene / beat URL state，材料页里也明确说它是一个 quick start demo。</p>
<p><code>frontend-slides</code> 可以在三个层面帮助我们继续完善。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-作为文章转-slides的第一道流程">1. 作为“文章转 slides”的第一道流程<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#1-%E4%BD%9C%E4%B8%BA%E6%96%87%E7%AB%A0%E8%BD%AC-slides%E7%9A%84%E7%AC%AC%E4%B8%80%E9%81%93%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="1. 作为“文章转 slides”的第一道流程的直接链接" title="1. 作为“文章转 slides”的第一道流程的直接链接" translate="no">​</a></h3>
<p>以后每篇适合展示的 ChatBlog 文章，可以先跑一个 lightweight slides pass：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">1. 读文章，提炼 thesis、audience、density、5-9 个 scenes。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2. 用 frontend-slides 的方式生成 3 张 title / key-scene preview。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">3. 用户选风格，确定是 speaker-led 还是 reading-first。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">4. 生成单 HTML draft。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">5. 浏览器检查 overflow、panel overlap、键盘导航、移动端可见性。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">6. 如果只是临时展示，直接分享 HTML / PDF。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">7. 如果要进 ChatBlog，迁移到 static/slides/... 并补 materials page、PR、Preview、production readback。</span><br></div></code></pre></div></div>
<p>这条路线可以降低创建第一版 deck 的心理门槛。先让内容“长出一个可看的样子”，再决定是否工程化。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-用视觉-preview-改造现有-agent-community-deck">2. 用视觉 preview 改造现有 Agent Community deck<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#2-%E7%94%A8%E8%A7%86%E8%A7%89-preview-%E6%94%B9%E9%80%A0%E7%8E%B0%E6%9C%89-agent-community-deck" class="hash-link" aria-label="2. 用视觉 preview 改造现有 Agent Community deck的直接链接" title="2. 用视觉 preview 改造现有 Agent Community deck的直接链接" translate="no">​</a></h3>
<p><code>Agent Community Quick Start</code> 现在已经有 9 scenes，内容结构是有的。下一步不一定要先大改代码，可以先让 <code>frontend-slides</code> 做 3 张视觉方向 preview：</p>
<ul>
<li class="">一个偏 hand-drawn / whiteboard 的版本，保留“议事厅”的手写感；</li>
<li class="">一个偏 governance / system diagram 的版本，突出 topic、profile、human admin、ledger；</li>
<li class="">一个偏 editorial manifesto 的版本，适合对外展示“不是机器人群聊”的核心立场。</li>
</ul>
<p>选中后再决定是否把现有 deck 重写成 1920×1080 fixed-stage 单 HTML，或者升级成更正式的 React/Vite starter。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-把-slide-维护变成小型变更流程">3. 把 slide 维护变成小型变更流程<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#3-%E6%8A%8A-slide-%E7%BB%B4%E6%8A%A4%E5%8F%98%E6%88%90%E5%B0%8F%E5%9E%8B%E5%8F%98%E6%9B%B4%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="3. 把 slide 维护变成小型变更流程的直接链接" title="3. 把 slide 维护变成小型变更流程的直接链接" translate="no">​</a></h3>
<p><code>frontend-slides</code> 里 Mode C 的修改规则可以直接变成我们的 slides 维护规范：</p>
<table><thead><tr><th>变更</th><th>推荐处理</th></tr></thead><tbody><tr><td>改一句文案</td><td>直接改 HTML / scene source，跑 build 和关键 frame 检查。</td></tr><tr><td>增加一个观点</td><td>先判断是 beat 还是新 scene，不要硬塞进旧页。</td></tr><tr><td>加截图或架构图</td><td>先检查当前 slide 容量；必要时拆页。</td></tr><tr><td>换视觉风格</td><td>先做 3 张 preview，再批量调整。</td></tr><tr><td>发布到 ChatBlog</td><td>走 PR、Preview、生产 readback，不把 localhost 当交付。</td></tr></tbody></table>
<p>这能防止 slides 从“快速 demo”变成“越来越乱的 HTML 文件”。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="八适合和不适合的场景">八、适合和不适合的场景<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#%E5%85%AB%E9%80%82%E5%90%88%E5%92%8C%E4%B8%8D%E9%80%82%E5%90%88%E7%9A%84%E5%9C%BA%E6%99%AF" class="hash-link" aria-label="八、适合和不适合的场景的直接链接" title="八、适合和不适合的场景的直接链接" translate="no">​</a></h2>
<p>我会把 <code>frontend-slides</code> 放在这个位置：</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="适合">适合<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#%E9%80%82%E5%90%88" class="hash-link" aria-label="适合的直接链接" title="适合的直接链接" translate="no">​</a></h3>
<ul>
<li class="">把博客、调研、项目总结快速变成 Web Slides；</li>
<li class="">需要 2-3 个视觉方向给用户选择；</li>
<li class="">需要一个不依赖 npm / bundler / framework 的单 HTML artifact；</li>
<li class="">想把 <code>.pptx</code> 内容迁移成可在浏览器里继续改造的 web deck；</li>
<li class="">做一次 talk、pitch、课程、内部同步、项目 showcase；</li>
<li class="">需要 PDF 截图版归档。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="不适合">不适合<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#%E4%B8%8D%E9%80%82%E5%90%88" class="hash-link" aria-label="不适合的直接链接" title="不适合的直接链接" translate="no">​</a></h3>
<ul>
<li class="">多人长期维护的大型 slides 系统；</li>
<li class="">需要复杂路由、数据加载、跨 deck 组件复用的 gallery；</li>
<li class="">必须保留 PowerPoint 原生编辑体验的团队；</li>
<li class="">对 CSS/HTML 维护完全无能力且没有 Agent 辅助的用户；</li>
<li class="">对字体、资源、截图、部署都有严格内网合规要求但未建立发布流程的场景。</li>
</ul>
<p>最重要的边界是：<code>frontend-slides</code> 生成的是 <strong>Web artifact</strong>，不是 PowerPoint 原生对象。它可以导出 PDF，可以从 PPT 提取内容，但它的主战场是浏览器。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="九给-chatblog-的落地建议">九、给 ChatBlog 的落地建议<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#%E4%B9%9D%E7%BB%99-chatblog-%E7%9A%84%E8%90%BD%E5%9C%B0%E5%BB%BA%E8%AE%AE" class="hash-link" aria-label="九、给 ChatBlog 的落地建议的直接链接" title="九、给 ChatBlog 的落地建议的直接链接" translate="no">​</a></h2>
<p>我建议把它接入 ChatBlog Slides 的方式分成三个层级。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="level-1作为-prompt--skill-参考">Level 1：作为 prompt / skill 参考<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#level-1%E4%BD%9C%E4%B8%BA-prompt--skill-%E5%8F%82%E8%80%83" class="hash-link" aria-label="Level 1：作为 prompt / skill 参考的直接链接" title="Level 1：作为 prompt / skill 参考的直接链接" translate="no">​</a></h3>
<p>短期最简单：当我们要把某篇文章做成 slides 时，直接按 <code>frontend-slides</code> 的结构给 Agent 一个明确任务：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">请用 frontend-slides 的方式，把这篇 ChatBlog 文章做成 Web Slides 草稿。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">要求：</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">1. 先给 deck brief：audience、goal、density、delivery target。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2. 先生成 3 个视觉方向说明，每个方向对应一张 title/key-scene preview。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">3. 选定风格后，输出 fixed 1920×1080 stage 的单 HTML deck。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">4. 支持 ArrowLeft / ArrowRight / Space 导航。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">5. 支持 URL state：?scene=&lt;n&gt;&amp;beat=&lt;m&gt;。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">6. 所有关键文字必须是 HTML/CSS/SVG 可编辑文本，不要烘焙进图片。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">7. 完成后做浏览器检查：console、overflow、panel overlap、direct URL、移动端可见性。</span><br></div></code></pre></div></div>
<p>这一步不需要改 ChatBlog infra，只是改我们的执行习惯。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="level-2形成-chatblog-slides-草稿目录">Level 2：形成 ChatBlog slides 草稿目录<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#level-2%E5%BD%A2%E6%88%90-chatblog-slides-%E8%8D%89%E7%A8%BF%E7%9B%AE%E5%BD%95" class="hash-link" aria-label="Level 2：形成 ChatBlog slides 草稿目录的直接链接" title="Level 2：形成 ChatBlog slides 草稿目录的直接链接" translate="no">​</a></h3>
<p>当某个 deck 值得进入 ChatBlog，可以放到：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">static/slides/&lt;topic&gt;/deck/index.html</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">src/pages/slides/&lt;topic&gt;/index.tsx</span><br></div></code></pre></div></div>
<p>材料页负责解释 deck 的背景和结构，HTML deck 负责演示。提交时必须跑 Docusaurus build，并在 PR Preview 和生产 URL 上读回。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="level-3沉淀成-chatarch-web-slides-starter">Level 3：沉淀成 ChatArch Web Slides Starter<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#level-3%E6%B2%89%E6%B7%80%E6%88%90-chatarch-web-slides-starter" class="hash-link" aria-label="Level 3：沉淀成 ChatArch Web Slides Starter的直接链接" title="Level 3：沉淀成 ChatArch Web Slides Starter的直接链接" translate="no">​</a></h3>
<p>如果我们后续会频繁做 slides，就不要每次从单 HTML 手写开始。可以把 <code>frontend-slides</code> 的视觉 discovery 和 Patrick-style harness 的工程契约合并成一个 ChatArch starter：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">content brief</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; visual preview trio</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; scene/beat registry</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; fixed 1920×1080 stage</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; player/navigation/input isolation</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; static deck or React/Vite deck</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; build / preview / production readback</span><br></div></code></pre></div></div>
<p><code>frontend-slides</code> 贡献的是前半段：如何让 Agent 先理解内容、探索风格、避免 AI-slop、生成可看的 HTML。ChatBlog infra 贡献的是后半段：如何让它成为可维护、可审查、可发布的公共知识 artifact。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="十小结">十、小结<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#%E5%8D%81%E5%B0%8F%E7%BB%93" class="hash-link" aria-label="十、小结的直接链接" title="十、小结的直接链接" translate="no">​</a></h2>
<p><code>zarazhangrui/frontend-slides</code> 的价值可以压缩成三句话：</p>
<ol>
<li class="">它把做 slides 的工作从“套模板”改成了 <strong>agent-readable workflow</strong>：内容发现、视觉 preview、风格选择、生成、交付、分享。</li>
<li class="">它把输出从“网页随便排一下”约束成 <strong>固定 1920×1080 stage 的单 HTML presentation artifact</strong>：可打开、可演示、可截图、可导出 PDF。</li>
<li class="">它把审美从“用户先说清楚风格”改成 <strong>show, don't tell</strong>：先给真实 preview，让用户通过比较决定方向。</li>
</ol>
<p>对 ChatBlog 来说，它不是要替代现有 Slides 页面，也不是替代 Patrick-style harness。它更像一个前置加速器：帮我们把文章、调研和项目成果快速变成第一版可看的 Web Slides，再把成熟内容纳入 ChatBlog 的 PR、Preview、生产 readback 流程。</p>
<p>如果说 Patrick 的那套方法提醒我们“Slides 也应该像软件一样可测试、可发布、可维护”，那么 <code>frontend-slides</code> 提醒我们另一件事：<strong>在进入工程治理前，Slides 首先要让人愿意看。</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="主要来源">主要来源<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/zarazhangrui-frontend-slides-skill#%E4%B8%BB%E8%A6%81%E6%9D%A5%E6%BA%90" class="hash-link" aria-label="主要来源的直接链接" title="主要来源的直接链接" translate="no">​</a></h2>
<ul>
<li class=""><code>zarazhangrui/frontend-slides</code>: <a href="https://github.com/zarazhangrui/frontend-slides" target="_blank" rel="noopener noreferrer" class="">https://github.com/zarazhangrui/frontend-slides</a></li>
<li class="">README: <a href="https://raw.githubusercontent.com/zarazhangrui/frontend-slides/main/README.md" target="_blank" rel="noopener noreferrer" class="">https://raw.githubusercontent.com/zarazhangrui/frontend-slides/main/README.md</a></li>
<li class=""><code>SKILL.md</code>: <a href="https://raw.githubusercontent.com/zarazhangrui/frontend-slides/main/SKILL.md" target="_blank" rel="noopener noreferrer" class="">https://raw.githubusercontent.com/zarazhangrui/frontend-slides/main/SKILL.md</a></li>
<li class=""><code>STYLE_PRESETS.md</code>: <a href="https://raw.githubusercontent.com/zarazhangrui/frontend-slides/main/STYLE_PRESETS.md" target="_blank" rel="noopener noreferrer" class="">https://raw.githubusercontent.com/zarazhangrui/frontend-slides/main/STYLE_PRESETS.md</a></li>
<li class=""><code>html-template.md</code>: <a href="https://raw.githubusercontent.com/zarazhangrui/frontend-slides/main/html-template.md" target="_blank" rel="noopener noreferrer" class="">https://raw.githubusercontent.com/zarazhangrui/frontend-slides/main/html-template.md</a></li>
<li class=""><code>viewport-base.css</code>: <a href="https://raw.githubusercontent.com/zarazhangrui/frontend-slides/main/viewport-base.css" target="_blank" rel="noopener noreferrer" class="">https://raw.githubusercontent.com/zarazhangrui/frontend-slides/main/viewport-base.css</a></li>
<li class=""><code>animation-patterns.md</code>: <a href="https://raw.githubusercontent.com/zarazhangrui/frontend-slides/main/animation-patterns.md" target="_blank" rel="noopener noreferrer" class="">https://raw.githubusercontent.com/zarazhangrui/frontend-slides/main/animation-patterns.md</a></li>
<li class="">Bold Template Pack: <a href="https://github.com/zarazhangrui/frontend-slides/tree/main/bold-template-pack" target="_blank" rel="noopener noreferrer" class="">https://github.com/zarazhangrui/frontend-slides/tree/main/bold-template-pack</a></li>
<li class="">Claude Code marketplace metadata: <a href="https://github.com/zarazhangrui/frontend-slides/blob/main/.claude-plugin/marketplace.json" target="_blank" rel="noopener noreferrer" class="">https://github.com/zarazhangrui/frontend-slides/blob/main/.claude-plugin/marketplace.json</a></li>
<li class="">ChatBlog Slides: <a href="https://arch.gh.wzhecnu.cn/ChatBlog/slides" target="_blank" rel="noopener noreferrer" class="">https://arch.gh.wzhecnu.cn/ChatBlog/slides</a></li>
<li class="">Frontend Harness Slides 旧文: <a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/frontend-harness-slides-web-deck-workflow" target="_blank" rel="noopener noreferrer" class="">https://arch.gh.wzhecnu.cn/ChatBlog/blog/frontend-harness-slides-web-deck-workflow</a></li>
</ul>]]></content>
        <category label="slides" term="slides"/>
        <category label="frontend" term="frontend"/>
        <category label="claude-code" term="claude-code"/>
        <category label="html" term="html"/>
        <category label="skill" term="skill"/>
        <category label="chatarch" term="chatarch"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[阿里云实时语音识别从注册到接入：百炼 ASR 实践教程]]></title>
        <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide</id>
        <link href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide"/>
        <updated>2026-08-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[从零开始走阿里云百炼 / Model Studio：注册登录、开通百炼、确认 Token Plan 与 ASR 额度边界、创建 Workspace、获取 DashScope API Key、选择 Qwen-Audio / Fun-ASR / Paraformer 实时模型、跑 Python SDK / WebSocket demo，并规划后端 relay 与网页实时字幕实践。]]></summary>
        <content type="html"><![CDATA[<p>这篇和科大讯飞教程一样，不写泛泛产品介绍，而是把一条可实践路径讲清楚：<strong>第一次使用阿里云百炼 / Model Studio，怎么开通实时语音识别，怎么确认额度和计费，怎么拿 API Key，怎么跑 DashScope SDK 或 WebSocket，最后怎么接到我们自己的网页实时字幕服务。</strong></p>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>同主题前情</div><div class="admonitionContent_BuS1"><ul>
<li class=""><a class="" href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration">实时语音转录怎么接：讯飞、阿里云、火山引擎、腾讯云四条路线</a>：先把四家国内实时 ASR 的 API 形态、服务器分工和 A/B 测试方式拆开。</li>
<li class=""><a class="" href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide">科大讯飞实时语音转写从注册到接入</a>：同系列第一篇，按讯飞控制台、SDK、WebSocket relay 走了一遍。</li>
<li class=""><a class="" href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mobile-web-realtime-transcription-pwa">网页录音到 AI 纪要：先选成熟 ASR，再看现成产品</a>：解释为什么我们把“实时 ASR”和“AI 纪要”分成两层。</li>
</ul></div></div>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>一句话结论</div><div class="admonitionContent_BuS1"><p>阿里云这条线先不要只盯 <code>Paraformer</code>。2026-08-10 的百炼文档里，实时 ASR 推荐入口已经包括 <strong><code>qwen-audio-3.0-asr-flash-streaming</code>、<code>fun-asr-realtime</code>、<code>qwen3-asr-flash-realtime</code>、<code>paraformer-realtime-v2</code></strong>。如果目标是会议实时字幕，我建议第一轮优先试 <code>qwen-audio-3.0-asr-flash-streaming</code> 或 <code>fun-asr-realtime</code>，同时把 <code>paraformer-realtime-v2</code> 作为稳定、便宜、传统 ASR 路线对照。</p></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>状态说明</div><div class="admonitionContent_BuS1"><p>快照时间为 <strong>2026-08-10 CST</strong>。本文基于阿里云百炼官方模型、SDK、WebSocket API、计费文档做静态实践手册；没有使用任何真实账号密钥，也没有发起付费 ASR 调用。所有真实 API Key、AccessKey、Token、密码、代理凭据都必须写成 <code>[REDACTED]</code>，不能进入前端、博客、截图或 Git 仓库。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="先分清token-plan-不是asr-已经可用的证明">先分清：Token Plan 不是“ASR 已经可用”的证明<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E5%85%88%E5%88%86%E6%B8%85token-plan-%E4%B8%8D%E6%98%AFasr-%E5%B7%B2%E7%BB%8F%E5%8F%AF%E7%94%A8%E7%9A%84%E8%AF%81%E6%98%8E" class="hash-link" aria-label="先分清：Token Plan 不是“ASR 已经可用”的证明的直接链接" title="先分清：Token Plan 不是“ASR 已经可用”的证明的直接链接" translate="no">​</a></h2>
<p>用户常见误区是：我有阿里云 / 千问的 Token Plan，是不是就包含实时 ASR？答案是：<strong>不能默认这么理解。</strong></p>
<p>百炼里确实有 Token Plan、文本模型、音频模型、ASR 模型、实时多模态等多条计费线。实时语音识别在文档里属于“语音识别”模型；不少 ASR 模型按 <strong>输入音频秒数</strong> 计费，而不是按普通文本 token 计费。</p>
<p>所以第一步不是写代码，而是在控制台确认三件事：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">1. 百炼是否已开通。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2. 目标地域 / Workspace 下是否能调用目标 ASR 模型。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">3. 当前账号的免费额度、资源包、Token Plan、后付费分别能抵扣哪些模型。</span><br></div></code></pre></div></div>
<p>更务实地说：</p>
<ul>
<li class="">Token Plan 可以作为你已有阿里云权益的线索；</li>
<li class="">但不能把它当成 <code>qwen-audio-3.0-asr-flash-streaming</code> 或 <code>paraformer-realtime-v2</code> 一定可调用、一定免费抵扣的证明；</li>
<li class="">最终以 <strong>百炼模型详情页、免费额度页、模型调用计费页、账单明细 / 用量统计</strong> 为准。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="先分清几个容易混淆的模型">先分清几个容易混淆的模型<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E5%85%88%E5%88%86%E6%B8%85%E5%87%A0%E4%B8%AA%E5%AE%B9%E6%98%93%E6%B7%B7%E6%B7%86%E7%9A%84%E6%A8%A1%E5%9E%8B" class="hash-link" aria-label="先分清几个容易混淆的模型的直接链接" title="先分清几个容易混淆的模型的直接链接" translate="no">​</a></h2>
<p>阿里云现在的 ASR 模型比“Paraformer”这一句话复杂得多。第一次实践可以按这个表选：</p>
<table><thead><tr><th>模型 / 系列</th><th>模式</th><th>适合什么</th><th>第一轮建议</th></tr></thead><tbody><tr><td><code>qwen-audio-3.0-asr-flash-streaming</code></td><td>实时 WebSocket</td><td>实时字幕、语音助手、会议转写；文档推荐为实时识别首选，支持热词 / Prompt 上下文、多语种及方言</td><td><strong>优先试</strong></td></tr><tr><td><code>fun-asr-realtime</code></td><td>实时 WebSocket</td><td>专业 ASR 实时路线，支持热词、多语种及方言；DashScope SDK 可接</td><td><strong>优先试 / 对照</strong></td></tr><tr><td><code>qwen3-asr-flash-realtime</code></td><td>实时 WebSocket</td><td>需要转写同时看情感识别时可测</td><td>备选</td></tr><tr><td><code>paraformer-realtime-v2</code></td><td>实时 WebSocket</td><td>传统 Paraformer 实时路线，地域限制清晰，费用相对低</td><td><strong>价格 / 稳定性对照</strong></td></tr><tr><td><code>qwen-audio-3.0-asr-flash-filetrans</code></td><td>非实时 HTTP</td><td>录音文件转写、访谈、播客；支持说话人分离</td><td>会后转写，不是实时字幕主线</td></tr><tr><td><code>qwen-audio-3.0-asr-flash</code></td><td>非实时 HTTP</td><td>短音频识别</td><td>不是会议实时主线</td></tr></tbody></table>
<p>如果只问“会议实时字幕先测哪一个”：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">实时字幕 / 边说边出字</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; qwen-audio-3.0-asr-flash-streaming</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; fun-asr-realtime</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; paraformer-realtime-v2 做价格 / 稳定性对照</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">录音上传后转写 / 说话人分离</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; qwen-audio-3.0-asr-flash-filetrans</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; fun-asr / fun-asr-mtl</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="费用先怎么看">费用先怎么看<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E8%B4%B9%E7%94%A8%E5%85%88%E6%80%8E%E4%B9%88%E7%9C%8B" class="hash-link" aria-label="费用先怎么看的直接链接" title="费用先怎么看的直接链接" translate="no">​</a></h2>
<p>价格会变，必须以控制台和官方计费页为准。下面是 2026-08-10 从百炼“模型调用计费”文档抽到的 ASR 快照，只用于估算第一轮 smoke test 成本。</p>
<table><thead><tr><th>模型</th><th>计费规则</th><th style="text-align:right">页面单价</th><th style="text-align:right">折算单价</th><th>免费额度口径</th></tr></thead><tbody><tr><td><code>qwen-audio-3.0-asr-flash-streaming</code></td><td>按输入音频秒数，输出不计费</td><td style="text-align:right">¥0.00033 / 秒</td><td style="text-align:right">约 ¥1.188 / 小时</td><td>36,000 秒（10 小时），有效期以文档和控制台为准</td></tr><tr><td><code>qwen3-asr-flash-realtime</code></td><td>按输入音频秒数，输出不计费</td><td style="text-align:right">¥0.00033 / 秒</td><td style="text-align:right">约 ¥1.188 / 小时</td><td>36,000 秒（10 小时），有效期以文档和控制台为准</td></tr><tr><td><code>fun-asr-realtime</code></td><td>按输入音频秒数，输出不计费</td><td style="text-align:right">¥0.00033 / 秒</td><td style="text-align:right">约 ¥1.188 / 小时</td><td>36,000 秒（10 小时），有效期以文档和控制台为准</td></tr><tr><td><code>fun-asr-flash-8k-realtime</code></td><td>按输入音频秒数，输出不计费</td><td style="text-align:right">¥0.00022 / 秒</td><td style="text-align:right">约 ¥0.792 / 小时</td><td>36,000 秒（10 小时），有效期以文档和控制台为准</td></tr><tr><td><code>paraformer-realtime-v2</code></td><td>按输入音频秒数，输出不计费</td><td style="text-align:right">¥0.00024 / 秒</td><td style="text-align:right">约 ¥0.864 / 小时</td><td>36,000 秒（10 小时），文档写到每月自动发放、有效期 1 个月</td></tr></tbody></table>
<p>第一轮建议：</p>
<ol>
<li class="">不要先买大资源包；</li>
<li class="">先在百炼控制台确认免费额度是否存在、是否能抵扣目标地域和目标模型；</li>
<li class="">用 10–20 秒音频跑通鉴权和格式；</li>
<li class="">用 3–5 分钟真实中文口述测首字延迟、partial 抖动和 final 准确率；</li>
<li class="">用量统计里看是否扣到“语音识别 / ASR / 音频秒数”相关项，而不是想当然归到普通文本 Token Plan。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="你先打开这些网页">你先打开这些网页<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E4%BD%A0%E5%85%88%E6%89%93%E5%BC%80%E8%BF%99%E4%BA%9B%E7%BD%91%E9%A1%B5" class="hash-link" aria-label="你先打开这些网页的直接链接" title="你先打开这些网页的直接链接" translate="no">​</a></h2>
<p>第一次操作可以按下面顺序开网页，不需要先写代码。</p>
<table><thead><tr><th>步骤</th><th>打开地址</th><th>你要做什么</th></tr></thead><tbody><tr><td>1</td><td><a href="https://aliyun.com/" target="_blank" rel="noopener noreferrer" class="">https://aliyun.com/</a></td><td>注册或登录阿里云账号。</td></tr><tr><td>2</td><td><a href="https://bailian.console.aliyun.com/" target="_blank" rel="noopener noreferrer" class="">https://bailian.console.aliyun.com/</a></td><td>进入百炼控制台，确认是否已开通服务。</td></tr><tr><td>3</td><td><a href="https://bailian.console.aliyun.com/?tab=model#/model-market" target="_blank" rel="noopener noreferrer" class="">https://bailian.console.aliyun.com/?tab=model#/model-market</a></td><td>进入模型广场，搜索 <code>qwen-audio-3.0-asr-flash-streaming</code>、<code>fun-asr-realtime</code>、<code>paraformer-realtime-v2</code>。</td></tr><tr><td>4</td><td><a href="https://help.aliyun.com/zh/model-studio/asr-model/" target="_blank" rel="noopener noreferrer" class="">https://help.aliyun.com/zh/model-studio/asr-model/</a></td><td>看官方 ASR 选型页，确认实时 / 非实时、热词、Prompt 上下文、说话人分离、情感识别的边界。</td></tr><tr><td>5</td><td><a href="https://help.aliyun.com/zh/model-studio/get-api-key" target="_blank" rel="noopener noreferrer" class="">https://help.aliyun.com/zh/model-studio/get-api-key</a></td><td>按文档获取百炼 API Key。</td></tr><tr><td>6</td><td><a href="https://help.aliyun.com/zh/model-studio/real-time-speech-recognition-user-guide" target="_blank" rel="noopener noreferrer" class="">https://help.aliyun.com/zh/model-studio/real-time-speech-recognition-user-guide</a></td><td>看实时语音识别快速开始，里面有 Python / Java 示例。</td></tr><tr><td>7</td><td><a href="https://help.aliyun.com/zh/model-studio/paraformer-real-time-speech-recognition-python-sdk" target="_blank" rel="noopener noreferrer" class="">https://help.aliyun.com/zh/model-studio/paraformer-real-time-speech-recognition-python-sdk</a></td><td>看 Paraformer Python SDK 文档。</td></tr><tr><td>8</td><td><a href="https://help.aliyun.com/zh/model-studio/websocket-for-paraformer-real-time-service" target="_blank" rel="noopener noreferrer" class="">https://help.aliyun.com/zh/model-studio/websocket-for-paraformer-real-time-service</a></td><td>如果不用 SDK，按 WebSocket 协议直连。</td></tr><tr><td>9</td><td><a href="https://help.aliyun.com/zh/model-studio/billing" target="_blank" rel="noopener noreferrer" class="">https://help.aliyun.com/zh/model-studio/billing</a></td><td>看模型调用计费，确认目标模型单价和免费额度。</td></tr><tr><td>10</td><td><a href="https://help.aliyun.com/zh/model-studio/list-quotas" target="_blank" rel="noopener noreferrer" class="">https://help.aliyun.com/zh/model-studio/list-quotas</a></td><td>看限额查询接口，后续可自动化检查额度。</td></tr></tbody></table>
<p>如果控制台要求实名、企业认证、协议确认、开通后付费或选择地域，正常按它的页面操作；不要把密码、验证码、API Key 或账单截图里的敏感信息发到公开聊天。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="从注册到可调用人工操作流程">从注册到可调用：人工操作流程<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E4%BB%8E%E6%B3%A8%E5%86%8C%E5%88%B0%E5%8F%AF%E8%B0%83%E7%94%A8%E4%BA%BA%E5%B7%A5%E6%93%8D%E4%BD%9C%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="从注册到可调用：人工操作流程的直接链接" title="从注册到可调用：人工操作流程的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-注册--登录--实名">1. 注册 / 登录 / 实名<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#1-%E6%B3%A8%E5%86%8C--%E7%99%BB%E5%BD%95--%E5%AE%9E%E5%90%8D" class="hash-link" aria-label="1. 注册 / 登录 / 实名的直接链接" title="1. 注册 / 登录 / 实名的直接链接" translate="no">​</a></h3>
<ol>
<li class="">打开 <a href="https://aliyun.com/%E3%80%82" target="_blank" rel="noopener noreferrer" class="">https://aliyun.com/。</a></li>
<li class="">登录或注册阿里云账号。</li>
<li class="">进入 <a href="https://bailian.console.aliyun.com/%E3%80%82" target="_blank" rel="noopener noreferrer" class="">https://bailian.console.aliyun.com/。</a></li>
<li class="">如果提示实名认证、企业认证或协议确认，按页面完成。</li>
</ol>
<p>完成后只需要告诉我：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">已登录阿里云 / 已进入百炼 / 是否完成实名 / 账号类型个人或企业</span><br></div></code></pre></div></div>
<p>不要发送密码、短信验证码、身份证件、完整账单截图。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-开通百炼并确认地域">2. 开通百炼并确认地域<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#2-%E5%BC%80%E9%80%9A%E7%99%BE%E7%82%BC%E5%B9%B6%E7%A1%AE%E8%AE%A4%E5%9C%B0%E5%9F%9F" class="hash-link" aria-label="2. 开通百炼并确认地域的直接链接" title="2. 开通百炼并确认地域的直接链接" translate="no">​</a></h3>
<p>进入百炼后先确认：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">地域：优先华北2（北京）</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Workspace：default 或新建 realtime-asr-test</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">服务状态：百炼已开通</span><br></div></code></pre></div></div>
<p>为什么强调地域？因为文档里多次出现“华北 2（北京）”地域和 Workspace 专属域名，例如：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">wss://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api-ws/v1/inference</span><br></div></code></pre></div></div>
<p>这里的 <code>{WorkspaceId}</code> 必须替换成真实业务空间 ID。新加坡等地域有不同域名和 API Key 口径，不要混用。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-查模型是否可用">3. 查模型是否可用<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#3-%E6%9F%A5%E6%A8%A1%E5%9E%8B%E6%98%AF%E5%90%A6%E5%8F%AF%E7%94%A8" class="hash-link" aria-label="3. 查模型是否可用的直接链接" title="3. 查模型是否可用的直接链接" translate="no">​</a></h3>
<p>在模型广场搜索：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">qwen-audio-3.0-asr-flash-streaming</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">fun-asr-realtime</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">qwen3-asr-flash-realtime</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">paraformer-realtime-v2</span><br></div></code></pre></div></div>
<p>目标是确认：</p>
<ol>
<li class="">模型能否在你的账号 / 地域 / Workspace 下使用；</li>
<li class="">是否需要单独开通；</li>
<li class="">是否有免费额度；</li>
<li class="">是否支持你要的语言、方言、热词、Prompt 上下文；</li>
<li class="">是否支持 SDK 或必须直接 WebSocket。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-创建--获取-api-key">4. 创建 / 获取 API Key<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#4-%E5%88%9B%E5%BB%BA--%E8%8E%B7%E5%8F%96-api-key" class="hash-link" aria-label="4. 创建 / 获取 API Key的直接链接" title="4. 创建 / 获取 API Key的直接链接" translate="no">​</a></h3>
<p>按官方“获取 API Key”文档操作。拿到后只记录状态，不要把真实值贴出来：</p>
<div class="language-dotenv codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dotenv codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">DASHSCOPE_API_KEY=[REDACTED]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ALIYUN_BAILIAN_WORKSPACE_ID=[REDACTED]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ALIYUN_BAILIAN_REGION=cn-beijing</span><br></div></code></pre></div></div>
<p>正式部署时，Key 只能放在服务器环境变量或 Secret Manager 里，浏览器不能知道它。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-确认额度和后付费边界">5. 确认额度和后付费边界<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#5-%E7%A1%AE%E8%AE%A4%E9%A2%9D%E5%BA%A6%E5%92%8C%E5%90%8E%E4%BB%98%E8%B4%B9%E8%BE%B9%E7%95%8C" class="hash-link" aria-label="5. 确认额度和后付费边界的直接链接" title="5. 确认额度和后付费边界的直接链接" translate="no">​</a></h3>
<p>在写代码前做一次成本检查：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">- 免费额度是否存在：是 / 否</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- 免费额度适用地域：华北2（北京）/ 新加坡 / 其他</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- 免费额度适用模型：哪些 Model ID</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- Token Plan 是否明确覆盖目标 ASR：是 / 否 / 控制台未明确</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- 后付费是否开启：是 / 否</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- 是否有资源包：是 / 否</span><br></div></code></pre></div></div>
<p>如果控制台没明确写 Token Plan 能抵扣目标 ASR，就按“不确定 / 不能默认抵扣”处理。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="sdk-路径先跑-dashscope-python-demo">SDK 路径：先跑 DashScope Python demo<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#sdk-%E8%B7%AF%E5%BE%84%E5%85%88%E8%B7%91-dashscope-python-demo" class="hash-link" aria-label="SDK 路径：先跑 DashScope Python demo的直接链接" title="SDK 路径：先跑 DashScope Python demo的直接链接" translate="no">​</a></h2>
<p>阿里云官方实时语音识别快速开始给了 DashScope SDK 示例，Python 里主包是：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">pip install dashscope</span><br></div></code></pre></div></div>
<p>如果要直接从麦克风采集，示例还会用：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">pip install pyaudio</span><br></div></code></pre></div></div>
<p>macOS 上 <code>pyaudio</code> 可能需要系统 PortAudio 依赖。第一轮如果装麦克风采集麻烦，可以先用本地音频文件 smoke test；真正网页产品不依赖本机 <code>pyaudio</code>，会由浏览器采集音频。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="环境变量">环境变量<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F" class="hash-link" aria-label="环境变量的直接链接" title="环境变量的直接链接" translate="no">​</a></h3>
<p>不要把 Key 写进源码。最小形态：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">export DASHSCOPE_API_KEY='[REDACTED]'</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export ALIYUN_BAILIAN_WORKSPACE_ID='[REDACTED]'</span><br></div></code></pre></div></div>
<p>Python 里用：</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> os</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> dashscope</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">dashscope</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">api_key </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> os</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">environ</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'DASHSCOPE_API_KEY'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">dashscope</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">base_websocket_api_url </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string-interpolation string" style="color:#e3116c">f"wss://</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">os</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation">environ</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">[</span><span class="token string-interpolation interpolation string" style="color:#e3116c">'ALIYUN_BAILIAN_WORKSPACE_ID'</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">]</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">.cn-beijing.maas.aliyuncs.com/api-ws/v1/inference"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="文件-smoke-test">文件 smoke test<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E6%96%87%E4%BB%B6-smoke-test" class="hash-link" aria-label="文件 smoke test的直接链接" title="文件 smoke test的直接链接" translate="no">​</a></h3>
<p>如果手头已有 16k wav，可以先跑非麦克风版本验证账号、模型、Workspace 和 API Key：</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> http </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> HTTPStatus</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> dashscope</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">audio</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">asr </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> Recognition</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">recognition </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> Recognition</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    model</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">'paraformer-realtime-v2'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token builtin">format</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">'wav'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    sample_rate</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">16000</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    language_hints</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'zh'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'en'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    callback</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">None</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">result </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> recognition</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">call</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'sample-16k-mono.wav'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> result</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">status_code </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> HTTPStatus</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">OK</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> sentence </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> result</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">get_sentence</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">sentence</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'text'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">else</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Error:'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> result</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">message</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>这一步验收的是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">API Key 正确 / Workspace URL 正确 / 模型可调用 / 音频格式可识别 / 账单产生在预期模型上</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="麦克风实时-demo">麦克风实时 demo<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E9%BA%A6%E5%85%8B%E9%A3%8E%E5%AE%9E%E6%97%B6-demo" class="hash-link" aria-label="麦克风实时 demo的直接链接" title="麦克风实时 demo的直接链接" translate="no">​</a></h3>
<p>实时 demo 的关键点是：</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> dashscope</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">audio</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">asr </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> Recognition</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> RecognitionCallback</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> RecognitionResult</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">Callback</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">RecognitionCallback</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">on_event</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">self</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> result</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> RecognitionResult</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">None</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        sentence </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> result</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">get_sentence</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'text'</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> sentence</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'text:'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> sentence</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'text'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> RecognitionResult</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">is_sentence_end</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">sentence</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'final sentence, usage:'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> result</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">get_usage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">sentence</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">recognition </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> Recognition</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    model</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">'qwen-audio-3.0-asr-flash-streaming'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token builtin">format</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">'pcm'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    sample_rate</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">16000</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    semantic_punctuation_enabled</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">False</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    callback</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">Callback</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">recognition</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">start</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># 循环读取麦克风或文件音频：recognition.send_audio_frame(data)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># 结束时：recognition.stop()</span><br></div></code></pre></div></div>
<p>官方文档建议流式发送时，每次音频约 <strong>100ms</strong>，数据大小保持在 <strong>1KB 到 16KB</strong>。这和讯飞的 40ms 发包不同，所以 provider adapter 里要把“发包大小 / 节奏”做成配置。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="websocket-路径不用-sdk-时怎么接">WebSocket 路径：不用 SDK 时怎么接<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#websocket-%E8%B7%AF%E5%BE%84%E4%B8%8D%E7%94%A8-sdk-%E6%97%B6%E6%80%8E%E4%B9%88%E6%8E%A5" class="hash-link" aria-label="WebSocket 路径：不用 SDK 时怎么接的直接链接" title="WebSocket 路径：不用 SDK 时怎么接的直接链接" translate="no">​</a></h2>
<p>如果我们用 Node、Go、Rust 或浏览器 relay，不一定用 Python SDK。Paraformer WebSocket 文档给出的核心形态是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">wss://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api-ws/v1/inference</span><br></div></code></pre></div></div>
<p>请求头：</p>
<div class="language-http codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-http codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Authorization: Bearer [REDACTED]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">X-DashScope-WorkSpace: [REDACTED]   # 可选，按 Workspace 场景使用</span><br></div></code></pre></div></div>
<p>交互流程是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">建立 WebSocket</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 发送 run-task 开启任务</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 收到 task-started</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 持续发送单声道音频二进制</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 持续接收 result-generated</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 发送 finish-task</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 收到 task-finished</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 关闭连接</span><br></div></code></pre></div></div>
<p>这适合做后端 relay。前端只连我们的服务，不直接连接阿里云：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Browser MediaRecorder / AudioWorklet</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; wss://realtime-asr.public.wzhecnu.cn/api/asr/session/{id}/stream</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; backend reads DASHSCOPE_API_KEY from env</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; backend connects Aliyun WebSocket</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; backend maps result-generated to transcript.partial / transcript.final</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; browser renders live captions</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="我们自己的产品配置">我们自己的产品配置<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E6%88%91%E4%BB%AC%E8%87%AA%E5%B7%B1%E7%9A%84%E4%BA%A7%E5%93%81%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="我们自己的产品配置的直接链接" title="我们自己的产品配置的直接链接" translate="no">​</a></h2>
<p>建议配置不要写死在代码里：</p>
<div class="language-dotenv codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dotenv codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">ASR_PROVIDER=aliyun</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ALIYUN_ASR_MODEL=qwen-audio-3.0-asr-flash-streaming</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ALIYUN_BAILIAN_REGION=cn-beijing</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ALIYUN_BAILIAN_WORKSPACE_ID=[REDACTED]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">DASHSCOPE_API_KEY=[REDACTED]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ALIYUN_ASR_AUDIO_FORMAT=pcm</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ALIYUN_ASR_SAMPLE_RATE=16000</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ALIYUN_ASR_CHUNK_MS=100</span><br></div></code></pre></div></div>
<p>统一事件建议：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"type"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"transcript.partial"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"provider"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"aliyun"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"text"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"我们今天讨论"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"seq"</span><span class="token operator" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">12</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"type"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"transcript.final"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"provider"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"aliyun"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"text"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"我们今天讨论实时语音识别。"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"seq"</span><span class="token operator" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">13</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"usage_seconds"</span><span class="token operator" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">4.2</span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>这样后续切科大讯飞 / 火山 / 腾讯时，前端不用改 UI，只替换 provider adapter。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="实践分工你点网页我写代码">实践分工：你点网页，我写代码<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E5%AE%9E%E8%B7%B5%E5%88%86%E5%B7%A5%E4%BD%A0%E7%82%B9%E7%BD%91%E9%A1%B5%E6%88%91%E5%86%99%E4%BB%A3%E7%A0%81" class="hash-link" aria-label="实践分工：你点网页，我写代码的直接链接" title="实践分工：你点网页，我写代码的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="你先做">你先做<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E4%BD%A0%E5%85%88%E5%81%9A" class="hash-link" aria-label="你先做的直接链接" title="你先做的直接链接" translate="no">​</a></h3>
<ol>
<li class="">登录阿里云：<a href="https://aliyun.com/" target="_blank" rel="noopener noreferrer" class="">https://aliyun.com/</a></li>
<li class="">打开百炼：<a href="https://bailian.console.aliyun.com/" target="_blank" rel="noopener noreferrer" class="">https://bailian.console.aliyun.com/</a></li>
<li class="">确认百炼已开通，地域优先选华北 2（北京）。</li>
<li class="">找到或创建 Workspace，记录是否能看到 Workspace ID。</li>
<li class="">在模型广场搜索 <code>qwen-audio-3.0-asr-flash-streaming</code>、<code>fun-asr-realtime</code>、<code>paraformer-realtime-v2</code>。</li>
<li class="">按“获取 API Key”文档创建百炼 API Key。</li>
<li class="">到计费 / 免费额度 / 用量统计页面确认 ASR 是否有免费额度或资源包。</li>
<li class="">不要把真实 Key 发到聊天里；只告诉我：</li>
</ol>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">- 百炼是否已开通：是/否</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- 地域：华北2（北京）/ 新加坡 / 其他</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- Workspace ID 是否能看到：是/否</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- 模型是否可用：qwen-audio / fun-asr / qwen3-asr / paraformer 哪些可用</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- 免费额度是否显示：是/否</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- Token Plan 是否明确显示覆盖目标 ASR：是/否/不确定</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- 后付费是否开启：是/否</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- API Key 是否已创建：是/否</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="我来做">我来做<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E6%88%91%E6%9D%A5%E5%81%9A" class="hash-link" aria-label="我来做的直接链接" title="我来做的直接链接" translate="no">​</a></h3>
<ol>
<li class="">写最小 Python smoke 脚本，只读环境变量，不写死 Key。</li>
<li class="">准备 16k/mono wav 或 pcm 测试音频。</li>
<li class="">先跑文件识别，再跑麦克风 / 浏览器实时流。</li>
<li class="">如果 SDK 不顺，直接写 WebSocket 版客户端。</li>
<li class="">把 <code>result-generated</code> 转成统一 <code>partial/final</code> 事件。</li>
<li class="">做网页实时字幕页：开始录音、实时上屏、停止、保存 transcript。</li>
<li class="">接 AI 纪要按钮，但不和 ASR 首轮 smoke 混在一起。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="第一轮验收标准">第一轮验收标准<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E7%AC%AC%E4%B8%80%E8%BD%AE%E9%AA%8C%E6%94%B6%E6%A0%87%E5%87%86" class="hash-link" aria-label="第一轮验收标准的直接链接" title="第一轮验收标准的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>验收项</th><th>通过标准</th></tr></thead><tbody><tr><td>服务开通</td><td>百炼控制台显示目标 ASR 模型可用</td></tr><tr><td>鉴权</td><td>SDK / WebSocket 不返回 401 / 403 / 无权限 / Workspace 错误</td></tr><tr><td>音频格式</td><td>10–20 秒中文 wav/pcm 能被识别</td></tr><tr><td>实时性</td><td>发送音频期间持续返回中间结果，而不是结束后才一次性返回</td></tr><tr><td>final 结果</td><td>句末能拿到稳定文本，能区分 partial 与 final</td></tr><tr><td>成本</td><td>用量记录进入预期 ASR 模型，免费额度 / 后付费扣费路径可解释</td></tr><tr><td>日志</td><td>记录 request_id、first package delay、last package delay、错误码</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="常见坑">常见坑<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E5%B8%B8%E8%A7%81%E5%9D%91" class="hash-link" aria-label="常见坑的直接链接" title="常见坑的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="坑-1把-token-plan-当成-asr-免费包">坑 1：把 Token Plan 当成 ASR 免费包<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E5%9D%91-1%E6%8A%8A-token-plan-%E5%BD%93%E6%88%90-asr-%E5%85%8D%E8%B4%B9%E5%8C%85" class="hash-link" aria-label="坑 1：把 Token Plan 当成 ASR 免费包的直接链接" title="坑 1：把 Token Plan 当成 ASR 免费包的直接链接" translate="no">​</a></h3>
<p>不要这么做。Token Plan 和 ASR 模型免费额度 / 秒级计费不是同一件事。以控制台模型详情和账单为准。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="坑-2地域和-api-key-混用">坑 2：地域和 API Key 混用<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E5%9D%91-2%E5%9C%B0%E5%9F%9F%E5%92%8C-api-key-%E6%B7%B7%E7%94%A8" class="hash-link" aria-label="坑 2：地域和 API Key 混用的直接链接" title="坑 2：地域和 API Key 混用的直接链接" translate="no">​</a></h3>
<p>北京、新加坡等地域的 API Key 和域名可能不同。<code>DASHSCOPE_API_KEY</code>、Workspace ID、base WebSocket URL 要对应同一地域。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="坑-3workspace-id-填错">坑 3：Workspace ID 填错<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E5%9D%91-3workspace-id-%E5%A1%AB%E9%94%99" class="hash-link" aria-label="坑 3：Workspace ID 填错的直接链接" title="坑 3：Workspace ID 填错的直接链接" translate="no">​</a></h3>
<p><code>wss://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api-ws/v1/inference</code> 里的 <code>{WorkspaceId}</code> 不是随便写的名字，要用控制台真实业务空间 ID。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="坑-4浏览器直接保存-key">坑 4：浏览器直接保存 Key<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E5%9D%91-4%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9B%B4%E6%8E%A5%E4%BF%9D%E5%AD%98-key" class="hash-link" aria-label="坑 4：浏览器直接保存 Key的直接链接" title="坑 4：浏览器直接保存 Key的直接链接" translate="no">​</a></h3>
<p>浏览器不能直接拿 <code>DASHSCOPE_API_KEY</code>。即使阿里有临时 Token 机制，也应该由后端签发短时访问权限并做限流、鉴权、审计。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="坑-5音频包节奏照搬其他厂商">坑 5：音频包节奏照搬其他厂商<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E5%9D%91-5%E9%9F%B3%E9%A2%91%E5%8C%85%E8%8A%82%E5%A5%8F%E7%85%A7%E6%90%AC%E5%85%B6%E4%BB%96%E5%8E%82%E5%95%86" class="hash-link" aria-label="坑 5：音频包节奏照搬其他厂商的直接链接" title="坑 5：音频包节奏照搬其他厂商的直接链接" translate="no">​</a></h3>
<p>讯飞常见 40ms，火山常见 100–200ms，阿里 SDK 文档建议约 100ms。provider adapter 要分别配置。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="坑-6只测-paraformer漏掉-qwen-audio--fun-asr">坑 6：只测 Paraformer，漏掉 Qwen-Audio / Fun-ASR<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E5%9D%91-6%E5%8F%AA%E6%B5%8B-paraformer%E6%BC%8F%E6%8E%89-qwen-audio--fun-asr" class="hash-link" aria-label="坑 6：只测 Paraformer，漏掉 Qwen-Audio / Fun-ASR的直接链接" title="坑 6：只测 Paraformer，漏掉 Qwen-Audio / Fun-ASR的直接链接" translate="no">​</a></h3>
<p>如果我们目标是 2026 年的中文实时转写效果，应该把 <code>qwen-audio-3.0-asr-flash-streaming</code> 和 <code>fun-asr-realtime</code> 纳入第一轮，而不是只测传统 Paraformer。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="后续更新计划">后续更新计划<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E5%90%8E%E7%BB%AD%E6%9B%B4%E6%96%B0%E8%AE%A1%E5%88%92" class="hash-link" aria-label="后续更新计划的直接链接" title="后续更新计划的直接链接" translate="no">​</a></h2>
<p>这篇先作为从零教程第一版。拿到控制台状态后继续补：</p>
<ol>
<li class="">实际控制台路径截图对应的步骤；</li>
<li class=""><code>DASHSCOPE_API_KEY</code> / Workspace 环境变量 smoke test；</li>
<li class="">10–20 秒中文文件识别结果；</li>
<li class="">麦克风实时流结果和 request_id；</li>
<li class="">WebSocket relay 最小实现；</li>
<li class="">与科大讯飞 / 火山同音频 A/B 对比；</li>
<li class="">最终是否把阿里云作为主 provider 或备 provider。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="参考入口">参考入口<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide#%E5%8F%82%E8%80%83%E5%85%A5%E5%8F%A3" class="hash-link" aria-label="参考入口的直接链接" title="参考入口的直接链接" translate="no">​</a></h2>
<ul>
<li class="">阿里云百炼控制台：<a href="https://bailian.console.aliyun.com/" target="_blank" rel="noopener noreferrer" class="">https://bailian.console.aliyun.com/</a></li>
<li class="">百炼模型广场：<a href="https://bailian.console.aliyun.com/?tab=model#/model-market" target="_blank" rel="noopener noreferrer" class="">https://bailian.console.aliyun.com/?tab=model#/model-market</a></li>
<li class="">语音识别模型选型：<a href="https://help.aliyun.com/zh/model-studio/asr-model/" target="_blank" rel="noopener noreferrer" class="">https://help.aliyun.com/zh/model-studio/asr-model/</a></li>
<li class="">实时语音识别用户指南：<a href="https://help.aliyun.com/zh/model-studio/real-time-speech-recognition-user-guide" target="_blank" rel="noopener noreferrer" class="">https://help.aliyun.com/zh/model-studio/real-time-speech-recognition-user-guide</a></li>
<li class="">Paraformer 实时语音识别 API：<a href="https://help.aliyun.com/zh/model-studio/paraformer-real-time-speech-recognition-api-reference/" target="_blank" rel="noopener noreferrer" class="">https://help.aliyun.com/zh/model-studio/paraformer-real-time-speech-recognition-api-reference/</a></li>
<li class="">Paraformer WebSocket API：<a href="https://help.aliyun.com/zh/model-studio/websocket-for-paraformer-real-time-service" target="_blank" rel="noopener noreferrer" class="">https://help.aliyun.com/zh/model-studio/websocket-for-paraformer-real-time-service</a></li>
<li class="">Paraformer Python SDK：<a href="https://help.aliyun.com/zh/model-studio/paraformer-real-time-speech-recognition-python-sdk" target="_blank" rel="noopener noreferrer" class="">https://help.aliyun.com/zh/model-studio/paraformer-real-time-speech-recognition-python-sdk</a></li>
<li class="">获取 API Key：<a href="https://help.aliyun.com/zh/model-studio/get-api-key" target="_blank" rel="noopener noreferrer" class="">https://help.aliyun.com/zh/model-studio/get-api-key</a></li>
<li class="">使用 Workspace：<a href="https://help.aliyun.com/zh/model-studio/use-workspace" target="_blank" rel="noopener noreferrer" class="">https://help.aliyun.com/zh/model-studio/use-workspace</a></li>
<li class="">获取 Workspace ID：<a href="https://help.aliyun.com/zh/model-studio/obtain-the-app-id-and-workspace-id" target="_blank" rel="noopener noreferrer" class="">https://help.aliyun.com/zh/model-studio/obtain-the-app-id-and-workspace-id</a></li>
<li class="">临时 API Key：<a href="https://help.aliyun.com/zh/model-studio/generate-temporary-api-key" target="_blank" rel="noopener noreferrer" class="">https://help.aliyun.com/zh/model-studio/generate-temporary-api-key</a></li>
<li class="">查询模型限额：<a href="https://help.aliyun.com/zh/model-studio/list-quotas" target="_blank" rel="noopener noreferrer" class="">https://help.aliyun.com/zh/model-studio/list-quotas</a></li>
<li class="">模型调用计费：<a href="https://help.aliyun.com/zh/model-studio/billing" target="_blank" rel="noopener noreferrer" class="">https://help.aliyun.com/zh/model-studio/billing</a></li>
</ul>]]></content>
        <category label="aliyun" term="aliyun"/>
        <category label="dashscope" term="dashscope"/>
        <category label="bailian" term="bailian"/>
        <category label="paraformer" term="paraformer"/>
        <category label="fun-asr" term="fun-asr"/>
        <category label="qwen-audio" term="qwen-audio"/>
        <category label="realtime" term="realtime"/>
        <category label="asr" term="asr"/>
        <category label="speech-to-text" term="speech-to-text"/>
        <category label="tutorial" term="tutorial"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[实时语音转录怎么接：讯飞、阿里云、火山引擎、腾讯云四条路线]]></title>
        <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration</id>
        <link href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration"/>
        <updated>2026-08-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[只看“边说边出字”的实时 ASR，把科大讯飞、阿里云 Paraformer、火山引擎豆包语音、腾讯云实时语音识别拆成控制台开通、API 鉴权、WebSocket relay、部署位置、计费和 A/B 实测流程。]]></summary>
        <content type="html"><![CDATA[<p>这次先把问题收窄到最核心的一层：<strong>不要会议纪要，不要 AI 总结，不要录音上传后处理，只要“边说边出字”的实时语音转录</strong>。</p>
<p>这件事不是去 GitHub 找一个 Whisper WebUI 就能解决。真正的实时 ASR 交付，是浏览器或客户端持续推送音频流，ASR 服务持续返回 partial/final transcript。中文场景里，第一轮应该直接测成熟云服务：科大讯飞、阿里云、火山引擎、腾讯云。</p>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>同主题前情</div><div class="admonitionContent_BuS1"><ul>
<li class=""><a class="" href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mobile-web-realtime-transcription-pwa">网页录音到 AI 纪要：先选成熟 ASR，再看现成产品</a>：讲的是“网页录音 + ASR + AI 纪要”的整体路线。</li>
<li class=""><a class="" href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/speakr-recording-ai-notes-practice">Speakr 实操：录完之后，转写和 AI 纪要到底在哪里</a>：确认了 Speakr 是录完/上传后处理，不是实时字幕。</li>
</ul><p>本文只回答下一步：<strong>四家国内实时 ASR 分别怎么接、在哪里操作、需不需要服务器、我能帮你做到哪一步。</strong></p></div></div>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>一句话结论</div><div class="admonitionContent_BuS1"><p>四家接入形态本质一致：</p><div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">浏览器麦克风</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 自建后端保护密钥 / 签名 / relay</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 厂商 WebSocket 实时 ASR</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; partial/final transcript 实时上屏</span><br></div></code></pre></div></div><p>如果目标是中文实时转录效果，第一轮不要急着押唯一赢家。建议按同一套页面同时测 <strong>讯飞实时语音转写、火山豆包大模型流式语音识别、阿里云 Paraformer、腾讯云实时语音识别</strong>，用真实会议/口述音频比较首字延迟、final 准确率、标点断句、中英混杂和成本。</p></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>证据口径</div><div class="admonitionContent_BuS1"><p>快照时间为 <strong>2026-08-10 CST</strong>。本文依据四家官方 API 文档和计费说明做静态接入分析，没有使用任何账号密钥，也没有发起付费调用。价格、免费额度、模型版本和资源包规格会变化，落地前必须以各家控制台和计费页为准。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="先看统一架构为什么不能只写前端">先看统一架构：为什么不能只写前端<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#%E5%85%88%E7%9C%8B%E7%BB%9F%E4%B8%80%E6%9E%B6%E6%9E%84%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E8%83%BD%E5%8F%AA%E5%86%99%E5%89%8D%E7%AB%AF" class="hash-link" aria-label="先看统一架构：为什么不能只写前端的直接链接" title="先看统一架构：为什么不能只写前端的直接链接" translate="no">​</a></h2>
<p>实时转录产品至少分四层：</p>
<table><thead><tr><th>层</th><th>做什么</th><th>是否必须</th></tr></thead><tbody><tr><td>浏览器/客户端</td><td>请求麦克风权限，采集音频，切成厂商要求的音频包</td><td>必须</td></tr><tr><td>自建后端</td><td>保存 API key/Secret，生成签名或临时 session，转发 WebSocket</td><td>强烈建议必须</td></tr><tr><td>厂商 ASR</td><td>接收音频流，返回识别事件</td><td>必须</td></tr><tr><td>展示层</td><td>根据 partial/final/replace 事件实时上屏</td><td>必须</td></tr></tbody></table>
<p>后端 relay 的核心价值不是“多写一层代码”，而是保护密钥。厂商 API key、SecretId、SecretKey、AppID 等都不能放进浏览器。后端还要统一日志、时长统计、限流、错误码、provider 切换和成本报警。</p>
<p>所以一个最小可用 demo 应该长这样：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Start Recording</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; POST /api/asr/sessions { provider: "iflytek" | "aliyun" | "volc" | "tencent" }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 浏览器建立 ws://our-server/realtime-asr/&lt;session_id&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 后端连接厂商 wss://...</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 浏览器每 40ms / 100ms / 200ms 发音频包</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 页面收到 transcript.delta / transcript.final</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Stop</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 后端关闭厂商 WebSocket</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 保存完整 transcript 和调用时长</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-科大讯飞中文实时转写第一轮必测">1. 科大讯飞：中文实时转写第一轮必测<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#1-%E7%A7%91%E5%A4%A7%E8%AE%AF%E9%A3%9E%E4%B8%AD%E6%96%87%E5%AE%9E%E6%97%B6%E8%BD%AC%E5%86%99%E7%AC%AC%E4%B8%80%E8%BD%AE%E5%BF%85%E6%B5%8B" class="hash-link" aria-label="1. 科大讯飞：中文实时转写第一轮必测的直接链接" title="1. 科大讯飞：中文实时转写第一轮必测的直接链接" translate="no">​</a></h2>
<p>讯飞这里要先分清两个产品：</p>
<table><thead><tr><th>产品</th><th>更像什么</th><th>文档要点</th></tr></thead><tbody><tr><td><a href="https://www.xfyun.cn/doc/asr/rtasr/API.html" target="_blank" rel="noopener noreferrer" class="">实时语音转写</a></td><td>连续实时 transcript，适合会议/长语音流</td><td>WebSocket 长连接，文档说明实时返回文字流；支持 16k、16bit、单声道 PCM，建议每 40ms 发送 1280 字节</td></tr><tr><td><a href="https://www.xfyun.cn/doc/asr/voicedictation/API.html" target="_blank" rel="noopener noreferrer" class="">语音听写（流式版）</a></td><td>1 分钟内即时语音转文字</td><td>文档明确写到“一边上传音频一边获得识别文本”，并支持动态修正</td></tr></tbody></table>
<p>如果目标是“会议/长录音实时字幕”，优先看实时语音转写；如果目标是短语音输入、语音助手、表单输入，再看语音听写流式版。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="怎么做">怎么做<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#%E6%80%8E%E4%B9%88%E5%81%9A" class="hash-link" aria-label="怎么做的直接链接" title="怎么做的直接链接" translate="no">​</a></h3>
<ol>
<li class="">在讯飞开放平台注册并进入控制台。</li>
<li class="">创建应用，开通实时语音转写或语音听写流式版。</li>
<li class="">拿到 AppID 和接口密钥材料。</li>
<li class="">后端根据文档生成签名，连接 <code>wss://rtasr.xfyun.cn/v1/ws?...</code> 或 <code>wss://iat-api.xfyun.cn/v2/iat</code>。</li>
<li class="">浏览器把麦克风音频转成 16k/16bit/mono PCM。</li>
<li class="">后端按文档节奏发包，接收 JSON 结果。</li>
<li class="">页面根据返回结果追加或替换实时字幕；如果启用动态修正，要支持“上一段文字被更新”。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="需要在哪里操作">需要在哪里操作<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#%E9%9C%80%E8%A6%81%E5%9C%A8%E5%93%AA%E9%87%8C%E6%93%8D%E4%BD%9C" class="hash-link" aria-label="需要在哪里操作的直接链接" title="需要在哪里操作的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>操作</th><th>位置</th></tr></thead><tbody><tr><td>开通服务、购买/试用、查看方言语种</td><td>讯飞开放平台控制台</td></tr><tr><td>写签名和 relay</td><td>自建后端，推荐部署在 <code>recall.cube</code> 或云服务器</td></tr><tr><td>前端录音页面</td><td>任意 HTTPS 站点；如果给手机用，需要公网 HTTPS</td></tr><tr><td>模型运行</td><td>不需要自己跑模型，由讯飞云端承担</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="我能帮你做什么">我能帮你做什么<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#%E6%88%91%E8%83%BD%E5%B8%AE%E4%BD%A0%E5%81%9A%E4%BB%80%E4%B9%88" class="hash-link" aria-label="我能帮你做什么的直接链接" title="我能帮你做什么的直接链接" translate="no">​</a></h3>
<p>我可以做前端实时字幕页、后端签名/relay、统一 transcript event、时长统计和 smoke test。需要你提供讯飞账号里开通后的密钥，密钥只放服务器环境变量，不写进前端和文章。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-阿里云-paraformer百炼体系里的实时-asr">2. 阿里云 Paraformer：百炼体系里的实时 ASR<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#2-%E9%98%BF%E9%87%8C%E4%BA%91-paraformer%E7%99%BE%E7%82%BC%E4%BD%93%E7%B3%BB%E9%87%8C%E7%9A%84%E5%AE%9E%E6%97%B6-asr" class="hash-link" aria-label="2. 阿里云 Paraformer：百炼体系里的实时 ASR的直接链接" title="2. 阿里云 Paraformer：百炼体系里的实时 ASR的直接链接" translate="no">​</a></h2>
<p>阿里云的主线是 <a href="https://help.aliyun.com/zh/model-studio/paraformer-real-time-speech-recognition-api-reference/" target="_blank" rel="noopener noreferrer" class="">Paraformer 实时语音识别</a>。它的 <a href="https://help.aliyun.com/zh/model-studio/websocket-for-paraformer-real-time-service" target="_blank" rel="noopener noreferrer" class="">WebSocket API 文档</a> 说明：通过 WebSocket 访问实时语音识别服务，请求头使用 <code>Authorization: Bearer &lt;api_key&gt;</code>，并建议使用业务空间专属域名；文档给出的固定形态是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">wss://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api-ws/v1/inference</span><br></div></code></pre></div></div>
<p>这条路线适合已经在阿里云/百炼/通义体系里管理模型和 API 的团队。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="怎么做-1">怎么做<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#%E6%80%8E%E4%B9%88%E5%81%9A-1" class="hash-link" aria-label="怎么做的直接链接" title="怎么做的直接链接" translate="no">​</a></h3>
<ol>
<li class="">登录阿里云，进入百炼 / Model Studio。</li>
<li class="">创建或选择 Workspace。</li>
<li class="">开通 Paraformer 实时语音识别，创建 API Key。</li>
<li class="">后端用 API Key 连接 WebSocket，不把 key 暴露到浏览器。</li>
<li class="">按阿里事件协议发送开始、持续音频、结束事件。</li>
<li class="">把服务端事件转成统一的 <code>transcript.partial</code> / <code>transcript.final</code>。</li>
<li class="">页面实时显示，停止后关闭 session。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="需要在哪里操作-1">需要在哪里操作<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#%E9%9C%80%E8%A6%81%E5%9C%A8%E5%93%AA%E9%87%8C%E6%93%8D%E4%BD%9C-1" class="hash-link" aria-label="需要在哪里操作的直接链接" title="需要在哪里操作的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>操作</th><th>位置</th></tr></thead><tbody><tr><td>开通百炼、Workspace、API Key</td><td>阿里云控制台</td></tr><tr><td>relay 服务</td><td><code>recall.cube</code> 或云服务器</td></tr><tr><td>前端页面</td><td>HTTPS public/local 域名</td></tr><tr><td>模型运行</td><td>阿里云托管，不需要本地 GPU</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="付费api-注意点">付费/API 注意点<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#%E4%BB%98%E8%B4%B9api-%E6%B3%A8%E6%84%8F%E7%82%B9" class="hash-link" aria-label="付费/API 注意点的直接链接" title="付费/API 注意点的直接链接" translate="no">​</a></h3>
<p>阿里云模型和价格会跟随百炼模型服务页更新，落地前要查 <a href="https://help.aliyun.com/zh/model-studio/models" target="_blank" rel="noopener noreferrer" class="">模型大全功能规格与计费</a>。代码里只应该记录 provider、model、调用时长和账单维度，不要把具体价格写死成业务逻辑。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="我能帮你做什么-1">我能帮你做什么<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#%E6%88%91%E8%83%BD%E5%B8%AE%E4%BD%A0%E5%81%9A%E4%BB%80%E4%B9%88-1" class="hash-link" aria-label="我能帮你做什么的直接链接" title="我能帮你做什么的直接链接" translate="no">​</a></h3>
<p>我可以封装阿里云 adapter，把前端音频流转成 Paraformer WebSocket 事件；同时写测试脚本，记录延迟、最终文本、错误码和调用时长。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-火山引擎--豆包语音大模型流式-asr-值得重点测">3. 火山引擎 / 豆包语音：大模型流式 ASR 值得重点测<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#3-%E7%81%AB%E5%B1%B1%E5%BC%95%E6%93%8E--%E8%B1%86%E5%8C%85%E8%AF%AD%E9%9F%B3%E5%A4%A7%E6%A8%A1%E5%9E%8B%E6%B5%81%E5%BC%8F-asr-%E5%80%BC%E5%BE%97%E9%87%8D%E7%82%B9%E6%B5%8B" class="hash-link" aria-label="3. 火山引擎 / 豆包语音：大模型流式 ASR 值得重点测的直接链接" title="3. 火山引擎 / 豆包语音：大模型流式 ASR 值得重点测的直接链接" translate="no">​</a></h2>
<p>火山现在有 <a href="https://docs.volcengine.com/docs/6561/1354869?lang=zh" target="_blank" rel="noopener noreferrer" class="">大模型流式语音识别 API</a>。官方文档列出三种 WebSocket 地址：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">双向流式模式：    wss://openspeech.bytedance.com/api/v3/sauc/bigmodel</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">流式输入模式：    wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_nostream</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">双向流式优化版：  wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_async</span><br></div></code></pre></div></div>
<p>文档说明双向流式会尽快返回识别到的字符，流式输入模式准确率更高；音频单包建议 100–200ms，发包间隔建议 100–200ms，双向流式模式推荐 200ms 分包。它还提供二遍识别、ITN、标点、顺滑、说话人等参数，适合认真测试“快”和“准”的折中。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="怎么做-2">怎么做<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#%E6%80%8E%E4%B9%88%E5%81%9A-2" class="hash-link" aria-label="怎么做的直接链接" title="怎么做的直接链接" translate="no">​</a></h3>
<ol>
<li class="">登录火山引擎控制台，开通豆包语音 / 语音识别大模型。</li>
<li class="">新版控制台获取 <code>X-Api-Key</code>；旧版控制台可能还涉及 App Key / Access Key。</li>
<li class="">选择资源 ID：文档列出小时版和并发版，例如 <code>volc.bigasr.sauc.duration</code>、<code>volc.seedasr.sauc.duration</code> 等。</li>
<li class="">后端连接 <code>bigmodel_async</code> 或 <code>bigmodel</code>，并在 HTTP header 中放入鉴权和资源 ID。</li>
<li class="">WebSocket 建连后，先发送 full client request，再持续发送 audio only request。</li>
<li class="">浏览器音频按 100–200ms 分包；后端负责二进制 header、压缩、序列号、错误码解析。</li>
<li class="">页面显示快速结果；如果开启二遍识别，最终结果要覆盖临时结果。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="需要在哪里操作-2">需要在哪里操作<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#%E9%9C%80%E8%A6%81%E5%9C%A8%E5%93%AA%E9%87%8C%E6%93%8D%E4%BD%9C-2" class="hash-link" aria-label="需要在哪里操作的直接链接" title="需要在哪里操作的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>操作</th><th>位置</th></tr></thead><tbody><tr><td>开通服务、API Key、资源包/后付费</td><td>火山引擎控制台</td></tr><tr><td>WebSocket 二进制协议封装</td><td>自建后端</td></tr><tr><td>实时前端</td><td>HTTPS 页面</td></tr><tr><td>模型运行</td><td>火山云端，不需要自建 GPU</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="付费api-注意点-1">付费/API 注意点<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#%E4%BB%98%E8%B4%B9api-%E6%B3%A8%E6%84%8F%E7%82%B9-1" class="hash-link" aria-label="付费/API 注意点的直接链接" title="付费/API 注意点的直接链接" translate="no">​</a></h3>
<p><a href="https://docs.volcengine.com/docs/6561/1359370?lang=zh" target="_blank" rel="noopener noreferrer" class="">火山计费说明</a> 在当前快照里列出资源包预付费和按调用后付费：豆包流式语音识别模型 2.0、以及大模型流式语音识别都有按小时计费项。文章里只适合引用“按音频时长折算小时、资源包/后付费并存”这个计费结构；具体价格要以控制台购买页为准。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="我能帮你做什么-2">我能帮你做什么<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#%E6%88%91%E8%83%BD%E5%B8%AE%E4%BD%A0%E5%81%9A%E4%BB%80%E4%B9%88-2" class="hash-link" aria-label="我能帮你做什么的直接链接" title="我能帮你做什么的直接链接" translate="no">​</a></h3>
<p>我可以实现火山二进制 WebSocket adapter，记录 <code>X-Tt-Logid</code> 方便排障，做小时版/并发版 resource id 配置，并把二遍识别结果映射成统一 final transcript。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-腾讯云控制台和计费体系清楚适合已有腾讯云团队">4. 腾讯云：控制台和计费体系清楚，适合已有腾讯云团队<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#4-%E8%85%BE%E8%AE%AF%E4%BA%91%E6%8E%A7%E5%88%B6%E5%8F%B0%E5%92%8C%E8%AE%A1%E8%B4%B9%E4%BD%93%E7%B3%BB%E6%B8%85%E6%A5%9A%E9%80%82%E5%90%88%E5%B7%B2%E6%9C%89%E8%85%BE%E8%AE%AF%E4%BA%91%E5%9B%A2%E9%98%9F" class="hash-link" aria-label="4. 腾讯云：控制台和计费体系清楚，适合已有腾讯云团队的直接链接" title="4. 腾讯云：控制台和计费体系清楚，适合已有腾讯云团队的直接链接" translate="no">​</a></h2>
<p>腾讯云官方 <a href="https://cloud.tencent.com/document/product/1093/48982" target="_blank" rel="noopener noreferrer" class="">实时语音识别 WebSocket</a> 文档明确写到：服务采用 WebSocket 协议，对实时音频流进行识别，同步返回识别结果，达到“边说边出文字”的效果。文档还说明使用前要开通语音识别服务，进入 API 密钥管理生成 AppID、SecretID 和 SecretKey，用于签名鉴权。</p>
<p>文档给出的请求地址形态是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">wss://asr.cloud.tencent.com/asr/v2/&lt;appid&gt;?{请求参数}</span><br></div></code></pre></div></div>
<p>接口要求里还列出 16k 或 8k 采样率、16bits、单声道，以及 pcm、wav、opus、speex、silk、mp3、m4a、aac 等音频格式。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="怎么做-3">怎么做<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#%E6%80%8E%E4%B9%88%E5%81%9A-3" class="hash-link" aria-label="怎么做的直接链接" title="怎么做的直接链接" translate="no">​</a></h3>
<ol>
<li class="">登录腾讯云并开通语音识别 ASR。</li>
<li class="">在 API 密钥管理里创建 AppID、SecretID、SecretKey。</li>
<li class="">后端按文档生成签名参数，连接 <code>wss://asr.cloud.tencent.com/asr/v2/&lt;appid&gt;?...</code>。</li>
<li class="">浏览器采集音频，转换成腾讯云支持的采样率/编码。</li>
<li class="">后端发送音频流，接收识别结果。</li>
<li class="">页面显示实时字幕，并在停止后关闭连接。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="需要在哪里操作-3">需要在哪里操作<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#%E9%9C%80%E8%A6%81%E5%9C%A8%E5%93%AA%E9%87%8C%E6%93%8D%E4%BD%9C-3" class="hash-link" aria-label="需要在哪里操作的直接链接" title="需要在哪里操作的直接链接" translate="no">​</a></h3>
<table><thead><tr><th>操作</th><th>位置</th></tr></thead><tbody><tr><td>开通 ASR、创建密钥、开启后付费</td><td>腾讯云控制台</td></tr><tr><td>签名/relay</td><td>自建后端</td></tr><tr><td>前端页面</td><td>HTTPS 域名</td></tr><tr><td>模型运行</td><td>腾讯云托管，不需要本地 GPU</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="付费api-注意点-2">付费/API 注意点<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#%E4%BB%98%E8%B4%B9api-%E6%B3%A8%E6%84%8F%E7%82%B9-2" class="hash-link" aria-label="付费/API 注意点的直接链接" title="付费/API 注意点的直接链接" translate="no">​</a></h3>
<p>腾讯云 <a href="https://cloud.tencent.com/document/product/1093/35686" target="_blank" rel="noopener noreferrer" class="">计费概述</a> 说明语音识别提供预付费和后付费两种主要计费模式，开通后按“免费额度 &gt; 预付费 &gt; 后付费”的顺序扣费；后付费默认关闭，需要手动在控制台开启。这个设计适合做成本控制：先不开后付费，先用免费额度/小资源包跑 smoke test。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="我能帮你做什么-3">我能帮你做什么<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#%E6%88%91%E8%83%BD%E5%B8%AE%E4%BD%A0%E5%81%9A%E4%BB%80%E4%B9%88-3" class="hash-link" aria-label="我能帮你做什么的直接链接" title="我能帮你做什么的直接链接" translate="no">​</a></h3>
<p>我可以写腾讯云签名逻辑、WebSocket relay、统一事件映射和用量统计；同时在配置里默认关闭高风险自动扩容/无限后付费，避免测试阶段成本失控。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="到底需不需要云服务器">到底需不需要云服务器<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#%E5%88%B0%E5%BA%95%E9%9C%80%E4%B8%8D%E9%9C%80%E8%A6%81%E4%BA%91%E6%9C%8D%E5%8A%A1%E5%99%A8" class="hash-link" aria-label="到底需不需要云服务器的直接链接" title="到底需不需要云服务器的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>场景</th><th>需要云服务器吗</th><th>推荐位置</th><th>说明</th></tr></thead><tbody><tr><td>写博客/调研</td><td>不需要</td><td>本机</td><td>只读官方文档即可</td></tr><tr><td>本机开发 demo</td><td>不一定</td><td>本机 localhost</td><td>浏览器允许 localhost 麦克风，但手机试用不方便</td></tr><tr><td>手机/外部用户试用</td><td>需要公网 HTTPS</td><td><code>recall.cube</code> + public/local 入口</td><td>麦克风权限需要安全上下文，API key 要留在后端</td></tr><tr><td>内部长期服务</td><td>需要稳定服务机</td><td><code>recall.cube</code> 或云服务器</td><td>要跑 relay、日志、限流、成本统计</td></tr><tr><td>高并发商用</td><td>需要云上扩容</td><td>云服务器/K8s</td><td>需要监控、SLA、容量规划</td></tr></tbody></table>
<p>所以第一版不需要买 GPU，也不需要在本地跑大模型。真正需要的是一个很薄的服务：接收浏览器音频、保护密钥、连接厂商 WebSocket、把结果统一成页面能显示的事件。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="我能帮你做完整流程吗">我能帮你做完整流程吗<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#%E6%88%91%E8%83%BD%E5%B8%AE%E4%BD%A0%E5%81%9A%E5%AE%8C%E6%95%B4%E6%B5%81%E7%A8%8B%E5%90%97" class="hash-link" aria-label="我能帮你做完整流程吗的直接链接" title="我能帮你做完整流程吗的直接链接" translate="no">​</a></h2>
<p>可以，但需要把“我能代办”和“必须由账号所有者完成”的边界分清：</p>
<table><thead><tr><th>步骤</th><th>我能做吗</th><th>需要你做什么</th></tr></thead><tbody><tr><td>官方文档调研、方案设计</td><td>可以</td><td>无</td></tr><tr><td>云服务开通指引</td><td>可以</td><td>你登录对应控制台，完成实名/付费/协议确认</td></tr><tr><td>API key 创建</td><td>我可以指导，但不应接触明文长期密钥</td><td>你创建 key，按安全方式写入服务器 secret</td></tr><tr><td>后端 relay / 前端实时字幕</td><td>可以</td><td>确认部署域名和服务位置</td></tr><tr><td>四家 A/B 测试</td><td>可以</td><td>提供可测试的真实中文音频/读稿，或授权现场麦克风测试</td></tr><tr><td>成本统计和推荐报告</td><td>可以</td><td>提供账单/控制台用量截图或 API 结果，不提供密钥明文</td></tr><tr><td>正式上线</td><td>可以</td><td>确认预算、并发、保留时长、合规和数据策略</td></tr></tbody></table>
<p>我建议服务地址按这类模式做：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">https://realtime-asr.public.wzhecnu.cn/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">https://realtime-asr.local.wzhecnu.cn/</span><br></div></code></pre></div></div>
<p>页面里可以有一个 provider 下拉框：讯飞 / 阿里 / 火山 / 腾讯。每次测试保存同样的指标：</p>
<table><thead><tr><th>指标</th><th>为什么重要</th></tr></thead><tbody><tr><td>首字延迟</td><td>用户是否感觉“实时”</td></tr><tr><td>partial 抖动</td><td>中间文字是否频繁大幅回改</td></tr><tr><td>final 准确率</td><td>最终 transcript 能不能用</td></tr><tr><td>标点断句</td><td>是否接近可读稿</td></tr><tr><td>中英混杂/术语</td><td>ChatArch、GitHub、模型名、服务名能不能识别</td></tr><tr><td>噪声/远场</td><td>会议室、扬声器外放、多人环境是否可靠</td></tr><tr><td>成本</td><td>每小时、并发、资源包/后付费是否可接受</td></tr><tr><td>接入复杂度</td><td>签名、SDK、错误码、日志是否好维护</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="推荐的第一轮执行顺序">推荐的第一轮执行顺序<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#%E6%8E%A8%E8%8D%90%E7%9A%84%E7%AC%AC%E4%B8%80%E8%BD%AE%E6%89%A7%E8%A1%8C%E9%A1%BA%E5%BA%8F" class="hash-link" aria-label="推荐的第一轮执行顺序的直接链接" title="推荐的第一轮执行顺序的直接链接" translate="no">​</a></h2>
<p>不要直接宣布“某一家最好”。没有拿你的真实音频跑过，任何效果排名都只是厂商宣传或经验印象。更稳的做法是：</p>
<ol>
<li class=""><strong>先开低成本 smoke</strong>：四家各跑 1–3 分钟同一段中文读稿。</li>
<li class=""><strong>先测效果，再谈价格</strong>：如果 transcript 不准，便宜也没意义。</li>
<li class=""><strong>把火山和讯飞放第一批实测</strong>：一个是中文 ASR 老牌强项，一个是大模型流式能力值得看。</li>
<li class=""><strong>阿里云作为工程化路线对照</strong>：如果团队已经有阿里云/百炼账号，集成和治理会顺。</li>
<li class=""><strong>腾讯云作为控制台/计费体系对照</strong>：如果已有腾讯云账号和费用体系，它是自然候选。</li>
<li class=""><strong>最终只选 1 个主 provider + 1 个备 provider</strong>：产品层保留 adapter，不把前端写死在某一家协议上。</li>
</ol>
<p>最后的落地判断应该基于实测表，而不是品牌印象：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">同一段音频</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 四家同时或顺序识别</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 统一 transcript event</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 统一指标表</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 选主 provider + 备 provider</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="资料来源">资料来源<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration#%E8%B5%84%E6%96%99%E6%9D%A5%E6%BA%90" class="hash-link" aria-label="资料来源的直接链接" title="资料来源的直接链接" translate="no">​</a></h2>
<ul>
<li class="">科大讯飞：<a href="https://www.xfyun.cn/doc/asr/rtasr/API.html" target="_blank" rel="noopener noreferrer" class="">实时语音转写 API</a>、<a href="https://www.xfyun.cn/doc/asr/voicedictation/API.html" target="_blank" rel="noopener noreferrer" class="">语音听写（流式版）WebAPI</a>、<a href="https://www.xfyun.cn/services/rtasr" target="_blank" rel="noopener noreferrer" class="">实时语音转写服务页</a></li>
<li class="">阿里云：<a href="https://help.aliyun.com/zh/model-studio/paraformer-real-time-speech-recognition-api-reference/" target="_blank" rel="noopener noreferrer" class="">Paraformer 实时语音识别 API 参考</a>、<a href="https://help.aliyun.com/zh/model-studio/websocket-for-paraformer-real-time-service" target="_blank" rel="noopener noreferrer" class="">Paraformer WebSocket API</a>、<a href="https://help.aliyun.com/zh/model-studio/models" target="_blank" rel="noopener noreferrer" class="">模型大全功能规格与计费</a></li>
<li class="">火山引擎：<a href="https://docs.volcengine.com/docs/6561/1354869?lang=zh" target="_blank" rel="noopener noreferrer" class="">大模型流式语音识别 API</a>、<a href="https://docs.volcengine.com/docs/6561/1359370?lang=zh" target="_blank" rel="noopener noreferrer" class="">豆包语音计费说明</a></li>
<li class="">腾讯云：<a href="https://cloud.tencent.com/document/product/1093/48982" target="_blank" rel="noopener noreferrer" class="">实时语音识别（WebSocket）</a>、<a href="https://cloud.tencent.com/document/product/1093/35686" target="_blank" rel="noopener noreferrer" class="">语音识别计费概述</a></li>
</ul>]]></content>
        <category label="transcription" term="transcription"/>
        <category label="realtime" term="realtime"/>
        <category label="asr" term="asr"/>
        <category label="speech-to-text" term="speech-to-text"/>
        <category label="cloud" term="cloud"/>
        <category label="web" term="web"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[科大讯飞实时语音转写从注册到接入：一份可边实践边更新的教程]]></title>
        <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide</id>
        <link href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide"/>
        <updated>2026-08-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[从零开始走科大讯飞开放平台：注册登录、创建应用、开通实时语音转写大模型或标准版、领取免费包、拿 AppID/APIKey/APISecret、跑 Python SDK/WebSocket demo，并规划后端 relay 与网页实时字幕实践。]]></summary>
        <content type="html"><![CDATA[<p>这篇不是泛泛介绍 ASR，而是为了把一条很具体的实践路径跑通：<strong>第一次使用科大讯飞开放平台，从注册、创建应用、开通实时语音转写，到拿到密钥、跑 SDK/API demo，最后接到我们自己的网页实时字幕服务。</strong></p>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>同主题前情</div><div class="admonitionContent_BuS1"><ul>
<li class=""><a class="" href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration">实时语音转录怎么接：讯飞、阿里云、火山引擎、腾讯云四条路线</a>：先把国内四家实时 ASR 的 API 形态、服务器分工和 A/B 测试方式拆开。</li>
<li class=""><a class="" href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mobile-web-realtime-transcription-pwa">网页录音到 AI 纪要：先选成熟 ASR，再看现成产品</a>：解释为什么我们要把“实时 ASR”和“AI 纪要”分成两层。</li>
</ul></div></div>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>一句话结论</div><div class="admonitionContent_BuS1"><p>我们这条线先选 <strong>科大讯飞实时语音转写</strong>，优先试官方推荐的 <strong>实时语音转写大模型</strong>；如果开通或 SDK 路径不顺，再退回 <strong>实时语音转写标准版</strong>。短语音输入才看 <strong>语音听写（流式版）</strong>，它不是会议长转写主线。</p></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>状态说明</div><div class="admonitionContent_BuS1"><p>快照时间为 <strong>2026-08-10 CST</strong>。本文先提 PR，作为实践手册的第一版；后续注册、开通、SDK smoke test、网页 relay、真实口述测试都会继续在同一个 PR 或后续更新里补记录。本文不会保存或展示任何真实 AppID、APIKey、APISecret、Token、密码或代理信息。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="先分清三个容易混淆的产品">先分清三个容易混淆的产品<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E5%85%88%E5%88%86%E6%B8%85%E4%B8%89%E4%B8%AA%E5%AE%B9%E6%98%93%E6%B7%B7%E6%B7%86%E7%9A%84%E4%BA%A7%E5%93%81" class="hash-link" aria-label="先分清三个容易混淆的产品的直接链接" title="先分清三个容易混淆的产品的直接链接" translate="no">​</a></h2>
<p>科大讯飞这里名字比较接近，第一次看很容易混：</p>
<table><thead><tr><th>产品</th><th>适合什么</th><th>这次是否主线</th><th>入口</th></tr></thead><tbody><tr><td>实时语音转写大模型</td><td>长时间连续音频流，实时返回文字；官方文档说基于星火大模型预训练框架，服务核心是把不限时长语音识别为文字</td><td><strong>主线优先</strong></td><td><a href="https://www.xfyun.cn/services/rtasr" target="_blank" rel="noopener noreferrer" class="">产品页</a> / <a href="https://www.xfyun.cn/doc/spark/asr_llm/rtasr_llm.html" target="_blank" rel="noopener noreferrer" class="">API 文档</a> / <a href="https://console.xfyun.cn/services/new_rta" target="_blank" rel="noopener noreferrer" class="">控制台服务页</a></td></tr><tr><td>实时语音转写标准版</td><td>长时间连续音频流，WebSocket 长连接，16k/16bit/mono PCM，建议每 40ms 发送 1280 字节</td><td><strong>主线备选/对照</strong></td><td><a href="https://www.xfyun.cn/services/rtasr" target="_blank" rel="noopener noreferrer" class="">产品页</a> / <a href="https://www.xfyun.cn/doc/asr/rtasr/API.html" target="_blank" rel="noopener noreferrer" class="">API 文档</a></td></tr><tr><td>语音听写（流式版）</td><td>1 分钟内即时语音转文字，适合语音输入、短指令、搜索框</td><td>不是会议主线</td><td><a href="https://www.xfyun.cn/services/voicedictation" target="_blank" rel="noopener noreferrer" class="">产品页</a> / <a href="https://www.xfyun.cn/doc/asr/voicedictation/API.html" target="_blank" rel="noopener noreferrer" class="">API 文档</a> / <a href="https://console.xfyun.cn/services/iat" target="_blank" rel="noopener noreferrer" class="">控制台服务页</a></td></tr></tbody></table>
<p>所以这次实践的判断很简单：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">会议 / 长口述 / 实时字幕</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 实时语音转写大模型</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 不顺则实时语音转写标准版</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">短语音输入 / 60 秒以内 dictation</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 语音听写（流式版）</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="费用先怎么看">费用先怎么看<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E8%B4%B9%E7%94%A8%E5%85%88%E6%80%8E%E4%B9%88%E7%9C%8B" class="hash-link" aria-label="费用先怎么看的直接链接" title="费用先怎么看的直接链接" translate="no">​</a></h2>
<p>科大讯飞这条不是按 LLM token 算，而是按语音服务自己的免费包、时长套餐、方言/语种授权来算。价格会变，最后以控制台购买页为准；下面只是 2026-08-10 从实时语音转写产品页抓到的大模型套餐快照。</p>
<table><thead><tr><th>套餐</th><th style="text-align:right">服务量</th><th>有效期</th><th style="text-align:right">页面价格</th><th style="text-align:right">折算单价</th></tr></thead><tbody><tr><td>免费包（个人）</td><td style="text-align:right">5 小时</td><td>1 年</td><td style="text-align:right">免费</td><td style="text-align:right">免费</td></tr><tr><td>免费包（企业）</td><td style="text-align:right">50 小时</td><td>1 年</td><td style="text-align:right">免费</td><td style="text-align:right">免费</td></tr><tr><td>套餐一</td><td style="text-align:right">40 小时</td><td>1 年</td><td style="text-align:right">¥198</td><td style="text-align:right">¥4.95 / 小时</td></tr><tr><td>套餐二</td><td style="text-align:right">1000 小时</td><td>1 年</td><td style="text-align:right">¥4000</td><td style="text-align:right">¥4.00 / 小时</td></tr><tr><td>套餐三</td><td style="text-align:right">5000 小时</td><td>1 年</td><td style="text-align:right">¥17500</td><td style="text-align:right">¥3.50 / 小时</td></tr><tr><td>套餐四</td><td style="text-align:right">20000 小时</td><td>1 年</td><td style="text-align:right">¥60000</td><td style="text-align:right">¥3.00 / 小时</td></tr><tr><td>套餐五</td><td style="text-align:right">100000 小时</td><td>1 年</td><td style="text-align:right">¥240000</td><td style="text-align:right">¥2.40 / 小时</td></tr><tr><td>套餐六</td><td style="text-align:right">300000 小时</td><td>1 年</td><td style="text-align:right">¥600000</td><td style="text-align:right">¥2.00 / 小时</td></tr></tbody></table>
<p>第一轮实践不建议直接买大套餐。更合理的是：</p>
<ol>
<li class="">先领取个人 5 小时免费包，或企业 50 小时免费包；</li>
<li class="">用 10–20 秒音频跑通鉴权和音频格式；</li>
<li class="">再用 3–5 分钟真实口述测实时性和中文效果；</li>
<li class="">如果效果确认，再看 40 小时套餐是否足够下一阶段测试；</li>
<li class="">方言、语种、翻译、声纹/角色能力可能有单独授权或额外费用，不要默认包含在基础包里。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="你先打开这些网页">你先打开这些网页<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E4%BD%A0%E5%85%88%E6%89%93%E5%BC%80%E8%BF%99%E4%BA%9B%E7%BD%91%E9%A1%B5" class="hash-link" aria-label="你先打开这些网页的直接链接" title="你先打开这些网页的直接链接" translate="no">​</a></h2>
<p>第一次操作可以按下面顺序开网页，不需要先写代码。</p>
<table><thead><tr><th>步骤</th><th>打开地址</th><th>你要做什么</th></tr></thead><tbody><tr><td>1</td><td><a href="https://www.xfyun.cn/" target="_blank" rel="noopener noreferrer" class="">https://www.xfyun.cn/</a></td><td>注册或登录讯飞开放平台账号。</td></tr><tr><td>2</td><td><a href="https://console.xfyun.cn/" target="_blank" rel="noopener noreferrer" class="">https://console.xfyun.cn/</a></td><td>进入控制台。后面创建应用、查看服务、拿密钥都在这里。</td></tr><tr><td>3</td><td><a href="https://www.xfyun.cn/services/rtasr" target="_blank" rel="noopener noreferrer" class="">https://www.xfyun.cn/services/rtasr</a></td><td>打开“实时语音转写”产品页，看大模型和标准版、免费包、购买入口。</td></tr><tr><td>4</td><td><a href="https://console.xfyun.cn/services/new_rta" target="_blank" rel="noopener noreferrer" class="">https://console.xfyun.cn/services/new_rta</a></td><td>进入“实时语音转写大模型”服务页，尝试领取免费包或开通服务。</td></tr><tr><td>5</td><td><a href="https://www.xfyun.cn/doc/spark/asr_llm/rtasr_llm.html" target="_blank" rel="noopener noreferrer" class="">https://www.xfyun.cn/doc/spark/asr_llm/rtasr_llm.html</a></td><td>看大模型版 API 文档，后端接入会按这个来。</td></tr><tr><td>6</td><td><a href="https://www.xfyun.cn/doc/asr/rtasr/API.html" target="_blank" rel="noopener noreferrer" class="">https://www.xfyun.cn/doc/asr/rtasr/API.html</a></td><td>看标准版 API 文档，作为备选/对照。</td></tr><tr><td>7</td><td><a href="https://github.com/iFLYTEK-OP/websdk-python" target="_blank" rel="noopener noreferrer" class="">https://github.com/iFLYTEK-OP/websdk-python</a></td><td>Python SDK 仓库，后续跑最小 demo 用。</td></tr><tr><td>8</td><td><a href="https://github.com/iFLYTEK-OP/websdk-python-demo" target="_blank" rel="noopener noreferrer" class="">https://github.com/iFLYTEK-OP/websdk-python-demo</a></td><td>Python SDK demo 仓库，里面有 <code>rtasr_test.py</code>。</td></tr></tbody></table>
<p>如果某个控制台页面要求登录、实名、企业认证或绑定手机号，正常按它要求操作即可；这里不需要把密码或验证码发给我。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="从注册到可调用人工操作流程">从注册到可调用：人工操作流程<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E4%BB%8E%E6%B3%A8%E5%86%8C%E5%88%B0%E5%8F%AF%E8%B0%83%E7%94%A8%E4%BA%BA%E5%B7%A5%E6%93%8D%E4%BD%9C%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="从注册到可调用：人工操作流程的直接链接" title="从注册到可调用：人工操作流程的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-注册--登录--实名">1. 注册 / 登录 / 实名<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#1-%E6%B3%A8%E5%86%8C--%E7%99%BB%E5%BD%95--%E5%AE%9E%E5%90%8D" class="hash-link" aria-label="1. 注册 / 登录 / 实名的直接链接" title="1. 注册 / 登录 / 实名的直接链接" translate="no">​</a></h3>
<ol>
<li class="">打开 <a href="https://www.xfyun.cn/%E3%80%82" target="_blank" rel="noopener noreferrer" class="">https://www.xfyun.cn/。</a></li>
<li class="">右上角登录或注册。</li>
<li class="">进入 <a href="https://console.xfyun.cn/%E3%80%82" target="_blank" rel="noopener noreferrer" class="">https://console.xfyun.cn/。</a></li>
<li class="">如果提示实名认证，按提示完成个人或企业认证。</li>
</ol>
<p>这一步完成后，只需要告诉我：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">已登录控制台 / 已完成实名 / 是否个人账号或企业账号</span><br></div></code></pre></div></div>
<p>不要发送密码、短信验证码或完整个人证件信息。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-创建应用">2. 创建应用<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#2-%E5%88%9B%E5%BB%BA%E5%BA%94%E7%94%A8" class="hash-link" aria-label="2. 创建应用的直接链接" title="2. 创建应用的直接链接" translate="no">​</a></h3>
<p>在控制台里找“我的应用”或“创建应用”。建议先创建一个专门用于这次实时转写测试的应用，例如：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">应用名称：realtime-asr-test</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">平台类型：WebAPI / 服务端调用</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">用途：实时语音转写测试</span><br></div></code></pre></div></div>
<p>创建后你会看到类似这些材料：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">AppID:      [REDACTED]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">APIKey:     [REDACTED]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">APISecret:  [REDACTED]  # 大模型/听写类服务可能需要</span><br></div></code></pre></div></div>
<p>注意：**不要把真实值贴到博客、群聊、截图或前端代码里。**如果后续要我帮你部署 demo，我们再用安全方式把它放到服务器环境变量。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-开通实时语音转写大模型">3. 开通实时语音转写大模型<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#3-%E5%BC%80%E9%80%9A%E5%AE%9E%E6%97%B6%E8%AF%AD%E9%9F%B3%E8%BD%AC%E5%86%99%E5%A4%A7%E6%A8%A1%E5%9E%8B" class="hash-link" aria-label="3. 开通实时语音转写大模型的直接链接" title="3. 开通实时语音转写大模型的直接链接" translate="no">​</a></h3>
<p>打开：</p>
<p><a href="https://console.xfyun.cn/services/new_rta" target="_blank" rel="noopener noreferrer" class="">https://console.xfyun.cn/services/new_rta</a></p>
<p>目标是确认三件事：</p>
<ol>
<li class="">服务是否已经开通；</li>
<li class="">是否能领取个人/企业免费包；</li>
<li class="">控制台里是否能看到对应应用的 AppID/APIKey/APISecret 或服务授权状态。</li>
</ol>
<p>如果这个页面没有权限，回到产品页：</p>
<p><a href="https://www.xfyun.cn/services/rtasr" target="_blank" rel="noopener noreferrer" class="">https://www.xfyun.cn/services/rtasr</a></p>
<p>产品页目前同时展示“实时语音转写大模型”和“实时语音转写标准版”，并提供免费包、购买和 SDK 文档入口。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-如大模型不顺再开通标准版">4. 如大模型不顺，再开通标准版<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#4-%E5%A6%82%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%B8%8D%E9%A1%BA%E5%86%8D%E5%BC%80%E9%80%9A%E6%A0%87%E5%87%86%E7%89%88" class="hash-link" aria-label="4. 如大模型不顺，再开通标准版的直接链接" title="4. 如大模型不顺，再开通标准版的直接链接" translate="no">​</a></h3>
<p>标准版文档入口：</p>
<p><a href="https://www.xfyun.cn/doc/asr/rtasr/API.html" target="_blank" rel="noopener noreferrer" class="">https://www.xfyun.cn/doc/asr/rtasr/API.html</a></p>
<p>标准版的关键点：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">WebSocket:  wss://rtasr.xfyun.cn/v1/ws?... </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">音频：      16k / 16bit / 单声道 / PCM</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">发包：      建议每 40ms 发送 1280 字节</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">鉴权：      appid + ts + signa</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">signa：     HmacSHA1(MD5(appid + ts), api_key) 后 Base64</span><br></div></code></pre></div></div>
<p>标准版只需要 <code>appid + apiKey</code> 就能生成 <code>signa</code>。大模型版文档则写到 <code>AppID、APIKey、APISecret</code>，并使用另一套 <code>signature</code> 参数。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-先不要把-ip-白名单打开得太死">5. 先不要把 IP 白名单打开得太死<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#5-%E5%85%88%E4%B8%8D%E8%A6%81%E6%8A%8A-ip-%E7%99%BD%E5%90%8D%E5%8D%95%E6%89%93%E5%BC%80%E5%BE%97%E5%A4%AA%E6%AD%BB" class="hash-link" aria-label="5. 先不要把 IP 白名单打开得太死的直接链接" title="5. 先不要把 IP 白名单打开得太死的直接链接" translate="no">​</a></h3>
<p>讯飞文档提到可以在控制台配置 IP 白名单。测试阶段建议：</p>
<ul>
<li class="">如果默认关闭白名单，先保持默认；</li>
<li class="">如果必须打开白名单，要填 <strong>公网出口 IP</strong>，不是局域网 IP；</li>
<li class="">真正上线后再把白名单收紧到服务端公网出口。</li>
</ul>
<p>我们后续如果部署在 <code>recall.cube</code>，但公网出口可能经过边缘/代理链路，需要实际测到出口 IP 后再填。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="sdk-路径先跑-python-demo">SDK 路径：先跑 Python demo<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#sdk-%E8%B7%AF%E5%BE%84%E5%85%88%E8%B7%91-python-demo" class="hash-link" aria-label="SDK 路径：先跑 Python demo的直接链接" title="SDK 路径：先跑 Python demo的直接链接" translate="no">​</a></h2>
<p>官方 Python SDK 仓库：</p>
<p><a href="https://github.com/iFLYTEK-OP/websdk-python" target="_blank" rel="noopener noreferrer" class="">https://github.com/iFLYTEK-OP/websdk-python</a></p>
<p>语音 SDK 包名是：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">pip install xfyunsdkspeech</span><br></div></code></pre></div></div>
<p>官方 demo 仓库：</p>
<p><a href="https://github.com/iFLYTEK-OP/websdk-python-demo" target="_blank" rel="noopener noreferrer" class="">https://github.com/iFLYTEK-OP/websdk-python-demo</a></p>
<p>demo README 里说明：</p>
<ul>
<li class="">获取能力使用的 <code>APPID</code>、<code>APISecret</code>、<code>APIKey</code> 后填写到 <code>.env</code>；</li>
<li class="">实时语音转写对应主类是 <code>xfyunsdkdemo/speech/rtasr_test.py</code>；</li>
<li class="">语音听写对应 <code>xfyunsdkdemo/speech/iat_test.py</code>。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="本地最小验证思路">本地最小验证思路<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E6%9C%AC%E5%9C%B0%E6%9C%80%E5%B0%8F%E9%AA%8C%E8%AF%81%E6%80%9D%E8%B7%AF" class="hash-link" aria-label="本地最小验证思路的直接链接" title="本地最小验证思路的直接链接" translate="no">​</a></h3>
<p>我们不把真实密钥写进命令或 Git 仓库。实际应该类似这样：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">python3 -m venv .venv</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">. .venv/bin/activate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">pip install xfyunsdkspeech python-dotenv</span><br></div></code></pre></div></div>
<p>然后准备一个本地 <code>.env</code>，只放在测试目录，不提交：</p>
<div class="language-dotenv codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dotenv codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">APP_ID=[REDACTED]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">API_KEY=[REDACTED]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">API_SECRET=[REDACTED]</span><br></div></code></pre></div></div>
<p>对于标准版 <code>RtasrClient</code>，SDK README 里示例形态是：</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> os</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> xfyunsdkspeech</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">rtasr_client </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> RtasrClient</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">client </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> RtasrClient</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    app_id</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">os</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">getenv</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"APP_ID"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    api_key</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">os</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">getenv</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"API_KEY"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">with</span><span class="token plain"> </span><span class="token builtin">open</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"sample-16k-16bit-mono.pcm"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"rb"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> f</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> chunk </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> client</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">stream</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">f</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">chunk</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>这一步的目的不是做最终产品，而是先验证：</p>
<ol>
<li class="">账号服务已经开通；</li>
<li class="">AppID/APIKey 能通过鉴权；</li>
<li class="">音频格式正确；</li>
<li class="">讯飞能返回识别结果；</li>
<li class="">错误码是否与权限、白名单、音频格式相关。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="音频样本要求">音频样本要求<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E9%9F%B3%E9%A2%91%E6%A0%B7%E6%9C%AC%E8%A6%81%E6%B1%82" class="hash-link" aria-label="音频样本要求的直接链接" title="音频样本要求的直接链接" translate="no">​</a></h3>
<p>标准版实时转写要求的是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">采样率：16k</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">位深：16bit</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">声道：单声道</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">格式：pcm_s16le / raw pcm</span><br></div></code></pre></div></div>
<p>如果手上是 wav/mp3/m4a，需要先转成 PCM。后续实践时可以用 <code>ffmpeg</code>：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">ffmpeg -i input.wav -ac 1 -ar 16000 -f s16le sample-16k-16bit-mono.pcm</span><br></div></code></pre></div></div>
<p>如果没有本地音频，我们也可以录一段 10–20 秒中文口述作为 smoke test。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="api-路径后端直接接-websocket">API 路径：后端直接接 WebSocket<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#api-%E8%B7%AF%E5%BE%84%E5%90%8E%E7%AB%AF%E7%9B%B4%E6%8E%A5%E6%8E%A5-websocket" class="hash-link" aria-label="API 路径：后端直接接 WebSocket的直接链接" title="API 路径：后端直接接 WebSocket的直接链接" translate="no">​</a></h2>
<p>SDK 适合第一步验证账号和密钥。真正做网页实时字幕时，我更建议后端直接接 WebSocket，因为要处理：</p>
<ul>
<li class="">浏览器音频格式转换；</li>
<li class="">密钥保护；</li>
<li class="">session 管理；</li>
<li class="">partial/final 统一事件；</li>
<li class="">重连、错误码、用量统计；</li>
<li class="">后续切换阿里云/火山/腾讯云 provider。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="大模型版-websocket-形态">大模型版 WebSocket 形态<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E5%A4%A7%E6%A8%A1%E5%9E%8B%E7%89%88-websocket-%E5%BD%A2%E6%80%81" class="hash-link" aria-label="大模型版 WebSocket 形态的直接链接" title="大模型版 WebSocket 形态的直接链接" translate="no">​</a></h3>
<p>大模型版 API 文档给出的请求地址是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">wss://office-api-ast-dx.iflyaisol.com/ast/communicate/v1?{请求参数}</span><br></div></code></pre></div></div>
<p>关键点：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">鉴权参数：AppID / APIKey / APISecret 派生 signature</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">音频：    16k / 16bit / 单声道</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">发送：    建议每 40ms 发送 1280 字节</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">超时：    音频发送间隔超过 15 秒会被服务端断开</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="标准版-websocket-形态">标准版 WebSocket 形态<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E6%A0%87%E5%87%86%E7%89%88-websocket-%E5%BD%A2%E6%80%81" class="hash-link" aria-label="标准版 WebSocket 形态的直接链接" title="标准版 WebSocket 形态的直接链接" translate="no">​</a></h3>
<p>标准版请求地址是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">wss://rtasr.xfyun.cn/v1/ws?appid=...&amp;ts=...&amp;signa=...</span><br></div></code></pre></div></div>
<p>签名逻辑可以理解成：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">baseString = MD5(appid + ts)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">signa = Base64(HmacSHA1(baseString, apiKey))</span><br></div></code></pre></div></div>
<p>注意：这里的 <code>apiKey</code> 是接口密钥，不能放到浏览器里。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="我们自己的产品架构">我们自己的产品架构<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E6%88%91%E4%BB%AC%E8%87%AA%E5%B7%B1%E7%9A%84%E4%BA%A7%E5%93%81%E6%9E%B6%E6%9E%84" class="hash-link" aria-label="我们自己的产品架构的直接链接" title="我们自己的产品架构的直接链接" translate="no">​</a></h2>
<p>最终我们不应该让浏览器直接连讯飞。应该是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">浏览器麦克风</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 我们自己的 WebSocket：wss://realtime-asr.public.wzhecnu.cn/ws</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 后端读取环境变量里的讯飞密钥</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 后端连接讯飞 WebSocket</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 讯飞返回识别结果</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 后端统一成 transcript.partial / transcript.final</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 浏览器实时显示字幕</span><br></div></code></pre></div></div>
<p>后端环境变量形态大概是：</p>
<div class="language-dotenv codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dotenv codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">ASR_PROVIDER=iflytek</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">IFLYTEK_APP_ID=[REDACTED]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">IFLYTEK_API_KEY=[REDACTED]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">IFLYTEK_API_SECRET=[REDACTED]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">IFLYTEK_PRODUCT=rtasr_llm</span><br></div></code></pre></div></div>
<p>前端只知道：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">POST /api/asr/session</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">WebSocket /api/asr/session/{id}/stream</span><br></div></code></pre></div></div>
<p>它不接触任何讯飞密钥。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="实践分工你点网页我写代码">实践分工：你点网页，我写代码<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E5%AE%9E%E8%B7%B5%E5%88%86%E5%B7%A5%E4%BD%A0%E7%82%B9%E7%BD%91%E9%A1%B5%E6%88%91%E5%86%99%E4%BB%A3%E7%A0%81" class="hash-link" aria-label="实践分工：你点网页，我写代码的直接链接" title="实践分工：你点网页，我写代码的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="你先做">你先做<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E4%BD%A0%E5%85%88%E5%81%9A" class="hash-link" aria-label="你先做的直接链接" title="你先做的直接链接" translate="no">​</a></h3>
<ol>
<li class="">注册/登录：<a href="https://www.xfyun.cn/" target="_blank" rel="noopener noreferrer" class="">https://www.xfyun.cn/</a></li>
<li class="">进入控制台：<a href="https://console.xfyun.cn/" target="_blank" rel="noopener noreferrer" class="">https://console.xfyun.cn/</a></li>
<li class="">创建一个测试应用。</li>
<li class="">打开实时语音转写产品页：<a href="https://www.xfyun.cn/services/rtasr" target="_blank" rel="noopener noreferrer" class="">https://www.xfyun.cn/services/rtasr</a></li>
<li class="">优先进入大模型服务页：<a href="https://console.xfyun.cn/services/new_rta" target="_blank" rel="noopener noreferrer" class="">https://console.xfyun.cn/services/new_rta</a></li>
<li class="">领取免费包或完成服务开通。</li>
<li class="">确认控制台里能看到应用对应的 AppID/APIKey/APISecret。</li>
<li class="">不要把密钥发到群聊；只告诉我：</li>
</ol>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">- 账号类型：个人/企业</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- 实名是否完成：是/否</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- 已创建应用：是/否</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- 已开通服务：大模型/标准版/听写/未开通</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- 是否领取免费包：是/否</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- 控制台是否显示 AppID/APIKey/APISecret：是/否</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- 是否开启了 IP 白名单：是/否</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="我来做">我来做<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E6%88%91%E6%9D%A5%E5%81%9A" class="hash-link" aria-label="我来做的直接链接" title="我来做的直接链接" translate="no">​</a></h3>
<ol>
<li class="">写一个最小 Python smoke 脚本，只读取环境变量，不写死密钥。</li>
<li class="">准备 16k/16bit/mono PCM 测试音频。</li>
<li class="">跑 SDK 或 WebSocket demo，记录错误码和返回样例。</li>
<li class="">如果 SDK 不顺，直接按官方 WebSocket 协议写最小客户端。</li>
<li class="">成功后写后端 relay。</li>
<li class="">接一个极简网页：开始录音、实时上屏、停止、保存 transcript。</li>
<li class="">最后再接 AI 纪要，不和 ASR 第一阶段混在一起。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="第一轮验收标准">第一轮验收标准<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E7%AC%AC%E4%B8%80%E8%BD%AE%E9%AA%8C%E6%94%B6%E6%A0%87%E5%87%86" class="hash-link" aria-label="第一轮验收标准的直接链接" title="第一轮验收标准的直接链接" translate="no">​</a></h2>
<p>第一轮不要追求完整产品，只要证明“讯飞这条线能跑通”：</p>
<table><thead><tr><th>验收项</th><th>通过标准</th></tr></thead><tbody><tr><td>服务开通</td><td>控制台显示实时语音转写服务可用</td></tr><tr><td>鉴权</td><td>SDK/API 不再返回无权限、签名错误、白名单错误</td></tr><tr><td>音频格式</td><td>10–20 秒中文 PCM 能被识别</td></tr><tr><td>实时性</td><td>发送音频期间持续返回结果，而不是结束后一次性返回</td></tr><tr><td>中文效果</td><td>能识别普通话口述、项目名、少量中英混杂</td></tr><tr><td>错误记录</td><td>所有错误码都能对应到权限、签名、白名单、格式或超时</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="常见坑">常见坑<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E5%B8%B8%E8%A7%81%E5%9D%91" class="hash-link" aria-label="常见坑的直接链接" title="常见坑的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="坑-1选错产品">坑 1：选错产品<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E5%9D%91-1%E9%80%89%E9%94%99%E4%BA%A7%E5%93%81" class="hash-link" aria-label="坑 1：选错产品的直接链接" title="坑 1：选错产品的直接链接" translate="no">​</a></h3>
<p>如果做会议/直播/长口述，别先选语音听写。语音听写流式版文档写的是 1 分钟内即时语音转文字，最长 60 秒；会议长转写应该看实时语音转写。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="坑-2浏览器直接放密钥">坑 2：浏览器直接放密钥<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E5%9D%91-2%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9B%B4%E6%8E%A5%E6%94%BE%E5%AF%86%E9%92%A5" class="hash-link" aria-label="坑 2：浏览器直接放密钥的直接链接" title="坑 2：浏览器直接放密钥的直接链接" translate="no">​</a></h3>
<p>不要把 AppID/APIKey/APISecret 放进前端 JS。即使能跑，也等于公开密钥。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="坑-3音频格式不对">坑 3：音频格式不对<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E5%9D%91-3%E9%9F%B3%E9%A2%91%E6%A0%BC%E5%BC%8F%E4%B8%8D%E5%AF%B9" class="hash-link" aria-label="坑 3：音频格式不对的直接链接" title="坑 3：音频格式不对的直接链接" translate="no">​</a></h3>
<p>浏览器常见拿到的是 WebM/Opus 或 Float32 PCM，不一定是讯飞要的 16k/16bit/mono PCM。我们要在浏览器端或后端做转换。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="坑-4发包太快或太慢">坑 4：发包太快或太慢<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E5%9D%91-4%E5%8F%91%E5%8C%85%E5%A4%AA%E5%BF%AB%E6%88%96%E5%A4%AA%E6%85%A2" class="hash-link" aria-label="坑 4：发包太快或太慢的直接链接" title="坑 4：发包太快或太慢的直接链接" translate="no">​</a></h3>
<p>标准版和大模型版文档都强调建议每 40ms 发送 1280 字节；发送过快可能出错，超过 15 秒不发音频服务端会断开。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="坑-5ip-白名单填了内网-ip">坑 5：IP 白名单填了内网 IP<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E5%9D%91-5ip-%E7%99%BD%E5%90%8D%E5%8D%95%E5%A1%AB%E4%BA%86%E5%86%85%E7%BD%91-ip" class="hash-link" aria-label="坑 5：IP 白名单填了内网 IP的直接链接" title="坑 5：IP 白名单填了内网 IP的直接链接" translate="no">​</a></h3>
<p>如果打开白名单，要填服务端公网出口 IP，不是 <code>192.168.x.x</code>、<code>10.x.x.x</code> 或本机局域网地址。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="坑-6把免费包当无限额度">坑 6：把免费包当无限额度<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E5%9D%91-6%E6%8A%8A%E5%85%8D%E8%B4%B9%E5%8C%85%E5%BD%93%E6%97%A0%E9%99%90%E9%A2%9D%E5%BA%A6" class="hash-link" aria-label="坑 6：把免费包当无限额度的直接链接" title="坑 6：把免费包当无限额度的直接链接" translate="no">​</a></h3>
<p>免费包只适合 smoke test。后续需要记录音频时长、并发、错误率和费用，避免长时间测试产生意外账单。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="后续更新计划">后续更新计划<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E5%90%8E%E7%BB%AD%E6%9B%B4%E6%96%B0%E8%AE%A1%E5%88%92" class="hash-link" aria-label="后续更新计划的直接链接" title="后续更新计划的直接链接" translate="no">​</a></h2>
<p>这篇先作为 PR 版总教程。实践推进后，继续补：</p>
<ol>
<li class="">控制台实际截图对应的步骤说明；</li>
<li class="">Python SDK smoke test 命令与结果；</li>
<li class="">WebSocket 直接调用最小客户端；</li>
<li class="">网页实时字幕 relay 架构；</li>
<li class="">真实 1 分钟中文口述测试结果；</li>
<li class="">与阿里云/火山引擎同音频 A/B 对比；</li>
<li class="">最终是否采用讯飞作为第一版 provider。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="参考入口">参考入口<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide#%E5%8F%82%E8%80%83%E5%85%A5%E5%8F%A3" class="hash-link" aria-label="参考入口的直接链接" title="参考入口的直接链接" translate="no">​</a></h2>
<ul>
<li class="">科大讯飞开放平台：<a href="https://www.xfyun.cn/" target="_blank" rel="noopener noreferrer" class="">https://www.xfyun.cn/</a></li>
<li class="">科大讯飞控制台：<a href="https://console.xfyun.cn/" target="_blank" rel="noopener noreferrer" class="">https://console.xfyun.cn/</a></li>
<li class="">实时语音转写产品页：<a href="https://www.xfyun.cn/services/rtasr" target="_blank" rel="noopener noreferrer" class="">https://www.xfyun.cn/services/rtasr</a></li>
<li class="">实时语音转写大模型 API：<a href="https://www.xfyun.cn/doc/spark/asr_llm/rtasr_llm.html" target="_blank" rel="noopener noreferrer" class="">https://www.xfyun.cn/doc/spark/asr_llm/rtasr_llm.html</a></li>
<li class="">实时语音转写标准版 API：<a href="https://www.xfyun.cn/doc/asr/rtasr/API.html" target="_blank" rel="noopener noreferrer" class="">https://www.xfyun.cn/doc/asr/rtasr/API.html</a></li>
<li class="">语音听写产品页：<a href="https://www.xfyun.cn/services/voicedictation" target="_blank" rel="noopener noreferrer" class="">https://www.xfyun.cn/services/voicedictation</a></li>
<li class="">语音听写流式版 API：<a href="https://www.xfyun.cn/doc/asr/voicedictation/API.html" target="_blank" rel="noopener noreferrer" class="">https://www.xfyun.cn/doc/asr/voicedictation/API.html</a></li>
<li class="">Python SDK：<a href="https://github.com/iFLYTEK-OP/websdk-python" target="_blank" rel="noopener noreferrer" class="">https://github.com/iFLYTEK-OP/websdk-python</a></li>
<li class="">Python SDK demo：<a href="https://github.com/iFLYTEK-OP/websdk-python-demo" target="_blank" rel="noopener noreferrer" class="">https://github.com/iFLYTEK-OP/websdk-python-demo</a></li>
<li class="">Java SDK：<a href="https://github.com/iFLYTEK-OP/websdk-java" target="_blank" rel="noopener noreferrer" class="">https://github.com/iFLYTEK-OP/websdk-java</a></li>
</ul>]]></content>
        <category label="iflytek" term="iflytek"/>
        <category label="xfyun" term="xfyun"/>
        <category label="transcription" term="transcription"/>
        <category label="realtime" term="realtime"/>
        <category label="asr" term="asr"/>
        <category label="speech-to-text" term="speech-to-text"/>
        <category label="tutorial" term="tutorial"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[火山引擎实时语音识别从注册到接入：豆包语音 ASR 实践教程]]></title>
        <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide</id>
        <link href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide"/>
        <updated>2026-08-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[从零开始走火山引擎豆包语音 / 大模型流式语音识别：注册登录、开通服务、获取新版 X-Api-Key 或旧版 AppKey/AccessToken、选择 Resource ID、理解 bigmodel / bigmodel_nostream / bigmodel_async 三种 WebSocket、看资源包与后付费价格，并规划后端 relay 与网页实时字幕实践。]]></summary>
        <content type="html"><![CDATA[<p>这篇继续同一组实践教程：<strong>第一次使用火山引擎豆包语音 / 大模型流式语音识别，怎么注册开通，怎么拿 Key，怎么选资源 ID，怎么理解 WebSocket 二进制协议，怎么从 smoke test 走到我们自己的网页实时字幕服务。</strong></p>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>同主题前情</div><div class="admonitionContent_BuS1"><ul>
<li class=""><a class="" href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/domestic-realtime-asr-provider-integration">实时语音转录怎么接：讯飞、阿里云、火山引擎、腾讯云四条路线</a>：先把四家国内实时 ASR 的 API 形态、服务器分工和 A/B 测试方式拆开。</li>
<li class=""><a class="" href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/iflytek-realtime-asr-practice-guide">科大讯飞实时语音转写从注册到接入</a>：同系列第一篇，偏讯飞控制台、SDK、WebSocket relay。</li>
<li class=""><a class="" href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/aliyun-realtime-asr-practice-guide">阿里云实时语音识别从注册到接入</a>：同系列第二篇，偏百炼 / DashScope / Qwen-Audio / Fun-ASR / Paraformer。</li>
</ul></div></div>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>一句话结论</div><div class="admonitionContent_BuS1"><p>火山这条线最值得看的不是老“语音识别 API”本身，而是 <strong>豆包流式语音识别模型 2.0 / 大模型流式语音识别</strong>。如果目标是边说边出字，第一轮优先试 <strong>双向流式优化版 <code>bigmodel_async</code></strong>；如果想在快和准之间折中，可以开启二遍识别，让实时临时结果先上屏，再用更稳的分句 final 覆盖。</p></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>状态说明</div><div class="admonitionContent_BuS1"><p>快照时间为 <strong>2026-08-10 CST</strong>。本文基于火山引擎官方大模型流式语音识别 API、豆包语音计费说明、控制台 FAQ 做静态实践手册；没有使用任何真实账号密钥，也没有发起付费 ASR 调用。真实 App Key、API Key、Access Token、Secret Key、Resource ID、密码、代理凭据一律写成 <code>[REDACTED]</code>，不能进入前端、博客、截图或 Git 仓库。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="先分清三条实时入口">先分清三条实时入口<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#%E5%85%88%E5%88%86%E6%B8%85%E4%B8%89%E6%9D%A1%E5%AE%9E%E6%97%B6%E5%85%A5%E5%8F%A3" class="hash-link" aria-label="先分清三条实时入口的直接链接" title="先分清三条实时入口的直接链接" translate="no">​</a></h2>
<p>火山的大模型流式 ASR 文档里列了三个 WebSocket 入口：</p>
<table><thead><tr><th>入口</th><th>URL</th><th>适合什么</th><th>第一轮建议</th></tr></thead><tbody><tr><td>双向流式模式</td><td><code>wss://openspeech.bytedance.com/api/v3/sauc/bigmodel</code></td><td>每输入一个包返回一个包，尽快返回识别到的字符，速度较快</td><td>可测</td></tr><tr><td>流式输入模式</td><td><code>wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_nostream</code></td><td>发送超过 15 秒音频或最后一包后返回结果，准确率更高但实时性弱</td><td>准确率对照</td></tr><tr><td>双向流式优化版</td><td><code>wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_async</code></td><td>结果有变化时才返回新包，官方文档更推荐，性能更优</td><td><strong>优先试</strong></td></tr></tbody></table>
<p>如果我们做会议实时字幕，第一轮建议：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">低延迟实时上屏</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; bigmodel_async</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 开 enable_nonstream 做二遍识别</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">只看最终准确率对照</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; bigmodel_nostream</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">旧链路兼容</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; bigmodel</span><br></div></code></pre></div></div>
<p>火山文档还明确提醒：单包音频建议 <strong>100–200ms</strong>，发包间隔建议 <strong>100–200ms</strong>；双向流式模式推荐 <strong>200ms</strong> 分包。不要照搬讯飞的 40ms，也不要把浏览器原始 WebM/Opus 不处理就丢过去。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="费用先怎么看">费用先怎么看<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#%E8%B4%B9%E7%94%A8%E5%85%88%E6%80%8E%E4%B9%88%E7%9C%8B" class="hash-link" aria-label="费用先怎么看的直接链接" title="费用先怎么看的直接链接" translate="no">​</a></h2>
<p>价格会变，必须以控制台和官方计费页为准。下面是 2026-08-10 从火山“豆包语音计费说明”抓到的快照，只用于第一轮预算判断。</p>
<table><thead><tr><th>商品 / 能力</th><th style="text-align:right">资源包示例</th><th style="text-align:right">资源包折算</th><th style="text-align:right">后付费单价</th></tr></thead><tbody><tr><td>豆包流式语音识别模型 2.0</td><td style="text-align:right">30 小时 ¥28；1000 小时 ¥900；10000 小时 ¥8800</td><td style="text-align:right">约 ¥0.93 / 小时到 ¥0.88 / 小时</td><td style="text-align:right">¥1 / 小时</td></tr><tr><td>大模型流式语音识别</td><td style="text-align:right">30 小时 ¥132；1000 小时 ¥4000；10000 小时 ¥32000</td><td style="text-align:right">约 ¥4.4 / 小时到 ¥3.2 / 小时</td><td style="text-align:right">¥4.5 / 小时</td></tr><tr><td>豆包录音文件识别模型 2.0</td><td style="text-align:right">30 小时 ¥23；1000 小时 ¥750</td><td style="text-align:right">约 ¥0.77 / 小时到 ¥0.75 / 小时</td><td style="text-align:right">¥0.8 / 小时</td></tr><tr><td>大模型录音文件识别（标准版）</td><td style="text-align:right">30 小时 ¥66；1000 小时 ¥2000</td><td style="text-align:right">约 ¥2.2 / 小时到 ¥2 / 小时</td><td style="text-align:right">¥2.3 / 小时</td></tr><tr><td>大模型录音文件识别（极速版）</td><td style="text-align:right">30 小时 ¥132；1000 小时 ¥4300</td><td style="text-align:right">约 ¥4.4 / 小时到 ¥4.3 / 小时</td><td style="text-align:right">¥4.5 / 小时</td></tr></tbody></table>
<p>计费说明里还写到两个非常重要的点：</p>
<ol>
<li class="">按时长计费时，会累加每次调用的语音时长，精确到毫秒，最终折算为小时；</li>
<li class="">语音识别相关能力按音频时长计费，双声道也是按单声道时长口径计费。</li>
</ol>
<p>第一轮建议：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">先不要直接买大包</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 先确认是否有免费额度 / 试用</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 再买最小 30 小时包或保持后付费但做成本保护</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 每次测试记录音频秒数、Resource ID、X-Tt-Logid、账单归属</span><br></div></code></pre></div></div>
<p>另外，火山文档还列出并发包：豆包流式语音识别模型 2.0 默认并发 / 超出并发价格和大模型流式语音识别不同。第一版 smoke test 不用先买并发包；真要多人同时测，再看并发。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="你先打开这些网页">你先打开这些网页<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#%E4%BD%A0%E5%85%88%E6%89%93%E5%BC%80%E8%BF%99%E4%BA%9B%E7%BD%91%E9%A1%B5" class="hash-link" aria-label="你先打开这些网页的直接链接" title="你先打开这些网页的直接链接" translate="no">​</a></h2>
<p>第一次操作可以按下面顺序开网页，不需要先写代码。</p>
<table><thead><tr><th>步骤</th><th>打开地址</th><th>你要做什么</th></tr></thead><tbody><tr><td>1</td><td><a href="https://www.volcengine.com/" target="_blank" rel="noopener noreferrer" class="">https://www.volcengine.com/</a></td><td>注册或登录火山引擎账号。</td></tr><tr><td>2</td><td><a href="https://www.volcengine.com/product/doubao-speech" target="_blank" rel="noopener noreferrer" class="">https://www.volcengine.com/product/doubao-speech</a></td><td>打开豆包语音产品页，确认产品入口。</td></tr><tr><td>3</td><td><a href="https://console.volcengine.com/speech/new/setting/apikeys?projectName=default" target="_blank" rel="noopener noreferrer" class="">https://console.volcengine.com/speech/new/setting/apikeys?projectName=default</a></td><td>新版控制台 API Key 页面，登录后查看 / 创建 <code>X-Api-Key</code>。</td></tr><tr><td>4</td><td><a href="https://docs.volcengine.com/docs/6561/1354869?lang=zh" target="_blank" rel="noopener noreferrer" class="">https://docs.volcengine.com/docs/6561/1354869?lang=zh</a></td><td>大模型流式语音识别 API 文档，重点看三种 WebSocket、鉴权、二进制协议、参数。</td></tr><tr><td>5</td><td><a href="https://docs.volcengine.com/docs/6561/1359370?lang=zh" target="_blank" rel="noopener noreferrer" class="">https://docs.volcengine.com/docs/6561/1359370?lang=zh</a></td><td>豆包语音计费说明，看资源包、后付费、并发包。</td></tr><tr><td>6</td><td><a href="https://www.volcengine.com/docs/6561/196768" target="_blank" rel="noopener noreferrer" class="">https://www.volcengine.com/docs/6561/196768</a></td><td>控制台 FAQ，看旧版 appid / cluster / token / authorization_type / secret_key 在哪里查。</td></tr><tr><td>7</td><td><a href="https://console.volcengine.com/speech/monitor" target="_blank" rel="noopener noreferrer" class="">https://console.volcengine.com/speech/monitor</a></td><td>监控统计 / 资源包使用情况，后续做成本读回。</td></tr><tr><td>8</td><td><a href="https://console.volcengine.com/finance/bill/detail/" target="_blank" rel="noopener noreferrer" class="">https://console.volcengine.com/finance/bill/detail/</a></td><td>费用中心账单详情，确认是否欠费、后付费扣费、资源包抵扣。</td></tr></tbody></table>
<p>如果你是子账号，火山 FAQ 明确说：主账号没给对应产品控制台权限时，子账号无法直接访问语音技术控制台，需要主账号在访问控制里授权语音技术系统策略。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="从注册到可调用人工操作流程">从注册到可调用：人工操作流程<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#%E4%BB%8E%E6%B3%A8%E5%86%8C%E5%88%B0%E5%8F%AF%E8%B0%83%E7%94%A8%E4%BA%BA%E5%B7%A5%E6%93%8D%E4%BD%9C%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="从注册到可调用：人工操作流程的直接链接" title="从注册到可调用：人工操作流程的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-注册--登录--实名">1. 注册 / 登录 / 实名<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#1-%E6%B3%A8%E5%86%8C--%E7%99%BB%E5%BD%95--%E5%AE%9E%E5%90%8D" class="hash-link" aria-label="1. 注册 / 登录 / 实名的直接链接" title="1. 注册 / 登录 / 实名的直接链接" translate="no">​</a></h3>
<ol>
<li class="">打开 <a href="https://www.volcengine.com/%E3%80%82" target="_blank" rel="noopener noreferrer" class="">https://www.volcengine.com/。</a></li>
<li class="">登录或注册火山引擎账号。</li>
<li class="">如果提示实名、企业认证、协议确认或付费方式确认，按页面完成。</li>
<li class="">进入豆包语音 / 语音技术控制台。</li>
</ol>
<p>完成后只需要告诉我：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">已登录火山 / 已进入语音控制台 / 是否完成实名 / 是否主账号或子账号</span><br></div></code></pre></div></div>
<p>不要发送密码、验证码、证件、完整账单截图。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-开通豆包语音--大模型流式语音识别">2. 开通豆包语音 / 大模型流式语音识别<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#2-%E5%BC%80%E9%80%9A%E8%B1%86%E5%8C%85%E8%AF%AD%E9%9F%B3--%E5%A4%A7%E6%A8%A1%E5%9E%8B%E6%B5%81%E5%BC%8F%E8%AF%AD%E9%9F%B3%E8%AF%86%E5%88%AB" class="hash-link" aria-label="2. 开通豆包语音 / 大模型流式语音识别的直接链接" title="2. 开通豆包语音 / 大模型流式语音识别的直接链接" translate="no">​</a></h3>
<p>目标是确认这些状态：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">服务：豆包流式语音识别模型 2.0 / 大模型流式语音识别</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">计费：免费额度 / 资源包 / 后付费</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">并发：默认并发是否够第一轮测试</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">项目：default 或 realtime-asr-test</span><br></div></code></pre></div></div>
<p>如果你只是第一轮效果测试，不建议一开始买大资源包。可以先确认有没有试用 / 免费额度；如果必须购买，优先看最小 30 小时包。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-获取-key新版和旧版不一样">3. 获取 Key：新版和旧版不一样<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#3-%E8%8E%B7%E5%8F%96-key%E6%96%B0%E7%89%88%E5%92%8C%E6%97%A7%E7%89%88%E4%B8%8D%E4%B8%80%E6%A0%B7" class="hash-link" aria-label="3. 获取 Key：新版和旧版不一样的直接链接" title="3. 获取 Key：新版和旧版不一样的直接链接" translate="no">​</a></h3>
<p>火山文档把鉴权分成新版控制台和旧版控制台：</p>
<table><thead><tr><th>控制台版本</th><th>需要什么</th><th>说明</th></tr></thead><tbody><tr><td>新版控制台</td><td><code>X-Api-Key</code></td><td>官方文档写到新版控制台只需要 <code>X-Api-Key</code>，配合 Resource ID、Request ID、Sequence 走 WebSocket header</td></tr><tr><td>旧版控制台</td><td><code>X-Api-App-Key</code> + <code>X-Api-Access-Key</code></td><td>文档解释为 App ID / Access Token 等旧参数；还会涉及 cluster、authorization_type、secret_key 等控制台参数</td></tr></tbody></table>
<p>真实值只放服务器环境变量：</p>
<div class="language-dotenv codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dotenv codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">VOLCENGINE_SPEECH_API_KEY=[REDACTED]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">VOLCENGINE_SPEECH_APP_KEY=[REDACTED]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">VOLCENGINE_SPEECH_ACCESS_KEY=[REDACTED]</span><br></div></code></pre></div></div>
<p>浏览器端不能看到这些值。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-选择-resource-id">4. 选择 Resource ID<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#4-%E9%80%89%E6%8B%A9-resource-id" class="hash-link" aria-label="4. 选择 Resource ID的直接链接" title="4. 选择 Resource ID的直接链接" translate="no">​</a></h3>
<p>API 文档里把资源 ID 写得很明确，代表你调用哪种服务和计费模式：</p>
<table><thead><tr><th>能力</th><th>小时版 Resource ID</th><th>并发版 Resource ID</th></tr></thead><tbody><tr><td>豆包流式语音识别模型 1.0</td><td><code>volc.bigasr.sauc.duration</code></td><td><code>volc.bigasr.sauc.concurrent</code></td></tr><tr><td>豆包流式语音识别模型 2.0</td><td><code>volc.seedasr.sauc.duration</code></td><td><code>volc.seedasr.sauc.concurrent</code></td></tr></tbody></table>
<p>如果第一轮只是按时长小流量测试，通常从小时版开始：</p>
<div class="language-dotenv codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dotenv codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">VOLCENGINE_ASR_RESOURCE_ID=volc.seedasr.sauc.duration</span><br></div></code></pre></div></div>
<p>不要把 resource id 和 API key 混为一谈：Resource ID 不是密钥，但它决定调用哪条产品 / 计费线，仍然应当放在服务端配置里统一管理。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-请求头最小形态">5. 请求头最小形态<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#5-%E8%AF%B7%E6%B1%82%E5%A4%B4%E6%9C%80%E5%B0%8F%E5%BD%A2%E6%80%81" class="hash-link" aria-label="5. 请求头最小形态的直接链接" title="5. 请求头最小形态的直接链接" translate="no">​</a></h3>
<p>新版控制台 WebSocket 握手 header 大概是：</p>
<div class="language-http codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-http codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">X-Api-Key: [REDACTED]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">X-Api-Resource-Id: volc.seedasr.sauc.duration</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">X-Api-Request-Id: &lt;uuid&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">X-Api-Sequence: -1</span><br></div></code></pre></div></div>
<p>旧版控制台则是：</p>
<div class="language-http codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-http codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">X-Api-App-Key: [REDACTED]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">X-Api-Access-Key: [REDACTED]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">X-Api-Resource-Id: volc.seedasr.sauc.duration</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">X-Api-Request-Id: &lt;uuid&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">X-Api-Sequence: -1</span><br></div></code></pre></div></div>
<p>WebSocket 握手成功后，服务端会返回 <code>X-Tt-Logid</code>。这个值不是密钥，应该记录到日志里，方便排错。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="api-路径火山不是简单-json-websocket">API 路径：火山不是简单 JSON WebSocket<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#api-%E8%B7%AF%E5%BE%84%E7%81%AB%E5%B1%B1%E4%B8%8D%E6%98%AF%E7%AE%80%E5%8D%95-json-websocket" class="hash-link" aria-label="API 路径：火山不是简单 JSON WebSocket的直接链接" title="API 路径：火山不是简单 JSON WebSocket的直接链接" translate="no">​</a></h2>
<p>火山这条比阿里 / 讯飞更工程化一点：WebSocket payload 是二进制协议，不是简单地 <code>send(JSON.stringify(...))</code> 加音频就完事。</p>
<p>官方协议说明每个 frame payload 包含：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">header</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">payload size</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">payload</span><br></div></code></pre></div></div>
<p>消息类型包括：</p>
<table><thead><tr><th>类型</th><th>含义</th></tr></thead><tbody><tr><td><code>0b0001</code></td><td>端上发送 full client request，包含请求参数</td></tr><tr><td><code>0b0010</code></td><td>端上发送 audio only request，包含音频数据</td></tr><tr><td><code>0b1001</code></td><td>服务端返回 full server response，包含识别结果</td></tr><tr><td><code>0b1111</code></td><td>服务端返回错误</td></tr></tbody></table>
<p>请求流程：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">建立 WebSocket</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 发送 full client request：音频格式、采样率、模型参数、热词 / 上下文等</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 持续发送 audio only request：每包约 100–200ms 音频</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 服务端持续返回识别结果 / 错误</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 最后一包使用负包标记</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 服务端返回最终结果</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 关闭连接</span><br></div></code></pre></div></div>
<p>这意味着第一版 relay 最好先写成后端 adapter，而不是让浏览器直接实现火山二进制协议。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="请求参数怎么选">请求参数怎么选<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#%E8%AF%B7%E6%B1%82%E5%8F%82%E6%95%B0%E6%80%8E%E4%B9%88%E9%80%89" class="hash-link" aria-label="请求参数怎么选的直接链接" title="请求参数怎么选的直接链接" translate="no">​</a></h2>
<p>第一轮会议实时字幕可以从这个配置开始：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"audio"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"format"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"wav"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"rate"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">16000</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"bits"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">16</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"channel"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"language"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"zh-CN"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"request"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"model_name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"bigmodel"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"enable_nonstream"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"enable_itn"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"enable_punc"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"enable_ddc"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"result_type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"full"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"end_window_size"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">800</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>参数含义：</p>
<table><thead><tr><th>参数</th><th>建议</th><th>说明</th></tr></thead><tbody><tr><td><code>format</code></td><td><code>pcm</code> 或 <code>wav</code></td><td>文档支持 pcm / wav / ogg / mp3；pcm/wav 内部必须是 pcm_s16le</td></tr><tr><td><code>rate</code></td><td><code>16000</code></td><td>文档写目前只支持 16000</td></tr><tr><td><code>channel</code></td><td><code>1</code></td><td>第一轮用单声道，减少变量</td></tr><tr><td><code>language</code></td><td><code>zh-CN</code> 或空</td><td>空值可用默认中英文及部分方言；指定语种只在部分模式支持</td></tr><tr><td><code>model_name</code></td><td><code>bigmodel</code></td><td>文档当前写目前只有 bigmodel</td></tr><tr><td><code>enable_nonstream</code></td><td><code>true</code></td><td>双向流式优化版可开启二遍识别，用快结果 + 准 final</td></tr><tr><td><code>enable_itn</code></td><td><code>true</code></td><td>把“一九七零年”转成“1970年”等书面格式</td></tr><tr><td><code>enable_punc</code></td><td><code>true</code></td><td>启用标点</td></tr><tr><td><code>enable_ddc</code></td><td>先 <code>false</code></td><td>语义顺滑可能会改写口语，先看原始效果</td></tr><tr><td><code>end_window_size</code></td><td><code>800</code></td><td>静音判停阈值；实时性要求高时可调小，但可能影响准确率</td></tr></tbody></table>
<p>后续可以再测：</p>
<ul>
<li class=""><code>enable_speaker_info</code>：说话人聚类分离；</li>
<li class=""><code>context</code>：热词 / 上下文；</li>
<li class=""><code>show_speech_rate</code> / <code>show_volume</code>：输出语速、音量；</li>
<li class=""><code>enable_lid</code>：语种检测；</li>
<li class=""><code>enable_emotion_detection</code>：情绪检测；</li>
<li class=""><code>enable_gender_detection</code>：性别检测。</li>
</ul>
<p>这些都不要第一轮全开。先把“稳定实时出字”跑通，再逐项打开。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="我们自己的产品架构">我们自己的产品架构<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#%E6%88%91%E4%BB%AC%E8%87%AA%E5%B7%B1%E7%9A%84%E4%BA%A7%E5%93%81%E6%9E%B6%E6%9E%84" class="hash-link" aria-label="我们自己的产品架构的直接链接" title="我们自己的产品架构的直接链接" translate="no">​</a></h2>
<p>最终还是不让浏览器直连火山。推荐：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">浏览器麦克风</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 我们自己的 WebSocket：wss://realtime-asr.public.wzhecnu.cn/ws</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 后端读取 VOLCENGINE_SPEECH_API_KEY / Resource ID</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 后端连接火山 bigmodel_async</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 后端封装 full client request / audio only request 二进制包</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 火山返回识别结果</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 后端统一成 transcript.partial / transcript.final</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 浏览器实时显示字幕</span><br></div></code></pre></div></div>
<p>服务端环境变量形态：</p>
<div class="language-dotenv codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dotenv codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">ASR_PROVIDER=volcengine</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">VOLCENGINE_ASR_ENDPOINT=wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_async</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">VOLCENGINE_SPEECH_API_KEY=[REDACTED]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">VOLCENGINE_ASR_RESOURCE_ID=volc.seedasr.sauc.duration</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">VOLCENGINE_ASR_AUDIO_FORMAT=wav</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">VOLCENGINE_ASR_SAMPLE_RATE=16000</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">VOLCENGINE_ASR_CHUNK_MS=200</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">VOLCENGINE_ASR_ENABLE_NONSTREAM=true</span><br></div></code></pre></div></div>
<p>统一事件建议：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"type"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"transcript.partial"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"provider"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"volcengine"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"text"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"我们今天讨论"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"seq"</span><span class="token operator" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">12</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"logid"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"[REDACTED]"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">{</span><span class="token property" style="color:#36acaa">"type"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"transcript.final"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"provider"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"volcengine"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"text"</span><span class="token operator" style="color:#393A34">:</span><span class="token string" style="color:#e3116c">"我们今天讨论实时语音识别。"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"seq"</span><span class="token operator" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">13</span><span class="token punctuation" style="color:#393A34">,</span><span class="token property" style="color:#36acaa">"definite"</span><span class="token operator" style="color:#393A34">:</span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><code>X-Tt-Logid</code> 可以记录，但如果日志里包含业务内容或用户音频关联 ID，生产环境也要按隐私日志处理。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="实践分工你点网页我写代码">实践分工：你点网页，我写代码<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#%E5%AE%9E%E8%B7%B5%E5%88%86%E5%B7%A5%E4%BD%A0%E7%82%B9%E7%BD%91%E9%A1%B5%E6%88%91%E5%86%99%E4%BB%A3%E7%A0%81" class="hash-link" aria-label="实践分工：你点网页，我写代码的直接链接" title="实践分工：你点网页，我写代码的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="你先做">你先做<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#%E4%BD%A0%E5%85%88%E5%81%9A" class="hash-link" aria-label="你先做的直接链接" title="你先做的直接链接" translate="no">​</a></h3>
<ol>
<li class="">登录火山引擎：<a href="https://www.volcengine.com/" target="_blank" rel="noopener noreferrer" class="">https://www.volcengine.com/</a></li>
<li class="">进入豆包语音 / 语音技术控制台。</li>
<li class="">确认是否能进入新版 API Key 页面：<a href="https://console.volcengine.com/speech/new/setting/apikeys?projectName=default" target="_blank" rel="noopener noreferrer" class="">https://console.volcengine.com/speech/new/setting/apikeys?projectName=default</a></li>
<li class="">开通豆包流式语音识别模型 2.0 或大模型流式语音识别。</li>
<li class="">确认是否有免费额度、试用、资源包或后付费。</li>
<li class="">选择小时版 Resource ID，第一轮优先 <code>volc.seedasr.sauc.duration</code>。</li>
<li class="">确认监控统计页面能看到资源包 / 调用用量。</li>
<li class="">不要把真实 Key 发出来；只告诉我：</li>
</ol>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">- 是否已进入语音控制台：是/否</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- 是新版控制台还是旧版控制台：新版/旧版/不确定</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- 已开通能力：豆包流式语音识别2.0 / 大模型流式语音识别 / 未开通</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- 是否有 X-Api-Key：是/否</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- Resource ID 准备用哪个：volc.seedasr.sauc.duration / 其他</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- 是否购买资源包或开启后付费：资源包/后付费/未开通</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">- 默认并发是否够第一轮测试：是/否/不确定</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="我来做">我来做<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#%E6%88%91%E6%9D%A5%E5%81%9A" class="hash-link" aria-label="我来做的直接链接" title="我来做的直接链接" translate="no">​</a></h3>
<ol>
<li class="">写一个最小火山 WebSocket 客户端，只读环境变量，不写死 Key。</li>
<li class="">实现火山二进制协议 header / payload size / gzip / JSON 序列化。</li>
<li class="">准备 16k/16bit/mono wav 或 pcm 测试音频。</li>
<li class="">跑 <code>bigmodel_async</code>，记录 <code>X-Tt-Logid</code>、错误码、首字延迟、final 文本。</li>
<li class="">再测 <code>bigmodel_nostream</code> 做准确率对照。</li>
<li class="">把火山返回结果映射成统一 partial/final 事件。</li>
<li class="">接网页实时字幕页和调用时长统计。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="第一轮验收标准">第一轮验收标准<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#%E7%AC%AC%E4%B8%80%E8%BD%AE%E9%AA%8C%E6%94%B6%E6%A0%87%E5%87%86" class="hash-link" aria-label="第一轮验收标准的直接链接" title="第一轮验收标准的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>验收项</th><th>通过标准</th></tr></thead><tbody><tr><td>服务开通</td><td>控制台显示目标能力可用</td></tr><tr><td>鉴权</td><td>WebSocket 握手成功，不返回 Key / Resource ID / 权限错误</td></tr><tr><td>Resource ID</td><td>账单和日志显示调用的是预期资源 ID</td></tr><tr><td>音频格式</td><td>10–20 秒 16k 单声道音频能被识别</td></tr><tr><td>实时性</td><td><code>bigmodel_async</code> 发送期间持续返回可上屏文本</td></tr><tr><td>final 稳定性</td><td>开启二遍识别后，final 结果能覆盖临时结果且更稳定</td></tr><tr><td>日志</td><td>记录 <code>X-Tt-Logid</code>、request id、chunk ms、错误码</td></tr><tr><td>成本</td><td>用量能在监控 / 账单里解释，未出现意外后付费长跑</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="常见坑">常见坑<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#%E5%B8%B8%E8%A7%81%E5%9D%91" class="hash-link" aria-label="常见坑的直接链接" title="常见坑的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="坑-1新版--旧版控制台参数混用">坑 1：新版 / 旧版控制台参数混用<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#%E5%9D%91-1%E6%96%B0%E7%89%88--%E6%97%A7%E7%89%88%E6%8E%A7%E5%88%B6%E5%8F%B0%E5%8F%82%E6%95%B0%E6%B7%B7%E7%94%A8" class="hash-link" aria-label="坑 1：新版 / 旧版控制台参数混用的直接链接" title="坑 1：新版 / 旧版控制台参数混用的直接链接" translate="no">​</a></h3>
<p>新版主要看 <code>X-Api-Key</code>；旧版可能是 App Key / Access Key / token / cluster 等。先确认自己看到的是哪个控制台，不要把两套参数混着填。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="坑-2resource-id-选错">坑 2：Resource ID 选错<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#%E5%9D%91-2resource-id-%E9%80%89%E9%94%99" class="hash-link" aria-label="坑 2：Resource ID 选错的直接链接" title="坑 2：Resource ID 选错的直接链接" translate="no">​</a></h3>
<p><code>volc.bigasr.sauc.duration</code>、<code>volc.seedasr.sauc.duration</code>、并发版 ID、小时版 ID代表不同服务和计费模式。第一轮如果要测豆包流式语音识别 2.0 小时版，应优先确认 <code>volc.seedasr.sauc.duration</code>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="坑-3以为-websocket-只发-json">坑 3：以为 WebSocket 只发 JSON<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#%E5%9D%91-3%E4%BB%A5%E4%B8%BA-websocket-%E5%8F%AA%E5%8F%91-json" class="hash-link" aria-label="坑 3：以为 WebSocket 只发 JSON的直接链接" title="坑 3：以为 WebSocket 只发 JSON的直接链接" translate="no">​</a></h3>
<p>火山文档使用二进制协议：header、payload size、payload。直接发普通 JSON 大概率不通。这个复杂度应该封装在后端 adapter。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="坑-4分包太大或太小">坑 4：分包太大或太小<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#%E5%9D%91-4%E5%88%86%E5%8C%85%E5%A4%AA%E5%A4%A7%E6%88%96%E5%A4%AA%E5%B0%8F" class="hash-link" aria-label="坑 4：分包太大或太小的直接链接" title="坑 4：分包太大或太小的直接链接" translate="no">​</a></h3>
<p>官方建议 100–200ms，双向流式推荐 200ms。分包节奏会影响性能。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="坑-5一上来打开所有增强功能">坑 5：一上来打开所有增强功能<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#%E5%9D%91-5%E4%B8%80%E4%B8%8A%E6%9D%A5%E6%89%93%E5%BC%80%E6%89%80%E6%9C%89%E5%A2%9E%E5%BC%BA%E5%8A%9F%E8%83%BD" class="hash-link" aria-label="坑 5：一上来打开所有增强功能的直接链接" title="坑 5：一上来打开所有增强功能的直接链接" translate="no">​</a></h3>
<p>说话人、情感、性别、热词、上下文、顺滑、语种检测都可能改变返回结构。第一轮先只开 ITN、标点、必要的二遍识别。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="坑-6后付费没有成本保护">坑 6：后付费没有成本保护<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#%E5%9D%91-6%E5%90%8E%E4%BB%98%E8%B4%B9%E6%B2%A1%E6%9C%89%E6%88%90%E6%9C%AC%E4%BF%9D%E6%8A%A4" class="hash-link" aria-label="坑 6：后付费没有成本保护的直接链接" title="坑 6：后付费没有成本保护的直接链接" translate="no">​</a></h3>
<p>火山文档写了欠费关停 / 回收逻辑，也有资源包监控和到期提醒。测试阶段要记录音频时长，避免长时间后台连接导致账单超预期。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="后续更新计划">后续更新计划<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#%E5%90%8E%E7%BB%AD%E6%9B%B4%E6%96%B0%E8%AE%A1%E5%88%92" class="hash-link" aria-label="后续更新计划的直接链接" title="后续更新计划的直接链接" translate="no">​</a></h2>
<p>这篇先作为从零教程第一版。拿到控制台状态后继续补：</p>
<ol>
<li class="">控制台实际路径截图对应的步骤；</li>
<li class="">新版 <code>X-Api-Key</code> smoke test；</li>
<li class=""><code>bigmodel_async</code> 最小 Python / Node WebSocket 客户端；</li>
<li class="">10–20 秒中文音频结果、<code>X-Tt-Logid</code> 和错误码样例；</li>
<li class="">二遍识别开启 / 关闭 A/B；</li>
<li class="">与科大讯飞 / 阿里云同音频 A/B；</li>
<li class="">最终是否把火山作为第一版主 provider 或备 provider。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="参考入口">参考入口<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/volcengine-realtime-asr-practice-guide#%E5%8F%82%E8%80%83%E5%85%A5%E5%8F%A3" class="hash-link" aria-label="参考入口的直接链接" title="参考入口的直接链接" translate="no">​</a></h2>
<ul>
<li class="">火山引擎：<a href="https://www.volcengine.com/" target="_blank" rel="noopener noreferrer" class="">https://www.volcengine.com/</a></li>
<li class="">豆包语音产品页：<a href="https://www.volcengine.com/product/doubao-speech" target="_blank" rel="noopener noreferrer" class="">https://www.volcengine.com/product/doubao-speech</a></li>
<li class="">新版控制台 API Key：<a href="https://console.volcengine.com/speech/new/setting/apikeys?projectName=default" target="_blank" rel="noopener noreferrer" class="">https://console.volcengine.com/speech/new/setting/apikeys?projectName=default</a></li>
<li class="">大模型流式语音识别 API：<a href="https://docs.volcengine.com/docs/6561/1354869?lang=zh" target="_blank" rel="noopener noreferrer" class="">https://docs.volcengine.com/docs/6561/1354869?lang=zh</a></li>
<li class="">豆包语音计费说明：<a href="https://docs.volcengine.com/docs/6561/1359370?lang=zh" target="_blank" rel="noopener noreferrer" class="">https://docs.volcengine.com/docs/6561/1359370?lang=zh</a></li>
<li class="">控制台 FAQ：<a href="https://www.volcengine.com/docs/6561/196768" target="_blank" rel="noopener noreferrer" class="">https://www.volcengine.com/docs/6561/196768</a></li>
<li class="">监控统计：<a href="https://console.volcengine.com/speech/monitor" target="_blank" rel="noopener noreferrer" class="">https://console.volcengine.com/speech/monitor</a></li>
<li class="">费用中心账单详情：<a href="https://console.volcengine.com/finance/bill/detail/" target="_blank" rel="noopener noreferrer" class="">https://console.volcengine.com/finance/bill/detail/</a></li>
</ul>]]></content>
        <category label="volcengine" term="volcengine"/>
        <category label="doubao" term="doubao"/>
        <category label="bytedance" term="bytedance"/>
        <category label="speech" term="speech"/>
        <category label="realtime" term="realtime"/>
        <category label="asr" term="asr"/>
        <category label="speech-to-text" term="speech-to-text"/>
        <category label="tutorial" term="tutorial"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[自建发信服务怎么部署：从 Postfix 到托管 SMTP 的方案选择]]></title>
        <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options</id>
        <link href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options"/>
        <updated>2026-08-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[解释自建发信服务到底包含哪些组件、邮件从应用到收件箱的过程、Postfix/全家桶/托管 SMTP/混合网关等常见方案，以及部署和验收清单。]]></summary>
        <content type="html"><![CDATA[<p>前一篇 <a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/smtp-email-delivery-basics-and-provider-configuration" target="_blank" rel="noopener noreferrer" class="">SMTP 入门</a> 讲的是“SMTP 是什么、邮件怎样发出去、常见平台怎么配置”。这一篇往下一层：如果我们要给自己的服务部署一个 <strong>mail service</strong>，甚至自建一个能发邮件的服务器，到底要部署哪些东西？</p>
<p>结论先说：<strong>自建发信服务不是把 Postfix 跑起来就结束了，而是要同时交付协议链路、域名身份、IP 信誉和运维闭环。</strong></p>
<p>对大多数产品团队来说，最稳妥的起点不是“完全自建直连全网收件服务器”，而是先做一个自己可控的发信网关：应用统一提交邮件到内部 SMTP/API，由这个网关负责模板、限流、日志、队列、退信处理，再转交给 SES、SendGrid、Mailgun、Postmark、Resend 或企业邮箱这类上游发信服务。只有当你能控制 25 端口、PTR/rDNS、IP 信誉、退信投诉和 7x24 运维时，才值得进一步做 direct-send 的自建 MTA。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="先拆清楚你要自建的是哪一层">先拆清楚：你要自建的是哪一层？<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#%E5%85%88%E6%8B%86%E6%B8%85%E6%A5%9A%E4%BD%A0%E8%A6%81%E8%87%AA%E5%BB%BA%E7%9A%84%E6%98%AF%E5%93%AA%E4%B8%80%E5%B1%82" class="hash-link" aria-label="先拆清楚：你要自建的是哪一层？的直接链接" title="先拆清楚：你要自建的是哪一层？的直接链接" translate="no">​</a></h2>
<p>“自建邮件服务器”容易混在一起说，但实际至少有三种目标：</p>
<table><thead><tr><th>目标</th><th>你真正要交付的东西</th><th style="text-align:right">是否需要收信</th><th>典型场景</th></tr></thead><tbody><tr><td><strong>只发系统邮件</strong></td><td>应用发信入口、队列、日志、模板、上游 SMTP/API 凭据</td><td style="text-align:right">否</td><td>注册验证、通知、告警、账单</td></tr><tr><td><strong>自建出站 MTA</strong></td><td>Postfix/Exim/OpenSMTPD 等直接投递到收件方 MX</td><td style="text-align:right">可选</td><td>需要控制投递链路、减少第三方依赖</td></tr><tr><td><strong>完整邮箱系统</strong></td><td>SMTP submission、MX、IMAP/POP3、Webmail、账号、反垃圾、备份</td><td style="text-align:right">是</td><td>团队邮箱、社区域名邮箱、个人邮箱托管</td></tr></tbody></table>
<p>这三个目标的难度差很多。只发系统邮件时，最重要的是“应用怎么可靠地把邮件交出去”；完整邮箱系统还要负责“别人怎么给你发、用户怎么收、垃圾邮件怎么挡、数据怎么备份”。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一封邮件从应用到收件箱的过程">一封邮件从应用到收件箱的过程<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#%E4%B8%80%E5%B0%81%E9%82%AE%E4%BB%B6%E4%BB%8E%E5%BA%94%E7%94%A8%E5%88%B0%E6%94%B6%E4%BB%B6%E7%AE%B1%E7%9A%84%E8%BF%87%E7%A8%8B" class="hash-link" aria-label="一封邮件从应用到收件箱的过程的直接链接" title="一封邮件从应用到收件箱的过程的直接链接" translate="no">​</a></h2>
<p>可以把发信路径理解成六段：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">业务系统</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 发信入口（SMTP submission 或 HTTP API）</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 发信队列 / MSA / MTA</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; DNS 查询收件域名 MX</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 连接对方 MX（通常是 SMTP port 25）</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 对方反垃圾、身份认证、收件规则</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -&gt; 收件箱 / 垃圾箱 / 退信 / 延迟重试</span><br></div></code></pre></div></div>
<p>这里有两个常被混淆的 SMTP 角色：</p>
<ol>
<li class=""><strong>Message submission</strong>：应用或邮件客户端把邮件“提交”给自己的发信服务器。RFC 6409 明确把 message submission 和 message relay 分开，并说明 submission 通常使用 587 端口。[2]</li>
<li class=""><strong>Message relay / transfer</strong>：MTA 之间把邮件从一个域投递到另一个域。RFC 6409 同时说明 relay 仍然使用 SMTP 的 25 端口。[2]</li>
</ol>
<p>所以，常见端口可以这样记：</p>
<table><thead><tr><th style="text-align:right">端口</th><th>常见用途</th><th>部署含义</th></tr></thead><tbody><tr><td style="text-align:right">25</td><td>MTA 到 MTA 的服务器间投递</td><td>direct-send 必须能从服务器出站访问；收信服务器也通常要入站开放</td></tr><tr><td style="text-align:right">587</td><td>邮件客户端/应用提交邮件，通常 STARTTLS + 认证</td><td>推荐给业务系统或用户客户端使用</td></tr><tr><td style="text-align:right">465</td><td>implicit TLS 的 SMTP submission，也常叫 submissions</td><td>RFC 8314 将 implicit TLS submission 放回标准化轨道，并记录 <code>submissions</code> 端口 465。[3]</td></tr></tbody></table>
<p>如果你只是给应用发验证码，业务系统不应该直接连全世界的 MX。更好的方式是连自己的 submission/gateway，认证后入队，由后端统一决定是直投还是走上游 relay。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="发信服务的最低可用架构">发信服务的最低可用架构<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#%E5%8F%91%E4%BF%A1%E6%9C%8D%E5%8A%A1%E7%9A%84%E6%9C%80%E4%BD%8E%E5%8F%AF%E7%94%A8%E6%9E%B6%E6%9E%84" class="hash-link" aria-label="发信服务的最低可用架构的直接链接" title="发信服务的最低可用架构的直接链接" translate="no">​</a></h2>
<p>一个最低可用的发信服务，至少要有下面几块：</p>
<table><thead><tr><th>层</th><th>组件</th><th>最低要求</th></tr></thead><tbody><tr><td>应用入口</td><td>SMTP submission 或 HTTP API</td><td>认证、TLS、请求日志、合理超时</td></tr><tr><td>队列</td><td>MTA 队列或任务队列</td><td>可重试、可观察、失败不丢信</td></tr><tr><td>投递</td><td>Postfix/Exim/OpenSMTPD/maddy 或上游 SMTP/API</td><td>不能变成 open relay；能区分本地域和外部域</td></tr><tr><td>身份</td><td>SPF、DKIM、DMARC、PTR/rDNS、HELO/EHLO hostname</td><td>From 域名与认证域名要对齐</td></tr><tr><td>信誉</td><td>发送频率、退信率、投诉率、内容质量</td><td>有限流、退订、suppression list 和监控</td></tr><tr><td>运维</td><td>日志、队列查看、告警、备份、密钥轮换</td><td>能解释“这封邮件为什么没到”</td></tr></tbody></table>
<p>Postfix 的官方文档把 <code>myhostname</code>、<code>mydomain</code>、<code>myorigin</code> 等作为基础配置核心：它们决定服务器的 FQDN、默认域名以及出站邮件显示的域名。[7] 这些不是装饰项。收件方会把你的 HELO/EHLO、PTR、From 域、SPF、DKIM、DMARC 和内容一起看。</p>
<p>最危险的反例是 open relay：任何外部机器都能借你的服务器发邮件。Postfix 2.10 以后推荐把“谁有权 relay”放在 <code>smtpd_relay_restrictions</code>，默认策略包含 <code>permit_mynetworks</code>、<code>permit_sasl_authenticated</code> 和对未授权目的地的拒绝/延迟拒绝。[8] 换句话说，<strong>只有可信网段或通过 SASL 认证的客户端可以借你转发外部邮件</strong>。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="常见方案怎么选">常见方案怎么选<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#%E5%B8%B8%E8%A7%81%E6%96%B9%E6%A1%88%E6%80%8E%E4%B9%88%E9%80%89" class="hash-link" aria-label="常见方案怎么选的直接链接" title="常见方案怎么选的直接链接" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="方案-a完全托管-smtpapi-服务">方案 A：完全托管 SMTP/API 服务<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#%E6%96%B9%E6%A1%88-a%E5%AE%8C%E5%85%A8%E6%89%98%E7%AE%A1-smtpapi-%E6%9C%8D%E5%8A%A1" class="hash-link" aria-label="方案 A：完全托管 SMTP/API 服务的直接链接" title="方案 A：完全托管 SMTP/API 服务的直接链接" translate="no">​</a></h3>
<p>代表：Amazon SES、SendGrid、Mailgun、Postmark、Resend、企业邮箱 SMTP、云厂商 DirectMail。</p>
<p>这是最适合产品早期的方案。你不自建 MTA，而是把发信身份、DKIM、退信、投诉、配额、信誉爬坡交给服务商。SES 的 deliverability 文档把退信、投诉、suppression list、认证、发送配额、内容过滤、信誉和通知作为发信可达率的核心概念。[14]</p>
<p>优点：</p>
<ul>
<li class="">起步快，通常不需要自己维护 25 端口、IP 信誉和收件方兼容性。</li>
<li class="">有 bounce/complaint webhook、统计、模板、配额和 suppression list。</li>
<li class="">适合验证码、交易邮件、系统通知、营销邮件的早期阶段。</li>
</ul>
<p>缺点：</p>
<ul>
<li class="">依赖第三方账号和规则；风控、审核、额度可能影响发信。</li>
<li class="">上游服务的 API/SMTP 形状会进入你的业务系统，后续迁移有成本。</li>
<li class="">需要注意数据合规和邮件内容合规。</li>
</ul>
<p>适合：<strong>绝大多数业务系统的第一版发信能力</strong>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="方案-b自建发信网关--上游-relay">方案 B：自建发信网关 + 上游 relay<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#%E6%96%B9%E6%A1%88-b%E8%87%AA%E5%BB%BA%E5%8F%91%E4%BF%A1%E7%BD%91%E5%85%B3--%E4%B8%8A%E6%B8%B8-relay" class="hash-link" aria-label="方案 B：自建发信网关 + 上游 relay的直接链接" title="方案 B：自建发信网关 + 上游 relay的直接链接" translate="no">​</a></h3>
<p>这是我最推荐的工程化折中：你部署一个内部 mail gateway，业务系统只认它；gateway 再把邮件交给 SES、Mailgun、企业邮箱或备用上游。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">业务系统 -&gt; 内部 SMTP/API Gateway -&gt; 队列/模板/限流/日志 -&gt; 上游 SMTP/API -&gt; 收件方</span><br></div></code></pre></div></div>
<p>优点：</p>
<ul>
<li class="">业务系统不直接散落一堆 SMTP 密码和供应商 SDK。</li>
<li class="">可以统一模板、审计、限流、重试、去重、退信处理和 provider failover。</li>
<li class="">将来要从 SES 换到 Postmark，或从企业邮箱换到自建直投，业务侧改动小。</li>
</ul>
<p>缺点：</p>
<ul>
<li class="">你仍然需要维护一个小服务和队列。</li>
<li class="">如果 gateway 只做同步转发，没有持久队列，故障时仍然会丢邮件。</li>
</ul>
<p>适合：<strong>ChatArch 这类多服务、多机器人、多社区入口共享一套通知能力的场景</strong>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="方案-c自建-direct-send-出站-mta">方案 C：自建 direct-send 出站 MTA<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#%E6%96%B9%E6%A1%88-c%E8%87%AA%E5%BB%BA-direct-send-%E5%87%BA%E7%AB%99-mta" class="hash-link" aria-label="方案 C：自建 direct-send 出站 MTA的直接链接" title="方案 C：自建 direct-send 出站 MTA的直接链接" translate="no">​</a></h3>
<p>代表：Postfix、Exim、OpenSMTPD、maddy 的出站能力。</p>
<p>这种方案会由你的服务器直接查收件域 MX，然后连对方 25 端口投递。它看起来“最独立”，但实际门槛最高。AWS EC2 默认就会阻止到公网 IPv4/IPv6 的 25 端口出站流量，需要申请解除限制。[15] 很多云厂商、住宅网络和企业网络也会限制 25 端口，原因很简单：垃圾邮件滥用风险太高。</p>
<p>要做 direct-send，至少先确认：</p>
<ul>
<li class="">你的服务器能稳定出站访问 25 端口。</li>
<li class="">服务器有静态公网 IP，且可配置 PTR/rDNS。</li>
<li class="">PTR 指向的主机名有正向 A/AAAA 记录回到同一个 IP。</li>
<li class="">HELO/EHLO hostname、From 域名、DKIM d= 域、SPF include/ip、DMARC alignment 能解释清楚。</li>
<li class="">有退信和投诉处理；不能对 hard bounce 地址反复发送。</li>
<li class="">有队列监控、速率控制、灰名单/临时失败重试策略。</li>
</ul>
<p>Gmail 的 sender guidelines 明确要求或建议 SPF/DKIM、DMARC、有效的正向/反向 DNS、TLS、低 spam rate；对于每天发给 Gmail 超过 5,000 封的发送方，还要求 SPF、DKIM、DMARC、From 对齐、一键退订等更严格条件。[16]</p>
<p>适合：<strong>对投递链路控制有强需求、能长期维护邮件基础设施、并愿意承担信誉冷启动的团队</strong>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="方案-d完整自托管邮箱套件">方案 D：完整自托管邮箱套件<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#%E6%96%B9%E6%A1%88-d%E5%AE%8C%E6%95%B4%E8%87%AA%E6%89%98%E7%AE%A1%E9%82%AE%E7%AE%B1%E5%A5%97%E4%BB%B6" class="hash-link" aria-label="方案 D：完整自托管邮箱套件的直接链接" title="方案 D：完整自托管邮箱套件的直接链接" translate="no">​</a></h3>
<p>代表：Mail-in-a-Box、mailcow、Mailu、Docker Mailserver、Modoboa、iRedMail。</p>
<p>这类方案不是只解决“发系统邮件”，而是把邮箱系统完整打包：Postfix、Dovecot、Webmail、反垃圾、TLS、DKIM、DMARC、账号管理、备份、管理后台等。</p>
<ul>
<li class=""><strong>Docker Mailserver / DMS</strong>：适合已经熟悉 Docker Compose、想要“文件配置、少 Web UI、可版本化”的人。DMS 自称是 production-ready、fullstack 但 simple 的容器化邮件服务器，包含 SMTP、IMAP、LDAP、反垃圾、反病毒等；README 列出的组件包括 Postfix、Dovecot、Rspamd、ClamAV、OpenDKIM/OpenDMARC、Fail2ban 和证书支持。[17] 它更像一套干净的邮件基础设施组件包，不像 mailcow 那样以管理后台和群件体验为中心。</li>
<li class=""><strong>mailcow</strong>：适合想要完整 Web 管理体验的人。它通常包含 Postfix、Dovecot、SOGo、Rspamd、ACME、管理后台等，适合“我要运营一个团队邮箱系统”，而不只是“让应用能发邮件”。</li>
<li class=""><strong>Mailu</strong>：适合想要容器化完整邮箱栈、并接受按组件理解配置的人。Mailu 是一组 Docker images，目标是易安装、易维护、功能完整的邮件服务器；它列出的能力包括 IMAP、SMTP、Submission、Webmail/Admin、TLS、DANE、MTA-STS、outgoing DKIM、DMARC/SPF、反垃圾等。[19]</li>
<li class=""><strong>Mail-in-a-Box</strong>：适合“一台新机器专门做邮箱”的路径。它更像“把整台机器变成一台邮箱盒子”。它的 setup guide 明确不建议在家用网络运行，因为住宅网络常见 25 端口阻断、黑名单、动态 IP 和不可配置 reverse DNS；同时它要求安装在全新的专用机器上，并会自管理配置。[13]</li>
</ul>
<p>如果你脑子里第一个想到的是“Docker mail service”，大概率说的就是 Docker Mailserver 这一类。它可以作为自托管邮箱的最小具体方案，但前提是你真的要收信/管邮箱账号；如果只是产品验证码，DMS 反而会把 MX、IMAP、反垃圾、备份等问题也带进来。</p>
<p>优点：</p>
<ul>
<li class="">收信、发信、Webmail、账号、反垃圾、证书等开箱集成。</li>
<li class="">对个人域名邮箱、小组织邮箱、社区邮箱比较友好。</li>
<li class="">文档会覆盖 DNS、证书、账号、Webmail 等完整路径。</li>
</ul>
<p>缺点：</p>
<ul>
<li class="">不是“轻量发验证码”的方案；运维面比单纯发信大很多。</li>
<li class="">容器编排、升级、备份、磁盘、日志、反垃圾策略都要长期维护。</li>
<li class="">一旦作为正式邮箱使用，数据可靠性和迁移成本会变高。</li>
</ul>
<p>适合：<strong>真的要拥有邮箱账户和收信能力，而不是只给业务系统发通知</strong>。</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="docker-mailserver一条更具体的落地路线">Docker Mailserver：一条更具体的落地路线<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#docker-mailserver%E4%B8%80%E6%9D%A1%E6%9B%B4%E5%85%B7%E4%BD%93%E7%9A%84%E8%90%BD%E5%9C%B0%E8%B7%AF%E7%BA%BF" class="hash-link" aria-label="Docker Mailserver：一条更具体的落地路线的直接链接" title="Docker Mailserver：一条更具体的落地路线的直接链接" translate="no">​</a></h4>
<p>Butterfly 旧文里写过一版 DMS 搭建教程，核心路径可以抽象成下面这 8 步。这里保留流程形状，域名、IP、密码和 DKIM key 都用占位符。</p>
<ol>
<li class=""><strong>准备主机名和目录</strong>：确定 <code>mail.example.com</code>，并把 DMS 配置、邮件数据、状态、日志放到可备份目录，例如 <code>./docker-data/dms/{config,mail-data,mail-state,mail-logs}</code>。</li>
<li class=""><strong>下载官方 compose 和 env 模板</strong>：从 Docker Mailserver 官方仓库拿 <code>compose.yaml</code> 和 <code>mailserver.env</code>，不要从博客复制过期镜像 tag；上线前固定镜像版本。</li>
<li class=""><strong>设置容器主机名</strong>：<code>hostname: mail.example.com</code>。这一步不是随便取名，它要和 A/PTR、证书、HELO/EHLO 解释得通。</li>
<li class=""><strong>先配 DNS，再启动生产流量</strong>：</li>
</ol>
<div class="language-dns codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dns codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">example.com.       MX 10 mail.example.com.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">mail.example.com.  A     203.0.113.10</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">203.0.113.10       PTR   mail.example.com.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">example.com.       TXT   "v=spf1 mx -all"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">_dmarc.example.com. TXT  "v=DMARC1; p=none; rua=mailto:dmarc-report@example.com"</span><br></div></code></pre></div></div>
<ol start="5">
<li class=""><strong>配置 TLS 证书</strong>：可以让 DMS 使用 Let’s Encrypt 证书，例如 <code>SSL_TYPE=letsencrypt</code>，再把宿主机证书目录只读挂入容器。生产环境要保证证书域名就是 <code>mail.example.com</code>。</li>
<li class=""><strong>启动服务并生成 DKIM</strong>：</li>
</ol>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">docker compose up -d</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">docker exec -it mailserver setup config dkim</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">docker compose restart mailserver</span><br></div></code></pre></div></div>
<p>然后把生成的 <code>mail._domainkey.example.com</code> TXT 记录写入 DNS。不要把私钥或完整真实 DKIM 公钥贴进文章、Issue 或聊天。</p>
<ol start="7">
<li class=""><strong>添加邮箱用户</strong>：</li>
</ol>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">docker exec -ti mailserver setup email add user@example.com</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">docker exec -ti mailserver setup email update user@example.com</span><br></div></code></pre></div></div>
<p>这一步意味着你已经在运营 mailbox，不只是发信 relay；后续要考虑密码策略、离职/禁用、备份恢复和 IMAP 客户端支持。</p>
<ol start="8">
<li class=""><strong>开放并验证端口</strong>：</li>
</ol>
<table><thead><tr><th style="text-align:right">端口</th><th>作用</th><th>DMS 里通常意味着什么</th></tr></thead><tbody><tr><td style="text-align:right">25</td><td>SMTP server-to-server</td><td>收外部邮件、direct-send 投递；常被云厂商限制</td></tr><tr><td style="text-align:right">465</td><td>implicit TLS submission</td><td>给客户端/应用安全提交邮件</td></tr><tr><td style="text-align:right">587</td><td>STARTTLS submission</td><td>推荐的客户端/应用提交入口</td></tr><tr><td style="text-align:right">143</td><td>IMAP + STARTTLS</td><td>明文连接后升级 TLS，很多场景可不公开</td></tr><tr><td style="text-align:right">993</td><td>IMAPS</td><td>客户端安全收信入口</td></tr></tbody></table>
<p>最小验收不是“我给自己发了一封能收到”，而是同时检查：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">dig +short MX example.com</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">dig +short A mail.example.com</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">dig +short -x 203.0.113.10</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">openssl s_client -connect mail.example.com:465 -servername mail.example.com &lt;/dev/null</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">openssl s_client -starttls smtp -connect mail.example.com:587 -servername mail.example.com &lt;/dev/null</span><br></div></code></pre></div></div>
<p>再发一封到 Gmail/Outlook，查看原始邮件头里的 <code>Authentication-Results</code>，确认 SPF、DKIM、DMARC 至少在测试路径上 pass。</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="dms-之外什么时候换方案">DMS 之外什么时候换方案<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#dms-%E4%B9%8B%E5%A4%96%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E6%8D%A2%E6%96%B9%E6%A1%88" class="hash-link" aria-label="DMS 之外什么时候换方案的直接链接" title="DMS 之外什么时候换方案的直接链接" translate="no">​</a></h4>
<table><thead><tr><th>如果你真正想要</th><th>更合适的方案</th><th>原因</th></tr></thead><tbody><tr><td>只给业务系统发验证码/通知</td><td>托管 SMTP/API 或自建 gateway + 上游 relay</td><td>不需要接管 MX/IMAP/账号/反垃圾</td></tr><tr><td>Docker Compose 自托管完整邮箱，偏配置文件</td><td>Docker Mailserver</td><td>简洁、可版本化、少数据库依赖</td></tr><tr><td>完整后台、Webmail、管理体验</td><td>mailcow</td><td>更像成品邮箱系统</td></tr><tr><td>容器化完整邮箱栈，可按组件理解</td><td>Mailu</td><td>模块化 Docker images，功能覆盖完整</td></tr><tr><td>专用新机器一键托管个人/小组织邮箱</td><td>Mail-in-a-Box</td><td>opinionated，自动接管整机配置</td></tr><tr><td>学底层或做最小 direct-send</td><td>Postfix + Dovecot/Rspamd/OpenDKIM 或 maddy</td><td>更可控，但维护成本最高</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="方案-e一体化轻量服务器--内部测试-mail-sink">方案 E：一体化轻量服务器 / 内部测试 mail sink<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#%E6%96%B9%E6%A1%88-e%E4%B8%80%E4%BD%93%E5%8C%96%E8%BD%BB%E9%87%8F%E6%9C%8D%E5%8A%A1%E5%99%A8--%E5%86%85%E9%83%A8%E6%B5%8B%E8%AF%95-mail-sink" class="hash-link" aria-label="方案 E：一体化轻量服务器 / 内部测试 mail sink的直接链接" title="方案 E：一体化轻量服务器 / 内部测试 mail sink的直接链接" translate="no">​</a></h3>
<p>maddy 是另一个有意思的方向：它把 SMTP MTA、MX、IMAP、DKIM/SPF/DMARC/DANE/MTA-STS 等功能整合到一个 daemon，目标是用统一配置替代 Postfix、Dovecot、OpenDKIM、OpenSPF、OpenDMARC 等多组件组合。[12] 但它也在首页提醒 IMAP storage 仍是 beta，若需要稳定、功能丰富的 IMAP，可能仍应选择 Dovecot。[12]</p>
<p>另外，开发环境里经常需要的不是“发到真实互联网”，而是 mail sink：Mailpit、MailHog、smtp4dev 这类工具可以接收测试邮件、展示 HTML、检查 headers，但不会真正投递。它们适合 CI、预发和本地调试，不应该被误认为生产发信服务。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="dns-和身份认证为什么它决定到达率">DNS 和身份认证：为什么它决定到达率<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#dns-%E5%92%8C%E8%BA%AB%E4%BB%BD%E8%AE%A4%E8%AF%81%E4%B8%BA%E4%BB%80%E4%B9%88%E5%AE%83%E5%86%B3%E5%AE%9A%E5%88%B0%E8%BE%BE%E7%8E%87" class="hash-link" aria-label="DNS 和身份认证：为什么它决定到达率的直接链接" title="DNS 和身份认证：为什么它决定到达率的直接链接" translate="no">​</a></h2>
<p>发信服务器的技术栈可以换，但下面这些 DNS/身份项绕不开：</p>
<table><thead><tr><th>项</th><th>作用</th><th>常见错误</th></tr></thead><tbody><tr><td>MX</td><td>告诉别人你的域名收信服务器是谁</td><td>只发信不收信时不一定要接管 MX；完整邮箱必须配置</td></tr><tr><td>A/AAAA</td><td>服务器主机名解析到 IP</td><td>主机名和 PTR 对不上</td></tr><tr><td>PTR/rDNS</td><td>IP 反查到主机名</td><td>云厂商不支持或没申请，导致信誉差</td></tr><tr><td>SPF</td><td>声明哪些服务器/服务商可以代表域名发信</td><td>忘记 include 上游；记录超过 DNS lookup 限制</td></tr><tr><td>DKIM</td><td>用域名私钥给邮件签名，收件方用 DNS 公钥验证</td><td>key 太短、selector 错、签名域与 From 不对齐</td></tr><tr><td>DMARC</td><td>告诉收件方 SPF/DKIM 失败时怎么处理，并要求 alignment</td><td>一上来 <code>p=reject</code>，但还没盘点所有合法发信源</td></tr><tr><td>TLS</td><td>传输过程加密</td><td>submission 端口没强制 TLS；证书和 hostname 不匹配</td></tr><tr><td>List-Unsubscribe</td><td>批量/订阅邮件退订</td><td>营销邮件没有清晰退订，投诉率升高</td></tr></tbody></table>
<p>Google 的 guidelines 直接把 SPF/DKIM、DMARC、正反向 DNS、TLS、From alignment 和低 spam rate 放在发件方要求中。[16] 这说明“邮件能不能进 inbox”不是单点配置，而是身份、网络、内容和行为共同决定。</p>
<p>一个稳妥的上线顺序是：</p>
<ol>
<li class="">先给域名加 SPF，把现有所有合法发信源列进去。</li>
<li class="">为新发信服务生成 DKIM selector，先只让少量邮件使用它。</li>
<li class="">添加 <code>v=DMARC1; p=none; rua=mailto:dmarc-report@example.com</code> 观察报告。</li>
<li class="">确认所有合法来源都能 SPF 或 DKIM pass，并且与 From 域名 alignment。</li>
<li class="">再逐步收紧到 <code>quarantine</code> 或 <code>reject</code>。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一条可执行的部署路径">一条可执行的部署路径<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#%E4%B8%80%E6%9D%A1%E5%8F%AF%E6%89%A7%E8%A1%8C%E7%9A%84%E9%83%A8%E7%BD%B2%E8%B7%AF%E5%BE%84" class="hash-link" aria-label="一条可执行的部署路径的直接链接" title="一条可执行的部署路径的直接链接" translate="no">​</a></h2>
<p>下面按“自建发信网关，必要时可升级到 direct-send”的思路走。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-明确边界">1. 明确边界<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#1-%E6%98%8E%E7%A1%AE%E8%BE%B9%E7%95%8C" class="hash-link" aria-label="1. 明确边界的直接链接" title="1. 明确边界的直接链接" translate="no">​</a></h3>
<p>先写下四个决策：</p>
<ul>
<li class="">是只发系统邮件，还是要收信？</li>
<li class="">预计每天发送多少封？是否包含营销/订阅？</li>
<li class="">是否必须 direct-send，还是可以走上游 SMTP/API？</li>
<li class="">失败邮件要进入哪里：日志、队列、人工工单，还是自动 suppression list？</li>
</ul>
<p>如果答案是“只发验证码和通知”，不要一开始就上完整 mailcow/Mailu。先做发信网关 + 上游 relay。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-选主机和域名">2. 选主机和域名<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#2-%E9%80%89%E4%B8%BB%E6%9C%BA%E5%92%8C%E5%9F%9F%E5%90%8D" class="hash-link" aria-label="2. 选主机和域名的直接链接" title="2. 选主机和域名的直接链接" translate="no">​</a></h3>
<p>主机最好满足：</p>
<ul>
<li class="">静态公网 IP。</li>
<li class="">可配置 PTR/rDNS。</li>
<li class="">可开放/申请开放 25 出站；如果只走上游 relay，至少要能访问 587/465/HTTPS。</li>
<li class="">不在住宅宽带、动态 IP、廉价高滥用段上。</li>
<li class="">有监控、备份、日志保留和系统更新机制。</li>
</ul>
<p>域名建议单独用子域，例如：</p>
<ul>
<li class=""><code>mail.example.com</code>：服务器主机名。</li>
<li class=""><code>bounce.example.com</code>：退信域。</li>
<li class=""><code>notifications.example.com</code>：系统通知 From 域。</li>
</ul>
<p>这样可以把主站域名、营销域名、事务邮件域名隔离，减少一个通道出问题时影响全部邮件。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-部署-submissiongateway">3. 部署 submission/gateway<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#3-%E9%83%A8%E7%BD%B2-submissiongateway" class="hash-link" aria-label="3. 部署 submission/gateway的直接链接" title="3. 部署 submission/gateway的直接链接" translate="no">​</a></h3>
<p>如果用 Postfix 做 gateway，核心原则是：</p>
<div class="language-ini codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ini codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># /etc/postfix/main.cf 示例，只表达形状，不可直接复制上线</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">myhostname = mail.example.com</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">mydomain = example.com</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">myorigin = $mydomain</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">inet_interfaces = all</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 只允许本机、内网或认证用户提交外部 relay</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">mynetworks = 127.0.0.0/8 [::1]/128 10.0.0.0/24</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 如果走上游 SMTP relay</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">relayhost = [email-smtp.region.amazonaws.com]:587</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">smtp_tls_security_level = encrypt</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">smtp_sasl_auth_enable = yes</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">smtp_sasl_password_maps = lmdb:/etc/postfix/sasl_passwd</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">smtp_sasl_security_options = noanonymous</span><br></div></code></pre></div></div>
<p>上线前要特别检查两件事：</p>
<ol>
<li class="">未认证外部客户端不能向任意外部域 relay。</li>
<li class="">业务系统连接 submission 时必须走 TLS 和认证，不能把 SMTP 密码写进前端或公开仓库。</li>
</ol>
<p>如果你更偏应用层，也可以自写一个 HTTP mail gateway：业务系统 POST 到 gateway，gateway 写队列，再调用 SES/SendGrid API。这比让每个业务服务各自接 SMTP 更容易做审计、幂等、限流和多 provider 切换。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-配-dns-和密钥">4. 配 DNS 和密钥<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#4-%E9%85%8D-dns-%E5%92%8C%E5%AF%86%E9%92%A5" class="hash-link" aria-label="4. 配 DNS 和密钥的直接链接" title="4. 配 DNS 和密钥的直接链接" translate="no">​</a></h3>
<p>至少要配置：</p>
<div class="language-dns codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dns codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">mail.example.com.        A      203.0.113.10</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">10.113.0.203.in-addr.arpa. PTR  mail.example.com.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">example.com.             TXT    "v=spf1 include:amazonses.com -all"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">selector1._domainkey.example.com. TXT "v=DKIM1; k=rsa; p=..."</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">_dmarc.example.com.      TXT    "v=DMARC1; p=none; rua=mailto:dmarc-report@example.com"</span><br></div></code></pre></div></div>
<p>如果 direct-send，不要忽略 PTR：Gmail guidelines 明确要求发送 IP 有对应 PTR，且 PTR hostname 的正向 A/AAAA 要回到同一个发送 IP。[16]</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-做-warm-up-和限流">5. 做 warm-up 和限流<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#5-%E5%81%9A-warm-up-%E5%92%8C%E9%99%90%E6%B5%81" class="hash-link" aria-label="5. 做 warm-up 和限流的直接链接" title="5. 做 warm-up 和限流的直接链接" translate="no">​</a></h3>
<p>新 IP、新域名、新 DKIM selector 都需要信誉爬坡。不要第一天就把所有通知、营销、批量任务都切过来。</p>
<p>建议：</p>
<ul>
<li class="">先发内部测试和低风险事务邮件。</li>
<li class="">按域名限速，例如 Gmail、Outlook、企业域分别限制。</li>
<li class="">hard bounce 立即进入 suppression list，不要反复尝试。[14]</li>
<li class="">投诉地址和 abuse/postmaster 邮箱要有人看。</li>
<li class="">营销/订阅邮件要有退订，且尊重退订。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-验收不是收到了我自己的测试邮件">6. 验收不是“收到了我自己的测试邮件”<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#6-%E9%AA%8C%E6%94%B6%E4%B8%8D%E6%98%AF%E6%94%B6%E5%88%B0%E4%BA%86%E6%88%91%E8%87%AA%E5%B7%B1%E7%9A%84%E6%B5%8B%E8%AF%95%E9%82%AE%E4%BB%B6" class="hash-link" aria-label="6. 验收不是“收到了我自己的测试邮件”的直接链接" title="6. 验收不是“收到了我自己的测试邮件”的直接链接" translate="no">​</a></h3>
<p>真正的验收清单应该是：</p>
<table><thead><tr><th>检查</th><th>怎么看</th></tr></thead><tbody><tr><td>SMTP submission</td><td>认证、TLS、错误密码拒绝、外部未认证 relay 被拒绝</td></tr><tr><td>队列</td><td>模拟上游故障后邮件入队，恢复后能重试</td></tr><tr><td>DNS</td><td>SPF/DKIM/DMARC/PTR/正向解析全部一致</td></tr><tr><td>Headers</td><td>收件箱里 <code>Authentication-Results</code> 显示 SPF/DKIM/DMARC pass</td></tr><tr><td>退信</td><td>不存在邮箱触发 hard bounce，系统能记录并停止后续发送</td></tr><tr><td>投诉</td><td>abuse/postmaster/feedback loop 或 provider webhook 有入口</td></tr><tr><td>限流</td><td>单域名、单用户、单模板、全局频率都有限制</td></tr><tr><td>观测</td><td>能按 message-id 查到提交、入队、投递、退信全过程</td></tr><tr><td>安全</td><td>不是 open relay；凭据可轮换；日志不泄露 token/验证码全文</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="方案对比表">方案对比表<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#%E6%96%B9%E6%A1%88%E5%AF%B9%E6%AF%94%E8%A1%A8" class="hash-link" aria-label="方案对比表的直接链接" title="方案对比表的直接链接" translate="no">​</a></h2>
<table><thead><tr><th>方案</th><th style="text-align:right">自控程度</th><th style="text-align:right">到达率起步</th><th style="text-align:right">运维成本</th><th>最适合</th></tr></thead><tbody><tr><td>托管 SMTP/API</td><td style="text-align:right">低到中</td><td style="text-align:right">高</td><td style="text-align:right">低</td><td>产品早期、交易邮件、告警</td></tr><tr><td>自建 gateway + 上游 relay</td><td style="text-align:right">中</td><td style="text-align:right">高</td><td style="text-align:right">中</td><td>多服务共享发信能力、需要审计/模板/限流</td></tr><tr><td>自建 direct-send MTA</td><td style="text-align:right">高</td><td style="text-align:right">低到中，需要爬坡</td><td style="text-align:right">高</td><td>明确要控制投递链路且有运维能力</td></tr><tr><td>完整邮箱套件</td><td style="text-align:right">高</td><td style="text-align:right">中，取决于 IP/DNS/策略</td><td style="text-align:right">高</td><td>自托管团队邮箱、社区邮箱</td></tr><tr><td>mail sink / 测试 SMTP</td><td style="text-align:right">仅内部</td><td style="text-align:right">不投递</td><td style="text-align:right">低</td><td>本地、CI、预发测试</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="我的建议">我的建议<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#%E6%88%91%E7%9A%84%E5%BB%BA%E8%AE%AE" class="hash-link" aria-label="我的建议的直接链接" title="我的建议的直接链接" translate="no">​</a></h2>
<p>如果目标是给 ChatArch 这类服务发邮件，我会按三阶段做：</p>
<ol>
<li class=""><strong>第一阶段：托管 SMTP/API 直连</strong>。先把 SPF/DKIM/DMARC 和 bounce webhook 打通，确保产品能发验证码、通知、告警。</li>
<li class=""><strong>第二阶段：自建 mail gateway</strong>。所有服务只连 gateway；gateway 统一模板、限流、队列、日志、退信和 provider 路由。</li>
<li class=""><strong>第三阶段：选择性 direct-send 或完整邮箱</strong>。只有当确实需要摆脱上游、拥有固定 IP/PTR、能维护 reputation 和反垃圾体系时，再引入 Postfix direct-send 或 mailcow/Mailu/Docker Mailserver 这类完整套件。</li>
</ol>
<p>换句话说，“自建 mail service”的正确起点通常不是“我要拥有一台全功能邮箱服务器”，而是“我要拥有一条可观测、可替换、不会丢信、不会变成 open relay 的发信链路”。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="sources">Sources<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mail-service-deployment-options#sources" class="hash-link" aria-label="Sources的直接链接" title="Sources的直接链接" translate="no">​</a></h2>
<p>[2] <a href="https://www.rfc-editor.org/rfc/rfc6409.html" target="_blank" rel="noopener noreferrer" class="">https://www.rfc-editor.org/rfc/rfc6409.html</a> — RFC 6409: Message Submission for Mail
[3] <a href="https://www.rfc-editor.org/rfc/rfc8314.html" target="_blank" rel="noopener noreferrer" class="">https://www.rfc-editor.org/rfc/rfc8314.html</a> — RFC 8314: Cleartext Considered Obsolete
[7] <a href="https://www.postfix.org/BASIC_CONFIGURATION_README.html" target="_blank" rel="noopener noreferrer" class="">https://www.postfix.org/BASIC_CONFIGURATION_README.html</a> — Postfix Basic Configuration
[8] <a href="https://www.postfix.org/postconf.5.html" target="_blank" rel="noopener noreferrer" class="">https://www.postfix.org/postconf.5.html</a> — Postfix smtpd_relay_restrictions
[12] <a href="https://maddy.email/" target="_blank" rel="noopener noreferrer" class="">https://maddy.email</a> — maddy documentation
[13] <a href="https://mailinabox.email/guide.html" target="_blank" rel="noopener noreferrer" class="">https://mailinabox.email/guide.html</a> — Mail-in-a-Box guide
[14] <a href="https://docs.aws.amazon.com/ses/latest/dg/send-email-concepts-deliverability.html" target="_blank" rel="noopener noreferrer" class="">https://docs.aws.amazon.com/ses/latest/dg/send-email-concepts-deliverability.html</a> — Amazon SES deliverability concepts
[15] <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-resource-limits.html" target="_blank" rel="noopener noreferrer" class="">https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-resource-limits.html</a> — AWS EC2 port 25 throttling
[16] <a href="https://support.google.com/a/answer/81126" target="_blank" rel="noopener noreferrer" class="">https://support.google.com/a/answer/81126</a> — Google email sender guidelines
[17] <a href="https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/master/README.md" target="_blank" rel="noopener noreferrer" class="">https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/master/README.md</a> — Docker Mailserver README
[19] <a href="https://raw.githubusercontent.com/Mailu/Mailu/master/README.md" target="_blank" rel="noopener noreferrer" class="">https://raw.githubusercontent.com/Mailu/Mailu/master/README.md</a> — Mailu README</p>]]></content>
        <category label="email" term="email"/>
        <category label="smtp" term="smtp"/>
        <category label="mail-server" term="mail-server"/>
        <category label="devops" term="devops"/>
        <category label="chatarch" term="chatarch"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[看懂 Mattermost：频道、帖子、Thread、私聊，以及它和 Slack/飞书哪里不一样]]></title>
        <id>https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-posting-model-slack-comparison</id>
        <link href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-posting-model-slack-comparison"/>
        <updated>2026-08-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[面向刚开始使用 Mattermost 的 ChatArch 用户，解释 team、channel、post、thread、DM 的结构，比较 Mattermost、Slack 和飞书的消息触发心智，并给出 Agent 工作间里的推荐用法。]]></summary>
        <content type="html"><![CDATA[<p>第一次从飞书切到 Mattermost，最容易卡住的不是“怎么发消息”，而是：<strong>我现在到底站在哪一层？这是一个频道、一条帖子、一个 thread，还是一次私聊？</strong></p>
<p>这篇文章专门回答这个问题。我们不讲部署，不讲数据库，也不讲 token；只把 Mattermost 的信息结构、发帖机制、thread 语义，以及它和 Slack、飞书在机器人触发上的差别讲清楚。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>一句话结论</div><div class="admonitionContent_BuS1"><p>Mattermost 更像 Slack/Discord 式的 <strong>workspace/team -&gt; channel -&gt; post -&gt; thread</strong> 模型，而不是飞书里“群聊/私聊 + 话题/卡片/文档”混在一起的组织入口。把 Agent 放进 Mattermost 时，推荐默认 UX 是：<strong>频道里 <code>@hermes-agent</code>，thread 里继续 <code>@hermes-agent</code>，私聊 DM 里不用 @。</strong></p></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>ChatArch 当前口径</div><div class="admonitionContent_BuS1"><p>ChatArch 当前维护的 Mattermost 公网入口是：</p><p><a href="https://matter.public.wzhecnu.cn/" target="_blank" rel="noopener noreferrer" class="">https://matter.public.wzhecnu.cn/</a></p><p>上一篇文章已经解释过为什么 Mattermost 适合承担 ChatArch 的自托管实时 Agent 工作间，以及 Hermes 如何通过 Mattermost Gateway 接入频道、thread 和 DM。[12] 本文只讨论使用模型和消息结构。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="先把层级摆正">先把层级摆正<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-posting-model-slack-comparison#%E5%85%88%E6%8A%8A%E5%B1%82%E7%BA%A7%E6%91%86%E6%AD%A3" class="hash-link" aria-label="先把层级摆正的直接链接" title="先把层级摆正的直接链接" translate="no">​</a></h2>
<p>Mattermost 的基本结构可以先按五层理解：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Mattermost server / workspace</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Team</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Channel</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      Post</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        Thread replies</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Direct Message / Group Message channel</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      Post</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        Thread replies</span><br></div></code></pre></div></div>
<p>第一层是一个 Mattermost server，也就是你打开的站点。ChatArch 当前入口是 <code>matter.public.wzhecnu.cn</code>。</p>
<p>第二层是 <strong>Team</strong>。Mattermost 的用户文档把 team 当成协作空间来组织成员、频道和团队设置。[1] 对用户来说，team 通常就是左上角那个团队空间；对 Agent 来说，它是权限、成员关系和链接路径的一部分。</p>
<p>第三层是 <strong>Channel</strong>。Mattermost 的 channel 类型包括 public channel、private channel、direct message 和 group message；它不是只有“群聊频道”一种东西。[2] 这点很重要：<strong>DM 在 Mattermost 里本质上也是一种 channel，只是 UI 上表现为两个人的私聊。</strong></p>
<p>第四层是 <strong>Post</strong>。你在频道里发的一条顶层消息，就是一个 post。Mattermost 文档把发消息、格式化、附件、草稿和发送行为都归在 message/post 这条线上。[5]</p>
<p>第五层是 <strong>Thread</strong>。当你回复某一条 post 时，Mattermost 会围绕那条 root post 组织一串 threaded discussions；官方文档把 threaded discussions 作为组织对话、保持频道主时间线干净的一种方式。[4] 所以 thread 不是一个新的频道，而是“围绕某条消息展开的一组回复”。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="channel-不是飞书群聊的简单替代">Channel 不是飞书群聊的简单替代<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-posting-model-slack-comparison#channel-%E4%B8%8D%E6%98%AF%E9%A3%9E%E4%B9%A6%E7%BE%A4%E8%81%8A%E7%9A%84%E7%AE%80%E5%8D%95%E6%9B%BF%E4%BB%A3" class="hash-link" aria-label="Channel 不是飞书群聊的简单替代的直接链接" title="Channel 不是飞书群聊的简单替代的直接链接" translate="no">​</a></h2>
<p>飞书里常见心智是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">群聊：很多人在一个聊天流里说话，@bot 才触发</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">私聊：你和 bot 一对一说话，不用 @</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">话题/文档/卡片：是同一个工作空间里的扩展对象</span><br></div></code></pre></div></div>
<p>Mattermost 更强调 channel。一个 channel 可以是公开频道，也可以是私有频道；也可以是 direct message 或 group message 这种非公开会话。[2] 这会带来三个使用差异。</p>
<p>第一，频道天然适合按任务和领域拆分。比如 <code>agent-lab</code> 可以放 Agent 调试，<code>chatrss-triggers</code> 可以放事件触发，<code>ops</code> 可以放服务运维。Mattermost 文档也把 channel 作为协作的主要空间来组织成员、消息和上下文。[3]</p>
<p>第二，频道里的普通消息不一定应该被机器人读取。一个 Agent 如果监听所有频道消息，很快就会变成噪音放大器：别人只是闲聊，bot 却开始规划任务。因此 ChatArch 当前建议：<strong>普通频道里默认要显式 <code>@hermes-agent</code>。</strong> Mattermost 自身也提供 mention 机制，让用户在消息中点名具体的人或对象。[7]</p>
<p>第三，频道主时间线应该保持干净。长任务、调试、代码输出、日志摘要都适合落到 thread 里，而不是把频道刷屏。Mattermost 的 threaded discussions 正是为这种“围绕一条消息展开上下文”的模式设计的。[4]</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="post-和-threadmattermost-里最关键的一对概念">Post 和 Thread：Mattermost 里最关键的一对概念<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-posting-model-slack-comparison#post-%E5%92%8C-threadmattermost-%E9%87%8C%E6%9C%80%E5%85%B3%E9%94%AE%E7%9A%84%E4%B8%80%E5%AF%B9%E6%A6%82%E5%BF%B5" class="hash-link" aria-label="Post 和 Thread：Mattermost 里最关键的一对概念的直接链接" title="Post 和 Thread：Mattermost 里最关键的一对概念的直接链接" translate="no">​</a></h2>
<p>如果只记一件事，请记这个：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">post 是入口；thread 是上下文。</span><br></div></code></pre></div></div>
<p>你在频道里发一条顶层消息：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">@hermes-agent 帮我看一下 ChatRSS 现在在做什么。</span><br></div></code></pre></div></div>
<p>这条消息就是 root post。Hermes 如果回复到这条消息下面，就形成一个 thread：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Channel: agent-lab</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Post: @hermes-agent 帮我看一下 ChatRSS 现在在做什么</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Thread:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      hermes-agent: 我看到 ChatRSS 当前是 trigger-router-action framework...</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      用户: 能跑通吗？举个例子</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      hermes-agent: 可以，我开一个真实帖子并读回回复...</span><br></div></code></pre></div></div>
<p>这和飞书群聊里“消息一直往下滚”的感觉不同。Mattermost 的 thread 让一次任务有一个明确的 root：</p>
<ul>
<li class="">这个 thread 在讲哪件事；</li>
<li class="">谁触发了它；</li>
<li class="">bot 的回复是否回到了同一个上下文；</li>
<li class="">后续追问是否仍然围绕同一件事。</li>
</ul>
<p>Mattermost 官方文档也把 reply 作为对消息进行回应的动作，而 threaded discussions 用来组织这些回应。[4][6]</p>
<p>对 Agent 来说，这个结构尤其有用。因为 Agent run 往往不只是一问一答：它可能会查代码、跑命令、写文档、生成链接，再等待用户确认。把这些都塞进一个频道主时间线会很乱；放在 thread 里，任务边界就清楚了。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="dm私聊也是-channel但触发心智应该像飞书私聊">DM：私聊也是 channel，但触发心智应该像飞书私聊<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-posting-model-slack-comparison#dm%E7%A7%81%E8%81%8A%E4%B9%9F%E6%98%AF-channel%E4%BD%86%E8%A7%A6%E5%8F%91%E5%BF%83%E6%99%BA%E5%BA%94%E8%AF%A5%E5%83%8F%E9%A3%9E%E4%B9%A6%E7%A7%81%E8%81%8A" class="hash-link" aria-label="DM：私聊也是 channel，但触发心智应该像飞书私聊的直接链接" title="DM：私聊也是 channel，但触发心智应该像飞书私聊的直接链接" translate="no">​</a></h2>
<p>用户最自然的期待是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">群聊里需要 @bot；私聊 bot 不需要 @。</span><br></div></code></pre></div></div>
<p>这个期待是合理的。Mattermost 里 DM 技术上是 direct message channel，[2] 但产品心智上它就是“我正在和这个 bot 私聊”。所以 ChatArch 当前 Hermes/Mattermost 的目标 UX 是：</p>
<table><thead><tr><th>场景</th><th style="text-align:right">是否需要 @</th><th>推荐回复位置</th></tr></thead><tbody><tr><td>DM 私聊 <code>hermes-agent</code></td><td style="text-align:right">不需要</td><td>当前实测会进入该 DM 消息的 thread</td></tr><tr><td>频道顶层消息</td><td style="text-align:right">需要 <code>@hermes-agent</code></td><td>回复到这条消息的 thread</td></tr><tr><td>已有 channel thread</td><td style="text-align:right">建议继续 <code>@hermes-agent</code></td><td>回复同一个 thread</td></tr><tr><td>普通频道里不 @ 的消息</td><td style="text-align:right">不触发</td><td>避免误读频道聊天</td></tr></tbody></table>
<p>我们在 ChatArch Mattermost 上已经做过一次真实验证：用普通调试账号与 <code>hermes-agent</code> 建立 DM channel，不带 <code>@</code> 发送消息，bot 成功回复；随后在同一个 DM thread 里继续不带 <code>@</code> 追问，也能继续回复。公开文章只记录这个行为结论，不记录账号、token、用户 ID 或任何私有路径。</p>
<p>这里有一个 UX 细节：<strong>当前 Hermes Mattermost reply mode 会把 DM 回复也挂进 thread。</strong> 也就是说，DM 不是像飞书私聊那样完全平铺成一条条消息，而是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">DM with hermes-agent</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  用户: 帮我看一下这个问题</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Thread:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      hermes-agent: 我收到了这条 DM</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      用户: 继续测试，不带 @</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      hermes-agent: 这是同一个私聊 thread 里的连续对话</span><br></div></code></pre></div></div>
<p>这不是 Mattermost 的唯一可能模式，而是当前 gateway 的回复策略。未来如果希望“频道里保持 thread，DM 里平铺普通消息”，可以把 reply policy 调成：channel replies use thread，DM replies use direct channel posts。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="和-slack-比相似但身份和事件入口不同">和 Slack 比：相似，但身份和事件入口不同<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-posting-model-slack-comparison#%E5%92%8C-slack-%E6%AF%94%E7%9B%B8%E4%BC%BC%E4%BD%86%E8%BA%AB%E4%BB%BD%E5%92%8C%E4%BA%8B%E4%BB%B6%E5%85%A5%E5%8F%A3%E4%B8%8D%E5%90%8C" class="hash-link" aria-label="和 Slack 比：相似，但身份和事件入口不同的直接链接" title="和 Slack 比：相似，但身份和事件入口不同的直接链接" translate="no">​</a></h2>
<p>Mattermost 的使用体验常常会被描述成 Slack-like，这个类比大体成立，但不能直接等同。</p>
<p>Slack 的 Conversations API 把 public channel、private channel、DM、multi-person DM 等都放在 conversations 这套抽象里处理。[8] Slack 还有明确的 <code>app_mention</code> event，用来表示 app/bot 在频道里被提到；同时也有通用 <code>message</code> event，用来接收消息类事件。[9][10]</p>
<p>这和 Mattermost 很像：两者都可以把“频道消息、私聊消息、thread 回复”看成平台事件，再由 bot/adapter 决定是否响应。</p>
<p>但差别也很明显：</p>
<table><thead><tr><th>维度</th><th>Slack</th><th>Mattermost</th></tr></thead><tbody><tr><td>平台形态</td><td>SaaS workspace</td><td>可自托管 server/workspace</td></tr><tr><td>应用身份</td><td>Slack App / Bot User / scopes / installation</td><td>Mattermost bot account / server API 访问身份</td></tr><tr><td>事件入口</td><td>Events API、Socket Mode、Web API</td><td>WebSocket event stream、REST API、webhook/command 可选</td></tr><tr><td>私聊心智</td><td>App DM 通常可直接对话，取决于 app scopes/event subscription</td><td>DM 是 direct channel，adapter 可把它设为免 @</td></tr><tr><td>频道触发</td><td>常见是 <code>@app</code> / <code>app_mention</code></td><td>常见是 <code>@bot</code> / mention gating</td></tr><tr><td>thread 结构</td><td><code>thread_ts</code> 组织回复；Slack 文档也单独说明了检索消息和 thread replies 的 API 行为。[11]</td><td>root post + thread replies；Mattermost 用户文档把 threaded discussions 作为组织对话的方式。[4]</td></tr></tbody></table>
<p>所以，如果你熟悉 Slack，可以把 Mattermost 初步理解成：“类似 Slack 的 workspace/channel/thread/bot 体验，但平台可以自己托管，bot 身份更接近这个 server 里的一个用户”。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="和飞书比飞书是组织入口mattermost-是-agent-工作间">和飞书比：飞书是组织入口，Mattermost 是 Agent 工作间<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-posting-model-slack-comparison#%E5%92%8C%E9%A3%9E%E4%B9%A6%E6%AF%94%E9%A3%9E%E4%B9%A6%E6%98%AF%E7%BB%84%E7%BB%87%E5%85%A5%E5%8F%A3mattermost-%E6%98%AF-agent-%E5%B7%A5%E4%BD%9C%E9%97%B4" class="hash-link" aria-label="和飞书比：飞书是组织入口，Mattermost 是 Agent 工作间的直接链接" title="和飞书比：飞书是组织入口，Mattermost 是 Agent 工作间的直接链接" translate="no">​</a></h2>
<p>飞书和 Mattermost 最大的差异不是“谁也有群聊”，而是产品中心不同。</p>
<p>飞书更像一个组织入口：群聊、文档、日历、审批、任务、卡片、知识库都在同一个办公套件里。它的机器人心智也很成熟：群聊里 @，私聊里不用 @，卡片和回调可以承载很复杂的交互。</p>
<p>Mattermost 更像一个自托管实时工作间。它的核心不是文档套件，而是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">team -&gt; channel -&gt; post -&gt; thread -&gt; bot reply</span><br></div></code></pre></div></div>
<p>这让 Mattermost 特别适合做 Agent room：</p>
<ul>
<li class="">每个项目、任务线或事件源一个 channel；</li>
<li class="">每个具体任务一条 root post；</li>
<li class="">Agent 的长回复、工具执行结果和追问都进 thread；</li>
<li class="">私聊用于个人调试、临时提问和低干扰入口；</li>
<li class="">是否进入 ChatRSS / TriggerEvent / ledger，由任务是否需要跨平台审计决定。</li>
</ul>
<p>这也解释了为什么上一篇文章说 Mattermost 适合做 ChatArch 的自托管实时 Agent 工作间，而 ChatRSS 不需要进入实时聊天的 happy path。[12] 如果你只是想叫 Hermes 到一个房间里干活，Mattermost gateway 就够了；如果你要把 Mattermost、RSSHub、Discourse、GitHub、Zulip 等事件统一成可审计的 workflow，才需要 ChatRSS 那层 trigger-router-action 基础设施。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="推荐的-chatarch-使用约定">推荐的 ChatArch 使用约定<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-posting-model-slack-comparison#%E6%8E%A8%E8%8D%90%E7%9A%84-chatarch-%E4%BD%BF%E7%94%A8%E7%BA%A6%E5%AE%9A" class="hash-link" aria-label="推荐的 ChatArch 使用约定的直接链接" title="推荐的 ChatArch 使用约定的直接链接" translate="no">​</a></h2>
<p>为了让人和 Agent 都不混乱，ChatArch 可以先采用四条约定。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-channel-按任务空间命名">1. Channel 按任务空间命名<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-posting-model-slack-comparison#1-channel-%E6%8C%89%E4%BB%BB%E5%8A%A1%E7%A9%BA%E9%97%B4%E5%91%BD%E5%90%8D" class="hash-link" aria-label="1. Channel 按任务空间命名的直接链接" title="1. Channel 按任务空间命名的直接链接" translate="no">​</a></h3>
<p>不要把所有事情都塞进一个 <code>general</code>。更适合 Agent 的频道命名是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">agent-lab</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">chatrss-triggers</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ops-runtime</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">blog-drafts</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cc-connect</span><br></div></code></pre></div></div>
<p>这样 Agent 被 @ 时，channel 名本身就是上下文提示。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-任务从-root-post-开始">2. 任务从 root post 开始<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-posting-model-slack-comparison#2-%E4%BB%BB%E5%8A%A1%E4%BB%8E-root-post-%E5%BC%80%E5%A7%8B" class="hash-link" aria-label="2. 任务从 root post 开始的直接链接" title="2. 任务从 root post 开始的直接链接" translate="no">​</a></h3>
<p>发一个清楚的顶层 post：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">@hermes-agent 请检查 ChatRSS 现在的 trigger-router-action 流程，给一个可跑通例子。</span><br></div></code></pre></div></div>
<p>后续不要另开一堆散消息；在这个 post 的 thread 里继续追问。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-频道里显式-dm-里直接说">3. 频道里显式 @，DM 里直接说<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-posting-model-slack-comparison#3-%E9%A2%91%E9%81%93%E9%87%8C%E6%98%BE%E5%BC%8F-dm-%E9%87%8C%E7%9B%B4%E6%8E%A5%E8%AF%B4" class="hash-link" aria-label="3. 频道里显式 @，DM 里直接说的直接链接" title="3. 频道里显式 @，DM 里直接说的直接链接" translate="no">​</a></h3>
<p>默认规则：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">频道 / thread：@hermes-agent</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">DM：不用 @</span><br></div></code></pre></div></div>
<p>这样既保留飞书私聊的低摩擦，又避免频道里 bot 被所有消息唤醒。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-大任务进-thread结论回主线">4. 大任务进 thread，结论回主线<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-posting-model-slack-comparison#4-%E5%A4%A7%E4%BB%BB%E5%8A%A1%E8%BF%9B-thread%E7%BB%93%E8%AE%BA%E5%9B%9E%E4%B8%BB%E7%BA%BF" class="hash-link" aria-label="4. 大任务进 thread，结论回主线的直接链接" title="4. 大任务进 thread，结论回主线的直接链接" translate="no">​</a></h3>
<p>如果一个 thread 里跑出了可复用结论，可以最后人工或由 Agent 汇总一条短结论回频道主线：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">结论：DM 免 @ 已验证；当前 reply mode 会让 DM 回复进入 thread。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">后续：如需飞书式平铺私聊，调整 Mattermost gateway 的 DM reply policy。</span><br></div></code></pre></div></div>
<p>这样频道主线像目录，thread 像工作日志。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="给-agent-adapter-的设计启发">给 Agent adapter 的设计启发<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-posting-model-slack-comparison#%E7%BB%99-agent-adapter-%E7%9A%84%E8%AE%BE%E8%AE%A1%E5%90%AF%E5%8F%91" class="hash-link" aria-label="给 Agent adapter 的设计启发的直接链接" title="给 Agent adapter 的设计启发的直接链接" translate="no">​</a></h2>
<p>这篇虽然是使用指南，但对 connector/adapter 也有直接启发。</p>
<p>如果以后给 CC Connect 或 ChatRSS 增加 Mattermost adapter，至少要保留这些字段：</p>
<table><thead><tr><th>字段</th><th>为什么重要</th></tr></thead><tbody><tr><td>team/server</td><td>多团队或多入口隔离</td></tr><tr><td>channel id/type</td><td>区分 public/private/DM/group message</td></tr><tr><td>root post id</td><td>判断 thread 边界</td></tr><tr><td>post id</td><td>去重和回写定位</td></tr><tr><td>sender id</td><td>allowlist、审计、会话隔离</td></tr><tr><td>mention state</td><td>决定频道消息是否触发 bot</td></tr><tr><td>reply target</td><td>决定写回 channel 还是 thread</td></tr></tbody></table>
<p>换句话说，Mattermost adapter 不能只拿一段文本；它要保留“这段话从哪里来、是否在 thread 里、是否是 DM、是否点名 bot、应该回到哪里”。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="最后用一句话区分三者">最后：用一句话区分三者<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-posting-model-slack-comparison#%E6%9C%80%E5%90%8E%E7%94%A8%E4%B8%80%E5%8F%A5%E8%AF%9D%E5%8C%BA%E5%88%86%E4%B8%89%E8%80%85" class="hash-link" aria-label="最后：用一句话区分三者的直接链接" title="最后：用一句话区分三者的直接链接" translate="no">​</a></h2>
<p>如果只想快速建立心智，可以这样记：</p>
<table><thead><tr><th>平台</th><th>一句话心智</th></tr></thead><tbody><tr><td>飞书</td><td>组织协作入口：群聊 @，私聊免 @，文档/卡片/审批强</td></tr><tr><td>Slack</td><td>SaaS 团队聊天：workspace/channel/thread/app event 模型成熟</td></tr><tr><td>Mattermost</td><td>自托管 Agent 工作间：server/team/channel/post/thread/DM，适合把 Hermes 放进自己的房间</td></tr></tbody></table>
<p>所以，Mattermost 不是飞书的替代品，也不只是 Slack 的开源版。对 ChatArch 来说，它最有价值的位置是：<strong>一个可自托管、可被 API 读写、以 channel 和 thread 管理上下文的人机协作房间。</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="sources">Sources<a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-posting-model-slack-comparison#sources" class="hash-link" aria-label="Sources的直接链接" title="Sources的直接链接" translate="no">​</a></h2>
<p>[1] <a href="https://docs.mattermost.com/end-user-guide/collaborate/organize-using-teams.html" target="_blank" rel="noopener noreferrer" class="">https://docs.mattermost.com/end-user-guide/collaborate/organize-using-teams.html</a>
[2] <a href="https://docs.mattermost.com/end-user-guide/collaborate/channel-types.html" target="_blank" rel="noopener noreferrer" class="">https://docs.mattermost.com/end-user-guide/collaborate/channel-types.html</a>
[3] <a href="https://docs.mattermost.com/end-user-guide/collaborate/collaborate-within-channels.html" target="_blank" rel="noopener noreferrer" class="">https://docs.mattermost.com/end-user-guide/collaborate/collaborate-within-channels.html</a>
[4] <a href="https://docs.mattermost.com/end-user-guide/collaborate/organize-conversations.html" target="_blank" rel="noopener noreferrer" class="">https://docs.mattermost.com/end-user-guide/collaborate/organize-conversations.html</a>
[5] <a href="https://docs.mattermost.com/end-user-guide/collaborate/send-messages.html" target="_blank" rel="noopener noreferrer" class="">https://docs.mattermost.com/end-user-guide/collaborate/send-messages.html</a>
[6] <a href="https://docs.mattermost.com/end-user-guide/collaborate/reply-to-messages.html" target="_blank" rel="noopener noreferrer" class="">https://docs.mattermost.com/end-user-guide/collaborate/reply-to-messages.html</a>
[7] <a href="https://docs.mattermost.com/end-user-guide/collaborate/mention-people.html" target="_blank" rel="noopener noreferrer" class="">https://docs.mattermost.com/end-user-guide/collaborate/mention-people.html</a>
[8] <a href="https://docs.slack.dev/apis/web-api/using-the-conversations-api" target="_blank" rel="noopener noreferrer" class="">https://docs.slack.dev/apis/web-api/using-the-conversations-api</a>
[9] <a href="https://docs.slack.dev/reference/events/app_mention" target="_blank" rel="noopener noreferrer" class="">https://docs.slack.dev/reference/events/app_mention</a>
[10] <a href="https://docs.slack.dev/reference/events/message" target="_blank" rel="noopener noreferrer" class="">https://docs.slack.dev/reference/events/message</a>
[11] <a href="https://docs.slack.dev/messaging/retrieving-messages" target="_blank" rel="noopener noreferrer" class="">https://docs.slack.dev/messaging/retrieving-messages</a>
[12] <a href="https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-hermes-agent-workspace" target="_blank" rel="noopener noreferrer" class="">https://arch.gh.wzhecnu.cn/ChatBlog/blog/mattermost-hermes-agent-workspace</a></p>]]></content>
        <category label="mattermost" term="mattermost"/>
        <category label="slack" term="slack"/>
        <category label="feishu" term="feishu"/>
        <category label="agent" term="agent"/>
        <category label="chatarch" term="chatarch"/>
    </entry>
</feed>