用户在对话框里随便问,系统能听懂、查得到、答得准。这篇我把搭一套能落地的 AI 客服,从架构到代码、到中途怎么接中转 API,原原本本写给你。
写在前面
几乎每个做产品的人都动过"上个 AI 客服"的念头。道理很简单:人工客服贵、有班次、半夜没人,而用户的问题 80% 是重复的——退换货怎么弄、订单到哪了、这个功能怎么用。这些重复问题,正是 AI 最擅长接的活。
但真去搭,很多人第一步就卡住:直接拿大模型当客服,它会一本正经地胡编 。用户问"我的订单 12345 到哪了",模型根本不知道你的订单系统,只会瞎猜。所以一套能用的 AI 客服,绝不是"接个大模型 API"那么简单,它至少得有三层能力:听懂问题、查到答案、稳定作答 。
这篇就带你把这套系统从零搭起来。我会把架构、知识库怎么接、多轮会话怎么管、关键代码、以及中途接入 4sapi.com 这个中转 API 的具体步骤都讲清楚。
一、先想清楚:一个靠谱的 AI 客服要干哪几件事
动手之前先理清链路。用户发来一句话,系统要顺序做完这几件事:
用户提问
↓
① 意图识别 这句话是问订单?问售后?还是闲聊?
↓
② 知识检索 从知识库/业务系统里捞出相关资料(RAG)
↓
③ 答案生成 把检索到的资料 + 问题,交给大模型组织成回答
↓
④ 兜底与转人工 答不了的,老实承认并转人工,绝不硬编 Copy
这里头 ①③ 靠大语言模型 ,② 的语义检索靠向量嵌入(Embedding)模型 。两类模型,又是分散在不同厂商——这正是我们中途要引入中转 API 的原因。
二、架构设计:四个 Agent + 一个中转网关
我把系统拆成"一个总调度 + 四个职责 Agent + 一个统一网关":
┌──────────────────────────┐
用户消息 ──→ │ Dispatcher 会话总调度 │
└─────────────┬────────────┘
│ 按意图分流
┌──────────────┬──────────┼──────────┬──────────────┐
↓ ↓ ↓ ↓ ↓
[意图Agent] [检索Agent] [回答Agent] [兜底Agent] 会话记忆
└──────────────┴──────────┼──────────┴──────────────┘
↓
┌──────────────────────────┐
│ 统一 API 网关 (4sapi) │ ← 文本/Embedding 都走这
└──────────────────────────┘ Copy
关键设计点还是最底层:所有 Agent 不直接调各家官方 API,统一走一个中转网关。 对客服这种场景,好处尤其明显:
对话模型和 Embedding 模型用同一个 Key :检索要 Embedding、回答要 Chat,一套凭证全搞定;
换模型不动业务 :哪天发现某个模型答得更好/更便宜,改一个 model 参数就切;
用量集中可控 :客服是高频调用场景,所有消耗在一个后台看,对账和预算一目了然。
我选的网关是 4sapi.com ,兼容 OpenAI 格式,对话和 Embedding 模型都能通过它统一调用。
三、中途接入 4sapi:三步把网关接通
整套系统再漂亮,模型调不通就是空架子。接中转 API 其实就三步。
第一步:拿 Key、记住 BaseURL
注册 4sapi.com 后,在后台创建一个 API 令牌(形如 sk-xxxxxxxx),并记下接口地址:
放进环境变量,别硬编码进代码 :
bash
# .env
FOURSAPI_BASE_URL = https://4sapi.com/v1
FOURSAPI_KEY = sk-你的令牌 Copy
第二步:封装统一客户端
4sapi 兼容 OpenAI 格式,直接用 openai SDK,把 base_url 指过去。对话和 Embedding 都从这里复用:
python
# gateway.py —— 统一 API 网关客户端
import os
from openai import OpenAI
client = OpenAI(
api_key = os.environ[ "FOURSAPI_KEY" ],
base_url = os.environ[ "FOURSAPI_BASE_URL" ], # 关键:指向中转网关
)
def chat (model, messages, ** kwargs):
resp = client.chat.completions.create(
model = model, messages = messages, ** kwargs
)
return resp.choices[ 0 ].message.content
def embed (text, model = "text-embedding-3-small" ):
"""语义检索要用的向量,也走同一个网关"""
resp = client.embeddings.create( model = model, input = text)
return resp.data[ 0 ].embedding Copy
第三步:发请求验证连通
业务别急着写,先确认网关通了:
python
if __name__ == "__main__" :
print (chat(
model = "gpt-4o-mini" ,
messages = [{ "role" : "user" , "content" : "回复两个字:通了" }],
)) # 正常应打印「通了」 Copy
能打印结果,说明对话和 Embedding 两条路都打通了。本质就是「改一个 base_url + 配一个 Key」。
四、逐个环节实现:四个 Agent 怎么写
4.1 意图 Agent —— 先分清用户到底想干嘛
别上来就回答。先让模型给问题贴个标签,决定后面怎么走:
python
def intent_agent (question):
prompt = f """判断用户问题属于哪类,只返回一个词:
order(订单/物流) / aftersale(退换售后) / faq(功能咨询) / chat(闲聊) / other
用户问题: { question } """
return chat(
model = "gpt-4o-mini" , # 分类用小模型够了,省钱
messages = [{ "role" : "user" , "content" : prompt}],
).strip() Copy
4.2 检索 Agent —— 从知识库捞出真凭据(RAG 核心)
这是 AI 客服不胡编的关键。先把知识库文档切片、做好向量索引(离线一次性完成);用户提问时,把问题也转成向量,捞出最相关的几条:
python
import numpy as np
def retrieve_agent (question, kb_vectors, kb_texts, top_k = 3 ):
q_vec = embed(question) # 问题向量,走网关
# 余弦相似度排序,取最相关的 top_k 条
sims = [np.dot(q_vec, v) for v in kb_vectors]
idx = np.argsort(sims)[ - top_k:][:: - 1 ]
return [kb_texts[i] for i in idx] Copy
实际生产里别自己手撸相似度,用 FAISS / Milvus 这类向量库扛检索。这里写成裸算法只是让你看清原理。
4.3 回答 Agent —— 拿着证据组织人话
把检索到的资料塞进 prompt,严令模型只能基于资料作答 ,这是压制幻觉的关键一招:
python
def answer_agent (question, contexts, history):
context_text = " \n " .join(contexts)
system = f """你是客服助手。只能依据【参考资料】回答,
资料里没有的,就说"这个我需要帮您转接人工",绝不要编。
【参考资料】
{ context_text } """
messages = [{ "role" : "system" , "content" : system}]
messages += history # 带上多轮上下文
messages.append({ "role" : "user" , "content" : question})
return chat( model = "gpt-4o" , messages = messages, temperature = 0.3 ) Copy
注意 temperature=0.3——客服场景要的是稳定准确,不是创意发挥,温度调低能显著减少胡说。
4.4 兜底 Agent —— 答不了就老实转人工
python
def fallback_agent (question, ticket_system):
# 触发条件:检索为空、用户连续追问、或回答里出现"转接人工"
ticket_system.create(question) # 落工单,转人工
return "这个问题我帮您转接人工客服,请稍候~" Copy
五、串起来:会话总调度
四个 Agent 用一个调度器按意图编排,并维护多轮会话记忆:
python
def handle_message (question, session):
history = session.get_history() # 取该用户的多轮上下文
intent = intent_agent(question)
if intent == "chat" :
reply = chat( model = "gpt-4o-mini" ,
messages = history + [{ "role" : "user" , "content" : question}])
else :
contexts = retrieve_agent(question, KB_VEC , KB_TXT )
if not contexts: # 检索不到 → 兜底
reply = fallback_agent(question, TICKET_SYS )
else :
reply = answer_agent(question, contexts, history)
session.append(question, reply) # 写回会话记忆
return reply Copy
所有模型调用——分类、检索、回答——全走同一个 4sapi 网关,一个 Key 搞定 。
六、前端:一个能嵌进任何页面的对话窗
前端就是个常见的聊天气泡窗,核心是流式输出,让回答一个字一个字蹦出来,体验不卡:
jsx
function ChatWidget () {
const [ msgs , setMsgs ] = useState ([]);
const [ input , setInput ] = useState ( "" );
async function send () {
setMsgs ( m => [ ... m, { role: "user" , text: input }]);
const res = await fetch ( "/api/chat" , {
method: "POST" ,
body: JSON . stringify ({ question: input }),
});
// 用 SSE 流式接收,逐字渲染助手回复
}
return (
< div className = "chat-widget" >
< MsgList items = {msgs} />
< input value = {input} onChange = { e => setInput (e.target.value)}
placeholder = "有什么可以帮您?" />
< button onClick = {send}>发送</ button >
</ div >
);
} Copy
安全提醒:/api/chat 背后每次都在烧 token,上线前务必加用户鉴权 + 单用户频率限制 ,否则被人写脚本刷一晚上,账单很难看。另外,知识库里别塞敏感数据,模型有可能在回答里带出来。
七、几条实战经验
意图识别用小模型,回答才上大模型 。客服 80% 是分类和检索,真正需要 gpt-4o 的只有最后组织语言那一步,账单立刻降一截。
回答 Agent 必须强约束"只依据资料" 。这一句 system prompt,是 AI 客服从"能用"到"敢上线"的分水岭。
多轮会话记忆要做长度截断 。别把几十轮全塞进去,token 爆炸还更容易跑偏,留最近 6~8 轮足够。
检索结果先过一遍相关度阈值 。相似度太低的就当没检索到,直接走兜底,强行硬答比转人工更伤口碑。
所有网关调用包重试 + 超时 。高频场景下偶发超时很正常,别让一次失败把整个会话搞挂。
写在最后
搭一套 AI 客服,难的从来不是"调用大模型",而是怎么让它在不知道的时候老实闭嘴、在知道的时候答得准 。
意图先分流、检索给证据、回答带约束、答不了转人工——这四件事想清楚,AI 客服就敢上线了。
中途接入 4sapi 这一步,把"对话模型 + Embedding 模型分头对接"的麻烦,收敛成了一个 base_url、一个 Key。复杂度挡在网关后面,业务代码只管编排逻辑 ——这套思路,在后面几篇里你还会反复见到。
本文代码为说明思路的简化示意,具体模型名称、接口字段请以 4sapi.com 官方文档为准。涉及付费接口,请做好用量控制与数据合规。