打开网易新闻 查看精彩图片

最近基于 DwarfStar 4 做了一个 MoonBit native binding: tonyfettes/ds4 。通过这层 binding,MoonBit 程序现在可以直接加载本地 GGUF 模型,创建推理 session,编码 chat prompt,采样 token,并把 DeepSeek V4 Flash 的生成结果流式输出。

DwarfStar 是 antirez(redis 作者)做的一个针对 DeepSeek V4 模型形态做深度优化的小型推理引擎。这意味着 MoonBit 程序可以在本地跑起一条完整的 LLM 推理循环:不需要把 prompt 发到远程 API,也不需要把模型能力包装成另一个外部服务。模型文件在本地,推理在本地,应用层控制流也在 MoonBit 里。

从聊天 API 到 Token Stream 和 Tool Call

很多用户熟悉 ChatGPT、Codex、Claude Code 这类聊天或 coding assistant 应用:输入一段话,模型一边思考一边流式输出;有时模型还会请求读取文件、运行命令或者调用某个工具。

从应用开发者的角度看,这通常表现为一次 API 调用:把 messages、system prompt、tool definitions 和采样参数发给模型服务,再从服务端接收一串流式事件。不同平台会把接口叫做 chat completion、responses、messages 或其他名字,但底层都离不开同一个过程:把对话转成 token 序列,然后让模型一个 token 一个 token 地继续生成。

一个典型的流程大致是这样:

  1. 应用把用户输入、系统指令、历史消息和工具说明整理成模型需要的 prompt。

  2. tokenizer 把 prompt 切成 token id,包括普通文本 token 和模型约定的特殊 token。

  3. 推理引擎把这些 token 喂进模型,得到“下一个 token 是什么”的概率分布。

  4. sampler 根据 temperature、top-p 等参数选择一个 token。

  5. 这个 token 被解码成文本片段,作为 stream delta 发给客户端。

  6. 刚生成的 token 再喂回模型,继续预测下一个 token。

  7. 直到遇到结束 token、stop sequence,或者应用主动停止。

所以,API 里看到的“流式输出”本质上就是 autoregressive inference 的外层包装。模型不是一次性吐出完整回答,而是不断预测下一个 token;API 服务再把这些 token 包装成更适合应用消费的事件流。

不同模型有不同的 chat template。DS4 在加载 GGUF 词表时会找到一组特殊 token,例如:

  • <|begin▁of▁sentence|> :对话开始。

  • <|User|> :接下来是用户消息。

  • <|Assistant|> :接下来是助手回复。

  • / :控制或标记 thinking 内容。

  • <|end▁of▁sentence|> :生成结束。

  • |DSML| :DeepSeek 工具调用标记的一部分。

假设用户输入是:

Write a short haiku about MoonBit

在 DS4 里,构造给模型看的 prompt 时,并不是只把这句话原样丢给模型,而是会变成类似这样的结构:

<|begin▁of▁sentence|>
You are a helpful assistant
<|User|>
Write a short haiku about MoonBit
<|Assistant|>

其中, <|begin▁of▁sentence|> 、 <|User|> 、 <|Assistant|> 、 是词表里的特殊 token,会被直接映射成对应 token id;普通文本则会经过 BPE tokenizer 切分成多个 token。如果关闭 thinking mode,DS4 会在 assistant prefix 后放入 ,相当于告诉模型直接进入回答阶段。

Tool call 也是类似的思路。模型并不会真的执行工具,它只会生成结构化意图;真正执行工具的一定是宿主程序。DeepSeek V4 的工具调用格式不是任意文本。DS4 里会查找 |DSML| 这个特殊 token,并围绕它渲染 DeepSeek 的 DSML tool call 文本,例如:

<|DSML|tool_calls>
<|DSML|invoke name="read_file">
<|DSML|parameter name="path" string="true">README.md |DSML|parameter>
|DSML|invoke>
|DSML|tool_calls>

这里需要区分两层: |DSML| 本身是词表里的特殊 token;但 <|DSML|tool_calls> 、 <|DSML|invoke ...> 、 <|DSML|parameter ...> 这些完整标签不是各自单独的特殊 token,而是由普通字符和 |DSML| 这个特殊 token 共同组成的 DSML 文本协议。模型生成这段文本后,服务端或宿主程序再把它解析成 OpenAI/Anthropic 风格的 tool call event。

如果模型在远端,这些事情通常由 API 服务帮应用包装好;如果模型在本地,就需要宿主程序自己控制 token stream、解析 tool call、执行工具并更新上下文。MoonBit 在这里扮演的就是这个宿主程序角色。

用 MoonBit 构建本地 Agent

仓库里的 cmd/ds4mbt 是一个 MoonBit coding micro agent。它基于同一层 DS4 binding,把本地 DeepSeek V4 Flash 推理接到一个简单的工具循环上。

cmd/ds4mbt 的基本循环可以概括成:

  1. 把用户任务、工作目录和工具说明写入 transcript。

  2. 调用 DS4 binding 生成一轮 assistant response。

  3. 从 response 中解析 <|DSML|tool_calls> 里的 read 、 write 、 edit 、 bash invocation。

  4. 在 MoonBit 里执行这些工具。

  5. 把 observations 追加回 transcript。

  6. 继续下一轮推理,直到模型不再发出工具调用。

它在 system prompt 里约定了四种工具,并要求模型用 DSML 输出工具调用:

<|DSML|tool_calls>
<|DSML|invoke name="read">
<|DSML|parameter name="path" string="true">relative/path |DSML|parameter>
|DSML|invoke>
<|DSML|invoke name="bash">
<|DSML|parameter name="command" string="true">moon check --target native |DSML|parameter>
|DSML|invoke>
|DSML|tool_calls>

MoonBit 侧的工作是解析这段 DSML block,把 invocation 转成内部的工具调用,然后执行文件读取、写入、编辑或 shell 命令。工具结果会作为 observation 写回 transcript,下一轮推理时又会成为模型上下文的一部分。

这个结构很接近 coding assistant 的基本工作方式。模型不直接操作文件系统,也不直接运行命令;它只生成工具意图。MoonBit 程序负责把意图变成真实操作,并把结果反馈给模型。

使用上,可以先构建命令:

moon build cmd/ds4mbt --target native

然后让本地模型执行一个工作区任务:

moon run cmd/ds4mbt --target native -- \
--model /path/to/model.gguf \
--cwd /path/to/project \
--prompt "Read the MoonBit package and explain the main entry point."

这只是一个 micro agent,但它展示了关键路径:MoonBit 可以在本地掌握 token stream、chat template、session 状态和 tool call loop。模型推理是 DS4 runtime 的工作,应用编排则是 MoonBit 的工作。

展望

tonyfettes/ds4 还是一个早期 binding,后面还有不少可以继续打磨的方向。

首先是更高层的 chat / agent API。现在的接口已经能支撑 CLI 和 micro agent,但还可以继续整理成更稳定的 library surface,例如 message abstraction、streaming callback、stop condition、错误类型和长期 session 管理。

其次是 OpenAI/Anthropic 风格的本地接口。既然远程 API 可以把模型输出组织成 token stream、message delta 和 tool call event,本地 runtime 也可以提供类似的抽象。未来可以在 MoonBit 侧封装更接近这些应用模型的 API,让本地模型更容易替换远程模型服务。

第三是更提供完整的 agent runtime 或者接入现有的 agent 框架。 cmd/ds4mbt 目前是一个 micro agent,后续可以继续探索权限控制、工具隔离、并行工具调用、可恢复 transcript、结构化日志和更可靠的 tool parsing。

DeepSeek V4 Flash 可以在 MoonBit 程序里本地跑起来之后,重点就不只是“调用了一个 C runtime”,而是 MoonBit 可以成为本地模型应用的控制层:组织 token stream、管理上下文、解析 tool call、执行工具,并把这些能力组合进自己的工具链和应用里。