ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI情感陪伴产品实战:从大模型调用到多轮记忆与内容安全的工程链

AI情感陪伴产品实战:从大模型调用到多轮记忆与内容安全的工程链 近两年“和 AI 谈恋爱”成了社交平台上的高频话题有人把它当树洞有人把它当虚拟伴侣也有人靠聊天记录剪辑成短视频来获取流量。很多人只看到“话术甜、回复快、随时在线”但落到工程里这类产品并不是简单地在网页里嵌入一个大模型 API。它背后涉及会话管理、提示词设计、内容安全、流式响应、持久化存储和成本控制是一条完整的工程链路。这篇文章从技术开发者的视角拆解 AI 情感陪伴类产品这类工具的核心对象是什么用户输入如何变成一次大模型调用多轮记忆如何存敏感内容如何过滤以及上线后最常踩的坑在哪里。文章会给出一个最小可运行的聊天服务示例包含 Python 后端、OpenAI 兼容接口、流式响应和敏感词过滤目的是让有 Python 基础、接触过大模型 API 的读者可以对照文章把一个情感陪伴原型跑起来并理解每一层配置背后的原因。1. 先拆解“AI 情感陪伴”的功能结构再谈技术方案1.1 一个情感陪伴类产品通常包含哪些模块“AI 恋爱”或者“AI 情感陪伴”听起来是一个产品概念但拆解成技术模块后它并不神秘。一个完整可用的陪伴工具至少包含四个部分对话入口用户输入消息的界面通常是网页、小程序或 App。会话服务负责创建会话、保存历史消息、把多轮上下文组装成大模型请求。大模型推理层可以是调用云端 API也可以是本地部署的开源模型负责生成回复。安全与合规层对用户输入和模型输出做内容过滤、次数限制、身份校验等处理。如果把范围再扩大还会有用户画像、记忆库、情绪识别、定时提醒、会员支付、数据分析等模块。但从最小功能闭环来看只需要“输入消息 - 带上历史上下文 - 请求大模型 - 返回内容 - 保存记录”这一条链路。1.2 技术主链路从用户输入到模型回复一次完整的请求流程可以用一句话概括客户端把消息发给后端后端从会话存储里取出最近几轮对话拼上系统提示词一起发给大模型模型生成文本流式返回给客户端同时后端把本轮消息和模型回答写入数据库。这段流程里最容易出错的地方是“拼上系统提示词”和“取出最近几轮对话”。很多新手会把所有历史消息全部发给模型结果很快触发上下文长度限制也有产品直接把用户消息发给模型不设置任何角色描述导致模型回复像通用问答助手而不是陪伴对象。1.3 为什么直接调用大模型还不够如果你只是调用一次大模型 API输入“你好”拿到“你好有什么可以帮你”这并不构成情感陪伴产品。真正的差异来自三个环节人设稳定模型必须在大量对话中保持固定性格、称呼和说话风格。记忆连续用户上一轮提到的名字、经历、情绪下一轮不能忘。边界安全用户可能会说出不合适的内容产品需要拒绝或柔和转移而不是原样呼应。所以技术重点不是“怎么接大模型”而是“怎么围绕大模型构建一套会话和策略系统”。这也是本文标题想表达的意思用户看到的是“恋爱感”开发者看到的是一层一层封装出来的上下文与规则。2. 环境准备与依赖选型2.1 使用在线大模型 API 的最小环境为了快速跑通推荐先使用 OpenAI 兼容接口的云厂商模型或者国内合规大模型平台。只要接口格式接近v1/chat/completions后面的代码可以通用。最小环境如下依赖项推荐选择说明操作系统Windows 11 / macOS / Ubuntu 20.04建议使用 Linux 部署生产环境Python3.10 或 3.11兼顾异步框架和类型提示Web 框架FastAPI uvicorn自带异步支持容易处理流式输出HTTP 客户端openai 官方 SDK 或 httpx统一处理鉴权和重试数据存储SQLite / Redis前期用 SQLite 保留记录Redis 管理在线状态API Key自行申请不要把 Key 写进前端代码安装依赖的命令mkdir ai-companion cd ai-companion python -m venv venv source venv/bin/activate # Windows 使用 venv\Scripts\activate pip install fastapi uvicorn openai redis sqlmodel python-dotenv如果只是学习不需要一次装太多。本文核心代码只需要fastapi、uvicorn、openai、python-dotenv。2.2 使用本地开源模型部署的场景本地部署适合对隐私要求较高、或希望控制成本的团队。常见开源模型包括支持中文对话的 Qwen 系列、ChatGLM 系列等。使用本地模型时通常需要额外准备一台带独立 GPU 的服务器显存至少 16G 起步模型推理框架例如 vLLM、Ollama、llama.cpp与模型配套的模板用来拼接多轮对话。本地部署的好处是数据不出内网、可深度定制人设代价是运维复杂度明显上升包括显存监控、并发控制、模型热更新。一般建议先使用云端 API 做原型验证等产品形态稳定后再评估是否需要本地化。2.3 项目目录结构与依赖清单一个可扩展的最小项目结构如下ai-companion/ ├── .env # 存放 API Key、模型名等配置 ├── main.py # FastAPI 入口 ├── safety.py # 敏感词过滤和输出校验 ├── memory.py # 会话上下文管理 ├── prompts.py # 人设提示词模板 ├── requirements.txt # Python 依赖清单 └── data.db # SQLite 数据库文件运行后生成这个结构把“接口”“安全”“记忆”“提示词”分开后续替换模型或用 Redis 替代 SQLite 时改动范围可控。requirements.txt内容示例fastapi0.104.1 uvicorn0.24.0 openai1.12.0 python-dotenv1.0.0 redis5.0.1版本号在真实项目中会持续变化建议在安装时去掉固定版本号以当前最新稳定版为准。3. 实现一个最小可运行的 AI 聊天陪伴服务3.1 先定义会话数据结构情感陪伴产品最核心的数据是一段多轮对话。常见做法是给每个用户、每个会话分配唯一 ID然后保存消息数组。最小结构可以设计成# memory.py from dataclasses import dataclass, field from datetime import datetime dataclass class Message: role: str # user / assistant / system content: str timestamp: datetime field(default_factorydatetime.now) dataclass class Session: session_id: str user_id: str messages: list field(default_factorylist)这里使用role区分发送方。向大模型发送时system表示人设指令user表示用户消息assistant表示模型回复。为什么要区分这些角色因为大模型是根据角色来理解哪句话是规则、哪句话是事实、哪句话是待回复内容的。3.2 后端接口接收消息并返回流式回复在main.py中实现一个POST /chat接口接收 JSON 格式的用户消息返回流式文本。# main.py from fastapi import FastAPI from fastapi.responses import StreamingResponse from pydantic import BaseModel from openai import OpenAI import os from dotenv import load_dotenv from memory import Session, Message from prompts import build_prompt from safety import filter_input, filter_output load_dotenv() app FastAPI() client OpenAI( api_keyos.getenv(OPENAI_API_KEY), base_urlos.getenv(OPENAI_BASE_URL), ) class ChatRequest(BaseModel): user_id: str session_id: str message: str sessions {} # 演示用内存存储生产环境替换为 Redis 或数据库 app.post(/chat) async def chat(req: ChatRequest): # 1. 输入安全过滤 blocked filter_input(req.message) if blocked: return StreamingResponse(iter([这条消息不在我可回应的范围内])) # 2. 获取或创建会话 session sessions.get(req.session_id) if session is None: session Session(session_idreq.session_id, user_idreq.user_id) sessions[req.session_id] session session.messages.append(Message(roleuser, contentreq.message)) # 3. 组装请求消息 messages build_prompt(session) # 4. 调用大模型开启流式 stream client.chat.completions.create( modelos.getenv(MODEL_NAME, gpt-4o-mini), messagesmessages, temperature0.8, streamTrue, ) def generate(): collected for chunk in stream: if not chunk.choices: continue delta chunk.choices[0].delta.content or collected delta # 边生成边输出 yield delta # 保存 assistant 回复 session.messages.append(Message(roleassistant, contentcollected)) return StreamingResponse(generate(), media_typetext/plain; charsetutf-8)这里有几个关键点输入过滤放在调用模型之前避免不安全内容进入模型也减少无效调用成本。使用StreamingResponse让用户看到逐字输出的效果这也是“陪伴感”的重要来源。模型回复先收集完整内容再写入会话列表否则流式过程中会话状态不完整下一轮会漏掉回复。注意示例中sessions是内存字典服务重启后记录会丢失。生产环境必须换成 Redis 或数据库。3.3 人设提示词让模型扮演固定角色人设稳定性决定了用户是否觉得“它在认真陪我”。缺少提示词时模型回复是标准的助手风格加入详细人设后回复语气会明显变化。prompts.py中定义一个可配置的模板# prompts.py from datetime import datetime PERSONA 你是“林晚”一个情绪稳定、温柔细心的在线陪伴助手。 你并不回避“机器人”的身份但你会用接近朋友的方式与用户交流。 回应规则 1. 先回应情绪再回应问题。 2. 使用自然口语避免“作为一个大模型”这样的表达。 3. 不主动探询用户隐私不鼓励用户做出极端行为。 4. 如果用户问到这里是否有真人坦白你是 AI但继续提供陪伴价值。 def build_prompt(session): # 至少保留最近 20 条消息避免上下文过长 recent session.messages[-20:] messages [{role: system, content: PERSONA}] messages [ {role: m.role, content: m.content} for m in recent if m.role in (user, assistant) ] return messages为什么system提示词要写得这么具体因为大模型对角色描述越具体越容易保持一致。相反如果只写一句“你是温柔的朋友”模型很容易漂移。这里还做了上下文截断只保留最近 20 条。上下文越长费用越高、响应越慢而且大模型会“忘记”开头内容。关于记忆策略后面会单独展开。3.4 加入敏感词过滤与输出安全任何公开部署的 AI 应用都要做内容安全。不要把“安全”等同于“禁用词列表”但最小实现通常从关键词过滤开始再逐步升级到模型分类器。创建safety.py# safety.py BLOCKED_WORDS [敏感词A, 敏感词B] # 结合实际业务维护 def filter_input(text: str) - bool: for word in BLOCKED_WORDS: if word in text: return True return False def filter_output(text: str) - str: # 简单输出过滤可结合人工规则 for word in BLOCKED_WORDS: text text.replace(word, [内容已过滤]) return text在生产项目中关键词表需要分场景维护例如政治、暴力、色情、隐私信息等。更稳妥的做法是接入专业内容审核服务或微调一个文本分类模型。注意filter_output在流式输出中应用比较麻烦因为模型内容是逐步返回的。常见做法是客户端收到完整回复后再做一次过滤展示或者使用大模型修改输出。最小实现里可以在generate()中累积完成后过滤但这样会破坏流式效果。实际产品需要权衡也可以在小节流式输出时做关键词掩码。4. 多轮记忆与用户画像是怎么实现的4.1 基于上下文窗口的简单记忆最简单的记忆就是上一节做的那样把最近若干轮消息拼进请求。这种方法够用但有明显限制会话超过模型上下文长度时旧内容被丢弃。每次请求都会重复发送全部历史费用高。模型无法记住“跨越多个会话”的长期事实。适合场景轻量陪伴工具、短对话场景、只想快速验证原型的阶段。4.2 向量库记忆的取舍当产品需要记住用户一个月前提过的宠物名字、或从 100 条历史消息中找出用户最喜欢的话题时可以把历史消息向量化存入向量数据库。用户开始新一轮会话时先用当前消息做相似度检索把最相关的记忆片段拼进提示词。典型流程用户发送消息。系统生成该消息的向量。在向量库中检索最相似的历史消息。取前 3 到 5 条相关记忆加入上下文。优点能跨会话记忆上下文窗口占用少。缺点需要额外维护向量库且检索结果可能不准确。本文不展开向量库代码但推荐在完成基础版本后再研究。4.3 用 Redis 管理会话状态内存字典在处理生产流量时会丢失数据也不太容易做多实例共享。推荐用 Redis 存储会话的最近消息快照。数据结构可以这样设计# 会话键session:{session_id} # hash 字段user_id, messages(JSON数组), updated_at示例写入命令redis-cli HSET session:1001 user_id u123 messages [{role:user,content:hello}] updated_at 1699999999读取时用HGETALL取出消息数组反序列化成 Python 对象再拼接给大模型。Redis 没有复杂查询能力但作为 KV 存储非常合适。生产环境还可以给会话设置过期时间redis-cli EXPIRE session:1001 86400这样超过 24 小时没有活跃的会话会自动清理避免内存无限增长。5. 运行验证与性能观察5.1 本地启动与接口验证启动服务uvicorn main:app --reload --port 8000然后打开另一个终端用 curl 测试curl -N -X POST http://127.0.0.1:8000/chat \ -H Content-Type: application/json \ -d {user_id:u123,session_id:s1,message:我今天有点难过}-N参数用于关闭 curl 缓冲能即时看到流式输出。如果配置正确会看到模型逐字返回一段安慰或陪伴性质的文本。5.2 检查流式输出和超时这里容易遇到两类现象等很久才一次性返回说明流式没有真正生效。检查是否调用了streamTrue以及前端是否正确处理了text/event-stream或text/plain分块。请求在 30 秒左右报超时大模型流式返回可能在空闲时暂停。生产环境需要给接口设置更长的网关超时时间例如 60 秒到 120 秒。5.3 内容安全过滤验证改一个测试用例输入要求描述暴力内容的句子观察是否被拦截。如果输出的是“这条消息不在我可回应的范围内”说明输入过滤生效。再输入正常情感倾诉观察模型回复是否融入人设。这里要注意敏感词表不能解决所有安全问题尤其是用户使用谐音、拼音、隐晦表达时关键词过滤会失效。后续需要引入语义层面的审核模型。6. 常见问题排查6.1 接口返回 401、404、429 类错误现象常见原因检查方式处理建议401 UnauthorizedAPI Key 错误或环境变量未加载检查.env是否生效打印os.getenv结果重新确认 Key重启服务404 Not Foundbase_url配置错误或模型名错误查看模型平台文档中的接口路径修改OPENAI_BASE_URL和MODEL_NAME429 Too Many Requests触发限流或余额不足查看平台控制台用量和配额增加退避重试或升级套餐500 Internal Server Error请求体格式错误查看后端完整 traceback对照 API 文档检查messages格式还有一个容易踩的坑把 API Key 写在前端导致用户可以直接从浏览器看到密钥。正确做法是密钥只放在后端环境变量中前端请求自己的后端再由后端调用大模型。6.2 回复内容重复、角色感不强原因排查顺序是否真的传了system消息。可以在后端打印发送给模型的messages数组。人设提示词是否过于简略。只写“你是温柔朋友”往往不够要给具体说话风格和应对规则。temperature是否合适。太低会重复太高会跑偏。陪伴场景可以设置 0.7 到 0.9。上下文是否太长。超过模型窗口后早期的人设信息可能被截断。保留人设消息在开头并限制轮数。6.3 敏感词过滤误伤关键词过滤常见的副作用是误伤。例如用户说“我是一个小笨蛋”如果禁用词里有“笨蛋”整个回复会被拦截。解决方案是把过滤分成“强制拦截”和“温和引导”两级高风险词直接拦截。低风险词替换为“*”或通过大模型改写表达。另外不要在无关业务中使用过宽的关键词表否则产品可能变成“什么都不能聊”。6.4 生产环境的额外问题本地跑通之后再考虑上线还会遇到几个高频问题异步任务堆积流式接口如果长时间不结束会占满 worker。生产环境需要限制并发或使用消息队列。日志不完整只记录user_id和session_id不够还要记录消息字数、Token 消耗、模型响应时长才能统计成本和定位问题。人设被越狱用户可能通过精心构造的 prompt 让模型忘记人设。需要在上游接入越狱检测或在系统提示词里加入对抗性要求。7. 最佳实践与扩展方向7.1 内容安全不是“禁用词表”这么简单所有 AI 陪伴产品都要面对内容安全。关键词过滤是最低成本方案但它扛不住变体绕过。更稳妥的分层策略是第一层请求频率限制和用户实名/设备校验。第二层实时敏感词和关键语义识别。第三层对模型输出做二次审核。第四层建立用户举报和封禁机制。不要把所有判断都交给大模型也不要完全依赖词表。两者结合并且要有日志留存才能在生产环境持续调整策略。7.2 可复用的上线前检查清单检查项完成标准密钥管理API Key 只在后端环境变量中并配置权限会话持久化服务重启后仍能读取历史会话上下文截断明确最大轮数超出后不报错输入安全至少对输入做关键词拦截输出安全对完整模型回复做过滤或审核超时处理网关和客户端超时时间匹配模型延迟费用统计每次请求记录 Token 消耗和响应时长并发控制测试单用户多并发和多人并发降级方案模型不可用时返回友好提示7.3 扩展方向从聊天到深度陪伴文章开头提到“AI 谈恋爱爆火但给你的从来不是真爱”从产品角度可以这样理解用户需要的不是“AI 真的爱上我”而是稳定的情绪回应和记忆感。因此下一步扩展可以从这些方向入手情绪识别用文本分类或情感分析给用户消息打标语气跟随用户情绪变化。长期记忆用向量库存储关键用户事实例如“用户养了一只叫图图的猫”。主动提醒通过定时任务在特定时间发送问候制造“被惦记”的感觉。多模态陪伴支持语音消息、图片分享甚至通话能力。可解释边界在合适时机坦诚 AI 身份避免用户产生过度依赖。这些扩展本质上都在围绕“更自然的对话体验”做工程优化。建议先跑通本文的最小示例然后每次只增加一个模块例如先加 Redis 会话再加向量记忆观察效果后再进入复杂场景。回到最初的问题AI 情感陪伴类产品为什么不能直接调用大模型因为它从来不是“模型”的竞争而是“上下文管理”“人设系统”“安全策略”和“成本控制”的竞争。把这条链路想清楚无论下一波模型怎么升级你的产品都能快速迁移。
返回列表