中文 ▾
获取 API 密钥

使用 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。这就是全部设置。