经常不断地学习,你就什么都知道。你知道得越多,你就越有力量。—— 高尔基

Needle 2:把工具调用、设备使用与结构化提取装进 14MB 的小小引擎

项目地址:https://github.com/cactus-compute/needle

当人们谈起模型能力时,话题往往会自然滑向更大的参数规模、更长的上下文和更复杂的部署链路。Needle 2 走向了另一条路线:它把目光投向体积、内存和可执行性,试图让工具调用、设备使用与结构化信息提取,以更轻巧的姿态落在微小设备之上。

Needle 2 是一个开放的 4500 万参数模型,面向工具调用、设备使用和结构化提取而设计。它将完整模型放入一个 14MB 的单一二进制文件中,一次完整会话的运行内存约为 28MB。对于手机、可穿戴设备、智能家居和机器人这样空间与资源都格外珍贵的场景,这种紧凑感像是把一套原本庞大的能力,折叠成了可以随身携带的小型工具箱。

这个仓库提供的是 Python 包,覆盖推理、LoRA 微调和导出流程。安装之后,开发者可以描述自己的工具,再从 Python 中调用它们。推理引擎会在首次使用时获取,而推理过程本身不进行网络访问。

一枚单文件模型,承载完整会话

Needle 2 的表达并不依赖一组零散的模型文件。权重被封装进单一的 14MB 引擎里,不需要额外管理独立模型文件。模型一旦准备就绪,后续推理可以在没有网络参与的状态下进行。

这种形式让模型更像一个自带内容的器件:它不要求在运行时四处寻找权重,也不把推理过程系在网络连接上。对于希望把能力放进设备侧的应用而言,模型文件、内存占用与运行边界都显得格外重要,而 Needle 2 将这些部分收拢到相对明确的范围之中。

Needle 2 还采用了 256 token 的滑动窗口,并将工具固定为 KV sinks。无论对话持续多久,总内存都保持在约 28MB 的量级。对话可以继续向前流动,而内存的边界并不会随之无限膨胀。

输入是文本,输出是结构化结果

工具调用最怕的并不是模型没有话说,而是模型说得很多,却没有形成可以被程序可靠接收的结果。

Needle 2 为工具调用提供了一个简洁的契约:文本输入,JSON 输出,工具调用以结构化数据返回。它会根据 schema 编译字节级语法,并在生成时对每一个 token 施加约束。

这意味着,工具描述不只是写给人看的文档,也会成为模型理解调用目标与填写参数的重要依据。工具名称、参数类型、文档字符串、字段约束与可选值,都能参与构成模型工作的边界。

README 中对此给出了一个直接的提示:描述工具这件事,本身就是核心工作。工具写得清楚,模型才能更准确地判断该调用什么、该如何填入参数。

从一个 Python 函数开始

Needle 2 的工具调用入口很轻。只需安装 Python 包:

1
pip install cactus-needle

随后,将普通函数标记为工具。函数签名提供参数类型,文档字符串承担工具描述,run() 则完成从模型选择调用到执行函数并返回结果的整个闭环。

1
2
3
4
5
6
7
8
9
10
import needle

@needle.tool
def get_weather(city: str):
"Get the current weather for a city."
return {"city": city, "temp_c": 27, "sky": "clear"}

agent = needle.Needle(tools=[get_weather])
print(agent.run("what's it like in Lagos right now?")["results"])
# [{'city': 'Lagos', 'temp_c': 27, 'sky': 'clear'}]

在这段流程里,get_weather 不再只是一个等待手动调用的函数。它拥有名称、参数类型、说明文字与返回数据,因而成为模型可以理解和选择的工具。

用户提出自然语言请求,模型判断是否需要使用工具,Needle 执行对应函数,再将结果以结构化形式交回。调用链路没有被拆成一堆彼此陌生的组件,而是围绕 Python 函数自然展开。

让模型在大量工具中找到眼前最合适的那个

当工具数量增加时,真正需要解决的问题不只是调用,还包括选择。

Needle 2 支持声明较大的工具目录,并借助内置检索头,在每一轮只渲染最相关的五个工具。随后,语法约束也会收缩到这一个更小的工具集合之中。

这像是在一座工具仓库里先点亮最可能被使用的五盏灯。模型不需要面对所有工具的全部细节,而是先聚焦于当前请求最相关的一小部分候选项,再在受约束的范围中完成调用。

工具检索与语法约束在这里并肩工作:前者负责缩小视野,后者负责约束输出。一个帮助模型找到更接近的问题入口,另一个帮助模型沿着正确的结构走到结果。

不只给答案,也给出置信度

Needle 2 的每个响应都带有经过校准的置信度分数,来自一个学习得到的 head。开发者可以设置阈值,在置信度高于阈值时采取行动,在低于阈值时转交处理。

这让工具调用不再只有二元选择。模型并非单纯地给出一个调用结果,而是同时交出它对这次结果的信心。

在实际流程中,阈值可以成为一道明确的分界线。高于阈值,系统继续执行。低于阈值,系统将请求升级处理。模型的输出因此不只是一个动作建议,也是一份带着自我判断的结构化信号。

从文本中抽取有形的数据

工具调用之外,Needle 2 也提供结构化提取能力。

当一段文本里藏着需要被整理的信息时,可以先声明目标数据的形状,再调用 extract()。传入一个 Pydantic 模型,得到的便是一个具有类型的对象。

1
2
3
4
5
6
7
8
9
from pydantic import BaseModel

class Invoice(BaseModel):
vendor: str
total: float
due_date: str

invoice = needle.extract("Invoice from Acme Corp, $1,200.00, due 2026-09-01", Invoice)
print(invoice.vendor, invoice.total) # -> Acme Corp 1200.0

在这个例子里,一句包含供应商、金额与日期的文本,被整理为具有明确字段的数据对象。文本不再只是等待阅读的内容,也可以被收进预先定义好的数据轮廓中。

Needle 2 还支持参数描述与可选值、被编译进解码语法的值约束、原始 JSON Schema、通过 complete() 驱动循环、响应契约、系统事实与工具检索等能力。它们共同指向一个目标:让自然语言交互与结构化程序接口之间的距离更短一些。

在浏览器里试着调一调工具

Needle 2 提供 Playground,用于在浏览器中试用模型。可以选择预设,编辑工具或提示词,再执行运行操作。后续查询会沿用同一段对话。

1
needle playground

也可以启动一个使用自定义权重的 Playground:

1
needle playground --weights my.cact

默认情况下,服务地址为 http://127.0.0.1:7860

Playground 会在提供服务之前下载并初始化模型,因此首次查询可以立即得到响应。界面中的 Finetune on these tools 按钮还会从界面发起下方的微调流程,并返回一个经过微调的模型。

这让工具描述、提示词、连续对话与微调流程不再完全散落在命令行和代码文件中,而是也拥有一个可以直接操作的浏览器入口。

用 LoRA 为工具集注入新的习惯

Needle 2 的微调流程基于冻结的基础模型进行 LoRA 微调,并在导出时将 adapter 合并。这样,一次微调运行成本较低,而最终得到的调优模型仍然是一个可以在同一引擎上运行的单一 .cact 文件。

整个流程可以拆成几个清晰阶段:准备数据,可选地合成数据,执行 LoRA 微调,构建调优后的 .cact 文件,然后直接运行。

训练数据采用 JSONL 格式,每一行是一个样本。reasoning 字段是可选的。离题样本使用 answers: [] 表示。

如果希望从工具 schema 文件生成样本,或在已有数据集基础上扩充样本,可以使用数据合成命令。这个过程需要设置 OPENROUTER_API_KEY

1
2
3
export OPENROUTER_API_KEY=sk-or-...
needle generate-data --tools my_tools.json --num-samples 500 --output data.jsonl
needle generate-data --augment data.jsonl --num-samples 500

也可以通过设置 OPENROUTER_URL,使用兼容 OpenAI 的网关替代默认的 OpenRouter 端点。

接下来,使用 LoRA 进行微调:

1
needle finetune data.jsonl --epochs 10

也可以在训练前基于数据中的工具再生成额外样本,并同时指定 LoRA 参数:

1
needle finetune data.jsonl --epochs 10 --generate 300 --lora-rank 16 --lora-alpha 32

微调命令包含多个关键选项:

  • --epochs 默认值为 3
  • --lora-rank 默认值为 16
  • --lora-alpha 默认值为 32
  • --lr 默认值为 1e-4
  • --batch-size 默认值为 16
  • --max-len 默认值为 1024
  • --val-split 默认值为 0.1
  • --checkpoint 用于指定基础 checkpoint
  • --out 用于指定输出位置

训练部分使用 JAX,可以运行在 JAX 支持的加速器上。NVIDIA 环境可以安装 CUDA 版本:

1
pip install "cactus-needle[gpu]"

Apple Silicon 环境则可以使用 metal extra:

1
pip install "cactus-needle[metal]"

把调优结果构建为新的 .cact

LoRA 微调结束后,可以将 adapter 合并进基础模型,并完成量化,构建新的 .cact 文件。

1
needle build checkpoints/needle2.pkl --lora checkpoints/needle_lora.pkl --out my_needle.cact

如果需要更小的模型,可以加入 --bits 2。默认导出会遵循 checkpoint 声明的逐层 bit map;如果 checkpoint 没有声明,则回退到 4 bit。

基础模型在缺失时会自动下载。通过设置 NEEDLE_HF_REPO,也可以指定相应的模型仓库位置。

构建完成之后,调优模型可以直接交给同一个引擎运行,不需要重新编译:

1
2
3
4
import needle

agent = needle.Needle(weights="my_needle.cact", tools=[...])
agent.run("...")

从基础模型到 LoRA adapter,再到最终的 .cact 文件,这条链路始终围绕同一个目标展开:让调优后的模型仍保持为一个独立、紧凑、可直接运行的模型文件。

小模型并不等于小野心

Needle 2 的模型配方被称为 Simple Attention Network。它采用 Hadamard MLP 代替 FFN,并结合 GQA attention、engram key-value memory 与 multi-lane hyper-connections。

每个 block 都携带自己的更新规则。模型中的四条残差流会先被 RMS 归一化并展平,随后使用正交 Walsh-Hadamard 变换。这个固定矩阵可以在 n log n 时间内完成应用。

这些结构最终服务于同一个方向:让一个 4500 万参数的模型,以单一 14MB 二进制与约 28MB 会话内存的形式,承担工具调用、设备使用和结构化提取。

它并不试图把一切塞进无限延展的资源里,而是把边界写进自身的设计中。模型大小有边界,内存有边界,输出格式有边界,工具候选范围也有边界。正是在这些明确的边界之间,Needle 2 将文本、工具、结构化数据、置信度与微调流程编织到了一起。

当一个模型能够读懂工具说明、挑选工具、填写参数、执行调用、返回结构化结果,并在必要时交出自己的置信度时,它不再只是生成文字的角色。它更像一根细小却锋利的针,在受限的设备空间里,穿过自然语言与程序动作之间那层原本厚重的布。