☰
多语言AI Agent幽默理解与生成系统实战解析
2026/10/17 16:13:23 网站建设 项目流程

带过一个面向多语言用户的AI Agent项目,最让我头疼的不是多轮对话,也不是工具调用,而是幽默。用户抛来一句“你这回答比我家Wi-Fi还快,就是信号不太好”,Agent接不住这个梗,冷冰冰回了一句“感谢您的反馈,我们会继续优化网络速度”,隔着屏幕都是尴尬。为了根治这个问题,我们启动了一套多语言幽默理解与生成系统的研发,目标很直接:让Agent能识别“用户在开玩笑”,能判断当前适不适合接梗,也知道怎么回一个至少不冷场的笑话。

这篇文章我把从立项到上线的完整过程梳理一遍,包括语料怎么建、理解模型怎么搭、生成模块怎么迭代、Agent怎么集成,以及评测阶段踩过的坑。适合正在做对话AI、Agent产品,或者对幽默计算、情感计算感兴趣的工程师参考。我会尽量写实操层面的东西,不堆理论。

1. 为什么幽默是自然语言理解里的“地狱难度”:先拆问题再动工

1.1 幽默到底难在哪:语言层、语用层、文化层的三层拆解

幽默让人工智障翻车,大多数时候可以归结为一个底层机制:预期违背。听众先建立某个心理预期,然后在关键位置被突然推翻,这个落差带来笑意。AI要做的是双份工作——既要知道“正常的语义走向”是什么,又要知道“哪里出现了反常”。

拆开来看,幽默至少落在四个层面。第一是语言层,比如谐音、双关、字形变化。中文里“压力山大”被谐音成“鸭梨山大”就是典型的音形碰撞;英文里“Time flies like an arrow; fruit flies like a banana”同时用到fly和like的多义性,这种幽默直接长在语言结构上,脱离语言就不存在。第二是语义层,常见手法是类别切换和歧义利用,一个词在句中被强行换到另一个语义域。第三是语用层,包括讽刺、反语、自嘲、调侃,这一层最难,因为字面和真实意图经常完全相反。第四是文化层,某个段子好笑是因为听众共享一段经历、一个网络事件、一种生活场景,换一群人就成了天书。

这也是为什么“你为什么不笑”对模型来说是个非常难以回答的问题。它不理解“这是什么梗”,更不理解“为什么这里是梗”。所以我们一开始就定了个原则:幽默理解不等于幽默检测。光判断“文本是不是笑话”远远不够,Agent还需要定位笑点,识别幽默类型,最后还要判断“在这个对话场景里,该不该接这个梗”。这是一个面向行动的完整链路,不是一个分类器能对付的。

1.2 多语言让难度翻倍,但解题框架可以收敛

刚启动的时候,团队差点掉进“做一个通用幽默引擎”的大坑里。通用幽默生成目前还属于研究题,不解决工程问题。我们很快把范围收敛成了一个更可执行的框架:三个能力加一个库。

三个能力分别是:检测当前文本是否含幽默;识别幽默类型并定位笑点;根据对话语境决定是否接梗、用什么风格接。一个库是:多语言文化常识与流行梗知识库。

为什么不用“让模型自己学会一切”?原因有二。第一,幽默语料在整个NLP生态里本来就稀缺,多语言更是少得可怜,纯靠端到端模型从碎数据里学出稳定的幽默感,不现实。第二,产品要的是“得体”而不是“爆笑”。一个Agent每天面对大量严肃请求,幽默只是辅助调节气氛。优先保证不翻车,比偶尔惊艳重要得多。这个定位决定了后面所有技术选型。

2. 语料与标注:这项工程80%的精力都耗在这里

2.1 多语言幽默数据从哪来,什么样的数据能用

数据是整个项目最先卡脖子的地方。通用NLP任务有大把开源语料,但“幽默”是个窄题材,公开资源零散且质量参差。

我们最终采取了“多源混合”的路子。中文主要来自网络笑话合集的公开页面、相声片段转写、社交平台上带幽默标签的公开文本,以及谐音梗评论区按热度做的筛选;英文来自公开的joke文本集、单口喜剧转写素材、社交媒体的pun标签内容;日文来自漫才转写、网上的駄洒落合集、讨论串里典型“装傻+吐槽”结构的对话。韩文、泰语等更大依赖字幕组和综艺节目的公开转写资源。

这些数据不能直接进训练集,必须过两关。第一关是可搬运性,优先用开放许可或我们自行转写的资源,避免版权隐患。第二关是安全合规,这一步要严格做,去掉攻击性玩笑、地域歧视、人身攻击类内容。我们建了一个负向过滤清单,规则先粗筛一遍,再用模型做一遍语义过滤。注意,这个清洗不能省,幽默生成领域最容易踩的就是价值观红线,宁可让语料库小一点,也不能留隐患。

最后的量级是:中文11万条,英文8万条,日文5万条,泰语、阿拉伯语这类低资源语言各1万条左右。后续还做了数据增强:基于已有真笑话,让模型做“同构改写”——保留情节结构和笑点机制,替换人物、场景、物品,扩展出大量变体。中英文在这种增强下收益很明显,低资源语言因为基础量太小,增强后依然不够用。

2.2 标注规范怎么做,笑点位置为什么值得单独标

有了原始文本,下一步是标注。我们设计了多维标注格式,每条数据包含五个字段:

  • is_humor:二分类,是否幽默
  • humor_type:多标签,文字游戏/双关、情境幽默、讽刺反语、荒诞、自嘲、文化梗
  • punchline_start / punchline_end:笑点起止位置
  • culture_dependence:0到2,代表完全无需背景、需要轻度文化背景、高度依赖特定文化和语言结构
  • offensiveness:0到5,超过2分的样本不进入训练集

这里最想强调的是笑点定位字段。一开始我也觉得标个“是否幽默”就够了,但做到后面发现,笑点位置的价值远超预期。它不仅给理解模型提供了可解释性,还直接服务生成模块——生成时可以复用“先铺垫后反转”的结构,而不是凭空瞎编。

标注一致性的情况可以给大家做个参考。3名标注员独立标注,二分类的Kappa约0.78,质量不错;幽默类型多标签的Kappa约0.62,勉强可用;但笑点位置刚开始只有0.5左右,非常主观。后来我们改了方案,让标注员先选句子,再选词,并且允许标“没有清晰笑点”这个选项,Kappa才升到0.6附近。这算是一条非常实用的经验:笑点边界标注要允许“无解”,别硬逼标注员给每个句子找笑点。

2.3 跨语言对齐:直译是陷阱,复述才是正路

多语言幽默系统最折腾人的,是怎么把不同语言的幽默语料“对齐”。开始时我们找“平行幽默语料”——同一笑话的多语言版本,找了一圈,能用的只有几十条,而且绝大多数是传统英语笑话的机械翻译,直译后笑点完全消失。

后来换了个思路,用跨语言复述来构建对齐。具体做法是:让精通双语的人看一条中文幽默,不要求直译,而是“用英文重新讲一个同类型、能让英文用户笑出来的段子”。这样建出来的不是句子级平行语料,而是“幽默类型对齐”语料。它保留了笑点机制在不同语言里的等价映射,这对训练跨语言理解模型很有帮助。

为什么机器翻译在这里行不通?因为双关的笑点长在原文的音形层上。中文谐音梗“我今天有点‘鸭’力山大”,直译成英文只能得到“I feel duck pressure today”,英文母语者完全get不到。翻译能转述语义,但转述不了发音碰撞和拼写游戏。想靠通用翻译搞定幽默对齐,等于让翻译器去翻译一个它根本看不见的维度。

在表示层面,我们还用多语言编码模型做了软对齐:把相同幽默类型、相似笑点机制的样本在向量空间里拉近,作为训练数据的一部分。这块不用刻意追求精确映射,能让模型对不同语言的“同类幽默”产生共享表达就够了。

3. 幽默理解模块:不仅要判断“好不好笑”,还要定位“哪里好笑”

3.1 多任务共享编码器:检测、分类、定位三个头各司其职

理解模块的整体架构是基于多语言预训练编码器,在上面挂三个任务头:幽默检测头做二分类,类型识别头做多标签分类,笑点定位头对每个token做二分类。三个任务共享同一个编码器,这是刻意设计的,因为检测需要知道“哪里有笑点”,定位需要知道“整体是不是幽默”,类型判断又依赖两者的中间信息。共享编码器让三个任务互相提供归纳偏置,比各自训练三个模型稳定得多。

训练时我们用多任务加权损失,检测、类型、定位的权重大概是1.0比0.7比0.8。梯度直接累加在共享层上。实际训练下来,检测和类型任务收敛正常,笑点定位任务稍慢,但加上token级监督后,整个编码器对幽默关键位置的注意力明显更集中。

这里的另一个细节是上下文窗口。幽默极依赖对话前文,所以我们处理对话时会把Agent自己的上一轮回复、用户的上一轮输入和当前文本拼在一起。缺少上下文时,讽刺的误判率会明显上升。尤其是“反语”,单看一句话几乎无法判断,但结合前文往往一目了然。

部署时,我们把这个多任务模型蒸馏成小模型,分语言部署在边缘环境,单次推理控制在100毫秒以下。这是为了给后面的生成模块留出时间预算。

3.2 “预期违背”信号:把笑点当作信息熵增来做辅助监督

只靠分类模型能判断“是不是笑话”,但解释不了“为什么这里好笑”。为了让模型学到“笑点位置”这个关键结构,我们加了一个辅助信号:surprise score,也就是每个token在上下文里的“意外程度”。

具体做法是:用一个掩码语言模型,把序列中第i个token遮住,基于左右上下文预测它的概率,取对数概率作为该位置的惊讶值。实际出现的词如果概率很低,说明模型对这里有预期违背。再对整条序列做z-score归一化,超过1.5个标准差的token就标记为“候选笑点”。

举个例子,英文双关句“I used to be a baker, but I couldn't make enough dough.”——dough这个词同时承担“面团”和“钱”两层意思,模型看到前半句时大概率预期的是“面团”的延续,但后半句实际指向“钱”,这个位置就会冒出高surprise值。我们把这个信号作为额外的token级特征输入到笑点定位头,F1提升很明显。

注意,surprise只对语言层和某些语义层的幽默效果好,对“情境型幽默”几乎不起作用。情境幽默靠的是外部常识反转,比如“老板说周六要加班,我说不行,因为那天我的床需要我陪”,单看语言本身没有什么意外。所以surprise只能当辅助特征,不能替代完整的语义建模。

3.3 讽刺与双关:两类最难啃的特殊幽默怎么单独处理

讽刺在多语言场景下是重灾区。字面情感和真实情感极性相反,模型很容易被表面文本带跑。英文里我们识别积极/消极形容词,计算其情感极性与语境预期极性之间的差异,再把标点、表情符号这类副语言信号加进去。中文网络语境更特殊,像“呵呵”“真是棒棒哒”这类反语标记,得单独维护一张语用标记词表,并且要在对话级模型里判断,不能只看词典硬匹配。

双关处理则要引入音形信息。英文可以用音标词典类资源,枚举同音词和同形异义词作为候选;中文用拼音库,把同音字候选枚举出来,计算候选词与上下文是否形成语义冲突。这些候选信息编码成token级特征,一并送进encoder。实测对中文谐音梗的检测帮助最大,F1能到0.76。

这个模块的最终效果是:英文幽默检测F1约0.83,中文约0.79,日文约0.72,泰语约0.6。讽刺任务在中文上的精确率偏低,因为反语太依赖对话语气,这属于先天数据问题,只能靠语境窗口和语用标记慢慢补。

4. 幽默生成模块:三代方案复盘,从检索到受控生成

4.1 初代方案的教训:检索式为什么快但尴尬

最早做生成时图省事,直接上了检索式方案:向量召回最相似的段子,替换专名和实体,当作Agent的回复。上线一测,稳定性的确好,安全也可控,但两大问题立刻暴露。

第一是重复率高。用户第一次听到“你这人真有意思,像我家路由器,总爱自动重启”还觉得新鲜,第二次听到一模一样的逻辑就开始皱眉。第二是语境脱节。检索来的段子套不进用户刚说的具体内容,经常出现“用户聊堵车,Agent回一个关于猫的冷笑话”这种场面。检索式只能当冷启动方案,或者低资源语言的兜底,不能作为主力生成器。

4.2 从端到端到受控生成:为什么必须“先找笑点结构,再包装语言”

第二版我们试过直接用预训练语言模型端到端生成,条件前缀里塞入幽默类型、主题、语言等控制信息。效果比检索式灵活,但翻车记录也很刺激:生成到一半结构断裂、把前文笑话复制后跑飞、偶尔冒出不合适内容,而且在中文上生成英式幽默翻译腔的情况特别多。

折腾几轮后,我们把方案收敛为两阶段受控生成。

第一阶段是结构规划器。它根据用户输入、对话场景、幽默目标和闲聊状态,先输出一个小型JSON方案,包含幽默类型(比如文字游戏、荒诞、自嘲)、主题词、转折词、是否需要双关候选,以及目标语言和字数。这个阶段不直接生成句子,只决定“用什么笑点结构”。

第二阶段是文本填充器。它把规划器输出的JSON扩展成完整文本。这里有趣的约束来了——如果是英文pun,生成器会被约束到音标相似性层,必须使用同音词集合里的词;如果是中文谐音梗,生成器要优先从拼音库抽候选词,强制包含到句子里。

为什么把“结构”和“文本”拆开?因为幽默的核心在结构而不是辞藻。一句“前半句建立预期,后半句反转”的模板,在任何语言里都说得通。先定结构,再选语言外壳,比让模型直接从零吐出一段笑话稳定得多,也更容易控制类型不走偏。

4.3 生成后的自省回路:用理解模块拦住烂梗

这一步是整套系统里我个人觉得最有价值的设计。生成器输出一段话后,不直接回给用户,而是先走一遍理解模块,过两个关卡。

第一关是幽默自检:把生成文本丢进幽默检测和类型分类,如果置信度低于阈值,就重新生成。最多重试两次,仍然不合格就放弃讲笑话,退回普通回复。这套机制本质上是让Agent当自己的审稿人。

第二关是安全过滤。开放生成模型经常在不该冒犯的地方冒犯,所以我们上了“三色名单”机制:完全禁止词直接拦;高风险类别(对群体的攻击性内容,以及在情感敏感场景里的不当玩笑)触发后降级为无害回复;轻微擦边的样本进入人工抽查。

实际效果举例:用户说“我的猫把我的键盘当床了”,系统判定适合轻松接梗,规划器选择了“荒谬反转+拟声双关”结构,生成的中文回复是“它可能是在你打字的时候,伸爪帮你‘加班’”;英文版本则生成“Your cat is just adding ‘paw-sitive’ productivity to your workflow.”。这类段子不算爆笑,但在对话里不冷场、不冒犯,对Agent来说就达标了。

还要强调一点:接梗频率必须控制。同一个对话里,连续两轮都抛梗,用户会觉得AI在抖机灵。我们在Agent记忆里写了“本对话已接梗次数”,超过一次就强制走严肃回复分支。

5. Agent集成与产品化:幽默不幽默,要看场景脸色

5.1 理解-路由-生成-过滤:Agent内部的完整决策链路

当幽默模块被塞进真实Agent后,不能简单地“检测到笑话就回复笑话”。我们最终设计了一条四段式链路:

用户输入先进理解模块,做幽默检测、类型识别、笑点定位;然后进入意图路由层,判断当前对话到底适合哪种行为。路由输出四档:跟随用户玩笑继续调侃、适度接梗、不接梗但委婉回应、直接严肃回答。只有前两档才会调用生成模块,生成完再过安全过滤,最终输出给用户。

为什么要单独设计“是否该接梗”这一层?因为用户可能在宣泄情绪时说反话。比如“我辛辛苦苦干了一年,老板今天告诉我项目被砍了,我可真开心啊”——这里绝不能当幽默处理。我们在理解模块里加了一个“负面情绪+讽刺”联合判别子任务,一旦判定用户处于悲伤或愤怒状态,幽默生成模块整体关闭。安全设计必须前置,不能指望生成模型临场判断。

工程实现上,理解模块和生成模块是完全解耦的。理解模块蒸馏成小模型,分语言部署,保证低延迟;生成模块集中调用大模型能力,做异步处理。为了让等待不那么煎熬,回复采用流式输出,用户能看到打字过程,也在客观上给了生成模块更多的计算时间。

5.2 低资源语言的保底方案:翻译桥接与检索兜底

对于泰语、阿拉伯语这类数据量捉襟见肘的语言,我们用了和主流语言不同的策略。

第一步是翻译桥接:用户输入先翻译成英文,在英文侧做幽默检测和类型识别。第二步就讲究了,检测到幽默后,不能直接把英文笑话翻译回目标语言,那样笑点早就碎光了。我们采用“检索+文化改写”的保险方案:英文侧检索出最接近的段子,翻译回目标语言,再做本地化改写——把文化实体替换成本地人熟悉的常见对象。

比如面向泰语用户,笑话里的“隔壁客户”会改写成“堵车高峰期卖果汁的小贩”这类本地生活场景。做这个需要为目标语言维护一个小型本地化实体清单,工作量不大,但效果立竿见影。最终这类语言的接梗成功率不如主流语言,但翻车率显著降低。

这里的产品取舍很明确:宁可让低资源语言的Agent偶尔显得“没听懂你的笑话”,也不要回一个翻译腔满天飞的句子,那种尴尬比不接梗更伤人。

5.3 监控指标怎么定:接梗成功率比“好不好笑”更贴近业务

上线后监控什么,一开始团队吵过一轮。有人说要盯生成文本的趣味分,有人说要盯自定义冒犯指数。最后我们拍板,业务核心指标是接梗成功率:用户说了一句带幽默的话,系统是否判定为幽默,并给出一个相关且恰当的回应,通过线上抽样人工评估。中文和英文最终能做到约75%,低资源语言大约50%。这个数字直接反映了理解模块的召回和生成模块的贴合度。

另一个重要指标是用户继续对话率:Agent接梗后,用户愿意接着聊的比例。对比普通严肃回复,接梗后继续对话率高出6个百分点,说明幽默确实在和用户拉近距离。

负面反馈率也要重点盯。幽默最容易引发用户不适,初期负面反馈一度到8%,后面通过更保守的路由策略和安全过滤,收敛到了3%左右。我们每天还会人工复盘所有被安全过滤器拦截的样本,避免过滤过度误伤正常对话。

我还总结了一个经验公式:Agent适合接梗的概率 = 检测置信度 × 场景适配度 × 风格匹配度。三者相乘低于某个阈值就放弃接梗。这个公式在实际调参中非常好用,比单纯调模型阈值直观得多。

6. 评测体系与实际效果:BLEU在这个项目里几乎失效

6.1 为什么常规文本指标在幽默任务上不可信

做这套系统之前,我们走了不少弯路,其中最大的弯路就是试图用常规文本生成指标评估幽默质量。BLEU本质上是n-gram重叠度,它在“参考答案只有一种表达方式”的任务上合理,但幽默的天性就是求新求异。

同样的笑点,参考答案是“你可真是漏网之鱼”,模型说“你从网里跑了,属鱼的吧”,意思相同,n-gram重叠极低,BLEU会给出一个很低的分数。但在真实对话里,这两个回答的幽默效果可能不相上下。按BLEU调参,只会逼着模型去背参考答案,反而杀死创意。

困惑度这类指标同样不靠谱。语言模型眼里最“安全”的句子往往是无聊的套话,困惑度降到最低的那一刻,也是幽默感归零的那一刻。所以我们定了一个原则:自动指标只用于回归测试,防止模型升级后某类幽默能力断崖式下滑,最终决策只看人工评测和线上真实反馈。

6.2 多维度评测设计,以及上线后的真实数字

理解端的评测相对简单:幽默检测用F1、精确率、召回率、AUC;笑点定位用IOU,需要预测区间和标注区间有重叠才算命中。上面提到的中文0.79、英文0.83、日文0.72、泰语0.6,就是这个体系的数字。

生成端就麻烦多了。我们建了一套五维人工评测:趣味度(1到5)、自然度(1到5)、相关性(1到5)、文化适配度(1到3)、安全合规(是否通过)。每个样本至少两个人打分,出现较大分歧就第三人仲裁。最终第三代系统的成绩是趣味度平均3.6,自然度3.8,相关性3.5,文化适配度中英文约2.4到2.5,日文2.2,泰语1.9。

和前两代对比更直观:初代检索式的趣味度只有2.1,第二代端到端生成稍微好一点到了2.9,但安全违规率让人头疼,到第三代受控生成加自省回路才拉到3.6。

我们还在线上做了配对偏好测试:同一输入,一边是系统的幽默回复,另一边是“安全但无趣”的普通回复,用户盲选更喜欢哪个。第三代系统在英文和中文上胜率到了61%,低资源语言只有46%。这个46%说明在低资源语言上,幽默回复和普通回复已经拉不开差距了,只能靠继续灌数据和优化本地化改写来补。

评测过程还有一个老大难问题:众包标注员之间的主观差异。不同人对“好笑”的感知差距极大,刚开始Kappa只有0.55。后来我们要求标注员先做20条一致性测试,不过关的不能参与正式标注,整体一致性才升到0.6附近。幽默评测想完全客观是不可能的,能做的就是尽量让标准对齐到群体偏好的中位数上。

6.3 三个典型失败案例:从翻车样本里捞出的经验

案例一发生在日文漫才结构上。用户的输入是典型的“装傻加吐槽”双段结构,系统没识别出第二段“从前面的装傻里猛踩刹车”这个反转,直接把整段话当成了普通叙述,没有触发幽默分支。根因是漫才语料太少,标注时又把前文铺垫和吐槽点混在了一条样本里,模型根本学不到跨句转折。修复方案是给标注规范增加“结构跨度”字段,专门标出铺垫来自前文的哪一句,训练时给这类样本更高权重。

案例二是中文谐音梗翻译出口翻车。用户说“我今天有点‘鸭’力山大”,系统规划器选的是中文双关方案,但在英文对话里直接生成了按字面翻译的“duck pressure”式表达,英文用户完全笑不出来。根因是中英双关根本不存在可映射关系。修复办法是放弃直译,为每种语言单独维护自己的同音词与双关素材库,英文就改用英文常见的overload、dough这类同音双关素材重新构造段子。

案例三是反语误伤。一个用户抱怨天气,说“这天气可太棒了,出门五分钟回来就能洗个热水澡”,系统当成讽刺幽默回了个冷笑话,用户负面反馈。修复方式是把“可太棒了”这类高频反讽短语加入贬义语境检测词表,并让路由层变得更保守——一旦负面情绪分偏高,幽默分支直接关闭。这个案例后来成了我们安全策略的标杆:宁可无趣,不能冒犯。

做完这个项目,我最大的体会是:幽默对Agent来说不是一个可以单独优化的“任务”,而是一套系统性的行为约束。理解要先行,安全要兜底,生成只是最后那一下“临门一脚”。技术上最值得投入的其实是语料和评测体系,而不是反复换更大的模型。模型再大,也补不上标注规范里缺的那一维“笑点位置”。最后分享一个小技巧:Agent要记录“本轮是否已经接过梗”。真实用户对连续抖机灵的容忍度比我们想象中低得多,一个对话里接一次梗是幽默,接三次就变成骚扰了。这个分寸感,恰恰是幽默系统里最难用模型参数表达、却又最影响体验的东西。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询