DeepWerewolf
No description available
Install / Use
/learn @af-74413592/DeepWerewolfREADME
中文狼人杀 Agentic-RL 训练案例配置指南
8B模型 https://huggingface.co/haowu11/DeepWereWolf-Qwen3-8B-Grpo-Agentic-Chinese
32B模型 https://huggingface.co/haowu11/DeepWereWolf-Qwen3-32B-Grpo-Agentic-Chinese
####################################################################
基于框架版本信息
- agent-lightning: commit: 5724f63cfc75bcc2f4fb56958ef384d307717c18 | date: Sep 13, 2025 (或者直接pip install -e . 安装本仓库)
- AgentScope: commit: 458e8eedc94bba89bc3e4c6756e35fb4defbc0ac | date: Sep 15, 2025 (截至2025-9-30日的版本 v1.0.4 测试了都是没有api冲突的)
- VERL: version: v0.5.0
- VLLM: version: v0.10.2 (sglang的后端训练更稳定不容易崩溃,可以看sglang的分支,但sglang 32B的训练偶尔也会炸掉,需要注意重启)
- flash-attn version: v2.8.3
相关仓库链接
- agent-lightning 官方: https://github.com/microsoft/agent-lightning
- AgentScope 官方: https://github.com/agentscope-ai/agentscope
- 中文狼人杀修改版: https://github.com/af-74413592/agentscope
####################################################################
数据集 用fake-data.py 生成或者直接下载gsm8k的train.parquert都可以,仅仅起到一个迭代器的作用
####################################################################
一、核心执行脚本
-
训练脚本路径
example/werewolf/train.sh或者 train-fsdp2.sh 都是可以的 -
客户端启动命令
python werewolf_agent.py
二、agent-lightning 源码修改位置(核心改动)(可以直接git clone 本仓库)
2.0 添加examples/werewolf 实现
2.1 注释 Triplet 导出逻辑(防止覆盖)
- 文件路径:
agentlightning/runner.py - 修改位置:第 115 行
- 原代码(注释掉):
if trace_spans: triplets = self.triplet_exporter.export(trace_spans) ```
2.2 修改 Trace 列表构造(agentlightning/verl/daemon.py 第 338 行):
trace_list = [
{"prompt_ids": t.prompt.get("token_ids", []), "response_ids": t.response.get("token_ids", []), "reward": t.reward}
for t in rollout.triplets
]
2.3 修正 Reward 取值逻辑(第 418 行):
原代码(注释掉):
reward_list.append(sample_info["reward"])
新代码(替换为):
reward_list.append(trace["reward"])
agentlightning/verl/trainer.py 298行 注释了第一次val函数
# if self.val_reward_fn is not None and self.config.trainer.get("val_before_train", True):
# val_metrics = self._validate()
# assert val_metrics, f"{val_metrics=}"
# pprint(f"Initial validation metrics: {val_metrics}")
# logger.log(data=val_metrics, step=self.global_steps)
# if self.config.trainer.get("val_only", False):
# return
其他改动
agentlightning/runner.py 加入了重试逻辑
手动rollout限制和最大token限制,可以自行放开这段代码
result = await rollout_method(task.input, task.rollout_id, resources_update.resources)
valid_result = [t for t in result if len(t.prompt.get("token_ids")) + len(t.response.get("token_ids")) <= 10000]
if len(valid_result) > 64:
#降低最大rollout
import random
new_result = random.sample(valid_result, 64)
else:
new_result = valid_result
# rollout_obj = self._to_rollout_object(result, task.rollout_id)
rollout_obj = self._to_rollout_object(new_result, task.rollout_id)
agentlightning/daemon.py
if n_transition == 0:
raise Exception("Empty transitions !!!!!!!")
examples/werewolf/werewolf_agent.py
import random
if random.random() < 0.8:
agent = ReActAgent(
name=name,
sys_prompt=Prompts.system_prompt,
model=OpenAIChatModel(
model_name=llm.model,
client_args={"base_url": llm.endpoint},
api_key="xxx",
stream=False,
),
# formatter=DashScopeMultiAgentFormatter(),
formatter=OpenAIMultiAgentFormatter(),
)
else:
agent = ReActAgent(
name=name,
sys_prompt=Prompts.system_prompt.format(
player_name=name,
guidance=getattr(Prompts, f"notes_{role}"),
),
model=DashScopeChatModel(
model_name="qwen3-max-preview",
api_key=os.environ["DASHSCOPE_API_KEY"],
enable_thinking=True,
),
formatter=DashScopeMultiAgentFormatter(),
)
这一段函数引入了外部模型api进行对抗训练。也可以注释掉全都使用vllm客户端 如下
agent = ReActAgent(
name=name,
sys_prompt=Prompts.system_prompt,
model=OpenAIChatModel(
model_name=llm.model,
client_args={"base_url": llm.endpoint},
api_key="xxx",
stream=False,
),
# formatter=DashScopeMultiAgentFormatter(),
formatter=OpenAIMultiAgentFormatter(),
)
llm_reward_system_prompt = "这里进行着一个LLM狼人杀游戏,history上下文太长就不展示了,你的职责就是判断模型的回答是否有游戏无关的胡言乱语(这里不包含<think>格式或者各种tool_call还有<|im_start|>assistant这种其他消息头,都是正常输出,只看思考和回答中的纯文本部分),或者模型没有按照中文来回答。还有文本的可读性。如果有这些情况,则输出Low Quality,没有则输出High Quality,无需对游戏行为决策做出判断。以下是模型回答:\n\n" + response
llm_quality_reward = llm_api(llm_reward_system_prompt)
import time
#防止高频访问
time.sleep(0.5)
if "Low Quality" in llm_quality_reward:
triplet.reward = triplet.reward - 5.0
print(f"WARNING: Low Quality detected: {response}")
这几行是调用外部llm打分。文本通顺性reward,酌情添加
注意如果更改训练模型,记得替换self.tokenizer
agentlightning/verl/trainer.py fit函数 self._load_checkpoint()下面最好 time.sleep(60) 一会,有的时候会闪退。
二、安装agentscope框架 (需要手动修改)
核心修改 手动处理think消息(因为新版vllm不在支持--enable_thinging格式消息返回)
src/agentscope/model/_openai_model.py _parse_openai_completion_response函数开头if choice.message.content:下 改为
if choice.message.content:
try:
thinking_part = choice.message.content.split("<think>")[1].split("</think>")[0]
content_part = choice.message.content.split("</think>")[1]
content_blocks.append(
ThinkingBlock(
type="thinking",
thinking=thinking_part,
),
)
content_blocks.append(
TextBlock(
type="text",
text=content_part,
),
)
except:
content_blocks.append(
TextBlock(
type="text",
text=response.choices[0].message.content,
),
)
其他改动(建议)一个更安全的toolcall
for tool_call in choice.message.tool_calls or []:
try:
arguments_dict = _json_loads_with_repair(
tool_call.function.arguments,
)
except:
logger.warning(
"Failed parse arguments to a valid dict in the tool_call message, skipped."
)
if arguments_dict != {}:
for key,value in arguments_dict.items():
if key == "response" and value == None:
arguments_dict["response"] = ""
content_blocks.append(
ToolUseBlock(
type="tool_use",
id=tool_call.id,
name=tool_call.function.name,
input=arguments_dict,
),
)
else:
logger.warning(
"Failed parse arguments to a valid dict in the tool_call message, skipped."
)
还有一种更简单的办法 在src/agentscope/agent/_react_agent.py 的 generate_response函数下 加上 response = "" if response == None else response
其他改动(可选)压缩历史消息防止报错
处理过长的prompt:src/agentscope/model/openai_model.py OpenAIChatModel 的__call_ 函数
self.tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-8B")
conversations = [{"role":msg["role"], "content":msg["content"][0]['text'] if type(msg["content"]) == list else msg["content"]} for msg in messages]
input_ids = self.tokenizer.apply_chat_template(
conversations,
add_generation_prompt=True,
tokenize=True,
)
while len(input_ids) > 10000: (比maxlen稍微小一点)
messages[1]["content"][0]['text'] = messages[1]["content"][0]['text'][:150] + '\n...\n' + messages[1]["content"][0]['text'][200:]
conversations = [{"role":msg["role"], "content":msg["content"][0]['text'] if type(msg["content"]) == list else msg["content"]} for msg in messages]
input_ids = self.tokenizer.apply_chat_template(
conversations,
add_generation_prompt=True,
tokenize=True,
)
三、verlv0.5.0 改动 (需要手动修改)
注释掉 verl trainer/ppo/ray_trainer.py 415-418行 (因为不需要很大的train_batch_size)
real_train_batch_size = config.data.train_batch_size * config.actor_rollout_ref.rollout.n
assert real_train_batch_size % minimal_bsz == 0, (
f"real_train_batch_size ({real_train_batch_size}) must be divisible by minimal possible batch size "
f"({minimal_bsz})"
)
注释掉 verl trainer/ppo/ray_trainer.py 500 行
assert config.data.train_batch_size >= config.actor_rollout_ref.actor.ppo_mini_batch_size
由于可能是verl/vllm作为后端训练或者是其他什么原因,训练不是很稳定,可以开启monitor_train.sh脚本自动重启训练
四、train.sh 说明
data.train_batch_size=1 \
actor_rollout_ref.rollout.n=1 \
这两条可以压小,不需要太多rollout,agentlightning会把轨迹切开重组成新的rollout list, 开到2x2, 有的时候会rollout出来三四百条。
data.max_prompt_length=15360 \
data.max_response_length=1024 \
显存不够可以改小一点max_prompt_length
data.truncation='middle'
中间自动截断过长history
actor_rollout_ref.rollout.gpu_memory_utilization=0.4 \
4:6 分配推理和训练显存
trainer.save_freq=1
稳定了可以加大保存频率
trainer.test_freq=0
没有实现val方法,统计reward移动至train
超长序列可以尝试开启 actor_rollout_ref.actor.ulysses_sequence_parallel_size=2
trainer.default_local_dir='/root/dataDisk/checkpoints' \ 权重保存位置
trainer.max_actor_ckpt_to_keep=3 \ 打开自动删除历史权重
五、reward设计说明
def _process_triplets_with_rewards(self, wolf_win_flag: bool, NAME_TO_ROLE: dict) -> list[Triplet]:
spans = self.tracer.get_last_trace()
triplets = self.triplet_exporter.export(spans)
new_triplets= []
