File size: 11,814 Bytes
81463e4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
import gradio as gr
import modelscope_studio as mgr
import librosa
from transformers import AutoProcessor, Qwen2AudioForConditionalGeneration
from argparse import ArgumentParser
import requests
import os
from django.http import HttpResponse

# 默认的模型检查点路径
DEFAULT_CKPT_PATH = 'Qwen/Qwen2-Audio-7B-Instruct'

def text_to_speech(text2):
    data = {
        "text": text2,
        "text_language": "zh",
    }
    # 注意 URL 中的单引号应该是 URL 的一部分,需要正确转义
    response = requests.post('http://127.0.0.1:8000', json=data)
    
    if response.status_code == 200:
        audio_file_path = "/root/project/Qwen2-Audio/demo/output.mp3"
        with open(audio_file_path, "wb") as f:
            f.write(response.content)
        return audio_file_path
    else:
        print(f"错误:请求失败,状态码为 {response.status_code}")
        return None

def _get_args():
    """
    解析命令行参数,获取运行配置。

    返回:
        argparse.Namespace: 包含命令行参数的命名空间对象。
    """
    parser = ArgumentParser()
    parser.add_argument("-c", "--checkpoint-path", type=str, default=DEFAULT_CKPT_PATH,
                        help="Checkpoint name or path, default to %(default)r")  # 模型检查点路径
    parser.add_argument("--cpu-only", action="store_true", help="Run demo with CPU only")  # 是否仅使用CPU
    parser.add_argument("--inbrowser", action="store_true", default=False,
                        help="Automatically launch the interface in a new tab on the default browser.")  # 是否在浏览器中自动打开界面
    parser.add_argument("--server-port", type=int, default=15110,
                        help="Demo server port.")  # 指定服务器端口
    parser.add_argument("--server-name", type=str, default="0.0.0.0",
                        help="Demo server name.")  # 指定服务器名称

    args = parser.parse_args()
    return args

def add_text(chatbot, task_history, input):
    """
    将用户输入的文本内容添加到聊天记录中,并更新聊天机器人界面。

    参数:
        chatbot (gr.components.Chatbot): 聊天机器人组件。
        task_history (list): 任务历史记录。
        input (gr.inputs): 用户输入内容。

    返回:
        tuple: 更新后的聊天机器人界面和任务历史记录,以及重置后的用户输入框。
    """
    text_content = input.text  # 获取文本输入内容
    content = []
    if len(input.files) > 0:  # 如果用户上传了音频文件
        for i in input.files:
            content.append({'type': 'audio', 'audio_url': i.path})  # 将音频文件添加到内容列表中
    if text_content:  # 如果用户输入了文本
        content.append({'type': 'text', 'text': text_content})  # 将文本内容添加到内容列表中
    task_history.append({"role": "user", "content": content})  # 更新任务历史记录

    # 更新聊天机器人界面,添加用户输入
    chatbot.append([{
        "text": input.text,
        "files": input.files,
    }, None])
    return chatbot, task_history, None
'''
def add_file(chatbot, task_history, audio_file_path):
    """
    将音频文件添加到聊天记录中。

    参数:
        chatbot (gr.components.Chatbot): 聊天机器人组件。
        task_history (list): 任务历史记录。
        audio_file_path (str): 音频文件的路径。

    返回:
        tuple: 更新后的聊天机器人界面和任务历史记录。
    """
    # 确保任务历史记录中的音频条目是正确的格式
    task_history.append({"role": "user", "content": [{"type": "audio", "audio_url": audio_file_path}]})
    
    # 更新聊天记录,直接使用 audio_file_path 而不是 gr.Audio 组件
    chatbot.append((None, {"type": "audio", "audio_url": audio_file_path}))

    return chatbot, task_history
'''
import os

def add_file(chatbot, task_history, audio_path):
    if not os.path.isfile(audio_path):
        print(f"Error: The file {audio_path} does not exist.")
        return chatbot, task_history

    # 将音频文件信息添加到任务历史
    task_history.append({
        "role": "user",
        "content": [{"type": "audio", "audio_url": audio_path}]
    })

    # 假设 chatbot 组件可以接受字典格式的输入
    chatbot_state = [{
        "text": f"[Audio file: {os.path.basename(audio_path)}]",
        "files": [audio_path]  # 直接使用文件路径而不是 gr.File
    }, None]
    chatbot.append(chatbot_state)  # 更新 chatbot 状态

    return chatbot, task_history

def reset_user_input():
    """
    重置用户输入字段。

    返回:
        gr.update: 将文本框的值重置为空。
    """
    return gr.Textbox.update(value='')

def reset_state(task_history):
    """
    重置聊天记录和任务历史。

    参数:
        task_history (list): 当前的任务历史记录。

    返回:
        tuple: 清空的聊天记录和任务历史。
    """
    return [], []

def regenerate(chatbot, task_history):
    """
    重新生成最后的机器人响应。

    参数:
        chatbot (gr.components.Chatbot): 聊天机器人组件。
        task_history (list): 任务历史记录。

    返回:
        tuple: 更新后的聊天机器人界面和任务历史记录。
    """
    # 如果最后一条消息是助手生成的,则移除它
    if task_history and task_history[-1]['role'] == 'assistant':
        task_history.pop()
        chatbot.pop()
    # 如果任务历史记录不为空,重新生成响应
    if task_history:
        chatbot, task_history = predict(chatbot, task_history)
    return chatbot, task_history

def predict(chatbot, task_history):
    """
    根据当前任务历史记录生成模型响应,并将响应转换为音频文件添加到聊天记录中。

    参数:
        chatbot (gr.components.Chatbot): 聊天机器人组件。
        task_history (list): 任务历史记录。

    返回:
        tuple: 更新后的聊天机器人界面和任务历史记录。
    """
    print(f"{task_history=}")
    print(f"{chatbot=}")

    # 使用处理器将任务历史记录格式化为模型输入
    text = processor.apply_chat_template(task_history, add_generation_prompt=True, tokenize=False)
    audios = []

    # 遍历任务历史,查找音频内容并加载
    for message in task_history:
        if isinstance(message["content"], list):
            for ele in message["content"]:
                if ele["type"] == "audio":
                    audios.append(
                        librosa.load(ele['audio_url'], sr=processor.feature_extractor.sampling_rate)[0]
                    )

    if len(audios) == 0:  # 如果没有音频,则设置为 None
        audios = None
    print(f"{text=}")
    print(f"{audios=}")

    # 使用处理器生成模型输入
    inputs = processor(text=text, audios=audios, return_tensors="pt", padding=True)
    if not _get_args().cpu_only:  # 如果支持 GPU,则将输入数据移动到 CUDA 设备
        inputs["input_ids"] = inputs.input_ids.to("cuda")

    # 生成响应
    generate_ids = model.generate(**inputs, max_length=256)
    generate_ids = generate_ids[:, inputs.input_ids.size(1):]

    # 解码生成的文本响应
    # 假设其他参数已经正确设置
    response = processor.batch_decode(generate_ids, skip_special_tokens=True)[0]
    task_history.append({'role': 'assistant', 'content': response})
    chatbot.append((None, response))  # 添加文本响应

    # 将文本响应转换为语音
    audio_file_path = text_to_speech(response)
    if audio_file_path:
        chatbot, task_history = add_file(chatbot, task_history, audio_file_path)

    return chatbot, task_history

def _launch_demo(args):
    """
    启动Gradio的Web用户界面,展示Qwen2-Audio-Instruct模型的聊天功能。

    参数:
        args (argparse.Namespace): 从命令行解析的参数。
    """
    with gr.Blocks() as demo:
        # 添加页面标题和描述
        gr.Markdown(
            """<p align="center"><img src="https://qianwen-res.oss-cn-beijing.aliyuncs.com/assets/blog/qwenaudio/qwen2audio_logo.png" style="height: 80px"/><p>""")
        gr.Markdown("""<center><font size=8>Qwen2-Audio-Instruct Bot</center>""")
        gr.Markdown(
            """\
    <center><font size=3>This WebUI is based on Qwen2-Audio-Instruct, developed by Alibaba Cloud. \
    (本WebUI基于Qwen2-Audio-Instruct打造,实现聊天机器人功能。)</center>""")
        gr.Markdown("""\
    <center><font size=4>Qwen2-Audio <a href="https://modelscope.cn/models/qwen/Qwen2-Audio-7B">🤖 </a> 
    | <a href="https://huggingface.co/Qwen/Qwen2-Audio-7B">🤗</a>&nbsp | 
    Qwen2-Audio-Instruct <a href="https://modelscope.cn/models/qwen/Qwen2-Audio-7B-Instruct">🤖 </a> | 
    <a href="https://huggingface.co/Qwen/Qwen2-Audio-7B-Instruct">🤗</a>&nbsp | 
    &nbsp<a href="https://github.com/QwenLM/Qwen2-Audio">Github</a></center>""")
        
        # 创建聊天机器人组件
        chatbot = mgr.Chatbot(label='Qwen2-Audio-7B-Instruct', elem_classes="control-height", height=750)

        # 创建用户输入组件,支持文本、麦克风和文件上传
        user_input = mgr.MultimodalInput(
            interactive=True,
            sources=['microphone', 'upload'],
            submit_button_props=dict(value="🚀 Submit (发送)"),
            upload_button_props=dict(value="📁 Upload (上传文件)", show_progress=True),
        )
        task_history = gr.State([])  # 初始化任务历史状态

        with gr.Row():  # 创建清除历史和重试按钮
            empty_bin = gr.Button("🧹 Clear History (清除历史)")
            regen_btn = gr.Button("🤔️ Regenerate (重试)")

        # 当用户提交输入时,调用add_text函数,然后调用predict函数生成响应
        user_input.submit(fn=add_text,
                          inputs=[chatbot, task_history, user_input],
                          outputs=[chatbot, task_history, user_input]).then(
            predict, [chatbot, task_history], [chatbot, task_history], show_progress=True
        )
        # 清除历史按钮的点击事件处理,重置聊天记录和任务历史
        empty_bin.click(reset_state, outputs=[chatbot, task_history], show_progress=True)
        # 重试按钮的点击事件处理,重新生成最后的响应
        regen_btn.click(regenerate, [chatbot, task_history], [chatbot, task_history], show_progress=True)

    # 启动Gradio界面
    demo.queue().launch(
        share=False,  # 不共享URL
        inbrowser=args.inbrowser,  # 是否自动在浏览器中打开
        server_port=args.server_port,  # 指定服务器端口
        server_name=args.server_name,  # 指定服务器名称
        ssl_certfile="/root/project/cert.pem", 
        ssl_keyfile="/root/project/key.pem", 
        ssl_verify=False
    )

if __name__ == "__main__":
    args = _get_args()  # 获取命令行参数
    if args.cpu_only:
        device_map = "cpu"  # 如果指定了仅使用CPU,设置设备映射为CPU
    else:
        device_map = "auto"  # 否则自动选择设备

    # 加载模型
    model = Qwen2AudioForConditionalGeneration.from_pretrained(
        args.checkpoint_path,
        torch_dtype="auto",  # 自动选择数据类型
        device_map=device_map,  # 设置设备映射
        resume_download=True,  # 断点续传
    ).eval()
    model.generation_config.max_new_tokens = 2048  # 设置最大生成token数,用于长对话
    print("generation_config", model.generation_config)
    processor = AutoProcessor.from_pretrained(args.checkpoint_path, resume_download=True)  # 加载处理器
    _launch_demo(args)  # 启动演示界面