Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
61 changes: 34 additions & 27 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,55 @@
---
name: Bug Report
about: Report a bug to help us improve
name: 缺陷报告 (Bug)
about: 提交缺陷以帮助我们改进
title: '[BUG] '
labels: bug
assignees: ''
---

## Bug Description
## 修复方式(必选)

A clear and concise description of what the bug is.
你希望这个问题如何被解决?(请勾选一项)

## Steps to Reproduce
- [ ] 需要维护者修复(我暂时无法提交 PR)
- [ ] 我可以自行修复并提交 PR
- [ ] 不确定 / 需要讨论

1. Go to '...'
2. Configure '...'
3. Call method '...'
4. See error
## 缺陷描述

## Expected Behavior
请用简洁清晰的语言描述问题现象。

A clear and concise description of what you expected to happen.
## 复现步骤

## Actual Behavior
1. 进入 '...'
2. 配置 '...'
3. 调用方法 '...'
4. 看到错误

What actually happened.
## 期望行为

## Environment
你期望发生什么?

- **OS**: [e.g., macOS 14.0, Ubuntu 22.04]
- **Java Version**: [e.g., OpenJDK 17.0.2]
- **Assistant Agent Version**: [e.g., 1.0.0]
- **Spring Boot Version**: [e.g., 3.4.0]
## 实际行为

## Logs/Stack Trace
实际发生了什么?

## 环境信息

- **操作系统**: [例如 macOS 14.0, Ubuntu 22.04]
- **Java 版本**: [例如 OpenJDK 17.0.2]
- **Assistant Agent 版本**: [例如 1.0.0]
- **Spring Boot 版本**: [例如 3.4.0]

## 日志/堆栈

```
Paste relevant logs or stack trace here
请粘贴相关日志或堆栈信息(注意去除敏感信息)
```

## Configuration
## 配置

```yaml
# Relevant configuration (remove sensitive data)
# 相关配置(请去除敏感信息)
spring:
ai:
alibaba:
Expand All @@ -50,11 +58,10 @@ spring:
# ...
```

## Additional Context

Add any other context about the problem here.
## 额外上下文

## Possible Solution (optional)
补充任何其他有助于定位问题的信息(例如:相关链接、截图、录屏、最小复现仓库等)。

If you have an idea of what might be causing this or how to fix it.
## 可能的解决方案(可选)

如果你已经有初步排查结果或修复思路,请写在这里;如果你愿意提交 PR,也可以说明预计修改点/模块。
6 changes: 6 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
blank_issues_enabled: false

contact_links:
- name: 使用问题 / Q&A
url: https://github.com/<OWNER>/<REPO>/discussions
about: 使用咨询、安装问题、如何配置等,请优先在 Discussions 提问。
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