AI应用开发实战:从RAG到Agent,aikit工具箱全解析
1. 项目概述当AI遇上开源协作一个工具箱的诞生如果你最近在折腾大语言模型LLM的应用开发或者对AI Agent、RAG检索增强生成这些概念既兴奋又头疼那你大概率已经听过或正在寻找一个趁手的“瑞士军刀”。今天要聊的aikit就是这样一个由开源社区kaito-project发起并维护的AI工具包。它不是某个大厂的封闭产品而是一群实践者为了解决实际开发中的“脏活累活”而攒出来的工具箱。简单来说aikit的目标是让开发者无论是经验丰富的老手还是刚刚入门的新人都能更快速、更优雅地构建和部署基于大语言模型的智能应用把精力从繁琐的工程化细节中解放出来聚焦在真正的业务逻辑和创新上。我自己在尝试将ChatGPT的API接入到内部知识库或者构建一个能自动处理工单的AI客服原型时就深有体会模型调用只是第一步后面跟着的是令人望而生畏的提示词工程、上下文管理、流式输出处理、错误重试、成本监控……这些环节如果每个都从头造轮子项目还没开始热情就先耗掉一半。aikit的出现正是瞄准了这些痛点。它通过提供一组设计良好、开箱即用的高级抽象和工具函数把常见的AI应用开发模式固化下来。你可以把它理解为一个针对AI应用层的“框架”或“工具集”它不关心底层用的是OpenAI、Anthropic还是本地部署的模型它关心的是如何让你用一致的、高效的方式去使用它们。那么aikit具体适合谁呢首先是AI应用开发者无论是做原型验证还是产品开发它都能显著提升效率。其次是研究者当你需要快速搭建实验管道来验证某个想法时aikit的模块化设计能让你的代码更清晰、可复现性更强。最后对于技术团队负责人或架构师而言引入aikit这类工具可以作为团队内部AI能力建设的基础设施统一技术栈降低协作成本。接下来我们就深入这个项目的内部看看它到底是如何被设计和实现的以及在实际使用中有哪些门道和技巧。2. 核心架构与设计哲学拆解2.1 统一抽象层化解模型差异的“万能适配器”aikit最核心的设计思想之一就是提供了一个统一的抽象层来封装不同的大语言模型提供商。在AI应用开发中一个很现实的问题是今天你可能用GPT-4明天因为成本或性能考虑想换Claude后天又需要测试一下开源的Llama 3。如果业务代码里到处散落着针对特定API SDK的调用那么切换模型将是一场灾难。aikit的解决方案是定义一个通用的LLMClient接口或基类。这个接口规定了所有语言模型客户端都必须实现的一组核心方法比如generate生成文本、chat对话、embed生成向量等。对于OpenAI、Anthropic、Cohere等云服务商aikit提供了官方的适配器实现对于通过vLLM、TGI等框架部署的本地模型它也提供了相应的客户端。这意味着在你的业务代码中你只需要与这个统一的LLMClient交互。# 伪代码示例使用 aikit 的统一接口 from aikit.llm import OpenAIClient, AnthropicClient, LiteLLMClient # 初始化不同客户端的代码形式几乎一致 openai_client OpenAIClient(api_key“your_key”, model“gpt-4”) anthropic_client AnthropicClient(api_key“your_key”, model“claude-3-opus”) # LiteLLM 可以代理多种后端 litellm_client LiteLLMClient(model“openai/gpt-4”, api_base“your_proxy_url”) # 调用方式完全统一 response1 openai_client.chat(messages[{“role”: “user”, “content”: “Hello”}]) response2 anthropic_client.chat(messages[{“role”: “user”, “content”: “Hello”}])这种设计带来了巨大的灵活性。你可以通过配置文件轻松切换模型而不需要修改核心业务逻辑。这对于A/B测试不同模型的性能、实现故障转移当主用模型服务不可用时自动切换到备用模型、以及进行成本优化为不同任务分配不同价位的模型至关重要。注意统一抽象层虽然美好但也可能掩盖不同模型之间的细微差异。例如某些模型对上下文长度有特殊限制或者对消息格式有特定要求。aikit的适配器会在内部处理这些差异但作为开发者你仍需了解你所使用模型的特性并在设计提示词和上下文管理策略时加以考虑。2.2 模块化工具箱从提示词到向量检索的全链路覆盖aikit并非一个单一的整体而是由一系列松耦合的模块组成的工具箱。这种模块化设计让你可以按需取用而不是被迫接受一个庞大的框架。我们来拆解几个关键模块提示词管理模块这是AI应用的“灵魂”。aikit通常会提供模板引擎允许你定义带有变量的提示词模板并从文件或数据库中加载它们。这解决了硬编码提示词带来的维护难题。高级功能可能包括支持少量示例Few-shot的模板、支持从历史对话中动态选择相关示例的机制、以及提示词版本管理。对话与上下文管理模块大语言模型本身是无状态的。aikit的会话管理模块负责维护对话的历史记录并能智能地处理长上下文。例如当对话轮数太多超出模型上下文窗口时它可以根据策略如保留最近N轮或总结早期对话来压缩历史确保最重要的信息不被丢失。这个模块是构建连贯多轮对话应用的基础。工具调用与Agent框架为了让大语言模型能够执行具体操作如查询数据库、调用APIaikit会集成或提供构建AI Agent的能力。这包括定义工具函数的规范、将工具描述格式化后提供给模型、解析模型的输出以调用相应的工具并将工具执行结果返回给模型进行下一轮思考。这是实现自动化工作流的关键。检索增强生成RAG集成这是当前将大模型与私有知识结合的最主流方式。aikit的RAG模块会提供端到端的流水线支持包括文档加载与分块、文本向量化嵌入、向量数据库的对接如Chroma Pinecone Weaviate、以及检索结果的排序与重排。它简化了从原始文档到精准答案的整个过程。评估与监控模块开发AI应用离不开评估。aikit可能集成了一些常见的评估指标如回答相关性、事实准确性的自动化计算工具或者提供了便捷的日志记录和追踪接口帮助你监控每次调用的耗时、消耗的Token数以及成本这对于优化和调试至关重要。2.3 配置即代码与开发者体验优先aikit非常重视开发者的使用体验。它通常倡导“配置即代码”的理念允许你通过YAML、JSON或Python字典来定义整个应用或实验的配置。这包括模型参数、提示词模板路径、RAG的检索参数等。这样做的好处是你的实验过程可以被完整地复现配置也可以被纳入版本控制系统进行管理。此外良好的错误处理和重试机制也是其设计重点。网络波动、模型服务限流是生产环境中常见的问题。aikit的客户端内部会实现指数退避等重试策略并提供清晰的错误信息帮助开发者快速定位问题是出在认证、参数还是服务端。最后aikit作为一个开源项目其文档、示例代码和社区活跃度是评估其价值的重要部分。一个健康的项目会提供从“快速开始”到“高级用法”的完整指南并有丰富的示例展示如何组合各个模块来解决真实世界的问题比如构建一个客服机器人、一个智能文档分析工具或一个内部知识问答系统。3. 核心模块深度解析与实操要点3.1 提示词工程超越字符串拼接的艺术在aikit的语境下提示词管理远不止是简单的字符串格式化。一个健壮的提示词系统需要处理多轮对话、动态上下文、以及复杂的结构。aikit的提示词模块通常提供一个模板类你可以这样使用from aikit.prompts import PromptTemplate # 定义一个带变量和少量示例的模板 template PromptTemplate( template“”” 你是一个专业的{domain}专家。请根据以下上下文回答问题。 上下文 {context} 问题{question} 请用中文回答如果上下文信息不足请明确说明。 “””, input_variables[“domain”, “context”, “question”], # 可能支持附加示例 few_shot_examples[ { “input”: {“domain”: “法律”, “context”: “...”, “question”: “...”}, “output”: “根据相关条文...” } ] ) # 渲染提示词 rendered_prompt template.render( domain“医疗”, context“患者男35岁持续发热3天...” question“可能是什么原因” )实操要点一模板的组织与存储。千万不要把长篇大论的提示词模板写在Python代码里。最佳实践是将它们存储在单独的文本文件、Markdown文件或专门的配置目录中。aikit可能会支持从指定路径加载模板这样你可以独立于代码迭代和优化提示词甚至可以实现提示词的A/B测试。实操要点二处理消息列表。对于对话模型输入是一个消息列表messages每个消息有角色role和内容content。aikit的对话管理模块会帮你维护这个列表。你需要理解的是如何将你的模板渲染结果与历史对话记录组合成符合模型要求的消息列表。通常系统提示词system message只会在对话开始时插入一次而用户和助理的对话会交替追加。实操要点三温度temperature与顶层Ptop_p参数。这两个是控制模型生成“创造性”的核心参数。temperature越高如0.8-1.0输出越随机、多样越低如0-0.2输出越确定、保守。top_p核采样是另一种控制随机性的方法通常与temperature选其一即可。对于需要事实准确性的任务如问答建议使用低temperature0.1-0.3对于创意写作可以调高。aikit的客户端调用应能方便地传递这些参数。3.2 检索增强生成RAG流水线实战RAG是aikit工具箱中的重头戏。一个完整的RAG流程可以分解为以下步骤aikit为每一步都提供了工具或指导步骤1文档加载与预处理aikit可能会集成LangChain的DocumentLoader或类似工具支持从PDF、Word、HTML、Markdown、数据库等多种源加载文档。关键点在于预处理清洗无关字符页眉、页脚、处理编码问题。对于中文可能还需要进行基本的文本规范化。步骤2文本分块Chunking这是影响检索效果最关键的一步之一。分块太大检索出的信息可能包含太多噪声分块太小可能丢失完整的语义。aikit可能提供基于字符、句子或标记token的分块器。递归字符分块器尝试按段落、句子等自然边界分割是常用且效果不错的方法。标记分块器直接按LLM的token数分块能更精确地控制上下文窗口占用。重叠分块在块与块之间设置一定的重叠字符如100-200字符可以避免一个完整的句子或概念被割裂显著提升检索连贯性。步骤3向量化嵌入与存储分块后的文本需要转化为向量嵌入。aikit会统一封装多种嵌入模型如OpenAI的text-embedding-3-small, BGE, 本地模型等的调用。生成的向量需要存入向量数据库。aikit的价值在于它简化了与不同向量库Chroma, Weaviate, Qdrant, PGVector等的交互提供了一致的“写入”和“查询”接口。步骤4检索与重排用户提问时先将问题转化为向量然后在向量库中进行相似性搜索通常使用余弦相似度或点积返回最相似的K个文本块例如K5。然而简单的向量相似度可能不够精准。aikit可能集成“重排”模型这是一个更精细的排序步骤。重排模型如Cohere的rerank或BGE的交叉编码器会对检索出的每个块与问题进行深度相关性打分重新排序将最相关的1-2个块放在最前面从而大幅提升最终答案的质量。步骤5生成将重排后的顶级相关文本块作为“上下文”与用户原始“问题”一起填入我们之前设计好的提示词模板中形成最终的提示词发送给大语言模型生成答案。避坑指南RAG的常见失败模式是“检索不到”或“检索不准”。除了优化分块策略另一个关键点是索引的元数据。在存储向量时同时存储每个文本块的来源文件名、页码、创建时间等元数据。这样在检索后你可以根据元数据进行过滤例如“只检索来自最新版本手册的章节”或者在后处理阶段向用户展示引用来源增加可信度。3.3 Agent与工具调用赋予模型行动力AI Agent的核心是让LLM学会使用工具。aikit的Agent模块通常会提供一个框架让你能轻松地定义工具、创建Agent并运行。定义工具工具本质上是一个Python函数附带一个清晰的描述。描述至关重要因为LLM仅根据描述来决定是否以及如何使用该工具。from aikit.tools import tool tool(description“根据城市名称查询该城市当前的天气情况。输入应为城市名例如‘北京’。”) def get_weather(city: str) - str: # 这里调用真实天气API # 模拟返回 return f“{city}的天气是晴25摄氏度。”创建Agentaikit会提供一个高层API将LLM客户端、工具列表以及思考逻辑如ReAct范式封装起来。from aikit.agents import ReActAgent agent ReActAgent( llm_clientopenai_client, tools[get_weather, search_web, query_database], # 工具列表 max_iterations5 # 防止无限循环 )运行与解析你向Agent提问它会自主思考决定调用哪个工具解析工具返回的结果并决定下一步是继续调用工具还是直接给出最终答案。response agent.run(“北京和上海今天天气怎么样哪个更热”) print(response) # 输出可能是一个经过多步思考Thought、行动Action、观察Observation后得出的结论。实操心得工具的描述要尽可能精确、无歧义并说明输入格式。复杂的任务需要让Agent学会“分解”。监控Agent的思考过程日志对于调试其逻辑错误至关重要。此外要设置最大迭代次数并为工具调用添加异常处理防止Agent陷入死循环或因为工具失败而崩溃。4. 从零开始构建一个智能知识库助手为了将上述理论付诸实践我们以构建一个公司内部技术文档问答助手为例展示如何利用aikit的各个模块。4.1 环境搭建与初始化配置首先假设我们已经安装了aikit及其相关依赖。项目初始化从配置文件开始。我们创建一个config.yaml文件这是“配置即代码”的体现# config.yaml llm: provider: “openai” # 或 “anthropic”, “litellm” model: “gpt-4o-mini” # 根据成本和性能选择 api_key: ${OPENAI_API_KEY} # 建议从环境变量读取 temperature: 0.1 # 问答任务要求准确性 embedding: provider: “openai” model: “text-embedding-3-small” vector_store: type: “chroma” # 使用轻量级的Chroma本地运行 persist_directory: “./chroma_db” # 向量数据库持久化路径 rag: chunk_size: 1000 # 分块大小字符 chunk_overlap: 200 # 重叠大小 retrieval_top_k: 5 # 初步检索数量 rerank_top_n: 2 # 重排后保留的数量 prompts: qa_template_path: “./prompts/qa_template.txt”在代码中我们加载配置并初始化核心组件import os from aikit.config import load_config from aikit.llm import get_llm_client from aikit.embedding import get_embedding_client from aikit.vector_store import get_vector_store config load_config(“config.yaml”) # 初始化客户端 llm_client get_llm_client(config.llm) embed_client get_embedding_client(config.embedding) vector_store get_vector_store(config.vector_store) # 加载提示词模板 with open(config.prompts.qa_template_path, ‘r’, encoding‘utf-8’) as f: qa_template f.read()4.2 知识库构建与索引过程接下来我们编写一个脚本将公司的技术文档假设是docs/目录下的Markdown文件处理并存入向量数据库。import glob from aikit.document import DirectoryLoader, RecursiveTextSplitter # 1. 加载文档 loader DirectoryLoader(‘./docs’, glob“**/*.md”) documents loader.load() print(f“已加载 {len(documents)} 个文档。”) # 2. 文本分块 text_splitter RecursiveTextSplitter( chunk_sizeconfig.rag.chunk_size, chunk_overlapconfig.rag.chunk_overlap, separators[“\n\n”, “\n”, “。”, “.”, “ ”, “”] # 中文友好的分隔符 ) chunks text_splitter.split_documents(documents) print(f“分割为 {len(chunks)} 个文本块。”) # 3. 生成向量并存储 # 注意批量处理避免频繁API调用 batch_size 100 for i in range(0, len(chunks), batch_size): batch chunks[i:ibatch_size] texts [chunk.page_content for chunk in batch] metadatas [{“source”: chunk.metadata.get(“source”, “unknown”), “chunk_id”: ij} for j, chunk in enumerate(batch)] # 批量生成嵌入向量 embeddings embed_client.embed_documents(texts) # 存储到向量数据库 vector_store.add_embeddings(textstexts, embeddingsembeddings, metadatasmetadatas) print(f“已处理 {ilen(batch)}/{len(chunks)} 个块。”) vector_store.persist() # 持久化到磁盘 print(“知识库索引构建完成”)这个过程可能需要一些时间取决于文档数量和嵌入模型的速度。关键点在于分块策略的选择和元数据的保留。我们保留了source字段这样在回答时可以告诉用户答案来源于哪个文档。4.3 问答链的组装与查询实现索引建好后我们就可以实现问答功能了。这需要将检索、重排可选、提示词填充和生成串联起来形成一个“链”。from aikit.prompts import PromptTemplate from aikit.retrieval import VectorRetriever, Reranker class KnowledgeBaseQA: def __init__(self, llm_client, vector_store, embed_client, template_str): self.llm llm_client self.retriever VectorRetriever(vector_store, embed_client, top_kconfig.rag.retrieval_top_k) # 如果有重排模型可以初始化 # self.reranker Reranker(model“BAAI/bge-reranker-large”) self.prompt_template PromptTemplate(templatetemplate_str, input_variables[“context”, “question”]) def ask(self, question: str) - dict: # 1. 检索 retrieved_chunks self.retriever.get_relevant_documents(question) if not retrieved_chunks: return {“answer”: “抱歉知识库中未找到相关信息。”, “sources”: []} # 2. 可选重排 # if self.reranker: # retrieved_chunks self.reranker.rerank(question, retrieved_chunks)[:config.rag.rerank_top_n] # else: retrieved_chunks retrieved_chunks[:config.rag.rerank_top_n] # 简单取前N个 # 3. 构建上下文 context “\n\n”.join([f“来源{chunk.metadata[‘source’]}\n内容{chunk.page_content}” for chunk in retrieved_chunks]) # 4. 填充提示词并生成 prompt self.prompt_template.render(contextcontext, questionquestion) messages [{“role”: “user”, “content”: prompt}] response self.llm.chat(messagesmessages, temperatureconfig.llm.temperature) # 5. 返回答案和来源 sources list(set([chunk.metadata.get(“source”, “”) for chunk in retrieved_chunks])) return {“answer”: response.content, “sources”: sources} # 初始化问答助手 qa_assistant KnowledgeBaseQA(llm_client, vector_store, embed_client, qa_template) # 进行查询 result qa_assistant.ask(“我们公司的数据备份策略是什么”) print(f“问题{result[‘question’]}”) print(f“答案{result[‘answer’]}”) print(f“来源{result[‘sources’]}”)这个KnowledgeBaseQA类封装了完整的RAG流程。在实际产品中你还可以为其添加对话历史管理使其支持多轮追问。5. 生产环境部署与性能优化考量当原型验证通过准备将基于aikit的应用部署到生产环境时有几个关键的工程化问题需要解决。5.1 异步化与并发处理大语言模型API调用和嵌入生成通常是网络I/O密集型操作同步调用会严重阻塞服务响应。aikit的客户端应支持异步操作如提供async方法以便与像FastAPI、Sanic这样的异步Web框架集成。# 异步版本的问答函数示例 import asyncio from aikit.llm import AsyncOpenAIClient async def async_ask(question: str): async_client AsyncOpenAIClient(api_key“key”) # 异步检索假设retriever也支持异步 chunks await async_retriever.aget_relevant_documents(question) # 异步生成 response await async_client.achat(messages[...]) return response在Web服务中利用异步可以同时处理多个用户请求极大提高吞吐量。对于批量处理任务如构建大型知识库索引可以考虑使用任务队列如Celery、Dramatiq进行后台异步作业。5.2 缓存与成本控制LLM API调用是按Token收费的嵌入生成也可能产生费用。对于相对静态的内容如固定的知识库问答其中上下文是固定的或者重复的用户问题引入缓存机制能显著降低成本并提升响应速度。请求级缓存对完全相同的提示词包括系统提示、上下文、问题进行哈希将哈希值作为键将LLM的响应结果缓存起来可以存储在Redis或内存中。下次遇到相同请求时直接返回缓存结果。嵌入缓存文档块一旦被索引其嵌入向量就不会改变。务必在本地持久化存储这些向量避免每次启动服务都重新计算。向量数据库本身就是一个持久化缓存。成本监控aikit的客户端应该返回每次调用的Token使用详情输入Token、输出Token。你需要收集这些数据并聚合到监控系统如Prometheus或计费系统设置预算告警。5.3 可观测性与日志生产系统必须可观测。你需要记录请求与响应记录用户问题、检索到的上下文、生成的答案。注意隐私脱敏。性能指标记录每次LLM调用、嵌入调用、检索操作的延迟P95 P99。错误日志详细记录API调用失败、网络超时、速率限制等异常信息。链路追踪为每个用户请求分配一个唯一的Trace ID这个ID需要穿透整个调用链Web服务 - 检索 - LLM调用便于在出现问题时进行端到端的排查。你可以集成像OpenTelemetry这样的标准来收集追踪和指标数据。清晰的日志是调试复杂Agent思维链或RAG检索结果不理想的唯一途径。5.4 安全与合规性输入输出过滤永远不要相信用户的直接输入。在将用户问题送入LLM或用于检索前进行必要的清洗和过滤防止提示词注入攻击。对模型的输出内容也应进行安全检查过滤不当内容。数据隔离如果你的服务是多租户的确保向量数据库的索引和检索过程在逻辑或物理上是隔离的防止用户A查询到用户B的数据。审计日志记录谁在什么时候问了什么问题对于合规要求严格的行业如金融、医疗尤为重要。6. 常见问题排查与实战调试技巧即使有了aikit这样的工具在实际开发中你依然会遇到各种问题。下面是一些常见坑点及其解决方案。6.1 问题一检索结果不相关导致答案胡言乱语这是RAG系统最常见的问题。排查思路如下检查分块查看对于你的问题系统实际检索到了哪些文本块。是不是块太大、内容太杂尝试减小chunk_size或调整分块边界尝试按句子或章节分。检查嵌入模型你使用的嵌入模型是否适合你的文本领域通用嵌入模型对专业领域如法律、医学效果可能打折。可以尝试领域专用的嵌入模型或者在自有数据上微调一个嵌入模型。尝试重排如果初步检索向量相似度结果尚可但排序不佳引入一个重排模型是性价比最高的优化手段。丰富查询有时用户问题很短缺乏上下文。可以尝试“查询扩展”即利用LLM将原始问题重写或扩展成多个相关查询然后合并这些查询的检索结果。检查元数据过滤如果你的知识库有清晰的分类如产品A文档、产品B文档可以在检索时增加元数据过滤条件缩小搜索范围。6.2 问题二答案看起来正确但包含事实性错误幻觉即使提供了上下文LLM有时也会“脑补”出不存在的信息。强化提示词在提示词中明确且强硬地要求模型“仅根据提供的上下文回答”并说明“如果上下文没有足够信息请回答‘我不知道’或‘根据现有信息无法确定’”。可以多次强调。提供引用要求模型在答案中引用来源。例如在提示词中写“请在答案的相应部分用【来源X】的形式注明出处。” 这不仅能增加可信度也便于你事后验证。后处理验证对于关键事实可以设计一个后处理步骤让另一个LLM或规则系统检查答案中的关键陈述是否能在提供的上下文中找到支持。调整模型参数降低temperature到接近0减少模型的随机性使其更忠实于上下文。6.3 问题三Agent陷入循环或调用错误工具限制迭代次数这是必须的。设置一个合理的max_iterations如10次防止无限循环。优化工具描述检查工具的描述是否清晰、无歧义。描述应准确说明工具的功能、输入格式和输出示例。模糊的描述会导致模型误解。提供示例在给Agent的系统提示中提供几个正确使用工具解决多步问题的示例Few-shot learning能显著提升其表现。记录思维过程打开Agent的详细日志观察它的“思考”Thought步骤。看看它是如何分析问题、选择工具的。这能帮你发现逻辑错误进而优化提示词或工具设计。6.4 问题四系统响应速度慢定位瓶颈使用计时工具测量检索、嵌入、LLM生成各阶段的耗时。瓶颈通常出现在LLM生成尤其是长答案或嵌入计算上。异步化如前所述将所有I/O操作异步化。缓存对常见问题、静态上下文实施缓存。模型降级对于实时性要求高、准确性要求稍低的场景可以考虑使用更快、更便宜的模型如GPT-3.5-Turbo代替GPT-4。流式输出对于生成型任务使用LLM API的流式响应streaming可以让用户边生成边看到部分结果感知上的延迟会大大降低。aikit的客户端应支持流式处理。6.5 调试工具箱养成以下习惯会让调试效率倍增可视化检索结果写一个简单的工具函数输入一个问题打印出检索到的前K个文本块及其相似度分数。直观感受检索质量。提示词版本管理使用Git管理你的提示词模板文件。每次修改提示词后记录下版本和修改原因便于对比不同提示词的效果。构建评估集准备一组有标准答案的问题QA对定期运行你的系统计算答案的相似度或请人工评估。这是衡量系统迭代是否有效的客观标准。aikit这类工具的出现标志着AI应用开发正从“手工作坊”向“工业化”迈进。它通过封装最佳实践、提供一致接口极大地降低了开发门槛。然而工具终究是工具真正的挑战和魅力在于如何将这些模块巧妙地组合起来解决那些具体、复杂且独特的业务问题。理解其设计哲学掌握核心模块的运作细节并在实战中不断调试和优化你才能充分发挥它的威力打造出真正智能、可靠的应用。