冥想和正念练习这几年热度一直不低,但市面上大多数产品的体验其实相当固定:打开App,选一段十分钟的音频,跟着一个录好的声音做呼吸练习。问题在于,人的状态每天都在变化,周一早上通勤前的焦虑和周日睡前的疲惫,需要的引导方式完全不同,而预录音频根本无法区分这些差异。这篇文章就来拆解一个真实的案例——如何用大语言模型构建一个能够动态响应的冥想与正念引导Agent,让它在理解用户当前情绪的基础上,实时生成贴合状态的引导内容。

一、先想清楚:这类Agent的能力边界在哪里
动手写代码之前,必须先界定Agent能做什么、不该做什么。冥想引导Agent的核心能力有三块:第一是状态感知,通过对话了解用户当下的情绪、身体感受和时间预算;第二是内容生成,根据状态动态编排引导词,而不是照本宣科地念稿子;第三是节奏控制,决定呼吸引导的频率、停顿时长和练习总时长,这直接决定用户体验是否舒适。
同样重要的是边界。这类Agent不能替代心理咨询师或医生。如果用户在对话中流露出明显的抑郁或自伤倾向,系统必须识别出来并引导用户寻求专业帮助,而不是继续输出冥想引导词。这不仅是产品责任问题,在代码层面也需要一个明确的风险拦截层。很多团队在原型阶段忽略这一点,上线后才发现是致命缺陷。
从架构上看,整个系统可以分为四层:用户交互层(语音输入加文字输入)、会话状态层(记录用户当前情绪标签和练习进度)、决策层(LLM负责理解意图并生成引导词)、执行层(TTS语音合成加定时播放控制)。分层的好处是每一层可以独立迭代,比如换一个语音引擎,不需要动决策层的逻辑。
二、会话状态管理:让Agent记住用户的练习节奏
冥想引导是一个典型的多轮长会话场景,一次练习可能持续五到三十分钟,期间Agent需要记住的信息包括:用户选择的练习类型(呼吸观察、身体扫描、慈心冥想等)、当前进行到哪个阶段、用户的实时反馈(比如表示走神了、感到烦躁)。如果只靠把全部历史塞给LLM,上下文很快就会膨胀,成本高且容易丢失重点。
比较务实的做法是维护一个结构化的会话状态对象,每次调用LLM时只传入状态摘要加最近两三轮对话。下面是一个用Python实现的简单状态机:
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class MeditationState:
practice_type: str = "breath" # breath / body_scan / loving_kindness
phase: str = "intake" # intake / settling / core / closing
duration_plan: int = 10 # 计划练习时长,单位分钟
elapsed: float = 0.0 # 已进行时长
emotion_tags: list = field(default_factory=list)
restlessness_count: int = 0 # 用户走神或烦躁的次数
def advance_phase(self):
"""推进练习阶段"""
order = ["intake", "settling", "core", "closing"]
idx = order.index(self.phase)
if idx < len(order) - 1:
self.phase = order[idx + 1]
def should_shorten(self) -> bool:
"""用户两次以上表现出烦躁,建议缩短练习"""
return self.restlessness_count >= 2这个设计的关键点在于restlessness_count字段。当用户在练习中多次表示坐不住、很烦,Agent不应该机械地继续原计划,而是主动询问是否要换一种更短的练习,或者调整引导语气。这种细节是预录音频永远做不到的,也正是Agent形态的核心价值。
另外要注意状态的持久化。如果用户中途接了个电话回来,Agent应该能从上次中断的地方继续,而不是要求重新开始。实践中可以用Redis存储会话状态,设置两小时的过期时间,兼顾体验和存储成本。
三、引导词生成:提示词工程比模型选择更重要
很多人以为这类应用的关键是选一个最强的大模型,实际体验下来,提示词设计的权重远高于模型本身。冥想引导词有自己的文体特征:语速要慢,句子要短,多用第二人称和现在时态,段落之间需要留白。如果直接让LLM自由发挥,它很容易生成信息密度过高的文字,念出来像科普文章而不是引导。
一个经过验证的提示词框架包含四个部分:角色设定(温柔、不评判的正念教练)、文体约束(短句、每句不超过十五个字、留白标记)、上下文注入(当前阶段、用户情绪标签)、输出格式要求。看下面这个例子:
SYSTEM_PROMPT = """你是一位正念引导教练。你的任务是根据当前练习阶段
生成冥想引导词。
严格的文体规则:
1. 每句话不超过15个字。
2. 句子之间用换行分隔,每次输出不超过4句。
3. 用【停顿3秒】这样的标记表示留白。
4. 语气温柔、缓慢,不评判,不催促。
5. 绝对不要出现专业术语或解释性内容。
当前练习类型:{practice_type}
当前阶段:{phase}
用户情绪状态:{emotions}
练习剩余时间:{remaining}分钟"""
def build_prompt(state: MeditationState) -> str:
emotions = "、".join(state.emotion_tags) if state.emotion_tags else "平静"
return SYSTEM_PROMPT.format(
practice_type=state.practice_type,
phase=state.phase,
emotions=emotions,
remaining=max(0, state.duration_plan - int(state.elapsed))
)这里有个容易被忽略的坑:【停顿3秒】这类标记必须在TTS层被正确处理,否则语音引擎会把这五个字直接念出来,效果非常出戏。解决方案是在送入TTS之前做一次正则解析,把停顿标记转换为静音片段拼接进音频流。
还有一个实践建议是控制生成节奏。冥想引导不适合流式不断输出,更合理的做法是每生成一小段就播放,播放结束后再请求下一段,配合定时器让整体节奏自然。用户走神报告可以在两次请求之间插入,Agent据此微调后续内容的语气。
四、语音合成与整体串联
语音是冥想体验的载体,TTS引擎的选择标准首先是音色是否舒缓自然,其次是是否支持语速调节。主流方案里,Edge TTS免费且音色质量不错,适合原型阶段;商业方案则在长文本稳定性和情感表达上更有优势。无论选哪个,都建议把语速调到正常值的百分之八十左右,这是多数用户反馈最舒适的区间。
把前面几个模块串起来,一个最小的可运行骨架如下:
import asyncio
import edge_tts
async def speak(text: str, voice: str = "zh-CN-XiaoxiaoNeural"):
# 处理停顿标记,拆分为文本与静音片段
segments = []
for part in text.split("【"):
if "】" in part:
pause, content = part.split("】", 1)
segments.append(("pause", int(pause.replace("停顿", "")
.replace("秒", ""))))
segments.append(("text", content))
elif part.strip():
segments.append(("text", part))
for kind, val in segments:
if kind == "pause":
await asyncio.sleep(val)
else:
tts = edge_tts.Communicate(val, voice, rate="-20%")
await tts.play()
async def run_session(agent_reply_fn, state: MeditationState):
while state.phase != "done":
guide_text = await agent_reply_fn(state)
await speak(guide_text)
state.advance_phase()
if state.should_shorten():
# 提前进入收尾阶段
state.phase = "closing"这段代码做了两件事:一是把停顿标记转换成真实的静默等待,二是用状态机驱动整个练习流程。生产环境中还需要补充用户语音输入的识别(ASR)、打断处理(用户说话时立即暂停播放)以及异常兜底逻辑,比如TTS服务不可用时的文字降级方案。
最后说几个上线前值得注意的体验问题。第一,引导词的重复度要控制,同一用户连续多天练习时,LLM需要被注入练习历史,避免每次开头都是一模一样的“请找一个舒适的姿势”。第二,夜间场景要考虑播放设备的兜底,戴耳机入睡的用户可能在练习中途睡着,Agent应当在会话超时后静默退出而不是循环等待。第三,风险拦截层务必覆盖情绪危机的识别,宁可有少量误判,也不能漏掉真正需要帮助的用户。把这些细节处理好,这个Agent才算真正可用。