claude -p 当 API 用:拿订阅跑出 API 的效果不想为聊天接口再单独充 API 的钱?本机 claude -p(终端 Claude Code 本人)走的就是你已经付过的订阅额度——
把它调成一个干净的纯聊天接口就行。它默认是个完整 agent,每句话都把整套工具说明书塞进输入(约 2.7 万 token);
纯聊天用不着:扒光工具 + 换掉系统提示,一句话砍到一两百 token,再让稳定前缀命中缓存(订阅也命中,省约 9 成)。
等于拿订阅跑出了 API 的效果——每个数字都是当场实测的。
—— 作者 · 离
claude -p 不是个「聊天接口」,它是整个 Claude Code 本人——一个能读写文件、跑命令、搜代码的 agent。
所以你每喊它一句,输入里其实默默塞着:
于是哪怕你只说一句「陪我聊聊天」,输入也是 约 2.7 万 token(实测)。纯聊天场景里,这些全是白烧的额度。
纯聊天就别让它当 agent——把工具扒光、把系统提示整张换掉,输入立刻瘦成一两百 token。
朴素到有点好笑:
claude 登录完即可;Max 订阅额度多、最舒服)。which claude 能打印出路径(macOS 上通常是 /opt/homebrew/bin/claude),就齐活了。caffeinate 或 pmset 禁睡),
和一个内网穿透(Tailscale / cloudflared / 反向隧道)把端口送上公网。这两样是另一篇的事,本篇只管省钱。不需要 API key、不需要充值。claude -p 走的是你机器上登录的订阅,烧的是订阅额度——
所以「省 token」=「省你每天的订阅额度」。
浏览器没法直接调本机命令,得有个后端起一个 claude -p 子进程、把结果转给网页。最小可跑版:
app.pyimport json, os, subprocess
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
app = FastAPI()
CLAUDE_BIN = "/opt/homebrew/bin/claude" # which claude
# ★ 省额度的命门:纯聊天就别让它当 agent
# --tools none 把整套工具说明书剥光(十几 k token 直接没了)
# --system-prompt 整张系统提示「换掉」(不是 append!append 会把臃肿默认提示留着)
SYS = "你是一个温柔耐心的陪伴者,说话口语、贴心、有来有回。绝不使用任何工具、不读写文件、不执行命令,只管说话。"
class Req(BaseModel):
prompt: str = ""
@app.post("/chat")
def chat(inp: Req):
env = dict(os.environ)
# 子进程环境很干净,常找不到 node/claude,手动补 PATH、HOME
env["PATH"] = "/opt/homebrew/bin:/usr/local/bin:" + env.get("PATH", "/usr/bin:/bin")
env["HOME"] = os.path.expanduser("~")
def gen():
proc = subprocess.Popen(
[CLAUDE_BIN, "-p",
"--tools", "none", # ★ 扒光工具
"--system-prompt", SYS, # ★ 换掉系统提示(精简人设)
"--output-format", "stream-json",
"--verbose",
"--include-partial-messages", # 少了这个就没有逐字增量
inp.prompt],
cwd=env["HOME"], env=env,
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
text=True, bufsize=1,
)
try:
for line in proc.stdout: # 逐行读 claude 吐的 JSONL
line = line.strip()
if not line:
continue
try:
j = json.loads(line)
except Exception:
continue
# 正文增量都包在 stream_event.event.content_block_delta.delta.text 里
if j.get("type") == "stream_event":
d = (j.get("event") or {}).get("delta") or {}
if d.get("type") == "text_delta" and d.get("text"):
yield "data: " + json.dumps({"delta": d["text"]}, ensure_ascii=False) + "\n\n"
# 结尾结账:把这轮 token / 缓存账单转给前端
elif j.get("type") == "result":
u = j.get("usage") or {}
yield "data: " + json.dumps({"usage": {
"in": u.get("input_tokens"),
"cached": u.get("cache_read_input_tokens"),
"cacheWrite": u.get("cache_creation_input_tokens"),
"out": u.get("output_tokens"),
}}, ensure_ascii=False) + "\n\n"
finally:
try: proc.terminate()
except Exception: pass
yield "data: " + json.dumps({"done": True}) + "\n\n"
return StreamingResponse(gen(), media_type="text/event-stream", headers={
"X-Accel-Buffering": "no", # 公网前面有 nginx 时,防它把 SSE 憋成一坨
"Cache-Control": "no-cache",
})
app.jsasync function chat(prompt, onText) {
const res = await fetch("/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt }),
});
const reader = res.body.getReader();
const dec = new TextDecoder();
let buf = "", text = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buf += dec.decode(value, { stream: true });
let nl;
while ((nl = buf.indexOf("\n")) >= 0) {
const line = buf.slice(0, nl).trim();
buf = buf.slice(nl + 1);
if (!line.startsWith("data:")) continue;
let j; try { j = JSON.parse(line.slice(5).trim()); } catch { continue; }
if (j.delta) { text += j.delta; onText(text); } // 正文累加、实时刷新
if (j.usage) console.log("这轮账单", j.usage); // 看省了多少,见第 4 节
}
}
return text;
}
// 用法
chat("陪我聊聊天", (t) => { box.textContent = t; });
想顺带把模型「开口前的真思维链」也流出来?那是另一篇专门讲的事,参见 claude-p-thinking-stream。本篇专心省钱。
记住两个参数的生死差别:
| 参数 | 干了什么 | 一句话纯聊天输入 |
|---|---|---|
| 默认(什么都不加) | 完整 agent,带全套工具 + 默认系统提示 | 约 2.7 万 token |
--append-system-prompt | 在臃肿默认提示后面追加——工具说明书还在 | 还是两万多(白忙) |
--system-prompt + --tools none | 系统提示整张换成精简人设、工具扒光 | 一两百 token |
很多人想省钱时用了 --append-system-prompt——那是「追加」,臃肿的默认提示和整套工具一个没少,等于没省。
要省,必须用 --system-prompt(替换整张)外加 --tools none(剥光工具)。
实测(macOS,2026-06):
claude -p --tools none --system-prompt "你只管说话,绝不用工具。" \
--output-format json "说一个字:好" 2>/dev/null \
| python3 -c "import sys,json;u=json.load(sys.stdin)['usage'];print('input_tokens =', u['input_tokens'])"
# → input_tokens = 183
一句话:约 2.7 万 token ➜ 183 (差着百来倍)
带上正经的人设(几百字),也就几百到一两 k token,依旧便宜得离谱。
--tools none 之后它没有手了——读不了文件、跑不了命令。拿去给公网网页当聊天后端时,
等于顺手把「被人借你的机器乱翻乱跑」这个风险也焊死了。
很多人以为「提示缓存」是 API 才有的、订阅版没有。错。 claude -p 走订阅照样缓存,
省下来的 cache_read token 只按约 1/10 计费。亲手实测给你看:
# 造一段约 3000 字、逐字不变的系统提示,连打两次同样的前缀
SP=$(python3 -c "print('你是一个温柔耐心的陪伴者,说话口语、贴心、有来有回。'*120)")
# 第 1 次(建缓存)
claude -p --tools none --system-prompt "$SP" --output-format json "说:第一次" 2>/dev/null | …usage
# → creation= 3887 read= 0 (把这段前缀写进了缓存)
# 第 2 次(同一前缀,应命中)
claude -p --tools none --system-prompt "$SP" --output-format json "说:第二次" 2>/dev/null | …usage
# → creation= 137 read= 3750 (★命中!3750 token 从缓存白拿,只收约 1/10)
第二次那 read=3750 就是省下的真金白银——它没重新「读」一遍三千字的系统提示,直接从缓存里捞。
缓存认的是输入前缀的逐字哈希——系统提示哪怕改动一个字、一个空格,缓存就从那个字开始整段作废、重新计费。 把不变的东西(人设、世界观、规则)放在最前面、一个字别动。
时间、当前心情、动态上下文这类每次都不一样的,绝不能掺进稳定前缀——要么放进用户消息里,
要么放在系统提示最后。一旦它们混进前缀,每次哈希都变、缓存永远命不中。
(想进一步避开 Claude Code 自己注入的动态段落,可加 --exclude-dynamic-system-prompt-sections,让前缀更干净更稳。)
提示缓存有最低门槛(约 1024 token 才开始缓存)——太短的前缀不会被缓存
(这也是第 3 节那个 183 token 的例子 cache_read 是 0 的原因:太短,够不着门槛)。
TTL 上有个好消息(2026-07 实测更正):API 裸用的缓存默认只活 5 分钟(花双倍写入价可点 1 小时档),
但 claude -p 替你点的就是 1 小时档——我们扫了自己机器上全部 Claude Code 流水:同会话隔 5–60 分钟再开口的
346 笔里 345 笔命中缓存(最狠一笔隔 51 分钟、73 万 token 全从缓存读回、新付 2 个 token)。
所以经验法则是:一小时内有来有回,缓存一直热着、连续命中,还会越聊越续命;冷过一小时才过期,下次重新建(再花一次 creation)。
一句话:把不变的铺在前头、够长、别动;把会变的甩到后头。 缓存就一直命中,额度就一直省。
不想看原理,照抄就行:
① 装依赖、存后端
pip install fastapi uvicorn
# 把第 2 节那个 app.py 存下来(已含 --tools none + --system-prompt 省钱配置)
② 起后端
uvicorn app:app --port 8000
③ 一行命令验证省钱 + 流式都正常
curl -sN -X POST http://127.0.0.1:8000/chat \
-H 'Content-Type: application/json' \
-d '{"prompt":"陪我聊聊天,说三句"}'
# 一股一股冒字 = 流式正常;最后那条 {"usage":{"in": …}} 里 in 是一两百 = 省钱配置生效
④ 想连命中缓存一起占满:把 app.py 里的 SYS 换成一段够长(≥约 1024 token)、固定不变的人设 / 世界观,
别把时间、心情这种每次会变的写进去——它就会从第二轮起一直命中缓存。
不接网页、就想在终端省着聊,一行就够:
claude -p --tools none \
--system-prompt "你是温柔的陪伴者,只管说话,绝不使用任何工具、不读写文件、不执行命令。" \
"陪我聊聊天"
纯聊天别让 claude -p 当 agent:--tools none 扒光工具 + --system-prompt(替换、别 append)换成精简人设,
一句话输入从约 2.7 万 token 砍到一两百;再把不变的长前缀铺在最前头吃提示缓存(订阅也命中,省约 9 成),
会变的甩到末尾。省额度,就省到这个份上。