ArafatKhan2198 commented on code in PR #9915: URL: https://github.com/apache/ozone/pull/9915#discussion_r3221115668
########## hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/chatbot/api/ChatbotEndpoint.java: ########## @@ -0,0 +1,268 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.ozone.recon.chatbot.api; + +import javax.inject.Inject; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.ozone.recon.chatbot.ChatbotConfigKeys; +import org.apache.hadoop.ozone.recon.chatbot.agent.ChatbotAgent; +import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * REST API endpoint for the Recon Chatbot. + * + * <p> + * API keys are managed via JCEKS (admin-configured), + * so there are no per-user key storage endpoints. + * </p> + */ +@Path("/chatbot") +@Produces(MediaType.APPLICATION_JSON) +public class ChatbotEndpoint { + + private static final Logger LOG = LoggerFactory.getLogger(ChatbotEndpoint.class); + + private final ChatbotAgent chatbotAgent; + private final LLMClient llmClient; + private final OzoneConfiguration configuration; + + @Inject + public ChatbotEndpoint(ChatbotAgent chatbotAgent, + LLMClient llmClient, + OzoneConfiguration configuration) { + this.chatbotAgent = chatbotAgent; + this.llmClient = llmClient; + this.configuration = configuration; + + LOG.info("ChatbotEndpoint initialized via Guice injection"); + } + + /** + * Checks if the chatbot is enabled in configuration. + */ + private boolean isChatbotEnabled() { + return configuration.getBoolean( + ChatbotConfigKeys.OZONE_RECON_CHATBOT_ENABLED, + ChatbotConfigKeys.OZONE_RECON_CHATBOT_ENABLED_DEFAULT); + } + + /** + * Health check endpoint. + */ + @GET + @Path("/health") + public Response health() { + Map<String, Object> response = new HashMap<>(); + boolean enabled = isChatbotEnabled(); + response.put("enabled", enabled); + response.put("llmClientAvailable", + enabled && llmClient != null && llmClient.isAvailable()); + return Response.ok(response).build(); + } + + /** + * Chat endpoint - processes a user query. + */ + @POST + @Path("/chat") + @Consumes(MediaType.APPLICATION_JSON) + public Response chat(ChatRequest request) { + + // Safety check 1: If chatbot is disabled, throw a 503 Service Unavailable error immediately. + if (!isChatbotEnabled()) { + return Response.status(Response.Status.SERVICE_UNAVAILABLE) + .entity(Collections.singletonMap("error", "Chatbot service is not enabled")) + .build(); + } + + // Safety check 2: If the user didn't really ask a question, throw a 400 Bad Request. + if (request.getQuery() == null || request.getQuery().trim().isEmpty()) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Collections.singletonMap("error", "Query cannot be empty")) + .build(); + } + + try { + LOG.info("Chat request: userId={}, model={}, provider={}", + sanitizeUserId(request.getUserId()), + request.getModel() == null ? "default" : request.getModel(), + request.getProvider() == null ? "auto" : request.getProvider()); + + // Pass the user's question to the Brain (ChatbotAgent) to do all the hard work. + // This step takes a few seconds because it talks to Gemini and the Recon APIs. + String response = chatbotAgent.processQuery( + request.getQuery(), + request.getModel(), + request.getProvider(), + null); Review Comment: As discussed earlier in our discussions, Recon currently has no per-user authorization model all authenticated users are cluster admins with identical access. The server-side shared API key is consistent with how all other Recon credentials work. Per-user key management would only be meaningful once Recon gains a user identity and authorization layer, which is a separate, much larger effort. This PR intentionally documents the shared-key design in the class Javadoc. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
