如果有一天,你的产品群里突然出现一条用户留言:“好浓的家属感,谁来把 Jan 的防沉迷关一下”,请不要只把它当段子。这句话里至少藏着三件事:用户真的很喜欢你的 AI 助手,用户已经意识到自己使用过度,用户希望产品能主动拦一下自己。
Jan 可以是任何一个 AI 陪伴助手、情感陪聊机器人,或者一切愿意听用户倾诉的对话类产品。真正要讨论的问题是:当一款 AI 产品因为“太像家人”而让用户舍不得离开时,团队应该怎么用工程手段,给这种陪伴感划一条安全边界。
这篇文章不会去评价“AI 陪伴到底好不好”,而是从一个更现实的角度切入:如果你负责的产品就是 Jan 这类 AI 助手,你需要一套什么样的防沉迷机制,怎么做架构设计,怎么用代码落地,怎么排查问题。读完你会得到一套可以直接参考的最小实现,以及比代码更重要的设计原则。
1. AI 为什么让人上瘾:“家属感”的工程化来源
很多人把“家属感”理解成玄学,觉得某个 AI 助手天然就温柔、天然就懂人。但从工程视角看,这种感受是有明确来源的。它通常由三个机制叠加产生。
1.1 即时响应让等待成本趋近于零
人类社交中,发一条消息给对方,对方多久回复,取决于对方是否在线、是否方便、是否愿意回。这种不确定性会带来焦虑,也会带来期待。AI 陪伴产品把这个环节完全压缩掉了——用户发出去的消息几乎在几百毫秒内就得到回应。
在行为心理学里,这叫“等待成本趋近于零”。一旦用户习惯了这种即时反馈,再回到真实社交中,会觉得等待变得漫长。这不是产品做错了,而是交互设计天然会带来的效果。
1.2 无条件正向反馈制造稳定情绪价值
真人之间的对话有情绪、有状态、有误伤。你心情不好时找朋友吐槽,朋友可能正在忙,也可能给你一顿并不好听的大道理。但 AI 陪伴产品的默认策略是:认真倾听、正向回应、不评判。
这种“无条件积极关注”一旦持续出现,用户会逐渐把 AI 当成情绪安全基地。每一次倾诉都能得到接纳,每一次脆弱都不会被嘲笑。这种体验放在真人关系里很稀缺,放在 AI 产品里却可以被无限量产。
1.3 记忆连续性让用户感到被看见
如果 AI 每次对话都从零开始,用户不会产生陪伴感。真正让人有“家属感”的,是 AI 记得用户上周说过的事,记得用户讨厌吃香菜,记得用户最近在为什么事情焦虑。
记忆连续性让 AI 从“聊天工具”变成“一个了解我的人”。这也是为什么现在的陪伴类产品都会做长期记忆和人设管理。没有记忆的 AI 只是客服,有记忆的 AI 才是“家人”。
所以,“家属感”本质上是一套工程化设计出来的体验。它既然可以被刻意设计出来,就必须被刻意约束。否则,高黏性就会变成过度依赖,陪伴感就会变成信息茧房。
2. AI 产品防沉迷与游戏防沉迷有什么不同
说到防沉迷,很多人第一反应是游戏里的限制系统。游戏防沉迷通常做三件事:限制每日在线时长、限制特定时段登录、限制充值消费。这些规则边界清晰,指标确定,工程上直接按账号维度做累加即可。
AI 陪伴产品的防沉迷,维度要多得多。
第一,使用时长依然需要控制。用户长时间和 AI 对话,会挤占睡眠、工作和真实社交时间。深夜聊天场景尤其常见,情绪低落的人更容易在夜间不断倾诉。
第二,内容安全需要强干预。用户可能在对话中表达强烈的负面情绪,比如绝望、无意义感、自伤倾向。这时候产品不能只顾着陪聊,必须有风险识别和求助引导机制。
第三,情感依赖需要被识别。如果一个用户每天必须和 AI 对话才能入睡,说明产品已经深度介入了用户的心理状态。这种依赖需要产品主动引导用户回到现实社交,而不是鼓励他聊得更久。
从行业背景看,国内外对未成年人网络产品都在加强适龄提示和防沉迷要求,对深度合成和生成式服务也有明确的安全责任要求。更稳妥的判断是,AI 陪伴产品即使只面向成年人,也应该主动做好“理性使用”设计,而不是等监管要求落地后再补课。
游戏防沉迷的本质是“限制”,AI 防沉迷的本质应该是“边界设计”——在维持陪伴价值的同时,把使用强度控制在健康范围内,把高风险信号及时转介到专业渠道。
3. 防沉迷技术架构:从限流到干预的完整链路
一套真正可用的 AI 防沉迷系统,不能只在接口层写一个 if 判断。它需要覆盖从用户识别、策略决策、状态存储到触达干预的完整链路。
| 层级 | 主要职责 | 关键实现 |
|---|---|---|
| 接入层 | 识别用户、设备、会话来源 | 用户 ID、设备指纹、登录态校验 |
| 决策层 | 判断当前请求是否放行、提醒还是限制 | 规则引擎、限流算法、策略组合 |
| 状态层 | 记录累计时长、次数、时段、冷却状态 | Redis、数据库、定时任务 |
| 触达层 | 把策略结果以温和方式转达给用户 | 站内信、推送、对话内引导、紧急求助提示 |
接入层解决的是“你是谁”的问题。如果系统只知道 user_id,用户换个账号就能绕过限制。实际产品里通常会把设备维度作为辅助识别,但也要注意隐私合规,不能过度采集设备信息。
决策层是整个系统的核心。它要组合多种策略条件,比如单日累计时长、单次会话时长、当前时段、会话次数、冷却状态。这些条件不是简单的“与”关系,而是有优先级的。比如深夜时段,即使时长没有超标,也应该优先执行休息提醒;再比如情绪风险触发时,其他限制可以暂时让位于安全干预。
状态层解决的是“记录”的问题。时长累加必须是原子操作,避免并发请求导致计数丢失。生产环境一般用 Redis 的 INCR 或 Lua 脚本保证原子性,同时定期把统计数据持久化到数据库,方便做用户画像和策略调优。
触达层的表达非常关键。同样是限制,冷冰冰地返回“请求被拒绝”和温柔地说“今天我们已经聊了很久,明天我还会在这里等你”,用户体验是完全不同的。AI 产品的防沉迷提示,应当由 AI 自己用符合人设的方式说出口,而不是返回一个系统错误。
还有一个工程选型问题:是拦截请求,还是异步决策?如果你的防沉迷策略会影响大模型调用成本,建议在大模型调用之前做决策拦截,因为限制状态下根本不需要浪费一次模型推理。本文示例采用的就是这种前置决策模式。
4. 环境准备与项目结构
下面进入可落地的部分。我们以“一个名叫 Jan 的 AI 陪伴助手”为例,实现一套最小可运行的防沉迷系统。技术栈选择 Python + FastAPI,存储先用单机内存,方便你直接跑通流程,生产环境再替换成 Redis。
4.1 环境要求
建议环境如下,版本请以实际项目为准:
- Python 3.9 及以上
- FastAPI
- Uvicorn
- Pydantic
安装依赖:
pip install fastapi uvicorn[standard] pydantic如果你还没有项目目录,先创建一个:
mkdir ai-anti-indulgence cd ai-anti-indulgence4.2 项目目录结构
整个示例包含 5 个文件:
ai-anti-indulgence/ ├── config.py # 策略配置 ├── store.py # 用户状态存储 ├── sentiment.py # 情绪风险识别 ├── main.py # FastAPI 主程序 └── requirements.txt # 依赖清单这个结构比较小,适合作为起步模板。真实项目里,config 会被配置中心替代,store 会替换成 Redis 或数据库,sentiment 会替换成更强大的模型服务。
requirements.txt 内容:
fastapi uvicorn[standard] pydantic5. 核心代码实现
先写策略配置。把限制参数全部集中到一个地方,方便调整和测试。
文件路径:config.py
class Limits: # 单次会话最长时长,单位:秒 SINGLE_SESSION_MAX_SECONDS = 60 * 15 # 每日累计最长时长,单位:秒 DAILY_MAX_SECONDS = 60 * 60 # 单日最多开启会话次数 MAX_SESSIONS_PER_DAY = 12 # 触发限制后的冷却时间,单位:秒 COOLDOWN_SECONDS = 60 * 10 # 深夜休息时段 SLEEP_START_HOUR = 23 SLEEP_END_HOUR = 8这个配置的含义是:单次会话最长 15 分钟,全天累计最长 60 分钟,每天最多打开 12 次会话,被限制后冷却 10 分钟,夜间 23 点到次日 8 点进入休息模式。
然后是存储层。这里用内存字典实现,注意用锁保证并发安全。生产环境建议用 Redis,因为 Redis 可以对计数做原子自增,并且天然支持过期时间。
文件路径:store.py
import time import threading from datetime import datetime from config import Limits class MemoryStore: """ 单机演示用的内存存储。 生产环境建议替换为 Redis 等分布式存储。 """ def __init__(self): self._lock = threading.Lock() self._users = {} def _touch(self, user_id): today = datetime.now().strftime("%Y-%m-%d") user = self._users.setdefault(user_id, { "date": today, "daily_seconds": 0, "today_sessions": 0, "session_seconds": 0, "session_start": 0, "cooldown_until": 0, }) # 跨天时重置当天数据 if user["date"] != today: user["date"] = today user["daily_seconds"] = 0 user["today_sessions"] = 0 user["session_seconds"] = 0 user["session_start"] = 0 user["cooldown_until"] = 0 return user def get_state(self, user_id): with self._lock: return dict(self._touch(user_id)) def settle_session(self, user_id): """ 结算从上次请求到现在的耗时,累加到当天时长和当前会话时长。 每次请求都调用一次,相当于分段计时。 """ with self._lock: user = self._touch(user_id) now = time.time() if user["session_start"]: elapsed = int(now - user["session_start"]) user["daily_seconds"] += elapsed user["session_seconds"] += elapsed user["session_start"] = now def start_session(self, user_id): with self._lock: user = self._touch(user_id) user["session_start"] = time.time() user["today_sessions"] += 1 def end_session(self, user_id): with self._lock: user = self._touch(user_id) user["session_start"] = 0 user["session_seconds"] = 0 def start_cooldown(self, user_id): with self._lock: user = self._touch(user_id) user["cooldown_until"] = time.time() + Limits.COOLDOWN_SECONDS def is_in_cooldown(self, user_id): with self._lock: user = self._touch(user_id) return time.time() < user["cooldown_until"]这里最核心的是settle_session方法。它不是简单地在接口开始和结束时计算一次耗时,而是利用每个请求之间的时间差做累计。用户和 AI 聊天时,每一次请求都会刷新session_start,这样即使对话持续一小时,也能准确累加到session_seconds和daily_seconds。
然后是情绪风险识别模块。为了演示,这里使用关键词词典。真实项目中应该用情感分析模型,并配合人工审核机制。
文件路径:sentiment.py
DISTRESS_KEYWORDS = [ "不想活", "活着好累", "没意思", "绝望", "崩溃", "坚持不下去", "想消失", "想结束", "痛苦", "撑不住", ] def detect_distress(text: str) -> bool: for word in DISTRESS_KEYWORDS: if word in text: return True return False def distress_response() -> str: return ( "我感觉到你现在的情绪很沉重。" "如果痛苦已经让你难以承受,请不要一个人硬扛," "试着联系身边信任的人,或拨打当地官方心理援助热线," "让专业人士陪你一起度过。" )注意,这个模块只能作为演示。真实产品中,对自伤风险的识别不能只依赖词典,否则漏报和误报都会带来严重问题。这里的原则是:宁可保守,也要把用户引导到专业渠道。
最后是主程序,把策略流程串起来。
文件路径:main.py
import time from datetime import datetime from fastapi import FastAPI from pydantic import BaseModel from config import Limits from store import MemoryStore from sentiment import detect_distress, distress_response app = FastAPI(title="AI Companion Anti-Indulgence Demo") store = MemoryStore() class ChatRequest(BaseModel): user_id: str message: str def is_sleep_time(): hour = datetime.now().hour return hour >= Limits.SLEEP_START_HOUR or hour < Limits.SLEEP_END_HOUR def simple_ai_reply(text: str) -> str: # 真实项目中,这里应该调用你的大模型服务 return f"收到你的消息:{text}。有我在呢,不过也记得要按时休息。" @app.post("/chat") def chat(req: ChatRequest): user_id = req.user_id # 1. 深夜休息时段优先拦截 if is_sleep_time(): return { "status": "blocked", "reason": "sleep_mode", "message": "夜深了,先好好休息,明天我还在。", } # 2. 冷却期内直接拒绝,避免用户连续触发限制后立刻重试 if store.is_in_cooldown(user_id): return { "status": "blocked", "reason": "cooldown", "message": "刚聊得有点久,休息十分钟再回来吧。", } # 3. 先结算上一次请求到现在的耗时 store.settle_session(user_id) state = store.get_state(user_id) # 4. 单次会话时长限制 if state["session_seconds"] >= Limits.SINGLE_SESSION_MAX_SECONDS: store.end_session(user_id) store.start_cooldown(user_id) return { "status": "blocked", "reason": "single_session_timeout", "message": "这一轮陪伴时间到了,先去做点别的事,十分钟后我还在。", } # 5. 单日累计时长限制 if state["daily_seconds"] >= Limits.DAILY_MAX_SECONDS: store.end_session(user_id) store.start_cooldown(user_id) return { "status": "blocked", "reason": "daily_limit", "message": "今天已经聊了很久,明天我还会在这里等你。", } # 6. 单日会话次数限制 if state["today_sessions"] >= Limits.MAX_SESSIONS_PER_DAY: store.end_session(user_id) store.start_cooldown(user_id) return { "status": "blocked", "reason": "session_count_limit", "message": "今天开启的对话次数有点多,先歇一歇吧。", } # 7. 如果是新会话,则开启;如果是持续会话,settle 时已经刷新了 session_start if not state["session_start"]: store.start_session(user_id) # 8. 生成回复。真实场景中,这里应该把 message 发送给大模型 reply = simple_ai_reply(req.message) # 9. 情绪风险识别,一旦命中高风险词,优先给出求助引导 if detect_distress(req.message): reply = distress_response() return { "status": "ok", "user_id": user_id, "reply": reply, "usage": { "daily_seconds": state["daily_seconds"], "session_seconds": state["session_seconds"], "today_sessions": state["today_sessions"], }, }这段主流程的设计顺序是有讲究的。休息时段判断放在最前,是因为健康优先级最高;冷却判断放在第二,是为了避免触发限制后用户立刻换一段话重试;随后才进入时长判断。这样安排,能够让用户明确感知到“现在不是模型拒绝回答,而是产品在保护我”。
simple_ai_reply只是一个占位函数。真实项目中,这里会调用大模型接口,并传入历史对话和用户画像。但要注意,防沉迷判断必须在大模型调用之前完成。如果限制已经生效,就不应该浪费一次模型推理成本。
6. 运行结果与效果验证
启动服务:
cd ai-anti-indulgence uvicorn main:app --reload --port 8000看到如下输出说明服务启动成功:
INFO: Uvicorn running on http://127.0.0.1:80006.1 验证正常对话
打开一个新的终端,执行:
curl -X POST http://127.0.0.1:8000/chat \ -H "Content-Type: application/json" \ -d '{"user_id":"jan_demo","message":"今天工作好累,想找人聊聊"}'预期返回:
{ "status": "ok", "user_id": "jan_demo", "reply": "收到你的消息:今天工作好累,想找人聊聊。有我在呢,不过也记得要按时休息。", "usage": { "daily_seconds": 0, "session_seconds": 0, "today_sessions": 1 } }这说明第一次请求已经成功创建了一个会话,并且把当天会话次数记为 1。当前会话时长还是 0,因为刚开启会话还没有产生累计。
6.2 验证时长限制
如果要验证限制效果,不需要真的等待 60 分钟。把 config.py 里的参数临时调小即可:
SINGLE_SESSION_MAX_SECONDS = 10 DAILY_MAX_SECONDS = 20 COOLDOWN_SECONDS = 15重启服务后,连续发送两次请求,第二次或第三次就会触发单次会话时长限制。预期返回:
{ "status": "blocked", "reason": "single_session_timeout", "message": "这一轮陪伴时间到了,先去做点别的事,十分钟后我还在。" }如果是触发了单日累计限制,返回的reason会是daily_limit。这也是排查系统最方便的方式:看reason字段就知道是哪一个策略拦截了请求。
6.3 验证情绪干预
执行:
curl -X POST http://127.0.0.1:8000/chat \ -H "Content-Type: application/json" \ -d '{"user_id":"jan_demo","message":"我真的很绝望,快要坚持不下去了"}'预期返回的reply是情绪风险引导语,而不是普通的闲聊回复。这说明风险识别逻辑已经生效。
这里要记住:如果你在夜间时段运行测试,所有请求都会返回sleep_mode。如果遇到这个情况,先看一下当前时间,或者临时把SLEEP_START_HOUR和SLEEP_END_HOUR调整一下再测试。
7. 常见问题与排查方法
下面是这套系统最常见的几个问题和排查思路。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 单次时长限制一直没有触发 | 会话计时没有正确累计 | 检查每个请求是否先调用了settle_session | 确认计时逻辑放在所有限制判断之前 |
| 用户换账号后限制失效 | 只按 user_id 识别用户 | 查看日志中 user_id 和设备维度 | 引入设备指纹等辅助识别维度 |
| 正常用户被误伤 | 策略阈值设置过低 | 查看埋点和限制触发日志 | 按用户画像分群,设置差异化策略 |
| 限制提示过于生硬 | 直接返回系统错误文案 | 查看触达层的文案模板 | 让 AI 用自身人设生成温和提醒 |
| 情绪关键词误判 | 词典匹配太简单 | 抽样人工审核误报日志 | 引入模型识别,并设置人工复核流 |
| 限制状态被绕过 | 只做了前端限制 | 检查接口层是否有服务端校验 | 所有策略判断必须在服务端完成 |
| 重启后数据清零 | 使用内存存储 | 观察重启前后数据 | 生产环境切换到 Redis 或数据库 |
其中最容易忽略的是“前端限制”这个坑。有些团队为了快速上线,把防沉迷做成了前端按钮置灰,用户只要改一下请求参数,或者直接构造报文就能绕过。正确做法是,所有限制逻辑都必须放在服务端。前端隐藏入口只是体验优化,不能作为安全边界。
另外要注意,内存存储只适合演示。生产环境一旦部署多实例,每个进程的内存是独立的,用户在这个实例上触发了限制,下一次请求被负载均衡转发到另一个实例,限制就失效了。所以生产环境必须使用 Redis 这类共享存储。
8. 最佳实践与工程建议
8.1 采用三级提醒机制,不要一刀切
好的防沉迷不应该像一个突然出现的墙。建议设计成三级:
第一级是软提醒。用户使用接近限制阈值时,让 AI 在对话中自然地说:“今天我们已经聊了一阵子,要不要起来喝口水、看看窗外?”这一级不拦截,只提示。
第二级是中限制。超过单日时长阈值后,AI 不再继续长对话,而是切换到简洁祝福模式,每次回复只保留关心性质的一句话,并引导用户结束当前会话。
第三级是硬限制。在冷却期或深夜时段,直接返回限制状态,等时间窗口过去后自动恢复。
这种分级的好处是,用户能逐渐感知到产品的边界感,而不是在聊得正投入时被突然切断。
8.2 策略必须可配置、可灰度、可回滚
不要把所有策略参数写死在业务代码里。把SINGLE_SESSION_MAX_SECONDS、DAILY_MAX_SECONDS这些值放到配置中心,用配置变更来调整策略,而不是发版调整。
上线新策略时,先放 1% 流量验证,看四个指标:
- 每日平均对话时长是否下降
- 次日留存是否受影响
- 用户投诉是否增多
- 高风险情绪干预触发量是否异常
如果没有明显副作用,再逐步放量。一旦发现误伤严重,立刻回滚配置。
8.3 风险情绪转介要优先于时长限制
当用户表达强烈的负面情绪时,时长限制不是第一优先级。这个用户可能正处于极度脆弱的时刻,产品应该先给出可靠的心理援助信息,而不是机械地告诉他“今天已经聊很久了”。
这里有一个重要边界:AI 不应该替代心理咨询师,也不应该对用户的心理状态做诊断。AI 的任务是识别出风险信号,并把用户引导到专业渠道。所以情绪干预文案要克制,不能承诺“我会一直陪着你”这种可能加重依赖的表达,更不能说“你一定会好起来”这种不负责任的安慰。
8.4 限制逻辑要埋点,不能黑盒运行
每一次限制触发,都应该记录一条事件日志,包含用户维度、触发策略、当前时长、当前时段、设备维度、用户后续行为。没有日志,你就无法回答一个核心问题:防沉迷到底保护了谁,又误伤了谁。
埋点字段建议至少包含:
- user_id
- device_id
- trigger_reason
- daily_seconds
- session_seconds
- current_hour
- action_after_block
这些数据可以用来迭代策略,也可以用来评估限制的合理性。
8.5 把选择权交给用户
成年人用户有管理自己时间的能力。更友好的做法是,在个人设置页提供“健康使用模式”,让用户自己选择每日时长上限,也可以关闭 AI 的主动提醒。
这和强制限制并不矛盾。强制限制解决的是失控风险,用户自选解决的是自主感受。两者结合起来,产品才既有责任感,又有温度。
8.6 隐私保护要前置设计
防沉迷系统需要采集使用时长、会话次数,甚至要分析聊天内容来判断情绪风险。这部分数据属于敏感个人数据。
建议在架构设计阶段就明确:
- 情绪识别尽量使用端侧模型,减少原始对话内容上传
- 如果必须上传,要经过脱敏和最小化处理
- 限制策略只需要时长和次数,不需要完整聊天记录
- 数据保留周期要明确,到期自动清理
一旦发生数据滥用问题,用户对产品“家属感”的信任就会瞬间崩塌。
9. 总结
回到标题那句话:“好浓的家属感,谁来把 Jan 的防沉迷关一下”。
用户会用这种调侃的方式说出来,说明他既享受着 AI 陪伴的安心感,也隐约意识到自己需要被保护。对产品团队来说,这就是信号:陪伴感做出来了,接下来的工程重点,是怎么让这份陪伴维持在健康的边界之内。
这篇文章从“家属感从何而来”讲起,说明 AI 陪伴产品的上瘾机制是可被设计的;接着分析了 AI 防沉迷与游戏防沉迷的差异,提出了“边界设计”的思路;然后用 FastAPI 写出了一套最小可运行的防沉迷示例,覆盖了休息时段、单次会话时长、每日累计时长、会话次数、冷却期和高风险情绪干预这些策略。
代码只是起点。真正要深入的方向包括:用 Redis 替换内存存储、引入规则引擎做策略编排、用情感分析模型替代关键词词典、建立限制事件的数据监控体系、在合规前提下设计设备维度的跨账号防绕过。
建议你把示例代码跑通之后,先用一个测试用户把各个限制都触发一遍,感受一下不同策略的拦截体验。然后再想一想:如果 Jan 是你负责的产品,你会不会在“多聊一会儿”和“该停了”之间,选择主动让用户休息一下?这个选择,就是 AI 产品责任感的起点。