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