Files
aiagent/backend/app/services/agent_monitoring_service.py
renjianbo 036f533881 feat: Agent 监控与编排、仪表盘/配置页及文档更新
- agent_runtime: orchestrator、core/memory/schemas 调整
- agent_monitoring API、service、agent_llm_log 模型与 database 注册
- 前端 AgentDashboard、AgentConfig、Agents/MainLayout/路由与 AgentChat
- 文档:(红头)项目核心文档汇总、自主AI Agent改造完成情况、AI agent改造计划

Made-with: Cursor
2026-05-01 19:32:59 +08:00

235 lines
8.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Agent 监控服务 — 提供 Agent 专属统计数据
"""
from sqlalchemy.orm import Session
from sqlalchemy import func, and_
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional
from app.models.agent_llm_log import AgentLLMLog
from app.models.agent import Agent
from app.models.execution import Execution
import logging
logger = logging.getLogger(__name__)
class AgentMonitoringService:
"""Agent 监控服务"""
@staticmethod
def get_overview(db: Session, user_id: Optional[str] = None) -> Dict[str, Any]:
"""
获取 Agent 概览统计。
- 总对话次数Execution 中 agent_id 不为空的记录数)
- 总 LLM 调用次数
- 总 tokens 数(近似)
- 总工具调用次数
- 活跃 Agent 数
"""
user_filter = Agent.user_id == user_id if user_id else True
agent_ids_query = db.query(Agent.id).filter(user_filter)
agent_ids = {row[0] for row in agent_ids_query.all()}
# Agent 数量
agent_count = len(agent_ids)
# 对话次数Execution 表中 agent 执行的记录)
exec_filter = Execution.agent_id.in_(agent_ids) if agent_ids else False
chat_count = 0
if agent_ids:
chat_count = db.query(func.count(Execution.id)).filter(exec_filter).scalar() or 0
# LLM 调用统计
llm_filter = AgentLLMLog.agent_id.in_(agent_ids) if agent_ids else False
llm_count = 0
total_prompt = 0
total_completion = 0
if agent_ids:
stats = db.query(
func.count(AgentLLMLog.id),
func.coalesce(func.sum(AgentLLMLog.prompt_tokens), 0),
func.coalesce(func.sum(AgentLLMLog.completion_tokens), 0),
).filter(llm_filter).first()
llm_count = stats[0] or 0
total_prompt = stats[1] or 0
total_completion = stats[2] or 0
# 工具调用次数(从 ExecutionLog 统计 agent 相关)
tool_call_count = 0
if agent_ids:
tool_call_count = db.query(func.count(AgentLLMLog.id)).filter(
and_(
AgentLLMLog.agent_id.in_(agent_ids),
AgentLLMLog.tool_name.isnot(None),
)
).scalar() or 0
return {
"agent_count": agent_count,
"chat_count": chat_count,
"llm_call_count": llm_count,
"total_prompt_tokens": total_prompt,
"total_completion_tokens": total_completion,
"total_tokens": total_prompt + total_completion,
"tool_call_count": tool_call_count,
}
@staticmethod
def get_llm_calls(
db: Session,
user_id: Optional[str] = None,
days: int = 7,
limit: int = 50,
) -> List[Dict[str, Any]]:
"""获取最近 LLM 调用记录。"""
end_time = datetime.utcnow()
start_time = end_time - timedelta(days=days)
filters = [AgentLLMLog.created_at >= start_time]
if user_id:
filters.append(AgentLLMLog.user_id == user_id)
records = db.query(AgentLLMLog).filter(
and_(*filters)
).order_by(AgentLLMLog.created_at.desc()).limit(limit).all()
return [
{
"id": r.id,
"agent_id": r.agent_id,
"session_id": r.session_id,
"model": r.model,
"provider": r.provider,
"prompt_tokens": r.prompt_tokens,
"completion_tokens": r.completion_tokens,
"total_tokens": r.total_tokens,
"latency_ms": r.latency_ms,
"iteration_number": r.iteration_number,
"step_type": r.step_type,
"tool_name": r.tool_name,
"status": r.status,
"error_message": r.error_message,
"created_at": r.created_at.isoformat() if r.created_at else None,
}
for r in records
]
@staticmethod
def get_agent_stats(
db: Session,
user_id: Optional[str] = None,
days: int = 7,
) -> List[Dict[str, Any]]:
"""获取各 Agent 的用量统计(按 Agent 分组)。"""
end_time = datetime.utcnow()
start_time = end_time - timedelta(days=days)
filters = [AgentLLMLog.created_at >= start_time]
if user_id:
filters.append(AgentLLMLog.user_id == user_id)
rows = db.query(
AgentLLMLog.agent_id,
Agent.name.label("agent_name"),
func.count(AgentLLMLog.id).label("call_count"),
func.coalesce(func.sum(AgentLLMLog.prompt_tokens), 0).label("total_prompt"),
func.coalesce(func.sum(AgentLLMLog.completion_tokens), 0).label("total_completion"),
func.coalesce(func.sum(AgentLLMLog.total_tokens), 0).label("total_tokens"),
func.coalesce(func.avg(AgentLLMLog.latency_ms), 0).label("avg_latency"),
func.count(AgentLLMLog.tool_name).label("tool_calls"),
).outerjoin(
Agent, AgentLLMLog.agent_id == Agent.id
).filter(
and_(*filters)
).group_by(AgentLLMLog.agent_id).order_by(
func.count(AgentLLMLog.id).desc()
).all()
return [
{
"agent_id": r.agent_id or "未知",
"agent_name": r.agent_name or "未知 Agent",
"call_count": r.call_count,
"total_prompt_tokens": r.total_prompt,
"total_completion_tokens": r.total_completion,
"total_tokens": r.total_tokens,
"avg_latency_ms": round(r.avg_latency, 2) if r.avg_latency else 0,
"tool_call_count": r.tool_calls or 0,
}
for r in rows
]
@staticmethod
def get_tool_usage(
db: Session,
user_id: Optional[str] = None,
days: int = 7,
) -> List[Dict[str, Any]]:
"""获取工具调用频次统计。"""
end_time = datetime.utcnow()
start_time = end_time - timedelta(days=days)
filters = [
AgentLLMLog.created_at >= start_time,
AgentLLMLog.tool_name.isnot(None),
]
if user_id:
filters.append(AgentLLMLog.user_id == user_id)
rows = db.query(
AgentLLMLog.tool_name,
func.count(AgentLLMLog.id).label("call_count"),
func.coalesce(func.sum(AgentLLMLog.total_tokens), 0).label("total_tokens"),
func.coalesce(func.avg(AgentLLMLog.latency_ms), 0).label("avg_latency"),
).filter(
and_(*filters)
).group_by(AgentLLMLog.tool_name).order_by(
func.count(AgentLLMLog.id).desc()
).all()
return [
{
"tool_name": r.tool_name or "未知",
"call_count": r.call_count,
"total_tokens": r.total_tokens,
"avg_latency_ms": round(r.avg_latency, 2) if r.avg_latency else 0,
}
for r in rows
]
@staticmethod
def get_daily_trend(
db: Session,
user_id: Optional[str] = None,
days: int = 7,
) -> List[Dict[str, Any]]:
"""获取每日 LLM 调用趋势。"""
end_time = datetime.utcnow()
start_time = end_time - timedelta(days=days)
filters = [AgentLLMLog.created_at >= start_time]
if user_id:
filters.append(AgentLLMLog.user_id == user_id)
results = []
for i in range(days):
day_start = end_time - timedelta(days=days - i)
day_end = day_start + timedelta(days=1)
day_filter = and_(
*filters,
AgentLLMLog.created_at >= day_start,
AgentLLMLog.created_at < day_end,
)
day_count = db.query(func.count(AgentLLMLog.id)).filter(day_filter).scalar() or 0
day_tokens = db.query(
func.coalesce(func.sum(AgentLLMLog.total_tokens), 0)
).filter(day_filter).scalar() or 0
results.append({
"date": day_start.strftime("%m-%d"),
"call_count": day_count,
"total_tokens": day_tokens,
})
return results