From b3a39cbdf7e1bb9226b6e63af7583b125a0ff0a5 Mon Sep 17 00:00:00 2001 From: xuhd Date: Fri, 22 May 2026 12:22:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20V1=E8=AE=B0=E5=BF=86=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E3=80=81=E7=AE=A1=E7=90=86=E5=91=98=E8=AE=B0?= =?UTF-8?q?=E5=BF=86=E7=BC=96=E8=BE=91=E5=8F=8A=E5=AF=B9=E8=AF=9D=E8=AE=B0?= =?UTF-8?q?=E5=BF=86=E6=B3=A8=E5=85=A5=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 V1 记忆系统(memory.json)支持:提取结果强制中文、历史字段修复为增量累积日志 - 用户设置页面:V1 模式下新增 agent 选择器,可查看各 agent 的私有记忆 - 管理员记忆页面:区分 V1/V2 渲染,V1 支持完整 CRUD(用户画像/历史摘要/事实记录) - 后端新增 V1 记忆 CRUD API(sections/facts),前端同步添加对应 hooks 和 API 调用 - 新建专家页面:标记 bootstrap 线程类型,跳过记忆保存,侧边栏过滤该类线程 - 输入框工具栏新增「记忆」开关:关闭时 V1/V2 均不注入,开启时按 config.yaml 版本正常注入 - 修复 agent 目录不存在时的 FileNotFoundError,回退至默认配置 Co-Authored-By: Claude Sonnet 4.6 --- .../workspace/admin/admin-memory-page.tsx | 363 ++++++++++++- .../src/components/workspace/input-box.tsx | 31 ++ .../components/workspace/recent-chat-list.tsx | 4 +- .../settings/memory-settings-page.tsx | 174 +++++- frontend-web/src/core/admin/api.ts | 70 +++ frontend-web/src/core/admin/hooks.ts | 67 +++ frontend-web/src/core/memory/types.ts | 32 +- frontend-web/src/core/threads/hooks.ts | 1 + frontend-web/src/core/threads/types.ts | 11 +- frontend-web/src/pages/NewAgentPage.tsx | 2 +- .../app/gateway/routers/admin_users.py | 185 ++++++- .../backend/app/gateway/routers/memory.py | 139 ++++- offline-backend-20260512/backend/config.yaml | 16 +- .../deerflow/agents/lead_agent/agent.py | 16 +- .../deerflow/agents/lead_agent/prompt.py | 8 +- .../harness/deerflow/agents/memory/manager.py | 61 ++- .../agents/memory/message_processing.py | 39 ++ .../agents/memory/providers/v1_builtin.py | 161 ++++++ .../deerflow/agents/memory/v1/__init__.py | 1 + .../deerflow/agents/memory/v1/prompt.py | 371 +++++++++++++ .../deerflow/agents/memory/v1/queue.py | 253 +++++++++ .../deerflow/agents/memory/v1/storage.py | 226 ++++++++ .../agents/memory/v1/summarization_hook.py | 55 ++ .../deerflow/agents/memory/v1/updater.py | 495 ++++++++++++++++++ .../agents/middlewares/memory_middleware.py | 6 +- .../harness/deerflow/config/memory_config.py | 16 + .../deerflow/tools/builtins/memory_tool.py | 14 +- 27 files changed, 2725 insertions(+), 92 deletions(-) create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/agents/memory/providers/v1_builtin.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/agents/memory/v1/__init__.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/agents/memory/v1/prompt.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/agents/memory/v1/queue.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/agents/memory/v1/storage.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/agents/memory/v1/summarization_hook.py create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/agents/memory/v1/updater.py diff --git a/frontend-web/src/components/workspace/admin/admin-memory-page.tsx b/frontend-web/src/components/workspace/admin/admin-memory-page.tsx index eae8f24b..bd48cde9 100644 --- a/frontend-web/src/components/workspace/admin/admin-memory-page.tsx +++ b/frontend-web/src/components/workspace/admin/admin-memory-page.tsx @@ -19,8 +19,12 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Textarea } from "@/components/ui/textarea"; import { useAdminAddMemoryEntry, + useAdminAddV1Fact, useAdminDeleteMemoryEntry, + useAdminDeleteV1Fact, useAdminReplaceMemoryEntry, + useAdminUpdateV1Fact, + useAdminUpdateV1Section, useUserMemory, } from "@/core/admin/hooks"; import { useAllUsers } from "@/core/auth/hooks"; @@ -31,6 +35,288 @@ function getEntries(data: Record): string[] { return raw.filter((e): e is string => typeof e === "string" && e.trim().length > 0); } +// ── V1 只读视图 ──────────────────────────────────────────────────────────── + +const CTX_LABELS: Record = { + workContext: "工作背景", + personalContext: "个人信息", + topOfMind: "当前重点", +}; +const HISTORY_LABELS: Record = { + recentMonths: "近期", + earlierContext: "早期背景", + longTermBackground: "长期背景", +}; +const CATEGORY_COLORS: Record = { + preference: "bg-blue-100 text-blue-700", + knowledge: "bg-green-100 text-green-700", + context: "bg-yellow-100 text-yellow-700", + behavior: "bg-purple-100 text-purple-700", + goal: "bg-orange-100 text-orange-700", + correction: "bg-red-100 text-red-700", +}; + +const V1_CATEGORIES = ["preference", "knowledge", "context", "behavior", "goal", "correction"]; + +function extractSummary(val: unknown): string { + if (typeof val === "string") return val; + if (val && typeof val === "object" && "summary" in val) + return String((val as Record).summary ?? ""); + return ""; +} + +// 单个摘要字段的行内编辑 +function V1SectionField({ + label, + value, + onSave, +}: { + label: string; + value: string; + onSave: (v: string) => Promise; +}) { + const [editing, setEditing] = useState(false); + const [draft, setDraft] = useState(value); + const [saving, setSaving] = useState(false); + + async function handleSave() { + if (draft.trim() === value.trim()) { setEditing(false); return; } + setSaving(true); + try { await onSave(draft.trim()); setEditing(false); } + catch (err) { toast.error(err instanceof Error ? err.message : "保存失败"); } + finally { setSaving(false); } + } + + return ( +
+
+ {label} + {!editing && ( + + )} +
+ {editing ? ( +
+