Agent设计模式学习(基于langchain4j实现)(9) - 人机协同
<p>经过前面的一系列流程,招聘来到了最重要的1个环节,AI虽然强大,但是不能完全代替人做决定,最终还是要Boss决策这个候选人的去留。从系统设计角度来说,整个AI智能体环节中,要预留人工干预的能力,也称为"人机协同"(human_in_the_loop)</p><p> </p>
<p><span style="color: rgba(255, 0, 0, 1)">示例代码</span>:</p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_1dcb8dfb-81c2-4a54-8b67-6b9de0ed259b" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_1dcb8dfb-81c2-4a54-8b67-6b9de0ed259b" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_1dcb8dfb-81c2-4a54-8b67-6b9de0ed259b" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 0, 1)">@SpringBootApplication
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> _9a_HumanInTheLoop_Simple_Validator {
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span>
<span style="color: rgba(0, 128, 128, 1)"> 4</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> main(String[] args) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> IOException {
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span> ConfigurableApplicationContext context = SpringApplication.run(AgentDesignPatternApplication.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">, args);
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span> ChatModel model = context.getBean("ollamaChatModel", ChatModel.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> RagProvider ragProvider = context.getBean("ragProvider", RagProvider.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span>
<span style="color: rgba(0, 128, 128, 1)"> 9</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 3. 创建相关智能体</span>
<span style="color: rgba(0, 128, 128, 1)">10</span> InterviewOrganizer interviewOrganizer = AgenticServices.agentBuilder(InterviewOrganizer.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">11</span> <span style="color: rgba(0, 0, 0, 1)"> .chatModel(model)
</span><span style="color: rgba(0, 128, 128, 1)">12</span> .tools(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OrganizingTools())
</span><span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(0, 0, 0, 1)"> .contentRetriever(ragProvider.loadHouseRulesRetriever())
</span><span style="color: rgba(0, 128, 128, 1)">14</span> <span style="color: rgba(0, 0, 0, 1)"> .build();
</span><span style="color: rgba(0, 128, 128, 1)">15</span>
<span style="color: rgba(0, 128, 128, 1)">16</span> EmailAssistant emailAssistant = AgenticServices.agentBuilder(EmailAssistant.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">17</span> <span style="color: rgba(0, 0, 0, 1)"> .chatModel(model)
</span><span style="color: rgba(0, 128, 128, 1)">18</span> .tools(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OrganizingTools())
</span><span style="color: rgba(0, 128, 128, 1)">19</span> <span style="color: rgba(0, 0, 0, 1)"> .build();
</span><span style="color: rgba(0, 128, 128, 1)">20</span>
<span style="color: rgba(0, 128, 128, 1)">21</span> HiringDecisionProposer decisionProposer = AgenticServices.agentBuilder(HiringDecisionProposer.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">22</span> <span style="color: rgba(0, 0, 0, 1)"> .chatModel(model)
</span><span style="color: rgba(0, 128, 128, 1)">23</span> .outputKey("modelDecision"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">24</span> <span style="color: rgba(0, 0, 0, 1)"> .build();
</span><span style="color: rgba(0, 128, 128, 1)">25</span>
<span style="color: rgba(0, 128, 128, 1)">26</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 2. 定义人工验证环节</span>
<span style="color: rgba(0, 128, 128, 1)">27</span> HumanInTheLoop humanValidator =<span style="color: rgba(0, 0, 0, 1)"> AgenticServices.humanInTheLoopBuilder()
</span><span style="color: rgba(0, 128, 128, 1)">28</span> .description("验证模型提出的招聘决策"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">29</span> .inputKey("modelDecision"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">30</span> .outputKey("finalDecision") <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 由人工检查</span>
<span style="color: rgba(0, 128, 128, 1)">31</span> .requestWriter(request -><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">32</span> System.out.println("AI招聘助手建议: " +<span style="color: rgba(0, 0, 0, 1)"> request);
</span><span style="color: rgba(0, 128, 128, 1)">33</span> System.out.println("请确认最终决定。"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">34</span> System.out.println("选项: 邀请现场面试 (I), 拒绝 (R), 暂缓 (H)"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">35</span> System.out.print("> "); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 在实际系统中需要输入验证和错误处理</span>
<span style="color: rgba(0, 128, 128, 1)">36</span> <span style="color: rgba(0, 0, 0, 1)"> })
</span><span style="color: rgba(0, 128, 128, 1)">37</span> .responseReader(() -> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Scanner(System.in).nextLine())
</span><span style="color: rgba(0, 128, 128, 1)">38</span> <span style="color: rgba(0, 0, 0, 1)"> .build();
</span><span style="color: rgba(0, 128, 128, 1)">39</span>
<span style="color: rgba(0, 128, 128, 1)">40</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 3. 将智能体链接成工作流</span>
<span style="color: rgba(0, 128, 128, 1)">41</span> UntypedAgent hiringDecisionWorkflow =<span style="color: rgba(0, 0, 0, 1)"> AgenticServices.sequenceBuilder()
</span><span style="color: rgba(0, 128, 128, 1)">42</span> <span style="color: rgba(0, 0, 0, 1)"> .subAgents(decisionProposer, humanValidator)
</span><span style="color: rgba(0, 128, 128, 1)">43</span> .outputKey("finalDecision"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">44</span> <span style="color: rgba(0, 0, 0, 1)"> .build();
</span><span style="color: rgba(0, 128, 128, 1)">45</span>
<span style="color: rgba(0, 128, 128, 1)">46</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 4. 准备输入参数</span>
<span style="color: rgba(0, 128, 128, 1)">47</span> Map<String, Object> input =<span style="color: rgba(0, 0, 0, 1)"> Map.of(
</span><span style="color: rgba(0, 128, 128, 1)">48</span> "cvReview", <span style="color: rgba(0, 0, 255, 1)">new</span> CvReview(0.85<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">49</span> <span style="color: rgba(0, 0, 0, 1)">"""
</span><span style="color: rgba(0, 128, 128, 1)">50</span> <span style="color: rgba(0, 0, 0, 1)"> 技术能力强,但缺乏所需的React经验。
</span><span style="color: rgba(0, 128, 128, 1)">51</span> <span style="color: rgba(0, 0, 0, 1)"> 似乎是快速独立学习者。文化契合度良好。
</span><span style="color: rgba(0, 128, 128, 1)">52</span> <span style="color: rgba(0, 0, 0, 1)"> 工作许可可能存在潜在问题,但似乎可以解决。
</span><span style="color: rgba(0, 128, 128, 1)">53</span> <span style="color: rgba(0, 0, 0, 1)"> 薪资期望略高于计划预算。
</span><span style="color: rgba(0, 128, 128, 1)">54</span> <span style="color: rgba(0, 0, 0, 1)"> 决定继续进行现场面试。
</span><span style="color: rgba(0, 128, 128, 1)">55</span> """)
<span style="color: rgba(0, 128, 128, 1)">56</span> <span style="color: rgba(0, 0, 0, 1)"> );
</span><span style="color: rgba(0, 128, 128, 1)">57</span>
<span style="color: rgba(0, 128, 128, 1)">58</span> System.out.println(input + "\n"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">59</span>
<span style="color: rgba(0, 128, 128, 1)">60</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 5. 运行工作流</span>
<span style="color: rgba(0, 128, 128, 1)">61</span> String finalDecision =<span style="color: rgba(0, 0, 0, 1)"> (String) hiringDecisionWorkflow.invoke(input);
</span><span style="color: rgba(0, 128, 128, 1)">62</span>
<span style="color: rgba(0, 128, 128, 1)">63</span> System.out.println("\n=== 人工最终决定 ==="<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">64</span> System.out.println("(邀请现场面试 (I), 拒绝 (R), 暂缓 (H))\n"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">65</span> <span style="color: rgba(0, 0, 0, 1)"> System.out.println(finalDecision);
</span><span style="color: rgba(0, 128, 128, 1)">66</span>
<span style="color: rgba(0, 128, 128, 1)">67</span> UntypedAgent candidateResponder =<span style="color: rgba(0, 0, 0, 1)"> AgenticServices
</span><span style="color: rgba(0, 128, 128, 1)">68</span> <span style="color: rgba(0, 0, 0, 1)"> .conditionalBuilder()
</span><span style="color: rgba(0, 128, 128, 1)">69</span> .subAgents(agenticScope -> finalDecision.contains("I"<span style="color: rgba(0, 0, 0, 1)">), interviewOrganizer)
</span><span style="color: rgba(0, 128, 128, 1)">70</span> .subAgents(agenticScope -> finalDecision.contains("R"<span style="color: rgba(0, 0, 0, 1)">), emailAssistant)
</span><span style="color: rgba(0, 128, 128, 1)">71</span> .subAgents(agenticScope -> finalDecision.contains("H"), <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> HoldOnAssist())
</span><span style="color: rgba(0, 128, 128, 1)">72</span> <span style="color: rgba(0, 0, 0, 1)"> .build();
</span><span style="color: rgba(0, 128, 128, 1)">73</span>
<span style="color: rgba(0, 128, 128, 1)">74</span> String candidateContact = StringLoader.loadFromResource("/documents/candidate_contact.txt"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">75</span> String jobDescription = StringLoader.loadFromResource("/documents/job_description_backend.txt"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">76</span>
<span style="color: rgba(0, 128, 128, 1)">77</span>
<span style="color: rgba(0, 128, 128, 1)">78</span> Map<String, Object> arguments =<span style="color: rgba(0, 0, 0, 1)"> Map.of(
</span><span style="color: rgba(0, 128, 128, 1)">79</span> "candidateContact"<span style="color: rgba(0, 0, 0, 1)">, candidateContact,
</span><span style="color: rgba(0, 128, 128, 1)">80</span> "jobDescription"<span style="color: rgba(0, 0, 0, 1)">, jobDescription
</span><span style="color: rgba(0, 128, 128, 1)">81</span> <span style="color: rgba(0, 0, 0, 1)"> );
</span><span style="color: rgba(0, 128, 128, 1)">82</span>
<span style="color: rgba(0, 128, 128, 1)">83</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 6. 根据人工最终决定,进行下一步操作</span>
<span style="color: rgba(0, 128, 128, 1)">84</span> <span style="color: rgba(0, 0, 0, 1)"> candidateResponder.invoke(arguments);
</span><span style="color: rgba(0, 128, 128, 1)">85</span>
<span style="color: rgba(0, 128, 128, 1)">86</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 注意:人工参与和人工验证通常需要较长时间等待用户响应。
</span><span style="color: rgba(0, 128, 128, 1)">87</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 在这种情况下,建议使用异步智能体,这样它们不会阻塞工作流的其余部分,
</span><span style="color: rgba(0, 128, 128, 1)">88</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这些部分可以在用户回答之前潜在执行。</span>
<span style="color: rgba(0, 128, 128, 1)">89</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">90</span> }</pre>
</div>
<span class="cnblogs_code_collapse">_9a_HumanInTheLoop_Simple_Validator </span></div>
<p>解释一下:</p>
<p>27行,这里的 AgenticServices.<strong>humanInTheLoopBuilder</strong>() 表示这里需要人机协助,协助的方式由29行的 new Scanner(System.in).nextLine() 指定为终端控制台输入(也可以根据需要,换成其它方式,比如:读数据库,调用接口从其它系统获取)</p>
<p>67-71行,则是根据人工指令做出的响应(即前面学过过的条件工作流),如果输入H,则执行下面的Agent </p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_ef6c700a-cb4a-4c51-9f86-6dd90dfac617" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_ef6c700a-cb4a-4c51-9f86-6dd90dfac617" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_ef6c700a-cb4a-4c51-9f86-6dd90dfac617" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> HoldOnAssist {
</span><span style="color: rgba(0, 128, 128, 1)">2</span>
<span style="color: rgba(0, 128, 128, 1)">3</span> @Agent(description = "招聘流程暂缓"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">4</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> abort() {
</span><span style="color: rgba(0, 128, 128, 1)">5</span> System.out.println("招聘流程暂缓"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">6</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">7</span> }</pre>
</div>
<span class="cnblogs_code_collapse">HoldOnAssist </span></div>
<p> </p>
<p><span style="color: rgba(255, 0, 0, 1)">输出日志</span>:</p>
<div class="cnblogs_code"><img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" id="code_img_closed_ff148868-d85f-42d3-b110-8b0b9113cfb0" class="code_img_closed"><img src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" id="code_img_opened_ff148868-d85f-42d3-b110-8b0b9113cfb0" class="code_img_opened" style="display: none">
<div id="cnblogs_code_open_ff148868-d85f-42d3-b110-8b0b9113cfb0" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> {cvReview=
<span style="color: rgba(0, 128, 128, 1)"> 2</span> CvReview:- score = <span style="color: rgba(128, 0, 128, 1)">0.85</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span> - feedback = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">技术能力强,但缺乏所需的React经验。</span>
<span style="color: rgba(0, 128, 128, 1)"> 4</span> <span style="color: rgba(0, 0, 0, 1)">似乎是快速独立学习者。文化契合度良好。
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span> <span style="color: rgba(0, 0, 0, 1)">工作许可可能存在潜在问题,但似乎可以解决。
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 0, 1)">薪资期望略高于计划预算。
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 0, 1)">决定继续进行现场面试。
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span> <span style="color: rgba(128, 0, 0, 1)">"
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span> <span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 128, 1)">10</span>
<span style="color: rgba(0, 128, 128, 1)">11</span> AI招聘助手建议: **招聘理由汇总:**
<span style="color: rgba(0, 128, 128, 1)">12</span>
<span style="color: rgba(0, 128, 128, 1)">13</span> **优势:**<span style="color: rgba(0, 0, 0, 1)"> 技术能力强,文化契合度良好,学习能力佳
</span><span style="color: rgba(0, 128, 128, 1)">14</span>
<span style="color: rgba(0, 128, 128, 1)">15</span> **风险:**<span style="color: rgba(0, 0, 0, 1)"> 缺乏React经验,薪资略超预算,潜在工作许可问题
</span><span style="color: rgba(0, 128, 128, 1)">16</span>
<span style="color: rgba(0, 128, 128, 1)">17</span> **建议:**<span style="color: rgba(0, 0, 0, 1)"> 继续现场面试评估,面试后综合决定
</span><span style="color: rgba(0, 128, 128, 1)">18</span> <span style="color: rgba(0, 0, 0, 1)">请确认最终决定。
</span><span style="color: rgba(0, 128, 128, 1)">19</span> <span style="color: rgba(0, 0, 0, 1)">选项: 邀请现场面试 (I), 拒绝 (R), 暂缓 (H)
</span><span style="color: rgba(0, 128, 128, 1)">20</span> ><span style="color: rgba(0, 0, 0, 1)"> H
</span><span style="color: rgba(0, 128, 128, 1)">21</span>
<span style="color: rgba(0, 128, 128, 1)">22</span> === 人工最终决定 ===
<span style="color: rgba(0, 128, 128, 1)">23</span> <span style="color: rgba(0, 0, 0, 1)">(邀请现场面试 (I), 拒绝 (R), 暂缓 (H))
</span><span style="color: rgba(0, 128, 128, 1)">24</span>
<span style="color: rgba(0, 128, 128, 1)">25</span> <span style="color: rgba(0, 0, 0, 1)">H
</span><span style="color: rgba(0, 128, 128, 1)">26</span> 招聘流程暂缓</pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p><img src="https://img2024.cnblogs.com/blog/27612/202601/27612-20260131190653692-204541313.png" alt="image" width="480" loading="lazy" style="border: 1px solid rgba(0, 0, 0, 1)"></p>
<p> </p>
<p><span style="color: rgba(255, 0, 0, 1)">时序图</span>(简化版) - AI生成</p>
<p><img src="https://img2024.cnblogs.com/blog/27612/202601/27612-20260131190032208-585724069.png" alt="09_human_diagram_simple" width="480" loading="lazy" style="border: 1px solid rgba(0, 0, 0, 1)"></p>
<p>时序图(详细版) - AI生成<br><img src="https://img2024.cnblogs.com/blog/27612/202601/27612-20260131190151177-743821235.png" alt="09_human_diagram_detail" width="480" loading="lazy" style="border: 1px solid rgba(0, 0, 0, 1)"></p>
<p> </p>
<p>文中示例代码:</p>
<p>https://github.com/yjmyzz/agentic_turoial_with_langchain4j<br></p>
<p> </p>
<p>参考:</p>
<p>Building Effective AI Agents \ Anthropic</p>
<p>[译] AI Workflow & AI Agent:架构、模式与工程建议(Anthropic,2024)</p>
<p>Agents and Agentic AI | LangChain4j</p>
</div>
<div id="MySignature" role="contentinfo">
<div style="padding:5px;background:#ff9;border:solid 1px #ccc">作者:菩提树下的杨过<br />
出处:http://yjmyzz.cnblogs.com
<br/>
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
</div><br><br>
来源:https://www.cnblogs.com/yjmyzz/p/19558861/agentic-patterns-using-langchain4j-9-human_in_the_loop
頁:
[1]