基于 ChatGPT 与 Codex 的网站 AI 客服系统
整体架构、核心模块划分、数据流转、接口定义,以及关键功能的可运行代码实现。
系统整体架构
四个核心模块的职责
前端对话界面
React/Vue 组件:消息列表、输入、打字态、快捷芯片、流式渲染、语种切换与无障碍(aria / 键盘可达)。
后端消息处理服务
会话状态、意图路由、合规护栏、上下文组装、调用 ChatGPT、触发升级与日志审计。
ChatGPT 对话接口集成
OpenAI API:医疗合规 System Prompt、RAG 检索增强、Function Calling 查询 KB/CRM、SSE 流式回复。
Codex 代码生成 / 修复
基于 Codex 的自治代理:生成新意图与 FAQ 代码、修复缺陷、产出 PR,经人工评审后合入。
一次对话的请求链路
- 1用户发送
前端采集消息与 sessionId,经 WSS/HTTPS 加密上送 /api/chat。
- 2意图路由
后端按关键词与分类模型识别意图(价格 / 眼科 / 旅行 / 会员 / 紧急),并组装上下文与知识库片段。
- 3ChatGPT 生成
后端调用 ChatGPT(含合规护栏与 RAG),以 SSE 流式返回;命中转人工条件则路由到人工坐席。
- 4前端渲染
前端增量渲染回复,记录会话日志与满意度,供后续训练与 Codex 自修复使用。
Codex 链路独立运行:开发者提交工单(新意图 / Bug)→ Codex 生成或修复代码 → 产出 Pull Request → 评审合入生产。
关键接口 / 数据契约
POST /api/chat(SSE)请求与响应契约:
// 请求 Request
{
"sessionId": "sess_8f3a", // 会话标识
"message": "种植牙多少钱?", // 用户文本
"lang": "zh-CN" // 语言
}
// 响应 Response(text/event-stream,逐块)
data: {"delta": "单颗种植牙的指示性 USD 区间为 ..."}
data: {"delta": "最终以医院定价为准。"}
data: [DONE]
ChatGPT 调用契约(后端 → OpenAI):
POST https://api.openai.com/v1/chat/completions
{
"model": "gpt-4o-mini",
"stream": true,
"messages": [
{"role":"system","content":"合规护栏 + 知识库片段"},
{"role":"user","content":"[zh-CN] 种植牙多少钱?"}
]
}
Codex 任务契约(后端 → Codex):
{
"task": "为价格意图新增 FAQ 追问:是否含骨增量费用",
"file": "src/intents/price.js",
"failing_test": null,
"review": "required" // 必须人工评审后合入
}
关键功能实现(可直接运行)
后端消息处理服务 + ChatGPT 集成(Node 18+,需 express 与 openai):
// server.js
import express from 'express';
import OpenAI from 'openai';
const app = express();
app.use(express.json());
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
// 医疗合规护栏:禁止诊断 / 开方 / 疗效承诺
const GUARDRAILS = [
'你是跨境医疗旅行管家网站的 AI 助理,仅提供客观流程与资质信息。',
'绝不提供诊断、不开具处方、不承诺疗效、不比较医生优劣。',
'具体治疗须经执业医生面诊;费用以医院定价为准。',
'紧急医疗情况引导拨打当地急救电话(中国 120 / 美国 911)。',
'不确定时转人工坐席(escalate)。'
].join(String.fromCharCode(10));
// 意图路由:命中关键词直接定类,否则 general
const INTENTS = {
price: /价格|比价|费用|报价|usd|\$/i,
eye: /眼科|近视|激光|smile|lasik|icl|白内障/i,
travel: /行程|签证|机票|酒店|陪护/i,
member: /会员|登录|账号|保险/i,
emergency: /紧急|急救|意外|危险/i
};
function route(text){ for(const k in INTENTS) if(INTENTS[k].test(text)) return k; return 'general'; }
app.post('/api/chat', async (req, res) => {
const { sessionId, message, lang = 'zh-CN' } = req.body;
const intent = route(message);
const kb = await retrieveKB(intent, message); // 向量检索占位
const stream = await openai.chat.completions.create({
model: 'gpt-4o-mini', stream: true,
messages: [
{ role:'system', content: GUARDRAILS + '\n知识库片段:\n' + kb },
{ role:'user', content: '[' + lang + '] ' + message }
]
});
res.setHeader('Content-Type','text/event-stream');
for await (const chunk of stream) {
const t = (chunk.choices[0] && chunk.choices[0].delta && chunk.choices[0].delta.content) || '';
if (t) res.write('data: ' + JSON.stringify({ delta: t }) + String.fromCharCode(10) + String.fromCharCode(10));
}
res.write('data: [DONE]' + String.fromCharCode(10) + String.fromCharCode(10));
res.end();
});
async function retrieveKB(intent, q){ return '(示例)该意图的公开资质与价格区间说明。'; }
app.listen(3000, () => console.log('AI chat service on :3000'));
前端流式调用(浏览器):
// chat-client.js
export async function sendChat(sessionId, message, lang, onDelta) {
const res = await fetch('/api/chat', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({ sessionId, message, lang })
});
const reader = res.body.getReader();
const dec = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
for (const line of dec.decode(value).split(String.fromCharCode(10))) {
if (line.startsWith('data: ')) {
const payload = line.slice(6);
if (payload === '[DONE]') return;
onDelta(JSON.parse(payload).delta);
}
}
}
}
Codex 代码生成 / 修复模块(Python,需 openai,接入 Codex 时替换 model):
# codex_agent.py
import os, openai, sys
client = openai.OpenAI(api_key=os.environ['OPENAI_API_KEY'])
def generate_or_fix(task, file_path=None, failing_test=None):
ctx = ''
if file_path and os.path.exists(file_path):
ctx += '\n# 现有文件 ' + file_path + '\n' + open(file_path, encoding='utf-8').read()
if failing_test:
ctx += '\n# 失败测试输出\n' + failing_test
prompt = ('你是客服系统的代码工程师。任务:' + task +
'\n只输出可合并的代码 diff 或完整文件,并附简短说明。\n' + ctx)
r = client.chat.completions.create(
model='gpt-4o', # 接入 Codex 时替换为对应 code 模型
messages=[{'role':'system','content':'你是资深前端 / Node 工程师,熟悉本客服系统。'},
{'role':'user','content':prompt}])
return r.choices[0].message.content
if __name__ == '__main__':
print(generate_or_fix(sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else None))