Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
赵月辉
/
fastgpt-migrated
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
2eeea151
authored
Jun 23, 2026
by
light5980
Committed by
GitHub
Jun 23, 2026
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
debug: training count error (#7169)
parent
b46748e1
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
343 additions
and
24 deletions
+343
-24
projects/app/src/pageComponents/dataset/detail/CollectionCard/TrainingStates.tsx
+0
-0
projects/app/src/pageComponents/dataset/detail/CollectionCard/trainingStatesUtils.ts
+56
-0
projects/app/src/pages/api/core/dataset/collection/trainingDetail.ts
+36
-23
projects/app/test/api/core/dataset/collection/trainingStatus.test.ts
+85
-1
projects/app/test/pageComponents/dataset/detail/CollectionCard/trainingStatesUtils.test.ts
+166
-0
No files found.
projects/app/src/pageComponents/dataset/detail/CollectionCard/TrainingStates.tsx
View file @
2eeea151
This diff is collapsed.
Click to expand it.
projects/app/src/pageComponents/dataset/detail/CollectionCard/trainingStatesUtils.ts
0 → 100644
View file @
2eeea151
import
type
{
TrainingModeEnum
}
from
'@fastgpt/global/core/dataset/constants'
;
import
type
{
GetCollectionTrainingDetailResponseType
}
from
'@fastgpt/global/openapi/core/dataset/collection/api'
;
export
enum
TrainingStatus
{
NotStart
=
'NotStart'
,
Queued
=
'Queued'
,
Running
=
'Running'
,
Ready
=
'Ready'
,
Error
=
'Error'
}
/**
* 训练进度弹窗只有未开始阶段置灰;已完成、排队中、处理中和异常阶段都需要保持视觉激活。
*/
export
const
isTrainingStepHighlighted
=
(
status
:
TrainingStatus
)
=>
status
!==
TrainingStatus
.
NotStart
;
/**
* 判断当前集合是否已经没有任何剩余训练或最终异常。
*/
export
const
isTrainingDetailReady
=
(
trainingDetail
:
GetCollectionTrainingDetailResponseType
)
=>
Object
.
values
(
trainingDetail
.
queuedCounts
).
every
((
count
)
=>
count
===
0
)
&&
Object
.
values
(
trainingDetail
.
trainingCounts
).
every
((
count
)
=>
count
===
0
)
&&
Object
.
values
(
trainingDetail
.
errorCounts
).
every
((
count
)
=>
count
===
0
);
/**
* 根据当前集合各训练阶段的计数计算单个阶段的展示状态。
* 已进入后续阶段时,前序阶段展示为完成;仍被前序阶段阻塞时,后续阶段保持未开始。
*/
export
const
getTrainingStepStatus
=
({
trainingDetail
,
mode
,
modeOrder
}:
{
trainingDetail
:
GetCollectionTrainingDetailResponseType
;
mode
:
TrainingModeEnum
;
modeOrder
:
TrainingModeEnum
[];
})
=>
{
if
(
isTrainingDetailReady
(
trainingDetail
))
return
TrainingStatus
.
Ready
;
if
(
trainingDetail
.
errorCounts
[
mode
]
>
0
)
return
TrainingStatus
.
Error
;
if
(
trainingDetail
.
trainingCounts
[
mode
]
>
0
)
return
TrainingStatus
.
Running
;
if
(
trainingDetail
.
queuedCounts
[
mode
]
>
0
)
return
TrainingStatus
.
Queued
;
const
modeIndex
=
modeOrder
.
indexOf
(
mode
);
if
(
modeIndex
===
-
1
)
return
TrainingStatus
.
NotStart
;
const
hasLaterProgress
=
modeOrder
.
slice
(
modeIndex
+
1
).
some
((
nextMode
)
=>
{
return
(
trainingDetail
.
queuedCounts
[
nextMode
]
>
0
||
trainingDetail
.
trainingCounts
[
nextMode
]
>
0
||
trainingDetail
.
errorCounts
[
nextMode
]
>
0
);
});
return
hasLaterProgress
?
TrainingStatus
.
Ready
:
TrainingStatus
.
NotStart
;
};
projects/app/src/pages/api/core/dataset/collection/trainingDetail.ts
View file @
2eeea151
...
...
@@ -14,9 +14,9 @@ import {
}
from
'@fastgpt/global/openapi/core/dataset/collection/api'
;
import
{
BLOCKED_LOCK_TIME
,
activeTrainingMatch
,
finalErrorTrainingMatch
}
from
'@fastgpt/service/core/dataset/training/query'
;
import
{
subMinutes
}
from
'date-fns'
;
const
defaultCounts
:
Record
<
TrainingModeEnum
,
number
>
=
{
parse
:
0
,
...
...
@@ -27,6 +27,15 @@ const defaultCounts: Record<TrainingModeEnum, number> = {
imageParse
:
0
};
const
MODE_LOCK_TIMEOUT_MINUTES
:
Record
<
TrainingModeEnum
,
number
>
=
{
parse
:
10
,
qa
:
10
,
chunk
:
3
,
image
:
10
,
auto
:
10
,
imageParse
:
10
};
async
function
handler
(
req
:
ApiRequestProps
):
Promise
<
GetCollectionTrainingDetailResponseType
>
{
const
{
collectionId
}
=
parseApiInput
({
req
,
...
...
@@ -47,34 +56,38 @@ async function handler(req: ApiRequestProps): Promise<GetCollectionTrainingDetai
collectionId
:
new
Types
.
ObjectId
(
collection
.
_id
)
};
// Computed global queue
const
minId
=
(
await
MongoDatasetTraining
.
findOne
(
match
,
{
sort
:
{
_id
:
1
},
select
:
'_id'
}).
lean
()
)?.
_id
;
const
now
=
new
Date
();
const
activeTrainingExpr
=
Object
.
entries
(
MODE_LOCK_TIMEOUT_MINUTES
).
map
(
([
mode
,
timeoutMinutes
])
=>
({
mode
,
lockTime
:
{
$gt
:
subMinutes
(
now
,
timeoutMinutes
),
$lt
:
BLOCKED_LOCK_TIME
}
})
);
const
[
ququedCountData
,
trainingCountData
,
errorCountData
,
trainedCount
]
=
(
await
Promise
.
all
([
minId
?
MongoDatasetTraining
.
aggregate
([
{
$match
:
{
_id
:
{
$lt
:
new
Types
.
ObjectId
(
minId
)
},
retryCount
:
{
$gt
:
0
},
lockTime
:
{
$lt
:
BLOCKED_LOCK_TIME
}
}
},
{
$group
:
{
_id
:
'$mode'
,
count
:
{
$sum
:
1
}
}
}
])
:
Promise
.
resolve
([]),
MongoDatasetTraining
.
aggregate
([
{
$match
:
{
...
match
,
...
activeTrainingMatch
retryCount
:
{
$gt
:
0
},
lockTime
:
{
$lt
:
BLOCKED_LOCK_TIME
},
// 只统计当前集合里未被 worker 领取或锁超时后可重试的任务,避免跨知识库队列污染状态展示。
$nor
:
activeTrainingExpr
}
},
{
$group
:
{
_id
:
'$mode'
,
count
:
{
$sum
:
1
}
}
}
]),
MongoDatasetTraining
.
aggregate
([
{
$match
:
{
...
match
,
retryCount
:
{
$gt
:
0
},
$or
:
activeTrainingExpr
}
},
{
...
...
projects/app/test/api/core/dataset/collection/trainingStatus.test.ts
View file @
2eeea151
...
...
@@ -94,7 +94,7 @@ describe('collection training status api', () => {
});
});
it
(
'should
use active counts for progress and final errors for error tab
'
,
async
()
=>
{
it
(
'should
split current collection queued/running counts and final errors
'
,
async
()
=>
{
const
root
=
await
getRootUser
();
const
dataset
=
await
MongoDataset
.
create
({
name
:
'test'
,
...
...
@@ -120,6 +120,7 @@ describe('collection training status api', () => {
billId
:
'test'
,
mode
:
TrainingModeEnum
.
qa
,
retryCount
:
3
,
lockTime
:
new
Date
(),
errorMsg
:
'temporary failed'
},
{
...
...
@@ -128,6 +129,16 @@ describe('collection training status api', () => {
datasetId
:
dataset
.
_id
,
collectionId
:
collection
.
_id
,
billId
:
'test'
,
mode
:
TrainingModeEnum
.
parse
,
retryCount
:
3
,
lockTime
:
new
Date
(
'2000'
)
},
{
teamId
:
root
.
teamId
,
tmbId
:
root
.
tmbId
,
datasetId
:
dataset
.
_id
,
collectionId
:
collection
.
_id
,
billId
:
'test'
,
mode
:
TrainingModeEnum
.
chunk
,
retryCount
:
0
,
errorMsg
:
'final failed'
...
...
@@ -142,11 +153,84 @@ describe('collection training status api', () => {
});
expect
(
res
.
code
).
toBe
(
200
);
expect
(
res
.
data
.
queuedCounts
.
parse
).
toBe
(
1
);
expect
(
res
.
data
.
trainingCounts
.
parse
).
toBe
(
0
);
expect
(
res
.
data
.
queuedCounts
.
qa
).
toBe
(
0
);
expect
(
res
.
data
.
trainingCounts
.
qa
).
toBe
(
1
);
expect
(
res
.
data
.
errorCounts
.
qa
).
toBe
(
0
);
expect
(
res
.
data
.
errorCounts
.
chunk
).
toBe
(
1
);
});
it
(
'should not include other dataset training records in collection queued counts'
,
async
()
=>
{
const
root
=
await
getRootUser
();
const
[
dataset
,
otherDataset
]
=
await
MongoDataset
.
create
([
{
name
:
'current'
,
teamId
:
root
.
teamId
,
tmbId
:
root
.
tmbId
,
vectorModel
:
'test'
,
agentModel
:
'test'
},
{
name
:
'other'
,
teamId
:
root
.
teamId
,
tmbId
:
root
.
tmbId
,
vectorModel
:
'test'
,
agentModel
:
'test'
}
]);
const
[
collection
,
otherCollection
]
=
await
MongoDatasetCollection
.
create
([
{
name
:
'current'
,
type
:
DatasetCollectionTypeEnum
.
file
,
teamId
:
root
.
teamId
,
tmbId
:
root
.
tmbId
,
datasetId
:
dataset
.
_id
},
{
name
:
'other'
,
type
:
DatasetCollectionTypeEnum
.
file
,
teamId
:
root
.
teamId
,
tmbId
:
root
.
tmbId
,
datasetId
:
otherDataset
.
_id
}
]);
await
MongoDatasetTraining
.
create
([
...
Array
.
from
({
length
:
6
}).
map
(()
=>
({
teamId
:
root
.
teamId
,
tmbId
:
root
.
tmbId
,
datasetId
:
otherDataset
.
_id
,
collectionId
:
otherCollection
.
_id
,
billId
:
'test'
,
mode
:
TrainingModeEnum
.
chunk
,
retryCount
:
3
,
lockTime
:
new
Date
(
'2000'
)
})),
{
teamId
:
root
.
teamId
,
tmbId
:
root
.
tmbId
,
datasetId
:
dataset
.
_id
,
collectionId
:
collection
.
_id
,
billId
:
'test'
,
mode
:
TrainingModeEnum
.
parse
,
retryCount
:
3
,
lockTime
:
new
Date
(
'2000'
)
}
]);
const
res
=
await
Call
(
trainingDetailHandler
,
{
auth
:
root
,
query
:
{
collectionId
:
collection
.
_id
}
});
expect
(
res
.
code
).
toBe
(
200
);
expect
(
res
.
data
.
queuedCounts
.
parse
).
toBe
(
1
);
expect
(
res
.
data
.
queuedCounts
.
chunk
).
toBe
(
0
);
});
it
(
'should keep deprecated scrollList compatible with the collection list item schema'
,
async
()
=>
{
const
root
=
await
getRootUser
();
const
dataset
=
await
MongoDataset
.
create
({
...
...
projects/app/test/pageComponents/dataset/detail/CollectionCard/trainingStatesUtils.test.ts
0 → 100644
View file @
2eeea151
import
{
describe
,
expect
,
it
}
from
'vitest'
;
import
{
DatasetCollectionDataProcessModeEnum
,
TrainingModeEnum
}
from
'@fastgpt/global/core/dataset/constants'
;
import
type
{
GetCollectionTrainingDetailResponseType
}
from
'@fastgpt/global/openapi/core/dataset/collection/api'
;
import
{
getTrainingStepStatus
,
isTrainingStepHighlighted
,
TrainingStatus
}
from
'@/pageComponents/dataset/detail/CollectionCard/trainingStatesUtils'
;
const
createTrainingDetail
=
(
overrides
:
Partial
<
GetCollectionTrainingDetailResponseType
>
=
{}
):
GetCollectionTrainingDetailResponseType
=>
{
const
counts
=
{
parse
:
0
,
qa
:
0
,
chunk
:
0
,
image
:
0
,
auto
:
0
,
imageParse
:
0
};
return
{
trainingType
:
DatasetCollectionDataProcessModeEnum
.
chunk
,
advancedTraining
:
{
customPdfParse
:
false
,
imageIndex
:
false
,
autoIndexes
:
false
},
queuedCounts
:
{
...
counts
},
trainingCounts
:
{
...
counts
},
errorCounts
:
{
...
counts
},
trainedCount
:
0
,
...
overrides
};
};
describe
(
'trainingStatesUtils'
,
()
=>
{
it
(
'should mark parsing step as running while content parsing is active'
,
()
=>
{
const
trainingDetail
=
createTrainingDetail
({
trainingCounts
:
{
parse
:
1
,
qa
:
0
,
chunk
:
0
,
image
:
0
,
auto
:
0
,
imageParse
:
0
}
});
const
modeOrder
=
[
TrainingModeEnum
.
parse
,
TrainingModeEnum
.
chunk
];
expect
(
getTrainingStepStatus
({
trainingDetail
,
mode
:
TrainingModeEnum
.
parse
,
modeOrder
})
).
toBe
(
TrainingStatus
.
Running
);
expect
(
getTrainingStepStatus
({
trainingDetail
,
mode
:
TrainingModeEnum
.
chunk
,
modeOrder
})
).
toBe
(
TrainingStatus
.
NotStart
);
});
it
(
'should mark parsing step as queued while waiting to be picked by worker'
,
()
=>
{
const
trainingDetail
=
createTrainingDetail
({
queuedCounts
:
{
parse
:
1
,
qa
:
0
,
chunk
:
0
,
image
:
0
,
auto
:
0
,
imageParse
:
0
}
});
const
modeOrder
=
[
TrainingModeEnum
.
parse
,
TrainingModeEnum
.
chunk
];
expect
(
getTrainingStepStatus
({
trainingDetail
,
mode
:
TrainingModeEnum
.
parse
,
modeOrder
})
).
toBe
(
TrainingStatus
.
Queued
);
expect
(
getTrainingStepStatus
({
trainingDetail
,
mode
:
TrainingModeEnum
.
chunk
,
modeOrder
})
).
toBe
(
TrainingStatus
.
NotStart
);
});
it
(
'should mark earlier steps ready after later steps start'
,
()
=>
{
const
trainingDetail
=
createTrainingDetail
({
trainingCounts
:
{
parse
:
0
,
qa
:
0
,
chunk
:
1
,
image
:
0
,
auto
:
0
,
imageParse
:
0
},
trainedCount
:
1
});
const
modeOrder
=
[
TrainingModeEnum
.
parse
,
TrainingModeEnum
.
chunk
];
expect
(
getTrainingStepStatus
({
trainingDetail
,
mode
:
TrainingModeEnum
.
parse
,
modeOrder
})
).
toBe
(
TrainingStatus
.
Ready
);
expect
(
getTrainingStepStatus
({
trainingDetail
,
mode
:
TrainingModeEnum
.
chunk
,
modeOrder
})
).
toBe
(
TrainingStatus
.
Running
);
});
it
(
'should keep multiple in-progress stages highlighted at the same time'
,
()
=>
{
const
trainingDetail
=
createTrainingDetail
({
trainingCounts
:
{
parse
:
1
,
qa
:
0
,
chunk
:
2
,
image
:
0
,
auto
:
0
,
imageParse
:
0
}
});
const
modeOrder
=
[
TrainingModeEnum
.
parse
,
TrainingModeEnum
.
chunk
];
const
parseStatus
=
getTrainingStepStatus
({
trainingDetail
,
mode
:
TrainingModeEnum
.
parse
,
modeOrder
});
const
chunkStatus
=
getTrainingStepStatus
({
trainingDetail
,
mode
:
TrainingModeEnum
.
chunk
,
modeOrder
});
expect
(
parseStatus
).
toBe
(
TrainingStatus
.
Running
);
expect
(
chunkStatus
).
toBe
(
TrainingStatus
.
Running
);
expect
(
isTrainingStepHighlighted
(
parseStatus
)).
toBe
(
true
);
expect
(
isTrainingStepHighlighted
(
chunkStatus
)).
toBe
(
true
);
});
it
(
'should highlight completed steps and gray out only not-started steps'
,
()
=>
{
expect
(
isTrainingStepHighlighted
(
TrainingStatus
.
Ready
)).
toBe
(
true
);
expect
(
isTrainingStepHighlighted
(
TrainingStatus
.
Queued
)).
toBe
(
true
);
expect
(
isTrainingStepHighlighted
(
TrainingStatus
.
Running
)).
toBe
(
true
);
expect
(
isTrainingStepHighlighted
(
TrainingStatus
.
Error
)).
toBe
(
true
);
expect
(
isTrainingStepHighlighted
(
TrainingStatus
.
NotStart
)).
toBe
(
false
);
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment