fix: 修复 Memory V2 合并导致的启动崩溃
master 的 Memory V2 重构删除了 memory/updater.py,admin_users.py 仍从中导入 get_memory_data 导致启动 ImportError。改用 BuiltinFileProvider 读取 V2 的 USER.md / 各 agent MEMORY.md;前端 MemoryBucketView 同步改为渲染 V2 条目列表。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
1 parent
028c5a34e6
commit
bf34b7df4e
2 files changed
+43
-56
No files matched your search
@@ -71,67 +71,38 @@ function MessageRow({ message }: { message: AdminMessage }) {
|
||||
);
|
||||
}
|
||||
|
||||
/** Render one memory bucket: context summaries + facts. */
|
||||
/** Render one Memory V2 bucket: a flat list of USER.md / MEMORY.md entries. */
|
||||
function MemoryBucketView({ bucket }: { bucket: AdminMemoryBucket }) {
|
||||
const data = bucket.data as {
|
||||
user?: Record<string, { summary?: string }>;
|
||||
history?: Record<string, { summary?: string }>;
|
||||
facts?: Array<{ content?: string; category?: string; confidence?: number }>;
|
||||
};
|
||||
const sections: Array<[string, string]> = [];
|
||||
const labels: Record<string, string> = {
|
||||
workContext: "工作背景",
|
||||
personalContext: "个人背景",
|
||||
topOfMind: "近期关注",
|
||||
recentMonths: "近几个月",
|
||||
earlierContext: "更早记录",
|
||||
longTermBackground: "长期背景",
|
||||
};
|
||||
for (const group of [data.user, data.history]) {
|
||||
for (const [key, value] of Object.entries(group ?? {})) {
|
||||
const summary = value?.summary?.trim();
|
||||
if (summary) sections.push([labels[key] ?? key, summary]);
|
||||
}
|
||||
}
|
||||
const facts = (data.facts ?? []).filter((f) => f.content?.trim());
|
||||
const raw = (bucket.data as { entries?: unknown }).entries;
|
||||
const entries = Array.isArray(raw)
|
||||
? raw.filter(
|
||||
(e): e is string => typeof e === "string" && e.trim().length > 0,
|
||||
)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<div className="rounded-md border p-4">
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
<Badge variant={bucket.name === "global" ? "default" : "secondary"}>
|
||||
{bucket.name === "global" ? "全局记忆" : `Agent: ${bucket.name}`}
|
||||
{bucket.name === "global" ? "用户画像" : `Agent: ${bucket.name}`}
|
||||
</Badge>
|
||||
{entries.length > 0 && (
|
||||
<span className="text-muted-foreground text-xs">
|
||||
{entries.length} 条
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{sections.length === 0 && facts.length === 0 && (
|
||||
{entries.length === 0 ? (
|
||||
<p className="text-muted-foreground text-sm">该记忆库暂无内容。</p>
|
||||
)}
|
||||
{sections.length > 0 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
{sections.map(([label, summary]) => (
|
||||
<div key={label}>
|
||||
<span className="text-muted-foreground text-xs">{label}</span>
|
||||
<p className="text-sm">{summary}</p>
|
||||
</div>
|
||||
) : (
|
||||
<ul className="flex flex-col gap-1.5">
|
||||
{entries.map((entry, i) => (
|
||||
<li key={i} className="flex items-start gap-2 text-sm">
|
||||
<span className="text-muted-foreground">·</span>
|
||||
<span className="flex-1 whitespace-pre-wrap">{entry}</span>
|
||||
</li>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{facts.length > 0 && (
|
||||
<div className="mt-3">
|
||||
<span className="text-muted-foreground text-xs">事实记录({facts.length})</span>
|
||||
<ul className="mt-1 flex flex-col gap-1">
|
||||
{facts.map((fact, i) => (
|
||||
<li key={i} className="flex items-start gap-2 text-sm">
|
||||
<span className="text-muted-foreground">·</span>
|
||||
<span className="flex-1">{fact.content}</span>
|
||||
{fact.category && (
|
||||
<Badge variant="outline" className="shrink-0 text-xs">
|
||||
{fact.category}
|
||||
</Badge>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -23,7 +23,7 @@ from app.gateway.deps import (
|
||||
get_current_user_from_request,
|
||||
get_thread_store,
|
||||
)
|
||||
from deerflow.agents.memory.updater import get_memory_data
|
||||
from deerflow.agents.memory import BuiltinFileProvider
|
||||
from deerflow.config.agents_config import AGENT_NAME_PATTERN
|
||||
from deerflow.config.paths import get_paths
|
||||
from deerflow.runtime import serialize_channel_values
|
||||
@@ -136,24 +136,40 @@ class AdminMemoryResponse(BaseModel):
|
||||
buckets: list[AdminMemoryBucket] = Field(default_factory=list)
|
||||
|
||||
|
||||
def _read_memory_bucket(user_id: str, agent_id: str | None) -> dict:
|
||||
"""Read one Memory V2 file into a JSON-able ``{"entries": [...]}`` dict.
|
||||
|
||||
``agent_id is None`` -> the user's ``USER.md`` (profile, cross-agent);
|
||||
otherwise that agent's ``MEMORY.md`` (per-agent working notes).
|
||||
"""
|
||||
provider = BuiltinFileProvider()
|
||||
provider.initialize(user_id=user_id, agent_id=agent_id)
|
||||
target = "user" if agent_id is None else "memory"
|
||||
return {"entries": provider.read(target)}
|
||||
|
||||
|
||||
@router.get("/{user_id}/memory", response_model=AdminMemoryResponse)
|
||||
async def get_user_memory(user_id: str, request: Request) -> AdminMemoryResponse:
|
||||
"""Return every memory bucket (global + per-agent) for one user."""
|
||||
"""Return every memory bucket for one user.
|
||||
|
||||
Memory V2 layout: the ``global`` bucket is ``USER.md`` (user profile);
|
||||
each per-agent bucket is that agent's ``MEMORY.md`` working notes.
|
||||
"""
|
||||
await _require_admin(request)
|
||||
|
||||
buckets: list[AdminMemoryBucket] = [
|
||||
AdminMemoryBucket(name="global", data=get_memory_data(None, user_id=user_id))
|
||||
AdminMemoryBucket(name="global", data=_read_memory_bucket(user_id, None))
|
||||
]
|
||||
|
||||
agents_root = get_paths().user_dir(user_id) / "agents"
|
||||
if agents_root.is_dir():
|
||||
for entry in sorted(agents_root.iterdir(), key=lambda p: p.name.lower()):
|
||||
if not entry.is_dir() or not (entry / "memory.json").exists():
|
||||
if not entry.is_dir() or not (entry / "MEMORY.md").exists():
|
||||
continue
|
||||
if not AGENT_NAME_PATTERN.match(entry.name):
|
||||
continue
|
||||
try:
|
||||
data = get_memory_data(entry.name, user_id=user_id)
|
||||
data = _read_memory_bucket(user_id, entry.name)
|
||||
except Exception:
|
||||
logger.warning("Failed to read memory bucket %s for user", entry.name)
|
||||
continue
|
||||
|
||||
Reference in new issue
Block a user