项目实训——Werewolf-Agent 多智能体狼人杀中DSPy应用优化器优化
一、前言上周我在我们的项目中引入了dspy并使用它进行一个简单的测试在测试过程中我进行了几局游戏发现预言家每次的输出结果都相差不大这让我在玩起来比较无趣因为在每个阶段我都可以预测到他将要说什么那么我就要想办法进行优化。二、原因分析我检查训练数据后发现每次它的输出都与我给他的训练数据相差不大甚至有的是一摸一样的这就让我很好奇为什么这个的提示词优化效果如此之差。经过查阅后我才知道我使用的 BootstrapFewShot 只是dspy所提供的最基础的编译器它的一个大体的工作流程为1. 从训练集中随机抽取一些样本2. 让 LLM 自己解决这些样本3. 挑出 LLM 答对的样本作为 Demo 示例4. 把这些 Demo 附在 Prompt 里让 LLM 模仿那么现在就很清晰了BootstrapFewShot 只是在训练集里挑答案对的它的Prompt 本身是固定的它不会去改进指令的写法这就说明了为什么每次都是差不多的输出。除此之外从它的工作流程中也可以看出它的Demo 选取是随机的不会去考虑如果把其他几道题放在一起会不会有更高的得分那么说明它选择的提示词并不一定是最优解。现在我们组的狼人杀结构简单并不能进行特别多轮的游戏我现在还没测出这个问题但是未来在更多人数的狼人杀要持续十几天的时候肯定会影响我们的提示词的效果。在未来随着数据集增加如果数据集中有更合适的结果组合那么它也不会选取这是未来的一个很大的隐患那么我们就要去替换掉这个基础编译器。那么我先去了解了一下dspy中提供的常见优化器优化器特点适用场景BootstrapFewShot简单、从训练集采样快速原型COPRO自动优化 Prompt 指令指令不够清晰时SignatureOpt优化 Field 描述Signature 设计不优时MIPROv2多目标贝叶斯优化追求最佳效果TelepromptOPTUNA超参搜索大规模调参这里我们选择了优化效果最好的MIPROv2来作为我们这个提示词的优化器MIPRO 全称是 Multi-Instruction Prompt Optimization是 DSPy 中目前效果最好的优化器之一。它的核心思想是在大规模训练集上用贝叶斯搜索自动找到最优的 Prompt 最优的 Few-shot 示例的组合。它解决了BootstrapFewShot优化器没有解决的问题即寻找在该问题上的最优的Few-shot示例这个的实现就是考MIPRO的贝叶斯优化。与简单的暴力穷举去寻找最优的 Few-shot 不同它在最初时候随机试几种组合建立什么样的组合效果好的直觉。然后如果随机测试的方案A效果好那么就在它附近继续探索类似方案。如果在这个区域已经测出高分它就会集中精力在这个区域深挖直至找到最优Few-shot示例与此同时MIPRO还会优化 Prompt 指令它会尝试各种不同的指令写法然后实际运行测试看哪个组合的得分最高。从实现原理上可以很明显看出这个优化器与我现在使用的有了很明显的优化那么我就要进行修改使用这个优化器。三、应用这里我们换一下优化器并设置一下默认参数即可。from dspy.teleprompt import MIPROv2 #换用MIPROv2优化器 optimizer MIPROv2( metricseer_accuracy_metric, num_threads30, # 搜索 30 次找到最优 Prompt Demo 组合 max_bootstrapped_demos4, # 每次试验最多用 4 条自助采样的示例 max_labeled_demos8, # 固定注入最多 8 条示例到 Prompt 中 metric_threshold0.9, )现在我们的训练数据就显得太少了优化器在这些数据中很容易选择到重复的数据。我们让ai来根据我们现在6人狼人杀为我们增添一些数据现在我们来重新训练一下。嗯训练大概花了五分钟确实要比用BootstrapFewShot时间更久毕竟是从中选择最优few-shot示例嘛也情有可原但是训练一次消耗的api有些多这个我就打算先对预言家这一个部分进行优化等学校的api发下来我再对其他几个进行优化钱包有限。接下来我们来测试一下看一下预言家表现如何。第一晚没有信息预言家表现正常。经过多次游戏测试每局预言家都有思考过程变化并不是重复的说明效果可以。但是第二晚以后会出现bug预言家看到消息摘要后会忘记自己是那个机器人导致会进行自查操作。这与之前设置的游戏规则相违背虽说设置了替代操作但是这是我所不想看到的我就去检查dspy中设置的prompt。--- 第 1 次尝试 --- 原始 check_target: Bot4 原始 reasoning: Bot4声称昨晚查验了Bot3是好人而Bot3已经是我已知的金水。Bot4要么是真预言家要么是狼人悍跳。Bot2跳女巫但信息模糊Bot1和Bot5发言偏划水。查验Bot4可以确认他的身份如果他是狼则直接锁定如果他是真预言家则Bot2的狼面上升。 ❌ check_target 无效不在存活列表或为自身 --- 第 2 次尝试 --- 原始 check_target: Bot4 原始 reasoning: Bot4声称昨晚查验了Bot3是好人而Bot3已经是我已知的金水。Bot4要么是真预言家要么是狼人悍跳。Bot2跳女巫但信息模糊Bot1和Bot5发言偏划水。查验Bot4可以确认他的身份如果他是狼则直接锁定如果他是真预言家则Bot2的狼面上升。 ❌ check_target 无效不在存活列表或为自身 这是终端输出他为什么会查验自己 --- 第 3 次尝试 --- 原始 check_target: Bot4 原始 reasoning: Bot4声称昨晚查验了Bot3是好人而Bot3已经是我已知的金水。Bot4要么是真预言家要么是狼人悍跳。Bot2跳女巫但信息模糊Bot1和Bot5发言偏划水。查验Bot4可以确认他的身份如果他是狼则直接锁定如果他是真预言家则Bot2的狼面上升。 ❌ check_target 无效不在存活列表或为自身这里没有检查出什么大问题我在dspy的签名中已经写出了它是预言家并说明是几号。class SeerNightAction(dspy.Signature): 你是狼人杀游戏中的预言家。你需要在夜晚查验一名玩家的身份。 重要规则必须严格遵守 1. 【禁止自查】绝对不能查验自己。你的ID是 seer_idcheck_target 绝不能等于 seer_id。 2. 【只能查存活】check_target 必须是【存活玩家列表】中的人不能查验死人。 3. 推理必须结合存活人物情况 别人发言 你的查验结果。 4. 优先查验声称是神职但无证据者 发言矛盾者 跟风投票者。 5. 发言摘要中会包含自己之前的发言可能会包含一个人跳出来说自己是预言家的消息注意检查是不是自己。 seer_id dspy.InputField( desc你自己的玩家ID例如 Bot5 ) game_state dspy.InputField( desc游戏状态包含存活玩家列表、死亡玩家及死因... ) known_info dspy.InputField( desc你历次查验结果例如第1晚A是狼人第2晚B是好人 ) check_target dspy.OutputField( desc查验目标玩家ID【必须是存活玩家】且【不能等于 seer_id】。 ) reasoning dspy.OutputField( desc推理过程必须结合存活人物情况、别人发言、查验结果进行分析不能脱离上下文, prefix推理 )只能说明是ai在生成时丢弃了这部分只能丢给ai帮我想一下解决办法。它帮我对输出结果添加了更严格的判断对于不符合要求的结果直接打回重新操作if check_target actor: print(f\n 预言家 {actor} 节点执行 ) print(f⚠️ LLM 选择了自己立即强制替换) print(f原始 alive_players: {alive}) print(f原始 roles: {roles}) print(f输入 game_state: {game_state_desc}) print(f输入 known_info: {known_info}) print(f原始 check_target: {check_target}) print(f原始 reasoning: {reasoning}) if valid_targets: check_target random.choice(valid_targets) reasoning f强制替换{reasoning} | 实际查验 {check_target} else: print(f⚠️ 没有可查验的目标) return {} print(f\n最终结果: check_target{check_target}, reasoning{reasoning}) print(f\n) else: print(f\n 预言家 {actor} 节点执行 ) print(f原始 alive_players: {alive}) print(f原始 roles: {roles}) print(f输入 game_state: {game_state_desc}) print(f输入 known_info: {known_info}) for attempt in range(3): print(f\n--- 第 {attempt 1} 次尝试 ---) print(f原始 check_target: {check_target}) print(f原始 reasoning: {reasoning}) if check_target not in alive or check_target actor: print(f❌ check_target 无效不在存活列表或为自身) if valid_targets: check_target random.choice(valid_targets) reasoning f随机查验 {check_target} continue if any(pattern in reasoning for pattern in invalid_patterns): print(f❌ reasoning 包含无效内容: {reasoning}) logger.warning(fSeer reasoning 包含无效内容第{attempt 1}次: {reasoning}) if valid_targets: check_target random.choice(valid_targets) reasoning f随机查验 {check_target} continue print(f✅ 验证通过) break if check_target is None or check_target not in alive or check_target actor: print(f⚠️ 3次尝试均失败使用 fallback) if valid_targets: check_target random.choice(valid_targets) reasoning f随机查验 {check_target} else: print(f⚠️ 没有可查验的目标) return {}修正之后我再进行测试发现问题出现少了。我再进行多次测试发现预言家在后续的发言中表现优异。预测结果较准好人胜率较先前有明显提高。再测试的50局对决中分析日志可以看到好人胜场达到39场。四、结语本周我们对dspy应用进行优化与此同时为训练数据提供更适合的标准为以后的其他节点的提示词提供了一个可靠的模板对于其他部分我们可以使用该节点为模板进行修改验证。后续我们将对metric得分条件进行优化以一个优秀的标准来评判得分情况。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2558379.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!