cactus-compute needle
Needle 2 是一个开源的 4500 万参数模型,用于工具调用、设备使用和结构化提取。整个模型是一个 14MB 的单一二进制文件,运行完整会话仅需约 28MB 内存。它基于我们的 Simple Attention Network 研究成果构建,使用 Cactus Quants 压缩至 CQ2 位精度,并集成到其自有的引擎中。在以下基准测试中,Needle 2 与 FunctionGemma 270M、LFM2.5 230M 和 Apple FM 等其他小模型互有胜负,但体积小 5 到 70 倍,且仅用 2 位精度对比它们的 f16 精度。本仓库是 Python 包:包含推理、LoRA 微调和导出功能。执行 pip install cactus-needle,描述你的工具,然后从 Python 中调用它们。推理引擎从 Hugging Face 获取一次并缓存;无需其他构建步骤。
自包含:权重烘焙进单个 14MB 引擎中;无需管理单独的模型文件,推理过程不进行网络请求。
简单契约:工具调用以结构化数据返回,文本输入、JSON 输出;从你的 schema 编译的字节级语法约束每一个 token。
置信度门控:每个响应都携带来自学习头(learned head)的校准置信度分数;设置阈值,高于阈值则执行,低于阈值则升级处理。
工具检索:声明一个大型目录,内置检索头每轮仅呈现前五个工具,语法被约束在该子集内。
有界内存:256 token 滑动窗口,工具作为 KV 锚点固定,因此无论对话持续多久,总内存都保持在 28MB 附近。
权重:huggingface.co/Cactus-Compute/needle2 · 源码:github.com/cactus-compute/needle
Simple Attention Network
Needle 2 是一个 Simple Attention Network,这是我们用于密集型小模型的方案:用 Hadamard MLP 替代 FFN,GQA 注意力,engram 键值记忆,以及多通道超连接。设计细节和消融实验见论文:arXiv:2607.18363。
每个块都携带其更新规则。其中 x̂ 是四个残差流的 RMS 归一化展平,H 是正交 Walsh-Hadamard 变换(一个固定矩阵,以 n log n 时间应用,无需读取权重),(kₜ, vₜ) 是从哈希 n-gram 表中收集的行,P 是路由 logits A 的双随机归一化,通过 Sinkhorn 迭代计算;a、b、g 以及所有 σ 门都是可学习的且依赖输入。注意力和 MLP 残差都经过 sandwich-norm 和门控,engram 位点在两层触发,解码由从声明的 schema 编译的字节级语法约束。
快速开始
pip install cactus-needle
Needle 读取你的工具描述来决定调用什么以及如何填充参数,因此好的描述是关键。你可以通过三种方式实现,从最少控制到最多控制。
简单方式:装饰一个函数。签名提供参数类型,docstring 是工具描述,run() 完成整个循环:模型选择调用,Needle 执行你的函数,将结果反馈回去,并返回最终响应,已执行的工具结果附加为 results。
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'}]
中等方式:描述每个参数并提供选项。Needle 读取 Google 风格的 Args: 块来获取每个参数的描述;默认值使参数可选;Literal 成为模型必须从中选择的固定集合(它不能输出其他任何值)。
from typing import Literal
@needle.tool
def set_thermostat(temperature: int, mode: Literal["heat", "cool", "auto"] = "auto"):
"""Set the thermostat.
Args:
temperature: target temperature in Celsius
mode: heating strategy to use
"""
return {"temperature": temperature, "mode": mode}
agent = needle.Needle(tools=[set_thermostat])
agent.run("make it 21 and cool the room")
高级方式:使用 needle.Field 约束值,通过 Annotated 内联附加。范围、模式、长度和条目数量会被编译进解码语法中,因此模型只能输出满足这些约束的值。
from typing import Annotated
@needle.tool
def send_money(
amount: Annotated[float, needle.Field(gt=0, le=10000, description="USD, up to 10,000")],
to: Annotated[str, needle.Field(pattern=r"^@[a-z0-9_]+$", description="recipient handle")],
memo: Annotated[str, needle.Field(max_length=80)] = "",
):
"Send money to a handle."
return {"sent": amount, "to": to}
Field 支持 description、enum、const、ge/le/gt/lt、multiple_of、min_length/max_length、pattern、format、min_items/max_items 和 unique_items。
提取:要从文本中提取结构化数据,声明形状并调用 extract()。传入一个 Pydantic 模型,你会得到一个类型化对象。
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
手动方式——装饰器只是构建一个 JSON schema;你可以直接传递该 schema,这正是 Needle 所消费的内容。这是不使用装饰器设置描述和约束的方法:
tools = [{
"name": "set_lights",
"description": "Turn a room's lights on or off and set brightness",
"parameters": {
"type": "object",
"properties": {
"room": {"type": "string", "description": "which room to control"},
"on": {"type": "boolean"},
"brightness": {"type": "integer", "minimum": 0, "maximum": 100},
},
"required": ["room", "on"],
},
}]
agent = needle.Needle(tools=tools)
更喜欢自己驱动循环而不是使用 run()?complete() 返回原始调用,由你来执行: