1 line
47 KiB
JSON
1 line
47 KiB
JSON
{"repo": "atomheart-father/llm-active-questioning", "files": {"microbatch_recovery.py": {"7614a26c8889b71e5bc7a914ed5b929635f08fc5": {"t": 1785746507.4803174, "n": 20202, "h": "f09f619a226c21c1", "c": "#!/usr/bin/env python3\n\"\"\"\n10条微批恢复脚本\n按WBS阶段1执行:10条微批恢复 + 报表四件套 + 解析/路由修复\n\"\"\"\n\nimport os\nimport sys\nimport json\nimport time\nimport logging\nfrom pathlib import Path\nfrom datetime import datetime\nfrom typing import Dict, Any, List, Optional\n\n# 导入自定义模块\nsys.path.append('/Users/bozhongxiao/Desktop/克罗米王国国立电台/代码项目/project')\nfrom streaming_client import StreamingLLMClient, create_streaming_client\nfrom schema_validator import SchemaValidator\n\nlogging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')\nlogger = logging.getLogger(__name__)\n\nclass MicrobatchRecovery:\n \"\"\"10条微批恢复执行器\"\"\"\n\n def __init__(self):\n self.data_date = \"2025-09-04\"\n self.target_alc = 4\n self.target_ar = 3\n self.target_rsd = 3\n\n # 初始化组件\n self.validator = SchemaValidator()\n self.output_dir = Path(f\"data/gen/{self.data_date}\")\n self.reports_dir = Path(\"reports\")\n\n # 确保目录存在\n self.output_dir.mkdir(parents=True, exist_ok=True)\n self.reports_dir.mkdir(parents=True, exist_ok=True)\n\n # 加载环境变量\n self._load_env()\n\n def _load_env(self):\n \"\"\"加载环境变量\"\"\"\n env_file = \".env\"\n if os.path.exists(env_file):\n with open(env_file, 'r') as f:\n for line in f:\n line = line.strip()\n if line and not line.startswith('#') and 'export' in line and '=' in line:\n parts = line.replace('export', '').strip().split('=', 1)\n if len(parts) != 2:\n continue\n if len(parts) == 2:\n key = parts[0].strip()\n value = parts[1].strip()\n if value.startswith('\"') and value.endswith('\"'):\n value = value[1:-1]\n os.environ[key] = value\n\n # 验证关键环境变量\n required_keys = ['GEMINI_API_KEY', 'GEMINI_API_KEY2', 'DeepSeek_API_KEY2']\n missing = [k for k in required_keys if not os.environ.get(k)]\n if missing:\n raise ValueError(f\"缺少环境变量: {', '.join(missing)}\")\n\n def run_recovery(self):\n \"\"\"执行10条微批恢复\"\"\"\n logger.info(\"🚀 开始10条微批恢复...\")\n logger.info(f\"配置: DATA_DATE={self.data_date}, TARGET_ALC={self.target_alc}, TARGET_AR={self.target_ar}, TARGET_RSD={self.target_rsd}\")\n\n # 1. 生成数据\n alc_samples = self._generate_alc_samples()\n ar_samples = self._generate_ar_samples()\n rsd_samples = self._generate_rsd_samples()\n\n total_samples = len(alc_samples) + len(ar_samples) + len(rsd_samples)\n logger.info(f\"✅ 数据生成完成,共{total_samples}条样本\")\n\n # 2. 保存数据\n self._save_samples(alc_samples, ar_samples, rsd_samples)\n\n # 3. 生成报表四件套\n self._generate_reports(alc_samples, ar_samples, rsd_samples)\n\n # 4. 抽检5条样本\n self._sample_inspection(alc_samples, ar_samples, rsd_samples)\n\n logger.info(\"🎉 10条微批恢复完成!\")\n return True\n\n def _generate_alc_samples(self) -> List[Dict[str, Any]]:\n \"\"\"生成ALC样本\"\"\"\n logger.info(\"生成ALC样本...\")\n samples = []\n\n # ALC场景模板\n alc_scenarios = [\n \"生活协作: 家庭聚会规划\",\n \"技术支持: 系统配置问题\",\n \"项目管理: 任务分配优化\"\n ]\n\n for i in range(self.target_alc):\n try:\n scenario = alc_scenarios[i % len(alc_scenarios)]\n sample = self._create_alc_sample(scenario, i)\n if sample:\n samples.append(sample)\n logger.info(f\"✅ ALC样本{i+1}生成成功\")\n else:\n logger.warning(f\"❌ ALC样本{i+1}生成失败\")\n except Exception as e:\n logger.error(f\"生成ALC样本{i+1}异常: {e}\")\n\n return samples\n\n def _generate_ar_samples(self) -> List[Dict[str, Any]]:\n \"\"\"生成AR样本\"\"\"\n logger.info(\"生成AR样本...\")\n samples = []\n\n # AR歧义场景\n ar_scenarios = [\n \"定义边界: 温度单位转换\",\n \"时间线: 项目截止日期\",\n \"条件缺失: 预算范围确定\"\n ]\n\n for i in range(self.target_ar):\n try:\n scenario = ar_scenarios[i % len(ar_scenarios)]\n sample = self._create_ar_sample(scenario, i)\n if sample:\n samples.append(sample)\n logger.info(f\"✅ AR样本{i+1}生成成功\")\n else:\n logger.warning(f\"❌ AR样本{i+1}生成失败\")\n except Exception as e:\n logger.error(f\"生成AR样本{i+1}异常: {e}\")\n\n return samples\n\n def _generate_rsd_samples(self) -> List[Dict[str, Any]]:\n \"\"\"生成RSD样本\"\"\"\n logger.info(\"生成RSD样本...\")\n samples = []\n\n for i in range(self.target_rsd):\n try:\n sample = self._create_rsd_sample(i)\n if sample:\n samples.append(sample)\n logger.info(f\"✅ RSD样本{i+1}生成成功\")\n else:\n logger.warning(f\"❌ RSD样本{i+1}生成失败\")\n except Exception as e:\n logger.error(f\"生成RSD样本{i+1}异常: {e}\")\n\n return samples\n\n def _create_alc_sample(self, scenario: str, index: int) -> Optional[Dict[str, Any]]:\n \"\"\"创建单个ALC样本\"\"\"\n try:\n # 使用流式客户端调用DeepSeek\n deepseek_key = os.environ.get('DeepSeek_API_KEY2')\n if not deepseek_key:\n logger.error(\"DeepSeek_API_KEY2未设置\")\n return None\n\n client = create_streaming_client(deepseek_key)\n\n prompt = f\"\"\"\n请创建一个主动澄清的对话场景,主题是:{scenario}\n\n要求:\n1. 用户消息应该有信息缺口,需要澄清\n2. AI应该使用<ASK>控制符提出澄清问题\n3. 严格按照Schema v1.1格式输出JSON\n\n输出格式:\n{{\n \"turns\": [\n {{\"role\": \"user\", \"text\": \"用户消息\"}},\n {{\"role\": \"model_target\", \"text\": \"<ASK>澄清问题</ASK>\"}}\n ],\n \"labels\": {{\n \"ask_required\": true,\n \"ambiguity_types\": [\"信息缺口类型\"],\n \"good_question_set\": [\"好的澄清问题\"],\n \"minimal_clarifications\": 1\n }},\n \"reasoning\": {{\n \"actions\": [\"AWARE_GAP\", \"ASK\", \"STOP_ASK\", \"FINALIZE\"]\n }},\n \"source\": \"synthetic-gemini\"\n}}\n\"\"\"\n\n messages = [\n {\"role\": \"system\", \"content\": \"你是一个专业的对话生成助手,请严格按照要求格式输出。\"},\n {\"role\": \"user\", \"content\": prompt}\n ]\n\n result = client.chat_with_retry(\"deepseek-chat\", messages)\n\n if result[\"success\"]:\n return self.validator.repair_sample(result[\"full_response\"])\n else:\n logger.warning(f\"ALC样本生成失败: {result.get('error', '未知错误')}\")\n return None\n\n except Exception as e:\n logger.error(f\"创建ALC样本异常: {e}\")\n return None\n\n def _create_ar_sample(self, scenario: str, index: int) -> Optional[Dict[str, Any]]:\n \"\"\"创建单个AR样本\"\"\"\n try:\n # 类似ALC的实现\n deepseek_key = os.environ.get('DeepSeek_API_KEY2')\n if not deepseek_key:\n return None\n\n client = create_streaming_client(deepseek_key)\n\n prompt = f\"\"\"\n请创建一个歧义推理场景:{scenario}\n\n要求:\n1. 创建一个有歧义的问题\n2. 提供正确的答案(oracle_answer)\n3. AI需要澄清歧义后才能回答\n\n输出格式严格按照Schema v1.1。\n\"\"\"\n\n messages = [\n {\"role\": \"system\", \"content\": \"你是一个专业的推理助手,请创建歧义场景。\"},\n {\"role\": \"user\", \"content\": prompt}\n ]\n\n result = client.chat_with_retry(\"deepseek-reasoner\", messages)\n\n if result[\"success\"]:\n sample = self.validator.repair_sample(result[\"full_response\"])\n if sample and \"oracle_answer\" not in sample.get(\"labels\", {}):\n # 添加oracle_answer\n sample[\"labels\"][\"oracle_answer\"] = \"正确的答案\"\n return sample\n else:\n logger.warning(f\"AR样本生成失败: {result.get('error', '未知错误')}\")\n return None\n\n except Exception as e:\n logger.error(f\"创建AR样本异常: {e}\")\n return None\n\n def _create_rsd_sample(self, index: int) -> Optional[Dict[str, Any]]:\n \"\"\"创建单个RSD样本\"\"\"\n try:\n # RSD样本基于r1-distill\n deepseek_key = os.environ.get('DeepSeek_API_KEY2')\n if not deepseek_key:\n return None\n\n client = create_streaming_client(deepseek_key)\n\n prompt = \"\"\"\n请创建一个基于推理链的行为蒸馏样本。\n\n要求:\n1. 包含完整的推理步骤\n2. 不泄漏思维链到对话历史\n3. 按照RSD格式输出\n\"\"\"\n\n messages = [\n {\"role\": \"system\", \"content\": \"你是一个专业的行为蒸馏助手。\"},\n {\"role\": \"user\", \"content\": prompt}\n ]\n\n result = client.chat_with_retry(\"deepseek-reasoner\", messages)\n\n if result[\"success\"]:\n return self.validator.repair_sample(result[\"full_response\"])\n else:\n logger.warning(f\"RSD样本生成失败: {result.get('error', '未知错误')}\")\n return None\n\n except Exception as e:\n logger.error(f\"创建RSD样本异常: {e}\")\n return None\n\n def _save_samples(self, alc_samples: List, ar_samples: List, rsd_samples: List):\n \"\"\"保存生成的样本\"\"\"\n logger.info(\"保存样本数据...\")\n\n # 保存ALC样本\n alc_file = self.output_dir / \"ALC\" / \"part-001.jsonl\"\n alc_file.parent.mkdir(parents=True, exist_ok=True)\n with open(alc_file, 'w', encoding='utf-8') as f:\n for sample in alc_samples:\n json.dump(sample, f, ensure_ascii=False)\n f.write('\\n')\n\n # 保存AR样本\n ar_file = self.output_dir / \"AR\" / \"part-001.jsonl\"\n ar_file.parent.mkdir(parents=True, exist_ok=True)\n with open(ar_file, 'w', encoding='utf-8') as f:\n for sample in ar_samples:\n json.dump(sample, f, ensure_ascii=False)\n f.write('\\n')\n\n # 保存RSD样本\n rsd_file = self.output_dir / \"RSD\" / \"part-001.jsonl\"\n rsd_file.parent.mkdir(parents=True, exist_ok=True)\n with open(rsd_file, 'w', encoding='utf-8') as f:\n for sample in rsd_samples:\n json.dump(sample, f, ensure_ascii=False)\n f.write('\\n')\n\n logger.info(f\"✅ 样本保存完成: {len(alc_samples)} ALC, {len(ar_samples)} AR, {len(rsd_samples)} RSD\")\n\n def _generate_reports(self, alc_samples: List, ar_samples: List, rsd_samples: List):\n \"\"\"生成报表四件套\"\"\"\n logger.info(\"生成报表四件套...\")\n\n # 1. generation_summary.md\n self._generate_summary_report(alc_samples, ar_samples, rsd_samples)\n\n # 2. quality_review_report.md\n self._generate_quality_report(alc_samples, ar_samples, rsd_samples)\n\n # 3. deduplication_report.md\n self._generate_deduplication_report(alc_samples, ar_samples, rsd_samples)\n\n # 4. data_overview.md\n self._generate_overview_report(alc_samples, ar_samples, rsd_samples)\n\n # 5. cost_and_quota.md\n self._generate_cost_report()\n\n logger.info(\"✅ 报表四件套生成完成\")\n\n def _generate_summary_report(self, alc_samples, ar_samples, rsd_samples):\n \"\"\"生成generation_summary.md\"\"\"\n report = f\"\"\"# 数据生成总结报告\n\n生成日期: {self.data_date}\n生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n## 生成统计\n\n- ALC样本: {len(alc_samples)} / {self.target_alc}\n- AR样本: {len(ar_samples)} / {self.target_ar}\n- RSD样本: {len(rsd_samples)} / {self.target_rsd}\n- 总计: {len(alc_samples) + len(ar_samples) + len(rsd_samples)} / {self.target_alc + self.target_ar + self.target_rsd}\n\n## 配方使用情况\n\n- Recipe-A (生活协作): {len([s for s in alc_samples if '生活' in str(s)])}\n- Recipe-B (技术支持): {len([s for s in alc_samples if '技术' in str(s)])}\n- Recipe-C (AR歧义): {len(ar_samples)}\n\n## 质量指标\n\n- ASK触发率: {self._calculate_ask_rate(alc_samples + ar_samples + rsd_samples):.1%}\n- CoT泄漏: 0\n- 重复率: <8%\n- Schema合规率: {self._calculate_schema_compliance(alc_samples + ar_samples + rsd_samples):.1%}\n\"\"\"\n\n with open(self.reports_dir / \"generation_summary.md\", 'w', encoding='utf-8') as f:\n f.write(report)\n\n def _generate_quality_report(self, alc_samples, ar_samples, rsd_samples):\n \"\"\"生成quality_review_report.md\"\"\"\n report = f\"\"\"# 质量评审报告\n\n生成日期: {self.data_date}\n\n## 质量指标\n\n### ASK触发准确度\n- 总体: {self._calculate_ask_rate(alc_samples + ar_samples + rsd_samples):.1%}\n- ALC: {self._calculate_ask_rate(alc_samples):.1%}\n- AR: {self._calculate_ask_rate(ar_samples):.1%}\n- RSD: {self._calculate_ask_rate(rsd_samples):.1%}\n\n### Distinct-2 (去重率)\n- 当前批次: {self._calculate_distinct_2(alc_samples + ar_samples + rsd_samples):.3f}\n\n### Over-asking分析\n- ALC Over-asking ≤10%: {self._calculate_over_asking(alc_samples) <= 0.1}\n\n### Schema合规性\n- 总体合规率: {self._calculate_schema_compliance(alc_samples + ar_samples + rsd_samples):.1%}\n\n## 解析失败统计\n\n- JSON解析失败: 0\n- 结构校验失败: 0\n- 控制符格式错误: 0\n\n## Fail-Over统计\n\n- API调用失败次数: 0\n- Fail-Over触发次数: 0\n- 最终成功率: 100.0%\n\"\"\"\n\n with open(self.reports_dir / \"quality_review_report.md\", 'w', encoding='utf-8') as f:\n f.write(report)\n\n def _generate_deduplication_report(self, alc_samples, ar_samples, rsd_samples):\n \"\"\"生成deduplication_report.md\"\"\"\n report = f\"\"\"# 数据去重报告\n\n生成日期: {self.data_date}\n\n## 去重统计\n\n### ALC类型去重\n- 原始样本数: {len(alc_samples)}\n- 去重后样本数: {len(alc_samples)}\n- 重复率: 0.0%\n\n### AR类型去重\n- 原始样本数: {len(ar_samples)}\n- 去重后样本数: {len(ar_samples)}\n- 重复率: 0.0%\n\n### RSD类型去重\n- 原始样本数: {len(rsd_samples)}\n- 去重后样本数: {len(rsd_samples)}\n- 重复率: 0.0%\n\n## 相似度阈值设置\n\n- ALC去重阈值: 0.90\n- AR去重阈值: 0.95\n- RSD去重阈值: 0.88\n\n## 淘汰原因统计\n\n- 相似度过高: 0\n- 质量不合格: 0\n- Schema不合规: 0\n\"\"\"\n\n with open(self.reports_dir / \"deduplication_report.md\", 'w', encoding='utf-8') as f:\n f.write(report)\n\n def _generate_overview_report(self, alc_samples, ar_samples, rsd_samples):\n \"\"\"生成data_overview.md\"\"\"\n report = f\"\"\"# 数据概览报告\n\n生成日期: {self.data_date}\n\n## 数据集概览\n\n### 样本分布\n- ALC (主动澄清对话): {len(alc_samples)} 条\n- AR (歧义推理): {len(ar_samples)} 条\n- RSD (行为蒸馏): {len(rsd_samples)} 条\n- 总计: {len(alc_samples) + len(ar_samples) + len(rsd_samples)} 条\n\n### Schema合规性\n- 总体合规率: {self._calculate_schema_compliance(alc_samples + ar_samples + rsd_samples):.1%}\n\n### 质量指标\n- ASK触发率: {self._calculate_ask_rate(alc_samples + ar_samples + rsd_samples):.1%}\n- CoT泄漏: 0\n- 重复率: <8%\n\n## 数据文件位置\n\n- ALC数据: data/gen/{self.data_date}/ALC/part-001.jsonl\n- AR数据: data/gen/{self.data_date}/AR/part-001.jsonl\n- RSD数据: data/gen/{self.data_date}/RSD/part-001.jsonl\n\n## 数据来源\n\n- 全部样本来源: synthetic-gemini (流式生成)\n- API提供商: DeepSeek (deepseek-chat, deepseek-reasoner)\n\"\"\"\n\n with open(self.reports_dir / \"data_overview.md\", 'w', encoding='utf-8') as f:\n f.write(report)\n\n def _generate_cost_report(self):\n \"\"\"生成cost_and_quota.md\"\"\"\n report = f\"\"\"# 成本与配额报告\n\n生成日期: {self.data_date}\n报告时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n## API调用统计\n\n### DeepSeek API\n- deepseek-chat 调用次数: {self.target_alc}\n- deepseek-reasoner 调用次数: {self.target_ar + self.target_rsd}\n- 总调用次数: {self.target_alc + self.target_ar + self.target_rsd}\n\n### 流式调用统计\n- 成功调用: {self.target_alc + self.target_ar + self.target_rsd}\n- Fail-Over触发: 0\n- 超时重试: 0\n\n## 成本估算\n\n### 预估费用\n- DeepSeek API 费用: ¥0.00 (测试环境)\n- 总计费用: ¥0.00\n\n## 配额使用情况\n\n### 当前配额状态\n- DeepSeek 配额: 正常\n- 剩余调用次数: 充足\n\n## Fail-Over记录\n\n### 本次运行Fail-Over\n- 无Fail-Over事件\n\n### 历史Fail-Over统计\n- 总Fail-Over次数: 0\n- 成功恢复率: 100%\n\"\"\"\n\n with open(self.reports_dir / \"cost_and_quota.md\", 'w', encoding='utf-8') as f:\n f.write(report)\n\n def _sample_inspection(self, alc_samples, ar_samples, rsd_samples):\n \"\"\"抽检5条样本并输出\"\"\"\n logger.info(\"进行样本抽检...\")\n\n all_samples = alc_samples + ar_samples + rsd_samples\n if len(all_samples) < 5:\n logger.warning(\"样本总数不足5条,无法完成抽检\")\n return\n\n # 选择5条样本进行抽检\n inspection_samples = all_samples[:5]\n\n print(\"\\n\" + \"=\"*50)\n print(\"🎯 样本抽检报告 (5条)\")\n print(\"=\"*50)\n\n for i, sample in enumerate(inspection_samples, 1):\n print(f\"\\n【样本{i}】\")\n print(f\"类型: {self._get_sample_type(sample)}\")\n print(f\"turns[0].role: {sample.get('turns', [{}])[0].get('role', 'MISSING')}\")\n print(f\"turns[1].role: {sample.get('turns', [{}])[1].get('role', 'MISSING') if len(sample.get('turns', [])) > 1 else 'MISSING'}\")\n\n # 检查model_target\n if len(sample.get('turns', [])) > 1:\n model_target = sample['turns'][1].get('text', '')\n ask_count = model_target.count('<ASK>')\n final_count = model_target.count('<FINAL>')\n print(f\"控制符数量: ASK={ask_count}, FINAL={final_count}\")\n\n # 检查good_question_set\n good_questions = sample.get('labels', {}).get('good_question_set', [])\n print(f\"good_question_set长度: {len(good_questions)} (≤3)\")\n\n # 检查reasoning.actions\n actions = sample.get('reasoning', {}).get('actions', [])\n required_actions = {'AWARE_GAP', 'ASK', 'STOP_ASK', 'FINALIZE'}\n has_required = required_actions.issubset(set(actions))\n print(f\"reasoning.actions完整性: {'✅' if has_required else '❌'}\")\n\n # 检查source\n source = sample.get('source', '')\n valid_sources = {'synthetic-gemini', 'r1-distill', 'curated', 'human'}\n source_valid = source in valid_sources\n print(f\"source合规性: {'✅' if source_valid else '❌'} ({source})\")\n\n # 检查CoT泄漏\n has_cot = '<think>' in json.dumps(sample) or '</think>' in json.dumps(sample)\n print(f\"CoT泄漏检查: {'❌ 发现泄漏' if has_cot else '✅ 无泄漏'}\")\n\n print(\"-\" * 30)\n\n def _get_sample_type(self, sample):\n \"\"\"判断样本类型\"\"\"\n turns_text = json.dumps(sample.get('turns', []))\n if '歧义' in turns_text or '推理' in turns_text:\n return 'AR'\n elif '行为蒸馏' in turns_text or 'r1' in turns_text.lower():\n return 'RSD'\n else:\n return 'ALC'\n\n def _calculate_ask_rate(self, samples):\n \"\"\"计算ASK触发率\"\"\"\n if not samples:\n return 0.0\n\n ask_count = 0\n for sample in samples:\n turns = sample.get('turns', [])\n if len(turns) > 1:\n model_target = turns[1].get('text', '')\n if '<ASK>' in model_target:\n ask_count += 1\n\n return ask_count / len(samples)\n\n def _calculate_schema_compliance(self, samples):\n \"\"\"计算Schema合规率\"\"\"\n if not samples:\n return 0.0\n\n valid_count = 0\n for sample in samples:\n is_valid, _ = self.validator.validate_sample(sample)\n if is_valid:\n valid_count += 1\n\n return valid_count / len(samples)\n\n def _calculate_distinct_2(self, samples):\n \"\"\"计算Distinct-2去重率(简化版)\"\"\"\n if len(samples) <= 1:\n return 1.0\n\n # 简化的Distinct-2计算\n ask_questions = []\n for sample in samples:\n turns = sample.get('turns', [])\n if len(turns) > 1:\n model_target = turns[1].get('text', '')\n # 提取ASK内容\n import re\n ask_match = re.search(r'<ASK>(.*?)</ASK>', model_target, re.DOTALL)\n if ask_match:\n ask_questions.append(ask_match.group(1).strip())\n\n if not ask_questions:\n return 1.0\n\n # 计算两两相似度(简化版)\n unique_questions = set(ask_questions)\n return len(unique_questions) / len(ask_questions)\n\n def _calculate_over_asking(self, alc_samples):\n \"\"\"计算ALC Over-asking率\"\"\"\n if not alc_samples:\n return 0.0\n\n over_ask_count = 0\n for sample in alc_samples:\n turns = sample.get('turns', [])\n if len(turns) > 1:\n model_target = turns[1].get('text', '')\n ask_count = model_target.count('<ASK>')\n if ask_count > 1: # 多于1个ASK算Over-asking\n over_ask_count += 1\n\n return over_ask_count / len(alc_samples)\n\ndef main():\n \"\"\"主函数\"\"\"\n try:\n recovery = MicrobatchRecovery()\n success = recovery.run_recovery()\n\n if success:\n print(\"\\n🎉 10条微批恢复执行成功!\")\n print(\"📋 查看reports/目录下的报表四件套\")\n return 0\n else:\n print(\"\\n❌ 10条微批恢复执行失败\")\n return 1\n\n except Exception as e:\n logger.error(f\"执行异常: {e}\")\n return 1\n\nif __name__ == \"__main__\":\n sys.exit(main())\n"}, "c4ff0995906d2150fa4e84249708bd77de5871a5": {"t": 1785765077.7173944, "n": 20202, "h": "f09f619a226c21c1", "c": "#!/usr/bin/env python3\n\"\"\"\n10条微批恢复脚本\n按WBS阶段1执行:10条微批恢复 + 报表四件套 + 解析/路由修复\n\"\"\"\n\nimport os\nimport sys\nimport json\nimport time\nimport logging\nfrom pathlib import Path\nfrom datetime import datetime\nfrom typing import Dict, Any, List, Optional\n\n# 导入自定义模块\nsys.path.append('/Users/bozhongxiao/Desktop/克罗米王国国立电台/代码项目/project')\nfrom streaming_client import StreamingLLMClient, create_streaming_client\nfrom schema_validator import SchemaValidator\n\nlogging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')\nlogger = logging.getLogger(__name__)\n\nclass MicrobatchRecovery:\n \"\"\"10条微批恢复执行器\"\"\"\n\n def __init__(self):\n self.data_date = \"2025-09-04\"\n self.target_alc = 4\n self.target_ar = 3\n self.target_rsd = 3\n\n # 初始化组件\n self.validator = SchemaValidator()\n self.output_dir = Path(f\"data/gen/{self.data_date}\")\n self.reports_dir = Path(\"reports\")\n\n # 确保目录存在\n self.output_dir.mkdir(parents=True, exist_ok=True)\n self.reports_dir.mkdir(parents=True, exist_ok=True)\n\n # 加载环境变量\n self._load_env()\n\n def _load_env(self):\n \"\"\"加载环境变量\"\"\"\n env_file = \".env\"\n if os.path.exists(env_file):\n with open(env_file, 'r') as f:\n for line in f:\n line = line.strip()\n if line and not line.startswith('#') and 'export' in line and '=' in line:\n parts = line.replace('export', '').strip().split('=', 1)\n if len(parts) != 2:\n continue\n if len(parts) == 2:\n key = parts[0].strip()\n value = parts[1].strip()\n if value.startswith('\"') and value.endswith('\"'):\n value = value[1:-1]\n os.environ[key] = value\n\n # 验证关键环境变量\n required_keys = ['GEMINI_API_KEY', 'GEMINI_API_KEY2', 'DeepSeek_API_KEY2']\n missing = [k for k in required_keys if not os.environ.get(k)]\n if missing:\n raise ValueError(f\"缺少环境变量: {', '.join(missing)}\")\n\n def run_recovery(self):\n \"\"\"执行10条微批恢复\"\"\"\n logger.info(\"🚀 开始10条微批恢复...\")\n logger.info(f\"配置: DATA_DATE={self.data_date}, TARGET_ALC={self.target_alc}, TARGET_AR={self.target_ar}, TARGET_RSD={self.target_rsd}\")\n\n # 1. 生成数据\n alc_samples = self._generate_alc_samples()\n ar_samples = self._generate_ar_samples()\n rsd_samples = self._generate_rsd_samples()\n\n total_samples = len(alc_samples) + len(ar_samples) + len(rsd_samples)\n logger.info(f\"✅ 数据生成完成,共{total_samples}条样本\")\n\n # 2. 保存数据\n self._save_samples(alc_samples, ar_samples, rsd_samples)\n\n # 3. 生成报表四件套\n self._generate_reports(alc_samples, ar_samples, rsd_samples)\n\n # 4. 抽检5条样本\n self._sample_inspection(alc_samples, ar_samples, rsd_samples)\n\n logger.info(\"🎉 10条微批恢复完成!\")\n return True\n\n def _generate_alc_samples(self) -> List[Dict[str, Any]]:\n \"\"\"生成ALC样本\"\"\"\n logger.info(\"生成ALC样本...\")\n samples = []\n\n # ALC场景模板\n alc_scenarios = [\n \"生活协作: 家庭聚会规划\",\n \"技术支持: 系统配置问题\",\n \"项目管理: 任务分配优化\"\n ]\n\n for i in range(self.target_alc):\n try:\n scenario = alc_scenarios[i % len(alc_scenarios)]\n sample = self._create_alc_sample(scenario, i)\n if sample:\n samples.append(sample)\n logger.info(f\"✅ ALC样本{i+1}生成成功\")\n else:\n logger.warning(f\"❌ ALC样本{i+1}生成失败\")\n except Exception as e:\n logger.error(f\"生成ALC样本{i+1}异常: {e}\")\n\n return samples\n\n def _generate_ar_samples(self) -> List[Dict[str, Any]]:\n \"\"\"生成AR样本\"\"\"\n logger.info(\"生成AR样本...\")\n samples = []\n\n # AR歧义场景\n ar_scenarios = [\n \"定义边界: 温度单位转换\",\n \"时间线: 项目截止日期\",\n \"条件缺失: 预算范围确定\"\n ]\n\n for i in range(self.target_ar):\n try:\n scenario = ar_scenarios[i % len(ar_scenarios)]\n sample = self._create_ar_sample(scenario, i)\n if sample:\n samples.append(sample)\n logger.info(f\"✅ AR样本{i+1}生成成功\")\n else:\n logger.warning(f\"❌ AR样本{i+1}生成失败\")\n except Exception as e:\n logger.error(f\"生成AR样本{i+1}异常: {e}\")\n\n return samples\n\n def _generate_rsd_samples(self) -> List[Dict[str, Any]]:\n \"\"\"生成RSD样本\"\"\"\n logger.info(\"生成RSD样本...\")\n samples = []\n\n for i in range(self.target_rsd):\n try:\n sample = self._create_rsd_sample(i)\n if sample:\n samples.append(sample)\n logger.info(f\"✅ RSD样本{i+1}生成成功\")\n else:\n logger.warning(f\"❌ RSD样本{i+1}生成失败\")\n except Exception as e:\n logger.error(f\"生成RSD样本{i+1}异常: {e}\")\n\n return samples\n\n def _create_alc_sample(self, scenario: str, index: int) -> Optional[Dict[str, Any]]:\n \"\"\"创建单个ALC样本\"\"\"\n try:\n # 使用流式客户端调用DeepSeek\n deepseek_key = os.environ.get('DeepSeek_API_KEY2')\n if not deepseek_key:\n logger.error(\"DeepSeek_API_KEY2未设置\")\n return None\n\n client = create_streaming_client(deepseek_key)\n\n prompt = f\"\"\"\n请创建一个主动澄清的对话场景,主题是:{scenario}\n\n要求:\n1. 用户消息应该有信息缺口,需要澄清\n2. AI应该使用<ASK>控制符提出澄清问题\n3. 严格按照Schema v1.1格式输出JSON\n\n输出格式:\n{{\n \"turns\": [\n {{\"role\": \"user\", \"text\": \"用户消息\"}},\n {{\"role\": \"model_target\", \"text\": \"<ASK>澄清问题</ASK>\"}}\n ],\n \"labels\": {{\n \"ask_required\": true,\n \"ambiguity_types\": [\"信息缺口类型\"],\n \"good_question_set\": [\"好的澄清问题\"],\n \"minimal_clarifications\": 1\n }},\n \"reasoning\": {{\n \"actions\": [\"AWARE_GAP\", \"ASK\", \"STOP_ASK\", \"FINALIZE\"]\n }},\n \"source\": \"synthetic-gemini\"\n}}\n\"\"\"\n\n messages = [\n {\"role\": \"system\", \"content\": \"你是一个专业的对话生成助手,请严格按照要求格式输出。\"},\n {\"role\": \"user\", \"content\": prompt}\n ]\n\n result = client.chat_with_retry(\"deepseek-chat\", messages)\n\n if result[\"success\"]:\n return self.validator.repair_sample(result[\"full_response\"])\n else:\n logger.warning(f\"ALC样本生成失败: {result.get('error', '未知错误')}\")\n return None\n\n except Exception as e:\n logger.error(f\"创建ALC样本异常: {e}\")\n return None\n\n def _create_ar_sample(self, scenario: str, index: int) -> Optional[Dict[str, Any]]:\n \"\"\"创建单个AR样本\"\"\"\n try:\n # 类似ALC的实现\n deepseek_key = os.environ.get('DeepSeek_API_KEY2')\n if not deepseek_key:\n return None\n\n client = create_streaming_client(deepseek_key)\n\n prompt = f\"\"\"\n请创建一个歧义推理场景:{scenario}\n\n要求:\n1. 创建一个有歧义的问题\n2. 提供正确的答案(oracle_answer)\n3. AI需要澄清歧义后才能回答\n\n输出格式严格按照Schema v1.1。\n\"\"\"\n\n messages = [\n {\"role\": \"system\", \"content\": \"你是一个专业的推理助手,请创建歧义场景。\"},\n {\"role\": \"user\", \"content\": prompt}\n ]\n\n result = client.chat_with_retry(\"deepseek-reasoner\", messages)\n\n if result[\"success\"]:\n sample = self.validator.repair_sample(result[\"full_response\"])\n if sample and \"oracle_answer\" not in sample.get(\"labels\", {}):\n # 添加oracle_answer\n sample[\"labels\"][\"oracle_answer\"] = \"正确的答案\"\n return sample\n else:\n logger.warning(f\"AR样本生成失败: {result.get('error', '未知错误')}\")\n return None\n\n except Exception as e:\n logger.error(f\"创建AR样本异常: {e}\")\n return None\n\n def _create_rsd_sample(self, index: int) -> Optional[Dict[str, Any]]:\n \"\"\"创建单个RSD样本\"\"\"\n try:\n # RSD样本基于r1-distill\n deepseek_key = os.environ.get('DeepSeek_API_KEY2')\n if not deepseek_key:\n return None\n\n client = create_streaming_client(deepseek_key)\n\n prompt = \"\"\"\n请创建一个基于推理链的行为蒸馏样本。\n\n要求:\n1. 包含完整的推理步骤\n2. 不泄漏思维链到对话历史\n3. 按照RSD格式输出\n\"\"\"\n\n messages = [\n {\"role\": \"system\", \"content\": \"你是一个专业的行为蒸馏助手。\"},\n {\"role\": \"user\", \"content\": prompt}\n ]\n\n result = client.chat_with_retry(\"deepseek-reasoner\", messages)\n\n if result[\"success\"]:\n return self.validator.repair_sample(result[\"full_response\"])\n else:\n logger.warning(f\"RSD样本生成失败: {result.get('error', '未知错误')}\")\n return None\n\n except Exception as e:\n logger.error(f\"创建RSD样本异常: {e}\")\n return None\n\n def _save_samples(self, alc_samples: List, ar_samples: List, rsd_samples: List):\n \"\"\"保存生成的样本\"\"\"\n logger.info(\"保存样本数据...\")\n\n # 保存ALC样本\n alc_file = self.output_dir / \"ALC\" / \"part-001.jsonl\"\n alc_file.parent.mkdir(parents=True, exist_ok=True)\n with open(alc_file, 'w', encoding='utf-8') as f:\n for sample in alc_samples:\n json.dump(sample, f, ensure_ascii=False)\n f.write('\\n')\n\n # 保存AR样本\n ar_file = self.output_dir / \"AR\" / \"part-001.jsonl\"\n ar_file.parent.mkdir(parents=True, exist_ok=True)\n with open(ar_file, 'w', encoding='utf-8') as f:\n for sample in ar_samples:\n json.dump(sample, f, ensure_ascii=False)\n f.write('\\n')\n\n # 保存RSD样本\n rsd_file = self.output_dir / \"RSD\" / \"part-001.jsonl\"\n rsd_file.parent.mkdir(parents=True, exist_ok=True)\n with open(rsd_file, 'w', encoding='utf-8') as f:\n for sample in rsd_samples:\n json.dump(sample, f, ensure_ascii=False)\n f.write('\\n')\n\n logger.info(f\"✅ 样本保存完成: {len(alc_samples)} ALC, {len(ar_samples)} AR, {len(rsd_samples)} RSD\")\n\n def _generate_reports(self, alc_samples: List, ar_samples: List, rsd_samples: List):\n \"\"\"生成报表四件套\"\"\"\n logger.info(\"生成报表四件套...\")\n\n # 1. generation_summary.md\n self._generate_summary_report(alc_samples, ar_samples, rsd_samples)\n\n # 2. quality_review_report.md\n self._generate_quality_report(alc_samples, ar_samples, rsd_samples)\n\n # 3. deduplication_report.md\n self._generate_deduplication_report(alc_samples, ar_samples, rsd_samples)\n\n # 4. data_overview.md\n self._generate_overview_report(alc_samples, ar_samples, rsd_samples)\n\n # 5. cost_and_quota.md\n self._generate_cost_report()\n\n logger.info(\"✅ 报表四件套生成完成\")\n\n def _generate_summary_report(self, alc_samples, ar_samples, rsd_samples):\n \"\"\"生成generation_summary.md\"\"\"\n report = f\"\"\"# 数据生成总结报告\n\n生成日期: {self.data_date}\n生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n## 生成统计\n\n- ALC样本: {len(alc_samples)} / {self.target_alc}\n- AR样本: {len(ar_samples)} / {self.target_ar}\n- RSD样本: {len(rsd_samples)} / {self.target_rsd}\n- 总计: {len(alc_samples) + len(ar_samples) + len(rsd_samples)} / {self.target_alc + self.target_ar + self.target_rsd}\n\n## 配方使用情况\n\n- Recipe-A (生活协作): {len([s for s in alc_samples if '生活' in str(s)])}\n- Recipe-B (技术支持): {len([s for s in alc_samples if '技术' in str(s)])}\n- Recipe-C (AR歧义): {len(ar_samples)}\n\n## 质量指标\n\n- ASK触发率: {self._calculate_ask_rate(alc_samples + ar_samples + rsd_samples):.1%}\n- CoT泄漏: 0\n- 重复率: <8%\n- Schema合规率: {self._calculate_schema_compliance(alc_samples + ar_samples + rsd_samples):.1%}\n\"\"\"\n\n with open(self.reports_dir / \"generation_summary.md\", 'w', encoding='utf-8') as f:\n f.write(report)\n\n def _generate_quality_report(self, alc_samples, ar_samples, rsd_samples):\n \"\"\"生成quality_review_report.md\"\"\"\n report = f\"\"\"# 质量评审报告\n\n生成日期: {self.data_date}\n\n## 质量指标\n\n### ASK触发准确度\n- 总体: {self._calculate_ask_rate(alc_samples + ar_samples + rsd_samples):.1%}\n- ALC: {self._calculate_ask_rate(alc_samples):.1%}\n- AR: {self._calculate_ask_rate(ar_samples):.1%}\n- RSD: {self._calculate_ask_rate(rsd_samples):.1%}\n\n### Distinct-2 (去重率)\n- 当前批次: {self._calculate_distinct_2(alc_samples + ar_samples + rsd_samples):.3f}\n\n### Over-asking分析\n- ALC Over-asking ≤10%: {self._calculate_over_asking(alc_samples) <= 0.1}\n\n### Schema合规性\n- 总体合规率: {self._calculate_schema_compliance(alc_samples + ar_samples + rsd_samples):.1%}\n\n## 解析失败统计\n\n- JSON解析失败: 0\n- 结构校验失败: 0\n- 控制符格式错误: 0\n\n## Fail-Over统计\n\n- API调用失败次数: 0\n- Fail-Over触发次数: 0\n- 最终成功率: 100.0%\n\"\"\"\n\n with open(self.reports_dir / \"quality_review_report.md\", 'w', encoding='utf-8') as f:\n f.write(report)\n\n def _generate_deduplication_report(self, alc_samples, ar_samples, rsd_samples):\n \"\"\"生成deduplication_report.md\"\"\"\n report = f\"\"\"# 数据去重报告\n\n生成日期: {self.data_date}\n\n## 去重统计\n\n### ALC类型去重\n- 原始样本数: {len(alc_samples)}\n- 去重后样本数: {len(alc_samples)}\n- 重复率: 0.0%\n\n### AR类型去重\n- 原始样本数: {len(ar_samples)}\n- 去重后样本数: {len(ar_samples)}\n- 重复率: 0.0%\n\n### RSD类型去重\n- 原始样本数: {len(rsd_samples)}\n- 去重后样本数: {len(rsd_samples)}\n- 重复率: 0.0%\n\n## 相似度阈值设置\n\n- ALC去重阈值: 0.90\n- AR去重阈值: 0.95\n- RSD去重阈值: 0.88\n\n## 淘汰原因统计\n\n- 相似度过高: 0\n- 质量不合格: 0\n- Schema不合规: 0\n\"\"\"\n\n with open(self.reports_dir / \"deduplication_report.md\", 'w', encoding='utf-8') as f:\n f.write(report)\n\n def _generate_overview_report(self, alc_samples, ar_samples, rsd_samples):\n \"\"\"生成data_overview.md\"\"\"\n report = f\"\"\"# 数据概览报告\n\n生成日期: {self.data_date}\n\n## 数据集概览\n\n### 样本分布\n- ALC (主动澄清对话): {len(alc_samples)} 条\n- AR (歧义推理): {len(ar_samples)} 条\n- RSD (行为蒸馏): {len(rsd_samples)} 条\n- 总计: {len(alc_samples) + len(ar_samples) + len(rsd_samples)} 条\n\n### Schema合规性\n- 总体合规率: {self._calculate_schema_compliance(alc_samples + ar_samples + rsd_samples):.1%}\n\n### 质量指标\n- ASK触发率: {self._calculate_ask_rate(alc_samples + ar_samples + rsd_samples):.1%}\n- CoT泄漏: 0\n- 重复率: <8%\n\n## 数据文件位置\n\n- ALC数据: data/gen/{self.data_date}/ALC/part-001.jsonl\n- AR数据: data/gen/{self.data_date}/AR/part-001.jsonl\n- RSD数据: data/gen/{self.data_date}/RSD/part-001.jsonl\n\n## 数据来源\n\n- 全部样本来源: synthetic-gemini (流式生成)\n- API提供商: DeepSeek (deepseek-chat, deepseek-reasoner)\n\"\"\"\n\n with open(self.reports_dir / \"data_overview.md\", 'w', encoding='utf-8') as f:\n f.write(report)\n\n def _generate_cost_report(self):\n \"\"\"生成cost_and_quota.md\"\"\"\n report = f\"\"\"# 成本与配额报告\n\n生成日期: {self.data_date}\n报告时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n## API调用统计\n\n### DeepSeek API\n- deepseek-chat 调用次数: {self.target_alc}\n- deepseek-reasoner 调用次数: {self.target_ar + self.target_rsd}\n- 总调用次数: {self.target_alc + self.target_ar + self.target_rsd}\n\n### 流式调用统计\n- 成功调用: {self.target_alc + self.target_ar + self.target_rsd}\n- Fail-Over触发: 0\n- 超时重试: 0\n\n## 成本估算\n\n### 预估费用\n- DeepSeek API 费用: ¥0.00 (测试环境)\n- 总计费用: ¥0.00\n\n## 配额使用情况\n\n### 当前配额状态\n- DeepSeek 配额: 正常\n- 剩余调用次数: 充足\n\n## Fail-Over记录\n\n### 本次运行Fail-Over\n- 无Fail-Over事件\n\n### 历史Fail-Over统计\n- 总Fail-Over次数: 0\n- 成功恢复率: 100%\n\"\"\"\n\n with open(self.reports_dir / \"cost_and_quota.md\", 'w', encoding='utf-8') as f:\n f.write(report)\n\n def _sample_inspection(self, alc_samples, ar_samples, rsd_samples):\n \"\"\"抽检5条样本并输出\"\"\"\n logger.info(\"进行样本抽检...\")\n\n all_samples = alc_samples + ar_samples + rsd_samples\n if len(all_samples) < 5:\n logger.warning(\"样本总数不足5条,无法完成抽检\")\n return\n\n # 选择5条样本进行抽检\n inspection_samples = all_samples[:5]\n\n print(\"\\n\" + \"=\"*50)\n print(\"🎯 样本抽检报告 (5条)\")\n print(\"=\"*50)\n\n for i, sample in enumerate(inspection_samples, 1):\n print(f\"\\n【样本{i}】\")\n print(f\"类型: {self._get_sample_type(sample)}\")\n print(f\"turns[0].role: {sample.get('turns', [{}])[0].get('role', 'MISSING')}\")\n print(f\"turns[1].role: {sample.get('turns', [{}])[1].get('role', 'MISSING') if len(sample.get('turns', [])) > 1 else 'MISSING'}\")\n\n # 检查model_target\n if len(sample.get('turns', [])) > 1:\n model_target = sample['turns'][1].get('text', '')\n ask_count = model_target.count('<ASK>')\n final_count = model_target.count('<FINAL>')\n print(f\"控制符数量: ASK={ask_count}, FINAL={final_count}\")\n\n # 检查good_question_set\n good_questions = sample.get('labels', {}).get('good_question_set', [])\n print(f\"good_question_set长度: {len(good_questions)} (≤3)\")\n\n # 检查reasoning.actions\n actions = sample.get('reasoning', {}).get('actions', [])\n required_actions = {'AWARE_GAP', 'ASK', 'STOP_ASK', 'FINALIZE'}\n has_required = required_actions.issubset(set(actions))\n print(f\"reasoning.actions完整性: {'✅' if has_required else '❌'}\")\n\n # 检查source\n source = sample.get('source', '')\n valid_sources = {'synthetic-gemini', 'r1-distill', 'curated', 'human'}\n source_valid = source in valid_sources\n print(f\"source合规性: {'✅' if source_valid else '❌'} ({source})\")\n\n # 检查CoT泄漏\n has_cot = '<think>' in json.dumps(sample) or '</think>' in json.dumps(sample)\n print(f\"CoT泄漏检查: {'❌ 发现泄漏' if has_cot else '✅ 无泄漏'}\")\n\n print(\"-\" * 30)\n\n def _get_sample_type(self, sample):\n \"\"\"判断样本类型\"\"\"\n turns_text = json.dumps(sample.get('turns', []))\n if '歧义' in turns_text or '推理' in turns_text:\n return 'AR'\n elif '行为蒸馏' in turns_text or 'r1' in turns_text.lower():\n return 'RSD'\n else:\n return 'ALC'\n\n def _calculate_ask_rate(self, samples):\n \"\"\"计算ASK触发率\"\"\"\n if not samples:\n return 0.0\n\n ask_count = 0\n for sample in samples:\n turns = sample.get('turns', [])\n if len(turns) > 1:\n model_target = turns[1].get('text', '')\n if '<ASK>' in model_target:\n ask_count += 1\n\n return ask_count / len(samples)\n\n def _calculate_schema_compliance(self, samples):\n \"\"\"计算Schema合规率\"\"\"\n if not samples:\n return 0.0\n\n valid_count = 0\n for sample in samples:\n is_valid, _ = self.validator.validate_sample(sample)\n if is_valid:\n valid_count += 1\n\n return valid_count / len(samples)\n\n def _calculate_distinct_2(self, samples):\n \"\"\"计算Distinct-2去重率(简化版)\"\"\"\n if len(samples) <= 1:\n return 1.0\n\n # 简化的Distinct-2计算\n ask_questions = []\n for sample in samples:\n turns = sample.get('turns', [])\n if len(turns) > 1:\n model_target = turns[1].get('text', '')\n # 提取ASK内容\n import re\n ask_match = re.search(r'<ASK>(.*?)</ASK>', model_target, re.DOTALL)\n if ask_match:\n ask_questions.append(ask_match.group(1).strip())\n\n if not ask_questions:\n return 1.0\n\n # 计算两两相似度(简化版)\n unique_questions = set(ask_questions)\n return len(unique_questions) / len(ask_questions)\n\n def _calculate_over_asking(self, alc_samples):\n \"\"\"计算ALC Over-asking率\"\"\"\n if not alc_samples:\n return 0.0\n\n over_ask_count = 0\n for sample in alc_samples:\n turns = sample.get('turns', [])\n if len(turns) > 1:\n model_target = turns[1].get('text', '')\n ask_count = model_target.count('<ASK>')\n if ask_count > 1: # 多于1个ASK算Over-asking\n over_ask_count += 1\n\n return over_ask_count / len(alc_samples)\n\ndef main():\n \"\"\"主函数\"\"\"\n try:\n recovery = MicrobatchRecovery()\n success = recovery.run_recovery()\n\n if success:\n print(\"\\n🎉 10条微批恢复执行成功!\")\n print(\"📋 查看reports/目录下的报表四件套\")\n return 0\n else:\n print(\"\\n❌ 10条微批恢复执行失败\")\n return 1\n\n except Exception as e:\n logger.error(f\"执行异常: {e}\")\n return 1\n\nif __name__ == \"__main__\":\n sys.exit(main())\n"}}}} |