第 1 课

装好 cactus-compute,跑通第一次工具调用并看懂返回结构

用 pip 安装 cactus-compute 4.1.0 并核对版本,关闭匿名用量统计;用 @cactus.tool 和 run() 跑通官方天气示例,看引擎和权重缓存在哪里;用 complete() 单步执行,读懂 function_calls、reasoning、confidence、peak_ram_mb,把执行结果传回模型;再看清三条边界:会多调、不闲聊、不支持中文。

图文20 分钟Cactus Needle 官方文档 ↗

课程目录第 1 / 3 课

学习位置仅保存在当前浏览器,有效期 180 天。

01 / 图文教材

图文教材

本课对照的官方页面:Needle Python Docs 、GitHub 仓库 README 。测试环境:2026-11-12,Asia/Shanghai,Linux x86_64 云主机(无显卡),Python 3.13.5,cactus-compute 4.1.0(PyPI 发布于 2026-11-08)。下面所有「对照结果」都是这台机器的真实输出,只做了节选。

Cactus 是一套跑在手机、手表和普通电脑 CPU 上的小模型。它的 Python 包 cactus-compute 把三样东西装在一起:做工具调用和结构化抽取的 Needle 3、做语音转文字的 Whistle、做看图识字的 Mirage。引擎不到 2 MB,Needle 3 的权重约 35 MB,第一次用时从 Hugging Face 下载并缓存,之后完全在本机推理,不需要 API Key。本课先装好它,跑通第一次工具调用,再看懂它的返回结构和边界。

第 1 步:建一个虚拟环境并安装 cactus-compute

官方要求 Python 3.9 或更高。默认安装就包含文本、语音和视觉三部分的运行时,不会装 JAX。

mkdir -p ~/cactus-lab && cd ~/cactus-lab
python3 -m venv .venv
source .venv/bin/activate
pip install -U cactus-compute

对照结果(节选):

Collecting cactus-compute
Successfully installed Pillow-13.0.0 ... cactus-compute-4.1.0
  ... huggingface_hub-2.4.0 ...

确认版本:

python -c "import cactus; print(cactus.__version__)"

对照结果:

4.1.0

官方说明里提到,包会上报匿名用量计数(函数名、版本、系统,不含提示词和输出)。想关掉,在终端里先执行一次:

export CACTUS_TELEMETRY=0

第 2 步:跑通第一个工具调用

Needle 3 不聊天,它只做一件事:读懂一句话,决定该调哪个函数、参数填什么。新建 weather.py(代码来自官方 README):

import cactus

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

with cactus.Cactus(tools=[get_weather]) as agent:
    print(agent.run("What's the weather in Lagos?")["results"])

运行:

python weather.py

对照结果:

[{'city': 'Lagos', 'temp_c': 27, 'sky': 'clear'}]

@cactus.tool 会读函数签名得到参数类型,读文档字符串得到工具描述。run() 是完整的循环:模型选出调用,Cactus 替你执行 get_weather("Lagos"),再把结果交回模型,最后把执行结果放在 results 里返回。第一次运行会下载引擎和权重,测试机上整次耗时约 5 秒。下载的文件在缓存目录里:

du -sh ~/.cache/cactus/engine ~/.cache/cactus/models/*

对照结果(做完本系列三课之后):

1.9M    /home/box/.cache/cactus/engine
21M     /home/box/.cache/cactus/models/mirage
34M     /home/box/.cache/cactus/models/needle3
17M     /home/box/.cache/cactus/models/whistle

第 3 步:用 complete() 单步执行,看懂返回结构

run() 帮你执行了函数。想先检查模型要调什么、再决定执行不执行,就用 complete(),它每次只走一轮。新建 first_turn.py,工具定义来自官方 Python 文档:

import json
from typing import Literal
import cactus

@cactus.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}

@cactus.tool(triggers=[r"\b(turn|switch|power|flip)\b.*\b(on|off)\b",
                       r"\btoggle\b"])
def control_device(device: str,
                   action: Literal["on", "off", "toggle"]):
    "Switch or toggle any named smart-home device."
    return {"device": device, "action": action}

agent = cactus.Cactus(tools=[set_thermostat, control_device])

r = agent.complete("toggle the garage door")
print("type:", r["type"])
for c in r["function_calls"]:
    print("call:", c["name"], c["arguments"])
print("reasoning:", r["reasoning"])
print("confidence:", r["confidence"])
print("peak_ram_mb:", r["peak_ram_mb"])

out = control_device(**r["function_calls"][0]["arguments"])
r = agent.complete(json.dumps(out))
print("after result:", r["type"], r["function_calls"])

运行 python first_turn.py,对照结果:

type: call
call: control_device {'device': 'garage door', 'action': 'toggle'}
reasoning: 'toggle' -> action 'toggle', 'garage door' -> device 'garage door'.
confidence: 0.5182
peak_ram_mb: 114.0
after result: respond []

几个字段的意思:

  • function_calls:模型要调的函数和参数。参数由一套从你的类型定义编译出来的语法约束逐字生成,所以格式一定合法,action 不可能出现 on、off、toggle 以外的值。
  • reasoning:模型说明每个参数取自原句的哪一段。
  • confidence:0 到 1 的置信度,下一课会用它决定「直接执行」还是「先问用户」。每次运行数值可能有零点零几的出入。
  • peak_ram_mb:这一轮的内存峰值,测试机上约 114 MB。
  • 把函数执行结果用 json.dumps 传回 complete(),模型会判断还要不要继续调用。这里返回 respond 和空列表,表示这件事做完了。

triggers 是一组正则表达式。请求命中时,这一轮必须调用这个工具,适合用来兜住描述里写不全的说法,比如「toggle」。

第 4 步:看清它的边界:空调用、多余调用和语言

在 first_turn.py 末尾追加下面几行,再运行一次:

print("---")
for q in ["make it 21 and cool the room",
          "write me a poem about the sea",
          "把客厅的空调调到 21 度"]:
    agent.reset()
    r = agent.complete(q)
    print(q)
    for c in r["function_calls"]:
        print("    ", c["name"], c["arguments"])
    if not r["function_calls"]:
        print("     (空调用)")

对照结果(--- 之后的部分):

make it 21 and cool the room
     set_thermostat {'temperature': 21, 'mode': 'cool'}
     control_device {'device': 'room', 'action': 'on'}
write me a poem about the sea
     (空调用)
把客厅的空调调到 21 度
     (空调用)

三条结果各说明一件事:

  • 会多调:第一句正确调了 set_thermostat,但额外调了一次 control_device(device='room', action='on')。从参数看,模型把「cool the room」里的 room 当成了一个要打开的设备。这是 1 亿参数级小模型的真实表现,下一课会讲怎么用工具设计和置信度把它拦住。
  • 不会闲聊:跟工具无关的请求返回空列表。官方把这定为约定:没有工具能处理时就是空调用,不会生成任何自由文本,你的代码必须处理这种情况。
  • 不懂中文:Needle 3 和 Whistle、Mirage 的官方语言都是英语、德语、法语、西班牙语、意大利语、荷兰语和波兰语。中文请求在测试机上返回空调用。面向中文用户的产品,可以先把指令翻成英文再交给它,或者用官方的微调流程训练自己的版本。

agent.reset() 会清空对话但保留工具。官方建议同一组工具只建一个 Cactus 实例反复用,不要每轮都新建。

常见坑

  • 以为它会回答问题:Needle 3 只输出函数调用。要自然语言回复,得把工具结果交给你自己的模板或别的模型。
  • 直接读取可选参数:可选参数在原句里没有依据时会被省略,arguments 里可能根本没有这个键。用 .get() 读。
  • 装完 import 报错:多半是没激活虚拟环境。用 python -m pip install -U cactus-compute 可以避免装到别的 Python 里。
  • 从旧版 cactus-needle 升级:官方 README 写明 4.x 统一用 import cactus 和 cactus 命令;Needle 2 留在 cactus-compute<4 和仓库的 legacy 分支。
  • 下载慢或报 Hugging Face 限速:首次运行要从 Hugging Face 拉约 37 MB 文件。终端里出现「unauthenticated requests to the HF Hub」只是提示,不影响使用。

小结

这一课装好了 cactus-compute 4.1.0,用 run() 跑通了第一个工具调用,用 complete() 看懂了 function_calls、reasoning、confidence 等字段,也看到了它的三条边界:会多调、不闲聊、不支持中文。下一课学怎么把工具定义写对,用置信度决定执行还是确认,并用 extract() 从一句话里抽出结构化数据。