refactor: overhaul agent workflow engine with scoped tool execution, configurable LLM model profiles, and enhanced state management.
This commit is contained in:
@@ -5,6 +5,7 @@ import dev.sonpx.loyalty.mcp.model.Result;
|
||||
import dev.sonpx.loyalty.mcp.reward.criteria.CampaignRuleCriteria;
|
||||
import dev.sonpx.loyalty.mcp.reward.model.CampaignRule;
|
||||
import dev.sonpx.loyalty.mcp.service.CampaignRuleService;
|
||||
import dev.sonpx.loyalty.mcp.util.SearchUtils;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.ai.mcp.annotation.McpTool;
|
||||
@@ -21,19 +22,20 @@ public class CampaignRuleTools {
|
||||
|
||||
@McpTool(description = """
|
||||
TÌM KIẾM THỂ LỆ / RULE của chiến dịch. Trả về: tên rule, loại rule, chiến dịch gốc, công thức thưởng.
|
||||
Dùng khi người dùng hỏi: "thể lệ", "rule", "quy tắc", "điều kiện", "công thức thưởng", "cách tính điểm".
|
||||
KHÔNG dùng tool này khi người dùng chỉ hỏi về thông tin cơ bản chiến dịch (tên, owner, loại) → hãy dùng tool searchCampaigns.
|
||||
- Param `search` (String): từ khóa tìm kiếm theo tên hoặc mã rule cụ thể.
|
||||
- QUAN TRỌNG: Khi người dùng hỏi lấy danh sách chung ("danh sách rule", "xem thể lệ"), KHÔNG truyền param `search` (để null hoặc rỗng ""). Tuyệt đối KHÔNG truyền các từ chung như "rule", "thể lệ", "danh sách" vào `search`.
|
||||
- KHÔNG dùng tool này khi người dùng chỉ hỏi về thông tin cơ bản chiến dịch (tên, owner, loại) → hãy dùng tool searchCampaigns.
|
||||
Hiển thị tối đa 10 bản ghi.""")
|
||||
public Result<List<CampaignRule>> searchRules(String search) {
|
||||
CampaignRuleCriteria criteria = new CampaignRuleCriteria();
|
||||
criteria.setSearch(search);
|
||||
criteria.setSearch(SearchUtils.sanitizeSearch(search));
|
||||
return campaignRuleService.getAll(criteria);
|
||||
}
|
||||
|
||||
@McpTool(description = "Đếm số lượng RULE/THỂ LỆ, không phải chiến dịch. Cho phép tìm kiếm với param search.")
|
||||
@McpTool(description = "Đếm số lượng RULE/THỂ LỆ, không phải chiến dịch. Để search = null nếu đếm tất cả.")
|
||||
public Result<Long> countRules(String search) {
|
||||
CampaignRuleCriteria criteria = new CampaignRuleCriteria();
|
||||
criteria.setSearch(search);
|
||||
criteria.setSearch(SearchUtils.sanitizeSearch(search));
|
||||
return campaignRuleService.count(criteria);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import dev.sonpx.loyalty.mcp.model.Result;
|
||||
import dev.sonpx.loyalty.mcp.reward.criteria.CampaignCriteria;
|
||||
import dev.sonpx.loyalty.mcp.reward.model.Campaign;
|
||||
import dev.sonpx.loyalty.mcp.service.CampaignService;
|
||||
import dev.sonpx.loyalty.mcp.util.SearchUtils;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.ai.mcp.annotation.McpTool;
|
||||
@@ -21,18 +22,19 @@ public class CampaignTools {
|
||||
|
||||
@McpTool(description = """
|
||||
TÌM KIẾM CHIẾN DỊCH (Campaign). Trả về thông tin CƠ BẢN của chiến dịch: tên, mã, owner, loại, thời gian.
|
||||
Dùng khi người dùng hỏi: "danh sách chiến dịch", "tìm chiến dịch", "chiến dịch nào", "campaign nào".
|
||||
KHÔNG dùng tool này khi người dùng hỏi về: rule, thể lệ, quy tắc, điều kiện, công thức thưởng → hãy dùng tool searchRules.""")
|
||||
- Param `search` (String): từ khóa tìm kiếm theo tên hoặc mã chiến dịch cụ thể.
|
||||
- QUAN TRỌNG: Khi người dùng hỏi lấy danh sách chung ("danh sách chiến dịch", "xem các chiến dịch"), KHÔNG truyền param `search` (để null hoặc rỗng ""). Tuyệt đối KHÔNG truyền các từ chung như "chiến dịch", "danh sách", "campaign" vào `search`.
|
||||
- KHÔNG dùng tool này khi người dùng hỏi về: rule, thể lệ, quy tắc, điều kiện, công thức thưởng → hãy dùng tool searchRules.""")
|
||||
public Result<List<Campaign>> searchCampaigns(String search) {
|
||||
CampaignCriteria criteria = new CampaignCriteria();
|
||||
criteria.setSearch(search);
|
||||
criteria.setSearch(SearchUtils.sanitizeSearch(search));
|
||||
return campaignService.getAll(criteria);
|
||||
}
|
||||
|
||||
@McpTool(description = "Đếm số lượng CHIẾN DỊCH (Campaign), không phải rule. Cho phép tìm kiếm với param search.")
|
||||
@McpTool(description = "Đếm số lượng CHIẾN DỊCH (Campaign), không phải rule. Để search = null nếu đếm tất cả.")
|
||||
public Result<Long> countCampaigns(String search) {
|
||||
CampaignCriteria criteria = new CampaignCriteria();
|
||||
criteria.setSearch(search);
|
||||
criteria.setSearch(SearchUtils.sanitizeSearch(search));
|
||||
return campaignService.count(criteria);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package dev.sonpx.loyalty.mcp.util;
|
||||
|
||||
import java.text.Normalizer;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Utility for sanitizing search parameters passed to MCP tools.
|
||||
*/
|
||||
public class SearchUtils {
|
||||
|
||||
private static final Set<String> GENERIC_KEYWORDS = Set.of(
|
||||
"chien dich", "chiendich",
|
||||
"danh sach", "danhsach",
|
||||
"campaign", "campaigns",
|
||||
"rule", "rules",
|
||||
"the le", "thele",
|
||||
"tat ca", "tatca",
|
||||
"tat ca chien dich",
|
||||
"danh sach chien dich",
|
||||
"danh sach rule",
|
||||
"danh sach the le",
|
||||
"list", "show", "xem"
|
||||
);
|
||||
|
||||
/**
|
||||
* Sanitizes the input search string.
|
||||
* If the search term consists only of generic query noise (e.g. "chiến dịch", "danh sách chiến dịch", "campaign"),
|
||||
* returns null so the API returns all items rather than filtering by the word "chiến dịch".
|
||||
*
|
||||
* @param search the raw search string from LLM tool call
|
||||
* @return cleaned search string or null if generic
|
||||
*/
|
||||
public static String sanitizeSearch(String search) {
|
||||
if (search == null || search.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String trimmed = search.trim();
|
||||
String normalized = removeAccents(trimmed.toLowerCase());
|
||||
|
||||
if (GENERIC_KEYWORDS.contains(normalized)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Strip leading/trailing generic intent words
|
||||
String cleaned = normalized
|
||||
.replaceAll("^(danh sach|chien dich|campaign|rule|the le|tat ca|xem|list)\\s+", "")
|
||||
.replaceAll("\\s+(danh sach|chien dich|campaign|rule|the le|tat ca)$", "")
|
||||
.trim();
|
||||
|
||||
if (cleaned.isEmpty() || GENERIC_KEYWORDS.contains(cleaned)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
private static String removeAccents(String src) {
|
||||
return Normalizer.normalize(src, Normalizer.Form.NFD)
|
||||
.replaceAll("\\p{M}", "")
|
||||
.replace("đ", "d")
|
||||
.replace("Đ", "d");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package dev.sonpx.loyalty.mcp.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class SearchUtilsTest {
|
||||
|
||||
@Test
|
||||
void testGenericKeywordsReturnNull() {
|
||||
assertNull(SearchUtils.sanitizeSearch("chiến dịch"));
|
||||
assertNull(SearchUtils.sanitizeSearch("danh sách chiến dịch"));
|
||||
assertNull(SearchUtils.sanitizeSearch("campaign"));
|
||||
assertNull(SearchUtils.sanitizeSearch("rule"));
|
||||
assertNull(SearchUtils.sanitizeSearch("thể lệ"));
|
||||
assertNull(SearchUtils.sanitizeSearch("danh sách rule"));
|
||||
assertNull(SearchUtils.sanitizeSearch("tất cả chiến dịch"));
|
||||
assertNull(SearchUtils.sanitizeSearch(""));
|
||||
assertNull(SearchUtils.sanitizeSearch(" "));
|
||||
assertNull(SearchUtils.sanitizeSearch(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSpecificKeywordsPreserved() {
|
||||
assertEquals("Sinh Nhật", SearchUtils.sanitizeSearch("Sinh Nhật"));
|
||||
assertEquals("HAPPY BIRTHDAY", SearchUtils.sanitizeSearch("HAPPY BIRTHDAY"));
|
||||
assertEquals("Chiến dịch Sinh Nhật", SearchUtils.sanitizeSearch("Chiến dịch Sinh Nhật"));
|
||||
assertEquals("MUA HÀNG TÍCH ĐIỂM", SearchUtils.sanitizeSearch("MUA HÀNG TÍCH ĐIỂM"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user