☰
Flowable工作流引擎集成大模型LLM节点实战指南
2026/10/1 5:23:15 网站建设 项目流程

Flowable 工作流中如何接入大模型 LLM 节点

最近在做一个内部审批系统的改造,需求很直接:原来的工作流只有“如果年龄大于60走A分支,否则走B分支”这类哑逻辑,碰到要“看一眼附件内容再判断”的场景完全抓瞎。比如合同审批里,业务员交上来一份扫描件,流程只能靠人工点按钮往下推,推完领导还得自己翻三页PDF找风险条款。

所以我们决定在 Flowable 工作流引擎里接入大模型,让流程节点具备“读了内容后做判断”的能力。这个需求在圈子里越来越常见,很多人想在 BPM 流程里加一个“AI节点”,但不知道从哪下手。我这次把完整方案跑了一遍,踩了不少坑,把能直接用的东西整理出来,希望能帮到正打算做这件事的人。

Flowable 本身是 Java 生态里很有代表性的开源工作流引擎,支持 BPMN 2.0 规范。它可以帮我们管理流程状态、任务分发、权限控制这些事,但它不关心业务数据的内容。而大模型 LLM 恰好相反,它擅长理解文本、摘要、分类、抽取和生成,但对“走到哪一步了”“该谁审了”完全没有概念。把两者接起来,本质上是一个朴素的想法:让工作流里的每个节点,既能按规则流转,又能理解规则之外的非结构化信息。

1. 为什么要把大模型节点搬进 Flowable,而不是换一个“AI工作流工具”

先说清楚这个问题,很多团队一听到“AI工作流”,第一反应是上 Dify、n8n、Coze 这类现成的编排工具。这些工具确实好用,拖拖拽拽就能搭一个带模型的流程,页面还漂亮。但它们解决的是“内容处理管线”的问题,不是“企业业务流程”的问题。

举一个我遇到的实际场景:差旅报销单,员工上传了发票 PDF,系统需要判断“这张发票是不是本次差旅产生的、金额是否超出部门预算”,然后再走财务复审。如果用 Dify 搭,消息进来跑一轮模型、抽个结果、返回给用户,这件事就结束了。但企业里面,这单需要先给直属主管批、再到财务备案、最后出纳打款,每一步都有角色权限、时限提醒、历史留痕,还要和公司现有的 OA 组织架构打通。这些是典型的工作流引擎职责,换成 AI 编排工具来做,等于把业务系统重写一遍。

所以我的结论是:不是谁替代谁的问题,而是 Flowable 管流程、LLM 管内容理解,两个东西是正交的。企业实际的合理路径是——流程依然是 Flowable 在跑,大模型作为一个“会思考的节点”嵌入到流程的某个环节里。

在 Flowable 里,这件事的技术底座并不复杂。核心就是把“调用 LLM”的动作封装成一个服务任务(Service Task),流程走到这个节点时,引擎自动触发 Java 代码,代码负责拼接 prompt、调用模型接口、拿回结果、写回流程变量,然后流程继续往下走。剩下的,就是处理好异步、超时、异常这些生产环境必须面对的问题。

顺带说一句,如果你还没接触过 Flowable,建议先花半天时间过一遍它的核心概念:流程定义(process definition)、流程实例(process instance)、任务(task)、执行实例(execution)、监听器(listener)、服务任务(service task)。这篇文章默认你至少有这个基础。

2. 接入前先把 Flowable 的扩展点摸清楚:Service Task、Listener 还是 External Task

Flowable 给了很多种方式让我们插入自定义代码,但“接入 LLM”这个场景并不是每种都合适。我这里把能用的几个位置挨个说一遍,你选型的时候可以直接对照。

2.1 Service Task(服务任务):承载 LLM 调用的主力节点

Service Task 是 BPMN 里专门用来“自动执行逻辑”的节点。它可以指定一个 Java 委托类,流程引擎在进入该节点时,自动实例化并调用类的 execute 方法。这个时机、触发方式、事务边界都非常明确,非常适合承载一次 LLM 调用。

看一个最简配置:

<serviceTask id="aiReviewTask" name="AI 审批初筛" flowable:delegateExpression="${llmReviewDelegate}" />

对应的委托类只要实现 JavaDelegate 接口:

@Component("llmReviewDelegate") public class LlmReviewDelegate implements JavaDelegate { @Override public void execute(DelegateExecution execution) { String content = (String) execution.getVariable("applyContent"); String suggestion = callLlm("你是审批助手...", content); execution.setVariable("aiSuggestion", suggestion); } }

delegateExpression用的是 Spring Bean 名称,Flowable 会从 Spring 容器里找到这个 Bean 并调用。这里有个小坑,如果你用的是flowable:class方式指定全限定类名,类的实例化和生命周期管理就不归 Spring 管,想注入别的 Service 会比较别扭。所以我推荐用delegateExpression,和 Spring Boot 配合更顺。

2.2 Execution Listener(执行监听器):适合轻量级旁路逻辑

Flowable 里还有监听器机制,可以在流程节点(或整个流程)进入、离开、结束时挂一段代码。相比 Service Task,监听器更像“切面”,它不改变流程元素本身,只是给某个节点追加行为。

举个例子,你不想修改原有 BPMN 文件,只想在“提交申请”节点之后自动调用大模型生成一个摘要字段,这时就可以挂一个 ExecutionListener。

不过监听器不适合做核心的 LLM 决策节点。因为它的语义是“监听并响应事件”,不是一个独立的处理环节。如果逻辑复杂,维护起来比较绕,而且不好在流程图上直观表达“这里有一个 AI 环节”。一般人看到流程图上的 Service Task,一眼就知道这是自动处理节点;但看到一条监听器配置,通常要翻代码才明白发生了什么。

2.3 External Task(外部任务):把调用交给独立服务

Flowable 还支持外部任务模式,流程引擎把任务发布到外部队列,由其他服务拉取后处理,再回传结果。这种方式的好处是把“流程引擎”和“LLM 调用服务”彻底解耦,适合团队里做微服务拆分时用。

但这个模式也带来额外的运维成本。你需要额外部署一个 Worker 进程,处理队列入站、轮询、结果提交、超时续约这些事。对大多数中后台系统来说,并没有必要为一次 LLM 调用就拆一个服务出来。

三种方式我用一张表总结一下:

接入方式触发语义适合场景复杂度
Service Task + JavaDelegate到达节点自动执行流程主链路上的 AI 判断、摘要、分类低,推荐
Execution Listener节点或流程事件触发旁路增强、自动填充字段低,但语义不直观
External Task引擎分发到外部队列团队有独立 AI 服务、需要跨语言中高,按需使用

所以对于绝大多数“在 Flowable 流程里加 AI 能力”的需求,直接用 Service Task 就是最稳的选型。后面的内容也围绕这条主线展开。

3. 从零实现一个 LLM Service Task 的完整路径

到了实际编码环节。我先给出一套 Spring Boot 项目里的完整代码骨架,然后讲解每一步的设计思路。这里以调用 OpenAI 兼容接口为例,这套写法同样适配国内主流的模型服务商,因为它们的接口格式基本对齐 OpenAI。

3.1 环境准备与依赖引入

假设你有一个 Spring Boot 3.x 项目,需要引入 Flowable 的 Starter:

<dependency> <groupId>org.flowable</groupId> <artifactId>flowable-spring-boot-starter</artifactId> <version>7.1.0</version> </dependency>

再准备一个 HTTP 客户端,可以用 Spring 自带的 RestClient(Spring Boot 3.2+)或者 OkHttp。直接拿 JDK 自带 HttpClient 也行,我这里用 RestClient,简洁一点:

@Service public class LlmGateway { private final RestClient restClient = RestClient.builder() .baseUrl("https://api.example.com/v1") .defaultHeader("Authorization", "Bearer " + System.getenv("LLM_API_KEY")) .build(); public String chat(String systemPrompt, String userContent, double temperature) { Map<String, Object> payload = new HashMap<>(); payload.put("model", "gpt-4o-mini"); payload.put("messages", List.of( Map.of("role", "system", "content", systemPrompt), Map.of("role", "user", "content", userContent) )); payload.put("temperature", temperature); Map<String, Object> resp = restClient.post() .uri("/chat/completions") .body(payload) .retrieve() .body(new ParameterizedTypeReference<Map<String, Object>>() {}); // 解析 choices[0].message.content List<?> choices = (List<?>) resp.get("choices"); Map<?, ?> first = (Map<?, ?>) choices.get(0); Map<?, ?> message = (Map<?, ?>) first.get("message"); return (String) message.get("content"); } }

提示:API Key 千万别写死在代码里。用环境变量或者配置中心管理,这算是一个基础但经常有人踩的规范问题。

在实际项目里,我更推荐用 Spring AI Alibaba 或 LangChain4j 这类框架来封装模型调用,它们把接口调用、JSON 解析、函数调用的细节都统一处理了,代码能少写一半。但如果只是做一次简单接入,自己封装一个 RestClient 反而更直观,出问题时更好排查。

3.2 定义 BPMN 流程:一个带 AI 节点的审批流程示例

下面是一个简化的流程定义。它表示:用户提交申请 → AI 自动初筛 → 判断初筛结果是否通过 → 通过则自动归档,否则转人工复审。

<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:flowable="http://flowable.org/bpmn"> <process id="aiApprovalProcess" name="AI 辅助审批流程" isExecutable="true"> <startEvent id="start" /> <sequenceFlow id="flow1" sourceRef="start" targetRef="aiReview" /> <serviceTask id="aiReview" name="AI 初筛" flowable:delegateExpression="${llmReviewDelegate}" /> <sequenceFlow id="flow2" sourceRef="aiReview" targetRef="preJudgement" /> <exclusiveGateway id="preJudgement" /> <sequenceFlow id="flowPass" sourceRef="preJudgement" targetRef="autoArchive"> <conditionExpression xsi:type="tFormalExpression"> ${aiSuggestedPass == true} </conditionExpression> </sequenceFlow> <sequenceFlow id="flowReject" sourceRef="preJudgement" targetRef="manualReview"> <conditionExpression xsi:type="tFormalExpression"> ${aiSuggestedPass == false} </conditionExpression> </sequenceFlow> <serviceTask id="autoArchive" name="自动归档" flowable:delegateExpression="${archiveDelegate}" /> <userTask id="manualReview" name="人工复审" flowable:assignee="${reviewer}" /> <endEvent id="end" /> </process> </definitions>

这个流程里最关键的节点就是aiReview。它执行完以后,往流程变量里写入aiSuggestedPass,网关根据这个变量决定走向。

3.3 实现 JavaDelegate:把流程变量变成 prompt,再把结果写回流程

委托类是核心中的核心。它的任务是:

  1. 从当前执行上下文里取出业务数据;
  2. 构造一个清晰的 prompt;
  3. 调用模型;
  4. 把结果转换、校验、回写到 execution 中。

直接看代码:

@Component("llmReviewDelegate") public class LlmReviewDelegate implements JavaDelegate { private final LlmGateway llmGateway; public LlmReviewDelegate(LlmGateway llmGateway) { this.llmGateway = llmGateway; } @Override public void execute(DelegateExecution execution) { // 1. 读取流程变量 String applicant = (String) execution.getVariable("applicant"); String applyContent = (String) execution.getVariable("applyContent"); BigDecimal amount = (BigDecimal) execution.getVariable("amount"); // 2. 构造 system prompt String systemPrompt = """ 你是公司内部的审批助手。你会收到一份业务申请材料,需要你判断是否建议通过。 请严格按以下 JSON 格式输出结果,不要输出任何解释: {"pass": true 或 false, "reason": "一句话理由", "riskPoints": ["风险1", "风险2"]} 注意:金额超过 10000 元时,pass 必须为 false。 """; // 3. 拼接用户内容 String userContent = "申请人:" + applicant + "\n申请金额:" + amount + "\n申请事由:" + applyContent; // 4. 调用模型 String rawResult = llmGateway.chat(systemPrompt, userContent, 0.2); // 5. 解析 JSON 并校验 LlmReviewResult result = parseJson(rawResult); // 6. 写回流程变量 execution.setVariable("aiSuggestedPass", result.isPass()); execution.setVariable("aiReviewReason", result.getReason()); execution.setVariable("aiRiskPoints", String.join(",", result.getRiskPoints())); } }

这里有个容易被忽略的细节:流程变量名和模型返回的字段名要提前约定好。因为在网关表达式里写的就是${aiSuggestedPass},一旦变量名改掉,整个流程路由就失效了,而且这种错误通常不会在部署时报错,只会在运行时表现为“流程走了错误分支”。

3.4 Prompt 设计:三个维度的信息边界

我最近在处理几个 LLM 接入项目时,一直在用一套三段式 prompt 组织思路,和你分享:

  • Key(我是谁):系统提示词里定义好模型角色、职责边界、输出格式约束;
  • Query(我在找什么):把当前节点的业务目标写清楚,也就是“这次调用要模型产出什么决策”;
  • Value(我能提供什么):把流程上下文(表单数据、历史审批意见、人员信息)作为价值内容打包提供给模型。

对应到上面的代码:

Key(系统角色):你是公司内部审批助手... Query(任务目标):判断是否建议通过,按 JSON 输出... Value(上下文材料):申请人 + 金额 + 申请事由...

这个结构帮我想清楚了很多问题。之前经常出现 prompt 里塞了一堆不相关的字段,模型反而被带偏。如果每次写 prompt 前都能问自己“我是谁、我要找什么、我能提供什么”,上下文污染的问题会少很多。

3.5 部署启动与跑通流程

代码写完之后,启动 Spring Boot 应用,Flowable 会自动扫描并部署 classpath 下的processes/*.bpmn20.xml文件。启动过程中,它会校验 XML 语法、检查委托类是否存在。

跑通流程的测试代码可以这样写:

runtimeService.startProcessInstanceByKey("aiApprovalProcess", Map.of( "applicant", "张三", "applyContent", "申请购买一台测试服务器,用于搭建预发布环境", "amount", BigDecimal.valueOf(8000) ));

启动后观察日志,正常情况下会看到:

flowable - Processing service task aiReviewTask

然后查看流程变量:

List<HistoricVariableInstance> vars = historyService .createHistoricVariableInstanceQuery() .processInstanceId(processInstanceId) .list();

如果走到这一步,说明模型返回结果已经成功回写到流程变量里,后续网关分支也能正确路由了。

4. 同步调调用是最大的坑:异步服务和错误兜底怎么设计

第一次把 LLM 节点部署上去,大概率会遇到一个打脸的场景:同步调模型,超时。模型服务不像数据库,耗时通常在两秒到十几秒不等,如果流量一大,几十个流程同时卡在 AI 节点上,数据库连接池会被长时间占用,整个系统都可能被拖垮。

这个问题的解法就是 Flowable 的异步执行能力。

4.1 让 Service Task 异步执行

在 BPMN XML 的<serviceTask>上加一句:

<serviceTask id="aiReview" name="AI 初筛" flowable:delegateExpression="${llmReviewDelegate}" flowable:async="true" />

加上flowable:async="true"以后,Flowable 引擎执行到这个节点时,不会同步在当前线程里调用委托类,而是把任务挂到异步执行器的队列中。这个异步执行器是引擎自带的一个线程池,可以在配置里调整:

flowable.async-executor-activate=true flowable.async-executor.core-pool-size=10 flowable.async-executor.max-pool-size=20 flowable.async-executor.default-async-job-acquire-wait-time=10

它带来的两个直接好处:

  1. 流程实例的推进和 LLM 调用解耦。即使模型服务慢或不可用,当前请求线程也能及时返回,后续逻辑由后台线程继续执行;
  2. 失败重试变成默认行为。引擎对异步任务有默认重试机制,默认重试次数是 3 次,每次重试的时间间隔由default-retry-time-cycle控制。

这里需要特别提醒一句:如果 LLM 服务不是你自己的,务必配置一下重试次数,因为模型服务的瞬时故障太多了,重试能救回相当一部分流程。

flowable.async-executor.retry-times=3

4.2 事务边界:不要在数据库事务里打外部接口

Flowable 的 Service Task 默认和流程引擎的事务绑定。在同步模式下,委托类里的所有业务逻辑都在同一个事务里,如果委托类中调了外部模型接口,事务会长时间挂着,锁定资源。

所以我的建议是:LLM 调用节点统一走异步模式。异步模式下,Flowable 会在内部做事务分段——任务入队是一个事务,执行又是一个事务。即使模型调用失败,也不会影响主流程已经完成的数据库操作。

如果你因为需求原因只能同步调用(比如必须在用户请求内返回结果),那建议至少给模型接口做一层本地超时:

HttpClient client = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(3)) .build();

并且把读取超时设为 5 秒,超过就抛异常。千万不要让 HTTP 请求无限等下去。

4.3 模型调用失败的三种常见情况与对策

在 Flowable 的日志里,最常看到的守护任务失败日志指向三种情况:

第一种:超时。模型服务端排队时间长,或网络不稳定。对策是重试。可以专门捕获超时异常,并给委托类标记一个重试次数,达到上限后直接把流程推到人工节点。

第二种:token 超限。输入材料太长,超出模型上下文窗口。对策是先在外部做文本截断或摘要,把长文本压缩。我们在实际项目里最稳妥的做法是,调用模型前先对“申请事由”做超长截断,保证交给模型的文本不超过 3000 字。

第三种:返回格式非预期。模型偶尔会“发挥”,返回的 JSON 里多了注释、少了字段,或者直接变成了一句话。解析失败后有两种处理:一是让模型重试一次,用“输出格式必须是合法 JSON,没有其他内容”来纠正;二是解析失败就默认走人工兜底。

基于这些情况,一个健壮的 Service Task 设计应该是这样:

@Override public void execute(DelegateExecution execution) { try { LlmReviewResult result = doCallAndParse(execution); execution.setVariable("aiSuggestedPass", result.isPass()); execution.setVariable("aiReviewReason", result.getReason()); execution.setVariable("aiStatus", "SUCCESS"); } catch (Exception e) { execution.setVariable("aiStatus", "FAILED"); execution.setVariable("aiError", e.getMessage()); // 不抛异常,让流程继续走,由网关判断 aiStatus 决定是否转人工 } }

这里的关键设计是:AI 节点失败不应该让整个流程卡死。捕获异常后把状态写入变量,由后续网关分支决定是走人工补救还是终止流程。这比把异常往上抛、让引擎死等更符合生产要求。

<exclusiveGateway id="aiResultGateway" /> <sequenceFlow sourceRef="aiResultGateway" targetRef="manualReview"> <conditionExpression>${aiStatus == 'FAILED'}</conditionExpression> </sequenceFlow> <sequenceFlow sourceRef="aiResultGateway" targetRef="preJudgement"> <conditionExpression>${aiStatus == 'SUCCESS'}</conditionExpression> </sequenceFlow>

4.4 幂等性:重试导致重复调用的风险

Flowable 异步任务的自动重试是个强力特性,但也带来了一个副作用:模型调用可能在同一流程里执行多次。虽然大模型调用不像转账接口那样有资金风险,但 token 是按调用次数计费的,重复调用等于烧钱。

处理方式有两种:

一种是通过流程变量做幂等标记。执行开始时先检查变量,如果存在就不重复计算:

if (execution.getVariable("aiAlreadyCalled") != null) { return; } execution.setVariable("aiAlreadyCalled", true);

另一种是控制重试粒度。如果模型调用彻底失败,我们其实希望流程走人工兜底,而不是反复重试。可以在委托类里对特定类型的异常(如校验失败)直接标记失败,只有网络超时类异常才抛出给 Flowable 去重试。

这个细节在模型接入项目里很值得做——一方面是费用,另一方面是避免同一次审批里模型给出两个不同结果,影响人工评审的判断。

5. 进阶玩法:让 LLM 节点真正参与流程决策与人工辅助

最后聊几组我们验证过的进阶用法,这些事情做完,AI 节点就不只是“调用一下模型返回个字符串”,而是真正融入到业务流程里。

5.1 两段式调用:先摘要,再决策,结果更可控

一次调用让模型直接做决策,容易因为上下文太长把模型“绕晕”。我建议把节点拆成两步:

第一步,把申请的全文、附件文本做提炼,生成一个结构化的摘要(关键要素、金额、周期、风险摘要);第二步,基于这份摘要再做是否通过的判断。

比如 Delegate 里先调用一次summaryLlm(),拿到摘要;再调用一次decisionLlm(),传入摘要返回判断。

虽然多了一层调用,但实际效果往往比单次调用更稳定。

String summary = llmGateway.chat("你是信息抽取助手,提炼以下内容的要素", content, 0.3); String decision = llmGateway.chat("基于摘要判断是否通过,输出 JSON", summary, 0.2);

关键原因是:大模型在处理“先浓缩再判断”的链路时,注意力更集中。尤其是采购、合同、报销这种动辄几千字的输入,一上来就让模型直接决策,很容易漏掉藏在后面的限制条款。

5.2 接入人工审批场景:AI 预审意见先写进任务表单

不是所有流程都要让 AI 直接拍板。很多场景里,AI 只是给审批人一个“初筛意见”,最终拍板还是人。

做法是:在调用完 LLM 后,往流程变量写 AI 的结论和建议;然后在人工审批节点的任务表单里,把 AI 意见展示给审批人。

比如 User Task 的候选人看到表单时,额外渲染几个字段:

  • AI 初审结论:建议通过 / 建议拒绝
  • AI 理由:xxx
  • AI 风险提示:xxx

这个实现非常简单,就是在流程变量里存好:

execution.setVariable("aiReviewComment", "建议通过,理由:申请金额在团队预算范围内,资源规格合理。");

然后在表单里读取展示即可。审批人不用从头看一遍材料,先看 AI 摘要,再决定是否认可,效率能提高一大截。

5.3 用 LLM 返回结果驱动网关:从“规则路由”到“语义路由”

传统工作流的网关分支靠的是硬编码条件,比如${amount > 10000}。但这种规则只能处理结构化数据,处理不了语义。

有了 LLM 节点之后,网关条件可以基于模型输出扩展。

比如在工单分配流程里,我们希望根据用户反馈的内容自动判断工单类型(网络故障、账号问题、支付失败、其他),流程再走不同的处理队列。用 LLM 节点做分类,把文本映射到预定义类别,然后把类别名存成流程变量:

execution.setVariable("ticketCategory", classifiedValue);

网关条件:

<sequenceFlow sourceRef="gateway" targetRef="networkTeam"> <conditionExpression>${ticketCategory == 'NETWORK'}</conditionExpression> </sequenceFlow> <sequenceFlow sourceRef="gateway" targetRef="accountTeam"> <conditionExpression>${ticketCategory == 'ACCOUNT'}</conditionExpression> </sequenceFlow>

这就是“语义路由”——流程的路由条件不再依赖数字大小,而是依赖内容的理解结果,应用面一下子宽了很多。

5.4 LLM 节点的成本控制与审计

最后讲一个容易被忽略的点:在正式把 LLM 节点上线前,一定要把费用模型搞清楚。

我的建议是给 LLM 节点做统一封装,记录三件事:调用的模型名称、消耗的 token 数、调用耗时。把这些数据写入数据库日志表,或者至少输出到日志文件。团队里没有成本意识的话,一个早上可能就跑掉几百块的 token 费用,而且前端没有任何感知。

另一个是审计要求。如果流程涉及财务、合同、合规,AI 的原始返回内容、prompt 版本、调用时间都需要留档,方便事后回溯。万一 AI 给出错误建议导致流程走错,至少能查清楚是哪一版 prompt 哪次调用出的问题。

我在项目里习惯直接建一张ai_invoke_log表,字段包括流程实例 ID、节点 ID、模型名、输入摘要、完整输出、token 数、耗时、调用结果。这张表既是审计依据,也是后面调 prompt 的样本数据来源。

6. 一些个人体会与后续打算

这次把 LLM 接进 Flowable,整体思路总结下来就是:用 Service Task 承载 AI 能力,用流程变量传递输入输出,用异步模式保稳定性,用失败兜底保证流程不中断。它没有特别炫技的地方,但每个环节都需要踩过坑才能体会到设计的重要性。

我印象最深的一点是:在做这种集成时,不要急着先写代码,先把“AI 在这个流程里到底要解决什么问题”想清楚。是分类、是抽取、是摘要,还是决策?这个答案直接决定了 prompt 结构、节点位置和后续的路由设计。

后续我计划在这个基础上再扩展两个方向:一个是在 User Task 上通过 Listener 实现 AI 辅助的自动归档建议,另一个是把多轮 LLM 调用编排成一个独立的 AI 服务,Flowable 侧只通过 HTTP 调用这个服务,进一步降低耦合。到时候有落地经验,再继续分享。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询