mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-10-19 05:57:39 +02:00
fix: retain knowledge indexing errors and explain failure causes
This commit is contained in:
1 parent
bb96a084e0
commit
5dc9cb1541
7 files changed
+94
-11
No files matched your search
@@ -97,3 +97,8 @@
|
||||
|
||||
- Excel 第 28 行:现有预检已经区分相同内容和 ID 冲突,后端跳过/保留不会覆盖本地数据;确认动作仍用于记录冲突处理。按钮明确改为“确认跳过导入”或“确认保留本地版本”,结果分别显示“已跳过导入”“已保留本地版本”。创建、覆盖确认及另存语义保留。14 项既有与新增包导入测试通过。
|
||||
- Excel 第 15 行:关联事实入口原先先切到 facts 并清空筛选,改为留在当前页面查询并直接打开详情;无关联只提示,多条取消也留在原页。4 项实际函数回归覆盖单条、空集合、多条选择/取消和 API 失败,不再发生列表导航或筛选重置。
|
||||
|
||||
## 追加:知识库索引提示与工具挂载核对
|
||||
|
||||
- Excel 第 53 行:原截图的服务错误明确为 401 Unauthorized/Incorrect API key。页面改为按认证、权限、限流、超时、网络错误给出对应检查建议,保留原错误供定位;未知错误不推断原因。状态 API 不再五分钟后隐藏失败,下一次索引由现有 resetLastError 清除;空知识列表也显示已有失败。实际状态渲染与错误保留回归通过。
|
||||
- Excel 第 54 行:当前启动、动态初始化及 ApplyConfig 路径均注册知识检索工具;真实 RegisterKnowledgeTool + Agent.ToolsForRole 验证默认角色和显式允许角色均含搜索/风险列表定义,显式不允许角色不被绕过。截图只有模型回复,缺少当时启用配置、工具目录和角色列表,不能据此证明当前注册缺陷;本项标为当前未复现,保留历史证据不足的限制。
|
||||
@@ -55,8 +55,8 @@
|
||||
| 50 | — | 监听器编辑后点击保存没有反应 | 已修复弹窗事件冒泡阻断;实际委托保存和失败提示回归通过 |
|
||||
| 51 | — | 创建 Malleable Profile 时 UA、响应头和 Jitter 配置被静默丢弃 | 已修复并有针对性验证 |
|
||||
| 52 | — | C2 载荷生成未校验回连地址,非法地址仍能生成 Oneliner | 已修复:生成前校验主机格式;非法输入拒绝,HTTPS/IPv6 API 回归通过 |
|
||||
| 53 | — | 知识库索引构建失败原因不怎么太清晰 | 待核对 |
|
||||
| 54 | — | Agent 未挂载知识检索工具 | 待核对 |
|
||||
| 53 | — | 知识库索引构建失败原因不怎么太清晰 | 已修复错误原因提示和五分钟后消失;状态渲染及错误保留回归通过 |
|
||||
| 54 | — | Agent 未挂载知识检索工具 | 当前注册及角色工具可见性测试通过,未复现;历史实例缺配置/角色证据,不绕过现有权限 |
|
||||
| 55 | — | - 选择其专用测试会话,打开「终端」。 | 已修复,Unix Shell 回归通过 |
|
||||
| 56 | — | AI看代码给提的问题 | 已修复并有针对性验证 |
|
||||
| 57 | — | 审计日志搜索无结果后,点击“清空”不会清除关键词或恢复列表,无反应 | 当前重置会清空关键词并请求首个无筛选页面,回归未复现 |
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"cyberstrike-ai/internal/knowledge"
|
||||
"cyberstrike-ai/internal/mcp/builtin"
|
||||
"go.uber.org/zap"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRegisteredKnowledgeToolsAreAvailableToDefaultAndSelectedRoles(t *testing.T) {
|
||||
a := setupTestAgent(t)
|
||||
knowledge.RegisterKnowledgeTool(a.mcpServer, nil, nil, zap.NewNop())
|
||||
for _, role := range [][]string{nil, {builtin.ToolSearchKnowledgeBase, builtin.ToolListKnowledgeRiskTypes}} {
|
||||
available := map[string]bool{}
|
||||
for _, tool := range a.ToolsForRole(role) {
|
||||
available[tool.Function.Name] = true
|
||||
}
|
||||
for _, name := range []string{builtin.ToolSearchKnowledgeBase, builtin.ToolListKnowledgeRiskTypes} {
|
||||
if !available[name] {
|
||||
t.Errorf("registered tool %s unavailable to role %v", name, role)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Explicit role restrictions remain effective.
|
||||
for _, tool := range a.ToolsForRole([]string{builtin.ToolListKnowledgeRiskTypes}) {
|
||||
if tool.Function.Name == builtin.ToolSearchKnowledgeBase {
|
||||
t.Fatal("role restriction bypassed")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -489,13 +489,8 @@ func (h *KnowledgeHandler) GetIndexStatus(c *gin.Context) {
|
||||
// 获取索引器的错误信息
|
||||
if h.indexer != nil {
|
||||
lastError, lastErrorTime := h.indexer.GetLastError()
|
||||
if lastError != "" {
|
||||
// 如果错误是最近发生的(5 分钟内),则返回错误信息
|
||||
if time.Since(lastErrorTime) < 5*time.Minute {
|
||||
status["last_error"] = lastError
|
||||
status["last_error_time"] = lastErrorTime.Format(time.RFC3339)
|
||||
}
|
||||
}
|
||||
// Keep a failed operation visible until the indexer clears it for the next run.
|
||||
appendKnowledgeIndexError(status, lastError, lastErrorTime)
|
||||
|
||||
// 获取重建索引状态
|
||||
isRebuilding, totalItems, current, failed, lastItemID, lastChunks, startTime := h.indexer.GetRebuildStatus()
|
||||
@@ -564,3 +559,11 @@ func parseInt(s string) (int, error) {
|
||||
_, err := fmt.Sscanf(s, "%d", &result)
|
||||
return result, err
|
||||
}
|
||||
|
||||
func appendKnowledgeIndexError(status map[string]interface{}, message string, occurredAt time.Time) {
|
||||
if message == "" {
|
||||
return
|
||||
}
|
||||
status["last_error"] = message
|
||||
status["last_error_time"] = occurredAt.Format(time.RFC3339)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestKnowledgeIndexFailureRemainsVisibleUntilCleared(t *testing.T) {
|
||||
occurred := time.Now().Add(-time.Hour)
|
||||
status := map[string]interface{}{}
|
||||
appendKnowledgeIndexError(status, "test-only embedding failure: 401 Unauthorized", occurred)
|
||||
if status["last_error"] == nil || status["last_error_time"] != occurred.Format(time.RFC3339) {
|
||||
t.Fatal("older failure disappeared")
|
||||
}
|
||||
cleared := map[string]interface{}{}
|
||||
appendKnowledgeIndexError(cleared, "", time.Time{})
|
||||
if _, ok := cleared["last_error"]; ok {
|
||||
t.Fatal("cleared failure remained")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
const fs=require('node:fs'),vm=require('node:vm'),test=require('node:test'),assert=require('node:assert/strict');
|
||||
const source=fs.readFileSync('web/static/js/knowledge.js','utf8');
|
||||
const c=vm.createContext({});vm.runInContext(source.slice(0,source.indexOf('\n\n',source.indexOf('return \'请根据'))+2),c);
|
||||
test('embedding failures give the matching configuration or service advice',()=>{
|
||||
for(const [error,expected] of [['status code: 401 Incorrect API key provided','API 密钥'],['403 Forbidden','访问权限'],['429 rate limit','请求频率'],['context deadline exceeded','超时'],['dial: no such host','DNS'],['unknown error','上方错误']]) assert.ok(c.knowledgeIndexErrorAdvice(error).includes(expected));
|
||||
});
|
||||
|
||||
test('actual status renderer shows specific advice even with zero items',async()=>{
|
||||
const container={style:{},innerHTML:''};
|
||||
const ctx=vm.createContext({apiFetch:async()=>({ok:true,json:async()=>({total_items:0,last_error:'401 Unauthorized test-only failure'})}),document:{getElementById:()=>container},escapeHtml:s=>s,indexProgressInterval:null,clearInterval(){},showNotification(){},console});
|
||||
vm.runInContext(source.slice(0,source.indexOf('\n\n',source.indexOf('return \'请根据'))+2),ctx);
|
||||
vm.runInContext(source.slice(source.indexOf('async function updateIndexProgress('),source.indexOf('function stopIndexProgressPolling(')),ctx);
|
||||
await ctx.updateIndexProgress();
|
||||
assert.equal(container.style.display,'block');assert.match(container.innerHTML,/API 密钥/);assert.match(container.innerHTML,/401 Unauthorized/);
|
||||
});
|
||||
@@ -1,3 +1,13 @@
|
||||
function knowledgeIndexErrorAdvice(error) {
|
||||
const text = String(error || '');
|
||||
if (/\b401\b|incorrect api key|invalid api key|unauthorized/i.test(text)) return '嵌入服务拒绝认证。请检查知识库嵌入配置的 API 密钥及服务地址,保存后重试。';
|
||||
if (/\b403\b|forbidden/i.test(text)) return '嵌入服务拒绝访问。请检查账号权限及所选嵌入模型的访问权限。';
|
||||
if (/\b429\b|rate limit|quota/i.test(text)) return '嵌入服务限制请求。请检查请求频率及账号可用额度,稍后重试。';
|
||||
if (/timeout|deadline exceeded/i.test(text)) return '嵌入服务请求超时。请检查服务可用性、网络和代理配置后重试。';
|
||||
if (/connection refused|no such host|network is unreachable/i.test(text)) return '无法连接嵌入服务。请检查服务地址、DNS、网络和代理配置。';
|
||||
return '请根据上方错误检查嵌入模型和服务配置,修正后重试。';
|
||||
}
|
||||
|
||||
// 知识库管理相关功能
|
||||
function _t(key, opts) {
|
||||
return typeof window.t === 'function' ? window.t(key, opts) : key;
|
||||
@@ -464,7 +474,7 @@ async function updateIndexProgress() {
|
||||
// 检查是否正在重建索引(优先使用重建状态)
|
||||
const isRebuilding = status.is_rebuilding || false;
|
||||
|
||||
if (totalItems === 0) {
|
||||
if (totalItems === 0 && !lastError) {
|
||||
// 没有知识项,隐藏进度条
|
||||
progressContainer.style.display = 'none';
|
||||
if (indexProgressInterval) {
|
||||
@@ -495,7 +505,7 @@ async function updateIndexProgress() {
|
||||
${escapeHtml(lastError)}
|
||||
</div>
|
||||
<div style="color: #999; font-size: 12px; margin-bottom: 12px;">
|
||||
可能的原因:嵌入模型配置错误、API密钥无效、余额不足等。请检查配置后重试。
|
||||
${escapeHtml(knowledgeIndexErrorAdvice(lastError))}
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
|
||||
<button onclick="buildKnowledgeIndex()" style="
|
||||
|
||||
Reference in new issue
Block a user