run_stats.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. """
  2. run_stats.py — 运行统计工具
  3. =============================
  4. 为 Optimizer agent 提供运行数据分析和配置更新能力。
  5. 连接 AgentPaaS API 获取 agent 运行数据。
  6. """
  7. from __future__ import annotations
  8. import json
  9. import os
  10. import urllib.request
  11. import urllib.error
  12. from pathlib import Path
  13. from typing import Dict, List, Optional
  14. PAAS_URL = os.environ.get("AGENTPAAS_URL", "http://127.0.0.1:8000")
  15. API_KEY = os.environ.get("AGENTPAAS_API_KEY", "")
  16. def _api_call(method: str, path: str, data: dict = None) -> dict:
  17. """调用 AgentPaaS API。"""
  18. url = f"{PAAS_URL}/api/v1{path}"
  19. body = json.dumps(data).encode("utf-8") if data else None
  20. req = urllib.request.Request(url, data=body, method=method)
  21. req.add_header("Content-Type", "application/json")
  22. if API_KEY:
  23. req.add_header("Authorization", f"Bearer {API_KEY}")
  24. try:
  25. with urllib.request.urlopen(req, timeout=30) as resp:
  26. return json.loads(resp.read())
  27. except urllib.error.HTTPError as e:
  28. return {"error": f"HTTP {e.code}: {e.read().decode()[:300]}"}
  29. except urllib.error.URLError as e:
  30. return {"error": f"Connection error: {e}"}
  31. def get_run_stats(agent_id: str, last_n: int = 50) -> Dict:
  32. """
  33. 获取 agent 最近 N 次运行的统计数据。
  34. Args:
  35. agent_id: Agent ID
  36. last_n: 最近 N 次运行
  37. Returns:
  38. {
  39. "agent_id": str,
  40. "total_runs": int,
  41. "success_rate": float,
  42. "avg_steps": float,
  43. "avg_duration_ms": float,
  44. "avg_input_tokens": float,
  45. "avg_output_tokens": float,
  46. "tool_usage": {tool: count},
  47. "error_patterns": [str],
  48. "runs": [简要运行记录]
  49. }
  50. """
  51. result = _api_call("GET", f"/agents/{agent_id}/runs?limit={last_n}")
  52. if "error" in result:
  53. return result
  54. runs = result.get("runs", [])
  55. if not runs:
  56. return {
  57. "agent_id": agent_id,
  58. "total_runs": 0,
  59. "message": "No runs found",
  60. }
  61. total = len(runs)
  62. successful = [r for r in runs if r.get("status") == "completed"]
  63. failed = [r for r in runs if r.get("status") == "failed"]
  64. # 步数统计
  65. steps = [r.get("steps", 0) for r in runs if r.get("steps")]
  66. durations = [r.get("duration_ms", 0) for r in runs if r.get("duration_ms")]
  67. input_tokens = [r.get("input_tokens", 0) for r in runs if r.get("input_tokens")]
  68. output_tokens = [r.get("output_tokens", 0) for r in runs if r.get("output_tokens")]
  69. # 错误模式
  70. error_patterns = []
  71. for r in failed:
  72. err = r.get("error", "")
  73. if err and err not in error_patterns:
  74. error_patterns.append(err[:200])
  75. return {
  76. "agent_id": agent_id,
  77. "total_runs": total,
  78. "success_rate": len(successful) / total if total > 0 else 0,
  79. "failure_count": len(failed),
  80. "avg_steps": sum(steps) / len(steps) if steps else 0,
  81. "avg_duration_ms": sum(durations) / len(durations) if durations else 0,
  82. "avg_input_tokens": sum(input_tokens) / len(input_tokens) if input_tokens else 0,
  83. "avg_output_tokens": sum(output_tokens) / len(output_tokens) if output_tokens else 0,
  84. "error_patterns": error_patterns[:5],
  85. "runs_summary": [
  86. {
  87. "run_id": r.get("id"),
  88. "status": r.get("status"),
  89. "steps": r.get("steps"),
  90. "duration_ms": r.get("duration_ms"),
  91. }
  92. for r in runs[:10]
  93. ],
  94. }
  95. def get_user_feedback(agent_id: str, last_n: int = 20) -> Dict:
  96. """
  97. 获取 agent 的用户反馈数据。
  98. 从 runs 的 output 和重试模式中推断用户满意度。
  99. (未来可接入显式评分 API)
  100. Returns:
  101. {
  102. "agent_id": str,
  103. "total_feedbacks": int,
  104. "avg_rating": float | null,
  105. "retry_rate": float,
  106. "common_complaints": [str]
  107. }
  108. """
  109. result = _api_call("GET", f"/agents/{agent_id}/runs?limit={last_n}")
  110. if "error" in result:
  111. return result
  112. runs = result.get("runs", [])
  113. if not runs:
  114. return {"agent_id": agent_id, "total_feedbacks": 0}
  115. # 推断重试:相似 input 在短时间内出现多次
  116. inputs_seen = {}
  117. retries = 0
  118. for r in runs:
  119. inp = r.get("input", "")[:100]
  120. if inp in inputs_seen:
  121. retries += 1
  122. inputs_seen[inp] = True
  123. return {
  124. "agent_id": agent_id,
  125. "total_feedbacks": len(runs),
  126. "avg_rating": None, # 暂无显式评分
  127. "retry_rate": retries / len(runs) if runs else 0,
  128. "retry_count": retries,
  129. "common_complaints": [], # 需要 NLP 分析 output
  130. }
  131. def get_current_config(agent_id: str) -> Dict:
  132. """
  133. 获取 agent 当前配置。
  134. Returns:
  135. agent 详情(含 config)
  136. """
  137. return _api_call("GET", f"/agents/{agent_id}")
  138. def submit_update(agent_id: str, config: Dict, changelog: str) -> Dict:
  139. """
  140. 提交 agent 配置更新(自动版本+1)。
  141. Args:
  142. agent_id: Agent ID
  143. config: 新配置
  144. changelog: 变更说明
  145. Returns:
  146. 更新结果
  147. """
  148. return _api_call("PUT", f"/agents/{agent_id}", {
  149. "config": config,
  150. "changelog": changelog,
  151. })