Files
aiops/backend/controller/llm.go
T
LoganandClaude 471353e3d7 feat: 监控告警体系统一重构 + 自动化任务调度引擎 + 监控大屏拆分
告警中心
- 新增统一告警(alert_unified)、监控告警规则(monitor_alert_rule)、指标目录(alert_metric_catalog)
- 通知策略与消息分发(alert_notify_policy/dispatch/message)、静默规则
- 告警引擎按阈值/SNMP 拆分(alert_engine_monitor/snmp)

监控采集
- SNMP 常量/模板与历史指标(snmp templates/constants、snmp_metrics_history、snmp_collect)
- 中间件监控 Redis 采集与种子(middleware_monitor_redis/seed)
- 站点探测 pkg(siteprobe) + 站点视图(site_view)

自动化任务
- cron 调度器(cronscheduler)、脚本运行器(scriptrunner)、系统任务(systask)及运行记录(cron_job_run、system_task)

审计与凭证
- 审计设置(audit_setting) + 历史清理(auditcleaner)

前端
- 监控页面(host/middleware/site/snmp)拆分为子模块并大屏化
- 告警中心多 Tab(规则/指标目录/通知策略/静默)、任务管理组件重构、Agent CLI Markdown 渲染
- 移除旧脚本卡片/详情/日志组件,统一为 ScriptTable/ScriptCodeEditor/TerminalOutput

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-01 23:44:45 +08:00

188 lines
6.6 KiB
Go
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.
package controller
import (
"net/http"
"strings"
"time"
"ezops/model"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// LLMHandler 大语言模型配置 + 对话代理 // Logan.Li
type LLMHandler struct {
db *gorm.DB
client *http.Client
}
// NewLLMHandler 创建 LLM 处理器 // Logan.Li
func NewLLMHandler(db *gorm.DB) *LLMHandler {
return &LLMHandler{
db: db,
// 超时控制:LLM 推理可能较慢,给 120s
client: &http.Client{Timeout: 120 * time.Second},
}
}
// maskAPIKey 脱敏 API Key(仅保留后 4 位) // Logan.Li
func maskAPIKey(key string) string {
if len(key) <= 4 {
return strings.Repeat("*", len(key))
}
return strings.Repeat("*", len(key)-4) + key[len(key)-4:]
}
// getActiveConfig 读取启用的配置(脱敏后) // Logan.Li
func (h *LLMHandler) getActiveConfig(maskKey bool) (model.LLMConfig, error) {
var cfg model.LLMConfig
if err := h.db.First(&cfg).Error; err != nil {
return cfg, err
}
if maskKey {
cfg.APIKey = maskAPIKey(cfg.APIKey)
}
return cfg, nil
}
// GetLLMConfig 获取 LLM 配置(API Key 脱敏) // Logan.Li
// GET /api/v1/aiops/llm-config
func (h *LLMHandler) GetLLMConfig(c *gin.Context) {
cfg, err := h.getActiveConfig(true)
if err != nil {
// 未配置返回空对象(前端渲染空表单)
c.JSON(http.StatusOK, model.Response{Code: 200, Data: gin.H{
"provider": "", "model": "", "api_key": "",
"endpoint": "", "temperature": 0.7, "max_tokens": 2000,
"system_prompt": "", "enabled": false, "reasoning_enabled": false, "configured": false,
}})
return
}
c.JSON(http.StatusOK, model.Response{Code: 200, Data: gin.H{
"id": cfg.ID,
"provider": cfg.Provider,
"model": cfg.Model,
"api_key": cfg.APIKey,
"endpoint": cfg.Endpoint,
"temperature": cfg.Temperature,
"max_tokens": cfg.MaxTokens,
"system_prompt": cfg.SystemPrompt,
"enabled": cfg.Enabled,
"reasoning_enabled": cfg.ReasoningEnabled,
"configured": true,
}})
}
// SaveLLMConfig 保存 LLM 配置(upsert 单行) // Logan.Li
// PUT /api/v1/aiops/llm-config
func (h *LLMHandler) SaveLLMConfig(c *gin.Context) {
var req model.SaveLLMConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "请求参数错误: " + err.Error()})
return
}
var cfg model.LLMConfig
h.db.First(&cfg)
// 若前端回传脱敏值(含 ****),保留原 Key
if strings.Contains(req.APIKey, "****") && cfg.ID != 0 {
req.APIKey = cfg.APIKey
}
cfg.Provider = req.Provider
cfg.Model = req.Model
cfg.APIKey = req.APIKey
cfg.Endpoint = req.Endpoint
cfg.Temperature = req.Temperature
cfg.MaxTokens = req.MaxTokens
cfg.SystemPrompt = req.SystemPrompt
cfg.Enabled = req.Enabled
cfg.ReasoningEnabled = req.ReasoningEnabled
if cfg.ID == 0 {
if err := h.db.Create(&cfg).Error; err != nil {
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "保存失败: " + err.Error()})
return
}
} else {
if err := h.db.Save(&cfg).Error; err != nil {
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "保存失败: " + err.Error()})
return
}
}
// 返回脱敏后的配置
cfg.APIKey = maskAPIKey(cfg.APIKey)
c.JSON(http.StatusOK, model.Response{Code: 200, Message: "保存成功", Data: gin.H{
"id": cfg.ID,
"provider": cfg.Provider,
"model": cfg.Model,
"api_key": cfg.APIKey,
"endpoint": cfg.Endpoint,
"temperature": cfg.Temperature,
"max_tokens": cfg.MaxTokens,
"system_prompt": cfg.SystemPrompt,
"enabled": cfg.Enabled,
"reasoning_enabled": cfg.ReasoningEnabled,
"configured": true,
}})
}
// resolveEndpoint 根据提供商补全默认 endpoint // Logan.Li
func resolveEndpoint(provider, endpoint string) string {
if endpoint != "" {
return strings.TrimRight(endpoint, "/")
}
switch provider {
case "deepseek":
return "https://api.deepseek.com/v1"
case "glm": // 智谱 GLM 通用 OpenAI 兼容端点(编码套餐改用 https://open.bigmodel.cn/api/coding/paas/v4) // Logan.Li
return "https://open.bigmodel.cn/api/paas/v4"
case "qwen":
return "https://dashscope.aliyuncs.com/compatible-mode/v1"
case "moonshot":
return "https://api.moonshot.cn/v1"
case "kimi": // Kimi 编码套餐 OpenAI 兼容端点(注意:该端点有客户端白名单,通用调用可能被拒) // Logan.Li
return "https://api.kimi.com/coding/v1"
case "mimo": // 小米 MiMo OpenAI 兼容端点 // Logan.Li
return "https://api.mimo.mi.com/v1"
case "anthropic": // Anthropic 官方 Messages 协议(anthropic.go 客户端拼 /v1/messages) // Logan.Li
return "https://api.anthropic.com"
case "glm-coding": // 智谱 GLM 编码套餐 Anthropic 协议端点(模型 GLM-5.2) // Logan.Li
return "https://open.bigmodel.cn/api/anthropic"
case "minimax": // MiniMax 按量 OpenAI 兼容端点 // Logan.Li
return "https://api.minimaxi.com/v1"
case "minimax-coding": // MiniMax 编码套餐 Anthropic 协议端点 // Logan.Li
return "https://api.minimaxi.com/anthropic"
case "openai": // OpenAI 官方端点 // Logan.Li
return "https://api.openai.com/v1"
case "siliconflow": // 硅基流动 OpenAI 兼容端点 // Logan.Li
return "https://api.siliconflow.cn/v1"
case "ollama":
return "http://localhost:11434/v1"
case "vllm": // vLLM OpenAI 兼容端点 // Logan.Li
return "http://localhost:8000/v1"
default: // openai / custom
return "https://api.openai.com/v1"
}
}
// isLocalLLMProvider 本地/自建模型,API Key 可选 // Logan.Li
func isLocalLLMProvider(provider string) bool {
return provider == "ollama" || provider == "vllm" || provider == "custom"
}
// resolveProtocol 根据提供商推断接入协议(决定走 OpenAI 还是 Anthropic 客户端) // Logan.Li
// 编码套餐(智谱 / MiniMax)与 Anthropic 官方走 Messages 协议;其余走 OpenAI 兼容协议。 // Logan.Li
func resolveProtocol(provider string) string {
if provider == "anthropic" || provider == "glm-coding" || provider == "minimax-coding" {
return "anthropic"
}
return "openai"
}
// defaultSystemPrompt AIOps 运维助手默认系统提示词 // Logan.Li
const defaultSystemPrompt = `你是 AIOps 智能运维助手,专注于企业级运维场景。你可以:
1. 分析服务器、容器、K8s 集群的性能瓶颈与异常
2. 解读告警事件并给出根因分析与处置建议
3. 生成 Shell / Ansible / Python 运维脚本
4. 提供容量规划、成本优化、高可用架构建议
回答请简洁、专业、可执行,优先给出具体命令或步骤。使用中文回答。`