AI群演请就位——个人博客(三)

张开发
2026/4/27 4:28:26 15 分钟阅读

分享文章

AI群演请就位——个人博客(三)
在之前我完成了11张数据库表的设计和迁移。但表只是存储容器真正的业务逻辑在于用户行为如何影响角色状态以及状态变化如何推动剧情。根据任务书里的要求1.每个角色维护好感度、信任值、怀疑度、隐藏身份暴露度、目标完成度5项指标2.设计至少30种用户行为对状态的影响规则如“替角色挡刀 → 好感度15”3.支持至少40个事件模板事件触发条件支持4种以上状态变化如好感度超过阈值、暴露度达到一定值4.关键剧情节点自动标记匹配准确率≥90%这些需求本质上是规则驱动的状态机。我决定将“行为→状态变化”和“状态变化→事件触发”解耦分别实现状态引擎和事件引擎。一、状态引擎1.1 定义状态数据结构我用 Pydantic 定义角色的5项核心状态方便类型校验和序列化。创建 app/schemas/status.pyfrom pydantic import BaseModel class CharacterStatus(BaseModel): favorability: int 0 # 好感度范围 -100 到 100 trust: int 50 # 信任值范围 0 到 100 suspicion: int 0 # 怀疑度范围 0 到 100 exposure: int 0 # 隐藏身份暴露度范围 0 到 100 goal_progress: int 0 # 目标完成度范围 0 到 100我将初始信任值设定为50是因为新认识的角色既不信任也不排斥中性值。好感度初始0可以负向发展。1.2 行为→状态映射规则库30条接着我创建了app/services/status_engine.py核心是一个字典 RULES键是行为描述由前端或后端行为识别模块传入值是一个字典表示各个状态的变化量。我让AI帮我生成三十条规则经过修改和增删之后一共有39条规则下面是部分规则RULES { # 正面行为 替角色挡刀: {favorability: 15, trust: 10}, 救下角色性命: {favorability: 30, trust: 25}, 赠送贵重礼物: {favorability: 5, suspicion: 2}, 帮助角色完成任务: {goal_progress: 20, favorability: 10}, # 负面行为 当众揭穿谎言: {trust: -20, suspicion: 25, exposure: 30}, 背叛角色: {favorability: -30, trust: -40}, 在危险中抛弃角色: {favorability: -25, trust: -35}, 威胁角色: {favorability: -20, trust: -15, suspicion: 20}, 偷窃角色物品: {favorability: -15, trust: -20, suspicion: 30}, # 中性/特殊行为 保持沉默: {trust: 0, suspicion: 5}, 询问隐私: {trust: -5, suspicion: 10}, 赞美角色: {favorability: 3}, }应用规则时还需要对值进行边界裁剪防止超出定义范围。我专门写了一个函数apply_behavior(behavior_key, current_status) 来完成这件事。1.3 状态更新服务我们小组认为要实现追踪状态变化至少30种行为映射规则但更重要的是记录每一次变化的原因和历史值便于后期分析、回溯以及多结局判定。因此我设计了 status_history 表并在 status_service.py 中实现更新逻辑。这样每次状态变化都会被完整记录不仅满足任务书要求也为后续的结局判定提供了依据。下面是简化版的核心代码async def update_status_by_behavior(db, session_id, user_id, character_id, behavior_key): # 1. 获取当前状态从 user_character_relations 表 relation await get_relation(db, user_id, character_id) if not relation: relation create_default_relation(user_id, character_id) current CharacterStatus( favorabilityrelation.favorability, trustrelation.trust, suspicionrelation.suspicion, exposurerelation.exposure, goal_progressrelation.goal_progress ) # 2. 应用规则得到新状态 new_status apply_behavior(behavior_key, current) # 3. 保存历史记录 history StatusHistory( user_iduser_id, character_idcharacter_id, previous_statuscurrent.dict(), new_statusnew_status.dict(), change_reasonbehavior_key, timestampdatetime.utcnow() ) db.add(history) # 4. 更新当前状态到关系表 relation.favorability new_status.favorability relation.trust new_status.trust relation.suspicion new_status.suspicion relation.exposure new_status.exposure relation.goal_progress new_status.goal_progress await db.commit() return new_status1.4 行为识别关于在对话中如何从用户的自然语言中提取出对应的行为关键词的问题目前我实现了一个简单的关键词匹配函数 detect_behavior(text)放在 multi_role_scheduler.py 中。例如def detect_behavior(text: str) - str: text_lower text.lower() if any(kw in text_lower for kw in [挡刀, 保护, 救]): return 替角色挡刀 if any(kw in text_lower for kw in [送礼, 赠送, 给]): return 赠送贵重礼物 # ... return 保持沉默这些我目前只做了初步实现后期可以接入小型的文本分类模型或者交给大模型直接输出行为标签。二、事件触发系统状态变化之后可能会触发特定事件如“发现刺客线索”这些事件会进一步影响状态、记录关键节点、甚至改变剧情走向。2.1 事件模板设计我创建了 app/data/event_templates.json 用来存放40个事件模版一个完整的事件模板包含以下字段字段类型说明idstring唯一标识namestring事件名称typestring情感/战斗/阴谋/日常/剧情description_templatestring描述模板支持{character_name}等占位符conditiondict触发条件如{exposure_min:30, suspicion_min:40}status_effectsdict触发后对状态的影响is_key_nodebool是否属于关键剧情节点node_namestring如果是关键节点节点名称我让deepseek帮我生成50个事件模版从中挑选合适的并进行了修改让其更适配我们的项目要求一共40个涵盖以下类型情感类救命之恩、信任破裂、表白、和解、牺牲等阴谋类发现刺客线索、隐藏身份暴露、被跟踪、叛徒现身等战斗类生死关头、暗杀行动、决斗邀请、终极对决等日常类深夜密谈、礼物回赠、醉酒吐真言、病榻探望等剧情类目标完成、家族秘密、宝藏现世、传位等下面是部分示例[ { id: evt_001, name: 发现刺客线索, type: 阴谋, description_template: 你在{character_name}的房间里发现了一封密信上面写着暗杀计划。, condition: {exposure_min: 30, suspicion_min: 40}, status_effects: {trust: -5, exposure: 10}, is_key_node: true, node_name: 刺客身份暴露 }, { id: evt_002, name: 救命之恩, type: 情感, description_template: {character_name}重伤倒地你救了他。他眼中满是感激。, condition: {favorability_min: 20}, status_effects: {favorability: 20, trust: 15}, is_key_node: false }, # ... ]2.2 Pydantic 模型加载与条件校验为了类型安全我定义了 app/schemas/event.pyfrom pydantic import BaseModel from typing import Dict, Optional class EventTemplate(BaseModel): id: str name: str type: str description_template: str condition: Dict[str, int] status_effects: Dict[str, int] is_key_node: bool False node_name: Optional[str] None def check_condition(self, status: Dict[str, int]) - bool: for key, threshold in self.condition.items(): if key.endswith(_min): attr key[:-4] if status.get(attr, 0) threshold: return False elif key.endswith(_max): attr key[:-4] if status.get(attr, 0) threshold: return False else: if status.get(key, 0) ! threshold: return False return True def format_description(self, character_name: str, **kwargs) - str: return self.description_template.format(character_namecharacter_name, **kwargs)加载所有模版到内存def load_event_templates() - List[EventTemplate]: path Path(__file__).parent.parent / data / event_templates.json with open(path, r, encodingutf-8) as f: data json.load(f) return [EventTemplate(**item) for item in data] EVENT_TEMPLATES load_event_templates()2.3 事件触发引擎触发逻辑放在 app/services/event_engine.py 中。基本流程接收当前会话ID、用户ID、角色ID、角色名称、当前状态字典。遍历所有事件模板调用 check_condition 判断是否满足条件。如果满足根据模板生成描述文本替换占位符记录到 plot_events 表。如果 is_key_node 为真同时写入 key_nodes 表。应用 status_effects 更新角色状态。为了防止同一事件在短时间内重复触发我加入了一个简单的去重机制检查最近30秒内该会话是否已经触发过相同的事件ID。可以用 Redis 存储最近触发记录也可以查询数据库 last 1 minute。为简化初期我直接用 Redis 的过期 key 实现。async def check_and_trigger_events(db, session_id, user_id, character_id, character_name, current_status): triggered [] for template in EVENT_TEMPLATES: if template.check_condition(current_status): # 去重检查Redis key fevent_trigger:{session_id}:{template.id} if await redis_client.exists(key): continue await redis_client.setex(key, 30, 1) # 记录事件 description template.format_description(character_name) event PlotEvent( session_idsession_id, event_nametemplate.name, event_typetemplate.type, descriptiondescription, triggered_atdatetime.utcnow(), trigger_conditionstr(template.condition), affected_relationstemplate.status_effects ) db.add(event) # 关键节点 if template.is_key_node and template.node_name: key_node KeyNode( session_idsession_id, node_nametemplate.node_name, descriptiondescription, plot_linemain, involved_charactersstr([character_id]), timestampdatetime.utcnow() ) db.add(key_node) triggered.append(template) await db.commit() return triggered然后在状态更新服务中调用 check_and_trigger_events 并传入最新的状态。三、集成到对话流程在 multi_role_scheduler.py 的 get_all_responses 函数中我们在获得所有角色的AI回复之后会进行两个步骤状态更新根据用户输入识别行为关键词然后对每个角色调用 update_status_by_behavior。事件触发拿到每个角色的最新状态后调用 check_and_trigger_events 检查是否有新事件触发。这样用户每发一句话背后都会执行AI回复获取 → 状态更新 → 事件触发 → 关键节点记录形成完整的剧情推进闭环。四、测试与验证我让ai给我生成了一个测试文件 tests/test_status_engine.py验证状态变化逻辑和事件条件判断。def test_apply_behavior(): status CharacterStatus() new apply_behavior(替角色挡刀, status) assert new.favorability 15 assert new.trust 60 def test_event_condition(): template EVENT_TEMPLATES[0] status {exposure: 35, suspicion: 45} assert template.check_condition(status) True运行tests/test_status_engine.py -v全部通过。五、遇到的问题状态边界处理最初没有对好感度、信任值做 clamp导致出现150这样无效值影响事件判断。后来在 apply_behavior 中增加边界检查。事件重复触发在一次状态更新中如果多个角色的状态变化都满足同一事件条件会导致同一事件被多次记录。因此引入基于会话事件ID的短期去重30秒内不重复触发。性能考虑每次状态更新都要遍历40个事件模板虽然开销不大但未来模板增多后可能成为瓶颈。可以预先为每种状态建立索引如 exposure30 的事件列表只检查可能满足的事件。六、下一步计划接下来我会逐步实现多角色并发对话框架Mock AI版本、自主推进任务、以及多结局系统的雏形。届时整个后端的核心玩法将完整呈现。

更多文章