Files
HomeAgent/internal/agent/core/status.go
JianFeeeee f11de37bf2 feat(scheduler): M7 可观测性 + 压力测试 + 端到端测试
设计依据 docs/zh/input-scheduler-design.md §11.5(O1/O2)、§11.6(E1/E2)。

- 可观测性:KernelStatus 新增 Scheduler 段(running/三集合深度/计数/
  深度上限),由 GetKernelStatus 从 DumpScheduler 原子快照填充;
  新增 events.EventScheduler,挂起/恢复各发一条(action/task/level)
- TaskKind.String() 便于日志与状态输出
- 新增 scheduler_e2e_test.go 3 项:
  · 压力:200 排队输入 + 50 中断全部经真实 loop 执行,结束时三集合排空、
    LLM 调用数精确等于输入数、无 Rejected
  · 可观测性:挂起/恢复事件齐备,状态快照计数一致
  · 端到端:完整启动 schedulerLoop+interceptLoop,经真实 channel 投递
    L1 任务与 L4 中断,验证「LLM 流式中断 → 挂起 → 中断先完成 → 原任务恢复」
    整条链路(LLM 调用数 = 丢弃1+中断1+恢复1+常规1)
- 验收:agent 全量 + -race;全仓 build/vet 通过
2026-09-13 00:45:28 +08:00

257 lines
6.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package core
import (
"fmt"
"runtime"
"time"
agentIO "gitcode.com/JianFeeeee/HomeAgent/internal/agent/io"
"gitcode.com/JianFeeeee/HomeAgent/internal/memory"
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/document"
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/social"
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/text"
"gitcode.com/JianFeeeee/HomeAgent/internal/meta"
"gitcode.com/JianFeeeee/HomeAgent/internal/plugin"
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
"gitcode.com/JianFeeeee/HomeAgent/internal/tracker"
)
// StatusProvider 内核状态查询接口。插件通过此接口查看内核运行动态。
type StatusProvider interface {
GetKernelStatus() *KernelStatus
}
// 状态 DTO 使用内置 SDK 的中立类型,保证与插件层解耦。
type KernelStatus = sdk.KernelStatus
type PluginInfo = sdk.PluginInfo
type ChannelInfo = sdk.ChannelInfo
type MemoryStatus = sdk.MemoryStatus
type KnowledgeStatus = sdk.KnowledgeStatus
type DocumentStatus = sdk.DocumentStatus
type TextMemoryStatus = sdk.TextMemoryStatus
type SocialStatus = sdk.SocialStatus
type LLMStatus = sdk.LLMStatus
type ContextStatus = sdk.ContextStatus
type RuntimeStatus = sdk.RuntimeStatus
type TrackerStatus = sdk.TrackerStatus
type BuildStatus = sdk.BuildStatus
func channelInfoFromIO(ch agentIO.ChannelInfo) ChannelInfo {
return ChannelInfo{
Name: ch.Name,
Type: fmt.Sprintf("%d", ch.Type),
Ready: true,
}
}
// collectKernelStatus 聚合内核各子系统状态快照。
// 接收所有子系统引用均为可选——nil 表示不可用),返回统一的状态报告。
func collectKernelStatus(
startTime time.Time,
agentID string,
providerName string,
sourceCount int,
stageHost *StageHost,
iom *agentIO.IOManager,
pluginReg *plugin.Registry,
memDB *memory.GraphDB,
ks interface{ List() []string },
docStore *document.Store,
textMem *text.Memory,
socialStore *social.SocialStore,
trk *tracker.Tracker,
) *KernelStatus {
status := &KernelStatus{
Uptime: time.Since(startTime).Round(time.Second).String(),
StartTime: startTime.Format(time.RFC3339),
AgentID: agentID,
// 构建身份取自内核自己的 meta-ldflags 注入点),
// 而非 SDK 仓的硬编码版本。
Build: BuildStatus{
Version: meta.Version,
Commit: meta.Commit,
BuildTime: meta.BuildTime,
SDKCompatible: meta.SDKCompatibleVersion,
KernelName: meta.KernelName,
// AGPL-3.0 §13状态页向网络使用者展示取得源码的入口。
SourceURL: meta.SourceURL,
},
Runtime: RuntimeStatus{
Goroutines: runtime.NumGoroutine(),
GoVersion: runtime.Version(),
},
LLM: LLMStatus{
Available: providerName != "",
Provider: providerName,
Sources: sourceCount,
},
}
// Plugins
if pluginReg != nil {
names := pluginReg.List()
for _, n := range names {
status.Plugins = append(status.Plugins, PluginInfo{Name: n, Loaded: true})
}
}
// Tools
if stageHost != nil {
status.Tools = stageHost.GetToolDefs()
}
// Channels
if iom != nil {
for _, ch := range iom.ListChannels() {
status.Channels = append(status.Channels, channelInfoFromIO(ch))
}
}
// Graph memory
if memDB != nil {
status.Memory.Available = true
if info, err := memDB.Introspect(); err == nil {
if ec, ok := info["entity_count"].(int); ok {
status.Memory.EntityCount = ec
}
if rc, ok := info["relation_count"].(int); ok {
status.Memory.RelationCount = rc
}
if et, ok := info["entity_type_count"].(int); ok {
status.Memory.EntityTypes = et
}
}
}
// Knowledge
if ks != nil {
status.Knowledge.Available = true
status.Knowledge.Items = ks.List()
status.Knowledge.ItemCount = len(status.Knowledge.Items)
}
// Documents
if docStore != nil {
status.Documents.Available = true
stats := docStore.Stats()
if dc, ok := stats["doc_count"].(int); ok {
status.Documents.DocCount = dc
}
if vc, ok := stats["vector_count"].(int); ok {
status.Documents.VectorCount = vc
}
}
// Text memory
if textMem != nil {
status.TextMemory.Available = true
status.TextMemory.FileCount = textMem.FileCount()
}
// Social
if socialStore != nil {
status.Social.Available = true
if persons, err := socialStore.ListPersons(); err == nil {
status.Social.PersonCount = len(persons)
}
}
// Tracker
if trk != nil {
status.Tracker.Available = true
status.Tracker.Dir = trk.MergeDir()
}
// Memory
var m runtime.MemStats
runtime.ReadMemStats(&m)
status.Runtime.MemoryMB = int64(m.Alloc / 1024 / 1024)
return status
}
// GetKernelStatus 返回 Agent 驱动的内核状态快照。
func (a *Agent) GetKernelStatus() *KernelStatus {
providerName := ""
sourceCount := 0
if a.providerManager != nil {
sourceCount = len(a.providerManager.List())
}
if a.provider != nil {
providerName = a.provider.Name()
}
var textMem *text.Memory
if a.textMem != nil {
textMem = a.textMem
}
var socialStore *social.SocialStore
if a.social != nil {
socialStore = a.social
}
var trk *tracker.Tracker
if a.tracker != nil {
trk = a.tracker
}
// 注意knowledge 在 collectKernelStatus 里是**接口**参数,
// 而 (*knowledge.Store)(nil) 塞进接口后 `ks != nil` 仍为真 → 调 List() 直接 panic。
// 所以这里必须先判具体指针再进行接口赋值healthcheck_kernel 会走到这条路径)。
var knowledgeLister interface{ List() []string }
if a.knowledge != nil {
knowledgeLister = a.knowledge
}
ks := collectKernelStatus(
a.startTime,
string(a.id),
providerName,
sourceCount,
a.stageHost,
a.io,
a.pluginReg,
a.memory,
knowledgeLister,
a.docStore,
textMem,
socialStore,
trk,
)
ks.ONNX = a.onnxStatus()
ks.Scheduler = a.schedulerStatus()
return ks
}
// onnxStatus 汇总统一多模态向量空间ONNX 模型)的启用状态。
//
// 判据是 Loaded()provider 真正打开且元数据合法),**不是**「配置里写了 provider」——
// 后者在模型缺失 / 运行时缺失时也为真,拿它当判据就是假绿。
func (a *Agent) onnxStatus() sdk.ONNXStatus {
st := sdk.ONNXStatus{Provider: a.embeddingProvider}
if a.multimodalSpace != nil && a.multimodalSpace.Loaded() {
st.Enabled = true
st.Dim = a.multimodalSpace.Dim()
st.Fingerprint = a.multimodalSpace.Fingerprint()
// 模态是可选能力:只有底层 provider 报出来时才带出。
if mr, ok := a.multimodalSpace.(interface{ Modalities() []string }); ok {
st.Modalities = mr.Modalities()
}
return st
}
switch {
case a.embeddingError != "":
st.Reason = "打开失败: " + a.embeddingError
case a.embeddingProvider == "":
st.Reason = "未配置统一向量空间 provider走词嵌入/TF-IDF 回退路径)"
default:
st.Reason = "provider 未加载"
}
return st
}
var _ StatusProvider = (*Agent)(nil)
var _ sdk.StatusAPI = (*Agent)(nil)