ns = params_kw.copy() limit = int(ns.get('limit') or 200) single_id = ns.get('id') or None from ahserver.filestorage import FileStorage import os db = DBPools() dbname = get_module_dbname('llmage') recovered = 0 failed = 0 skipped = 0 errors = [] try: async with db.sqlorContext(dbname) as sor: if single_id: sql = """select a.id, a.llmid, a.ioinfo, a.status, b.model from llmusage a left join llm b on a.llmid = b.id where a.id = ${id}$""" params = {'id': single_id} else: sql = """select a.id, a.llmid, a.ioinfo, a.status, b.model from llmusage a left join llm b on a.llmid = b.id where a.usages is null and a.status = 'SUCCEEDED' order by a.use_date desc""" params = {'page': 1, 'rows': limit} recs = await sor.sqlExe(sql, params) if isinstance(recs, dict): rows = recs.get('rows', []) else: rows = recs if recs else [] if not rows: return { "widgettype": "Message", "options": { "title": "恢复Usages", "cwidth": 20, "cheight": 5, "timeout": 5, "message": "没有找到需要恢复的记录" } } fs = FileStorage() for r in rows: rid = r.id if hasattr(r, 'id') else r.get('id', '') model = r.model if hasattr(r, 'model') else r.get('model', '') ioinfo = r.ioinfo if hasattr(r, 'ioinfo') else r.get('ioinfo', None) if not ioinfo: skipped += 1 continue try: # ioinfo 可能是 JSON 内容,也可能是文件路径 io_data = None if ioinfo.startswith('{') or ioinfo.startswith('"'): # 直接是 JSON 内容 io_data = json.loads(ioinfo) else: # 文件路径 real_path = fs.realPath(ioinfo) if not os.path.isfile(real_path): errors.append(f'{rid}: 文件不存在') failed += 1 continue with open(real_path, 'r', encoding='utf-8') as f: io_data = json.load(f) outputs = io_data.get('output', []) if not outputs: errors.append(f'{rid}: output为空') failed += 1 continue # 从最后一条output开始倒序找usage usage = None for out in reversed(outputs): if isinstance(out, dict) and out.get('usage'): usage = out['usage'] break if not usage: errors.append(f'{rid}: output中未找到usage') failed += 1 continue usages_str = json.dumps(usage, ensure_ascii=False) await sor.U('llmusage', { 'id': rid, 'usages': usages_str }) recovered += 1 except Exception as e: debug(f'recover_usages error for {rid}: {e}') errors.append(f'{rid}: {e}') failed += 1 except Exception as e: exception(f'recover_usages error: {e}') return { "widgettype": "Error", "options": { "title": "恢复Usages失败", "cwidth": 20, "cheight": 5, "timeout": 5, "message": str(e) } } total = recovered + failed + skipped msg = f"处理 {total} 条: 恢复成功 {recovered}, 失败 {failed}, 跳过 {skipped}" if errors: msg += f"\n失败详情(前5条): {'; '.join(errors[:5])}" return { "widgettype": "Message", "options": { "title": "恢复Usages完成", "cwidth": 30, "cheight": 6, "timeout": 8, "message": msg } }