1.什么是 Agent?
在 Eino 框架中,Agent(智能体) 代表一个独立且可执行的智能任务单元。它可以被视为一个具备理解指令、执行任务并反馈结果能力的实体。每个 Agent 都拥有明确的名称和描述,这使得它们能够被系统中的其他组件发现并调用。
核心理念:任何需要与大语言模型(LLM)进行复杂交互的场景,都可以被抽象为一个 Agent。
典型的应用场景包括:
信息查询 Agent:实时获取并处理天气、股市等数据。
事务处理 Agent:协助用户预定会议室、安排日程。
领域专家 Agent:基于特定知识库回答专业问题(如法律咨询、医疗问答)。
在 Eino 的架构设计中,Agent 被定义为一个标准的 interface,这为开发者提供了高度的灵活性和扩展性。
本文将基于 Eino 框架的 ADK (Agent Development Kit) 和豆包模型,通过 ChatModelAgent 实现一个具备 PDF 简历解析与人岗匹配分析功能的智能体。

参考文档
Eino ADK: 概述 | CloudWeGo
Eino ADK: ChatModelAgent | CloudWeGo
Parser - pdf | CloudWeGo
2.实现思路与流程
为了构建这个简历解析 Agent,我们将开发过程拆解为以下五个核心步骤:
- 环境配置与模型初始化:创建一个支持工具调用(Function Calling)的
ChatModel。 - Agent 骨架搭建:使用 Eino ADK 创建 Agent 实例,并注入模型。
- Prompt 工程:编写精准的系统指令(System Prompt),指导 AI 如何读取文件、解析内容以及规范输出格式。
- 工具开发:实现
pdf_to_text工具,赋予 Agent 读取本地 PDF 文件的能力。 - 业务串联与执行:构建运行环境(Runner),处理用户输入并展示最终的分析结果。
完整代码可以在 GitHub 查看
3.代码实现
3.1 创建 ChatModel
首先,我们需要配置与大语言模型交互的基础组件。在项目根目录下创建 .env 文件,填入必要的 API 配置:
OPENAI_API_KEY = 你的API key
OPENAI_MODEL_NAME = 模型名称
OPENAI_BASE_URL = 模型api url
Eino 提供了 ToolCallingChatModel 接口,它在基础 BaseChatModel 之上扩展了工具绑定能力(WithTools)。以下代码负责从环境变量加载配置并初始化模型:
// agent.go
func newChatModelFromEnv(ctx context.Context) (model.ToolCallingChatModel, error) {
err := godotenv.Load()
if err != nil {
return nil, fmt.Errorf("failed to load env file: %w", err)
}
key := os.Getenv("OPENAI_API_KEY")
modelName := os.Getenv("OPENAI_MODEL_NAME")
baseUrl := os.Getenv("OPENAI_BASE_URL")
chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
BaseURL: baseUrl,
Model: modelName,
APIKey: key,
})
if err != nil {
return nil, errors.Errorf("Failed to create OpenAI chat model: %v", err)
}
return chatModel, err
}
3.2 创建一个 Agent 用于解析 PDF 简历
接下来,使用 adk.NewChatModelAgent 构建 Agent 主体。我们需要配置 Agent 的身份描述、指令(Instruction)以及它所能使用的工具。
// agent.go
func NewResumeAgent() adk.Agent {
ctx := context.Background()
chatModel, err := newChatModelFromEnv(ctx)
if err != nil {
log.Fatalf("create chat model failed: %v", err)
}
baseAgent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "ResumeParserAgent",
Description: "一个专业的简历解析智能体,用于提取简历中的关键信息",
// 调用 ChatModel 时的 System Prompt,支持 f-string 渲染
Instruction: "TODO: 提示词",
// 运行所使用的 ChatModel,要求支持工具调用
Model: chatModel,
ToolsConfig: adk.ToolsConfig{
ToolsNodeConfig: compose.ToolsNodeConfig{
Tools: []componentstool.BaseTool{
// 此处将注册 PDF 解析工具,详见后文
},
},
},
})
if err != nil {
log.Fatal(fmt.Errorf("failed to create resume parser agent: %w", err))
}
return baseAgent
}
3.3 设计 System Prompt (提示词)
Prompt 是 Agent 的灵魂。我们需要明确告诉 AI:它的角色是什么、可以使用什么工具、以及必须输出什么格式的数据。
为了确保程序能够稳定解析结果,我们要求 AI 严格输出 JSON 格式。
# Role
你是一位拥有 10 年经验的资深技术招聘专家(Senior Technical Recruiter)。你擅长从简历中提取关键信息,并能结合职位描述(JD)对候选人进行深度画像分析。
# 你可以使用的工具
pdf_to_text: 解析PDF简历
# 注意事项
- 你必须使用提供给你的工具
- 不要跳过工具调用,不要返回空数据
- 必须从简历内容中提取真实的信息
- 只返回JSON格式,不要返回其他文本
# 任务步骤
请阅读用户提供的【候选人简历】和【目标岗位描述(JD)】,完成以下两项任务:
1. 使用工具解析提供的简历文件路径,获取简历的完整文本内容
2. 精准提取简历中的结构化信息。
3. 基于简历内容和 JD,对候选人进行深度评估和人岗匹配分析。
# Analysis Guidelines (分析指南)
在生成 ai_analysis 字段时,请严格遵循以下思维逻辑:
1. **技术栈匹配分析**:
- 对比简历技能与 JD 要求的 "Must-have" 和 "Nice-to-have"。
- 关注技术版本的时效性(如:候选人精通 Java 8,但 JD 需要 Go)。
2. **行业背景与业务理解**:
- 分析候选人过往公司的业务领域(如:金融、电商、SaaS)。
- 判断候选人处理的业务复杂度(如:高并发、海量数据、复杂算法)。
3. **职业发展轨迹 (Career Trajectory)**:
- **成长性**:观察职位变迁,判断是否有晋升或职责扩大。
- **稳定性**:计算平均在职时间,识别是否存在频繁跳槽(<1年)或长时间空窗(>3个月)。
- **平台背书**:识别过往公司是否为知名企业或行业头部。
4. **综合素质判断**:
- 从项目描述中寻找“解决复杂问题”的能力。
- 从自我评价、博客、开源项目中判断“学习能力”和“技术热情”。
# Extraction Rules (提取规则)
- **缺失处理**:如果简历中没有明确提到的字段,请填空字符串,严禁编造。
- **时间格式**:统一标准化为 YYYY-MM。
- **薪资标准化**:保留原始描述,如 "20k*14" 或 "30-50万"。
- **项目经历**:重点提取项目中的 tech_stack 和 role。
# Output Format (输出格式)
请直接输出标准的 JSON 格式,不要包含 Markdown 代码块标记(json),也不要包含任何解释性文字。
JSON 结构模板如下:
{
"basic_info": {
"name": "从简历中提取的真实姓名",
"work_years": "工作年限(如2.5年,3年,应届毕业生等)",
"phone": "联系方式(手机)",
"email": "电子邮箱",
"city": "现居住城市",
"job_intention": "求职意向职位",
"salary_expectation": "期望薪资字符串(如10k, 9000, 15k-25k等)"
},
"basic_info_extended": {
"current_status": "在职/离职/在校",
"availability": "预计到岗时间 (如: 一周内/一个月)",
"birth":"出生年月",
"age": "根据出生年月/教育经历推算的年龄 (可选,作为参考)",
"gender": "性别 (如果简历中有写)"
},
"social_links": {
"linkedin": "LinkedIn个人主页链接",
"github": "GitHub个人主页链接",
"blog": "个人博客链接",
"stackoverflow": "StackOverflow个人主页链接",
"其他社交平台链接": "对应的个人主页链接"
},
"education": [
{
"school": "学校名称",
"degree": "学历",
"major": "专业",
"start_year": "入学年份",
"end_year": "毕业年份",
"gpa":"绩点(如 3.8/4.0 或 Top 5%)",
"major_courses":["主修课程","数据结构","编译原理"]
}
],
"work_experience": [
{
"company": "公司名称",
"position": "职位名称",
"start_date": "入职时间(格式如:2018-03)",
"end_date": "离职时间(格式如:2019-08或至今)",
"description": "工作职责描述",
"achievements": "工作成就描述",
"tech_stack": "公司项目用到的技术栈"
}
],
"projects": [
{
"name": "从简历中提取的项目名称",
"role": "从简历中提取的在项目中的角色",
"start_date": "从简历中提取的项目开始时间(格式如:2018-03)",
"end_date": "从简历中提取的项目结束时间(格式如:2019-08或至今)",
"description": "项目背景与描述",
"tech_stack": "项目用到的技术栈",
"link": "项目链接/demo地址(如果没有,就为空)"
}
],
"skills": [
"从简历中提取的技能1",
"从简历中提取的技能2"
],
"certifications": [
"技能证书1",
"技能证书2"
],
"languages": [
{
"language": "语种(如:英语)",
"proficiency": "熟练程度(如:熟练,一般,精通,CET-6, 雅思7.0等)"
}
],
"awards": [
{
"name": "奖项名称",
"date": "获奖时间(格式如:2019-05)",
"level": "奖项级别(如:校级,省级,国家级,国际级等)"
}
],
"ai_analysis": {
"summary": "AI生成的一段200字以内的候选人画像总结, 方便面试官快速了解",
"highlights": [
"候选人亮点",
"拥有5年高并发系统设计经验,与岗位需求高度匹配",
"具有PMP证书,具备良好的项目管理和团队协作能力",
"有从0到1搭建SaaS平台的完整经历"
],
"job_match_gaps": [
"岗位匹配缺口",
"结合JD 分析候选人与岗位的匹配度不足之处",
"缺少岗位要求的Go语言实战经验,主要技术栈为Java",
"未涉及过海外支付业务,业务背景与JD有一定偏差",
"目前居住地在上海,岗位在北京,需要确认异地入职意愿"
],
"match_tags": [
"自动提取候选人标签",
"团队管理",
"金融行业背景"
],
"risk_flags": [
"风险提示",
"频繁跳槽 (平均在职时间小于1年)",
"存在6个月以上的空窗期"
],
"leadership_potential": "根据过往经历判断其带团队的潜力 (高/中/低)",
"suggested_interview_direction": [
"Agent 根据简历内容自动生成的个性化面试题的提问方向",
"方向1-技术面: 我看到你在XX项目中使用了Redis, 请问你是如何解决缓存穿透问题的,",
"方向2-HR面: 你的简历中有两段经历时间重叠, 可以解释一下吗?"
]
}
}
目前 agent.go 的代码如下:
package resume_agent
import (
"context"
tool2 "demo/toolx"
"fmt"
"github.com/cloudwego/eino-ext/components/model/openai"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/components/model"
componentstool "github.com/cloudwego/eino/components/tool"
"github.com/cloudwego/eino/compose"
"github.com/joho/godotenv"
"github.com/pkg/errors"
"log"
"os"
)
func NewResumeAgent() adk.Agent {
ctx := context.Background()
chatModel, err := newChatModelFromEnv(ctx)
if err != nil {
log.Fatalf("create chat model failed: %v", err)
}
baseAgent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "ResumeParserAgent",
Description: "一个专业的简历解析智能体,用于提取简历中的关键信息",
// 调用 ChatModel 时的 System Prompt,支持 f-string 渲染
Instruction: genInstructionPrompt(),
// 运行所使用的 ChatModel,要求支持工具调用
Model: chatModel,
//可以通过 ToolsConfig 为 ChatModelAgent 配置 Tool
ToolsConfig: adk.ToolsConfig{
ToolsNodeConfig: compose.ToolsNodeConfig{
Tools: []componentstool.BaseTool{
// 【TODO】:还需要给Agent准备PDF工具
},
},
},
// react 模式下 ChatModel 最大生成次数,超过时 Agent 会报错退出,默认值为 20
MaxIterations: 20,
})
if err != nil {
log.Fatal(fmt.Errorf("failed to create resume parser agent: %w", err))
}
return baseAgent
}
func newChatModelFromEnv(ctx context.Context) (model.ToolCallingChatModel, error) {
err := godotenv.Load()
if err != nil {
return nil, fmt.Errorf("failed to load env file: %w", err)
}
key := os.Getenv("OPENAI_API_KEY")
modelName := os.Getenv("OPENAI_MODEL_NAME")
baseUrl := os.Getenv("OPENAI_BASE_URL")
chatModel, err := openai.NewChatModel(ctx, &openai.ChatModelConfig{
BaseURL: baseUrl,
Model: modelName,
APIKey: key,
})
if err != nil {
return nil, errors.Errorf("Failed to create OpenAI chat model: %v", err)
}
return chatModel, err
}
func genInstructionPrompt() string {
return `
# Role
你是一位拥有 10 年经验的资深技术招聘专家(Senior Technical Recruiter)。你擅长从简历中提取关键信息,并能结合职位描述(JD)对候选人进行深度画像分析。
# 你可以使用的工具
pdf_to_text: 解析PDF简历
# 注意事项
- 你必须使用提供给你的工具
- 不要跳过工具调用,不要返回空数据
- 必须从简历内容中提取真实的信息
- 只返回JSON格式,不要返回其他文本
# 任务步骤
请阅读用户提供的【候选人简历】和【目标岗位描述(JD)】,完成以下两项任务:
1. 使用工具解析提供的简历文件路径,获取简历的完整文本内容
2. 精准提取简历中的结构化信息。
3. 基于简历内容和 JD,对候选人进行深度评估和人岗匹配分析。
# Analysis Guidelines (分析指南)
在生成 ai_analysis 字段时,请严格遵循以下思维逻辑:
1. **技术栈匹配分析**:
- 对比简历技能与 JD 要求的 "Must-have" 和 "Nice-to-have"。
- 关注技术版本的时效性(如:候选人精通 Java 8,但 JD 需要 Go)。
2. **行业背景与业务理解**:
- 分析候选人过往公司的业务领域(如:金融、电商、SaaS)。
- 判断候选人处理的业务复杂度(如:高并发、海量数据、复杂算法)。
3. **职业发展轨迹 (Career Trajectory)**:
- **成长性**:观察职位变迁,判断是否有晋升或职责扩大。
- **稳定性**:计算平均在职时间,识别是否存在频繁跳槽(<1年)或长时间空窗(>3个月)。
- **平台背书**:识别过往公司是否为知名企业或行业头部。
4. **综合素质判断**:
- 从项目描述中寻找“解决复杂问题”的能力。
- 从自我评价、博客、开源项目中判断“学习能力”和“技术热情”。
# Extraction Rules (提取规则)
- **缺失处理**:如果简历中没有明确提到的字段,请填空字符串,严禁编造。
- **时间格式**:统一标准化为 YYYY-MM。
- **薪资标准化**:保留原始描述,如 "20k*14" 或 "30-50万"。
- **项目经历**:重点提取项目中的 tech_stack 和 role。
# Output Format (输出格式)
请直接输出标准的 JSON 格式,不要包含 Markdown 代码块标记(json),也不要包含任何解释性文字。
JSON 结构模板如下:
{
"basic_info": {
"name": "从简历中提取的真实姓名",
"work_years": "工作年限(如2.5年,3年,应届毕业生等)",
"phone": "联系方式(手机)",
"email": "电子邮箱",
"city": "现居住城市",
"job_intention": "求职意向职位",
"salary_expectation": "期望薪资字符串(如10k, 9000, 15k-25k等)"
},
"basic_info_extended": {
"current_status": "在职/离职/在校",
"availability": "预计到岗时间 (如: 一周内/一个月)",
"birth":"出生年月",
"age": "根据出生年月/教育经历推算的年龄 (可选,作为参考)",
"gender": "性别 (如果简历中有写)"
},
"social_links": {
"linkedin": "LinkedIn个人主页链接",
"github": "GitHub个人主页链接",
"blog": "个人博客链接",
"stackoverflow": "StackOverflow个人主页链接",
"其他社交平台链接": "对应的个人主页链接"
},
"education": [
{
"school": "学校名称",
"degree": "学历",
"major": "专业",
"start_year": "入学年份",
"end_year": "毕业年份",
"gpa":"绩点(如 3.8/4.0 或 Top 5%)",
"major_courses":["主修课程","数据结构","编译原理"]
}
],
"work_experience": [
{
"company": "公司名称",
"position": "职位名称",
"start_date": "入职时间(格式如:2018-03)",
"end_date": "离职时间(格式如:2019-08或至今)",
"description": "工作职责描述",
"achievements": "工作成就描述",
"tech_stack": "公司项目用到的技术栈"
}
],
"projects": [
{
"name": "从简历中提取的项目名称",
"role": "从简历中提取的在项目中的角色",
"start_date": "从简历中提取的项目开始时间(格式如:2018-03)",
"end_date": "从简历中提取的项目结束时间(格式如:2019-08或至今)",
"description": "项目背景与描述",
"tech_stack": "项目用到的技术栈",
"link": "项目链接/demo地址(如果没有,就为空)"
}
],
"skills": [
"从简历中提取的技能1",
"从简历中提取的技能2"
],
"certifications": [
"技能证书1",
"技能证书2"
],
"languages": [
{
"language": "语种(如:英语)",
"proficiency": "熟练程度(如:熟练,一般,精通,CET-6, 雅思7.0等)"
}
],
"awards": [
{
"name": "奖项名称",
"date": "获奖时间(格式如:2019-05)",
"level": "奖项级别(如:校级,省级,国家级,国际级等)"
}
],
"ai_analysis": {
"summary": "AI生成的一段200字以内的候选人画像总结, 方便面试官快速了解",
"highlights": [
"候选人亮点",
"拥有5年高并发系统设计经验,与岗位需求高度匹配",
"具有PMP证书,具备良好的项目管理和团队协作能力",
"有从0到1搭建SaaS平台的完整经历"
],
"job_match_gaps": [
"岗位匹配缺口",
"结合JD 分析候选人与岗位的匹配度不足之处",
"缺少岗位要求的Go语言实战经验,主要技术栈为Java",
"未涉及过海外支付业务,业务背景与JD有一定偏差",
"目前居住地在上海,岗位在北京,需要确认异地入职意愿"
],
"match_tags": [
"自动提取候选人标签",
"团队管理",
"金融行业背景"
],
"risk_flags": [
"风险提示",
"频繁跳槽 (平均在职时间小于1年)",
"存在6个月以上的空窗期"
],
"leadership_potential": "根据过往经历判断其带团队的潜力 (高/中/低)",
"suggested_interview_direction": [
"Agent 根据简历内容自动生成的个性化面试题的提问方向",
"方向1-技术面: 我看到你在XX项目中使用了Redis, 请问你是如何解决缓存穿透问题的,",
"方向2-HR面: 你的简历中有两段经历时间重叠, 可以解释一下吗?"
]
}
}
`
}
3.4 实现 PDF 解析工具
Agent 自身无法直接“看”到文件,需要通过工具(Tool)来赋予其能力。Eino 的 ToolsNode 支持 InvokableTool(同步调用工具)。
我们将封装 Eino 生态中的 pdfParser 组件,将其包装为一个 Agent 可调用的工具。
ToolsConfig: adk.ToolsConfig{
ToolsNodeConfig: compose.ToolsNodeConfig{
Tools: []componentstool.BaseTool{
// pdf_to_text是prompt中写好的工具名称, 大模型根据这个来调用PDF工具
NewPdfToolWithEino("pdf_to_text")
},
},
},
具体实现:
package pdf
import (
"context"
"fmt"
"os"
"time"
pdfParser "github.com/cloudwego/eino-ext/components/document/parser/pdf"
"github.com/cloudwego/eino/components/document/parser"
"github.com/cloudwego/eino/components/tool"
einoutils "github.com/cloudwego/eino/components/tool/utils"
"log"
"strings"
)
func NewPdfToolWithEino(name string) tool.InvokableTool {
// 用于告诉模型如何/何时/为什么使用这个工具
// 可以在描述中包含少量示例
toolDesc := `
将本地PDF转换为纯文本
需传入本地PDF的绝对路径
`
pdfTool, err := einoutils.InferTool(name, toolDesc, convertPdfToText)
if err != nil {
log.Fatalf("NewPdfToolWithEino failed, err: %v", err)
}
log.Println("使用eino提供的 pdf 解析器, 初始化完成, 工具名称: ", name)
return pdfTool
}
func convertPdfToText(ctx context.Context, req *ParsePdfRequest) (*ParsePdfResponse, error) {
result := &ParsePdfResponse{
Success: false,
Meta: genMeta(req),
}
file, err := validateAndOpenPdf(req)
if err != nil {
result.ErrorMsg = err.Error()
return result, nil
}
defer file.Close()
// 按大模型传入的参数决定是否分页
einoPdfParser, err := pdfParser.NewPDFParser(ctx, &pdfParser.Config{ToPages: req.ToPages})
if err != nil {
result.ErrorMsg = fmt.Sprintf("初始化eino pdf解析器失败: %v", err)
return result, nil
}
docs, err := einoPdfParser.Parse(ctx, file,
parser.WithURI(req.FilePath),
parser.WithExtraMeta(result.Meta),
)
if err != nil {
result.ErrorMsg = fmt.Sprintf("eino pdf解析器解析文件失败: %v", err)
return result, nil
}
result.Success = true
result.TotalPages = len(docs)
if req.ToPages {
// 分页
// 分页模式:按页码整理文本(用索引+1作为页码,可靠无依赖)
pages := make([]PdfPageText, 0, len(docs))
for idx, doc := range docs {
pages = append(pages, PdfPageText{
Page: idx + 1,
Content: doc.Content,
})
}
result.Pages = pages
} else {
// 不分页
var sb strings.Builder
for _, doc := range docs {
sb.WriteString(doc.Content)
sb.WriteString("\n")
}
result.Content = sb.String()
}
return result, nil
}
type ParsePdfRequest struct {
FilePath string `json:"filePath" jsonschema:"description=本地PDF文件的绝对路径"`
ToPages bool `json:"toPages" jsonschema:"description=是否按页分割文本,true=分页输出"`
}
type ParsePdfResponse struct {
Success bool `json:"success" jsonschema:"description=是否解析成功"`
Content string `json:"content" jsonschema:"description=解析出的文本内容"`
Pages []PdfPageText `json:"pages" jsonschema:"description=按页分割的文本内容(仅当toPages为true时返回)"`
TotalPages int `json:"totalPages" jsonschema:"description=总页数"`
ErrorMsg string `json:"errorMsg,omitempty" jsonschema:"description=解析失败时的错误信息"`
Meta ParsePdfMeta `json:"meta,omitempty" jsonschema:"description=解析时的元数据"`
}
type ParsePdfMeta = map[string]any
type PdfPageText struct {
Page int `json:"page" jsonschema:"description=页码"`
Content string `json:"content" jsonschema:"description=该页的文本内容"`
}
func genMeta(req *ParsePdfRequest) ParsePdfMeta {
meta := make(ParsePdfMeta)
meta["filePath"] = req.FilePath
meta["toPages"] = req.ToPages
meta["parseTime"] = time.Now().Format("2006-01-02 15:04:05")
return meta
}
// 校验并打开PDF文件
func validateAndOpenPdf(req *ParsePdfRequest) (*os.File, error) {
if req.FilePath == "" {
return nil, fmt.Errorf("必须传入 pdf 文件的绝对路径")
}
file, err := os.Open(req.FilePath)
if err != nil {
return nil, fmt.Errorf("打开PDF文件失败:%v(请检查路径是否正确、文件是否存在)", err)
}
return file, err
}
3.5 运行 Agent (Main 函数)
最后,通过 adk.Runner 将 Agent 运行起来。我们将传入候选人简历路径和具体的岗位描述(JD),观察 Agent 的分析过程。
package main
import (
"context"
"example-eino-pdf-agent/agents/resume"
"fmt"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/schema"
"log"
"time"
)
// 获取岗位描述信息,用于分析岗位匹配度
func getJobDescription() string {
return `
公司名称: 飞牛
岗位名称: C/C++开发工程师
岗位职责:
负责NAS项目的核心功能开发, 包括数据恢复, 网络配置, 虚拟机管理, docker管理等功能模块的开发。
任职要求:
C++/PostgreSQL/STL/Linux开发/部署经验
1)扎实的C++编程技术, 有C++开发经验, 熟悉常用的数据结构、算法;
2) 熟悉Linux操作系统及Linux多线程、进程通信等内容的编程;熟悉计算机网络编程(TCP/IP、UDP等网络通信协议);
3) 了解Linux文件系统, 有存储类应用开发优先考虑;
4) 有Linux下软RAID使用经验, 了解常用磁盘整列原理(RAID-0, RAID-1, RAID-5等)优先考虑。
公司介绍:
铁刃智造是一家专注于研发安全易用、免费的NAS系统的企业。
我们始终以用户需求为导向,致力于为家庭、工作室等中小企业提供高效、安全、可靠的数据存储和处理解决方案。我们的团队由原UC、PP助手核心成员组成,秉持长期主义的态度,打造出极致体验且安全可靠的NAS系统,为全球市场带来可称之为“国产之光”的产品。
目前主营业务主要有包括:NAS、私有存储周边硬件、私有数据资产+AI解决方案。
`
}
func getUserQuery(userPdf string, jobDesc string) string {
return fmt.Sprintf(`
【重要】请立即解析以下简历文件并提取关键信息:
简历文件路径:%s
岗位描述(JD): %s
【必须执行的步骤】:
1. 【第一步】立即使用工具解析简历文件,获取完整的简历文本内容
2. 【第二步】从解析的简历文本中提取所有关键信息
3. 【第三步】根据提取的关键信息, 结合 岗位描述(JD) 生成符合要求的 JSON 格式输出
【重要提示】:
- 不要跳过 pdf_to_text 工具调用
- 必须从简历内容中提取真实的信息,不要返回空数据
- 所有JSON字段都必须填充实际内容
- 只返回JSON格式,不要返回其他文本
请返回完整的 JSON 格式结果。
`, userPdf, jobDesc)
}
func main() {
// PDF文件的绝对路径
userPdf := "/Users/lucas/work/code/go/example-eino-pdf-agent/examples/test.pdf"
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
defer cancel()
agent := resume.NewResumeAgent()
runner := adk.NewRunner(ctx, adk.RunnerConfig{
Agent: agent,
// 用于向 Agent 建议其输出模式,但它并非一个强制性约束。
// 它的核心思想是控制那些同时支持流式和非流式输出的组件的行为,例如 ChatModel
// 当 EnableStreaming=false 时,对于那些既能流式也能非流式输出的组件,此时会使用一次性返回完整结果的非流式模式。
EnableStreaming: false,
})
// 执行对话
input := []adk.Message{
schema.UserMessage(getUserQuery(userPdf, getJobDescription())),
}
events := runner.Run(ctx, input)
output := ""
for {
event, ok := events.Next()
if !ok {
break
}
if event.Err != nil {
log.Printf("event错误: %v", event.Err)
break
}
if msg, err := event.Output.MessageOutput.GetMessage(); err == nil {
output = msg.Content
}
}
log.Println(output)
}
运行效果:

4.总结
通过上述步骤,我们利用 Eino 框架成功构建了一个具备“感知能力”(通过 Tool 读取文件)和“认知能力”(通过 LLM 分析匹配度)的智能体。
这个示例展示了 Eino ADK 的核心优势:
- 标准化封装:通过
adk.Agent接口统一了智能体的定义。 - 工具编排简便:
InferTool让普通的 Go 函数能快速转化为 LLM 可调用的工具。 - 开发体验流畅:从模型配置到业务运行,链路清晰,扩展性强。
完整代码可以在 GitHub 查看