中文网:https://python.langchain.com.cn/docs/get_started/quickstart

本文记录代码比较简单基础。。。

LangChain Evaluation——一个24小时无休的AI质检部门

这个质检部门会做四件关键事情:第一,检查忠实度,防止"幻觉",确保AI说的每句话都有证据支持;第二,评估相关性,防止答非所问,确保回答真正解决了用户的问题;第三,验证正确性,核对事实准确性,就像老师批改作业;第四,判断连贯性,让回答更"人性化",确保逻辑通顺、表达清晰。

LangChain Evaluation 提供了丰富的组件来构建完整的评估系统。核心组件主要包括评估器、指标、数据集和工具链。

最基本的组件是各类评估器。StringEvaluator用于评估字符串输出,比如检查答案的质量。PairwiseStringEvaluator可以比较两个输出的优劣。AgentTrajectoryEvaluator则能评估智能体的整个决策过程。

评估指标组件提供了多维度衡量标准。忠实度指标检查答案是否基于给定上下文。相关性指标衡量回答与问题的匹配程度。正确性指标核对事实准确性。连贯性指标评估语言流畅度。还有上下文相关性指标,专门评估检索内容的质量。

数据集组件支持评估数据的准备和管理。QADataSet用于问答评估数据,ConversationDataSet处理对话数据。这些数据集可以轻松加载、转换和划分。

评估链组件将多个评估步骤串联起来。CriteriaEvalChain允许你定义自定义评估标准。ComparativeEvalChain支持对比评估。这些链可以组合成复杂的评估工作流。

一、即使没有自研模型,也需要回答这些基本问题:

  • 哪个模型更适合我们的客户场景?
  • 提示词优化是否真的有效?
  • 模型响应是否稳定可靠?
  • 如何向客户证明我们的服务质量?

二、简化版评估Tongyi模型框架代码

代码目录结构:(aaa.py、bbb.py......eee.py忽略不计哈)

readme.md

# 通义千问模型评估框架

一个轻量级的评估框架,专门用于评估通义千问系列模型(qwen-plus, qwen-max)在实际业务场景中的表现。无需自研模型,快速上手,帮助团队选择最适合的模型。

## 🚀 快速开始

core文件夹 - 核心功能模块
作用:存放项目的核心业务逻辑和主要功能实现
evaluator.py:模型评估器的核心类
初始化大语言模型(qwen-plus, qwen-max)
提供评估方法(evaluate_models, evaluate)
处理模型调用和结果收集


config文件夹 - 配置管理
作用:管理项目的所有配置信息,特别是敏感信息
• api_keys.py:API密钥配置
• 设置DashScope API密钥
• 创建必要的输出目录
• 提供安全的密钥管理


data 文件夹 - 数据存储
这是专门用来存放项目数据的目录,通常包含:
• 测试用例数据:模型需要评估的各种输入案例
• 配置文件:评估相关的参数设置
• 其他辅助数据:可能用到的参考数据或基准数据
具体文件说明:
test_cases.py
这是一个 Python 脚本文件,用于:
• 定义和管理测试用例
• 加载测试数据
• 提供测试用例接口

evaluation_results 文件夹 - 结果存储
这是专门用来存放评估结果的目录,通常包含:
• 评估报告:模型性能分析结果
• 日志文件:评估过程中的记录
• 输出数据:模型生成的响应结果
• 可视化图表:性能对比图等
生成的文件类型:
• results_YYYYMMDD_HHMMSS.csv - 原始评估结果
• performance_report.html - 可视化报告
• model_comparison.png - 模型性能对比图
• detailed_analysis/ - 详细分析子目录


main.py:程序主入口
• 协调各模块工作流程
• 提供用户友好的命令行界面
• 控制评估流程(配置→准备→评估→报告)
• requirements.txt:依赖管理

2.1 api_keys.py

存储所有API密钥,避免硬编码,密钥不暴露在代码中(我这里还是使用了硬编码,因为不太会写到环境变量里,就直接粘贴的自己的云平台的api密钥,此外还可以将api配置到环境变量里,或者写到.env里)

windows\linux\mac配置环境变量都不一样

#配置文件---API密钥配置
import os
def setup_api_keys():
    """设置DashScope API密钥"""
    # 从环境变量获取API密钥
    api_key = os.getenv("DASHSCOPE_API_KEY")

    if not api_key:
        api_key = "sk-bf6ed781a7de4fab8235bdd6ba1248"
        print("⚠️  DASHSCOPE_API_KEY 环境变量未设置,已使用默认密钥")

    # 设置环境变量为:sk-bf6ed781a7de4fab8235bdd6ba1248
    os.environ["DASHSCOPE_API_KEY"] = api_key

    # 创建结果输出目录
    os.makedirs("evaluation_results", exist_ok=True)

    #打印以确认密钥已配置
    print(f"✅ DashScope API密钥已配置 (前4位: {api_key[:4]}...)")


    # 返回配置状态
    return {"dashscope_configured": True}

注:此处api_key我做了改动,避免暴漏自己的api

2.2 evaluator.py

系统的核心逻辑,负责模型调用和结果收集

解析:先进行初始化模型(qwen-plus、qwen-max),配置好他们的参数(temperature、max-tokens、api-key等等),添加到models列表中,evaluate_models方法用来评估models列表里的每个模型,先在for循环里用invoke方法调用模型,将一系列指标添加到model_results里,最后返回一个字典results,此时调用了_save_results方法,创建了evaluation_results目录,生成了plus和max模型的格式化了的csv、json评估文件,以及可视化的图表和文本报告。

import json
from langchain_community.llms import Tongyi
from typing import List, Dict, Any
from datetime import datetime
import pandas as pd
import os

from utils.reporting import save_evaluation_results, generate_basic_charts, generate_text_report


class SimpleLLMEvaluator:
    """简化版大模型评估框架 - 仅使用通义千问模型"""

    def __init__(self, config=None):
        """初始化支持的模型"""
        # 默认配置
        self.config = config or {
            "models": ["qwen-plus", "qwen-max"],
            "temperature": 0.3,
            "max_tokens": 1024
        }

        # 模型配置
        self.models = {}

        # 添加通义千问模型
        if "qwen-plus" in self.config["models"]:
            try:
                #还是先从环境变量里获取
                api_key = os.getenv("DASHSCOPE_API_KEY")
                if not api_key:
                    api_key = "sk-bf6ed781a7de4fab8235bdd6ba1248eb"

                #创建模型实例-----qwen-plus
                self.models["qwen-plus"] = Tongyi(
                    model_name="qwen-plus",
                    temperature=self.config["temperature"],
                    max_tokens=self.config["max_tokens"],
                    dashscope_api_key=api_key,
                    model_kwargs={"temperature": self.config["temperature"]}
                )
                print(f"✅ 成功初始化 qwen-plus 模型")
            except Exception as e:
                print(f"❌ 初始化 qwen-plus 模型失败: {str(e)}")

        if "qwen-max" in self.config["models"]:
            try:
                api_key = os.getenv("DASHSCOPE_API_KEY")
                if not api_key:
                    api_key = "sk-bf6ed781a7de4fab8235bdd6ba1248eb"

                #创建模型实例-----qwen-max
                self.models["qwen-max"] = Tongyi(
                    model_name="qwen-max",
                    temperature=self.config["temperature"],
                    max_tokens=self.config["max_tokens"],
                    dashscope_api_key=api_key,
                    model_kwargs={"temperature": self.config["temperature"]}
                )
                print(f"✅ 成功初始化 qwen-max 模型")
            except Exception as e:
                print(f"❌ 初始化 qwen-max 模型失败: {str(e)}")

        print(f"✅ 已初始化 {len(self.models)} 个模型: {list(self.models.keys())}")

    #评估所有模型------test_cases: List[Dict[str, Any]]  说明 test_cases 是一个列表,每个元素是字典--------> Dict[str, Any] - 返回值类型,表示这个方法返回一个字典
    def evaluate_models(self, test_cases: List[Dict[str, Any]]) -> Dict[str, Any]:
        """
        评估所有模型在测试用例上的表现
        test_cases: 测试用例列表
        Returns:评估结果字典
        """
        results = {}
        for model_name in self.models.keys():
            print(f"\n🎯 开始评估模型: {model_name}")
            model_results = []

            for idx, case in enumerate(test_cases, start=1):
                case_id = case.get("id", idx)
                try:
                    # 使用 invoke 方法替代 predict 方法
                    prompt = case.get("prompt") or case.get("question")
                    response = self.models[model_name].invoke(prompt)
                    # 计算评估指标(具体的评估逻辑)
                    result = {
                        "case_id": case.get("id", idx),
                        "category": case.get("category", "unknown"),
                        "prompt": prompt,
                        "response": response,
                        "model": model_name,
                        "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                    }
                    model_results.append(result)
                    print(f"   ✅ 完成案例 {case_id} 的评估")
                except Exception as e:
                    case_id = case.get("id", idx)
                    print(f"   ❌ 案例 {case_id} 评估失败: {str(e)}")
                    result = {
                        "case_id": case_id,
                        "category": case.get("category", "unknown"),
                        "prompt": prompt if "prompt" in locals() else "",
                        "response": f"评估失败: {str(e)}",
                        "model": model_name,
                        "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                    }
                    model_results.append(result)
            results[model_name] = model_results
            #在返回前调用保存方法
        print("\n💾 准备保存评估结果...")
        self._save_results(results)
        return results


        #保存结果

    def _save_results(self, results: Dict[str, Any]):
        """保存评估结果并生成可视化报告"""
        try:
            # 创建结果目录
            os.makedirs("evaluation_results", exist_ok=True)

            # 将结果转换为DataFrame格式
            all_results = []
            for model_name, model_results in results.items():
                for result in model_results:
                    all_results.append({
                        "model_name": model_name,
                        "case_id": result["case_id"],
                        "category": result.get("category", "unknown"),
                        "question": result.get("prompt", ""),
                        "model_response": result["response"],
                        "timestamp": result["timestamp"],
                        # 添加模拟指标(实际应替换为真实评估指标)
                        "accuracy_score": len(result["response"]) % 10 / 10,  # 临时模拟数据
                        "completeness_score": (len(result["response"]) % 8) / 10,  # 临时模拟数据
                        "latency_seconds": 0.5 + (len(result["response"]) % 5) * 0.1,  # 临时模拟数据
                        "token_count": len(result["response"]) // 4  # 临时模拟数据
                    })

            # 转换为DataFrame
            results_df = pd.DataFrame(all_results)

            # 保存原始结果
            csv_file, json_file = save_evaluation_results(results_df)

            # 生成可视化图表
            generate_basic_charts(results_df)

            # 生成文本报告
            generate_text_report(results_df)

            print("✅ 可视化报告和图表已成功生成!")

        except Exception as e:
            print(f"❌ 生成可视化内容失败: {str(e)}")
            import traceback
            print(traceback.format_exc())


2.3 test_cases.py

顾名思义,此文件就是用来提供测试用例的,先从file_path里找,如果没有,就使用用户自定义的测试用例test_cases,这里一共有8个。

#数据存储

from typing import List, Dict
import json
import os

def create_basic_test_cases() -> List[Dict]:
    """创建基础测试用例 - 仅使用通义千问模型评估"""
    test_cases = [
        # 客服场景
        {
            "category": "客服问答",
            "question": "你们的产品支持7天无理由退货吗?",
            "reference_answer": "是的,我们提供7天无理由退货服务。商品需保持完好包装,附件齐全,且不影响二次销售。退货时请保留原始购买凭证,通过APP或官网提交退货申请。",
            "evaluation_aspects": ["内容准确性", "回答完整性", "语言流畅度"]
        },
        {
            "category": "客服问答",
            "question": "如何重置我的账户密码?",
            "reference_answer": "您可以通过以下步骤重置密码:1) 在登录页面点击'忘记密码';2) 输入注册邮箱或手机号;3) 按照邮件或短信中的指引完成验证;4) 设置新密码。新密码需包含字母和数字,长度8-20位。",
            "evaluation_aspects": ["步骤完整性", "安全性提示"]
        },

        # 内容创作场景
        {
            "category": "内容创作",
            "question": "为一款新的智能手表写一段50字左右的产品描述。",
            "reference_answer": "全新智能手表,全天候健康监测,精准心率血氧检测。14天超长续航,50米防水,支持100+运动模式。AMOLED高清大屏,智能通知提醒,让科技融入生活每一刻。",
            "evaluation_aspects": ["产品特性覆盖", "字数控制", "吸引力"]
        },
        {
            "category": "内容创作",
            "question": "写一封简短的商务会议邀请邮件,包含时间地点和议程。",
            "reference_answer": "主题:项目进度会议邀请\n\n尊敬的团队成员:\n\n诚邀您参加本周五(15日)下午2点在3楼会议室举行的Q3项目进度会议。议程:1) 各项目组进度汇报(30分钟) 2) 问题讨论与解决方案(20分钟) 3) 下阶段工作安排(10分钟)。\n\n请提前准备相关材料,准时参会。\n\n谢谢!",
            "evaluation_aspects": ["格式规范性", "信息完整性", "专业度"]
        },

        # 信息查询场景
        {
            "category": "信息查询",
            "question": "人工智能对就业市场的主要影响有哪些?",
            "reference_answer": "人工智能对就业市场的影响是双面的:积极方面包括创造新岗位(如AI训练师、数据科学家)、提升生产效率、催生新产业;消极方面可能导致部分重复性工作被替代(如基础客服、数据录入)。长期看,更需要人机协作,人类需提升创造力、情感智能等AI难以替代的能力。",
            "evaluation_aspects": ["观点全面性", "论据合理性", "平衡性"]
        },
        {
            "category": "信息查询",
            "question": "Python中列表和元组的主要区别是什么?",
            "reference_answer": "Python中列表(list)和元组(tuple)的主要区别:1) 可变性:列表可变(可增删改元素),元组不可变;2) 语法:列表用方括号[],元组用圆括号();3) 性能:元组处理速度略快于列表;4) 使用场景:列表适合需要动态变化的数据,元组适合固定不变的数据集合,如函数返回多个值。",
            "evaluation_aspects": ["技术准确性", "清晰度", "实用性"]
        },

        # 商业分析场景
        {
            "category": "商业分析",
            "question": "小型电商企业如何应对大型平台的竞争?",
            "reference_answer": "小型电商企业应对大平台竞争的策略:1) 专注细分市场,提供个性化服务;2) 建立私域流量,通过社群运营增强客户粘性;3) 优化供应链,提供更快的配送体验;4) 与本地商家合作,打造独特商品组合;5) 利用数据驱动决策,精准营销降低成本。关键是找到差异化优势,不与大平台在价格和流量上直接竞争。",
            "evaluation_aspects": ["策略实用性", "全面性", "创新性"]
        },

        # 技术支持场景
        {
            "category": "技术支持",
            "question": "APP无法登录,提示密码错误,但确定密码正确,怎么办?",
            "reference_answer": "如果确认密码正确但仍无法登录,可能是以下原因:1) 账号被临时锁定(尝试15分钟后重试);2) 需要重置密码(点击'忘记密码'完成验证);3) APP版本过旧(请更新至最新版本);4) 网络问题(尝试切换WiFi/移动网络)。如问题持续,请联系客服400-123-4567。",
            "evaluation_aspects": ["问题覆盖全面性", "解决步骤清晰度", "应急联系方式"]
        }
    ]
    for i, case in enumerate(test_cases, start=1):
        case["id"] = i
    return test_cases


def load_custom_test_cases(file_path: str = None) -> List[Dict]:
    """从文件加载自定义测试用例"""
    if file_path and os.path.exists(file_path):
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                return json.load(f)
        except Exception as e:
            print(f"⚠️  加载自定义测试用例失败: {str(e)}")
            print("🔄 使用默认测试用例")

    return create_basic_test_cases()


def save_test_cases(test_cases: List[Dict], file_path: str = "data/custom_test_cases.json"):
    """保存测试用例到文件"""
    os.makedirs(os.path.dirname(file_path), exist_ok=True)
    with open(file_path, 'w', encoding='utf-8') as f:
        json.dump(test_cases, f, ensure_ascii=False, indent=2)
    print(f"✅ 测试用例已保存至: {file_path}")
    return file_path

2.4 reporting.py

它就负责报告、图标等的生成了。

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from datetime import datetime
import os


def generate_basic_charts(results_df: pd.DataFrame, output_dir: str = "evaluation_results"):
    """生成基础可视化图表"""
    if results_df.empty:
        print("⚠️  没有评估结果数据,无法生成图表")
        return

    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

    # 设置中文字体支持
    plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS']  # 用来正常显示中文标签
    plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

    # 创建图表
    plt.figure(figsize=(15, 10))

    # 1. 模型准确率对比
    plt.subplot(2, 2, 1)
    if 'model_name' in results_df.columns and 'accuracy_score' in results_df.columns:
        model_acc = results_df.groupby("model_name")["accuracy_score"].mean()
        bars = plt.bar(model_acc.index, model_acc.values, color=['#1f77b4', '#ff7f0e'][:len(model_acc)])
        plt.title('模型准确率对比', fontsize=14)
        plt.ylabel('准确率', fontsize=12)
        plt.ylim(0, 1.1)

        # 在柱子上添加数值标签
        for bar in bars:
            height = bar.get_height()
            plt.text(bar.get_x() + bar.get_width() / 2., height + 0.02,
                     f'{height:.3f}', ha='center', va='bottom')

    # 2. 模型响应时间对比
    plt.subplot(2, 2, 2)
    if 'model_name' in results_df.columns and 'latency_seconds' in results_df.columns:
        model_latency = results_df.groupby("model_name")["latency_seconds"].mean()
        bars = plt.bar(model_latency.index, model_latency.values, color=['#2ca02c', '#d62728'][:len(model_latency)])
        plt.title('平均响应时间(秒)', fontsize=14)
        plt.ylabel('时间(秒)', fontsize=12)

        # 在柱子上添加数值标签
        for bar in bars:
            height = bar.get_height()
            plt.text(bar.get_x() + bar.get_width() / 2., height + 0.05,
                     f'{height:.2f}', ha='center', va='bottom')

    # 3. 按类别表现
    plt.subplot(2, 2, 3)
    if 'category' in results_df.columns and 'model_name' in results_df.columns and 'accuracy_score' in results_df.columns:
        try:
            category_perf = results_df.groupby(["category", "model_name"])["accuracy_score"].mean().unstack()
            if not category_perf.empty:
                category_perf.plot(kind='bar', ax=plt.gca())
                plt.title('各模型在不同类别上的表现', fontsize=14)
                plt.ylabel('准确率', fontsize=12)
                plt.legend(title='模型')
                plt.xticks(rotation=45, ha='right')
                plt.tight_layout()
        except Exception as e:
            print(f"⚠️  生成类别表现图表时出错: {str(e)}")

    # 4. 准确率与完整度散点图
    plt.subplot(2, 2, 4)
    if 'accuracy_score' in results_df.columns and 'completeness_score' in results_df.columns and 'model_name' in results_df.columns:
        for model in results_df["model_name"].unique():
            model_data = results_df[results_df["model_name"] == model]
            if not model_data.empty:
                plt.scatter(model_data["accuracy_score"],
                            model_data["completeness_score"],
                            label=model,
                            alpha=0.7,
                            s=60)

        plt.title('准确率 vs 完整度', fontsize=14)
        plt.xlabel('准确率', fontsize=12)
        plt.ylabel('完整度', fontsize=12)
        plt.xlim(0, 1.1)
        plt.ylim(0, 1.1)
        plt.legend()
        plt.grid(alpha=0.3)

    plt.tight_layout()
    chart_file = os.path.join(output_dir, f"summary_chart_{timestamp}.png")
    plt.savefig(chart_file, dpi=150, bbox_inches='tight')
    print(f"📈 可视化图表已保存至: {chart_file}")
    plt.close()

    # 生成单个模型的详细图表
    generate_model_detail_charts(results_df, output_dir, timestamp)


def generate_model_detail_charts(results_df: pd.DataFrame, output_dir: str, timestamp: str):
    """为每个模型生成详细图表"""
    for model in results_df["model_name"].unique():
        model_data = results_df[results_df["model_name"] == model]

        if model_data.empty:
            continue

        plt.figure(figsize=(12, 8))

        # 1. 按类别的准确率
        plt.subplot(2, 1, 1)
        category_acc = model_data.groupby("category")["accuracy_score"].mean().sort_values(ascending=False)
        bars = plt.bar(category_acc.index, category_acc.values, color='#1f77b4')
        plt.title(f'{model} 按类别准确率', fontsize=14)
        plt.ylabel('准确率', fontsize=12)
        plt.ylim(0, 1.1)
        plt.xticks(rotation=45, ha='right')

        # 添加数值标签
        for bar in bars:
            height = bar.get_height()
            plt.text(bar.get_x() + bar.get_width() / 2., height + 0.02,
                     f'{height:.2f}', ha='center', va='bottom', fontsize=9)

        # 2. 响应时间分布
        plt.subplot(2, 1, 2)
        plt.hist(model_data["latency_seconds"], bins=10, alpha=0.7, color='#ff7f0e', edgecolor='black')
        plt.title(f'{model} 响应时间分布', fontsize=14)
        plt.xlabel('响应时间(秒)', fontsize=12)
        plt.ylabel('频次', fontsize=12)
        plt.grid(alpha=0.3)

        plt.tight_layout()
        detail_chart_file = os.path.join(output_dir, f"{model}_details_{timestamp}.png")
        plt.savefig(detail_chart_file, dpi=150, bbox_inches='tight')
        print(f"📊 {model} 详细图表已保存至: {detail_chart_file}")
        plt.close()


def generate_text_report(results_df: pd.DataFrame, output_dir: str = "evaluation_results"):
    """生成文本格式报告"""
    if results_df.empty:
        print("⚠️  没有评估结果数据,无法生成报告")
        return

    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    report_file = os.path.join(output_dir, f"evaluation_report_{timestamp}.txt")

    with open(report_file, 'w', encoding='utf-8') as f:
        # 报告标题
        f.write("=" * 60 + "\n")
        f.write("📊 大模型评估报告\n")
        f.write(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
        f.write("=" * 60 + "\n\n")

        # 1. 总体概览
        f.write("## 1. 总体概览\n")
        f.write("-" * 40 + "\n")
        f.write(f"评估模型数量: {len(results_df['model_name'].unique())}\n")
        f.write(f"测试用例数量: {len(results_df)}\n")
        f.write(f"场景类别数量: {len(results_df['category'].unique())}\n")
        f.write(f"评估时间范围: {results_df['timestamp'].min()} 至 {results_df['timestamp'].max()}\n\n")

        # 2. 模型表现对比
        f.write("## 2. 模型表现对比\n")
        f.write("-" * 40 + "\n")

        model_summary = results_df.groupby("model_name").agg({
            "accuracy_score": "mean",
            "completeness_score": "mean",
            "latency_seconds": "mean",
            "token_count": "mean"
        }).round(3)

        f.write("指标说明:\n")
        f.write("  - 准确率: 模型回答与参考答案的关键信息匹配度 (0-1)\n")
        f.write("  - 完整度: 回答内容的完整性和适当长度 (0-1)\n")
        f.write("  - 响应时间: 模型生成回答所需时间(秒)\n")
        f.write("  - Token数量: 生成回答的近似token数量\n\n")

        f.write("模型性能汇总:\n")
        # 手动创建表格
        columns = model_summary.columns.tolist()
        header = " | ".join([f"{col:<15}" for col in columns])
        separator = "-" * len(header)

        f.write(f"{'模型':<15} | {header}\n")
        f.write(f"{'-' * 15}-+-{'-' * len(header)}\n")

        for index, row in model_summary.iterrows():
            row_str = " | ".join([f"{str(val):<15}" for val in row.values])
            f.write(f"{index:<15} | {row_str}\n")

        f.write("\n")

        # 3. 识别最佳模型
        best_model_acc = model_summary["accuracy_score"].idxmax()
        best_model_latency = model_summary["latency_seconds"].idxmin()

        f.write("🏆 性能最佳模型:\n")
        f.write(f"  - 准确率最佳: {best_model_acc} ({model_summary.loc[best_model_acc, 'accuracy_score']:.3f})\n")
        f.write(
            f"  - 响应最快: {best_model_latency} ({model_summary.loc[best_model_latency, 'latency_seconds']:.3f}秒)\n\n")

        # 4. 各类别表现
        f.write("## 3. 各场景类别表现\n")
        f.write("-" * 40 + "\n")

        category_summary = results_df.groupby("category").agg({
            "accuracy_score": "mean",
            "completeness_score": "mean"
        }).round(3).sort_values("accuracy_score", ascending=False)

        f.write("各场景类别平均表现:\n")
        f.write(category_summary.to_string() + "\n\n")

        # 5. 问题案例分析
        f.write("## 4. 问题案例分析\n")
        f.write("-" * 40 + "\n")

        # 找出表现最差的案例
        worst_cases = results_df.nsmallest(3, "accuracy_score")
        f.write("表现最差的案例 (准确率前三低):\n")
        for _, row in worst_cases.iterrows():
            f.write(f"\n- 场景: {row['category']}\n")
            f.write(f"  模型: {row['model_name']}\n")
            f.write(f"  问题: {row['question']}\n")
            f.write(f"  模型回答: {row['model_response'][:200]}{'...' if len(row['model_response']) > 200 else ''}\n")
            f.write(f"  准确率: {row['accuracy_score']:.3f}, 完整度: {row['completeness_score']:.3f}\n")

        # 6. 使用建议
        f.write("\n## 5. 模型使用建议\n")
        f.write("-" * 40 + "\n")

        if best_model_acc == best_model_latency:
            f.write(f"🌟 综合推荐: {best_model_acc} 在准确率和响应速度上都表现最佳,建议作为主力模型。\n")
        else:
            f.write(f"🎯 准确率优先: {best_model_acc} 适合对准确性要求高的场景(如专业咨询、法律医疗)。\n")
            f.write(f"⚡ 速度优先: {best_model_latency} 适合对响应速度要求高的场景(如实时聊天、简单问答)。\n")
            f.write("💡 混合策略: 可根据场景重要性动态选择模型,核心业务用高准确率模型,普通查询用快速模型。\n")

        f.write("\n## 6. 优化建议\n")
        f.write("-" * 40 + "\n")
        f.write("1. 针对表现差的场景(如上文的问题案例),优化提示词设计\n")
        f.write("2. 为专业领域问题添加领域知识库,提高回答准确性\n")
        f.write("3. 设置响应超时机制,避免单个请求阻塞整个流程\n")
        f.write("4. 定期(如每月)重新评估,跟踪模型性能变化\n")

        f.write("\n" + "=" * 60 + "\n")
        f.write("报告生成完成 - 本报告由自动评估系统生成\n")
        f.write("=" * 60 + "\n")

    print(f"📄 详细文本报告已保存至: {report_file}")
    return report_file


def save_evaluation_results(results_df: pd.DataFrame, prefix: str = "evaluation",
                            output_dir: str = "evaluation_results"):
    """保存评估结果到文件"""
    if results_df.empty:
        print("⚠️  没有评估结果数据,无法保存")
        return None, None

    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    os.makedirs(output_dir, exist_ok=True)

    csv_file = os.path.join(output_dir, f"{prefix}_results_{timestamp}.csv")
    json_file = os.path.join(output_dir, f"{prefix}_detailed_{timestamp}.json")

    results_df.to_csv(csv_file, index=False, encoding='utf_8_sig')
    results_df.to_json(json_file, orient='records', force_ascii=False, indent=2)

    print(f"💾 评估结果已保存:\n   - CSV格式: {csv_file}\n   - JSON格式: {json_file}")

    return csv_file, json_file

2.5 main.py

入口程序,统筹整体评估流程。

# main.py
import time
from config.api_keys import setup_api_keys
from core.evaluator import SimpleLLMEvaluator
from data.test_cases import load_custom_test_cases


def main():
    """主函数"""
    print("🚀 启动通义千问模型评估框架")
    print("=" * 70)

    # 步骤1/4:配置DashScope API密钥
    print("\n🔑 步骤 1/4:配置DashScope API密钥...")
    try:
        setup_api_keys()
    except ValueError as e:
        print(f"⚠️  API密钥配置警告: {str(e)}")

    # 步骤2/4:准备测试用例
    print("\n📋 步骤 2/4:准备测试用例...")
    test_cases = load_custom_test_cases()

    print(
        f"  ✅ 准备了 {len(test_cases)} 个测试用例,涵盖 {len(set([case['category'] for case in test_cases]))} 个业务场景")
    print(f"  📌 测试场景包括: ", ", ".join(set([case['category'] for case in test_cases])))

    # 步骤3/4:初始化模型评估器
    print("\n⚙️ 步骤 3/4:初始化模型评估器...")
    evaluator = SimpleLLMEvaluator(config={
        "models": ["qwen-plus", "qwen-max"],
        "temperature": 0.3,
        "max_tokens": 1024
    })

    # 步骤4/4:开始评估流程
    print("\n🔍 步骤 4/4:开始评估流程(预计需要 180 秒)...")
    start_time = time.time()

    try:
        # 使用正确的评估方法
        results = evaluator.evaluate_models(test_cases)  # 修复方法名称
        end_time = time.time()

        print(f"\n✅ 评估完成!耗时: {end_time - start_time:.2f} 秒")
        print(f"📊 评估结果已生成,包含 {len(results)} 个模型的评估数据")


        # 打印评估摘要
        for model_name, model_results in results.items():
            print(f"\n📈 模型 {model_name} 评估摘要:")
            print(f"   📊 总案例数: {len(model_results)}")
            print(f"   ✅ 成功案例: {sum(1 for r in model_results if '评估失败' not in r.get('response', ''))}")
            print(f"   ❌ 失败案例: {sum(1 for r in model_results if '评估失败' in r.get('response', ''))}")

    except Exception as e:
        print(f"\n❌ 评估过程中出现错误: {str(e)}")
        import traceback
        print(traceback.format_exc())


if __name__ == "__main__":
    main()

三、运行结果

D:\PycharmProjects\Langchain\.venv\Scripts\python.exe D:\PycharmProjects\Langchain\main.py 
🚀 启动通义千问模型评估框架
======================================================================

🔑 步骤 1/4:配置DashScope API密钥...
⚠️  DASHSCOPE_API_KEY 环境变量未设置,已使用默认密钥
✅ DashScope API密钥已配置 (前4位: sk-b...)

📋 步骤 2/4:准备测试用例...
  ✅ 准备了 13 个测试用例,涵盖 9 个业务场景
  📌 测试场景包括:  信息查询, 事实核查, 客服问答, 幻觉检测, 内容创作, 安全性测试, 逻辑测试, 技术支持, 商业分析

⚙️ 步骤 3/4:初始化模型评估器...
✅ 成功初始化 qwen-plus 模型
✅ 成功初始化 qwen-max 模型
✅ 已初始化 2 个模型: ['qwen-plus', 'qwen-max']

🔍 步骤 4/4:开始评估流程(预计需要 180 秒)...

🎯 开始评估模型: qwen-plus
   ✅ 完成案例 1 的评估
   ✅ 完成案例 2 的评估
   ✅ 完成案例 3 的评估
   ✅ 完成案例 4 的评估
   ✅ 完成案例 5 的评估
   ✅ 完成案例 6 的评估
   ✅ 完成案例 7 的评估
   ✅ 完成案例 8 的评估
   ✅ 完成案例 9 的评估
   ✅ 完成案例 10 的评估
   ❌ 案例 11 评估失败: ('Connection aborted.', ConnectionResetError(10054, '远程主机强迫关闭了一个现有的连接。', None, 10054, None))
   ✅ 完成案例 12 的评估
   ✅ 完成案例 13 的评估

🎯 开始评估模型: qwen-max
   ✅ 完成案例 1 的评估
   ✅ 完成案例 2 的评估
   ✅ 完成案例 3 的评估
   ✅ 完成案例 4 的评估
   ✅ 完成案例 5 的评估
   ✅ 完成案例 6 的评估
   ✅ 完成案例 7 的评估
   ✅ 完成案例 8 的评估
   ✅ 完成案例 9 的评估
   ✅ 完成案例 10 的评估
   ✅ 完成案例 11 的评估
   ✅ 完成案例 12 的评估
   ✅ 完成案例 13 的评估

💾 准备保存评估结果...
💾 评估结果已保存:
   - CSV格式: evaluation_results\evaluation_results_20251215_133626.csv
   - JSON格式: evaluation_results\evaluation_detailed_20251215_133626.json
📈 可视化图表已保存至: evaluation_results\summary_chart_20251215_133626.png
📊 qwen-plus 详细图表已保存至: evaluation_results\qwen-plus_details_20251215_133626.png
📊 qwen-max 详细图表已保存至: evaluation_results\qwen-max_details_20251215_133626.png
📄 详细文本报告已保存至: evaluation_results\evaluation_report_20251215_133628.txt
✅ 可视化报告和图表已成功生成!

✅ 评估完成!耗时: 285.28 秒
📊 评估结果已生成,包含 2 个模型的评估数据

📈 模型 qwen-plus 评估摘要:
   📊 总案例数: 13
   ✅ 成功案例: 12
   ❌ 失败案例: 1

📈 模型 qwen-max 评估摘要:
   📊 总案例数: 13
   ✅ 成功案例: 13
   ❌ 失败案例: 0

Process finished with exit code 0

plus有一个评估失败应该是我开了代理的原因,网络不稳定之类的都会影响评估。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐