资讯动态

LangChain4j+LangGraph4j低代码智能体平台设计

发布时间:2026/9/28 7:46:28 来源:尧图企业网站定制
1. 项目概述为什么一个“低代码工作流通用智能体平台”值得从零设计最近三个月我连续参与了四家不同行业客户的AI落地咨询发现一个高度一致的痛点业务部门拿着现成的大模型API却连最基础的“客户投诉自动分类工单生成负责人分派”这种三步流程都搭不起来。不是模型不行是写代码太重——要自己写状态管理、异常重试、人工干预入口、日志追踪、权限校验……最后做出来的东西业务方看不懂运维方不敢接开发团队累到想转行。这时候“基于 LangChain4j LangGraph4j 的低代码工作流通用智能体平台”就不是一句技术口号而是一条能真正把AI能力塞进业务流水线里的物理通道。LangChain4j 是 Java 生态里目前最成熟、文档最全、社区最活跃的 LLM 应用开发框架它把 Prompt 工程、工具调用Tool Calling、RAG 检索、流式响应这些高频操作封装成了可组合的组件LangGraph4j 则是它的“大脑升级包”专为解决“智能体不是单次问答而是多步骤、有状态、可中断、能回溯”的本质问题而生。它用图Graph来建模智能体的行为逻辑节点是具体动作比如“查数据库”、“调用CRM接口”、“生成摘要”边是决策条件比如“如果查不到客户ID则跳转到人工确认节点”。这和传统 Spring Boot 写 REST API 的思维完全不同——你不再定义“接口怎么进、数据怎么出”而是定义“这个任务该怎么一步步走完”。所谓“低代码”在这里绝不是拖拽几个按钮就完事。它指的是业务逻辑的编排、分支判断的配置、外部系统连接的声明、人工介入点的设置全部通过结构化 JSON/YAML 或可视化编辑器完成底层运行时由统一引擎解析执行开发者只需聚焦在“原子能力”的封装上。比如销售团队想加一个“根据客户历史订单预测本次采购意向”的节点他们不需要改一行 Java 代码只需要在平台里选中“预测意向”这个已注册的智能体能力填入输入字段映射订单ID → input.id再配置超时时间与失败重试策略即可。而这个能力背后可能是 LangChain4j 封装的一个带 RAG 增强的 LLM 调用链也可能是对接内部 BI 系统的 JDBC 查询对业务使用者完全透明。这个架构真正解决的是 AI 工程化落地的“最后一公里”断裂一边是大厂开源的炫酷模型和框架一边是车间主任、HRBP、区域销售总监这些真实用户。他们不关心 Transformer 层数只关心“我上传一份简历5秒内能不能给我排出Top3候选人并自动发邮件通知面试官”。平台的价值就是把 LangChain4j 的工程严谨性、LangGraph4j 的流程表达力翻译成业务语言再用一套可验证、可灰度、可审计的运行时兜住所有不确定性。接下来我会拆解这套架构到底长什么样为什么每个模块都非如此不可以及你在实际搭建时最容易在哪个环节卡住三天。2. 整体架构设计与核心思路拆解为什么必须是“LangChain4j LangGraph4j”双引擎2.1 架构全景图四层分离各司其职整个平台不是一锅炖而是严格划分为四个逻辑层每一层都有明确的边界和不可替代的作用。我画过不下二十版草图最终确认只有这种分层才能兼顾灵活性、可观测性和交付速度能力层Capability Layer这是整个平台的“肌肉”。所有能被复用的原子能力都注册在这里比如sendEmail、queryCrmById、generateSummaryWithRag。它们由 Java 开发者用 LangChain4j 编写本质是符合Tool接口规范的方法自带参数校验、错误码定义、调用耗时统计。关键点在于每个能力必须是无状态的、幂等的、可独立测试的。我见过太多团队把“查数据库发邮件更新状态”打包成一个“全能工具”结果一出错就无法定位是哪一步挂了也无法单独重试某一步。能力层只管“我能做什么”不管“我该什么时候做”。编排层Orchestration Layer这是平台的“小脑”由 LangGraph4j 全权负责。它不写任何业务逻辑只做一件事根据预定义的图结构Graph驱动能力层的工具按顺序、按条件执行并管理整个过程的状态快照State Snapshot。比如一个“合同审核”工作流图结构会定义先调用extractClauseFromPdf节点成功后并行触发checkLegalRisk和checkFinancialRisk两个节点任一失败则进入manualReview节点所有成功则走sendApprovedNotice。LangGraph4j 的核心价值在于它把这种复杂的依赖、并行、条件分支、循环、中断恢复全部抽象成图的遍历算法开发者只需声明节点和边不用手写状态机代码。低代码层Low-Code Layer这是业务人员的“操作台”。它提供两套界面一是 YAML/JSON 编辑器支持直接编写 LangGraph4j 兼容的图定义我们内部叫“Workflow DSL”二是可视化画布拖拽节点、连线、配置参数。两者实时双向同步。这里的关键设计是“能力目录”——所有能力层注册的工具会自动生成带参数说明、示例值、错误码列表的卡片业务人员选中卡片往画布一放参数表单就自动生成连下拉选项的枚举值都是从工具的Parameter注解里实时读取的。我们曾让一位没写过代码的HR专员在20分钟内搭出了“新员工入职材料自动核验”工作流她唯一需要做的就是从目录里选verifyIdCard、checkBackgroundReport、sendWelcomeEmail三个工具然后连上线、配好超时时间。运行时层Runtime Layer这是平台的“心脏”一个轻量级、高可用的 Java 进程。它负责加载编排层定义的图、实例化能力层的工具、执行 LangGraph4j 的图遍历引擎、持久化每一步的执行日志与状态快照、暴露标准的 REST/gRPC 接口供外部系统触发。它不包含任何业务代码只做调度、监控、容错。我们用 Spring Boot 打包但刻意剥离了所有 Web MVC 逻辑所有 HTTP 请求都由统一的WorkflowGateway控制器接收解析后转给 LangGraph4j 的Graph实例执行。这样做的好处是当业务量激增时你可以水平扩展运行时实例而能力层和编排层的代码完全不动。这四层之间靠严格的契约Contract通信。能力层输出的是ToolResult对象包含output返回值、error错误信息、metadata耗时、token数等编排层输入的是MapString, Object类型的 State低代码层生成的是符合GraphSchema的 JSON运行时层暴露的是POST /v1/workflows/{id}/execute这样的标准化接口。契约一旦定义清楚四层可以独立演进——前端换框架不影响后端模型升级不影响工作流定义这才是低代码可持续的生命线。2.2 为什么不是 Spring AI为什么不是纯 LangChain4j这个问题几乎每次技术评审都会被问到。我的回答很直接Spring AI 是个优秀的“胶水框架”但它本质上还是在帮你更方便地调用 LLM它没有内置的工作流状态管理能力。你可以用 Spring AI 写一个ChatClient但当你需要“先问用户要邮箱再查数据库再根据结果决定是否发优惠券中间还要支持用户随时打断”时你就得自己手写状态存储、上下文传递、会话超时清理——这恰恰是 LangGraph4j 解决的核心问题。至于纯 LangChain4j它确实提供了Runnable链式调用但那是线性的、无状态的。一个Runnable执行完输出给下一个Runnable仅此而已。它无法表达“如果A失败跳转到B如果A成功且B返回值包含‘紧急’则并发执行C和D否则串行执行E和F”。这种非线性、条件化、带状态的控制流正是 LangGraph4j 的图模型所擅长的。LangChain4j 是“积木”LangGraph4j 是“图纸”和“施工队”。没有图纸积木堆得再高也盖不出能住人的房子。我们做过对比实验用纯 LangChain4j 实现一个带人工审核节点的报销审批流代码量是 LangGraph4j 方案的 3.2 倍且所有分支逻辑都散落在if-else里新增一个“财务复核”节点需要修改 7 个地方而用 LangGraph4j只需在图定义里加一个节点、连两条边运行时自动识别并执行。前者是“编码”后者是“配置”。2.3 “通用”二字的硬约束如何避免沦为又一个定制化项目很多团队做的“智能体平台”半年后就变成了“XX银行信贷审批专用平台”或“YY电商客服专用平台”因为从第一天起就为了满足某个客户的需求把业务规则硬编码进了引擎。我们的“通用性”有三条铁律能力注册即契约任何新能力接入必须实现Tool接口并通过平台提供的ToolValidator进行校验。校验项包括方法签名是否规范、Parameter注解是否完整、是否有Description、是否声明了ToolErrorCodes。通不过校验能力根本进不了目录。这保证了所有能力在参数、错误、描述层面的一致性是低代码配置的基础。状态 Schema 强约束每个工作流的初始 State 必须是一个明确定义的 JSON Schema。比如“简历筛选”工作流其 Schema 规定必须有resumeText: string、jobTitle: string、requiredSkills: array字段。低代码编辑器会根据这个 Schema 生成表单运行时会严格校验传入的请求体。这杜绝了“这个字段有时有有时没有”的混乱也让下游系统能放心对接。运行时零业务逻辑运行时层的代码库里绝对不允许出现if (workflowId.equals(resume-screening)) { ... }这样的分支。所有差异化行为都由编排层的图定义和能力层的工具实现来承载。运行时只认“图”和“工具”不认“业务场景”。这意味着今天上线“简历筛选”明天上线“合同生成”运行时代码一行都不用改只需要发布新的图定义和新的工具 Jar 包。这三条看似增加了初期接入成本但换来的是指数级的复用可能。我们内部有个“能力集市”已有 87 个经过 QA 验证的能力来自不同团队。销售团队搭“客户尽调”工作流时直接复用了风控团队的checkCreditRisk工具和法务团队的extractClauses工具只花了 15 分钟。3. 核心细节解析与实操要点从能力封装到图定义的完整闭环3.1 能力层实操如何写出一个“生产就绪”的 LangChain4j Tool一个合格的 Tool远不止是把一段业务逻辑包进Tool注解那么简单。我总结了五个必须落实的细节少一个后期维护就会变成噩梦。第一参数校验必须前置到注解层。别指望前端或运行时帮你做。LangChain4j 支持Parameter(required true, description 客户唯一标识) String customerId但这只是文档。真正的校验要用NotBlank、Size(max32)等 Bean Validation 注解。我们还扩展了一个ValidEmail注解专门校验邮箱格式。运行时在调用前会自动触发ValidationUtil.validate(toolInput)校验失败直接返回400 Bad Request和清晰的错误信息而不是让工具执行到一半再抛IllegalArgumentException。Tool(queryCustomerProfile) public class QueryCustomerProfileTool { Autowired private CustomerService customerService; public ToolResult execute( Parameter(description 客户唯一标识, required true) NotBlank(message customerId 不能为空) Size(max 32, message customerId 长度不能超过32位) String customerId, Parameter(description 是否包含历史订单详情, required false) DefaultValue(false) Boolean includeOrders) { // 校验通过后才开始真正的业务逻辑 CustomerProfile profile customerService.findById(customerId); if (profile null) { return ToolResult.error(CUSTOMER_NOT_FOUND, 未找到ID为 customerId 的客户); } // ... 构建返回结果 return ToolResult.success(Map.of(profile, profile)); } }第二错误码必须结构化、可追溯。ToolResult.error(CODE, message)是基础但还不够。我们要求每个错误码必须在ToolErrorCodes注解里声明并附带处理建议。比如CUSTOMER_NOT_FOUND的建议是“检查客户ID是否正确或联系CRM管理员”。运行时会将这个建议一并返回给调用方前端可以直接展示给用户而不是只显示“查询失败”。第三性能指标必须自动埋点。每个 Tool 执行前后运行时会自动记录startTime、endTime、inputTokens、outputTokens、totalCost如果模型支持计费。这些数据会打上toolName、workflowId、executionId标签推送到 Prometheus。我们据此建立了“工具健康度看板”实时监控每个工具的 P95 延迟、错误率、平均 Token 消耗。当queryCrmById的延迟突然从 200ms 升到 2s告警立刻触发运维不用等业务方投诉。第四超时与重试必须声明式配置。不要在 Tool 方法里写Thread.sleep()或for (int i0; i3; i)。我们在Tool注解上扩展了timeoutSeconds 5和maxRetries 2属性。运行时会用Resilience4j的TimeLimiter和Retry来包装执行失败时自动重试并在ToolResult的metadata里记录重试次数。业务方在低代码层配置时就能看到这个工具默认支持重试且知道超时是多久。第五敏感信息必须自动脱敏。任何包含password、apiKey、idCardNumber字段的输入或输出在日志和监控中必须被***替换。我们用Sensitive注解标记这些字段运行时的LoggingInterceptor会自动扫描ToolResult的output和error字段进行正则匹配脱敏。这不仅是安全要求更是合规底线。提示新手常犯的错误是把数据库连接、HTTP 客户端等资源写在 Tool 类里导致并发时资源耗尽。正确做法是所有资源DataSource、RestTemplate都由 Spring 管理Tool 类只负责组装参数、调用服务、处理结果。Tool 实例是无状态的可以被 LangGraph4j 多次复用。3.2 编排层实操LangGraph4j 图定义的黄金法则LangGraph4j 的图定义核心就是一个MapString, Object但如何组织这个 Map决定了工作流的可维护性。我们提炼出三条“黄金法则”法则一节点命名即语义拒绝node1,step2这类占位符。每个节点名必须清晰表达其职责且遵循动词名词格式如fetchCustomerData、validateResumeContent、sendApprovalNotification。这样当工作流执行出错日志里直接显示Failed to execute node: fetchCustomerData工程师一眼就知道问题出在哪而不是去翻图定义找node1对应什么。法则二边Edge必须基于明确的条件键Condition Key。LangGraph4j 支持ConditionalEdge但新手容易滥用字符串匹配。我们强制规定所有条件分支必须基于State中的一个特定字段且该字段的值必须是预定义的枚举。例如一个审核节点的输出 State 中必须有一个reviewResult: string字段其值只能是APPROVED、REJECTED、NEEDS_REVIEW。边的条件就写成state.get(reviewResult).equals(APPROVED)。这比state.get(output).toString().contains(通过)可靠一万倍也便于后续做自动化测试。法则三人工干预节点必须有“状态锚点”。这是低代码平台的灵魂。当工作流走到manualReview节点时它不能只是暂停必须把当前完整的 State包括所有已执行步骤的输出、原始输入、执行路径序列化成一个唯一的reviewId存入数据库并生成一个带时效性的reviewUrl。业务人员点击链接看到的不是一个空白表单而是所有上下文数据都已预填好他只需勾选“通过/驳回”并填写意见。提交后reviewUrl的回调接口会用reviewId找回 State注入reviewDecision和reviewComment字段然后 LangGraph4j 自动从manualReview节点继续向下执行。这个reviewId就是状态锚点它让“人机协同”真正落地。下面是一个真实的“销售线索分配”工作流图定义YAML 格式展示了上述法则的应用# workflow-id: sales-lead-distribution schema: input: leadId: string leadSource: string # enum: [WEB, CALL, EVENT] leadScore: number output: assignedTo: string assignmentReason: string nodes: - id: fetchLeadDetails tool: queryLeadById input: leadId: $$.input.leadId - id: calculateAssignmentScore tool: calculateLeadScore input: leadSource: $$.input.leadSource leadScore: $$.input.leadScore historicalConversionRate: $$.nodes.fetchLeadDetails.output.historicalConversionRate - id: determineAssignmentRule tool: determineAssignmentRule input: calculatedScore: $$.nodes.calculateAssignmentScore.output.score - id: assignToSalesRep tool: assignToSalesRep input: leadId: $$.input.leadId rule: $$.nodes.determineAssignmentRule.output.rule score: $$.nodes.calculateAssignmentScore.output.score - id: sendAssignmentNotification tool: sendEmail input: to: $$.nodes.assignToSalesRep.output.assignedTo subject: 新线索分配通知 body: $$.nodes.assignToSalesRep.output.assignmentMessage - id: manualOverride tool: createManualReview input: workflowId: sales-lead-distribution state: $$ reason: 自动分配规则未覆盖此线索类型 edges: - from: fetchLeadDetails to: calculateAssignmentScore - from: calculateAssignmentScore to: determineAssignmentRule - from: determineAssignmentRule condition: $$.nodes.determineAssignmentRule.output.rule MANUAL to: manualOverride - from: determineAssignmentRule condition: $$.nodes.determineAssignmentRule.output.rule ! MANUAL to: assignToSalesRep - from: assignToSalesRep to: sendAssignmentNotification - from: manualOverride to: sendAssignmentNotification注意manualOverride节点的input.state: $$这个$$.nodes.xxx是 LangGraph4j 的变量引用语法$$代表整个当前 State。createManualReview工具会把这个 State 存库并生成 URL。整个图定义没有任何一行 Java 代码但已经完整表达了业务逻辑。3.3 低代码层实操可视化画布背后的“双向同步”魔法很多人以为可视化画布就是个花架子背后还是得写 JSON。我们的画布之所以能成为生产力工具核心在于“双向同步”机制的设计。同步的起点是 Schema。当一个新工作流被创建低代码层首先向运行时发起GET /v1/capabilities请求获取所有已注册工具的元数据名称、描述、参数列表、错误码。然后它根据这些元数据动态生成一个完整的WorkflowSchema这个 Schema 描述了“哪些节点可用”、“每个节点有哪些参数”、“参数的类型和约束”。画布的所有操作都严格遵循这个 Schema。拖拽节点时画布不是简单地在画布上放一个图标。它会根据选中的工具从WorkflowSchema中读取其参数定义自动生成一个参数配置弹窗。这个弹窗里的每一个输入框都绑定了一个BindingExpression比如input.leadId绑定到$.input.leadIdoutput.assignedTo绑定到$.nodes.assignToSalesRep.output.assignedTo。用户在弹窗里填的值会实时转换成符合 LangGraph4j 要求的input或output映射表达式。连线时画布会智能提示。当从determineAssignmentRule节点拖出一条线画布会分析该节点的输出 Schema发现它有rule: string字段于是只允许你连到那些input参数中声明了rule字段的节点如assignToSalesRep并自动填充input.rule: $$.nodes.determineAssignmentRule.output.rule。如果你试图连到一个没有rule字段的节点画布会红色高亮并提示“目标节点不接受 rule 参数”。保存时画布会执行一次完整的 Schema 校验。它会检查所有节点的参数是否都已配置没有required参数为空、所有连线的表达式是否语法正确$$.xxx是否指向了真实存在的节点和字段、是否存在环路A→B→C→A、是否有孤立节点没有入边也没有出边。校验失败保存按钮置灰并给出精确到字段的错误提示。打开一个已有工作流时画布会先加载其 YAML 定义然后逐行解析根据nodes数组创建节点根据edges数组创建连线并根据每个节点的input表达式反向填充参数配置弹窗里的值。用户看到的就是他上次编辑时的样子而不是一堆 JSON。这套机制的代价是前期开发投入巨大但回报是业务人员真的可以“所见即所得”。我们曾让一位刚入职两周的实习生在没有看任何文档的情况下用画布修复了一个因参数名拼写错误导致的失败工作流——她只是把画布上标红的lead_id改成了leadId保存问题就解决了。4. 实操过程与核心环节实现从零搭建一个可运行的最小平台4.1 环境准备与依赖管理Maven 的精准拿捏平台基于 JDK 17 和 Spring Boot 3.2 构建。LangChain4j 和 LangGraph4j 的版本选择是第一个需要谨慎决策的点。截至 2024 年 10 月我们锁定的组合是io.github.langchain4j:langchain4j-core:0.32.0io.github.langchain4j:langchain4j-spring-boot-starter:0.32.0io.github.langchain4j:langchain4j-langgraph4j:0.32.0org.springframework.boot:spring-boot-starter-web:3.2.10org.springframework.boot:spring-boot-starter-data-jpa:3.2.10com.h2database:h2:2.2.224开发用生产换 PostgreSQL为什么是 0.32.0因为这是第一个将 LangGraph4j 作为一级模块深度集成的 LangChain4j 版本之前的langchain4j-langgraph4j是一个独立的、API 不稳定的快照版。0.32.0 的Graph类提供了invoke()、stream()、astream()等完备的执行方法且State的序列化/反序列化逻辑稳定不会像早期版本那样因为State里包含了 Lambda 表达式而导致 JSON 序列化失败。Maven 的pom.xml中最关键的配置是langchain4j-spring-boot-starter的自动配置。它会自动扫描所有Tool注解的类并注册为 Spring Bean。但我们禁用了它默认的ChatMemory配置因为我们的工作流状态由 LangGraph4j 自己管理不需要额外的会话记忆。在application.yml中我们显式关闭langchain4j: chat-memory: disabled: true同时我们启用了langgraph4j的调试模式方便开发期查看图的执行轨迹langgraph4j: debug: enabled: true log-level: DEBUG这个配置会让 LangGraph4j 在每一步执行前后打印详细的Node ID、Input State、Output State、Execution Time是排查逻辑错误的第一手资料。4.2 能力层实现一个可立即复用的 RAG 工具示例让我们动手实现一个最常用的能力searchKnowledgeBase它能基于用户问题在企业知识库中检索最相关的几条内容。这是 RAG检索增强生成的基石。首先定义工具类Component Tool(searchKnowledgeBase) public class SearchKnowledgeBaseTool { private static final Logger log LoggerFactory.getLogger(SearchKnowledgeBaseTool.class); Autowired private VectorStore vectorStore; // 我们用 Qdrant 作为向量数据库 Autowired private EmbeddingModel embeddingModel; // 用 BGE-M3 模型 /** * 在知识库中检索与问题最相关的内容片段 * param question 用户提出的问题 * param topK 返回的最相关片段数量默认为3 * return 检索到的片段列表每个片段包含 content 和 source */ public ToolResult execute( Parameter(description 用户提出的问题用于检索, required true) NotBlank(message question 不能为空) String question, Parameter(description 返回的最相关片段数量, required false) Min(value 1, message topK 至少为1) Max(value 10, message topK 最多为10) DefaultValue(3) Integer topK) { long startTime System.currentTimeMillis(); try { // 1. 将问题嵌入为向量 Embedding queryEmbedding embeddingModel.embed(question).content(); // 2. 在向量库中搜索最相似的 topK 个片段 ListDocument relevantDocs vectorStore.search(SearchRequest.builder() .queryEmbedding(queryEmbedding) .maxResults(topK) .build()) .map(SearchResponse::documents) .orElse(Collections.emptyList()); // 3. 构建返回结果 ListMapString, String results relevantDocs.stream() .map(doc - Map.of( content, doc.text(), source, doc.metadata().get(source).toString(), score, doc.score().toString() )) .collect(Collectors.toList()); long endTime System.currentTimeMillis(); log.debug(RAG search completed. Question: {}, TopK: {}, Results: {}, Cost: {}ms, question, topK, results.size(), (endTime - startTime)); return ToolResult.success(Map.of(results, results)); } catch (Exception e) { long endTime System.currentTimeMillis(); log.error(RAG search failed. Question: {}, TopK: {}, Error: {}, question, topK, e.getMessage(), e); return ToolResult.error(RAG_SEARCH_FAILED, 知识库检索失败请稍后重试。错误详情 e.getMessage()); } } }这个工具的关键点在于参数校验完备NotBlank、Min、Max、DefaultValue一个不少。错误处理专业捕获所有Exception记录详细日志并返回结构化的错误码RAG_SEARCH_FAILED。性能可观测手动记录startTime/endTime并在日志中打印耗时为后续优化提供依据。返回结构清晰results是一个ListMap每个 Map 包含content、source、score前端可以直接渲染。部署时只需把这个类编译进一个独立的tools-knowledge-base-1.0.0.jar然后放到运行时的classpath下或者通过 Spring Boot 的spring-boot-devtools热加载它就会自动出现在低代码层的“能力目录”里。4.3 运行时层实现一个极简但健壮的 WorkflowGateway运行时的核心是WorkflowGateway。它是一个 Spring MVC 的RestController只暴露一个POST /v1/workflows/{id}/execute接口。它的代码量很少但责任重大。RestController RequestMapping(/v1/workflows) public class WorkflowGateway { Autowired private WorkflowRegistry workflowRegistry; // 管理所有已加载的工作流图 Autowired private ObjectMapper objectMapper; // Jackson ObjectMapper用于序列化/反序列化 State PostMapping(/{id}/execute) public ResponseEntityWorkflowExecutionResult executeWorkflow( PathVariable String id, RequestBody MapString, Object input, RequestHeader(value X-Request-ID, required false) String requestId) { // 1. 生成唯一 executionId String executionId UUID.randomUUID().toString(); if (requestId null) { requestId executionId; } // 2. 从注册中心获取对应的工作流图 Graph graph workflowRegistry.getGraph(id); if (graph null) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(WorkflowExecutionResult.error(WORKFLOW_NOT_FOUND, 未找到ID为 id 的工作流)); } // 3. 构建初始 State MapString, Object initialState new HashMap(); initialState.put(input, input); initialState.put(executionId, executionId); initialState.put(requestId, requestId); initialState.put(startTime, Instant.now().toString()); // 4. 执行图 try { // LangGraph4j 的 invoke 方法会返回最终的 State MapString, Object finalState graph.invoke(initialState); // 5. 提取业务输出 Object output finalState.get(output); if (output null) { output Collections.emptyMap(); } return ResponseEntity.ok(WorkflowExecutionResult.success(output, finalState)); } catch (Exception e) { // 6. 捕获所有运行时异常统一处理 log.error(Workflow execution failed. ID: {}, ExecutionID: {}, Error: {}, id, executionId, e.getMessage(), e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(WorkflowExecutionResult.error(WORKFLOW_EXECUTION_FAILED, 工作流执行失败请联系管理员。ExecutionID: executionId)); } } }这个WorkflowGateway的精妙之处在于它的“无侵入性”它不关心input里具体是什么字段只负责把它塞进initialState.input。它不解析finalState的结构只负责把整个finalState作为元数据返回让调用方自己去取output。它用UUID生成executionId并透传X-Request-ID为全链路追踪Tracing打下基础。它的异常处理只做两件事记录日志、返回标准错误响应。所有具体的业务错误都由各个 Tool 自己产生并返回WorkflowGateway只做“信使”。这个设计让运行时层真正做到了“只做调度不做业务”为未来的水平扩展和高可用部署铺平了道路。4.4 低代码层实现用 React 构建一个可工作的画布原型低代码层的前端我们选用 React 18 TypeScript Ant Design。核心是ReactFlow这个库它提供了强大的节点、连线、缩放、拖拽能力。画布的初始化是从GET /v1/workflows/{id}获取 YAML 定义开始的。我们写了一个YamlParser工具类它能把上面那个“销售线索分配”的 YAML解析成ReactFlow所需的Node[]和Edge[]数组// Node 类型定义 interface WorkflowNode { id: string; type: tool | manual; data: { label: string; toolName: string; parameters: Recordstring, string; // key: parameter name, value: binding expression }; } // Edge 类型定义 interface WorkflowEdge { id: string; source: string; // source node id target: string; // target node id animated: boolean; label?: string; condition?: string; // e.g., $.nodes.determineAssignmentRule.output.rule MANUAL } // 解析函数 const parseYamlToReactFlow (yamlString: string): { nodes: WorkflowNode[], edges: WorkflowEdge[] } { const yamlObj jsYaml.load(yamlString) as any; const nodes: WorkflowNode[] yamlObj.nodes.map((node: any) ({ id: node.id, type: node.tool createManualReview ? manual : tool, data: { label: node.id,

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑