feat: add AgentRunGraphSummary and related functionality for graph summary retrieval

This commit is contained in:
mlogclub
2026-04-12 14:08:16 +08:00
parent e2d2a951a9
commit c3ac4c3e28
5 changed files with 211 additions and 0 deletions
@@ -58,3 +58,22 @@ func (c *AgentRunLogController) GetBy(id int64) *web.JsonResult {
} }
return web.JsonData(builders.BuildAgentRunLog(item)) return web.JsonData(builders.BuildAgentRunLog(item))
} }
func (c *AgentRunLogController) AnyGraphSummary() *web.JsonResult {
if _, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionConversationView); err != nil {
return web.JsonError(err)
}
aiAgentID, _ := params.GetInt64(c.Ctx, "aiAgentId")
summary := services.AgentRunLogService.BuildGraphSummary(aiAgentID)
return web.JsonData(&response.AgentRunGraphSummaryResponse{
TriageCount: summary.TriageCount,
TriagePrepareTicket: summary.TriagePrepareTicket,
TriagePrepareTicketReady: summary.TriagePrepareTicketReady,
TriageHandoff: summary.TriageHandoff,
TriageContinueAnswering: summary.TriageContinueAnswering,
AnalyzeCount: summary.AnalyzeCount,
PrepareDraftCount: summary.PrepareDraftCount,
CreateTicketCount: summary.CreateTicketCount,
HandoffCount: summary.HandoffCount,
})
}
@@ -71,3 +71,15 @@ type AgentRunLogResponse struct {
TraceData string `json:"traceData"` TraceData string `json:"traceData"`
CreatedAt string `json:"createdAt"` CreatedAt string `json:"createdAt"`
} }
type AgentRunGraphSummaryResponse struct {
TriageCount int64 `json:"triageCount"`
TriagePrepareTicket int64 `json:"triagePrepareTicket"`
TriagePrepareTicketReady int64 `json:"triagePrepareTicketReady"`
TriageHandoff int64 `json:"triageHandoff"`
TriageContinueAnswering int64 `json:"triageContinueAnswering"`
AnalyzeCount int64 `json:"analyzeCount"`
PrepareDraftCount int64 `json:"prepareDraftCount"`
CreateTicketCount int64 `json:"createTicketCount"`
HandoffCount int64 `json:"handoffCount"`
}
@@ -2,6 +2,7 @@ package services
import ( import (
"cs-agent/internal/models" "cs-agent/internal/models"
"cs-agent/internal/pkg/toolx"
"cs-agent/internal/repositories" "cs-agent/internal/repositories"
"strings" "strings"
@@ -17,6 +18,18 @@ func newAgentRunLogService() *agentRunLogService {
type agentRunLogService struct{} type agentRunLogService struct{}
type AgentRunGraphSummary struct {
TriageCount int64
TriagePrepareTicket int64
TriagePrepareTicketReady int64
TriageHandoff int64
TriageContinueAnswering int64
AnalyzeCount int64
PrepareDraftCount int64
CreateTicketCount int64
HandoffCount int64
}
func (s *agentRunLogService) Get(id int64) *models.AgentRunLog { func (s *agentRunLogService) Get(id int64) *models.AgentRunLog {
return repositories.AgentRunLogRepository.Get(sqls.DB(), id) return repositories.AgentRunLogRepository.Get(sqls.DB(), id)
} }
@@ -83,3 +96,37 @@ func (s *agentRunLogService) ApplyHITLStatusFilter(cnd *sqls.Cnd, hitlStatus str
} }
return cnd return cnd
} }
func (s *agentRunLogService) BuildGraphSummary(aiAgentID int64) *AgentRunGraphSummary {
buildBaseCnd := func() *sqls.Cnd {
cnd := sqls.NewCnd()
if aiAgentID > 0 {
cnd.Eq("ai_agent_id", aiAgentID)
}
return cnd
}
return &AgentRunGraphSummary{
TriageCount: s.countByGraphTool(buildBaseCnd(), toolx.GraphTriageServiceRequestToolCode),
TriagePrepareTicket: s.countByGraphToolAndPlanReason(buildBaseCnd(), toolx.GraphTriageServiceRequestToolCode, "%prepare_ticket%"),
TriagePrepareTicketReady: s.countByGraphToolAndPlanReason(buildBaseCnd(), toolx.GraphTriageServiceRequestToolCode, "%prepare_ticket with ready ticket draft%"),
TriageHandoff: s.countByGraphToolAndPlanReason(buildBaseCnd(), toolx.GraphTriageServiceRequestToolCode, "%handoff_to_human%"),
TriageContinueAnswering: s.countByGraphToolAndPlanReason(buildBaseCnd(), toolx.GraphTriageServiceRequestToolCode, "%continue_answering%"),
AnalyzeCount: s.countByGraphTool(buildBaseCnd(), toolx.GraphAnalyzeConversationToolCode),
PrepareDraftCount: s.countByGraphTool(buildBaseCnd(), toolx.GraphPrepareTicketDraftToolCode),
CreateTicketCount: s.countByGraphTool(buildBaseCnd(), toolx.GraphCreateTicketConfirmToolCode),
HandoffCount: s.countByGraphTool(buildBaseCnd(), toolx.GraphHandoffConversationToolCode),
}
}
func (s *agentRunLogService) countByGraphTool(baseCnd *sqls.Cnd, graphToolCode string) int64 {
cnd := baseCnd
cnd.Eq("graph_tool_code", strings.TrimSpace(graphToolCode))
return s.Count(cnd)
}
func (s *agentRunLogService) countByGraphToolAndPlanReason(baseCnd *sqls.Cnd, graphToolCode string, planReasonLike string) int64 {
cnd := baseCnd
cnd.Eq("graph_tool_code", strings.TrimSpace(graphToolCode))
cnd.Where("plan_reason LIKE ?", strings.TrimSpace(planReasonLike))
return s.Count(cnd)
}
+115
View File
@@ -13,6 +13,13 @@ import { ListPagination } from "@/components/list-pagination"
import { OptionCombobox } from "@/components/option-combobox" import { OptionCombobox } from "@/components/option-combobox"
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { import {
Drawer, Drawer,
DrawerContent, DrawerContent,
@@ -32,8 +39,10 @@ import {
} from "@/components/ui/table" } from "@/components/ui/table"
import { import {
fetchAgentRunLog, fetchAgentRunLog,
fetchAgentRunGraphSummary,
fetchAgentRunLogs, fetchAgentRunLogs,
fetchAIAgentsAll, fetchAIAgentsAll,
type AgentRunGraphSummary,
type AgentRunLog, type AgentRunLog,
type AIAgent, type AIAgent,
type PageResult, type PageResult,
@@ -119,6 +128,7 @@ export default function DashboardAgentRunLogsPage() {
const [page, setPage] = useState(1) const [page, setPage] = useState(1)
const [limit, setLimit] = useState(20) const [limit, setLimit] = useState(20)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [summaryLoading, setSummaryLoading] = useState(true)
const [detailLoading, setDetailLoading] = useState(false) const [detailLoading, setDetailLoading] = useState(false)
const [detailOpen, setDetailOpen] = useState(false) const [detailOpen, setDetailOpen] = useState(false)
const [activeLog, setActiveLog] = useState<AgentRunLog | null>(null) const [activeLog, setActiveLog] = useState<AgentRunLog | null>(null)
@@ -126,6 +136,17 @@ export default function DashboardAgentRunLogsPage() {
results: [], results: [],
page: { page: 1, limit: 20, total: 0 }, page: { page: 1, limit: 20, total: 0 },
}) })
const [graphSummary, setGraphSummary] = useState<AgentRunGraphSummary>({
triageCount: 0,
triagePrepareTicket: 0,
triagePrepareTicketReady: 0,
triageHandoff: 0,
triageContinueAnswering: 0,
analyzeCount: 0,
prepareDraftCount: 0,
createTicketCount: 0,
handoffCount: 0,
})
const [aiAgents, setAiAgents] = useState<AIAgent[]>([]) const [aiAgents, setAiAgents] = useState<AIAgent[]>([])
const activeTraceData = useMemo( const activeTraceData = useMemo(
() => safeParseJSON(activeLog?.traceData ?? ""), () => safeParseJSON(activeLog?.traceData ?? ""),
@@ -176,6 +197,23 @@ export default function DashboardAgentRunLogsPage() {
void loadData() void loadData()
}, [loadData]) }, [loadData])
useEffect(() => {
async function loadGraphSummary() {
setSummaryLoading(true)
try {
const data = await fetchAgentRunGraphSummary({
aiAgentId: aiAgentId === "all" ? undefined : aiAgentId,
})
setGraphSummary(data)
} catch (error) {
toast.error(error instanceof Error ? error.message : "加载 Graph 摘要失败")
} finally {
setSummaryLoading(false)
}
}
void loadGraphSummary()
}, [aiAgentId])
useEffect(() => { useEffect(() => {
async function loadAIAgents() { async function loadAIAgents() {
try { try {
@@ -294,6 +332,57 @@ export default function DashboardAgentRunLogsPage() {
</Button> </Button>
</div> </div>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<SummaryCard
title="统一分流"
description="triage_service_request 调用次数"
value={graphSummary.triageCount}
loading={summaryLoading}
/>
<SummaryCard
title="推荐建单"
description="triage 推荐 prepare_ticket 次数"
value={graphSummary.triagePrepareTicket}
loading={summaryLoading}
/>
<SummaryCard
title="草稿就绪"
description="triage 推荐建单且 ticketDraft 已就绪"
value={graphSummary.triagePrepareTicketReady}
loading={summaryLoading}
/>
<SummaryCard
title="推荐转人工"
description="triage 推荐 handoff_to_human 次数"
value={graphSummary.triageHandoff}
loading={summaryLoading}
/>
<SummaryCard
title="继续解答"
description="triage 推荐 continue_answering 次数"
value={graphSummary.triageContinueAnswering}
loading={summaryLoading}
/>
<SummaryCard
title="风险分析"
description="analyze_conversation 调用次数"
value={graphSummary.analyzeCount}
loading={summaryLoading}
/>
<SummaryCard
title="工单草稿"
description="prepare_ticket_draft 调用次数"
value={graphSummary.prepareDraftCount}
loading={summaryLoading}
/>
<SummaryCard
title="最终动作"
description={`建单 ${graphSummary.createTicketCount} / 转人工 ${graphSummary.handoffCount}`}
value={graphSummary.createTicketCount + graphSummary.handoffCount}
loading={summaryLoading}
/>
</div>
<div className="overflow-hidden rounded-lg border bg-background"> <div className="overflow-hidden rounded-lg border bg-background">
<Table> <Table>
<TableHeader className="bg-muted/40"> <TableHeader className="bg-muted/40">
@@ -521,6 +610,32 @@ export default function DashboardAgentRunLogsPage() {
) )
} }
function SummaryCard({
title,
description,
value,
loading,
}: {
title: string
description: string
value: number
loading: boolean
}) {
return (
<Card size="sm">
<CardHeader>
<CardTitle>{title}</CardTitle>
<CardDescription>{description}</CardDescription>
</CardHeader>
<CardContent>
<div className="text-2xl font-semibold tracking-tight">
{loading ? "--" : value}
</div>
</CardContent>
</Card>
)
}
function safeParseJSON(value: string) { function safeParseJSON(value: string) {
if (!value.trim()) { if (!value.trim()) {
return null return null
+18
View File
@@ -416,6 +416,18 @@ export type AgentRunLog = {
createdAt: string createdAt: string
} }
export type AgentRunGraphSummary = {
triageCount: number
triagePrepareTicket: number
triagePrepareTicketReady: number
triageHandoff: number
triageContinueAnswering: number
analyzeCount: number
prepareDraftCount: number
createTicketCount: number
handoffCount: number
}
export type AdminAgentProfile = { export type AdminAgentProfile = {
id: number id: number
userId: number userId: number
@@ -895,6 +907,12 @@ export function fetchAgentRunLog(id: number) {
return request<AgentRunLog>(`/api/console/agent-run-log/${id}`) return request<AgentRunLog>(`/api/console/agent-run-log/${id}`)
} }
export function fetchAgentRunGraphSummary(params?: { aiAgentId?: string }) {
return request<AgentRunGraphSummary>(
`/api/console/agent-run-log/graph/summary${toQueryString(params)}`
)
}
export function updateSkillDefinitionStatus(id: number, status: number) { export function updateSkillDefinitionStatus(id: number, status: number) {
return request<void>("/api/console/skill-definition/update_status", { return request<void>("/api/console/skill-definition/update_status", {
method: "POST", method: "POST",