1. 这篇文章真正要解决的问题
你有没有过这样的经历?年初立下Flag要每天健身、学英语、读书,结果不到一个月就默默放弃。或者,你开发了一个帮助用户养成习惯的小程序,却发现用户活跃度断崖式下跌,根本“催不动”?
这正是许多个人开发者和产品经理面临的经典困境:我们精心设计的监督工具,用户为什么不用?是提醒不够频繁,还是激励不够诱人?最近,我在B站AI创造公开赛中看到一个名为“我做了个监督小程序,却不想催你”的项目,它提供了一个截然不同的解题思路。这个项目没有采用传统的“打卡+提醒”强监督模式,而是尝试用AI构建一种更温和、更智能的陪伴式监督。
本文将深入拆解这个项目的核心逻辑。我们不止步于复述其功能,而是要探究一个更深层的问题:在用户普遍对“被监督”感到反感的今天,如何利用AI技术设计出用户真正愿意长期使用的习惯养成产品?我们将从产品理念、技术实现(特别是AI Agent的应用)、代码实战到避坑指南,为你完整呈现如何构建一个“不招人烦”的智能监督助手。无论你是想学习AI与小程序结合的最新实践,还是正在为自家产品的用户留存问题寻找灵感,这篇文章都将提供可直接落地的参考。
2. 从“监督”到“陪伴”:产品理念的范式转变
在深入代码之前,我们必须先理解这个项目背后的核心理念,这是它区别于市面上无数打卡App的关键。
传统的习惯养成工具,其逻辑本质上是“行为主义”的:设定目标(如每天跑步)→ 定时提醒(Push通知)→ 完成打卡(获得积分/勋章)→ 未完成则惩罚(连续记录中断)。这套系统简单有效,但也带来了明显的副作用:监督压力。用户将工具视为一个冰冷的监工,一旦产生逆反心理或暂时性失败,就容易彻底放弃。
“不想催你”这个项目,其理念更接近“人本主义”和“认知行为疗法”。它试图将AI从“监督者”转变为“陪伴者”或“教练”。其目标不是确保用户100%执行计划,而是帮助用户理解自己、接纳波动、并可持续地调整计划。这种转变体现在几个关键设计点上:
- 动态目标协商:AI不会死板地要求用户执行原定计划。当用户多次未完成时,AI会主动建议“是否将目标从‘每天学习2小时’调整为‘每周学习10小时’?”,让计划更贴合用户真实的生活节奏。
- 归因分析与共情:当用户未完成任务时,AI不会简单标记为“失败”,而是会引导用户记录原因(如“今天加班太累”、“身体不适”),并给予理解性的反馈,如“辛苦了,休息也是为了更好地前进。我们明天再试试?”
- 正向反馈循环:奖励机制不仅与“是否完成”挂钩,更与“用户的努力程度”、“自我觉察的深度”相关联。即使任务没完成,但认真记录了原因和感受,也能获得积极的认可。
这种理念下,技术实现的重点就从“如何更精准地推送提醒”,变成了“如何让AI更自然地理解用户状态并做出个性化互动”。这正是AI Agent技术可以大显身手的地方。
3. 技术架构核心:AI Agent 驱动的交互引擎
这个项目的技术骨架是一个典型的“前端(小程序) + 后端(服务端) + AI 服务”的三层架构,但其灵魂在于后端如何组织AI能力来驱动交互。
用户输入 │ ▼ 微信小程序前端 (UI/交互层) │ (HTTPS API) ▼ 后端服务 (Node.js/Python) ├── 用户状态管理模块 (存储目标、记录、历史) ├── 对话理解与路由模块 (NLU) ├── **AI Agent 协调引擎** (核心) │ ├── 目标评估Agent │ ├── 情绪感知Agent │ ├── 计划调整Agent │ └── 激励生成Agent └── 外部AI服务调用 (如文心一言、GPT API) │ ▼ 生成个性化响应,更新用户状态核心模块解析:
- 对话理解与路由模块:解析用户发送的文本(如“今天不想学了”、“我完成了阅读任务”),识别其意图(是汇报完成、表达困难、请求帮助还是单纯聊天)和关键实体(任务名、情绪词、原因)。
- AI Agent 协调引擎:这是项目的大脑。它不是一个单一的AI模型,而是一组分工协作的“智能体”(Agent)。
- 目标评估Agent:分析用户历史完成数据,判断当前目标的合理性(太简单?太难?)。
- 情绪感知Agent:从用户输入中识别情绪状态(积极、疲惫、沮丧、焦虑),为后续回应定调。
- 计划调整Agent:在评估和感知的基础上,生成调整目标的建议(如拆分任务、延长期限、降低难度)。
- 激励生成Agent:根据用户当前状态和成就,生成个性化的鼓励话语,避免千篇一律的“加油”。
- 外部AI服务调用:上述各个Agent的具体推理和文本生成能力,通常通过调用如OpenAI GPT、文心一言、通义千问等大模型的API来实现。关键在于设计好的**系统提示词(System Prompt)**来让大模型扮演好特定Agent的角色。
这种架构的优势在于可解释性和可迭代性。每个Agent的职责明确,我们可以单独优化“情绪感知”的准确度,或调整“计划调整”的策略,而不影响整体系统。
4. 环境准备与关键技术选型
要复现或借鉴这样一个项目,你需要准备以下开发环境和技术栈。
4.1 开发环境
- 操作系统:Windows 10/11, macOS, 或 Linux (如Ubuntu 20.04+)。
- Node.js:推荐 LTS 版本 (如 v18.x 或 v20.x)。这是后端服务的常见选择。
- Python:可选,版本 3.8+。如果你计划使用一些成熟的AI框架(如LangChain)或进行更复杂的模型微调,Python环境会更方便。
- 代码编辑器:VS Code (推荐) 或 WebStorm。
- 微信开发者工具:用于小程序端的开发、调试和预览。
4.2 技术选型建议
- 小程序前端:原生微信小程序开发(使用WXML、WXSS、JS),或使用跨端框架如Taro、Uni-app(需考虑包大小和性能)。
- 后端服务:
- 语言:Node.js (Express/Koa框架) 或 Python (FastAPI/Flask框架)。Node.js在IO密集型、实时交互场景有优势;Python在AI生态集成上更丰富。
- 数据库:轻量级可选 SQLite (开发测试)、MySQL/PostgreSQL (生产);文档型可选 MongoDB,更适合存储用户灵活的行为日志。
- AI能力接入:
- 国内可稳定访问:百度文心一言(ERNIE)、阿里通义千问、智谱AI(GLM)、月之暗面(Kimi)。这些平台提供标准的API接口,需申请API Key。
- 核心依赖:用于调用AI API的SDK,例如
openai库(兼容多种API)、各厂商官方SDK。
- 关键第三方服务:
- 用户认证:直接使用微信小程序登录能力。
- 云存储/部署:考虑腾讯云开发(TCB)或阿里云函数计算(FC),实现免运维部署,尤其适合个人开发者。
5. 核心流程拆解:一次完整的智能交互是如何发生的
让我们跟随一个用户场景,看看数据是如何在系统中流动的。
场景:用户“小明”设定目标“每周跑步3次”。本周第三次,他发送消息:“今天下雨,而且感觉有点累,不想跑了。”
步骤1:用户输入与上传
- 小明在小程序聊天框输入文字,点击发送。
- 小程序前端通过
wx.request将消息{userId: ‘xxx’, content: ‘今天下雨…’, taskId: ‘run_week’}发送至后端API。
步骤2:意图识别与状态获取
- 后端接收到请求,首先通过一个轻量级的NLU模块(可以是规则匹配,也可以用小模型)进行意图识别。识别结果为:
report_skip_with_reason(带原因的跳过汇报)。 - 同时,从数据库查询小明本周的跑步记录(已跑2次)和该任务的历史数据。
步骤3:AI Agent 协同工作这是最核心的步骤。后端服务组织一次对大模型的调用,但这次调用承载了多个Agent的“思考”。
# 示例:使用 OpenAI 兼容 API (如智谱GLM) 进行 Agent 推理 import openai # 配置API基础信息,此处以智谱AI为例 client = openai.OpenAI( api_key="your_glm_api_key", base_url="https://open.bigmodel.cn/api/paas/v4/" # GLM的API端点 ) def generate_ai_response(user_input, user_history, task_status): # 构建一个复杂的 System Prompt 来定义AI的角色和思考流程 system_prompt = """ 你是一个习惯养成教练,擅长鼓励和引导用户,而不是施加压力。请根据以下模块逐步思考,并生成最终回复: 1. 【情绪感知】分析用户输入“{user_input}”中表达的情绪和潜在原因。关键词:{reason_keywords}。 2. 【目标评估】用户当前任务“{task_name}”状态为:{task_status}。评估其目标的合理性及本次未完成的影响。 3. 【计划调整建议】基于以上分析,是否需要建议用户调整目标(如改为本周2次)?如果需要,给出具体、温和的建议。 4. 【激励生成】生成一段不超过100字的回复。要求:a) 共情用户提及的原因;b) 认可用户已付出的努力(已完成{completed_times}次);c) 传递“灵活性”和“可持续性”比“严格坚持”更重要的理念;d) 语气温暖、支持。 请直接输出最终回复,不要输出思考过程。 """.format( user_input=user_input, reason_keywords="下雨,累", task_name="每周跑步3次", task_status="本周已完成2/3次", completed_times=2 ) # 组织对话历史,让AI有上下文 messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_input} ] try: response = client.chat.completions.create( model="glm-4", # 指定模型 messages=messages, temperature=0.7, # 控制创造性,不宜过高以保证稳定性 max_tokens=500 ) ai_reply = response.choices[0].message.content return ai_reply except Exception as e: # 降级处理:返回预设的鼓励语 return “听起来你今天需要休息。没关系,倾听身体的声音也是自律的一部分。已经完成两次跑步很棒了,我们明天再看看状态如何?” # 调用函数 final_reply = generate_ai_response(“今天下雨,而且感觉有点累,不想跑了。”, user_history, task_status)步骤4:响应返回与状态更新
- 将AI生成的回复(例如:“下雨天确实不适合户外运动,你的决定很明智。身体感到疲惫时,强迫自己反而容易受伤。本周你已经成功完成了两次跑步,这已经很棒了!如果愿意,我们可以把第三次移到周末,或者这周就保持两次的成果,你觉得呢?”)返回给小程序前端。
- 同时,在数据库更新该任务的状态。这里不是标记为“失败”,而是可能记录为“主动跳过(原因:天气/疲劳)”,并关联AI的回复。这为后续分析用户行为模式积累了宝贵数据。
步骤5:前端展示与交互小程序前端收到回复后,以聊天气泡的形式展示。界面UI应营造轻松、友好的氛围,避免使用红色、感叹号等带来压迫感的元素。
6. 关键代码实现示例
让我们聚焦于几个关键的后端Node.js代码片段。
6.1 后端服务入口与路由 (app.js)
// app.js - 使用 Express.js const express = require('express'); const bodyParser = require('body-parser'); const userRouter = require('./routes/user'); const taskRouter = require('./routes/task'); const chatRouter = require('./routes/chat'); // 处理AI对话的核心路由 const app = express(); app.use(bodyParser.json()); // 注册路由 app.use('/api/user', userRouter); app.use('/api/task', taskRouter); app.use('/api/chat', chatRouter); // 重点:对话接口 const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); });6.2 核心对话处理接口 (routes/chat.js)
// routes/chat.js const express = require('express'); const router = express.Router(); const { handleUserMessage } = require('../agents/coordinator'); // 引入Agent协调器 const { verifyToken } = require('../middleware/auth'); // 简单的JWT验证中间件 // POST /api/chat/message router.post('/message', verifyToken, async (req, res) => { try { const { userId, content, taskId } = req.body; if (!content || content.trim() === '') { return res.status(400).json({ error: '消息内容不能为空' }); } // 1. 获取用户当前任务状态(从数据库) const userTask = await Task.findOne({ userId, _id: taskId }).populate('history'); const taskStatus = { name: userTask.name, target: userTask.target, completed: userTask.completedCount, history: userTask.history }; // 2. 调用AI Agent协调引擎处理消息 const aiResponse = await handleUserMessage(userId, content, taskStatus); // 3. 保存本次交互记录 const newRecord = new InteractionRecord({ userId, taskId, userMessage: content, aiResponse: aiResponse.text, // aiResponse可能包含文本和元数据 intent: aiResponse.detectedIntent, timestamp: new Date() }); await newRecord.save(); // 4. 根据AI建议,可能更新任务状态(例如,同意了调整目标) if (aiResponse.suggestedAdjustment) { await updateTaskTarget(taskId, aiResponse.suggestedAdjustment); } // 5. 返回AI回复给前端 res.json({ success: true, reply: aiResponse.text, meta: aiResponse.meta // 可包含建议调整、表情符号等扩展信息 }); } catch (error) { console.error('处理消息失败:', error); res.status(500).json({ success: false, reply: '哎呀,教练好像走神了,请稍后再试。', error: 'Internal server error' }); } }); module.exports = router;6.3 AI Agent协调引擎核心 (agents/coordinator.js)
// agents/coordinator.js const { callLLM } = require('../services/llmService'); // 封装好的大模型调用服务 const { analyzeIntent } = require('./intentAgent'); const { evaluateGoal } = require('./evaluationAgent'); async function handleUserMessage(userId, userInput, taskStatus) { // Step 1: 基础意图识别(可规则+小模型结合) const baseIntent = await analyzeIntent(userInput); // Step 2: 构建给大模型的“思考指令”(System Prompt) // 这里融合了多个Agent的职责 const systemPrompt = ` 你是一个习惯养成教练“小伴”。请按以下步骤思考并生成回复: 1. 情绪与归因:识别用户话语中的情绪和原因。用户说:“${userInput}” 2. 目标评估:用户当前任务“${taskStatus.name}”进度为 ${taskStatus.completed}/${taskStatus.target}。评估其合理性。 3. 决策与建议:基于1和2,决定是鼓励继续、建议调整目标,还是单纯共情。调整建议需具体。 4. 生成回复:用温暖、支持、不施加压力的口吻写一段话。认可努力,接纳感受,强调进步而非完美。 输出格式:{“reply”: “你的回复文本”, “adjustment”: “可选的具体调整建议,如无则null”, “intent”: “${baseIntent}”} `; // Step 3: 调用大模型 const llmResponse = await callLLM([ { role: 'system', content: systemPrompt }, { role: 'user', content: userInput } ]); // Step 4: 解析大模型的返回(通常是JSON字符串) let parsedResponse; try { parsedResponse = JSON.parse(llmResponse); } catch (e) { // 如果模型返回的不是标准JSON,降级处理 parsedResponse = { reply: llmResponse || '我理解你的感受,我们慢慢来。', adjustment: null, intent: baseIntent }; } return parsedResponse; } module.exports = { handleUserMessage };6.4 大模型服务封装 (services/llmService.js)
// services/llmService.js - 以通义千问为例 const axios = require('axios'); async function callLLM(messages, model = 'qwen-max') { const API_KEY = process.env.DASHSCOPE_API_KEY; // 从环境变量读取 const url = 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation'; const data = { model, input: { messages }, parameters: { temperature: 0.8, top_p: 0.8, result_format: 'message' // 或 'text' } }; try { const response = await axios.post(url, data, { headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' } }); // 根据通义千问返回格式解析 return response.data.output.text || response.data.output.choices[0].message.content; } catch (error) { console.error('调用大模型API失败:', error.response?.data || error.message); throw new Error('AI服务暂时不可用'); } } module.exports = { callLLM };7. 运行、测试与效果验证
7.1 本地运行
- 克隆后端代码,安装依赖:
npm install。 - 配置环境变量:在项目根目录创建
.env文件,填入你的数据库连接字符串和AI平台API Key。DATABASE_URL=mongodb://localhost:27017/habit_coach DASHSCOPE_API_KEY=your_aliyun_api_key_here PORT=3000 - 启动后端服务:
npm start或node app.js。控制台应显示Server running on port 3000。 - 使用API测试工具:用 Postman 或 curl 测试
/api/chat/message接口。curl -X POST http://localhost:3000/api/chat/message \ -H "Content-Type: application/json" \ -H "Authorization: Bearer <your_jwt_token>" \ -d '{ "userId": "test_user_123", "content": "今天工作好忙,完全忘了看书。", "taskId": "read_book_001" }' - 验证响应:应收到一个JSON响应,包含
success: true和一个符合“教练”口吻的reply字段。
7.2 小程序端联调
- 在微信开发者工具中导入小程序项目。
- 修改小程序配置文件
app.json中的host为你的本地服务器地址(需在微信后台配置合法域名或开启开发环境不校验域名)。 - 在小程序页面中调用
wx.request发送消息,查看界面是否能正常收发并展示AI回复。
7.3 效果验证要点
- 功能性:AI是否能正确理解“完成”、“跳过”、“求助”、“闲聊”等不同意图?
- 人性化:回复是否避免了机械感?是否在不同情境下(首次完成、连续失败、用户抱怨)有不同的、恰当的语气?
- 业务逻辑:当AI建议调整目标时,后端是否正确更新了数据库中的任务目标?
- 稳定性:在大模型API调用失败时,降级策略是否生效,是否返回了友好的默认回复?
8. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
小程序发送消息后无响应或报错request:fail | 1. 后端服务未启动或崩溃。 2. 小程序配置的服务器域名不正确或未在微信后台配置。 3. 本地开发未开启“不校验合法域名”。 | 1. 检查后端控制台日志,确认服务是否在运行。 2. 检查微信开发者工具控制台的Network面板,查看请求详情和错误码。 3. 核对小程序项目设置中的域名列表。 | 1. 重启后端服务,检查端口占用。 2. 在微信公众平台配置服务器域名(request合法域名)。 3. 开发阶段勾选“不校验合法域名、web-view域名、TLS版本”。 |
| 调用AI API返回超时或错误 | 1. 网络问题。 2. API Key 无效或过期。 3. 请求格式不符合API要求。 4. 模型服务额度用尽或限流。 | 1. 在后端服务日志中查看详细的错误信息。 2. 使用 curl或 Postman 直接测试AI厂商的API端点。3. 检查账单和调用额度。 | 1. 增加请求超时时间,加入重试机制(如指数退避)。 2. 重新生成并配置正确的API Key。 3. 严格按照厂商API文档调整请求体格式。 4. 购买额度或切换备用API Key。 |
| AI回复内容不符合预期(如太啰嗦、不相关) | 1. System Prompt 设计不佳,指令不清晰。 2. temperature参数设置过高,导致随机性太强。3. 对话历史(messages)组织有误。 | 1. 打印出实际发送给AI的完整messages进行审查。2. 尝试简化Prompt,分步骤明确指令。 3. 将 temperature调低(如0.3-0.7)。 | 1. 迭代优化System Prompt,使用“角色定义+任务步骤+输出格式”的清晰结构。 2. 对关键任务(如目标调整)可要求AI以指定JSON格式返回,便于程序化处理。 3. 引入输出内容的后处理过滤。 |
| 数据库连接失败 | 1. 数据库服务未启动。 2. 连接字符串错误。 3. 网络或防火墙限制。 | 1. 检查数据库进程(如sudo systemctl status mongod)。2. 使用数据库客户端工具测试连接。 3. 查看后端启动时的连接错误日志。 | 1. 启动数据库服务。 2. 修正 .env文件中的连接字符串。3. 配置正确的数据库访问权限。 |
| 用户会话状态混乱 | 1. 未正确处理用户身份(userId)。 2. 对话历史记录关联错误。 3. 后端服务无状态,但依赖了本地内存。 | 1. 检查每个API请求是否都正确携带并验证了用户token。 2. 检查数据库查询条件,确保 userId和taskId准确。3. 确保所有状态都持久化到数据库。 | 1. 强化JWT中间件,确保从token中解析出的userId被正确使用。 2. 所有用户相关的数据操作都必须显式使用 userId作为查询条件。3. 避免使用全局变量存储用户状态。 |
9. 最佳实践与工程建议
Prompt工程是核心:项目的“智能”程度很大程度上取决于你如何设计System Prompt。建议:
- 分角色撰写:明确告诉AI“你是一个…的教练”。
- 结构化思考链:使用“请按以下步骤思考:1… 2… 3…”的格式,引导AI进行逻辑推理。
- 控制输出格式:要求AI以JSON等格式输出,便于后端解析和后续处理。
- 持续迭代:收集bad cases(奇怪的回复),不断优化Prompt。
成本与性能优化:
- 缓存策略:对于常见、通用的问候语或固定回答(如“你好”),可以不调用大模型,直接返回缓存结果。
- 异步处理:非实时必要的操作(如保存详细交互日志、生成周报)可以放入消息队列异步执行,避免阻塞主响应。
- 模型选型:在保证效果的前提下,优先选择性价比更高的模型(如国内的中等规模模型),并在非核心对话中使用。
用户体验与安全:
- 设置频率限制:防止用户恶意刷接口消耗AI额度。
- 内容安全过滤:在将用户输入发送给AI前,以及将AI回复返回给用户前,都应进行敏感词、不良信息的基础过滤。可以利用各大云平台提供的内容安全API。
- 明确能力边界:在UI上提示用户,AI是习惯教练,不能替代专业医疗、心理或法律建议。
数据驱动迭代:
- 记录完整交互:保存用户输入、AI回复、识别出的意图、建议的调整等。这是优化模型和Prompt的黄金数据。
- 定义关键指标:除了传统的“日活/月活”,更应关注“平均单用户对话轮次”、“用户主动发起对话的比例”、“目标调整建议的采纳率”等能反映“陪伴”价值的指标。
- A/B测试:对不同的Prompt版本或交互策略进行小流量A/B测试,用数据决定哪种方式更能提升用户长期留存。
构建一个“不想催你”的监督小程序,技术实现只是骨架,真正的血肉在于对用户心理的洞察和对AI交互的精心设计。它不再是一个冷冰冰的工具,而是一个有同理心的数字伙伴。这种从“工具”到“伙伴”的转变,或许是所有希望提升用户粘性的产品值得探索的方向。你可以从本文提供的架构和代码示例起步,注入你自己的产品思考,打造出独一无二的智能习惯教练。