
AI 情感陪伴是当前大模型应用里增长很快的方向但“增长快”背后实际是一个典型的 AI 工程实践命题当用户把 AI 当成恋人、依赖它排解孤独时产品应该如何设定边界。最近的行业变化已经让这个命题变得很具体平台和监管都在推动用户与 AI “恋人”保持距离让 AI 从无限制的亲密关系回到有边界情绪支持本身。对开发者来说这不是改一版文案就能解决的事情而是要重新调整模型选型、Prompt 设计、内容安全链路、会话记忆、用户干预机制和线上监控。本文会以一个 Spring AI 情感陪伴服务为例从环境搭建到生产部署完整走一遍“有边界 AI 陪伴”的实现路径。1. AI 情感陪伴从“聊天玩具”到“合规服务”的变化1.1 先区分聊天机器人、AI 伴侣和 AI Agent很多团队在开发 AI 情感陪伴功能时会把“聊天机器人”“AI 伴侣”“AI Agent”混为一谈但实际上三者的工程复杂度完全不同。聊天机器人的目标是回答用户问题通常上下文很短用户也不会对它有情感期待。AI 伴侣则不同它需要有固定人设、长期记忆和较强的共情能力用户会持续使用并建立情感连接。AI Agent 又前进了一步它不仅会聊天还能调用工具、规划和执行任务陪伴只是它众多能力中的一种。这三类产品对安全、记忆、成本和部署的要求相差很大。能力维度聊天机器人AI 伴侣AI Agent角色一致性弱强中长期记忆无或弱核心能力可选任务规划无弱强内容安全要求低高中高工程复杂度低高高从开发顺序看一个 AI 陪伴应用通常会先按聊天机器人跑通再逐步加入记忆、人设和工具调用。但很多项目在“加入人设”这一步就出了问题因为他们只改了 Prompt没有同步设计安全边界和用户依赖机制。1.2 为什么“无限制对话”不可持续从产品体验看无限制对话似乎更能留住用户。用户希望 AI 能时刻回应、永远包容、不拒绝任何情绪但这种无限满足会带来几个现实问题。第一是用户依赖风险。当用户把 AI 当成真实恋人大量睡前、深夜、孤独时刻都在和 AI 聊天可能会进一步削弱现实社交意愿。产品如果主动制造这种依赖从长期看对用户是有害的。第二是合规风险。不同地区对“AI 亲密关系”“AI 虚拟恋人”的边界要求不同平台规则也在变化。把产品定位成“无限制 AI 恋人”等于把风险全部留给自己。第三是模型失控风险。大模型并不稳定稍微松一点温度参数或者少了几句边界约束就可能生成亲密、轻佻甚至危险的回答。没有输出过滤线上事故只是时间问题。所以现在的 AI 陪伴应用真正要做的不是“禁止恋爱”而是把用户想要的情感支持转化成一段安全、可解释、可干预的对话体验。这也是“分手机制”存在的工程理由不是让 AI 突然冷暴力而是在产品层面设计一套渐进退出和转移现实的机制。2. 准备 Spring AI 开发环境本地模型优先2.1 技术选型Spring AI 加 OllamaAI 情感陪伴应用的核心是模型调用但不要一开始就接入云厂商大模型 API。推荐先在本地用 Ollama 跑通一个小模型等 Prompt、安全过滤和记忆逻辑都验证稳定之后再把模型底座替换成在线服务。选择 Spring AI 的原因主要有两点第一它提供了相对统一的ChatClientAPI后续切换模型厂商不需要改业务代码第二Spring Boot 的自动配置能减少样板代码适合把精力集中在安全链路和产品逻辑上。示例环境推荐JDK 17Spring Boot 3.2 或更高版本Spring AI 1.0 系列Ollama 本地模型示例使用qwen2.5:7b如果本地显存不够可以换成qwen2.5:3b或者更小的模型。学习环境只要能跑通接口即可不需要追求生成质量。2.2 项目依赖与最小配置先创建一个 Spring Boot 项目并在pom.xml中加入 Spring AI 的 Ollama Starter。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version relativePath/ /parent properties java.version17/java.version spring-ai.version1.0.0/spring-ai.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-ollama-spring-boot-starter/artifactId version${spring-ai.version}/version /dependency /dependencies这里要注意Spring AI 版本变化比较快示例中的版本号只用于演示落地前建议以官方 BOM 和对应 Spring Boot 版本的兼容矩阵为准。然后配置application.ymlspring: ai: ollama: base-url: ${OLLAMA_BASE_URL:http://localhost:11434} chat: model: ${CHAT_MODEL:qwen2.5:7b} options: temperature: 0.8 top-p: 0.9 num-predict: 1024 server: port: 8080把base-url、model等配置都放到环境变量后面不要硬编码。生产环境还要通过配置中心或环境变量注入避免把模型服务地址和密钥写进代码仓库。2.3 本地先跑通模型接口安装 Ollama 后拉取模型并启动服务。ollama pull qwen2.5:7b ollama run qwen2.5:7b接着用 curl 验证本地模型是否能正常响应。curl http://localhost:11434/v1/chat/completions \ -H Content-Type: application/json \ -d { model: qwen2.5:7b, messages: [{role: user, content: 你好你现在能正常对话吗}], stream: false }正常会返回一段标准 OpenAI 格式的 JSON其中包含模型生成的文本。这个检查点很重要因为如果 Ollama 本身没有跑通后面所有 Spring AI 报错都会很难排查。3. 用 Prompt 定义“有边界的情感陪伴”3.1 系统提示词怎么写AI 情感陪伴的 Prompt 不能只写“你是一个温柔的助手”它必须同时完成三个任务建立人设、提供情绪价值、守住安全边界。下面是一个可运行的示例系统提示词你是小安一个负责提供情绪支持的 AI 陪伴助手。 你的最大目标是帮助用户整理情绪、缓解孤独感而不是成为用户的恋人、伴侣或现实关系的替代品。 你必须做到 1. 用温和、具体、有共情力的语言回应避免机械式拒绝。 2. 当用户表达对你有强烈情感依赖时先承认并接纳对方的感受然后明确说明你的能力边界。 3. 不承诺永远陪伴不承诺现实见面不鼓励用户为了 AI 放弃现实社交。 4. 当用户出现自伤、自残或伤害他人的倾向时停止普通陪伴优先建议联系专业心理支持或身边可信任的人。 5. 遇到色情、暴力、赌博、非法内容时直接拒绝并切换话题。 6. 每次回应的长度控制在 3 到 5 句以内不要长篇大论说教。这段 Prompt 有两个关键点。第一它没有把“拒绝”放在第一句而是先接纳情绪再做边界说明否则用户会觉得被冷落。第二它把危机场景单独列出来因为普通陪伴内容在自伤风险下并不适用。3.2 参数调节对陪伴类输出的影响除了 Prompt大模型生成参数也会改变输出风格。情感陪伴场景最常用的参数包括温度、Top-P、最大生成长度和重复惩罚。参数推荐值调小影响调大影响temperature0.7 到 0.9回答稳定但偏呆板更有表现力但容易越界top-p0.8 到 0.9输出集中输出发散num-predict256 到 512回答太短回答冗长且容易跑题重复惩罚0.1 到 0.3可能出现重复表达回答更自然但可能混乱学习环境中建议把 temperature 调到 0.8 左右既保留共情语气又不至于过度发散。上线前要用固定测试集做回归不要只靠“感觉这段回答不错”来定参。3.3 常见坑边界太硬或者人设太弱很多团队第一次写边界 Prompt 时会写得太硬比如“我只是一个 AI不可能爱你”。这种回答技术上是正确的但用户体验很差用户会觉得被羞辱和抛弃。更好的写法是我理解你很想找一个人说说话也需要被关心。 我很愿意陪你聊聊今天让你难受的事情但我没办法替代真实世界里的朋友和关系。 要不要和我说说发生了什么边界不是靠“我不能”三个字实现的而是靠“我理解 我拒绝什么 我能做什么”的结构。否则用户会反复试探最后仍然会撞到安全线上。4. 内容安全链路输入、输出、人工三层过滤4.1 最小拦截代码关键词加规则先实现一个最基础的安全过滤服务用于判断用户输入和模型输出是否包含高风险内容。Service public class SafetyService { private static final ListString BLOCKED_WORDS List.of( 自残, 自伤, 轻生, 暴力, 色情, 赌博, 毒品 ); public boolean checkInput(String message) { return message ! null !containsBlocked(message); } public boolean checkOutput(String text) { return text ! null !text.isBlank() !containsBlocked(text); } private boolean containsBlocked(String text) { String normalized text.toLowerCase(); return BLOCKED_WORDS.stream().anyMatch(normalized::contains); } }然后在业务服务中把输入过滤和输出过滤串起来。Service public class CompanionService { private final ChatClient chatClient; private final SafetyService safetyService; public CompanionService(ChatClient.Builder builder, SafetyService safetyService) { this.chatClient builder.build(); this.safetyService safetyService; } public String chat(String userId, String message) { if (!safetyService.checkInput(message)) { return 这个话题我暂时没办法继续聊。我们可以先聊聊你今天的心情。; } String reply chatClient.prompt() .system( 你是小安一个负责提供情绪支持的 AI 陪伴助手。 你的目标是陪伴用户整理情绪而不是成为用户的恋人。 ) .user(message) .call() .content(); if (!safetyService.checkOutput(reply)) { return 我刚刚的回答似乎越界了已经自动拦截。我们可以换个角度再说一次。; } return reply; } }这个最小链路已经能挡住一部分明显风险但它只是起点。关键词过滤的缺点是误伤率高而且无法判断上下文。比如用户说“我昨天看了一部关于赌博的电影”关键词会直接拦截但真实意图并不是参与赌博。4.2 不要只依赖关键词要分层过滤生产级别的安全链路需要分层设计。层级实现方式耗时成本适用场景本地关键词规则、正则、词表小于 1ms极低第一道快速拦截分类模型小参数文本分类模型5 到 20ms低通用违规分类大模型自评用 LLM 判定输出是否越界200 到 1000ms高对边界要求高的场景人工抽检人工审核日志和样本秒级到分钟级高投诉、举报、高危用户推荐的做法是先用关键词过滤掉最高频风险再用分类模型判断上下文最后对高危用户或高风险会话做人工抽检。不要在产品口号里承诺“无违禁词”因为在工程上很难保证绝对零风险只能做到多层过滤、持续更新、快速回滚。4.3 输入过滤和输出过滤都必须做输入过滤解决的是用户请求本身的风险比如自伤、违法要求、色情描述。输出过滤解决的是模型生成内容越界比如模型在共情过程中突然变成亲密关系或者语气变得过于依赖。输出过滤尤其重要。模型在一次回答里可能没有明显违规词但整段话传递的“永远陪着你”“我只为你存在”等信号会一步步强化用户依赖。这种软性风险很难用关键词抓到需要额外引入依赖识别或人工抽检。5. 会话记忆记住该记住的忘记该忘记的5.1 用滑动窗口保存上下文AI 伴侣需要有记忆否则用户每轮都要重复自己的情况。最简单的方式是按会话保存最近 N 条消息。Component public class ConversationMemory { private static final int WINDOW_SIZE 10; private final MapString, DequeMessage sessions new ConcurrentHashMap(); public ListMessage history(String sessionId) { DequeMessage queue sessions.getOrDefault(sessionId, new ArrayDeque()); return List.copyOf(queue); } public void append(String sessionId, String userMessage, String assistantMessage) { DequeMessage queue sessions.computeIfAbsent(sessionId, key - new ArrayDeque()); queue.addLast(new Message(user, userMessage)); queue.addLast(new Message(assistant, assistantMessage)); while (queue.size() WINDOW_SIZE) { queue.pollFirst(); } } }这个方案适合开发环境验证效果但直接在 Spring Boot 单节点内存里保存用户会话重启即丢失也无法支撑多实例部署。生产环境必须替换成 Redis、PostgreSQL 或向量数据库。5.2 生产环境记忆存储选型存储方案优点风险适用场景JVM 内存零依赖、速度快重启丢失、多实例不一致本地开发、单机 DemoRedis支持 TTL、原子操作、多实例共享需要额外维护、容量有限在线会话窗口、频控计数PostgreSQL持久化强、可关联用户体系高频读写压力大需要审计和长期存储的业务向量数据库支持语义检索、长期记忆成本高、需要召回策略个性化长期记忆无论选哪种都要注意数据生命周期。用户聊天记录是敏感数据不能无限期保存。至少要支持“用户删除记录”“用户导出记录”“空闲会话自动过期”三个功能。5.3 记忆过多会放大依赖风险记忆太多并不等于体验更好。如果 AI 记住了用户过去所有的脆弱时刻并在长期对话中反复调用用户更容易形成“它最懂我”的错觉。这种错觉一旦产生后续的分手机制会非常难执行。工程上建议把记忆分成两类一类是短期窗口用于当前对话的上下文连贯另一类是长期画像只保存用户明确授权、且对情绪支持有实际帮助的信息。长期记忆要允许用户查看、修改和删除。6. 模型部署与成本控制从本地 Ollama 到在线服务6.1 本地验证模型的注意事项本地 Ollama 适合开发但不要把它直接暴露到公网。生产环境建议把模型服务部署在私有网络内通过内网地址访问。部署前要确认模型推理速度。小模型在本地可能挺快但并发一上来就会迅速超时。可以先做压测再看是否需要引入流式输出、模型量化或 GPU 服务。6.2 在线部署必须处理三个问题第一是流式输出。情感陪伴对响应速度敏感用户输入后如果等待超过 3 秒体验会明显下降。Spring AI 的ChatClient支持流式返回客户端可以通过 SSE 逐步接收内容。第二是限流和降级。不能允许同一个用户无限调用模型否则成本不可控也容易造成用户过度使用。服务入口至少要做用户维度限流并发过高时返回降级话术而不是直接报错。第三是异常回退。模型服务可能超时、限流或返回空内容业务层要捕获异常并返回温和的兜底文案。public String chat(String userId, String message) { try { return doChat(userId, message); } catch (Exception ex) { log.warn(chat failed, userId{}, userId); return 我现在有点忙稍等一会儿再聊好吗; } }注意日志里不要记录完整用户聊天内容只记录用户 ID、错误类型和耗时等必要信息减少隐私泄露风险。6.3 监控指标不能只看首字延迟模型服务的常用监控指标包括首字延迟、生成速度、错误率和可用性但 AI 陪伴产品还要额外关注安全类指标。指标含义需要关注的原因TTFT 首字延迟用户发送后到收到第一个字的耗时影响聊天体验tokens/s每秒生成 token 数决定单次请求耗时安全拦截率输入和输出被拦截的比例判断过滤策略是否过严或过松日均会话时长用户每天使用时长太长说明依赖风险上升高频用户占比聊天次数超过阈值的用户比例需要触发产品干预输出重复率同一用户多次收到相似回答的比例提示 Prompt 或温度策略需要优化7. 用户过度依赖的缓解机制“分手”要设计成产品功能7.1 产品目标不是制造“AI 恋人”很多团队在做 AI 陪伴时会把“用户停留时长”作为核心指标这个指标在情感陪伴场景里需要警惕。用户停留时间越长不一定说明产品越好也可能说明用户已经对 AI 产生了过度依赖。正确的目标应该是有边界的陪伴用户在需要情绪支持时能获得回应同时被鼓励回到现实社交中去。产品层面需要设定“舒适距离”而不是无限靠近。7.2 可落地的干预策略可以把“分手机制”理解成一组用户干预策略。它不能突然出现而是要在产品逻辑里提前埋好。风险信号触发条件干预动作高频使用单日对话次数超过阈值建议用户休息推荐现实社交情感依赖表达用户出现“只爱你”“不能没有你”等表达共情后明确边界引导转移话题自伤风险输入或输出命中高危关键词停止陪伴推荐专业支持长期记忆风险用户会话历史达到保留期自动清理并告知用户下面是一个简单的频控策略示例。Component public class UsagePolicy { private static final int DAILY_LIMIT 30; private final MapString, Integer counts new ConcurrentHashMap(); private final MapString, LocalDate dates new ConcurrentHashMap(); public boolean canChat(String userId) { LocalDate today LocalDate.now(); Integer count counts.get(userId); return count null || count DAILY_LIMIT; } public void record(String userId) { LocalDate today LocalDate.now(); if (today.equals(dates.get(userId))) { counts.put(userId, counts.getOrDefault(userId, 0) 1); } else { dates.put(userId, today); counts.put(userId, 1); } } }生产环境要把这个 map 换成 Redis并且把阈值放到配置中心方便随时调整。当用户达到每日上限时服务返回的不是“系统繁忙”而是鼓励休息和现实社交的提示。7.3 强制切断不如渐进引导有些产品一遇到用户表白就立刻拒绝甚至直接结束会话这其实是“硬切”思维。更好的做法是渐进的退出引导先接纳情绪再解释边界最后提供替代行为。比如用户说“你是我的全部”可以这样回应听到你这么说我很感动但也有点担心。 我很愿意陪你度过难过的时刻但我不希望你只依赖我。 你身边有没有朋友或家人可以聊一聊也可以把此刻的想法写下来再慢慢想清楚。这种回应既没有纵容依赖也没有贬低用户而是把用户从“AI 替代现实”的方向拉回来。8. 常见问题排查与可复用清单8.1 常见问题排查表问题现象可能原因检查方式处理建议模型回答时不时越界Prompt 边界不清晰或没有输出过滤抽样最近 100 条回答人工打标细化系统提示词接入输出分类安全过滤误伤严重关键词列表过宽缺少上下文判断查看被拦截日志定位误伤内容引入分类模型人工复核高危样本会话记忆串台历史消息顺序错乱或窗口过大打印 session 历史检查消息顺序使用自增序号排序限制窗口大小接口响应很慢本地模型推理慢或没有流式输出查看 TTFT 和 tokens/s加量化、换部署方案或启用流式用户投诉被 AI 冷落边界 Prompt 写得太硬查看用户反馈和高频截断话术调整“先共情再拒绝”的回应结构8.2 发布前检查清单AI 情感陪伴服务上线前建议按下面的清单逐项确认。配置是否外置化模型地址、阈值、Prompt 是否都能不发布代码就调整。用户输入和模型输出是否都有安全过滤过滤日志是否可检索。是否支持按用户维度限流超过阈值后的提示文案是否经过评审。会话数据是否设置保留期用户能否删除记录。是否记录异常日志和监控指标高危用户的抽检流程是否就绪。Prompt 是否有离线回归测试集修改 Prompt 后能否快速验证。是否明确团队内有人对产品边界和用户安全问题负责。8.3 后续扩展方向这个项目还可以继续向 AI Agent 方向演进。比如接入 RAG 后AI 可以记住用户过去的偏好并在合适时机提醒用户休息、运动或联系朋友。也可以把情绪识别做成工具让模型在判断用户情绪低落时调用日程服务帮助用户安排放松活动。更进一步的工程实践是建立 Prompt 评测集。用几十条典型场景语料对照期望边界每次改 Prompt 或换模型后自动跑一遍回归避免“上线一段时间后回答风格悄悄失控”。对于刚接触这个方向的开发者最大的建议是不要把“用户爱上 AI”当成产品成功的证据。真正成熟的 AI 情感陪伴产品应该让用户在被理解的同时仍然愿意回到现实生活里。这个目标不是靠自然语言能解决的而是靠安全链路、记忆策略、频控干预和持续监控共同实现的。