使用 LLM API 构建聊天机器人:Python 教程
Abliterated 模型是大语言模型,其中执行安全拒绝的机制已被移除,允许模型在不触发内容过滤器的情况下回答争议性、成人或小众问题。这种方法让开发者完全控制模型的语气和知识截止日期,对于编码、角色扮演和创意写作尤其有价值,因为标准护栏可能会阻止有用的输出。
已更新

要点
- API 是无状态的,因此你的代码拥有对话历史记录,并在每次请求时发送它。
- 流式输出、工具调用和 JSON 模式均使用标准的 OpenAI 参数。
- 在接近 100,000 token 的上下文窗口限制前,裁剪或总结历史记录。
- 1,000 次聊天,每次输入 2,000 token 和输出 400 token,成本约为 $0.50。
核心理念:聊天机器人是一个循环和一个列表
语言模型在请求之间不会记住任何内容。每次调用 POST /v1/chat/completions 都是独立的。所谓的记忆是你的程序再次发送之前的消息,每条消息都带有角色标签:system 用于固定指令,user 用于用户输入,assistant 用于之前的模型回复。
因此整体架构很小。保持一个列表。追加用户消息。发送列表。追加回复。重复。本教程中从流式输出到工具的所有其他内容都是该循环的变体。
此处的 API 兼容 OpenAI,因此你使用常规的 openai 包。将 base URL 设置为 https://api.abliterated.cc/v1,使用你的密钥,并将模型设置为 uncensored。从 API 密钥页面获取密钥;$0.50 的试用额度足以发送数百条简短测试消息。
步骤 1:构建一个可用的控制台聊天机器人
使用 pip install openai 安装 SDK 并运行此代码。
from openai import OpenAI
client = OpenAI(
base_url="https://api.abliterated.cc/v1",
api_key="YOUR_KEY",
)
messages = [
{"role": "system", "content": "You are a concise, friendly support assistant for a bike shop."}
]
while True:
text = input("you: ")
if text.strip() in {"quit", "exit"}:
break
messages.append({"role": "user", "content": text})
resp = client.chat.completions.create(
model="uncensored",
messages=messages,
temperature=0.7,
)
answer = resp.choices[0].message.content
messages.append({"role": "assistant", "content": answer})
print("bot:", answer)
这是一个完整的聊天机器人。系统消息设定其任务,列表承载记忆,temperature 控制回复的多样性。对于需要事实准确的客服类机器人,将其调低至 0.2 左右;对于创意类机器人,调高至 1.0 左右。
编写设定任务的系统提示词
系统消息是提升聊天机器人性价比最高的地方。保持简短且具体。指明角色、受众、回复的长度和语气,以及当机器人不知道答案时该怎么做。诸如“你为自行车店回答问题。回复最多三句话。如果问题是关于维修价格,请说必须由机械师检查自行车”这样的提示词,比一页通用的美德描述更有效。
两种习惯会有回报。首先,以动作形式而非态度形式放置规则:“当请求不明确时问一个澄清性问题”,而不是“要乐于助人”。其次,使用恶意输入进行测试,例如空消息、非常长的粘贴内容以及与你的产品无关的请求。根据观察结果修复提示词,然后将这些输入保留为回归测试列表。
由于系统消息会在每轮对话中重新发送,其中的每个字词都会在每次请求时被计费。一个 400 token 的提示词,经过 1,000 次聊天,每次 5 轮,将增加 2,000,000 个输入 token,即 2 x $0.15 = $0.30。这很小,但这说明了冗长的提示词在大规模使用时会累积成本。
步骤 2:流式输出回复
在聊天窗口中等待完整回答感觉较慢。设置 stream: true 并在数据到达时逐块打印。流式输出使用服务器发送事件,并以包含用量信息的块结束,因此你可以记录 token 数量。
from openai import OpenAI
client = OpenAI(base_url="https://api.abliterated.cc/v1", api_key="YOUR_KEY")
messages = [{"role": "user", "content": "Explain tire pressure in two sentences."}]
stream = client.chat.completions.create(
model="uncensored",
messages=messages,
stream=True,
)
reply = ""
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
part = chunk.choices[0].delta.content
reply += part
print(part, end="", flush=True)
if getattr(chunk, "usage", None):
print("\ntokens:", chunk.usage.prompt_tokens, chunk.usage.completion_tokens)
记得将组装好的 reply 追加到你的历史记录中,否则下一轮对话将不知道机器人说了什么。
步骤 3:在上下文增长前进行管理
上下文窗口为 100,000 token,由提示词和补全共享。大多数聊天机器人永远不会接近该限制,但由于每一轮都要为整个提示词付费,成本会随历史记录增长。一个简单的策略可以同时控制两者:
- 始终保留系统消息。
- 保留最后 10 到 20 条消息的原文。
- 当列表超过你选择的大小限制(例如 估计 8,000 个 token)时,将最旧的块发送给模型,指令为“在 150 字以内总结此对话”,并用该总结作为系统消息替换该块。
粗略估计每四个英文字符约对应一个 token 就足以触发。每个回复中的 usage 数字会在事后给出确切数字,因此你可以校准限制。
如果通过删除最旧的消息来截断,请以 user 和 assistant 对的形式删除。没有问题的回复,或没有回复的问题,会让模型感到困惑,程度超出你的预期。
步骤 4:为机器人提供工具
函数调用允许模型请求你的代码执行某些操作,例如查询订单。你使用 tools 描述函数,模型以 tool_calls 条目回复,你执行该函数,然后将结果作为 role: "tool" 消息发回。
import json
from openai import OpenAI
client = OpenAI(base_url="https://api.abliterated.cc/v1", api_key="YOUR_KEY")
tools = [{
"type": "function",
"function": {
"name": "get_order_status",
"description": "Look up the status of an order by its id",
"parameters": {
"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"],
},
},
}]
def get_order_status(order_id):
return {"order_id": order_id, "status": "shipped"}
messages = [{"role": "user", "content": "Where is order A1042?"}]
resp = client.chat.completions.create(
model="uncensored", messages=messages, tools=tools, tool_choice="auto"
)
msg = resp.choices[0].message
if msg.tool_calls:
messages.append(msg)
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
result = get_order_status(**args)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(result),
})
final = client.chat.completions.create(
model="uncensored", messages=messages, tools=tools
)
print(final.choices[0].message.content)
在执行任何具有副作用的操作之前,始终验证参数。模型提出调用请求;你的代码决定是否执行它。
步骤 5:使用 JSON 模式进行结构化输出
当程序的下一步需要数据而非文本时,例如将消息分类,请使用带有 response_format 的 JSON 模式。在提示词中也提及 JSON 一词和预期的键。
curl https://api.abliterated.cc/v1/chat/completions \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "uncensored",
"response_format": {"type": "json_object"},
"messages": [
{"role": "system", "content": "Reply with JSON: {\"intent\": string, \"urgent\": boolean}"},
{"role": "user", "content": "My brakes squeal and I ride tomorrow morning."}
]
}'
仍需用 try 块包裹 json.loads。如果解析失败,将错误文本追加后重试一次;失败的请求不收费。
错误、成本和部署注意事项
处理错误、重试和速率限制
真实用户会在不恰当的时刻发送请求,网络也会失败。包装调用,以免一次糟糕的响应导致对话崩溃。
import time
from openai import OpenAI, APIError, RateLimitError
client = OpenAI(base_url="https://api.abliterated.cc/v1", api_key="YOUR_KEY")
def chat(messages, tries=3):
for attempt in range(tries):
try:
resp = client.chat.completions.create(
model="uncensored", messages=messages, temperature=0.7
)
return resp.choices[0].message.content
except RateLimitError:
time.sleep(2 ** attempt)
except APIError as exc:
if attempt == tries - 1:
raise
time.sleep(1)
return "Sorry, the assistant is busy. Please try again in a moment."
完整密钥允许 8 个并发请求和每分钟 300 个请求;试用密钥允许 2 个和 60 个。如果你的机器人同时服务于许多用户,请在队列中放置请求,并将 worker 并发数限制在 8 以下,而不是依赖重试。由于失败的请求不收费,重试没有 token 成本。保持重试次数较小,以便在真实中断时快速失败。
此外,在每次对话轮次中记录 X-Request-Id 头。当回复看起来错误时,该 ID 让你稍后找到确切的请求。
成本和部署
价格为每 1M 输入 token $0.15,每 1M 输出 token $0.50。假设支持聊天总共使用 2,000 个输入 token 和 400 个输出 token。
- 输入:0.002M x $0.15 = $0.0003
- 输出:0.0004M x $0.50 = $0.0002
- 一次聊天:$0.0005,因此 1,000 次聊天成本为 $0.50
在 定价页面 查看当前费率和充值奖励。对于生产环境,将密钥保留在服务器上,不要放在浏览器代码中,并监控 X-Balance-USD 响应头,以便在额度耗尽前发出警报。完整密钥允许 8 个并发请求和每分钟 300 个请求,因此对突发请求进行排队,而不是同时发送。文档 列出了所有支持的参数。
更多指南
问答
构建聊天机器人需要框架吗?
不需要。OpenAI SDK 和消息列表足以构建可用的机器人。仅在需要其额外功能时再添加框架。
API 会记住之前的消息吗?
不会。每个请求都是独立的,因此你的代码必须每次重新发送历史记录。
如何防止历史记录变得过长?
保留系统消息和最近的对话轮次,并在列表长度超过你设定的阈值时,将较早的内容总结为简短的备注。
机器人可以调用我自己的函数吗?
可以。传入 tools 和 tool_choice,当回复包含 tool_calls 时运行函数,并将结果作为工具消息返回。
只差一张表单,即可获得密钥
创建账户,复制密钥,更改base URL。这就是全部设置。