Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
devmadhuu commented on PR #9915: URL: https://github.com/apache/ozone/pull/9915#issuecomment-4614654457 @ArafatKhan2198 though PR is merged by you, but as discussed, I still don't see any test if chatbot is functional in secure cluster which is bare minimum requirement. -- 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]
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
adoroszlai commented on code in PR #9915: URL: https://github.com/apache/ozone/pull/9915#discussion_r3346138376 ## hadoop-ozone/dist/src/main/license/bin/LICENSE.txt: ## @@ -261,6 +261,21 @@ CDDL 1.1 + GPLv2 with classpath exception org.glassfish.jaxb:txw2 +Apache License 2.0 += + com.squareup.okhttp3:okhttp + com.squareup.okhttp3:okhttp-sse + com.squareup.okio:okio + com.squareup.retrofit2:converter-jackson + com.squareup.retrofit2:retrofit + dev.ai4j:openai4j + dev.langchain4j:langchain4j-anthropic + dev.langchain4j:langchain4j-core + dev.langchain4j:langchain4j-open-ai + org.jetbrains.kotlin:kotlin-stdlib-common + org.jetbrains.kotlin:kotlin-stdlib-jdk7 + org.jetbrains.kotlin:kotlin-stdlib-jdk8 + Apache License 2.0 = Review Comment: Created HDDS-15469 for this. -- 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]
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
adoroszlai commented on code in PR #9915:
URL: https://github.com/apache/ozone/pull/9915#discussion_r3346119890
##
pom.xml:
##
@@ -1656,6 +1675,57 @@
snakeyaml
${snakeyaml.version}
+
+
+software.amazon.awssdk
+apache-client
+${aws-java-sdk2.version}
+
+
+software.amazon.awssdk
+auth
+${aws-java-sdk2.version}
+
+
+software.amazon.awssdk
+aws-core
+${aws-java-sdk2.version}
+
+
+software.amazon.awssdk
+http-client-spi
+${aws-java-sdk2.version}
+
+
+software.amazon.awssdk
+identity-spi
+${aws-java-sdk2.version}
+
+
+software.amazon.awssdk
+regions
+${aws-java-sdk2.version}
+
+
+software.amazon.awssdk
+s3
+${aws-java-sdk2.version}
+
+
+software.amazon.awssdk
+s3-transfer-manager
+${aws-java-sdk2.version}
+
+
+software.amazon.awssdk
+sdk-core
+${aws-java-sdk2.version}
+
+
+software.amazon.awssdk
+utils
+${aws-java-sdk2.version}
+
Review Comment:
This will also be fixed in #10421.
--
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]
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
adoroszlai commented on code in PR #9915:
URL: https://github.com/apache/ozone/pull/9915#discussion_r3346119125
##
pom.xml:
##
@@ -1601,6 +1609,17 @@
jooq-meta
${jooq.version}
+
+
+org.junit.jupiter
+junit-jupiter-api
+${junit5.version}
+
+
+org.junit.jupiter
+junit-jupiter-params
+${junit5.version}
+
Review Comment:
This will be fixed in #10421.
--
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]
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
adoroszlai commented on code in PR #9915:
URL: https://github.com/apache/ozone/pull/9915#discussion_r3340978215
##
hadoop-ozone/dist/src/main/license/jar-report.txt:
##
@@ -2,14 +2,14 @@ share/ozone/lib/aircompressor.jar
share/ozone/lib/animal-sniffer-annotations.jar
share/ozone/lib/annotations.jar
share/ozone/lib/annotations.jar
-share/ozone/lib/apache-log4j-extras.jar
-share/ozone/lib/aopalliance.jar
share/ozone/lib/aopalliance-repackaged.jar
+share/ozone/lib/aopalliance.jar
+share/ozone/lib/apache-log4j-extras.jar
Review Comment:
Please don't move around lines unnecessarily.
##
hadoop-ozone/dist/src/main/compose/ozone/docker-config:
##
@@ -23,7 +23,7 @@ CORE-SITE.XML_hadoop.proxyuser.hadoop.groups=*
OZONE-SITE.XML_ozone.om.address=om
OZONE-SITE.XML_ozone.om.http-address=om:9874
OZONE-SITE.XML_ozone.scm.http-address=scm:9876
-OZONE-SITE.XML_ozone.scm.container.size=100MB
+OZONE-SITE.XML_ozone.scm.container.size=1GB
Review Comment:
Why was this changed? We use a small value to reduce chances of disk out of
space error in acceptance tests.
##
hadoop-ozone/dist/src/main/license/bin/LICENSE.txt:
##
@@ -261,6 +261,21 @@ CDDL 1.1 + GPLv2 with classpath exception
org.glassfish.jaxb:txw2
+Apache License 2.0
+=
+ com.squareup.okhttp3:okhttp
+ com.squareup.okhttp3:okhttp-sse
+ com.squareup.okio:okio
+ com.squareup.retrofit2:converter-jackson
+ com.squareup.retrofit2:retrofit
+ dev.ai4j:openai4j
+ dev.langchain4j:langchain4j-anthropic
+ dev.langchain4j:langchain4j-core
+ dev.langchain4j:langchain4j-open-ai
+ org.jetbrains.kotlin:kotlin-stdlib-common
+ org.jetbrains.kotlin:kotlin-stdlib-jdk7
+ org.jetbrains.kotlin:kotlin-stdlib-jdk8
+
Apache License 2.0
=
Review Comment:
Please don't duplicate License sections, add the dependencies in existing
sections in alphabetical order.
##
pom.xml:
##
@@ -1601,6 +1609,17 @@
jooq-meta
${jooq.version}
+
+
+org.junit.jupiter
+junit-jupiter-api
+${junit5.version}
+
+
+org.junit.jupiter
+junit-jupiter-params
+${junit5.version}
+
Review Comment:
We already import `junit-bom`.
https://github.com/apache/ozone/blob/2020d864c78c90582f9f3f661ea98a49cb51e608/pom.xml#L300-L306
##
pom.xml:
##
@@ -1656,6 +1675,57 @@
snakeyaml
${snakeyaml.version}
+
+
+software.amazon.awssdk
+apache-client
+${aws-java-sdk2.version}
+
+
+software.amazon.awssdk
+auth
+${aws-java-sdk2.version}
+
+
+software.amazon.awssdk
+aws-core
+${aws-java-sdk2.version}
+
+
+software.amazon.awssdk
+http-client-spi
+${aws-java-sdk2.version}
+
+
+software.amazon.awssdk
+identity-spi
+${aws-java-sdk2.version}
+
+
+software.amazon.awssdk
+regions
+${aws-java-sdk2.version}
+
+
+software.amazon.awssdk
+s3
+${aws-java-sdk2.version}
+
+
+software.amazon.awssdk
+s3-transfer-manager
+${aws-java-sdk2.version}
+
+
+software.amazon.awssdk
+sdk-core
+${aws-java-sdk2.version}
+
+
+software.amazon.awssdk
+utils
+${aws-java-sdk2.version}
+
Review Comment:
These are unnecessary. AWS SDK v2 is only used in tests and that module
uses the BOM:
https://github.com/apache/ozone/blob/2020d864c78c90582f9f3f661ea98a49cb51e608/hadoop-ozone/integration-test-s3/pom.xml#L29-L30
--
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]
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
ArafatKhan2198 merged PR #9915: URL: https://github.com/apache/ozone/pull/9915 -- 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]
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
ArafatKhan2198 commented on PR #9915: URL: https://github.com/apache/ozone/pull/9915#issuecomment-4599559387 ## Green Run - https://github.com/ArafatKhan2198/ozone/actions/runs/26800762880 -- 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]
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
devmadhuu commented on code in PR #9915: URL: https://github.com/apache/ozone/pull/9915#discussion_r468255 ## hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/chatbot/ChatbotConfigKeys.java: ## @@ -0,0 +1,196 @@ +/* + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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; + +import org.apache.hadoop.hdds.annotation.InterfaceAudience; +import org.apache.hadoop.hdds.annotation.InterfaceStability; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; + +/** + * Configuration keys for Recon Chatbot service. + */ [email protected] [email protected] +public final class ChatbotConfigKeys { + + public static final String OZONE_RECON_CHATBOT_PREFIX = "ozone.recon.chatbot."; + + // ── Feature toggle ── + public static final String OZONE_RECON_CHATBOT_ENABLED = OZONE_RECON_CHATBOT_PREFIX + "enabled"; + public static final boolean OZONE_RECON_CHATBOT_ENABLED_DEFAULT = false; + + // ── Provider selection ── + /** + * Active default provider: openai, gemini, anthropic. + */ + public static final String OZONE_RECON_CHATBOT_PROVIDER = OZONE_RECON_CHATBOT_PREFIX + "provider"; + public static final String OZONE_RECON_CHATBOT_PROVIDER_DEFAULT = "gemini"; + + // ── Default model ─── + public static final String OZONE_RECON_CHATBOT_DEFAULT_MODEL = OZONE_RECON_CHATBOT_PREFIX + "default.model"; + public static final String OZONE_RECON_CHATBOT_DEFAULT_MODEL_DEFAULT = "gemini-2.5-flash"; + + // ── HTTP timeout for provider calls ─ + public static final String OZONE_RECON_CHATBOT_TIMEOUT_MS = OZONE_RECON_CHATBOT_PREFIX + "timeout.ms"; + public static final int OZONE_RECON_CHATBOT_TIMEOUT_MS_DEFAULT = 12; + + // ── Per-provider API keys (resolved via JCEKS / CredentialHelper) ── + public static final String OZONE_RECON_CHATBOT_OPENAI_API_KEY = OZONE_RECON_CHATBOT_PREFIX + "openai.api.key"; + public static final String OZONE_RECON_CHATBOT_GEMINI_API_KEY = OZONE_RECON_CHATBOT_PREFIX + "gemini.api.key"; + public static final String OZONE_RECON_CHATBOT_ANTHROPIC_API_KEY = OZONE_RECON_CHATBOT_PREFIX + + "anthropic.api.key"; + + // ── Per-provider base URL overrides (optional) ── + public static final String OZONE_RECON_CHATBOT_OPENAI_BASE_URL = OZONE_RECON_CHATBOT_PREFIX + "openai.base.url"; + public static final String OZONE_RECON_CHATBOT_OPENAI_BASE_URL_DEFAULT = "https://api.openai.com";; + + // ── Execution policy + public static final String OZONE_RECON_CHATBOT_EXEC_MAX_RECORDS = OZONE_RECON_CHATBOT_PREFIX + + "exec.max.records"; Review Comment: If this is really needed. ## hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/chatbot/agent/ToolExecutor.java: ## @@ -0,0 +1,373 @@ +/* + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.agent; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.Ht
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
devmadhuu commented on code in PR #9915:
URL: https://github.com/apache/ozone/pull/9915#discussion_r126609
##
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/chatbot/llm/LangChain4jDispatcher.java:
##
@@ -0,0 +1,414 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.llm;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import dev.langchain4j.data.message.AiMessage;
+import dev.langchain4j.data.message.SystemMessage;
+import dev.langchain4j.data.message.UserMessage;
+import dev.langchain4j.model.anthropic.AnthropicChatModel;
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.chat.request.ChatRequest;
+import dev.langchain4j.model.chat.response.ChatResponse;
+import dev.langchain4j.model.googleai.GoogleAiGeminiChatModel;
+import dev.langchain4j.model.openai.OpenAiChatModel;
+import dev.langchain4j.model.output.TokenUsage;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.recon.chatbot.ChatbotConfigKeys;
+import org.apache.hadoop.ozone.recon.chatbot.security.CredentialHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link LLMClient} implementation backed by
+ * https://github.com/langchain4j/langchain4j";>LangChain4j.
+ *
+ * This is the only class in the chatbot that knows about LangChain4j. It
resolves the
+ * correct provider for a given model, builds a {@link ChatLanguageModel},
translates the
+ * message list into LangChain4j types, fires the completion, and returns a
normalised
+ * {@link LLMResponse}. Everything above this class ({@code ChatbotAgent},
+ * {@code ChatbotEndpoint}) depends only on the {@link LLMClient}
interface.
+ *
+ * Startup: reads configuration and checks which providers have API
keys. No
+ * network calls are made until {@link #chatCompletion} is first invoked.
+ *
+ * Provider routing — resolved in this order on every call:
+ *
+ * Explicit {@code _provider} key in the parameters map, or a {@code
provider:model}
+ * prefix in the model string.
+ * Reverse lookup in the configured model lists ({@link
#supportedModels}): the same
+ * map that drives {@code GET /chatbot/models}. Adding a model to
+ * {@code ozone.recon.chatbot.openai.models} in {@code ozone-site.xml}
makes it
+ * routable with no code change.
+ * If the model is not found and no hint was given, {@link LLMException}
is thrown
+ * directing the caller to {@code GET /api/v1/chatbot/models}.
+ *
+ *
+ * Model caching: building a {@link ChatLanguageModel} creates an
HTTP client and
+ * SSL context, so each {@code (provider, model)} pair is built once and
cached in
+ * {@link #modelCache}. If the first call with that model fails, the entry is
evicted so a
+ * bad model name cannot get stuck in the cache permanently.
+ */
+@Singleton
+public class LangChain4jDispatcher implements LLMClient {
+
+ private static final Logger LOG =
+ LoggerFactory.getLogger(LangChain4jDispatcher.class);
+
+ private final OzoneConfiguration configuration;
+ private final CredentialHelper credentialHelper;
+ private final Duration timeout;
+ private final String defaultProvider;
+
+ /**
+ * Per-provider static model lists — used by getSupportedModels() and
isAvailable().
+ * A provider only appears here if its API key is configured.
+ */
+ private final Map> supportedModels = new HashMap<>();
+
+ /**
+ * Cache of built {@link ChatLanguageModel} instances, keyed by {@code
"provider:model"}.
+ *
+ * Building a model involves constructing an HTTP client, SSL context,
and connection pool —
+ * expensive operations that should happen once, not on every request. This
cache ensures each
+ * (provider, model) pair is built exactly once and then reused for all
subsequent calls.
+ *
+ * {@link ConcurrentHashMap} is used because multiple chatbot executor
threads may call
+ * {@link
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
ArafatKhan2198 commented on code in PR #9915: URL: https://github.com/apache/ozone/pull/9915#discussion_r3325061227 ## hadoop-ozone/recon/src/main/resources/chatbot/recon-fallback-prompt-template.txt: ## Review Comment: I tried to avoid adding license headers to these files, as the raw text is fed directly to the LLM and the header might interfere with the prompts. Instead, I have added it to rat-exclusions.txt to exclude the chatbot resources directory from the RAT check. -- 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]
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
ArafatKhan2198 commented on code in PR #9915: URL: https://github.com/apache/ozone/pull/9915#discussion_r3325028303 ## hadoop-ozone/recon/pom.xml: ## @@ -62,6 +62,22 @@ commons-io commons-io + + dev.langchain4j + langchain4j-anthropic + + + dev.langchain4j + langchain4j-core + Review Comment: Done. I've updated both jar-report.txt and LICENSE.txt to include the new transitive dependencies introduced by LangChain4j (such as jtokkit, okhttp, retrofit, and kotlin-stdlib). -- 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]
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
ArafatKhan2198 commented on code in PR #9915: URL: https://github.com/apache/ozone/pull/9915#discussion_r3325026354 ## pom.xml: ## @@ -569,6 +569,26 @@ + +dev.langchain4j +langchain4j-anthropic +0.36.2 + + +dev.langchain4j +langchain4j-core +0.36.2 Review Comment: Thanks for pointing this out. LangChain4j officially dropped Java 8 support starting with 0.36.0. I have downgraded the langchain4j.version to 0.35.0. This is the latest release that should compiles to Java 8. -- 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]
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
adoroszlai commented on code in PR #9915: URL: https://github.com/apache/ozone/pull/9915#discussion_r3319512100 ## pom.xml: ## @@ -515,6 +569,26 @@ + +dev.langchain4j +langchain4j-anthropic +0.36.2 + + +dev.langchain4j +langchain4j-core +0.36.2 + + +dev.langchain4j +langchain4j-google-ai-gemini +0.36.2 + + +dev.langchain4j +langchain4j-open-ai +0.36.2 + Review Comment: > every other third-party library in the project follows this same pattern of individual version entries. Introducing a BOM import for just this one library would be inconsistent with how the rest of the project is structured. 1. BOM import has been in use for quite some time now, so please do switch to `langchain4j-bom`: https://github.com/apache/ozone/blob/cfb8adea488da08a56f75acf46011574325482aa/pom.xml#L229-L327 2. Even if BOM is not available for some dependency, please define a property for consistent versioning of multi-module dependency, example: https://github.com/apache/ozone/blob/cfb8adea488da08a56f75acf46011574325482aa/pom.xml#L517-L526 ## hadoop-ozone/recon/pom.xml: ## @@ -62,6 +62,22 @@ commons-io commons-io + + dev.langchain4j + langchain4j-anthropic + + + dev.langchain4j + langchain4j-core + Review Comment: ``` Jar files under share/ozone/lib in the build have changed. Please update: * hadoop-ozone/dist/src/main/license/bin/LICENSE.txt (add new dependencies with the appropriate license, delete any removed dependencies) * hadoop-ozone/dist/src/main/license/jar-report.txt (based on the diff shown below) If you notice unexpected differences (can happen when the check is run in a fork), please first update your branch from upstream master to get any other recent dependency changes. If you are running this locally after build with -DskipShade, please ignore any ozone-filesystem jars reported to be missing. Changes detected: --- /dev/fd/63 2026-05-27 10:02:54.566056827 + +++ /dev/fd/62 2026-05-27 10:02:54.566056827 + @@ -35,6 +35,7 @@ share/ozone/lib/commons-pool2.jar share/ozone/lib/commons-text.jar share/ozone/lib/commons-validator.jar +share/ozone/lib/converter-jackson.jar share/ozone/lib/curator-client.jar share/ozone/lib/curator-framework.jar share/ozone/lib/derby.jar @@ -158,6 +159,7 @@ share/ozone/lib/jsp-api.jar share/ozone/lib/jspecify.jar share/ozone/lib/jsr311-api.jar +share/ozone/lib/jtokkit.jar share/ozone/lib/kerb-core.jar share/ozone/lib/kerb-crypto.jar share/ozone/lib/kerb-util.jar @@ -165,7 +167,14 @@ share/ozone/lib/kerby-config.jar share/ozone/lib/kerby-pkix.jar share/ozone/lib/kerby-util.jar +share/ozone/lib/kotlin-stdlib-common.jar +share/ozone/lib/kotlin-stdlib-jdk7.jar +share/ozone/lib/kotlin-stdlib-jdk8.jar share/ozone/lib/kotlin-stdlib.jar +share/ozone/lib/langchain4j-anthropic.jar +share/ozone/lib/langchain4j-core.jar +share/ozone/lib/langchain4j-google-ai-gemini.jar +share/ozone/lib/langchain4j-open-ai.jar share/ozone/lib/listenablefuture-empty-to-avoid-conflict-with-guava.jar share/ozone/lib/log4j-api.jar share/ozone/lib/log4j-core.jar @@ -193,7 +202,11 @@ share/ozone/lib/netty-transport.Final.jar share/ozone/lib/nimbus-jose-jwt.jar share/ozone/lib/okhttp-jvm.jar +share/ozone/lib/okhttp-sse.jar +share/ozone/lib/okhttp.jar share/ozone/lib/okio-jvm.jar +share/ozone/lib/okio.jar +share/ozone/lib/openai4j.jar share/ozone/lib/opentelemetry-api.jar share/ozone/lib/opentelemetry-common.jar share/ozone/lib/opentelemetry-context.jar @@ -259,6 +272,7 @@ share/ozone/lib/re2j.jar share/ozone/lib/reflections.jar share/ozone/lib/reload4j.jar +share/ozone/lib/retrofit.jar share/ozone/lib/rocksdb-checkpoint-differ.jar share/ozone/lib/rocksdbjni.jar share/ozone/lib/simpleclient.jar ``` https://github.com/ArafatKhan2198/ozone/actions/runs/26503992485/job/78053259617#step:13:18 ## hadoop-ozone/recon/src/main/resources/chatbot/recon-fallback-prompt-template.txt: ## Review Comment: ``` Files with unapproved licenses: /home/runner/work/ozone/ozone/hadoop-ozone/recon/src/main/resources/chatbot/recon-summarization-prompt.txt /home/runner/work/ozone/ozone/hadoop-ozone/recon/src/main/resources/chatbot/recon-fallback-prompt-template.txt /home/runner/work/ozone/ozone/hadoop-ozone/recon/src/main/resources/chatbot/recon-api-guide.md /home/runner/work/ozone/ozone/hadoop-ozone/recon/src/main/resources/chatbot/recon-tool-selection-prompt-preamble.txt ``` ## pom.xml: ##
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
devmadhuu commented on code in PR #9915:
URL: https://github.com/apache/ozone/pull/9915#discussion_r3273813427
##
hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/chatbot/llm/TestLLMDispatcher.java:
##
@@ -0,0 +1,191 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.llm;
+
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.recon.chatbot.ChatbotConfigKeys;
+import org.apache.hadoop.ozone.recon.chatbot.security.CredentialHelper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link LangChain4jDispatcher}.
+ *
+ * These tests exercise routing (which provider is selected for a given
model name)
+ * and availability checking without making any real network calls.
+ * All tests that verify provider routing do so by confirming the correct
provider name
+ * appears in the exception message thrown when no API key is configured —
this is the
+ * cheapest way to prove the routing decision without mocking the LangChain4j
internals.
+ */
+public class TestLLMDispatcher {
Review Comment:
We can rename this class to TestLangChain4jDispatcher
##
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/chatbot/agent/ChatbotAgent.java:
##
@@ -0,0 +1,773 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.agent;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.recon.chatbot.ChatbotConfigKeys;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient.ChatMessage;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient.LLMResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Main chatbot agent that orchestrates the conversation flow.
+ * Handles tool selection (figuring out what API to call), executing those
calls,
+ * and summarization (feeding the data back to the LLM to write a nice answer).
+ */
+@Singleton
+public class ChatbotAgent {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(ChatbotAgent.class);
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+ private static final Pattern JSON_PATTERN = Pattern.compile("\\{.*\\}",
Pattern.DOTALL);
Review Comment:
It will misparse any LLM response containing reasoning text alongside the
JSON, or any JSON value containing a closing brace. Can you unit test this by
passing a hardcoded LLM response where if reasoning text also there along with
JSON
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
ArafatKhan2198 commented on code in PR #9915:
URL: https://github.com/apache/ozone/pull/9915#discussion_r3264703839
##
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/chatbot/llm/LangChain4jDispatcher.java:
##
@@ -0,0 +1,405 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.llm;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import dev.langchain4j.data.message.AiMessage;
+import dev.langchain4j.data.message.SystemMessage;
+import dev.langchain4j.data.message.UserMessage;
+import dev.langchain4j.model.anthropic.AnthropicChatModel;
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.chat.request.ChatRequest;
+import dev.langchain4j.model.chat.response.ChatResponse;
+import dev.langchain4j.model.googleai.GoogleAiGeminiChatModel;
+import dev.langchain4j.model.openai.OpenAiChatModel;
+import dev.langchain4j.model.output.TokenUsage;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.recon.chatbot.ChatbotConfigKeys;
+import org.apache.hadoop.ozone.recon.chatbot.security.CredentialHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * {@link LLMClient} implementation that sends chat requests to cloud LLM
providers using
+ * https://github.com/langchain4j/langchain4j";>LangChain4j.
+ *
+ * Purpose
+ * The Recon chatbot needs to call external APIs (OpenAI, Google Gemini,
Anthropic Claude)
+ * with a stable Java contract. This class is the only place that talks to
LangChain4j: it
+ * picks the right provider, builds a {@link ChatLanguageModel} for the
requested model,
+ * converts messages into LangChain4j types, runs one completion, and maps the
result back to
+ * {@link LLMResponse}. Higher layers ({@link
org.apache.hadoop.ozone.recon.chatbot.agent.ChatbotAgent},
+ * {@link org.apache.hadoop.ozone.recon.chatbot.api.ChatbotEndpoint}) depend
only on {@link LLMClient}.
+ *
+ * Lifecycle (no background work)
+ * The class is registered in Guice as a singleton: one instance exists for
the whole Recon process.
+ * There is no timer, no scheduled task, and no long-lived outbound
connection. At startup
+ * the constructor only reads configuration and records which providers have
API keys (for
+ * {@link #isAvailable()} and {@link #getSupportedModels()}). Actual network
calls happen
+ * only when {@link #chatCompletion} runs on an HTTP request thread.
+ *
+ * Request flow (one chat completion)
+ * Each user message is handled synchronously on the thread that serves the
REST call:
+ *
+ * User HTTP request
+ * |
+ * v
+ * Jersey dispatches to ChatbotEndpoint (request thread)
+ * |
+ * v
+ * ChatbotAgent orchestrates tool selection / summarization
+ * |
+ * v
+ * LangChain4jDispatcher.chatCompletion(...)
+ * |
+ * +-- Resolve provider (see below)
+ * |
+ * +-- Build a new ChatLanguageModel for that provider + model name
(configuration only)
+ * |
+ * +-- Translate ChatMessage list to LangChain4j messages (system /
user / assistant)
+ * |
+ * +-- chatModel.chat(ChatRequest) --> outbound HTTPS to the
vendor (may take seconds)
+ * |
+ * v
+ * LLMResponse returned to the agent, then JSON to the client
+ *
+ *
+ * How provider routing works
+ * When {@link #chatCompletion} runs, the provider is chosen in this
order:
+ *
+ * Optional {@code _provider} entry in the parameters map (e.g. {@code
"gemini"}).
+ * If the model string looks like {@code provider:model}, the prefix
before {@code :}
+ * is the provider.
+ * Otherwise the model name: {@code gpt-} / {@code o1} / {@code o3} →
{@code openai};
+ * {@code gemini} → {@code gemini}; {@code claude} → {@code
anthropic}.
+ * If still unclear, {@link
ChatbotConfigKeys#OZONE_RECON_CHATBOT_PROVIDER} is used.
+ *
+ * For each call, a fresh {@link ChatLanguageModel} is built with the exact
model id the
+ * caller passed (for example
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
ArafatKhan2198 commented on code in PR #9915:
URL: https://github.com/apache/ozone/pull/9915#discussion_r3264702033
##
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconControllerModule.java:
##
@@ -131,6 +131,12 @@ protected void configure() {
install(new ReconOmTaskBindingModule());
install(new ReconDaoBindingModule());
bind(ReconTaskStatusUpdaterManager.class).in(Singleton.class);
+// Only install chatbot bindings when the feature is explicitly enabled.
+// This prevents startup-time failures (e.g. bad credential provider paths)
+// from breaking Recon when the chatbot is intentionally disabled.
+if (ChatbotConfigKeys.isChatbotEnabled(new ConfigurationProvider().get()))
{
Review Comment:
Done. Added getConf() on ReconServer and changed the guard to
ChatbotConfigKeys.isChatbotEnabled(reconServer.getConf()) so we use the config
from the server instance already held by ReconControllerModule instead of going
through ConfigurationProvider.
--
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]
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
ArafatKhan2198 commented on code in PR #9915:
URL: https://github.com/apache/ozone/pull/9915#discussion_r3264699690
##
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/chatbot/agent/ChatbotAgent.java:
##
@@ -0,0 +1,800 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.agent;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.recon.chatbot.ChatbotConfigKeys;
+import org.apache.hadoop.ozone.recon.chatbot.ChatbotException;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient.ChatMessage;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient.LLMResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Main chatbot agent that orchestrates the conversation flow.
+ * Handles tool selection (figuring out what API to call), executing those
calls,
+ * and summarization (feeding the data back to the LLM to write a nice answer).
+ */
+@Singleton
+public class ChatbotAgent {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(ChatbotAgent.class);
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+ private static final Pattern JSON_PATTERN = Pattern.compile("\\{.*\\}",
Pattern.DOTALL);
+
+ // A specific Recon API endpoint we want to handle carefully because it can
return millions of rows.
+ private static final String LIST_KEYS_ENDPOINT_SUFFIX = "/keys/listKeys";
+
+ /**
+ * Allowlist of Recon API path prefixes the chatbot is permitted to call.
+ *
+ * This is the primary defence against prompt injection: even if an attacker
tricks
+ * the LLM into outputting an arbitrary endpoint, the Java layer will reject
it here
+ * before ToolExecutor makes any network call. Only paths listed here can
ever be
+ * executed. The check uses prefix matching so that parameterised paths like
+ * /api/v1/containers/unhealthy/MISSING are covered by the
/api/v1/containers entry.
+ */
+ private static final Set ALLOWED_ENDPOINT_PREFIXES =
+ Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+ "/api/v1/clusterState",
+ "/api/v1/datanodes",
+ "/api/v1/pipelines",
+ "/api/v1/containers",
+ "/api/v1/keys",
+ "/api/v1/volumes",
+ "/api/v1/buckets",
+ "/api/v1/task/status",
+ "/api/v1/metrics",
+ "/api/v1/utilization",
+ "/api/v1/namespace",
+ "/api/v1/om"
+ )));
+
+ // The connection to Gemini/OpenAI
+ private final LLMClient llmClient;
+
+ // The hands that execute the internal API calls
+ private final ToolExecutor toolExecutor;
+
+ // The Cheat Sheet of all available APIs loaded from the .md file
+ private final String apiSchema;
+
+ // Prompt preamble for tool selection — loaded from classpath resource
+ private final String toolSelectionPreamble;
+
+ // System prompt for the summarization LLM call — loaded from classpath
resource
+ private final String summarizationPrompt;
+
+ // Template for the fallback response when no endpoint matches — loaded from
classpath resource
+ private final String fallbackPromptTemplate;
+
+ // Max API calls we allow per question (so the LLM doesn't DOS our server)
+ private final int maxToolCalls;
+
+
+ private final String defaultModel;
+ private final int maxRecordsPerAnswer;
+ private final int maxPagesPerAnswer;
+ private final int pageSizePerCall;
+ private final boolean requireSafeScope;
+
+ @Inject
+ public ChatbotAgent(LLMClient llmClient,
+
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
ArafatKhan2198 commented on code in PR #9915:
URL: https://github.com/apache/ozone/pull/9915#discussion_r3264705458
##
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/chatbot/agent/ChatbotAgent.java:
##
@@ -0,0 +1,773 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.agent;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.recon.chatbot.ChatbotConfigKeys;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient.ChatMessage;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient.LLMResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Main chatbot agent that orchestrates the conversation flow.
+ * Handles tool selection (figuring out what API to call), executing those
calls,
+ * and summarization (feeding the data back to the LLM to write a nice answer).
+ */
+@Singleton
+public class ChatbotAgent {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(ChatbotAgent.class);
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+ private static final Pattern JSON_PATTERN = Pattern.compile("\\{.*\\}",
Pattern.DOTALL);
Review Comment:
Resolved. Removed JSON_PATTERN and added extractFirstJsonObject(), which
uses brace-depth counting with string-awareness (ignores {/} inside quoted
values, handles escapes). It returns the first complete outermost JSON object,
so it works for SINGLE_ENDPOINT, MULTI_ENDPOINT (nested tool_calls), and
DOCUMENTATION_QUERY, and when the LLM wraps JSON in prose.
--
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]
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
ArafatKhan2198 commented on code in PR #9915:
URL: https://github.com/apache/ozone/pull/9915#discussion_r3264248935
##
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/chatbot/llm/LLMClient.java:
##
@@ -0,0 +1,185 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.llm;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * LLMClient is the "Master Contract" for the whole Chatbot system.
+ *
+ * Purpose:
+ * The ChatbotAgent doesn't know (or care) if it's talking to OpenAI, Gemini,
or a Local LLM.
+ * It strictly relies on this interface. This interface forces every AI client
to guarantee
+ * that they will accept exactly the same input and return exactly the same
output.
+ *
+ * By using this contract, we can add 10 new AI models to Recon tomorrow,
+ * and we will never have to edit the ChatbotAgent's code to support them!
+ */
+public interface LLMClient {
+
+ /**
+ * The core action: Send a conversation to an AI and wait for its answer.
+ *
+ * API keys are always resolved server-side via
+ * {@link org.apache.hadoop.ozone.recon.chatbot.security.CredentialHelper}
from
+ * the Hadoop credential store or {@code ozone-site.xml}. There is no
per-request
+ * key parameter — all callers should be cluster admins using the shared
server key.
+ *
+ * @param messages The back-and-forth chat history so far (System Prompts,
User Questions, etc.)
+ * @param model The specific model name (e.g. "gpt-4.1" or
"gemini-2.5-flash")
+ * @param parameters Extra rules like "temperature" (how creative the AI
should be) or
+ * "max_tokens" (how long the answer can be)
+ * @return A standardized LLMResponse object containing the AI's final text.
+ * @throws LLMException if the network fails, the API key is missing, or the
provider returns an error.
+ */
+ LLMResponse chatCompletion(
+ List messages,
+ String model,
+ Map parameters) throws LLMException;
+
+ /**
+ * Quick check to see if this client is ready to work (e.g., does it have an
API key saved?)
+ */
+ boolean isAvailable();
+
+ /**
+ * Asks the AI client for a list of all the different models it supports
right now.
+ * We use this to populate the drop-down menu in the user interface!
+ */
+ List getSupportedModels();
+
+ // =
+ // Data Transfer Objects (DTOs)
+ // These are the standardized containers we use to pass information around.
+ // =
+
+ /**
+ * A single message in a conversation.
+ * Every message needs a "role" (who is speaking: user or assistant)
+ * and "content" (what they actually said).
+ */
+ class ChatMessage {
+private final String role;
+private final String content;
+
+public ChatMessage(String role, String content) {
+ this.role = role;
+ this.content = content;
+}
+
+public String getRole() {
+ return role;
+}
+
+public String getContent() {
+ return content;
+}
+ }
+
+ /**
+ * The standardized package that every AI MUST return when it finishes
thinking.
+ * Instead of OpenAI returning one JSON format and Gemini returning a
completely different one,
+ * our background code forces them both to output this clean Java object.
+ */
+ class LLMResponse {
+
+// The actual text the AI typed out
+private final String content;
+
+// Which AI model specifically answered this? (e.g. "gpt-4")
+private final String model;
+
+// How many "words" the user asked
+private final int promptTokens;
+
+// How many "words" the AI answered with
+private final int completionTokens;
+
+// Extra sneaky information about the answer (like why it stopped typing)
+private final Map metadata;
+
+public LLMResponse(String content, String model,
+ int promptTokens, int completionTokens,
+ Map metadata) {
+ this.content = content;
+ this.model = model;
+ this.promptTokens = promptTokens;
+ this.com
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
ArafatKhan2198 commented on code in PR #9915:
URL: https://github.com/apache/ozone/pull/9915#discussion_r3264210827
##
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/chatbot/agent/ChatbotAgent.java:
##
@@ -0,0 +1,800 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.agent;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.recon.chatbot.ChatbotConfigKeys;
+import org.apache.hadoop.ozone.recon.chatbot.ChatbotException;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient.ChatMessage;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient.LLMResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Main chatbot agent that orchestrates the conversation flow.
+ * Handles tool selection (figuring out what API to call), executing those
calls,
+ * and summarization (feeding the data back to the LLM to write a nice answer).
+ */
+@Singleton
+public class ChatbotAgent {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(ChatbotAgent.class);
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+ private static final Pattern JSON_PATTERN = Pattern.compile("\\{.*\\}",
Pattern.DOTALL);
+
+ // A specific Recon API endpoint we want to handle carefully because it can
return millions of rows.
+ private static final String LIST_KEYS_ENDPOINT_SUFFIX = "/keys/listKeys";
+
+ /**
+ * Allowlist of Recon API path prefixes the chatbot is permitted to call.
+ *
+ * This is the primary defence against prompt injection: even if an attacker
tricks
+ * the LLM into outputting an arbitrary endpoint, the Java layer will reject
it here
+ * before ToolExecutor makes any network call. Only paths listed here can
ever be
+ * executed. The check uses prefix matching so that parameterised paths like
+ * /api/v1/containers/unhealthy/MISSING are covered by the
/api/v1/containers entry.
+ */
+ private static final Set ALLOWED_ENDPOINT_PREFIXES =
+ Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+ "/api/v1/clusterState",
+ "/api/v1/datanodes",
+ "/api/v1/pipelines",
+ "/api/v1/containers",
+ "/api/v1/keys",
+ "/api/v1/volumes",
+ "/api/v1/buckets",
+ "/api/v1/task/status",
+ "/api/v1/metrics",
+ "/api/v1/utilization",
+ "/api/v1/namespace",
+ "/api/v1/om"
+ )));
+
+ // The connection to Gemini/OpenAI
+ private final LLMClient llmClient;
+
+ // The hands that execute the internal API calls
+ private final ToolExecutor toolExecutor;
+
+ // The Cheat Sheet of all available APIs loaded from the .md file
+ private final String apiSchema;
+
+ // Prompt preamble for tool selection — loaded from classpath resource
+ private final String toolSelectionPreamble;
+
+ // System prompt for the summarization LLM call — loaded from classpath
resource
+ private final String summarizationPrompt;
+
+ // Template for the fallback response when no endpoint matches — loaded from
classpath resource
+ private final String fallbackPromptTemplate;
+
+ // Max API calls we allow per question (so the LLM doesn't DOS our server)
+ private final int maxToolCalls;
+
+
+ private final String defaultModel;
+ private final int maxRecordsPerAnswer;
+ private final int maxPagesPerAnswer;
+ private final int pageSizePerCall;
+ private final boolean requireSafeScope;
+
+ @Inject
+ public ChatbotAgent(LLMClient llmClient,
+
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
ArafatKhan2198 commented on code in PR #9915:
URL: https://github.com/apache/ozone/pull/9915#discussion_r3264058735
##
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/chatbot/llm/LangChain4jDispatcher.java:
##
@@ -0,0 +1,405 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.llm;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import dev.langchain4j.data.message.AiMessage;
+import dev.langchain4j.data.message.SystemMessage;
+import dev.langchain4j.data.message.UserMessage;
+import dev.langchain4j.model.anthropic.AnthropicChatModel;
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.chat.request.ChatRequest;
+import dev.langchain4j.model.chat.response.ChatResponse;
+import dev.langchain4j.model.googleai.GoogleAiGeminiChatModel;
+import dev.langchain4j.model.openai.OpenAiChatModel;
+import dev.langchain4j.model.output.TokenUsage;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.recon.chatbot.ChatbotConfigKeys;
+import org.apache.hadoop.ozone.recon.chatbot.security.CredentialHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * {@link LLMClient} implementation that sends chat requests to cloud LLM
providers using
+ * https://github.com/langchain4j/langchain4j";>LangChain4j.
+ *
+ * Purpose
+ * The Recon chatbot needs to call external APIs (OpenAI, Google Gemini,
Anthropic Claude)
+ * with a stable Java contract. This class is the only place that talks to
LangChain4j: it
+ * picks the right provider, builds a {@link ChatLanguageModel} for the
requested model,
+ * converts messages into LangChain4j types, runs one completion, and maps the
result back to
+ * {@link LLMResponse}. Higher layers ({@link
org.apache.hadoop.ozone.recon.chatbot.agent.ChatbotAgent},
+ * {@link org.apache.hadoop.ozone.recon.chatbot.api.ChatbotEndpoint}) depend
only on {@link LLMClient}.
+ *
+ * Lifecycle (no background work)
+ * The class is registered in Guice as a singleton: one instance exists for
the whole Recon process.
+ * There is no timer, no scheduled task, and no long-lived outbound
connection. At startup
+ * the constructor only reads configuration and records which providers have
API keys (for
+ * {@link #isAvailable()} and {@link #getSupportedModels()}). Actual network
calls happen
+ * only when {@link #chatCompletion} runs on an HTTP request thread.
+ *
+ * Request flow (one chat completion)
+ * Each user message is handled synchronously on the thread that serves the
REST call:
+ *
+ * User HTTP request
+ * |
+ * v
+ * Jersey dispatches to ChatbotEndpoint (request thread)
+ * |
+ * v
+ * ChatbotAgent orchestrates tool selection / summarization
+ * |
+ * v
+ * LangChain4jDispatcher.chatCompletion(...)
+ * |
+ * +-- Resolve provider (see below)
+ * |
+ * +-- Build a new ChatLanguageModel for that provider + model name
(configuration only)
+ * |
+ * +-- Translate ChatMessage list to LangChain4j messages (system /
user / assistant)
+ * |
+ * +-- chatModel.chat(ChatRequest) --> outbound HTTPS to the
vendor (may take seconds)
+ * |
+ * v
+ * LLMResponse returned to the agent, then JSON to the client
+ *
+ *
+ * How provider routing works
+ * When {@link #chatCompletion} runs, the provider is chosen in this
order:
+ *
+ * Optional {@code _provider} entry in the parameters map (e.g. {@code
"gemini"}).
+ * If the model string looks like {@code provider:model}, the prefix
before {@code :}
+ * is the provider.
+ * Otherwise the model name: {@code gpt-} / {@code o1} / {@code o3} →
{@code openai};
+ * {@code gemini} → {@code gemini}; {@code claude} → {@code
anthropic}.
+ * If still unclear, {@link
ChatbotConfigKeys#OZONE_RECON_CHATBOT_PROVIDER} is used.
+ *
+ * For each call, a fresh {@link ChatLanguageModel} is built with the exact
model id the
+ * caller passed (for example
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
ArafatKhan2198 commented on code in PR #9915:
URL: https://github.com/apache/ozone/pull/9915#discussion_r3264041352
##
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/chatbot/llm/LangChain4jDispatcher.java:
##
@@ -0,0 +1,405 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.llm;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import dev.langchain4j.data.message.AiMessage;
+import dev.langchain4j.data.message.SystemMessage;
+import dev.langchain4j.data.message.UserMessage;
+import dev.langchain4j.model.anthropic.AnthropicChatModel;
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.chat.request.ChatRequest;
+import dev.langchain4j.model.chat.response.ChatResponse;
+import dev.langchain4j.model.googleai.GoogleAiGeminiChatModel;
+import dev.langchain4j.model.openai.OpenAiChatModel;
+import dev.langchain4j.model.output.TokenUsage;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.recon.chatbot.ChatbotConfigKeys;
+import org.apache.hadoop.ozone.recon.chatbot.security.CredentialHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * {@link LLMClient} implementation that sends chat requests to cloud LLM
providers using
+ * https://github.com/langchain4j/langchain4j";>LangChain4j.
+ *
+ * Purpose
+ * The Recon chatbot needs to call external APIs (OpenAI, Google Gemini,
Anthropic Claude)
+ * with a stable Java contract. This class is the only place that talks to
LangChain4j: it
+ * picks the right provider, builds a {@link ChatLanguageModel} for the
requested model,
+ * converts messages into LangChain4j types, runs one completion, and maps the
result back to
+ * {@link LLMResponse}. Higher layers ({@link
org.apache.hadoop.ozone.recon.chatbot.agent.ChatbotAgent},
+ * {@link org.apache.hadoop.ozone.recon.chatbot.api.ChatbotEndpoint}) depend
only on {@link LLMClient}.
+ *
+ * Lifecycle (no background work)
+ * The class is registered in Guice as a singleton: one instance exists for
the whole Recon process.
+ * There is no timer, no scheduled task, and no long-lived outbound
connection. At startup
+ * the constructor only reads configuration and records which providers have
API keys (for
+ * {@link #isAvailable()} and {@link #getSupportedModels()}). Actual network
calls happen
+ * only when {@link #chatCompletion} runs on an HTTP request thread.
+ *
+ * Request flow (one chat completion)
+ * Each user message is handled synchronously on the thread that serves the
REST call:
+ *
+ * User HTTP request
+ * |
+ * v
+ * Jersey dispatches to ChatbotEndpoint (request thread)
+ * |
+ * v
+ * ChatbotAgent orchestrates tool selection / summarization
+ * |
+ * v
+ * LangChain4jDispatcher.chatCompletion(...)
+ * |
+ * +-- Resolve provider (see below)
+ * |
+ * +-- Build a new ChatLanguageModel for that provider + model name
(configuration only)
+ * |
+ * +-- Translate ChatMessage list to LangChain4j messages (system /
user / assistant)
+ * |
+ * +-- chatModel.chat(ChatRequest) --> outbound HTTPS to the
vendor (may take seconds)
+ * |
+ * v
+ * LLMResponse returned to the agent, then JSON to the client
+ *
+ *
+ * How provider routing works
+ * When {@link #chatCompletion} runs, the provider is chosen in this
order:
+ *
+ * Optional {@code _provider} entry in the parameters map (e.g. {@code
"gemini"}).
+ * If the model string looks like {@code provider:model}, the prefix
before {@code :}
+ * is the provider.
+ * Otherwise the model name: {@code gpt-} / {@code o1} / {@code o3} →
{@code openai};
+ * {@code gemini} → {@code gemini}; {@code claude} → {@code
anthropic}.
+ * If still unclear, {@link
ChatbotConfigKeys#OZONE_RECON_CHATBOT_PROVIDER} is used.
+ *
+ * For each call, a fresh {@link ChatLanguageModel} is built with the exact
model id the
+ * caller passed (for example
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
ArafatKhan2198 commented on code in PR #9915: URL: https://github.com/apache/ozone/pull/9915#discussion_r3263839920 ## pom.xml: ## @@ -515,6 +569,26 @@ + +dev.langchain4j +langchain4j-anthropic +0.36.2 + + +dev.langchain4j +langchain4j-core +0.36.2 + + +dev.langchain4j +langchain4j-google-ai-gemini +0.36.2 + + +dev.langchain4j +langchain4j-open-ai +0.36.2 + Review Comment: Thanks for the suggestion. In principle, using the `langchain4j-bom` is a good idea and I agree it would make version updates cleaner. However, Ozone manages all dependency versions centrally in the root `pom.xml` under ``, and every other third-party library in the project follows this same pattern of individual version entries. Introducing a BOM import for just this one library would be inconsistent with how the rest of the project is structured. Also, worth noting — the `` tags you are seeing are only in the root `pom.xml`'s `` block. If you check `recon/pom.xml`, the four LangChain4j dependency entries there have no `` tags at all — they inherit the version from the root. So there is already a single source of truth for the version. Updating it in the future is still just one change in the root `pom.xml`. Happy to add the BOM if there is a strong preference, but it would require a broader discussion about whether Ozone should standardize BOM imports for external libraries. -- 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]
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
ArafatKhan2198 commented on code in PR #9915: URL: https://github.com/apache/ozone/pull/9915#discussion_r3254921803 ## hadoop-ozone/recon/pom.xml: ## @@ -62,6 +62,22 @@ commons-io commons-io + + dev.langchain4j + langchain4j-anthropic Review Comment: Thanks for pointing out the OkHttp dependency tree. I dug into this, and because `okhttp` (pulled by LangChain4j) and `okhttp-jvm` (pulled by OpenTelemetry) have different artifact IDs, Maven doesn't actually treat them as a version conflict. This split happened because OkHttp 5.x moved to Kotlin Multiplatform and shifted its core classes into the new `-jvm` artifact. Because they are technically different artifacts, adding an `` for `okhttp-jvm` inside the `langchain4j-anthropic` block won't have any effect, since LangChain4j doesn't pull in `okhttp-jvm` to begin with (it only pulls `okhttp` 4.x). At runtime, both JARs will be present on the classpath. Fortunately, this won't cause a problem because OkHttp 5.x is highly backward-compatible with 4.x. The JVM will simply load the `OkHttpClient` class from whichever JAR it scans first, and both OpenTelemetry and LangChain4j will function normally. If we ever want to strictly enforce a single OkHttp version across the entire project to avoid classpath pollution, the safest way would be to manage both `okhttp` and `okhttp-jvm` centrally in the root `hadoop-ozone/pom.xml`'s `` section. For the scope of this PR, though, it is safe to leave as-is since it won't cause any runtime crashes. ## hadoop-ozone/recon/pom.xml: ## @@ -62,6 +62,22 @@ commons-io commons-io + + dev.langchain4j + langchain4j-anthropic Review Comment: Thanks for pointing out the OkHttp dependency tree. I dug into this, and because `okhttp` (pulled by LangChain4j) and `okhttp-jvm` (pulled by OpenTelemetry) have different artifact IDs, Maven doesn't actually treat them as a version conflict. This split happened because OkHttp 5.x moved to Kotlin Multiplatform and shifted its core classes into the new `-jvm` artifact. Because they are technically different artifacts, adding an `` for `okhttp-jvm` inside the `langchain4j-anthropic` block won't have any effect, since LangChain4j doesn't pull in `okhttp-jvm` to begin with (it only pulls `okhttp` 4.x). At runtime, both JARs will be present on the classpath. Fortunately, this won't cause a problem because OkHttp 5.x is highly backward-compatible with 4.x. The JVM will simply load the `OkHttpClient` class from whichever JAR it scans first, and both OpenTelemetry and LangChain4j will function normally. If we ever want to strictly enforce a single OkHttp version across the entire project to avoid classpath pollution, the safest way would be to manage both `okhttp` and `okhttp-jvm` centrally in the root `hadoop-ozone/pom.xml`'s `` section. For the scope of this PR, though, it is safe to leave as-is since it won't cause any runtime crashes. cc: @devmadhuu -- 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]
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
devmadhuu commented on code in PR #9915:
URL: https://github.com/apache/ozone/pull/9915#discussion_r3225978467
##
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/chatbot/agent/ChatbotAgent.java:
##
@@ -0,0 +1,800 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.agent;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.recon.chatbot.ChatbotConfigKeys;
+import org.apache.hadoop.ozone.recon.chatbot.ChatbotException;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient.ChatMessage;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient.LLMResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Main chatbot agent that orchestrates the conversation flow.
+ * Handles tool selection (figuring out what API to call), executing those
calls,
+ * and summarization (feeding the data back to the LLM to write a nice answer).
+ */
+@Singleton
+public class ChatbotAgent {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(ChatbotAgent.class);
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+ private static final Pattern JSON_PATTERN = Pattern.compile("\\{.*\\}",
Pattern.DOTALL);
+
+ // A specific Recon API endpoint we want to handle carefully because it can
return millions of rows.
+ private static final String LIST_KEYS_ENDPOINT_SUFFIX = "/keys/listKeys";
+
+ /**
+ * Allowlist of Recon API path prefixes the chatbot is permitted to call.
+ *
+ * This is the primary defence against prompt injection: even if an attacker
tricks
+ * the LLM into outputting an arbitrary endpoint, the Java layer will reject
it here
+ * before ToolExecutor makes any network call. Only paths listed here can
ever be
+ * executed. The check uses prefix matching so that parameterised paths like
+ * /api/v1/containers/unhealthy/MISSING are covered by the
/api/v1/containers entry.
+ */
+ private static final Set ALLOWED_ENDPOINT_PREFIXES =
+ Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+ "/api/v1/clusterState",
+ "/api/v1/datanodes",
+ "/api/v1/pipelines",
+ "/api/v1/containers",
+ "/api/v1/keys",
+ "/api/v1/volumes",
+ "/api/v1/buckets",
+ "/api/v1/task/status",
+ "/api/v1/metrics",
+ "/api/v1/utilization",
+ "/api/v1/namespace",
+ "/api/v1/om"
+ )));
+
+ // The connection to Gemini/OpenAI
+ private final LLMClient llmClient;
+
+ // The hands that execute the internal API calls
+ private final ToolExecutor toolExecutor;
+
+ // The Cheat Sheet of all available APIs loaded from the .md file
+ private final String apiSchema;
+
+ // Prompt preamble for tool selection — loaded from classpath resource
+ private final String toolSelectionPreamble;
+
+ // System prompt for the summarization LLM call — loaded from classpath
resource
+ private final String summarizationPrompt;
+
+ // Template for the fallback response when no endpoint matches — loaded from
classpath resource
+ private final String fallbackPromptTemplate;
+
+ // Max API calls we allow per question (so the LLM doesn't DOS our server)
+ private final int maxToolCalls;
+
+
+ private final String defaultModel;
+ private final int maxRecordsPerAnswer;
+ private final int maxPagesPerAnswer;
+ private final int pageSizePerCall;
+ private final boolean requireSafeScope;
+
+ @Inject
+ public ChatbotAgent(LLMClient llmClient,
+
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
devmadhuu commented on code in PR #9915:
URL: https://github.com/apache/ozone/pull/9915#discussion_r3225921848
##
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/chatbot/agent/ChatbotAgent.java:
##
@@ -0,0 +1,800 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.agent;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.recon.chatbot.ChatbotConfigKeys;
+import org.apache.hadoop.ozone.recon.chatbot.ChatbotException;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient.ChatMessage;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient.LLMResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Main chatbot agent that orchestrates the conversation flow.
+ * Handles tool selection (figuring out what API to call), executing those
calls,
+ * and summarization (feeding the data back to the LLM to write a nice answer).
+ */
+@Singleton
+public class ChatbotAgent {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(ChatbotAgent.class);
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+ private static final Pattern JSON_PATTERN = Pattern.compile("\\{.*\\}",
Pattern.DOTALL);
+
+ // A specific Recon API endpoint we want to handle carefully because it can
return millions of rows.
+ private static final String LIST_KEYS_ENDPOINT_SUFFIX = "/keys/listKeys";
+
+ /**
+ * Allowlist of Recon API path prefixes the chatbot is permitted to call.
+ *
+ * This is the primary defence against prompt injection: even if an attacker
tricks
+ * the LLM into outputting an arbitrary endpoint, the Java layer will reject
it here
+ * before ToolExecutor makes any network call. Only paths listed here can
ever be
+ * executed. The check uses prefix matching so that parameterised paths like
+ * /api/v1/containers/unhealthy/MISSING are covered by the
/api/v1/containers entry.
+ */
+ private static final Set ALLOWED_ENDPOINT_PREFIXES =
+ Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+ "/api/v1/clusterState",
+ "/api/v1/datanodes",
+ "/api/v1/pipelines",
+ "/api/v1/containers",
+ "/api/v1/keys",
+ "/api/v1/volumes",
+ "/api/v1/buckets",
+ "/api/v1/task/status",
+ "/api/v1/metrics",
+ "/api/v1/utilization",
+ "/api/v1/namespace",
+ "/api/v1/om"
+ )));
+
+ // The connection to Gemini/OpenAI
+ private final LLMClient llmClient;
+
+ // The hands that execute the internal API calls
+ private final ToolExecutor toolExecutor;
+
+ // The Cheat Sheet of all available APIs loaded from the .md file
+ private final String apiSchema;
+
+ // Prompt preamble for tool selection — loaded from classpath resource
+ private final String toolSelectionPreamble;
+
+ // System prompt for the summarization LLM call — loaded from classpath
resource
+ private final String summarizationPrompt;
+
+ // Template for the fallback response when no endpoint matches — loaded from
classpath resource
+ private final String fallbackPromptTemplate;
+
+ // Max API calls we allow per question (so the LLM doesn't DOS our server)
+ private final int maxToolCalls;
+
+
+ private final String defaultModel;
+ private final int maxRecordsPerAnswer;
+ private final int maxPagesPerAnswer;
+ private final int pageSizePerCall;
+ private final boolean requireSafeScope;
+
+ @Inject
+ public ChatbotAgent(LLMClient llmClient,
+
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
devmadhuu commented on code in PR #9915:
URL: https://github.com/apache/ozone/pull/9915#discussion_r3225876503
##
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/chatbot/llm/LLMClient.java:
##
@@ -0,0 +1,185 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.llm;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * LLMClient is the "Master Contract" for the whole Chatbot system.
+ *
+ * Purpose:
+ * The ChatbotAgent doesn't know (or care) if it's talking to OpenAI, Gemini,
or a Local LLM.
+ * It strictly relies on this interface. This interface forces every AI client
to guarantee
+ * that they will accept exactly the same input and return exactly the same
output.
+ *
+ * By using this contract, we can add 10 new AI models to Recon tomorrow,
+ * and we will never have to edit the ChatbotAgent's code to support them!
+ */
+public interface LLMClient {
+
+ /**
+ * The core action: Send a conversation to an AI and wait for its answer.
+ *
+ * API keys are always resolved server-side via
+ * {@link org.apache.hadoop.ozone.recon.chatbot.security.CredentialHelper}
from
+ * the Hadoop credential store or {@code ozone-site.xml}. There is no
per-request
+ * key parameter — all callers should be cluster admins using the shared
server key.
+ *
+ * @param messages The back-and-forth chat history so far (System Prompts,
User Questions, etc.)
+ * @param model The specific model name (e.g. "gpt-4.1" or
"gemini-2.5-flash")
+ * @param parameters Extra rules like "temperature" (how creative the AI
should be) or
+ * "max_tokens" (how long the answer can be)
+ * @return A standardized LLMResponse object containing the AI's final text.
+ * @throws LLMException if the network fails, the API key is missing, or the
provider returns an error.
+ */
+ LLMResponse chatCompletion(
+ List messages,
+ String model,
+ Map parameters) throws LLMException;
+
+ /**
+ * Quick check to see if this client is ready to work (e.g., does it have an
API key saved?)
+ */
+ boolean isAvailable();
+
+ /**
+ * Asks the AI client for a list of all the different models it supports
right now.
+ * We use this to populate the drop-down menu in the user interface!
+ */
+ List getSupportedModels();
+
+ // =
+ // Data Transfer Objects (DTOs)
+ // These are the standardized containers we use to pass information around.
+ // =
+
+ /**
+ * A single message in a conversation.
+ * Every message needs a "role" (who is speaking: user or assistant)
+ * and "content" (what they actually said).
+ */
+ class ChatMessage {
+private final String role;
+private final String content;
+
+public ChatMessage(String role, String content) {
+ this.role = role;
+ this.content = content;
+}
+
+public String getRole() {
+ return role;
+}
+
+public String getContent() {
+ return content;
+}
+ }
+
+ /**
+ * The standardized package that every AI MUST return when it finishes
thinking.
+ * Instead of OpenAI returning one JSON format and Gemini returning a
completely different one,
+ * our background code forces them both to output this clean Java object.
+ */
+ class LLMResponse {
+
+// The actual text the AI typed out
+private final String content;
+
+// Which AI model specifically answered this? (e.g. "gpt-4")
+private final String model;
+
+// How many "words" the user asked
+private final int promptTokens;
+
+// How many "words" the AI answered with
+private final int completionTokens;
+
+// Extra sneaky information about the answer (like why it stopped typing)
+private final Map metadata;
+
+public LLMResponse(String content, String model,
+ int promptTokens, int completionTokens,
+ Map metadata) {
+ this.content = content;
+ this.model = model;
+ this.promptTokens = promptTokens;
+ this.completi
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
devmadhuu commented on code in PR #9915:
URL: https://github.com/apache/ozone/pull/9915#discussion_r3225839755
##
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/ReconRestServletModule.java:
##
@@ -65,7 +67,7 @@ public ReconRestServletModule(ConfigurationSource conf) {
@Override
protected void configureServlets() {
-configureApi(BASE_API_PATH, API_PACKAGE);
+configureApi(BASE_API_PATH, API_PACKAGE, CHATBOT_API_PACKAGE);
Review Comment:
Always gets registered regardless of feature flag.
##
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/chatbot/agent/ChatbotAgent.java:
##
@@ -0,0 +1,800 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.agent;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.recon.chatbot.ChatbotConfigKeys;
+import org.apache.hadoop.ozone.recon.chatbot.ChatbotException;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient.ChatMessage;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient.LLMResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Main chatbot agent that orchestrates the conversation flow.
+ * Handles tool selection (figuring out what API to call), executing those
calls,
+ * and summarization (feeding the data back to the LLM to write a nice answer).
+ */
+@Singleton
+public class ChatbotAgent {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(ChatbotAgent.class);
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+ private static final Pattern JSON_PATTERN = Pattern.compile("\\{.*\\}",
Pattern.DOTALL);
+
+ // A specific Recon API endpoint we want to handle carefully because it can
return millions of rows.
+ private static final String LIST_KEYS_ENDPOINT_SUFFIX = "/keys/listKeys";
+
+ /**
+ * Allowlist of Recon API path prefixes the chatbot is permitted to call.
+ *
+ * This is the primary defence against prompt injection: even if an attacker
tricks
+ * the LLM into outputting an arbitrary endpoint, the Java layer will reject
it here
+ * before ToolExecutor makes any network call. Only paths listed here can
ever be
+ * executed. The check uses prefix matching so that parameterised paths like
+ * /api/v1/containers/unhealthy/MISSING are covered by the
/api/v1/containers entry.
+ */
+ private static final Set ALLOWED_ENDPOINT_PREFIXES =
+ Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+ "/api/v1/clusterState",
+ "/api/v1/datanodes",
+ "/api/v1/pipelines",
+ "/api/v1/containers",
+ "/api/v1/keys",
+ "/api/v1/volumes",
+ "/api/v1/buckets",
+ "/api/v1/task/status",
+ "/api/v1/metrics",
+ "/api/v1/utilization",
+ "/api/v1/namespace",
+ "/api/v1/om"
+ )));
+
+ // The connection to Gemini/OpenAI
+ private final LLMClient llmClient;
+
+ // The hands that execute the internal API calls
+ private final ToolExecutor toolExecutor;
+
+ // The Cheat Sheet of all available APIs loaded from the .md file
+ private final String apiSchema;
+
+ // Prompt preamble for tool selection — loaded from classpath resource
+ private final String toolSelectionPreamble;
+
+ // System prompt for the summarization LLM call — loaded from classpath
resource
+ private final String summarizationPrompt;
+
+ // Template for the fallback response when no endpoint matches — loaded from
classpath resource
+ priva
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
devmadhuu commented on code in PR #9915:
URL: https://github.com/apache/ozone/pull/9915#discussion_r3224534208
##
hadoop-ozone/recon/pom.xml:
##
@@ -62,6 +62,22 @@
commons-io
commons-io
+
+ dev.langchain4j
+ langchain4j-anthropic
Review Comment:
If you run `mvn dependency:tree` this dependency of `okhttp:4.12.0` will be
emitted as a transitive dependency at compile time from this artifact:
`langchain4j-anthropic` and same `okhttp-jvm:5.3.2` of different version will
be emitted at runtime from `opentelemetry-exporter-sender-okhttp` artifact from
main pom.xml. So adding an exclusion for `okhttp-jvm` in the recon pom.xml
should be done.
##
pom.xml:
##
@@ -515,6 +569,26 @@
+
+dev.langchain4j
+langchain4j-anthropic
+0.36.2
+
+
+dev.langchain4j
+langchain4j-core
+0.36.2
+
+
+dev.langchain4j
+langchain4j-google-ai-gemini
+0.36.2
+
+
+dev.langchain4j
+langchain4j-open-ai
+0.36.2
+
Review Comment:
each langchain4j-* artifact added separately at 0.36.2 version rather than
importing the langchain4j-bom. If need update of version later for 4 separate
version entries, we should use bom
##
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/chatbot/llm/LangChain4jDispatcher.java:
##
@@ -0,0 +1,405 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.llm;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import dev.langchain4j.data.message.AiMessage;
+import dev.langchain4j.data.message.SystemMessage;
+import dev.langchain4j.data.message.UserMessage;
+import dev.langchain4j.model.anthropic.AnthropicChatModel;
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.chat.request.ChatRequest;
+import dev.langchain4j.model.chat.response.ChatResponse;
+import dev.langchain4j.model.googleai.GoogleAiGeminiChatModel;
+import dev.langchain4j.model.openai.OpenAiChatModel;
+import dev.langchain4j.model.output.TokenUsage;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.recon.chatbot.ChatbotConfigKeys;
+import org.apache.hadoop.ozone.recon.chatbot.security.CredentialHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * {@link LLMClient} implementation that sends chat requests to cloud LLM
providers using
+ * https://github.com/langchain4j/langchain4j";>LangChain4j.
+ *
+ * Purpose
+ * The Recon chatbot needs to call external APIs (OpenAI, Google Gemini,
Anthropic Claude)
+ * with a stable Java contract. This class is the only place that talks to
LangChain4j: it
+ * picks the right provider, builds a {@link ChatLanguageModel} for the
requested model,
+ * converts messages into LangChain4j types, runs one completion, and maps the
result back to
+ * {@link LLMResponse}. Higher layers ({@link
org.apache.hadoop.ozone.recon.chatbot.agent.ChatbotAgent},
+ * {@link org.apache.hadoop.ozone.recon.chatbot.api.ChatbotEndpoint}) depend
only on {@link LLMClient}.
+ *
+ * Lifecycle (no background work)
+ * The class is registered in Guice as a singleton: one instance exists for
the whole Recon process.
+ * There is no timer, no scheduled task, and no long-lived outbound
connection. At startup
+ * the constructor only reads configuration and records which providers have
API keys (for
+ * {@link #isAvailable()} and {@link #getSupportedModels()}). Actual network
calls happen
+ * only when {@link #chatCompletion} runs on an HTTP request thread.
+ *
+ * Request flow (one chat completion)
+ * Each user message is handled synchronously on the thread that serves the
REST call:
+ *
+ * User HTTP request
+ * |
+ * v
+ * Jersey dispatches to ChatbotEndpoint (request thread)
+ * |
+ * v
+ * ChatbotAgent orchestrates tool selection / summarization
+ * |
+ * v
+ * LangC
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ *
+ *
+ * API keys are managed via JCEKS (admin-configured),
+ * so there are no per-user key storage endpoints.
+ *
+ */
+@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 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 w
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
devmadhuu commented on code in PR #9915:
URL: https://github.com/apache/ozone/pull/9915#discussion_r3218443087
##
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/chatbot/llm/AnthropicClient.java:
##
@@ -0,0 +1,175 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.llm;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.recon.chatbot.ChatbotConfigKeys;
+import org.apache.hadoop.ozone.recon.chatbot.security.CredentialHelper;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Direct client for Anthropic Claude models using Composition.
+ */
+public class AnthropicClient implements LLMClient {
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+ private static final String ANTHROPIC_VERSION = "2023-06-01";
+
+ private final OzoneConfiguration configuration;
+ private final CredentialHelper credentialHelper;
+ private final LLMNetworkClient networkClient;
+
+ public AnthropicClient(OzoneConfiguration configuration,
+ CredentialHelper credentialHelper,
+ int timeoutMs) {
+this.configuration = configuration;
+this.credentialHelper = credentialHelper;
+this.networkClient = new LLMNetworkClient(timeoutMs);
+ }
+
+ @Override
+ public LLMResponse chatCompletion(List messages, String model,
String apiKey, Map parameters) throws LLMException {
Review Comment:
Do we not need to whitelists body parameters via `ALLOWED_BODY_PARAMS` here
also same as `OpenAIClient` ?
##
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/chatbot/agent/ChatbotAgent.java:
##
@@ -0,0 +1,809 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.agent;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.recon.chatbot.ChatbotConfigKeys;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient.ChatMessage;
+import org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient.LLMResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Main chatbot agent that orchestrates the conversation flow.
+ * Handles tool selection (figuring out what API to call), executing those
calls,
+ * and summarization (feeding the data back to the LLM to write a nice answer).
+ */
+@Singleton
+public class ChatbotAgent {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(ChatbotAgent.class);
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ // A specific Recon API endpoint we want to handle carefully because it can
return millions of rows.
+ private static final Strin
Re: [PR] HDDS-14816. Add Recon AI Assistant backend foundation with Multi LLM integration [ozone]
priyeshkaratha commented on code in PR #9915:
URL: https://github.com/apache/ozone/pull/9915#discussion_r3210195726
##
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/chatbot/security/LLMClient2.java:
##
@@ -0,0 +1,182 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.security;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * LLMClient is the "Master Contract" for the whole Chatbot system.
+ *
+ * Purpose:
+ * The ChatbotAgent doesn't know (or care) if it's talking to OpenAI, Gemini,
or a Local LLM.
+ * It strictly relies on this interface. This interface forces every AI client
to guarantee
+ * that they will accept exactly the same input and return exactly the same
output.
+ *
+ * By using this contract, we can add 10 new AI models to Recon tomorrow,
+ * and we will never have to edit the ChatbotAgent's code to support them!
+ */
+public interface LLMClient2 {
Review Comment:
This file seems like duplicate of
org.apache.hadoop.ozone.recon.chatbot.llm.LLMClient
--
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]
