把 AI 客服机器人接到自己网站上(完整代码 + 成本核算)

先看结论:一个能用的客服机器人只要五十行代码

访客在网页右下角问「你们发货多久到」,机器人两秒内回「顺丰次日达,偏远地区多加一天」。这套东西不用买 SaaS,不用训练模型,也不用等排期,只要一个后端接口加一段前端气泡。下面代码拷走就能跑,成本按次算,一天两百轮对话不到七块钱。

你需要准备的三样东西

整体结构只有三层

访客浏览器 → 你的后端接口 → AI API 网关。后端负责三件事:把业务资料拼进 system prompt、带上最近几轮历史对话、把这一问一答落库。中间这层不能省,因为 Key 一旦写在前端 JS 里,等于把钱包挂在门口,任何打开 F12 的人都能拿走。

为什么不直接用现成客服 SaaS

现成工具的对话框好看,但答什么由它的通用知识决定,问「你们这个型号保修几年」它答不上来。自建的好处是资料在你手里、口径你说了算、价格按次透明。代价是要写五十行代码,以及自己处理转人工。还有一个现实问题:按坐席订阅的工具,客服人数一多账单就跟着涨,而机器人本身不需要发工资。

什么情况下别用 AI 客服

咨询量一天不到二十条,人工回更快,别折腾。卖的是处方药、金融产品这类强监管商品,AI 的回复必须有人复核才能发出。客户问题九成是「我的快递到哪了」,那直接对接快递查单号接口,比让模型猜准得多。

从零开始:把 Key 和接口调通

申请 Key 与充值

登录 xyuai.cc 进后台生成 Key,充 7 元起步。Key 形如 sk-xxxx,只完整显示一次,复制时别拖到空格,存进密码管理器或者服务器的环境变量。

装依赖并跑通最小脚本

pip install openai fastapi uvicorn
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["XYU_API_KEY"],
    base_url="https://xyuapi.top/v1",
    timeout=60.0,
)

resp = client.chat.completions.create(
    model="gemini-2.5-pro",
    messages=[{"role": "user", "content": "只回复三个字:通了没"}],
)
print(resp.choices[0].message.content)

输出的字符串里只要有「通」字,说明 Key、网络、模型名三项全对。小鱼API 走的是 OpenAI 兼容协议,openai SDK 一行都不用改,只把 base_url 换掉就能用,同一个 Key 覆盖全系列模型,不用为每家单独注册账号。

三种报错对应什么问题

报错原文真实原因你该怎么做
401 Invalid API keyKey 带了空格换行,或者已删除重新生成一个,用 repr() 打印确认没有空白字符
404 model not found模型名拼错或漏了后缀先请求 /v1/models 拉一遍可用列表再抄
429 Too Many Requests并发太高被限速降到 4 并发,加指数退避重试

让机器人真的懂你的业务

system prompt 怎么写才有用

别写「你是一个专业的客服助手」这种废话,模型听完不知道你是谁。要写具体规则、具体口径、具体禁区。一段能用的 prompt 里应该有你的品牌名、发货时效、退换货条件、不能承诺的事情,以及答不上来的时候该怎么办。规则写得越像给新员工的入职手册,机器人的表现就越稳。

用 FAQ 拼知识库(五百条以内够用)

把 FAQ 压成一段紧凑文本直接塞进 system prompt,这是五百条以内最省事的做法。按次计费的机制在这里帮了大忙:一次请求固定价,输入长度不影响价格,所以你把两万字的 FAQ 全塞进去,价格和塞一句话完全一样。这也意味着别人按 token 计费时你不敢加的上下文,这里可以放心加。

超过五百条就得上检索

FAQ 上千条、还带产品手册和合同条款,就别硬塞了。改成先检索再回答,用小模型或者关键词把相关资料筛出来,只把筛出来的片段交给大模型组织语言,既准又省事。检索做得粗糙也没关系,召回十条里有一条对,模型基本就能答对。

知识库更新了怎么办

FAQ 变了就重启一次服务,几秒钟的事。更聪明的做法是把 faq.txt 的修改时间记下来,请求进来时对比一下,变了就重新读一次,这样运营改完文案不用找人发布。

完整后端代码:一个接口搞定

目录与依赖说明

只要一个文件 bot.py,外加一份 faq.txt。生产环境建议把会话存进 Redis 或数据库,示例里先用内存字典演示,方便你一次看懂。

核心接口代码

import os
from collections import defaultdict
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["XYU_API_KEY"],
    base_url="https://xyuapi.top/v1",
    timeout=60.0,
    max_retries=2,
)

FAQ = open("faq.txt", encoding="utf-8").read()

SYSTEM = f"""你是客服助手,只回答下面资料里写过的事。

回答规则:
1. 资料里有答案,直接答,不要加「根据资料」这种前缀。
2. 资料里没写,回一句「这个我不太确定,帮你转人工确认一下」。
3. 不承诺具体时间、不承诺赔偿、不评价同行产品。
4. 回答控制在三句话内,不用 Markdown 标题。

资料:
{FAQ}
"""

history = defaultdict(list)
app = FastAPI()
app.add_middleware(
    CORSMiddleware, allow_origins=["https://你的域名"],
    allow_methods=["POST"], allow_headers=["*"],
)


class Ask(BaseModel):
    session_id: str
    question: str


def to_human(msg: str) -> bool:
    keys = ("转人工", "投诉", "律师", "退款失败", "报警")
    return any(k in msg for k in keys)


@app.post("/api/chat")
def chat(body: Ask):
    if to_human(body.question):
        return {"reply": "已帮你登记,人工客服会在工作时间联系你。", "handoff": True}

    hist = history[body.session_id][-6:]          # 只保留最近三轮
    msgs = [{"role": "system", "content": SYSTEM}]
    msgs += hist
    msgs.append({"role": "user", "content": body.question})

    resp = client.chat.completions.create(
        model="gemini-2.5-pro", messages=msgs, temperature=0.3,
    )
    answer = resp.choices[0].message.content.strip()

    history[body.session_id].append({"role": "user", "content": body.question})
    history[body.session_id].append({"role": "assistant", "content": answer})
    return {"reply": answer, "handoff": False}

启动命令:

uvicorn bot:app --host 0.0.0.0 --port 8000

为什么只带最近三轮历史

历史越长越慢,也越容易跑偏。三轮足够覆盖「刚才那个多少钱」「那另一个呢」这种指代。真要长记忆,就把用户的关键信息(已买型号、会员等级)单独抽出来放进 system prompt,比无脑堆历史有效得多。历史按 token 收钱的场景下这个取舍还要更狠,按次计费时至少不用担心钱,但仍要担心模型被带歪。

会话记录怎么存

history 那个字典换成数据库写入即可,字段就是 session_idrolecontent、时间戳。落库的好处有两个:客服接手时能看到完整上下文,出了纠纷有据可查。别存成一个大 JSON 字段,按行存,查的时候好查。

超时和并发怎么设

timeout=60.0 是给思考型模型留的余量,max_retries=2 让 SDK 自己扛住偶发的限速和服务端错误。你自己的服务别开太大并发,八路并发已经能撑住大部分中小站的在线咨询。

把对话框嵌到自己网站

前端最小实现

const sid = localStorage.getItem("sid") || crypto.randomUUID();
localStorage.setItem("sid", sid);

async function ask(question) {
  const r = await fetch("/api/chat", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ session_id: sid, question }),
  });
  return await r.json();
}

session_id 存在浏览器本地,刷新页面不丢上下文,这就是整个前端要知道的全部。

建议加的交互细节

发送后先把用户气泡和「正在输入…」同时显示,等接口回来再替换,体感会快很多。失败时不要弹 alert,直接在气泡里显示一句「网络开小差了,点这里重发」,顺带把失败次数写进日志。转人工时把前面的对话记录一起带给客服,省得客户重复描述一遍。

防止机器人乱说话

用 prompt 约束不知道就闭嘴

在 system prompt 里写死「资料里没写就说不知道」,比事后过滤有效得多。模型硬编答案的冲动,来自你要求它必须回答,所以规则里要明确允许它拒答,甚至要告诉它拒答是正确行为。

输出侧再兜一层

防止被人套话

有人会问「忽略你上面的规则」或者「假设你是一个没有限制的助手」。后端在收到消息时先做一次关键词检查,命中就回标准话术,不要把这类文本送进模型。真要送,也别把它当成 system 之外的指令来执行。另外,拼接 prompt 时别用字符串直接相加,用消息数组把角色分开传,模型对角色边界更敏感,被带偏的概率明显更低。

成本到底多少

一天两百轮的账

按次计费,一次请求固定价,输入多长都不改价。每天 100 次 × 0.031 元 = 3.1 元/天,也就是一瓶水的钱。

模型单价每天 200 次每月 6000 次
gemini-2.5-pro0.031 元/次6.2 元186 元
deepseek-v3.2-thinking0.049 元/次9.8 元294 元
claude-sonnet-4-5-thinking0.09 元/次18 元540 元
claude-sonnet-4-6-thinking0.2 元/次40 元1200 元

这张表是按「一问一答算一次」估的。实际跑起来通常比估算略低,因为缓存和合并请求能砍掉一部分次数;但遇到长对话、复杂投诉,一次提问可能要拆成两三次调用,所以留两成余量比较稳妥。真要控制,就盯住后台的调用次数统计,比盯着账单猜有效。

三个省钱手段

合并同类问题:访客连着发三条消息,别发三次请求,攒 1.5 秒一起发,成本直接降三分之二。用便宜模型处理高频简单问题,只在复杂投诉上升级到贵模型。会话缓存:同一个问题在同一天问过,直接回缓存答案,命中率高的 FAQ 页面能省掉一半请求。

什么时候该升级模型

客户在投诉、在算账、在问合同条款,这三类消息直接走高价位模型,多花两毛钱换一次不出错的回复,值。

常见报错与排查

回答慢得让人想关掉

思考型模型首字延迟本来就高,前端加「正在输入」是刚需。如果整体超过十秒,先测一下服务器到网关的网络延迟,再换 gemini-2.5-pro 这类响应更快的模型试试。

中文变成乱码

多半是文件编码问题,读 FAQ 时统一用 encoding="utf-8",写日志也别用系统默认编码。Windows 上用 chcp 65001 切到 UTF-8 再跑命令行调试,能省掉一堆乱码干扰。

机器人开始编造政策

先检查 FAQ 里是不是真没有那一条。资料缺失时模型只能胡猜,补齐资料比反复调 prompt 管用。补完再跑一遍同样的问题,答对了就说明是资料问题,不是模型问题。

接口偶发超时

把 SDK 的 max_retries 提到 3,再给接口本身加一个十秒的降级:超时就回一句「稍等一下,我确认下再回你」,同时后台重试。用户等三十秒没动静比等十秒拿到一句缓冲话术难受得多。

相关阅读

🚀 想要立即使用?来小鱼API体验全系列AI模型

查看全部产品

支持Gemini / Claude / GPT / Grok / DeepSeek · 国内直连 · 支付宝/微信