LangChain4j Java AI 应用开发实战(二十四):人机协同与非 AI Agent —— 混合执行系统

发布时间:2026/10/4 12:07:51
LangChain4j Java AI 应用开发实战(二十四):人机协同与非 AI Agent —— 混合执行系统 1. 为什么 AI Agent 需要人工节点混合执行系统的真实场景在 LangChain4j 里做 Java AI Agent 开发走到第二十四篇绕不开一个现实问题AI Agent 不是万能的。我见过太多团队把整条业务链全塞给 LLM结果在评分聚合、状态回写、最终审批这些环节翻车。混合执行系统Hybrid Execution System要解决的就是这件事——让 AI Agent、NonAI Agent纯 Java 逻辑和 HumanInTheLoop人工介入三种节点在同一个工作流里协同谁擅长什么就干什么。先说清楚三种节点的定位。AI Agent 负责语义理解、文本生成、推理判断比如分析简历、总结面试反馈NonAI Agent 负责确定性计算、数据转换、规则判断比如求平均分、按阈值改状态、格式转换HumanInTheLoop 负责敏感决策、责任归属、复杂判断比如最终录用拍板、金额超过阈值的审批。这三者在 LangChain4j Agentic 框架里是一等公民用同一套编排 API 就能串起来。为什么不能全用 AI举个我踩过的坑招聘系统里三方评审分数是 0.85、0.72、0.80让 LLM 算平均值Token 消耗约 200延迟 1.5 秒准确率 99.7%——偶尔会算错。换成纯 Java 代码Token 消耗 0延迟 0.00001 秒准确率 100%。这种场景用 AI 就是浪费钱还引入不确定性。为什么不能全用代码因为这个候选人技术强但薪资期望偏高建议邀请现场面试这种总结纯规则写不出来。而是否最终录用这种决策AI 不能背责任必须人工拍板。所以混合执行系统的核心价值是AI 做它擅长的语义理解、文本生成代码做它擅长的确定性计算、规则判断人做人类擅长的复杂决策、责任归属。本篇就带你从零搭一套可恢复的人机协同工作流包含可复制的 Agent 编排配置、状态回传代码以及一次完整的中断-恢复验证。适合谁看有 Java 基础、正在用 LangChain4j 做 Agent 应用、遇到AI 算不准或审批环节卡住问题的开发者。如果你还没接触过 Agentic 工作流建议先看前面顺序、循环、并行、条件分支几篇本篇默认你已经会sequenceBuilder()和parallelBuilder()。2. TaoToken 前置准备模型接入与依赖配置在写混合执行系统之前得先把模型通道打通。LangChain4j 本身不绑定任何模型供应商你需要一个兼容 OpenAI 协议的接入点。我用的是 TaoToken 的 API 通道它兼容 OpenAI 的/v1/chat/completions协议LangChain4j 的OpenAiChatModel可以直接对接不需要改任何业务代码。先看 Maven 依赖。LangChain4j 的 Agentic 模块在 1.x 版本后独立出来了加上 OpenAI 集成和日志dependencies dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version1.1.0/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-agentic/artifactId version1.1.0-beta7/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-open-ai/artifactId version1.1.0/version /dependency dependency groupIdch.qos.logback/groupId artifactIdlogback-classic/artifactId version1.5.6/version /dependency /dependencies注意langchain4j-agentic目前还是 beta 版本API 可能微调建议锁定版本号。接下来配置模型。TaoToken 的 API 地址是https://taotoken.net/apiKey 在控制台的 API Keys 页面生成。我建议把配置放到application.properties或环境变量里别硬编码# application.properties taotoken.base-urlhttps://taotoken.net/api taotoken.api-key${TAOTOKEN_API_KEY} taotoken.modelgpt-4o-mini然后在 Java 里构建ChatModelimport dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.model.chat.ChatModel; import java.time.Duration; public class ModelFactory { public static ChatModel buildChatModel() { String baseUrl System.getProperty(taotoken.base-url, https://taotoken.net/api); String apiKey System.getenv(TAOTOKEN_API_KEY); String modelName System.getProperty(taotoken.model, gpt-4o-mini); return OpenAiChatModel.builder() .baseUrl(baseUrl) .apiKey(apiKey) .modelName(modelName) .temperature(0.2) .timeout(Duration.ofSeconds(60)) .logRequests(true) .logResponses(true) .build(); } }这里有几个参数值得说。temperature(0.2)是因为招聘评审这种场景需要稳定输出别让模型太发散timeout(60s)是防止长文本评审超时logRequests/logResponses在调试阶段打开能直接看到请求体和响应体排查 401 或模型名错误特别有用。如果你用的是 Claude Code 或 Cline 这类编码工具做辅助开发配置方式类似Base URL 填https://taotoken.net/apiKey 填控制台生成的Model ID 填gpt-4o-mini或你账号下可用的模型。三件套Base URL Key Model ID缺一不可很多人 401 就是因为 Key 没设进环境变量。验证通道是否通跑一个最小请求public class SmokeTest { public static void main(String[] args) { ChatModel model ModelFactory.buildChatModel(); String reply model.chat(用一句话说明什么是混合执行系统); System.out.println(reply); } }如果控制台打印出模型回复说明通道正常。如果报401 Unauthorized检查TAOTOKEN_API_KEY环境变量是否生效如果报Connection refused检查 baseUrl 是否写成了https://taotoken.net/api注意结尾没有/v1LangChain4j 会自动补。3. 可复制的混合执行编排配置AI NonAI Human 三件套这一节是核心直接给你能跑的配置。整个工作流分五步三方并行 AI 评审 → NonAI 聚合分数 → NonAI 更新状态 → AI 生成决策建议 → Human 最终确认。先定义数据模型package com.langchain4j.domain; public record CvReview(double score, String feedback) { Override public String toString() { return CvReview{score score , feedback feedback }; } }3.1 NonAI Agent纯 Java 逻辑成为工作流一等公民LangChain4j Agentic 的核心设计是任何 Java 类的方法加上Agent注解就能像 AI Agent 一样参与编排。看聚合器package com.langchain4j.agentic._08_non_ai_agents; import com.langchain4j.domain.CvReview; import dev.langchain4j.agentic.Agent; import dev.langchain4j.service.V; public class ScoreAggregator { Agent(description 聚合 HR/经理/团队评审为一个综合评审, outputKey combinedCvReview) public CvReview aggregate( V(hrReview) CvReview hr, V(managerReview) CvReview mgr, V(teamMemberReview) CvReview team) { System.out.println(ScoreAggregator 被调用输入参数); System.out.println( hrReview: hr); System.out.println( managerReview: mgr); System.out.println( teamMemberReview: team); double avgScore (hr.score() mgr.score() team.score()) / 3.0; String combinedFeedback String.join(\n\n, HR 评审 hr.feedback(), 经理评审 mgr.feedback(), 团队成员评审 team.feedback()); CvReview result new CvReview(avgScore, combinedFeedback); System.out.println(ScoreAggregator 输出综合评分 avgScore); return result; } }关键点Agent声明这是 Agentdescription供 Supervisor 理解用途outputKey指定输出写入AgenticScope的键名V(hrReview)表示从AgenticScope读取上游输出键名必须和上游outputKey完全一致方法体就是普通 Java零 LLM 调用。状态更新器同理package com.langchain4j.agentic._08_non_ai_agents; import com.langchain4j.domain.CvReview; import dev.langchain4j.agentic.Agent; import dev.langchain4j.service.V; public class StatusUpdate { Agent(description 根据分数更新申请状态) public void update(V(combinedCvReview) CvReview aggregateCvReview) { double score aggregateCvReview.score(); System.out.println(StatusUpdate 被调用分数 score); if (score 8.0) { System.out.println(申请状态已更新为已邀请); } else { System.out.println(申请状态已更新为已拒绝); } } }注意返回void的 NonAI Agent 不写回AgenticScope只产生副作用数据库更新、发消息。真实项目里把System.out.println换成jdbcTemplate.update(...)即可。3.2 HumanInTheLoop中断-恢复的关键人工节点用humanInTheLoopBuilder()构建核心是responseProvider回调——工作流执行到这里会暂停调用回调拿到人类输入后继续import dev.langchain4j.agentic.AgenticServices; import dev.langchain4j.agentic.agent.HumanInTheLoop; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; HumanInTheLoop humanValidator AgenticServices .humanInTheLoopBuilder() .description(验证模型建议的招聘决策) .outputKey(finalDecision) .responseProvider(scope - { System.out.println(AI 招聘助手建议 scope.readState(request)); System.out.println(请确认最终决策。); System.out.println(选项邀请现场面试 (I)、拒绝 (R)、保留 (H)); System.out.print( ); BufferedReader reader new BufferedReader( new InputStreamReader(System.in)); try { return reader.readLine(); } catch (IOException e) { throw new RuntimeException(读取输入失败, e); } }) .async(true) .build();async(true)是生产环境必设项。不设的话responseProvider会阻塞工作流线程池等待人工输入期间整个线程被占死。设了之后等待期间线程释放工作流在拿到输入后从暂停点恢复。3.3 完整编排五步串联把 AI Agent、NonAI Agent、Human 串成顺序工作流import dev.langchain4j.agentic.AgenticServices; import dev.langchain4j.agentic.UntypedAgent; import java.util.Map; import java.util.concurrent.Executors; public class HybridWorkflow { public static void main(String[] args) { var chatModel ModelFactory.buildChatModel(); // 1. 三个 AI 评审 Agent并行 HrCvReviewer hrReviewer AgenticServices .agentBuilder(HrCvReviewer.class) .chatModel(chatModel) .outputKey(hrReview) .build(); ManagerCvReviewer managerReviewer AgenticServices .agentBuilder(ManagerCvReviewer.class) .chatModel(chatModel) .outputKey(managerReview) .build(); TeamMemberCvReviewer teamReviewer AgenticServices .agentBuilder(TeamMemberCvReviewer.class) .chatModel(chatModel) .outputKey(teamMemberReview) .build(); var executor Executors.newFixedThreadPool(3); UntypedAgent parallelReview AgenticServices .parallelBuilder() .subAgents(hrReviewer, managerReviewer, teamReviewer) .executor(executor) .build(); // 2. 决策建议 AI Agent HiringDecisionProposer proposer AgenticServices .agentBuilder(HiringDecisionProposer.class) .chatModel(chatModel) .outputKey(request) .build(); // 3. 人工验证节点 HumanInTheLoop humanValidator buildHumanValidator(); // 4. 顺序编排并行评审 - 聚合 - 状态更新 - 决策建议 - 人工确认 UntypedAgent workflow AgenticServices .sequenceBuilder() .subAgents( parallelReview, new ScoreAggregator(), new StatusUpdate(), proposer, humanValidator ) .outputKey(finalDecision) .build(); // 5. 执行 MapString, Object input Map.of( candidateCv, 候选人简历内容..., jobDescription, 后端工程师岗位描述..., hrRequirements, HR 要求..., phoneInterviewNotes, 电话面试笔记... ); Object result workflow.invoke(input); System.out.println( 最终决策 ); System.out.println(result); executor.shutdown(); } }这段配置里parallelReview是 AI 并行节点ScoreAggregator和StatusUpdate是 NonAI 顺序节点proposer是 AI 节点humanValidator是人工节点。五种节点在同一个sequenceBuilder()里无缝混排这就是混合执行系统的编排能力。4. 验证请求与成功结果一次完整的中断-恢复流程配置写完得验证协同是否真的生效。我设计了一个最小可复现的验证流程重点观察三件事NonAI Agent 是否被调用、HumanInTheLoop 是否暂停、恢复后是否继续执行。4.1 准备测试输入在src/test/resources/documents/下放几个文本文件内容随意但要能触发评审逻辑# tailored_cv.txt 张三5 年后端经验精通 Java、Spring Boot、MySQL。 主导过日活百万的订单系统重构。 # job_description_backend.txt 招聘高级后端工程师要求 5 年以上 Java 经验 熟悉分布式系统有高并发经验优先。 # hr_requirements.txt 本科以上学历稳定性好薪资范围 30-40K。 # phone_interview_notes.txt 沟通清晰对分布式事务有深入理解。 期望薪资 38K可接受。4.2 运行并观察输出执行HybridWorkflow.main()控制台会依次打印ScoreAggregator 被调用输入参数 hrReview: CvReview{score0.85, feedback...} managerReview: CvReview{score0.72, feedback...} teamMemberReview: CvReview{score0.80, feedback...} ScoreAggregator 输出综合评分 0.79 StatusUpdate 被调用分数0.79 申请状态已更新为已拒绝 AI 招聘助手建议 该候选人技术基础扎实学习能力强文化契合度高。 主要风险点工作许可待确认薪资略超预算。 综合建议邀请现场面试。 请确认最终决策。 选项邀请现场面试 (I)、拒绝 (R)、保留 (H) 到这里工作流暂停了光标停在后面等待输入。这就是 HumanInTheLoop 的中断点。此时如果你去看线程栈会发现工作流线程已经释放因为async(true)不会阻塞其他任务。输入I回车工作流恢复执行 I 最终决策 I4.3 验证要点第一NonAI Agent 确实被调用了——ScoreAggregator和StatusUpdate的打印都出现了说明纯 Java 逻辑作为工作流节点生效。第二HumanInTheLoop 确实暂停了——光标等待输入没有直接跳过。第三恢复后确实继续了——输入I后拿到了最终结果。如果你想验证中断-恢复的持久化能力生产环境必须可以把responseProvider改成从数据库轮询.responseProvider(scope - { String taskId (String) scope.readState(approvalTaskId); // 轮询数据库等待人工审批24 小时超时 ApprovalTask task approvalService.waitForApproval( taskId, Duration.ofHours(24)); return task.isApproved() ? APPROVED: task.getComment() : REJECTED: task.getComment(); }) .async(true)这样即使服务重启只要审批任务还在数据库里工作流就能从暂停点恢复。这才是生产级的人机协同。5. 本篇常见错误排查401、local proxy failed、reading choices、OAuth混合执行系统跑起来后报错集中在几个地方。我按真实遇到的频率排一下。5.1 401 Unauthorized最常见。LangChain4j 抛dev.langchain4j.exception.AuthenticationException: 401原因通常是 Key 没设对。检查三件套// 打印实际使用的配置排查用 System.out.println(baseUrl baseUrl); System.out.println(apiKey (apiKey null ? null : apiKey.substring(0, 8) ...)); System.out.println(model modelName);如果apiKey是null说明环境变量TAOTOKEN_API_KEY没生效检查 IDE 的运行配置或 shell 的export。如果 Key 有值但仍 401检查 baseUrl 是否写成了https://taotoken.net/api/v1——LangChain4j 会自动补/v1写重了会变成/v1/v1/chat/completions。5.2 local proxy failed报错形如java.net.ConnectException: local proxy failed或Failed to connect to /127.0.0.1:7890。这是本地网络配置残留导致的LangChain4j 默认会读取系统代理设置。解决办法是在构建模型时显式禁用代理OpenAiChatModel.builder() .baseUrl(baseUrl) .apiKey(apiKey) .modelName(modelName) // 关键清空代理配置 .build();如果还不行检查 JVM 启动参数里有没有-Dhttp.proxyHost之类的残留或者环境变量HTTP_PROXY、HTTPS_PROXY是否指向了不可用的地址。清掉即可。5.3 reading choices 相关报错报错形如Cannot deserialize value of type ... from Object value (token JsonToken.START_OBJECT)或reading choices失败。这通常是模型返回的 JSON 结构和 LangChain4j 期望的不一致。原因可能是模型名写错比如把gpt-4o-mini写成了gpt-4o-mini-2024或者账号下没有该模型权限。排查方法打开logResponses(true)看原始响应体。如果响应体里是{error: {message: model not found}}那就是模型名问题。换成账号下可用的模型 ID 即可。5.4 OAuth 相关报错如果你用的是 Claude Code 或 Cline 这类工具可能遇到OAuth token expired或invalid_grant。这类工具走的是 OAuth 流程和 API Key 是两套认证。解决办法是在工具的设置里重新登录或者切换到 API Key 模式。TaoToken 的 API Key 在控制台的 API Keys 页面生成填到工具的 Base URL Key Model ID 三件套里即可。5.5 常见问题速查表问题现象可能原因解决方案NonAI Agent 未被调用V参数名与上游outputKey不匹配用常量类统一 key 名HumanInTheLoop 卡死不返回未设async(true)阻塞线程池加.async(true)循环不退出exitCondition无兜底maxIterations 超时保护类型转换失败上下游类型不兼容用agentAction做适配状态 key 冲突多个 Agent 写同名 key用前缀区分如step1_result401Key 未设或 baseUrl 写重检查三件套local proxy failed系统代理残留清空代理配置reading choices 失败模型名错误换可用模型 ID6. 语义一致的 CTA 与下一步混合执行系统跑通后你会发现真正的难点不在编排 API而在节点粒度设计。我的经验是能交给 NonAI Agent 的步骤越多工作流越快、越准、越便宜。每次设计一个 Agent 步骤前问自己三个问题——需要语义理解吗需要创造性生成吗结果需要 100% 确定吗三个都是否就用 NonAI Agent。HumanInTheLoop 的粒度也要控制。只在真正需要人类判断的节点介入涉及金额的决策、法律合规判断、可能影响用户权益的操作、AI 置信度低于阈值的判断。纯数据格式转换、简单的是否路由、信息查询类操作不该让人工介入否则就是审批地狱。如果你在接入模型通道时遇到问题可以直接用 TaoToken 的 API 通道Base URL 填https://taotoken.net/apiKey 在控制台生成。模型对话调试可以用 模型对话 页面快速验证长期做编码和 Agent 开发Coding Plan 更划算Key 管理在 API Keys接入细节看 接入文档。下一篇讲 A2A 协议——让不同 Agent 应用之间跨进程、跨网络通信与协作。混合执行系统解决的是单应用内的协同A2A 解决的是多应用间的协同两者结合才是完整的 Agent 生态。