无代码AI应用构建平台LLMStack:从数据集成到智能体部署实战
在人工智能工程化实践中,大语言模型(LLM)的应用构建常面临高门槛与复杂集成挑战。其核心原理在于通过API调用与提示词工程,将模型的通用能力转化为解决特定业务问题的智能应用。这一过程的技术价值在于显著降低AI落地成本,并实现私有数据与前沿模型的深度结合。典型的应用场景包括基于知识库的智能问答、自动化工作流编排以及跨平台聊天机器人部署。本文聚焦的LLMStack平台,正是通过其创新的可视化链式编排与
1. 项目概述与核心价值
如果你正在寻找一个能让你快速把想法变成可交互AI应用的工具,但又不想被复杂的代码和部署流程劝退,那么LLMStack很可能就是你一直在找的答案。简单来说,它是一个“无代码”平台,核心目标是让你像搭积木一样,通过拖拽和配置,就能构建出功能强大的生成式AI智能体、工作流和聊天机器人。我花了几天时间深度体验和部署了这个平台,它最吸引我的地方在于,它把构建AI应用的门槛降到了极低,同时又把专业开发者才关心的灵活性、数据集成和私有化部署能力保留了下来。你可以用它连接自己的数据源(比如公司内部的文档、数据库),调用不同的AI模型(如OpenAI GPT、Claude等),并最终将应用发布为网页、API,甚至集成到Slack或Discord中。无论你是业务人员想快速验证一个AI点子,还是开发者想搭建一个内部AI工具原型,LLMStack都提供了一个非常高效的起点。
2. 核心架构与设计思路拆解
2.1 为什么是“无代码”而非“低代码”?
LLMStack将自己定位为“No-code”平台,这与市面上许多“Low-code”平台有本质区别。低代码通常意味着你仍然需要理解一些编程逻辑,甚至写一些脚本。而LLMStack的设计哲学更彻底:它试图通过可视化的“链”(Chain)来完全替代编程。在这个链中,每个节点(Node)代表一个处理单元,比如“读取用户输入”、“调用GPT-4模型”、“查询向量数据库”、“格式化输出”。你只需要在图形化界面中拖拽这些节点,并用连线定义数据流(上一个节点的输出作为下一个节点的输入),就完成了一个AI应用的逻辑编排。
这种设计的优势非常明显: 极快的原型构建速度 。我测试过一个简单的“基于知识库的问答机器人”,从导入PDF文档到配置好问答流程并发布,整个过程不超过15分钟。这对于需要快速响应业务需求的团队来说,价值巨大。当然,这种设计也意味着深度定制能力有一定上限,但对于覆盖80%的常见AI应用场景(如问答、内容生成、数据提取、工作流自动化)来说,已经完全足够。
2.2 多模型链式编排:超越单一AI模型的局限
单一的大语言模型(LLM)能力再强,也有其边界。比如,GPT-4长于理解和生成,但在精确计算、实时信息获取或特定领域推理上可能不足。LLMStack的核心能力“链式编排”(Chaining)就是为了解决这个问题。
你可以设计这样的工作流:用户输入一个问题 -> 第一个LLM节点(如GPT-3.5)负责解析问题意图并拆解成子任务 -> 根据子任务,触发不同的分支,有的分支去调用一个联网搜索工具节点获取实时信息,有的分支去查询你本地的向量数据库获取内部知识 -> 将获取到的所有信息汇总,传递给第二个LLM节点(如Claude-3),让它进行综合分析与格式整理 -> 最后输出结构化的答案。
这个过程完全在可视化界面中完成。这种链式设计带来了几个关键优势:
- 成本优化 :你可以用更便宜、更快的模型(如GPT-3.5 Turbo)处理简单的解析任务,只在最终需要高质量输出的环节调用更强大的模型(如GPT-4),从而有效控制API调用成本。
- 能力互补 :结合不同模型的专长,比如用Claude处理长文档,用GPT-4进行创意写作,用专门微调过的模型处理特定领域问题。
- 流程标准化 :将复杂的AI处理流程固化成一个可重复使用的“模板”或“应用”,任何团队成员都可以直接使用,确保了输出质量的一致性。
2.3 数据与AI的深度集成:从“通用”到“专属”
让AI理解你独有的业务数据,是使其产生实际价值的关键。LLMStack在这方面提供了开箱即用的解决方案。它内置了完整的 数据预处理和向量化流水线 。
当你通过平台上传文件(支持PDF、Word、Excel、PPT、TXT、CSV等)或连接外部数据源(如Google Drive、Notion、网页)时,后台会自动进行以下操作:
- 文本提取与分割 :从文件中提取文本,并按照语义或长度将其分割成更小的“块”(Chunks)。这个分割策略很重要,太大则检索不精准,太小则失去上下文。LLMStack提供了配置选项,允许你调整块大小和重叠区间。
- 向量化嵌入 :使用你配置的嵌入模型(如OpenAI的
text-embedding-3-small)将每个文本块转换为高维向量(即一组数字)。语义相近的文本,其向量在空间中的距离也更近。 - 向量存储 :将这些向量及其对应的原始文本,存储在内置的向量数据库中(LLMStack默认使用ChromaDB)。这个过程完全自动化,无需你编写任何ETL脚本。
之后,在你的AI链中,就可以插入一个“向量搜索”节点。当用户提问时,该节点会自动将问题转换为向量,并在数据库中搜索最相似的几个文本块,将这些块作为“上下文”或“参考材料”提供给后续的LLM节点。这样,LLM生成的回答就能基于你的私有数据,实现精准的“知识库问答”或“文档智能分析”。
注意 :数据预处理的质量直接决定最终应用的效果。对于格式复杂的PDF或扫描件,文本提取可能会有错误或遗漏。在实际操作中,对于关键业务数据,建议先进行人工抽查,确保提取的文本准确无误。LLMStack也允许你直接粘贴或输入纯文本内容,这对于处理已经清洗好的数据非常方便。
3. 从零开始部署与核心配置实战
3.1 本地开发环境部署详解
官方推荐使用 pip 安装,这是最快捷的方式。但为了环境的纯净和可管理性,我强烈建议先使用 conda 或 venv 创建一个独立的Python虚拟环境。
# 1. 创建并激活虚拟环境 (以conda为例)
conda create -n llmstack python=3.10
conda activate llmstack
# 2. 安装LLMStack核心包
pip install llmstack
安装过程会拉取一系列依赖,包括FastAPI(Web框架)、LangChain(用于链编排的底层库)、ChromaDB(向量数据库)等。如果网络环境不佳,可能需要配置镜像源。
安装完成后,直接运行 llmstack 命令即可启动服务。首次运行会进行初始化:
- 在用户主目录(
~)下创建.llmstack文件夹。 - 在该文件夹内生成配置文件(
config)和SQLite数据库文件。 - 启动后端API服务(默认端口3000)和前端Web界面。
启动成功后,浏览器会自动打开 http://localhost:3000 。默认的管理员账号是 admin ,密码是 promptly 。 登录后第一件事,就是务必在设置中修改这个默认密码!
3.2 关键配置:连接你的AI模型与工具
平台初始状态下,很多AI模型提供商(称为“Provider”)的API密钥是空的。你需要手动配置才能使用。
-
配置API密钥 :
- 登录后,点击右上角用户头像,进入 “Settings” 。
- 在 “API Keys” 选项卡下,你会看到一长列供应商,如OpenAI、Anthropic (Claude)、Cohere、Google AI (Gemini)、Hugging Face等。
- 找到你需要的供应商,点击“Add Key”,填入从对应平台申请的API密钥。
- 实操心得 :建议优先配置OpenAI和Anthropic,这是目前生态最成熟、效果最稳定的两个。你可以同时配置多个同类型供应商的密钥,LLMStack在调用时会按顺序尝试,这为故障转移和负载均衡提供了基础。
-
配置全局默认密钥(可选,适合团队部署) : 如果你是在为团队部署LLMStack,可能不希望每个用户都去配置自己的密钥。这时可以编辑
~/.llmstack/config文件(首次运行后生成),以如下格式添加全局密钥:providers: openai: api_key: "sk-your-openai-key-here" anthropic: api_key: "sk-your-anthropic-key-here"这样,所有用户在该实例上创建应用时,都可以直接使用这些预配置的模型,无需个人密钥。管理员也可以在后台界面统一管理。
-
配置数据持久化与外部工具 :
- 数据库 :默认使用SQLite,适合轻量级使用。对于生产环境,你可以在配置中修改为PostgreSQL或MySQL,提升并发性能和可靠性。
- 向量数据库 :默认使用ChromaDB(嵌入式模式)。对于大量数据,可以考虑配置外部的ChromaDB服务或切换至Weaviate、Pinecone等专业向量数据库。
- 外部工具 :在“Data Sources”部分,可以配置Google Drive、Notion等的OAuth授权,实现从这些平台直接同步数据。
3.3 Docker部署与生产环境考量
对于希望一键部署或用于生产环境的用户,LLMStack也提供了Docker Compose方案。这是更推荐的生产级部署方式,因为它能更好地管理服务的生命周期和依赖。
# 1. 克隆仓库(获取docker-compose.yml文件)
git clone https://github.com/trypromptly/LLMStack.git
cd LLMStack
# 2. 启动服务
docker-compose up -d
这个命令会启动多个容器,包括Web应用、后台任务处理器(Celery worker)、Redis(消息队列)和PostgreSQL(数据库)。所有服务通过Docker网络互联,配置都预设在 docker-compose.yml 文件中。
生产环境部署要点 :
- 安全 :务必修改
docker-compose.yml中的默认密码(特别是PostgreSQL和Redis),并设置复杂的管理员密码。考虑通过反向代理(如Nginx)配置HTTPS。 - 资源 :向量搜索和模型推理比较消耗CPU/内存。根据用户量和数据规模,需要为Docker容器分配足够的资源。可以在
docker-compose.yml中为服务设置deploy.resources.limits。 - 备份 :定期备份PostgreSQL数据库和上传的文件存储目录(默认在Docker卷中)。这是恢复服务的生命线。
- 监控 :Docker Compose部署的服务,可以通过
docker-compose logs -f查看日志,或集成Prometheus、Grafana进行监控。
4. 构建你的第一个AI智能体:实战演练
理论说了这么多,我们动手构建一个实用的AI智能体:一个“技术文档摘要与问答助手”。它的功能是,用户上传一篇技术文档(如API手册),它可以自动生成摘要,并能回答用户基于该文档提出的问题。
4.1 第一步:创建应用与导入数据
- 登录LLMStack,点击“Create New App”。
- 给应用起个名字,比如“TechDoc Helper”,选择“Chat App”作为模板(这是一个好的起点,包含了基础的对话界面)。
- 进入应用编辑器后,首先处理数据。点击左侧边栏的 “Data Sources” ,然后点击“Add Data Source”。
- 选择“Upload Files”,将你的技术文档PDF拖入上传区域。平台会上传文件,并自动开始后台的文本提取、分割和向量化过程。你可以在“Data Sources”列表看到处理状态,显示为“Processed”即完成。
4.2 第二步:设计AI处理链(Chain)
这是核心环节。点击编辑器画布上的“Chain”标签页,你会看到一个以“User Input”节点开始的空白链。
-
解析用户意图 :
- 从右侧节点库中,拖拽一个 “Text Prompt” 节点到画布上,并将其连接到“User Input”节点之后。
- 配置这个Text Prompt节点:在“Prompt”输入框中,编写一个“系统提示词”(System Prompt),用于判断用户意图。例如:
你是一个文档分析助手。请分析用户的输入,判断他是想要“总结文档”还是“询问文档相关问题”。 如果用户输入包含“总结”、“摘要”、“概括”等关键词,或者是一个模糊的请求(如“介绍一下这个文档”),则输出“summary”。 否则,输出“qa”。 只输出这两个词之一,不要有任何其他解释。 - 将这个节点的输出命名为
intent(方便后续引用)。
-
构建摘要分支 :
- 拖拽一个 “Condition” (条件判断)节点。将其输入连接到上一步的
intent输出。 - 在条件节点中设置规则:
{{intent}}等于summary。 - 当条件为真时,我们需要触发摘要生成。拖拽一个 “Vector Store Search” 节点,连接到条件节点的“True”分支。在配置中,选择你刚刚上传的文档作为数据源,并设置返回的文本块数量(例如5-8个,以覆盖主要章节)。
- 接着,拖拽一个 “LLM” 节点连接到搜索节点之后。选择模型(如GPT-4),并编写提示词:
基于以下上下文,为这份技术文档生成一份简洁、全面的摘要,突出其核心功能、API主要端点和关键使用方法。 上下文:{{steps.vector_store_search.output}} - 将这个LLM节点的输出命名为
summary_result。
- 拖拽一个 “Condition” (条件判断)节点。将其输入连接到上一步的
-
构建问答分支 :
- 将条件节点的“False”分支(即
intent为qa)连接到一个新的 “Vector Store Search” 节点。配置类似,但这里搜索的依据是用户原始问题。 - 在这个搜索节点后,连接一个 “LLM” 节点。提示词可以这样写:
请严格根据以下提供的上下文信息来回答问题。如果上下文信息不足以回答问题,请直接说“根据提供的文档,我无法回答这个问题”。 问题:{{inputs.user_input}} 上下文:{{steps.vector_store_search_1.output}} 答案: - 将这个节点的输出命名为
qa_result。
- 将条件节点的“False”分支(即
-
合并输出 :
- 拖拽一个 “Text” 节点作为最终输出节点。
- 我们需要根据不同的分支,将不同的结果传递给这个节点。可以使用一个 “Join” 节点,或者更简单的方法:在最终“Text”节点的内容中,使用Jinja2模板语法进行判断。
{% if steps.condition.output == ‘summary’ %} {{steps.summarize_llm.output}} {% else %} {{steps.qa_llm.output}} {% endif %} - 最后,将这个“Text”节点的输出,连接到画布最右侧的 “App Output” 。
4.3 第三步:配置与发布
- 测试 :点击画布上方的“Test”按钮。在右侧的测试面板输入“请总结一下这篇文档”,看看是否触发摘要分支并生成内容。再输入一个具体问题,如“如何调用XX API?”,测试问答分支。
- 优化 :根据测试结果,调整提示词、向量搜索返回的块数量或条件判断逻辑。提示词工程是影响效果的关键,需要反复迭代。
- 发布 :测试满意后,点击“Publish”。你可以选择将应用发布为一个公开或私有的链接,也可以将其嵌入到其他网站中。更重要的是,LLMStack会为这个应用自动生成一个API端点,你可以从其他系统(如CRM、内部工单系统)通过HTTP请求来调用它。
通过这个例子,你可以看到,无需编写任何代码,我们就构建了一个能理解意图、并能基于私有数据提供两种不同服务的复杂AI智能体。这就是LLMStack无代码编排威力的直观体现。
5. 高级功能与集成场景探索
5.1 连接外部API与工具(Webhook/HTTP节点)
真正的智能体不能只活在对话里,它需要能与现实世界交互。LLMStack提供了 “HTTP Processor” 节点,允许你在AI链中调用任何外部API。
场景示例:构建一个智能天气查询助手
- 在链中,用一个LLM节点解析用户输入,提取出“城市名”和“日期”实体。
- 将提取出的实体,传递给一个“HTTP Processor”节点。配置该节点向一个公共天气API(如OpenWeatherMap)发送GET请求,URL中动态填入城市名。
- 将天气API返回的JSON数据,传递给下一个LLM节点,让它以友好、自然的口吻组织成回复给用户。
配置HTTP节点的关键细节 :
- URL与参数 :支持动态变量,如
https://api.weatherapi.com/v1/forecast.json?key=YOUR_KEY&q={{extracted_city}}。 - 认证 :支持在请求头中添加API Key、Bearer Token等。
- 错误处理 :可以配置节点在请求失败时的后备输出,避免整个链条因一个外部服务故障而崩溃。
这个能力极大地扩展了AI链的边界,使其可以查询数据库、发送邮件、触发业务流程,真正成为连接AI与业务系统的“胶水”。
5.2 多租户与团队协作管理
对于企业用户,LLMStack的多租户(Multi-tenancy)架构非常实用。管理员可以在后台( /admin )创建不同的“组织”(Organization)。
- 数据隔离 :每个组织下的用户,只能看到和访问本组织内创建的数据源、AI应用和访问记录。这满足了不同部门或项目组之间的数据安全与隐私要求。
- 用户与权限 :管理员可以为每个组织添加用户,并分配角色。目前角色管理相对基础,但足以区分普通用户(创建和使用应用)和管理员(管理组织内用户和应用)。
- 统一计费与监控 :如果使用云服务商的API,所有调用都可以在组织层面进行监控和成本分析,便于财务核算。
5.3 与Slack、Discord等协作平台集成
这是LLMStack一个非常出彩的功能。你构建好的AI应用,可以一键发布为Slack或Discord机器人。
-
Slack集成 :
- 在Slack API网站创建一个新的Slack App,获取
Bot User OAuth Token和Signing Secret。 - 在LLMStack应用编辑器的“Publish”页面,找到“Slack”集成选项,填入上述凭证。
- 配置触发命令(如
/askdoc)和需要监听的消息频道。 - 部署后,团队成员在Slack频道中输入
/askdoc 我们的Q3目标是什么?,机器人就会调用你部署的、基于公司内部数据训练的AI链来回答问题,实现知识查询的即时化、场景化。
- 在Slack API网站创建一个新的Slack App,获取
-
Discord集成 :
- 过程类似,需要在Discord开发者门户创建应用和机器人,获取Token。
- 配置后,AI助手就能在Discord服务器中响应指令或特定关键词。
这种集成让AI能力无缝嵌入到日常工作中,极大地提升了工具的采纳率和实用性。
6. 常见问题、排查技巧与性能优化
在实际部署和使用中,你肯定会遇到各种问题。以下是我踩过坑后总结的一些经验。
6.1 安装与启动问题
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
pip install llmstack 失败,提示某些包冲突或编译错误。 |
Python环境混乱,或系统缺少编译依赖。 | 强烈建议使用全新的虚拟环境 。对于编译错误(常见于某些需要C扩展的包如 psycopg2 ),在Ubuntu/Debian上可尝试 sudo apt-get install python3-dev build-essential ;在macOS上需确保Xcode命令行工具已安装。 |
运行 llmstack 命令后,浏览器没有自动打开,或访问 localhost:3000 连接被拒绝。 |
端口被占用,或服务启动失败。 | 检查3000端口是否被占用: lsof -i:3000 。尝试指定其他端口启动: llmstack --port 5000 。查看启动日志,通常会有错误信息输出到终端。 |
| 上传文件后,一直显示“Processing”,无法完成。 | 后台任务处理器(Celery worker)没有正常运行,或向量化模型下载失败。 | 确保已安装Docker,因为默认的文本嵌入任务依赖Docker容器。检查Docker服务是否运行: docker ps 。如果是网络问题导致模型下载失败,尝试在设置中更换为可访问的嵌入模型,或配置代理环境变量。 |
6.2 应用构建与效果调优
| 问题现象 | 可能原因 | 解决方案与技巧 |
|---|---|---|
| AI回答的内容与我的文档无关,胡编乱造(“幻觉”)。 | 向量搜索返回的上下文不相关,或提示词没有强制模型基于上下文回答。 | 1. 优化搜索 :调整文本分割的块大小(chunk size)和重叠区(overlap)。对于技术文档,较小的块(如256字符)和一定的重叠(如50字符)可能效果更好。2. 强化提示词 :在LLM节点的提示词中,使用强硬的指令,如“ 必须严格仅依据以下上下文回答 ,如果上下文不包含答案,请说‘不知道’。”并重复强调。 |
| 回答速度很慢。 | 链路过长,或使用了响应慢的大模型(如GPT-4),或向量数据库搜索未优化。 | 1. 链路分析 :检查链中是否有不必要的节点或串行依赖。尽可能让可以并行的操作(如同时搜索多个数据源)并行。2. 模型选型 :在非关键环节使用更快、更便宜的模型(如GPT-3.5 Turbo)。3. 索引优化 :对于大数据集,确保向量数据库建立了索引。考虑使用更高效的向量数据库(如PgVector with HNSW索引)。 |
| 条件判断(Condition节点)不准确。 | 判断逻辑过于简单,或依赖的上游LLM节点输出不稳定。 | 不要完全依赖一个简单的关键词匹配或LLM的单一判断。可以设计“投票机制”:用多个不同角度的提示词让LLM分析意图,然后根据多个结果做综合判断。或者,将复杂的意图识别拆解成多个步骤,逐步细化。 |
| 处理长文档时内存不足或超时。 | 一次性加载的上下文过长,超过了模型令牌(Token)限制。 | 使用“Map-Reduce”策略:先将文档分割成多个部分,分别进行总结或分析(Map),再将多个结果合并成一个最终总结(Reduce)。这需要在链中设计循环或使用更高级的编排模式。 |
6.3 生产环境运维要点
- 监控与日志 :除了查看Docker日志,应将LLMStack的应用日志(通常输出到标准输出)收集到集中式日志系统(如ELK、Loki)。关键监控指标包括:API响应时间、各模型提供商调用错误率、任务队列积压情况。
- 速率限制与成本控制 :在设置中为每个API密钥配置速率限制(Rate Limit),防止意外循环调用导致天价账单。对于OpenAI等按Token计费的模型,可以在链中插入“Token计数”节点进行估算和预警。
- 版本管理与回滚 :LLMStack的应用编辑器支持保存不同版本。在对生产应用进行重大修改前,务必先保存一个版本。如果新版本出现问题,可以快速回滚到稳定版本。
- 数据更新 :当源文档更新后,需要重新处理数据源。LLMStack支持对数据源进行“Reprocess”操作,这会更新向量数据库中的索引。对于频繁更新的知识库,可以考虑设置自动化的同步与处理流水线。
7. 横向对比与选型建议
在无代码/低代码AI平台领域,LLMStack并非唯一选择。它与LangFlow、Flowise、Dify等工具处于同一赛道。以下是基于我个人体验的对比分析,帮助你决策:
| 特性 | LLMStack | LangFlow / Flowise | Dify |
|---|---|---|---|
| 核心定位 | 无代码构建AI智能体与应用 ,强调开箱即用的数据集成和最终应用发布。 | 低代码可视化编排LangChain链 ,更偏向于为开发者提供一个构建和测试LangChain原语的图形化工具。 | 一站式AI应用开发平台 ,功能非常全面,覆盖从编排、评估、发布到运维的全生命周期。 |
| 上手难度 | 较低 。界面直观,预设模板多,从数据导入到发布集成的路径清晰。 | 中等 。需要你对LangChain的概念(如Chains, Agents, Tools)有一定理解,更像一个可视化编程IDE。 | 中等偏上 。功能庞杂,概念较多(如工作流、模型、数据集、插件),需要一定学习成本。 |
| 数据与知识库 | 内置且强大 。数据源类型丰富,预处理和向量化全自动,与链编排无缝集成,体验流畅。 | 较弱或需手动集成 。通常需要你自己编写代码或配置来连接向量数据库和处理数据。 | 内置且强大 。与LLMStack类似,提供了完善的知识库管理功能。 |
| 集成与发布 | 优秀 。原生支持Slack、Discord、API发布,多租户功能适合团队。 | 一般 。更侧重于链的开发与测试,发布为独立应用需要额外工作。 | 非常优秀 。提供了更丰富的部署选项和API管理功能,企业级特性更明显。 |
| 部署方式 | 支持 pip 单机部署和 Docker Compose 生产部署,相对轻量。 |
通常以Docker容器形式部署。 | 支持多种部署方式,包括云原生部署,架构更复杂但也更 scalable。 |
| 适合人群 | 业务人员、产品经理、全栈开发者 ,希望快速将AI想法落地为可交互应用,并集成到现有工作流中。 | AI工程师、研究者、开发者 ,希望可视化地探索和构建复杂的LangChain链,用于研究或作为更大系统的一部分。 | 企业团队、AI应用开发者 ,需要一套完整的平台来管理AI应用的开发、协作、评估和运维。 |
选型建议 :
- 如果你的核心需求是**“快速” 和 “集成”**,想用最短时间把一个基于私有数据的聊天机器人或自动化流程跑起来,并连到Slack上, LLMStack是最直接的选择 。
- 如果你是一个开发者,主要用 LangChain ,想找一个图形化工具来设计和调试复杂的链逻辑,那么 LangFlow或Flowise 可能更适合你。
- 如果你是一个中小团队或企业,需要一个功能全面的 平台级产品 来系统化地开发和管理多个AI应用,注重权限、审计、版本管理和性能监控,那么 Dify 值得深入评估。
LLMStack在易用性和功能完备性之间取得了很好的平衡,特别是其“数据导入->处理->应用构建->发布集成”的端到端流程非常顺畅,对于大多数想快速尝鲜并看到实际效果的个人和团队来说,它是一个阻力最小、成就感来得最快的工具。
更多推荐




所有评论(0)