// Package assistant 实现管理平台 AI 助手:基于实时平台统计信息(供应商、 // 模型、账号、用量、事件投递等)回答管理员的自然语言问题。 package assistant import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "log/slog" "net/http" "time" "aigateway.local/core/internal/gateway" "github.com/jackc/pgx/v5/pgxpool" ) var ErrUnavailable = errors.New("AI 助手服务暂不可用") // Resolver 提供默认模型供应商(注入 gateway 的 provider resolver)。 type Resolver interface { Resolve(code string) (gateway.ResolvedAdapter, error) } // Service 管理平台 AI 助手。 type Service struct { pool *pgxpool.Pool resolver Resolver client *http.Client logger *slog.Logger model string } func NewService(pool *pgxpool.Pool, resolver Resolver, model string, logger *slog.Logger) *Service { return &Service{ pool: pool, resolver: resolver, logger: logger, model: model, client: &http.Client{Timeout: 60 * time.Second}, } } // Answer 回答管理员提问。 func (s *Service) Answer(ctx context.Context, message string) (string, error) { if s == nil || s.resolver == nil { return "", ErrUnavailable } resolved, err := s.resolver.Resolve("") if err != nil { return "", fmt.Errorf("%w: 未配置默认模型供应商", ErrUnavailable) } model := s.model if model == "" { model = "gpt-4o-mini" // 兜底;实际以上游支持为准 } systemPrompt, err := s.platformSummary(ctx) if err != nil { s.logger.Warn("assistant platform summary failed", "error", err) systemPrompt = "你是 AI 网关管理助手的系统提示,请基于平台知识回答。" } payload, _ := json.Marshal(map[string]any{ "model": model, "messages": []map[string]any{ {"role": "system", "content": systemPrompt}, {"role": "user", "content": message}, }, "temperature": 0.2, }) request, err := http.NewRequestWithContext(ctx, http.MethodPost, resolved.Adapter.Target().String()+"/v1/chat/completions", bytes.NewReader(payload)) if err != nil { return "", err } request.Header.Set("Content-Type", "application/json") resolved.Adapter.Prepare(request) response, err := s.client.Do(request) if err != nil { return "", fmt.Errorf("%w: 模型调用失败: %v", ErrUnavailable, err) } defer response.Body.Close() raw, _ := io.ReadAll(io.LimitReader(response.Body, 1<<20)) if response.StatusCode/100 != 2 { return "", fmt.Errorf("%w: 模型返回 HTTP %d", ErrUnavailable, response.StatusCode) } var decoded struct { Choices []struct { Message struct { Content string `json:"content"` } `json:"message"` } `json:"choices"` } if json.Unmarshal(raw, &decoded) != nil || len(decoded.Choices) == 0 { return "", fmt.Errorf("%w: 模型响应格式无效", ErrUnavailable) } return decoded.Choices[0].Message.Content, nil } // platformSummary 汇总平台实时状态注入系统提示。 func (s *Service) platformSummary(ctx context.Context) (string, error) { var providers, models, admins, portals, apiKeys, pendingOutbox, todayRequests, todayTokens int64 _ = s.pool.QueryRow(ctx, `SELECT (SELECT count(*) FROM gateway.providers WHERE enabled), (SELECT count(*) FROM gateway.provider_models WHERE enabled), (SELECT count(*) FROM gateway.admin_accounts), (SELECT count(*) FROM gateway.portal_users), (SELECT count(*) FROM gateway.api_keys WHERE enabled), (SELECT count(*) FROM gateway.outbox_events WHERE status='pending'), (SELECT count(*) FROM gateway.audit_events WHERE recorded_at >= date_trunc('day', now())), (SELECT COALESCE(sum(prompt_tokens+completion_tokens),0) FROM gateway.audit_events WHERE recorded_at >= date_trunc('day', now()))`).Scan(&providers, &models, &admins, &portals, &apiKeys, &pendingOutbox, &todayRequests, &todayTokens) return fmt.Sprintf(`你是 AI 网关管理助手。以下是平台实时状态(由系统注入,回答时请引用准确数字): - 启用模型供应商: %d 个 - 启用的上游模型: %d 个 - 管理员账号: %d 个, 门户账号: %d 个 - 启用 API Key: %d 个 - 待处理 outbox 事件: %d 条 - 今日请求: %d 次, 今日 Token 消耗: %d 请用中文简洁回答管理员的问题;涉及平台配置建议时说明操作路径(如"供应商管理→新增供应商")。`, providers, models, admins, portals, apiKeys, pendingOutbox, todayRequests, todayTokens), nil }