feat(nas): 模型调用统计镜像与界面 - sync_model_calls 表+拉取+聚合统计;fam-ui 新增'🤖 模型统计'页(按模型成功/失败/成功率/平均耗时 + 最近调用明细含失败原因)
This commit is contained in:
@@ -268,6 +268,74 @@ def get_sync_people() -> List[Dict]:
|
||||
conn.close()
|
||||
|
||||
|
||||
def upsert_sync_model_calls(rows: List[Dict]) -> int:
|
||||
"""批量 upsert Oracle 传来的 model_calls 增量(幂等,重复覆盖)。"""
|
||||
if not rows:
|
||||
return 0
|
||||
conn = get_conn()
|
||||
n = 0
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
for r in rows:
|
||||
cur.execute(
|
||||
"""INSERT INTO sync_model_calls
|
||||
(id, provider, model, video_id, filename, started_at,
|
||||
duration_sec, success, error, created_at, synced_at)
|
||||
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s, NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
provider=VALUES(provider),
|
||||
model=VALUES(model),
|
||||
video_id=VALUES(video_id),
|
||||
filename=VALUES(filename),
|
||||
started_at=VALUES(started_at),
|
||||
duration_sec=VALUES(duration_sec),
|
||||
success=VALUES(success),
|
||||
error=VALUES(error),
|
||||
created_at=VALUES(created_at),
|
||||
synced_at=NOW()""",
|
||||
(r.get('id'), r.get('provider'), r.get('model'),
|
||||
r.get('video_id'), r.get('filename'), r.get('started_at'),
|
||||
r.get('duration_sec') or 0, 1 if r.get('success') else 0,
|
||||
(r.get('error') or '')[:500], r.get('created_at')))
|
||||
n += 1
|
||||
conn.commit()
|
||||
return n
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_sync_model_calls(limit: int = 200) -> List[Dict]:
|
||||
"""最近模型调用记录(前端统计展示)。"""
|
||||
conn = get_conn()
|
||||
try:
|
||||
cur = conn.cursor(pymysql.cursors.DictCursor)
|
||||
cur.execute(
|
||||
"SELECT id, provider, model, video_id, filename, started_at, "
|
||||
"duration_sec, success, error, created_at "
|
||||
"FROM sync_model_calls ORDER BY id DESC LIMIT %s", (limit,))
|
||||
return cur.fetchall()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_sync_model_calls_stats() -> Dict:
|
||||
"""模型调用统计:按 provider+model 聚合成功/失败/平均耗时。"""
|
||||
conn = get_conn()
|
||||
try:
|
||||
cur = conn.cursor(pymysql.cursors.DictCursor)
|
||||
cur.execute(
|
||||
"""SELECT provider, model,
|
||||
SUM(CASE WHEN success=1 THEN 1 ELSE 0 END) AS ok_cnt,
|
||||
SUM(CASE WHEN success=0 THEN 1 ELSE 0 END) AS fail_cnt,
|
||||
ROUND(AVG(duration_sec), 1) AS avg_duration,
|
||||
MAX(created_at) AS last_call
|
||||
FROM sync_model_calls
|
||||
GROUP BY provider, model ORDER BY provider, model""")
|
||||
return cur.fetchall()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_sync_named_members() -> List[str]:
|
||||
"""已命名成员的真名列表(供 UI 下拉 / 快捷选择)。"""
|
||||
conn = get_conn()
|
||||
|
||||
@@ -83,21 +83,23 @@ class OracleSync:
|
||||
videos = data.get('videos', []) or []
|
||||
events = data.get('events', []) or []
|
||||
people = data.get('people', []) or []
|
||||
model_calls = data.get('model_calls', []) or []
|
||||
server_time = data.get('server_time', '') or ''
|
||||
|
||||
n_videos = db_layer.upsert_sync_videos(videos)
|
||||
n_events = db_layer.upsert_sync_events(events)
|
||||
n_people = db_layer.upsert_sync_people(people)
|
||||
n_calls = db_layer.upsert_sync_model_calls(model_calls)
|
||||
|
||||
if server_time:
|
||||
db_layer.set_sync_cursor(server_time)
|
||||
|
||||
self._last_sync_at = datetime.now()
|
||||
self._last_error = None
|
||||
self._last_count = (n_videos, n_events, n_people)
|
||||
self._last_count = (n_videos, n_events, n_people, n_calls)
|
||||
logger.info(
|
||||
f"同步完成: videos+{n_videos} events+{n_events} people+{n_people} "
|
||||
f"since={since!r} -> server_time={server_time}")
|
||||
f"model_calls+{n_calls} since={since!r} -> server_time={server_time}")
|
||||
return True
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user