Skip to content

Commit fc5c38b

Browse files
Chatbot integration
1 parent b0b2a25 commit fc5c38b

13 files changed

Lines changed: 2758 additions & 0 deletions

File tree

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
* <p>
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
* <p>
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.ozone.recon.chatbot;
19+
20+
import org.apache.hadoop.hdds.annotation.InterfaceAudience;
21+
import org.apache.hadoop.hdds.annotation.InterfaceStability;
22+
23+
/**
24+
* Configuration keys for Recon Chatbot service.
25+
*/
26+
@InterfaceAudience.Private
27+
@InterfaceStability.Unstable
28+
public final class ChatbotConfigKeys {
29+
30+
private ChatbotConfigKeys() {
31+
// No instances
32+
}
33+
34+
public static final String OZONE_RECON_CHATBOT_PREFIX = "ozone.recon.chatbot.";
35+
36+
// ── Feature toggle ──────────────────────────────────────────
37+
public static final String OZONE_RECON_CHATBOT_ENABLED = OZONE_RECON_CHATBOT_PREFIX + "enabled";
38+
public static final boolean OZONE_RECON_CHATBOT_ENABLED_DEFAULT = false;
39+
40+
// ── Provider selection ──────────────────────────────────────
41+
/** Active default provider: openai, gemini, anthropic. */
42+
public static final String OZONE_RECON_CHATBOT_PROVIDER = OZONE_RECON_CHATBOT_PREFIX + "provider";
43+
public static final String OZONE_RECON_CHATBOT_PROVIDER_DEFAULT = "gemini";
44+
45+
// ── Default model ───────────────────────────────────────────
46+
public static final String OZONE_RECON_CHATBOT_DEFAULT_MODEL = OZONE_RECON_CHATBOT_PREFIX + "default.model";
47+
public static final String OZONE_RECON_CHATBOT_DEFAULT_MODEL_DEFAULT = "gemini-2.5-flash";
48+
49+
// ── HTTP timeout for provider calls ─────────────────────────
50+
public static final String OZONE_RECON_CHATBOT_TIMEOUT_MS = OZONE_RECON_CHATBOT_PREFIX + "timeout.ms";
51+
public static final int OZONE_RECON_CHATBOT_TIMEOUT_MS_DEFAULT = 120000;
52+
53+
// ── Per-provider API keys (resolved via JCEKS / CredentialHelper) ──
54+
public static final String OZONE_RECON_CHATBOT_OPENAI_API_KEY = OZONE_RECON_CHATBOT_PREFIX + "openai.api.key";
55+
public static final String OZONE_RECON_CHATBOT_GEMINI_API_KEY = OZONE_RECON_CHATBOT_PREFIX + "gemini.api.key";
56+
public static final String OZONE_RECON_CHATBOT_ANTHROPIC_API_KEY = OZONE_RECON_CHATBOT_PREFIX
57+
+ "anthropic.api.key";
58+
59+
// ── Per-provider base URL overrides (optional) ──────────────
60+
public static final String OZONE_RECON_CHATBOT_OPENAI_BASE_URL = OZONE_RECON_CHATBOT_PREFIX + "openai.base.url";
61+
public static final String OZONE_RECON_CHATBOT_OPENAI_BASE_URL_DEFAULT = "https://api.openai.com";
62+
63+
public static final String OZONE_RECON_CHATBOT_GEMINI_BASE_URL = OZONE_RECON_CHATBOT_PREFIX + "gemini.base.url";
64+
public static final String OZONE_RECON_CHATBOT_GEMINI_BASE_URL_DEFAULT = "https://generativelanguage.googleapis.com";
65+
66+
public static final String OZONE_RECON_CHATBOT_ANTHROPIC_BASE_URL = OZONE_RECON_CHATBOT_PREFIX
67+
+ "anthropic.base.url";
68+
public static final String OZONE_RECON_CHATBOT_ANTHROPIC_BASE_URL_DEFAULT = "https://api.anthropic.com";
69+
70+
// ── Execution policy ────────────────────────────────────────
71+
public static final String OZONE_RECON_CHATBOT_EXEC_MAX_RECORDS = OZONE_RECON_CHATBOT_PREFIX
72+
+ "exec.max.records";
73+
public static final int OZONE_RECON_CHATBOT_EXEC_MAX_RECORDS_DEFAULT = 1000;
74+
75+
public static final String OZONE_RECON_CHATBOT_EXEC_MAX_PAGES = OZONE_RECON_CHATBOT_PREFIX + "exec.max.pages";
76+
public static final int OZONE_RECON_CHATBOT_EXEC_MAX_PAGES_DEFAULT = 5;
77+
78+
public static final String OZONE_RECON_CHATBOT_EXEC_PAGE_SIZE = OZONE_RECON_CHATBOT_PREFIX + "exec.page.size";
79+
public static final int OZONE_RECON_CHATBOT_EXEC_PAGE_SIZE_DEFAULT = 200;
80+
81+
public static final String OZONE_RECON_CHATBOT_EXEC_REQUIRE_SAFE_SCOPE = OZONE_RECON_CHATBOT_PREFIX
82+
+ "exec.require.safe.scope";
83+
public static final boolean OZONE_RECON_CHATBOT_EXEC_REQUIRE_SAFE_SCOPE_DEFAULT = true;
84+
85+
// ── Agent configuration ─────────────────────────────────────
86+
public static final String OZONE_RECON_CHATBOT_MAX_TOOL_CALLS = OZONE_RECON_CHATBOT_PREFIX + "max.tool.calls";
87+
public static final int OZONE_RECON_CHATBOT_MAX_TOOL_CALLS_DEFAULT = 5;
88+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
* <p>
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
* <p>
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.ozone.recon.chatbot;
19+
20+
import com.google.inject.AbstractModule;
21+
import com.google.inject.Scopes;
22+
import org.apache.hadoop.ozone.recon.chatbot.agent.ChatbotAgent;
23+
import org.apache.hadoop.ozone.recon.chatbot.agent.ToolExecutor;
24+
import org.apache.hadoop.ozone.recon.chatbot.api.ChatbotEndpoint;
25+
import org.apache.hadoop.ozone.recon.chatbot.llm.LLMProvider;
26+
import org.apache.hadoop.ozone.recon.chatbot.llm.LLMProviderRouter;
27+
import org.apache.hadoop.ozone.recon.chatbot.security.CredentialHelper;
28+
29+
/**
30+
* Guice module for Chatbot dependency injection.
31+
*/
32+
public class ChatbotModule extends AbstractModule {
33+
34+
@Override
35+
protected void configure() {
36+
// Bind credential helper (JCEKS key management)
37+
bind(CredentialHelper.class).in(Scopes.SINGLETON);
38+
39+
// Bind LLM provider — router delegates to direct providers
40+
bind(LLMProvider.class).to(LLMProviderRouter.class).in(Scopes.SINGLETON);
41+
42+
// Bind agent components
43+
bind(ToolExecutor.class).in(Scopes.SINGLETON);
44+
bind(ChatbotAgent.class).in(Scopes.SINGLETON);
45+
46+
// Bind API endpoint
47+
bind(ChatbotEndpoint.class).in(Scopes.SINGLETON);
48+
}
49+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Ozone Recon Chatbot
2+
3+
AI-powered chatbot for Apache Ozone Recon that answers natural language questions
4+
about your cluster by calling Recon REST APIs and summarizing the results.
5+
6+
## Architecture
7+
8+
```
9+
[ Web UI / Client ]
10+
|
11+
[ ChatbotEndpoint (REST API) ]
12+
|
13+
[ ChatbotAgent (Orchestrator) ]
14+
| |
15+
[ LLMProviderRouter ] [ ToolExecutor ]
16+
| (calls Recon APIs)
17+
[ GeminiProvider ]
18+
[ OpenAIProvider ]
19+
[ AnthropicProvider ]
20+
[ DeepSeekProvider ]
21+
```
22+
23+
## How It Works
24+
25+
1. **User asks a question**`POST /api/v1/chatbot/chat`
26+
2. **LLM Call #1 (Tool Selection)** → LLM reads the Recon API schema and
27+
decides which endpoint to call (e.g. `/api/v1/volumes`)
28+
3. **ToolExecutor** → calls the Recon API internally, gets real cluster data
29+
4. **LLM Call #2 (Summarization)** → LLM reads the raw JSON and produces
30+
a human-friendly answer
31+
32+
## Configuration
33+
34+
Set in `ozone-site.xml` or via Docker Compose environment variables:
35+
36+
```xml
37+
<!-- Enable chatbot -->
38+
<property>
39+
<name>ozone.recon.chatbot.enabled</name>
40+
<value>true</value>
41+
</property>
42+
43+
<!-- Default provider: gemini, openai, anthropic, deepseek -->
44+
<property>
45+
<name>ozone.recon.chatbot.provider</name>
46+
<value>gemini</value>
47+
</property>
48+
49+
<!-- Default model -->
50+
<property>
51+
<name>ozone.recon.chatbot.default.model</name>
52+
<value>gemini-2.5-flash</value>
53+
</property>
54+
55+
<!-- API key (use JCEKS in production) -->
56+
<property>
57+
<name>ozone.recon.chatbot.gemini.api.key</name>
58+
<value>your-api-key</value>
59+
</property>
60+
```
61+
62+
### API Key Management
63+
64+
- **Development**: Set keys in `.env` or `ozone-site.xml` (plaintext)
65+
- **Production**: Store keys in Hadoop JCEKS:
66+
```bash
67+
hadoop credential create ozone.recon.chatbot.gemini.api.key \
68+
-provider jceks://file/path/to/credentials.jceks \
69+
-value "your-api-key"
70+
```
71+
72+
## API Endpoints
73+
74+
### Health Check
75+
```bash
76+
GET /api/v1/chatbot/health
77+
```
78+
79+
### Chat
80+
```bash
81+
POST /api/v1/chatbot/chat
82+
Content-Type: application/json
83+
84+
{
85+
"query": "How many unhealthy containers are there?",
86+
"model": "gemini-2.5-flash"
87+
}
88+
```
89+
90+
### List Available Models
91+
```bash
92+
GET /api/v1/chatbot/models
93+
```
94+
95+
## Supported Providers
96+
97+
| Provider | Models | Auth |
98+
|----------|--------|------|
99+
| **Gemini** | gemini-2.5-flash, gemini-1.5-pro, etc. | Bearer token |
100+
| **OpenAI** | gpt-4.1, gpt-4o, o3-mini, etc. | Bearer token |
101+
| **Anthropic** | claude-3-5-sonnet, claude-3-opus, etc. | x-api-key header |
102+
| **DeepSeek** | deepseek-chat, deepseek-reasoner | Bearer token |
103+
104+
The model name determines routing — `gemini-*` goes to Gemini, `gpt-*` to OpenAI, etc.
105+
106+
## Adding a New Provider
107+
108+
1. Create a class extending `DirectLLMProvider`
109+
2. Override: `getProviderName()`, `getApiKeyConfigKey()`,
110+
`getDefaultBaseUrl()`, `buildChatRequest()`, `parseResponse()`,
111+
`getSupportedModels()`
112+
3. Register it in `LLMProviderRouter` constructor
113+
4. Add config keys in `ChatbotConfigKeys`
114+
115+
## License
116+
117+
Apache License 2.0

0 commit comments

Comments
 (0)