Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -148,19 +148,48 @@ private SearchResultSet executeUnifiedSearch(Map<String, Object> params, ToolCon
String query = (String) params.get("query");
Integer limit = params.containsKey("limit") ? ((Number) params.get("limit")).intValue() : 10;

// 解析要搜索的源类型列表
List<SearchSourceType> sourceTypes = parseSourceTypes(params);
// 解析要搜索的源 keys (可以是类型名,也可以是 provider 名)
List<String> sourceKeys = parseSourceKeys(params);

log.info("UnifiedSearchCodeactTool#executeUnifiedSearch - reason=开始统一搜索, query={}, sourceTypes={}, limit={}",
query, sourceTypes, limit);
log.info("UnifiedSearchCodeactTool#executeUnifiedSearch - reason=开始统一搜索, query={}, sources={}, limit={}",
query, sourceKeys, limit);

// 并行搜索多个数据源
List<SearchResultItem> allItems = new ArrayList<>();
int successCount = 0;
int failureCount = 0;

for (SearchSourceType sourceType : sourceTypes) {
for (String key : sourceKeys) {
try {
// 1. 尝试作为 Provider Name 匹配
Optional<SearchProvider> namedProvider = searchProviders.stream()
.filter(p -> p.getName().equalsIgnoreCase(key))
.findFirst();

if (namedProvider.isPresent()) {
// 按指定 Provider 名称搜索
SearchProvider provider = namedProvider.get();
// 当指定具体 Provider 时,尝试使用 CUSTOM 类型,或者该 Provider 支持的第一个类型
// 这里为了简单,我们传递 CUSTOM 类型,如果 Provider 不处理 SourceType 也没关系
SearchRequest request = buildSearchRequest(query, SearchSourceType.CUSTOM, limit);

List<SearchResultItem> items = provider.search(request);
allItems.addAll(items);
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential NullPointerException: if provider.search(request) returns null on line 176, calling allItems.addAll(items) will throw an NPE. Consider adding a null check or ensuring search() never returns null.

Copilot uses AI. Check for mistakes.
successCount++;
log.debug("UnifiedSearchCodeactTool#executeUnifiedSearch - reason=指定Provider搜索成功, provider={}, resultCount={}",
provider.getName(), items.size());
continue;
}

// 2. 尝试作为 SourceType 匹配
SearchSourceType sourceType;
try {
sourceType = SearchSourceType.valueOf(key.toUpperCase());
} catch (IllegalArgumentException e) {
log.warn("UnifiedSearchCodeactTool#executeUnifiedSearch - reason=无效的源类型或Provider名称, key={}", key);
continue;
}

// 查找支持该源类型的 provider
List<SearchProvider> providers = searchProviders.stream()
.filter(p -> p.supports(sourceType))
Expand All @@ -185,8 +214,8 @@ private SearchResultSet executeUnifiedSearch(Map<String, Object> params, ToolCon
}
catch (Exception e) {
failureCount++;
log.error("UnifiedSearchCodeactTool#executeUnifiedSearch - reason=单源搜索失败, sourceType={}, error={}",
sourceType, e.getMessage(), e);
log.error("UnifiedSearchCodeactTool#executeUnifiedSearch - reason=搜索执行失败, sourceKey={}, error={}",
key, e.getMessage(), e);
}
}

Expand All @@ -208,35 +237,49 @@ private SearchResultSet executeUnifiedSearch(Map<String, Object> params, ToolCon
}

/**
* 解析要搜索的源类型列表
* 解析要搜索的源列表(字符串)
*/
private List<SearchSourceType> parseSourceTypes(Map<String, Object> params) {
List<SearchSourceType> sourceTypes = new ArrayList<>();
private List<String> parseSourceKeys(Map<String, Object> params) {
List<String> keys = new ArrayList<>();

// 如果指定了 sources 参数
if (params.containsKey("sources")) {
Object sourcesObj = params.get("sources");
if (sourcesObj instanceof List) {
List<?> sourcesList = (List<?>) sourcesObj;
for (Object source : sourcesList) {
try {
SearchSourceType type = SearchSourceType.valueOf(source.toString().toUpperCase());
sourceTypes.add(type);
}
catch (Exception e) {
log.warn("UnifiedSearchCodeactTool#parseSourceTypes - reason=无效的源类型, source={}", source);
if (source != null) {
keys.add(source.toString());
}
}
}
}

// 如果没有指定或解析失败,使用默认的源类型
if (sourceTypes.isEmpty()) {
sourceTypes.add(SearchSourceType.PROJECT);
sourceTypes.add(SearchSourceType.KNOWLEDGE);
// 如果没有指定,使用默认的源类型
if (keys.isEmpty()) {
keys.add(SearchSourceType.PROJECT.name());
keys.add(SearchSourceType.KNOWLEDGE.name());
Comment on lines +260 to +263
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistency in default values: parseSourceKeys() returns uppercase enum names like "PROJECT" and "KNOWLEDGE" as defaults (lines 260-261), but getAvailableSourceNames() returns lowercase enum names (line 274). While this works due to the toUpperCase() call in executeUnifiedSearch, it creates an inconsistent API. Consider using lowercase in parseSourceKeys to match getAvailableSourceNames().

Suggested change
// 如果没有指定,使用默认的源类型
if (keys.isEmpty()) {
keys.add(SearchSourceType.PROJECT.name());
keys.add(SearchSourceType.KNOWLEDGE.name());
// 如果没有指定,使用默认的源类型(与 getAvailableSourceNames 的小写形式保持一致)
if (keys.isEmpty()) {
keys.add(SearchSourceType.PROJECT.name().toLowerCase());
keys.add(SearchSourceType.KNOWLEDGE.name().toLowerCase());

Copilot uses AI. Check for mistakes.
}

return sourceTypes;
return keys;
}

/**
* 获取所有可用的源名称(包括标准类型和Provider名称)。
*/
private List<Object> getAvailableSourceNames() {
List<Object> names = new ArrayList<>();
// 添加标准类型
for (SearchSourceType type : SearchSourceType.values()) {
names.add(type.name().toLowerCase());
}
// 添加 Provider 名称
if (searchProviders != null) {
for (SearchProvider p : searchProviders) {
names.add(p.getName());
}
}
return names;
}

/**
Expand Down Expand Up @@ -265,9 +308,9 @@ private CodeactToolDefinition buildCodeactDefinition() {
.addParameter(ParameterNode.builder()
.name("sources")
.type(ParameterType.ARRAY)
.description("要搜索的数据源列表,默认搜索 projectknowledge")
.description("要搜索的数据源列表,支持标准类型(project, knowledge, web等)或指定Provider名称")
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation description has been updated to mention "支持标准类型(project, knowledge, web等)或指定Provider名称" which correctly reflects the new functionality. However, the example values shown as "project, knowledge, web等" don't match the actual enum values returned by getAvailableSourceNames() which uses lowercase. Consider updating the documentation to clarify the expected format.

Suggested change
.description("要搜索的数据源列表,支持标准类型(project, knowledge, web等)或指定Provider名称")
.description("要搜索的数据源列表,支持标准小写类型(如:projectknowledge、web 等)或指定 Provider 名称")

Copilot uses AI. Check for mistakes.
.required(false)
.enumValues(Arrays.asList("project", "knowledge", "web", "experience"))
.enumValues(getAvailableSourceNames())
.build())
.addParameter(ParameterNode.builder()
.name("limit")
Expand Down Expand Up @@ -335,9 +378,9 @@ private String buildInputSchema() {
sourcesProp.put("type", "array");
Map<String, Object> itemsProp = new LinkedHashMap<>();
itemsProp.put("type", "string");
itemsProp.put("enum", Arrays.asList("project", "knowledge", "web", "experience"));
itemsProp.put("enum", getAvailableSourceNames());
sourcesProp.put("items", itemsProp);
sourcesProp.put("description", "要搜索的数据源列表,默认搜索 project 和 knowledge");
sourcesProp.put("description", "要搜索的数据源列表,支持标准类型或Provider名称");
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation description mentions "支持标准类型或Provider名称" but doesn't clarify the format. The actual enum values are lowercase (from getAvailableSourceNames()), but the documentation example doesn't make this clear. Consider being more explicit about the expected format.

Suggested change
sourcesProp.put("description", "要搜索的数据源列表,支持标准类型或Provider名称");
sourcesProp.put("description",
"要搜索的数据源列表。每个元素为小写字符串,必须匹配 getAvailableSourceNames() 返回的标准类型名称或 Provider 名称。");

Copilot uses AI. Check for mistakes.
properties.put("sources", sourcesProp);

// limit 参数(可选)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.assistant.agent.autoconfigure;
package com.alibaba.assistant.agent.start.config;

import com.alibaba.assistant.agent.autoconfigure.CodeactAgent;
import com.alibaba.assistant.agent.common.tools.CodeactTool;
import com.alibaba.assistant.agent.common.tools.ReplyCodeactTool;
import com.alibaba.assistant.agent.common.tools.SearchCodeactTool;
Expand All @@ -26,6 +27,7 @@
import com.alibaba.assistant.agent.extension.experience.fastintent.FastIntentService;
import com.alibaba.assistant.agent.extension.experience.hook.FastIntentReactHook;
import com.alibaba.assistant.agent.extension.experience.spi.ExperienceProvider;
import com.alibaba.assistant.agent.extension.search.tools.SearchCodeactToolFactory;
import com.alibaba.assistant.agent.extension.search.tools.UnifiedSearchCodeactTool;
import com.alibaba.assistant.agent.common.enums.Language;
import com.alibaba.cloud.ai.graph.agent.hook.Hook;
Expand Down Expand Up @@ -245,7 +247,7 @@ def subscribe_medicine_reminder():
*
* @param chatModel Spring AI的ChatModel
* @param replyCodeactTools Reply模块的工具列表(可选)
* @param searchCodeactTools Search模块的工具列表(可选)
* @param searchCodeactToolFactory Search模块的工具工厂(可选)
* @param triggerCodeactTools Trigger模块的工具列表(可选)
* @param unifiedSearchCodeactTool 统一搜索工具(可选)
* @param mcpToolCallbackProvider MCP工具提供者(由MCP Client Boot Starter自动注入,可选)
Expand All @@ -254,7 +256,7 @@ def subscribe_medicine_reminder():
public CodeactAgent grayscaleCodeactAgent(
ChatModel chatModel,
@Autowired(required = false) List<ReplyCodeactTool> replyCodeactTools,
@Autowired(required = false) List<SearchCodeactTool> searchCodeactTools,
@Autowired(required = false) SearchCodeactToolFactory searchCodeactToolFactory,
@Autowired(required = false) List<TriggerCodeactTool> triggerCodeactTools,
@Autowired(required = false) UnifiedSearchCodeactTool unifiedSearchCodeactTool,
@Autowired(required = false) ToolCallbackProvider mcpToolCallbackProvider,
Expand All @@ -277,9 +279,12 @@ public CodeactAgent grayscaleCodeactAgent(
}

// 添加Search工具
if (searchCodeactTools != null && !searchCodeactTools.isEmpty()) {
allCodeactTools.addAll(searchCodeactTools);
logger.info("CodeactAgentConfig#grayscaleCodeactAgent - reason=添加SearchCodeactTools, count={}", searchCodeactTools.size());
if (searchCodeactToolFactory != null) {
List<SearchCodeactTool> searchTools = searchCodeactToolFactory.createTools();
if (!searchTools.isEmpty()) {
allCodeactTools.addAll(searchTools);
logger.info("CodeactAgentConfig#grayscaleCodeactAgent - reason=添加SearchCodeactTools, count={}", searchTools.size());
}
}

// 添加Reply工具
Expand Down Expand Up @@ -364,7 +369,7 @@ public CodeactAgent grayscaleCodeactAgent(
.allowIO(false)
.allowNativeAccess(false)
.executionTimeout(30000)
.tools(replyCodeactTools.toArray(new ToolCallback[0]))
.tools(replyCodeactTools != null ? replyCodeactTools.toArray(new ToolCallback[0]) : new ToolCallback[0])
.codeactTools(allCodeactTools)
.hooks(reactHooks)
.subAgentHooks(codeactHooks)
Expand Down