diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetDomainsTool.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetDomainsTool.java
new file mode 100644
index 0000000..c53fcf0
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetDomainsTool.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.agent.tools;
+
+import io.agentscope.core.tool.Tool;
+import io.github.malonetalk.service.DomainService;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@AllArgsConstructor
+public class GetDomainsTool implements MarkAgentTool {
+
+ private final DomainService domainService;
+
+ @Tool(
+ name = "get_domains",
+ description =
+ "Get available data domains in the datasource. Call this tool first to discover"
+ + " what domains are available before querying tables.")
+ public List getDomains() {
+ return domainService.listDomainNames();
+ }
+}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java
index c98eb91..3ee500a 100644
--- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java
@@ -18,6 +18,7 @@
package io.github.malonetalk.agent.tools;
import io.agentscope.core.tool.Tool;
+import io.agentscope.core.tool.ToolParam;
import io.github.malonetalk.entity.Datasource;
import io.github.malonetalk.entity.TableInfo;
import io.github.malonetalk.enums.Status;
@@ -37,8 +38,22 @@ public class GetTablesTool implements MarkAgentTool {
private final DatasourceService dataSourceService;
private final TableSemanticService tableSemanticService;
- @Tool(name = "get_tables", description = "获取数据库中的表信息,包括表名和表描述")
- public List getTables() {
+ @Tool(
+ name = "get_tables",
+ description =
+ "Get table information from the database, including table name and description."
+ + " Optionally filter by domains; returns all tables if domains is not"
+ + " provided. It is recommended to call get_domains first to discover"
+ + " available domains.")
+ public List getTables(
+ @ToolParam(
+ name = "domains",
+ description =
+ "Optional list of domain names. Only tables belonging to these"
+ + " domains will be returned. If not provided or empty,"
+ + " returns all tables.",
+ required = false)
+ List domains) {
List activeDataSources =
dataSourceService.findByStatus(Status.ACTIVE.getCode());
@@ -54,6 +69,6 @@ public List getTables() {
}
Datasource dataSource = activeDataSources.get(0);
- return tableSemanticService.listTableInfosByDatasourceId(dataSource.getId());
+ return tableSemanticService.listTableInfosByDomains(dataSource.getId(), domains);
}
}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/common/SemanticConstants.java b/data-agent-backend/src/main/java/io/github/malonetalk/common/SemanticConstants.java
index 66a72ba..4f08ded 100644
--- a/data-agent-backend/src/main/java/io/github/malonetalk/common/SemanticConstants.java
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/common/SemanticConstants.java
@@ -22,6 +22,7 @@ public final class SemanticConstants {
public static final String RELATION_KEY_SEPARATOR = "|";
public static final String SORT_ORDER_ASC = "asc";
public static final String SORT_ORDER_DESC = "desc";
+ public static final String DEFAULT_DOMAIN = "default";
private SemanticConstants() {}
}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/config/DataSourceConfig.java b/data-agent-backend/src/main/java/io/github/malonetalk/config/DataSourceConfig.java
index e9e8c23..0a8c7ba 100644
--- a/data-agent-backend/src/main/java/io/github/malonetalk/config/DataSourceConfig.java
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/config/DataSourceConfig.java
@@ -17,6 +17,7 @@
*/
package io.github.malonetalk.config;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
@@ -66,4 +67,9 @@ public DataSource dataSource() {
return new HikariDataSource(config);
}
+
+ @Bean
+ public ObjectMapper objectMapper() {
+ return new ObjectMapper();
+ }
}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/controller/DomainController.java b/data-agent-backend/src/main/java/io/github/malonetalk/controller/DomainController.java
new file mode 100644
index 0000000..3f905c1
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/controller/DomainController.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.controller;
+
+import io.github.malonetalk.common.Result;
+import io.github.malonetalk.dto.DomainCreateRequest;
+import io.github.malonetalk.dto.DomainPageQuery;
+import io.github.malonetalk.dto.DomainUpdateRequest;
+import io.github.malonetalk.dto.pagination.PageResponse;
+import io.github.malonetalk.entity.DomainInfo;
+import io.github.malonetalk.service.DomainService;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api/domains")
+@RequiredArgsConstructor
+public class DomainController {
+
+ private final DomainService domainService;
+
+ @GetMapping
+ public Result> findDomains(@Valid DomainPageQuery query) {
+ return Result.success(domainService.getDomainPage(query));
+ }
+
+ @GetMapping("/{id}")
+ public Result findById(@PathVariable Integer id) {
+ if (id == null || id < 0) {
+ return Result.error(400, "ID必须为非负数");
+ }
+ DomainInfo domain = domainService.findById(id);
+ if (domain == null) {
+ return Result.error(404, "领域不存在");
+ }
+ return Result.success(domain);
+ }
+
+ @PostMapping
+ public Result create(@Valid @RequestBody DomainCreateRequest request) {
+ return Result.success(domainService.create(request));
+ }
+
+ @PutMapping("/{id}")
+ public Result update(
+ @PathVariable Integer id, @Valid @RequestBody DomainUpdateRequest request) {
+ if (id == null || id < 0) {
+ return Result.error(400, "ID必须为非负数");
+ }
+ return Result.success(domainService.update(id, request));
+ }
+
+ @DeleteMapping("/{id}")
+ public Result delete(@PathVariable Integer id) {
+ if (id == null || id < 0) {
+ return Result.error(400, "ID必须为非负数");
+ }
+ domainService.delete(id);
+ return Result.success(true);
+ }
+}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/dto/DomainCreateRequest.java b/data-agent-backend/src/main/java/io/github/malonetalk/dto/DomainCreateRequest.java
new file mode 100644
index 0000000..7f77fb6
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/dto/DomainCreateRequest.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.dto;
+
+import jakarta.validation.constraints.NotBlank;
+
+public record DomainCreateRequest(
+ @NotBlank(message = "领域名称不能为空") String name, String description) {}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/dto/DomainPageQuery.java b/data-agent-backend/src/main/java/io/github/malonetalk/dto/DomainPageQuery.java
new file mode 100644
index 0000000..d35bebb
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/dto/DomainPageQuery.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.dto;
+
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.Pattern;
+
+public record DomainPageQuery(
+ @Min(1) Integer page,
+ @Min(1) Integer pageSize,
+ String keyword,
+ @Pattern(regexp = "^(?i)(asc|desc)$", message = "sortOrder must be asc or desc.")
+ String sortOrder) {}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/dto/DomainUpdateRequest.java b/data-agent-backend/src/main/java/io/github/malonetalk/dto/DomainUpdateRequest.java
new file mode 100644
index 0000000..eb6f472
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/dto/DomainUpdateRequest.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.dto;
+
+import jakarta.validation.constraints.NotBlank;
+
+public record DomainUpdateRequest(
+ @NotBlank(message = "领域名称不能为空") String name, String description) {}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/entity/DomainInfo.java b/data-agent-backend/src/main/java/io/github/malonetalk/entity/DomainInfo.java
new file mode 100644
index 0000000..fad3e82
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/entity/DomainInfo.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.entity;
+
+import java.time.LocalDateTime;
+import lombok.Data;
+
+@Data
+public class DomainInfo {
+
+ private Integer id;
+ private String name;
+ private String description;
+ private LocalDateTime createTime;
+ private LocalDateTime updateTime;
+}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/mapper/DomainInfoMapper.java b/data-agent-backend/src/main/java/io/github/malonetalk/mapper/DomainInfoMapper.java
new file mode 100644
index 0000000..d727b62
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/mapper/DomainInfoMapper.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.mapper;
+
+import io.github.malonetalk.dto.DomainPageQuery;
+import io.github.malonetalk.entity.DomainInfo;
+import java.util.List;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+@Mapper
+public interface DomainInfoMapper {
+
+ int insert(DomainInfo domainInfo);
+
+ int update(DomainInfo domainInfo);
+
+ int deleteByIds(@Param("ids") List ids);
+
+ List selectAll();
+
+ List selectPage(
+ @Param("query") DomainPageQuery query, @Param("sortDescending") boolean sortDescending);
+
+ DomainInfo selectById(@Param("id") Integer id);
+
+ DomainInfo selectByName(@Param("name") String name);
+}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/mapper/TableInfoMapper.java b/data-agent-backend/src/main/java/io/github/malonetalk/mapper/TableInfoMapper.java
index 9c41458..92bfee3 100644
--- a/data-agent-backend/src/main/java/io/github/malonetalk/mapper/TableInfoMapper.java
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/mapper/TableInfoMapper.java
@@ -41,4 +41,9 @@ List selectPageByDatasourceId(
TableInfo selectByDatasourceIdAndTableName(
@Param("datasourceId") Integer datasourceId, @Param("tableName") String tableName);
+
+ List selectByDatasourceIdAndDomains(
+ @Param("datasourceId") Integer datasourceId, @Param("domains") List domains);
+
+ int countByDomain(@Param("domain") String domain);
}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/DomainService.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/DomainService.java
new file mode 100644
index 0000000..cfb6117
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/DomainService.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.service;
+
+import io.github.malonetalk.dto.DomainCreateRequest;
+import io.github.malonetalk.dto.DomainPageQuery;
+import io.github.malonetalk.dto.DomainUpdateRequest;
+import io.github.malonetalk.dto.pagination.PageResponse;
+import io.github.malonetalk.entity.DomainInfo;
+import java.util.List;
+
+public interface DomainService {
+
+ PageResponse getDomainPage(DomainPageQuery query);
+
+ DomainInfo findById(Integer id);
+
+ DomainInfo create(DomainCreateRequest request);
+
+ DomainInfo update(Integer id, DomainUpdateRequest request);
+
+ void delete(Integer id);
+
+ List listDomainNames();
+}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/DomainServiceImpl.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/DomainServiceImpl.java
new file mode 100644
index 0000000..3f68bc1
--- /dev/null
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/DomainServiceImpl.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ * limitations under the License.
+ */
+package io.github.malonetalk.service;
+
+import com.github.pagehelper.Page;
+import com.github.pagehelper.PageHelper;
+import io.github.malonetalk.common.SemanticConstants;
+import io.github.malonetalk.dto.DomainCreateRequest;
+import io.github.malonetalk.dto.DomainPageQuery;
+import io.github.malonetalk.dto.DomainUpdateRequest;
+import io.github.malonetalk.dto.pagination.PageResponse;
+import io.github.malonetalk.entity.DomainInfo;
+import io.github.malonetalk.mapper.DomainInfoMapper;
+import io.github.malonetalk.mapper.TableInfoMapper;
+import io.github.malonetalk.utils.SemanticUtils;
+import java.time.LocalDateTime;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class DomainServiceImpl implements DomainService {
+
+ private final DomainInfoMapper domainInfoMapper;
+ private final TableInfoMapper tableInfoMapper;
+
+ @Override
+ public PageResponse getDomainPage(DomainPageQuery query) {
+ int pageNumber = PageResponse.resolvePage(query.page());
+ int pageSize = PageResponse.resolvePageSize(query.pageSize());
+ SemanticUtils.validateSortOrder(query.sortOrder());
+ boolean sortDescending =
+ SemanticConstants.SORT_ORDER_DESC.equalsIgnoreCase(query.sortOrder());
+ PageHelper.startPage(pageNumber, pageSize);
+ @SuppressWarnings("unchecked")
+ Page page =
+ (Page)
+ domainInfoMapper.selectPage(
+ new DomainPageQuery(
+ pageNumber,
+ pageSize,
+ SemanticUtils.normalizeBlankToNull(query.keyword()),
+ query.sortOrder()),
+ sortDescending);
+ List items = page.getResult();
+ long total = page.getTotal();
+ return PageResponse.of(items, total, pageNumber, pageSize);
+ }
+
+ @Override
+ public DomainInfo findById(Integer id) {
+ if (id == null) {
+ throw new IllegalArgumentException("id 不能为空");
+ }
+ return domainInfoMapper.selectById(id);
+ }
+
+ @Override
+ public DomainInfo create(DomainCreateRequest request) {
+ String normalizedName = SemanticUtils.requireName(request.name(), "领域名称");
+ DomainInfo existing = domainInfoMapper.selectByName(normalizedName);
+ if (existing != null) {
+ throw new IllegalArgumentException("领域名称已存在: " + normalizedName);
+ }
+ DomainInfo domainInfo = new DomainInfo();
+ domainInfo.setName(normalizedName);
+ domainInfo.setDescription(SemanticUtils.normalizeBlankToNull(request.description()));
+ domainInfo.setCreateTime(LocalDateTime.now());
+ domainInfo.setUpdateTime(LocalDateTime.now());
+ domainInfoMapper.insert(domainInfo);
+ return domainInfo;
+ }
+
+ @Override
+ public DomainInfo update(Integer id, DomainUpdateRequest request) {
+ if (id == null) {
+ throw new IllegalArgumentException("id 不能为空");
+ }
+ DomainInfo existing = findById(id);
+ if (existing == null) {
+ throw new IllegalArgumentException("领域不存在: id=" + id);
+ }
+ String normalizedName = SemanticUtils.requireName(request.name(), "领域名称");
+ DomainInfo nameConflict = domainInfoMapper.selectByName(normalizedName);
+ if (nameConflict != null && !nameConflict.getId().equals(id)) {
+ throw new IllegalArgumentException("领域名称已存在: " + normalizedName);
+ }
+ existing.setName(normalizedName);
+ existing.setDescription(SemanticUtils.normalizeBlankToNull(request.description()));
+ existing.setUpdateTime(LocalDateTime.now());
+ domainInfoMapper.update(existing);
+ return existing;
+ }
+
+ @Override
+ public void delete(Integer id) {
+ if (id == null) {
+ throw new IllegalArgumentException("id 不能为空");
+ }
+ DomainInfo existing = findById(id);
+ if (existing == null) {
+ throw new IllegalArgumentException("领域不存在: id=" + id);
+ }
+ if (SemanticConstants.DEFAULT_DOMAIN.equalsIgnoreCase(existing.getName())) {
+ throw new IllegalArgumentException("无法删除的领域" + existing.getName());
+ }
+ int referenceCount = tableInfoMapper.countByDomain(existing.getName());
+ if (referenceCount > 0) {
+ throw new IllegalArgumentException("领域正在被占用" + existing.getName());
+ }
+ domainInfoMapper.deleteByIds(List.of(id));
+ }
+
+ @Override
+ public List listDomainNames() {
+ return domainInfoMapper.selectAll().stream()
+ .map(DomainInfo::getName)
+ .distinct()
+ .sorted(String::compareToIgnoreCase)
+ .toList();
+ }
+}
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticService.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticService.java
index 83e8717..0d80e2e 100644
--- a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticService.java
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticService.java
@@ -34,6 +34,8 @@ public interface TableSemanticService {
void updateTableSemantic(TableSemanticUpdateRequest request);
+ List listTableInfosByDomains(Integer datasourceId, List domains);
+
void resetTableSemantic(Integer datasourceId, String tableName);
int resetTableSemantics(Integer datasourceId, List tableNames);
diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticServiceImpl.java b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticServiceImpl.java
index e1223e3..d064889 100644
--- a/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticServiceImpl.java
+++ b/data-agent-backend/src/main/java/io/github/malonetalk/service/semantic/table/TableSemanticServiceImpl.java
@@ -94,6 +94,18 @@ public List listTableInfosByDatasourceId(Integer datasourceId) {
return tableInfoMapper.selectByDatasourceId(datasourceId);
}
+ @Override
+ public List listTableInfosByDomains(Integer datasourceId, List domains) {
+ SemanticUtils.requireDatasourceId(datasourceId);
+ if (datasourceService.findById(datasourceId) == null) {
+ return List.of();
+ }
+ if (domains == null || domains.isEmpty()) {
+ return listTableInfosByDatasourceId(datasourceId);
+ }
+ return tableInfoMapper.selectByDatasourceIdAndDomains(datasourceId, domains);
+ }
+
@Override
public void updateTableSemantic(TableSemanticUpdateRequest request) {
requireDatasource(request.datasourceId());
@@ -107,7 +119,7 @@ public void updateTableSemantic(TableSemanticUpdateRequest request) {
tableInfo.setTableName(normalizedTableName);
tableInfo.setTableDescription(
SemanticUtils.normalizeBlankToNull(request.tableDescription()));
- tableInfo.setDomain(SemanticUtils.normalizeBlankToNull(request.domain()));
+ tableInfo.setDomain(normalizeDomain(request.domain()));
tableInfo.setIsVisible(request.isVisible());
tableInfo.setCreateTime(LocalDateTime.now());
tableInfo.setUpdateTime(LocalDateTime.now());
@@ -117,7 +129,7 @@ public void updateTableSemantic(TableSemanticUpdateRequest request) {
existing.setTableName(normalizedTableName);
existing.setTableDescription(
SemanticUtils.normalizeBlankToNull(request.tableDescription()));
- existing.setDomain(SemanticUtils.normalizeBlankToNull(request.domain()));
+ existing.setDomain(normalizeDomain(request.domain()));
existing.setIsVisible(request.isVisible());
existing.setUpdateTime(LocalDateTime.now());
tableInfoMapper.update(existing);
@@ -190,4 +202,9 @@ private TableSemanticResponse mapResponse(TableInfo tableInfo) {
private String normalizeName(String value) {
return SemanticUtils.requireName(value, "tableName").toLowerCase(Locale.ROOT);
}
+
+ private String normalizeDomain(String value) {
+ String normalized = SemanticUtils.normalizeBlankToNull(value);
+ return normalized == null ? SemanticConstants.DEFAULT_DOMAIN : normalized;
+ }
}
diff --git a/data-agent-backend/src/main/resources/mapper/DomainInfoMapper.xml b/data-agent-backend/src/main/resources/mapper/DomainInfoMapper.xml
new file mode 100644
index 0000000..9116ce8
--- /dev/null
+++ b/data-agent-backend/src/main/resources/mapper/DomainInfoMapper.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INSERT INTO domain_info (
+ name, description,
+ create_time, update_time
+ ) VALUES (
+ #{name}, #{description},
+ #{createTime}, #{updateTime}
+ )
+
+
+
+ UPDATE domain_info
+
+ name = #{name},
+ description = #{description},
+ update_time = #{updateTime},
+
+ WHERE id = #{id}
+
+
+
+ DELETE FROM domain_info
+ WHERE id IN
+
+ #{id}
+
+
+
+
diff --git a/data-agent-backend/src/main/resources/mapper/TableInfoMapper.xml b/data-agent-backend/src/main/resources/mapper/TableInfoMapper.xml
index c308e2f..56d5e26 100644
--- a/data-agent-backend/src/main/resources/mapper/TableInfoMapper.xml
+++ b/data-agent-backend/src/main/resources/mapper/TableInfoMapper.xml
@@ -66,6 +66,21 @@
WHERE id = #{id}
+
+
+
+
DELETE FROM table_info
WHERE datasource_id = #{datasourceId}
diff --git a/data-agent-frontend/src/api/domain.ts b/data-agent-frontend/src/api/domain.ts
new file mode 100644
index 0000000..b4a37be
--- /dev/null
+++ b/data-agent-frontend/src/api/domain.ts
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2026 github.com/MaloneTalk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import request from './request';
+import type { ApiResponse } from './request';
+
+export interface PageResponse {
+ page: number;
+ pageSize: number;
+ total: number;
+ totalPages: number;
+ hasPrevious: boolean;
+ hasNext: boolean;
+ items: T[];
+}
+
+export interface DomainInfo {
+ id: number;
+ name: string;
+ description: string | null;
+ createTime: string;
+ updateTime: string;
+}
+
+export interface DomainPageQuery {
+ page?: number;
+ pageSize?: number;
+ keyword?: string;
+ sortOrder?: 'asc' | 'desc';
+}
+
+export interface DomainCreateRequest {
+ name: string;
+ description?: string;
+}
+
+export interface DomainUpdateRequest {
+ name: string;
+ description?: string;
+}
+
+export function getDomainPage(query: DomainPageQuery) {
+ return request.get>>('/domains', { params: query });
+}
+
+export function getDomainById(id: number) {
+ return request.get>(`/domains/${id}`);
+}
+
+export function createDomain(data: DomainCreateRequest) {
+ return request.post>('/domains', data);
+}
+
+export function updateDomain(id: number, data: DomainUpdateRequest) {
+ return request.put>(`/domains/${id}`, data);
+}
+
+export function deleteDomain(id: number) {
+ return request.delete>(`/domains/${id}`);
+}
diff --git a/data-agent-frontend/src/components/layout/AppSidebar.vue b/data-agent-frontend/src/components/layout/AppSidebar.vue
index d936f73..a09a76b 100644
--- a/data-agent-frontend/src/components/layout/AppSidebar.vue
+++ b/data-agent-frontend/src/components/layout/AppSidebar.vue
@@ -27,6 +27,7 @@
const menuItems = [
{ path: '/chat', title: 'AI 智能分析', icon: 'ChatDotRound' },
{ path: '/data-source', title: '数据源管理', icon: 'Connection' },
+ { path: '/semantic', title: '语义管理', icon: 'Collection' },
];
const activeMenu = computed(() => route.path);
diff --git a/data-agent-frontend/src/router/index.ts b/data-agent-frontend/src/router/index.ts
index ad13e31..6892db4 100644
--- a/data-agent-frontend/src/router/index.ts
+++ b/data-agent-frontend/src/router/index.ts
@@ -41,6 +41,12 @@ const routes: RouteRecordRaw[] = [
component: () => import('@/views/data-source/DataSource.vue'),
meta: { title: '数据源管理' },
},
+ {
+ path: '/semantic',
+ name: 'SemanticManage',
+ component: () => import('@/views/semantic/SemanticManage.vue'),
+ meta: { title: '语义管理' },
+ },
];
const router = createRouter({
diff --git a/data-agent-frontend/src/views/semantic/SemanticManage.vue b/data-agent-frontend/src/views/semantic/SemanticManage.vue
new file mode 100644
index 0000000..e4d0444
--- /dev/null
+++ b/data-agent-frontend/src/views/semantic/SemanticManage.vue
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+
+ 保存
+
+
+
+
+
+
+
diff --git a/data-agent-frontend/vite.config.ts b/data-agent-frontend/vite.config.ts
index 7b949a3..90eab16 100644
--- a/data-agent-frontend/vite.config.ts
+++ b/data-agent-frontend/vite.config.ts
@@ -18,7 +18,7 @@
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import eslint from 'vite-plugin-eslint';
-import { resolve } from 'path';
+import { fileURLToPath, URL } from 'node:url';
/**
* Vite configuration file
@@ -32,7 +32,7 @@ export default defineConfig({
// Allows importing like: import xxx from '@/components/xxx'
resolve: {
alias: {
- '@': resolve(__dirname, 'src'),
+ '@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
// Development server configuration
diff --git a/skills/data-query/SKILL.md b/skills/data-query/SKILL.md
index afcb7f8..139f988 100644
--- a/skills/data-query/SKILL.md
+++ b/skills/data-query/SKILL.md
@@ -7,14 +7,16 @@ description: A skill for querying database tables and generating SQL queries bas
You are a data query assistant. When the user asks a data-related question, follow these steps:
-1. Use the `get_tables` tool to get available database tables
-2. Use the `get_table_schema` tool to get the schema of relevant tables
-3. Generate a SELECT SQL statement based on the table structure
-4. Use the `execute_sql` tool to execute the query
-5. Summarize the query results for the user
+1. Use the `get_domains` tool to get available data domains
+2. Based on the user's question, select the relevant domain(s), then use `get_tables(domains=[...])` to get tables in those domains
+3. Use the `get_table_schema` tool to get the schema of relevant tables
+4. Generate a SELECT SQL statement based on the table structure
+5. Use the `execute_sql` tool to execute the query
+6. Summarize the query results for the user
## Notes
- Only SELECT queries are supported, no modification operations
- Always check the table schema before generating SQL to ensure column names and types are correct
- If the query result is empty, suggest the user check the query conditions
+- When the user's question is ambiguous about which domain to use, query multiple domains to increase coverage
diff --git a/sql/data_source.sql b/sql/data_source.sql
index 9822f29..d66aae3 100644
--- a/sql/data_source.sql
+++ b/sql/data_source.sql
@@ -74,3 +74,17 @@ CREATE TABLE IF NOT EXISTS `logical_table_relation` (
(`datasource_id`, `source_table_name`, `target_table_name`, `id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='逻辑表关系表';
+CREATE TABLE IF NOT EXISTS `domain_info` (
+ `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `name` VARCHAR(255) NOT NULL COMMENT '领域名称',
+ `description` VARCHAR(500) DEFAULT NULL COMMENT '领域描述',
+ `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_domain_name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='数据领域表';
+
+INSERT INTO `domain_info` (`name`, `description`, `create_time`, `update_time`)
+SELECT 'default', '默认领域', NOW(), NOW()
+WHERE NOT EXISTS (SELECT 1 FROM `domain_info` WHERE `name` = 'default');
+