评审完成后,需要根据结果选择下一步:通过则安排面试,未通过则发送拒绝通知。本讲用两个分支演示条件工作流;邮件、日程和状态更新工具均沿用原文的 mock 实现。
一、定义不同分支的Agent
1.1 EmailAssistant (发邮件拒绝候选人Agent)
public interface EmailAssistant {
@Agent("向未通过筛选的候选人发送拒绝邮件,返回已发送邮件的ID,若无法发送则返回0")
@SystemMessage("""
您需要向未通过第一轮评审的求职候选人发送一封友好的邮件。
同时,您需要将申请状态更新为“已拒绝”。
您应返回已发送邮件的ID。
""")
@UserMessage("""
被拒候选人:{{candidateContact}}
申请职位:{{jobDescription}}
""")
int send(@V("candidateContact") String candidateContact, @V("jobDescription") String jobDescription);
}
邮件分支需要候选人的联系信息,示例用 candidate_contact.txt 作为输入:
候选人联系卡片:
姓名:John Doe
地址:Rue des Carmes 12, 2000 Antwerp, Belgium(比利时安特卫普)
邮箱:john.doe.dev@protonmail.com
电话:+32 495 67 89 23
1.2 InterviewOrganizer (组织面试Agent)
public interface InterviewOrganizer {
@Agent("为申请人安排现场面试")
@SystemMessage("""
您通过向所有相关员工发送日历邀请来安排现场会议,
时间定在从当前日期起一周后的上午,时长为3小时。
这是相关的职位空缺:{{jobDescription}}
您还需通过祝贺邮件邀请候选人,告知面试详情,
以及他/她来现场前需要注意的事项。
最后,您需要将申请状态更新为“已邀请现场面试”。
""")
@UserMessage("""
为此候选人安排现场面试会议(需遵守外部访客政策):{{candidateContact}}
""")
String organize(@V("candidateContact") String candidateContact, @V("jobDescription") String jobDescription);
}
二、(可选)定义工具
两个分支都需要业务工具配合:
拒绝流程:发送拒绝邮件,更新简历申请状态。
安排面试流程:通过工具获取当前日期、查询面试官等员工信息、创建面试日程、发送通知,最后更新申请状态。
下面定义相关工具。原文使用 mock 实现演示调用过程,不会据此确认真实邮件发送、日程创建或状态持久化。
2.1 OrganizingTools
public class OrganizingTools {
@Tool
public Date getCurrentDate(){
return new Date();
}
@Tool("根据给定的职位描述ID,查找需要参加现场面试的人员的电子邮件地址和姓名")
public List<String> getInvolvedEmployeesForInterview(@P("职位描述ID") String jobDescriptionId){
// 演示用虚拟实现
return new ArrayList<>(List.of(
"Anna Bolena: hiring.manager@company.com",
"Chris Durue: near.colleague@company.com",
"Esther Finnigan: vp@company.com"));
}
@Tool("根据电子邮件地址为员工创建日程条目")
public void createCalendarEntry(@P("员工电子邮件地址列表") List<String> emailAddress, @P("会议主题") String topic, @P("开始日期和时间,格式为yyyy-mm-dd hh:mm") String start, @P("结束日期和时间,格式为yyyy-mm-dd hh:mm") String end){
// 演示用虚拟实现
System.out.println("*** 已创建日程条目 ***");
System.out.println("主题:" + topic);
System.out.println("开始时间:" + start);
System.out.println("结束时间:" + end);
}
@Tool
public int sendEmail(@P("收件人电子邮件地址列表") List<String> to, @P("抄送电子邮件地址列表") List<String> cc, @P("邮件主题") String subject, @P("正文") String body){
// 演示用虚拟实现
System.out.println("*** 已发送邮件 ***");
System.out.println("收件人:" + to);
System.out.println("抄送:" + cc);
System.out.println("主题:" + subject);
System.out.println("正文:" + body);
return 1234; // 虚拟邮件ID
}
@Tool
public void updateApplicationStatus(@P("职位描述ID") String jobDescriptionId, @P("候选人(名,姓)") String candidateName, @P("新的申请状态") String newStatus){
// 演示用虚拟实现
System.out.println("*** 已更新申请状态 ***");
System.out.println("职位描述ID:" + jobDescriptionId);
System.out.println("候选人姓名:" + candidateName);
System.out.println("新状态:" + newStatus);
}
}
2.2 RAG 工具
通常每个公司都有一些自己的访客制度,比如:车应该停在哪,衣冠必须端正,来面试时必须前台登记等等。这些LLM显然不知道,需要借助RAG工具
@Component("ragProvider")
public class RagProvider {
@Autowired
@Qualifier("ollamaEmbeddingModel")
OllamaEmbeddingModel ollamaEmbeddingModel;
public ContentRetriever loadHouseRulesRetriever() {
Document doc = loadDocument(toPath("documents/house_rules.txt"));
InMemoryEmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore<>();
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.documentSplitter(DocumentSplitters.recursive(200, 10))
.embeddingModel(ollamaEmbeddingModel)
.embeddingStore(store)
.build();
ingestor.ingest(List.of(doc));
return EmbeddingStoreContentRetriever.builder()
.embeddingStore(store)
.embeddingModel(ollamaEmbeddingModel)
.maxResults(2)
.minScore(0.8)
.build();
}
public static Path toPath(String relativePath) {
try {
URL fileUrl = Utils.class.getClassLoader().getResource(relativePath);
return Paths.get(fileUrl.toURI());
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
house_rules.txt内容如下:
1.仅允许在停车场后方指定户外区域吸烟。
2.办公区安静时段为下午2点至4点,请避免在开放空间大声交谈。
3.员工须至少提前24小时通过内部日历预订会议室。
4.自行车必须停放在主楼外侧的专用自行车停放区。
5.所有外部访客须凭有效身份证件在前台登记方可进入。
6.外部访客均须佩戴临时访客证,并确保证件清晰可见。
三、条件工作流示例
/**
* 此示例演示了条件式智能体工作流。
* 基于评分和候选人资料,我们将执行以下操作之一:
* - 调用一个智能体,为该候选人的现场面试做好一切准备
* - 调用一个智能体,发送一封友好的邮件,告知我们不会推进该候选人的申请
* by 菩提树下的杨过(yjmyzz.cnblogs.com)
*/
@SpringBootApplication
public class _5a_Conditional_Workflow_Example {
public static void main(String[] args) throws IOException {
ConfigurableApplicationContext context = SpringApplication.run(AgentDesignPatternApplication.class, args);
ChatModel model = context.getBean("ollamaChatModel", ChatModel.class);
RagProvider ragProvider = context.getBean("ragProvider", RagProvider.class);
// 2. 在此包中定义两个子智能体:
// - EmailAssistant.java
// - InterviewOrganizer.java
// 3. 使用AgenticServices创建所有智能体
EmailAssistant emailAssistant = AgenticServices.agentBuilder(EmailAssistant.class)
.chatModel(model)
.tools(new OrganizingTools()) // 该智能体可以使用那里定义的所有工具
.build();
InterviewOrganizer interviewOrganizer = AgenticServices.agentBuilder(InterviewOrganizer.class)
.chatModel(model)
.tools(new OrganizingTools())
.contentRetriever(ragProvider.loadHouseRulesRetriever()) // 这是如何为智能体添加RAG的方式
.build();
// 4. 构建条件式工作流
UntypedAgent candidateResponder = AgenticServices // 使用UntypedAgent,除非您定义了合成的智能体,请参见_2_Sequential_Agent_Example
.conditionalBuilder()
.subAgents(agenticScope -> ((CvReview) agenticScope.readState("cvReview")).score >= 0.8, interviewOrganizer)
.subAgents(agenticScope -> ((CvReview) agenticScope.readState("cvReview")).score < 0.8, emailAssistant)
.build();
// 重要提示:当定义了多个条件时,它们会按顺序执行。
// 如果您想在这里并行执行,请使用异步智能体,如_5b_Conditional_Workflow_Example_Async中所示
// 5. 从resources/documents/中的文本文件加载参数
String candidateCv = StringLoader.loadFromResource("/documents/tailored_cv.txt");
String candidateContact = StringLoader.loadFromResource("/documents/candidate_contact.txt");
String jobDescription = StringLoader.loadFromResource("/documents/job_description_backend.txt");
CvReview cvReviewFail = new CvReview(0.6, "简历不错,但缺少一些与后端职位相关的技术细节。");
CvReview cvReviewPass = new CvReview(0.9, "简历非常出色,符合后端职位的所有要求。");
// 5. 因为我们使用了无类型智能体,所以需要传递所有输入参数的映射
Map<String, Object> arguments = Map.of(
"candidateCv", candidateCv,
"candidateContact", candidateContact,
"jobDescription", jobDescription,
"cvReview", cvReviewPass // 更改为cvReviewFail以查看另一个分支
);
// 5. 调用条件式智能体,根据评审结果对候选人做出回应
candidateResponder.invoke(arguments);
}
}
代码第 52 行传入评分为 0.9 的 cvReview,按示例条件进入安排面试的分支。换成 cvReviewFail 可观察拒绝分支;本站没有重新运行这两个分支。
时序图(简化版,原文标注为 AI 生成)

原文配图:条件工作流:面试与拒绝分支:简化时序图。
时序图(详细版,原文标注为 AI 生成)

原文配图:条件工作流:面试与拒绝分支:详细时序图。
运行结果:
展开原文日志或请求/响应(未复测)
2026-01-17T16:53:37.524+08:00 INFO 15192 --- [langchain4j-study] [ main] d.l.http.client.log.LoggingHttpClient : HTTP request:
- method: POST
- url: http://localhost:11434/api/embed
- headers: [Content-Type: application/json]
- body: {
"model" : "nomic-embed-text:latest",
"input" : [ "1.仅允许在停车场后方指定户外区域吸烟。\r\n2.办公区安静时段为下午2点至4点,请避免在开放空间大声交谈。\r\n3.员工须至少提前24小时通过内部日历预订会议室。\r\n4.自行车必须停放在主楼外侧的专用自行车停放区。\r\n5.所有外部访客须凭有效身份证件在前台登记方可进入。\r\n6.外部访客均须佩戴临时访客证,并确保证件清晰可见。" ]
}
2026-01-17T16:53:42.826+08:00 INFO 15192 --- [langchain4j-study] [ main] d.l.http.client.log.LoggingHttpClient : HTTP response:
- status code: 200
- headers: [content-type: application/json; charset=utf-8], [date: Sat, 17 Jan 2026 08:53:42 GMT], [transfer-encoding: chunked]
- body: {"model":"nomic-embed-text:latest","embeddings":[[0.028599557,0.057497256,...-0.054139506]],"total_duration":5262584300,"load_duration":5247416900,"prompt_eval_count":161}
2026-01-17T16:53:42.871+08:00 INFO 15192 --- [langchain4j-study] [ main] d.l.http.client.log.LoggingHttpClient : HTTP request:
- method: POST
- url: http://localhost:11434/api/embed
- headers: [Content-Type: application/json]
- body: {
"model" : "nomic-embed-text:latest",
"input" : [ "为此候选人安排现场面试会议(需遵守外部访客政策):候选人联系卡片:\r\n姓名:John Doe\r\n地址:Rue des Carmes 12, 2000 Antwerp, Belgium(比利时安特卫普)\r\n邮箱:john.doe.dev@protonmail.com\r\n电话:+32 495 67 89 23\n" ]
}
2026-01-17T16:53:42.895+08:00 INFO 15192 --- [langchain4j-study] [ main] d.l.http.client.log.LoggingHttpClient : HTTP response:
- status code: 200
- headers: [content-type: application/json; charset=utf-8], [date: Sat, 17 Jan 2026 08:53:42 GMT], [transfer-encoding: chunked]
- body: {"model":"nomic-embed-text:latest","embeddings":[[0.018723998,0.067336,...-0.042845238]],"total_duration":22005300,"load_duration":12774100,"prompt_eval_count":88}
2026-01-17T16:53:42.939+08:00 INFO 15192 --- [langchain4j-study] [ main] d.l.http.client.log.LoggingHttpClient : HTTP request:
- method: POST
- url: http://localhost:11434/api/chat
- headers: [Content-Type: application/json]
- body: {
"model" : "deepseek-v3.1:671b-cloud",
"messages" : [ {
"role" : "system",
"content" : "您通过向所有相关员工发送日历邀请来安排现场会议,\n时间定在从当前日期起一周后的上午,时长为3小时。\n这是相关的职位空缺:职位描述 ID: 123A\r\n后端工程师(金融科技,安特卫普)\r\n------------------------------------------------------------\r\n我们正在招聘一位**后端工程师**,协助我们构建并完善支付与对账服务。您将主要使用 **Java (Spring Boot)**,并与产品及运营团队紧密合作,确保金融交易系统的可靠性和可扩展性。\r\n\r\n**职责:**\r\n* 设计、实现并维护能够处理大规模支付与对账业务的后端服务。\r\n* 在云原生环境中使用 **PostgreSQL**、**Kafka**、**Docker** 和 **Kubernetes**。\r\n* 专注于构建可靠、幂等的系统,并具备强大的可观测性。\r\n* 支持将遗留服务迁移至现代的 REST API。\r\n\r\n**要求:**\r\n* 具备 Java 和 Spring Boot 后端开发的职业经验。\r\n* 深入理解关系型数据库和事件驱动架构。\r\n* 熟悉容器化技术和部署流程。\r\n* 有金融或定价系统相关经验者优先。\r\n* 要求英语流利,会荷兰语者更佳。\n您还需通过祝贺邮件邀请候选人,告知面试详情,\n以及他/她来现场前需要注意的事项。\n最后,您需要将申请状态更新为“已邀请现场面试”。\n"
}, {
"role" : "user",
"content" : "为此候选人安排现场面试会议(需遵守外部访客政策):候选人联系卡片:\r\n姓名:John Doe\r\n地址:Rue des Carmes 12, 2000 Antwerp, Belgium(比利时安特卫普)\r\n邮箱:john.doe.dev@protonmail.com\r\n电话:+32 495 67 89 23\n\n\nAnswer using the following information:\n1.仅允许在停车场后方指定户外区域吸烟。\r\n2.办公区安静时段为下午2点至4点,请避免在开放空间大声交谈。\r\n3.员工须至少提前24小时通过内部日历预订会议室。\r\n4.自行车必须停放在主楼外侧的专用自行车停放区。\r\n5.所有外部访客须凭有效身份证件在前台登记方可进入。\r\n6.外部访客均须佩戴临时访客证,并确保证件清晰可见。"
} ],
"options" : {
"stop" : [ ]
},
"stream" : false,
"tools" : [ {
"type" : "function",
"function" : {
"name" : "getInvolvedEmployeesForInterview",
"description" : "根据给定的职位描述ID,查找需要参加现场面试的人员的电子邮件地址和姓名",
"parameters" : {
...
}
}
}, {
"type" : "function",
"function" : {
"name" : "createCalendarEntry",
"description" : "根据电子邮件地址为员工创建日程条目",
"parameters" : {
...
}
}
}, {
"type" : "function",
"function" : {
"name" : "getCurrentDate"
}
}, {
"type" : "function",
"function" : {
"name" : "updateApplicationStatus",
"parameters" : {
...
}
}
}, {
"type" : "function",
"function" : {
"name" : "sendEmail",
"parameters" : {
...
}
}
} ]
}
2026-01-17T16:53:45.099+08:00 INFO 15192 --- [langchain4j-study] [ main] d.l.http.client.log.LoggingHttpClient : HTTP response:
- status code: 200
- headers: [content-type: application/json; charset=utf-8], [date: Sat, 17 Jan 2026 08:53:45 GMT], [transfer-encoding: chunked]
- body: {"model":"deepseek-v3.1:671b-cloud","remote_model":"deepseek-v3.1:671b","remote_host":"https://ollama.com:443","created_at":"2026-01-17T08:53:44.795645806Z","message":{"role":"assistant","content":"我将为您安排John Doe的现场面试。首先让我获取当前日期,然后找到需要参加面试的相关员工,并安排会议。","tool_calls":[{"id":"call_che9srkc","function":{"index":0,"name":"getCurrentDate","arguments":{}}}]},"done":true,"done_reason":"stop","total_duration":766029891,"prompt_eval_count":991,"eval_count":37}
2026-01-17T16:53:45.113+08:00 INFO 15192 --- [langchain4j-study] [ main] d.l.http.client.log.LoggingHttpClient : HTTP request:
- method: POST
- url: http://localhost:11434/api/chat
- headers: [Content-Type: application/json]
- body: {
"model" : "deepseek-v3.1:671b-cloud",
"messages" : [ {
"role" : "system",
"content" : "您通过向所有相关员工发送日历邀请来安排现场会议,\n时间定在从当前日期起一周后的上午,时长为3小时。\n这是相关的职位空缺:职位描述 ID: 123A\r\n后端工程师(金融科技,安特卫普)\r\n------------------------------------------------------------\r\n我们正在招聘一位**后端工程师**,协助我们构建并完善支付与对账服务。您将主要使用 **Java (Spring Boot)**,并与产品及运营团队紧密合作,确保金融交易系统的可靠性和可扩展性。\r\n\r\n**职责:**\r\n* 设计、实现并维护能够处理大规模支付与对账业务的后端服务。\r\n* 在云原生环境中使用 **PostgreSQL**、**Kafka**、**Docker** 和 **Kubernetes**。\r\n* 专注于构建可靠、幂等的系统,并具备强大的可观测性。\r\n* 支持将遗留服务迁移至现代的 REST API。\r\n\r\n**要求:**\r\n* 具备 Java 和 Spring Boot 后端开发的职业经验。\r\n* 深入理解关系型数据库和事件驱动架构。\r\n* 熟悉容器化技术和部署流程。\r\n* 有金融或定价系统相关经验者优先。\r\n* 要求英语流利,会荷兰语者更佳。\n您还需通过祝贺邮件邀请候选人,告知面试详情,\n以及他/她来现场前需要注意的事项。\n最后,您需要将申请状态更新为“已邀请现场面试”。\n"
}, {
"role" : "user",
"content" : "为此候选人安排现场面试会议(需遵守外部访客政策):候选人联系卡片:\r\n姓名:John Doe\r\n地址:Rue des Carmes 12, 2000 Antwerp, Belgium(比利时安特卫普)\r\n邮箱:john.doe.dev@protonmail.com\r\n电话:+32 495 67 89 23\n\n\nAnswer using the following information:\n1.仅允许在停车场后方指定户外区域吸烟。\r\n2.办公区安静时段为下午2点至4点,请避免在开放空间大声交谈。\r\n3.员工须至少提前24小时通过内部日历预订会议室。\r\n4.自行车必须停放在主楼外侧的专用自行车停放区。\r\n5.所有外部访客须凭有效身份证件在前台登记方可进入。\r\n6.外部访客均须佩戴临时访客证,并确保证件清晰可见。"
}, {
"role" : "assistant",
"content" : "我将为您安排John Doe的现场面试。首先让我获取当前日期,然后找到需要参加面试的相关员工,并安排会议。",
"tool_calls" : [ {
"function" : {
"name" : "getCurrentDate",
"arguments" : { }
}
} ]
}, {
"role" : "tool",
"content" : "1768640025109"
} ],
"options" : {
"stop" : [ ]
},
"stream" : false,
"tools" : [ {
"type" : "function",
"function" : {
"name" : "getInvolvedEmployeesForInterview",
"description" : "根据给定的职位描述ID,查找需要参加现场面试的人员的电子邮件地址和姓名",
"parameters" : {
...
}
}
}, {
"type" : "function",
"function" : {
"name" : "createCalendarEntry",
"description" : "根据电子邮件地址为员工创建日程条目",
"parameters" : {
...
}
}
}, {
"type" : "function",
"function" : {
"name" : "getCurrentDate"
}
}, {
"type" : "function",
"function" : {
"name" : "updateApplicationStatus",
"parameters" : {
...
}
}
}, {
"type" : "function",
"function" : {
"name" : "sendEmail",
"parameters" : {
...
},
"required" : [ "to", "cc", "subject", "body" ]
}
}
} ]
}
2026-01-17T16:53:46.607+08:00 INFO 15192 --- [langchain4j-study] [ main] d.l.http.client.log.LoggingHttpClient : HTTP response:
- status code: 200
- headers: [content-type: application/json; charset=utf-8], [date: Sat, 17 Jan 2026 08:53:46 GMT], [transfer-encoding: chunked]
- body: {"model":"deepseek-v3.1:671b-cloud","remote_model":"deepseek-v3.1:671b","remote_host":"https://ollama.com:443","created_at":"2026-01-17T08:53:45.888540222Z","message":{"role":"assistant","content":"","tool_calls":[{"id":"call_pb0rggf6","function":{"index":0,"name":"getInvolvedEmployeesForInterview","arguments":{"jobDescriptionId":"123A"}}}]},"done":true,"done_reason":"stop","total_duration":645456006,"prompt_eval_count":1035,"eval_count":21}
2026-01-17T16:53:46.609+08:00 INFO 15192 --- [langchain4j-study] [ main] d.l.http.client.log.LoggingHttpClient : HTTP request:
- method: POST
- url: http://localhost:11434/api/chat
- headers: [Content-Type: application/json]
- body: {
"model" : "deepseek-v3.1:671b-cloud",
"messages" : [ {
"role" : "system",
"content" : "您通过向所有相关员工发送日历邀请来安排现场会议,\n时间定在从当前日期起一周后的上午,时长为3小时。\n这是相关的职位空缺:职位描述 ID: 123A\r\n后端工程师(金融科技,安特卫普)\r\n------------------------------------------------------------\r\n我们正在招聘一位**后端工程师**,协助我们构建并完善支付与对账服务。您将主要使用 **Java (Spring Boot)**,并与产品及运营团队紧密合作,确保金融交易系统的可靠性和可扩展性。\r\n\r\n**职责:**\r\n* 设计、实现并维护能够处理大规模支付与对账业务的后端服务。\r\n* 在云原生环境中使用 **PostgreSQL**、**Kafka**、**Docker** 和 **Kubernetes**。\r\n* 专注于构建可靠、幂等的系统,并具备强大的可观测性。\r\n* 支持将遗留服务迁移至现代的 REST API。\r\n\r\n**要求:**\r\n* 具备 Java 和 Spring Boot 后端开发的职业经验。\r\n* 深入理解关系型数据库和事件驱动架构。\r\n* 熟悉容器化技术和部署流程。\r\n* 有金融或定价系统相关经验者优先。\r\n* 要求英语流利,会荷兰语者更佳。\n您还需通过祝贺邮件邀请候选人,告知面试详情,\n以及他/她来现场前需要注意的事项。\n最后,您需要将申请状态更新为“已邀请现场面试”。\n"
}, {
"role" : "user",
"content" : "为此候选人安排现场面试会议(需遵守外部访客政策):候选人联系卡片:\r\n姓名:John Doe\r\n地址:Rue des Carmes 12, 2000 Antwerp, Belgium(比利时安特卫普)\r\n邮箱:john.doe.dev@protonmail.com\r\n电话:+32 495 67 89 23\n\n\nAnswer using the following information:\n1.仅允许在停车场后方指定户外区域吸烟。\r\n2.办公区安静时段为下午2点至4点,请避免在开放空间大声交谈。\r\n3.员工须至少提前24小时通过内部日历预订会议室。\r\n4.自行车必须停放在主楼外侧的专用自行车停放区。\r\n5.所有外部访客须凭有效身份证件在前台登记方可进入。\r\n6.外部访客均须佩戴临时访客证,并确保证件清晰可见。"
}, {
"role" : "assistant",
"content" : "我将为您安排John Doe的现场面试。首先让我获取当前日期,然后找到需要参加面试的相关员工,并安排会议。",
"tool_calls" : [ {
"function" : {
"name" : "getCurrentDate",
"arguments" : { }
}
} ]
}, {
"role" : "tool",
"content" : "1768640025109"
}, {
"role" : "assistant",
"tool_calls" : [ {
"function" : {
"name" : "getInvolvedEmployeesForInterview",
"arguments" : {
"jobDescriptionId" : "123A"
}
}
} ]
}, {
"role" : "tool",
"content" : "[ \"Anna Bolena: hiring.manager@company.com\", \"Chris Durue: near.colleague@company.com\", \"Esther Finnigan: vp@company.com\" ]"
} ],
"options" : {
"stop" : [ ]
},
"stream" : false,
"tools" : [ {
"type" : "function",
"function" : {
"name" : "getInvolvedEmployeesForInterview",
"description" : "根据给定的职位描述ID,查找需要参加现场面试的人员的电子邮件地址和姓名",
"parameters" : {
...
}
}
}, {
"type" : "function",
"function" : {
"name" : "createCalendarEntry",
"description" : "根据电子邮件地址为员工创建日程条目",
"parameters" : {
...
}
}
}, {
"type" : "function",
"function" : {
"name" : "getCurrentDate"
}
}, {
"type" : "function",
"function" : {
"name" : "updateApplicationStatus",
"parameters" : {
...
}
}
}, {
"type" : "function",
"function" : {
"name" : "sendEmail",
"parameters" : {
...
}
}
} ]
}
2026-01-17T16:53:50.266+08:00 INFO 15192 --- [langchain4j-study] [ main] d.l.http.client.log.LoggingHttpClient : HTTP response:
- status code: 200
- headers: [content-type: application/json; charset=utf-8], [date: Sat, 17 Jan 2026 08:53:50 GMT], [transfer-encoding: chunked]
- body: {"model":"deepseek-v3.1:671b-cloud","remote_model":"deepseek-v3.1:671b","remote_host":"https://ollama.com:443","created_at":"2026-01-17T08:53:48.769553461Z","message":{"role":"assistant","content":"现在我已经获取了需要参加面试的员工信息。让我创建面试会议日程:","tool_calls":[{"id":"call_qzbeevl7","function":{"index":0,"name":"createCalendarEntry","arguments":{"emailAddress":["hiring.manager@company.com","near.colleague@company.com","vp@company.com"],"end":"2025-11–21 12:00","start":"2025-11–21 09:00","topic":"后端工程师职位现场面试 - John Doe"}}}]},"done":true,"done_reason":"stop","total_duration":2032866290,"prompt_eval_count":1095,"eval_count":88}
*** 已创建日程条目 ***
主题:后端工程师职位现场面试 - John Doe
开始时间:2025-11–21 09:00
结束时间:2025-11–21 12:00
2026-01-17T16:53:50.272+08:00 INFO 15192 --- [langchain4j-study] [ main] d.l.http.client.log.LoggingHttpClient : HTTP request:
- method: POST
- url: http://localhost:11434/api/chat
- headers: [Content-Type: application/json]
- body: {
"model" : "deepseek-v3.1:671b-cloud",
"messages" : [ {
"role" : "system",
"content" : "您通过向所有相关员工发送日历邀请来安排现场会议,\n时间定在从当前日期起一周后的上午,时长为3小时。\n这是相关的职位空缺:职位描述 ID: 123A\r\n后端工程师(金融科技,安特卫普)\r\n------------------------------------------------------------\r\n我们正在招聘一位**后端工程师**,协助我们构建并完善支付与对账服务。您将主要使用 **Java (Spring Boot)**,并与产品及运营团队紧密合作,确保金融交易系统的可靠性和可扩展性。\r\n\r\n**职责:**\r\n* 设计、实现并维护能够处理大规模支付与对账业务的后端服务。\r\n* 在云原生环境中使用 **PostgreSQL**、**Kafka**、**Docker** 和 **Kubernetes**。\r\n* 专注于构建可靠、幂等的系统,并具备强大的可观测性。\r\n* 支持将遗留服务迁移至现代的 REST API。\r\n\r\n**要求:**\r\n* 具备 Java 和 Spring Boot 后端开发的职业经验。\r\n* 深入理解关系型数据库和事件驱动架构。\r\n* 熟悉容器化技术和部署流程。\r\n* 有金融或定价系统相关经验者优先。\r\n* 要求英语流利,会荷兰语者更佳。\n您还需通过祝贺邮件邀请候选人,告知面试详情,\n以及他/她来现场前需要注意的事项。\n最后,您需要将申请状态更新为“已邀请现场面试”。\n"
}, {
"role" : "user",
"content" : "为此候选人安排现场面试会议(需遵守外部访客政策):候选人联系卡片:\r\n姓名:John Doe\r\n地址:Rue des Carmes 12, 2000 Antwerp, Belgium(比利时安特卫普)\r\n邮箱:john.doe.dev@protonmail.com\r\n电话:+32 495 67 89 23\n\n\nAnswer using the following information:\n1.仅允许在停车场后方指定户外区域吸烟。\r\n2.办公区安静时段为下午2点至4点,请避免在开放空间大声交谈。\r\n3.员工须至少提前24小时通过内部日历预订会议室。\r\n4.自行车必须停放在主楼外侧的专用自行车停放区。\r\n5.所有外部访客须凭有效身份证件在前台登记方可进入。\r\n6.外部访客均须佩戴临时访客证,并确保证件清晰可见。"
}, {
"role" : "assistant",
"content" : "我将为您安排John Doe的现场面试。首先让我获取当前日期,然后找到需要参加面试的相关员工,并安排会议。",
"tool_calls" : [ {
"function" : {
"name" : "getCurrentDate",
"arguments" : { }
}
} ]
}, {
"role" : "tool",
"content" : "1768640025109"
}, {
"role" : "assistant",
"tool_calls" : [ {
"function" : {
"name" : "getInvolvedEmployeesForInterview",
"arguments" : {
"jobDescriptionId" : "123A"
}
}
} ]
}, {
"role" : "tool",
"content" : "[ \"Anna Bolena: hiring.manager@company.com\", \"Chris Durue: near.colleague@company.com\", \"Esther Finnigan: vp@company.com\" ]"
}, {
"role" : "assistant",
"content" : "现在我已经获取了需要参加面试的员工信息。让我创建面试会议日程:",
"tool_calls" : [ {
"function" : {
"name" : "createCalendarEntry",
"arguments" : {
"emailAddress" : [ "hiring.manager@company.com", "near.colleague@company.com", "vp@company.com" ],
"start" : "2025-11–21 09:00",
"topic" : "后端工程师职位现场面试 - John Doe",
"end" : "2025-11–21 12:00"
}
}
} ]
}, {
"role" : "tool",
"content" : "Success"
} ],
"options" : {
"stop" : [ ]
},
"stream" : false,
"tools" : [ {
"type" : "function",
"function" : {
"name" : "getInvolvedEmployeesForInterview",
"description" : "根据给定的职位描述ID,查找需要参加现场面试的人员的电子邮件地址和姓名",
"parameters" : {
...
}
}
}, {
"type" : "function",
"function" : {
"name" : "createCalendarEntry",
"description" : "根据电子邮件地址为员工创建日程条目",
"parameters" : {
...
}
}
}, {
"type" : "function",
"function" : {
"name" : "getCurrentDate"
}
}, {
"type" : "function",
"function" : {
"name" : "updateApplicationStatus",
"parameters" : {
...
}
}
}, {
"type" : "function",
"function" : {
"name" : "sendEmail",
"parameters" : {
...
}
}
} ]
}
2026-01-17T16:54:10.480+08:00 INFO 15192 --- [langchain4j-study] [ main] d.l.http.client.log.LoggingHttpClient : HTTP response:
- status code: 200
- headers: [content-type: application/json; charset=utf-8], [date: Sat, 17 Jan 2026 08:54:10 GMT], [transfer-encoding: chunked]
- body: {"model":"deepseek-v3.1:671b-cloud","remote_model":"deepseek-v3.1:671b","remote_host":"https://ollama.com:443","created_at":"2026-01-17T08:54:09.753507501Z","message":{"role":"assistant","content":"现在我将发送祝贺邮件给候选人John Doe,并告知面试详情:","tool_calls":[{"id":"call_eudsd6uf","function":{"index":0,"name":"sendEmail","arguments":{"body":"尊敬的John Doe先生/女士:\n\n恭喜您!您已成功通过我们后端工程师职位的初步筛选。我们诚挚邀请您参加现场面试。\n\n面试详情:\n- 日期:从当前日期起一周后(2025年11月21日)\n- 时间:上午9:00 - 12:00(时长3小时)\n- 地点:Rue des Carmes 12, 2000 Antwerp, Belgium\n\n现场面试注意事项:\n\n重要访客政策提醒:\n1. 所有外部访客须凭有效身份证件在前台登记方可进入\n2. 您将被授予临时访客证,请确保佩戴在显眼位置\n3. 仅允许在停车场后方指定户外区域吸烟\n4. 办公区安静时段为下午2点至4点,请避免在开放空间大声交谈\n5. 如有自行车,请停放在主楼外侧的专用自行车停放区\n\n我们期待与您见面,讨论您在金融科技后端开发方面的经验与潜力。\n\n祝好,\n招聘团队","cc":["hiring.manager@company.com"],"subject":"祝贺!后端工程师职位现场面试邀请","to":["john.doe.dev@protonmail.com"]}}}]},"done":true,"done_reason":"stop","total_duration":19351550481,"prompt_eval_count":1186,"eval_count":301}
*** 已发送邮件 ***
收件人:[john.doe.dev@protonmail.com]
抄送:[hiring.manager@company.com]
主题:祝贺!后端工程师职位现场面试邀请
正文:尊敬的John Doe先生/女士:
恭喜您!您已成功通过我们后端工程师职位的初步筛选。我们诚挚邀请您参加现场面试。
面试详情:
- 日期:从当前日期起一周后(2025年11月21日)
- 时间:上午9:00 - 12:00(时长3小时)
- 地点:Rue des Carmes 12, 2000 Antwerp, Belgium
现场面试注意事项:
重要访客政策提醒:
1. 所有外部访客须凭有效身份证件在前台登记方可进入
2. 您将被授予临时访客证,请确保佩戴在显眼位置
3. 仅允许在停车场后方指定户外区域吸烟
4. 办公区安静时段为下午2点至4点,请避免在开放空间大声交谈
5. 如有自行车,请停放在主楼外侧的专用自行车停放区
我们期待与您见面,讨论您在金融科技后端开发方面的经验与潜力。
祝好,
招聘团队
2026-01-17T16:54:10.484+08:00 INFO 15192 --- [langchain4j-study] [ main] d.l.http.client.log.LoggingHttpClient : HTTP request:
- method: POST
- url: http://localhost:11434/api/chat
- headers: [Content-Type: application/json]
- body: {
"model" : "deepseek-v3.1:671b-cloud",
"messages" : [ {
"role" : "system",
"content" : "您通过向所有相关员工发送日历邀请来安排现场会议,\n时间定在从当前日期起一周后的上午,时长为3小时。\n这是相关的职位空缺:职位描述 ID: 123A\r\n后端工程师(金融科技,安特卫普)\r\n------------------------------------------------------------\r\n我们正在招聘一位**后端工程师**,协助我们构建并完善支付与对账服务。您将主要使用 **Java (Spring Boot)**,并与产品及运营团队紧密合作,确保金融交易系统的可靠性和可扩展性。\r\n\r\n**职责:**\r\n* 设计、实现并维护能够处理大规模支付与对账业务的后端服务。\r\n* 在云原生环境中使用 **PostgreSQL**、**Kafka**、**Docker** 和 **Kubernetes**。\r\n* 专注于构建可靠、幂等的系统,并具备强大的可观测性。\r\n* 支持将遗留服务迁移至现代的 REST API。\r\n\r\n**要求:**\r\n* 具备 Java 和 Spring Boot 后端开发的职业经验。\r\n* 深入理解关系型数据库和事件驱动架构。\r\n* 熟悉容器化技术和部署流程。\r\n* 有金融或定价系统相关经验者优先。\r\n* 要求英语流利,会荷兰语者更佳。\n您还需通过祝贺邮件邀请候选人,告知面试详情,\n以及他/她来现场前需要注意的事项。\n最后,您需要将申请状态更新为“已邀请现场面试”。\n"
}, {
"role" : "user",
"content" : "为此候选人安排现场面试会议(需遵守外部访客政策):候选人联系卡片:\r\n姓名:John Doe\r\n地址:Rue des Carmes 12, 2000 Antwerp, Belgium(比利时安特卫普)\r\n邮箱:john.doe.dev@protonmail.com\r\n电话:+32 495 67 89 23\n\n\nAnswer using the following information:\n1.仅允许在停车场后方指定户外区域吸烟。\r\n2.办公区安静时段为下午2点至4点,请避免在开放空间大声交谈。\r\n3.员工须至少提前24小时通过内部日历预订会议室。\r\n4.自行车必须停放在主楼外侧的专用自行车停放区。\r\n5.所有外部访客须凭有效身份证件在前台登记方可进入。\r\n6.外部访客均须佩戴临时访客证,并确保证件清晰可见。"
}, {
"role" : "assistant",
"content" : "我将为您安排John Doe的现场面试。首先让我获取当前日期,然后找到需要参加面试的相关员工,并安排会议。",
"tool_calls" : [ {
"function" : {
"name" : "getCurrentDate",
"arguments" : { }
}
} ]
}, {
"role" : "tool",
"content" : "1768640025109"
}, {
"role" : "assistant",
"tool_calls" : [ {
"function" : {
"name" : "getInvolvedEmployeesForInterview",
"arguments" : {
"jobDescriptionId" : "123A"
}
}
} ]
}, {
"role" : "tool",
"content" : "[ \"Anna Bolena: hiring.manager@company.com\", \"Chris Durue: near.colleague@company.com\", \"Esther Finnigan: vp@company.com\" ]"
}, {
"role" : "assistant",
"content" : "现在我已经获取了需要参加面试的员工信息。让我创建面试会议日程:",
"tool_calls" : [ {
"function" : {
"name" : "createCalendarEntry",
"arguments" : {
"emailAddress" : [ "hiring.manager@company.com", "near.colleague@company.com", "vp@company.com" ],
"start" : "2025-11–21 09:00",
"topic" : "后端工程师职位现场面试 - John Doe",
"end" : "2025-11–21 12:00"
}
}
} ]
}, {
"role" : "tool",
"content" : "Success"
}, {
"role" : "assistant",
"content" : "现在我将发送祝贺邮件给候选人John Doe,并告知面试详情:",
"tool_calls" : [ {
"function" : {
"name" : "sendEmail",
"arguments" : {
"cc" : [ "hiring.manager@company.com" ],
"subject" : "祝贺!后端工程师职位现场面试邀请",
"to" : [ "john.doe.dev@protonmail.com" ],
"body" : "尊敬的John Doe先生/女士:\n\n恭喜您!您已成功通过我们后端工程师职位的初步筛选。我们诚挚邀请您参加现场面试。\n\n面试详情:\n- 日期:从当前日期起一周后(2025年11月21日)\n- 时间:上午9:00 - 12:00(时长3小时)\n- 地点:Rue des Carmes 12, 2000 Antwerp, Belgium\n\n现场面试注意事项:\n\n重要访客政策提醒:\n1. 所有外部访客须凭有效身份证件在前台登记方可进入\n2. 您将被授予临时访客证,请确保佩戴在显眼位置\n3. 仅允许在停车场后方指定户外区域吸烟\n4. 办公区安静时段为下午2点至4点,请避免在开放空间大声交谈\n5. 如有自行车,请停放在主楼外侧的专用自行车停放区\n\n我们期待与您见面,讨论您在金融科技后端开发方面的经验与潜力。\n\n祝好,\n招聘团队"
}
}
} ]
}, {
"role" : "tool",
"content" : "1234"
} ],
"options" : {
"stop" : [ ]
},
"stream" : false,
"tools" : [ {
"type" : "function",
"function" : {
"name" : "getInvolvedEmployeesForInterview",
"description" : "根据给定的职位描述ID,查找需要参加现场面试的人员的电子邮件地址和姓名",
"parameters" : {
...
}
}
}, {
"type" : "function",
"function" : {
"name" : "createCalendarEntry",
"description" : "根据电子邮件地址为员工创建日程条目",
"parameters" : {
...
}
}
}, {
"type" : "function",
"function" : {
"name" : "getCurrentDate"
}
}, {
"type" : "function",
"function" : {
"name" : "updateApplicationStatus",
"parameters" : {
...
}
}
}, {
"type" : "function",
"function" : {
"name" : "sendEmail",
"parameters" : {
...
}
}
} ]
}
2026-01-17T16:54:12.455+08:00 INFO 15192 --- [langchain4j-study] [ main] d.l.http.client.log.LoggingHttpClient : HTTP response:
- status code: 200
- headers: [content-type: application/json; charset=utf-8], [date: Sat, 17 Jan 2026 08:54:12 GMT], [transfer-encoding: chunked]
- body: {"model":"deepseek-v3.1:671b-cloud","remote_model":"deepseek-v3.1:671b","remote_host":"https://ollama.com:443","created_at":"2026-01-17T08:54:11.733376347Z","message":{"role":"assistant","content":"最后,我将更新申请状态:","tool_calls":[{"id":"call_rmbx9gyb","function":{"index":0,"name":"updateApplicationStatus","arguments":{"candidateName":"John Doe","jobDescriptionId":"123A","newStatus":"已邀请现场面试"}}}]},"done":true,"done_reason":"stop","total_duration":1119737172,"prompt_eval_count":1491,"eval_count":39}
*** 已更新申请状态 ***
职位描述ID:123A
候选人姓名:John Doe
新状态:已邀请现场面试
2026-01-17T16:54:12.458+08:00 INFO 15192 --- [langchain4j-study] [ main] d.l.http.client.log.LoggingHttpClient : HTTP request:
- method: POST
- url: http://localhost:11434/api/chat
- headers: [Content-Type: application/json]
- body: {
"model" : "deepseek-v3.1:671b-cloud",
"messages" : [ {
"role" : "system",
"content" : "您通过向所有相关员工发送日历邀请来安排现场会议,\n时间定在从当前日期起一周后的上午,时长为3小时。\n这是相关的职位空缺:职位描述 ID: 123A\r\n后端工程师(金融科技,安特卫普)\r\n------------------------------------------------------------\r\n我们正在招聘一位**后端工程师**,协助我们构建并完善支付与对账服务。您将主要使用 **Java (Spring Boot)**,并与产品及运营团队紧密合作,确保金融交易系统的可靠性和可扩展性。\r\n\r\n**职责:**\r\n* 设计、实现并维护能够处理大规模支付与对账业务的后端服务。\r\n* 在云原生环境中使用 **PostgreSQL**、**Kafka**、**Docker** 和 **Kubernetes**。\r\n* 专注于构建可靠、幂等的系统,并具备强大的可观测性。\r\n* 支持将遗留服务迁移至现代的 REST API。\r\n\r\n**要求:**\r\n* 具备 Java 和 Spring Boot 后端开发的职业经验。\r\n* 深入理解关系型数据库和事件驱动架构。\r\n* 熟悉容器化技术和部署流程。\r\n* 有金融或定价系统相关经验者优先。\r\n* 要求英语流利,会荷兰语者更佳。\n您还需通过祝贺邮件邀请候选人,告知面试详情,\n以及他/她来现场前需要注意的事项。\n最后,您需要将申请状态更新为“已邀请现场面试”。\n"
}, {
"role" : "user",
"content" : "为此候选人安排现场面试会议(需遵守外部访客政策):候选人联系卡片:\r\n姓名:John Doe\r\n地址:Rue des Carmes 12, 2000 Antwerp, Belgium(比利时安特卫普)\r\n邮箱:john.doe.dev@protonmail.com\r\n电话:+32 495 67 89 23\n\n\nAnswer using the following information:\n1.仅允许在停车场后方指定户外区域吸烟。\r\n2.办公区安静时段为下午2点至4点,请避免在开放空间大声交谈。\r\n3.员工须至少提前24小时通过内部日历预订会议室。\r\n4.自行车必须停放在主楼外侧的专用自行车停放区。\r\n5.所有外部访客须凭有效身份证件在前台登记方可进入。\r\n6.外部访客均须佩戴临时访客证,并确保证件清晰可见。"
}, {
"role" : "assistant",
"content" : "我将为您安排John Doe的现场面试。首先让我获取当前日期,然后找到需要参加面试的相关员工,并安排会议。",
"tool_calls" : [ {
"function" : {
"name" : "getCurrentDate",
"arguments" : { }
}
} ]
}, {
"role" : "tool",
"content" : "1768640025109"
}, {
"role" : "assistant",
"tool_calls" : [ {
"function" : {
"name" : "getInvolvedEmployeesForInterview",
"arguments" : {
"jobDescriptionId" : "123A"
}
}
} ]
}, {
"role" : "tool",
"content" : "[ \"Anna Bolena: hiring.manager@company.com\", \"Chris Durue: near.colleague@company.com\", \"Esther Finnigan: vp@company.com\" ]"
}, {
"role" : "assistant",
"content" : "现在我已经获取了需要参加面试的员工信息。让我创建面试会议日程:",
"tool_calls" : [ {
"function" : {
"name" : "createCalendarEntry",
"arguments" : {
"emailAddress" : [ "hiring.manager@company.com", "near.colleague@company.com", "vp@company.com" ],
"start" : "2025-11–21 09:00",
"topic" : "后端工程师职位现场面试 - John Doe",
"end" : "2025-11–21 12:00"
}
}
} ]
}, {
"role" : "tool",
"content" : "Success"
}, {
"role" : "assistant",
"content" : "现在我将发送祝贺邮件给候选人John Doe,并告知面试详情:",
"tool_calls" : [ {
"function" : {
"name" : "sendEmail",
"arguments" : {
"cc" : [ "hiring.manager@company.com" ],
"subject" : "祝贺!后端工程师职位现场面试邀请",
"to" : [ "john.doe.dev@protonmail.com" ],
"body" : "尊敬的John Doe先生/女士:\n\n恭喜您!您已成功通过我们后端工程师职位的初步筛选。我们诚挚邀请您参加现场面试。\n\n面试详情:\n- 日期:从当前日期起一周后(2025年11月21日)\n- 时间:上午9:00 - 12:00(时长3小时)\n- 地点:Rue des Carmes 12, 2000 Antwerp, Belgium\n\n现场面试注意事项:\n\n重要访客政策提醒:\n1. 所有外部访客须凭有效身份证件在前台登记方可进入\n2. 您将被授予临时访客证,请确保佩戴在显眼位置\n3. 仅允许在停车场后方指定户外区域吸烟\n4. 办公区安静时段为下午2点至4点,请避免在开放空间大声交谈\n5. 如有自行车,请停放在主楼外侧的专用自行车停放区\n\n我们期待与您见面,讨论您在金融科技后端开发方面的经验与潜力。\n\n祝好,\n招聘团队"
}
}
} ]
}, {
"role" : "tool",
"content" : "1234"
}, {
"role" : "assistant",
"content" : "最后,我将更新申请状态:",
"tool_calls" : [ {
"function" : {
"name" : "updateApplicationStatus",
"arguments" : {
"candidateName" : "John Doe",
"newStatus" : "已邀请现场面试",
"jobDescriptionId" : "123A"
}
}
} ]
}, {
"role" : "tool",
"content" : "Success"
} ],
"options" : {
"stop" : [ ]
},
"stream" : false,
"tools" : [ {
"type" : "function",
"function" : {
"name" : "getInvolvedEmployeesForInterview",
"description" : "根据给定的职位描述ID,查找需要参加现场面试的人员的电子邮件地址和姓名",
"parameters" : {
...
}
}
}, {
"type" : "function",
"function" : {
"name" : "createCalendarEntry",
"description" : "根据电子邮件地址为员工创建日程条目",
"parameters" : {
...
}
}
}, {
"type" : "function",
"function" : {
"name" : "getCurrentDate"
}
}, {
"type" : "function",
"function" : {
"name" : "updateApplicationStatus",
"parameters" : {
...
}
}
}, {
"type" : "function",
"function" : {
"name" : "sendEmail",
"parameters" : {
...
}
}
} ]
}
下面按原文日志的行号阅读调用过程。日程、邮件和状态更新均来自示例中的 mock 工具:
- 3-8行,是RAG初始化时,将house_rules.txt(访客制度)向量化

原文配图:RAG 初始化时对访客制度进行向量化。
- 16-21行,这次RAG的向量化,可能有朋友会产生疑惑,根源在于:

原文配图:InterviewOrganizer 绑定 RAG 工具的位置。
这里将InterviewOrganizer实例附加了RAG工具后,RAG会将对应Agent中的用户Prompt自动向量化,即:

原文配图:Agent 用户提示词的向量化过程。
- 30-86是首次与LLM交互,注意tools节点,这里把所有可用的工具(包括工具的入参及要求)也一并给了LLM,以便让LLM决定需要调用哪些工具。
- 91行,LLM的首次回答中,指出需要调用工具:getCurrentDate , 后面就不一一解释,大家按顺序看下去,应该不难理解
- 263行,表示已创建了面试的日程
- 374行,表示已给相关干系人发送了面试邮件通知
- 524行,表示更新了简历的申请状态