三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

个性化人机交互:用户自定义称谓的识别、记忆与应用实践

个性化人机交互:用户自定义称谓的识别、记忆与应用实践

1. 先搞清楚“叫我那两个字”到底在解决什么问题

看到“叫我那两个字”这个标题,很多人第一反应可能是某个社交互动、游戏彩蛋或者网络梗。但如果你是从技术实现、产品逻辑或者用户行为分析的角度来看,它背后指向的其实是一个很具体的问题:如何让一个系统(比如AI助手、游戏NPC、智能设备)在交互中,识别并响应用户指定的、特定的、通常是昵称或爱称的“两个字”

这听起来简单,但落地时坑不少。它不是一个标准的语音唤醒词设置,也不是简单的关键词匹配。用户期望的是在自然对话的上下文中,系统能“听懂”并“记住”这个专属称呼,并在后续交互中主动使用。比如,用户说“以后你就叫我‘阿强’”,那么系统之后问候或回应时,就应该用“阿强”来称呼用户。

所以,这个主题的核心价值在于:它探讨的是个性化人机交互中的一个关键能力——用户自定义称谓的识别、记忆与上下文应用。无论是做聊天机器人、智能语音助手、沉浸式游戏,还是任何需要拟人化交互的产品,这个功能都能显著提升用户的归属感和互动真实感。

适合看这篇文章的人,主要是产品经理、交互设计师、前后端开发工程师,以及所有需要为系统添加“个性化称呼”功能的开发者。最关键的挑战不是技术多高深,而在于如何把“用户随口一说”的意图,稳定、准确地转化成系统可执行、可记忆、可复用的逻辑。

2. 别急着写代码:先拆解交互场景与数据流

在动手实现之前,最容易栽跟头的地方就是需求理解不清。用户一句“叫我那两个字”,背后可能有多种触发方式和预期。

2.1 明确核心交互场景

我们得先把“叫我那两个字”这个动作发生的场景框定清楚。通常有以下几种:

  1. 主动设置型:用户通过明确的指令进行设置,例如:“嘿Siri,以后叫我‘老板’。” 或是在聊天框输入:“/nickname 阿强”。这种场景最清晰,意图明确。
  2. 对话引导型:在自然聊天中,系统询问:“我该怎么称呼您呢?”,用户回答:“叫我‘小明’就行。” 这里需要从上下文中提取出“小明”这个实体。
  3. 模糊提及型:用户可能在闲聊中说:“我朋友都叫我‘老铁’。” 系统需要判断这是否是一个希望被如此称呼的指令,还是仅仅在陈述一个事实。这是最难处理的场景。
  4. 多模态输入型:用户可能通过语音、文字,甚至在未来结合手势或表情来表达这个意图。输入方式不同,处理链路也不同。

对于大多数项目,我建议先从“主动设置型”和“对话引导型”这两个确定性高的场景入手。把这两个跑通、跑稳,再考虑更复杂的模糊意图识别。不要一上来就想处理所有情况,那样很容易让逻辑变得复杂且不可控。

2.2 设计关键数据流与状态

无论场景如何,核心数据流是相似的。我们需要设计几个关键模块:

  • 意图识别模块:判断用户当前输入是否包含“设置称呼”的意图。关键词触发(如“叫我”、“称呼我”、“以后叫我”)是简单有效的方法,也可以使用更复杂的NLU(自然语言理解)模型。
  • 实体抽取模块:从用户语句中准确提取出那“两个字”(也可能是三个字或特定词组)。这里要注意中文的复杂性,比如“叫我‘开心果’”和“叫我开心果”(没有引号),提取算法需要能处理。
  • 称谓验证与标准化模块:提取出来的称呼是否合法、适宜?是否需要过滤敏感词、不雅词汇?是否需要做长度限制(比如最多10个字符)?这个模块保障了系统的安全性和体验下限。
  • 用户状态存储模块:将验证通过的称谓与当前用户ID绑定,持久化存储到数据库或用户配置文件中。这是实现“记忆”功能的基础。
  • 上下文应用模块:在后续的对话生成或语音合成中,如何将存储的称谓自然地插入到回应中。例如,系统回应:“好的,阿强。今天有什么可以帮您?” 而不是生硬地替换。

下面是一个简化的核心数据流程图,可以帮助你在开发前理清思路:

graph TD A[用户输入: “以后叫我阿强”] --> B(意图识别模块); B -- 识别为“设置称呼”意图 --> C(实体抽取模块); C -- 抽取出“阿强” --> D(称谓验证模块); D -- 验证通过 --> E(用户状态存储模块); E -- 持久化“用户ID-阿强” --> F(上下文应用模块); F -- 生成回应“好的,阿强...” --> G[系统输出]; D -- 验证不通过 --> H[返回错误提示]; B -- 识别为其他意图 --> I[进入其他对话流程];

把这个流程图画清楚,后续的编码、调试、排查问题都会有条理得多。

3. 从单次交互到持久化记忆:分步实现

理解了场景和数据流,我们就可以开始动手实现了。我建议分成三步走:先搞定单次对话中的提取与回应,再实现跨会话的记忆存储,最后处理应用时的自然度。

3.1 第一步:实现单轮对话的提取与回应

这个阶段的目标是,在一次对话中,能正确理解用户意图并给出即时反馈。先不用管数据存储。

1. 搭建一个简单的对话框架你可以使用任何你熟悉的框架,比如 Python 的FlaskFastAPI提供一个 HTTP 接口,或者直接写一个控制台交互脚本。核心是有一个函数来处理用户输入。

# 示例:一个简单的处理函数骨架 def handle_user_message(user_id, message): """ 处理用户消息的核心函数 :param user_id: 用户唯一标识 :param message: 用户发送的文本消息 :return: 系统回复的文本 """ # 1. 意图识别 if is_set_nickname_intent(message): # 2. 实体抽取 nickname = extract_nickname(message) if nickname: # 3. 即时回应 return f"好的,我记住了,以后就叫你{nickname}。" else: return "我没听清楚你想让我叫你什么,能再说一遍吗?" else: # 其他对话逻辑 return handle_other_intent(message)

2. 实现意图识别 (is_set_nickname_intent)初期可以用规则匹配,简单可靠。

def is_set_nickname_intent(message): keywords = ["叫我", "称呼我", "叫我为", "以后叫我", "就叫我"] for kw in keywords: if kw in message: return True return False

3. 实现实体抽取 (extract_nickname)这是关键一步。对于有明确引导词的情况,可以用正则表达式。

import re def extract_nickname(message): # 匹配引导词后的内容,考虑中文引号或直接跟随 # 模式1: “叫我‘某某’” # 模式2: “叫我某某” patterns = [ r"叫我[‘\"']([^’\"']+)[’\"']", # 带引号 r"叫我(\S{2,10})[,。!?\s]|$", # 直接跟随,2-10个非空字符,直到标点或结尾 ] for pattern in patterns: match = re.search(pattern, message) if match: nickname = match.group(1).strip() # 简单过滤,比如长度检查 if 1 <= len(nickname) <= 10: return nickname return None

注意:正则表达式很难覆盖所有中文表达变化。这只是入门方案。生产环境需要考虑更复杂的 NLP 分词和实体识别模型,或者使用大语言模型(LLM)的 API 来完成抽取,准确率会高很多。

4. 测试单轮交互写个简单的测试脚本,验证功能。

test_cases = [ "以后叫我‘阿强’", "叫我小明", "称呼我为老板", "今天天气怎么样", # 非设置意图 ] for msg in test_cases: reply = handle_user_message("test_user_001", msg) print(f"输入: {msg}\n回复: {reply}\n")

确保对于设置意图的输入,能正确提取出“阿强”、“小明”、“老板”并给出相应回复。

3.2 第二步:引入持久化存储与用户状态

单次回应成功了,但下次对话系统又忘了。所以我们需要把称呼存下来。

1. 选择存储方案

  • 简单场景/演示:使用内存字典或文件(如 JSON)。但服务器重启数据就丢失。
    user_nickname_db = {} # 内存字典
  • 正式项目:使用数据库。SQLite(轻量)、MySQL/PostgreSQL(稳定)、Redis(高速缓存)都是好选择。这里以 SQLite 为例。
    import sqlite3 import os DB_PATH = "user_data.db" def init_db(): conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute('''CREATE TABLE IF NOT EXISTS user_prefs (user_id TEXT PRIMARY KEY, nickname TEXT)''') conn.commit() conn.close() def save_nickname(user_id, nickname): conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute("REPLACE INTO user_prefs (user_id, nickname) VALUES (?, ?)", (user_id, nickname)) conn.commit() conn.close() def get_nickname(user_id): conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute("SELECT nickname FROM user_prefs WHERE user_id=?", (user_id,)) row = c.fetchone() conn.close() return row[0] if row else None

2. 修改处理函数,加入存储逻辑

def handle_user_message_v2(user_id, message): if is_set_nickname_intent(message): nickname = extract_nickname(message) if nickname: # 新增:保存到数据库 save_nickname(user_id, nickname) return f"好的,我记住了,以后就叫你{nickname}。" else: return "我没听清楚你想让我叫你什么,能再说一遍吗?" else: # 在其他意图处理前,先尝试读取昵称 stored_nickname = get_nickname(user_id) # 将昵称传递给其他意图处理函数,用于生成个性化回复 return handle_other_intent_v2(message, stored_nickname)

现在,用户设置一次,后续对话都能通过user_id查到他的昵称了。

3.3 第三步:在后续交互中自然应用称谓

存储不是目的,自然应用才是。我们需要在系统主动发起对话或回应用户时,巧妙地把昵称用上。

1. 设计回复模板不要在所有回复里都生硬地加上昵称,那样会很奇怪。只在合适的场景使用,比如问候、确认、表达关心时。

# 回复模板库 GREETING_TEMPLATES = [ "{nickname},你好!有什么可以帮您?", "下午好,{nickname}。", "{nickname},欢迎回来。", ] CONFIRMATION_TEMPLATES = [ "明白了,{nickname}。", "好的,{nickname},这就为您处理。", ] # 通用回复(当不适合或没有昵称时) DEFAULT_REPLIES = ["你好!", "我在。", "请说。"]

2. 修改其他意图处理函数

import random def handle_other_intent_v2(message, nickname): # 假设这里有一个简单的意图判断 if is_greeting_intent(message): # 例如,用户说“你好” if nickname: template = random.choice(GREETING_TEMPLATES) return template.format(nickname=nickname) else: return random.choice(DEFAULT_GREETINGS) # 没有昵称时的默认问候 elif is_confirmation_intent(message): # 用户确认某事 if nickname: template = random.choice(CONFIRMATION_TEMPLATES) return template.format(nickname=nickname) else: return "好的。" # ... 其他意图处理 else: return "抱歉,我还没学会处理这个呢。"

3. 控制使用频率避免每句话都带昵称,会让用户觉得啰嗦或虚假。可以通过设置一个概率,或者只在对话开始和关键节点使用。更高级的做法是根据对话历史和用户偏好动态决定。

4. 进阶考量与常见“坑点”排查

基础功能跑通后,要让它真正可用、可靠,还需要考虑更多边界情况和工程细节。

4.1 称谓的验证、清洗与安全

用户输入是不可信的,必须清洗和验证。

  • 长度限制:防止用户输入超长字符串攻击。一般限制在2-20个字符内。
  • 敏感词过滤:建立一份敏感词库(包括脏话、政治敏感词、广告等),对提取出的昵称进行过滤。如果命中,可以回复:“这个称呼不太合适呢,换一个吧?”
  • 特殊字符处理:是否允许表情符号、外文、数字?根据产品定位决定。通常建议只允许中英文、数字和少数安全符号(如·)。
  • 去空格:用户输入“叫我 阿 强”,提取后应该变成“阿强”。
  • 唯一性(可选):是否允许不同用户设置相同昵称?对于强社交系统可能需要唯一性检查。

4.2 多轮对话与上下文维护

在复杂的多轮对话中,“叫我那两个字”的意图可能不会在一句话里完整表达。

  • 场景A
    • 用户:“我想改个称呼。”
    • 系统:“你想让我叫你什么呢?”
    • 用户:“阿强。”
  • 场景B
    • 用户:“那个词……”
    • 系统:“哪个词?”
    • 用户:“就是上次让你叫我的。”
    • 系统:“你是指‘阿强’吗?”

这需要系统维护对话状态(Dialog State)。你可以用一个简单的字典在内存中为每个会话保存临时状态。

# 简单的对话状态管理 dialog_states = {} # key: session_id, value: state_dict def handle_message_with_state(session_id, user_id, message): state = dialog_states.get(session_id, {}) # 如果状态显示正在等待用户输入昵称 if state.get('awaiting_nickname'): nickname = message.strip() # 这次输入直接当作昵称 if validate_nickname(nickname): save_nickname(user_id, nickname) del state['awaiting_nickname'] # 清除状态 dialog_states[session_id] = state return f"好的,{nickname},记住了。" else: return "这个称呼不合适哦,请重新告诉我。" # 如果是首次触发设置意图 elif is_set_nickname_intent(message): state['awaiting_nickname'] = True dialog_states[session_id] = state return “你想让我叫你什么呢?” # ... 其他逻辑

对于长时间运行的服务器,需要考虑状态超时和清理机制。

4.3 性能、扩展性与多模态

  • 性能:实体抽取(尤其是用LLM或复杂NLP模型时)和数据库查询可能是瓶颈。对于高频服务,考虑缓存用户昵称(如用Redis),避免每次对话都查库。
  • 扩展性:如果称呼系统需要支持更多属性(如用户喜欢的颜色、音乐风格),你的数据表结构和处理逻辑需要提前设计好,避免后期大改。
  • 多模态:如果支持语音输入,你需要先将语音转为文本(ASR),然后再走上述文本处理流程。在语音场景下,引导词的设计要更口语化,比如“你可以叫我XX”。

4.4 问题排查清单:当“叫我那两个字”不灵了

功能上线后,如果用户反馈系统没记住称呼,或者称呼用错了,可以按以下顺序排查:

  1. 检查输入:用户原话是什么?是否包含了预设的引导词(“叫我”、“称呼我”)?是否在昵称前后加了奇怪的符号或空格?先看原始日志
  2. 检查意图识别:你的is_set_nickname_intent函数是否匹配到了这条消息?是不是用户用了方言或同义词(如“喊我”、“称我”)?考虑扩大关键词列表或引入更灵活的匹配方式。
  3. 检查实体抽取:如果意图识别对了,昵称抽出来了吗?用用户的原始输入单独跑一下extract_nickname函数,看返回结果是不是None。可能是正则表达式没覆盖到这种句式。
  4. 检查验证规则:抽出来的昵称,是否因为长度超标、包含敏感词或特殊字符而被过滤掉了?查看验证模块的日志或返回值。
  5. 检查存储:昵称成功通过验证后,是否写入了数据库?检查数据库对应user_id的记录是否存在。确认写库操作没有抛出异常。
  6. 检查读取与应用:在后续对话中,系统是否用正确的user_id去查询数据库?查询结果是否被正确传递给了回复生成模块?回复模板的格式化(format)有没有出错?
  7. 检查对话状态:如果是多轮对话设置,检查对话状态(dialog_states)是否被正确设置和清除。是否存在会话超时导致状态丢失?

按照这个链路从前往后查,大部分问题都能定位。最常出问题的环节往往是第2步(意图识别不全面)第3步(实体抽取不准确)

5. 从功能到体验:一些务实的设计建议

最后,抛开纯技术实现,聊几点让这个功能更“像那么回事”的经验。

不要过度设计:初期用规则(关键词+正则)实现一个可用版本,比等待一个完美的AI模型要实际得多。快速上线,收集真实用户数据,再迭代优化。

提供修改和清除功能:允许用户说“以后别叫我阿强了”或“叫我新名字:大力”。这需要在意图识别里加入“清除昵称”和“更新昵称”的逻辑,并对应更新数据库。

设计降级策略:当昵称验证失败、抽取失败,或者系统不确定时,要有友好的降级回应。比如:“我好像没听清你喜欢的称呼,能再说一遍吗?”或者暂时使用通用称呼(“您”)。

考虑隐私与合规:如果涉及语音助手,在用户设置个性化称呼时,是否需要明确的隐私提示?存储的昵称数据如何管理和删除?这些需要在产品设计初期就考虑。

测试要覆盖边界:不仅要测“叫我小明”,还要测:

  • 超长输入:“叫我‘一二三四五六七八九十’”
  • 空输入:“叫我”
  • 特殊字符:“叫我@#¥”
  • 中英文混合:“叫我Tom老师”
  • 重复设置:用户先让叫“阿强”,又让叫“大力”,系统是否以最后一次为准?

实现“叫我那两个字”这个功能,技术上并不构成壁垒,真正的挑战在于对交互场景的细致拆解和对边界情况的周全处理。它不是一个孤立的特性,而是嵌入到整个对话系统和用户状态管理中的一环。先把它当作一个完整的“用户偏好设置与调用”的小系统来设计,跑通核心链路,再逐步打磨细节和体验,这样出来的效果会更扎实,也更容易维护。

← 返回列表