首页 / 博客 / AI·大模型接入

微信机器人多轮对话与上下文记忆

分类:AI·大模型接入 · 标签:微信机器人多轮对话、上下文记忆、微信机器人开发

前言

大多数微信机器人只能做"一问一答"——用户发一句话,机器人回一句,下一条消息又从零开始。这在客服场景、AI助手场景中严重制约体验:用户说"帮我查一下上海的天气",紧接着说"那北京呢",机器人却完全不知道"那北京"指什么。多轮对话与上下文记忆正是解决这一问题的核心技术。本文从原理到实操,结合 WechatApi 的 iPad 协议接入方案,带你完整搭建一套有记忆的微信机器人系统。

一、多轮对话的核心挑战:状态管理

单轮对话无状态,处理起来最简单——收到消息,生成回复,结束。多轮对话的难点在于:谁在说话、说了什么、上下文是什么,这三件事必须在每一轮都正确维护。

具体来说,开发者面临三个层面的挑战:

1. 会话识别:微信消息进来时,如何判断这条消息属于哪个"对话线程"?个人微信中,一个 fromUser(发消息人的微信ID)通常对应一个对话,但群聊里多人同时聊,需要用 fromUser + roomId 联合作为会话键。

2. 上下文窗口:大语言模型(LLM)有 token 限制,不可能把从第一条消息开始的所有历史都塞进去。需要设计滑动窗口、摘要压缩等策略,在"记得够多"和"不超限"之间取得平衡。

3. 超时与重置:用户可能隔了一天再回来聊,此时继续上次的上下文还是重置?通常以会话超时时间(如 30 分钟)为界,超时则自动开启新对话。

理解了这三个挑战,后面的方案设计就有了明确目标。

二、WechatApi 接入:拿到稳定的消息流

要实现多轮对话,首先得有稳定可靠的消息接收机制。个人微信并没有官方开放接口,市面上常见方案是 Hook 注入或协议层模拟。WechatApi 基于 iPad 协议实现,无需在 Windows 上运行微信客户端,设备以 iPad 身份登录,稳定性和存活率显著优于 PC Hook 方案。

接入流程简述:

  1. 前往 控制台 注册账号,获取 appId(设备ID)和 VideosApi-token(鉴权 Token)。
  2. 在控制台配置消息回调 Webhook 地址(你自己服务器的 HTTPS 接口)。
  3. 扫码登录个人微信账号,设备上线后,所有收到的消息都会以 POST 请求推送到你的 Webhook。

消息体示例(WechatApi 推送格式):

json{
  "appId": "wx_device_001",
  "msgType": 1,
  "content": "那北京呢",
  "fromUser": "wxid_abc123",
  "toUser": "wxid_self",
  "roomId": "",
  "msgId": "7890123456",
  "createTime": 1718000000
}

roomId 为空表示私聊,有值则是群消息。fromUser 是发送者 wxid,这两个字段组合起来就是我们的会话键。

三、上下文存储方案设计

拿到消息流后,需要设计上下文存储结构。推荐使用 Redis 作为会话存储,原因是:读写速度快、天然支持过期(TTL 即超时重置)、结构灵活。

会话键设计:

场景会话键格式示例
私聊session:{appId}:{fromUser}session:wx001:wxid_abc123
群聊(区分发言人)session:{appId}:{roomId}:{fromUser}session:wx001:room456:wxid_abc123
群聊(群共享上下文)session:{appId}:{roomId}session:wx001:room456

群聊是否共享上下文取决于业务需求。客服机器人通常用私聊或区分发言人;群助手(如 FAQ 机器人)有时需要群共享,让后续问题能引用前面的讨论。

存储结构:

每个会话键对应一个 JSON 列表,按时间顺序存放对话轮次:

json[
  {"role": "user", "content": "帮我查一下上海的天气", "ts": 1718000000},
  {"role": "assistant", "content": "上海今天晴,26°C。", "ts": 1718000005},
  {"role": "user", "content": "那北京呢", "ts": 1718000060},
  {"role": "assistant", "content": "北京今天多云,22°C。", "ts": 1718000065}
]

设置 TTL(如 1800 秒 = 30 分钟),用户超过 30 分钟不说话,下次发消息时 Redis 键已过期,自动开启新对话,无需手动清理。

滑动窗口控制:

为了不超出 LLM 的 token 限制,每次组装 prompt 时只取最近 N 轮(如最近 10 轮)。如果需要保留更长期的记忆,可以定期对历史做摘要压缩,把"前 20 轮"压缩成一段 200 字的摘要,插入到上下文开头。

四、完整代码实现:Python Flask 示例

下面给出一个完整的 Python 实现骨架,涵盖消息接收、上下文管理、调用 LLM、通过 WechatApi 回复三个环节。

pythonimport json
import redis
import requests
from flask import Flask, request, jsonify

app = Flask(__name__)
r = redis.Redis(host='localhost', port=6379, decode_responses=True)

WECHAT_API_BASE = "https://api.wechatapi.net"   # 示意域名,以控制台为准
VIDEOS_API_TOKEN = "your_videos_api_token_here"
APP_ID = "your_app_id_here"
SESSION_TTL = 1800   # 30 分钟超时
MAX_HISTORY = 10     # 最多保留最近 10 轮

def get_session_key(from_user, room_id=""):
    if room_id:
        return f"session:{APP_ID}:{room_id}:{from_user}"
    return f"session:{APP_ID}:{from_user}"

def load_history(session_key):
    raw = r.get(session_key)
    if raw:
        return json.loads(raw)
    return []

def save_history(session_key, history):
    # 只保留最近 MAX_HISTORY 轮(每轮 2 条:user + assistant)
    if len(history) > MAX_HISTORY * 2:
        history = history[-(MAX_HISTORY * 2):]
    r.set(session_key, json.dumps(history, ensure_ascii=False), ex=SESSION_TTL)

def call_llm(messages):
    """调用你选择的 LLM,返回回复文本(此处为示意,替换为实际 SDK)"""
    # 例如调用 OpenAI / Claude / 本地模型 等
    # response = openai.ChatCompletion.create(model="gpt-4o", messages=messages)
    # return response.choices[0].message.content
    return "(LLM 回复占位)"

def send_wechat_message(to_user, content, room_id=""):
    """通过 WechatApi 发送消息"""
    payload = {
        "appId": APP_ID,
        "toUser": to_user,
        "content": content,
    }
    if room_id:
        payload["roomId"] = room_id
    headers = {
        "VideosApi-token": VIDEOS_API_TOKEN,
        "Content-Type": "application/json"
    }
    resp = requests.post(
        f"{WECHAT_API_BASE}/send-text",   # 示意路径
        json=payload,
        headers=headers
    )
    return resp.json()

@app.route("/webhook", methods=["POST"])
def webhook():
    data = request.json
    msg_type = data.get("msgType", 0)
    if msg_type != 1:   # 只处理文本消息
        return jsonify({"ret": 200})

    from_user = data["fromUser"]
    room_id = data.get("roomId", "")
    user_text = data["content"]

    session_key = get_session_key(from_user, room_id)
    history = load_history(session_key)

    # 追加用户消息
    history.append({"role": "user", "content": user_text})

    # 组装带系统提示的消息列表
    messages = [{"role": "system", "content": "你是一个专业的微信智能助手。"}]
    messages += [{"role": h["role"], "content": h["content"]} for h in history]

    # 调用 LLM
    reply = call_llm(messages)

    # 追加 assistant 回复,保存历史
    history.append({"role": "assistant", "content": reply})
    save_history(session_key, history)

    # 发送回复
    send_wechat_message(from_user, reply, room_id)
    return jsonify({"ret": 200})

if __name__ == "__main__":
    app.run(port=8080)

代码结构清晰,核心逻辑在 /webhook 路由中:取历史 → 组装 prompt → 调用 LLM → 存历史 → 发回复,五步走。

五、主动重置与指令识别

上下文记忆并非越多越好。有几种情况需要主动重置会话:

用户主动重置:识别特定指令,如"重新开始"、"清除记录"、"新对话",收到后立即删除 Redis 键。

话题切换检测:可以在每轮调用 LLM 时加一个判断:*当前用户输入与上一轮的话题是否发生了明显切换?* 如果是,可以只保留最近 2 轮而非 10 轮,减少无关上下文干扰。

业务场景隔离:对于微信客服机器人而言,一次客服会话结束(如用户说"谢谢,再见")后应自动归档并重置上下文,下次来的是新问题,不应该继承上次的投诉记录。

用 bash 快速测试 Redis 会话状态:

bash# 查看某用户的会话历史(原始 JSON)
redis-cli GET "session:wx_device_001:wxid_abc123"

# 查看剩余 TTL(秒)
redis-cli TTL "session:wx_device_001:wxid_abc123"

# 手动重置某用户会话
redis-cli DEL "session:wx_device_001:wxid_abc123"

# 查看所有活跃会话数量
redis-cli KEYS "session:wx_device_001:*" | wc -l

六、群聊多轮对话的特殊处理

群聊场景远比私聊复杂。几个关键问题:

@触发 vs 关键词触发:群里消息量大,机器人不应该响应所有消息。通常有两种激活方式——被 @ 时响应(content 里包含 @机器人昵称);或者消息包含特定前缀/关键词时响应。WechatApi 推送的消息体中 content 字段包含完整原始文本,开发者自行判断是否需要回复即可。

多人并发上下文:群里同时有多人在和机器人对话,需要以 roomId + fromUser 组合作为会话键(而不是只用 roomId),确保 A 的上下文不会干扰 B。

群共享摘要微信群管理机器人有时需要对群内讨论做摘要,这种场景下可以维护一个群级别的"公共上下文",独立于每个用户的私有会话,专门用于群内容聚合。

防刷保护:多轮对话系统要做好频率限制。同一用户 1 分钟内发超过 N 条消息,进入冷却状态,不再调用 LLM,避免被刷爆接口费用。Redis 的 INCR + EXPIRE 组合可以轻松实现滑动窗口限流。

七、上下文摘要压缩:让记忆走得更远

当对话超过 20 轮,直接截断会丢失重要信息(比如用户在第 3 轮说了自己的姓名和需求,但第 20 轮才追问细节)。摘要压缩是更优雅的解法:

压缩时机:当历史长度超过阈值(如 20 轮),触发摘要。

压缩方式:取前 15 轮历史,让 LLM 生成一段 200 字以内的摘要,描述对话的核心背景信息(用户身份、已解决问题、待处理事项等)。

重组上下文:把摘要作为一条特殊的 system 消息插入,保留最近 5 轮原始对话,组合成新的上下文继续对话。

这样,即使对话持续几小时,机器人也不会"失忆",同时 token 消耗始终在可控范围内。对于需要长期跟进用户的微信SCRM场景,还可以将摘要持久化到数据库(如 MySQL),实现跨天、跨设备的长期用户画像积累。

八、注意事项与常见坑

1. 消息去重:网络抖动可能导致同一条消息被推送两次。用 msgId 做幂等判断,同一 msgId 收到后直接忽略第二次。

2. 异步化处理:Webhook 接口必须在 5 秒内返回 200,否则 WechatApi 会认为推送失败并重试。耗时的 LLM 调用要放入消息队列(Celery、RQ 等)异步处理,Webhook 本身只负责接收和入队。

3. 长文本分段发送:LLM 有时会生成很长的回复,微信单条消息过长会被截断。检测回复长度,超过阈值(如 500 字)时自动分段,多次调用发送接口。

4. 表情与特殊字符:微信消息中可能含 emoji、引用消息、语音消息等非文本内容。非文本消息(msgType != 1)在 Webhook 处直接过滤,避免传入 LLM 导致报错。

5. 多设备一致性:如果同一个微信号在多台设备(多个 appId)上登录,需要统一会话键前缀策略,或者限定只在一台设备上运行机器人逻辑。WechatApi 的 个人微信API 文档中有关于多设备管理的说明,上线前建议仔细阅读。

小结

多轮对话与上下文记忆不是一个单点功能,而是消息接收、会话管理、LLM 调用、回复发送四个环节的协同设计。核心要素归纳为:稳定的消息流(iPad 协议接入,避免 PC Hook 频繁掉线)、结构化的会话存储(Redis + TTL,私聊/群聊分别建键)、合理的上下文窗口(滑动截断 + 摘要压缩)、健壮的工程细节(消息去重、异步化、限流)。

基于 WechatApi 搭建的多轮对话机器人,从接入到上线,核心代码量不超过 300 行,是个人开发者和中小团队快速落地 AI 微信助手的高性价比路径。如有疑问,可前往 开发文档控制台 进一步了解。

想动手试试?

WechatApi 提供扫码登录、消息收发、好友与群管理等 REST 接口,注册后几分钟跑通。

立即免费注册查看开发文档

相关产品页

🔗 个人微信API(产品页)🔗 微信机器人开发(产品页)🔗 微信客服机器人(产品页)

相关文章

微信接入通义千问做智能客服(国产大模型)微信机器人接入知识库 RAG,做企业专属智能问答微信 AI 机器人多轮对话与上下文管理实战微信 AI 客服意图识别与智能转人工方案
© 2025 WechatApi · 企业级微信智能机器人接入平台
官网价格帮助文档博客
苏ICP备2024128799号 · 苏ICP备2023038368号