feat: 完善堡垒机与 Agent 智能运维能力
- 支持 Windows Agent 构建、安装脚本和统一上报协议 - 增强 RDP 剪贴板、虚拟盘和文件传输能力 - 新增 AIOps 上下文工具、告警 RCA trace 落库和回放接口 - 重构 docs 模块文档并补充终端、堡垒机、监控、AI Agent 设计
This commit is contained in:
1 parent
a9513c2056
commit
e0d652d211
169 files changed
+12192
-9116
No files matched your search
@@ -0,0 +1,39 @@
|
||||
package aiopscontext
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"ezops/pkg/agentcli/tools"
|
||||
)
|
||||
|
||||
// AgentProvider 适配 Agent CLI tools.AIOpsContextProvider。 // Logan.Li
|
||||
type AgentProvider struct {
|
||||
service *Service
|
||||
}
|
||||
|
||||
// NewAgentTools 返回可注册到 Agent CLI 的 AIOps 平台只读工具。 // Logan.Li
|
||||
func NewAgentTools(service *Service) []tools.Tool {
|
||||
provider := &AgentProvider{service: service}
|
||||
return []tools.Tool{
|
||||
tools.NewAIOpsAssetSearchTool(provider),
|
||||
tools.NewAIOpsAlertListTool(provider),
|
||||
tools.NewAIOpsMetricSummaryTool(provider),
|
||||
}
|
||||
}
|
||||
|
||||
// SearchAssets 查询资产。 // Logan.Li
|
||||
func (p *AgentProvider) SearchAssets(ctx context.Context, keyword string, limit int) (interface{}, error) {
|
||||
return p.service.SearchAssets(ctx, keyword, limit)
|
||||
}
|
||||
|
||||
// ListAlerts 查询告警。 // Logan.Li
|
||||
func (p *AgentProvider) ListAlerts(ctx context.Context, target, severity, status, timeRange string, limit int) (interface{}, error) {
|
||||
return p.service.ListAlerts(ctx, AlertQuery{
|
||||
Target: target, Severity: severity, Status: status, TimeRange: timeRange, Limit: limit,
|
||||
})
|
||||
}
|
||||
|
||||
// MetricSummary 查询指标摘要。 // Logan.Li
|
||||
func (p *AgentProvider) MetricSummary(ctx context.Context, target, metric, timeRange string, limit int) (interface{}, error) {
|
||||
return p.service.MetricSummary(ctx, target, metric, timeRange, limit)
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
package aiopscontext
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"ezops/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Service 汇聚 AIOps 平台上下文,后续可复用为 MCP Server 的数据源。 // Logan.Li
|
||||
type Service struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewService 创建上下文服务。 // Logan.Li
|
||||
func NewService(db *gorm.DB) *Service {
|
||||
return &Service{db: db}
|
||||
}
|
||||
|
||||
// Summary 返回平台总览上下文。 // Logan.Li
|
||||
func (s *Service) Summary(ctx context.Context) (*Summary, error) {
|
||||
out := Summary{
|
||||
HighLoadHosts: []AssetSummary{},
|
||||
RecentAlerts: []AlertSummary{},
|
||||
}
|
||||
db := s.db.WithContext(ctx)
|
||||
db.Model(&model.Server{}).Count(&out.ServerTotal)
|
||||
db.Model(&model.Server{}).Where("agent_status = ? OR status = ?", "online", "running").Count(&out.ServerOnline)
|
||||
db.Model(&model.KubernetesCluster{}).Count(&out.ClusterTotal)
|
||||
db.Model(&model.AlertEvent{}).Where("status = ?", "firing").Count(&out.FiringAlerts)
|
||||
db.Model(&model.AgentAlert{}).Where("resolved = ?", false).Count(&out.UnresolvedAlerts)
|
||||
|
||||
assets, err := s.SearchAssets(ctx, "", 8)
|
||||
if err == nil {
|
||||
for _, a := range assets {
|
||||
if a.CPUUsage >= 80 || a.MemoryUsage >= 80 || a.DiskUsage >= 85 {
|
||||
out.HighLoadHosts = append(out.HighLoadHosts, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
out.RecentAlerts, _ = s.ListAlerts(ctx, AlertQuery{Limit: 10})
|
||||
out.GeneratedAt = time.Now()
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// SearchAssets 查询资产摘要,默认按更新时间倒序返回。 // Logan.Li
|
||||
func (s *Service) SearchAssets(ctx context.Context, keyword string, limit int) ([]AssetSummary, error) {
|
||||
if limit <= 0 || limit > 50 {
|
||||
limit = 10
|
||||
}
|
||||
var servers []model.Server
|
||||
q := s.db.WithContext(ctx).Model(&model.Server{})
|
||||
kw := strings.TrimSpace(keyword)
|
||||
if kw != "" {
|
||||
like := "%" + kw + "%"
|
||||
q = q.Where("hostname LIKE ? OR ip LIKE ? OR public_ip LIKE ? OR tags LIKE ? OR description LIKE ?",
|
||||
like, like, like, like, like)
|
||||
}
|
||||
if err := q.Order("updated_at DESC").Limit(limit).Find(&servers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]AssetSummary, 0, len(servers))
|
||||
for _, srv := range servers {
|
||||
out = append(out, assetFromServer(srv))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ListAlerts 查询统一告警摘要。 // Logan.Li
|
||||
func (s *Service) ListAlerts(ctx context.Context, query AlertQuery) ([]AlertSummary, error) {
|
||||
limit := query.Limit
|
||||
if limit <= 0 || limit > 100 {
|
||||
limit = 20
|
||||
}
|
||||
since := sinceByRange(query.TimeRange, 24*time.Hour)
|
||||
targetLike := "%" + strings.TrimSpace(query.Target) + "%"
|
||||
|
||||
var events []model.AlertEvent
|
||||
q := s.db.WithContext(ctx).Model(&model.AlertEvent{}).Where("created_at >= ?", since)
|
||||
if query.Target != "" {
|
||||
q = q.Where("target LIKE ? OR target_name LIKE ? OR target_ip LIKE ? OR rule_name LIKE ?",
|
||||
targetLike, targetLike, targetLike, targetLike)
|
||||
}
|
||||
if query.Severity != "" {
|
||||
q = q.Where("severity = ?", query.Severity)
|
||||
}
|
||||
if query.Status != "" {
|
||||
q = q.Where("status = ?", query.Status)
|
||||
}
|
||||
if err := q.Order("created_at DESC").Limit(limit).Find(&events).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := make([]AlertSummary, 0, len(events)+limit)
|
||||
for _, e := range events {
|
||||
out = append(out, alertFromEvent(e))
|
||||
}
|
||||
|
||||
var agentAlerts []model.AgentAlert
|
||||
aq := s.db.WithContext(ctx).Model(&model.AgentAlert{}).Where("created_at >= ?", since)
|
||||
if query.Target != "" {
|
||||
aq = aq.Where("hostname LIKE ? OR rule_name LIKE ?", targetLike, targetLike)
|
||||
}
|
||||
if query.Severity != "" {
|
||||
aq = aq.Where("severity = ?", query.Severity)
|
||||
}
|
||||
if query.Status == "firing" || query.Status == "active" {
|
||||
aq = aq.Where("resolved = ?", false)
|
||||
} else if query.Status == "resolved" {
|
||||
aq = aq.Where("resolved = ?", true)
|
||||
}
|
||||
if err := aq.Order("created_at DESC").Limit(limit).Find(&agentAlerts).Error; err == nil {
|
||||
for _, a := range agentAlerts {
|
||||
out = append(out, alertFromAgent(a))
|
||||
}
|
||||
}
|
||||
|
||||
sort.SliceStable(out, func(i, j int) bool {
|
||||
return out[i].StartedAt.After(out[j].StartedAt)
|
||||
})
|
||||
if len(out) > limit {
|
||||
out = out[:limit]
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// MetricSummary 查询主机指标摘要。 // Logan.Li
|
||||
func (s *Service) MetricSummary(ctx context.Context, target, metric, timeRange string, limit int) (*MetricSummary, error) {
|
||||
hostname, err := s.resolveHostname(ctx, target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if metric == "" {
|
||||
metric = "cpu_usage"
|
||||
}
|
||||
if limit <= 0 || limit > 500 {
|
||||
limit = 240
|
||||
}
|
||||
since := sinceByRange(timeRange, time.Hour)
|
||||
var reports []model.AgentReport
|
||||
if err := s.db.WithContext(ctx).Where("hostname = ? AND report_time >= ?", hostname, since).
|
||||
Order("report_time ASC").Limit(limit).Find(&reports).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &MetricSummary{Target: target, Hostname: hostname, Metric: metric, TimeRange: normalizeRangeLabel(timeRange), Points: []MetricPoint{}}
|
||||
values := make([]float64, 0, len(reports))
|
||||
for _, r := range reports {
|
||||
v, ok := metricValue(r, metric)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
values = append(values, v)
|
||||
if len(out.Points) < 60 || len(reports) <= 60 {
|
||||
out.Points = append(out.Points, MetricPoint{Time: r.ReportTime, Value: round2(v)})
|
||||
}
|
||||
}
|
||||
fillMetricStats(out, values)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// BuildPack 构建面向 LLM 的上下文包。 // Logan.Li
|
||||
func (s *Service) BuildPack(ctx context.Context, req ContextPackRequest) (*ContextPack, error) {
|
||||
if req.Limit <= 0 {
|
||||
req.Limit = 10
|
||||
}
|
||||
scope := strings.TrimSpace(req.Target)
|
||||
var primaryAlert *AlertSummary
|
||||
if req.AlertID > 0 {
|
||||
alert, err := s.GetAlert(ctx, req.AlertID)
|
||||
if err == nil {
|
||||
primaryAlert = alert
|
||||
if scope == "" {
|
||||
scope = alert.Target
|
||||
}
|
||||
}
|
||||
}
|
||||
pack := &ContextPack{
|
||||
Scope: scope, TimeRange: normalizeRangeLabel(req.TimeRange),
|
||||
Assets: []AssetSummary{}, Alerts: []AlertSummary{}, Metrics: []MetricSummary{},
|
||||
GeneratedAt: time.Now(), Evidence: []EvidenceItem{},
|
||||
}
|
||||
if scope != "" {
|
||||
pack.Assets, _ = s.SearchAssets(ctx, scope, req.Limit)
|
||||
for _, asset := range pack.Assets {
|
||||
pack.Evidence = append(pack.Evidence, EvidenceItem{
|
||||
Type: "asset", Source: "asset_servers",
|
||||
Summary: fmt.Sprintf("资产 %s ip=%s protocol=%s os=%s status=%s agent=%s",
|
||||
asset.Hostname, asset.IP, asset.Protocol, asset.OSType, asset.Status, asset.AgentStatus),
|
||||
Confidence: 0.9,
|
||||
})
|
||||
}
|
||||
}
|
||||
pack.Alerts, _ = s.ListAlerts(ctx, AlertQuery{Target: scope, TimeRange: req.TimeRange, Limit: req.Limit})
|
||||
if primaryAlert != nil {
|
||||
pack.Alerts = prependAlertIfMissing(pack.Alerts, *primaryAlert)
|
||||
}
|
||||
metrics := req.Metrics
|
||||
if len(metrics) == 0 {
|
||||
metrics = []string{"cpu_usage", "mem_usage", "load1"}
|
||||
}
|
||||
for _, metric := range metrics {
|
||||
if scope == "" {
|
||||
break
|
||||
}
|
||||
ms, err := s.MetricSummary(ctx, scope, metric, req.TimeRange, 240)
|
||||
if err == nil && ms.SampleSize > 0 {
|
||||
pack.Metrics = append(pack.Metrics, *ms)
|
||||
pack.Evidence = append(pack.Evidence, EvidenceItem{
|
||||
Type: "metric", Source: "monitor_agent_reports",
|
||||
Summary: fmt.Sprintf("%s 最近 %s: latest=%.2f avg=%.2f p95=%.2f max=%.2f trend=%s",
|
||||
ms.Metric, ms.TimeRange, ms.Latest, ms.Avg, ms.P95, ms.Max, ms.Trend),
|
||||
Confidence: 0.82,
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, a := range pack.Alerts {
|
||||
pack.Evidence = append(pack.Evidence, EvidenceItem{
|
||||
Type: "alert", Source: a.Source,
|
||||
Summary: fmt.Sprintf("[%s|%s] %s target=%s value=%.2f threshold=%.2f",
|
||||
a.Severity, a.Status, a.RuleName, a.Target, a.Value, a.Threshold),
|
||||
Confidence: 0.78,
|
||||
})
|
||||
}
|
||||
return pack, nil
|
||||
}
|
||||
|
||||
// GetAlert 获取单条平台告警摘要。 // Logan.Li
|
||||
func (s *Service) GetAlert(ctx context.Context, id uint) (*AlertSummary, error) {
|
||||
var event model.AlertEvent
|
||||
if err := s.db.WithContext(ctx).Take(&event, id).Error; err == nil {
|
||||
alert := alertFromEvent(event)
|
||||
return &alert, nil
|
||||
}
|
||||
var agentAlert model.AgentAlert
|
||||
if err := s.db.WithContext(ctx).Take(&agentAlert, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
alert := alertFromAgent(agentAlert)
|
||||
return &alert, nil
|
||||
}
|
||||
|
||||
func (s *Service) resolveHostname(ctx context.Context, target string) (string, error) {
|
||||
target = strings.TrimSpace(target)
|
||||
if target == "" {
|
||||
return "", fmt.Errorf("target 不能为空")
|
||||
}
|
||||
var srv model.Server
|
||||
err := s.db.WithContext(ctx).Where("hostname = ? OR ip = ? OR public_ip = ?", target, target, target).Take(&srv).Error
|
||||
if err == nil {
|
||||
return srv.Hostname, nil
|
||||
}
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return target, nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
func assetFromServer(srv model.Server) AssetSummary {
|
||||
return AssetSummary{
|
||||
ID: srv.ID, Hostname: srv.Hostname, IP: srv.IP, PublicIP: srv.PublicIP,
|
||||
Protocol: srv.Protocol, OSType: srv.OSType, OS: srv.OS, Status: srv.Status,
|
||||
AgentStatus: srv.AgentStatus, CPUUsage: srv.CPUUsage, MemoryUsage: srv.MemoryUsage,
|
||||
DiskUsage: srv.DiskUsage, Tags: srv.Tags, Description: srv.Description,
|
||||
LastCheckAt: srv.LastCheckAt, UpdatedAt: srv.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func alertFromEvent(e model.AlertEvent) AlertSummary {
|
||||
target := e.Target
|
||||
if target == "" {
|
||||
target = e.TargetName
|
||||
}
|
||||
if target == "" {
|
||||
target = e.TargetIP
|
||||
}
|
||||
started := e.StartedAt
|
||||
if started.IsZero() {
|
||||
started = e.CreatedAt
|
||||
}
|
||||
return AlertSummary{
|
||||
ID: e.ID, Source: "monitor_alert_events", RuleName: firstNonEmpty(e.RuleName, e.Title),
|
||||
Severity: e.Severity, Status: e.Status, Target: target, Metric: e.MetricName,
|
||||
Value: e.Value, Threshold: e.Threshold, Message: e.Message, StartedAt: started,
|
||||
ResolvedAt: e.ResolvedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func alertFromAgent(a model.AgentAlert) AlertSummary {
|
||||
status := "firing"
|
||||
if a.Resolved {
|
||||
status = "resolved"
|
||||
}
|
||||
return AlertSummary{
|
||||
ID: a.ID, Source: "monitor_agent_alerts", RuleName: a.RuleName,
|
||||
Severity: a.Severity, Status: status, Target: a.Hostname, Metric: a.Metric,
|
||||
Value: a.Value, Threshold: a.Threshold, Message: a.Message,
|
||||
StartedAt: a.CreatedAt, ResolvedAt: a.ResolvedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func prependAlertIfMissing(alerts []AlertSummary, alert AlertSummary) []AlertSummary {
|
||||
for _, item := range alerts {
|
||||
if item.Source == alert.Source && item.ID == alert.ID {
|
||||
return alerts
|
||||
}
|
||||
}
|
||||
return append([]AlertSummary{alert}, alerts...)
|
||||
}
|
||||
|
||||
func metricValue(r model.AgentReport, metric string) (float64, bool) {
|
||||
switch metric {
|
||||
case "cpu", "cpu_usage":
|
||||
return r.CPUUsage, true
|
||||
case "cpu_iowait":
|
||||
return r.CPUIOwait, true
|
||||
case "mem", "memory", "mem_usage", "memory_usage":
|
||||
return r.MemUsage, true
|
||||
case "mem_available":
|
||||
return r.MemAvailable, true
|
||||
case "swap", "mem_swap_used":
|
||||
return r.MemSwapUsed, true
|
||||
case "load1":
|
||||
return r.Load1, true
|
||||
case "load5":
|
||||
return r.Load5, true
|
||||
case "load15":
|
||||
return r.Load15, true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
func fillMetricStats(out *MetricSummary, values []float64) {
|
||||
out.SampleSize = len(values)
|
||||
if len(values) == 0 {
|
||||
out.Trend = "unknown"
|
||||
return
|
||||
}
|
||||
minV, maxV, sum := values[0], values[0], 0.0
|
||||
for _, v := range values {
|
||||
if v < minV {
|
||||
minV = v
|
||||
}
|
||||
if v > maxV {
|
||||
maxV = v
|
||||
}
|
||||
sum += v
|
||||
}
|
||||
sorted := append([]float64(nil), values...)
|
||||
sort.Float64s(sorted)
|
||||
p95Idx := int(math.Ceil(float64(len(sorted))*0.95)) - 1
|
||||
if p95Idx < 0 {
|
||||
p95Idx = 0
|
||||
}
|
||||
out.Latest = round2(values[len(values)-1])
|
||||
out.Min = round2(minV)
|
||||
out.Max = round2(maxV)
|
||||
out.Avg = round2(sum / float64(len(values)))
|
||||
out.P95 = round2(sorted[p95Idx])
|
||||
out.Trend = trendOf(values)
|
||||
}
|
||||
|
||||
func trendOf(values []float64) string {
|
||||
if len(values) < 4 {
|
||||
return "stable"
|
||||
}
|
||||
n := len(values)
|
||||
head := avg(values[:n/3])
|
||||
tail := avg(values[n-n/3:])
|
||||
if tail-head > 5 {
|
||||
return "up"
|
||||
}
|
||||
if head-tail > 5 {
|
||||
return "down"
|
||||
}
|
||||
return "stable"
|
||||
}
|
||||
|
||||
func avg(values []float64) float64 {
|
||||
if len(values) == 0 {
|
||||
return 0
|
||||
}
|
||||
sum := 0.0
|
||||
for _, v := range values {
|
||||
sum += v
|
||||
}
|
||||
return sum / float64(len(values))
|
||||
}
|
||||
|
||||
func sinceByRange(label string, fallback time.Duration) time.Time {
|
||||
switch strings.ToLower(strings.TrimSpace(label)) {
|
||||
case "15m":
|
||||
return time.Now().Add(-15 * time.Minute)
|
||||
case "30m":
|
||||
return time.Now().Add(-30 * time.Minute)
|
||||
case "2h":
|
||||
return time.Now().Add(-2 * time.Hour)
|
||||
case "6h":
|
||||
return time.Now().Add(-6 * time.Hour)
|
||||
case "24h", "1d":
|
||||
return time.Now().Add(-24 * time.Hour)
|
||||
case "7d":
|
||||
return time.Now().Add(-7 * 24 * time.Hour)
|
||||
default:
|
||||
return time.Now().Add(-fallback)
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeRangeLabel(label string) string {
|
||||
if strings.TrimSpace(label) == "" {
|
||||
return "1h"
|
||||
}
|
||||
return label
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, v := range values {
|
||||
if strings.TrimSpace(v) != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func round2(v float64) float64 {
|
||||
return math.Round(v*100) / 100
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package aiopscontext
|
||||
|
||||
import "time"
|
||||
|
||||
// Summary 平台智能上下文总览,供智能助手和 Agent 工具复用。 // Logan.Li
|
||||
type Summary struct {
|
||||
ServerTotal int64 `json:"server_total"`
|
||||
ServerOnline int64 `json:"server_online"`
|
||||
ClusterTotal int64 `json:"cluster_total"`
|
||||
FiringAlerts int64 `json:"firing_alerts"`
|
||||
UnresolvedAlerts int64 `json:"unresolved_alerts"`
|
||||
HighLoadHosts []AssetSummary `json:"high_load_hosts"`
|
||||
RecentAlerts []AlertSummary `json:"recent_alerts"`
|
||||
GeneratedAt time.Time `json:"generated_at"`
|
||||
}
|
||||
|
||||
// AssetSummary 脱敏后的资产摘要,不包含凭证字段。 // Logan.Li
|
||||
type AssetSummary struct {
|
||||
ID uint `json:"id"`
|
||||
Hostname string `json:"hostname"`
|
||||
IP string `json:"ip"`
|
||||
PublicIP string `json:"public_ip,omitempty"`
|
||||
Protocol string `json:"protocol"`
|
||||
OSType string `json:"os_type"`
|
||||
OS string `json:"os,omitempty"`
|
||||
Status string `json:"status"`
|
||||
AgentStatus string `json:"agent_status"`
|
||||
CPUUsage float64 `json:"cpu_usage"`
|
||||
MemoryUsage float64 `json:"memory_usage"`
|
||||
DiskUsage float64 `json:"disk_usage"`
|
||||
Tags string `json:"tags,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
LastCheckAt *time.Time `json:"last_check_at,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// AlertQuery 告警查询条件。 // Logan.Li
|
||||
type AlertQuery struct {
|
||||
Target string `json:"target,omitempty"`
|
||||
Severity string `json:"severity,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
TimeRange string `json:"time_range,omitempty"`
|
||||
Limit int `json:"limit,omitempty"`
|
||||
}
|
||||
|
||||
// AlertSummary 统一告警摘要,合并平台告警和 Agent 告警。 // Logan.Li
|
||||
type AlertSummary struct {
|
||||
ID uint `json:"id"`
|
||||
Source string `json:"source"`
|
||||
RuleName string `json:"rule_name"`
|
||||
Severity string `json:"severity"`
|
||||
Status string `json:"status"`
|
||||
Target string `json:"target"`
|
||||
Metric string `json:"metric"`
|
||||
Value float64 `json:"value"`
|
||||
Threshold float64 `json:"threshold"`
|
||||
Message string `json:"message"`
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
ResolvedAt *time.Time `json:"resolved_at,omitempty"`
|
||||
}
|
||||
|
||||
// MetricSummary 指标摘要,强调证据链需要的统计值和样本窗口。 // Logan.Li
|
||||
type MetricSummary struct {
|
||||
Target string `json:"target"`
|
||||
Hostname string `json:"hostname"`
|
||||
Metric string `json:"metric"`
|
||||
TimeRange string `json:"time_range"`
|
||||
SampleSize int `json:"sample_size"`
|
||||
Latest float64 `json:"latest"`
|
||||
Min float64 `json:"min"`
|
||||
Max float64 `json:"max"`
|
||||
Avg float64 `json:"avg"`
|
||||
P95 float64 `json:"p95"`
|
||||
Trend string `json:"trend"`
|
||||
Points []MetricPoint `json:"points"`
|
||||
}
|
||||
|
||||
// MetricPoint 指标采样点,默认只返回少量抽样,避免 token 膨胀。 // Logan.Li
|
||||
type MetricPoint struct {
|
||||
Time time.Time `json:"time"`
|
||||
Value float64 `json:"value"`
|
||||
}
|
||||
|
||||
// ContextPackRequest 上下文包请求。 // Logan.Li
|
||||
type ContextPackRequest struct {
|
||||
Target string `json:"target"`
|
||||
AlertID uint `json:"alert_id"`
|
||||
Metrics []string `json:"metrics"`
|
||||
TimeRange string `json:"time_range"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
// ContextPack 面向 LLM 的平台上下文包。 // Logan.Li
|
||||
type ContextPack struct {
|
||||
Scope string `json:"scope"`
|
||||
TimeRange string `json:"time_range"`
|
||||
Assets []AssetSummary `json:"assets"`
|
||||
Alerts []AlertSummary `json:"alerts"`
|
||||
Metrics []MetricSummary `json:"metrics"`
|
||||
Evidence []EvidenceItem `json:"evidence"`
|
||||
GeneratedAt time.Time `json:"generated_at"`
|
||||
}
|
||||
|
||||
// EvidenceItem 供模型引用的证据摘要。 // Logan.Li
|
||||
type EvidenceItem struct {
|
||||
Type string `json:"type"`
|
||||
Source string `json:"source"`
|
||||
Summary string `json:"summary"`
|
||||
Confidence float64 `json:"confidence"`
|
||||
}
|
||||
Reference in new issue
Block a user