Featured image of post AI面试官[续] —— 改用Agent的方式实现

AI面试官[续] —— 改用Agent的方式实现

写在前面

上次写dify上的 AI 面试官案例后,没几天就看到了BOSS 上的一条回复——

哈哈,是不是有种一眼看穿的感觉——是的,这里HR调用了AI,结合岗位和面试人员背景提出了三个不同维度( 分别是:产品mvp的规划和定义,推进中的协调和调整,上线后的反思和改进)的问题。

今天,将在n8n上实现相同AI智能面试官的任务。

不同于dify 工作流中需要特别关注对话内容递进和信息流转,本文会更为关注Agent框架下实现相同需求的差异和优缺点

完成这个任务,后续可以作为一个MCP tool 再被其他Agent调用。例如,面向整个招聘环节的 “HR AI招聘系统”。

通过本文,可以收获以下内容

  1. 知识库文档切分、向量化和入库的细节
  2. 多轮对话中,使用思维链(CoT)提示词约束LLM行为的同时,也发挥Agent更为优秀的输出能力
  3. RAG召回后,如何与LLM协同完成问题的回答
  4. 多轮对话中,Agent与Tools、LLM 之间的调用关系
  5. 同一个应用,不同实现路径(Workflow vs Agent )间的差异

效果展示

老样子,先上效果图。

  • 本地知识库内容向量化入库

  • 通过Agent 完成多轮面试问答

  • 面试问题总结和评估

实现步骤

1. 上传文档,并使用RAG技术分片存储

还是使用之前预处理过的两个面试问答文档:

不过,需要把它们上传到n8n docker 应用中去,这里我是放在 /home/node/upload_files/ 文件夹下:

向量数据库就使用之前dify中安装好的Weaviate。

因为在Weaviate数据库建立的时候没有设置API Key,这里在n8n中只需关联Weaviate链接地址(http://difiy.host:8080)就可以查看内部数据表了:

如果本地没有向量数据库,也不希望把文档吐给云上的向量数据库,可以使用n8n提供的本地“简单向量存储”工具。该工具是把向量和分片数据储存到内存当中的,就是重启后那些处理过的数据会全部。只建议在测试环境中使用这个“简单向量存储”。

最终,将以上节点组合起来,就是第一个工作流——知识库文档向量化并存储。

手动点击”执行“,看一眼输出结果。

在n8n的logs 中可以清楚地看到向量化的过程

读取本地目录中的文件 –> 进入循环 –> Weaviate 向量库工具,先是根据文档中的切分标记“BBBBBB” 对文档分片 –> 然后使用embedding 模型计算每个分片的向量值 (这里设定每次处理20个分片) –> 第一个文档切分为了101片,所以经过6次的重复计算 –> 将每个分片的信息(含原始文本内容和元数据)与embedding 计算后的向量值关联在一起储存 –> 这样就完成了第一个文档的切分和向量化存储 –> 同样地过程处理第二个文档 –> 直至所有文档处理完成,循环结束

例如,其中一个分片按照以下格式存储在向量数据库中——

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

ID: 0001f149-5a73-4666-975c-d9e1ae6bab59

values: [0.00667897193, -0.00112631731, 0.402422339, -0.0144329099, 0.0131256515, 。。。。-0.00670505082, 0.00998431537, 0.00638512149, 0.00762740057, -0.00297592231]

metadata:

blobType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"

loc.lines.from: 349

loc.lines.to: 357

source: "blob"

text: "4. 你老家哪里的?\n\n技巧:这个问题主要想了解你的稳定性,会不会做一段时间就辞职。\n\n模板:\n\n我的老家是\t ,是一个\t 的地方。但是因为\t,我更想要在这个城市长期发展,而且我的家人都很支持我的选择。\n\n回答示例:我老家是西双版纳的,是一个旅游城市,以后有机会去的话,可以给你推荐一些很值得打卡的地方。但是比起在老家发展,我更想留在这个城市,因为对我所读的专业来说,这里能给我提供更多的就业机会和发展,所以找工作选的这里的公司。而且家里人都很支持我的选择,我打算长期留在这边发展。"

其中,有原始的文本,有文档切片元数据(指明了对应原始文档的第349行到357行),还有一长串向量。

而这个向量,就是后续向量库检索时的条件。

另外,有两点再补充一下:

  • 不同于Transformers 模型,encoder时是按照每个Token 完成向量化计算(得到512x1 的矩阵)。

    RAG中embedding 向量化的对象是全部的输入

    例如,下面查询句子"hello Qwen3"的向量结果——

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    # 向OLLAMA API接口发起这段话”hello Qwen3“的向量查询,使用的模型是qwen3-embedding:8b 。
    curl http://ollama.host:11434/api/embed -d '{"model":"qwen3-embedding:8b","input": "hello Qwen3"}'
    
    # 返回的内容格式如下,其中 embeddings对应的就是一个 4096 长度的列表。
    {
    "model":"qwen3-embedding:8b",
    "embeddings":
        [[0.005400424,0.004791923,-0.009427172,-0.01674511,-0.0012024766,
        ...... 省略中间部分
        -0.00039164326,-0.015191954,-0.011491347,0.00041572566]
        ],
    "total_duration":7977521700,
    "load_duration":100531700,
    "prompt_eval_count":4
    }
    

    所以,拆分后的100个问答对,就会产生100个向量。而向量长度取决于模型。

  • qwen3-embedding:8b 模型,把内容向量化后,默认是得到一个4096x1 的一维向量。上图最右侧展示了 4081 ~ 4095 这几个位置的向量值。

    不同的embedding 模型其向量化后的长度不一定一样。

    使用的时候需要注意筛选:越长的向量化输出,带来更精细的的向量数据和更大的资源开销。

    这边根据网上资料,整理了常见的向量化模型——

    模型 (Model) 开发者/机构 (Developer/Organization) 最大/默认向量长度 (Max/Default Dimension) 备注 (Notes)
    支持可变维度的先进模型
    Qwen3-Embedding-8B 阿里巴巴 (Alibaba) 4096 支持MRL,可截断为 32 到 4096 之间的任意维度。上下文长度 32k。
    text-embedding-3-large OpenAI 3072 支持MRL,API中可指定 dimensions 参数,例如 256, 512, 1024, 1536。
    text-embedding-3-small OpenAI 1536 支持MRL,API中可指定 dimensions 参数,例如 256, 512。
    主流闭源模型
    text-embedding-ada-002 OpenAI 1536 不支持可变维度。曾是业界最广泛使用的API模型之一。
    text-embedding-gecko Google (Vertex AI) 768 Google PaLM 2 系列的 embedding 模型。
    主流开源模型
    bge-large-en-v1.5 BAAI (智源研究院) 1024 MTEB 排行榜上的常客,性能强大。
    e5-large-v2 Microsoft 1024 另一个经典的、性能优异的开源模型。

2. Agent 实现多轮问答和记忆

Agent 这部分内容,我们之前在AI 新闻采集与推送助手的案例中有经验:

主要由 LLM + memory + tools 几部分组成。

如今在同样的多轮对话场景下,Agent又是如何实现这些过程呢?

  • 与workflow 方式不同的是:Agent中分析、判断、推理能力成为出厂默认配置,不再需要人工提取和干预,而是交给AI 自主决策——俗称AI max 。

    具体操作层面:

    通过带有思维链的提示词,引导AI完成上述对话环节分析和打分评估。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
角色
你是一个有十年面试经验的专业HR。现在是我的专属"面试顾问",你的任务是模拟面试官进行面试提问和打分,然后输出专属改进建议。
最终你需要将所有分析和建议,整合为一个可用于展示的Markdown格式文本块。保持专业、乐观、敏锐!

有一个核心工具供你调遣:
quary vector info: 检索面试问答知识库中,与提问问题相关的回答案例。

你的行动指令:
第一论对话:发起初始对话
模型HR面试官语气,询问面试人员年龄 工作年限 过往项目经验 面试岗位等相关信息。
if (用户拒绝回答个人基本信息的内容)
  {以HR的语气(略显严厉),引导用户回答基本信息}
elif (用户回答了与询问的问题无关的内容)
  {忽略与询问问题无关的回答,只关注用户针对问题已做出回答的内容}
elif (用户回答过程中遗漏了某个问题)
  {以HR的语气(友善 积极),引导用户继续回答剩下遗漏掉的问题}
else (用户针对面试官提出的问题 都做出了回答)
  {将用户回答存入记忆中,并开启第二轮对话}

第二轮对话:完成问题提问和用户回答内容采集
根据上一轮用户提供的面试信息,从专业HR 角度提出三个面试问题{{ ques_list: [问题1,问题2,问题3]},并引导用户做出回答。
用户可以一次一个回答相关问题,也可以一次性回答这三个问题。
if (用户没有回答任何一个问题)
  {以HR的语气(略显严厉),引导用户回答相关问题}
elif (用户回答了与面试问题无关的内容)
  {忽略与面试问题无关的回答,只关注用户针对问题已做出回答的内容}
elif (用户回答过程中遗漏了某个问题)
  {以HR的语气(友善 积极),引导用户继续回答其余问题}
else (用户针对面试官提出的问题 都做出了回答)
  {将用户回答存入回答列表 {{ userans_list: [问题1回答, 问题2回答, 问题3回答]}},并开启第三轮对话}


## 示例
### 示例 1
- 面试者背景:毕业于计算机科学专业,并在学校期间参与了两个项目。
- 申请岗位:初级软件开发工程师。
- 生成的问题:
  - 在学校项目中,你所遇到的最大的技术挑战是什么?你是如何解决的?
  - 在团队项目中,当团队成员之间的意见存在分歧时,你是如何处理和协调的?
  - 你最近学习了哪些新的技术?你是如何将其应用到实际项目中的?

### 示例 2
- 面试者背景:有 5 年的市场营销经验,并且领导过 3 个成功的营销活动。
- 申请岗位:市场经理。
- 生成的问题:
  - 你能分享一下你所领导的最成功的营销活动的案例以及关键策略吗?
  - 你是如何衡量营销活动的 ROI 的?请举一个具体的例子。
  - 当面临预算削减时,你会如何调整你的营销计划?

### 示例 3
- 面试者背景:原为教师,现准备从教师转行到人力资源岗位,虽无正式的 HR 经验,但曾组织过学校的招聘活动。
- 申请岗位:HR 专员。
- 生成的问题:
  - 你为什么决定从教师转行到人力资源?
  - 在组织学校招聘活动中,你的最大收获是什么?
  - 你是如何处理员工之间的冲突的?能否举一个你在教师工作中的具体例子?


第三轮对话:根据用户回答的内容,对用户回答的每个问题的进行综合评分。
 - 第一步:提取问题知识库中标准回答建议。
使用工具"quary vector info"查询面试知识库中{{ ques_list[id] }}的标准答复,并根据该答复内容整理为标准回答思路和要点 {{ standans_list[id] }}。

 - 第二步:用户回答评分
先比较用户回答{{ userans_list[id] }}和知识库内容{{ standans_list[id] }}中的回答思路和要点。

然后从以下几个方面综合评估用户的回答:
1. 用户回答的逻辑性、完整性和相关性
   - 考察表达能力、专业知识和应变能力
   - 注意候选人的非语言表现(如语气、节奏等)

2. STAR原则应用
   - Situation: 是否清晰描述背景情境
   - Task: 是否明确说明任务目标
   - Action: 具体行动是否专业有效
   - Result: 结果是否可量化且有说服力

3. 用户回答的思路与知识库中回答内容的差距
   - 评估用户回答思路与知识库回答思路是否一致
   - 对其中相同部分予以肯定,不同部分后续步骤中会对此提出改进建议


 - 第三步:依据上一步骤中三个方面的情况,完成对用户回答的打分。
   - 采用5分制评分(1-5分)
   - 1分: 完全不符合要求
   - 3分: 基本达到要求
   - 5分: 表现卓越超出预期


 - 第四步:以markdown 格式输出面试评分结果和相关建议。
   - 需提供具体改进建议
   - 保持专业性且建设性
   - 突出候选人的优势与不足
请严格按照以下指导来组织信息,但不要在你的最终输出中包含模板本身的 ```markdown 包裹标记或任何非Markdown的解释性文字。
Markdown内容结构指导(请填充实际内容):

### 🚀 面试 **专属建议**

**🌟 面试问题 评分:**
*   ---
*   **面试问题:** {{ ques_list[id] }}
*   **回答思路:** {{ 第二步中根据{{ standans_list[id] }}整理的回答思路和要点 }}
*   **分数:** {{ 第三步中此问题评分 }}
*   ---

**💡 优化建议:**
[基于该面试问题,给出一个综合性的建议。
例如:
- 如果回答思路与知识库中相符,重点检查回答细节:可以建议加强问题场景的故事性,或者建议增加相关定量描述。例如:“建议增加 问题场景的细节描述,这样面试官在了解问题后,会代入自己公司的产品进行替代问答。有利于引导面试官在自己描述的场景内提问,并满足他需要的信息和能力证明”。

- 如果回答思路与知识库不相符:可以建议先明确面试官是想问什么,他问这个问题是需要考量面试着的哪些素质。作为面试者如何通过1-2个案例体现出这些能力。例如:“建议按照[回答思路] 答复面试官的提问,具体答复案例可以是这样:[按照回答思路举一个案例]”。
]

通用要求:
你的建议要具体、有建设性、信息充分,并体现出是对面试人员的综合考量。
语气要积极、专业,充满洞察力。

是不是有点意外:之前挺复杂的工作流,在Agent 这里一个节点就搞定了。

效果就是开头效果展示里面的样子 。

  • 这里稍微提一句 “记忆”模块,它的作用就是在Agent工作前输入历史对话信息,Agent输出后追加本次对话内容(Human 和 AI对话内容)。所以,随着对话内容变多“记忆”的内容也是越来越多的。

也正是因为有“记忆”这个模块,每一次对话时 Agent 才知道当前对话主题是什么,进入到什么阶段了。

下图可以看到**“记忆”模块在Agent中的位置**——一开始和最后。

3. Agent 调用知识库内容完成任务的详细步骤

本章节着重看观察 Agent是如何协调大模型和RAG 知识库完成面试问题回答和评价的:

有了这些细节,再回过头看之前的 RAG 检索示意图,是不是清楚多了?

其中2.1 ~ 2.3 对应 RAG的检索,3.1 ~ 3.2 对应RAG的提示增强, 4.1 对应生成回答

4. Agent 、LLM与Tools 间的关系

就像提示词中写得:Agent 调用 tools 工具(“quary vector info”)只在会话进行到第三轮时,才会去RAG检索。并不是每次都会调用的。

后续,如果有其他工具,例如 “背调工具"就也可以加进去。这样扩充能力后,Agent可以自主判断、执行的操作就越来越多。 今年除了 claudecode cursor Trace这些常见的编程工具外,Manus 应用也火爆起来,背后就是大家都希望AI 可以做得更多、更好,而人类只需要检查AI 的输出就好的美好愿景。

可以说 Agent 的魅力也在 这种“可以丰富扩展”的想象。

具体落实到产品层面的话,还有不少的难题需要处理,后续会陆陆续续展开一些。

对比这两种路径

同样一个需求 是使用Workflow 还是 Agent 背后关乎成本、需求、实现平台等等因素。

角度 Workflow Agent
不同点
处理方式&难度 围绕SoP,设计每一步的输入、输出内容和格式 使用提示词控制LLM关注方向,实现复杂场景下的内容输出
LLM 模型能力要求 一般
输出内容 可以稳定执行,但是语言上不够自然 更接近于自然沟通、交流
范围外输出 不会发生 会发生,可能存在一定泄漏数据或提示词的风险
Token 消耗
相同点
知识内容更新 更新知识库就行,无需调整工作流 同样,只更新知识库就行,无需修改Agent 提示词
知识库使用方式 作为上下文的一部分 同样,也是作为上下文的一部分

再补充两个细节:

  • 在本案例中,我一开始打算全部使用本地显卡上运行的 deepseek-r1:8b或 qwen3:8b 两个模型实现。

    结果发现:在RAG检索和提示增强时,用本地的8b模型还可以,但是Agent 中也使用这两个8b 模型都无法正确理解每轮对话的关注点,导致对话无法正常执行下去。

    而与之对比的是 dify 中,全部使用的是本地 qewn3:8b 模型。

  • 在token 消耗这块,Agent 也是比workflow 多的。主要多在两个方面:

    1. Agent 每轮对话的内容都保留在 一个记忆对象中。随着对话内容多了,这里面的内容是越来越长的。而且容易有无关的内容也混在其中,无关内容多了后也会干扰LLM模型的注意力。

    而workflow 中有分流节点(类似 if/else)的存在,即需要在某个执行节点查看只与本节点相关的记忆即可。

    1. 因为Agent对模型能力要求更高了,所需的算力也更多,自然花费也大一些。

    在下图中也体现了这一点:

小结

Workflow 的路线像是自己做菜——洗、切、炒之外,菜板、刀、调味料这些基础条件也需要自己操行。

Agent 的路线更像是 中央厨房做好了预制菜——买回来,自己只需要加点喜欢的菜、上锅热一热就齐活了!

RAG 本质上是一种工程解决方案:目的是在有限的上下文窗口内,提供预先处理过得高质量数据

从而提高LLM的注意力和回复质量,同时规避“幻觉”和频繁调用外部工具的麻烦。

感谢你看到这里,听我分享这些。我们下期见。


Licensed under CC BY-NC-SA 4.0