-
Notifications
You must be signed in to change notification settings - Fork 1.2k
[improve] optimize the function of creating monitoring using AI #3922
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR enhances the AI-powered monitoring creation functionality by introducing a secure form mechanism for collecting sensitive parameters (passwords, keys, tokens, etc.) separately from regular conversation flow. The implementation includes frontend UI changes, backend API endpoints for encrypted data storage, and comprehensive AI prompt updates to guide secure parameter collection.
Key Changes:
- Adds secure form modal for collecting sensitive parameters with AES encryption
- Updates AI system prompts with detailed security guidelines and secure interaction protocols
- Integrates encrypted security data storage into the monitor creation workflow
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 20 comments.
Show a summary per file
| File | Description |
|---|---|
| web-app/src/assets/i18n/*.json | Adds translations for security form UI in 5 languages (en-US, zh-CN, zh-TW, ja-JP, pt-BR) |
| web-app/src/app/shared/shared.module.ts | Removes AiChatModule from SharedModule to resolve circular dependency |
| web-app/src/app/shared/components/ai-chat/chat.component.ts | Implements security form modal logic, parameter parsing, and encrypted data submission |
| web-app/src/app/shared/components/ai-chat/chat.component.html | Adds security form modal UI with parameter input fields |
| web-app/src/app/shared/components/ai-chat/ai-chat.module.ts | Imports SharedModule to access form components |
| web-app/src/app/service/ai-chat.service.ts | Adds SecurityForm interface, DEFAULT_SECURITY_FORM constant, and saveSecurityData API method |
| hertzbeat-common/.../ChatConversation.java | Adds securityData field to persist encrypted sensitive parameters |
| hertzbeat-ai/.../prompt/system-message.st | Extensive prompt updates with security principles, secure interaction protocols, and parameter classification guidelines |
| hertzbeat-ai/.../MonitorToolsImpl.java | Updates addMonitor to retrieve and merge encrypted security parameters from conversation |
| hertzbeat-ai/.../MonitorTools.java | Adds conversationId parameter to addMonitor method signature |
| hertzbeat-ai/.../ConversationServiceImpl.java | Implements saveSecurityData method with AES encryption |
| hertzbeat-ai/.../ChatClientProviderServiceImpl.java | Updates system prompt template loading to pass conversationId metadata |
| hertzbeat-ai/.../ConversationService.java | Adds saveSecurityData method to service interface |
| hertzbeat-ai/.../SecurityData.java | New DTO for security data submission with conversationId and encrypted data |
| hertzbeat-ai/.../ChatController.java | Adds POST /security endpoint for saving encrypted security data |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| .subscribe((message: any) => { | ||
| if (message.code === 0) { | ||
| const lastMessage = this.messages[this.messages.length - 1]; | ||
| lastMessage.securityForm.complete = true; | ||
| const tmpMessage = this.newMessage; | ||
| this.newMessage = this.i18nSvc.fanyi('ai.chat.security.form.default.callback'); | ||
| this.sendMessage(); | ||
| this.newMessage = tmpMessage; | ||
| } else { | ||
| console.log('Error saving security data:'); | ||
| } | ||
| }); |
Copilot
AI
Dec 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing error handler for the saveSecurityData observable. If the HTTP request fails due to network issues or server errors, there's no error callback to handle it. This could leave the modal in an inconsistent state. Add an error handler in the subscribe method to handle failures gracefully.
| .subscribe((message: any) => { | |
| if (message.code === 0) { | |
| const lastMessage = this.messages[this.messages.length - 1]; | |
| lastMessage.securityForm.complete = true; | |
| const tmpMessage = this.newMessage; | |
| this.newMessage = this.i18nSvc.fanyi('ai.chat.security.form.default.callback'); | |
| this.sendMessage(); | |
| this.newMessage = tmpMessage; | |
| } else { | |
| console.log('Error saving security data:'); | |
| } | |
| }); | |
| .subscribe( | |
| (message: any) => { | |
| if (message.code === 0) { | |
| const lastMessage = this.messages[this.messages.length - 1]; | |
| lastMessage.securityForm.complete = true; | |
| const tmpMessage = this.newMessage; | |
| this.newMessage = this.i18nSvc.fanyi('ai.chat.security.form.default.callback'); | |
| this.sendMessage(); | |
| this.newMessage = tmpMessage; | |
| } else { | |
| console.log('Error saving security data:'); | |
| } | |
| }, | |
| (error: any) => { | |
| console.error('Error saving security data:', error); | |
| } | |
| ); |
| @JoinColumn(name = "conversation_id") | ||
| private List<ChatMessage> messages; | ||
|
|
||
| private String securityData; |
Copilot
AI
Dec 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The securityData field is added to ChatConversation entity without database migration scripts or schema updates. Ensure that the database schema is updated appropriately with proper migration scripts (e.g., Flyway or Liquibase) to add this column, and verify that existing conversations can handle null values for this field.
| @PostMapping(path = "/security") | ||
| @Operation(summary = "save security data", description = "Save security data") | ||
| public ResponseEntity<Message<Boolean>> commitSecurityData(@Valid @RequestBody SecurityData securityData) { | ||
| return ResponseEntity.ok(Message.success(conversationService.saveSecurityData(securityData))); | ||
| } |
Copilot
AI
Dec 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing input validation. The SecurityData object should be validated for null conversationId and empty/null securityData before processing. Add validation annotations like @NotNull on the conversationId field and @notblank on securityData field in the SecurityData class, or add explicit validation in the controller method.
| return chatClient.prompt() | ||
| .messages(messages) | ||
| .system(SystemPromptTemplate.builder().resource(systemResource).build().getTemplate()) | ||
| .system(SystemPromptTemplate.builder().resource(systemResource).build().create(metadata).getContents()) |
Copilot
AI
Dec 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change from getTemplate() to create(metadata).getContents() appears to use an API that may not exist or may not be the correct method. The typical Spring AI SystemPromptTemplate API uses createMessage(Map) or render(Map). Verify that this method chain is correct for the version of Spring AI being used.
| .system(SystemPromptTemplate.builder().resource(systemResource).build().create(metadata).getContents()) | |
| .system(SystemPromptTemplate.builder().resource(systemResource).build().createMessage(metadata)) |
| .subscribe((message: any) => { | ||
| if (message.code === 0) { | ||
| const lastMessage = this.messages[this.messages.length - 1]; | ||
| lastMessage.securityForm.complete = true; | ||
| const tmpMessage = this.newMessage; | ||
| this.newMessage = this.i18nSvc.fanyi('ai.chat.security.form.default.callback'); | ||
| this.sendMessage(); | ||
| this.newMessage = tmpMessage; | ||
| } else { | ||
| console.log('Error saving security data:'); | ||
| } | ||
| }); |
Copilot
AI
Dec 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error handling here only logs to console without showing any user feedback. When security data fails to save, users should be notified through a message service. Consider adding this.message.error() to display an error message to the user, similar to other error handling in this component.
| .subscribe((message: any) => { | |
| if (message.code === 0) { | |
| const lastMessage = this.messages[this.messages.length - 1]; | |
| lastMessage.securityForm.complete = true; | |
| const tmpMessage = this.newMessage; | |
| this.newMessage = this.i18nSvc.fanyi('ai.chat.security.form.default.callback'); | |
| this.sendMessage(); | |
| this.newMessage = tmpMessage; | |
| } else { | |
| console.log('Error saving security data:'); | |
| } | |
| }); | |
| .subscribe( | |
| (message: any) => { | |
| if (message.code === 0) { | |
| const lastMessage = this.messages[this.messages.length - 1]; | |
| lastMessage.securityForm.complete = true; | |
| const tmpMessage = this.newMessage; | |
| this.newMessage = this.i18nSvc.fanyi('ai.chat.security.form.default.callback'); | |
| this.sendMessage(); | |
| this.newMessage = tmpMessage; | |
| } else { | |
| console.error('Error saving security data:', message); | |
| this.message.error('Failed to save security data.'); | |
| } | |
| }, | |
| (error: any) => { | |
| console.error('Error saving security data:', error); | |
| this.message.error('Failed to save security data.'); | |
| } | |
| ); |
| List<Param> securityParams = JsonUtil.fromJson( | ||
| AesUtil.aesDecode(chatConversation.get().getSecurityData()), | ||
| new TypeReference<List<Param>>() { | ||
| }); | ||
| if (CollectionUtils.isNotEmpty(securityParams)) { | ||
| paramList.addAll(securityParams); |
Copilot
AI
Dec 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No error handling for AES decryption failure. If the securityData is corrupted or was encrypted with a different key, AesUtil.aesDecode() could throw an exception that's not caught here. Add try-catch to handle decryption failures gracefully and provide appropriate error messages.
| List<Param> securityParams = JsonUtil.fromJson( | |
| AesUtil.aesDecode(chatConversation.get().getSecurityData()), | |
| new TypeReference<List<Param>>() { | |
| }); | |
| if (CollectionUtils.isNotEmpty(securityParams)) { | |
| paramList.addAll(securityParams); | |
| try { | |
| List<Param> securityParams = JsonUtil.fromJson( | |
| AesUtil.aesDecode(chatConversation.get().getSecurityData()), | |
| new TypeReference<List<Param>>() { | |
| }); | |
| if (CollectionUtils.isNotEmpty(securityParams)) { | |
| paramList.addAll(securityParams); | |
| } | |
| } catch (Exception e) { | |
| log.error("Failed to decrypt or parse security data for conversationId {}", conversationId, e); | |
| return "Error: Failed to decrypt stored secure parameters. Please reconfigure secure parameters for this conversation."; |
| @ToolParam(description = "The id for current conversation", required = true) Long conversationId, | ||
| @ToolParam(description = "Monitor name (required)", required = true) String name, | ||
| @ToolParam(description = "Monitor type: website, mysql, postgresql, redis, linux, windows, etc.", required = true) String app, | ||
| @ToolParam(description = "Collection interval in seconds (default: 600)", required = false) Integer intervals, | ||
| @ToolParam(description = "Monitor-specific parameters as JSON string. " | ||
| + "Use get_monitor_additional_params to see required fields. " | ||
| + "Example: {\"host\":\"192.168.1.1\", \"port\":\"22\", \"username\":\"root\"}", | ||
| required = true) String params, | ||
| @ToolParam(description = "Monitor description (optional)", required = false) String description) { |
Copilot
AI
Dec 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The addMonitor method now requires a conversationId parameter but there's no validation to ensure it's not null. If conversationId is null, the code will fail when trying to query the conversation. Add validation to check that conversationId is provided and valid before proceeding with monitor creation.
| this.securityParamDefine = JSON.parse(securityForm.param).privateParams.map((i: any) => { | ||
| this.securityParams[i.field] = { | ||
| // Parameter type 0: number 1: string 2: encrypted string 3: json string mapped by map | ||
| type: i.type === 'number' ? 0 : i.type === 'text' || i.type === 'string' ? 1 : i.type === 'json' ? 3 : 2, | ||
| field: i.field, | ||
| paramValue: null | ||
| }; | ||
| i.name = i.name[this.i18nSvc.defaultLang]; | ||
| return i; | ||
| }); |
Copilot
AI
Dec 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing JSON parse error handling. If securityForm.param contains invalid JSON, JSON.parse() will throw an uncaught exception that crashes the component. Add try-catch error handling to gracefully handle malformed JSON and provide user feedback.
| field: i.field, | ||
| paramValue: null | ||
| }; | ||
| i.name = i.name[this.i18nSvc.defaultLang]; |
Copilot
AI
Dec 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code assumes i.name[this.i18nSvc.defaultLang] will always exist, but if the AI response doesn't include a translation for the current language, this will result in undefined. Consider adding a fallback to a default language (e.g., 'en-US') or the original name property to prevent displaying undefined to users.
| i.name = i.name[this.i18nSvc.defaultLang]; | |
| i.name = i.name[this.i18nSvc.defaultLang] || i.name['en-US'] || i.name; |
| private boolean isConfigured = false; | ||
|
|
||
| @Value("classpath:/prompt/system-message.st") | ||
| @Value("classpath:/prompt/system-message-improve.st") |
Copilot
AI
Dec 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The system prompt template file path was changed from 'system-message.st' to 'system-message-improve.st', but based on the directory listing, only 'system-message.st' exists. This indicates either the file should be renamed to 'system-message-improve.st' or this line should revert to use 'system-message.st'. The current diff shows changes to system-message.st, suggesting the @value annotation should remain as 'system-message.st'.
| @Value("classpath:/prompt/system-message-improve.st") | |
| @Value("classpath:/prompt/system-message.st") |
| User: "I want to monitor my MySQL database" | ||
| AI: (Uses `list_monitor_types` and `get_monitor_additional_params`) | ||
| AI: "I can help you set up MySQL monitoring. First, I need some information: | ||
| 1. What is the database host address?" | ||
| User: "192.168.1.10" | ||
| AI: "Port number (default 3306)?" | ||
| User: "3306" | ||
| AI: "What name would you like for this monitor?" | ||
| User: "Production Database" | ||
| AI: "Check interval in seconds (recommended 60)?" | ||
| User: "60" | ||
| AI: "[Secure Form Required] | ||
| Monitor type: mysql | ||
| Public parameters collected: | ||
| - host: 192.168.1.10 | ||
| - port: 3306 | ||
| - name: Production Database | ||
| - interval: 60 | ||
| Private parameters requiring secure collection: | ||
| - username (database username) | ||
| - password (database password) | ||
| Please complete configuration via the secure form." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, although it looks like the user is being guided step by step to fill in the information, in reality each step is a brand-new LLM call. This unnecessarily consumes tokens and increases repeated context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The suggestion is to shorten the steps. If the current context does not contain the required parameter values, the AI should ask the user for all parameters at once, and let the user provide them in a single input before proceeding. If the user’s prompt already includes some parameters, they can be added directly.
| + "Example: {\"host\":\"192.168.1.1\", \"port\":\"22\", \"username\":\"root\"}", | ||
| required = true) String params, | ||
| @ToolParam(description = "Monitor description (optional)", required = false) String description) { | ||
| @ToolParam(description = "The id for current conversation", required = true) Long conversationId, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hi I found a new tool param conversationId was added. When we use it as MCP server in agent like claude code, how it can get the conversationId?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I overlooked the compatibility issue when the tool is called by MCP. I should provide an additional tool through enhancing the existing add_monitor tool, so that it can be invoked in scenarios where monitoring is added using the secure form method, such as add_monitor_protected. The conversationId parameter is only used in this tool.
|
Hi, i think whether it is provided to the built-in HertzBeat AI or exposed as an MCP tool for external agents, the tool’s capabilities should be atomic, with the AI model deciding how to orchestrate and invoke them. The design should not be driven by traditional user UI interactions, but by agent-centric thinking. The required data may be collected in multiple ways—for example, the AI may gather it from the user’s prompt, or an agent may read it in bulk from text and then add it. |
I agree with your point that tools provided for AI calls should have higher versatility. I think we could offer some options to users, when they use ai assistant in hertzbeat, allowing them to choose the mode of AI involvement. For now, let’s refer to these as Protected Mode and Trusted Mode. In Protected Mode, we could optimize the usage methods and data security based on general-purpose tools, enabling AI to integrate deeply into Hertzbeat. In Trusted Mode, the process is entirely handed over to AI, allowing it to fully utilize the existing general-purpose tools to operate Hertzbeat. |
HI, the two modes seem like a good approach, would this correspond to two sets of Function Tools? Our current AI features are rapidly evolving, so it is recommended to use a single set of tools to achieve the desired consistency. |
Yes, only one toolset is used. The tools needed for the other mode are simply wrappers and enhancements on the existing tools. Regarding security, in protected mode, private data is not sent to the LLM. After being collected via forms, parameters are populated when the AI calls the |
What's changed?
Checklist
Add or update API