当前位置:   article > 正文

AGI|教你用一部电影的时间训练个人专属Agent

AGI|教你用一部电影的时间训练个人专属Agent

本期摘要

Agent是一个超越简单文本生成的人工智能系统。它使用大型语言模型(LLM)作为其中央计算引擎,使其能够进行对话、执行任务、推理并显示一定程度的自主权。

本文带你手把手尝试如何训练一个属于自己的私人订制Agent。

01

Agent如何工作?

1、当用户给出一个任务task之后可以从memory中查询记录(可选),查询出的结果(如果有)给AgentLLM进行判断是否可复用,这里指的复用是针对时效性没那么高的任务,例如对过去时的数据“中国19-22年的出生及死亡人口数据”,但如果查询股票数据,天气这种对时效性有很高要求的任务则不适合复用。

2、Agent对任务实现的方式有很多,可以拆解任务、使用lCOT或REACT框架、SOP(Standard Operating Procedure)标准作业规程等等。其目的都是将一个复杂的任务分成n个可在one step内即可完成的子任务。

3、对于子任务,是否需要调用工具,如果无需调用工具则只需要进行一次推理即可;对于需要调用工具的子任务AgentLLM会根据任务描述调用一个或多个工具,根据工具返回结果判断是否可以更改任务状态。待所有的子任务都完成状态变更之后AgentLLM会对结果进行评估反思,判断当前任务是否已经完成。如果某些子任务因为种种原因无法完成,AgentLLM会采取别的方法完成此任务,重复以上步骤直到可以给出结果为止,当然这里的Loop需要设置最大重试次数避免死循环。

4、当AgentLLM判断可以完成任务后可以进行历史任务存储(可选)。长期记忆是将数据存储在数据库中,以便下次查询,短期记忆则保存在内存或缓存中,程序结束时释放。

02

Function Call 原理

在一些任务中我们希望LLM返回我们格式化的数据如json、xml等,function call则需要LLM返回特定的json格式,以OpenAI为例,需要提供工具的描述信息。

from openai import OpenAI  
import json  
  
client = OpenAI()  
  
  
  
def get_current_weather(location, unit="fahrenheit"):  
    """Get the current weather in a given location"""  
    if "tokyo" in location.lower():  
        return json.dumps({"location": "Tokyo", "temperature": "10", "unit": unit})  
    elif "san francisco" in location.lower():  
        return json.dumps({"location": "San Francisco", "temperature": "72", "unit": unit})  
    elif "paris" in location.lower():  
        return json.dumps({"location": "Paris", "temperature": "22", "unit": unit})  
    else:  
        return json.dumps({"location": location, "temperature": "unknown"})  
  
def run_conversation():  
      
    messages = [{"role": "user", "content": "What's the weather like in San Francisco, Tokyo, and Paris?"}]  
    tools = [  
        {  
            "type": "function",  
            "function": {  
                "name": "get_current_weather",  
                "description": "Get the current weather in a given location",  
                "parameters": {  
                    "type": "object",  
                    "properties": {  
                        "location": {  
                            "type": "string",  
                            "description": "The city and state, e.g. San Francisco, CA",  
                        },  
                        "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},  
                    },  
                    "required": ["location"],  
                },  
            },  
        }  
    ]  
    response = client.chat.completions.create(  
        model="gpt-3.5-turbo-1106",  
        messages=messages,  
        tools=tools,  
        tool_choice="auto",   
    )  
    response_message = response.choices[0].message  
    tool_calls = response_message.tool_calls  
      
    if tool_calls:  
          
          
        available_functions = {  
            "get_current_weather": get_current_weather,  
        }   
        messages.append(response_message)   
          
        for tool_call in tool_calls:  
            function_name = tool_call.function.name  
            function_to_call = available_functions[function_name]  
            function_args = json.loads(tool_call.function.arguments)  
            function_response = function_to_call(  
                location=function_args.get("location"),  
                unit=function_args.get("unit"),  
            )  
            messages.append(  
                {  
                    "tool_call_id": tool_call.id,  
                    "role": "tool",  
                    "name": function_name,  
                    "content": function_response,  
                }  
            )   
        second_response = client.chat.completions.create(  
            model="gpt-3.5-turbo-1106",  
            messages=messages,  
        )   
        return second_response  
print(run_conversation())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80

在推理结果中可以拿到类似{“name”: “get_current_weather”, “params”: {“location”: “北京”, “unit”: “celsius”}}这样的json数据,这里有需要调用的工具名称以及参数信息,接下来只需要编写代码实现工具调用,将工具返回的结果构造成message加入到与LLM对话的上下文中即可实现工具调用。这里的难点在于对一个开源模型来说,如何根据任务以及提供的工具描述给出正确的工具名称以及正确的参数。

03

开源模型工具调用微调

  • 开源项目地址:LLaMA-Factory(https://github.com/hiyouga/LLaMA-Factory);

  • 作者知乎最佳实践地址:单卡 3 小时训练专属大模型 Agent:基于 LLaMA Factory 实战(https://zhuanlan.zhihu.com/p/678989191#ref_10)。

以下为复现实验数据过程记录

Chat模型微调

模型 Yi-6B-Chat 硬件信息 NVIDIA A100-SXM4-80GB
sft超参

CUDA_VISIBLE_DEVICES=0 python src/train_bash.py \   
    --stage sft \   
    --do_train \   
    --model_name_or_path /mnt/models/Yi-6B-Chat \   
    --dataset glaive_toolcall \   
    --template yi \   
    --finetuning_type lora \   
    --lora_target q_proj,v_proj \   
    --output_dir yi_agent_checkopint \   
    --lora_target all \   
    --overwrite_cache \   
    --per_device_train_batch_size 4 \   
    --gradient_accumulation_steps 4 \   
    --lr_scheduler_type cosine \   
    --logging_steps 10 \   
    --save_steps 1000 \   
    --learning_rate 5e-4 \   
    --num_train_epochs 3 \   
    --plot_loss \   
    --fp16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

export model

python src/export_model.py \   
    --model_name_or_path /mnt/models/Yi-6B-Chat \   
    --adapter_name_or_path yi_agent_checkopint \   
    --template yi \   
    --finetuning_type lora \   
    --export_dir Yi-Agent-6b-Chat \   
    --export_size 2 \   
    --export_legacy_format False
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

web demo

python src/web_demo.py --model_name_or_path Yi-Agent-6b-Chat --template yi

训练过程日志

{'train_runtime': 7735.6787, 'train_samples_per_second': 3.878, 'train_steps_per_second': 0.242, 'train_loss': 0.3381453339894613, 'epoch': 3.0}  
100%|████████████████████████████████████████████████████████████████████████████████████| 1875/1875 [2:08:55<00:00, 4.13s/it]  
[INFO|trainer.py:2889] 2024-01-25 13:39:49,599 >> Saving model checkpoint to yi_agent_checkopint  
[INFO|tokenization_utils_base.py:2432] 2024-01-25 13:39:49,709 >> tokenizer config file saved in yi_agent_checkopint/tokenizer_config.json  
[INFO|tokenization_utils_base.py:2441] 2024-01-25 13:39:49,709 >> Special tokens file saved in yi_agent_checkopint/special_tokens_map.json  
***** train metrics *****  
  epoch = 3.0  
  train_loss = 0.3381  
  train_runtime = 2:08:55.67  
  train_samples_per_second = 3.878  
  train_steps_per_second = 0.242  
Figure saved: yi_agent_checkopint/training_loss.png  
01/25/2024 13:39:49 - WARNING - llmtuner.extras.ploting - No metric eval_loss to plot.  
[INFO|modelcard.py:452] 2024-01-25 13:39:49,848 >> Dropping the following result as it does not have all the necessary fields:  
{'task': {'name': 'Causal Language Modeling', 'type': 'text-generation'}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

**/****/**测试结果

测试Tools

[  
    {  
        "name": "get_province_list",  
        "description": "获取省份ID",  
        "parameters": {  
            "type": "object",  
            "properties": {}  
        }  
    },  
    {  
        "name": "get_cities_list",  
        "description": "根据省份ID查询城市地区ID",  
        "parameters": {  
            "type": "object",  
            "properties": {  
                "province_id": {  
                    "type": "string",  
                    "description": "省份ID,可以通过调用get_province_list获取省份ID"  
                }  
            },  
            "required": [  
                "province_id"  
            ]  
        }  
    },  
    {  
        "name": "get_history_weather",  
        "description": "根据城市ID和日期查询历史天气信息,日期支持从2011-01-01开始。注:个别地区个别日期数据记录可能会不存在",  
        "parameters": {  
            "type": "object",  
            "properties": {  
                "city_id": {  
                    "type": "string",  
                    "description": "城市地区ID,可以通过调用get_cities_list获取城市地区ID"  
                },  
                "weather_date": {  
                    "type": "string",  
                    "description": "日期,格式:2017-07-15,日期不能大于等于今日日期"  
                }  
            },  
            "required": [  
                "city_id",  
                "weather_date"  
            ]  
        }  
    },  
    {  
        "name": "get_river_environment",  
        "description": "查询地表水水质",  
        "parameters": {  
            "type": "object",  
            "properties": {  
                "page": {  
                    "type": "integer",  
                    "description": "第几页(默认1)"  
                },  
                "province": {  
                    "type": "string",  
                    "description": "省份,例:江苏省"  
                },  
                "river": {  
                    "type": "string",  
                    "description": "流域,例:海河流域"  
                },  
                "section": {  
                    "type": "string",  
                    "description": "断面名称,例:鼓楼外大街"  
                }  
            },  
            "required": []  
        }  
    },  
    {  
        "name": "get_environment_air_pm",  
        "description": "查询的城市PM2.5数据",  
        "parameters": {  
            "type": "object",  
            "properties": {  
                "city": {  
                    "type": "string",  
                    "description": "城市名称的中文名称或拼音,如:上海 或 shanghai"  
                }  
            },  
            "required": [  
                "city"  
            ]  
        }  
    },  
    {  
        "name": "get_toutiao_news",  
        "description": "新闻列表查询",  
        "parameters": {  
            "type": "object",  
            "properties": {  
                "type": {  
                    "type": "string",  
                    "description": "支持类型 top(推荐,默认) guonei(国内) guoji(国际) yule(娱乐) tiyu(体育) junshi(军事) keji(科技) caijing(财经) youxi(游戏) qiche(汽车) jiankang(健康)"  
                },  
                "page": {  
                    "type": "string",  
                    "description": "当前页数, 默认1, 最大50"  
                },  
                "page_size": {  
                    "type": "string",  
                    "description": "每页返回条数, 默认30 , 最大30"  
                },  
                "is_filter": {  
                    "type": "string",  
                    "description": "是否只返回有内容详情的新闻, 1:是, 默认0"  
                }  
            },  
            "required": []  
        }  
    },  
    {  
        "name": "chejian_query",  
        "description": "根据车辆注册日期及类型,计算车辆的下次上线检验时间。本计算结果仅供参考。",  
        "parameters": {  
            "type": "object",  
            "properties": {  
                "type": {  
                    "type": "string",  
                    "description": "车辆类型, 3:9座(含)以下非营运小微型载客汽车(面包车除外) 4:摩托车 7:非营运大型轿车 1:营运车辆 2:货车、大中型客车 6:面包车 5:其他机动车"  
                },  
                "reg_date": {  
                    "type": "string",  
                    "description": "注册登记日期,格式:2022-11-02"  
                },  
                "iis_sg": {  
                    "type": "integer",  
                    "description": "事故情况(是否发生过致人伤亡事故或存在非法改装被依法处罚的交通违法),如是传1"  
                }  
            },  
            "required": [  
                "type",  
                "reg_date"  
            ]  
        }  
    },  
    {  
        "name": "loan_calc_query",  
        "description": "公积金贷款计算器用于计算用户在申请公积金贷款时,选择等额本金和等额本息两种不同的还款方式后,每一期需偿还公积金贷款的月供,以及利息总额和还款总额。",  
        "parameters": {  
            "type": "object",  
            "properties": {  
                "money": {  
                    "type": "integer",  
                    "description": "贷款金额(0 < money <= 500),单位(万),如70表示70万;"  
                },  
                "year": {  
                    "type": "integer",  
                    "description": "贷款年限,单位(年),仅限输入 5、10、15、20、25、30"  
                },  
                "active": {  
                    "type": "string",  
                    "description": "贷款利率,默认3.25"  
                }  
            },  
            "required": [  
                "money",  
                "year"  
            ]  
        }  
    },  
    {  
        "name": "icp_query",  
        "description": "网站icp备案查询",  
        "parameters": {  
            "type": "object",  
            "properties": {  
                "domainName": {  
                    "type": "string",  
                    "description": "获取的域名,如:juhe.cn"  
                }  
            },  
            "required": [  
                "domainName"  
            ]  
        }  
    },  
    {  
        "name": "airport_query",  
        "description": "获取全球机场三字码",  
        "parameters": {  
            "type": "object",  
            "properties": {  
                "airport": {  
                    "type": "string",  
                    "description": "关键词(可匹配城市机场的中英文名称、机场三字码)"  
                },  
                "page": {  
                    "type": "integer",  
                    "description": "页码(默认为1)"  
                },  
                "per_page": {  
                    "type": "integer",  
                    "description": "每页显示数量(默认为20,最大为100)"  
                }  
            },  
            "required": [  
                "airport"  
            ]  
        }  
    },  
    {  
        "name": "aptabnormal_query",  
        "description": "根据机场三字码查询国内机场不正常航班列表",  
        "parameters": {  
            "type": "object",  
            "properties": {  
                "airport": {  
                    "type": "string",  
                    "description": "机场三字码,字母大写(如:PEK),可通过airport_query获取三字码"  
                }  
            },  
            "required": [  
                "airport"  
            ]  
        }  
    }  
]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221

测试问题

请参考工具调用能力测试中的场景列(https://www.yuque.com/mrbun/sgr5h5/hsnz17g1a1wr6k2t#KmgD)

04

预训练模型微调

硬件信息 NVIDIA-4090 24G 单卡
sft超参

CUDA_VISIBLE_DEVICES=0 python src/train_bash.py \   
    --stage sft \   
    --do_train \   
    --model_name_or_path /data/models/Yi-6B \   
    --dataset glaive_toolcall,alpaca_gpt4_en,alpaca_gpt4_zh,oaast_sft_zh \   
    --max_samples 8000 \   
    --template default \   
    --finetuning_type lora \   
    --lora_target q_proj,v_proj \   
    --output_dir yi_agent_checkopint \   
    --lora_target all \   
    --overwrite_cache \   
    --per_device_train_batch_size 1 \   
    --gradient_accumulation_steps 4 \   
    --lr_scheduler_type cosine \   
    --logging_steps 10 \   
    --save_steps 1000 \   
    --learning_rate 5e-5 \   
    --num_train_epochs 2 \   
    --plot_loss \   
    --fp16 \   
    --flash_attn
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

export model

python src/export_model.py \   
    --model_name_or_path /data/models/Yi-6B \   
    --adapter_name_or_path /data/projects/LLaMA-Factory/yi_agent_checkopint \   
    --template default \   
    --finetuning_type lora \   
    --export_dir Yi-Agent-6B-Chat \   
    --export_size 2 \   
    --export_legacy_format False
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

web demo

python src/web_demo.py \   
    --model_name_or_path Yi-Agent-6B-Chat \   
    --template default
  • 1
  • 2
  • 3

测试结果不再赘述。

05

总结

通过SFT微调后可以让原本不具备工具调用能力的模型实现工具调用。通过测试结果可以看出对于复杂场景的效果不是很好,单工具的场景正确率很高,测试的场景是中文场景,训练集中是英文,泛化效果也很不错,我正在准备以下类型数据集,如果有类似的数据集可以在下面贴出连接。

  • API参数描述中需要调用另外一个接口拿到的场景,例如天气查询中的城市id需要调用获取城市idAPI拿到。

  • 对于问题中参数信息不完整,主动抛出问题获取更详细参数信息的场景。

  • 多工具场景。

模型已发布到modelscope Yi-Agent-6B-Chat

(https://modelscope.cn/models/mrsteamedbun/Yi-Agent-6B-Chat/summary)

如何学习大模型 AI ?

由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。

但是具体到个人,只能说是:

“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。

这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。

我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

在这里插入图片描述

第一阶段(10天):初阶应用

该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。

  • 大模型 AI 能干什么?
  • 大模型是怎样获得「智能」的?
  • 用好 AI 的核心心法
  • 大模型应用业务架构
  • 大模型应用技术架构
  • 代码示例:向 GPT-3.5 灌入新知识
  • 提示工程的意义和核心思想
  • Prompt 典型构成
  • 指令调优方法论
  • 思维链和思维树
  • Prompt 攻击和防范

第二阶段(30天):高阶应用

该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。

  • 为什么要做 RAG
  • 搭建一个简单的 ChatPDF
  • 检索的基础概念
  • 什么是向量表示(Embeddings)
  • 向量数据库与向量检索
  • 基于向量检索的 RAG
  • 搭建 RAG 系统的扩展知识
  • 混合检索与 RAG-Fusion 简介
  • 向量模型本地部署

第三阶段(30天):模型训练

恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。

到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?

  • 为什么要做 RAG
  • 什么是模型
  • 什么是模型训练
  • 求解器 & 损失函数简介
  • 小实验2:手写一个简单的神经网络并训练它
  • 什么是训练/预训练/微调/轻量化微调
  • Transformer结构简介
  • 轻量化微调
  • 实验数据集的构建

第四阶段(20天):商业闭环

对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。

  • 硬件选型
  • 带你了解全球大模型
  • 使用国产大模型服务
  • 搭建 OpenAI 代理
  • 热身:基于阿里云 PAI 部署 Stable Diffusion
  • 在本地计算机运行大模型
  • 大模型的私有化部署
  • 基于 vLLM 部署大模型
  • 案例:如何优雅地在阿里云私有部署开源大模型
  • 部署一套开源 LLM 项目
  • 内容安全
  • 互联网信息服务算法备案

学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。

如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/爱喝兽奶帝天荒/article/detail/878913
推荐阅读
相关标签
  

闽ICP备14008679号