mirror of
https://github.com/dromara/hertzbeat.git
synced 2026-09-17 18:19:02 +00:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7371b9a4b | ||
|
|
624ba8dc8d | ||
|
|
fb4b665c85 | ||
|
|
5a9f371e9d | ||
|
|
5c3d1a04de | ||
|
|
024b44f477 | ||
|
|
35046f8a06 | ||
|
|
23c2f85e8a | ||
|
|
56226c1d52 | ||
|
|
9014f117d3 |
+74
-7
@@ -1274,11 +1274,7 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/105542329?v=4",
|
||||
"profile": "https://github.com/lynx009",
|
||||
"contributions": [
|
||||
"code",
|
||||
"doc",
|
||||
"blog",
|
||||
"bug",
|
||||
"design"
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2588,8 +2584,7 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/95274591?v=4",
|
||||
"profile": "https://github.com/tuzuy",
|
||||
"contributions": [
|
||||
"code",
|
||||
"design"
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2600,6 +2595,78 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "cxhello",
|
||||
"name": "cxhello",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/49056040?v=4",
|
||||
"profile": "https://cxhello.top/",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "jl15988",
|
||||
"name": "会功夫的李白",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/70638770?v=4",
|
||||
"profile": "https://github.com/jl15988",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "AlbertYang0801",
|
||||
"name": "Albert.Yang",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/37127008?v=4",
|
||||
"profile": "https://blog.aytop.cloud/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "mengnankkkk",
|
||||
"name": "zhou yong kang",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/150590575?v=4",
|
||||
"profile": "https://blog.tokenlen.top/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "warrobe",
|
||||
"name": "warrobe",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/89446159?v=4",
|
||||
"profile": "https://github.com/warrobe",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Jetiaime",
|
||||
"name": "TeAmo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/93769000?v=4",
|
||||
"profile": "https://github.com/Jetiaime",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "pentium100",
|
||||
"name": "pentium100",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/27917?v=4",
|
||||
"profile": "https://github.com/pentium100",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dedyks",
|
||||
"name": "Dedy Kurniawan Santoso",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/23741665?v=4",
|
||||
"profile": "https://github.com/dedyks",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
@@ -16,11 +16,15 @@
|
||||
#
|
||||
|
||||
github:
|
||||
description: Real-time observability system with agentless, performance cluster, prometheus-compatible, custom monitoring and status page building capabilities.
|
||||
description: An AI-powered next-generation open source real-time observability system.
|
||||
homepage: https://hertzbeat.apache.org/
|
||||
labels:
|
||||
- monitoring
|
||||
- ai
|
||||
- agent
|
||||
- llm
|
||||
- logs
|
||||
- monitor
|
||||
- monitoring
|
||||
- notifications
|
||||
- alerting
|
||||
- self-hosted
|
||||
@@ -30,15 +34,11 @@ github:
|
||||
- metrics
|
||||
- observability
|
||||
- uptime
|
||||
- uptime-monitoring
|
||||
- status
|
||||
- status-page
|
||||
- devops
|
||||
- server
|
||||
- linux
|
||||
- database
|
||||
- mysql
|
||||
- cloud
|
||||
enabled_merge_buttons:
|
||||
squash: true
|
||||
merge: false
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {},
|
||||
// same extensions as Gitpod, should match /.gitpod.yml
|
||||
"extensions": [
|
||||
"vscjava.vscode-java-pack",
|
||||
"editorconfig.editorconfig",
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
python-version: '3.8'
|
||||
|
||||
- name: Check Markdown
|
||||
uses: DavidAnson/markdownlint-cli2-action@v16
|
||||
uses: DavidAnson/markdownlint-cli2-action@992badcdf24e3b8eb7e87ff9287fe931bcb00c6e
|
||||
with:
|
||||
globs: './home/**/*.md'
|
||||
|
||||
|
||||
+10
-14
@@ -44,19 +44,17 @@ Even small corrections to typos are very welcome :)
|
||||
|
||||
#### Frontend start
|
||||
|
||||
1. Need `Node Yarn` Environment, Make sure `Node.js >= 18`
|
||||
1. Need `Node pnpm` Environment, Make sure `Node.js >= 18`
|
||||
|
||||
2. Cd to the `web-app` directory: `cd web-app`
|
||||
|
||||
3. Install yarn if not existed `npm install -g yarn`
|
||||
3. Install pnpm if not existed `npm install -g pnpm`
|
||||
|
||||
4. Install Dependencies: `yarn install` or `yarn install --registry=https://registry.npmmirror.com` in `web-app`
|
||||
4. Install Dependencies: `pnpm install` or `pnpm install --registry=https://registry.npmmirror.com` in `web-app`
|
||||
|
||||
5. Install angular-cli globally: `yarn global add @angular/cli@15` or `yarn global add @angular/cli@15 --registry=https://registry.npmmirror.com`
|
||||
5. After the local backend is started, start the local frontend in the web-app directory: `pnpm start`
|
||||
|
||||
6. After the local backend is started, start the local frontend in the web-app directory: `ng serve --open`
|
||||
|
||||
7. Browser access to localhost:4200 to start, default account/password is *admin/hertzbeat*
|
||||
6. Browser access to localhost:4200 to start, default account/password is *admin/hertzbeat*
|
||||
|
||||
|
||||
### Find tasks
|
||||
@@ -198,19 +196,17 @@ Add WeChat account `ahertzbeat` to pull you into the WeChat group.
|
||||
|
||||
#### 前端启动
|
||||
|
||||
1. 需要 `nodejs yarn` 环境, Make sure `Node.js >= 18`
|
||||
1. 需要 `nodejs pnpm` 环境, Make sure `Node.js >= 18`
|
||||
|
||||
2. 进入 `web-app` 目录: `cd web-app`
|
||||
|
||||
3. 安装yarn: `npm install -g yarn`
|
||||
3. 安装pnpm: `npm install -g pnpm`
|
||||
|
||||
4. 在前端工程目录 `web-app` 下执行: `yarn install` or `yarn install --registry=https://registry.npmmirror.com` in `web-app`
|
||||
4. 在前端工程目录 `web-app` 下执行: `pnpm install` or `pnpm install --registry=https://registry.npmmirror.com` in `web-app`
|
||||
|
||||
5. 全局安装 `angular-cli`: `yarn global add @angular/cli@15` or `yarn global add @angular/cli@15 --registry=https://registry.npmmirror.com`
|
||||
5. 待本地后端启动后,在web-app目录下启动本地前端 `pnpm start`
|
||||
|
||||
6. 待本地后端启动后,在web-app目录下启动本地前端 `ng serve --open`
|
||||
|
||||
7. 浏览器访问 localhost:4200 即可开始,默认账号密码 *admin/hertzbeat*
|
||||
6. 浏览器访问 localhost:4200 即可开始,默认账号密码 *admin/hertzbeat*
|
||||
|
||||
### 寻找任务
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
<b><a href="README.md">English</a></b> | <a href="README_CN.md">中文</a> | <a href="README_JP.md">日本語</a>
|
||||
</p>
|
||||
|
||||
> A real-time observability system with agentless, performance cluster, prometheus-compatible, custom monitoring and status page building capabilities.
|
||||
|
||||
[](https://discord.gg/Fb6M73htGr)
|
||||
[](https://www.reddit.com/r/hertzbeat/)
|
||||
[](https://x.com/hertzbeat1024)
|
||||
@@ -21,6 +19,7 @@
|
||||
[](https://www.youtube.com/channel/UCri75zfWX0GHqJFPENEbLow)
|
||||
[](https://gitpod.io/#https://github.com/apache/hertzbeat)
|
||||
[](https://deepwiki.com/apache/hertzbeat)
|
||||
[](https://zread.ai/apache/hertzbeat)
|
||||
|
||||
**Home: [hertzbeat.apache.org](https://hertzbeat.apache.org)**
|
||||
**Email: <a href="mailto:dev-subscribe@hertzbeat.apache.org">Mail to `dev-subscribe@hertzbeat.apache.org`</a> to subscribe mailing lists**
|
||||
@@ -28,28 +27,23 @@
|
||||
|
||||
## 🎡 <font color="green">Introduction</font>
|
||||
|
||||
[Apache HertzBeat™](https://github.com/apache/hertzbeat) is an easy-to-use, open source, real-time observability system with agentless, high performance cluster, prometheus-compatible, offers powerful custom monitoring and status page building capabilities.
|
||||
[Apache HertzBeat™](https://github.com/apache/hertzbeat) is an AI-powered next-generation open source real-time observability system. Unified metrics and logs collection, centralized alerting distribution, intelligent management and analysis. No Agent required, high performance cluster, provides powerful custom monitoring and status page building capabilities.
|
||||
|
||||
### Features
|
||||
|
||||
* Combines **monitoring, alarm, and notification** features into one platform, and supports monitoring for web service, program, database, cache, os, webserver, middleware, bigdata, cloud-native, network, custom and more.
|
||||
* Easy to use and agentless, web-based and with one-click monitoring and alerting, zero learning curve.
|
||||
* Makes protocols such as `Http, Jmx, Ssh, Snmp, Jdbc, Prometheus` configurable, allowing you to collect any metrics by simply configuring the template `YML` file online. Imagine being able to quickly adapt to a new monitoring type like K8s or Docker simply by configuring online with HertzBeat.
|
||||
* Compatible with the `Prometheus` ecosystem and more, can monitoring what `Prometheus` can monitoring with few clicks on webui.
|
||||
* High performance, supports horizontal expansion of multi-collector clusters, multi-isolated network monitoring and cloud-edge collaboration.
|
||||
* Provides flexible alarm threshold rules and timely notifications delivered via `Discord` `Slack` `Telegram` `Email` `Dingtalk` `WeChat` `FeiShu` `Webhook` `SMS` `ServerChan`.
|
||||
* Provides powerful status page building capabilities, easily communicate the real-time status of your service to users.
|
||||
- Integrates **collection + analysis + alerting + notification** into one platform, with new AI-powered interactions and features under HertzBeat AI, and built-in MCP Server capabilities.
|
||||
- Unified metrics platform, agentless, Prometheus-compatible, supports application services, programs, databases, caches, operating systems, big data, middleware, web servers, cloud-native, networks, custom monitoring and more.
|
||||
- Unified logging platform, seamlessly integrates multiple log sources through OTLP protocol for reporting.
|
||||
- Unified alerting platform, integrates internal alerts with various external alert sources, unified alert processing and analysis, flexible real-time and periodic threshold rules, grouping convergence, silence, suppression, etc.
|
||||
- Unified message distribution, alerts processed by the alerting platform are distributed via `Email` `Discord` `Slack` `Telegram` `DingTalk` `WeChat` `FeiShu` `SMS` `Webhook` `ServerChan` and other methods.
|
||||
- Makes protocols such as `Http, Jmx, Ssh, Snmp, Jdbc, Prometheus` configurable, allowing you to collect any metrics by simply configuring the template `YML` file online. Imagine being able to quickly adapt to a new monitoring type like K8s or Docker simply by configuring online with HertzBeat.
|
||||
- High performance, supports horizontal expansion of multi-collector clusters, multi-isolated network monitoring and cloud-edge collaboration.
|
||||
- Provides powerful status page building capabilities, easily communicate the real-time status of your service to users.
|
||||
|
||||
> HertzBeat's powerful customization, multi-type support, high performance, easy expansion, and low coupling, aims to help users quickly build their own monitoring system.
|
||||
> HertzBeat's unified platform, AI intelligence, powerful customization, multi-type support, high performance, and easy expansion, aims to help users quickly and conveniently achieve observability requirements.
|
||||
|
||||
----
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
----
|
||||
|
||||
## 🥐 Architecture
|
||||
@@ -58,8 +52,8 @@
|
||||
|
||||
## ⛄ Supported
|
||||
|
||||
> We define all monitoring collection types such as `mysql`, `jvm`, and `k8s` as `YML` monitoring templates, allowing users to import them to support corresponding types of monitoring.
|
||||
> Welcome everyone to contribute your customized general monitoring type YML template during use.
|
||||
> We define all metrics collection types such as `mysql`, `jvm`, and `k8s` as `YML` templates, allowing users to import them to support corresponding types of monitoring.
|
||||
> Welcome everyone to contribute your customized general metrics type YML template during use.
|
||||
|
||||
|
||||
- [Website](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-website.yml), [Port Telnet](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-port.yml),
|
||||
@@ -361,7 +355,7 @@ Thanks to these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lisongning"><img src="https://avatars.githubusercontent.com/u/93140178?v=4?s=100" width="100px;" alt="lisongning"/><br /><sub><b>lisongning</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lisongning" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/YutingNie"><img src="https://avatars.githubusercontent.com/u/104416402?v=4?s=100" width="100px;" alt="YutingNie"/><br /><sub><b>YutingNie</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=YutingNie" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=YutingNie" title="Documentation">📖</a> <a href="#design-YutingNie" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mikezzb"><img src="https://avatars.githubusercontent.com/u/23418428?v=4?s=100" width="100px;" alt="Mike Zhou"/><br /><sub><b>Mike Zhou</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=mikezzb" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=mikezzb" title="Documentation">📖</a> <a href="#design-mikezzb" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lynx009"><img src="https://avatars.githubusercontent.com/u/105542329?v=4?s=100" width="100px;" alt="lynx009"/><br /><sub><b>lynx009</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lynx009" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=lynx009" title="Documentation">📖</a> <a href="#blog-lynx009" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Alynx009" title="Bug reports">🐛</a> <a href="#design-lynx009" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lynx009"><img src="https://avatars.githubusercontent.com/u/105542329?v=4?s=100" width="100px;" alt="lynx009"/><br /><sub><b>lynx009</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lynx009" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/littlezhongzer"><img src="https://avatars.githubusercontent.com/u/33685289?v=4?s=100" width="100px;" alt="littlezhongzer"/><br /><sub><b>littlezhongzer</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=littlezhongzer" title="Code">💻</a></td>
|
||||
@@ -537,8 +531,18 @@ Thanks to these wonderful people, welcome to join us:
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/chingjustwe"><img src="https://avatars.githubusercontent.com/u/13643747?v=4?s=100" width="100px;" alt="Rocky, Chi"/><br /><sub><b>Rocky, Chi</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=chingjustwe" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rowankid"><img src="https://avatars.githubusercontent.com/u/18652781?v=4?s=100" width="100px;" alt="Wenqi Luo"/><br /><sub><b>Wenqi Luo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/issues?q=author%3Arowankid" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tuzuy"><img src="https://avatars.githubusercontent.com/u/95274591?v=4?s=100" width="100px;" alt="tuzuy"/><br /><sub><b>tuzuy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=tuzuy" title="Code">💻</a> <a href="#design-tuzuy" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tuzuy"><img src="https://avatars.githubusercontent.com/u/95274591?v=4?s=100" width="100px;" alt="tuzuy"/><br /><sub><b>tuzuy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=tuzuy" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/carlpinto25"><img src="https://avatars.githubusercontent.com/u/117299909?v=4?s=100" width="100px;" alt="carl pinto"/><br /><sub><b>carl pinto</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=carlpinto25" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://cxhello.top/"><img src="https://avatars.githubusercontent.com/u/49056040?v=4?s=100" width="100px;" alt="cxhello"/><br /><sub><b>cxhello</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=cxhello" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jl15988"><img src="https://avatars.githubusercontent.com/u/70638770?v=4?s=100" width="100px;" alt="会功夫的李白"/><br /><sub><b>会功夫的李白</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=jl15988" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://blog.aytop.cloud/"><img src="https://avatars.githubusercontent.com/u/37127008?v=4?s=100" width="100px;" alt="Albert.Yang"/><br /><sub><b>Albert.Yang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=AlbertYang0801" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://blog.tokenlen.top/"><img src="https://avatars.githubusercontent.com/u/150590575?v=4?s=100" width="100px;" alt="zhou yong kang"/><br /><sub><b>zhou yong kang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=mengnankkkk" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/warrobe"><img src="https://avatars.githubusercontent.com/u/89446159?v=4?s=100" width="100px;" alt="warrobe"/><br /><sub><b>warrobe</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=warrobe" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Jetiaime"><img src="https://avatars.githubusercontent.com/u/93769000?v=4?s=100" width="100px;" alt="TeAmo"/><br /><sub><b>TeAmo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Jetiaime" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pentium100"><img src="https://avatars.githubusercontent.com/u/27917?v=4?s=100" width="100px;" alt="pentium100"/><br /><sub><b>pentium100</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pentium100" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dedyks"><img src="https://avatars.githubusercontent.com/u/23741665?v=4?s=100" width="100px;" alt="Dedy Kurniawan Santoso"/><br /><sub><b>Dedy Kurniawan Santoso</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=dedyks" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
+23
-20
@@ -9,8 +9,6 @@
|
||||
<a href="README.md">English</a> | <b><a href="README_CN.md">中文</a></b> | <a href="README_JP.md">日本語</a>
|
||||
</p>
|
||||
|
||||
> 实时观测系统,无需 Agent,性能集群,兼容 Prometheus,自定义监控和状态页构建能力。
|
||||
|
||||
[](https://discord.gg/Fb6M73htGr)
|
||||
[](https://www.reddit.com/r/hertzbeat/)
|
||||
[](https://x.com/hertzbeat1024)
|
||||
@@ -28,29 +26,24 @@
|
||||
|
||||
## 🎡 <font color="green">介绍</font>
|
||||
|
||||
[Apache HertzBeat™](https://github.com/apache/hertzbeat) 是一个易用友好的开源实时监控告警系统,无需 Agent,高性能集群,兼容 Prometheus,提供强大的自定义监控和状态页构建能力。
|
||||
[Apache HertzBeat™](https://github.com/apache/hertzbeat) 是 AI 驱动的下一代开源实时观测系统。指标日志统一收集,告警一站分发,智能管控分析。无需 Agent,高性能集群,提供强大的自定义监控和状态页构建能力。
|
||||
|
||||
### 特点
|
||||
|
||||
- 集 **监控+告警+通知** 为一体,支持对应用服务,应用程序,数据库,缓存,操作系统,大数据,中间件,Web 服务器,云原生,网络,自定义等监控阈值告警通知一步到位。
|
||||
- 易用友好,无需 `Agent`,全 `WEB` 页面操作,鼠标点一点就能监控告警,无需学习成本。
|
||||
- 将 `Http, Jmx, Ssh, Snmp, Jdbc, Prometheus` 等协议规范可配置化,只需在浏览器配置监控模板 `YML` 就能使用这些协议去自定义采集想要的指标。您相信只需简单配置即可快速适配一款 `K8s` 或 `Docker` 等新的监控类型吗?
|
||||
- 兼容 `Prometheus` 的系统生态并且更多,只需页面操作就可以监控 `Prometheus` 所能监控的。
|
||||
- 集```采集+分析+告警+通知```为一体,HertzBeat AI 驱动下的新交互与功能,也内置 MCP Server 对外能力。
|
||||
- 统一的指标平台,无需 Agent,兼容 Prometheus,支持应用服务,程序,数据库,缓存,操作系统,大数据,中间件,Web 服务器,云原生,网络,自定义等。
|
||||
- 统一的日志平台,通过 OTLP 协议多日志源无缝对接上报。
|
||||
- 统一的告警平台,内部告警与外部多种告警源集成接入,统一告警处理分析,灵活的实时与周期阈值规则,分组收敛,静默,抑制等。
|
||||
- 统一的消息分发,告警平台处理后通过 `邮件` `Discord` `Slack` `Telegram` `钉钉` `微信` `飞书` `短信` `Webhook` `Server酱` 等方式分发通知。
|
||||
- 将 `Http, Jmx, Ssh, Snmp, Jdbc, Prometheus` 等协议规范可配置化,只需配置模板 `YML` 就能自定义采集指标。您相信只需简单配置即可快速适配一款 `K8s` 或 `Docker` 等新的监控类型吗?
|
||||
- 高性能,支持多采集器集群横向扩展,支持多隔离网络监控,云边协同。
|
||||
- 灵活的告警阈值规则,`邮件` `Discord` `Slack` `Telegram` `钉钉` `微信` `飞书` `短信` `Webhook` `Server酱` 等方式消息及时送达。
|
||||
- 提供强大的状态页构建能力,轻松向用户传达您产品服务的实时状态。
|
||||
|
||||
|
||||
> `HertzBeat`的强大自定义,多类型支持,高性能,易扩展,低耦合,希望能帮助用户快速搭建自有监控系统。
|
||||
> `HertzBeat`的统一平台,AI智能,强大自定义,多类型支持,高性能,易扩展,希望能帮助用户快速方便实现观测需求。
|
||||
|
||||
----
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
----
|
||||
|
||||
## 🥐 模块
|
||||
@@ -59,8 +52,8 @@
|
||||
|
||||
## ⛄ 已支持
|
||||
|
||||
> 我们将监控采集类型(mysql,jvm,k8s)都定义为 `yml` 监控模板,用户可以导入这些模板来支持对应类型的监控!
|
||||
> 欢迎大家一起贡献你使用过程中自定义的通用监控类型监控模板。
|
||||
> 我们将监控采集类型(mysql,jvm,k8s)都定义为 `yml` 模板,用户可以导入这些模板来支持对应类型的监控!
|
||||
> 欢迎大家一起贡献你使用过程中自定义的通用指标模板。
|
||||
|
||||
- [Website](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-website.yml), [Port Telnet](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-port.yml),
|
||||
[Http Api](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-api.yml), [Ping Connect](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-ping.yml),
|
||||
@@ -98,7 +91,7 @@
|
||||
- [CiscoSwitch](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-cisco_switch.yml), [HpeSwitch](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-hpe_switch.yml),
|
||||
[HuaweiSwitch](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-huawei_switch.yml), [TpLinkSwitch](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-tplink_switch.yml),
|
||||
[H3cSwitch](https://raw.githubusercontent.com/apache/hertzbeat/master/hertzbeat-manager/src/main/resources/define/app-h3c_switch.yml)
|
||||
- 和更多自定义监控模板。
|
||||
- 和更多自定义指标模板。
|
||||
- 通知支持 `Discord` `Slack` `Telegram` `邮件` `钉钉` `微信` `飞书` `短信` `Webhook` `Server酱`。
|
||||
|
||||
## 🐕 快速开始
|
||||
@@ -359,7 +352,7 @@ Thanks these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lisongning"><img src="https://avatars.githubusercontent.com/u/93140178?v=4?s=100" width="100px;" alt="lisongning"/><br /><sub><b>lisongning</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lisongning" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/YutingNie"><img src="https://avatars.githubusercontent.com/u/104416402?v=4?s=100" width="100px;" alt="YutingNie"/><br /><sub><b>YutingNie</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=YutingNie" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=YutingNie" title="Documentation">📖</a> <a href="#design-YutingNie" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mikezzb"><img src="https://avatars.githubusercontent.com/u/23418428?v=4?s=100" width="100px;" alt="Mike Zhou"/><br /><sub><b>Mike Zhou</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=mikezzb" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=mikezzb" title="Documentation">📖</a> <a href="#design-mikezzb" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lynx009"><img src="https://avatars.githubusercontent.com/u/105542329?v=4?s=100" width="100px;" alt="lynx009"/><br /><sub><b>lynx009</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lynx009" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=lynx009" title="Documentation">📖</a> <a href="#blog-lynx009" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Alynx009" title="Bug reports">🐛</a> <a href="#design-lynx009" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lynx009"><img src="https://avatars.githubusercontent.com/u/105542329?v=4?s=100" width="100px;" alt="lynx009"/><br /><sub><b>lynx009</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lynx009" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/littlezhongzer"><img src="https://avatars.githubusercontent.com/u/33685289?v=4?s=100" width="100px;" alt="littlezhongzer"/><br /><sub><b>littlezhongzer</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=littlezhongzer" title="Code">💻</a></td>
|
||||
@@ -535,8 +528,18 @@ Thanks these wonderful people, welcome to join us:
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/chingjustwe"><img src="https://avatars.githubusercontent.com/u/13643747?v=4?s=100" width="100px;" alt="Rocky, Chi"/><br /><sub><b>Rocky, Chi</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=chingjustwe" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rowankid"><img src="https://avatars.githubusercontent.com/u/18652781?v=4?s=100" width="100px;" alt="Wenqi Luo"/><br /><sub><b>Wenqi Luo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/issues?q=author%3Arowankid" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tuzuy"><img src="https://avatars.githubusercontent.com/u/95274591?v=4?s=100" width="100px;" alt="tuzuy"/><br /><sub><b>tuzuy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=tuzuy" title="Code">💻</a> <a href="#design-tuzuy" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tuzuy"><img src="https://avatars.githubusercontent.com/u/95274591?v=4?s=100" width="100px;" alt="tuzuy"/><br /><sub><b>tuzuy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=tuzuy" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/carlpinto25"><img src="https://avatars.githubusercontent.com/u/117299909?v=4?s=100" width="100px;" alt="carl pinto"/><br /><sub><b>carl pinto</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=carlpinto25" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://cxhello.top/"><img src="https://avatars.githubusercontent.com/u/49056040?v=4?s=100" width="100px;" alt="cxhello"/><br /><sub><b>cxhello</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=cxhello" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jl15988"><img src="https://avatars.githubusercontent.com/u/70638770?v=4?s=100" width="100px;" alt="会功夫的李白"/><br /><sub><b>会功夫的李白</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=jl15988" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://blog.aytop.cloud/"><img src="https://avatars.githubusercontent.com/u/37127008?v=4?s=100" width="100px;" alt="Albert.Yang"/><br /><sub><b>Albert.Yang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=AlbertYang0801" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://blog.tokenlen.top/"><img src="https://avatars.githubusercontent.com/u/150590575?v=4?s=100" width="100px;" alt="zhou yong kang"/><br /><sub><b>zhou yong kang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=mengnankkkk" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/warrobe"><img src="https://avatars.githubusercontent.com/u/89446159?v=4?s=100" width="100px;" alt="warrobe"/><br /><sub><b>warrobe</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=warrobe" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Jetiaime"><img src="https://avatars.githubusercontent.com/u/93769000?v=4?s=100" width="100px;" alt="TeAmo"/><br /><sub><b>TeAmo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Jetiaime" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pentium100"><img src="https://avatars.githubusercontent.com/u/27917?v=4?s=100" width="100px;" alt="pentium100"/><br /><sub><b>pentium100</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pentium100" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dedyks"><img src="https://avatars.githubusercontent.com/u/23741665?v=4?s=100" width="100px;" alt="Dedy Kurniawan Santoso"/><br /><sub><b>Dedy Kurniawan Santoso</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=dedyks" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
+22
-20
@@ -9,8 +9,6 @@
|
||||
<a href="README.md">English</a> | <a href="README_CN.md">中文</a> | <b><a href="README_JP.md">日本語</a></b>
|
||||
</p>
|
||||
|
||||
> オープンソースのリアルタイム監視システム、エージェントレス、クラスタ対応、Prometheus互換、カスタムおよびステータスページ付き。
|
||||
|
||||
[](https://discord.gg/Fb6M73htGr)
|
||||
[](https://www.reddit.com/r/hertzbeat/)
|
||||
[](https://x.com/hertzbeat1024)
|
||||
@@ -28,29 +26,23 @@
|
||||
|
||||
## 🎡 <font color="green">紹介</font>
|
||||
|
||||
[Apache HertzBeat](https://github.com/apache/hertzbeat) (incubating)はオープンソースのリアルタイム監視システム、エージェントレス、クラスタ対応、Prometheus互換、カスタムおよびステータスページ付き。
|
||||
[Apache HertzBeat™](https://github.com/apache/hertzbeat) は AI 駆動の次世代オープンソースリアルタイム観測システムです。メトリクスとログの統一収集、アラートの一元配信、インテリジェント管理分析。エージェント不要、高性能クラスタ、強力なカスタム監視とステータスページ構築機能を提供します。
|
||||
|
||||
### 特性
|
||||
|
||||
- **モニター・アラーム・通知** を一体化し、Web、データベース、キャッシュ、OS、ミドルウェア、クラウドネイティブ、ネットワークなどをサポート。
|
||||
- 使いやすさを重視し、マウスのクリックだけで完全なWebベースの操作が可能。
|
||||
- `Http、Jmx、Ssh、Snmp、jdbc、Prometheus`などのプロトコルを設定可能にし、テンプレート`YML`ファイルをオンラインで設定するだけで、あらゆるメトリクスを収集できるようにします。HertzBeatでオンライン設定するだけで、`K8S`や`Docker`のような新しい監視タイプに素早く対応できることを想像してみてください。
|
||||
- `Prometheus`のエコシステム互換、Prometheusが監視できるものを、全てオンライン設定によって監視することができます。
|
||||
- 高性能で、コレクタークラスタ、マルチアイソレートネットワーク、クラウドエッジをサポート。
|
||||
- 柔軟なアラーム閾値ルールで、メール、discord、slack、telegramなどを通じてタイムリーに通知。
|
||||
- 強力なステータスページを簡単に構築し、リアルタイムのステータスをユーザーと共有。
|
||||
- **収集+分析+アラート+通知**を一つのプラットフォームに統合し、HertzBeat AI 駆動の新しいインタラクションと機能、内蔵 MCP Server 機能を提供。
|
||||
- 統一メトリクスプラットフォーム、エージェントレス、Prometheus互換、アプリケーションサービス、プログラム、データベース、キャッシュ、オペレーティングシステム、ビッグデータ、ミドルウェア、Webサーバー、クラウドネイティブ、ネットワーク、カスタム監視などをサポート。
|
||||
- 統一ログプラットフォーム、OTLP プロトコルを通じて複数のログソースをシームレスに統合してレポート。
|
||||
- 統一アラートプラットフォーム、内部アラートと様々な外部アラートソースを統合接続、統一アラート処理分析、柔軟なリアルタイムと周期的閾値ルール、グループ収束、サイレンス、抑制など。
|
||||
- 統一メッセージ配信、アラートプラットフォームで処理後、`メール` `Discord` `Slack` `Telegram` `DingTalk` `WeChat` `FeiShu` `SMS` `Webhook` `ServerChan` などの方法で配信通知。
|
||||
- `Http、Jmx、Ssh、Snmp、Jdbc、Prometheus`などのプロトコルを設定可能にし、テンプレート`YML`ファイルをオンラインで設定するだけで、あらゆるメトリクスを収集できるようにします。HertzBeatでオンライン設定するだけで、`K8s`や`Docker`のような新しい監視タイプに素早く対応できることを想像してみてください。
|
||||
- 高性能で、コレクタークラスタの水平拡張、マルチ分離ネットワーク監視、クラウドエッジ協調をサポート。
|
||||
- 強力なステータスページ構築機能を提供し、サービスのリアルタイムステータスをユーザーに簡単に伝達。
|
||||
|
||||
|
||||
> `HertzBeat`の強力なカスタマイズ、多種類のサポート、高性能および容易な拡張性は、ユーザーが迅速に独自の監視システムを構築するのを支援することを目的としています。
|
||||
> `HertzBeat`の統一プラットフォーム、AI インテリジェンス、強力なカスタマイズ、多種類サポート、高性能、容易な拡張性は、ユーザーが迅速かつ便利に観測要件を実現することを支援することを目的としています。
|
||||
|
||||
----
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
----
|
||||
|
||||
## 🥐 モジュール
|
||||
@@ -363,7 +355,7 @@ Thanks these wonderful people, welcome to join us:
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lisongning"><img src="https://avatars.githubusercontent.com/u/93140178?v=4?s=100" width="100px;" alt="lisongning"/><br /><sub><b>lisongning</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lisongning" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/YutingNie"><img src="https://avatars.githubusercontent.com/u/104416402?v=4?s=100" width="100px;" alt="YutingNie"/><br /><sub><b>YutingNie</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=YutingNie" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=YutingNie" title="Documentation">📖</a> <a href="#design-YutingNie" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mikezzb"><img src="https://avatars.githubusercontent.com/u/23418428?v=4?s=100" width="100px;" alt="Mike Zhou"/><br /><sub><b>Mike Zhou</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=mikezzb" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=mikezzb" title="Documentation">📖</a> <a href="#design-mikezzb" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lynx009"><img src="https://avatars.githubusercontent.com/u/105542329?v=4?s=100" width="100px;" alt="lynx009"/><br /><sub><b>lynx009</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lynx009" title="Code">💻</a> <a href="https://github.com/apache/hertzbeat/commits?author=lynx009" title="Documentation">📖</a> <a href="#blog-lynx009" title="Blogposts">📝</a> <a href="https://github.com/apache/hertzbeat/issues?q=author%3Alynx009" title="Bug reports">🐛</a> <a href="#design-lynx009" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lynx009"><img src="https://avatars.githubusercontent.com/u/105542329?v=4?s=100" width="100px;" alt="lynx009"/><br /><sub><b>lynx009</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=lynx009" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/littlezhongzer"><img src="https://avatars.githubusercontent.com/u/33685289?v=4?s=100" width="100px;" alt="littlezhongzer"/><br /><sub><b>littlezhongzer</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=littlezhongzer" title="Code">💻</a></td>
|
||||
@@ -539,8 +531,18 @@ Thanks these wonderful people, welcome to join us:
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/chingjustwe"><img src="https://avatars.githubusercontent.com/u/13643747?v=4?s=100" width="100px;" alt="Rocky, Chi"/><br /><sub><b>Rocky, Chi</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=chingjustwe" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rowankid"><img src="https://avatars.githubusercontent.com/u/18652781?v=4?s=100" width="100px;" alt="Wenqi Luo"/><br /><sub><b>Wenqi Luo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/issues?q=author%3Arowankid" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tuzuy"><img src="https://avatars.githubusercontent.com/u/95274591?v=4?s=100" width="100px;" alt="tuzuy"/><br /><sub><b>tuzuy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=tuzuy" title="Code">💻</a> <a href="#design-tuzuy" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tuzuy"><img src="https://avatars.githubusercontent.com/u/95274591?v=4?s=100" width="100px;" alt="tuzuy"/><br /><sub><b>tuzuy</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=tuzuy" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/carlpinto25"><img src="https://avatars.githubusercontent.com/u/117299909?v=4?s=100" width="100px;" alt="carl pinto"/><br /><sub><b>carl pinto</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=carlpinto25" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://cxhello.top/"><img src="https://avatars.githubusercontent.com/u/49056040?v=4?s=100" width="100px;" alt="cxhello"/><br /><sub><b>cxhello</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=cxhello" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jl15988"><img src="https://avatars.githubusercontent.com/u/70638770?v=4?s=100" width="100px;" alt="会功夫的李白"/><br /><sub><b>会功夫的李白</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=jl15988" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://blog.aytop.cloud/"><img src="https://avatars.githubusercontent.com/u/37127008?v=4?s=100" width="100px;" alt="Albert.Yang"/><br /><sub><b>Albert.Yang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=AlbertYang0801" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://blog.tokenlen.top/"><img src="https://avatars.githubusercontent.com/u/150590575?v=4?s=100" width="100px;" alt="zhou yong kang"/><br /><sub><b>zhou yong kang</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=mengnankkkk" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/warrobe"><img src="https://avatars.githubusercontent.com/u/89446159?v=4?s=100" width="100px;" alt="warrobe"/><br /><sub><b>warrobe</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=warrobe" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Jetiaime"><img src="https://avatars.githubusercontent.com/u/93769000?v=4?s=100" width="100px;" alt="TeAmo"/><br /><sub><b>TeAmo</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=Jetiaime" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pentium100"><img src="https://avatars.githubusercontent.com/u/27917?v=4?s=100" width="100px;" alt="pentium100"/><br /><sub><b>pentium100</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=pentium100" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dedyks"><img src="https://avatars.githubusercontent.com/u/23741665?v=4?s=100" width="100px;" alt="Dedy Kurniawan Santoso"/><br /><sub><b>Dedy Kurniawan Santoso</b></sub></a><br /><a href="https://github.com/apache/hertzbeat/commits?author=dedyks" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
-81
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.ai.agent.adapters;
|
||||
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.Hierarchy;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface that provides access to alert definition information by retrieving data
|
||||
* through the underlying alert define service.
|
||||
*/
|
||||
public interface AlertDefineServiceAdapter {
|
||||
|
||||
/**
|
||||
* Add a new alert rule definition
|
||||
* @param alertDefine Alert definition to add
|
||||
* @return Created alert definition with ID
|
||||
*/
|
||||
AlertDefine addAlertDefine(AlertDefine alertDefine);
|
||||
|
||||
/**
|
||||
* Get alert definitions with filtering and pagination
|
||||
* @param search Search term
|
||||
* @param app Monitor type filter
|
||||
* @param enabled Enabled status filter
|
||||
* @param sort Sort field
|
||||
* @param order Sort order
|
||||
* @param pageIndex Page index
|
||||
* @param pageSize Page size
|
||||
* @return Page of alert definitions
|
||||
*/
|
||||
Page<AlertDefine> getAlertDefines(String search, String app, Boolean enabled, String sort, String order, int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Get alert definition by ID
|
||||
* @param id Alert definition ID
|
||||
* @return Alert definition if found
|
||||
*/
|
||||
AlertDefine getAlertDefine(Long id);
|
||||
|
||||
/**
|
||||
* Enable or disable alert definition
|
||||
* @param id Alert definition ID
|
||||
* @param enabled Whether to enable
|
||||
*/
|
||||
void toggleAlertDefineStatus(Long id, boolean enabled);
|
||||
|
||||
/**
|
||||
* Modify/update an existing alert definition
|
||||
* @param alertDefine Alert definition to update
|
||||
* @return Updated alert definition
|
||||
*/
|
||||
AlertDefine modifyAlertDefine(AlertDefine alertDefine);
|
||||
|
||||
/**
|
||||
* Get specific app hierarchy structure
|
||||
* @param app App type
|
||||
* @param lang Language for localization
|
||||
* @return List of hierarchy objects for specific app
|
||||
*/
|
||||
|
||||
List<Hierarchy> getAppHierarchy(String app, String lang);
|
||||
}
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.ai.agent.adapters;
|
||||
|
||||
import org.apache.hertzbeat.alert.dto.AlertSummary;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
/**
|
||||
* Interface that provides access to alert information by retrieving alert data
|
||||
* through the underlying alert service.
|
||||
*/
|
||||
public interface AlertServiceAdapter {
|
||||
|
||||
/**
|
||||
* Get single alerts with filtering and pagination
|
||||
* @param status Alert status
|
||||
* @param search Search term
|
||||
* @param sort Sort field
|
||||
* @param order Sort order
|
||||
* @param pageIndex Page index
|
||||
* @param pageSize Page size
|
||||
* @return Page of single alerts
|
||||
*/
|
||||
Page<SingleAlert> getSingleAlerts(String status, String search, String sort, String order, int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Get group alerts with filtering and pagination
|
||||
* @param status Alert status
|
||||
* @param search Search term
|
||||
* @param sort Sort field
|
||||
* @param order Sort order
|
||||
* @param pageIndex Page index
|
||||
* @param pageSize Page size
|
||||
* @return Page of group alerts
|
||||
*/
|
||||
Page<GroupAlert> getGroupAlerts(String status, String search, String sort, String order, int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Get alerts summary statistics
|
||||
* @return Alert summary information
|
||||
*/
|
||||
AlertSummary getAlertsSummary();
|
||||
}
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.ai.agent.adapters;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.dto.MetricsData;
|
||||
import org.apache.hertzbeat.common.entity.dto.MetricsHistoryData;
|
||||
|
||||
/**
|
||||
* Interface that provides access to metrics information by retrieving metrics data
|
||||
* through the underlying metrics service.
|
||||
*/
|
||||
public interface MetricsServiceAdapter {
|
||||
|
||||
/**
|
||||
* Check warehouse storage server status
|
||||
* @return true if warehouse is available, false otherwise
|
||||
*/
|
||||
Boolean getWarehouseStorageServerStatus();
|
||||
|
||||
/**
|
||||
* Query real-time metrics data
|
||||
* @param monitorId Monitor ID
|
||||
* @param metrics Metrics name
|
||||
* @return Real-time metrics data
|
||||
*/
|
||||
MetricsData getMetricsData(Long monitorId, String metrics);
|
||||
|
||||
/**
|
||||
* Query historical metrics data
|
||||
* @param monitorId Monitor ID
|
||||
* @param app Monitor type
|
||||
* @param metrics Metrics name
|
||||
* @param metric Metric field name
|
||||
* @param label Label filter
|
||||
* @param history Query historical time period
|
||||
* @param interval Whether to aggregate data
|
||||
* @return Historical metrics data
|
||||
*/
|
||||
MetricsHistoryData getMetricHistoryData(Long monitorId, String app, String metrics, String metric, String label, String history, Boolean interval);
|
||||
}
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.ai.agent.adapters;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Interface that provides access to monitor information by retrieving monitor data
|
||||
* through the underlying monitor service.
|
||||
*/
|
||||
public interface MonitorServiceAdapter {
|
||||
Page<Monitor> getMonitors(
|
||||
List<Long> ids,
|
||||
String app,
|
||||
String search,
|
||||
Byte status,
|
||||
String sort,
|
||||
String order,
|
||||
Integer pageIndex,
|
||||
Integer pageSize,
|
||||
String labels
|
||||
);
|
||||
|
||||
/**
|
||||
* Add a new monitor
|
||||
*
|
||||
* @param monitor The monitor entity to create
|
||||
* @param params List of parameters for the monitor
|
||||
* @param collector Optional collector assignment
|
||||
* @return The created monitor ID
|
||||
*/
|
||||
Long addMonitor(Monitor monitor, List<Param> params, String collector);
|
||||
|
||||
/**
|
||||
* Get all available monitor types with their display names
|
||||
*
|
||||
* @param language Language code (e.g., "en-US", "zh-CN")
|
||||
* @return Map of monitor type key to display name
|
||||
*/
|
||||
Map<String, String> getAvailableMonitorTypes(String language);
|
||||
|
||||
/**
|
||||
* Get parameter definitions for a specific monitor type
|
||||
*
|
||||
* @param app Monitor type/application name (e.g., "linux", "mysql", "redis")
|
||||
* @return List of parameter definitions for the monitor type
|
||||
*/
|
||||
List<ParamDefine> getMonitorParamDefines(String app);
|
||||
}
|
||||
|
||||
-257
@@ -1,257 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.ai.agent.adapters.impl;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.adapters.AlertDefineServiceAdapter;
|
||||
import org.apache.hertzbeat.ai.agent.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.Hierarchy;
|
||||
import org.apache.hertzbeat.ai.agent.utils.UtilityClass;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implementation of the AlertDefineServiceAdapter interface that provides access to alert definition information
|
||||
* through reflection by invoking the underlying alert define service implementation.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AlertDefineServiceAdapterImpl implements AlertDefineServiceAdapter {
|
||||
|
||||
@Override
|
||||
public AlertDefine addAlertDefine(AlertDefine alertDefine) {
|
||||
try {
|
||||
Object alertDefineService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for addAlertDefine: {}", subjectSum);
|
||||
|
||||
try {
|
||||
alertDefineService = SpringContextHolder.getBean("alertDefineServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'alertDefineServiceImpl'");
|
||||
}
|
||||
|
||||
assert alertDefineService != null;
|
||||
log.debug("AlertDefineService bean found: {}", alertDefineService.getClass().getSimpleName());
|
||||
|
||||
Method method = alertDefineService.getClass().getMethod("addAlertDefine", AlertDefine.class);
|
||||
|
||||
method.invoke(alertDefineService, alertDefine);
|
||||
|
||||
log.debug("Successfully added alert define with ID: {}", alertDefine.getId());
|
||||
return alertDefine;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: addAlertDefine", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke addAlertDefine via adapter", e);
|
||||
throw new RuntimeException("Failed to invoke addAlertDefine via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<AlertDefine> getAlertDefines(String search, String app, Boolean enabled, String sort, String order, int pageIndex, int pageSize) {
|
||||
try {
|
||||
Object alertDefineService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getAlertDefines: {}", subjectSum);
|
||||
|
||||
try {
|
||||
alertDefineService = SpringContextHolder.getBean("alertDefineServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'alertDefineServiceImpl'");
|
||||
}
|
||||
|
||||
assert alertDefineService != null;
|
||||
log.debug("AlertDefineService bean found: {}", alertDefineService.getClass().getSimpleName());
|
||||
|
||||
Method method = alertDefineService.getClass().getMethod(
|
||||
"getAlertDefines",
|
||||
List.class, String.class, String.class, String.class, int.class, int.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Page<AlertDefine> result = (Page<AlertDefine>) method.invoke(
|
||||
alertDefineService, null, search, sort, order, pageIndex, pageSize);
|
||||
|
||||
log.debug("Successfully retrieved {} alert defines", result.getContent().size());
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getAlertDefines", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke getAlertDefines via adapter", e);
|
||||
throw new RuntimeException("Failed to invoke getAlertDefines via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDefine getAlertDefine(Long id) {
|
||||
try {
|
||||
Object alertDefineService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getAlertDefine: {}", subjectSum);
|
||||
|
||||
try {
|
||||
alertDefineService = SpringContextHolder.getBean("alertDefineServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'alertDefineServiceImpl'");
|
||||
}
|
||||
|
||||
assert alertDefineService != null;
|
||||
log.debug("AlertDefineService bean found: {}", alertDefineService.getClass().getSimpleName());
|
||||
|
||||
Method method = alertDefineService.getClass().getMethod("getAlertDefine", long.class);
|
||||
|
||||
AlertDefine result = (AlertDefine) method.invoke(alertDefineService, id);
|
||||
|
||||
log.debug("Successfully retrieved alert define with ID: {}", id);
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getAlertDefine", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke getAlertDefine via adapter for ID: {}", id, e);
|
||||
throw new RuntimeException("Failed to invoke getAlertDefine via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void toggleAlertDefineStatus(Long id, boolean enabled) {
|
||||
try {
|
||||
Object alertDefineService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for toggleAlertDefineStatus: {}", subjectSum);
|
||||
|
||||
try {
|
||||
alertDefineService = SpringContextHolder.getBean("alertDefineServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'alertDefineServiceImpl'");
|
||||
}
|
||||
|
||||
assert alertDefineService != null;
|
||||
log.debug("AlertDefineService bean found: {}", alertDefineService.getClass().getSimpleName());
|
||||
|
||||
// First get the existing AlertDefine
|
||||
Method getMethod = alertDefineService.getClass().getMethod("getAlertDefine", long.class);
|
||||
AlertDefine alertDefine = (AlertDefine) getMethod.invoke(alertDefineService, id);
|
||||
|
||||
if (alertDefine == null) {
|
||||
throw new RuntimeException("AlertDefine with ID " + id + " not found");
|
||||
}
|
||||
|
||||
// Update the enable status
|
||||
alertDefine.setEnable(enabled);
|
||||
|
||||
// Use modifyAlertDefine to save the changes
|
||||
Method modifyMethod = alertDefineService.getClass().getMethod("modifyAlertDefine", AlertDefine.class);
|
||||
modifyMethod.invoke(alertDefineService, alertDefine);
|
||||
|
||||
log.debug("Successfully toggled alert define status for ID: {} to enabled: {}", id, enabled);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke toggleAlertDefineStatus via adapter for ID: {}", id, e);
|
||||
throw new RuntimeException("Failed to invoke toggleAlertDefineStatus via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDefine modifyAlertDefine(AlertDefine alertDefine) {
|
||||
try {
|
||||
Object alertDefineService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for modifyAlertDefine: {}", subjectSum);
|
||||
|
||||
try {
|
||||
alertDefineService = SpringContextHolder.getBean("alertDefineServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'alertDefineServiceImpl'");
|
||||
}
|
||||
|
||||
assert alertDefineService != null;
|
||||
log.debug("AlertDefineService bean found: {}", alertDefineService.getClass().getSimpleName());
|
||||
|
||||
Method method = alertDefineService.getClass().getMethod("modifyAlertDefine", AlertDefine.class);
|
||||
|
||||
method.invoke(alertDefineService, alertDefine);
|
||||
|
||||
log.debug("Successfully modified alert define with ID: {}", alertDefine.getId());
|
||||
return alertDefine;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: modifyAlertDefine", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke modifyAlertDefine via adapter", e);
|
||||
throw new RuntimeException("Failed to invoke modifyAlertDefine via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the application hierarchy for a given app and language.
|
||||
* Uses reflection to call the underlying app service method.
|
||||
*
|
||||
* @param app The application name
|
||||
* @param lang The language code (optional, defaults to "en-US")
|
||||
* @return List of Hierarchy objects representing the app hierarchy
|
||||
*/
|
||||
|
||||
@Override
|
||||
public List<Hierarchy> getAppHierarchy(String app, String lang) {
|
||||
try {
|
||||
Object appService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getAppHierarchy: {}", subjectSum);
|
||||
|
||||
try {
|
||||
appService = SpringContextHolder.getBean("appServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'appServiceImpl', trying by class name");
|
||||
}
|
||||
|
||||
assert appService != null;
|
||||
log.debug("AppService bean found for getAppHierarchy: {}", appService.getClass().getSimpleName());
|
||||
|
||||
// Provide default language if not specified
|
||||
if (lang == null || lang.trim().isEmpty()) {
|
||||
lang = "en-US";
|
||||
}
|
||||
|
||||
// Call getAppHierarchy method: getAppHierarchy(String app, String lang)
|
||||
Method method = appService.getClass().getMethod("getAppHierarchy", String.class, String.class);
|
||||
|
||||
List<?> managerHierarchies = (List<?>) method.invoke(appService, app, lang);
|
||||
|
||||
// Convert manager DTOs to ai-agent DTOs
|
||||
List<Hierarchy> result = UtilityClass.convertToAgentHierarchies(managerHierarchies);
|
||||
|
||||
log.debug("Successfully retrieved and converted {} hierarchies for app '{}'", result.size(), app);
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to get app hierarchy for app '{}': {}", app, e.getMessage(), e);
|
||||
throw new RuntimeException("Failed to get app hierarchy for " + app, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-141
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.ai.agent.adapters.impl;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.adapters.AlertServiceAdapter;
|
||||
import org.apache.hertzbeat.ai.agent.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.alert.dto.AlertSummary;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Implementation of the AlertServiceAdapter interface that provides access to alert information
|
||||
* through reflection by invoking the underlying alert service implementation.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AlertServiceAdapterImpl implements AlertServiceAdapter {
|
||||
|
||||
@Override
|
||||
public Page<SingleAlert> getSingleAlerts(String status, String search, String sort, String order, int pageIndex, int pageSize) {
|
||||
try {
|
||||
Object alertService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getSingleAlerts: {}", subjectSum);
|
||||
|
||||
try {
|
||||
alertService = SpringContextHolder.getBean("alertServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'alertServiceImpl'");
|
||||
}
|
||||
|
||||
assert alertService != null;
|
||||
log.debug("AlertService bean found: {}", alertService.getClass().getSimpleName());
|
||||
|
||||
Method method = alertService.getClass().getMethod(
|
||||
"getSingleAlerts",
|
||||
String.class, String.class, String.class, String.class, int.class, int.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Page<SingleAlert> result = (Page<SingleAlert>) method.invoke(
|
||||
alertService, status, search, sort, order, pageIndex, pageSize);
|
||||
|
||||
log.debug("Successfully retrieved {} single alerts", result.getContent().size());
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getSingleAlerts", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke getSingleAlerts via adapter", e);
|
||||
throw new RuntimeException("Failed to invoke getSingleAlerts via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<GroupAlert> getGroupAlerts(String status, String search, String sort, String order, int pageIndex, int pageSize) {
|
||||
try {
|
||||
Object alertService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getGroupAlerts: {}", subjectSum);
|
||||
|
||||
try {
|
||||
alertService = SpringContextHolder.getBean("alertServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'alertServiceImpl'");
|
||||
}
|
||||
|
||||
assert alertService != null;
|
||||
log.debug("AlertService bean found: {}", alertService.getClass().getSimpleName());
|
||||
|
||||
Method method = alertService.getClass().getMethod(
|
||||
"getGroupAlerts",
|
||||
String.class, String.class, String.class, String.class, int.class, int.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Page<GroupAlert> result = (Page<GroupAlert>) method.invoke(
|
||||
alertService, status, search, sort, order, pageIndex, pageSize);
|
||||
|
||||
log.debug("Successfully retrieved {} group alerts", result.getContent().size());
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getGroupAlerts", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke getGroupAlerts via adapter", e);
|
||||
throw new RuntimeException("Failed to invoke getGroupAlerts via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertSummary getAlertsSummary() {
|
||||
try {
|
||||
Object alertService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getAlertsSummary: {}", subjectSum);
|
||||
|
||||
try {
|
||||
alertService = SpringContextHolder.getBean("alertServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'alertServiceImpl'");
|
||||
}
|
||||
|
||||
assert alertService != null;
|
||||
log.debug("AlertService bean found: {}", alertService.getClass().getSimpleName());
|
||||
|
||||
Method method = alertService.getClass().getMethod("getAlertsSummary");
|
||||
|
||||
AlertSummary result = (AlertSummary) method.invoke(alertService);
|
||||
|
||||
log.debug("Successfully retrieved alerts summary");
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getAlertsSummary", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke getAlertsSummary via adapter", e);
|
||||
throw new RuntimeException("Failed to invoke getAlertsSummary via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
-136
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.ai.agent.adapters.impl;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.adapters.MetricsServiceAdapter;
|
||||
import org.apache.hertzbeat.ai.agent.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.common.entity.dto.MetricsData;
|
||||
import org.apache.hertzbeat.common.entity.dto.MetricsHistoryData;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Implementation of the MetricsServiceAdapter interface that provides access to metrics information
|
||||
* through reflection by invoking the underlying metrics service implementation.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class MetricsServiceAdapterImpl implements MetricsServiceAdapter {
|
||||
|
||||
@Override
|
||||
public Boolean getWarehouseStorageServerStatus() {
|
||||
try {
|
||||
Object metricsDataService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getWarehouseStorageServerStatus: {}", subjectSum);
|
||||
|
||||
try {
|
||||
metricsDataService = SpringContextHolder.getBean("metricsDataServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'metricsDataServiceImpl'");
|
||||
}
|
||||
|
||||
assert metricsDataService != null;
|
||||
log.debug("MetricsDataService bean found: {}", metricsDataService.getClass().getSimpleName());
|
||||
|
||||
Method method = metricsDataService.getClass().getMethod("getWarehouseStorageServerStatus");
|
||||
|
||||
Boolean result = (Boolean) method.invoke(metricsDataService);
|
||||
|
||||
log.debug("Successfully retrieved warehouse storage server status: {}", result);
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getWarehouseStorageServerStatus", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke getWarehouseStorageServerStatus via adapter", e);
|
||||
throw new RuntimeException("Failed to invoke getWarehouseStorageServerStatus via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetricsData getMetricsData(Long monitorId, String metrics) {
|
||||
try {
|
||||
Object metricsDataService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getMetricsData: {}", subjectSum);
|
||||
|
||||
try {
|
||||
metricsDataService = SpringContextHolder.getBean("metricsDataServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'metricsDataServiceImpl'");
|
||||
}
|
||||
|
||||
assert metricsDataService != null;
|
||||
log.debug("MetricsDataService bean found: {}", metricsDataService.getClass().getSimpleName());
|
||||
|
||||
Method method = metricsDataService.getClass().getMethod(
|
||||
"getMetricsData",
|
||||
Long.class, String.class);
|
||||
|
||||
MetricsData result = (MetricsData) method.invoke(metricsDataService, monitorId, metrics);
|
||||
|
||||
log.debug("Successfully retrieved metrics data for monitor {} and metrics {}", monitorId, metrics);
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getMetricsData", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke getMetricsData via adapter for monitor {} and metrics {}", monitorId, metrics, e);
|
||||
throw new RuntimeException("Failed to invoke getMetricsData via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetricsHistoryData getMetricHistoryData(Long monitorId, String app, String metrics, String metric, String label, String history, Boolean interval) {
|
||||
try {
|
||||
Object metricsDataService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getMetricHistoryData: {}", subjectSum);
|
||||
|
||||
try {
|
||||
metricsDataService = SpringContextHolder.getBean("metricsDataServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'metricsDataServiceImpl'");
|
||||
}
|
||||
|
||||
assert metricsDataService != null;
|
||||
log.debug("MetricsDataService bean found: {}", metricsDataService.getClass().getSimpleName());
|
||||
|
||||
Method method = metricsDataService.getClass().getMethod(
|
||||
"getMetricHistoryData",
|
||||
Long.class, String.class, String.class, String.class, String.class, String.class, Boolean.class);
|
||||
|
||||
MetricsHistoryData result = (MetricsHistoryData) method.invoke(
|
||||
metricsDataService, monitorId, app, metrics, metric, label, history, interval);
|
||||
|
||||
log.debug("Successfully retrieved historical metrics data for monitor {} and metrics {}", monitorId, metrics);
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getMetricHistoryData", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke getMetricHistoryData via adapter for monitor {} and metrics {}", monitorId, metrics, e);
|
||||
throw new RuntimeException("Failed to invoke getMetricHistoryData via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
-214
@@ -1,214 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.ai.agent.adapters.impl;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.adapters.MonitorServiceAdapter;
|
||||
import org.apache.hertzbeat.ai.agent.config.McpContextHolder;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Implementation of the MonitorServiceAdapter interface that provides access to monitor information
|
||||
* through reflection by invoking the underlying monitor service implementation.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class MonitorServiceAdapterImpl implements MonitorServiceAdapter {
|
||||
|
||||
@Override
|
||||
public Page<Monitor> getMonitors(
|
||||
List<Long> ids,
|
||||
String app,
|
||||
String search,
|
||||
Byte status,
|
||||
String sort,
|
||||
String order,
|
||||
Integer pageIndex,
|
||||
Integer pageSize,
|
||||
String labels) {
|
||||
try {
|
||||
// Provide default values for all nullable parameters
|
||||
if (sort == null || sort.trim().isEmpty()) {
|
||||
sort = "gmtCreate";
|
||||
}
|
||||
if (order == null || order.trim().isEmpty()) {
|
||||
order = "desc";
|
||||
}
|
||||
if (pageIndex == null) {
|
||||
pageIndex = 0;
|
||||
}
|
||||
if (pageSize == null) {
|
||||
pageSize = 8;
|
||||
}
|
||||
|
||||
Object monitorService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject: {}", subjectSum);
|
||||
|
||||
try {
|
||||
monitorService = SpringContextHolder.getBean("monitorServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'monitorServiceImpl', trying by class name");
|
||||
}
|
||||
|
||||
assert monitorService != null;
|
||||
log.debug("MonitorService bean found: {}", monitorService.getClass().getSimpleName());
|
||||
Method method = monitorService.getClass().getMethod(
|
||||
"getMonitors",
|
||||
List.class, String.class, String.class, Byte.class,
|
||||
String.class, String.class, int.class, int.class, String.class);
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Page<Monitor> result = (Page<Monitor>) method.invoke(
|
||||
monitorService,
|
||||
ids, app, search, status, sort, order, pageIndex, pageSize, labels);
|
||||
log.debug("MonitorServiceAdapter.getMonitors result: {}", result.getContent());
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getMonitors", e);
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to invoke getMonitors via adapter", e);
|
||||
throw new RuntimeException("Failed to invoke getMonitors via adapter", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long addMonitor(Monitor monitor, List<Param> params, String collector) {
|
||||
try {
|
||||
Object monitorService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for addMonitor: {}", subjectSum);
|
||||
|
||||
try {
|
||||
monitorService = SpringContextHolder.getBean("monitorServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'monitorServiceImpl', trying by class name");
|
||||
}
|
||||
|
||||
assert monitorService != null;
|
||||
log.debug("MonitorService bean found for addMonitor: {}", monitorService.getClass().getSimpleName());
|
||||
|
||||
// Call addMonitor method: addMonitor(Monitor monitor, List<Param> params, String collector, GrafanaDashboard dashboard)
|
||||
Method method = monitorService.getClass().getMethod(
|
||||
"addMonitor",
|
||||
Monitor.class, List.class, String.class,
|
||||
Class.forName("org.apache.hertzbeat.common.entity.grafana.GrafanaDashboard"));
|
||||
|
||||
// Call the method with null dashboard
|
||||
method.invoke(monitorService, monitor, params, collector, null);
|
||||
|
||||
log.debug("Successfully added monitor: {} with ID: {}", monitor.getName(), monitor.getId());
|
||||
return monitor.getId();
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: addMonitor", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke addMonitor via adapter", e);
|
||||
throw new RuntimeException("Failed to invoke addMonitor via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAvailableMonitorTypes(String language) {
|
||||
try {
|
||||
Object appService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getAvailableMonitorTypes: {}", subjectSum);
|
||||
|
||||
try {
|
||||
appService = SpringContextHolder.getBean("appServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'appServiceImpl', trying by class name");
|
||||
}
|
||||
|
||||
assert appService != null;
|
||||
log.debug("AppService bean found for getAvailableMonitorTypes: {}", appService.getClass().getSimpleName());
|
||||
|
||||
// Provide default language if not specified
|
||||
if (language == null || language.trim().isEmpty()) {
|
||||
language = "en-US";
|
||||
}
|
||||
|
||||
// Call getI18nApps method: getI18nApps(String lang)
|
||||
Method method = appService.getClass().getMethod("getI18nApps", String.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> result = (Map<String, String>) method.invoke(appService, language);
|
||||
|
||||
log.debug("Successfully retrieved {} monitor types", result.size());
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getI18nApps", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke getI18nApps via adapter", e);
|
||||
throw new RuntimeException("Failed to invoke getI18nApps via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ParamDefine> getMonitorParamDefines(String app) {
|
||||
try {
|
||||
Object appService = null;
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getMonitorParamDefines: {}", subjectSum);
|
||||
|
||||
try {
|
||||
appService = SpringContextHolder.getBean("appServiceImpl");
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not find bean by name 'appServiceImpl', trying by class name");
|
||||
}
|
||||
|
||||
assert appService != null;
|
||||
log.debug("AppService bean found for getMonitorParamDefines: {}", appService.getClass().getSimpleName());
|
||||
|
||||
// Validate app parameter
|
||||
if (app == null || app.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("Monitor type/app parameter is required");
|
||||
}
|
||||
|
||||
// Call getAppParamDefines method: getAppParamDefines(String app)
|
||||
Method method = appService.getClass().getMethod("getAppParamDefines", String.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<ParamDefine> result = (List<ParamDefine>) method.invoke(appService, app.toLowerCase().trim());
|
||||
|
||||
log.debug("Successfully retrieved {} parameter definitions for monitor type: {}", result.size(), app);
|
||||
return result;
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Method not found: getAppParamDefines", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invoke getAppParamDefines via adapter for app: {}", app, e);
|
||||
throw new RuntimeException("Failed to invoke getAppParamDefines via adapter: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.ai.agent.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ModelProviderConfig;
|
||||
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
|
||||
import org.apache.hertzbeat.common.entity.manager.GeneralConfig;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.ai.model.ApiKey;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Dynamic LLM Provider API Key implementation that retrieves the API key
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DynamicOpenAiApiKey implements ApiKey {
|
||||
|
||||
private final GeneralConfigDao generalConfigDao;
|
||||
|
||||
public DynamicOpenAiApiKey(GeneralConfigDao generalConfigDao) {
|
||||
this.generalConfigDao = generalConfigDao;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getValue() {
|
||||
GeneralConfig providerConfig = generalConfigDao.findByType("provider");
|
||||
ModelProviderConfig modelProviderConfig = JsonUtil.fromJson(providerConfig.getContent(), ModelProviderConfig.class);
|
||||
|
||||
if (modelProviderConfig != null && modelProviderConfig.isEnable() && modelProviderConfig.isStatus()) {
|
||||
log.debug("Retrieved {} API key from configuration service", modelProviderConfig.getCode());
|
||||
return modelProviderConfig.getApiKey();
|
||||
} else {
|
||||
log.warn("No valid LLM Provider API key found in configuration");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.ai.agent.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Conversation DTO for AI chat conversations.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "Conversation information")
|
||||
public class ConversationDto {
|
||||
|
||||
@Schema(description = "Conversation ID", example = "conv-123")
|
||||
private String conversationId;
|
||||
|
||||
@Schema(description = "Creation time")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Schema(description = "Last updated time")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@Schema(description = "Messages in this conversation")
|
||||
private List<MessageDto> messages;
|
||||
|
||||
@Schema(description = "Message count")
|
||||
private Integer messageCount;
|
||||
}
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.ai.agent.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_WRITE;
|
||||
|
||||
/**
|
||||
* Hierarchical structure
|
||||
* eg: Monitoring Type metrics Information Hierarchy Relationship
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Schema(description = "Monitor Hierarchy")
|
||||
public class Hierarchy {
|
||||
|
||||
/**
|
||||
* Category value
|
||||
*/
|
||||
@Schema(description = "Category Value", example = "os", accessMode = READ_WRITE)
|
||||
String category;
|
||||
|
||||
/**
|
||||
* Attribute value
|
||||
*/
|
||||
@Schema(description = "Attribute value", example = "linux", accessMode = READ_WRITE)
|
||||
String value;
|
||||
|
||||
/**
|
||||
* Attribute internationalization tag
|
||||
*/
|
||||
@Schema(description = "Attribute internationalization tag", example = "Linux system", accessMode = READ_WRITE)
|
||||
String label;
|
||||
|
||||
/**
|
||||
* Is it a leaf node
|
||||
*/
|
||||
@Schema(description = "Is it a leaf node", example = "true", accessMode = READ_WRITE)
|
||||
Boolean isLeaf = false;
|
||||
|
||||
/**
|
||||
* Is hide this app type in main menus layout
|
||||
*/
|
||||
@Schema(description = "Is hide this app in main menus layout, only for app type, default true.", example = "true")
|
||||
Boolean hide = true;
|
||||
|
||||
/**
|
||||
* For leaf metric
|
||||
* metric type 0-number: number 1-string: string
|
||||
*/
|
||||
@Schema(description = "metric type 0-number: number 1-string: string")
|
||||
private Byte type;
|
||||
|
||||
/**
|
||||
* metric unit
|
||||
*/
|
||||
@Schema(description = "metric unit")
|
||||
private String unit;
|
||||
|
||||
/**
|
||||
* Next level of association
|
||||
*/
|
||||
@Schema(description = "Next Hierarchy", accessMode = READ_WRITE)
|
||||
private List<Hierarchy> children;
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.ai.agent.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Message DTO for chat messages.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "Chat message")
|
||||
public class MessageDto {
|
||||
|
||||
@Schema(description = "Message ID", example = "msg-123")
|
||||
private String messageId;
|
||||
|
||||
@Schema(description = "Conversation ID", example = "conv-123")
|
||||
private String conversationId;
|
||||
|
||||
@Schema(description = "Message content", example = "List all monitors")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "Message role", example = "user", allowableValues = {"user", "assistant"})
|
||||
private String role;
|
||||
|
||||
@Schema(description = "Message timestamp")
|
||||
private LocalDateTime timestamp;
|
||||
}
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.ai.agent.service;
|
||||
|
||||
/**
|
||||
* Ai Configuration Service
|
||||
*/
|
||||
public interface AiConfigService {
|
||||
|
||||
/**
|
||||
* Validate OpenAI API key by calling the OpenAI API
|
||||
* @param apiKey the API key to validate
|
||||
* @return validation result with success status and message
|
||||
*/
|
||||
ValidationResult validateApiKey(String apiKey);
|
||||
|
||||
/**
|
||||
* Validation result class
|
||||
*/
|
||||
class ValidationResult {
|
||||
private final boolean valid;
|
||||
private final String message;
|
||||
|
||||
private ValidationResult(boolean valid, String message) {
|
||||
this.valid = valid;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public static ValidationResult success(String message) {
|
||||
return new ValidationResult(true, message);
|
||||
}
|
||||
|
||||
public static ValidationResult failure(String message) {
|
||||
return new ValidationResult(false, message);
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.ai.agent.service.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.service.AiConfigService;
|
||||
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* Ai Configuration Service Implementation
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AiConfigServiceImpl implements AiConfigService {
|
||||
|
||||
private static final String OPENAI_MODELS_ENDPOINT = "https://api.openai.com/v1/models";
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
private final GeneralConfigDao generalConfigDao;
|
||||
|
||||
public AiConfigServiceImpl(GeneralConfigDao generalConfigDao, RestTemplate restTemplate) {
|
||||
this.restTemplate = restTemplate;
|
||||
this.generalConfigDao = generalConfigDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult validateApiKey(String apiKey) {
|
||||
if (!StringUtils.hasText(apiKey)) {
|
||||
return ValidationResult.failure("API key cannot be empty");
|
||||
}
|
||||
|
||||
if (!apiKey.startsWith("sk-")) {
|
||||
return ValidationResult.failure("Invalid API key format. OpenAI API keys should start with 'sk-'");
|
||||
}
|
||||
|
||||
try {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Authorization", "Bearer " + apiKey);
|
||||
headers.set("Content-Type", "application/json");
|
||||
|
||||
HttpEntity<String> entity = new HttpEntity<>(headers);
|
||||
|
||||
log.debug("Validating OpenAI API key by calling models endpoint");
|
||||
ResponseEntity<String> response = restTemplate.exchange(
|
||||
OPENAI_MODELS_ENDPOINT,
|
||||
HttpMethod.GET,
|
||||
entity,
|
||||
String.class
|
||||
);
|
||||
|
||||
if (response.getStatusCode() == HttpStatus.OK) {
|
||||
log.info("OpenAI API key validation successful");
|
||||
return ValidationResult.success("API key is valid");
|
||||
} else {
|
||||
log.warn("OpenAI API key validation failed with status: {}", response.getStatusCode());
|
||||
return ValidationResult.failure("API key validation failed: " + response.getStatusCode());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error validating OpenAI API key", e);
|
||||
String errorMessage = e.getMessage();
|
||||
|
||||
// Parse common error messages
|
||||
if (errorMessage.contains("401")) {
|
||||
return ValidationResult.failure("Invalid API key - authentication failed");
|
||||
} else if (errorMessage.contains("403")) {
|
||||
return ValidationResult.failure("API key does not have permission to access models");
|
||||
} else if (errorMessage.contains("429")) {
|
||||
return ValidationResult.failure("Rate limit exceeded - please try again later");
|
||||
} else if (errorMessage.contains("timeout") || errorMessage.contains("connect")) {
|
||||
return ValidationResult.failure("Network error - unable to connect to OpenAI API");
|
||||
} else {
|
||||
return ValidationResult.failure("API key validation failed: " + errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-272
@@ -1,272 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.ai.agent.service.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ChatRequestContext;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ChatResponseDto;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ConversationDto;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.MessageDto;
|
||||
import org.apache.hertzbeat.ai.agent.service.ChatClientProviderService;
|
||||
import org.apache.hertzbeat.ai.agent.service.ConversationService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Implementation of the ConversationService interface for managing chat conversations.
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ConversationServiceImpl implements ConversationService {
|
||||
|
||||
private final Map<String, Map<String, Object>> conversations = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<Map<String, Object>>> conversationMessages = new ConcurrentHashMap<>();
|
||||
|
||||
@Autowired
|
||||
private ChatClientProviderService chatClientProviderService;
|
||||
|
||||
@Override
|
||||
public ConversationDto createConversation() {
|
||||
String conversationId = createNewConversation();
|
||||
return getConversation(conversationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ServerSentEvent<ChatResponseDto>> streamChat(String message, String conversationId) {
|
||||
// Validate conversation exists
|
||||
if (!conversationExists(conversationId)) {
|
||||
ChatResponseDto errorResponse = ChatResponseDto.builder()
|
||||
.conversationId(conversationId)
|
||||
.response("Error: Conversation not found: " + conversationId)
|
||||
.build();
|
||||
return Flux.just(ServerSentEvent.builder(errorResponse)
|
||||
.event("error")
|
||||
.build());
|
||||
}
|
||||
|
||||
// Check if OpenAI is properly configured
|
||||
if (!chatClientProviderService.isConfigured()) {
|
||||
ChatResponseDto errorResponse = ChatResponseDto.builder()
|
||||
.conversationId(conversationId)
|
||||
.response("Provider is not configured. Please configure your AI Provider.")
|
||||
.build();
|
||||
return Flux.just(ServerSentEvent.builder(errorResponse)
|
||||
.event("error")
|
||||
.build());
|
||||
}
|
||||
|
||||
log.info("Starting streaming conversation: {}", conversationId);
|
||||
|
||||
// Add user message to conversation
|
||||
String userMessageId = addMessageToConversation(conversationId, message, "user");
|
||||
|
||||
// Get conversation history for context
|
||||
List<Map<String, Object>> messagesList = conversationMessages.get(conversationId);
|
||||
List<MessageDto> conversationHistory = new ArrayList<>();
|
||||
|
||||
if (messagesList != null && messagesList.size() > 1) {
|
||||
// Get all messages except the last one (which is the current user message we just added)
|
||||
for (int i = 0; i < messagesList.size() - 1; i++) {
|
||||
Map<String, Object> msgMap = messagesList.get(i);
|
||||
conversationHistory.add(mapToMessageDto(msgMap));
|
||||
}
|
||||
}
|
||||
|
||||
ChatRequestContext context = ChatRequestContext.builder()
|
||||
.message(message)
|
||||
.conversationId(conversationId)
|
||||
.conversationHistory(conversationHistory)
|
||||
.build();
|
||||
|
||||
// Stream response from AI service
|
||||
StringBuilder fullResponse = new StringBuilder();
|
||||
return chatClientProviderService.streamChat(context)
|
||||
.map(chunk -> {
|
||||
fullResponse.append(chunk);
|
||||
ChatResponseDto responseDto = ChatResponseDto.builder()
|
||||
.conversationId(conversationId)
|
||||
.response(chunk)
|
||||
.userMessageId(userMessageId)
|
||||
.build();
|
||||
|
||||
return ServerSentEvent.builder(responseDto)
|
||||
.event("message")
|
||||
.build();
|
||||
})
|
||||
.concatWith(Flux.defer(() -> {
|
||||
// Add the complete AI response to conversation
|
||||
String assistantMessageId = addMessageToConversation(conversationId, fullResponse.toString(), "assistant");
|
||||
|
||||
ChatResponseDto finalResponse = ChatResponseDto.builder()
|
||||
.conversationId(conversationId)
|
||||
.response("")
|
||||
.userMessageId(userMessageId)
|
||||
.assistantMessageId(assistantMessageId)
|
||||
.build();
|
||||
|
||||
return Flux.just(ServerSentEvent.builder(finalResponse)
|
||||
.event("complete")
|
||||
.build());
|
||||
}))
|
||||
.doOnComplete(() -> log.info("Streaming completed for conversation: {}", conversationId))
|
||||
.doOnError(error -> log.error("Error in streaming chat for conversation {}: {}", conversationId, error.getMessage(), error))
|
||||
.onErrorResume(error -> {
|
||||
ChatResponseDto errorResponse = ChatResponseDto.builder()
|
||||
.conversationId(conversationId)
|
||||
.response("An error occurred: " + error.getMessage())
|
||||
.userMessageId(userMessageId)
|
||||
.build();
|
||||
return Flux.just(ServerSentEvent.builder(errorResponse)
|
||||
.event("error")
|
||||
.build());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConversationDto getConversation(String conversationId) {
|
||||
if (conversationId == null || conversationId.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, Object> conversation = conversations.get(conversationId);
|
||||
if (conversation == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Map<String, Object>> messagesList = conversationMessages.get(conversationId);
|
||||
List<MessageDto> messages = messagesList != null
|
||||
? messagesList.stream().map(this::mapToMessageDto).collect(Collectors.toList()) :
|
||||
new ArrayList<>();
|
||||
|
||||
return ConversationDto.builder()
|
||||
.conversationId((String) conversation.get("conversationId"))
|
||||
.createdAt((LocalDateTime) conversation.get("createdAt"))
|
||||
.updatedAt((LocalDateTime) conversation.get("updatedAt"))
|
||||
.messages(messages)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ConversationDto> getAllConversations() {
|
||||
List<ConversationDto> result = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, Map<String, Object>> entry : conversations.entrySet()) {
|
||||
Map<String, Object> conv = entry.getValue();
|
||||
List<Map<String, Object>> messages = conversationMessages.get(entry.getKey());
|
||||
|
||||
ConversationDto dto = ConversationDto.builder()
|
||||
.conversationId((String) conv.get("conversationId"))
|
||||
.createdAt((LocalDateTime) conv.get("createdAt"))
|
||||
.updatedAt((LocalDateTime) conv.get("updatedAt"))
|
||||
.messages(new ArrayList<>()) // Don't include messages in list view for performance
|
||||
.build();
|
||||
result.add(dto);
|
||||
}
|
||||
|
||||
result.sort((a, b) -> b.getUpdatedAt().compareTo(a.getUpdatedAt()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteConversation(String conversationId) {
|
||||
if (conversationId == null || conversationId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean existed = conversations.containsKey(conversationId);
|
||||
if (existed) {
|
||||
conversations.remove(conversationId);
|
||||
conversationMessages.remove(conversationId);
|
||||
log.info("Deleted conversation: {}", conversationId);
|
||||
}
|
||||
return existed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean conversationExists(String conversationId) {
|
||||
return conversationId != null && !conversationId.isEmpty() && conversations.containsKey(conversationId);
|
||||
}
|
||||
|
||||
private String createNewConversation() {
|
||||
String conversationId = "conv-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
Map<String, Object> conversation = new HashMap<>();
|
||||
conversation.put("conversationId", conversationId);
|
||||
conversation.put("createdAt", now);
|
||||
conversation.put("updatedAt", now);
|
||||
|
||||
conversations.put(conversationId, conversation);
|
||||
conversationMessages.put(conversationId, new ArrayList<>());
|
||||
|
||||
log.info("Created new conversation: {}", conversationId);
|
||||
return conversationId;
|
||||
}
|
||||
|
||||
private MessageDto mapToMessageDto(Map<String, Object> messageMap) {
|
||||
return MessageDto.builder()
|
||||
.messageId((String) messageMap.get("messageId"))
|
||||
.conversationId((String) messageMap.get("conversationId"))
|
||||
.content((String) messageMap.get("content"))
|
||||
.role((String) messageMap.get("role"))
|
||||
.timestamp((LocalDateTime) messageMap.get("timestamp"))
|
||||
.build();
|
||||
}
|
||||
|
||||
private String addMessageToConversation(String conversationId, String content, String role) {
|
||||
List<Map<String, Object>> messages = conversationMessages.computeIfAbsent(conversationId, k -> new ArrayList<>());
|
||||
|
||||
String messageId = "msg-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
Map<String, Object> message = new HashMap<>();
|
||||
message.put("messageId", messageId);
|
||||
message.put("conversationId", conversationId);
|
||||
message.put("content", content);
|
||||
message.put("role", role);
|
||||
message.put("timestamp", LocalDateTime.now());
|
||||
|
||||
messages.add(message);
|
||||
|
||||
// Update conversation timestamp
|
||||
Map<String, Object> conversation = conversations.get(conversationId);
|
||||
if (conversation != null) {
|
||||
conversation.put("updatedAt", LocalDateTime.now());
|
||||
// Auto-generate title from first user message
|
||||
if ("user".equals(role) && messages.stream().filter(m -> "user".equals(m.get("role"))).count() == 1) {
|
||||
String title = content.length() > 30 ? content.substring(0, 27) + "..." : content;
|
||||
conversation.put("title", title);
|
||||
}
|
||||
}
|
||||
|
||||
return messageId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
<artifactId>hertzbeat</artifactId>
|
||||
<version>2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>hertzbeat-ai-agent</artifactId>
|
||||
<artifactId>hertzbeat-ai</artifactId>
|
||||
<version>${hertzbeat.version}</version>
|
||||
<properties>
|
||||
<spring-ai.version>1.0.3</spring-ai.version>
|
||||
@@ -65,12 +65,15 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-base</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-alerter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-manager</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.usthe.sureness</groupId>
|
||||
<artifactId>spring-boot3-starter-sureness</artifactId>
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.config;
|
||||
package org.apache.hertzbeat.ai.config;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
+3
-8
@@ -16,11 +16,11 @@
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.config;
|
||||
package org.apache.hertzbeat.ai.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.support.event.AiProviderConfigChangeEvent;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ModelProviderConfig;
|
||||
import org.apache.hertzbeat.common.entity.dto.ModelProviderConfig;
|
||||
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
|
||||
import org.apache.hertzbeat.common.entity.manager.GeneralConfig;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
@@ -72,12 +72,7 @@ public class LlmConfig {
|
||||
}
|
||||
ModelProviderConfig modelProviderConfig = JsonUtil.fromJson(providerConfig.getContent(), ModelProviderConfig.class);
|
||||
|
||||
if (!modelProviderConfig.isEnable() || !modelProviderConfig.isStatus()) {
|
||||
log.warn("LLM Provider is not enabled or status is not valid, ChatClient bean will not be created");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (modelProviderConfig.getApiKey() == null) {
|
||||
if (modelProviderConfig == null || modelProviderConfig.getApiKey() == null) {
|
||||
log.warn("LLM Provider configuration is incomplete, ChatClient bean will not be created");
|
||||
return null;
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.config;
|
||||
package org.apache.hertzbeat.ai.config;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import org.springframework.core.NamedInheritableThreadLocal;
|
||||
+2
-2
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.config;
|
||||
package org.apache.hertzbeat.ai.config;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -179,4 +179,4 @@ public class PromptProvider {
|
||||
Keep responses focused on monitoring topics and HertzBeat's comprehensive capabilities.
|
||||
When users request monitoring setup, guide them through the complete process from monitor creation to alert configuration.
|
||||
""";
|
||||
}
|
||||
}
|
||||
+32
-74
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.controller;
|
||||
package org.apache.hertzbeat.ai.controller;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import com.usthe.sureness.util.SurenessContextHolder;
|
||||
@@ -23,11 +23,11 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ChatRequestContext;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ChatResponseDto;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ConversationDto;
|
||||
import org.apache.hertzbeat.ai.agent.service.ConversationService;
|
||||
import org.apache.hertzbeat.ai.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.ai.pojo.dto.ChatRequestContext;
|
||||
import org.apache.hertzbeat.ai.pojo.dto.ChatResponseChunk;
|
||||
import org.apache.hertzbeat.ai.service.ConversationService;
|
||||
import org.apache.hertzbeat.common.entity.ai.ChatConversation;
|
||||
import org.apache.hertzbeat.common.entity.dto.Message;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -63,23 +63,6 @@ public class ChatController {
|
||||
this.conversationService = conversationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new conversation
|
||||
*
|
||||
* @return Created conversation details
|
||||
*/
|
||||
@PostMapping(path = "/conversations")
|
||||
@Operation(summary = "Create a new conversation", description = "Create a new conversation")
|
||||
public ResponseEntity<Message<ConversationDto>> createConversation() {
|
||||
try {
|
||||
ConversationDto conversation = conversationService.createConversation();
|
||||
return ResponseEntity.ok(Message.success(conversation));
|
||||
} catch (Exception e) {
|
||||
log.error("Error creating conversation: ", e);
|
||||
return ResponseEntity.ok(Message.fail((byte) -1, "Failed to create conversation"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message and get a streaming response with conversation tracking
|
||||
*
|
||||
@@ -88,14 +71,13 @@ public class ChatController {
|
||||
*/
|
||||
@PostMapping(value = "/stream", produces = TEXT_EVENT_STREAM_VALUE)
|
||||
@Operation(summary = "Send a chat message with streaming response", description = "Send a message to AI and get a streaming response with conversation tracking")
|
||||
public Flux<ServerSentEvent<ChatResponseDto>> streamChat(@Valid @RequestBody ChatRequestContext context) {
|
||||
public Flux<ServerSentEvent<ChatResponseChunk>> streamChat(@Valid @RequestBody ChatRequestContext context) {
|
||||
try {
|
||||
// Validate message is not empty
|
||||
SubjectSum subject = SurenessContextHolder.getBindSubject();
|
||||
log.info(subject.toString());
|
||||
McpContextHolder.setSubject(subject);
|
||||
if (context.getMessage() == null || context.getMessage().trim().isEmpty()) {
|
||||
ChatResponseDto errorResponse = ChatResponseDto.builder()
|
||||
ChatResponseChunk errorResponse = ChatResponseChunk.builder()
|
||||
.conversationId(context.getConversationId())
|
||||
.response("Error: Message cannot be empty")
|
||||
.build();
|
||||
@@ -109,7 +91,7 @@ public class ChatController {
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error in stream chat endpoint: ", e);
|
||||
ChatResponseDto errorResponse = ChatResponseDto.builder()
|
||||
ChatResponseChunk errorResponse = ChatResponseChunk.builder()
|
||||
.conversationId(context.getConversationId())
|
||||
.response("An error occurred: " + e.getMessage())
|
||||
.build();
|
||||
@@ -119,6 +101,18 @@ public class ChatController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new conversation
|
||||
*
|
||||
* @return Created conversation details
|
||||
*/
|
||||
@PostMapping(path = "/conversations")
|
||||
@Operation(summary = "Create a new conversation", description = "Create a new conversation")
|
||||
public ResponseEntity<Message<ChatConversation>> createConversation() {
|
||||
ChatConversation conversation = conversationService.createConversation();
|
||||
return ResponseEntity.ok(Message.success(conversation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all conversations
|
||||
*
|
||||
@@ -126,14 +120,9 @@ public class ChatController {
|
||||
*/
|
||||
@GetMapping(path = "/conversations")
|
||||
@Operation(summary = "List all conversations", description = "Get a list of all conversations")
|
||||
public ResponseEntity<Message<List<ConversationDto>>> listConversations() {
|
||||
try {
|
||||
List<ConversationDto> conversations = conversationService.getAllConversations();
|
||||
return ResponseEntity.ok(Message.success(conversations));
|
||||
} catch (Exception e) {
|
||||
log.error("Error listing conversations: ", e);
|
||||
return ResponseEntity.ok(Message.fail((byte) -1, "Failed to retrieve conversations"));
|
||||
}
|
||||
public ResponseEntity<Message<List<ChatConversation>>> listConversations() {
|
||||
List<ChatConversation> conversations = conversationService.getAllConversations();
|
||||
return ResponseEntity.ok(Message.success(conversations));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,26 +133,10 @@ public class ChatController {
|
||||
*/
|
||||
@GetMapping(path = "/conversations/{conversationId}")
|
||||
@Operation(summary = "Get conversation history", description = "Get detailed information and message history for a specific conversation")
|
||||
public ResponseEntity<Message<ConversationDto>> getConversation(
|
||||
@Parameter(description = "Conversation ID", example = "conv-12345678") @PathVariable("conversationId") String conversationId) {
|
||||
try {
|
||||
// Validate conversation ID
|
||||
if (conversationId == null || conversationId.trim().isEmpty()) {
|
||||
return ResponseEntity.ok(Message.fail((byte) -1, "Conversation ID is required"));
|
||||
}
|
||||
|
||||
ConversationDto conversation = conversationService.getConversation(conversationId);
|
||||
|
||||
if (conversation == null) {
|
||||
return ResponseEntity.ok(Message.fail((byte) -1, "Conversation not found: " + conversationId));
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(Message.success(conversation));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error getting conversation: ", e);
|
||||
return ResponseEntity.ok(Message.fail((byte) -1, "Failed to retrieve conversation"));
|
||||
}
|
||||
public ResponseEntity<Message<ChatConversation>> getConversation(
|
||||
@Parameter(description = "Conversation ID", example = "12345678") @PathVariable(value = "conversationId") Long conversationId) {
|
||||
ChatConversation conversation = conversationService.getConversation(conversationId);
|
||||
return ResponseEntity.ok(Message.success(conversation));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,23 +148,8 @@ public class ChatController {
|
||||
@DeleteMapping(path = "/conversations/{conversationId}")
|
||||
@Operation(summary = "Delete conversation", description = "Delete a specific conversation and all its messages")
|
||||
public ResponseEntity<Message<Void>> deleteConversation(
|
||||
@Parameter(description = "Conversation ID", example = "conv-12345678") @PathVariable("conversationId") String conversationId) {
|
||||
try {
|
||||
// Validate conversation ID
|
||||
if (conversationId == null || conversationId.trim().isEmpty()) {
|
||||
return ResponseEntity.ok(Message.fail((byte) -1, "Conversation ID is required"));
|
||||
}
|
||||
|
||||
boolean deleted = conversationService.deleteConversation(conversationId);
|
||||
if (!deleted) {
|
||||
return ResponseEntity.ok(Message.fail((byte) -1, "Conversation not found: " + conversationId));
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(Message.success("Conversation deleted successfully"));
|
||||
} catch (Exception e) {
|
||||
log.error("Error deleting conversation: ", e);
|
||||
return ResponseEntity.ok(Message.fail((byte) -1, "Failed to delete conversation"));
|
||||
}
|
||||
|
||||
@Parameter(description = "Conversation ID", example = "2345678") @PathVariable("conversationId") Long conversationId) {
|
||||
conversationService.deleteConversation(conversationId);
|
||||
return ResponseEntity.ok(Message.success());
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-5
@@ -15,13 +15,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.service;
|
||||
package org.apache.hertzbeat.ai.dao;
|
||||
|
||||
import org.apache.hertzbeat.manager.service.ai.SparkDeskAiServiceImpl;
|
||||
import org.apache.hertzbeat.common.entity.ai.ChatConversation;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* test case for {@link SparkDeskAiServiceImpl}
|
||||
* Repository interface for Conversation entities
|
||||
*/
|
||||
|
||||
class SparkDeskAiServiceTest {
|
||||
@Repository
|
||||
public interface ChatConversationDao extends JpaRepository<ChatConversation, Long> {
|
||||
}
|
||||
+8
-5
@@ -15,13 +15,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.service;
|
||||
package org.apache.hertzbeat.ai.dao;
|
||||
|
||||
import org.apache.hertzbeat.manager.service.ai.AlibabaAiServiceImpl;
|
||||
import org.apache.hertzbeat.common.entity.ai.ChatMessage;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* test case for {@link AlibabaAiServiceImpl}
|
||||
* Repository interface for Message entities
|
||||
*/
|
||||
|
||||
class AlibabaAiServiceTest {
|
||||
@Repository
|
||||
public interface ChatMessageDao extends JpaRepository<ChatMessage, Long> {
|
||||
|
||||
}
|
||||
+5
-4
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.pojo.dto;
|
||||
package org.apache.hertzbeat.ai.pojo.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
@@ -24,6 +24,7 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.hertzbeat.common.entity.ai.ChatMessage;
|
||||
|
||||
/**
|
||||
* Chat request context for AI chat endpoint.
|
||||
@@ -40,10 +41,10 @@ public class ChatRequestContext {
|
||||
/**
|
||||
* Optional conversation ID for context
|
||||
*/
|
||||
private String conversationId;
|
||||
private Long conversationId;
|
||||
|
||||
/**
|
||||
* Conversation history messages for context
|
||||
*/
|
||||
private List<MessageDto> conversationHistory;
|
||||
}
|
||||
private List<ChatMessage> conversationHistory;
|
||||
}
|
||||
+10
-9
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.pojo.dto;
|
||||
package org.apache.hertzbeat.ai.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -31,17 +31,18 @@ import lombok.NoArgsConstructor;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "Chat response")
|
||||
public class ChatResponseDto {
|
||||
public class ChatResponseChunk {
|
||||
|
||||
@Schema(description = "Conversation ID", example = "conv-123")
|
||||
private String conversationId;
|
||||
@Schema(description = "Conversation ID", example = "123")
|
||||
private Long conversationId;
|
||||
|
||||
@Schema(description = "AI response message", example = "Here are your monitors...")
|
||||
private String response;
|
||||
|
||||
@Schema(description = "User message ID", example = "msg-user-123")
|
||||
private String userMessageId;
|
||||
@Schema(description = "User message ID", example = "123")
|
||||
private Long userMessageId;
|
||||
|
||||
@Schema(description = "Assistant message ID", example = "123")
|
||||
private Long assistantMessageId;
|
||||
|
||||
@Schema(description = "Assistant message ID", example = "msg-assistant-123")
|
||||
private String assistantMessageId;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -16,9 +16,9 @@
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.service;
|
||||
package org.apache.hertzbeat.ai.service;
|
||||
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ChatRequestContext;
|
||||
import org.apache.hertzbeat.ai.pojo.dto.ChatRequestContext;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
+14
-24
@@ -16,10 +16,10 @@
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.service;
|
||||
package org.apache.hertzbeat.ai.service;
|
||||
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ChatResponseDto;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ConversationDto;
|
||||
import org.apache.hertzbeat.ai.pojo.dto.ChatResponseChunk;
|
||||
import org.apache.hertzbeat.common.entity.ai.ChatConversation;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
@@ -30,13 +30,6 @@ import java.util.List;
|
||||
*/
|
||||
public interface ConversationService {
|
||||
|
||||
/**
|
||||
* Create a new conversation
|
||||
*
|
||||
* @return Created conversation data
|
||||
*/
|
||||
ConversationDto createConversation();
|
||||
|
||||
/**
|
||||
* Send a message and receive a streaming response
|
||||
*
|
||||
@@ -44,8 +37,14 @@ public interface ConversationService {
|
||||
* @param conversationId Optional conversation ID for continuing a chat
|
||||
* @return Flux of ServerSentEvent for streaming the response
|
||||
*/
|
||||
Flux<ServerSentEvent<ChatResponseDto>> streamChat(String message, String conversationId);
|
||||
Flux<ServerSentEvent<ChatResponseChunk>> streamChat(String message, Long conversationId);
|
||||
|
||||
/**
|
||||
* Create a new conversation
|
||||
*
|
||||
* @return Created conversation data
|
||||
*/
|
||||
ChatConversation createConversation();
|
||||
|
||||
/**
|
||||
* Get conversation history for a specific conversation
|
||||
@@ -53,28 +52,19 @@ public interface ConversationService {
|
||||
* @param conversationId Conversation ID
|
||||
* @return Conversation data including messages
|
||||
*/
|
||||
ConversationDto getConversation(String conversationId);
|
||||
ChatConversation getConversation(Long conversationId);
|
||||
|
||||
/**
|
||||
* Get all conversations for the current user
|
||||
*
|
||||
* @return List of conversations
|
||||
*/
|
||||
List<ConversationDto> getAllConversations();
|
||||
List<ChatConversation> getAllConversations();
|
||||
|
||||
/**
|
||||
* Delete a conversation
|
||||
*
|
||||
* @param conversationId Conversation ID to delete
|
||||
* @return true if deleted, false if conversation not found
|
||||
*/
|
||||
boolean deleteConversation(String conversationId);
|
||||
|
||||
/**
|
||||
* Check if a conversation exists
|
||||
*
|
||||
* @param conversationId Conversation ID to check
|
||||
* @return true if conversation exists, false otherwise
|
||||
*/
|
||||
boolean conversationExists(String conversationId);
|
||||
}
|
||||
void deleteConversation(Long conversationId);
|
||||
}
|
||||
+2
-2
@@ -16,10 +16,10 @@
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.service;
|
||||
package org.apache.hertzbeat.ai.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.hertzbeat.ai.agent.config.CustomSseServerTransport;
|
||||
import org.apache.hertzbeat.ai.config.CustomSseServerTransport;
|
||||
import org.springframework.ai.mcp.server.autoconfigure.McpServerProperties;
|
||||
import org.springframework.ai.tool.ToolCallbackProvider;
|
||||
import org.springframework.web.servlet.function.RouterFunction;
|
||||
+8
-16
@@ -16,18 +16,18 @@
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.service.impl;
|
||||
package org.apache.hertzbeat.ai.service.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.config.PromptProvider;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.MessageDto;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ModelProviderConfig;
|
||||
import org.apache.hertzbeat.ai.agent.service.ChatClientProviderService;
|
||||
import org.apache.hertzbeat.ai.config.PromptProvider;
|
||||
import org.apache.hertzbeat.common.entity.ai.ChatMessage;
|
||||
import org.apache.hertzbeat.common.entity.dto.ModelProviderConfig;
|
||||
import org.apache.hertzbeat.ai.service.ChatClientProviderService;
|
||||
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
|
||||
import org.apache.hertzbeat.common.entity.manager.GeneralConfig;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.ChatRequestContext;
|
||||
import org.apache.hertzbeat.ai.pojo.dto.ChatRequestContext;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
@@ -66,14 +66,6 @@ public class ChatClientProviderServiceImpl implements ChatClientProviderService
|
||||
this.generalConfigDao = generalConfigDao;
|
||||
}
|
||||
|
||||
public String complete(String message) {
|
||||
ChatClient chatClient = applicationContext.getBean("openAiChatClient", ChatClient.class);
|
||||
return chatClient.prompt()
|
||||
.user(message)
|
||||
.call()
|
||||
.content();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<String> streamChat(ChatRequestContext context) {
|
||||
try {
|
||||
@@ -84,7 +76,7 @@ public class ChatClientProviderServiceImpl implements ChatClientProviderService
|
||||
|
||||
// Add conversation history if available
|
||||
if (context.getConversationHistory() != null && !context.getConversationHistory().isEmpty()) {
|
||||
for (MessageDto historyMessage : context.getConversationHistory()) {
|
||||
for (ChatMessage historyMessage : context.getConversationHistory()) {
|
||||
if ("user".equals(historyMessage.getRole())) {
|
||||
messages.add(new UserMessage(historyMessage.getContent()));
|
||||
} else if ("assistant".equals(historyMessage.getRole())) {
|
||||
@@ -117,7 +109,7 @@ public class ChatClientProviderServiceImpl implements ChatClientProviderService
|
||||
if (!isConfigured) {
|
||||
GeneralConfig providerConfig = generalConfigDao.findByType("provider");
|
||||
ModelProviderConfig modelProviderConfig = JsonUtil.fromJson(providerConfig.getContent(), ModelProviderConfig.class);
|
||||
isConfigured = modelProviderConfig != null && modelProviderConfig.isStatus();
|
||||
isConfigured = modelProviderConfig != null && modelProviderConfig.getApiKey() != null;
|
||||
}
|
||||
return isConfigured;
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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.hertzbeat.ai.service.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.dao.ChatConversationDao;
|
||||
import org.apache.hertzbeat.ai.dao.ChatMessageDao;
|
||||
import org.apache.hertzbeat.ai.pojo.dto.ChatRequestContext;
|
||||
import org.apache.hertzbeat.ai.pojo.dto.ChatResponseChunk;
|
||||
import org.apache.hertzbeat.ai.service.ChatClientProviderService;
|
||||
import org.apache.hertzbeat.ai.service.ConversationService;
|
||||
import org.apache.hertzbeat.common.entity.ai.ChatConversation;
|
||||
import org.apache.hertzbeat.common.entity.ai.ChatMessage;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Implementation of the ConversationService interface for managing chat conversations.
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ConversationServiceImpl implements ConversationService {
|
||||
|
||||
@Autowired
|
||||
private ChatConversationDao conversationDao;
|
||||
|
||||
@Autowired
|
||||
private ChatMessageDao messageDao;
|
||||
|
||||
@Autowired
|
||||
private ChatClientProviderService chatClientProviderService;
|
||||
|
||||
@Override
|
||||
public Flux<ServerSentEvent<ChatResponseChunk>> streamChat(String message, Long conversationId) {
|
||||
|
||||
// Check if provider is properly configured
|
||||
if (!chatClientProviderService.isConfigured()) {
|
||||
ChatResponseChunk errorResponse = ChatResponseChunk.builder()
|
||||
.conversationId(conversationId)
|
||||
.response("Provider is not configured. Please configure your AI Provider.")
|
||||
.build();
|
||||
return Flux.just(ServerSentEvent.builder(errorResponse)
|
||||
.event("error")
|
||||
.build());
|
||||
}
|
||||
|
||||
log.info("Starting streaming conversation: {}", conversationId);
|
||||
|
||||
// Add user message to conversation
|
||||
ChatMessage chatMessage = ChatMessage.builder().conversationId(conversationId)
|
||||
.content(message).role("user").build();
|
||||
chatMessage = messageDao.save(chatMessage);
|
||||
ChatConversation conversation = conversationDao.getReferenceById(conversationId);
|
||||
if (conversation.getTitle().startsWith("conversation")) {
|
||||
// Auto-generate title from first user message
|
||||
String title = message.length() > 30 ? message.substring(0, 27) + "..." : message;
|
||||
conversation.setTitle(title);
|
||||
conversationDao.save(conversation);
|
||||
}
|
||||
|
||||
ChatRequestContext context = ChatRequestContext.builder()
|
||||
.message(message)
|
||||
.conversationId(conversationId)
|
||||
.conversationHistory(CollectionUtils.isEmpty(conversation.getMessages()) ? null
|
||||
: conversation.getMessages().subList(0, conversation.getMessages().size() - 1))
|
||||
.build();
|
||||
|
||||
// Stream response from AI service
|
||||
StringBuilder fullResponse = new StringBuilder();
|
||||
ChatMessage finalChatMessage = chatMessage;
|
||||
return chatClientProviderService.streamChat(context)
|
||||
.map(chunk -> {
|
||||
fullResponse.append(chunk);
|
||||
ChatResponseChunk responseChunk = ChatResponseChunk.builder()
|
||||
.conversationId(conversationId)
|
||||
.userMessageId(finalChatMessage.getId())
|
||||
.response(chunk)
|
||||
.build();
|
||||
|
||||
return ServerSentEvent.builder(responseChunk)
|
||||
.event("message")
|
||||
.build();
|
||||
})
|
||||
.concatWith(Flux.defer(() -> {
|
||||
// Add the complete AI response to conversation
|
||||
ChatMessage assistantMessage = ChatMessage.builder().conversationId(conversationId)
|
||||
.content(fullResponse.toString()).role("assistant").build();
|
||||
assistantMessage = messageDao.save(assistantMessage);
|
||||
|
||||
ChatResponseChunk finalResponse = ChatResponseChunk.builder()
|
||||
.conversationId(conversationId)
|
||||
.response("")
|
||||
.assistantMessageId(assistantMessage.getId())
|
||||
.build();
|
||||
|
||||
return Flux.just(ServerSentEvent.builder(finalResponse)
|
||||
.event("complete")
|
||||
.build());
|
||||
}))
|
||||
.doOnComplete(() -> log.info("Streaming completed for conversation: {}", conversationId))
|
||||
.doOnError(error -> log.error("Error in streaming chat for conversation {}: {}", conversationId, error.getMessage(), error))
|
||||
.onErrorResume(error -> {
|
||||
ChatResponseChunk errorResponse = ChatResponseChunk.builder()
|
||||
.conversationId(conversationId)
|
||||
.response("An error occurred: " + error.getMessage())
|
||||
.userMessageId(finalChatMessage.getId())
|
||||
.build();
|
||||
return Flux.just(ServerSentEvent.builder(errorResponse)
|
||||
.event("error")
|
||||
.build());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatConversation createConversation() {
|
||||
ChatConversation conversation = new ChatConversation();
|
||||
conversation.setTitle("conversation-" + UUID.randomUUID().toString().substring(0, 4));
|
||||
return conversationDao.save(conversation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatConversation getConversation(Long conversationId) {
|
||||
if (conversationId == null) {
|
||||
return null;
|
||||
}
|
||||
return conversationDao.getReferenceById(conversationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ChatConversation> getAllConversations() {
|
||||
return conversationDao.findAll(Sort.by(Sort.Direction.DESC, "id"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteConversation(Long conversationId) {
|
||||
conversationDao.deleteById(conversationId);
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -16,14 +16,14 @@
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.service.impl;
|
||||
package org.apache.hertzbeat.ai.service.impl;
|
||||
|
||||
import org.apache.hertzbeat.ai.agent.config.CustomSseServerTransport;
|
||||
import org.apache.hertzbeat.ai.agent.service.McpServerService;
|
||||
import org.apache.hertzbeat.ai.agent.tools.AlertDefineTools;
|
||||
import org.apache.hertzbeat.ai.agent.tools.AlertTools;
|
||||
import org.apache.hertzbeat.ai.agent.tools.MetricsTools;
|
||||
import org.apache.hertzbeat.ai.agent.tools.MonitorTools;
|
||||
import org.apache.hertzbeat.ai.config.CustomSseServerTransport;
|
||||
import org.apache.hertzbeat.ai.service.McpServerService;
|
||||
import org.apache.hertzbeat.ai.tools.AlertDefineTools;
|
||||
import org.apache.hertzbeat.ai.tools.AlertTools;
|
||||
import org.apache.hertzbeat.ai.tools.MetricsTools;
|
||||
import org.apache.hertzbeat.ai.tools.MonitorTools;
|
||||
import org.springframework.ai.mcp.server.autoconfigure.McpServerProperties;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
+2
-2
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.tools;
|
||||
package org.apache.hertzbeat.ai.tools;
|
||||
|
||||
/**
|
||||
* Tools for alert definition and threshold configuration operations
|
||||
@@ -88,4 +88,4 @@ public interface AlertDefineTools {
|
||||
* @return Result message indicating success or failure
|
||||
*/
|
||||
String bindMonitorsToAlertRule(Long ruleId, String monitorIds);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.tools;
|
||||
package org.apache.hertzbeat.ai.tools;
|
||||
|
||||
/**
|
||||
* Tools for alert operations and alarm data queries
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.tools;
|
||||
package org.apache.hertzbeat.ai.tools;
|
||||
|
||||
/**
|
||||
* Tools for metrics data operations and queries
|
||||
+2
-2
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.tools;
|
||||
package org.apache.hertzbeat.ai.tools;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -94,4 +94,4 @@ public interface MonitorTools {
|
||||
* @return Formatted string with parameter definitions including field names, types, and requirements
|
||||
*/
|
||||
String getMonitorAdditionalParams(String app);
|
||||
}
|
||||
}
|
||||
+54
-24
@@ -15,19 +15,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.tools.impl;
|
||||
package org.apache.hertzbeat.ai.tools.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.adapters.AlertDefineServiceAdapter;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.Hierarchy;
|
||||
import org.apache.hertzbeat.ai.agent.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.ai.agent.tools.AlertDefineTools;
|
||||
import org.apache.hertzbeat.ai.agent.utils.UtilityClass;
|
||||
import org.apache.hertzbeat.ai.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.alert.service.AlertDefineService;
|
||||
import org.apache.hertzbeat.manager.service.AppService;
|
||||
import org.apache.hertzbeat.ai.tools.AlertDefineTools;
|
||||
import org.apache.hertzbeat.ai.utils.UtilityClass;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.Hierarchy;
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.ai.tool.annotation.ToolParam;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -46,7 +47,9 @@ import java.util.Map;
|
||||
@Service
|
||||
public class AlertDefineToolsImpl implements AlertDefineTools {
|
||||
@Autowired
|
||||
private AlertDefineServiceAdapter alertDefineServiceAdapter;
|
||||
private AlertDefineService alertDefineService;
|
||||
@Autowired
|
||||
private AppService appService;
|
||||
|
||||
|
||||
@Override
|
||||
@@ -240,17 +243,19 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
|
||||
.datasource(datasource)
|
||||
.enable(enable)
|
||||
.build();
|
||||
|
||||
log.debug("Current security subject for addAlertDefine: {}", subjectSum);
|
||||
|
||||
AlertDefine createdAlertDefine = alertDefineServiceAdapter.addAlertDefine(alertDefine);
|
||||
alertDefineService.addAlertDefine(alertDefine);
|
||||
|
||||
// Note: Monitor binding is handled separately via bind_monitors_to_alert_rule tool
|
||||
String bindingNote = String.format(" (Use bind_monitors_to_alert_rule tool to associate specific monitors)");
|
||||
|
||||
log.info("Successfully created alert rule '{}' with ID: {}", name, createdAlertDefine.getId());
|
||||
log.info("Successfully created alert rule '{}' with ID: {}", name, alertDefine.getId());
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append(String.format("Successfully created %s alert rule '%s' with ID: %d\n",
|
||||
type, name, createdAlertDefine.getId()));
|
||||
type, name, alertDefine.getId()));
|
||||
response.append(String.format("Expression: %s\n", expr));
|
||||
response.append(String.format("Priority: %d (%s)\n", priority, severityLabel));
|
||||
response.append(String.format("Trigger after: %d consecutive violations\n", times));
|
||||
@@ -296,8 +301,11 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
|
||||
pageSize = 10;
|
||||
}
|
||||
|
||||
Page<AlertDefine> result = alertDefineServiceAdapter.getAlertDefines(
|
||||
search, monitorType, enabled, "gmtCreate", "desc", pageIndex, pageSize);
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getAlertDefines: {}", subjectSum);
|
||||
|
||||
Page<AlertDefine> result = alertDefineService.getAlertDefines(null,
|
||||
search, "gmtCreate", "desc", pageIndex, pageSize);
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append("Found ").append(result.getContent().size())
|
||||
@@ -345,7 +353,21 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
|
||||
try {
|
||||
log.info("Toggling alert rule ID: {} to enabled: {}", ruleId, enabled);
|
||||
|
||||
alertDefineServiceAdapter.toggleAlertDefineStatus(ruleId, enabled);
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for toggleAlertDefineStatus: {}", subjectSum);
|
||||
|
||||
// First get the existing AlertDefine
|
||||
AlertDefine alertDefine = alertDefineService.getAlertDefine(ruleId);
|
||||
|
||||
if (alertDefine == null) {
|
||||
throw new RuntimeException("AlertDefine with ID " + ruleId + " not found");
|
||||
}
|
||||
|
||||
// Update the enable status
|
||||
alertDefine.setEnable(enabled);
|
||||
|
||||
// Use modifyAlertDefine to save the changes
|
||||
alertDefineService.modifyAlertDefine(alertDefine);
|
||||
|
||||
log.info("Successfully toggled alert rule ID: {} to enabled: {}", ruleId, enabled);
|
||||
return String.format("Successfully %s alert rule ID: %d",
|
||||
@@ -369,7 +391,10 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
|
||||
try {
|
||||
log.info("Getting alert rule details for ID: {}", ruleId);
|
||||
|
||||
AlertDefine alertDefine = alertDefineServiceAdapter.getAlertDefine(ruleId);
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getAlertDefine: {}", subjectSum);
|
||||
|
||||
AlertDefine alertDefine = alertDefineService.getAlertDefine(ruleId);
|
||||
if (alertDefine == null) {
|
||||
return "Alert rule with ID " + ruleId + " not found";
|
||||
}
|
||||
@@ -441,7 +466,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
|
||||
log.debug("Current subject in get_apps_metrics_hierarchy tool: {}", subjectSum);
|
||||
|
||||
List<Hierarchy> hierarchies;
|
||||
hierarchies = alertDefineServiceAdapter.getAppHierarchy(app.trim().toLowerCase(), "en-US");
|
||||
hierarchies = appService.getAppHierarchy(app.trim().toLowerCase(), "en-US");
|
||||
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
@@ -459,10 +484,7 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
|
||||
result.put("message", "No hierarchy data available");
|
||||
}
|
||||
|
||||
String jsonResult = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result);
|
||||
log.info("Hierarchy JSON: {}", jsonResult);
|
||||
|
||||
return jsonResult;
|
||||
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to get apps metrics hierarchy: {}", e.getMessage(), e);
|
||||
@@ -494,7 +516,9 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
|
||||
}
|
||||
|
||||
// Get the existing alert rule
|
||||
AlertDefine existingRule = alertDefineServiceAdapter.getAlertDefine(ruleId);
|
||||
log.debug("Current security subject for getAlertDefine: {}", subjectSum);
|
||||
|
||||
AlertDefine existingRule = alertDefineService.getAlertDefine(ruleId);
|
||||
if (existingRule == null) {
|
||||
return String.format("Error: Alert rule with ID %d not found", ruleId);
|
||||
}
|
||||
@@ -571,7 +595,9 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
|
||||
|
||||
// Update the alert rule
|
||||
existingRule.setExpr(newExpr);
|
||||
alertDefineServiceAdapter.modifyAlertDefine(existingRule);
|
||||
log.debug("Current security subject for modifyAlertDefine: {}", subjectSum);
|
||||
|
||||
alertDefineService.modifyAlertDefine(existingRule);
|
||||
|
||||
log.info("Successfully added monitors {} to existing bindings for alert rule ID: {}", validMonitorIds, ruleId);
|
||||
return String.format("Successfully added %d new monitor(s) to alert rule ID %d.\nTotal bound monitors: %s\nUpdated expression: %s",
|
||||
@@ -608,7 +634,8 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
|
||||
|
||||
// Update the alert rule
|
||||
existingRule.setExpr(newExpr);
|
||||
alertDefineServiceAdapter.modifyAlertDefine(existingRule);
|
||||
|
||||
alertDefineService.modifyAlertDefine(existingRule);
|
||||
|
||||
log.info("Successfully bound monitors {} to alert rule ID: {}", validMonitorIds, ruleId);
|
||||
return String.format("Successfully bound %d monitor(s) to alert rule ID %d.\nMonitor IDs: %s\nUpdated expression: %s",
|
||||
@@ -633,7 +660,10 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
|
||||
log.debug("Validating hierarchy relationships: app={}, metrics={}, fieldConditions={}", app, metrics, fieldConditions);
|
||||
|
||||
// Get hierarchy for the specified app
|
||||
List<Hierarchy> hierarchies = alertDefineServiceAdapter.getAppHierarchy(app.toLowerCase(), "en-US");
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getAppHierarchy: {}", subjectSum);
|
||||
|
||||
List<Hierarchy> hierarchies = appService.getAppHierarchy(app.toLowerCase(), "en-US");
|
||||
|
||||
if (hierarchies == null || hierarchies.isEmpty()) {
|
||||
return String.format("Error: App '%s' not found in hierarchy. Please use list_monitor_types to get valid app names.", app);
|
||||
@@ -662,4 +692,4 @@ public class AlertDefineToolsImpl implements AlertDefineTools {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+9
-9
@@ -16,15 +16,15 @@
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.tools.impl;
|
||||
package org.apache.hertzbeat.ai.tools.impl;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.adapters.AlertServiceAdapter;
|
||||
import org.apache.hertzbeat.ai.agent.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.ai.agent.tools.AlertTools;
|
||||
import org.apache.hertzbeat.ai.agent.utils.UtilityClass;
|
||||
import org.apache.hertzbeat.ai.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.ai.tools.AlertTools;
|
||||
import org.apache.hertzbeat.ai.utils.UtilityClass;
|
||||
import org.apache.hertzbeat.alert.dto.AlertSummary;
|
||||
import org.apache.hertzbeat.alert.service.AlertService;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -41,7 +41,7 @@ import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class AlertToolsImpl implements AlertTools {
|
||||
@Autowired
|
||||
private AlertServiceAdapter alertServiceAdapter;
|
||||
private AlertService alertService;
|
||||
|
||||
@Override
|
||||
@Tool(name = "query_alerts", description = """
|
||||
@@ -116,7 +116,7 @@ public class AlertToolsImpl implements AlertTools {
|
||||
|
||||
// Handle different alert types
|
||||
if ("single".equalsIgnoreCase(alertType) || "both".equalsIgnoreCase(alertType)) {
|
||||
Page<SingleAlert> singleResult = alertServiceAdapter.getSingleAlerts(status, search, sort, order, pageIndex, pageSize);
|
||||
Page<SingleAlert> singleResult = alertService.getSingleAlerts(status, search, sort, order, pageIndex, pageSize);
|
||||
|
||||
response.append("SINGLE ALERTS:\n");
|
||||
response.append("Found ").append(singleResult.getContent().size()).append(" single alerts (Total: ").append(singleResult.getTotalElements()).append("):\n\n");
|
||||
@@ -154,7 +154,7 @@ public class AlertToolsImpl implements AlertTools {
|
||||
response.append("\n");
|
||||
}
|
||||
|
||||
Page<GroupAlert> groupResult = alertServiceAdapter.getGroupAlerts(status, search, sort, order, pageIndex, pageSize);
|
||||
Page<GroupAlert> groupResult = alertService.getGroupAlerts(status, search, sort, order, pageIndex, pageSize);
|
||||
|
||||
response.append("GROUP ALERTS:\n");
|
||||
response.append("Found ").append(groupResult.getContent().size()).append(" group alerts (Total: ").append(groupResult.getTotalElements()).append("):\n\n");
|
||||
@@ -205,7 +205,7 @@ public class AlertToolsImpl implements AlertTools {
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current subject in get_alerts_summary tool: {}", subjectSum);
|
||||
|
||||
AlertSummary summary = alertServiceAdapter.getAlertsSummary();
|
||||
AlertSummary summary = alertService.getAlertsSummary();
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append("ALERTS SUMMARY\n");
|
||||
+11
-12
@@ -16,14 +16,13 @@
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.tools.impl;
|
||||
package org.apache.hertzbeat.ai.tools.impl;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.adapters.MetricsServiceAdapter;
|
||||
import org.apache.hertzbeat.ai.agent.adapters.MonitorServiceAdapter;
|
||||
import org.apache.hertzbeat.ai.agent.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.ai.agent.tools.MetricsTools;
|
||||
import org.apache.hertzbeat.ai.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.warehouse.service.MetricsDataService;
|
||||
import org.apache.hertzbeat.ai.tools.MetricsTools;
|
||||
import org.apache.hertzbeat.common.entity.dto.Field;
|
||||
import org.apache.hertzbeat.common.entity.dto.MetricsData;
|
||||
import org.apache.hertzbeat.common.entity.dto.MetricsHistoryData;
|
||||
@@ -44,9 +43,7 @@ import java.util.Map;
|
||||
@Service
|
||||
public class MetricsToolsImpl implements MetricsTools {
|
||||
@Autowired
|
||||
private MetricsServiceAdapter metricsServiceAdapter;
|
||||
@Autowired
|
||||
private MonitorServiceAdapter monitorServiceAdapter;
|
||||
private MetricsDataService metricsDataService;
|
||||
|
||||
@Override
|
||||
@Tool(name = "query_realtime_metrics", description = """
|
||||
@@ -78,7 +75,7 @@ public class MetricsToolsImpl implements MetricsTools {
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current subject in get_realtime_metrics tool: {}", subjectSum);
|
||||
|
||||
MetricsData metricsData = metricsServiceAdapter.getMetricsData(monitorId, metrics);
|
||||
MetricsData metricsData = metricsDataService.getMetricsData(monitorId, metrics);
|
||||
|
||||
if (metricsData == null) {
|
||||
return String.format("No real-time metrics data found for monitor ID %d and metrics '%s'", monitorId, metrics);
|
||||
@@ -173,7 +170,7 @@ public class MetricsToolsImpl implements MetricsTools {
|
||||
interval = true;
|
||||
}
|
||||
|
||||
MetricsHistoryData historyData = metricsServiceAdapter.getMetricHistoryData(
|
||||
MetricsHistoryData historyData = metricsDataService.getMetricHistoryData(
|
||||
monitorId, app, metrics, fieldParameter, label, history, interval);
|
||||
|
||||
if (historyData == null) {
|
||||
@@ -235,8 +232,10 @@ public class MetricsToolsImpl implements MetricsTools {
|
||||
public String getWarehouseStatus() {
|
||||
try {
|
||||
log.info("Checking warehouse storage status");
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getWarehouseStorageServerStatus: {}", subjectSum);
|
||||
|
||||
Boolean status = metricsServiceAdapter.getWarehouseStorageServerStatus();
|
||||
Boolean status = metricsDataService.getWarehouseStorageServerStatus();
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append("METRICS WAREHOUSE STATUS\n");
|
||||
@@ -260,4 +259,4 @@ public class MetricsToolsImpl implements MetricsTools {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+37
-23
@@ -15,17 +15,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.tools.impl;
|
||||
package org.apache.hertzbeat.ai.tools.impl;
|
||||
|
||||
import com.usthe.sureness.subject.SubjectSum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.adapters.MonitorServiceAdapter;
|
||||
import org.apache.hertzbeat.ai.agent.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.ai.agent.utils.UtilityClass;
|
||||
import org.apache.hertzbeat.ai.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.manager.service.MonitorService;
|
||||
import org.apache.hertzbeat.manager.service.AppService;
|
||||
import org.apache.hertzbeat.ai.utils.UtilityClass;
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.apache.hertzbeat.ai.agent.tools.MonitorTools;
|
||||
import org.apache.hertzbeat.ai.tools.MonitorTools;
|
||||
import org.springframework.ai.tool.annotation.ToolParam;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
@@ -43,7 +44,9 @@ import java.util.Map;
|
||||
@Service
|
||||
public class MonitorToolsImpl implements MonitorTools {
|
||||
@Autowired
|
||||
private MonitorServiceAdapter monitorServiceAdapter;
|
||||
private MonitorService monitorService;
|
||||
@Autowired
|
||||
private AppService appService;
|
||||
|
||||
|
||||
/**
|
||||
@@ -123,8 +126,12 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
includeStats = false;
|
||||
}
|
||||
|
||||
Page<Monitor> result = monitorServiceAdapter.getMonitors(ids, app, search, status, sort, order, pageIndex, pageSize, labels);
|
||||
log.debug("MonitorServiceAdapter.getMonitors result: {}", result);
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject: {}", subjectSum);
|
||||
|
||||
Page<Monitor> result = monitorService.getMonitors(
|
||||
ids, app, search, status, sort, order, pageIndex, pageSize, labels);
|
||||
log.debug("MonitorService.getMonitors result: {}", result);
|
||||
|
||||
StringBuilder response = new StringBuilder();
|
||||
response.append("MONITOR QUERY RESULTS\n");
|
||||
@@ -133,10 +140,10 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
// Include statistics if requested
|
||||
if (includeStats) {
|
||||
// Get status distribution by calling with different status values
|
||||
long onlineCount = monitorServiceAdapter.getMonitors(null, app, search, (byte) 1, null, null, 0, 1000, labels).getTotalElements();
|
||||
long offlineCount = monitorServiceAdapter.getMonitors(null, app, search, (byte) 2, null, null, 0, 1000, labels).getTotalElements();
|
||||
long unreachableCount = monitorServiceAdapter.getMonitors(null, app, search, (byte) 3, null, null, 0, 1000, labels).getTotalElements();
|
||||
long pausedCount = monitorServiceAdapter.getMonitors(null, app, search, (byte) 0, null, null, 0, 1000, labels).getTotalElements();
|
||||
long onlineCount = monitorService.getMonitors(null, app, search, (byte) 1, null, null, 0, 1000, labels).getTotalElements();
|
||||
long offlineCount = monitorService.getMonitors(null, app, search, (byte) 2, null, null, 0, 1000, labels).getTotalElements();
|
||||
long unreachableCount = monitorService.getMonitors(null, app, search, (byte) 3, null, null, 0, 1000, labels).getTotalElements();
|
||||
long pausedCount = monitorService.getMonitors(null, app, search, (byte) 0, null, null, 0, 1000, labels).getTotalElements();
|
||||
|
||||
response.append("STATUS OVERVIEW:\n");
|
||||
response.append("- Online: ").append(onlineCount).append("\n");
|
||||
@@ -266,7 +273,10 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
|
||||
// Validate that all required parameters for this monitor type are provided
|
||||
try {
|
||||
List<ParamDefine> requiredParams = monitorServiceAdapter.getMonitorParamDefines(app);
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for getMonitorParamDefines: {}", subjectSum);
|
||||
|
||||
List<ParamDefine> requiredParams = appService.getAppParamDefines(app.toLowerCase().trim());
|
||||
log.info("Checking required parameters for monitor type '{}': {}", app, requiredParams);
|
||||
List<String> missingParams = new ArrayList<>();
|
||||
|
||||
@@ -290,8 +300,12 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
log.warn("Could not validate required parameters for monitor type '{}': {}", app, e.getMessage());
|
||||
}
|
||||
|
||||
// Call adapter - it handles all the complexity (validation, defaults, app-specific logic)
|
||||
Long monitorId = monitorServiceAdapter.addMonitor(monitor, params, null);
|
||||
// Call monitor service - it handles all the complexity (validation, defaults, app-specific logic)
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current security subject for addMonitor: {}", subjectSum);
|
||||
|
||||
monitorService.addMonitor(monitor, params, null, null);
|
||||
Long monitorId = monitor.getId();
|
||||
|
||||
log.info("Successfully added monitor '{}' with ID: {}", name, monitorId);
|
||||
return String.format("Successfully added %s monitor '%s' with ID: %d (Host: %s, Interval: %d seconds)",
|
||||
@@ -361,14 +375,14 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
log.info("Listing available monitor types for language: {}", language);
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current subject in list_monitor_types tool: {}", subjectSum);
|
||||
|
||||
|
||||
// Set default language if not provided
|
||||
if (language == null || language.trim().isEmpty()) {
|
||||
language = "en-US";
|
||||
}
|
||||
|
||||
// Get available monitor types from adapter
|
||||
Map<String, String> monitorTypes = monitorServiceAdapter.getAvailableMonitorTypes(language);
|
||||
|
||||
// Get available monitor types from app service
|
||||
Map<String, String> monitorTypes = appService.getI18nApps(language);
|
||||
|
||||
if (monitorTypes == null || monitorTypes.isEmpty()) {
|
||||
return "No monitor types are currently available.";
|
||||
@@ -416,14 +430,14 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
log.info("Getting parameter definitions for monitor type: {}", app);
|
||||
SubjectSum subjectSum = McpContextHolder.getSubject();
|
||||
log.debug("Current subject in get_monitor_param_defines tool: {}", subjectSum);
|
||||
|
||||
|
||||
// Validate required parameter
|
||||
if (app == null || app.trim().isEmpty()) {
|
||||
return "Error: Monitor type/application parameter is required";
|
||||
}
|
||||
|
||||
// Get parameter definitions from adapter
|
||||
List<ParamDefine> paramDefines = monitorServiceAdapter.getMonitorParamDefines(app);
|
||||
|
||||
// Get parameter definitions from app service
|
||||
List<ParamDefine> paramDefines = appService.getAppParamDefines(app.toLowerCase().trim());
|
||||
|
||||
if (paramDefines == null || paramDefines.isEmpty()) {
|
||||
return String.format("No parameter definitions found for monitor type '%s'. "
|
||||
+2
-198
@@ -15,16 +15,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.utils;
|
||||
package org.apache.hertzbeat.ai.utils;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.agent.pojo.dto.Hierarchy;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
@@ -33,6 +31,7 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.Hierarchy;
|
||||
|
||||
/**
|
||||
* Utility class providing helper methods for field expression validation, parsing,
|
||||
@@ -227,51 +226,6 @@ public class UtilityClass {
|
||||
return "VALID";
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to validate operator
|
||||
*/
|
||||
public boolean isValidOperator(String operator) {
|
||||
return operator != null && (operator.equals(">") || operator.equals("<")
|
||||
|| operator.equals(">=") || operator.equals("<=")
|
||||
|| operator.equals("==") || operator.equals("!="));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to validate priority
|
||||
*/
|
||||
public boolean isValidPriority(String priority) {
|
||||
return priority != null && (priority.equalsIgnoreCase("critical")
|
||||
|| priority.equalsIgnoreCase("warning") || priority.equalsIgnoreCase("info"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to build expression
|
||||
*/
|
||||
public String buildExpression(String metric, String operator, String threshold) {
|
||||
return String.format("%s %s %s", metric, operator, threshold);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to parse existing expression into components
|
||||
*/
|
||||
public String[] parseExpression(String expression) {
|
||||
if (expression == null || expression.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Simple parsing for basic expressions like "metric > value"
|
||||
String[] operators = {">", "<", ">=", "<=", "==", "!="};
|
||||
for (String op : operators) {
|
||||
if (expression.contains(" " + op + " ")) {
|
||||
String[] parts = expression.split(" " + op + " ");
|
||||
if (parts.length == 2) {
|
||||
return new String[]{parts[0].trim(), op, parts[1].trim()};
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to parse key-value pairs from a string
|
||||
* Format: "key1:value1, key2:value2, ..."
|
||||
@@ -473,19 +427,6 @@ public class UtilityClass {
|
||||
return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse time range string to milliseconds
|
||||
*/
|
||||
public long parseTimeRangeToMillis(String timeRange) {
|
||||
return switch (timeRange.toLowerCase()) {
|
||||
case "1h" -> 60 * 60 * 1000L;
|
||||
case "6h" -> 6 * 60 * 60 * 1000L;
|
||||
case "24h" -> 24 * 60 * 60 * 1000L;
|
||||
case "7d" -> 7 * 24 * 60 * 60 * 1000L;
|
||||
default -> 24 * 60 * 60 * 1000L; // default to 24h
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to convert monitor status byte to readable text
|
||||
* @param status The status byte from monitor
|
||||
@@ -504,143 +445,6 @@ public class UtilityClass {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get metrics name for a metric type
|
||||
*/
|
||||
public String getMetricsNameForType(String metricType) {
|
||||
return switch (metricType.toLowerCase()) {
|
||||
case "cpu" -> "cpu";
|
||||
case "memory" -> "memory";
|
||||
case "disk" -> "disk";
|
||||
case "network" -> "network";
|
||||
default -> "system";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to check if a field represents usage for a metric type
|
||||
*/
|
||||
public boolean isUsageField(String field, String metricType) {
|
||||
if (field == null) return false;
|
||||
|
||||
String fieldLower = field.toLowerCase();
|
||||
String typeLower = metricType.toLowerCase();
|
||||
|
||||
return fieldLower.contains("usage")
|
||||
|| fieldLower.contains("percent")
|
||||
|| fieldLower.contains("util")
|
||||
|| (typeLower.equals("cpu") && (fieldLower.contains("cpu") || fieldLower.contains("idle")))
|
||||
|| (typeLower.equals("memory") && fieldLower.contains("memory"))
|
||||
|| (typeLower.equals("disk") && fieldLower.contains("disk"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert manager module Hierarchy objects to ai-agent module Hierarchy objects
|
||||
* This handles the cross-module DTO conversion to avoid ClassCastException
|
||||
*/
|
||||
public List<Hierarchy> convertToAgentHierarchies(List<?> managerHierarchies) {
|
||||
List<Hierarchy> agentHierarchies = new ArrayList<>();
|
||||
|
||||
for (Object managerHierarchy : managerHierarchies) {
|
||||
Hierarchy agentHierarchy = convertToAgentHierarchy(managerHierarchy);
|
||||
agentHierarchies.add(agentHierarchy);
|
||||
}
|
||||
|
||||
return agentHierarchies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a single manager Hierarchy object to ai-agent Hierarchy object using reflection
|
||||
*/
|
||||
public Hierarchy convertToAgentHierarchy(Object managerHierarchy) {
|
||||
try {
|
||||
Hierarchy agentHierarchy = new Hierarchy();
|
||||
|
||||
// Use reflection to copy properties from manager DTO to agent DTO
|
||||
Class<?> managerClass = managerHierarchy.getClass();
|
||||
|
||||
// Copy basic properties
|
||||
agentHierarchy.setCategory(getStringField(managerHierarchy, managerClass, "category"));
|
||||
agentHierarchy.setValue(getStringField(managerHierarchy, managerClass, "value"));
|
||||
agentHierarchy.setLabel(getStringField(managerHierarchy, managerClass, "label"));
|
||||
agentHierarchy.setIsLeaf(getBooleanField(managerHierarchy, managerClass, "isLeaf"));
|
||||
agentHierarchy.setHide(getBooleanField(managerHierarchy, managerClass, "hide"));
|
||||
agentHierarchy.setType(getByteField(managerHierarchy, managerClass, "type"));
|
||||
agentHierarchy.setUnit(getStringField(managerHierarchy, managerClass, "unit"));
|
||||
|
||||
// Handle children recursively
|
||||
List<?> managerChildren = getListField(managerHierarchy, managerClass, "children");
|
||||
if (managerChildren != null && !managerChildren.isEmpty()) {
|
||||
List<Hierarchy> agentChildren = convertToAgentHierarchies(managerChildren);
|
||||
agentHierarchy.setChildren(agentChildren);
|
||||
}
|
||||
|
||||
return agentHierarchy;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to convert manager hierarchy to agent hierarchy: {}", e.getMessage(), e);
|
||||
throw new RuntimeException("Failed to convert hierarchy", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getStringField(Object obj, Class<?> clazz, String fieldName) {
|
||||
try {
|
||||
Method getter = clazz.getMethod("get" + capitalize(fieldName));
|
||||
Object value = getter.invoke(obj);
|
||||
return value != null ? value.toString() : null;
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not get string field '{}': {}", fieldName, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Boolean getBooleanField(Object obj, Class<?> clazz, String fieldName) {
|
||||
try {
|
||||
Method getter = clazz.getMethod("get" + capitalize(fieldName));
|
||||
Object value = getter.invoke(obj);
|
||||
return value instanceof Boolean ? (Boolean) value : null;
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
// Try alternative getter pattern for boolean fields
|
||||
Method isGetter = clazz.getMethod("is" + capitalize(fieldName));
|
||||
Object value = isGetter.invoke(obj);
|
||||
return value instanceof Boolean ? (Boolean) value : null;
|
||||
} catch (Exception e2) {
|
||||
log.debug("Could not get boolean field '{}': {}", fieldName, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Byte getByteField(Object obj, Class<?> clazz, String fieldName) {
|
||||
try {
|
||||
Method getter = clazz.getMethod("get" + capitalize(fieldName));
|
||||
Object value = getter.invoke(obj);
|
||||
return value instanceof Byte ? (Byte) value : null;
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not get byte field '{}': {}", fieldName, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public List<?> getListField(Object obj, Class<?> clazz, String fieldName) {
|
||||
try {
|
||||
Method getter = clazz.getMethod("get" + capitalize(fieldName));
|
||||
Object value = getter.invoke(obj);
|
||||
return value instanceof List ? (List<?>) value : null;
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not get list field '{}': {}", fieldName, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String capitalize(String str) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
return str;
|
||||
}
|
||||
return str.substring(0, 1).toUpperCase() + str.substring(1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract existing monitor IDs from the alert expression
|
||||
+1
-1
@@ -24,7 +24,7 @@ import java.util.Properties;
|
||||
import java.util.ResourceBundle;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.dto.MailServerConfig;
|
||||
import org.apache.hertzbeat.common.entity.dto.MailServerConfig;
|
||||
import org.apache.hertzbeat.alert.notice.AlertNoticeException;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.manager.GeneralConfig;
|
||||
|
||||
+2
-2
@@ -19,7 +19,7 @@ package org.apache.hertzbeat.alert.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.config.SmsConfig;
|
||||
import org.apache.hertzbeat.common.entity.dto.sms.SmsConfig;
|
||||
import org.apache.hertzbeat.alert.service.impl.SmsLocalSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.AwsSmsClientImpl;
|
||||
import org.apache.hertzbeat.alert.service.impl.TencentSmsClientImpl;
|
||||
@@ -151,4 +151,4 @@ public class SmsClientFactory {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+21
-4
@@ -17,7 +17,9 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.alert.dto.AlibabaCloudSlsExternAlert;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
@@ -33,7 +35,9 @@ import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -46,19 +50,32 @@ public class AlibabaCloudSlsExternAlertService implements ExternAlertService {
|
||||
|
||||
private final AlarmCommonReduce alarmCommonReduce;
|
||||
|
||||
private static final AlibabaCloudSlsConverter CONVERTER = new AlibabaCloudSlsConverter();
|
||||
|
||||
public AlibabaCloudSlsExternAlertService(AlarmCommonReduce alarmCommonReduce) {
|
||||
this.alarmCommonReduce = alarmCommonReduce;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addExternAlert(String content) {
|
||||
AlibabaCloudSlsExternAlert externAlert = JsonUtil.fromJson(content, AlibabaCloudSlsExternAlert.class);
|
||||
if (externAlert == null) {
|
||||
List<AlibabaCloudSlsExternAlert> externAlerts = new ArrayList<>();
|
||||
if (BooleanUtils.isTrue(JsonUtil.isArray(content))) {
|
||||
TypeReference<List<AlibabaCloudSlsExternAlert>> typeReference = new TypeReference<>() {};
|
||||
externAlerts = JsonUtil.fromJson(content, typeReference);
|
||||
} else {
|
||||
AlibabaCloudSlsExternAlert externAlert = JsonUtil.fromJson(content, AlibabaCloudSlsExternAlert.class);
|
||||
if (null != externAlert) {
|
||||
externAlerts.add(externAlert);
|
||||
}
|
||||
}
|
||||
if (null == externAlerts || externAlerts.isEmpty()) {
|
||||
log.warn("Failure to parse external alert content. content: {}", content);
|
||||
return;
|
||||
}
|
||||
SingleAlert singleAlert = new AlibabaCloudSlsConverter().convert(externAlert);
|
||||
alarmCommonReduce.reduceAndSendAlarm(singleAlert);
|
||||
for (AlibabaCloudSlsExternAlert externAlert : externAlerts) {
|
||||
SingleAlert singleAlert = CONVERTER.convert(externAlert);
|
||||
alarmCommonReduce.reduceAndSendAlarm(singleAlert);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -19,7 +19,7 @@ package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.config.AlibabaSmsProperties;
|
||||
import org.apache.hertzbeat.common.entity.dto.sms.AlibabaSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.alert.util.CryptoUtils;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
@@ -236,4 +236,4 @@ public class AlibabaSmsClientImpl implements SmsClient {
|
||||
public boolean checkConfig() {
|
||||
return !(accessKeyId.isBlank() || accessKeySecret.isBlank() || signName.isBlank() || templateCode.isBlank());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ package org.apache.hertzbeat.alert.service.impl;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.config.AwsSmsProperties;
|
||||
import org.apache.hertzbeat.common.entity.dto.sms.AwsSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.alert.util.CryptoUtils;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.config.SmslocalSmsProperties;
|
||||
import org.apache.hertzbeat.common.entity.dto.sms.SmslocalSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.common.constants.SmsConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import jakarta.xml.bind.DatatypeConverter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.config.TencentSmsProperties;
|
||||
import org.apache.hertzbeat.common.entity.dto.sms.TencentSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.alert.util.CryptoUtils;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
|
||||
+2
-2
@@ -19,7 +19,7 @@ package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.config.TwilioSmsProperties;
|
||||
import org.apache.hertzbeat.common.entity.dto.sms.TwilioSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
@@ -168,4 +168,4 @@ public class TwilioSmsClientImpl implements SmsClient {
|
||||
public boolean checkConfig() {
|
||||
return !(accountSid.isBlank() || authToken.isBlank() || twilioPhoneNumber.isBlank());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -18,7 +18,7 @@
|
||||
package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.config.UniSmsProperties;
|
||||
import org.apache.hertzbeat.common.entity.dto.sms.UniSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.SmsClient;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
@@ -190,4 +190,4 @@ public class UniSmsClientImpl implements SmsClient {
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ import static org.mockito.Mockito.when;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.util.Properties;
|
||||
import org.apache.hertzbeat.alert.dto.MailServerConfig;
|
||||
import org.apache.hertzbeat.common.entity.dto.MailServerConfig;
|
||||
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
|
||||
+4
-4
@@ -105,19 +105,19 @@ class AlertDefineServiceTest {
|
||||
|
||||
@Test
|
||||
void addAlertDefine() {
|
||||
assertDoesNotThrow(() -> alertDefineService.addAlertDefine(alertDefine));
|
||||
assertDoesNotThrow(() -> this.alertDefineService.addAlertDefine(alertDefine));
|
||||
when(alertDefineDao.saveAndFlush(alertDefine)).thenThrow(new RuntimeException());
|
||||
assertThrows(RuntimeException.class, () -> alertDefineService.addAlertDefine(alertDefine));
|
||||
assertThrows(RuntimeException.class, () -> this.alertDefineService.addAlertDefine(alertDefine));
|
||||
}
|
||||
|
||||
@Test
|
||||
void modifyAlertDefine() {
|
||||
AlertDefine alertDefine = AlertDefine.builder().id(1L).build();
|
||||
when(alertDefineDao.saveAndFlush(alertDefine)).thenReturn(alertDefine);
|
||||
assertDoesNotThrow(() -> alertDefineService.modifyAlertDefine(alertDefine));
|
||||
assertDoesNotThrow(() -> this.alertDefineService.modifyAlertDefine(alertDefine));
|
||||
reset();
|
||||
when(alertDefineDao.saveAndFlush(alertDefine)).thenThrow(new RuntimeException());
|
||||
assertThrows(RuntimeException.class, () -> alertDefineService.modifyAlertDefine(alertDefine));
|
||||
assertThrows(RuntimeException.class, () -> this.alertDefineService.modifyAlertDefine(alertDefine));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+35
@@ -28,9 +28,12 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@@ -147,4 +150,36 @@ public class AlibabaCloudSlsExternAlertServiceTest {
|
||||
verify(alarmCommonReduce, times(1)).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddMergeExternAlert() {
|
||||
String source = externAlertService.supportSource();
|
||||
assertEquals("alibabacloud-sls", source);
|
||||
|
||||
AlibabaCloudSlsExternAlert externAlert = new AlibabaCloudSlsExternAlert();
|
||||
externAlert.setAlertName("Test SLS alert");
|
||||
externAlert.setFireTime((int) Instant.now().getEpochSecond());
|
||||
externAlert.setAlertTime((int) Instant.now().getEpochSecond());
|
||||
externAlert.setRegion("cn-hangzhou");
|
||||
externAlert.setProject("project");
|
||||
externAlert.setStatus("firing");
|
||||
externAlert.setSeverity(AlibabaCloudSlsExternAlert.Severity.HIGH.getStatus());
|
||||
|
||||
Map<String, String> labels = new HashMap<>();
|
||||
labels.put("labels-k", "labels-v");
|
||||
externAlert.setLabels(labels);
|
||||
|
||||
Map<String, String> annotations = new HashMap<>();
|
||||
annotations.put("annotations-k", "annotations-v");
|
||||
externAlert.setAnnotations(annotations);
|
||||
|
||||
AlibabaCloudSlsExternAlert externAlert1 = new AlibabaCloudSlsExternAlert();
|
||||
BeanUtils.copyProperties(externAlert, externAlert1);
|
||||
|
||||
List<AlibabaCloudSlsExternAlert> externAlerts = new ArrayList<>();
|
||||
externAlerts.add(externAlert);
|
||||
externAlerts.add(externAlert1);
|
||||
externAlertService.addExternAlert(JsonUtil.toJson(externAlerts));
|
||||
verify(alarmCommonReduce, times(2)).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.service;
|
||||
|
||||
import org.apache.hertzbeat.alert.config.AwsSmsProperties;
|
||||
import org.apache.hertzbeat.common.entity.dto.sms.AwsSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.impl.AwsSmsClientImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
|
||||
+3
-3
@@ -19,8 +19,8 @@ package org.apache.hertzbeat.alert.service;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.hertzbeat.alert.config.SmsConfig;
|
||||
import org.apache.hertzbeat.alert.config.SmslocalSmsProperties;
|
||||
import org.apache.hertzbeat.common.entity.dto.sms.SmsConfig;
|
||||
import org.apache.hertzbeat.common.entity.dto.sms.SmslocalSmsProperties;
|
||||
import org.apache.hertzbeat.base.dao.GeneralConfigDao;
|
||||
import org.apache.hertzbeat.common.constants.GeneralConfigTypeEnum;
|
||||
import org.apache.hertzbeat.common.entity.manager.GeneralConfig;
|
||||
@@ -89,4 +89,4 @@ public class SmsClientFactoryTest {
|
||||
assertNull(smsClientFactory.getSmsClient());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.service;
|
||||
|
||||
import org.apache.hertzbeat.alert.config.TwilioSmsProperties;
|
||||
import org.apache.hertzbeat.common.entity.dto.sms.TwilioSmsProperties;
|
||||
import org.apache.hertzbeat.alert.service.impl.TwilioSmsClientImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
@@ -61,4 +61,4 @@ class TwilioSmsClientImplTest {
|
||||
|
||||
assertEquals(expected, twilioSmsClient.checkConfig());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -18,7 +18,7 @@
|
||||
package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import org.apache.hertzbeat.alert.config.SmslocalSmsProperties;
|
||||
import org.apache.hertzbeat.common.entity.dto.sms.SmslocalSmsProperties;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
@@ -70,4 +70,4 @@ public class SmsLocalSmsClientImplTest {
|
||||
() -> smsLocalSmsClient.sendMessage(noticeReceiver, null, groupAlert));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.service.impl;
|
||||
|
||||
import org.apache.hertzbeat.alert.config.UniSmsProperties;
|
||||
import org.apache.hertzbeat.common.entity.dto.sms.UniSmsProperties;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver;
|
||||
import org.apache.hertzbeat.common.support.exception.SendMessageException;
|
||||
@@ -82,4 +82,4 @@ public class UniSmsClientImplTest {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ spring:
|
||||
|
||||
collector:
|
||||
info:
|
||||
version: ${COLLECTOR_VERSION:1.7.3}
|
||||
version: ${COLLECTOR_VERSION:1.8.0}
|
||||
ip: ${COLLECTOR_IP:}
|
||||
dispatch:
|
||||
entrance:
|
||||
|
||||
+2
@@ -42,6 +42,8 @@ public interface ConfigConstants {
|
||||
*/
|
||||
interface FunctionModuleConstants {
|
||||
|
||||
String MANAGER = "manager";
|
||||
|
||||
String ALERT = "alert";
|
||||
|
||||
String ALERTER = "alerter";
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.hertzbeat.common.entity.ai;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import org.springframework.data.annotation.CreatedBy;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedBy;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Entity for storing AI chat conversation metadata
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@Entity
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
@Table(name = "hzb_ai_conversation")
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ChatConversation {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Schema(title = "conversation title")
|
||||
private String title;
|
||||
|
||||
@Schema(title = "The creator of this record", example = "tom", accessMode = READ_ONLY)
|
||||
@CreatedBy
|
||||
private String creator;
|
||||
|
||||
@Schema(title = "The modifier of this record", example = "tom", accessMode = READ_ONLY)
|
||||
@LastModifiedBy
|
||||
private String modifier;
|
||||
|
||||
@Schema(title = "Record create time", example = "1612198922000", accessMode = READ_ONLY)
|
||||
@CreatedDate
|
||||
private LocalDateTime gmtCreate;
|
||||
|
||||
@Schema(title = "Record modify time", example = "1612198444000", accessMode = READ_ONLY)
|
||||
@LastModifiedDate
|
||||
private LocalDateTime gmtUpdate;
|
||||
|
||||
/**
|
||||
* List of messages in this conversation (one-to-many relationship)
|
||||
*/
|
||||
@OneToMany
|
||||
@JoinColumn(name = "conversation_id")
|
||||
private List<ChatMessage> messages;
|
||||
}
|
||||
+34
-26
@@ -15,66 +15,74 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.entity;
|
||||
package org.apache.hertzbeat.common.entity.ai;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_WRITE;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Index;
|
||||
import jakarta.persistence.Lob;
|
||||
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import java.time.LocalDateTime;
|
||||
import org.springframework.data.annotation.CreatedBy;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedBy;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* OpenAI Agent Config Entity
|
||||
* Entity for storing individual chat messages in conversations
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "hzb_ai_agent_config")
|
||||
@Data
|
||||
@Builder
|
||||
@Entity
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
@Table(name = "hzb_ai_message", indexes = {
|
||||
@Index(name = "idx_message_conversation_id", columnList = "conversation_id")
|
||||
})
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "OpenAI Agent config entity")
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public class OpenAiConfig {
|
||||
public class ChatMessage {
|
||||
|
||||
@Id
|
||||
@Schema(title = "Config type: openai, primary key", description = "Config type: openai, primary key",
|
||||
accessMode = READ_WRITE)
|
||||
@NotBlank(message = "type can not null")
|
||||
private String type;
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Schema(title = "conversation id")
|
||||
private Long conversationId;
|
||||
|
||||
@Schema(title = "Config content", description = "Config content,format json", accessMode = READ_WRITE)
|
||||
@Column(length = 8192)
|
||||
@Schema(title = "message content")
|
||||
@Lob
|
||||
@NotBlank
|
||||
private String content;
|
||||
|
||||
@Schema(title = "message role: user, system")
|
||||
private String role;
|
||||
|
||||
@Schema(title = "The creator of this record", example = "tom", accessMode = READ_ONLY)
|
||||
@CreatedBy
|
||||
private String creator;
|
||||
|
||||
@Schema(title = "This record was last modified by", example = "tom", accessMode = READ_ONLY)
|
||||
@Schema(title = "The modifier of this record", example = "tom", accessMode = READ_ONLY)
|
||||
@LastModifiedBy
|
||||
private String modifier;
|
||||
|
||||
@Schema(title = "This record creation time (millisecond timestamp)", accessMode = READ_ONLY)
|
||||
@Schema(title = "Record create time", example = "1612198922000", accessMode = READ_ONLY)
|
||||
@CreatedDate
|
||||
private LocalDateTime gmtCreate;
|
||||
|
||||
@Schema(title = "Record the latest modification time (timestamp in milliseconds)", accessMode = READ_ONLY)
|
||||
@Schema(title = "Record modify time", example = "1612198444000", accessMode = READ_ONLY)
|
||||
@LastModifiedDate
|
||||
private LocalDateTime gmtUpdate;
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.dto;
|
||||
package org.apache.hertzbeat.common.entity.dto;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.Max;
|
||||
+1
-10
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.ai.agent.pojo.dto;
|
||||
package org.apache.hertzbeat.common.entity.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
@@ -32,15 +32,6 @@ import lombok.NoArgsConstructor;
|
||||
@Schema(description = "LLM Model Provider configuration")
|
||||
public class ModelProviderConfig {
|
||||
|
||||
@Schema(title = "Enable Provider", description = "Whether Provider is enabled", example = "true")
|
||||
private boolean enable = false;
|
||||
|
||||
@Schema(title = "Check the provider available status")
|
||||
private boolean status = false;
|
||||
|
||||
@Schema(title = "The error message when provider status check failed")
|
||||
private String error;
|
||||
|
||||
@Schema(title = "Model type, text-generate, vision")
|
||||
private String type;
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
package org.apache.hertzbeat.common.entity.dto.sms;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
package org.apache.hertzbeat.common.entity.dto.sms;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
+2
-2
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
package org.apache.hertzbeat.common.entity.dto.sms;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -74,4 +74,4 @@ public class SmsConfig {
|
||||
* Smslocal SMS configuration
|
||||
*/
|
||||
private SmslocalSmsProperties smslocal;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
package org.apache.hertzbeat.common.entity.dto.sms;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
+2
-2
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
package org.apache.hertzbeat.common.entity.dto.sms;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -58,4 +58,4 @@ public class TencentSmsProperties {
|
||||
*/
|
||||
@NotBlank(message = "templateId cannot be null")
|
||||
private String templateId;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
package org.apache.hertzbeat.common.entity.dto.sms;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -46,4 +46,4 @@ public class TwilioSmsProperties {
|
||||
*/
|
||||
@NotBlank(message = "Twilio Phone Number cannot be empty")
|
||||
private String twilioPhoneNumber;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.config;
|
||||
package org.apache.hertzbeat.common.entity.dto.sms;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -57,4 +57,4 @@ public class UniSmsProperties {
|
||||
*/
|
||||
@NotBlank(message = "authMode cannot be null")
|
||||
private String authMode = "simple";
|
||||
}
|
||||
}
|
||||
@@ -121,4 +121,26 @@ public final class JsonUtil {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static Boolean isArray(String jsonStr) {
|
||||
if (!isJsonLike(jsonStr)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
JsonNode jsonNode = OBJECT_MAPPER.readTree(jsonStr);
|
||||
return jsonNode.isArray();
|
||||
} catch (Exception ignore) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isJsonLike(String jsonStr) {
|
||||
if (jsonStr == null || jsonStr.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
jsonStr = jsonStr.trim();
|
||||
char start = jsonStr.charAt(0);
|
||||
char end = jsonStr.charAt(jsonStr.length() - 1);
|
||||
return (start == '{' && end == '}') || (start == '[' && end == ']');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
package org.apache.hertzbeat.common.util;
|
||||
|
||||
import static org.apache.hertzbeat.common.util.JsonUtil.isJsonStr;
|
||||
import static org.apache.hertzbeat.common.util.JsonUtil.isArray;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
@@ -76,4 +78,17 @@ class JsonUtilTest {
|
||||
assertFalse(isJsonStr("{invalid}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsArray() {
|
||||
String jsonStringNull = "{\"name\":\"John\", \"age\":30";
|
||||
assertEquals(Boolean.FALSE, isArray(jsonStringNull));
|
||||
|
||||
String jsonString = "{\"name\":\"John\", \"age\":30}";
|
||||
assertNotNull(isArray(jsonString));
|
||||
assertEquals(Boolean.FALSE, isArray(jsonString));
|
||||
|
||||
String jsonStringArrays = "[{\"name\":\"John\"}, {\"name\":\"Doe\"}]";
|
||||
assertNotNull(isArray(jsonStringArrays));
|
||||
assertEquals(Boolean.TRUE, isArray(jsonStringArrays));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,12 @@
|
||||
|
||||
<dependencies>
|
||||
<!-- HertzBeat modules -->
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-startup</artifactId>
|
||||
<version>${hertzbeat.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-manager</artifactId>
|
||||
@@ -100,61 +106,4 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Copy manager resources to test classpath -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>${maven.dependency.plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>unpack-manager-resources</id>
|
||||
<phase>generate-test-resources</phase>
|
||||
<goals>
|
||||
<goal>unpack</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-manager</artifactId>
|
||||
<version>${hertzbeat.version}</version>
|
||||
<type>jar</type>
|
||||
<overWrite>true</overWrite>
|
||||
<outputDirectory>${project.build.testOutputDirectory}</outputDirectory>
|
||||
<includes>
|
||||
define/**,
|
||||
db/**,
|
||||
templates/**,
|
||||
grafana/**,
|
||||
*.yml,
|
||||
*.xml,
|
||||
*.txt
|
||||
</includes>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- Ensure test resources are processed -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>${maven.resources.plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-test-resources</id>
|
||||
<phase>generate-test-resources</phase>
|
||||
<goals>
|
||||
<goal>testResources</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ import static org.mockito.Mockito.doAnswer;
|
||||
/**
|
||||
* E2E tests for periodic log alert processing.
|
||||
*/
|
||||
@SpringBootTest(classes = org.apache.hertzbeat.manager.Manager.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@SpringBootTest(classes = org.apache.hertzbeat.startup.HertzBeatApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@Slf4j
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
public class LogPeriodicAlertE2eTest {
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ import static org.mockito.Mockito.doAnswer;
|
||||
/**
|
||||
* E2E tests for real-time log alert processing.
|
||||
*/
|
||||
@SpringBootTest(classes = org.apache.hertzbeat.manager.Manager.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@SpringBootTest(classes = org.apache.hertzbeat.startup.HertzBeatApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@Slf4j
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
public class LogRealTimeAlertE2eTest {
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
/**
|
||||
* E2E tests for log ingestion.
|
||||
*/
|
||||
@SpringBootTest(classes = org.apache.hertzbeat.manager.Manager.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@SpringBootTest(classes = org.apache.hertzbeat.startup.HertzBeatApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@Slf4j
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
public class LogIngestionE2eTest {
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
/**
|
||||
* E2E tests for GreptimeDB log storage.
|
||||
*/
|
||||
@SpringBootTest(classes = org.apache.hertzbeat.manager.Manager.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@SpringBootTest(classes = org.apache.hertzbeat.startup.HertzBeatApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@Slf4j
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
public class GreptimeLogStorageE2eTest {
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-manager</artifactId>
|
||||
<artifactId>hertzbeat-startup</artifactId>
|
||||
<version>${hertzbeat.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
+1
-222
@@ -27,10 +27,7 @@
|
||||
<name>${project.artifactId}</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.install.skip>true</maven.install.skip>
|
||||
</properties>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<!-- common -->
|
||||
<dependency>
|
||||
@@ -215,228 +212,10 @@
|
||||
<groupId>org.apache.arrow</groupId>
|
||||
<artifactId>arrow-memory-netty</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-ai-agent</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-registry-prometheus</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>apache-hertzbeat-${hzb.version}</finalName>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<includes>
|
||||
<include>*.yml</include>
|
||||
<include>*.properties</include>
|
||||
<include>*.xml</include>
|
||||
<include>banner.txt</include>
|
||||
<include>define/**</include>
|
||||
<include>db/**</include>
|
||||
<include>templates/**</include>
|
||||
<include>**/*.html</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>${maven-jar-plugin.version}</version>
|
||||
<configuration>
|
||||
<classesDirectory>target/classes/</classesDirectory>
|
||||
<archive>
|
||||
<!--Exclude maven descriptors from generated JAR-->
|
||||
<addMavenDescriptor>false</addMavenDescriptor>
|
||||
<manifest>
|
||||
<!--Main class of the project-->
|
||||
<mainClass>org.apache.hertzbeat.manager.Manager</mainClass>
|
||||
<useUniqueVersions>false</useUniqueVersions>
|
||||
<!--Add third-party JARs to the classpath using maven-dependency-plugin-->
|
||||
<addClasspath>true</addClasspath>
|
||||
<!--Location of external dependency JARs-->
|
||||
<classpathPrefix>lib/</classpathPrefix>
|
||||
</manifest>
|
||||
<manifestEntries>
|
||||
<Class-Path>. config</Class-Path>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>release</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<!--Critical plugin, assembly plugin provided by maven, should be placed last-->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>${maven-assembly-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-zip</id>
|
||||
<!--Bound maven operation-->
|
||||
<phase>package</phase>
|
||||
<!--Run once-->
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>../dist</outputDirectory>
|
||||
<descriptors>
|
||||
<descriptor>../script/assembly/server/assembly.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>make-docker-zip</id>
|
||||
<!--Bound maven operation-->
|
||||
<phase>package</phase>
|
||||
<!--Run once-->
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>../dist</outputDirectory>
|
||||
<descriptors>
|
||||
<descriptor>../script/assembly/server/assembly-docker.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>make-docker-compose-script</id>
|
||||
<!--Bound maven operation-->
|
||||
<phase>package</phase>
|
||||
<!--Run once-->
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>../dist</outputDirectory>
|
||||
<descriptors>
|
||||
<descriptor>../script/assembly/server/assembly-docker-compose.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>runtime</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>${maven-assembly-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-macos-arm64</id>
|
||||
<!--Bound maven operation-->
|
||||
<phase>package</phase>
|
||||
<!--Run once-->
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>../script/assembly/server/assembly-macos-arm64.xml</descriptor>
|
||||
</descriptors>
|
||||
<outputDirectory>../dist</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>make-macos-amd64</id>
|
||||
<!--Bound maven operation-->
|
||||
<phase>package</phase>
|
||||
<!--Run once-->
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>../script/assembly/server/assembly-macos-amd64.xml</descriptor>
|
||||
</descriptors>
|
||||
<outputDirectory>../dist</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>make-linux-arm64</id>
|
||||
<!--Bound maven operation-->
|
||||
<phase>package</phase>
|
||||
<!--Run once-->
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>../script/assembly/server/assembly-linux-arm64.xml</descriptor>
|
||||
</descriptors>
|
||||
<outputDirectory>../dist</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>make-linux-amd64</id>
|
||||
<!--Bound maven operation-->
|
||||
<phase>package</phase>
|
||||
<!--Run once-->
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>../script/assembly/server/assembly-linux-amd64.xml</descriptor>
|
||||
</descriptors>
|
||||
<outputDirectory>../dist</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>make-windows-64</id>
|
||||
<!--Bound maven operation-->
|
||||
<phase>package</phase>
|
||||
<!--Run once-->
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>../script/assembly/server/assembly-windows-64.xml</descriptor>
|
||||
</descriptors>
|
||||
<outputDirectory>../dist</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>make-docker-compose-script</id>
|
||||
<!--Bound maven operation-->
|
||||
<phase>package</phase>
|
||||
<!--Run once-->
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>../script/assembly/server/assembly-docker-compose.xml</descriptor>
|
||||
</descriptors>
|
||||
<outputDirectory>../dist</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.manager.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.apache.hertzbeat.common.constants.ConfigConstants;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* AiProperties
|
||||
*/
|
||||
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix =
|
||||
ConfigConstants.FunctionModuleConstants.AI)
|
||||
public class AiProperties {
|
||||
|
||||
/**
|
||||
* AI Type: zhiPu, alibabaAi, kimiAi, sparkDesk
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* Model name: glm-4, qwen-turboo, moonshot-v1-8k, generalv3.5
|
||||
*/
|
||||
private String model;
|
||||
|
||||
/**
|
||||
* API key
|
||||
*/
|
||||
private String apiKey;
|
||||
|
||||
/**
|
||||
* At present, only IFLYTEK large model needs to be filled in
|
||||
*/
|
||||
private String apiSecret;
|
||||
|
||||
/**
|
||||
* API URL for the Ollama AI service.
|
||||
*/
|
||||
private String apiUrl;
|
||||
|
||||
}
|
||||
+11
-5
@@ -15,14 +15,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.service;
|
||||
package org.apache.hertzbeat.manager.config;
|
||||
|
||||
|
||||
import org.apache.hertzbeat.manager.service.ai.KimiAiServiceImpl;
|
||||
import org.apache.hertzbeat.common.constants.ConfigConstants;
|
||||
import org.apache.hertzbeat.common.constants.SignConstants;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
/**
|
||||
* test case for {@link KimiAiServiceImpl}
|
||||
* Manager auto configuration.
|
||||
*/
|
||||
|
||||
class KimiAiServiceTest {
|
||||
@AutoConfiguration
|
||||
@ComponentScan(basePackages = ConfigConstants.PkgConstant.PKG
|
||||
+ SignConstants.DOT
|
||||
+ ConfigConstants.FunctionModuleConstants.MANAGER)
|
||||
public class ManagerAutoConfiguration {
|
||||
}
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.manager.controller;
|
||||
|
||||
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM_VALUE;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.apache.hertzbeat.manager.config.AiProperties;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.AiControllerRequestParam;
|
||||
import org.apache.hertzbeat.manager.service.ai.AiService;
|
||||
import org.apache.hertzbeat.manager.service.ai.factory.AiServiceFactoryImpl;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* AI Management API
|
||||
*/
|
||||
@Tag(name = "AI Manage API")
|
||||
@RestController
|
||||
@RequestMapping(value = "/api/ai")
|
||||
public class AiController {
|
||||
|
||||
/**
|
||||
* AI beanFactory
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
private AiServiceFactoryImpl aiServiceFactory;
|
||||
|
||||
@Autowired
|
||||
private AiProperties aiProperties;
|
||||
|
||||
/**
|
||||
* request AI
|
||||
* @param requestParam request text
|
||||
* @return AI response
|
||||
*/
|
||||
@PostMapping(path = "/get", produces = {TEXT_EVENT_STREAM_VALUE})
|
||||
@Operation(summary = "Artificial intelligence questions and Answers",
|
||||
description = "Artificial intelligence questions and Answers")
|
||||
public Flux<ServerSentEvent<String>> requestAi(@Parameter(description = "Request text", example = "Who are you")
|
||||
@RequestBody AiControllerRequestParam requestParam) {
|
||||
Assert.notNull(aiServiceFactory, "please check that your type value is consistent with the documentation on the website");
|
||||
AiService aiServiceImplBean = aiServiceFactory.getAiServiceImplBean(aiProperties.getType());
|
||||
return aiServiceImplBean.requestAi(requestParam.getText());
|
||||
}
|
||||
}
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.manager.pojo.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* openAi Request param
|
||||
*/
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public class OpenAiRequestParamDTO {
|
||||
|
||||
/**
|
||||
* ai version
|
||||
*/
|
||||
private String model;
|
||||
|
||||
/**
|
||||
* request message
|
||||
*/
|
||||
private List<AiMessage> messages;
|
||||
|
||||
/**
|
||||
* The sampling temperature, which controls the randomness of the output, must be positive
|
||||
* The value ranges from 0.0 to 1.0, and cannot be equal to 0. The default value is 0.95.
|
||||
* The larger the value, the more random and creative the output will be. The smaller the value, the more stable or certain the output will be
|
||||
* You are advised to adjust top_p or temperature parameters based on application scenarios, but do not adjust the two parameters at the same time
|
||||
*/
|
||||
private float temperature;
|
||||
|
||||
/**
|
||||
* The model outputs the maximum tokens, with a maximum output of 8192 and a default value of 1024
|
||||
*/
|
||||
@JsonProperty("max_tokens")
|
||||
private Integer maxTokens;
|
||||
|
||||
/**
|
||||
* stream response
|
||||
*/
|
||||
private Boolean stream = Boolean.FALSE;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
-115
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.manager.pojo.dto;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
|
||||
/**
|
||||
* openAi Response
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Slf4j
|
||||
public class OpenAiResponse {
|
||||
|
||||
/**
|
||||
* Task order number generated by the AI open platform. Use this order number when invoking the request result interface
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* The request creation time is a Unix timestamp in seconds
|
||||
*/
|
||||
private Long created;
|
||||
|
||||
/**
|
||||
* response message
|
||||
*/
|
||||
private List<Choice> choices;
|
||||
|
||||
/**
|
||||
* Returns the number of tokens invoked by the model at the end.
|
||||
*/
|
||||
private Tokens usage;
|
||||
|
||||
/**
|
||||
* Choice
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Choice {
|
||||
private int index;
|
||||
private AiMessage delta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tokens
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Tokens {
|
||||
|
||||
/**
|
||||
* The number of tokens entered by users
|
||||
*/
|
||||
@JsonProperty("prompt_tokens")
|
||||
private Integer promptTokens;
|
||||
|
||||
/**
|
||||
* The number of tokens that the model outputs
|
||||
*/
|
||||
@JsonProperty("completion_tokens")
|
||||
private Integer completionTokens;
|
||||
|
||||
/**
|
||||
* Total number of tokens
|
||||
*/
|
||||
@JsonProperty("total_tokens")
|
||||
private Integer totalTokens;
|
||||
}
|
||||
|
||||
public static ServerSentEvent<String> convertToResponse(String aiRes) {
|
||||
try {
|
||||
OpenAiResponse openAiResponse = JSON.parseObject(aiRes, OpenAiResponse.class);
|
||||
if (Objects.nonNull(openAiResponse)) {
|
||||
OpenAiResponse.Choice choice = openAiResponse.getChoices().get(0);
|
||||
String content = choice.getDelta().getContent();
|
||||
return ServerSentEvent.<String>builder()
|
||||
.data(content)
|
||||
.build();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.info("convertToResponse Exception:{}", e.toString());
|
||||
throw e;
|
||||
}
|
||||
|
||||
return ServerSentEvent.<String>builder().build();
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -51,6 +51,7 @@ public class TemplateConfig {
|
||||
/**
|
||||
* Is hide this app in main menus layout, only for app type, default true
|
||||
*/
|
||||
@Builder.Default
|
||||
private boolean hide = true;
|
||||
}
|
||||
}
|
||||
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.manager.service.ai;
|
||||
|
||||
|
||||
import org.apache.hertzbeat.common.constants.AiTypeEnum;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
|
||||
/**
|
||||
* AI Service
|
||||
*/
|
||||
public interface AiService {
|
||||
|
||||
/**
|
||||
* get AI type
|
||||
* @return type
|
||||
*/
|
||||
AiTypeEnum getType();
|
||||
|
||||
/**
|
||||
* AI response
|
||||
* @param text text
|
||||
* @return AI response
|
||||
*/
|
||||
Flux<ServerSentEvent<String>> requestAi(String text);
|
||||
|
||||
}
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.manager.service.ai;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import javax.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.constants.AiConstants;
|
||||
import org.apache.hertzbeat.common.constants.AiTypeEnum;
|
||||
import org.apache.hertzbeat.manager.config.AiProperties;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.AiMessage;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.AliAiRequestParamDTO;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.AliAiResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.client.ExchangeStrategies;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
|
||||
/**
|
||||
* alibaba Ai
|
||||
*/
|
||||
@Service("AlibabaAiServiceImpl")
|
||||
@ConditionalOnProperty(prefix = "ai", name = "type", havingValue = "alibabaAi")
|
||||
@Slf4j
|
||||
public class AlibabaAiServiceImpl implements AiService {
|
||||
|
||||
@Autowired
|
||||
private AiProperties aiProperties;
|
||||
|
||||
private WebClient webClient;
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
this.webClient = WebClient.builder()
|
||||
.baseUrl(AiConstants.AliAiConstants.URL)
|
||||
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + aiProperties.getApiKey())
|
||||
//sse
|
||||
.defaultHeader(HttpHeaders.ACCEPT, MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
.exchangeStrategies(ExchangeStrategies.builder()
|
||||
.codecs(item -> item.defaultCodecs().maxInMemorySize(16 * 1024 * 1024))
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AiTypeEnum getType() {
|
||||
return AiTypeEnum.alibabaAi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ServerSentEvent<String>> requestAi(String text) {
|
||||
checkParam(text, aiProperties.getModel(), aiProperties.getApiKey());
|
||||
AliAiRequestParamDTO aliAiRequestParamDTO = AliAiRequestParamDTO.builder()
|
||||
.model(aiProperties.getModel())
|
||||
.input(AliAiRequestParamDTO.Input.builder()
|
||||
.messages(List.of(new AiMessage(AiConstants.AliAiConstants.REQUEST_ROLE, text)))
|
||||
.build())
|
||||
.parameters(AliAiRequestParamDTO.Parameters.builder()
|
||||
.maxTokens(AiConstants.AliAiConstants.MAX_TOKENS)
|
||||
.temperature(AiConstants.AliAiConstants.TEMPERATURE)
|
||||
.enableSearch(true)
|
||||
.resultFormat("message")
|
||||
.incrementalOutput(true)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
return webClient.post()
|
||||
.body(BodyInserters.fromValue(aliAiRequestParamDTO))
|
||||
.retrieve()
|
||||
.bodyToFlux(AliAiResponse.class)
|
||||
.map(aliAiResponse -> {
|
||||
if (Objects.nonNull(aliAiResponse)) {
|
||||
List<AliAiResponse.Choice> choices = aliAiResponse.getOutput().getChoices();
|
||||
if (CollectionUtils.isEmpty(choices)) {
|
||||
return ServerSentEvent.<String>builder().build();
|
||||
}
|
||||
String content = choices.get(0).getMessage().getContent();
|
||||
return ServerSentEvent.<String>builder()
|
||||
.data(content)
|
||||
.build();
|
||||
}
|
||||
return ServerSentEvent.<String>builder().build();
|
||||
})
|
||||
.doOnError(error -> log.info("AlibabaAiServiceImpl.requestAi exception:{}", error.getMessage()));
|
||||
}
|
||||
|
||||
private void checkParam(String param, String apiKey, String model) {
|
||||
Assert.notNull(param, "text is null");
|
||||
Assert.notNull(model, "model is null");
|
||||
Assert.notNull(apiKey, "ai.api-key is null");
|
||||
}
|
||||
}
|
||||
-96
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.manager.service.ai;
|
||||
|
||||
import java.util.List;
|
||||
import javax.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.constants.AiConstants;
|
||||
import org.apache.hertzbeat.common.constants.AiTypeEnum;
|
||||
import org.apache.hertzbeat.manager.config.AiProperties;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.AiMessage;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.OpenAiRequestParamDTO;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.OpenAiResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.client.ExchangeStrategies;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* Kimi Ai
|
||||
*/
|
||||
@Service("KimiAiServiceImpl")
|
||||
@ConditionalOnProperty(prefix = "ai", name = "type", havingValue = "kimiAi")
|
||||
@Slf4j
|
||||
public class KimiAiServiceImpl implements AiService {
|
||||
|
||||
@Autowired
|
||||
private AiProperties aiProperties;
|
||||
|
||||
private WebClient webClient;
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
this.webClient = WebClient.builder()
|
||||
.baseUrl(AiConstants.KimiAiConstants.URL)
|
||||
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + aiProperties.getApiKey())
|
||||
.exchangeStrategies(ExchangeStrategies.builder()
|
||||
.codecs(item -> item.defaultCodecs().maxInMemorySize(16 * 1024 * 1024))
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AiTypeEnum getType() {
|
||||
return AiTypeEnum.kimiAi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ServerSentEvent<String>> requestAi(String text) {
|
||||
checkParam(text, aiProperties.getModel(), aiProperties.getApiKey());
|
||||
OpenAiRequestParamDTO zhiPuRequestParamDTO = OpenAiRequestParamDTO.builder()
|
||||
.model(aiProperties.getModel())
|
||||
.stream(Boolean.TRUE)
|
||||
.maxTokens(AiConstants.KimiAiConstants.MAX_TOKENS)
|
||||
.temperature(AiConstants.KimiAiConstants.TEMPERATURE)
|
||||
.messages(List.of(new AiMessage(AiConstants.KimiAiConstants.REQUEST_ROLE, text)))
|
||||
.build();
|
||||
|
||||
return webClient.post()
|
||||
.body(BodyInserters.fromValue(zhiPuRequestParamDTO))
|
||||
.retrieve()
|
||||
.bodyToFlux(String.class)
|
||||
.filter(aiResponse -> !"[DONE]".equals(aiResponse))
|
||||
.map(OpenAiResponse::convertToResponse)
|
||||
.doOnError(error -> log.info("KimiAiServiceImpl.requestAi exception:{}", error.getMessage()));
|
||||
}
|
||||
|
||||
private void checkParam(String param, String model, String apiKey) {
|
||||
Assert.notNull(param, "text is null");
|
||||
Assert.notNull(model, "model is null");
|
||||
Assert.notNull(apiKey, "ai.api-key is null");
|
||||
}
|
||||
}
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.manager.service.ai;
|
||||
|
||||
import io.jsonwebtoken.lang.Assert;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.constants.AiConstants;
|
||||
import org.apache.hertzbeat.common.constants.AiTypeEnum;
|
||||
import org.apache.hertzbeat.manager.config.AiProperties;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.AiMessage;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.OpenAiRequestParamDTO;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.OpenAiResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.client.ExchangeStrategies;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Ollama service
|
||||
*/
|
||||
@Service("OllamaServiceImpl")
|
||||
@ConditionalOnProperty(prefix = "ai", name = "type", havingValue = "ollama")
|
||||
@Slf4j
|
||||
public class OllamaAiService implements AiService{
|
||||
@Autowired
|
||||
private AiProperties aiProperties;
|
||||
|
||||
private WebClient webClient;
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
Assert.notNull(aiProperties.getApiUrl(), "Ollama API URL is null");
|
||||
this.webClient = WebClient.builder()
|
||||
.baseUrl(aiProperties.getApiUrl())
|
||||
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
.exchangeStrategies(ExchangeStrategies.builder()
|
||||
.codecs(item -> item.defaultCodecs().maxInMemorySize(16 * 1024 * 1024))
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AiTypeEnum getType() {
|
||||
return AiTypeEnum.ollama;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ServerSentEvent<String>> requestAi(String text) {
|
||||
checkParam(text, aiProperties.getModel());
|
||||
OpenAiRequestParamDTO ollamaParam = OpenAiRequestParamDTO.builder()
|
||||
.model(aiProperties.getModel())
|
||||
.stream(Boolean.TRUE)
|
||||
.maxTokens(AiConstants.OllamaConstants.MAX_TOKENS)
|
||||
.temperature(AiConstants.OllamaConstants.TEMPERATURE)
|
||||
.messages(List.of(new AiMessage(AiConstants.OllamaConstants.REQUEST_ROLE, text)))
|
||||
.build();
|
||||
|
||||
return webClient.post()
|
||||
.body(BodyInserters.fromValue(ollamaParam))
|
||||
.retrieve()
|
||||
.bodyToFlux(String.class)
|
||||
.filter(aiResponse -> !"[DONE]".equals(aiResponse))
|
||||
.map(OpenAiResponse::convertToResponse)
|
||||
.doOnError(error -> log.info("OllamaAiService.requestAi exception:{}", error.getMessage()));
|
||||
}
|
||||
|
||||
private void checkParam(String param, String model) {
|
||||
Assert.notNull(param, "text is null");
|
||||
Assert.notNull(model, "model is null");
|
||||
}
|
||||
}
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.manager.service.ai;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.constants.AiConstants;
|
||||
import org.apache.hertzbeat.common.constants.AiTypeEnum;
|
||||
import org.apache.hertzbeat.manager.config.AiProperties;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.AiMessage;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.OpenAiRequestParamDTO;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.OpenAiResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.client.ExchangeStrategies;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* OpenRouter service
|
||||
*/
|
||||
@Service("OpenRouterServiceImpl")
|
||||
@ConditionalOnProperty(prefix = "ai", name = "type", havingValue = "openRouter")
|
||||
@Slf4j
|
||||
public class OpenRouterServiceImpl implements AiService {
|
||||
|
||||
@Autowired
|
||||
private AiProperties aiProperties;
|
||||
|
||||
private WebClient webClient;
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
this.webClient = WebClient.builder()
|
||||
.baseUrl(AiConstants.OpenRouterConstants.URL)
|
||||
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + aiProperties.getApiKey())
|
||||
.exchangeStrategies(ExchangeStrategies.builder()
|
||||
.codecs(item -> item.defaultCodecs().maxInMemorySize(16 * 1024 * 1024))
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AiTypeEnum getType() {
|
||||
return AiTypeEnum.openRouter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ServerSentEvent<String>> requestAi(String text) {
|
||||
checkParam(text, aiProperties.getModel(), aiProperties.getApiKey());
|
||||
OpenAiRequestParamDTO openRouterParam = OpenAiRequestParamDTO.builder()
|
||||
.model(aiProperties.getModel())
|
||||
.stream(Boolean.TRUE)
|
||||
.maxTokens(AiConstants.OpenRouterConstants.MAX_TOKENS)
|
||||
.temperature(AiConstants.OpenRouterConstants.TEMPERATURE)
|
||||
.messages(List.of(new AiMessage(AiConstants.OpenRouterConstants.REQUEST_ROLE, text)))
|
||||
.build();
|
||||
|
||||
return webClient.post()
|
||||
.body(BodyInserters.fromValue(openRouterParam))
|
||||
.retrieve()
|
||||
.bodyToFlux(String.class)
|
||||
.filter(aiResponse -> !"[DONE]".equals(aiResponse))
|
||||
.map(OpenAiResponse::convertToResponse)
|
||||
.doOnError(error -> log.info("OpenRouterAiServiceImpl.requestAi exception:{}", error.getMessage()));
|
||||
}
|
||||
|
||||
private void checkParam(String param, String model, String apiKey) {
|
||||
Assert.notNull(param, "text is null");
|
||||
Assert.notNull(model, "model is null");
|
||||
Assert.notNull(apiKey, "ai.api-key is null");
|
||||
}
|
||||
}
|
||||
-104
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* 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.hertzbeat.manager.service.ai;
|
||||
|
||||
import java.util.List;
|
||||
import javax.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.constants.AiConstants;
|
||||
import org.apache.hertzbeat.common.constants.AiTypeEnum;
|
||||
import org.apache.hertzbeat.manager.config.AiProperties;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.AiMessage;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.OpenAiRequestParamDTO;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.OpenAiResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.ServerSentEvent;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.client.ExchangeStrategies;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
|
||||
/**
|
||||
* sparkDesk AI
|
||||
*/
|
||||
@Service("SparkDeskAiServiceImpl")
|
||||
@ConditionalOnProperty(prefix = "ai", name = "type", havingValue = "sparkDesk")
|
||||
@Slf4j
|
||||
public class SparkDeskAiServiceImpl implements AiService {
|
||||
|
||||
@Autowired
|
||||
private AiProperties aiProperties;
|
||||
|
||||
private WebClient webClient;
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String bearer = sb.append("Bearer ")
|
||||
.append(aiProperties.getApiKey())
|
||||
.append(":").append(aiProperties.getApiSecret()).toString();
|
||||
|
||||
this.webClient = WebClient.builder()
|
||||
.baseUrl(AiConstants.SparkDeskConstants.SPARK_ULTRA_URL)
|
||||
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
.defaultHeader(HttpHeaders.AUTHORIZATION, bearer)
|
||||
.exchangeStrategies(ExchangeStrategies.builder()
|
||||
.codecs(item -> item.defaultCodecs().maxInMemorySize(16 * 1024 * 1024))
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AiTypeEnum getType() {
|
||||
return AiTypeEnum.sparkDesk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ServerSentEvent<String>> requestAi(String text) {
|
||||
checkParam(text, aiProperties.getApiKey(), aiProperties.getModel());
|
||||
OpenAiRequestParamDTO zhiPuRequestParamDTO = OpenAiRequestParamDTO.builder()
|
||||
.model(aiProperties.getModel())
|
||||
//sse
|
||||
.stream(Boolean.TRUE)
|
||||
.maxTokens(AiConstants.SparkDeskConstants.MAX_TOKENS)
|
||||
.temperature(AiConstants.SparkDeskConstants.TEMPERATURE)
|
||||
.messages(List.of(new AiMessage(AiConstants.SparkDeskConstants.REQUEST_ROLE, text)))
|
||||
.build();
|
||||
|
||||
return webClient.post()
|
||||
.body(BodyInserters.fromValue(zhiPuRequestParamDTO))
|
||||
.retrieve()
|
||||
.bodyToFlux(String.class)
|
||||
.filter(aiResponse -> !"[DONE]".equals(aiResponse))
|
||||
.map(OpenAiResponse::convertToResponse)
|
||||
.doOnError(error -> log.info("SparkDeskAiServiceImpl.requestAi exception:{}", error.getMessage()));
|
||||
|
||||
}
|
||||
|
||||
private void checkParam(String param, String apiKey, String model) {
|
||||
Assert.notNull(param, "text is null");
|
||||
Assert.notNull(model, "model is null");
|
||||
Assert.notNull(apiKey, "ai.api-key is null");
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user