🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI
广告☁️ 云服务器特惠阿里云首购 8 折 · 腾讯云合作特惠
DeepSeek Harness Hub
← 返回列表

linxuhao/Deepseek-Continuity

MCP兼容 / 相关生态spec-screened扫描:无法判定在 GitHub 查看 ↗
需源码安装

场记 / Continuity

暂不能直接安装(需源码编译或环境不满足):缺少 main/exports/bin 入口声明。 · 最近上游提交 2026/9/21 · 已提供中文文档

DeepSeek Harness 插件,用于本地图像 / 语音 / 音乐 / 音效生成:同一角色在多次调用中始终保持一致,退化输出会被拒绝而非返回,空闲时 GPU 完全不被占用。若将图像部分交给任何 OpenAI 形态的 API,其引擎和 10.1 GiB 权重便永远不会被安装。

综合分
32.1
GitHub 分
32.1
用户评分
—
★ Stars
3
周下载量
—
兼容 / 相关生态插件(非 dsh 原生,请按其对应运行时安装)
git clone https://github.com/linxuhao/Deepseek-Continuity.git
信任档位:已验证本站已于 3 天前真实安装成功(L4 · 真实安装)
是什么
生态应用(桌面端 / Web 外壳,不以 dsh plugin add 安装)
装得上吗
本站已真实安装成功(L4 · 真实安装,非静态推断)
安全吗
本站尚未对该插件做风险分级(暂未覆盖,不等同于无风险)
还在维护吗
活跃:最近一次提交在 4 天前

档位由下列信号合成:本站实装验证(真实安装,当前最高到 L4)· 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。下方各区块是它的证据明细。 验证判据与等级说明 →

🟢实装验证通过· 2026/9/23
由本站实装验证器在真实 dsh 环境安装成功,非静态推断。
数据截至 2026/9/23(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
安装兼容性检查需源码安装

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-plugin-continuity @ 0.5.6
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✗入口文件缺少入口声明

缺少 main/exports/bin 入口声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后验证 2026/9/20 11:29:05

用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动

README

由 DeepSeek 最新模型翻译生成
场记 / Continuity

一个 DeepSeek Harness 插件,为 agent 提供本地图像 / 语音 / 音乐 / 音效生成能力,通过转录听到自己输出的声音,并记住自己创作的内容——同一个角色在每次调用中都保持一致,失败的生成绝不会被当作成功蒙混过关。

本地运行。模型按请求惰性加载,空闲时释放,因此当你不使用它时,GPU 完全不受影响——实测常驻内存 0.21 GiB。你可以在同一块显卡上玩游戏。

或者只保留你想要本地化的那一半:图像生成支持任何 OpenAI 形式的 /v1/images/ API,转录支持任何 OpenAI 形式的 /v1/audio/transcriptions,只要告诉 continuity-setup 即可,这样那一半的引擎和权重就永远不会被下载——8 GiB 显存门槛也随图像那一半一起消失。参见自带后端。

场记是电影片场的连贯性监督。他们的全部工作就是两件事:确保每次拍摄之间服装、发型和道具保持一致,并在片场发现错误,避免它被剪进成片。这正是本插件的职责。

效果展示

一次 create_character 调用固定了这张脸。之后的一切都只是携带一个场景的 subject_image 调用——无需手动传递参考图像,无需重新描述角色,无需修图。以下就是工具返回的文件。

create_character — 后续每次调用都以此作为参照基准
“特写肖像,由下方的灯笼照亮”
“严格侧面,黄昏时分的悬崖边”
“从背后,回头越过肩膀看,雪”

“坐在篝火旁,擦拭她的护手,低角度”
“wearing heavy red lacquered plate armour”
“as a stark black and white woodcut print”

失明的右眼、穿过眉骨的伤疤、骨质吊坠和 brass gauntlet 在七张图中都保持一致。这正是关键所在:同一段描述通过 generate_image 每次都会生成不同的女人,这就是为什么一个游戏最终会有三个主角。

有两件事它没有做到,保留在这里是因为一个只展示成功案例的演示无法让你了解这个工具:

- 风格请求只部分生效。 木刻风格成功了。“Pixel-art sprite”被要求了两次,两次都被忽略——参考图像主导了输出的风格,而这正是让面部保持一致的机制。
- 盔甲是叠加的,而不是替换的。 面部和 gauntlet 保持了一致,但红色板甲是覆盖在灰色外套上,而不是替换它。

声音也是如此

create_actor 一次,然后每行台词调用一次 actor_tts。每行台词都作为独立的 24 kHz 单声道 WAV 返回;这里将它们合并成一个片段,因为 GitHub 无法内联播放 .wav。

Kestrel — three lines, one voice

▶ 播放这段 19 秒的片段 — GitHub 会从 README 中移除 ,所以上面的图片是静态图,链接会打开 GitHub 自己的播放器。

别碰那扇门。上一个碰它的人,我埋在山下第三棵松树底下。

我这只眼睛看不见,可另一只看得比你清楚。

拿上灯,跟紧我。这条路我走过十七次,没有哪一次是一样的。

三行不同的台词,三种不同的长度,同一个声音。通过 generate_speech——相同的语音描述,没有 actor——这三行台词会是三个不同的人;这一说法的依据在它实际能做到的两件事中。

它能做什么

| | 工具 | |
|---|---|---|
| 外观 | create_character create_animal create_object import_subject subject_image | 固定一个角色、动物或道具一次;之后每张图像都是同一个 |
| 声音 | create_actor import_actor actor_tts | 选定一个声音一次;之后每行台词都是那个声音 |
| 听觉 | transcribe | 将 WAV 读回为文本——包括这个插件刚刚生成的 WAV |
| 音乐 | generate_music | Stable Audio,最长 120 秒,无循环点——为场景配乐,而不是 BGM 循环 |
| 音效 | gen_sfx sfx_presets | 程序化 sfxr:毫秒级,给定种子后字节完全一致,完全不需要模型和 VRAM |
| 一次性工具 | generate_image generate_speech | 用于那些永不重复出现的东西;它们自己的描述就是这么说的,并指回固定工具 |
| 后处理 | remove_bg slice_sheet | 真正的 RGBA 抠图(CPU),以及把网格图切成单帧 |
| 状态 | continuity_status | 哪些引擎在运行、哪些能力已开启、资源存放在哪里 |

其中有两个是读入而非写出,而它们正是人们会忽略的:

- import_actor / import_subject 固定你已经拥有的东西——一个真实演员的
录音、一张在别处绘制的角色设定图——而下游的一切都与原生选角完全相同(实测:导入的演员
对原生演员的跟踪精度达到 11 Hz)。
- transcribe 闭合了循环。一句吞掉了最后两个词的克隆台词听起来完全正常;
只有当你把它读回来并与脚本对比时才会发现。这也是在你没有转录文本时,为导入的录音
补全转录的方式(ASR 模型按需加载,并随其余部分一起卸载——驻留时 3.05 GB,处理
9.5 秒音频耗时 0.4 秒)。

上表是简版——全部 21 个工具,已分组,见 工具。

安装

uvx --from dsh-continuity continuity-setup

这一条命令完成整个后端:预检 → 构建引擎 → 只获取这台机器能用的权重
→ 启动它们。

PyPI 发行版是 dsh-continuity
(导入名仍是 continuity_mcp)。它不是* continuity-mcp——PyPI 上的那个名字
属于一个无关项目,所以不要 uvx continuity-mcp。

若要从源码运行:uvx --from git+https://github.com/linxuhao/Deepseek-Continuity continuity-setup

然后把插件添加到你的 dsh profile。dsh plugin 会调用 pnpm,所以如果还没装就先装上
(corepack enable pnpm);没有它,命令会停在
pnpm not found on PATH:

dsh plugin --profile  add dsh-plugin-continuity

把它添加到一个已经有 app bundle 的 profile。如果你把它指向一个新 profile,dsh
会创建一个只包含 @deepseek-ai/dsh-base 加这个插件的 profile——没有 app,所以启动它
什么也不做并且会挂起。你自己在
~/.dsh/profiles//package.json 中添加 app:

"dsh": { "profile": { "bundles": [
"@deepseek-ai/dsh-base", "@deepseek-ai/dsh-headless", "dsh-plugin-continuity"
] } }

该 bundle 从环境变量读取设置,所以在启动 profile 之前,导出 continuity-setup 为你的机器
打印的内容:

export CONTINUITY_STATE_DIR=~/.continuity
export CONTINUITY_SD_SERVER=http://127.0.0.1:9020
export CONTINUITY_AUDIO_SERVER=http://127.0.0.1:9021

若要改为手动接线——continuity-setup 会为你的机器打印填好的这个块:

- insert:
- id: continuity
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: continuity
transport: stdio
command: uvx
args: ['--from', 'dsh-continuity', 'continuity-mcp']
env:
CONTINUITY_STATE_DIR: !!js process.env.CONTINUITY_STATE_DIR ?? ''
SD_SERVER: !!js process.env.CONTINUITY_SD_SERVER ?? ''
AUDIO_SERVER: !!js process.env.CONTINUITY_AUDIO_SERVER ?? ''

(完整行,每个透传项都有文档说明:bundle/cordis.patch.yml)

continuity-setup 会在下载任何内容之前检查机器,并根据检测结果调整安装规模。先运行
continuity-setup --check 看看它会做什么——该命令只读取硬件信息,不做任何更改:

体检结果:
GPU     AMD Radeon RX 7800 XT (RADV NAVI32)  (16.0 GiB, 此刻可用 15.8 GiB, DISCRETE_GPU, vulkan device 1)
未选 AMD Radeon RX 7900 XTX (RADV NAVI31) (24.0 GiB, 此刻可用 1.4 GiB)
跳过 llvmpipe —— 软件渲染, 不是真显卡
内存    30.9 GiB
磁盘    3118.4 GiB 可用 / 需要 34 GiB
生图    启用
音频    启用
抠图默认档  best

其中有两个细节之所以存在,是因为天真的做法是错的:

- 它会跳过 llvmpipe。 这个软件光栅化器会宣称自己有 30.9 GiB 的“VRAM”(其实就是你的
系统内存),在任何“挑最大显卡”的比拼中都会胜出。然后所有东西都会跑在
CPU 上——能跑、看起来完全正常,但慢到没法用。
- 它按空闲 VRAM 挑选,按总 VRAM 设门槛。 在上面这台机器上,那块 24 GiB 的显卡
实际只有 1.4 GiB 空闲,因为另一个进程占着它;按大小挑选会选中它,然后
就 OOM 了。但“这块卡够不够好”是个硬件问题,所以那一项用的是总量——
否则一块 16 GiB 的卡会因为开着游戏而被拒绝。

最低要求

| | 最低 | 说明 |
|---|---|---|
| GPU | 8 GiB VRAM | 峰值为 6.80 GiB(实测)。请求是串行化的,所以峰值是单个模型,而不是总和。 |
| GPU API | Vulkan 1.2+ | 不需要 CUDA,不需要 ROCm。 内核在运行时编译为 SPIR-V。 |
| 磁盘 | 安装期间 34 GiB,之后 21.8 GiB | 19.7 权重 + 2.1 运行时镜像 + 8.5 构建层(可回收)+ 4 余量。 |
| 主机内存 | 16 GiB(8 GiB 勉强可用——见下文) | 由瞬时峰值决定,而非空闲状态。 |
| CPU | 任意 x86-64 | 背景移除在 CPU 上运行。 |

仅音频安装(见下文)需要安装期间 20 GiB,之后 9.5 GiB。

上表中的每一行都关乎你在本地运行的那一半。--image-api-server(或 --sd-server)
会把 GPU 那一行降到音频那一半所需的 4 GiB,并留下 10.1 GiB 的权重不被下载,
--asr-server 再省下 2.3 GiB,--audio-server 省下其余部分——见
自带后端。

本页所有 VRAM/内存数字都是 GiB(2³⁰ 字节),这也是 rocm-smi 和
vulkaninfo 报告的单位。本 README 的早期版本把它们标为 GB;那是错的,会让
余量看起来比实际更紧张。

选择 Vulkan 而非 CUDA 不是偏好——这正是它能跑起来的原因。ROCm 会算错
在这类 GPU 上进行 VAE 解码(ROCm#6633):
对相同输入进行五次解码,返回了五个互不相关的结果。Vulkan/RADV
在运行时编译 SPIR-V,而不是查找按架构划分的内核表,并且在这里既正确
又更快。副作用是可在三家厂商之间移植。

GPU 厂商

| | 容器如何获取 GPU | 状态 |
|---|---|---|
| AMD | /dev/dri + 镜像内的 mesa RADV | 已测试(RX 7800 XT、RX 7900 XTX) |
| Intel | /dev/dri + 镜像内的 mesa ANV —— 相同机制 | 未测试 |
| NVIDIA | nvidia-container-toolkit 注入宿主机驱动(docker-compose.nvidia.yml) | 未测试 |

我只有 AMD 显卡,所以我不会声称更多。代码中没有任何 AMD 特有的东西 —— 没有 CUDA、没有 ROCm、没有 HIP、没有 /dev/kfd、没有 gfx 目标 —— 而且 ggml 的 Vulkan
后端在 NVIDIA 上被广泛运行。但“被广泛运行”并不等于“我验证过”。

NVIDIA 路径是一种真正不同的接线方式,而不只是不同的显卡:NVIDIA 的 Vulkan
ICD 位于宿主机驱动中,必须由 nvidia-container-toolkit 注入,并且
NVIDIA_DRIVER_CAPABILITIES 要包含 graphics —— 默认的 compute,utility 会让你得到
可用的 CUDA 和 Vulkan 中空的设备列表。continuity-setup 会检测 NVIDIA,使用
正确的 compose overlay,并告诉你该路径尚未验证。欢迎提供任一方向的报告。

宿主机 RAM 详情

空闲时微不足道;峰值才是决定机器规格的因素。

| 操作 | 峰值 RSS |
|---|---|
| 空闲 | 0.52 GiB |
| 音乐 | 0.50 GiB |
| 语音 | 1.63 GiB |
| 图像(1024²) | 4.94 GiB |
| remove_bg quality="best" | 7.74 GiB |
| remove_bg quality="fast" | 1.33 GiB |

背景去除是上限,而且其成本与输入尺寸无关 —— 256 / 512 /
1024 px 的峰值都在约 6.8 GiB,因为 BiRefNet 以固定的内部分辨率运行。

在 16 GiB 上一切都能工作。 低于 12 GiB 时,continuity-setup 会将默认值设为
quality="fast"(u2netp):峰值降至 1.33 GiB,运行时间为 0.6 秒而不是 7.2 秒。在
典型的游戏精灵图上,两者肉眼很难区分 —— 已在洋红色
背景上并排检查并放大边缘。在有空间的情况下,best 仍然是默认值,因为
这些模型原则上在精细边缘(头发、半透明边缘)上确实不同,但请把 fast
视为一个合理的选择,而不是降级的回退方案。

一条规则,而不是分级列表

任务被串行化,因此在任何时刻都只需要一个模型。其他所有内容都会在任务开始前
释放。这就是整个 VRAM 策略。(唯一的例外是拆分部署:如果图像后端与
音频后端位于不同主机上,它们就不会争用同一张卡,因此不会释放任何内容 —— 为远程任务释放本地 VRAM 没有任何好处,反而会付出重新加载的代价。)

它带来的一个特性比节省几秒钟更有价值:无论你调用什么、以什么顺序调用,峰值 VRAM 都是恒定的 6.80 GiB。 这是在交替
语音→图像→语音→图像序列:

| | 峰值 | 语音 | 图像 | 6 次调用 |
|---|---|---|---|---|
| 保持模型常驻 | 10.94 GiB | 平均 2.8 秒 | 11.5 秒 | 42.9 秒 |
| 释放不需要的部分 | 6.79 GiB | 4.8 秒 | 11.6 秒 | 49.2 秒 |

保持常驻快 16%,但装不进 8 GiB 显卡——而“念一句台词,再画点东西”是最普通的序列。本 README 的早期版本针对这种重叠场景引用了 7.84 GiB;那来自我碰巧测试的一个更轻量的序列,把它当作上限是错误的。克隆语音也会让其参考音频常驻,其余部分就来自这里。

重新加载的实际代价:4.8 秒而非 1.2 秒,且只发生在切换走之后的第一次调用。连续十句对话只需付一次:

第 1 句 4.63s   之后九句平均 1.19s   十句合计 15.4s

所以不存在 VRAM 分级列表,也不存在 12 GiB 阈值。高于 8 GiB 时每张卡表现完全一致。低于 8 GiB 时,安装程序会解释为什么图像生成装不下,并询问是否只安装音频那一半——它不会悄悄替换成另一个产品:

生图    显存不足
Fake GTX 1060 只有 6.0 GiB, 而生图实测峰值 6.80 GiB, 需要 8 GiB。
换更小的生图模型省不下这部分 (Q4 与 Q8 峰值相同 6.60 / 6.59), 降分辨率也不行
—— 瓶颈是那个 8 GiB 不量化的文本编码器。
音频那半仍然可以装: 铸声/配音/听写/音乐/音效/抠图都能用, 4 GiB 就够。

⚠️ 这张卡装不了生图那半。
只装音频那半 (铸声/配音/听写/音乐/音效/抠图)? [y/N]

纯音频安装是一个真正的产品,而不是安慰奖:铸声、对话、音乐、音效和抠图都能在 4 GiB 下运行。

完全无法适配的是:图像模型。量化它并不会改变 VRAM——Q4_0(2.29 GiB 权重)峰值为 6.60 GiB,Q8_0(4.01 GiB)为 6.59 GiB,完全相同。降低分辨率也没有帮助(512 / 768 / 1024 峰值都一样;只有时间变化)。瓶颈是那个 8 GiB 未量化的 4B 文本编码器。所以没有“中等”图像档位可提供,只有装或不装。(Q4_0 仍然随附——相同 VRAM,磁盘少 1.7 GiB。)

要让图像低于 8 GiB,意味着更换文本编码器或模型家族。这可行,但会把身份固定从原生 ref_images 移到 IP-Adapter,而后者在此未经验证——而身份固定正是关键所在。

唯一仍然取决于资源的是主机 RAM,而那是另一种资源:低于 12 GiB RAM 时,抠图默认降为 quality="fast"(见上文)。

零常驻

在 RX 7800 XT 上测得,显卡上没有其他任务:

| | GPU |
|---|---|
| 空闲 | 0.21 GiB |
| 图像生成期间 | 6.80 GiB |
| 完成后 2 秒 | 0.21 GiB |
| TTS 期间 | 2.39 GiB |
| TTS 后 120 秒 | 0.21 GiB |

图像是免费的:引擎按请求流式加载权重,从不保持常驻。音频由空闲计时器释放——不是立即释放,因为连续为十句台词配音的人不应每次都付重新加载的代价。重新加载没有可测量的成本:同一个 TTS 请求
冷启动和热启动都花了 3.0 秒,因为权重是 mmap 映射的,并且驻留在页缓存中。

空闲计时器位于引擎中,而不在这里(audio_server.json 中的 idle_unload_ms,
默认 120 000 毫秒)。它以前是这个进程中的一个线程,而那个版本有一个值得指出的漏洞:
它从它自己的上一个任务开始计时,因此任何其他人加载的模型——另一个直接与引擎通信的
客户端,或者这个服务器之前被 SIGKILL 杀掉的进程——对它来说永远不可见。实测:通过直接
调用引擎加载一个模型,让这个服务器继续运行,等待超过超时时间,显存没有任何变化。引擎的
计时器以会话是否实际驻留为依据,所以它不关心是谁加载了模型。

请求是串行化的,并且所有不需要的内容都会先被释放,因此峰值 = 单个最大的模型,始终如此。
空闲计时器覆盖了这条规则无法覆盖的一种情况:在最后一个任务之后,没有下一个任务来触发
释放。关闭 agent 会立即释放显存——这个服务器在退出时仍然会卸载,因为计时器只保证最终
释放,而有人退出程序去玩游戏时,不应该为他们的显卡等两分钟。

它实际做的两件事

1. 身份在多次调用之间保持不变。 生成后端是无状态的:同一个角色请求两次,你会得到两个
仅仅彼此相似的人。在 Qwen3-TTS 上以某个角色四行台词的音高离散度进行实测——相同的音色
描述,相同的台词,唯一变量是是否固定了参考:

| 测试音色 | 直接送入模型 | 通过 Continuity |
|---|---|---|
| 一个明亮的女声旁白 | 125 Hz | 5 Hz |
| 一个沙哑的老年嗓音 | 74 Hz | 29 Hz |

两个不同的音色,两个不同的幅度,方向相同。看比值,而不是标题数字——一个描述会漂移
多远取决于这个描述本身。并且把 f0 离散度当作代理指标,而不是最终结论:自相关音高跟踪
会在低沉的沙哑嗓音上产生八度错误(上表较早的一次运行报告为 76 Hz,而经过八度校正后的
数字是 29),所以上面的数字将每一行的搜索范围锚定到参考。真正的验收测试是听试听片段,
这就是为什么 create_actor 会给你一个。

这个数字无法展示的是最重要的部分:漂移不是随机的。

| | 4 行台词的音高离散度 |
|---|---|
| 默认采样 | 125 Hz |
| 贪心解码 | 242 Hz——更糟 |
| 固定参考 | 5 Hz |

在贪心解码下,种子可证明是无效的——种子 5 / 99 / 777 产生了同一个完全相同的 sha256——
所以随机性被完全消除了,而它仍然漂移了 242 Hz。身份是输入文本的函数,而不是随机抽取的
函数。 temperature=0 和 top_k=1 无法解决这个问题。只有固定到一个参考产物才能解决。

create_actor(name, voice)          -> 试听片段;在确定之前先听
actor_tts(actor, text)             -> 每一行都是相同的音色

create_character / create_animal / create_object (name, appearance)
subject_image(subject, scene)      -> 相同外观,新场景 / 角度 / 服装

身份与服装是分开的:先固定面部和体型,然后在场景提示词中更换衣服。一个穿着靛蓝色长袍的参考形象,被要求 wearing heavy red armor,返回时穿着盔甲,但面部相同。

已经在别处选定了你的角色? import_actor 和 import_subject 可以固定你提供的素材——一段真实录音、一个 ElevenLabs 片段、来自其他工具的角色设定表——下游的一切行为完全相同。音频会为你归一化为 24 kHz 单声道(输入为 44.1 kHz 立体声,已验证:参考 f0 一致,且导入的演员与原生选定的演员误差在 11 Hz 以内)。

import_actor 需要知道录音说了什么——克隆会将音频与文本对齐,错误的转录会被听成错误的声音。如果你省略它,系统会为你转录,结果会标记为机器听写,这样你就可以检查那唯一一行决定一切的内容。同一个工具也单独暴露为 transcribe,值得用它来检查你刚生成的一行:一个吞掉了最后两个词的克隆听起来完全正常,只有当你读回来时才会显现出来。(两者都经过 ASR 模型,该模型按需加载,并与其他所有内容一起释放——加载时占用 3.05 GB,9.5 秒音频耗时 0.4 秒。)

查看它生成的内容

固定工具会告诉智能体在提交之前先查看参考图。因此它们返回图像,而不仅仅是路径——一张 512 px 的 JPEG(约 35 KB)与文本一起,作为 MCP 图像内容。这个插件中没有 VLM,将来也不会有: 视觉模型需要自己的显存,这会破坏峰值 = 单个最大模型这一特性,而 8 GiB 的下限正依赖于此。运行框架已经有一个模型;把图片交给它,而不是再运行第二个。

在 dsh 0.1.1-rc.1 上使用视觉模型进行了端到端验证——普通的 deepseek-v4-flash 不接受图像,并会回答 INVALID_REQUEST: This model does not support image:
yaml
- id: agent-default-model
config:
provider: deepseek-official
model: deepseek-v4-flash-vision-exp

当被要求固定“一个方形金属灯笼,恰好有五块蓝色玻璃面板和一个绿色手柄”,然后逐项对照该描述检查渲染结果时,智能体回答:

面板数量 — 不符合。 图中实际可见的是 4 块蓝色面板(正面 2 + 右侧面 2),并非 5 块。
而且从"每面 2 块"的网格规律看,若其余两面同规格,总数应为 8 块。

它数了数,它不同意刚刚被给出的提示词,并说出了它实际看到的内容。这就是统计检查无法闭合的循环:它们能捕捉到灰色的 PNG,而这个能捕捉到“那不是我要求的东西。”

在不支持图像输入的模型上,该块会降级为 [image unavailable],运行会正常继续——这是在 dsh 上观察到的,而非假设;智能体随后会表示它没有收到图像
而不是从 appearance 去猜。CONTINUITY_INLINE_IMAGES=0 只发送文本。

一个较早的注意事项,现已更正:这里早先的一个实验中,一个自托管的 27B VLM 在胸部渲染图上打出了 9/9/10 的分数,而那些渲染图的盖子形状明显是错的,我当时把这归结为“VLM 评判者对几何形状视而不见”。上面的面板计数结果是证据,表明那只是关于那个模型的陈述,而不是一条普遍规律。把几何形状明确写进 appearance 仍然是更省事的修复办法,但这个检查现在值得跑一跑。

2. 退化输出会被拒绝。 一个计算错误的后端会返回一个格式完美、全为零的 WAV,或者一张纯灰色的 PNG,并带有 HTTP 200。每个产物都会被检查(图像标准差、音频 RMS、非有限样本),调用会大声失败,而不是在垃圾之上报告成功。抠图还会额外得到一份质量报告——大部分透明、什么都没移除、主体碎成碎片、主体被蛀出空洞——每一项都有具体的警告,而不是静默通过。

再加上 remove_bg:扩散模型会把“透明背景”画成不透明的棋盘格;这个工具会把它变成真正的 RGBA 抠图,而精灵图需要这个。还有 gen_sfx,它以程序化方式合成 sfxr 风格的游戏音效——给定种子后逐位一致,毫秒级,不需要 GPU——因为扩散模型是制作 40 毫秒金币拾取音效的错误工具。

工具

21 个工具。一切都返回绝对本地文件路径,而不是 URL——智能体和引擎在同一台机器上,所以路径可以直接进入你的游戏项目,无需下载步骤,也没有需要运行或配置错误的文件服务器。

| | |
|---|---|
| 语音 | create_actor import_actor actor_tts transcribe list_actors delete_actor generate_speech |
| 外观 | create_character create_animal create_object import_subject subject_image list_subjects delete_subject generate_image |
| 音频 | generate_music gen_sfx |
| 后期 | remove_bg slice_sheet |
| 元 | continuity_status |

generate_image 和 generate_speech 是为一次性使用而存在的,并且它们在自己的描述中就是这么说的:它们明确告诉智能体,它们产生的内容不会在下次调用时回来,并指向固定工具来处理任何反复出现的内容。

每个结果面向两类受众

每个工具都返回同一次调用的两种描述:

| | 谁读它 | 它是什么 |
|---|---|---|
| content | LLM | 中文散文,包括 ⚠️ 警告在内——原样不变,它就是提示词 |
| structuredContent | 你的程序 | 一个类型化对象;模型在 results.py 中,它的 JSON Schema 就是工具的 outputSchema |
jsonc
// generate_image
{"ok": true, "error": null, "warnings": [],
"path": "/home/you/.continuity/generated/img_1787322514_9a3f.png",
"width": 1024, "height": 1024, "seed": null, "clamped": false}

// remove_bg,在一个糟糕的抠图上——⚠️ 在两半里都有,绝不只出现在散文里
{"ok": true, "warnings": ["抠图结果很可能不对: 被去掉的区域细节密度是主体的 68% …"],
"path": "…/cut_1787322526_5381.png", "mode_used": "rembg", "model": "u2netp",
"transparent_ratio": 0.551}

// 任何失败 —— 这段文字仍然是那条指导性的中文消息,告诉 LLM 下一步该做什么
{"ok": false, "error": "actor '郭靖' 不存在 —— 先调 create_actor(…) 铸声, 再用它说台词。", "warnings": []}

不要用正则从这段文字里抠路径。 这段文字是一个提示词:每当 agent 的行为需要它改变时,它就会被改写,而一个不再匹配的正则只会静默失败——你拿到的是一个空路径,而不是一个错误。ok 和 path 才是契约;中文不是。

结构是统一的。ok 始终存在,并且是唯一值得优先分支判断的字段:当它为 false 时,只有 ok / error / warnings 有意义,其余一切都是 null。文字里的每一个 ⚠️ 在 warnings 中都有一条对应的字符串。路径始终是绝对路径。

选角与固定(create_character / create_animal / create_object / import_subject)会同时内联返回参考图像和结构化内容——这四个被标注为 Annotated[CallToolResult, …],这是 mcp 2.0 接受的“多个内容块外加一个声明的输出 schema”的唯一形式。模型在每次调用时都会被校验,因此任何偏离文字所述内容的字段都会抛错,而不是被发布出去。

各项限制,以及每一条存在的原因

这里的每一个数字都是实测出的失败边界,而不是策略。

| 限制 | 值 | 超过它会发生什么 |
|---|---|---|
| 行长 | 200 字符 | 600 字符把 GPU 卡死了:amdgpu GPU reset(6),设备丢失,另一张卡上一个无关进程被杀。200 是已知安全最大值的一半。 |
| 参考音频 | 15 秒 | 每秒约 0.19 GiB 显存:15 秒 → 6.59 GiB,30 秒 → 9.04 GiB。15 秒是仍低于图像峰值的最后一个值,因此语音永远不会成为上限。所有显卡都用同一个值——3–10 秒已经足以固定音色,所以给更大的显卡设更大的上限只会意味着“这段音频在我的机器上能导入,在你的机器上不能”。 |
| 选角脚本 | 45 字符 | 它会生成参考音频,而这段音频随后会在之后的每一行被重新读取。字符数是个糟糕的代理指标(60 字符实测 19.1 秒,而不是按比例预测的 13.7 秒),所以真正的时长会在选角之后被检查并报告。 |
| 图像尺寸 | 1024 px | 1280 把显存推到 14.5/16.4 GiB;2048 让驱动陷入 restore_userptr_worker 抖动,进程卡在不可中断的 D 状态——比一次干净的 OOM 更糟。 |
| 音乐长度 | 120 秒 | 不是安全限制:引擎会在 120 秒处静默截断并报告成功。这条限制把它变成一个显式的 clamped 字段。 |

低于 24 kHz 的导入音频会被接受,但会被标记:上采样无法恢复被丢弃的那个八度,所以克隆出来的声音会比你给它的文件更沉闷。这值得一条警告,而不是静默通过——它和这个插件存在的意义所要捕捉的其他一切,是同一种失败形态。
超大输入会按类型区别处理,这是有意为之。 过大的图像会被缩放,结果会回报给你(原图 2400x1600 → 存为 1024x682)——缩放后的图片仍然描绘的是同一个东西。过长的参考音频会被拒绝,而不是裁剪:把音频尾部切掉会让转录文本描述的内容不再是音频所说的内容,而这种对齐正是克隆所依赖的。静默裁剪会交给你一个成功导入、但听起来像别人的演员。

dsh 如何运行它

不是等到第一次工具调用时才惰性执行——而是在 profile 启动时。dsh-mcp-client 的 apply() 会在 fiber 激活之前等待连接以及工具列表,因此 agent 一启动工具就已存在。有两个值得了解的后果:

- failOnStartupError 默认为 false。 如果 MCP 服务器无法启动,启动仍会成功,但注册的工具数为零,而且没有任何东西会提醒你——agent 只会报告这些工具不存在。(问我怎么知道的。)如果你更希望 profile 拒绝启动,就把这一行设为 true。
- 重连默认开启:500 ms,翻倍至 30 s 上限,10 次尝试,然后放弃并注销工具。每次尝试都会生成一个全新的服务器进程。

关闭是由 MCP SDK 拥有的三步阶梯,这也是上面 VRAM 声明成立的原因:

| 步骤 | 预算 | 我们做什么 |
|---|---|---|
| 关闭我们的 stdin | 2 s | 服务器循环结束,正常退出,atexit 释放模型 |
| SIGTERM | 2 s | 信号处理器释放,然后 os._exit——Python 在这里不会运行 atexit |
| SIGKILL | — | 什么都不会运行;引擎中的空闲计时器稍后仍会释放它 |

实测:stdin 路径 0.16 s,SIGTERM 路径 0.11 s,两者都会释放。卸载调用被限制在 1.5 s,正是因为预算是 2 s——慢引擎绝不能把我们推入 SIGTERM 步骤,那样释放根本不会发生。而且它会无条件触发,而不是查询此进程自己的记账:VRAM 属于引擎,引擎比任何一代服务器都活得久,所以重连后的一代账本是空的,否则会完全跳过释放。

不使用 dsh 运行它(streamable-http)

默认传输是 stdio,dsh 路径没有任何变化——不带参数的 continuity-mcp 行为与之前完全一样。对于不是自己生成进程的调用方(HTTP shell、另一台机器、多个客户端共享一个已加载的模型),把它作为长期运行的 streamable-http 服务器来运行:

continuity-mcp --http                                   # 127.0.0.1:9030/mcp
continuity-mcp --http --host 127.0.0.1 --port 9030 --path /mcp   # 同上,完整写出
CONTINUITY_TRANSPORT=streamable-http continuity-mcp     # 用环境变量代替标志

| 标志 | 环境变量 | 默认值 |
|---|---|---|
| --transport {stdio,sse,streamable-http}(--http 是最后一项的简写) | CONTINUITY_TRANSPORT | stdio |
| --host | CONTINUITY_HTTP_HOST | 127.0.0.1 |
| --port | CONTINUITY_HTTP_PORT | 9030 |
| --path | CONTINUITY_HTTP_PATH | /mcp |

将 MCP 客户端指向 http://127.0.0.1:9030/mcp。

它默认绑定到回环地址,你应该保持这样。 这里没有任何形式的身份验证,而且这些工具会向本机磁盘写入文件,并删除演员和主体。绑定到 0.0.0.0 就等于把这些能力交给网段上的任何人——如果你需要它可被访问,就在前面放一个反向代理。端口 9030 与两个引擎(9020 / 9021)保持距离。

通过 HTTP 时,VRAM 保证不变:图像生成和 TTS 仍然共享一个进程级锁,因此多个客户端同时连接意味着它们会排队,而不是两个模型同时驻留在显卡上。HTTP 确实改变的是上面的关闭阶梯——长期运行的服务器不会被 dsh 回收,因此模型会保持加载状态,直到引擎自身的 idle_unload_ms(默认空闲 120 000 ms)释放它们,或者直到你停止该进程。

自带后端(可选)

有两个独立的后端 URL,因此你可以把一个能力移出本机,同时让另一个保持本地:

| | 环境变量 | 它必须是什么 |
|---|---|---|
| 图像 | SD_SERVER | 一个 stable-diffusion.cpp sd-server(/sdcpp/v1/img_gen + 轮询,接受 ref_images) |
| 音频 | AUDIO_SERVER(+ 可选的 AUDIO_API_KEY) | 一个 audio.cpp audiocpp_server(/v1/audio/speech、/v1/audio/transcriptions、/v1/tasks/run、/v1/tasks/unload_models),提供 qwen3-tts / qwen3-tts-base / stable-audio / qwen3-asr |
| asr | ASR_SERVER + ASR_API_KEY | 任何 OpenAI 形态的东西:在 /v1/audio/transcriptions 上使用 multipart file + model,返回 {"text": ...}。默认使用 AUDIO_SERVER。给它根 URL,不要带 /v1——路径会被追加。 |
| 通过 API 进行图像生成 | IMAGE_API_SERVER + IMAGE_API_KEY | 任何 OpenAI 形态的东西:/v1/images/generations,当涉及参考图像时再加上 /v1/images/edits;响应中的 b64_json 或 url 均可接受。设置它会优先于 SD_SERVER——与 ASR 开关不同,它选择一种协议,而不仅仅是一个地址,因为我们自己的引擎说的是 sd.cpp 的 /sdcpp/v1/img_gen,没有别的引擎说这个。 |

将图像生成指向托管 API 是这里能提供的最大节省——10.1 GiB 的权重以及整个 8 GiB VRAM 门槛——但这也是在转换中损失最多的能力,而且每一项损失都会在工具自身的 warnings 中报告,而不是留给你去发现:

- seed 在标准形态中不存在。它会被丢弃,结果会报告 seed: null,而不是回显一个无法复现任何东西的数字。
- 尺寸是固定枚举,具体是哪一个因提供商而异(IMAGE_API_SIZES)。请求会被吸附到最接近的宽高比;generate_image 随后会缩小到你要求的尺寸,而固定主体的参考图像会以提供商的尺寸存储。
- steps / cfg_scale 无处可去。
- 参考图像走 /v1/images/edits,各家提供商在这里差异最大——蒙版
编辑、多图参考、风格迁移都是这样表达的。因此身份固定
就成了那个后端的属性,而不是这个包所提供的东西。

按形状测试,而非按提供商测试。 这条路径是按文档中的
OpenAI 形状构建的,并在本地 mock 上端到端跑过(b64_json 和 url 两种响应、
生成和编辑、密钥请求头、seed 正确地不出现在请求中)。没有调用任何线上托管
服务。在信任一个新后端做固定运行之前,先试一次 generate_image。

只有 ASR 有自己单独的一行,原因在于端点,而不是模型:转写是
唯一一个具有行业标准形状的能力,所以别人的 ASR 是一个你可以真正指向的
东西——vLLM、托管 API、另一台机器。语音和音乐没有这种选项:克隆会把
voice_ref 作为内联 base64 提交,并附带一个 reference_text,而音乐走
/v1/tasks/run——两者都是 audio.cpp 自己的形状,没有第三方会说。它们的“BYO”只能
意味着另一个 audiocpp_server,而这正是 AUDIO_SERVER 已经是的东西。

另一台机器上的另一个 audiocpp_server 几乎总是位于一个需要
令牌的网关之后,所以 AUDIO_API_KEY 会在发往
AUDIO_SERVER 的每一个请求上发送 Authorization: Bearer ——语音、音乐、本地转写、卸载和健康探测都一样。
未设置时,不发送请求头,请求与原来完全一致。它不会跟随转写到别处:
当 ASR_SERVER 指向一个不同的后端时,那个后端的密钥是 ASR_API_KEY。

这两条路径不共享假设,代码也将它们分开。 对于我们自己的引擎,
模型是我们选的,所以我们知道它以 16 kHz 运行,并下采样到该频率——什么都不会丢失,
上传体积缩小三分之一,并且本地 VRAM 的那套操作也适用。对于一个不是我们选的
后端,这些我们一概不知,所以音频按原样发送:决定是否重采样是那个服务的事,
为一个模型想要宽带音频的服务预先下采样,会丢掉它训练时所依据的东西。

这就剩下那些本身很挑剔的服务器。vLLM 的 /v1/audio/transcriptions 会以
400 Invalid or unsupported audio file 拒绝 22.05 kHz 和 24 kHz,并且对采样率
只字不提——而这里的参考音频是 24 kHz,因为那是克隆模型想要的。所以标准路径上的
一个 400 会以 16 kHz 重试一次(400 意味着请求被拒绝,而音频是我们唯一能改变的
东西)。对于已知属于这一阵营的后端,设置 ASR_SEND_RATE=16000,跳过这次浪费的往返。

告诉安装器哪一半是你的,它就会完全跳过那一半——不装权重、不装引擎、
不做 VRAM 门控——同时工具仍保持注册:

continuity-setup --sd-server http://your-box:9020      # 生图你自己供; 本地只装音频
continuity-setup --audio-server http://your-box:9021   # 反过来
continuity-setup --asr-server http://your-box:9000 \
--asr-api-key sk-...                  # 只把听写挪出去, 那 2.3 GiB 不下
continuity-setup --image-api-server https://api.openai.com \
--image-api-key sk-...                # 生图整半交给标准 API, 10.1 GiB 不下

这比听起来更重要:没有它,自带图像那一半仍然会下载 10.1 GiB 的图像权重,并启动一个永远不会有人调用的本地 sd-server,然后因为本地显卡太小而拒绝启用图像工具。在 5.3 GiB 的集成显卡上,--sd-server 把“生图 显存不足”变成“生图(BYO)”,并且什么都不下载。

注意它与 --no-image 的刻意区分:后者意味着“我不想要这个能力”(工具不注册);--sd-server 意味着“我自己提供这个能力”(工具正常工作)。

将 BYO 音频引擎升级到 0.4.0: ASR 模型是新的,而 BYO 意味着安装器从不触碰你的引擎——所以 transcribe(以及没有转录文本时的 import_actor)会在早于它的引擎上失败。把 deploy/audio_server.json.tmpl 中的 qwen3-asr 条目加入你的引擎配置,并从 audio-cpp/audio.cpp-gguf 获取 Qwen3-ASR-1.7B-GGUF/qwen3-asr-1.7b-q8_0.gguf(2.3 GiB)。本地安装重新运行 continuity-setup 即可免费获得两者。其他一切不变:其他工具并不知道这个模型的存在。

这些选项中的每一个也都可以作为普通的运行时环境变量使用,无论你是从 PyPI 安装还是接入了 dsh 插件。通过插件,它们被命名为 CONTINUITY_ASR_SERVER、CONTINUITY_IMAGE_API_SERVER 等等——cordis.patch.yml 显式列出了每个键,因为那个 env 块是一个固定字典而不是透传:没有在那里列出的键在服务器端就不存在,所以设置它没有效果,也没有任何东西会报告这一点。continuity_status 会指出哪一侧不可达。gen_sfx 完全不需要后端。

要清楚这里的“你自己的后端”意味着什么:同一个引擎,在别处。 它不是一种提供商抽象。客户端说的是 sd.cpp 和 audio.cpp 特定的 HTTP 形态,所以你不能把 SD_SERVER 指向一个 OpenAI 兼容端点、一个 ComfyUI 实例,或一个裸的 IP-Adapter 服务器,并指望它能工作。它真正适合的用途是:在更强的机器上运行这些引擎,或在多个 agent 之间共享一个后端。(本 README 的早期版本暗示任何支持参考图像的后端都可以。代码从来不是这样。)

如果你走远程,有一个约束:身份固定需要图像后端接受参考图像。代码使用的是 sd.cpp 的 ref_images;没有它就没有固定,而固定正是全部意义所在。

参考音频曾经是第二个约束——引擎拿到的是一个文件系统路径并自己打开文件,所以远程音频后端意味着选角成功,而之后的每一行都失败。那不是引擎限制,而是错误的端点:/v1/audio/speech
将引用以内联 base64 形式接收(上限 5 MiB;15 秒的引用约为 720 KB),完全按照
图像路径一直以来传递 ref_images 的方式。现在两半是对称的,无需
共享目录。

现有技术

对当前 MCP 生态系统的调研——MiniMax-MCP、openrouter-mcp-multimodal、AtlasCloud、
dsh vision/draw 插件,以及四个游戏素材服务器——发现其中若干支持语音克隆,
没有任何一个支持视觉主体固定,也没有任何一个支持输出验证。

布局

package.json + cordis.patch.yml   dsh 包(npm)——一行插件,位于仓库根目录
以便 dsh plugin add github:... 可用,而不仅是 npm 名称
src/continuity_mcp/               MCP 服务器:固定、护栏、验证、抠图、
VRAM 生命周期
src/continuity_mcp/deploy/        compose + 引擎 Dockerfile + 权重清单
pyproject.toml                    PyPI 发行版(dsh-continuity)

许可证

MIT

上游仓库有新提交时邮件通知你(每天最多一封,无更新不打扰),随时一键退订。

同作者(linxuhao)的其他插件

💬 加入社群

插件用法、部署报错、新插件第一时间同步——群里问,比一个人翻文档快。

DPharness QQ 群二维码,QQ 扫码进群
QQ 扫码进群
DPharness 飞书群二维码,飞书扫码进群
飞书扫码进群