From 324cf04cc3fe77479cd413e7fd10851a43d1e2c8 Mon Sep 17 00:00:00 2001 From: "DESKTOP-72TV0V4\\caoxiaozhu" Date: Tue, 23 Dec 2025 15:07:19 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E4=BA=86=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 215 ++++++++++++ data/qwen2.5-7b-sft.json | 82 +++++ llm_config.py | 348 ++++++++++++++++++++ model_evaluation.py | 686 +++++++++++++++++++++++++++++++++++++++ requirements.txt | 15 + 5 files changed, 1346 insertions(+) create mode 100644 README.md create mode 100644 data/qwen2.5-7b-sft.json create mode 100644 llm_config.py create mode 100644 model_evaluation.py create mode 100644 requirements.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..69727fe --- /dev/null +++ b/README.md @@ -0,0 +1,215 @@ +# 大模型微调验证系统 + +为模型评估添加了**LLM评分**和**LLM评价理由**两个新指标,结合传统指标(BLEU、ROUGE-L等),提供全面的评估方案。 + +## 核心特性 + +### ✨ 双重评估模式 +1. **模拟评估模式**(默认) + - ✅ 无需配置,立即可用 + - ✅ 基于传统指标的智能评分 + - ✅ 运行速度快,无额外成本 + +2. **真实LLM评估模式** + - 使用OpenAI GPT等大语言模型 + - 提供语义级别的智能评分 + - 详细的评价理由和改进建议 + +### 📊 评估维度 +1. **答案准确性** - 与参考答案的一致性 +2. **逻辑清晰度** - 答案逻辑和条理性 +3. **完整性** - 是否遗漏关键信息 +4. **表达质量** - 语言流畅度和专业性 + +评分范围:1-10分 + +## 快速开始 + +### 1. 安装依赖 +```bash +# 安装核心依赖 +pip install -r requirements.txt + +# 或手动安装 +pip install numpy pandas openpyxl nltk jieba tqdm xlrd + +# 如需使用真实LLM API,安装OpenAI库 +pip install openai +``` + +### 2. 运行评估(模拟模式 - 默认) +```bash +python model_evaluation.py +``` +- ✅ 无需配置,立即可用 +- ✅ 基于传统指标的智能评分 +- ✅ 运行速度快 + +### 3. 启用真实LLM评估 +```bash +# 设置API密钥 +export OPENAI_API_KEY="your-api-key" +export API_BASE_URL="http://10.10.10.122:1234/v1" # 可选 + +# 编辑 llm_config.py,设置 USE_REAL_LLM = True +# 运行评估 +python model_evaluation.py +``` + +### 4. 单个评估测试 +```bash +python -c "from model_evaluation import test_single_evaluation; test_single_evaluation()" +``` + +## 文件结构 + +``` +LLMS_FT_Test/ +├── model_evaluation.py # 主文件(已整合所有评估功能) +├── llm_config.py # 配置文件(API密钥、评估模式等) +├── requirements.txt # 依赖列表 +└── README.md # 本文档 +``` + +## 输出文件 + +- `{文件名}_evaluation.xlsx` - 单个文件评估结果 +- `evaluation_summary.xlsx` - 汇总报告 +- `all_detailed_results.xlsx` - 详细结果 + +## 评估指标 + +### 传统指标(无需LLM API) +- **BLEU分数**:基于n-gram的精确度 +- **ROUGE-L分数**:基于最长公共子序列的F1分数 +- **字符重叠率**:字符级别的重叠程度 +- **长度相似度**:答案长度的相似性 +- **完全匹配率**:完全匹配的比例 +- **关键词重叠率**:关键词的重叠程度 + +### LLM评估(需API) +- **智能评分**:基于语义理解的1-10分评分 +- **评价理由**:详细的评分依据和改进建议 + +## 支持的API + +- **OpenAI API** (GPT-3.5, GPT-4, GPT-4-turbo) +- **兼容OpenAI API的服务**(如自定义部署的API) +- **可扩展支持其他提供商**(Anthropic Claude、通义千问等) + +## 数据格式 + +支持 `.json` 和 `.jsonl` 格式: + +**JSON格式(数组):** +```json +[ + { + "question": "问题内容", + "answer": "参考答案", + "output": "模型输出" + } +] +``` + +**JSONL格式(每行一个JSON):** +```jsonl +{"question": "问题1", "answer": "答案1", "output": "输出1"} +{"question": "问题2", "answer": "答案2", "output": "输出2"} +``` + +## 配置选项 + +编辑 `llm_config.py` 文件: + +```python +# 使用模拟评估(默认,无需配置) +USE_REAL_LLM = False + +# 或使用真实LLM API(需配置API密钥) +USE_REAL_LLM = True +``` + +运行以下命令查看所有配置选项: +```bash +python llm_config.py +``` + +## 自定义评估 + +```python +from model_evaluation import ModelEvaluator +from llm_config import USE_REAL_LLM + +evaluator = ModelEvaluator() + +# 准备数据 +question = "什么是机器学习?" +reference = "机器学习是人工智能的一个分支..." +candidate = "机器学习是一种让计算机学习的方法..." + +# 获取LLM评估 +prompt = evaluator.get_llm_evaluation_prompt(reference, candidate, question) + +if USE_REAL_LLM: + score, reason = evaluator.call_llm_for_evaluation(prompt) +else: + score, reason = evaluator.get_mock_llm_evaluation(reference, candidate, question) + +# 计算传统指标 +metrics = evaluator.evaluate_all_metrics(reference, candidate) +``` + +## 注意事项 + +1. **默认模式**:系统默认使用模拟评估(无需API) +2. **API调用**:真实LLM评估需要有效的API密钥 +3. **并发限制**:批量评估时建议控制并发数,避免API限制 +4. **数据备份**:评估前请备份原始数据 +5. **结果验证**:建议先使用模拟模式验证数据格式 + +## 常见问题 + +**Q: 如何切换到模拟评估模式?** +A: 在 `llm_config.py` 中设置 `USE_REAL_LLM = False` + +**Q: 模拟评估的权重如何调整?** +A: 修改 `llm_config.py` 中的 `EVALUATION_WEIGHTS` 字典 + +**Q: 如何添加新的评估指标?** +A: 在 `ModelEvaluator` 类中添加新的计算方法,并在 `evaluate_all_metrics` 中调用 + +**Q: 支持哪些LLM提供商?** +A: 目前支持OpenAI GPT系列,可扩展支持其他兼容的API + +## 更新日志 + +### v2.0(整合版) +- ✅ 整合所有功能到 `model_evaluation.py` +- ✅ 新增 `test_single_evaluation()` 测试函数 +- ✅ 删除独立的测试文件和示例文件 +- ✅ 保留 `llm_config.py` 作为配置文件 +- ✅ 优化代码结构和导入关系 +- ✅ 精简项目结构,减少重复文件 +- ✅ 合并所有文档到README.md + +### v1.0(原始版) +- 基础评估功能 +- 模拟评估模式 +- 真实LLM评估模式 + +## 许可证 + +本项目采用 MIT 许可证。 + +## 贡献 + +欢迎提交 Issue 和 Pull Request 来改进项目。 + +## 联系方式 + +如有问题或建议,请在项目页面提交 Issue。 + +--- + +⚠️ **重要提醒**:使用真实LLM API会产生费用,建议先用模拟模式测试整个流程。 diff --git a/data/qwen2.5-7b-sft.json b/data/qwen2.5-7b-sft.json new file mode 100644 index 0000000..d45a0ce --- /dev/null +++ b/data/qwen2.5-7b-sft.json @@ -0,0 +1,82 @@ +[ + { + "input": "依据《项目施工合同价款(进度款)结算(成本列账)管理标准》,施工进度款的合同结算审批环节的责任人具体权责事项是什么?", + "answer": "用户的问题是询问施工进度款合同结算审批环节责任人的具体权责事项。首先,需在表格中找到二级流程为“合同结算审批”的条目,确定责任人后提取其权责事项。\n查看表格,该条目责任人是业务分管领导,具体权责事项为“1.负责根据《项目施工进度款支付报审表》审核、审查意见,按照合同结算审批权限,完成合同结算审批”。\n确认该条目与问题相关,提取上述信息。答案需简洁,忠于原文。\n总结:筛选对应条目,提取责任人的具体权责事项。\n依据《项目施工合同价款(进度款)结算(成本列账)管理标准》,在施工进度款的合同结算审批环节,责任人(业务分管领导)的具体权责事项为:负责根据《项目施工进度款支付报审表》审核、审查意见,按照合同结算审批权限,完成合同结算审批。", + "output": "依据《项目施工合同价款(进度款)结算(成本列账)管理标准》,在施工进度款的合同结算审批环节,责任人的具体权责事项是:1. 负责根据《项目施工进度款支付报审表》审核、审查意见,按照合同结算审批权限,完成合同结算审批。" + }, + { + "input": "在《项目施工预付款支付报审表》中,承包单位能否使用财务专用章进行签章?", + "answer": "用户的问题是想知道承包单位是否可以用财务专用章签章,需要从表单合规性标准中寻找答案。\n首先,明确问题意图是询问特定印章的使用权限,应关注“表单合规性标准”中关于签章的条款。\n查看“表单合规性标准”第(8)条:“承包单位签章必须为单位公章或项目部专用章或合同专用章,不得使用财务专用章等其他类签章替代”。其中明确不允许使用财务专用章,与问题直接相关,其他条款未涉及,属于相关信息。\n提取该内容后,简洁回答。\n总结:问题针对能否使用财务专用章签章,对应合规性标准第(8)条,提取相关内容即可。\n在《项目施工预付款支付报审表》中,承包单位不能使用财务专用章进行签章,其签章必须为单位公章或项目部专用章或合同专用章。", + "output": "不能使用财务专用章进行签章。承包单位应使用单位公章或项目部专用章进行签章。" + }, + { + "input": "依据施工结算款的内容,资金支付申请环节(项目实施部门专责)的流程载体是什么?", + "answer": "用户的问题是询问施工结算款中资金支付申请环节(项目实施部门专责)的流程载体。首先,需在表格中找到一级流程为“资金支付审批流程(价款结算业务财权审批流程)”、二级流程为“资金支付申请”、责任部门为“项目实施部门”且责任人为“项目实施部门专责”的条目。\n查看表格内容,该条目下的“流程载体”部分有明确说明。\n提取该内容,确认与问题中的环节、责任部门和责任人匹配,其他条目不涉及此情况。\n答案需直接引用原文,简洁明了。\n总结:找到对应条目后提取流程载体即可。\n依据《项目施工合同价款(结算款)结算(成本列账)管理标准》,在资金支付申请环节,项目实施部门专责的流程载体是工程业务支付申请单。", + "output": "依据施工结算款的内容,在资金支付申请环节(项目实施部门专责),流程载体是工程业务支付申请单。" + }, + { + "input": "在水电(燃料动力)费结算款中,资金支付审核阶段的价款结算类型是什么?", + "answer": "用户的问题是询问在水电(燃料动力)费结算款的资金支付审核阶段的价款结算类型。首先,需要找到二级流程为资金支付审核的条目。\n可能关联的内容模块是第三个和第四个条目,这两个条目二级流程均为资金支付审核,价款结算类型均为水电(燃料动力)费结算款。\n提取该价款结算类型信息,与问题相关,没有无关内容。\n生成答案时,直接陈述价款结算类型,语气简洁。\n总结:找到对应条目后,提取价款结算类型信息即为答案。\n在水电(燃料动力)费结算款中,资金支付审核阶段的价款结算类型是水电(燃料动力)费结算款。", + "output": "依据《项目“七项”费用_项目水电费_项目水电费结算管理标准》中关于水电(燃料动力)费结算款的内容,分析资金支付审核阶段的价款结算类型。\n相关内容:该阶段的价款结算类型是“水电(燃料动力)费”。\n生成答案:在水电(燃料动力)费结算款中,资金支付审核阶段的价款结算类型是“水电(燃料动力)费”。" + }, + { + "input": "依据水电(燃料动力)费结算款的内容,会计核算环节的审批依据有哪些?", + "answer": "用户的问题是依据水电(燃料动力)费结算款的内容,询问会计核算环节的审批依据。首先,定位到二级流程为“会计核算(生成及传递付款指令)”的条目。\n查看表格,第七个对象符合条件,其“审批(发起、审查、审核)依据”为“1. 工程费用报销单2.发票3. 水、电费结算单(燃料动力购买清单)4. 《业务手册》规定的其他报销资料”。\n该内容直接对应问题,其他条目无相关信息,因此是答案来源。\n答案需列出审批依据,保持原文。\n总结:找到对应条目,提取审批依据即可。\n依据水电(燃料动力)费结算款的内容,会计核算环节的审批依据有:\n1. 工程费用报销单;\n2. 发票;\n3. 水、电费结算单(燃料动力购买清单);\n4. 《业务手册》规定的其他报销资料。", + "output": "依据《项目“水电费”结算(成本列账)管理标准》\n用户的问题是询问在水电(燃料动力)费结算款的会计核算环节的审批依据。首先,找到二级流程为“会计核算(生成及传递付款指令)”的条目。\n表格中第七个条目符合,其审批(发起、审查、审核)依据为“1. 工程费用报销单2.发票3. 水、电费结算单(燃料动力购买清单)4. 《业务手册》规定的其他报销资料”。\n该内容直接回答问题,因此确定为答案。\n答案需列出审批依据,引用原文。\n总结:找到对应条目,提取审批依据即可。\n依据水电(燃料动力)费结算款的内容,在会计核算环节的审批依据如下:\n1. 工程费用报销单\n2. 发票\n3. 水、电费结算单(燃料动力购买清单)\n4. 《业务手册》规定的其他报销资料" + }, + { + "input": "依据水电(燃料动力)费结算款,资金支付审批环节的流程载体、节点描述以及责任部门(单位)是什么?", + "answer": "用户的问题是询问水电(燃料动力)费结算款在资金支付审批环节的流程载体、节点描述和责任部门(单位)。首先,需要从表格中找到一级流程为“资金支付审批流程”、二级流程为“资金支付审批”的条目。\n查看表格内容,第五个对象符合要求,其一级流程是“资金支付审批流程”,二级流程是“资金支付审批”。\n该条目下的“流程载体”“节点描述(工作步骤)”“责任部门(单位)”即为所需内容,其他条目因流程不同,不相关。\n提取这些信息,按要求组织答案,确保忠于原文,逻辑清晰。\n总结:找到对应条目,提取相关字段信息作为答案。\n依据水电(燃料动力)费结算款,资金支付审批环节的相关情况如下:\n1. **流程载体**:工程费用报销单。\n2. **节点描述**:审批资金支付流程。\n3. **责任部门(单位)**:财务分管领导、主要负责人。", + "output": "依据水电(燃料动力)费结算款,资金支付审批环节的相关情况如下:\n1. 流程载体:工程费用报销单\n2. 节点描述:审批资金支付流程\n3. 责任部门(单位):财务分管领导、主要负责人所属部门" + }, + { + "input": "在施工预付款的会计核算环节,节点描述、经办人角色及主要风险点是什么?", + "answer": "用户的问题是询问施工预付款中会计核算环节的节点描述、经办人角色及主要风险点。首先,需要从表格中找到二级流程为“会计核算”的条目。\n查看表格内容,流程节点100的条目符合条件,其一级流程是“资金支付审批流程(价款结算业务财权审批流程)”,二级流程是“会计核算”。\n该条目对应的节点描述(工作步骤)是“付款单据制证”,经办人是“发起”,主要风险点描述是“1.违规结算:核算“银行存款金额”与经审批的单据不一致”。其他条目因二级流程不符,不属于相关内容。\n答案需准确提取这些信息,分点说明。\n总结:通过筛选特定二级流程的条目,提取出节点描述、经办人角色及主要风险点。\n根据《项目施工合同价款(预付款)结算(成本列账)管理标准》,在施工预付款的会计核算环节,相关信息如下:\n- **节点描述(工作步骤)**:付款单据制证\n- **经办人角色**:发起\n- **主要风险点**:违规结算:核算“银行存款金额”与经审批的单据不一致", + "output": "依据《项目施工合同价款(预付款)结算(成本列账)管理标准》,在施工预付款的会计核算环节:\n- 节点描述:付款单据制证\n- 经办人角色:发起\n- 主要风险点:1. 违规结算:核算“银行存款金额”与经审批的单据不一致" + }, + { + "input": "我是项目实施部门专责,在施工结算款资金支付申请阶段,我需要做什么?我的责任是什么?发起依据有哪些?", + "answer": "用户的问题是项目实施部门专责在施工结算款资金支付申请阶段的工作、责任和发起依据。首先,查找一级流程为“资金支付审批流程(价款结算业务财权审批流程)”、二级流程为“资金支付申请”且责任人为项目实施部门专责的条目。\n相关内容模块中,工作包括提供报账资料、发起资金支付系统流程并填制申请单、完善申请单要素并发起审批。责任涉及资料真实性、数据准确性等。依据包括发票、合同等。\n提取的信息均与问题相关,无无关内容。\n答案需分点列出,保持原文表述,逻辑清晰。\n总结:找到对应条目,提取工作、责任和依据,形成答案。\n根据《项目施工合同价款(结算款)结算(成本列账)管理标准》,作为项目实施部门专责,在施工结算款资金支付申请阶段,您需要做的工作、责任及发起依据如下:\n1. **工作内容**:\n - 按照相关标准提供报账资料(结算依据)。\n - 按照《电网管理平台操作手册》发起资金支付系统流程,填制(推送)生成《工程业务支付申请单》。\n - 结合报账资料完善《工程业务支付申请单》所有结算要素并发起单据审批流程。\n2. **责任**:\n - 对报账资料的真实性、合法性及提供的合规性、准确性负责。\n - 对电网管理平台资产域的各项报销数据的完整性、准确性负责。\n - 对单据结算要素的完整性、准确性负主要责任。\n3. **发起依据**:发票;施工合同、合同价款结算协议;项目施工结算款支付报审表;《业务手册》规定的其他资料。", + "output": "您在施工结算款资金支付申请阶段需要做的工作和责任如下:\n1. 负责按照《价款结算(成本列账)资料提供与审核标准》(通用+专用),提供《工程业务支付申请单》单据中的所有结算要素(包括但不限:收款账户信息、项目信息、发票信息、合同信息、企业性质信息、业务活动、资金属性、预算科目、概预算内容名称、付款类型、支付内容、支付方式、扣预付款、预留质保金、预留挂账款、其他抵扣额、申请支付金额、预计支付时间、紧急支付原因等)并发起单据审批流程,对《工程业务支付申请单》要素的完整性、准确性负主要责任。\n2. 负责按照《价款结算(成本列账)事项审核标准(通用)》,审核本次报销流程审批、法规政策执行和其他合规事项,对报销事项流程审批合规性、报销事项政策执行合规性、其他事项合规性负责。\n3. 发起资金支付申请流程,对系统流程发起的及时性负责。\n4. 负责提供报账资料,对报账资料的真实性、合法性、合规性、准确性负责。\n5. 负责完善《工程业务支付申请单》中的所有结算要素并发起审批流程,对单据结算要素的完整性、准确性负主要责任。\n6. 负责审核本次报销流程审批、法规政策执行和其他合规事项,对报销事项流程审批合规性、报销事项政策执行合规性、其他事项合规性负责。\n7. 发起资金支付申请流程,对系统流程发起的及时性负责。\n8. 审核报账资料,对报账资料的真实性、合法性、合规性、准确性负责。\n9. 完善《工程业务支付申请单》中的所有结算要素并发起审批流程,对单据结算要素的完整性、准确性负主要责任。\n10. 审核本次报销流程审批、法规政策执行和其他合规事项,对报销事项流程审批合规性、报销事项政策执行合规性、其他事项合规性负责。\n11. 发起资金支付申请流程,对系统流程发起的及时性负责。\n12. 审核报账资料,对报账资料的真实性、合法性、合规性、准确性负责。\n13. 完善《工程业务支付申请单》中的所有结算要素并发起审批流程,对单据结算要素的完整性、准确性负主要责任。\n14. 审核本次报销流程审批、法规政策执行和其他合规事项,对报销事项流程审批合规性、报销事项政策执行合规性、其他事项合规性负责。\n15. 发起资金支付申请流程,对系统流程发起的及时性负责。\n16. 审核报账资料,对报账资料的真实性、合法性、合规性、准确性负责。\n17. 完善《工程业务支付申请单》中的所有结算要素并发起审批流程,对单据结算要素的完整性、准确性负主要责任。\n18. 审核本次报销流程审批、法规政策执行和其他合规事项,对报销事项流程审批合规性、报销事项政策执行合规性、其他事项合规性负责。\n19. 发起资金支付申请流程,对系统流程发起的及时性负责。\n20. 审核报账资料,对报账资料的真实性、合法性、合规性、准确性负责。\n21. 完善《工程业务支付申请单》中的所有结算要素并发起审批流程,对单据结算要素的完整性、准确性负主要责任。\n22. 审核本次报销流程审批、法规政策执行和其他合规事项,对报销事项流程审批合规性、报销事项政策执行合规性、其他事项合规性负责。\n23. 发起资金支付申请流程,对系统流程发起的及时性负责。\n24. 审核报账资料,对报账资料的真实性、合法性、合规性、准确性负责。\n25. 完善《工程业务支付申请单》中的所有结算要素并发起审批流程,对单据结算要素的完整性、准确性负主要责任。\n26. 审核本次报销流程审批、法规政策执行和其他合规事项,对报销事项流程审批合规性、报销事项政策执行合规性、其他事项合规性负责。\n27. 发起资金支付申请流程,对系统流程发起的及时性负责。\n28. 审核报账资料,对报账资料的真实性、合法性、合规性、准确性负责。\n29. 完善《工程业务支付申请单》中的所有结算要素并发起审批流程,对单据结算要素的完整性、准确性负主要责任。\n30. 审核本次报销流程审批、法规政策执行和其他合规事项,对报销事项流程审批合规性、报销事项政策执行合规性、其他事项合规性负责。\n31. 发起资金支付申请流程,对系统流程发起的及时性负责。\n32. 审核报账资料,对报账资料的真实性、合法性、合规性、准确性负责。\n33. 完善《工程业务支付申请单》中的所有结算要素并发起审批流程,对单据结算要素的完整性、准确性负主要责任。\n34. 审核本次报销流程审批、法规政策执行和其他合规事项,对报销事项流程审批合规性、报销事项政策执行合规性、其他事项合规性负责。\n35. 发起资金支付申请流程,对系统流程发起的及时性负责。\n36. 审核报账资料,对报账资料的真实性、合法性、合规性、准确性负责。\n37. 完善《工程业务支付申请单》中的所有结算要素并发起审批流程,对单据结算要素的完整性、准确性负主要责任。\n38. 审核本次报销流程审批、法规政策执行和其他合规事项,对报销事项流程审批合规性、报销事项政策执行合规性、其他事项合规性负责。\n39. 发起资金支付申请流程,对系统流程发起的及时性负责。\n40. 审核报账资料,对报账资料的真实性、合法性、合规性、准确性负责。\n41. 完善《工程业务支付申请单》中的所有结算要素并发起审批流程,对单据结算要素的完整性、准确性负主要责任。\n42. 审核本次报销流程审批、法规政策执行和其他合规事项,对报销事项流程审批合规性、报销事项政策执行合规性、其他事项合规性负责。\n43. 发起资金支付申请流程,对系统流程发起的及时性负责。\n44. 审核报账资料,对报账资料的真实性、合法性、合规性、准确性负责。\n45. 完善《工程业务支付申请单》中的所有结算要素并发起审批流程,对单据结算要素的完整性、准确性负主要责任。\n46. 审核本次报销流程审批、法规政策执行和其他合规事项,对报销事项流程审批合规性、报销事项政策执行合规性、其他事项合规性负责。\n47. 发起资金支付申请流程,对系统流程发起的及时性负责。\n48. 审核报账资料,对报账资料的真实性、合法性、合规性、准确性负责。\n49. 完善《工程业务支付申请单》中的所有结算要素并发起审批流程,对单据结算要素的完整性、准确性负主要责任。\n50. 审核本次报销流程审批、法规政策执行和其他合规事项,对报销事项流程审批合规性、报销事项政策执行合规性、其他事项合规性负责。\n51. 发起资金支付申请流程,对系统流程发起的及时性负责。\n52. 审核报账资料,对报账资料的真实性、合法性、合规性、准确性负责。\n53. 完善《工程业务支付申请单》中的所有结算要素并发起审批流程,对单据结算要素的完整性、准确性负主要责任。\n54. 审核本次报销流程审批、法规政策执行和其他合规事项,对报销事项流程审批合规性、报销事项政策执行合规性、其他事项合规性负责。\n55. 发起资金支付申请流程,对系统流程发起的及时性负责。\n56. 审核报账资料,对报账资料的真实性、合法性、合规性、准确性负责。\n57. 完善《工程业务支付申请单》中的所有结算要素并发起审批流程,对单据结算要素的完整性、准确性负主要责任。\n58. 审核本次报销流程审批、法规政策执行和其他合规事项,对报销事项流程审批合规性、报销事项政策执行合规性、其他事项合规性负责。\n59. 发起资金支付申请流程,对系统流程发起的及时性负责。\n60. 审核报账资料,对报账资料的真实性、合法性、合规性、准确性负责。\n61. 完善《工程业务支付申请单》中的所有结算要素并发起审批流程,对单据结算要素的完整性、准确性负主要责任。\n62. 审核本次报销流程审批、法规政策执行和其他合规事项,对报销事项流程审批合规性、报销事项政策执行合规性、其他事项合规性负责。\n63. 发起资金支付申请流程,对系统流程发起的及时" + }, + { + "input": "依据项目施工结算款支付报审表,表单各要素是否允许手写?", + "answer": "用户的问题是询问表单各要素是否允许手写,需要从表单合规性标准中查找相关内容。\n首先,分析问题意图是了解表单要素的填写方式限制,应关注表单合规性标准中相关条款。\n表单合规性标准第三条提到“表单各要素不得手写,不得模糊或涂改”,这与问题直接相关。\n其他条款如修改规定等内容不相关,可排除。\n提取该内容,组织答案准确引用原文,语气简洁。\n总结:答案在表单合规性标准第三条,表单各要素不得手写。\n依据项目施工结算款支付报审表,表单各要素不得手写。", + "output": "依据项目施工结算款支付报审表的内容,表单各要素不允许手写。" + }, + { + "input": "我是项目实施部门的项目(合同)负责人(项目经理),在施工预付款合同结算审核时,对表单所有要素负责的具体含义是什么?", + "answer": "用户的问题是项目实施部门的项目(合同)负责人(项目经理)在施工预付款合同结算审核时对表单所有要素负责的具体含义。首先,在节点20该负责人的记录中查找相关内容。\n可能关联的内容模块是表格中节点20该负责人的记录,其中权责事项提到对表单所有要素的合规性、准确性(真实性)负责,包括多个重点审核内容。\n提取该模块信息:具体含义是对《项目施工预付款支付报审表》所有要素的合规性、准确性(真实性)负责,重点包括结算条件、预付款金额比例、收款账户信息等方面的准确性和合规性。其他信息不相关,可排除。\n答案需说明具体含义,依据原文。\n总结:根据节点20中该负责人的权责事项,提取对表单要素负责的具体内容即可回答。\n根据《项目施工合同价款(预付款)结算(成本列账)管理标准》,作为项目实施部门的项目(合同)负责人(项目经理),在施工预付款合同结算审核时,对表单所有要素负责的具体含义是:对《项目施工预付款支付报审表》所有要素的合规性、准确性(真实性)负责,重点包括审核“合同是否生效、施工准备工作是否开始”以确保结算条件准确真实,审核“合同金额、预付款申请金额及比例”与合同约定一致,审核“承包单位收款账户信息”的一致性等。", + "output": "您在施工预付款合同结算审核时,对表单所有要素负责的具体含义是指您需要按照《价款结算(成本列账)资料提供与审核标准(通用)》中的“《施工预付款支付报审表~表单填制(审核)标准)”,对表单所有要素的完整性、准确性(真实性)负责,包括但不限于:合同信息、预付款相关情况、项目信息等。" + }, + { + "input": "依据施工结算款,资金支付审核阶段(计财部门专责)的流程载体是什么?", + "answer": "用户的问题是了解施工结算款中资金支付审核阶段计财部门专责的流程载体。首先,定位一级流程为“资金支付审批流程(价款结算业务财权审批流程)”、二级流程为“资金支付审核”且责任人为计财部门专责的条目。该条目显示流程载体为工程业务支付申请单。其他条目不相关。提取该流程载体信息作为答案。\n工程业务支付申请单", + "output": "依据施工结算款,资金支付审核阶段(计财部门专责)的流程载体是工程业务支付申请单。" + }, + { + "input": "在《项目施工预付款支付报审表》中,表单合规性标准共包含多少项内容?", + "answer": "用户的问题是询问表单合规性标准的项数,需要从表格内容中统计相关条款数量。\n首先,分析问题涉及合规性标准的结构,应查看“表单合规性标准”的条款编号。\n“表单合规性标准”的条款编号为(1)至(9),共9项内容,与问题直接相关。\n统计项数后,组织答案时准确说明数量,语气婉转。\n总结:通过查看表单合规性标准的条款编号,确定共有9项内容。\n在《项目施工预付款支付报审表》中,表单合规性标准共包含9项内容。", + "output": "表单合规性标准包含以下内容:1. 本表经审批后,作为结算依据,与报账资料具有同等效力;2. 本表不作为投资计划下达、资金预算下达、成本列账的依据;3. 本表第2次及以后排程审批的,可仅以项目施工预付款支付审批流程签为依据(可不重复报送《项目施工预付款支付报审表》);4. 本表需填制的表格格式(带*部分)为必填,其中“合同金额”“预付款申请金额”“预付款支付金额”“本次申请前累计支付金额”“本次申请后累计支付金额”“合同规定的其他付款条件及完成情况”“申请支付金额”“预计支付时间”“紧急支付原因”“支付方式”为必填项;5. 本表经审批完成后,按“申请支付金额”发起支付系统流程,确保“申请支付金额”为实际需求金额且“申请支付时间”为当月,对系统流程发起的及时性、准确性负责。" + }, + { + "input": "我是项目实施部门负责人,在施工结算款合同结算审核阶段,我的审核重点是什么?需要承担哪些责任?", + "answer": "用户的问题是项目实施部门负责人在施工结算款合同结算审核阶段的审核重点和责任。首先,在表格中寻找一级流程为“合同结算审批流程(价款结算业务事权审批流程)”、二级流程为“合同结算审核”且责任人为项目实施部门负责人的条目。\n该条目显示,审核重点为《项目施工结算款支付报审表》的关键要素,包括施工总结算完成情况与依据的一致性、质保金等计列的合规性、合同其他付款条件的完成情况,以及施工价款结算政策执行合规事项。\n责任方面,对表单关键要素的准确性(真实性)和价款结算事项政策执行合规性负责。\n这些内容与问题直接相关,无无关信息,可提取整理。\n总结:找到对应条目,提取审核重点和责任,按原文内容回答。\n依据《项目施工合同价款(结算款)结算(成本列账)管理标准》,在施工结算款合同结算审核阶段,您的审核重点及需承担的责任如下:\n\n1. **审核重点**:\n- 《项目施工结算款支付报审表》关键要素信息,包括“施工总结算完成情况”与施工总结算依据(实际情况)的一致性、“质保金预留、扣预付款等”计列的合规性、“合同规定的其他付款条件”的完成情况;\n- 施工价款结算政策执行合规事项。\n\n2. **承担责任**:\n- 对表单关键要素的准确性(真实性)负责;\n- 对该价款结算事项政策执行合规性负责。", + "output": "用户的问题是项目实施部门负责人在施工结算款合同结算审核阶段的审核重点和责任。首先,找到一级流程为“合同结算审批流程(价款结算业务事权审批流程)”、二级流程为“合同结算审核”且责任人为项目实施部门负责人的条目。\n该条目显示,审核重点包括报审表所有要素信息,主要风险点包括虚结价款、错结价款、逾结价款、违规结算等。责任包括对表单所有要素的合规性准确性负责,对结算事项政策执行合规性负责等。\n这些内容与问题直接相关,其他条目不涉及该阶段的责任人审核重点和责任,因此不相关。\n答案需分点列出审核重点和责任,逻辑清晰。\n总结:从对应条目提取审核重点和责任的具体内容。\n根据《项目施工合同价款(结算款)结算(成本列账)管理标准》,在施工结算款合同结算审核阶段,您的审核重点及责任如下:\n\n1. **审核重点**:\n - 按照《价款结算(成本列账)资料提供与审核标准(通用)》中的“《施工合同价款结算(成本列账)资料提供与审核标准》(通用)”,审核报审表所有要素信息,重点包括:(1)审核“施工总结算完成情况”是否与施工总结算依据(实际情况)一致,对该合同施工总结算审定金额的真实性、准确性负责(对本次价款结算达到合同规定的“结算条件”的准确性、真实性负责);(2)审核“质保金预留、扣预付款、扣除其他款项、预留挂账、本次申请支付”计列是否符合合同规定,审核“本次申请前累计支付金额”是否与合同台账或合同履行资料(实际情况)一致,审核“本次申请后累计支付金额(比例)”是否超合同规定,对“合同支付情况”的准确性负责;(3)审核“合同规定的其他付款条件”是否与合同规定一致,审核“实际完成情况”是否与实际工作开展情况相符,对“合同规定的其他付款条件及完成情况”的准确性、真实性负责。\n - 按照《价款结算(成本列账)事项审核标准(通用)》,审核施工价款结算政策执行合规事项,对该价款结算事项政策执行合规性负责。\n\n2. **需承担的责任**:\n - 对报审表所有要素的合规性、准确性(真实性)负责(对单据要素的完整性、准确性负次要责任)。\n - 对该价款结算事项政策执行合规性负责。\n - 对业务报审表审批的合规性、完整性负责。" + }, + { + "input": "依据《项目施工合同价款(结算款)结算(成本列账)管理标准》,资金支付申请环节中项目实施部门专责的经办人角色、审批依据以及主要风险点是什么?", + "answer": "用户的问题是依据指定标准,询问资金支付申请环节中项目实施部门专责的经办人角色、审批依据和主要风险点。首先,需要在表格中找到二级流程为“资金支付申请”且责任人为“项目实施部门专责”的条目。\n查看表格,该条目一级流程为“资金支付审批流程(价款结算业务财权审批流程)”,二级流程是“资金支付申请”,责任人是“项目实施部门专责”,经办人角色为“发起”,审批(发起、审查、审核)依据为“1.发票2.施工合同、合同价款结算协议3.项目施工结算款支付报审表4.《业务手册》规定的其他资料”,主要风险点描述包含违规结算、错结价款、逾结或早结价款等情况。\n确认这些信息与问题中的角色和环节匹配,无无关内容。答案需分点清晰呈现。\n总结:找到对应条目后,提取经办人角色、审批依据和主要风险点即可。\n依据《项目施工合同价款(结算款)结算(成本列账)管理标准》,在资金支付申请环节,项目实施部门专责的相关信息如下:\n- **经办人角色**:发起\n- **审批依据**:1.发票;2.施工合同、合同价款结算协议;3.项目施工结算款支付报审表;4.《业务手册》规定的其他资料\n- **主要风险点**:1.违规结算:报账资料缺失或冗余;2.错结价款:(1)单据扣预付款、预留质保金、预留挂账款、其他抵扣款、申请支付金额与审定报审表不一致;(2)单据支付方式与合同规定不一致;(3)单据收款单位与收款方账户名称实质不一致;3.逾结价款或早结价款:(1)单据预计付款时间不符合合同规定或实际履约要求;(2)单据收款方账户和账号信息有误,与合同约定(或收款信息变更函)不一致导致付款失败。", + "output": "依据《项目施工合同价款(结算款)结算(成本列账)管理标准》,在资金支付申请环节,项目实施部门专责的相关信息如下:\n经办人角色:发起\n审批依据:1. 发票2. 施工合同、合同价款结算协议3. 项目施工结算款支付报审表4. 《业务手册》规定的其他资料\n主要风险点描述:1. 违规结算:报账资料缺失或冗余2. 错结价款:(1)单据扣预付款、预留质保金、预留挂账款、其他抵扣款、申请支付金额与审定报审表不一致(2)单据支付方式与合同规定不一致(3)单据收款单位与收款方账户名称实质不一致3. 逾结价款或早结价款:(1)单据预计付款时间不符合合同规定或实际履约要求(2)单据收款方账户和账号信息有误,与合同约定(或收款信息变更函)不一致导致付款失败。" + }, + { + "input": "在办公费结算款中,资金支付审核环节中计财部门负责人需要完善哪些核算要素?", + "answer": "用户的问题是要了解办公费结算款中资金支付审核环节,计财部门负责人需要完善的核算要素。首先,需找到责任人是“计财部门负责人”的条目,即第四个对象。\n查看该对象的“责任人具体权责事项”,其中提到需完善《工程费用报销单》中的所有核算要素,包括付款账户信息等。\n确认这些内容与问题相关,无无关信息。\n生成答案时列出核算要素,语言简洁。\n总结:提取对应条目中的核算要素并呈现。\n在办公费结算款的资金支付审核环节中,计财部门负责人需要完善的《工程费用报销单》中的核算要素包括但不限于:付款账户信息、核算科目、资金计划科目等。", + "output": "在办公费结算款中,资金支付审核环节中计财部门负责人的主要审核内容包括:完善《工程费用报销单》中的所有核算要素,包括付款账户信息、核算科目、资金计划科目等。" + }, + { + "input": "在会议费结算款中,月度资金计划生成环节计财部门专责的责任人具体权责事项、审批依据和主要风险点是什么?", + "answer": "用户想知道会议费结算款里月度资金计划生成环节,计财部门专责的权责事项、审批依据和主要风险点。首先,在表格中筛选一级流程为“资金支付审批流程”、二级流程为“月度资金计划生成”、责任人是“计财部门专责”的条目,第六条目符合条件。然后提取该条目下的“责任人具体权责事项”“审批(发起、审查、审核)依据”“主要风险点描述”。权责事项是根据费用实施部门的资金支出需求编制并上报次月月度资金计划,对纳入计划的准确性负责;审批依据有工程费用报销单或业务部门提交的资金支出需求表等;主要风险点是未及时、准确将费用资金支付需求纳入月度资金计划。确认这些内容都来自该条目,无其他相关条目,进而整理出答案。\n在会议费结算款的月度资金计划生成环节,计财部门专责的相关信息如下:\n1. **具体权责事项**:\n 负责根据费用实施部门提交的费用资金支出需求,编制并上报次月月度资金计划,对该项资金支付需求是否纳入月度资金计划的准确性负责。\n2. **审批依据**:\n - 工程费用报销单或业务部门提交的资金支出需求表等。\n3. **主要风险点**:\n 逾结价款,即未及时、准确将费用资金支付需求纳入月度资金计划。", + "output": "在会议费结算款的月度资金计划生成环节,计财部门专责的相关信息如下:\n1. 责任人具体权责事项:负责根据费用实施部门提交的费用资金支出需求,编制并上报次月月度资金计划,对该项资金支付需求是否纳入月度资金计划的准确性负责\n2. 审批依据:工程费用报销单或业务部门提交的资金支出需求表等\n3. 主要风险点:逾结价款,即未及时、准确将费用资金支付需求纳入月度资金计划" + } +] \ No newline at end of file diff --git a/llm_config.py b/llm_config.py new file mode 100644 index 0000000..ccf9dc3 --- /dev/null +++ b/llm_config.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +大语言模型API配置文件 + +此文件包含所有与LLM评估相关的配置参数,包括: +- API密钥和访问配置 +- 模型参数 +- 评估模式设置 +- 提示词配置 +- 权重配置 + +使用说明: +--------- +1. 默认使用模拟评估模式(无需配置) +2. 如需使用真实LLM API,请: + - 安装依赖:pip install openai + - 在此处配置API密钥或使用环境变量 + - 修改 model_evaluation.py 中的 use_real_llm = True + +环境变量: +--------- +- OPENAI_API_KEY: OpenAI API密钥 +- API_BASE_URL: 自定义API地址(可选) +""" + +import os +from typing import Dict, Any + + +# ============================================================================= +# 评估模式配置 +# ============================================================================= + +# 是否使用真实LLM评估 +# False = 使用模拟评估(默认,推荐用于测试) +# True = 使用真实的大语言模型API(需要配置API密钥) +USE_REAL_LLM = True + + +# ============================================================================= +# API配置 +# ============================================================================= + +# OpenAI API配置 +OPENAI_CONFIG = { + # API密钥获取方式(优先级从高到低): + # 1. 环境变量 OPENAI_API_KEY + # 2. 直接在此处配置(不推荐,存在安全风险) + "api_key": os.environ.get("OPENAI_API_KEY", "123"), + + # API基础URL(可选) + # 使用默认值:https://api.openai.com/v1 + # 如使用代理或其他兼容API,请在此处配置 + "api_base": os.environ.get("API_BASE_URL", "http://10.10.10.122:1234/v1"), + + # 默认模型 + # 可选模型:gpt-3.5-turbo, gpt-4, gpt-4-turbo 等 + "model": "gpt-3.5-turbo", + + # 生成参数 + "temperature": 0, # 温度参数,0表示最确定的输出 + "max_tokens": 500, # 最大生成token数 + "timeout": 60, # API调用超时时间(秒) +} + + +# ============================================================================= +# 评估提示词配置 +# ============================================================================= + +# 评估维度权重(仅用于模拟评估,不影响真实LLM评估) +# 真实LLM评估使用其自身的语义理解能力 +EVALUATION_WEIGHTS = { + "bleu_score": 0.2, # BLEU分数权重 + "rouge_l_score": 0.25, # ROUGE-L分数权重 + "exact_match_rate": 0.25, # 完全匹配率权重 + "keyword_overlap_rate": 0.3 # 关键词重叠率权重 +} + +# 提示词模板 +PROMPT_TEMPLATE = """你是一位专业的文本质量评估专家。你的任务是对模型的生成答案与参考答案进行对比评估,并给出1-10分的评分。 + +评估维度: +1. 答案准确性:生成答案是否正确回答了问题,与参考答案在内容上是否一致 +2. 逻辑清晰度:答案的逻辑是否清晰,条理是否清楚 +3. 完整性:答案是否完整,是否遗漏了关键信息 +4. 表达质量:语言表达是否流畅、专业 + +评分标准: +- 10分:生成答案完全正确,逻辑清晰,表达优秀,与参考答案高度一致 +- 8-9分:生成答案基本正确,逻辑较清晰,表达较好,与参考答案较一致 +- 6-7分:生成答案部分正确,有一定逻辑,表达一般,与参考答案有一定差距 +- 4-5分:生成答案存在问题,逻辑不够清晰,表达欠佳,与参考答案差距较大 +- 2-3分:生成答案有较多错误,逻辑混乱,表达差,与参考答案差距很大 +- 1分:生成答案错误严重,无法理解或完全不相关 + +问题:{question} + +参考答案:{reference} + +模型生成答案:{candidate} + +请按照以下JSON格式返回评分结果: +{{ + "score": 评分(1-10的整数), + "reason": "详细评价理由,包括优点、不足及评分依据" +}} + +请确保: +1. 评分必须是1-10的整数 +2. 评价理由要详细具体,指出具体的优缺点 +3. 评价要客观公正,基于实际内容对比 +4. JSON格式要正确,可以直接解析 +""" + + +# ============================================================================= +# 其他模型提供商配置(扩展用) +# ============================================================================= + +# Anthropic Claude API配置(示例,如需使用请安装相应库) +ANTHROPIC_CONFIG = { + "api_key": os.environ.get("ANTHROPIC_API_KEY", ""), + "api_base": "https://api.anthropic.com", + "model": "claude-3-sonnet-20240229", + "temperature": 0, + "max_tokens": 500, +} + +# 通义千问API配置(示例) +QWEN_CONFIG = { + "api_key": os.environ.get("DASHSCOPE_API_KEY", ""), + "api_base": "https://dashscope.aliyuncs.com/api/v1", + "model": "qwen-turbo", + "temperature": 0, + "max_tokens": 500, +} + + +# ============================================================================= +# 配置验证函数 +# ============================================================================= + +def validate_openai_config() -> Dict[str, Any]: + """验证OpenAI配置是否正确""" + config = OPENAI_CONFIG.copy() + + # 检查API密钥 + if not config["api_key"]: + raise ValueError( + "未找到OpenAI API密钥!\n" + "请选择以下方式之一:\n" + "1. 设置环境变量:export OPENAI_API_KEY='your-api-key'\n" + "2. 在llm_config.py中直接配置(不推荐)\n" + "3. 使用模拟评估模式(USE_REAL_LLM = False)" + ) + + # 检查模型名称 + valid_models = ["gpt-3.5-turbo", "gpt-3.5-turbo-16k", "gpt-4", "gpt-4-turbo", "gpt-4o"] + if config["model"] not in valid_models: + print(f"警告:模型 {config['model']} 可能不受支持,支持的模型:{valid_models}") + + # 检查参数范围 + if not (0 <= config["temperature"] <= 2): + print(f"警告:temperature {config['temperature']} 超出推荐范围 [0, 2]") + + if config["max_tokens"] <= 0: + raise ValueError("max_tokens 必须大于 0") + + return config + + +def print_config_info(): + """打印当前配置信息""" + print("\n" + "=" * 60) + print("LLM评估配置信息") + print("=" * 60) + + print(f"评估模式: {'真实LLM API' if USE_REAL_LLM else '模拟评估(默认)'}") + print("-" * 60) + + if USE_REAL_LLM: + print("OpenAI API配置:") + print(f" API Base: {OPENAI_CONFIG['api_base']}") + print(f" 模型: {OPENAI_CONFIG['model']}") + print(f" 温度: {OPENAI_CONFIG['temperature']}") + print(f" 最大Token: {OPENAI_CONFIG['max_tokens']}") + + # 检查API密钥是否配置 + if OPENAI_CONFIG['api_key']: + print(f" API密钥: {'已配置' if OPENAI_CONFIG['api_key'] else '未配置'}") + else: + print(" API密钥: 未配置!") + else: + print("模拟评估权重配置:") + for key, value in EVALUATION_WEIGHTS.items(): + print(f" {key}: {value:.2%}") + print("\n模拟评估基于传统指标的综合加权计算") + + print("=" * 60 + "\n") + + +def switch_to_real_llm(): + """切换到真实LLM评估模式的配置检查""" + print("\n" + "=" * 60) + print("切换到真实LLM评估模式") + print("=" * 60) + + # 1. 检查依赖 + try: + import openai + print("✓ OpenAI库已安装") + except ImportError: + print("✗ 未安装OpenAI库") + print("请运行:pip install openai") + return False + + # 2. 检查API密钥 + try: + validate_openai_config() + print("✓ API密钥配置正确") + except ValueError as e: + print(f"✗ API密钥配置错误: {e}") + return False + + # 3. 测试API连接(可选) + print("\n配置验证通过!可以在model_evaluation.py中设置 use_real_llm = True") + + return True + + +def switch_to_mock_evaluation(): + """切换到模拟评估模式""" + print("\n" + "=" * 60) + print("切换到模拟评估模式") + print("=" * 60) + print("模拟评估使用传统指标的加权组合:") + print(f" BLEU分数: {EVALUATION_WEIGHTS['bleu_score']:.1%}") + print(f" ROUGE-L分数: {EVALUATION_WEIGHTS['rouge_l_score']:.1%}") + print(f" 完全匹配率: {EVALUATION_WEIGHTS['exact_match_rate']:.1%}") + print(f" 关键词重叠率: {EVALUATION_WEIGHTS['keyword_overlap_rate']:.1%}") + print("\n优点:") + print(" ✓ 无需配置API") + print(" ✓ 运行速度快") + print(" ✓ 无额外成本") + print("=" * 60) + + +# ============================================================================= +# 配置示例 +# ============================================================================= + +EXAMPLE_CONFIGURATIONS = { + "openai_gpt35": { + "description": "OpenAI GPT-3.5-Turbo(推荐)", + "model": "gpt-3.5-turbo", + "cost": "低", + "speed": "快", + "quality": "中等" + }, + "openai_gpt4": { + "description": "OpenAI GPT-4(高质量)", + "model": "gpt-4", + "cost": "高", + "speed": "中等", + "quality": "高" + }, + "openai_gpt4_turbo": { + "description": "OpenAI GPT-4-Turbo(推荐)", + "model": "gpt-4-turbo", + "cost": "中等", + "speed": "快", + "quality": "高" + }, + "anthropic_claude": { + "description": "Anthropic Claude-3-Sonnet", + "model": "claude-3-sonnet-20240229", + "cost": "中等", + "speed": "中等", + "quality": "高" + }, + "qwen_turbo": { + "description": "阿里云通义千问-Turbo", + "model": "qwen-turbo", + "cost": "低", + "speed": "快", + "quality": "中等" + } +} + + +def print_model_options(): + """打印可选模型列表""" + print("\n" + "=" * 60) + print("支持的模型列表") + print("=" * 60) + + for key, config in EXAMPLE_CONFIGURATIONS.items(): + print(f"\n{key}:") + print(f" 描述: {config['description']}") + print(f" 模型: {config['model']}") + print(f" 成本: {config['cost']}") + print(f" 速度: {config['speed']}") + print(f" 质量: {config['quality']}") + + print("\n" + "=" * 60) + + +# ============================================================================= +# 使用示例 +# ============================================================================= + +if __name__ == "__main__": + print_config_info() + + print("选择操作:") + print("1. 查看支持模型列表") + print("2. 验证真实LLM配置") + print("3. 查看配置示例") + print("4. 退出") + + choice = input("\n请输入选择 (1-4): ") + + if choice == "1": + print_model_options() + elif choice == "2": + if switch_to_real_llm(): + print("\n可以启用真实LLM评估模式!") + else: + print("\n请先解决配置问题") + elif choice == "3": + print("\n使用示例:") + print(""" +# 方式1:使用环境变量 +export OPENAI_API_KEY="your-api-key" +export USE_REAL_LLM="true" + +# 方式2:在代码中配置 +from llm_config import OPENAI_CONFIG +OPENAI_CONFIG["api_key"] = "your-api-key" +OPENUI_CONFIG["model"] = "gpt-3.5-turbo" + +# 方式3:使用模拟评估(默认) +# 无需配置,直接使用 +""") + else: + print("退出") diff --git a/model_evaluation.py b/model_evaluation.py new file mode 100644 index 0000000..fe64bbb --- /dev/null +++ b/model_evaluation.py @@ -0,0 +1,686 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +大模型微调验证系统 +用于评估模型输出与参考答案的相似度和质量 + +LLM评估配置: +- 默认使用模拟评估(基于传统指标) +- 如需使用真实LLM,请配置 llm_config.py 文件 +""" + +import json +import os +import re +from collections import Counter +from typing import Dict, List, Tuple, Any + +import numpy as np +from nltk.translate.bleu_score import sentence_bleu, SmoothingFunction +from nltk.tokenize import word_tokenize +import jieba +import jieba.posseg as pseg +from difflib import SequenceMatcher +import pandas as pd +from tqdm import tqdm + +# 导入LLM配置 +from llm_config import ( + USE_REAL_LLM, + OPENAI_CONFIG, + EVALUATION_WEIGHTS, + PROMPT_TEMPLATE, + validate_openai_config, + print_config_info +) + + +class ModelEvaluator: + """模型评估器""" + + def __init__(self): + # 初始化jieba分词 + jieba.initialize() + + # 停用词列表(中文常见停用词) + self.stopwords = { + '的', '了', '是', '在', '我', '有', '和', '就', '不', '人', '都', '一', '一个', + '上', '也', '很', '到', '说', '要', '去', '你', '会', '着', '没有', '看', '好', + '自己', '这', '那', '能', '下', '过', '他', '来', '对', '时候', '后', '如果' + } + + # 初始化平滑函数 + self.smoothing_function = SmoothingFunction() + + def get_llm_evaluation_prompt(self, reference: str, candidate: str, question: str = "") -> str: + """生成LLM评估提示词""" + # 使用配置中的提示词模板 + return PROMPT_TEMPLATE.format( + question=question, + reference=reference, + candidate=candidate + ) + + def call_llm_for_evaluation(self, prompt: str) -> Tuple[int, str]: + """调用大语言模型进行评估 + + 使用配置: + - API配置来自 llm_config.py 中的 OPENAI_CONFIG + - 支持环境变量和直接配置 + + 配置方法: + 1. 设置环境变量:export OPENAI_API_KEY='your-api-key' + 2. 在 llm_config.py 中直接修改 OPENAI_CONFIG + """ + # 验证配置 + config = validate_openai_config() + + try: + from openai import OpenAI + except ImportError: + raise ImportError( + "需要安装openai库:pip install openai\n" + "详细配置请参考 llm_config.py" + ) + + # 初始化客户端 + client = OpenAI( + api_key=config["api_key"], + base_url=config["api_base"] + ) + + # 调用API + try: + response = client.chat.completions.create( + model=config["model"], + messages=[ + {"role": "system", "content": "你是一个专业的文本质量评估专家。"}, + {"role": "user", "content": prompt} + ], + temperature=config["temperature"], + max_tokens=config["max_tokens"] + ) + + # 解析JSON响应 + import json + content = response.choices[0].message.content + try: + result = json.loads(content) + score = int(result.get("score", 0)) + reason = str(result.get("reason", "")) + return score, reason + except (json.JSONDecodeError, KeyError, ValueError): + # 如果无法解析JSON,尝试从文本中提取 + # 这里可以添加正则表达式来提取评分 + raise ValueError(f"无法解析LLM响应:{content}") + + except Exception as e: + print("api调用失败") + raise RuntimeError(f"API调用失败:{str(e)}") + + def get_mock_llm_evaluation(self, reference: str, candidate: str, question: str = "") -> Tuple[int, str]: + """获取模拟的LLM评估结果(用于演示) + + 使用配置: + - 权重来自 llm_config.py 中的 EVALUATION_WEIGHTS + """ + # 计算一些基本指标作为参考 + bleu = self.calculate_bleu_score(reference, candidate) + rouge_l = self.calculate_rouge_l(reference, candidate) + exact_match = self.calculate_exact_match_rate(reference, candidate) + keyword_overlap = self.calculate_keyword_overlap(reference, candidate) + + # 使用配置中的权重进行加权计算 + composite_score = ( + bleu * EVALUATION_WEIGHTS['bleu_score'] + + rouge_l * EVALUATION_WEIGHTS['rouge_l_score'] + + exact_match * EVALUATION_WEIGHTS['exact_match_rate'] + + keyword_overlap * EVALUATION_WEIGHTS['keyword_overlap_rate'] + ) + score = int(composite_score * 10) + + # 生成评价理由 + reason_parts = [] + + if exact_match > 0.9: + reason_parts.append("生成答案与参考答案内容完全一致") + elif bleu > 0.1 or rouge_l > 0.3: + reason_parts.append("生成答案在内容上与参考答案较为一致") + else: + reason_parts.append("生成答案与参考答案存在较大差异") + + if keyword_overlap > 0.5: + reason_parts.append("关键词重叠度较高") + elif keyword_overlap > 0.3: + reason_parts.append("关键词重叠度中等") + else: + reason_parts.append("关键词重叠度较低") + + reason = ";".join(reason_parts) + f"。综合指标:BLEU={bleu:.3f}, ROUGE-L={rouge_l:.3f}, 完全匹配率={exact_match:.3f}, 关键词重叠率={keyword_overlap:.3f}" + + return min(10, max(1, score)), reason + + def tokenize_chinese(self, text: str) -> List[str]: + """中文分词""" + if not text: + return [] + # 使用jieba进行分词 + words = jieba.cut(text) + return [word.strip() for word in words if len(word.strip()) > 0] + + def get_keywords(self, text: str) -> List[str]: + """提取关键词(名词、动词、形容词)""" + if not text: + return [] + + words = pseg.cut(text) + keywords = [] + for word, flag in words: + # 选择名词(n)、动词(v)、形容词(a)、区别词(b)等作为关键词 + if flag.startswith(('n', 'v', 'a', 'b', 'i')) and word not in self.stopwords and len(word) > 1: + keywords.append(word) + return keywords + + def calculate_bleu_score(self, reference: str, candidate: str) -> float: + """计算BLEU分数""" + if not reference or not candidate: + return 0.0 + + # 分词 + ref_tokens = self.tokenize_chinese(reference) + cand_tokens = self.tokenize_chinese(candidate) + + if not cand_tokens: + return 0.0 + + # 计算BLEU分数(使用1-gram到4-gram) + try: + score = sentence_bleu( + [ref_tokens], + cand_tokens, + smoothing_function=self.smoothing_function.method1 + ) + return score + except: + return 0.0 + + def calculate_rouge_l(self, reference: str, candidate: str) -> float: + """计算ROUGE-L分数""" + if not reference or not candidate: + return 0.0 + + ref_tokens = self.tokenize_chinese(reference) + cand_tokens = self.tokenize_chinese(candidate) + + if not ref_tokens or not cand_tokens: + return 0.0 + + # 计算最长公共子序列 + lcs = self._lcs_length(ref_tokens, cand_tokens) + + # 计算ROUGE-L F1分数 + ref_len = len(ref_tokens) + cand_len = len(cand_tokens) + + if ref_len == 0 or cand_len == 0: + return 0.0 + + precision = lcs / cand_len + recall = lcs / ref_len + + if precision + recall == 0: + return 0.0 + + rouge_l = (2 * precision * recall) / (precision + recall) + return rouge_l + + def _lcs_length(self, seq1: List[str], seq2: List[str]) -> int: + """计算最长公共子序列长度""" + m, n = len(seq1), len(seq2) + dp = [[0] * (n + 1) for _ in range(m + 1)] + + for i in range(1, m + 1): + for j in range(1, n + 1): + if seq1[i-1] == seq2[j-1]: + dp[i][j] = dp[i-1][j-1] + 1 + else: + dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + + return dp[m][n] + + def calculate_character_overlap(self, reference: str, candidate: str) -> float: + """计算字符重叠率""" + if not reference or not candidate: + return 0.0 + + ref_chars = set(reference) + cand_chars = set(candidate) + + if not cand_chars: + return 0.0 + + overlap = len(ref_chars & cand_chars) + total_cand = len(cand_chars) + + return overlap / total_cand if total_cand > 0 else 0.0 + + def calculate_length_similarity(self, reference: str, candidate: str) -> float: + """计算长度相似度""" + if not reference or not candidate: + return 0.0 + + ref_len = len(reference) + cand_len = len(candidate) + + if ref_len == 0 and cand_len == 0: + return 1.0 + + if ref_len == 0 or cand_len == 0: + return 0.0 + + # 使用相对差异计算相似度 + similarity = 1 - abs(ref_len - cand_len) / max(ref_len, cand_len) + return max(0, similarity) + + def calculate_exact_match_rate(self, reference: str, candidate: str) -> float: + """计算完全匹配率""" + if not reference and not candidate: + return 1.0 + + if not reference or not candidate: + return 0.0 + + # 去除空白字符后比较 + ref_clean = re.sub(r'\s+', '', reference.strip()) + cand_clean = re.sub(r'\s+', '', candidate.strip()) + + return 1.0 if ref_clean == cand_clean else 0.0 + + def calculate_keyword_overlap(self, reference: str, candidate: str) -> float: + """计算关键词重叠率""" + ref_keywords = set(self.get_keywords(reference)) + cand_keywords = set(self.get_keywords(candidate)) + + if not cand_keywords: + return 0.0 + + overlap = len(ref_keywords & cand_keywords) + total_cand = len(cand_keywords) + + return overlap / total_cand if total_cand > 0 else 0.0 + + def evaluate_all_metrics(self, reference: str, candidate: str) -> Dict[str, float]: + """计算所有评估指标""" + return { + 'bleu_score': self.calculate_bleu_score(reference, candidate), + 'rouge_l_score': self.calculate_rouge_l(reference, candidate), + 'character_overlap_rate': self.calculate_character_overlap(reference, candidate), + 'length_similarity': self.calculate_length_similarity(reference, candidate), + 'exact_match_rate': self.calculate_exact_match_rate(reference, candidate), + 'keyword_overlap_rate': self.calculate_keyword_overlap(reference, candidate) + } + + +def load_data(file_path: str) -> List[Dict[str, Any]]: + """加载数据文件 + + 支持 .jsonl (JSON Lines) 和 .json (JSON Array) 格式 + """ + data = [] + file_ext = os.path.splitext(file_path)[1].lower() + + if file_ext == '.jsonl': + # JSON Lines格式:每行一个JSON对象 + with open(file_path, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + if line: + try: + data.append(json.loads(line)) + except json.JSONDecodeError: + print(f"Warning: Failed to parse line: {line[:100]}") + elif file_ext == '.json': + # JSON Array格式:一个包含多个JSON对象的数组 + with open(file_path, 'r', encoding='utf-8') as f: + try: + json_data = json.load(f) + if isinstance(json_data, list): + data = json_data + else: + print(f"Warning: JSON file does not contain an array: {file_path}") + except json.JSONDecodeError as e: + print(f"Warning: Failed to parse JSON file: {file_path}, error: {e}") + else: + print(f"Warning: Unsupported file format: {file_ext}, only .json and .jsonl are supported") + + return data + + +def evaluate_dataset(data: List[Dict[str, Any]], evaluator: ModelEvaluator, use_real_llm: bool = False) -> Tuple[List[Dict], Dict[str, float]]: + """评估整个数据集 + + Args: + data: 数据列表 + evaluator: 评估器实例 + use_real_llm: 是否使用真实LLM评估(默认False,使用模拟评估) + """ + results = [] + total_metrics = { + 'bleu_score': 0.0, + 'rouge_l_score': 0.0, + 'character_overlap_rate': 0.0, + 'length_similarity': 0.0, + 'exact_match_rate': 0.0, + 'keyword_overlap_rate': 0.0, + 'llm_score': 0.0 + } + + print(f"\n开始评估 {len(data)} 条数据...") + + if use_real_llm: + print("注意:LLM评分功能使用真实的大语言模型API") + print("配置来源:llm_config.py") + else: + print("注意:LLM评分功能使用模拟评估(基于传统指标的综合评分)") + print("配置来源:llm_config.py 中的 EVALUATION_WEIGHTS") + + for idx, item in enumerate(tqdm(data, desc="评估进度")): + # 支持多种字段名格式 + input_text = item.get('question', item.get('Input', item.get('问题', ''))) + output_text = item.get('output', item.get('Output', item.get('生成答案', ''))) + answer_text = item.get('answer', item.get('Answer', item.get('参考答案', ''))) + + # 计算各项指标 + metrics = evaluator.evaluate_all_metrics(answer_text, output_text) + + # 获取LLM评估 + if use_real_llm: + try: + prompt = evaluator.get_llm_evaluation_prompt(answer_text, output_text, input_text) + llm_score, llm_reason = evaluator.call_llm_for_evaluation(prompt) + except Exception as e: + print(f" [Warning] LLM API调用失败,使用模拟评估: {e}") + llm_score, llm_reason = evaluator.get_mock_llm_evaluation(answer_text, output_text, input_text) + else: + llm_score, llm_reason = evaluator.get_mock_llm_evaluation(answer_text, output_text, input_text) + + # 添加原始数据 + result = { + 'index': idx + 1, + 'Input': input_text, + 'Output': output_text, + 'Answer': answer_text, + **metrics, + 'llm_score': llm_score, + 'llm_reason': llm_reason + } + results.append(result) + + # 累加指标 + for key, value in metrics.items(): + total_metrics[key] += value + total_metrics['llm_score'] += llm_score + + # 计算平均值 + num_samples = len(data) + for key in total_metrics: + total_metrics[key] /= num_samples if num_samples > 0 else 1 + + return results, total_metrics + + +def save_to_excel(results: List[Dict], total_metrics: Dict, output_dir: str, filename: str): + """保存结果到Excel文件""" + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + # 创建DataFrame + df = pd.DataFrame(results) + + # 重命名列名 + column_mapping = { + 'index': '序号', + 'Input': '问题', + 'Answer': '参考答案', + 'Output': '生成答案', + 'bleu_score': 'BLEU', + 'rouge_l_score': 'ROUGE-L', + 'character_overlap_rate': '字符重叠率', + 'length_similarity': '长度相似度', + 'exact_match_rate': '完全匹配率', + 'keyword_overlap_rate': '关键词重叠率', + 'llm_score': 'LLM评分', + 'llm_reason': 'LLM评价理由' + } + df = df.rename(columns=column_mapping) + + # 计算整体统计信息 + stats_df = pd.DataFrame([total_metrics]) + stats_column_mapping = { + 'bleu_score': 'BLEU', + 'rouge_l_score': 'ROUGE-L', + 'character_overlap_rate': '字符重叠率', + 'length_similarity': '长度相似度', + 'exact_match_rate': '完全匹配率', + 'keyword_overlap_rate': '关键词重叠率', + 'llm_score': 'LLM评分' + } + stats_df = stats_df.rename(columns=stats_column_mapping) + + # 保存到Excel + excel_path = os.path.join(output_dir, filename) + with pd.ExcelWriter(excel_path, engine='openpyxl') as writer: + # 写入详细结果 + df.to_excel(writer, sheet_name='详细结果', index=False) + + # 写入统计信息 + stats_df.to_excel(writer, sheet_name='整体统计', index=False) + + print(f"\n结果已保存到: {excel_path}") + return excel_path + + +def print_summary(total_metrics: Dict): + """打印评估摘要""" + print("\n" + "="*60) + print("模型评估结果摘要") + print("="*60) + print(f"BLEU分数: {total_metrics['bleu_score']:.4f}") + print(f"ROUGE-L分数: {total_metrics['rouge_l_score']:.4f}") + print(f"字符重叠率: {total_metrics['character_overlap_rate']:.4f}") + print(f"长度相似度: {total_metrics['length_similarity']:.4f}") + print(f"完全匹配率: {total_metrics['exact_match_rate']:.4f}") + print(f"关键词重叠率: {total_metrics['keyword_overlap_rate']:.4f}") + print(f"LLM评分: {total_metrics['llm_score']:.4f}") + print("="*60) + + # 计算综合评分(加权平均) + weights = { + 'bleu_score': 0.2, + 'rouge_l_score': 0.25, + 'character_overlap_rate': 0.15, + 'length_similarity': 0.1, + 'exact_match_rate': 0.15, + 'keyword_overlap_rate': 0.15 + } + + composite_score = sum(total_metrics[key] * weight for key, weight in weights.items()) + print(f"综合评分: {composite_score:.4f}") + print("="*60) + + +def main(): + """主函数""" + print("大模型微调验证系统") + print("="*60) + + # 数据目录 + data_dir = "data" + output_dir = "outputs" + + # 打印当前配置信息 + print_config_info() + + # 获取所有数据文件(支持 .jsonl 和 .json 格式) + data_files = [f for f in os.listdir(data_dir) if f.endswith(('.jsonl', '.json'))] + print(f"\n发现 {len(data_files)} 个数据文件:") + for file in data_files: + file_ext = os.path.splitext(file)[1] + print(f" - {file} ({file_ext})") + + # 初始化评估器 + evaluator = ModelEvaluator() + + # 存储所有结果 + all_results = [] + all_stats = {} + + # 逐个评估每个文件 + for filename in data_files: + print(f"\n{'='*60}") + print(f"评估文件: {filename}") + print(f"{'='*60}") + + file_path = os.path.join(data_dir, filename) + data = load_data(file_path) + print(f"加载数据: {len(data)} 条记录") + + # 评估数据(使用配置文件中的USE_REAL_LLM设置) + results, total_metrics = evaluate_dataset(data, evaluator, use_real_llm=USE_REAL_LLM) + + # 保存结果 + base_name = os.path.splitext(filename)[0] + excel_filename = f"{base_name}_evaluation.xlsx" + excel_path = save_to_excel(results, total_metrics, output_dir, excel_filename) + + # 存储结果 + all_results.extend([{**r, 'file': filename} for r in results]) + all_stats[filename] = total_metrics + + # 打印摘要 + print_summary(total_metrics) + + # 生成汇总报告 + print(f"\n{'='*60}") + print("生成汇总报告") + print(f"{'='*60}") + + # 创建汇总DataFrame + summary_data = [] + for filename, stats in all_stats.items(): + summary_data.append({ + '文件名': filename, + 'BLEU分数': stats['bleu_score'], + 'ROUGE-L分数': stats['rouge_l_score'], + '字符重叠率': stats['character_overlap_rate'], + '长度相似度': stats['length_similarity'], + '完全匹配率': stats['exact_match_rate'], + '关键词重叠率': stats['keyword_overlap_rate'], + 'LLM评分': stats['llm_score'] + }) + + summary_df = pd.DataFrame(summary_data) + + # 计算所有文件的平均分 + avg_scores = summary_df.select_dtypes(include=[np.number]).mean() + avg_df = pd.DataFrame([avg_scores]) + avg_df.index = ['平均分'] + + # 保存汇总报告 + summary_path = os.path.join(output_dir, "evaluation_summary.xlsx") + with pd.ExcelWriter(summary_path, engine='openpyxl') as writer: + summary_df.to_excel(writer, sheet_name='各文件评分', index=False) + avg_df.to_excel(writer, sheet_name='平均分') + + print(f"汇总报告已保存到: {summary_path}") + + # 打印最终汇总 + print(f"\n{'='*60}") + print("所有文件评估结果汇总") + print(f"{'='*60}") + print(summary_df.to_string(index=False, float_format='%.4f')) + print(f"{'='*60}") + print("平均分:") + print(avg_df.to_string(float_format='%.4f')) + print(f"{'='*60}") + + # 保存所有详细结果 + all_results_path = os.path.join(output_dir, "all_detailed_results.xlsx") + all_results_df = pd.DataFrame(all_results) + # 重命名列名 + all_column_mapping = { + 'index': '序号', + 'Input': '问题', + 'Answer': '参考答案', + 'Output': '生成答案', + 'bleu_score': 'BLEU', + 'rouge_l_score': 'ROUGE-L', + 'character_overlap_rate': '字符重叠率', + 'length_similarity': '长度相似度', + 'exact_match_rate': '完全匹配率', + 'keyword_overlap_rate': '关键词重叠率', + 'llm_score': 'LLM评分', + 'llm_reason': 'LLM评价理由' + } + all_results_df = all_results_df.rename(columns=all_column_mapping) + all_results_df.to_excel(all_results_path, index=False, engine='openpyxl') + print(f"\n所有详细结果已保存到: {all_results_path}") + + +# ============================================================================= +# 测试函数 +# ============================================================================= + +def test_single_evaluation(): + """测试单个评估""" + from llm_config import USE_REAL_LLM + + evaluator = ModelEvaluator() + + # 测试数据 + question = "什么是合同?" + reference_answer = "合同是当事人之间设立、变更、终止民事法律关系的协议。" + candidate_answer = "合同是双方或多方之间达成的协议,用于约定权利和义务。" + + print("="*60) + print("LLM评估测试") + print("="*60) + print(f"评估模式: {'真实LLM API' if USE_REAL_LLM else '模拟评估'}") + print(f"问题: {question}") + print(f"参考答案: {reference_answer}") + print(f"生成答案: {candidate_answer}") + print("-"*60) + + try: + # 获取LLM评估 + prompt = evaluator.get_llm_evaluation_prompt(reference_answer, candidate_answer, question) + + if USE_REAL_LLM: + print("尝试调用真实LLM API...") + score, reason = evaluator.call_llm_for_evaluation(prompt) + + # 同时计算传统指标 + print("\n传统指标计算:") + all_metrics = evaluator.evaluate_all_metrics(reference_answer, candidate_answer) + for key, value in all_metrics.items(): + print(f" {key}: {value:.4f}") + else: + print("使用模拟评估...") + score, reason = evaluator.get_mock_llm_evaluation(reference_answer, candidate_answer, question) + + print(f"\n[SUCCESS] 评估成功!") + print(f"评分: {score}/10") + print(f"评价理由: {reason}") + + return True + + except Exception as e: + print(f"\n[ERROR] 评估失败: {e}") + print("\n详细错误信息:") + import traceback + traceback.print_exc() + return False + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..30f1d58 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,15 @@ +# 核心依赖 +numpy>=1.21.0 +pandas>=1.3.0 +openpyxl>=3.0.0 +nltk>=3.8 +jieba>=0.42.1 +tqdm>=4.64.0 +xlrd>=2.0.0 + +# LLM API支持(可选) +openai>=1.0.0 + +# 其他可选依赖 +# requests>=2.28.0 # 用于HTTP请求 +# matplotlib>=3.5.0 # 用于结果可视化(如需要) \ No newline at end of file