package agent
import (
"context"
"encoding/json"
"fmt"
"hash/fnv"
"math/rand"
"strings"
"time"
"github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/schema"
)
type PolishTool struct{}
func NewPolishTool() *PolishTool {
return &PolishTool{}
}
func (t *PolishTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: "polish_resume",
Desc: "优化简历内容,包括个人简介、工作经历、项目描述等。输入需要优化的文本和字段类型,返回优化后的内容。",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"text": {
Desc: "需要优化的简历文本内容",
Required: true,
Type: schema.String,
},
"field_type": {
Desc: "字段类型:summary(个人简介)、experience(工作经历)、project(项目描述)、education(教育背景)、skill(技能描述)",
Required: true,
Type: schema.String,
Enum: []string{"summary", "experience", "project", "education", "skill"},
},
"context": {
Desc: "上下文信息,如公司名称、职位、项目名称等,可选",
Type: schema.String,
},
}),
}, nil
}
func (t *PolishTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
var args struct {
Text string `json:"text"`
FieldType string `json:"field_type"`
Context string `json:"context"`
}
if err := json.Unmarshal([]byte(argumentsInJSON), &args); err != nil {
return "", fmt.Errorf("failed to unmarshal arguments: %w", err)
}
if args.Text == "" {
return "", fmt.Errorf("text is required")
}
prompt := buildPolishPrompt(args.Text, args.FieldType, args.Context)
result := fmt.Sprintf(`{"polished_text": "%s", "suggestions": ["使用了更专业的行业术语", "突出了量化成果", "优化了表达结构"]}`,
escapeJSON(prompt))
return result, nil
}
func escapeJSON(s string) string {
s = strings.ReplaceAll(s, "\\", "\\\\")
s = strings.ReplaceAll(s, "\"", "\\\"")
s = strings.ReplaceAll(s, "\n", "\\n")
s = strings.ReplaceAll(s, "\r", "\\r")
return s
}
func buildPolishPrompt(text, fieldType, context string) string {
var fieldName string
var requirements string
switch fieldType {
case "summary":
fieldName = "个人简介"
requirements = `1. 突出核心竞争力和独特价值
2. 使用行动动词和量化成果
3. 保持专业但不过于生硬
4. 控制在150字以内
5. 使用STAR法则描述关键成就`
case "experience":
fieldName = "工作经历"
requirements = `1. 使用STAR法则(情境-任务-行动-结果)
2. 突出量化成果和数据指标
3. 使用专业术语和行业关键词
4. 突出个人贡献而非团队成果
5. 强调技术能力和解决问题的能力`
case "project":
fieldName = "项目描述"
requirements = `1. 清晰描述技术架构和技术栈
2. 突出个人技术贡献和解决的难题
3. 量化项目成果和影响
4. 使用专业技术术语
5. 说明项目复杂度和创新性`
case "education":
fieldName = "教育背景"
requirements = `1. 突出专业相关性
2. 强调学术成绩和荣誉
3. 描述相关课程和项目
4. 保持简洁专业`
case "skill":
fieldName = "技能描述"
requirements = `1. 按技术栈分类整理
2. 标注熟练程度
3. 突出核心竞争力
4. 与目标职位匹配`
default:
fieldName = "文本内容"
requirements = `1. 语言更加专业、精炼
2. 突出重点和亮点
3. 使用适当的行业术语
4. 格式清晰易读`
}
prompt := fmt.Sprintf(`你是一位专业的简历优化专家。请优化以下%s内容:
原文:
%s`, fieldName, text)
if context != "" {
prompt += fmt.Sprintf(`
上下文信息:
%s`, context)
}
prompt += fmt.Sprintf(`
优化要求:
%s
请直接输出优化后的内容,不要包含其他解释。`, requirements)
return prompt
}
type AnalyzeTool struct{}
func NewAnalyzeTool() *AnalyzeTool {
return &AnalyzeTool{}
}
func (t *AnalyzeTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: "analyze_resume",
Desc: "分析简历内容,提供专业的评估和改进建议。输入简历文本,返回分析报告。",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"resume_text": {
Desc: "简历文本内容",
Required: true,
Type: schema.String,
},
"target_position": {
Desc: "目标职位,可选",
Type: schema.String,
},
}),
}, nil
}
func (t *AnalyzeTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
var args struct {
ResumeText string `json:"resume_text"`
TargetPosition string `json:"target_position"`
}
if err := json.Unmarshal([]byte(argumentsInJSON), &args); err != nil {
return "", fmt.Errorf("failed to unmarshal arguments: %w", err)
}
if args.ResumeText == "" {
return "", fmt.Errorf("resume_text is required")
}
result := `{"score": 85.5, "strengths": ["项目经验丰富,技术栈匹配度高", "工作经历描述清晰", "有量化成果展示"], "weaknesses": ["个人简介不够突出", "缺少核心优势总结", "部分技术栈描述不够详细"], "suggestions": ["优化个人简介,突出核心竞争力", "增加核心优势板块", "补充技术细节描述"], "keyword_gaps": ["微服务", "云原生", "分布式系统"]}`
return result, nil
}
type KeywordExtractTool struct{}
func NewKeywordExtractTool() *KeywordExtractTool {
return &KeywordExtractTool{}
}
type GenerateQuestionsTool struct{}
func NewGenerateQuestionsTool() *GenerateQuestionsTool {
return &GenerateQuestionsTool{}
}
func (t *GenerateQuestionsTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
return &schema.ToolInfo{
Name: "generate_interview_questions",
Desc: "根据简历内容生成面试题目,包括选择题、填空题、简答题和算法设计题。输入简历文本,返回结构化的题目列表,每类5题,按类型排序。",
ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
"resume_text": {
Desc: "简历文本内容(JSON格式)",
Required: true,
Type: schema.String,
},
"keywords": {
Desc: "指定关键词,逗号分隔,如:Go,MySQL,微服务",
Type: schema.String,
},
"types": {
Desc: "题目类型,逗号分隔:mcq(选择题)、fill(填空题)、sa(简答题)、algo(算法设计题),默认全部",
Type: schema.String,
},
}),
}, nil
}
func (t *GenerateQuestionsTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) {
var args struct {
ResumeText string `json:"resume_text"`
Keywords string `json:"keywords"`
Types string `json:"types"`
Seed string `json:"seed"`
Random bool `json:"random"`
}
if err := json.Unmarshal([]byte(argumentsInJSON), &args); err != nil {
return "", fmt.Errorf("failed to unmarshal arguments: %w", err)
}
if args.ResumeText == "" && args.Keywords == "" {
return "", fmt.Errorf("resume_text or keywords is required")
}
if args.ResumeText == "" {
args.ResumeText = "基于关键词:" + args.Keywords
}
questions := generateMockQuestions(args.ResumeText, args.Keywords, args.Types, args.Seed, args.Random)
resultJSON, err := json.Marshal(questions)
if err != nil {
return "", err
}
return string(resultJSON), nil
}
func generateMockQuestions(resumeText string, keywordsStr, typesStr, seed string, random bool) []map[string]interface{} {
var questions []map[string]interface{}
techKeywords := extractTechKeywords(resumeText)
if len(techKeywords) == 0 {
techKeywords = []string{"HTML", "CSS", "JavaScript", "Go", "MySQL", "Redis"}
}
if keywordsStr != "" {
techKeywords = strings.Split(keywordsStr, ",")
}
selectedTypes := parseTypes(typesStr)
position := extractPosition(resumeText, techKeywords)
difficultyLevel := getDifficultyByPosition(position)
// 始终使用随机种子,保证每次生成不同题目
if seed != "" {
h := fnv.New32a()
h.Write([]byte(seed))
rand.Seed(int64(h.Sum32()))
} else {
rand.Seed(time.Now().UnixNano())
}
mcqQuestions := []questionItem{
{"Go语言中,以下哪个关键字用于声明常量?", []string{"var", "const", "static", "final"}, "B",
"**解析:** const是Go语言中用于声明常量的关键字。常量是在编译时确定的值,不可修改。", []string{"Go", "常量"}, "入门", "Go基础"},
{"以下哪个不是Go语言的基本类型?", []string{"int", "string", "float64", "ArrayList"}, "D",
"**解析:** Go语言的基本类型包括:bool、int系列、uint系列、float系列、complex系列、string。", []string{"Go", "类型"}, "入门", "Go基础"},
{"Go语言中,make函数不能用于创建哪种类型?", []string{"slice", "map", "channel", "struct"}, "D",
"**解析:** make函数用于创建slice、map和channel这三种引用类型。", []string{"Go", "make"}, "初级", "Go基础"},
{"以下哪个方法可以安全地并发访问map?", []string{"直接读写", "使用sync.Mutex", "使用atomic包", "无需任何措施"}, "B",
"**解析:** Go语言的map不是并发安全的,需要使用sync.Mutex进行保护。", []string{"Go", "并发", "map"}, "中级", "Go并发"},
{"Go语言中,interface{}表示什么?", []string{"空接口", "无类型", "错误类型", "指针类型"}, "A",
"**解析:** interface{}是空接口,任何类型都实现了空接口。", []string{"Go", "接口"}, "初级", "Go基础"},
{"Go语言中,defer语句的执行顺序是?", []string{"先进先出", "先进后出", "按顺序执行", "随机执行"}, "B",
"**解析:** defer语句采用栈式管理,遵循先进后出(LIFO)原则。", []string{"Go", "defer"}, "初级", "Go基础"},
{"以下哪种情况会导致goroutine泄漏?", []string{"channel发送后关闭", "channel接收但无发送者", "使用sync.WaitGroup", "使用context取消"}, "B",
"**解析:** 如果goroutine在等待从channel接收数据,但没有发送者,将永远阻塞。", []string{"Go", "goroutine", "并发"}, "进阶", "Go并发"},
{"Go语言中,GOMAXPROCS的作用是?", []string{"限制内存使用", "限制CPU核心数", "限制goroutine数量", "限制线程栈大小"}, "B",
"**解析:** GOMAXPROCS控制Go程序可以同时使用的操作系统线程数。", []string{"Go", "GOMAXPROCS", "并发"}, "中级", "Go并发"},
{"MySQL中,以下哪个索引类型适合全文搜索?", []string{"B-Tree", "HASH", "FULLTEXT", "SPATIAL"}, "C",
"**解析:** FULLTEXT索引专门用于全文搜索。", []string{"MySQL", "索引"}, "初级", "MySQL"},
{"Redis中,哪个命令可以原子性地设置键值并设置过期时间?", []string{"SET和EXPIRE", "SETNX", "SETEX", "MSET"}, "C",
"**解析:** SETEX命令可以原子性地完成设置值和过期时间两个操作。", []string{"Redis", "命令"}, "初级", "Redis"},
{"HTTP协议中,哪个方法不是幂等的?", []string{"GET", "PUT", "DELETE", "POST"}, "D",
"**解析:** POST方法不是幂等的,多次执行可能产生不同的结果。", []string{"HTTP", "REST"}, "入门", "网络协议"},
{"微服务架构中,服务发现的作用是?", []string{"负载均衡", "服务注册与查找", "数据加密", "日志收集"}, "B",
"**解析:** 服务发现允许服务动态注册和查找其他服务的网络位置。", []string{"微服务", "服务发现"}, "初级", "架构"},
{"Go语言中,以下哪个是值类型?", []string{"slice", "map", "string", "channel"}, "C",
"**解析:** string在Go中是值类型,虽然底层是指针+长度,但赋值时会复制。", []string{"Go", "类型"}, "初级", "Go基础"},
{"Go语言中,channel默认是?", []string{"有缓冲", "无缓冲", "双向", "单向"}, "B",
"**解析:** Go语言中make(chan T)创建的是无缓冲channel,发送和接收会阻塞直到对方就绪。", []string{"Go", "channel"}, "初级", "Go并发"},
{"MySQL中,InnoDB的默认隔离级别是?", []string{"READ UNCOMMITTED", "READ COMMITTED", "REPEATABLE READ", "SERIALIZABLE"}, "C",
"**解析:** InnoDB默认隔离级别是REPEATABLE READ,可以防止不可重复读。", []string{"MySQL", "事务"}, "中级", "MySQL"},
{"Redis中,哪种数据结构最适合实现排行榜?", []string{"String", "List", "Hash", "Sorted Set"}, "D",
"**解析:** Sorted Set(有序集合)支持分数排序,非常适合实现排行榜功能。", []string{"Redis", "数据结构"}, "初级", "Redis"},
{"Docker中,COPY和ADD命令的主要区别是?", []string{"无区别", "ADD支持URL和自动解压", "COPY支持URL", "ADD更快"}, "B",
"**解析:** ADD比COPY功能更丰富,支持从URL下载文件和自动解压tar.gz。", []string{"Docker", "镜像"}, "初级", "DevOps"},
{"Kubernetes中,Pod的最小运行实例数是?", []string{"0", "1", "2", "3"}, "A",
"**解析:** Pod的replicas可以设置为0,表示不运行任何实例。", []string{"Kubernetes", "Pod"}, "初级", "DevOps"},
{"TCP协议工作在OSI模型的哪一层?", []string{"网络层", "传输层", "应用层", "数据链路层"}, "B",
"**解析:** TCP是传输层协议,负责端到端的可靠数据传输。", []string{"TCP", "网络"}, "入门", "网络协议"},
{"HTTPS默认使用的端口号是?", []string{"80", "8080", "443", "8443"}, "C",
"**解析:** HTTPS默认使用443端口,HTTP默认使用80端口。", []string{"HTTP", "HTTPS"}, "入门", "网络协议"},
{"Git中,git merge和git rebase的主要区别是?", []string{"无区别", "merge保留历史,rebase线性化", "rebase保留历史", "merge更快"}, "B",
"**解析:** merge保留完整提交历史,rebase将提交线性化到目标分支上。", []string{"Git", "版本控制"}, "初级", "工具"},
{"RESTful API中,删除资源应该使用哪个HTTP方法?", []string{"GET", "POST", "PUT", "DELETE"}, "D",
"**解析:** DELETE方法用于删除指定的资源,是幂等操作。", []string{"API", "REST"}, "入门", "架构"},
{"HTML中,哪个标签用于定义无序列表?", []string{"
", "", "- ", "
"}, "B",
"**解析:** 标签用于定义无序列表,用于有序列表,- 是列表项,
是定义列表。", []string{"HTML", "列表"}, "入门", "HTML"},
{"HTML5中,哪个标签用于定义导航链接?", []string{"