ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

构建端到端AI应用:LLM网关、RAG与MCP技能整合实战

构建端到端AI应用:LLM网关、RAG与MCP技能整合实战 在实际 AI 基础设施AI Infra项目中一个能体现综合能力的架构往往比单一技术点更有说服力。将 LLM 网关、RAG检索增强生成系统、LLM-Wiki知识库与 MCP模型上下文协议和 Skills技能等概念结合构建一个端到端的智能应用是当前面试中展示工程化思维和落地能力的绝佳案例。这类项目不仅要求你理解每个组件的原理更考验你如何将它们有机整合解决从请求路由、知识检索到任务执行的完整链路问题。本文将带你从零开始梳理这样一个项目的核心架构、技术选型、关键实现步骤以及生产环境中的考量让你不仅能将项目清晰地写到简历上更能从容应对面试官对设计细节和实战经验的深度追问。1. 理解端到端 AI 应用的核心组件与职责在开始动手之前必须厘清项目中每个核心模块的定位和它们之间的协作关系。一个典型的端到端 AI 应用不是简单的模型调用而是一个包含流量治理、知识增强、任务编排的复杂系统。1.1 LLM 网关智能流量的统一入口与调度中心LLM 网关是整个系统的门面它负责接收所有外部请求并做出智能路由。它的核心职责远不止转发 API 调用。统一接入与协议转换外部应用可能通过 HTTP、gRPC 甚至 WebSocket 发起请求。网关需要将这些异构协议统一转换为内部服务能理解的格式通常是 HTTP/JSON并将内部服务的响应转换回去。这屏蔽了后端服务的复杂性。多模型路由与负载均衡当你的系统接入了多个 LLM 服务如 OpenAI GPT、Claude、本地部署的 Llama 等时网关需要根据策略进行路由。策略可以很简单如根据请求头中的model参数指定也可以很复杂如基于成本、延迟、当前负载或甚至对请求内容的简单分析例如代码生成请求路由到 CodeLlama创意写作路由到 GPT-4进行动态选择。同时对于同一模型的不同实例网关需要实现负载均衡。限流、鉴权与监控这是网关的生产级必备功能。你需要对不同的 API Key、用户或租户实施速率限制Rate Limiting防止滥用。同时网关是实施身份认证和授权AuthN/AuthZ的理想位置。所有的请求、响应、延迟、错误率指标都应在此被收集并接入监控系统如 Prometheus Grafana。请求/响应改写与缓存网关可以对用户的原始请求进行预处理例如添加系统提示词System Prompt、格式化历史对话记录。同样它也可以对模型的原始响应进行后处理如敏感词过滤、格式标准化。对于某些重复性高的查询可以引入缓存层直接返回缓存结果以降低成本和延迟。1.2 RAG 系统为 LLM 注入精准、实时的外部知识LLM 的“幻觉”问题和知识截止日期是其主要短板。RAG 通过引入外部知识库让模型生成基于事实的答案。检索Retrieval当用户提出一个问题时RAG 系统首先将该问题转换为一个向量Embedding然后在向量数据库如 Milvus, Pinecone, Weaviate, Qdrant中搜索与之最相似的文本片段Chunks。这个过程的关键在于“索引构建”和“检索策略”。索引构建涉及文档加载、文本分割、向量化检索策略则决定了是简单返回 top-k 个结果还是使用更复杂的混合搜索结合关键词和向量相似度。增强Augmentation检索到的相关文本片段被作为额外的上下文与用户的原始问题一起组合成一个新的、信息更丰富的提示词Prompt再发送给 LLM。生成GenerationLLM 基于这个增强了上下文的提示词生成最终答案。由于答案的依据来自提供的文档其准确性和可信度大幅提高。1.3 LLM-Wiki知识库RAG 的燃料与运营平台LLM-Wiki 是 RAG 系统中“知识”的载体和管理界面。它不是一个简单的文件服务器。知识摄取与管理支持多种格式文档PDF, Word, Markdown, 网页的上传、解析和批处理。提供知识库的版本管理、更新和回滚能力。数据处理流水线这是核心后台服务。它需要将上传的文档通过文本分割、向量化模型处理最终存入向量数据库。这个流水线需要健壮能处理失败重试、增量更新。运营与评估提供界面让运营人员查看知识库内容、搜索测试效果甚至能对“问题-答案”对进行标注用于后续评估检索质量或微调排序模型。1.4 MCP模型上下文协议与 Skills扩展 LLM 的行动能力LLM 本身是“思考者”但缺乏“执行者”的能力。MCP 和 Skills 的引入就是为了让 LLM 能调用外部工具和 API完成具体任务。MCPModel Context Protocol你可以将其理解为一套标准化的“工具描述”和“调用规范”。它定义了工具Tools的名称、描述、参数及其 JSON Schema。当 LLM 决定使用某个工具时它会按照 MCP 定义的格式生成一个结构化的调用请求。一个 MCP 服务器Server则负责实现这些工具的具体逻辑如执行代码、查询数据库、调用天气 API。MCP 的核心价值在于标准化使得不同的 LLM 客户端如 Claude Desktop能无缝接入各种工具服务器。Skills这是一个更宽泛的概念可以理解为 LLM 能够执行的特定任务或能力。一个 Skill 可能通过调用一个 MCP 工具来实现也可能通过其他方式如直接调用内部 API、执行一段脚本实现。例如“查询数据库”可以是一个 Skill它背后可能对应一个“执行 SQL”的 MCP 工具。在项目描述中我们可以将 Skills 视为我们为 LLM 赋予的一系列业务能力集合。它们与网关和 RAG 的关系用户的一个复杂请求到达网关后可能被路由到一个“Agent”处理流程。这个 Agent 首先会利用 RAG 系统获取相关知识然后分析问题规划步骤期间可能会决定调用一个或多个 Skills通过 MCP 或其他方式来获取数据或执行操作最后综合所有信息生成回答。网关负责协调这个流程的入口和出口。2. 技术选型与项目环境搭建明确了架构接下来需要为每个组件选择合适的技术栈。这里给出一个基于 Java/Spring Boot 生态的流行选型方案该方案成熟、社区活跃适合作为简历项目。2.1 核心组件技术选型组件推荐技术栈备选方案选型理由LLM 网关Spring Cloud GatewayKong, Apache APISIX, 自研 Netty 服务Spring Cloud Gateway 与 Spring Boot 生态无缝集成基于 Reactor 响应式编程性能好易于通过代码定义路由、过滤逻辑。RAG 核心框架LangChain4jLangChain (Python), LlamaIndexLangChain4j 是 Java 版的 LangChain提供了文档加载、分割、向量化、检索链等高级抽象极大简化开发。向量数据库Milvus或PgVectorWeaviate, Qdrant, ChromaMilvus 是专为向量搜索设计的分布式数据库性能强劲。PgVector 是 PostgreSQL 的扩展利用现有 PG 生态运维简单。向量化模型text2vec 系列 (如 BGE)或OpenAI EmbeddingsSentence Transformers, CohereBGE 等开源模型可本地部署无网络延迟和费用。OpenAI Embeddings API 简单易用效果稳定。大语言模型OpenAI GPT 系列 API或本地 Llama 系列Claude API, 通义千问, DeepSeek初期快速验证用 API成本可控。追求数据隐私和定制化可本地部署 Llama 2/3 等模型。MCP 服务器基于 MCP SDK (如 Typescript SDK)自建使用现成 MCP 服务器 (如文件系统、Git 服务器)MCP 协议相对较新Java 原生 SDK 不完善。可选用 Node.js/Python 编写轻量级 MCP 服务器通过 HTTP 与主 Java 服务交互。Skills 实现Spring Boot RESTful APIgRPC 服务 内部 SDK将每个 Skill 实现为一个独立的微服务或一个 Spring Boot 的 Controller通过 HTTP 调用简单清晰。知识库管理Spring Boot 前端 (Vue/React)直接使用数据库管理工具需要实现上传、解析、流水线触发等后台功能以及一个简单的前端管理界面。缓存RedisCaffeine (本地缓存)缓存高频且不变的知识问答对或作为会话缓存。消息队列RabbitMQ或KafkaRedis Streams用于解耦知识文档处理流水线实现异步处理。2.2 开发环境准备清单在开始编码前请确保你的本地开发环境已就绪。Java 开发环境JDK 17 或 21LTS 版本。Maven 3.6 或 Gradle。IDEIntelliJ IDEA推荐或 Eclipse。依赖服务建议使用 Docker Compose 一键启动PostgreSQL用于存储结构化数据用户信息、知识库元数据、操作日志等。如果使用 PgVector则必须用 PostgreSQL。Milvus如果选 Milvus 作为向量数据库。Redis用于缓存和会话存储。RabbitMQ用于异步任务队列。关键工具与账号Docker Docker Compose用于快速部署依赖的中间件。OpenAI API Key或其他 LLM API 密钥用于测试。Node.js如果选择用 Node.js 编写 MCP 服务器。下面是一个简单的docker-compose.yml示例用于启动 PostgreSQL带 PgVector、Redis 和 RabbitMQversion: 3.8 services: postgres: image: ankane/pgvector:latest # 包含 pgvector 扩展的镜像 environment: POSTGRES_DB: ai_infra POSTGRES_USER: admin POSTGRES_PASSWORD: password ports: - 5432:5432 volumes: - postgres_data:/var/lib/postgresql/data redis: image: redis:7-alpine ports: - 6379:6379 command: redis-server --appendonly yes volumes: - redis_data:/data rabbitmq: image: rabbitmq:3-management-alpine ports: - 5672:5672 # AMQP 协议端口 - 15672:15672 # 管理界面端口 environment: RABBITMQ_DEFAULT_USER: guest RABBITMQ_DEFAULT_PASS: guest volumes: - rabbitmq_data:/var/lib/rabbitmq volumes: postgres_data: redis_data: rabbitmq_data:使用命令docker-compose up -d启动服务。3. 构建核心模块从 RAG 问答系统开始我们以 RAG 系统作为核心能力切入点进行构建因为它直接体现了“知识模型”的融合。3.1 项目初始化与依赖配置创建一个 Spring Boot 项目例如使用 start.spring.io 添加以下核心依赖到你的pom.xmldependencies !-- Spring Boot 基础 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency !-- LangChain4j 核心 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version0.31.0/version !-- 请检查最新版本 -- /dependency !-- LangChain4j 用于 OpenAI -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-open-ai/artifactId version0.31.0/version /dependency !-- LangChain4j 用于本地 Embedding 模型 (可选) -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-embeddings-all-minilm-l6-v2/artifactId version0.31.0/version /dependency !-- LangChain4j 集成 Milvus -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-store-embedding-milvus/artifactId version0.31.0/version /dependency !-- 数据库驱动 -- dependency groupIdorg.postgresql/groupId artifactIdpostgresql/artifactId scoperuntime/scope /dependency !-- Redis -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !-- 工具类 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies3.2 实现文档处理与向量入库流水线首先我们需要一个服务能将上传的文档如公司内部 Wiki 的 Markdown 文件处理成向量并存储。1. 定义文档实体和元数据表import javax.persistence.*; import java.time.LocalDateTime; Entity Table(name knowledge_document) Data public class KnowledgeDocument { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; private String fileName; private String filePath; private String fileType; // e.g., md, pdf private Long fileSize; private String status; // UPLOADED, PROCESSING, COMPLETED, FAILED private LocalDateTime uploadedAt; private LocalDateTime processedAt; }2. 实现文档分割与向量化服务import dev.langchain4j.data.document.Document; import dev.langchain4j.data.document.DocumentSplitter; import dev.langchain4j.data.document.splitter.DocumentSplitters; import dev.langchain4j.data.embedding.Embedding; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.store.embedding.EmbeddingStore; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.nio.file.Paths; import java.util.List; Service Slf4j RequiredArgsConstructor public class DocumentProcessingService { private final EmbeddingModel embeddingModel; // 注入 Embedding 模型 Bean private final EmbeddingStoreTextSegment embeddingStore; // 注入向量存储 Bean public void processDocument(Long docId, String filePath) { try { // 1. 加载文档 Document document Document.from(Paths.get(filePath)); // 2. 分割文档例如按段落或固定大小 DocumentSplitter splitter DocumentSplitters.recursive(500, 50); // 目标大小500字符重叠50字符 ListTextSegment segments splitter.split(document); // 3. 为每个分段生成向量 for (TextSegment segment : segments) { Embedding embedding embeddingModel.embed(segment.text()).content(); // 4. 存储向量和原始文本片段到向量数据库 embeddingStore.add(embedding, segment); } log.info(文档 {} 处理完成生成 {} 个片段。, docId, segments.size()); // 更新数据库状态为 COMPLETED } catch (Exception e) { log.error(处理文档 {} 失败, docId, e); // 更新数据库状态为 FAILED } } }3. 配置 Embedding 模型和向量存储 Bean在application.yml中配置langchain4j: open-ai: api-key: ${OPENAI_API_KEY} # 如果使用本地模型则注释掉 open-ai 配置使用下面的 embedding-model bean在 Spring 配置类中定义 Beanimport dev.langchain4j.model.openai.OpenAiEmbeddingModel; import dev.langchain4j.store.embedding.milvus.MilvusEmbeddingStore; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class LangChain4jConfig { Value(${langchain4j.open-ai.api-key}) private String openAiApiKey; // 使用 OpenAI 的 Embedding 模型 Bean public EmbeddingModel embeddingModel() { return OpenAiEmbeddingModel.builder() .apiKey(openAiApiKey) .modelName(text-embedding-3-small) // 性价比高的模型 .build(); } // 使用 Milvus 作为向量存储 Bean public EmbeddingStoreTextSegment embeddingStore() { return MilvusEmbeddingStore.builder() .host(localhost) .port(19530) .dimension(1536) // text-embedding-3-small 的向量维度 .collectionName(knowledge_base) .build(); } }注意生产环境中API Key 和数据库连接信息应通过环境变量或配置中心注入不应硬编码在配置文件中。文档处理是一个耗时操作应该通过消息队列如 RabbitMQ触发异步任务而不是在 HTTP 请求线程中同步执行。3.3 实现 RAG 问答链处理完文档后就可以实现问答接口了。1. 创建问答请求/响应对象Data public class QuestionRequest { NotBlank private String question; private String conversationId; // 用于多轮对话 } Data public class AnswerResponse { private String answer; private ListString sourceDocuments; // 引用的源文档片段用于展示和溯源 private Long latency; }2. 实现 RAG 服务import dev.langchain4j.chain.ConversationalRetrievalChain; import dev.langchain4j.memory.ChatMemory; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.retriever.EmbeddingStoreRetriever; import dev.langchain4j.store.embedding.EmbeddingStore; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.time.Duration; import java.util.List; import java.util.stream.Collectors; Service RequiredArgsConstructor public class RagService { private final EmbeddingStoreTextSegment embeddingStore; private final EmbeddingModel embeddingModel; public AnswerResponse answerQuestion(QuestionRequest request) { long start System.currentTimeMillis(); // 1. 创建检索器 (Retriever) EmbeddingStoreRetriever retriever EmbeddingStoreRetriever.from(embeddingStore, embeddingModel, 3); // 返回 top-3 相关片段 // 2. 创建对话模型 ChatLanguageModel chatModel OpenAiChatModel.builder() .apiKey(System.getenv(OPENAI_API_KEY)) .modelName(gpt-3.5-turbo) .temperature(0.2) // 降低随机性使答案更确定 .timeout(Duration.ofSeconds(30)) .build(); // 3. 创建对话记忆用于多轮对话 ChatMemory chatMemory MessageWindowChatMemory.withMaxMessages(10); // 4. 构建检索增强对话链 ConversationalRetrievalChain chain ConversationalRetrievalChain.builder() .chatLanguageModel(chatModel) .retriever(retriever) .chatMemory(chatMemory) // 可选传入 conversationId 关联记忆 .build(); // 5. 执行链 String answer chain.execute(request.getQuestion()); // 6. 可选获取引用的源文档 // 注意LangChain4j 的链默认不直接返回源片段需要自定义或从检索器中再查一次。 ListTextSegment relevantSegments retriever.findRelevant(request.getQuestion()); ListString sources relevantSegments.stream() .map(TextSegment::text) .limit(3) .collect(Collectors.toList()); long latency System.currentTimeMillis() - start; AnswerResponse response new AnswerResponse(); response.setAnswer(answer); response.setSourceDocuments(sources); response.setLatency(latency); return response; } }3. 创建 RESTful 控制器import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; RestController RequestMapping(/api/rag) RequiredArgsConstructor public class RagController { private final RagService ragService; PostMapping(/ask) public AnswerResponse ask(Valid RequestBody QuestionRequest request) { return ragService.answerQuestion(request); } }现在启动应用向POST /api/rag/ask发送一个包含问题的 JSON 请求就能获得基于你已入库知识的答案了。4. 集成 LLM 网关与 Skills 能力有了核心的 RAG 能力我们需要一个更智能的网关来路由请求并让 LLM 能够调用外部 Skills。4.1 构建 Spring Cloud Gateway 作为 LLM 网关创建一个新的 Spring Boot 项目引入spring-cloud-starter-gateway依赖。1. 基础路由配置 (application.yml):server: port: 8080 spring: cloud: gateway: routes: - id: rag_service uri: http://localhost:8081 # RAG 后端服务地址 predicates: - Path/api/rag/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 # 每秒令牌数 redis-rate-limiter.burstCapacity: 20 # 令牌桶容量 redis-rate-limiter.requestedTokens: 1 # 每次请求消耗令牌数 - AddRequestHeaderX-API-Key, ${INTERNAL_API_KEY} # 内部服务间认证 - id: skills_service uri: http://localhost:8082 # Skills 服务地址 predicates: - Path/api/skills/** - id: direct_llm_proxy # 直接代理到外部 LLM API如 OpenAI uri: https://api.openai.com predicates: - Path/v1/** filters: - RewritePath/v1/(?segment.*), /v1/$\{segment} # 路径重写 - AddRequestHeaderAuthorization, Bearer ${OPENAI_API_KEY}2. 实现基于内容的智能路由更高级的网关可以根据请求内容决定路由。例如判断用户问题是需要知识库回答走 RAG还是需要执行一个计算任务走 Skills。import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.util.List; Component public class ContentBasedRoutingFilter implements GlobalFilter, Ordered { private final RouterService routerService; // 一个简单的分类服务 Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 只处理特定路径例如 /api/chat if (!exchange.getRequest().getPath().value().startsWith(/api/chat)) { return chain.filter(exchange); } // 从请求体或查询参数中获取用户消息注意这里简化了实际需要缓存或读取请求体 String userMessage exchange.getRequest().getQueryParams().getFirst(message); if (userMessage null) { return chain.filter(exchange); } // 调用一个简单的分类器可以是规则也可以是一个轻量级 ML 模型 String route routerService.classify(userMessage); // 根据分类结果修改请求路径转发到不同服务 ServerWebExchange modifiedExchange; switch (route) { case rag: modifiedExchange exchange.mutate() .request(exchange.getRequest().mutate().path(/api/rag/ask).build()) .build(); break; case calculator: modifiedExchange exchange.mutate() .request(exchange.getRequest().mutate().path(/api/skills/calculate).build()) .build(); break; default: // 默认走直接 LLM modifiedExchange exchange.mutate() .request(exchange.getRequest().mutate().path(/v1/chat/completions).build()) .build(); } return chain.filter(modifiedExchange); } Override public int getOrder() { return -1; // 高优先级 } }4.2 实现 Skills 服务Skills 服务是一个独立的 Spring Boot 应用提供各种可被 LLM 调用的工具 API。示例实现一个“天气查询” Skill// Skill 请求体 Data public class WeatherQueryRequest { private String city; private String date; // yyyy-MM-dd } // Skill 响应体 Data public class WeatherQueryResponse { private String city; private String date; private String condition; private String temperature; private String source; } RestController RequestMapping(/api/skills/weather) public class WeatherSkillController { PostMapping(/query) public WeatherQueryResponse queryWeather(RequestBody WeatherQueryRequest request) { // 这里模拟调用外部天气 API实际项目中应使用 RestTemplate 或 WebClient // 例如调用和风天气、OpenWeatherMap 等 WeatherQueryResponse response new WeatherQueryResponse(); response.setCity(request.getCity()); response.setDate(request.getDate()); response.setCondition(Sunny); response.setTemperature(22°C); response.setSource(Simulated Data); return response; } }关键点每个 Skill 的 API 应该设计得清晰、无状态输入输出最好是结构化的 JSON方便 LLM 通过 Function Calling 或类似机制来理解和调用。4.3 让 LLM 学会调用 SkillsFunction Calling 集成现在我们需要让 LLM例如 GPT知道它有哪些 Skills 可用并在需要时调用它们。这通常通过Function Calling机制实现。在 RAG 服务或一个新的 Agent 服务中你需要定义工具函数列表将每个 Skill 描述成一个函数包括名称、描述和参数 JSON Schema。在调用 LLM 时传入工具列表。解析 LLM 的响应如果 LLM 决定调用工具它的响应会包含一个特殊的tool_calls字段。执行工具调用根据 LLM 的指示去调用对应的 Skills 服务 API。将工具执行结果返回给 LLM让它生成最终回答给用户。这是一个简化的流程LangChain4j 提供了ToolExecutor等高级抽象来简化这个过程。你需要将 Skills 服务封装成Tool对象。// 示例使用 LangChain4j 的 AiServices 和 Tool 注解简化概念 import dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.UserMessage; import dev.langchain4j.service.V; import dev.langchain4j.service.spring.AiService; interface Assistant { String chat(String userMessage); } Service public class AgentService { private final Assistant assistant; public AgentService(ChatLanguageModel chatModel) { this.assistant AiServices.create(Assistant.class, chatModel, tools()); } private ListObject tools() { ListObject tools new ArrayList(); // 将 Skill 封装成 Tool tools.add(new WeatherTool(weatherService)); tools.add(new CalculatorTool()); // ... 添加其他 Skills return tools; } public String process(String userInput) { return assistant.chat(userInput); } } // 一个 Tool 的实现示例 class WeatherTool { Tool(Get the weather forecast for a specific city and date.) public String getWeatherForecast(P(The city name, e.g. Beijing) String city, P(The date in yyyy-MM-dd format) String date) { // 调用上面定义的 WeatherSkillController 的 API // 返回结构化的天气信息字符串 return String.format(The weather in %s on %s is Sunny, 22°C., city, date); } }这样当用户问“北京明天天气怎么样”时LLM 会识别出需要调用getWeatherForecast工具并生成正确的参数。你的服务会执行这个工具调用获取结果并最终由 LLM 整合成自然语言回复。5. 生产环境考量与常见问题排查一个能在简历上吸引面试官的项目必须体现出你对生产环境的思考。以下是几个关键方面和常见问题。5.1 生产环境部署清单方面具体措施配置外置化所有 API Key、数据库连接串、服务地址等敏感信息必须通过环境变量或配置中心如 Nacos, Apollo管理。健康检查与监控为每个服务添加 Spring Boot Actuator 端点 (/actuator/health)。集成 Prometheus 收集指标请求量、延迟、错误率、向量检索耗时。设置 Grafana 仪表盘。日志聚合使用 ELKElasticsearch, Logstash, Kibana或 Loki 集中收集和查询日志确保每个请求有唯一 Trace ID 串联所有服务日志。限流与熔断在网关层对用户和 API Key 实施限流。在服务间调用使用 Resilience4j 实现熔断、降级和重试。向量数据库运维Milvus 需要关注内存、磁盘和索引构建性能。PgVector 需要关注 PostgreSQL 的性能调优和索引IVFFlat 或 HNSW选择。定期进行向量数据的备份。知识库更新设计增量更新机制避免全量重建索引。使用消息队列解耦文档上传和处理过程。安全性API 网关实施 JWT 或 OAuth2 认证。对用户输入进行严格的校验和过滤防止 Prompt 注入攻击。Skills 服务接口需有权限控制。5.2 常见问题与排查路径问题现象可能原因检查点与解决方案RAG 回答不准确或“幻觉”1. 检索到的文档片段不相关。2. 提示词Prompt设计不佳。3. 上下文长度超限关键信息被截断。1.检查检索结果在问答接口中返回sourceDocuments人工评估相关性。可尝试调整文本分割策略大小、重叠或使用混合搜索关键词向量。2.优化 Prompt在系统提示词中明确要求“基于提供的上下文回答”并设定“不知道就说不知道”的规则。3.检查 Token 数计算输入上下文的 Token 数确保未超过模型限制。可对检索结果进行重排序或摘要。向量检索速度慢1. 向量数据库未建立索引或索引类型不当。2. 检索的向量维度太高。3. 网络延迟或资源不足。1.检查索引在 Milvus 或 PgVector 中为向量列创建合适的索引如 HNSW。2.考虑降维评估是否可以使用维度更低的 Embedding 模型如text-embedding-3-small的 1536 维。3.监控资源查看向量数据库的 CPU、内存和网络 IO。LLM 网关路由错误1. 路由规则配置错误。2. 基于内容的分类器RouterService判断失误。3. 下游服务不可用。1.检查网关日志查看spring-cloud-gateway的 DEBUG 级别日志确认路由匹配过程。2.测试分类器提供一批测试用例验证分类逻辑的准确性。3.检查下游服务健康网关应集成服务发现和健康检查自动剔除不健康的实例。Skills 调用失败1. LLM 生成的调用参数格式错误。2. Skills 服务 API 变更或宕机。3. 网络超时。1.加强参数校验在 Skills 服务端对输入参数进行严格校验并返回清晰的错误信息。2.实现 Fallback当 Skill 调用失败时提供降级方案如返回默认值或让 LLM 换一种方式回答。3.设置合理超时在调用 Skills 的 HTTP 客户端中配置连接超时和读取超时。知识文档处理失败1. 文档格式解析出错如损坏的 PDF。2. 向量化模型服务异常。3. 向量数据库写入失败。1.增强解析容错使用更健壮的解析库如 Apache Tika并对解析失败的文件进行记录和告警。2.异步与重试将处理任务放入消息队列实现失败后的自动重试和死信队列管理。3.监控处理流水线记录每个处理阶段的成功/失败状态和耗时。5.3 性能与成本优化建议Embedding 模型选型如果对延迟敏感且数据可公开优先考虑本地部署的开源 Embedding 模型如 BGE、E5。如果追求效果和稳定性可使用 OpenAI 等云服务但需关注成本。向量检索优化合理设置top_k参数通常 3-5 足够返回过多片段会增加 LLM 的 Token 消耗和成本。对检索结果可以使用重排序Re-ranking模型进行精排提升相关性。LLM 调用优化缓存对相同或相似的问答对进行缓存可以显著降低成本和延迟。缓存可以放在 Redis 中键可以是问题的 Embedding 向量或哈希值。流式响应对于长文本生成使用流式Streaming响应可以提升用户体验。模型分级简单的查询使用低成本模型如 GPT-3.5-Turbo复杂、重要的查询再使用高性能模型如 GPT-4。Skills 设计Skills 应保持轻量化和无状态避免长时间运行的操作。如果 Skill 本身很耗时应设计为异步触发通过回调或轮询告知结果。将这个项目写入简历时重点突出你如何设计并实现了一个整合了 LLM 网关、RAG、知识库和 Skills 的端到端 AI 应用系统强调你在架构设计、技术选型、生产问题排查和性能优化方面的思考与实践。面试时你可以沿着“需求分析 - 架构设计 - 技术选型理由 - 关键实现细节 - 遇到的坑及解决方案 - 未来优化方向”这条主线来阐述这远比单纯罗列技术名词更有深度。
返回列表