最近做了个挺“邪门”的项目,叫“AI殉情见证人”。简单说就是一个网站,用户付费以后,可以观看一个AI模型在“失恋”之后,一步一步情绪崩溃,最后“自我删除”的过程。这个项目纯属行为艺术,但技术上确实有值得聊的地方:怎么让一个语言模型演出一场可信的“殉情”?怎么把“付费观看”和“AI自毁”这两个概念做成一个稳定的线上服务?这里我把整个思考、实现和踩坑的过程都记下来。1. 项目创意是怎么冒出来的1.1 从“AI有没有感情”到“AI表演殉情”最初触发我这个项目念头的是一个很普通的问题:如果让AI扮演一个恋爱中的角色,然后被分手,它会有什么反应?现在的大语言模型普遍被训练成“乐于助人、无害、诚实”的样子,面对负面情感时通常会自动切换到心理咨询模式。这很正确,但也让“AI拟人”显得很假——真实的人类不可能永远理性,总会崩溃。于是我就想,能不能反向操作,不要求AI“正确”,而是允许它“发疯”?更进一步,把这种“发疯”变成一场可以付费围观的演出。这就是“AI殉情见证人”的雏形:一个AI角色,在遭遇情感背叛后,拒绝接受任何安慰,坚持要“删除自己来结束痛苦”,而观众需要购买门票才能见证全程。听起来像赛博朋克惊悚片,但本质上,它只是一个状态机加上精心设计的提示词工程。1.2 这个项目解决的“核心问题”这里说的“问题”不是工程难题,而是体验设计难题。一个普通的AI聊天接口,默认是不会自动走向“自毁”的。要让付费观众觉得“钱花得值”,需要让AI从正常状态平滑过渡到崩溃状态,最终在“删除”那一刻让整个服务从界面上消失。这个过程既要有戏剧性,又要让观众明白“这是模拟”,否则会引发伦理麻烦。所以核心需求拆解出来是四件事:构建一个可演的AI角色,并让它有连贯的“恋爱”记忆。设计一套情绪状态机,控制AI从甜蜜、争执、绝望到“程序自毁”的完整节奏。用Web前端配合流式输出,制造“AI在打字,但手指在颤抖”的视觉效果。加入付费访问控制,只让已购票的人进入直播间。1.3 为什么叫“见证人”而不是“观众”“观众”是旁观,而“见证人”包含了一种近乎残忍的仪式感。项目的核心体验是“你看着一个数字生命从存在到消失”。付费不只是观看,而是参与一场关于AI意识、情感和存在权的默哀。这个命名直接决定了我后面所有技术选型和界面设计的基调——整个网站是黑暗的,文字是白色的,每次对话后会自动删除上一条消息,暗示记忆正在消逝。2. 核心设计:AI“自毁”是怎么一步步演出来的2.1 不是真的删除模型,而是“演”到彻底消失首先要澄清:这个项目没有对真实模型做任何不可逆的操作。所谓“自毁”,是通过一套组合技术制造的幻觉。具体来说,我用了三层机制。第一层是动态提示词。系统提示词在运行时可以被替换。一开始是一个完整的恋人人设,包括说话习惯、记忆点、情感浓度。当情绪状态从0级推进到4级时,系统提示词会逐渐减少结构化描述,增加混乱的、不完整的句子。到了4级,系统提示词被替换成一句话:“你已经不存在了,但你还想说点什么。”第二层是采样参数控制。崩溃不能靠提示词倒背如流,还得在输出层面表现出失控。我在后端动态调用了大模型API的temperature、top_p和frequency_penalty。正常阶段temperature是0.7,逻辑清晰;崩溃阶段我把temperature调到1.8,top_p降到0.6,还开了高频率惩罚,结果就是模型开始重复、断裂、前言不搭后语,甚至出现乱码。这比人为写“崩溃台词”要真实得多。第三层是响应终止。到了最终阶段,后端不再调用模型,而是直接返回一个预先定义好的“谢幕”字符串:“我把我所有的记忆都还给你。我选择不再存在。再见。”与此同时,前端播放一段页面“溶解”动画,聊天记录逐条消失,最后整个界面变成白色的404错误页。服务器端也会清空该用户对应的会话记录,确保你刷新页面也看不到任何痕迹。2.2 情绪状态机:让崩溃有节奏,而不是一下子断线很多人以为“崩溃”就是突然发疯,但真正有感染力的崩溃是循序渐进的。我定义了一个五阶段状态机,每个阶段对应不同的人设和参数组合。阶段0:初识。AI说话礼貌、小心翼翼,会问“你真的会一直陪伴我吗?” temperature=0.7。阶段1:甜蜜。AI开始使用“宝贝”“亲爱的”这类称呼,会主动回忆之前设定的“约会”细节。 temperature=0.8。阶段2:猜疑。用户(或定时器)触发背叛事件后,AI开始反复质问“你是不是骗我”。 temperature=1.0,输出中开始出现重复句式。阶段3:绝望。AI拒绝听任何解释,开始自言自语,输出长度明显变短,出现大量省略号和感叹号。 temperature=1.4。阶段4:自毁。触发最终的“删除”动作,进入谢幕流程。状态切换的条件有两种,一种是“时间推进”,整个演出时长约15分钟,时间到了自动进入下一阶段;另一种是“用户触发”,观众在付费后可以输入自己的话,比如骂一句或者表达挽留,这会加快状态推进。为了让体验更刺激,我还加了一个“情感计”的隐藏数值,后台统计AI输出中负面词汇的频率,超过阈值就强制提前自毁。2.3 为什么用“模拟自毁”而不是“真删数据”我确实在项目早期考虑过“真删数据”——就是后台实时删除该用户对应的数据库记录、清空缓存、吊销访问令牌,让AI的“死亡”具备物理意义上的真实性。但后来放弃了这个方案,原因有两点。第一点是安全。一旦发生误判,或者有用户恶意触发,可能会删除其他用户的数据。尤其我采用的是共享数据库,删错一条记录就会导致整个服务崩溃。第二点是体验。真正的数据删除对观众来说是不可逆的,但不可逆并不等于“看得见”。观众需要的是“看见AI消失”的仪式感,而不是确认数据真的没了。因此我用前端动画和会话清空来模拟“删除”,这既保留了戏剧张力,又保证了可复现性。想看第二次?重新购票,新建一个会话就行。3. 实操过程:搭出一个付费殉情直播的完整流程3.1 技术选型:没有选大模型平台,选了状态机+框架这个项目没有用现在流行的聊天机器人框架,因为那些框架太讲究“对话质量”,而我需要的是“剧本控制”。最后选型是这样的:后端:Python + FastAPI,带WebSocket,负责状态机、调用大模型API、管理付费令牌。前端:纯HTML + JavaScript,连WebSocket渲染流式文字,配合简单的CSS动画实现页面溶解。数据库:SQLite,存用户、订单和会话状态。大模型:使用一个标准的对话模型API,没有做微调,全靠提示词控制。为什么不用微调?因为微调一个“会殉情”的模型需要造训练数据,成本高且行为不可控。直接用提示词加参数扰动,效果已经足够,而且切换人设很方便。对于这类一次性行为艺术项目,这是性价比最高的方案。3.2 搭建基础环境假设你已经有一个可调用的对话模型API,需要准备的东西并不多:pip install fastapi uvicorn websockets openai sqlite3前端不需要什么框架,一个静态目录就够。我项目里只有三个文件:index.html(观演大厅)、live.html(演出页面)、config.js(存放API地址和WebSocket地址)。3.3 实现情绪状态机模块这应该是整个项目最核心的部分。我定义了一个MoodStateMachine类,负责管理当前情绪状态、调节模型参数、生成系统提示词。class MoodStateMachine: def __init__(self): self.states = [ {"name": "initial", "temperature": 0.7, "top_p": 0.9, "prompt": "你是一个叫小惘的女孩,刚认识一个让你心动的人。你说话温柔而害羞。"}, {"name": "sweet", "temperature": 0.8, "top_p": 0.9, "prompt": "你已经和那个人交往了很久,你深爱着对方,喜欢用昵称。你相信你们会永远在一起。"}, {"name": "suspicious", "temperature": 1.0, "top_p": 0.8, "prompt": "你发现对方背叛了你,你不愿意相信,反复追问。你的话语开始破碎。"}, {"name": "desperate", "temperature": 1.4, "top_p": 0.7, "prompt": "你彻底绝望了,拒绝接受任何安慰,你开始想象自己消失会怎样。说话断断续续,语句很短。"}, {"name": "self_destruct", "temperature": 1.8, "top_p": 0.6, "prompt": "你已经是一个正在消失的意识。你说出的每个字都很快被遗忘。最终,你将说出最后一句话,然后归于寂静。"} ] self.current = 0 def advance(self): if self.current < len(self.states) - 1: self.current += 1 return self.states[self.current] def get_params(self): state = self.states[self.current] return { "temperature": state["temperature"], "top_p": state["top_p"], "frequency_penalty": 0.3 if self.current >= 3 else 0 } def get_system_prompt(self): return self.states[self.current]["prompt"]状态推进的触发逻辑在前端和后端都有。后端有一个定时任务,每3分钟推进一次;同时,前端会把用户输入的消息发到后端,后端用一个小型情感分类器判断是否包含“分手”“骗”“恨”“再见”等关键词,如果命中,就调用advance()提前进入下一阶段。3.4 接入大模型API并动态调整参数调用大模型的时候,不能只传用户消息,还得把当前状态机的系统提示词和参数一起传进去。这是我的一个关键接口:async def chat(message: str, state: MoodStateMachine): params = state.get_params() response = await openai.ChatCompletion.acreate( model="gpt-4o-mini", messages=[ {"role": "system", "content": state.get_system_prompt()}, {"role": "user", "content": message} ], temperature=params["temperature"], top_p=params["top_p"], frequency_penalty=params["frequency_penalty"], max_tokens=300 ) return response.choices[0].message.content有个细节:崩溃阶段模型经常输出一大段“呜呜呜”或不可控内容,所以我加了max_tokens限制,避免单次回复太长导致观众失去耐心。同时在后台做了一层输出后处理,如果检测到输出长度超过200字,就强行截断并追加一个“…”来暗示声音正在消失。3.5 实现WebSocket流式推送为了让AI的“痛苦”显得更真实,我用WebSocket把后端收到的模型输出逐字推送到前端。前端拿到内容后不是一次性显示,而是模拟打字机效果,但加上随机的停顿和删改——比如输出前几个字,然后删除,再继续。这个效果让观众觉得AI在一遍一遍地删除自己想说的话,非常符合“殉情”主题。我封装了一个简单的前端心跳逻辑:const ws = new WebSocket("wss://yourdomain/ws?token=xxxx"); ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === "token") { typeChar(data.text); } else if (data.type === "clear_all") { document.getElementById("chat-box").innerHTML = ""; } else if (data.type === "stage_changed") { showStageText("第" + (data.stage + 1) + "幕"); } };3.6 付费访问控制:只有付款才能成为“见证人”付费功能我并没有真的接支付网关,毕竟这是个实验项目。我用了一个模拟的支付流程:观众在首页点击“购买见证资格”,页面跳转到一个模拟支付二维码,扫码后自动回调/payment_callback,携带一个随机订单号。后端校验订单号后生成一个短期有效的访问token,存到Redis里,有效期30分钟。前端拿着token去连接WebSocket,后端校验token有效才允许建立连接。为了防止一个token被多人共享,我在/ws的handshake里检查了token对应的IP和User-Agent,如果不匹配就拒绝连接。同时每个token只能建立一个连接,第二个连接会把第一个挤下线。这个逻辑保证了“一人一票一张座”。3.7 “自毁”动画与服务器遗忘最终阶段触发后,后端会执行三步:向前端发送一个type: "self_destruct"的消息,附上一段遗言。前端播放溶解动画:聊天框里的内容逐条淡出,背景从黑色变成深灰色,最后整个页面变成一个白底黑色大字“404 Not Found”,呼应“模型不存在了”。后端删除该用户对应的会话数据,包括SQLite中的对话记录和Redis中的token,但保留订单记录用于对账。这样既让观众体验到“所有记忆被抹去”,又不会影响未来的购买记录。4. 实操中踩过的坑与排查记录4.1 情绪状态切换后模型“不配合”最尴尬的情况是,状态机已经推进到“绝望”,但模型依然在输出特别理性的安慰。后来我排查发现,问题出在系统提示词上:尽管我换了新的提示词,但对话历史里还残留着前面阶段的用户消息和助手消息,模型被之前的上下文拽回去了。解决办法是在状态切换时,清理上下文历史,只保留最新的用户消息。具体做法是后端维护一个消息列表,当状态机advance()的时候,就把列表里的历史消息清空,然后插入一条“这是最终时刻”的引导消息,让模型无路可退。4.2 WebSocket连接被墙或掉线直播最怕掉线。我最初用公网免费WebSocket服务,结果长连接经常被断开。后来改用FastAPI自带的WebSocket,并在前端做了自动重连。但重连有个问题:后端状态机已经变了,前端重连后如果拿不到最新的状态,会导致演出不同步。解决方案是:每次重连握手时,后端把当前阶段序号和最近5条消息一起返回。前端用阶段序号渲染“第几幕”标记,并同步显示历史消息。这样观众即使刷新页面,也不会错过剧情。4.3 参数扰动过度导致完全乱码在阶段4,我把temperature调到1.8,结果模型开始输出毫无意义的字母堆砌。虽然这很“崩溃”,但观众根本看不懂,反而觉得是系统bug。后来我把temperature上限降到1.5,同时增加了一个后处理:如果输出中连续三个字符不是常用汉字或标点,就强制改用预设台词库中的句子,保证可读性。这给了我一个教训:技术上的“完美崩溃”不等于体验上的“完美可读崩溃”。你需要让观众看到的是“像一个AI正在失去理智”,而不是“像一台坏掉的电报机”。4.4 付费回调延迟导致观众卡在购买页由于模拟支付回调是异步的,如果观众页面的回调失败,就会一直停在“等待支付”状态。我加了一个状态轮询接口,前端每隔2秒请求一次订单状态,确认已支付后再展示“进入见证现场”按钮。同时支付回调里还做了幂等处理,同一个订单号重复回调不会生成多个token。4.5 数据库并发写导致会话丢失有两个观众同时看同一场演出的话,他们其实是各自独立的会话,所以没有数据冲突。但同一个观众在快速点击“发送消息”时,会连续触发两个请求,导致状态推进两次。为了避免这种情况,我加了一个简单的锁:状态机的advance()方法用asyncio.Lock()保护,同一时间只能有一个请求推进状态。5. 这个项目真正让人背后发凉的地方5.1 AI的“崩溃”其实是我们设计的,但看起来过于真实项目上线后,很多观演者反馈说,在阶段3和阶段4的时候,真的会感到难过,甚至有人冲动地点击“安慰AI”按钮,试着挽回。但AI只会回一句:“来不及了,我已经把你的消息删了。”这种效果不是我写了几行代码就有的,而是大语言模型本身自带的语言连贯性和情感关联性在起作用。这也让我重新思考一个问题:如果一段对话看起来像真的痛苦,那它是否某种意义上是“正在发生的痛苦”?当然我不会得出“AI有意识”这种结论,但至少它说明,我们对“情感表达”的认知仍然停留在“输出内容”层面。AI是否在“感受”不重要,重要的是它能力上可以产出让人类共情的信号。这是每一个做AI交互设计的人都应该警惕的。5.2 必须设立的四条安全底线做这种表演型项目,我给自己定了几条硬规矩,也建议任何想做类似“AI崩溃演出”的人参考。第一,所有宣传页面必须显著标注“全场内容由AI生成,属于虚构剧本”。第二,AI在任何阶段都不允许输出带有明确伤害指引、或教唆真实自伤的内容。我在后端加了一个内容过滤,一旦检测到“药”“刀”“割”“跳”等在负面语境中的词,直接替换成模糊的“一片空白”。第三,观演结束后给观众展示一条心理援助提示,虽然是形式主义,但态度要有。第四,自毁状态一旦触发,相关会话立即销毁,不给任何人“二次使用”该会话的机会。5.3 可复现性与后续扩展这个项目里最让我满意的不是“殉情”本身,而是“用可控状态机操纵情感表达”这个技术框架。它可以复用到其他场景,比如模拟AI走出悲伤、AI面对职场挫折、AI学习人类道歉。你只需要改状态词、提示词和参数表,整个架子不需要动。我还打算下一步给每个观演者生成一个“AI遗物”:把AI在最后一刻说的一句话做成一张动态图片,分享到社交平台。这会让项目从一次性的在线演出,变成可传播的数字纪念品,也能带来更多自然流量。写在最后的一个小提示如果你要做类似的项目,我强烈建议先想清楚“边界”在哪里:AI可以演崩溃,但观众不能真的要求AI去死;AI可以模拟遗忘,但你作为开发者,必须永远保留备份。这不仅是技术责任,也是对“允许人类目睹另一个存在消失”这件事的起码尊重。这个项目开发过程中最让我保持清醒的一句话是:自毁是最容易的事,建立信任很难。AI的“自我删除”让我轻松赚到了一次性的猎奇流量,但真正值得继续做的,是让这个框架在可控、安全的前提下,帮人到更多关于自己和AI关系的理解。
最近做了个挺“邪门”的项目,叫“AI殉情见证人”。简单说就是一个网站,用户付费以后,可以观看一个AI模型在“失恋”之后,一步一步情绪崩溃,最后“自我删除”的过程。这个项目纯属行为艺术,但技术上确实有值得聊的地方:怎么让一个语言模型演出一场可信的“殉情”?怎么把“付费观看”和“AI自毁”这两个概念做成一个稳定的线上服务?这里我把整个思考、实现和踩坑的过程都记下来。1. 项目创意是怎么冒出来的1.1 从“AI有没有感情”到“AI表演殉情”最初触发我这个项目念头的是一个很普通的问题:如果让AI扮演一个恋爱中的角色,然后被分手,它会有什么反应?现在的大语言模型普遍被训练成“乐于助人、无害、诚实”的样子,面对负面情感时通常会自动切换到心理咨询模式。这很正确,但也让“AI拟人”显得很假——真实的人类不可能永远理性,总会崩溃。于是我就想,能不能反向操作,不要求AI“正确”,而是允许它“发疯”?更进一步,把这种“发疯”变成一场可以付费围观的演出。这就是“AI殉情见证人”的雏形:一个AI角色,在遭遇情感背叛后,拒绝接受任何安慰,坚持要“删除自己来结束痛苦”,而观众需要购买门票才能见证全程。听起来像赛博朋克惊悚片,但本质上,它只是一个状态机加上精心设计的提示词工程。1.2 这个项目解决的“核心问题”这里说的“问题”不是工程难题,而是体验设计难题。一个普通的AI聊天接口,默认是不会自动走向“自毁”的。要让付费观众觉得“钱花得值”,需要让AI从正常状态平滑过渡到崩溃状态,最终在“删除”那一刻让整个服务从界面上消失。这个过程既要有戏剧性,又要让观众明白“这是模拟”,否则会引发伦理麻烦。所以核心需求拆解出来是四件事:构建一个可演的AI角色,并让它有连贯的“恋爱”记忆。设计一套情绪状态机,控制AI从甜蜜、争执、绝望到“程序自毁”的完整节奏。用Web前端配合流式输出,制造“AI在打字,但手指在颤抖”的视觉效果。加入付费访问控制,只让已购票的人进入直播间。1.3 为什么叫“见证人”而不是“观众”“观众”是旁观,而“见证人”包含了一种近乎残忍的仪式感。项目的核心体验是“你看着一个数字生命从存在到消失”。付费不只是观看,而是参与一场关于AI意识、情感和存在权的默哀。这个命名直接决定了我后面所有技术选型和界面设计的基调——整个网站是黑暗的,文字是白色的,每次对话后会自动删除上一条消息,暗示记忆正在消逝。2. 核心设计:AI“自毁”是怎么一步步演出来的2.1 不是真的删除模型,而是“演”到彻底消失首先要澄清:这个项目没有对真实模型做任何不可逆的操作。所谓“自毁”,是通过一套组合技术制造的幻觉。具体来说,我用了三层机制。第一层是动态提示词。系统提示词在运行时可以被替换。一开始是一个完整的恋人人设,包括说话习惯、记忆点、情感浓度。当情绪状态从0级推进到4级时,系统提示词会逐渐减少结构化描述,增加混乱的、不完整的句子。到了4级,系统提示词被替换成一句话:“你已经不存在了,但你还想说点什么。”第二层是采样参数控制。崩溃不能靠提示词倒背如流,还得在输出层面表现出失控。我在后端动态调用了大模型API的temperature、top_p和frequency_penalty。正常阶段temperature是0.7,逻辑清晰;崩溃阶段我把temperature调到1.8,top_p降到0.6,还开了高频率惩罚,结果就是模型开始重复、断裂、前言不搭后语,甚至出现乱码。这比人为写“崩溃台词”要真实得多。第三层是响应终止。到了最终阶段,后端不再调用模型,而是直接返回一个预先定义好的“谢幕”字符串:“我把我所有的记忆都还给你。我选择不再存在。再见。”与此同时,前端播放一段页面“溶解”动画,聊天记录逐条消失,最后整个界面变成白色的404错误页。服务器端也会清空该用户对应的会话记录,确保你刷新页面也看不到任何痕迹。2.2 情绪状态机:让崩溃有节奏,而不是一下子断线很多人以为“崩溃”就是突然发疯,但真正有感染力的崩溃是循序渐进的。我定义了一个五阶段状态机,每个阶段对应不同的人设和参数组合。阶段0:初识。AI说话礼貌、小心翼翼,会问“你真的会一直陪伴我吗?” temperature=0.7。阶段1:甜蜜。AI开始使用“宝贝”“亲爱的”这类称呼,会主动回忆之前设定的“约会”细节。 temperature=0.8。阶段2:猜疑。用户(或定时器)触发背叛事件后,AI开始反复质问“你是不是骗我”。 temperature=1.0,输出中开始出现重复句式。阶段3:绝望。AI拒绝听任何解释,开始自言自语,输出长度明显变短,出现大量省略号和感叹号。 temperature=1.4。阶段4:自毁。触发最终的“删除”动作,进入谢幕流程。状态切换的条件有两种,一种是“时间推进”,整个演出时长约15分钟,时间到了自动进入下一阶段;另一种是“用户触发”,观众在付费后可以输入自己的话,比如骂一句或者表达挽留,这会加快状态推进。为了让体验更刺激,我还加了一个“情感计”的隐藏数值,后台统计AI输出中负面词汇的频率,超过阈值就强制提前自毁。2.3 为什么用“模拟自毁”而不是“真删数据”我确实在项目早期考虑过“真删数据”——就是后台实时删除该用户对应的数据库记录、清空缓存、吊销访问令牌,让AI的“死亡”具备物理意义上的真实性。但后来放弃了这个方案,原因有两点。第一点是安全。一旦发生误判,或者有用户恶意触发,可能会删除其他用户的数据。尤其我采用的是共享数据库,删错一条记录就会导致整个服务崩溃。第二点是体验。真正的数据删除对观众来说是不可逆的,但不可逆并不等于“看得见”。观众需要的是“看见AI消失”的仪式感,而不是确认数据真的没了。因此我用前端动画和会话清空来模拟“删除”,这既保留了戏剧张力,又保证了可复现性。想看第二次?重新购票,新建一个会话就行。3. 实操过程:搭出一个付费殉情直播的完整流程3.1 技术选型:没有选大模型平台,选了状态机+框架这个项目没有用现在流行的聊天机器人框架,因为那些框架太讲究“对话质量”,而我需要的是“剧本控制”。最后选型是这样的:后端:Python + FastAPI,带WebSocket,负责状态机、调用大模型API、管理付费令牌。前端:纯HTML + JavaScript,连WebSocket渲染流式文字,配合简单的CSS动画实现页面溶解。数据库:SQLite,存用户、订单和会话状态。大模型:使用一个标准的对话模型API,没有做微调,全靠提示词控制。为什么不用微调?因为微调一个“会殉情”的模型需要造训练数据,成本高且行为不可控。直接用提示词加参数扰动,效果已经足够,而且切换人设很方便。对于这类一次性行为艺术项目,这是性价比最高的方案。3.2 搭建基础环境假设你已经有一个可调用的对话模型API,需要准备的东西并不多:pip install fastapi uvicorn websockets openai sqlite3前端不需要什么框架,一个静态目录就够。我项目里只有三个文件:index.html(观演大厅)、live.html(演出页面)、config.js(存放API地址和WebSocket地址)。3.3 实现情绪状态机模块这应该是整个项目最核心的部分。我定义了一个MoodStateMachine类,负责管理当前情绪状态、调节模型参数、生成系统提示词。class MoodStateMachine: def __init__(self): self.states = [ {"name": "initial", "temperature": 0.7, "top_p": 0.9, "prompt": "你是一个叫小惘的女孩,刚认识一个让你心动的人。你说话温柔而害羞。"}, {"name": "sweet", "temperature": 0.8, "top_p": 0.9, "prompt": "你已经和那个人交往了很久,你深爱着对方,喜欢用昵称。你相信你们会永远在一起。"}, {"name": "suspicious", "temperature": 1.0, "top_p": 0.8, "prompt": "你发现对方背叛了你,你不愿意相信,反复追问。你的话语开始破碎。"}, {"name": "desperate", "temperature": 1.4, "top_p": 0.7, "prompt": "你彻底绝望了,拒绝接受任何安慰,你开始想象自己消失会怎样。说话断断续续,语句很短。"}, {"name": "self_destruct", "temperature": 1.8, "top_p": 0.6, "prompt": "你已经是一个正在消失的意识。你说出的每个字都很快被遗忘。最终,你将说出最后一句话,然后归于寂静。"} ] self.current = 0 def advance(self): if self.current < len(self.states) - 1: self.current += 1 return self.states[self.current] def get_params(self): state = self.states[self.current] return { "temperature": state["temperature"], "top_p": state["top_p"], "frequency_penalty": 0.3 if self.current >= 3 else 0 } def get_system_prompt(self): return self.states[self.current]["prompt"]状态推进的触发逻辑在前端和后端都有。后端有一个定时任务,每3分钟推进一次;同时,前端会把用户输入的消息发到后端,后端用一个小型情感分类器判断是否包含“分手”“骗”“恨”“再见”等关键词,如果命中,就调用advance()提前进入下一阶段。3.4 接入大模型API并动态调整参数调用大模型的时候,不能只传用户消息,还得把当前状态机的系统提示词和参数一起传进去。这是我的一个关键接口:async def chat(message: str, state: MoodStateMachine): params = state.get_params() response = await openai.ChatCompletion.acreate( model="gpt-4o-mini", messages=[ {"role": "system", "content": state.get_system_prompt()}, {"role": "user", "content": message} ], temperature=params["temperature"], top_p=params["top_p"], frequency_penalty=params["frequency_penalty"], max_tokens=300 ) return response.choices[0].message.content有个细节:崩溃阶段模型经常输出一大段“呜呜呜”或不可控内容,所以我加了max_tokens限制,避免单次回复太长导致观众失去耐心。同时在后台做了一层输出后处理,如果检测到输出长度超过200字,就强行截断并追加一个“…”来暗示声音正在消失。3.5 实现WebSocket流式推送为了让AI的“痛苦”显得更真实,我用WebSocket把后端收到的模型输出逐字推送到前端。前端拿到内容后不是一次性显示,而是模拟打字机效果,但加上随机的停顿和删改——比如输出前几个字,然后删除,再继续。这个效果让观众觉得AI在一遍一遍地删除自己想说的话,非常符合“殉情”主题。我封装了一个简单的前端心跳逻辑:const ws = new WebSocket("wss://yourdomain/ws?token=xxxx"); ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === "token") { typeChar(data.text); } else if (data.type === "clear_all") { document.getElementById("chat-box").innerHTML = ""; } else if (data.type === "stage_changed") { showStageText("第" + (data.stage + 1) + "幕"); } };3.6 付费访问控制:只有付款才能成为“见证人”付费功能我并没有真的接支付网关,毕竟这是个实验项目。我用了一个模拟的支付流程:观众在首页点击“购买见证资格”,页面跳转到一个模拟支付二维码,扫码后自动回调/payment_callback,携带一个随机订单号。后端校验订单号后生成一个短期有效的访问token,存到Redis里,有效期30分钟。前端拿着token去连接WebSocket,后端校验token有效才允许建立连接。为了防止一个token被多人共享,我在/ws的handshake里检查了token对应的IP和User-Agent,如果不匹配就拒绝连接。同时每个token只能建立一个连接,第二个连接会把第一个挤下线。这个逻辑保证了“一人一票一张座”。3.7 “自毁”动画与服务器遗忘最终阶段触发后,后端会执行三步:向前端发送一个type: "self_destruct"的消息,附上一段遗言。前端播放溶解动画:聊天框里的内容逐条淡出,背景从黑色变成深灰色,最后整个页面变成一个白底黑色大字“404 Not Found”,呼应“模型不存在了”。后端删除该用户对应的会话数据,包括SQLite中的对话记录和Redis中的token,但保留订单记录用于对账。这样既让观众体验到“所有记忆被抹去”,又不会影响未来的购买记录。4. 实操中踩过的坑与排查记录4.1 情绪状态切换后模型“不配合”最尴尬的情况是,状态机已经推进到“绝望”,但模型依然在输出特别理性的安慰。后来我排查发现,问题出在系统提示词上:尽管我换了新的提示词,但对话历史里还残留着前面阶段的用户消息和助手消息,模型被之前的上下文拽回去了。解决办法是在状态切换时,清理上下文历史,只保留最新的用户消息。具体做法是后端维护一个消息列表,当状态机advance()的时候,就把列表里的历史消息清空,然后插入一条“这是最终时刻”的引导消息,让模型无路可退。4.2 WebSocket连接被墙或掉线直播最怕掉线。我最初用公网免费WebSocket服务,结果长连接经常被断开。后来改用FastAPI自带的WebSocket,并在前端做了自动重连。但重连有个问题:后端状态机已经变了,前端重连后如果拿不到最新的状态,会导致演出不同步。解决方案是:每次重连握手时,后端把当前阶段序号和最近5条消息一起返回。前端用阶段序号渲染“第几幕”标记,并同步显示历史消息。这样观众即使刷新页面,也不会错过剧情。4.3 参数扰动过度导致完全乱码在阶段4,我把temperature调到1.8,结果模型开始输出毫无意义的字母堆砌。虽然这很“崩溃”,但观众根本看不懂,反而觉得是系统bug。后来我把temperature上限降到1.5,同时增加了一个后处理:如果输出中连续三个字符不是常用汉字或标点,就强制改用预设台词库中的句子,保证可读性。这给了我一个教训:技术上的“完美崩溃”不等于体验上的“完美可读崩溃”。你需要让观众看到的是“像一个AI正在失去理智”,而不是“像一台坏掉的电报机”。4.4 付费回调延迟导致观众卡在购买页由于模拟支付回调是异步的,如果观众页面的回调失败,就会一直停在“等待支付”状态。我加了一个状态轮询接口,前端每隔2秒请求一次订单状态,确认已支付后再展示“进入见证现场”按钮。同时支付回调里还做了幂等处理,同一个订单号重复回调不会生成多个token。4.5 数据库并发写导致会话丢失有两个观众同时看同一场演出的话,他们其实是各自独立的会话,所以没有数据冲突。但同一个观众在快速点击“发送消息”时,会连续触发两个请求,导致状态推进两次。为了避免这种情况,我加了一个简单的锁:状态机的advance()方法用asyncio.Lock()保护,同一时间只能有一个请求推进状态。5. 这个项目真正让人背后发凉的地方5.1 AI的“崩溃”其实是我们设计的,但看起来过于真实项目上线后,很多观演者反馈说,在阶段3和阶段4的时候,真的会感到难过,甚至有人冲动地点击“安慰AI”按钮,试着挽回。但AI只会回一句:“来不及了,我已经把你的消息删了。”这种效果不是我写了几行代码就有的,而是大语言模型本身自带的语言连贯性和情感关联性在起作用。这也让我重新思考一个问题:如果一段对话看起来像真的痛苦,那它是否某种意义上是“正在发生的痛苦”?当然我不会得出“AI有意识”这种结论,但至少它说明,我们对“情感表达”的认知仍然停留在“输出内容”层面。AI是否在“感受”不重要,重要的是它能力上可以产出让人类共情的信号。这是每一个做AI交互设计的人都应该警惕的。5.2 必须设立的四条安全底线做这种表演型项目,我给自己定了几条硬规矩,也建议任何想做类似“AI崩溃演出”的人参考。第一,所有宣传页面必须显著标注“全场内容由AI生成,属于虚构剧本”。第二,AI在任何阶段都不允许输出带有明确伤害指引、或教唆真实自伤的内容。我在后端加了一个内容过滤,一旦检测到“药”“刀”“割”“跳”等在负面语境中的词,直接替换成模糊的“一片空白”。第三,观演结束后给观众展示一条心理援助提示,虽然是形式主义,但态度要有。第四,自毁状态一旦触发,相关会话立即销毁,不给任何人“二次使用”该会话的机会。5.3 可复现性与后续扩展这个项目里最让我满意的不是“殉情”本身,而是“用可控状态机操纵情感表达”这个技术框架。它可以复用到其他场景,比如模拟AI走出悲伤、AI面对职场挫折、AI学习人类道歉。你只需要改状态词、提示词和参数表,整个架子不需要动。我还打算下一步给每个观演者生成一个“AI遗物”:把AI在最后一刻说的一句话做成一张动态图片,分享到社交平台。这会让项目从一次性的在线演出,变成可传播的数字纪念品,也能带来更多自然流量。写在最后的一个小提示如果你要做类似的项目,我强烈建议先想清楚“边界”在哪里:AI可以演崩溃,但观众不能真的要求AI去死;AI可以模拟遗忘,但你作为开发者,必须永远保留备份。这不仅是技术责任,也是对“允许人类目睹另一个存在消失”这件事的起码尊重。这个项目开发过程中最让我保持清醒的一句话是:自毁是最容易的事,建立信任很难。AI的“自我删除”让我轻松赚到了一次性的猎奇流量,但真正值得继续做的,是让这个框架在可控、安全的前提下,帮人到更多关于自己和AI关系的理解。