MuleSoft企业级AI编排:LLM集成的协议治理与韧性设计
1. 项目概述:当企业级集成平台遇上大语言模型
“AI Orchestration in Action: How MuleSoft and LLMs Fuel the Future of Enterprise AI”——这个标题不是一句空泛的宣传口号,而是我在过去18个月里亲手落地的三个核心生产系统的真实写照。它讲的不是“用LLM写个周报”,也不是“给客服加个聊天框”,而是把大语言模型真正嵌进企业血液里:让采购系统自动比对合同条款与法务知识库、让CRM里的销售线索经过多轮语义推理后触发精准的工单路由、让ERP中异常库存预警被自然语言重写成可执行的跨部门协同指令。MuleSoft在这里不是配角,它是那个在后台默默调度一切的“交响乐指挥家”——它不生成文字,但决定哪段数据该喂给哪个LLM、哪个模型输出该走哪条审批流、哪次调用失败时该降级到规则引擎还是人工兜底。我见过太多团队卡在“LLM很厉害,但不知道怎么让它进生产线”的阶段,而这个项目的核心价值,恰恰在于它提供了一套可审计、可监控、可回滚的企业级AI编排范式。如果你是架构师、集成工程师或AI产品负责人,正被“模型效果好但上线就崩”“POC很炫但无法规模化”这类问题困扰,那这篇内容就是为你写的。它不讲大道理,只讲我们踩过的坑、压测过的阈值、写进SOP的操作清单。
2. 核心设计逻辑:为什么非得是MuleSoft+LLM,而不是直接调API?
2.1 企业AI落地的三重断层,决定了不能“裸连”LLM
很多团队的第一反应是:“既然有OpenAI API,为啥还要绕一圈用MuleSoft?”这个问题我被问了至少37次。答案藏在企业真实运行的毛细血管里。我们拆解一下这三重断层:
第一重是 协议断层 。你的HR系统用的是SOAP 1.2,财务系统只认SAP IDoc,而LLM API要求JSON over HTTPS。如果让每个业务系统都自己写HTTP客户端、处理token刷新、做重试熔断,等于让所有业务团队都变成基础设施工程师。MuleSoft的Anypoint Platform天然支持200+连接器,能把SAP RFC调用、Oracle DB查询、Salesforce REST API、甚至老旧的IBM MQ消息,统一转换成标准的JSON事件流,再喂给LLM。这不是“多此一举”,而是把15个系统各自写15套HTTP胶水代码,压缩成1套可复用的集成流。
第二重是 治理断层 。LLM调用不是无成本的。一次合同审查可能触发3个模型(条款提取、风险识别、合规比对),每次调用都要计费、要审计、要限流。MuleSoft的API Manager能强制所有LLM请求走统一网关:你可以在控制台里设置“每个业务单元每月最多调用50万次GPT-4”,超限自动返回429;可以开启全链路日志,看到“销售部张三在14:22:03触发了合同分析,耗时2.3秒,消耗token 1842,命中缓存”;还能一键下线某个模型端点,而不影响其他业务。这种颗粒度的管控,是直接调用OpenAI SDK永远做不到的。
第三重是 韧性断层 。生产环境没有“永远在线”。去年Q3,我们合作的某云厂商LLM服务出现12分钟区域性中断。如果业务系统直连,这12分钟里所有合同审批都会卡死。而我们的MuleSoft流里预置了降级策略:当LLM健康检查失败时,自动切换到本地部署的Llama-3-8B(精度低20%,但100%可控),同时向运维告警。更关键的是,MuleSoft的事务管理能让整个流程具备“最终一致性”——即使LLM调用失败,前面从ERP拉取的合同PDF、后面要写入法务系统的审核记录,依然能通过异步补偿机制完成闭环。这种“故障隔离+优雅降级+状态追踪”的能力,才是企业敢把AI放进核心流程的底气。
2.2 架构选型背后的硬约束:为什么不是Kong+LangChain,也不是自研调度器?
市面上确实有更“轻量”的方案。比如用Kong做API网关+LangChain写Orchestration逻辑,或者用Airflow调度LLM任务。但我们做过三轮压测和TCO(总拥有成本)测算,最终锁定MuleSoft,原因很务实:
-
开发效率 :一个资深集成工程师用MuleSoft Studio拖拽配置,3天就能搭出带重试、熔断、日志、监控的LLM调用流;用Kong+Lua写同等逻辑,需要5天,且后续修改必须改代码、走CI/CD。在业务部门催着要“下周上线合同初筛功能”的压力下,可视化编排不是偷懒,是保命。
-
运维成熟度 :MuleSoft的Anypoint Monitoring能直接看到“LLM调用成功率99.97%,P95延迟1.8秒,错误集中在token超限(占比63%)”。而Kong的Prometheus指标需要自己定义、关联、告警。我们运维团队只有2个人,没精力维护一套定制化可观测性栈。
-
安全合规刚性要求 :金融客户明确要求“所有LLM输入输出必须经由企业DLP网关扫描”。MuleSoft的Policy框架允许我们在API网关层插入自定义Java策略,实时扫描JSON payload里的身份证号、银行卡号,发现即脱敏并阻断。LangChain跑在应用层,DLP必须侵入业务代码,违反“安全与业务解耦”原则。
-
长期演进成本 :当明年要接入内部微调的医疗大模型(需mTLS双向认证+私有VPC endpoint),MuleSoft只需新增一个HTTP连接器配置;而自研调度器要重写证书管理、VPC路由、健康检查模块。我们算过账:前6个月MuleSoft许可费比自研人力成本低42%。
所以这个选择不是技术情怀,是算出来的生存策略。就像你不会为了省几块钱螺丝钱,就放弃波音飞机的适航认证体系。
3. 核心实现细节:从零搭建一个可生产的AI编排流
3.1 环境准备与基础组件配置
在Anypoint Platform上启动一个生产级AI编排流,绝不是点几下鼠标的事。我们严格遵循“最小权限+分层隔离”原则,以下是经过审计确认的基线配置:
环境划分 :
dev:开发者沙箱,允许直连公共LLM(如OpenAI、Anthropic),但禁止访问任何生产数据库。staging:镜像生产环境,LLM调用走模拟服务(Mock Service),数据库只读副本。prod:严格隔离,所有LLM必须通过企业代理(Proxy)访问,且代理强制开启SSL解密与DLP扫描。
连接器配置要点 :
-
LLM连接器 :不使用通用HTTP连接器,而是创建专用的
llm-openai-connector。关键参数:base-url:https://api.openai.com/v1(生产环境替换为企业代理地址)timeout:connect=10000, read=30000(LLM响应波动大,读超时必须设够)retry-policy:max-attempts=3, backoff=exponential, jitter=true(指数退避防雪崩)circuit-breaker:failure-threshold=50%, rolling-window=60s, half-open-after=300s(熔断保护)
-
ERP连接器 (以SAP为例):
- 启用
RFC connection pooling,最大连接数设为20(避免LLM高并发时耗尽SAP连接) - 配置
idempotency key字段,确保同一合同ID重复提交不触发二次审批
- 启用
提示:所有连接器的敏感参数(API Key、SAP密码)必须存入Anypoint Vault,严禁硬编码。Vault策略设置为“仅prod环境可读”,dev/staging使用占位符
${vault::llm-api-key}。
API网关策略 :
在 /ai/contract-review API上启用三层策略:
Rate Limiting: 按client_id限流(销售部500次/小时,法务部2000次/小时)DataWeave Transformation: 对请求体做JSON Schema校验,强制contract_text字段长度≤10000字符(防LLM token爆炸)DLP Policy: 调用企业DLP服务,扫描contract_text中的PII,命中则返回400 Bad Request并附带脱敏建议
这套配置不是拍脑袋定的。我们用JMeter模拟了1000并发用户,发现当 read timeout 设为20秒时,P99延迟飙升至45秒(LLM偶尔卡顿),最终定为30秒——这是业务方能接受的最长等待时间。
3.2 核心编排流设计:合同智能审查的完整实现
以最典型的“采购合同智能审查”场景为例,整个MuleSoft流(Flow)分为7个原子步骤,全部在Anypoint Studio中可视化配置,无需写Java代码:
Step 1: 触发与解析
- 接收来自Salesforce的合同创建事件(Platform Event)
- 使用
JSON to Object转换器,将event payload转为Mule Message - 提取关键字段:
contract_id,vendor_name,contract_amount,contract_text(PDF文本已由前置OCR服务提取)
Step 2: 上下文增强
- 调用
salesforce-connector,根据vendor_name查出该供应商的历史合作记录、违约次数、评级 - 调用
erp-connector,获取本次采购物料的主数据(是否受出口管制?是否有替代品?) - 用
DataWeave脚本将原始合同文本、供应商信息、物料信息拼装成结构化Prompt:
{
"system_prompt": "你是一名资深法务顾问,请基于以下背景信息审查合同条款...",
"user_prompt": "合同文本:$(payload.contract_text) \n供应商历史:$(vars.vendorHistory) \n物料信息:$(vars.materialInfo)",
"temperature": 0.3,
"max_tokens": 2048
}
注意:这里不用LangChain的PromptTemplate,因为MuleSoft的DataWeave原生支持动态变量注入,且性能比调用Python脚本高3倍(实测1000TPS下延迟差12ms)。
Step 3: LLM调用与容错
- 调用
llm-openai-connector,POST到/chat/completions - 配置
on-error-propagate:当HTTP状态码为429(限流)或503(服务不可用)时,不终止流,而是进入Step 4降级分支 - 记录
correlation-id到MuleSoft的trackingId,用于全链路追踪
Step 4: 降级策略执行
- 若Step 3失败,则调用本地部署的
llama3-8b-quantized模型(Ollama API) - Prompt做简化:去掉供应商历史等长文本,只保留合同核心条款
- 设置
max_tokens=512,确保响应在2秒内返回(精度损失可接受)
Step 5: 输出结构化解析
- LLM返回的是自由文本,但下游系统需要结构化JSON。我们用正则+DataWeave双重校验:
- 先用
regex匹配"risk_level": "HIGH"等固定模式 - 再用
DataWeave的try-catch解析JSON片段,失败则触发人工审核队列
- 先用
- 最终输出标准化Schema:
{
"contract_id": "CT-2024-7890",
"risk_level": "MEDIUM",
"critical_issues": ["付款周期超过90天", "知识产权归属不明确"],
"recommendations": ["要求修改第5.2条", "增加违约金条款"]
}
Step 6: 业务决策路由
- 根据
risk_level字段,用choice-router分流:HIGH→ 发送邮件给首席法务官 + 创建Jira高优工单MEDIUM→ 推送企业微信待办 + 更新Salesforce合同状态为“需人工复核”LOW→ 自动更新合同状态为“已通过”,并触发ERP付款流程
Step 7: 审计与反馈闭环
- 将完整执行日志(含输入Prompt、LLM原始输出、结构化解析结果、耗时、token数)写入Elasticsearch
- 启用
Anypoint Monitoring的Custom Alert,当risk_level=HIGH且recommendations为空时,立即告警——这表示LLM可能在胡说,需要人工介入调优Prompt
这个7步流在生产环境稳定运行11个月,平均日调用量2.3万次,P95延迟1.9秒,错误率0.07%。最关键的是,当去年10月OpenAI服务中断时,Step 4降级成功接管了98.3%的流量,业务零感知。
3.3 关键参数调优:那些文档里不会写的实战经验
参数不是随便填的,每个数字背后都是血泪教训。以下是我们在压测和线上观察中总结的硬核参数:
| 参数 | 初始值 | 问题现象 | 调优后值 | 原因说明 |
|---|---|---|---|---|
LLM read timeout |
15s | P99延迟突增至42s,大量请求超时 | 30s | LLM生成长文本时,token流式返回间隔可达8-10秒,15s会误判为失败。30s覆盖99.2%的正常响应,且业务方接受“最长等半分钟” |
Retry max-attempts |
2 | 连续网络抖动时,2次重试后仍失败 | 3 | 实测AWS区域间网络抖动多为瞬时(<2s),3次重试+指数退避(1s, 3s, 9s)可覆盖99.8%的瞬时故障 |
Circuit breaker failure-threshold |
40% | 误熔断:单次LLM服务抖动导致整个API不可用 | 50% | 结合滚动窗口(60s)和半开状态(300s),50%阈值在“及时熔断”和“避免误伤”间取得平衡。低于40%太敏感,高于60%失去保护意义 |
ERP RFC pool size |
10 | 高峰期SAP连接耗尽,报错 RFC_NO_MORE_CONNECTIONS |
20 | 经监控发现,合同审查流平均占用SAP连接1.8秒,按1000TPS计算,理论需1800连接。但实际峰值并发约120,20连接足够,且避免SAP侧资源争抢 |
DLP scan timeout |
500ms | DLP服务偶发延迟,拖慢整体流程 | 1000ms | DLP是串行阻塞点,1000ms覆盖其P95延迟。超时则跳过扫描(业务允许),但记录审计日志供事后追溯 |
实操心得:这些参数必须和业务方一起敲定。比如
read timeout=30s,是我们拉着法务总监开了三次会才确认的——他同意“合同审查等30秒”,但拒绝“等60秒”。技术参数从来不是纯技术问题,而是业务SLA的翻译。
4. 实战问题排查:线上事故复盘与速查手册
4.1 典型故障场景与根因分析
故障1:合同审查流P95延迟从1.9秒骤升至8.7秒,持续42分钟
- 现象 :Anypoint Monitoring显示
llm-openai-connector的read time指标飙升,但http status 200比例未降 - 排查路径 :
- 查
correlation-id日志,发现延迟集中在Step 3,且Step 4降级未触发 → 排除LLM服务宕机 - 抓包分析:发现LLM返回的token流中,第327个token后出现长达5秒的静默期
- 对比测试:用curl复现相同Prompt,确认是LLM自身生成卡顿(非网络问题)
- 查
- 根因 :LLM在生成“知识产权归属”条款时,陷入循环推理,需人工干预停止
- 解决 :在
llm-openai-connector中启用stream=false(禁用流式响应),并设置stop=["\n\n"]参数,强制模型在段落结束时返回,避免无限生成
故障2:某天凌晨2点, /ai/contract-review API突然返回500,错误日志显示 java.lang.OutOfMemoryError: Metaspace
- 现象 :仅影响
staging环境,dev和prod正常;重启后恢复,但24小时后复现 - 排查路径 :
jstat -gc查看Metaspace使用率:持续增长至98%后OOMjmap -clstats分析类加载:发现com.mulesoft.connectors.llm.LlmConnectorConfig类被重复加载237次
- 根因 :开发误将LLM连接器配置放在Flow的
foreach循环内,每次循环新建连接器实例,导致类加载器泄漏 - 解决 :将连接器声明移至Flow外层,作为共享组件;添加SonarQube规则,禁止在循环内创建连接器
故障3:法务部反馈“HIGH风险合同没收到邮件”,但监控显示 Step 6 路由日志正常
- 现象 :
correlation-id日志显示risk_level=HIGH,且choice-router命中HIGH分支,但邮件服务无记录 - 排查路径 :
- 检查邮件连接器日志:发现
javax.mail.AuthenticationFailedException - 查Anypoint Vault:发现邮件密码在3天前被运维轮换,但Vault中
email-password密钥未同步更新
- 检查邮件连接器日志:发现
- 根因 :Vault密钥生命周期管理缺失,密码轮换后未触发连接器配置刷新
- 解决 :启用Vault的
key rotation webhook,密码更新时自动触发MuleSoft应用重启;建立密钥审计表,每周人工核查
4.2 常见问题速查表(一线工程师版)
| 问题现象 | 快速定位命令 | 根本原因 | 修复方案 | 预防措施 |
|---|---|---|---|---|
| LLM调用成功率下降 | anypoint-cli api-monitoring metrics get --api-id <id> --metric "http.status.4xx" --time-range "24h" |
429错误激增 → OpenAI配额用尽 | 临时切换至备用模型;联系OpenAI提升配额 | 在Anypoint中配置 quota alert ,当月用量达80%时自动邮件通知 |
| 合同文本解析失败率高 | grep "JSON parse error" /opt/mule/logs/*.log | tail -20 |
LLM输出格式不规范(如多出逗号、少引号) | 在Step 5增加 try-catch 包裹JSON解析,失败则转人工队列 |
在Prompt中强制要求“严格JSON格式,不加注释”,并用正则预清洗 |
| ERP数据拉取超时 | mule-cli app logs --app-name erp-connector --tail | grep "RFC timeout" |
SAP网关限流,单IP每分钟最多100次调用 | 为MuleSoft服务器配置SAP网关白名单IP池 | 在ERP连接器中启用 connection pooling 并设置 max-wait-time=5000 |
| DLP扫描误报率高 | curl -X POST http://dpl-service/scan -d '{"text":"张三身份证号110..."}' |
DLP规则过于宽松(如“张三”被误判为PII) | 联系DLP团队优化规则,增加上下文判断 | 在DataWeave中添加 if (payload.text contains "身份证号") then dlp-scan() else skip() |
| 流执行日志丢失 | anypoint-cli monitoring traces search --query "correlation-id:<id>" |
Anypoint Monitoring采样率设为10%,低频错误被丢弃 | 临时将采样率调至100%,定位后恢复 | 对 error 级别日志强制100%采样, info 级保持10% |
注意:所有修复必须走变更管理流程(Change Advisory Board)。我们规定,任何涉及
timeout、retry、pool-size的参数调整,都需附带JMeter压测报告,证明P95延迟未恶化。
5. 进阶实践:超越基础编排的深度整合技巧
5.1 LLM输出的可信度增强:不只是调用,更要验证
LLM的幻觉(Hallucination)是企业不敢放手的关键。我们不依赖“提示词工程”,而是用MuleSoft构建三层可信度过滤网:
第一层:事实锚定(Fact Anchoring)
在Step 2的Prompt构造中,强制LLM只能引用我们提供的“事实锚点”。例如:
- 锚点1:
《2024年供应商管理办法》第3.2条:付款周期不得超过60天 - 锚点2:
《信息安全条例》第7.1条:源代码所有权归甲方所有 - Prompt指令:
请仅基于以上两条法规审查合同,不得编造其他条款
这样,LLM的输出必然受限于锚点,幻觉空间被物理压缩。
第二层:交叉验证(Cross-Validation)
对高风险合同,不只调用一个模型。我们的流会并行触发:
- GPT-4:负责条款解读与风险分级
- Claude-3:负责法律依据溯源(返回具体法条编号)
- 本地Llama-3:负责中文语义一致性检查(对比GPT-4和Claude-3结论是否冲突)
只有当3个模型结论一致(或2/3共识),才进入Step 6路由;否则标记为NEED_HUMAN_VERIFY。
第三层:执行反推(Execution Reversal)
在Step 5解析出 recommendations 后,用DataWeave反向生成“执行验证Prompt”:
- 输入:
"要求修改第5.2条" - 输出:
"如果第5.2条已修改为'付款周期为30天',是否还存在HIGH风险?请回答YES或NO"
调用LLM二次验证。若返回YES,则说明原建议无效,自动升级至人工。
这套组合拳将LLM幻觉导致的误判率从12.7%降至0.9%,且所有验证步骤耗时控制在800ms内(实测P95)。
5.2 与企业知识库的动态耦合:让LLM真正“懂你”
很多企业买了Confluence、SharePoint,却只当文档库用。我们用MuleSoft把它变成LLM的“活体大脑”:
知识库热更新流 :
- 每日凌晨2点,触发
knowledge-sync-flow:- 调用Confluence REST API,拉取所有标记
ai-ready标签的页面 - 用
pdf-exporter插件将页面转为PDF,再用pdf-parser提取文本 - 用
sentence-transformers模型(本地部署)生成文本向量,存入Milvus向量库
- 调用Confluence REST API,拉取所有标记
- 同时,更新Anypoint Vault中的
knowledge-last-updated时间戳
LLM调用时的动态注入 :
在Step 2的Prompt构造中,不再硬编码法规条文,而是:
- 根据
contract_type(如“软件采购”、“设备租赁”)查询Milvus,召回Top3相关知识片段 - 将召回片段作为
context注入Prompt - 指令LLM:“请严格基于以下知识片段回答,未提及的内容请回答‘依据不足’”
这样,当法务部更新了《云服务采购指南》,24小时内LLM就能基于最新版本工作,无需重新训练模型。我们测算过,知识库动态耦合使LLM在专业领域问题上的准确率提升34%,且完全规避了“模型训练数据过期”的顽疾。
5.3 成本精细化管控:把每一分钱的LLM调用都管起来
LLM不是水电煤,必须精打细算。我们在MuleSoft中实现了三级成本管控:
一级:API网关级限额
- 按业务单元(
client_id)设置月度token预算:- 销售部:200万token(用于线索评分)
- 法务部:500万token(用于合同审查)
- IT部:50万token(用于IT工单分类)
- 预算用尽时,API网关返回
403 Forbidden,并附带{"reason":"token_quota_exceeded","remaining_days":7}
二级:流内级计量
- 在Step 3调用LLM后,用
DataWeave解析OpenAI响应头x-ratelimit-remaining-tokens,计算本次调用消耗token数 - 将
{contract_id, consumed_tokens, model_name, timestamp}写入Kafka,供财务系统对账
三级:模型级降级
- 配置
model-fallback-policy:- 当GPT-4调用成本 > $0.02/次时,自动降级至Claude-3($0.012/次)
- 当Claude-3成本 > $0.015/次时,降级至Llama-3-8B($0/次,仅算GPU电费)
- 成本阈值从Anypoint Vault动态读取,每日凌晨更新
这套机制让我们的LLM月度成本波动控制在±3%以内,去年Q4在业务量增长40%的情况下,LLM支出反而下降11%。
6. 经验沉淀:从项目中淬炼出的6条铁律
做完这三个生产系统,我和团队把踩过的坑、验证过的方案,浓缩成6条不写进PPT、但写进我们SOP的铁律:
铁律1:永远假设LLM会失败,永远假设网络会抖动,永远假设业务方会改需求
我们所有流都按“三重冗余”设计:LLM调用失败→降级模型;降级失败→人工队列;人工队列超时→自动邮件升级。这不是过度设计,而是把“最坏情况”当成默认场景。上线第一天,OpenAI就抖了8分钟,降级流扛住了全部流量。
铁律2:Prompt不是代码,但要像代码一样版本管理
我们把每个Prompt存在Git仓库,目录结构为 /prompts/{domain}/{use-case}/v1.2/ ,每次变更必须:
- 提交PR,附带测试用例(输入文本+期望输出)
- CI流水线自动用100个样本测试,准确率下降>2%则拒绝合并
- 生产环境只允许部署
main分支的tag版本,develop分支禁止上线
铁律3:监控指标必须和业务语言对齐,而不是技术语言
不要只看 http.status.5xx ,要看 contracts_reviewed_with_risk_level_HIGH ;不要只看 avg_latency ,要看 users_waiting_over_30_seconds 。我们仪表盘第一屏永远是:“今天有多少合同被正确拦截?多少风险被漏掉?多少用户多等了?”——这才是业务方真正关心的。
铁律4:安全不是加个防火墙,而是贯穿数据生命周期的锁链
从Salesforce传来的合同文本,在MuleSoft流中经历:
- Step 1:DLP扫描(脱敏PII)
- Step 2:Vault加密存储临时文件(AES-256)
- Step 3:LLM调用时,token流全程TLS 1.3加密
- Step 5:结构化输出写入ERP前,再次DLP校验
- Step 7:所有日志脱敏后存ES,审计员只能查
contract_id,看不到原文
铁律5:别迷信“端到端自动化”,人机协同才是生产力
我们刻意在Step 6设置了 MEDIUM 风险的人工复核环节。不是因为技术不行,而是因为:
- 法务总监说:“我要知道我的团队在哪些地方还在依赖AI,这样我才知道培训重点在哪”
- 销售VP说:“让销售经理看到AI的建议,再自己决定是否采纳,比全自动推送更有掌控感”
自动化不是消灭人,而是让人去做更高价值的判断。
铁律6:技术债比想象中更致命,每天留30分钟还债
我们雷打不动执行“Friday Refactor Hour”:
- 每周五下午4点,全员停下手头需求,只做三件事:
- 重构一个最丑陋的DataWeave脚本(目标:行数减少30%,可读性提升)
- 补全一个缺失的单元测试(覆盖率必须≥85%)
- 更新一份过时的Confluence文档(标注最后更新人和时间)
坚持11个月,技术债指数下降67%,新成员上手时间从2周缩短至3天。
最后分享一个小技巧:当你在Anypoint Studio里调试一个复杂流时,别只盯着 Message 面板。右键点击任意处理器,选择 View Debug Data ,你会看到该节点执行前后的完整内存快照——包括所有 vars 、 attributes 、 payload 的二进制大小。我们靠这个发现了多次隐式内存泄漏,比如一个 foreach 循环里无意中把10MB的PDF Base64字符串存进了 vars 。真正的高手,永远在细节里抠出确定性。
更多推荐
所有评论(0)