This commit is contained in:
2025-12-02 15:51:37 +08:00
parent 70e8627a96
commit bdf74652b3
17 changed files with 619 additions and 32 deletions

19
internal/infra/ai/ai.go Normal file
View File

@@ -0,0 +1,19 @@
package ai
import (
"context"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
)
// AI 定义了通用的 AI 管理接口。
// 它可以用于处理各种 AI 相关的任务,例如文本生成、内容审核等。
type AI interface {
// GenerateReview 根据提供的文本内容生成评论。
// prompt: 用于生成评论的输入文本。
// 返回生成的评论字符串和可能发生的错误。
GenerateReview(ctx context.Context, prompt string) (string, error)
// AIModel 返回当前使用的 AI 模型。
AIModel() models.AIModel
}

View File

@@ -0,0 +1,73 @@
package ai
import (
"context"
"fmt"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/config"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"github.com/google/generative-ai-go/genai"
"google.golang.org/api/option"
)
// geminiImpl 是 Gemini AI 服务的实现。
type geminiImpl struct {
client *genai.GenerativeModel
cfg *config.Gemini
}
// NewGeminiAI 创建一个新的 geminiImpl 实例。
func NewGeminiAI(ctx context.Context, cfg *config.AIConfig) (AI, error) {
// 检查 API Key 是否存在
if cfg.Gemini.APIKey == "" {
return nil, fmt.Errorf("Gemini API Key 未配置")
}
// 创建 Gemini 客户端
genaiClient, err := genai.NewClient(ctx, option.WithAPIKey(cfg.Gemini.APIKey))
if err != nil {
return nil, fmt.Errorf("创建 Gemini 客户端失败: %w", err)
}
return &geminiImpl{
client: genaiClient.GenerativeModel(cfg.Gemini.ModelName),
cfg: &cfg.Gemini,
}, nil
}
// GenerateReview 根据提供的文本内容生成评论。
func (g *geminiImpl) GenerateReview(ctx context.Context, prompt string) (string, error) {
serviceCtx, logger := logs.Trace(ctx, context.Background(), "GenerateReview")
logger.Debugf("开始调用 Gemini 生成评论prompt: %s", prompt)
timeoutCtx, cancel := context.WithTimeout(serviceCtx, time.Duration(g.cfg.Timeout)*time.Second)
defer cancel()
resp, err := g.client.GenerateContent(timeoutCtx, genai.Text(prompt))
if err != nil {
logger.Errorf("调用 Gemini API 失败: %v", err)
return "", fmt.Errorf("调用 Gemini API 失败: %w", err)
}
if resp == nil || len(resp.Candidates) == 0 || len(resp.Candidates[0].Content.Parts) == 0 {
logger.Warn("Gemini API 返回空内容或无候选评论")
return "", fmt.Errorf("Gemini API 返回空内容或无候选评论")
}
var review string
for _, part := range resp.Candidates[0].Content.Parts {
if txt, ok := part.(genai.Text); ok {
review += string(txt)
}
}
logger.Debugf("成功从 Gemini 生成评论: %s", review)
return review, nil
}
func (g *geminiImpl) AIModel() models.AIModel {
return models.AI_MODEL_GEMINI
}

View File

@@ -236,11 +236,14 @@ type AlarmNotificationConfig struct {
// AIConfig AI 服务配置
type AIConfig struct {
Gemini struct {
APIKey string `yaml:"api_key"` // Gemini API Key
ModelName string `yaml:"model_name"` // Gemini 模型名称,例如 "gemini-pro"
Timeout int `yaml:"timeout"` // AI 请求超时时间 (秒)
} `yaml:"gemini"`
Gemini Gemini `yaml:"gemini"`
}
// Gemini 代表 Gemini AI 服务的配置
type Gemini struct {
APIKey string `yaml:"api_key"` // Gemini API Key
ModelName string `yaml:"model_name"` // Gemini 模型名称,例如 "gemini-pro"
Timeout int `yaml:"timeout"` // AI 请求超时时间 (秒)
}
// NewConfig 创建并返回一个新的配置实例

View File

@@ -66,8 +66,8 @@ func NewLogger(cfg config.LogConfig) *Logger {
// 5. 构建 Logger
// zap.AddCaller() 会记录调用日志的代码行
// zap.AddCallerSkip(1) 可以向上跳层调用栈,如果我们将 logger.Info 等方法再封装一层,这个选项会很有用
zapLogger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
// zap.AddCallerSkip(2) 可以向上跳层调用栈,因为我们的日志方法被封装了两层 (Logger.Info -> Logger.logWithTrace)
zapLogger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(2))
return &Logger{sl: zapLogger.Sugar()}
}

View File

@@ -12,6 +12,12 @@ import (
"gorm.io/gorm"
)
type AIModel string
const (
AI_MODEL_GEMINI AIModel = "Gemini"
)
// Model 用于代替gorm.Model, 使用uint32以节约空间
type Model struct {
ID uint32 `gorm:"primarykey"`