// Package agent AI Agent 模块,基于 Eino 框架实现简历优化智能体 package agent import ( "context" "resume-platform/pkg/config" "github.com/cloudwego/eino/adk" "github.com/cloudwego/eino/compose" "github.com/cloudwego/eino/components/tool" "github.com/cloudwego/eino-ext/components/model/openai" ) // ResumeAgentConfig 简历 Agent 配置 type ResumeAgentConfig struct { ChatModel *openai.ChatModel // 聊天模型实例 Provider string // AI 供应商名称 BaseURL string // 接口地址 APIKey string // API Key Model string // 模型名称 MaxTokens int // 最大 token 数 Temperature float64 // 温度参数 } // ResumeAgent 简历优化智能体 type ResumeAgent struct { agent adk.Agent // Eino Agent 实例 config *ResumeAgentConfig // Agent 配置 } // NewResumeAgent 创建简历优化智能体 // @param ctx 上下文 // @param cfg AI 配置 // @return *ResumeAgent 智能体实例 // @return error 创建错误 // @author sunct func NewResumeAgent(ctx context.Context, cfg *config.AIConfig) (*ResumeAgent, error) { chatModel, err := createChatModel(ctx, cfg) if err != nil { return nil, err } tools := []tool.BaseTool{ NewPolishTool(), NewAnalyzeTool(), NewKeywordExtractTool(), NewGenerateQuestionsTool(), } maxTokens := getMaxTokens(cfg) agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{ Name: "ResumeAgent", Description: "专业的简历优化智能体,能够分析简历内容、提供优化建议、提取关键词,帮助用户打造高质量简历。", Instruction: "你是一位专业的简历优化专家。请根据用户的请求,使用可用的工具来帮助用户优化简历。\n\n可用工具:\n1. polish_resume - 优化简历内容\n2. analyze_resume - 分析简历并提供评估\n3. extract_keywords - 从简历中提取关键词\n4. generate_interview_questions - 根据简历内容生成面试题目\n\n请根据用户的具体需求选择合适的工具。", Model: chatModel, ToolsConfig: adk.ToolsConfig{ ToolsNodeConfig: compose.ToolsNodeConfig{ Tools: tools, }, }, MaxIterations: 20, }) if err != nil { return nil, err } return &ResumeAgent{ agent: agent, config: &ResumeAgentConfig{ ChatModel: chatModel, Provider: cfg.Provider, Model: getModelName(cfg), MaxTokens: maxTokens, }, }, nil } // createChatModel 根据配置创建 OpenAI 兼容的聊天模型 // @param ctx 上下文 // @param cfg AI 配置 // @return *openai.ChatModel 聊天模型实例 // @return error 创建错误 // @author sunct func createChatModel(ctx context.Context, cfg *config.AIConfig) (*openai.ChatModel, error) { var apiKey, baseURL, model string switch cfg.Provider { case "spark": apiKey = cfg.Spark.APIKey if apiKey == "" { parts := splitPassword(cfg.Spark.APIPassword) if len(parts) == 2 { apiKey = parts[0] } } baseURL = cfg.Spark.BaseURL model = getSparkModel(cfg.Spark.Model) case "tongyi": apiKey = cfg.Tongyi.APIKey baseURL = cfg.Tongyi.BaseURL model = cfg.Tongyi.Model case "doubao": apiKey = cfg.Doubao.APIKey baseURL = cfg.Doubao.BaseURL model = cfg.Doubao.Model case "deepseek": apiKey = cfg.DeepSeek.APIKey baseURL = cfg.DeepSeek.BaseURL model = cfg.DeepSeek.Model default: apiKey = cfg.Spark.APIKey if apiKey == "" { parts := splitPassword(cfg.Spark.APIPassword) if len(parts) == 2 { apiKey = parts[0] } } baseURL = cfg.Spark.BaseURL model = getSparkModel(cfg.Spark.Model) } maxTokens := getMaxTokens(cfg) return openai.NewChatModel(ctx, &openai.ChatModelConfig{ Model: model, APIKey: apiKey, BaseURL: baseURL + "/chat/completions", MaxTokens: &maxTokens, Temperature: &temperature, }) } var temperature = float32(0.7) // getSparkModel 获取讯飞星火模型全名 // @param model 模型简称(lite/pro/max) // @return string 完整模型名 // @author sunct func getSparkModel(model string) string { switch model { case "lite": return "spark-lite" case "pro": return "spark-pro" case "max": return "spark-max" default: return "spark-lite" } } // getModelName 根据供应商获取模型名称 // @param cfg AI 配置 // @return string 模型名 // @author sunct func getModelName(cfg *config.AIConfig) string { switch cfg.Provider { case "spark": return getSparkModel(cfg.Spark.Model) case "tongyi": return cfg.Tongyi.Model case "doubao": return cfg.Doubao.Model case "deepseek": return cfg.DeepSeek.Model default: return "spark-lite" } } // getMaxTokens 根据供应商获取最大 token 数 // @param cfg AI 配置 // @return int 最大 token 数 // @author sunct func getMaxTokens(cfg *config.AIConfig) int { switch cfg.Provider { case "spark": return cfg.Spark.MaxTokens case "tongyi": return cfg.Tongyi.MaxTokens case "doubao": return cfg.Doubao.MaxTokens case "deepseek": return cfg.DeepSeek.MaxTokens default: return cfg.Spark.MaxTokens } } // splitPassword 按冒号分割密码字符串,提取 Key 和 Secret // @param password 密码字符串 // @return []string 分割结果 // @author sunct func splitPassword(password string) []string { for i, c := range password { if c == ':' { return []string{password[:i], password[i+1:]} } } return []string{password} } // Run 执行 Agent 查询,获取最终回答 // @param ctx 上下文 // @param query 用户查询 // @return string Agent 回答内容 // @return error 执行错误 // @author sunct func (a *ResumeAgent) Run(ctx context.Context, query string) (string, error) { runner := adk.NewRunner(ctx, adk.RunnerConfig{Agent: a.agent}) iter := runner.Query(ctx, query) var result string for { event, ok := iter.Next() if !ok { break } if event.Output != nil { if msgOutput := event.Output.MessageOutput; msgOutput != nil { msg, err := msgOutput.GetMessage() if err == nil && msg != nil { result = msg.Content } } } } return result, nil } // GetProvider 获取当前使用的 AI 供应商名称 // @return string 供应商标识 // @author sunct func (a *ResumeAgent) GetProvider() string { return a.config.Provider }