From 3bc0b821a199da5054a149650f828f6e13d80911 Mon Sep 17 00:00:00 2001 From: Teery <96712976+Alexisxty@users.noreply.github.com> Date: Sun, 23 Feb 2025 16:07:33 +0800 Subject: [PATCH] =?UTF-8?q?update:=20=E4=BF=AE=E6=94=B9=E4=BA=86=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E7=BB=84=E4=BB=B6=E7=9A=84=E6=80=A7=E8=83=BD=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E4=BB=A3=E7=A0=81=20(#111)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update: optimize performance test files * update:异步测试方法 --------- Co-authored-by: hrz <1710360675@qq.com> --- core/utils/llm.py | 140 +++++++++--- performance_tester.py | 507 +++++++++++++++++++++++++++--------------- 2 files changed, 440 insertions(+), 207 deletions(-) diff --git a/core/utils/llm.py b/core/utils/llm.py index f48c103d..8fdec09b 100644 --- a/core/utils/llm.py +++ b/core/utils/llm.py @@ -1,5 +1,13 @@ import os import sys +import asyncio +from typing import List, Dict, Any + +# 添加项目根目录到Python路径 +current_dir = os.path.dirname(os.path.abspath(__file__)) +project_root = os.path.abspath(os.path.join(current_dir, "..", "..")) +sys.path.insert(0, project_root) + from config.logger import setup_logging import importlib from datetime import datetime @@ -21,39 +29,115 @@ def create_instance(class_name, *args, **kwargs): raise ValueError(f"不支持的LLM类型: {class_name},请检查该配置的type是否设置正确") -if __name__ == "__main__": +async def test_single_model(llm_name: str, llm_config: Dict[str, Any], test_prompt: str, config: Dict[str, Any]) -> Dict[str, Any]: + """异步测试单个模型""" + try: + # 获取实际的LLM类型 + llm_type = llm_config["type"] if "type" in llm_config else llm_name + llm = create_instance(llm_type, llm_config) + + # 开始测试 + dialogue = [] + dialogue.append({"role": "system", "content": config.get("prompt")}) + dialogue.append({"role": "user", "content": test_prompt}) + + start_time = datetime.now() + llm_responses = llm.response("test", dialogue) + response_message = [] + first_response_time = None + total_response_time = None + start = 0 + full_response = "" + + for content in llm_responses: + response_message.append(content) + full_response += content + + if is_segment(response_message): + segment_text = "".join(response_message[start:]) + segment_text = get_string_no_punctuation_or_emoji(segment_text) + if len(segment_text) > 0: + if first_response_time is None: + first_response_time = (datetime.now() - start_time).total_seconds() + start = len(response_message) + + total_response_time = (datetime.now() - start_time).total_seconds() + + return { + "name": llm_name, + "type": llm_type, + "first_response_time": first_response_time, + "total_response_time": total_response_time, + "response_length": len(full_response), + "status": "成功", + "response": full_response + } + + except Exception as e: + print(f"测试 {llm_name} 时发生错误: {str(e)}") + return { + "name": llm_name, + "type": llm_config.get("type", llm_name), + "first_response_time": None, + "total_response_time": None, + "response_length": 0, + "status": f"失败 - {str(e)}", + "response": "" + } + + +async def main(): """ - 响应速度测试 + LLM模型响应速度测试和排行(异步版本) """ config = read_config(get_project_dir() + "config.yaml") - llm = create_instance( - config["selected_module"]["LLM"] - if not "type" in config["LLM"][config["selected_module"]["LLM"]] - else - config["LLM"][config["selected_module"]["LLM"]]["type"], - config["LLM"][config["selected_module"]["LLM"]] - ) + test_prompt = "你好小智" + + print("开始并发测试所有模型...") + + # 创建所有模型的测试任务 + tasks = [] + for llm_name, llm_config in config["LLM"].items(): + task = asyncio.create_task(test_single_model(llm_name, llm_config, test_prompt, config)) + tasks.append(task) + + # 等待所有测试完成 + test_results = await asyncio.gather(*tasks) + + # 打印测试结果排行榜 + print("\n========= LLM模型性能测试排行榜 =========") + print("测试提示词:", test_prompt) + + # 过滤出成功的结果,并确保数值有效 + successful_results = [r for r in test_results if r["status"] == "成功" and r["first_response_time"] is not None] + + if successful_results: + print("\n1. 首次响应时间排行:") + sorted_by_first = sorted(successful_results, key=lambda x: x["first_response_time"]) + for i, result in enumerate(sorted_by_first, 1): + print(f"{i}. {result['name']}({result['type']}) - {result['first_response_time']:.2f}秒") + print(f" 响应内容: {result['response'][:50]}...") # 只显示前50个字符 - start_time = datetime.now() + print("\n2. 总响应时间排行:") + sorted_by_total = sorted(successful_results, key=lambda x: x["total_response_time"] or float('inf')) + for i, result in enumerate(sorted_by_total, 1): + if result["total_response_time"] is not None: + print(f"{i}. {result['name']}({result['type']}) - {result['total_response_time']:.2f}秒") - dialogue = [] - dialogue.append({"role": "system", "content": config.get("prompt")}) - dialogue.append({"role": "user", "content": "你好小智"}) - llm_responses = llm.response("test", dialogue) - response_message = [] - first_text = None - start = 0 + print("\n3. 响应长度比较:") + sorted_by_length = sorted(successful_results, key=lambda x: x["response_length"], reverse=True) + for i, result in enumerate(sorted_by_length, 1): + print(f"{i}. {result['name']}({result['type']}) - {result['response_length']}字符") + else: + print("\n没有成功完成测试的模型。") - for content in llm_responses: - response_message.append(content) + if len(test_results) != len(successful_results): + print("\n测试失败的模型:") + failed_results = [r for r in test_results if r["status"] != "成功" or r["first_response_time"] is None] + for result in failed_results: + print(f"- {result['name']}({result['type']}): {result['status']}") - if is_segment(response_message): - segment_text = "".join(response_message[start:]) - segment_text = get_string_no_punctuation_or_emoji(segment_text) - if len(segment_text) > 0: - if first_text is None: - first_text = segment_text - print("大模型首次返回耗时:" + str(datetime.now() - start_time)) - start = len(response_message) - print("大模型返回总耗时:" + str(datetime.now() - start_time)) +if __name__ == "__main__": + # 运行异步主函数 + asyncio.run(main()) diff --git a/performance_tester.py b/performance_tester.py index 557cc1e5..8bae2df0 100644 --- a/performance_tester.py +++ b/performance_tester.py @@ -1,27 +1,27 @@ import time +import aiohttp +import asyncio from tabulate import tabulate -from typing import Dict +from typing import Dict, List from core.utils.llm import create_instance as create_llm_instance from core.utils.tts import create_instance as create_tts_instance from core.utils.util import read_config import statistics from config.settings import get_config_file -from concurrent.futures import ThreadPoolExecutor import inspect import os -import requests import logging # 设置全局日志级别为WARNING,抑制INFO级别日志 logging.basicConfig(level=logging.WARNING) -class PerformanceTester: + +class AsyncPerformanceTester: def __init__(self): self.config = read_config(get_config_file()) - # 从配置读取测试句子,如果不存在则使用默认 self.test_sentences = self.config.get("module_test", {}).get( - "test_sentences", - ["你好,请介绍一下你自己", "What's the weather like today?", + "test_sentences", + ["你好,请介绍一下你自己", "What's the weather like today?", "请用100字概括量子计算的基本原理和应用前景"] ) self.results = { @@ -30,258 +30,407 @@ class PerformanceTester: "combinations": [] } - def _test_llm(self, llm_name: str, config: Dict) -> Dict: - """测试单个LLM性能""" + async def _check_ollama_service(self, base_url: str, model_name: str) -> bool: + """异步检查Ollama服务状态""" + async with aiohttp.ClientSession() as session: + try: + # 检查服务是否可用 + async with session.get(f"{base_url}/api/version") as response: + if response.status != 200: + print(f"🚫 Ollama服务未启动或无法访问: {base_url}") + return False + + # 检查模型是否存在 + async with session.get(f"{base_url}/api/tags") as response: + if response.status == 200: + data = await response.json() + models = data.get("models", []) + if not any(model["name"] == model_name for model in models): + print(f"🚫 Ollama模型 {model_name} 未找到,请先使用 ollama pull {model_name} 下载") + return False + else: + print(f"🚫 无法获取Ollama模型列表") + return False + return True + except Exception as e: + print(f"🚫 无法连接到Ollama服务: {str(e)}") + return False + + async def _test_tts(self, tts_name: str, config: Dict) -> Dict: + """异步测试单个TTS性能""" try: - # 跳过未配置密钥的模块 - if "api_key" in config and any(x in config["api_key"] for x in ["你的", "placeholder", "sk-xxx"]): - print(f"🚫 跳过未配置的LLM: {llm_name}") - return {"errors": 1} - + logging.getLogger("core.providers.tts.base").setLevel(logging.WARNING) + + token_fields = ["access_token", "api_key", "token"] + if any(field in config and any(x in config[field] for x in ["你的", "placeholder"]) for field in + token_fields): + print(f"⏭️ TTS {tts_name} 未配置access_token/api_key,已跳过") + return {"name": tts_name, "type": "tts", "errors": 1} + + module_type = config.get('type', tts_name) + tts = create_tts_instance( + module_type, + config, + delete_audio_file=True + ) + + print(f"🎵 测试 TTS: {tts_name}") + + tmp_file = tts.generate_filename() + await tts.text_to_speak("连接测试", tmp_file) + + if not tmp_file or not os.path.exists(tmp_file): + print(f"❌ {tts_name} 连接失败") + return {"name": tts_name, "type": "tts", "errors": 1} + + total_time = 0 + test_count = len(self.test_sentences[:2]) + + for i, sentence in enumerate(self.test_sentences[:2], 1): + start = time.time() + tmp_file = tts.generate_filename() + await tts.text_to_speak(sentence, tmp_file) + duration = time.time() - start + total_time += duration + + if tmp_file and os.path.exists(tmp_file): + print(f"✓ {tts_name} [{i}/{test_count}]") + else: + print(f"✗ {tts_name} [{i}/{test_count}]") + return {"name": tts_name, "type": "tts", "errors": 1} + + return { + "name": tts_name, + "type": "tts", + "avg_time": total_time / test_count, + "errors": 0 + } + + except Exception as e: + print(f"⚠️ {tts_name} 测试失败: {str(e)}") + return {"name": tts_name, "type": "tts", "errors": 1} + + async def _test_llm(self, llm_name: str, config: Dict) -> Dict: + """异步测试单个LLM性能""" + try: + # 对于Ollama,跳过api_key检查并进行特殊处理 + if llm_name == "Ollama": + base_url = config.get('base_url', 'http://localhost:11434') + model_name = config.get('model_name') + if not model_name: + print(f"🚫 Ollama未配置model_name") + return {"name": llm_name, "type": "llm", "errors": 1} + + if not await self._check_ollama_service(base_url, model_name): + return {"name": llm_name, "type": "llm", "errors": 1} + else: + if "api_key" in config and any(x in config["api_key"] for x in ["你的", "placeholder", "sk-xxx"]): + print(f"🚫 跳过未配置的LLM: {llm_name}") + return {"name": llm_name, "type": "llm", "errors": 1} + # 获取实际类型(兼容旧配置) module_type = config.get('type', llm_name) llm = create_llm_instance(module_type, config) - + # 统一使用UTF-8编码 test_sentences = [s.encode('utf-8').decode('utf-8') for s in self.test_sentences] - - total_time = 0 - first_token_times = [] - valid_times = [] - + + # 创建所有句子的测试任务 + sentence_tasks = [] for sentence in test_sentences: - sentence_start = time.time() # 记录整句开始时间 - first_token_received = False - - # 遍历响应流 - for chunk in llm.response("perf_test", [{"role": "user", "content": sentence}]): - if not first_token_received and chunk.strip() != '': - first_token_times.append(time.time() - sentence_start) - first_token_received = True - - # 计算整句耗时 - sentence_duration = time.time() - sentence_start - total_time += sentence_duration - valid_times.append(sentence_duration) - - # 新增有效性检查 - if len(first_token_times) == 0 or len(valid_times) == 0: + sentence_tasks.append(self._test_single_sentence(llm_name, llm, sentence)) + + # 并发执行所有句子测试 + sentence_results = await asyncio.gather(*sentence_tasks) + + # 处理结果 + valid_results = [r for r in sentence_results if r is not None] + if not valid_results: print(f"⚠️ {llm_name} 无有效数据,可能配置错误") - return {"errors": 1} - - # 过滤异常数据(超过3倍标准差) - mean = statistics.mean(valid_times) - stdev = statistics.stdev(valid_times) if len(valid_times) > 1 else 0 - filtered_times = [t for t in valid_times if t <= mean + 3*stdev] - - # 当有效数据不足时标记错误 + return {"name": llm_name, "type": "llm", "errors": 1} + + first_token_times = [r["first_token_time"] for r in valid_results] + response_times = [r["response_time"] for r in valid_results] + + # 过滤异常数据 + mean = statistics.mean(response_times) + stdev = statistics.stdev(response_times) if len(response_times) > 1 else 0 + filtered_times = [t for t in response_times if t <= mean + 3 * stdev] + if len(filtered_times) < len(test_sentences) * 0.5: print(f"⚠️ {llm_name} 有效数据不足,可能网络不稳定") - return {"errors": 1} + return {"name": llm_name, "type": "llm", "errors": 1} return { - "avg_response": total_time / len(test_sentences), - "avg_first_token": sum(first_token_times)/len(first_token_times), + "name": llm_name, + "type": "llm", + "avg_response": sum(response_times) / len(response_times), + "avg_first_token": sum(first_token_times) / len(first_token_times), "std_first_token": statistics.stdev(first_token_times) if len(first_token_times) > 1 else 0, - "std_response": statistics.stdev(valid_times) if len(valid_times) > 1 else 0, + "std_response": statistics.stdev(response_times) if len(response_times) > 1 else 0, "errors": 0 } except Exception as e: print(f"LLM {llm_name} 测试失败: {str(e)}") - return {"errors": 1} + return {"name": llm_name, "type": "llm", "errors": 1} - def _test_tts(self, tts_name: str, config: Dict) -> Dict: - """测试单个TTS性能""" + async def _test_single_sentence(self, llm_name: str, llm, sentence: str) -> Dict: + """测试单个句子的性能""" try: - # 关闭详细日志 - logging.getLogger("core.providers.tts.base").setLevel(logging.WARNING) - - # 跳过未配置密钥的模块 - token_fields = ["access_token", "api_key", "token"] - if any(field in config and any(x in config[field] for x in ["你的", "placeholder"]) for field in token_fields): - print(f"⏭️ TTS {tts_name} 未配置access_token/api_key,已跳过") - return {"errors": 1} - - # 获取实际类型(兼容旧配置) - module_type = config.get('type', tts_name) - tts = create_tts_instance( - module_type, - config, - delete_audio_file=True # 确保参数名称正确 - ) - - # 简化后的输出 - print(f"\n🎵 正在测试 TTS: {tts_name}") - print(f"🔊 测试 {tts_name}:", end="", flush=True) - - # 连接测试 - test_conn = tts.to_tts("连接测试") - if not os.path.exists(test_conn): - print("❌ 连接失败") - return {"errors": 1} - else: - print("✅") - - total_time = 0 - test_count = len(self.test_sentences[:2]) - - for i, sentence in enumerate(self.test_sentences[:2], 1): - start = time.time() - file_path = tts.to_tts(sentence) - duration = time.time() - start - total_time += duration - - # 显示简单的进度标识 - if os.path.exists(file_path): - print(f"✓[{i}/{test_count}]", end="", flush=True) - else: - print(f"✗[{i}/{test_count}]", end="", flush=True) - - print() # 换行 - return {"avg_time": total_time / test_count, "errors": 0} - - except requests.exceptions.ConnectionError: - print(f"\n⛔ {tts_name} 无法连接服务端") - return {"errors": 1} - except Exception as e: - print(f"\n⚠️ {tts_name} 测试失败: {str(e)}") - return {"errors": 1} + print(f"📝 {llm_name} 开始测试: {sentence[:20]}...") + sentence_start = time.time() + first_token_received = False + first_token_time = None - def run(self): - """执行全量测试并自动跳过未配置的模块""" - print("🔍 开始自动检测已配置的模块...") - - # 测试所有LLM - for llm_name, config in self.config.get("LLM", {}).items(): - # 特殊处理CozeLLM的配置检查 - if llm_name == "CozeLLM": - if any(x in config.get("bot_id", "") for x in ["你的"]) \ - or any(x in config.get("user_id", "") for x in ["你的"]): - print(f"⏭️ LLM {llm_name} 未配置bot_id/user_id,已跳过") - continue - # 通用的api_key配置检查 - if "api_key" in config and any(x in config["api_key"] for x in ["你的", "placeholder"]): - print(f"⏭️ LLM {llm_name} 未配置api_key,已跳过") - continue - - print(f"🚀 正在测试 LLM: {llm_name}") - self.results["llm"][llm_name] = self._test_llm(llm_name, config) - - # 测试所有TTS - for tts_name, config in self.config.get("TTS", {}).items(): - # 根据不同服务的token字段检测 - token_fields = ["access_token", "api_key", "token"] - if any(field in config and any(x in config[field] for x in ["你的", "placeholder"]) for field in token_fields): - print(f"⏭️ TTS {tts_name} 未配置access_token/api_key,已跳过") - continue - - print(f"🎵 正在测试 TTS: {tts_name}") - self.results["tts"][tts_name] = self._test_tts(tts_name, config) - - # 生成组合建议 - self._generate_combinations() - self._print_results() + async def process_response(): + nonlocal first_token_received, first_token_time + for chunk in llm.response("perf_test", [{"role": "user", "content": sentence}]): + if not first_token_received and chunk.strip() != '': + first_token_time = time.time() - sentence_start + first_token_received = True + print(f"✓ {llm_name} 首个Token: {first_token_time:.3f}s") + yield chunk + + response_chunks = [] + async for chunk in process_response(): + response_chunks.append(chunk) + + response_time = time.time() - sentence_start + print(f"✓ {llm_name} 完成响应: {response_time:.3f}s") + + if first_token_time is None: + first_token_time = response_time # 如果没有检测到first token,使用总响应时间 + + return { + "name": llm_name, + "type": "llm", + "first_token_time": first_token_time, + "response_time": response_time + } + except Exception as e: + print(f"⚠️ {llm_name} 句子测试失败: {str(e)}") + return None def _generate_combinations(self): """生成最佳组合建议""" - # 调整过滤条件,例如设为 >= 0.05 valid_llms = [ - k for k, v in self.results["llm"].items() + k for k, v in self.results["llm"].items() if v["errors"] == 0 and v["avg_first_token"] >= 0.05 ] valid_tts = [k for k, v in self.results["tts"].items() if v["errors"] == 0] + # 找出基准值 + min_first_token = min([self.results["llm"][llm]["avg_first_token"] for llm in valid_llms]) if valid_llms else 1 + min_tts_time = min([self.results["tts"][tts]["avg_time"] for tts in valid_tts]) if valid_tts else 1 + for llm in valid_llms: for tts in valid_tts: - llm_weight = 0.8 if self.results["llm"][llm]["avg_first_token"] < 1.0 else 0.6 - tts_weight = 1 - llm_weight - score = ( - self.results["llm"][llm]["avg_first_token"] * llm_weight + - self.results["tts"][tts]["avg_time"] * tts_weight - ) + # 计算相对性能分数(越小越好) + llm_score = self.results["llm"][llm]["avg_first_token"] / min_first_token + tts_score = self.results["tts"][tts]["avg_time"] / min_tts_time + + # 计算稳定性分数(标准差/平均值,越小越稳定) + llm_stability = self.results["llm"][llm]["std_first_token"] / self.results["llm"][llm][ + "avg_first_token"] + + # 综合得分(考虑性能和稳定性) + # 性能权重0.7,稳定性权重0.3 + llm_final_score = llm_score * 0.7 + llm_stability * 0.3 + + # 总分 = LLM得分(70%) + TTS得分(30%) + total_score = llm_final_score * 0.7 + tts_score * 0.3 + self.results["combinations"].append({ "llm": llm, "tts": tts, - "score": score, + "score": total_score, "details": { "llm_first_token": self.results["llm"][llm]["avg_first_token"], + "llm_stability": llm_stability, "tts_time": self.results["tts"][tts]["avg_time"] } }) - # 按综合得分排序 + # 分数越小越好 self.results["combinations"].sort(key=lambda x: x["score"]) def _print_results(self): - """控制台输出结果""" - # LLM结果表格 + """打印测试结果""" llm_table = [] for name, data in self.results["llm"].items(): if data["errors"] == 0: + stability = data["std_first_token"] / data["avg_first_token"] llm_table.append([ - name, - f"{data['avg_first_token']:.3f}s", - f"{data['avg_response']:.3f}s" + name, # 不需要固定宽度,让tabulate自己处理对齐 + f"{data['avg_first_token']:.3f}秒", + f"{data['avg_response']:.3f}秒", + f"{stability:.3f}" ]) if llm_table: print("\nLLM 性能排行:") print(tabulate( llm_table, - headers=["模块名称", "平均首Token时间", "平均总响应时间"], - tablefmt="github" + headers=["模型名称", "首字耗时", "总耗时", "稳定性"], + tablefmt="github", + colalign=("left", "right", "right", "right"), + disable_numparse=True )) else: print("\n⚠️ 没有可用的LLM模块进行测试。") - # TTS结果表格 tts_table = [] for name, data in self.results["tts"].items(): if data["errors"] == 0: tts_table.append([ - name, - f"{data['avg_time']:.3f}s" + name, # 不需要固定宽度 + f"{data['avg_time']:.3f}秒" ]) if tts_table: print("\nTTS 性能排行:") print(tabulate( tts_table, - headers=["模块名称", "平均合成时间"], - tablefmt="github" + headers=["模型名称", "合成耗时"], + tablefmt="github", + colalign=("left", "right"), + disable_numparse=True )) else: print("\n⚠️ 没有可用的TTS模块进行测试。") - # 最佳组合建议 if self.results["combinations"]: - print("\n推荐配置组合 (综合响应速度):") + print("\n推荐配置组合 (得分越小越好):") combo_table = [] - for combo in self.results["combinations"][:5]: # 显示前5名 + for combo in self.results["combinations"][:5]: combo_table.append([ - f"{combo['llm']} + {combo['tts']}", + f"{combo['llm']} + {combo['tts']}", # 不需要固定宽度 f"{combo['score']:.3f}", - f"{combo['details']['llm_first_token']:.3f}s", - f"{combo['details']['tts_time']:.3f}s" + f"{combo['details']['llm_first_token']:.3f}秒", + f"{combo['details']['llm_stability']:.3f}", + f"{combo['details']['tts_time']:.3f}秒" ]) - + print(tabulate( combo_table, - headers=["组合方案", "综合得分", "LLM首Token", "TTS合成"], - tablefmt="github" + headers=["组合方案", "综合得分", "LLM首字耗时", "稳定性", "TTS合成耗时"], + tablefmt="github", + colalign=("left", "right", "right", "right", "right"), + disable_numparse=True )) else: print("\n⚠️ 没有可用的模块组合建议。") - def _execute_with_timeout(self, func, args=(), kwargs={}, timeout=None): - with ThreadPoolExecutor(max_workers=1) as executor: - future = executor.submit(func, *args, **kwargs) - try: - result = future.result(timeout) - return list(result) if inspect.isgenerator(result) else result - except TimeoutError: - raise Exception("操作超时") + def _process_results(self, all_results): + """处理测试结果""" + for result in all_results: + if result["errors"] == 0: + if result["type"] == "llm": + self.results["llm"][result["name"]] = result + else: + self.results["tts"][result["name"]] = result + + async def run(self): + """执行全量异步测试""" + print("🔍 开始筛选可用模块...") + + # 创建所有测试任务 + all_tasks = [] + + # LLM测试任务 + for llm_name, config in self.config.get("LLM", {}).items(): + # 检查配置有效性 + if llm_name == "CozeLLM": + if any(x in config.get("bot_id", "") for x in ["你的"]) \ + or any(x in config.get("user_id", "") for x in ["你的"]): + print(f"⏭️ LLM {llm_name} 未配置bot_id/user_id,已跳过") + continue + elif "api_key" in config and any(x in config["api_key"] for x in ["你的", "placeholder", "sk-xxx"]): + print(f"⏭️ LLM {llm_name} 未配置api_key,已跳过") + continue + + # 对于Ollama,先检查服务状态 + if llm_name == "Ollama": + base_url = config.get('base_url', 'http://localhost:11434') + model_name = config.get('model_name') + if not model_name: + print(f"🚫 Ollama未配置model_name") + continue + + if not await self._check_ollama_service(base_url, model_name): + continue + + print(f"📋 添加LLM测试任务: {llm_name}") + module_type = config.get('type', llm_name) + llm = create_llm_instance(module_type, config) + + # 为每个句子创建独立任务 + for sentence in self.test_sentences: + sentence = sentence.encode('utf-8').decode('utf-8') + all_tasks.append(self._test_single_sentence(llm_name, llm, sentence)) + + # TTS测试任务 + for tts_name, config in self.config.get("TTS", {}).items(): + token_fields = ["access_token", "api_key", "token"] + if any(field in config and any(x in config[field] for x in ["你的", "placeholder"]) for field in + token_fields): + print(f"⏭️ TTS {tts_name} 未配置access_token/api_key,已跳过") + continue + print(f"🎵 添加TTS测试任务: {tts_name}") + all_tasks.append(self._test_tts(tts_name, config)) + + print( + f"\n✅ 找到 {len([t for t in all_tasks if 'test_single_sentence' in str(t)]) / len(self.test_sentences):.0f} 个可用LLM模块") + print(f"✅ 找到 {len([t for t in all_tasks if '_test_tts' in str(t)])} 个可用TTS模块") + print("\n⏳ 开始并发测试所有模块...\n") + + # 并发执行所有测试任务 + all_results = await asyncio.gather(*all_tasks, return_exceptions=True) + + # 处理LLM结果 + llm_results = {} + for result in [r for r in all_results if r and isinstance(r, dict) and r.get("type") == "llm"]: + llm_name = result["name"] + if llm_name not in llm_results: + llm_results[llm_name] = { + "name": llm_name, + "type": "llm", + "first_token_times": [], + "response_times": [], + "errors": 0 + } + llm_results[llm_name]["first_token_times"].append(result["first_token_time"]) + llm_results[llm_name]["response_times"].append(result["response_time"]) + + # 计算LLM平均值和标准差 + for llm_name, data in llm_results.items(): + if len(data["first_token_times"]) >= len(self.test_sentences) * 0.5: + self.results["llm"][llm_name] = { + "name": llm_name, + "type": "llm", + "avg_response": sum(data["response_times"]) / len(data["response_times"]), + "avg_first_token": sum(data["first_token_times"]) / len(data["first_token_times"]), + "std_first_token": statistics.stdev(data["first_token_times"]) if len( + data["first_token_times"]) > 1 else 0, + "std_response": statistics.stdev(data["response_times"]) if len(data["response_times"]) > 1 else 0, + "errors": 0 + } + + # 处理TTS结果 + for result in [r for r in all_results if r and isinstance(r, dict) and r.get("type") == "tts"]: + if result["errors"] == 0: + self.results["tts"][result["name"]] = result + + # 生成组合建议并打印结果 + print("\n📊 生成测试报告...") + self._generate_combinations() + self._print_results() + + +async def main(): + tester = AsyncPerformanceTester() + await tester.run() + if __name__ == "__main__": - tester = PerformanceTester() - tester.run() \ No newline at end of file + asyncio.run(main()) \ No newline at end of file