diff --git a/.cursor/rules/weclone-rules.mdc b/.cursor/rules/weclone-rules.mdc index df98ef9..26b1a8b 100644 --- a/.cursor/rules/weclone-rules.mdc +++ b/.cursor/rules/weclone-rules.mdc @@ -15,9 +15,3 @@ alwaysApply: true - Unless I ask you to, code comments don't need to be excessive. - Prefer using the encapsulated logger `from weclone.utils.log import logger` for printing. - When retrieving values from a parameter dictionary read from a configuration file, the `get` method should be preferred whenever possible. - - - - - - diff --git a/dataset/res_csv/pt/dataset_info.json b/dataset/res_csv/pt/dataset_info.json index e1ee546..cdd436c 100644 --- a/dataset/res_csv/pt/dataset_info.json +++ b/dataset/res_csv/pt/dataset_info.json @@ -3,4 +3,4 @@ "columns": { "prompt": "c" } -}} \ No newline at end of file +}} diff --git a/dataset/res_csv/sft/dataset_info.json b/dataset/res_csv/sft/dataset_info.json index 3055c42..f1133df 100644 --- a/dataset/res_csv/sft/dataset_info.json +++ b/dataset/res_csv/sft/dataset_info.json @@ -31,4 +31,4 @@ "assistant_tag": "assistant" } } -} \ No newline at end of file +} diff --git a/dataset/test_data-privacy.json b/dataset/test_data-privacy.json index 2ac3119..02461af 100644 --- a/dataset/test_data-privacy.json +++ b/dataset/test_data-privacy.json @@ -221,4 +221,4 @@ "有没有什么特别的收藏?" ] ] -} \ No newline at end of file +} diff --git a/dataset/test_data.json b/dataset/test_data.json index 6f9c386..7358806 100644 --- a/dataset/test_data.json +++ b/dataset/test_data.json @@ -154,4 +154,4 @@ "注意安全啊。" ] ] -} \ No newline at end of file +} diff --git a/ds_config.json b/ds_config.json index 92ba1a4..8a06442 100644 --- a/ds_config.json +++ b/ds_config.json @@ -25,4 +25,4 @@ "train_batch_size": "auto", "train_micro_batch_size_per_gpu": "auto", "wall_clock_breakdown": false -} \ No newline at end of file +} diff --git a/examples/mllm.template.jsonc b/examples/mllm.template.jsonc index 6d1502f..cb5ba94 100644 --- a/examples/mllm.template.jsonc +++ b/examples/mllm.template.jsonc @@ -82,4 +82,4 @@ "max_length": 50, "top_p": 0.65 } -} \ No newline at end of file +} diff --git a/tests/full_pipe.jsonc b/tests/full_pipe.jsonc index e0270a4..2f43e62 100644 --- a/tests/full_pipe.jsonc +++ b/tests/full_pipe.jsonc @@ -86,4 +86,4 @@ "max_length": 50, "top_p": 0.65 } -} \ No newline at end of file +} diff --git a/tests/test_full_pipe.py b/tests/test_full_pipe.py index ea06739..c6c4f0b 100644 --- a/tests/test_full_pipe.py +++ b/tests/test_full_pipe.py @@ -1,12 +1,14 @@ -import pytest -from unittest import mock -import sys +import functools import os import shutil -import functools import subprocess +import sys import time -from typing import Union, Optional, cast +from typing import Optional, Union, cast +from unittest import mock + +import pytest + from weclone.utils.log import logger sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) diff --git a/weclone-audio/src/Llasa/infer.py b/weclone-audio/src/Llasa/infer.py index bde7a4a..1053e80 100644 --- a/weclone-audio/src/Llasa/infer.py +++ b/weclone-audio/src/Llasa/infer.py @@ -1,12 +1,11 @@ import os + import soundfile as sf from text_to_speech import TextToSpeech - sample_audio_text = "对,这就是我万人敬仰的太乙真人,虽然有点婴儿肥,但也掩不住我逼人的帅气。" # 示例音频文本 sample_audio_path = os.path.join(os.path.dirname(__file__), "sample.wav") # 示例音频路径 tts = TextToSpeech(sample_audio_path, sample_audio_text) target_text = "晚上好啊" # 生成目标文本 result = tts.infer(target_text) sf.write(os.path.join(os.path.dirname(__file__), "output.wav"), result[1], result[0]) # 保存生成音频 - diff --git a/weclone-audio/src/Llasa/text_to_speech.py b/weclone-audio/src/Llasa/text_to_speech.py index 2bb468b..9a81d78 100644 --- a/weclone-audio/src/Llasa/text_to_speech.py +++ b/weclone-audio/src/Llasa/text_to_speech.py @@ -1,131 +1,125 @@ -import os -from transformers import AutoTokenizer, AutoModelForCausalLM -import torch -import soundfile as sf -from xcodec2.modeling_xcodec2 import XCodec2Model -import torchaudio - - -class TextToSpeech: - def __init__(self, sample_audio_path, sample_audio_text): - self.sample_audio_text = sample_audio_text - # 初始化模型 - llasa_3b = "HKUSTAudio/Llasa-3B" - xcodec2 = "HKUSTAudio/xcodec2" - - self.tokenizer = AutoTokenizer.from_pretrained(llasa_3b) - self.llasa_3b_model = AutoModelForCausalLM.from_pretrained( - llasa_3b, - trust_remote_code=True, - device_map="auto", - ) - self.llasa_3b_model.eval() - - self.xcodec_model = XCodec2Model.from_pretrained(xcodec2) - self.xcodec_model.eval().cuda() - - # 处理音频 - waveform, sample_rate = torchaudio.load(sample_audio_path) - if len(waveform[0]) / sample_rate > 15: - print("已将音频裁剪至前15秒。") - waveform = waveform[:, : sample_rate * 15] - - # 检查音频是否为立体声 - if waveform.size(0) > 1: - waveform_mono = torch.mean(waveform, dim=0, keepdim=True) - else: - waveform_mono = waveform - - self.prompt_wav = torchaudio.transforms.Resample( - orig_freq=sample_rate, new_freq=16000 - )(waveform_mono) - - # Encode the prompt wav - vq_code_prompt = self.xcodec_model.encode_code(input_waveform=self.prompt_wav) - vq_code_prompt = vq_code_prompt[0, 0, :] - self.speech_ids_prefix = self.ids_to_speech_tokens(vq_code_prompt) - self.speech_end_id = self.tokenizer.convert_tokens_to_ids("<|SPEECH_GENERATION_END|>") - - def ids_to_speech_tokens(self, speech_ids): - speech_tokens_str = [] - for speech_id in speech_ids: - speech_tokens_str.append(f"<|s_{speech_id}|>") - return speech_tokens_str - - def extract_speech_ids(self, speech_tokens_str): - speech_ids = [] - for token_str in speech_tokens_str: - if token_str.startswith("<|s_") and token_str.endswith("|>"): - num_str = token_str[4:-2] - num = int(num_str) - speech_ids.append(num) - else: - print(f"Unexpected token: {token_str}") - return speech_ids - - @torch.inference_mode() - def infer(self, target_text): - if len(target_text) == 0: - return None - elif len(target_text) > 300: - print("文本过长,请保持在300字符以内。") - target_text = target_text[:300] - - input_text = self.sample_audio_text + " " + target_text - - formatted_text = ( - f"<|TEXT_UNDERSTANDING_START|>{input_text}<|TEXT_UNDERSTANDING_END|>" - ) - - chat = [ - { - "role": "user", - "content": "Convert the text to speech:" + formatted_text, - }, - { - "role": "assistant", - "content": "<|SPEECH_GENERATION_START|>" - + "".join(self.speech_ids_prefix), - }, - ] - - input_ids = self.tokenizer.apply_chat_template( - chat, tokenize=True, return_tensors="pt", continue_final_message=True - ) - input_ids = input_ids.to("cuda") - - outputs = self.llasa_3b_model.generate( - input_ids, - max_length=2048, - eos_token_id=self.speech_end_id, - do_sample=True, - top_p=1, - temperature=0.8, - ) - generated_ids = outputs[0][input_ids.shape[1] - len(self.speech_ids_prefix): -1] - - speech_tokens = self.tokenizer.batch_decode( - generated_ids, skip_special_tokens=True - ) - - speech_tokens = self.extract_speech_ids(speech_tokens) - speech_tokens = torch.tensor(speech_tokens).cuda().unsqueeze(0).unsqueeze(0) - - gen_wav = self.xcodec_model.decode_code(speech_tokens) - gen_wav = gen_wav[:, :, self.prompt_wav.shape[1]:] - - return (16000, gen_wav[0, 0, :].cpu().numpy()) - - -if __name__ == "__main__": - # 如果遇到问题,请尝试将参考音频转换为WAV或MP3格式,将其裁剪至15秒以内,并缩短提示文本。 - sample_audio_text = "对,这就是我万人敬仰的太乙真人,虽然有点婴儿肥,但也掩不住我逼人的帅气。" - sample_audio_path = os.path.join(os.path.dirname(__file__), "sample.wav") - - tts = TextToSpeech(sample_audio_path, sample_audio_text) - target_text = "晚上好啊,吃了吗您" - result = tts.infer(target_text) - sf.write(os.path.join(os.path.dirname(__file__), "output.wav"), result[1], result[0]) - target_text = "我是老北京正黄旗!" - result = tts.infer(target_text) - sf.write(os.path.join(os.path.dirname(__file__), "output1.wav"), result[1], result[0]) +import os + +import soundfile as sf +import torch +import torchaudio +from transformers import AutoModelForCausalLM, AutoTokenizer +from xcodec2.modeling_xcodec2 import XCodec2Model + + +class TextToSpeech: + def __init__(self, sample_audio_path, sample_audio_text): + self.sample_audio_text = sample_audio_text + # 初始化模型 + llasa_3b = "HKUSTAudio/Llasa-3B" + xcodec2 = "HKUSTAudio/xcodec2" + + self.tokenizer = AutoTokenizer.from_pretrained(llasa_3b) + self.llasa_3b_model = AutoModelForCausalLM.from_pretrained( + llasa_3b, + trust_remote_code=True, + device_map="auto", + ) + self.llasa_3b_model.eval() + + self.xcodec_model = XCodec2Model.from_pretrained(xcodec2) + self.xcodec_model.eval().cuda() + + # 处理音频 + waveform, sample_rate = torchaudio.load(sample_audio_path) + if len(waveform[0]) / sample_rate > 15: + print("已将音频裁剪至前15秒。") + waveform = waveform[:, : sample_rate * 15] + + # 检查音频是否为立体声 + if waveform.size(0) > 1: + waveform_mono = torch.mean(waveform, dim=0, keepdim=True) + else: + waveform_mono = waveform + + self.prompt_wav = torchaudio.transforms.Resample(orig_freq=sample_rate, new_freq=16000)(waveform_mono) + + # Encode the prompt wav + vq_code_prompt = self.xcodec_model.encode_code(input_waveform=self.prompt_wav) + vq_code_prompt = vq_code_prompt[0, 0, :] + self.speech_ids_prefix = self.ids_to_speech_tokens(vq_code_prompt) + self.speech_end_id = self.tokenizer.convert_tokens_to_ids("<|SPEECH_GENERATION_END|>") + + def ids_to_speech_tokens(self, speech_ids): + speech_tokens_str = [] + for speech_id in speech_ids: + speech_tokens_str.append(f"<|s_{speech_id}|>") + return speech_tokens_str + + def extract_speech_ids(self, speech_tokens_str): + speech_ids = [] + for token_str in speech_tokens_str: + if token_str.startswith("<|s_") and token_str.endswith("|>"): + num_str = token_str[4:-2] + num = int(num_str) + speech_ids.append(num) + else: + print(f"Unexpected token: {token_str}") + return speech_ids + + @torch.inference_mode() + def infer(self, target_text): + if len(target_text) == 0: + return None + elif len(target_text) > 300: + print("文本过长,请保持在300字符以内。") + target_text = target_text[:300] + + input_text = self.sample_audio_text + " " + target_text + + formatted_text = f"<|TEXT_UNDERSTANDING_START|>{input_text}<|TEXT_UNDERSTANDING_END|>" + + chat = [ + { + "role": "user", + "content": "Convert the text to speech:" + formatted_text, + }, + { + "role": "assistant", + "content": "<|SPEECH_GENERATION_START|>" + "".join(self.speech_ids_prefix), + }, + ] + + input_ids = self.tokenizer.apply_chat_template( + chat, tokenize=True, return_tensors="pt", continue_final_message=True + ) + input_ids = input_ids.to("cuda") + + outputs = self.llasa_3b_model.generate( + input_ids, + max_length=2048, + eos_token_id=self.speech_end_id, + do_sample=True, + top_p=1, + temperature=0.8, + ) + generated_ids = outputs[0][input_ids.shape[1] - len(self.speech_ids_prefix) : -1] + + speech_tokens = self.tokenizer.batch_decode(generated_ids, skip_special_tokens=True) + + speech_tokens = self.extract_speech_ids(speech_tokens) + speech_tokens = torch.tensor(speech_tokens).cuda().unsqueeze(0).unsqueeze(0) + + gen_wav = self.xcodec_model.decode_code(speech_tokens) + gen_wav = gen_wav[:, :, self.prompt_wav.shape[1] :] + + return (16000, gen_wav[0, 0, :].cpu().numpy()) + + +if __name__ == "__main__": + # 如果遇到问题,请尝试将参考音频转换为WAV或MP3格式,将其裁剪至15秒以内,并缩短提示文本。 + sample_audio_text = "对,这就是我万人敬仰的太乙真人,虽然有点婴儿肥,但也掩不住我逼人的帅气。" + sample_audio_path = os.path.join(os.path.dirname(__file__), "sample.wav") + + tts = TextToSpeech(sample_audio_path, sample_audio_text) + target_text = "晚上好啊,吃了吗您" + result = tts.infer(target_text) + sf.write(os.path.join(os.path.dirname(__file__), "output.wav"), result[1], result[0]) + target_text = "我是老北京正黄旗!" + result = tts.infer(target_text) + sf.write(os.path.join(os.path.dirname(__file__), "output1.wav"), result[1], result[0]) diff --git a/weclone-audio/src/SparkTTS.py b/weclone-audio/src/SparkTTS.py index e12bb17..948ae42 100644 --- a/weclone-audio/src/SparkTTS.py +++ b/weclone-audio/src/SparkTTS.py @@ -1,14 +1,16 @@ -import re -import torch -from typing import Tuple -from pathlib import Path -from transformers import AutoTokenizer, AutoModelForCausalLM import os +import re import sys +from pathlib import Path +from typing import Tuple + +import torch +from transformers import AutoModelForCausalLM, AutoTokenizer + sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "./Spark-TTS"))) -from sparktts.utils.file import load_config from sparktts.models.audio_tokenizer import BiCodecTokenizer -from sparktts.utils.token_parser import LEVELS_MAP, GENDER_MAP, TASK_TOKEN_MAP +from sparktts.utils.file import load_config +from sparktts.utils.token_parser import GENDER_MAP, LEVELS_MAP, TASK_TOKEN_MAP class SparkTTS: @@ -55,18 +57,12 @@ class SparkTTS: Tuple[str, torch.Tensor]: Input prompt; global tokens """ - global_token_ids, semantic_token_ids = self.audio_tokenizer.tokenize( - prompt_speech_path - ) - global_tokens = "".join( - [f"<|bicodec_global_{i}|>" for i in global_token_ids.squeeze()] - ) + global_token_ids, semantic_token_ids = self.audio_tokenizer.tokenize(prompt_speech_path) + global_tokens = "".join([f"<|bicodec_global_{i}|>" for i in global_token_ids.squeeze()]) # Prepare the input tokens for the model if prompt_text is not None: - semantic_tokens = "".join( - [f"<|bicodec_semantic_{i}|>" for i in semantic_token_ids.squeeze()] - ) + semantic_tokens = "".join([f"<|bicodec_semantic_{i}|>" for i in semantic_token_ids.squeeze()]) inputs = [ TASK_TOKEN_MAP["tts"], "<|start_content|>", @@ -125,9 +121,7 @@ class SparkTTS: speed_label_tokens = f"<|speed_label_{speed_level_id}|>" gender_tokens = f"<|gender_{gender_id}|>" - attribte_tokens = "".join( - [gender_tokens, pitch_label_tokens, speed_label_tokens] - ) + attribte_tokens = "".join([gender_tokens, pitch_label_tokens, speed_label_tokens]) control_tts_inputs = [ TASK_TOKEN_MAP["controllable_tts"], @@ -175,9 +169,7 @@ class SparkTTS: prompt = self.process_prompt_control(gender, pitch, speed, text) else: - prompt, global_token_ids = self.process_prompt( - text, prompt_speech_path, prompt_text - ) + prompt, global_token_ids = self.process_prompt(text, prompt_speech_path, prompt_text) model_inputs = self.tokenizer([prompt], return_tensors="pt").to(self.device) # Generate speech using the model @@ -192,8 +184,7 @@ class SparkTTS: # Trim the output tokens to remove the input tokens generated_ids = [ - output_ids[len(input_ids):] - for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids) + output_ids[len(input_ids) :] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids) ] # Decode the generated tokens into text @@ -201,9 +192,7 @@ class SparkTTS: # Extract semantic token IDs from the generated text pred_semantic_ids = ( - torch.tensor([int(token) for token in re.findall(r"bicodec_semantic_(\d+)", predicts)]) - .long() - .unsqueeze(0) + torch.tensor([int(token) for token in re.findall(r"bicodec_semantic_(\d+)", predicts)]).long().unsqueeze(0) ) if gender is not None: diff --git a/weclone-audio/src/get_sample_audio.py b/weclone-audio/src/get_sample_audio.py index 8e53541..4d2d89d 100644 --- a/weclone-audio/src/get_sample_audio.py +++ b/weclone-audio/src/get_sample_audio.py @@ -1,18 +1,20 @@ -import os import argparse +import os + from pywxdump.db import MediaHandler + def main(): parser = argparse.ArgumentParser(description="Extract audio from WeChat database") - parser.add_argument("--db-path", type=str, required=True, - help="Path to WeChat database file") - parser.add_argument("--MsgSvrID", type=str, required=True, - help="Message server ID of the audio") - parser.add_argument("--save-path", type=str, - default=os.path.join(os.path.dirname(__file__), "sample.wav"), - help="Path to save the audio file (default: sample.wav in script directory)") - parser.add_argument("--rate", type=int, default=24000, - help="Sample rate for audio conversion (default: 24000)") + parser.add_argument("--db-path", type=str, required=True, help="Path to WeChat database file") + parser.add_argument("--MsgSvrID", type=str, required=True, help="Message server ID of the audio") + parser.add_argument( + "--save-path", + type=str, + default=os.path.join(os.path.dirname(__file__), "sample.wav"), + help="Path to save the audio file (default: sample.wav in script directory)", + ) + parser.add_argument("--rate", type=int, default=24000, help="Sample rate for audio conversion (default: 24000)") args = parser.parse_args() @@ -31,5 +33,6 @@ def main(): rate=args.rate, ) + if __name__ == "__main__": main() diff --git a/weclone-audio/src/infer.py b/weclone-audio/src/infer.py index 55094d5..10e3f9d 100644 --- a/weclone-audio/src/infer.py +++ b/weclone-audio/src/infer.py @@ -1,7 +1,7 @@ import os + import soundfile as sf import torch - from SparkTTS import SparkTTS model = SparkTTS("weclone-audio/pretrained_models/Spark-TTS-0.5B", "cuda") diff --git a/weclone-audio/src/server未完工/.env.example b/weclone-audio/src/server未完工/.env.example index 627d413..71ea56e 100644 --- a/weclone-audio/src/server未完工/.env.example +++ b/weclone-audio/src/server未完工/.env.example @@ -11,4 +11,4 @@ REQUIRE_API_KEY=True REMOVE_FILTER=False -EXPAND_API=True \ No newline at end of file +EXPAND_API=True diff --git a/weclone-audio/src/server未完工/handle_text.py b/weclone-audio/src/server未完工/handle_text.py index c4fba21..167b3bf 100644 --- a/weclone-audio/src/server未完工/handle_text.py +++ b/weclone-audio/src/server未完工/handle_text.py @@ -1,6 +1,8 @@ import re + import emoji + def prepare_tts_input_with_context(text: str) -> str: """ Prepares text for a TTS API by cleaning Markdown and adding minimal contextual hints diff --git a/weclone-audio/src/server未完工/requirements.txt b/weclone-audio/src/server未完工/requirements.txt index 4019693..f08da66 100644 --- a/weclone-audio/src/server未完工/requirements.txt +++ b/weclone-audio/src/server未完工/requirements.txt @@ -2,4 +2,4 @@ flask gevent python-dotenv edge-tts -emoji \ No newline at end of file +emoji diff --git a/weclone-audio/src/server未完工/server.py b/weclone-audio/src/server未完工/server.py index 486ffd3..b11533e 100644 --- a/weclone-audio/src/server未完工/server.py +++ b/weclone-audio/src/server未完工/server.py @@ -1,13 +1,13 @@ # server.py -from flask import Flask, request, send_file, jsonify -from gevent.pywsgi import WSGIServer -from dotenv import load_dotenv import os +from dotenv import load_dotenv +from flask import Flask, jsonify, request, send_file +from gevent.pywsgi import WSGIServer from handle_text import prepare_tts_input_with_context from tts_handler import generate_speech, get_models, get_voices -from utils import getenv_bool, require_api_key, AUDIO_FORMAT_MIME_TYPES +from utils import AUDIO_FORMAT_MIME_TYPES, getenv_bool, require_api_key app = Flask(__name__) load_dotenv() diff --git a/weclone-audio/src/server未完工/tts_handler.py b/weclone-audio/src/server未完工/tts_handler.py index f243acc..39dafef 100644 --- a/weclone-audio/src/server未完工/tts_handler.py +++ b/weclone-audio/src/server未完工/tts_handler.py @@ -1,10 +1,11 @@ -import edge_tts import asyncio -import tempfile -import subprocess import os +import subprocess +import tempfile from pathlib import Path +import edge_tts + # Language default (environment variable) DEFAULT_LANGUAGE = os.getenv('DEFAULT_LANGUAGE', 'en-US') diff --git a/weclone-audio/src/server未完工/utils.py b/weclone-audio/src/server未完工/utils.py index 7fe9845..c0f3ca3 100644 --- a/weclone-audio/src/server未完工/utils.py +++ b/weclone-audio/src/server未完工/utils.py @@ -1,9 +1,10 @@ # utils.py -from flask import request, jsonify -from functools import wraps import os +from functools import wraps + from dotenv import load_dotenv +from flask import jsonify, request load_dotenv() diff --git a/weclone/cli.py b/weclone/cli.py index 78e97c0..59d6550 100644 --- a/weclone/cli.py +++ b/weclone/cli.py @@ -1,12 +1,13 @@ -import click -import commentjson -from pathlib import Path +import functools import os import sys -import functools +from pathlib import Path + +import click +import commentjson -from weclone.utils.log import logger, capture_output from weclone.utils.config import load_config +from weclone.utils.log import capture_output, logger cli_config: dict | None = None diff --git a/weclone/core/inference/offline_infer.py b/weclone/core/inference/offline_infer.py index be7affa..e417834 100644 --- a/weclone/core/inference/offline_infer.py +++ b/weclone/core/inference/offline_infer.py @@ -1,20 +1,13 @@ -import json from typing import List, Optional, Union - -from llamafactory.data import get_dataset, get_template_and_fix_tokenizer -from llamafactory.extras.constants import IGNORE_INDEX +from llamafactory.data import get_template_and_fix_tokenizer from llamafactory.extras.misc import get_device_count -from llamafactory.extras.packages import is_vllm_available from llamafactory.hparams import get_infer_args from llamafactory.model import load_tokenizer from pydantic import BaseModel -from vllm.sampling_params import GuidedDecodingParams - - from vllm import LLM, SamplingParams from vllm.lora.request import LoRARequest - +from vllm.sampling_params import GuidedDecodingParams # 这里不需要写太好,transforms库后续更新自带vllm diff --git a/weclone/core/inference/online_infer.py b/weclone/core/inference/online_infer.py index 518cf71..8868d5e 100644 --- a/weclone/core/inference/online_infer.py +++ b/weclone/core/inference/online_infer.py @@ -1,26 +1,23 @@ -import json -import time -import requests from openai import OpenAI + class OnlineLLM: - def __init__(self, api_key: str, base_url: str,model_name: str,default_system: str): + def __init__(self, api_key: str, base_url: str, model_name: str, default_system: str): self.api_key = api_key self.base_url = base_url self.model_name = model_name self.default_system = default_system - self.client = OpenAI( - api_key=self.api_key, - base_url=self.base_url - ) + self.client = OpenAI(api_key=self.api_key, base_url=self.base_url) - - def chat(self,prompt_text, - temperature: float = 0.7, - max_tokens: int = 1024, - top_p: float = 0.95, - stream: bool = False, - enable_thinking: bool = False): + def chat( + self, + prompt_text, + temperature: float = 0.7, + max_tokens: int = 1024, + top_p: float = 0.95, + stream: bool = False, + enable_thinking: bool = False, + ): messages = [ {"role": "system", "content": self.default_system}, {"role": "user", "content": prompt_text}, @@ -29,12 +26,10 @@ class OnlineLLM: model=self.model_name, messages=messages, stream=stream, - temperature = temperature, + temperature=temperature, max_tokens=max_tokens, top_p=top_p, - # enable_thinking=enable_thinking 适配Qwen3动态开启推理 - + # enable_thinking=enable_thinking 适配Qwen3动态开启推理 ) return response - diff --git a/weclone/data/chat_parsers/wechat_parser.py b/weclone/data/chat_parsers/wechat_parser.py index 5bdb857..8fc4522 100644 --- a/weclone/data/chat_parsers/wechat_parser.py +++ b/weclone/data/chat_parsers/wechat_parser.py @@ -1,7 +1,8 @@ -import os import argparse -from pathlib import Path +import os import shutil +from pathlib import Path + import pandas as pd from tqdm import tqdm diff --git a/weclone/data/clean/strategies.py b/weclone/data/clean/strategies.py index 81c37df..d540979 100644 --- a/weclone/data/clean/strategies.py +++ b/weclone/data/clean/strategies.py @@ -1,12 +1,14 @@ import json -import pandas as pd +import os from abc import ABC, abstractmethod from dataclasses import dataclass -from typing import Any, Dict, List, Union +from typing import Any, Dict, List + +import pandas as pd from langchain_core.prompts import PromptTemplate -from weclone.data.models import QaPair, CutMessage, QaPairScore + +from weclone.data.models import QaPair, QaPairScore from weclone.prompts.clean_data import CLEAN_PROMPT -import os from weclone.utils.log import logger diff --git a/weclone/data/models.py b/weclone/data/models.py index 20c9d63..f19370d 100644 --- a/weclone/data/models.py +++ b/weclone/data/models.py @@ -1,8 +1,9 @@ from dataclasses import dataclass from enum import Enum -from typing import Union, Optional + from pandas import Timestamp from pydantic import BaseModel + from weclone.utils.i18n import MultiLangList diff --git a/weclone/data/qa_generator.py b/weclone/data/qa_generator.py index 5696299..1caf5db 100644 --- a/weclone/data/qa_generator.py +++ b/weclone/data/qa_generator.py @@ -1,20 +1,20 @@ +import json import os -import sys -import subprocess -from typing import Dict, List, Union import re +import subprocess +import sys +from typing import List, Union import pandas as pd -import json -from pandas import Timestamp from llamafactory.extras.packages import is_vllm_available +from pandas import Timestamp from weclone.data.clean.strategies import LLMCleaningStrategy from weclone.data.clean.strategies_online import OlineLLMCleaningStrategy +from weclone.data.models import ChatMessage, CutMessage, QaPair, skip_type_list +from weclone.data.strategies import LLMStrategy, TimeWindowStrategy from weclone.utils.config import load_config from weclone.utils.log import logger -from weclone.data.models import ChatMessage, CutMessage, skip_type_list, QaPair -from weclone.data.strategies import TimeWindowStrategy, LLMStrategy class DataProcessor: diff --git a/weclone/data/qa_generatorV2.py b/weclone/data/qa_generatorV2.py index 59cc74a..f827e67 100644 --- a/weclone/data/qa_generatorV2.py +++ b/weclone/data/qa_generatorV2.py @@ -1,27 +1,27 @@ +import json import os -import sys -import subprocess -from typing import Dict, List, Union import re +import subprocess +import sys +from typing import List, Union import pandas as pd -import json from pandas import Timestamp from weclone.data.clean.strategies import LLMCleaningStrategy from weclone.data.clean.strategies_online import OlineLLMCleaningStrategy -from weclone.utils.config import load_config -from weclone.utils.log import logger from weclone.data.models import ( ChatMessage, CutMessage, - skip_type_list, - cut_type_list, - QaPairV2, Message, + QaPairV2, + cut_type_list, + skip_type_list, ) -from weclone.data.strategies import TimeWindowStrategy, LLMStrategy +from weclone.data.strategies import LLMStrategy, TimeWindowStrategy from weclone.data.utils import check_image_file_exists +from weclone.utils.config import load_config +from weclone.utils.log import logger class DataProcessor: diff --git a/weclone/data/strategies.py b/weclone/data/strategies.py index 4a457f1..2476f0a 100644 --- a/weclone/data/strategies.py +++ b/weclone/data/strategies.py @@ -1,7 +1,8 @@ +from abc import ABC, abstractmethod from dataclasses import dataclass from typing import List + from .models import ChatMessage -from abc import ABC, abstractmethod @dataclass @@ -11,9 +12,7 @@ class ConversationStrategy(ABC): is_single_chat: bool @abstractmethod - def is_same_conversation( - self, history_msg: List[ChatMessage], current_msg: ChatMessage - ) -> bool: + def is_same_conversation(self, history_msg: List[ChatMessage], current_msg: ChatMessage) -> bool: """判断两条消息是否属于同一个对话""" pass @@ -24,12 +23,8 @@ class TimeWindowStrategy(ConversationStrategy): time_window: int # 时间窗口(分钟) - def is_same_conversation( - self, history_msg: List[ChatMessage], current_msg: ChatMessage - ) -> bool: - time_diff = abs( - (current_msg.CreateTime - history_msg[-1].CreateTime) - ).total_seconds() + def is_same_conversation(self, history_msg: List[ChatMessage], current_msg: ChatMessage) -> bool: + time_diff = abs((current_msg.CreateTime - history_msg[-1].CreateTime)).total_seconds() return time_diff <= self.time_window @@ -37,9 +32,7 @@ class TimeWindowStrategy(ConversationStrategy): class LLMStrategy(ConversationStrategy): """基于大模型判断策略""" - def is_same_conversation( - self, history_msg: List[ChatMessage], current_msg: ChatMessage - ) -> bool: + def is_same_conversation(self, history_msg: List[ChatMessage], current_msg: ChatMessage) -> bool: # 修复user_id错误,使用talker字段代替user_id return current_msg.talker == history_msg[-1].talker if history_msg else False @@ -51,10 +44,6 @@ class CompositeStrategy(ConversationStrategy): strategies: List[ConversationStrategy] require_all: bool = True # True表示所有策略都满足,False表示任一策略满足即可 - def is_same_conversation( - self, history_msg: List[ChatMessage], current_msg: ChatMessage - ) -> bool: - results = [ - s.is_same_conversation(history_msg, current_msg) for s in self.strategies - ] + def is_same_conversation(self, history_msg: List[ChatMessage], current_msg: ChatMessage) -> bool: + results = [s.is_same_conversation(history_msg, current_msg) for s in self.strategies] return all(results) if self.require_all else any(results) diff --git a/weclone/data/utils.py b/weclone/data/utils.py index 46f91df..085312f 100644 --- a/weclone/data/utils.py +++ b/weclone/data/utils.py @@ -1,5 +1,6 @@ import os from pathlib import Path + from weclone.utils.log import logger @@ -34,5 +35,5 @@ def check_image_file_exists(file_path: str) -> str | bool: if __name__ == "__main__": - path = "Storage\Image\2021-08\6ce3f785b4230246639c3dd0d4a8848c.dat" + path = "Storage\\Image\2021-08\6ce3f785b4230246639c3dd0d4a8848c.dat" print(check_image_file_exists(path)) diff --git a/weclone/eval/test_model.py b/weclone/eval/test_model.py index 59eeb2f..9dc35e6 100644 --- a/weclone/eval/test_model.py +++ b/weclone/eval/test_model.py @@ -1,10 +1,10 @@ import json +from typing import List, cast # 导入 cast + import openai from openai import OpenAI # 导入 OpenAI 类 - +from openai.types.chat import ChatCompletionMessageParam # 导入消息参数类型 from tqdm import tqdm -from typing import List, Dict, cast # 导入 cast -from openai.types.chat import ChatCompletionMessageParam # 导入消息参数类型 from weclone.utils.config import load_config @@ -19,10 +19,7 @@ config = { config = type("Config", (object,), config)() # 初始化 OpenAI 客户端 -client = OpenAI( - api_key="""sk-test""", - base_url="http://127.0.0.1:8005/v1" -) +client = OpenAI(api_key="""sk-test""", base_url="http://127.0.0.1:8005/v1") def handler_text(content: str, history: list, config): @@ -37,14 +34,14 @@ def handler_text(content: str, history: list, config): typed_messages = cast(List[ChatCompletionMessageParam], messages) response = client.chat.completions.create( model=config.model, - messages=typed_messages, # 传递转换后的列表 - max_tokens=50 + messages=typed_messages, # 传递转换后的列表 + max_tokens=50, ) except openai.APIError as e: history.pop() return "AI接口出错,请重试\n" + str(e) - resp = str(response.choices[0].message.content) # type: ignore + resp = str(response.choices[0].message.content) # type: ignore resp = resp.replace("\n ", "") history.append({"role": "assistant", "content": resp}) return resp diff --git a/weclone/eval/web_demo.py b/weclone/eval/web_demo.py index 98ec03e..63af1f0 100644 --- a/weclone/eval/web_demo.py +++ b/weclone/eval/web_demo.py @@ -1,4 +1,5 @@ from llamafactory.webui.interface import create_web_demo + from weclone.utils.config import load_config diff --git a/weclone/server/api_service.py b/weclone/server/api_service.py index 29d8efa..b07d027 100644 --- a/weclone/server/api_service.py +++ b/weclone/server/api_service.py @@ -1,9 +1,10 @@ import os -import uvicorn -from llamafactory.chat import ChatModel -from llamafactory.api.app import create_app -from weclone.utils.config import load_config +import uvicorn +from llamafactory.api.app import create_app +from llamafactory.chat import ChatModel + +from weclone.utils.config import load_config def main(): diff --git a/weclone/train/train_pt.py b/weclone/train/train_pt.py index b7fd547..d4ab046 100644 --- a/weclone/train/train_pt.py +++ b/weclone/train/train_pt.py @@ -1,4 +1,5 @@ from llamafactory.train.tuner import run_exp + from weclone.utils.config import load_config config = load_config("train_pt") diff --git a/weclone/train/train_sft.py b/weclone/train/train_sft.py index 78483d7..c7c5826 100644 --- a/weclone/train/train_sft.py +++ b/weclone/train/train_sft.py @@ -1,11 +1,14 @@ +import json import os import sys -import json -from llamafactory.train.tuner import run_exp + from llamafactory.extras.misc import get_current_device +from llamafactory.train.tuner import run_exp + +from weclone.data.clean.strategies import LLMCleaningStrategy from weclone.utils.config import load_config from weclone.utils.log import logger -from weclone.data.clean.strategies import LLMCleaningStrategy + def main(): train_config = load_config(arg_type="train_sft") diff --git a/weclone/utils/config.py b/weclone/utils/config.py index 06ef6af..2582f1a 100644 --- a/weclone/utils/config.py +++ b/weclone/utils/config.py @@ -1,7 +1,8 @@ import os -import commentjson import sys +import commentjson + from .log import logger from .tools import dict_to_argv diff --git a/weclone/utils/i18n.py b/weclone/utils/i18n.py index 9efee32..78502e4 100644 --- a/weclone/utils/i18n.py +++ b/weclone/utils/i18n.py @@ -1,4 +1,4 @@ -from typing import List, Dict, Optional, Tuple +from typing import Dict, List, Optional class MultiLangList: diff --git a/weclone/utils/length_cdf.py b/weclone/utils/length_cdf.py index f4f8fd5..7d2e17b 100644 --- a/weclone/utils/length_cdf.py +++ b/weclone/utils/length_cdf.py @@ -15,12 +15,12 @@ from collections import defaultdict import fire -from tqdm import tqdm -from weclone.utils.log import logger - from llamafactory.data import get_dataset, get_template_and_fix_tokenizer from llamafactory.hparams import get_train_args from llamafactory.model import load_tokenizer +from tqdm import tqdm + +from weclone.utils.log import logger def length_cdf( diff --git a/weclone/utils/log.py b/weclone/utils/log.py index ad164a8..1ebc71d 100644 --- a/weclone/utils/log.py +++ b/weclone/utils/log.py @@ -1,7 +1,8 @@ -from loguru import logger import sys from functools import wraps +from loguru import logger + logger.remove() logger.add( diff --git a/weclone/utils/tools.py b/weclone/utils/tools.py index 28bc29d..3c2fb11 100644 --- a/weclone/utils/tools.py +++ b/weclone/utils/tools.py @@ -5,5 +5,3 @@ def dict_to_argv(d): if v is not None: argv.append(str(v)) return argv - -