SkillAgentSearch skills...

DeepWerewolf

No description available

Install / Use

/learn @af-74413592/DeepWerewolf
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

中文狼人杀 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都可以,仅仅起到一个迭代器的作用

####################################################################

一、核心执行脚本

  1. 训练脚本路径
    example/werewolf/train.sh 或者 train-fsdp2.sh 都是可以的

  2. 客户端启动命令
    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= []
     
View on GitHub
GitHub Stars71
CategoryDevelopment
Updated3d ago
Forks8

Languages

Python

Security Score

90/100

Audited on Mar 29, 2026

No findings