将免费 LLM 引入 Python 脚本的两种方式
当人们搜索 Python 免费 LLM 时,通常指两种情况。第一种是你下载并自行运行的开放权重模型,使用 Ollama、llama.cpp 或 Hugging Face transformers 库等工具。第二种是托管接口,其成本极低或提供足够的试用额度,使其在原型开发中感觉免费。两者均合理,适用于不同场景。
本地运行意味着无需按 token 计费,数据也不会离开你的机器。这也意味着你需要为所选模型配备足够的 RAM 或 GPU 显存,需要等待下载,且能装进笔记本电脑的小型模型回答效果不如大型模型。托管 API 消除了硬件需求,但试用额度用完后需按 token 付费。本页诚实地介绍两者,然后展示本站托管选项的 Python 代码,该接口兼容 OpenAI,可与标准 openai 包配合使用。
本地模型与托管 API 对比
| 问题 | 自行运行 | 托管预付 API |
|---|---|---|
| 每个 token 的成本 | 无,但需支付硬件和电力费用 | 每 1M 输入 token $0.15,每 1M 输出 token $0.50 |
| 设置时间 | 安装运行时,下载权重,调整设置 | 安装一个包,粘贴密钥 |
| 上下文长度 | 受你的内存限制 | 100,000 token,包含提示词和补全 |
| 并发 | 取决于你的机器处理能力 | 完整密钥支持 8 个并发请求,试用密钥支持 2 个 |
| 拒绝响应 | 取决于你选择的权重 | 针对合法的成人使用进行无内容拒绝调优 |
| 离线使用 | 是 | 否 |
如果你有性能强大的 GPU 且工作涉及隐私或离线,本地方案是很好的选择,你可以停止阅读。如果你想要更大的模型而无需拥有硬件,或希望脚本在笔记本、服务器和 CI 运行器上表现一致,托管方案更简单。许多项目同时使用两者:开发时使用小型本地模型,生产时使用托管模型,只需更改一个 base URL 即可切换。
五分钟内设置托管接口
你需要先获取密钥。使用 Google 或电子邮件和密码注册,然后在 API 密钥页面 一键领取 $0.50 试用额度。试用有效期 7 天,每人限领一次,无需付款。然后安装 SDK 并将密钥保留在源代码之外。
pip install openai
export NOTRACK_API_KEY="sk-your-key-here"
客户端需要从标准 OpenAI 设置中进行两项更改:base URL 和密钥。模型 ID 为 uncensored,仅提供一种模型。
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.nsfwvideogenerator.cc/v1",
api_key=os.environ["NOTRACK_API_KEY"],
)
resp = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": "You are a concise writing assistant."},
{"role": "user", "content": "Give me three title ideas for a noir short story."},
],
temperature=0.8,
)
print(resp.choices[0].message.content)
print(resp.usage)
认证使用标准的 Authorization: Bearer 头,SDK 会自动为你添加。有两种方法:POST /v1/chat/completions 和 GET /v1/models。你可以通过 client.models.list() 确认连接,该调用返回可用的模型 ID。完整参数列表见 文档页面。
流式响应
对于用户交互的内容,使用流式输出。设置 stream=True 并在服务器发送事件到达时遍历数据块。流结束时报告使用情况,因此你仍可记录 token 数量。
stream = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": "Write a four-line poem about rain on a tin roof."}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
usage = getattr(chunk, "usage", None)
if usage:
print("\n", usage)
一个细节容易让人忽略:流式请求会占用其并发槽位,直到流结束。对于有 2 个槽位的试用密钥,两个打开的流意味着第三次调用会收到带有代码 concurrency 的 429 错误。务必消费或关闭流。
工具调用和 JSON 模式
该接口支持 tools、tool_choice、流式 tool_calls 以及以 role: "tool" 形式返回的结果。这里有一个包含一个函数的完整往返示例。
import json
tools = [{
"type": "function",
"function": {
"name": "word_count",
"description": "Count the words in a piece of text.",
"parameters": {
"type": "object",
"properties": {"text": {"type": "string"}},
"required": ["text"],
},
},
}]
messages = [{"role": "user", "content": "How many words are in: the quick brown fox jumps"}]
first = client.chat.completions.create(
model="uncensored", messages=messages, tools=tools, tool_choice="auto"
)
msg = first.choices[0].message
if msg.tool_calls:
messages.append(msg)
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
result = len(args["text"].split())
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": str(result),
})
final = client.chat.completions.create(model="uncensored", messages=messages, tools=tools)
print(final.choices[0].message.content)
务必防御性地解析 arguments;模型将其作为 JSON 字符串返回,且任何模型偶尔都会生成格式错误的字符串。对于不使用工具的结构化输出,请使用 JSON 模式。
resp = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": "Reply with a JSON object with keys title and tags (list of strings)."},
{"role": "user", "content": "Describe a lighthouse keeper's diary."},
],
response_format={"type": "json_object"},
temperature=0.2,
)
data = json.loads(resp.choices[0].message.content)
print(data["title"], data["tags"])
提及 JSON 并在提示词中命名键,同时保持低 temperature。在生产代码中,将 json.loads 包裹在 try 块中。
速率限制、错误和重试
完整密钥允许 8 个并发请求和每分钟 300 个请求。试用密钥允许 2 个和 60 个。超过限制时,你会收到 HTTP 429 错误,代码为 rate_limited(每分钟过多)或 concurrency(同时过多)。在临时过载时,API 还会返回 429,并带有 Retry-After: 5 头。SDK 会自动重试某些错误;对于扇出脚本,使用小型显式包装器更清晰。
import time
from concurrent.futures import ThreadPoolExecutor
from openai import RateLimitError
def ask(prompt, tries=4):
for attempt in range(tries):
try:
r = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": prompt}],
max_tokens=300,
)
return r.choices[0].message.content
except RateLimitError:
time.sleep(5 * (attempt + 1))
raise RuntimeError("still rate limited")
prompts = [f"Summarize idea number {i} in one sentence." for i in range(20)]
with ThreadPoolExecutor(max_workers=2) as pool: # 8 on a full key
results = list(pool.map(ask, prompts))
将 max_workers 匹配到你的密钥的并发限制,可在大多数 429 错误发生前防止它们。每个响应还包含 X-Request-Id、X-Balance-USD 和两个速率限制头,你可以在需要调试或支持时通过 client.chat.completions.with_raw_response.create(...) 读取它们。
如何选择本地模型与托管 API
抛开意识形态,回答关于项目的四个问题。
- 数据必须保留在你的机器上吗? 如果合同或习惯要求如此,请在本地运行。托管的提示词在此不用于训练,但它们确实会通过网络传输。
- 你有多少内存? 模型大小决定质量。没有专用 GPU 的笔记本电脑会将你限制在小模型上,并且答案会显示出这一点。托管模型为你提供具有 100,000 token 上下文窗口的大模型,这在本地很难匹配。
- 你将发送多少请求? 每周几百个请求在任何情况下都微不足道。每月数十万请求会使托管账单变成具体金额,如果本地服务器闲置,本地部署就会显得有吸引力。计算一下:100,000 个请求,每个请求 1,000 个输入 token 和 500 个输出 token,费用为 100,000 x $0.0004 = $40。
- 你需要以特定方式处理拒绝吗? 许多开放权重模型自带内置的拒绝行为,其他模型则经过调整以消除它。这里的托管模型经过调整,以便在合法成人使用时无需内容拒绝即可回答,因此你不需要寻找正确的权重。
如果你的答案指向两者,请先从托管开始一周。试用免费,你将了解提示词的需求,然后再购买硬件或下载另一个多 GB 文件。
Python 脚本中的常见错误
人们遇到的大多数问题都是小问题且可修复。
- 错误的 Base URL。 它必须以
/v1结尾。缺少后缀会导致 404,而不是有用的消息。如果有疑问,请打印client.base_url。 - 密钥在代码中。 如上所述从环境变量中读取它,并将你的
.env文件添加到.gitignore。 - 错误的模型名称。 唯一的模型 ID 是
uncensored。其他提供者的名称将不起作用,因此在移植代码时请更改它们。 - 无界循环。 永不结束的调用工具循环是一个昂贵的错误。将其限制在,例如,五轮。
- 忽略上下文预算。 提示词和完成共享 100,000 个 token。如果你永远附加每一轮,你最终会达到限制。修剪或总结旧消息。
- 假设 JSON 总是有效的。 即使在 JSON 模式下,在使用之前也要检查你需要的键。
当服务器端出现异常时,在联系 支持 时引用响应中的 X-Request-Id 值。这能让运营人员找到你的具体调用。
托管选项在 Python 工作中的成本
典型脚本调用,包含 1,000 个输入和 500 个输出 token,成本为 $0.00015 加上 $0.00025,即 $0.0004。$0.50 的试用额度大约可以覆盖 1,250 次调用。$20 的充值(最低额度)可以覆盖 50,000 次。额度永不过期,失败或被拒绝的请求不收费,且提示词不用于训练。充值最高可达 $500,从 $50 起有奖金,支付方式为加密货币,TRC20 上的 USDT 或 Base 上的 USDC。详见 定价页面。
仅限文本:无图像、音频、视频、嵌入或微调。唯一的硬性规则是不涉及未成年人的色情内容。对于需要更少拒绝的文本生成的 Python 项目,这就是全部功能。
