Files

239 lines
6.1 KiB
Go
Raw Permalink 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 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
}