deeptutor-memory 1.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- deeptutor/agents/loop/native_memory.py +24 -0
- deeptutor/api/routers/memory.py +785 -0
- deeptutor/services/memory/__init__.py +42 -0
- deeptutor/services/memory/consolidator/__init__.py +62 -0
- deeptutor/services/memory/consolidator/chunker.py +131 -0
- deeptutor/services/memory/consolidator/guards.py +104 -0
- deeptutor/services/memory/consolidator/line_doc.py +486 -0
- deeptutor/services/memory/consolidator/meta.py +194 -0
- deeptutor/services/memory/consolidator/modes/__init__.py +31 -0
- deeptutor/services/memory/consolidator/modes/_runtime.py +319 -0
- deeptutor/services/memory/consolidator/modes/_shims.py +129 -0
- deeptutor/services/memory/consolidator/modes/audit.py +481 -0
- deeptutor/services/memory/consolidator/modes/dedup.py +228 -0
- deeptutor/services/memory/consolidator/modes/merge.py +227 -0
- deeptutor/services/memory/consolidator/modes/update.py +706 -0
- deeptutor/services/memory/consolidator/parse.py +135 -0
- deeptutor/services/memory/consolidator/prompts/en/_meta.yaml +36 -0
- deeptutor/services/memory/consolidator/prompts/en/audit_l2.yaml +42 -0
- deeptutor/services/memory/consolidator/prompts/en/audit_l3.yaml +38 -0
- deeptutor/services/memory/consolidator/prompts/en/dedup.yaml +37 -0
- deeptutor/services/memory/consolidator/prompts/en/update_l2.yaml +45 -0
- deeptutor/services/memory/consolidator/prompts/en/update_l3.yaml +51 -0
- deeptutor/services/memory/consolidator/prompts/zh/_meta.yaml +35 -0
- deeptutor/services/memory/consolidator/prompts/zh/audit_l2.yaml +40 -0
- deeptutor/services/memory/consolidator/prompts/zh/audit_l3.yaml +34 -0
- deeptutor/services/memory/consolidator/prompts/zh/dedup.yaml +34 -0
- deeptutor/services/memory/consolidator/prompts/zh/update_l2.yaml +40 -0
- deeptutor/services/memory/consolidator/prompts/zh/update_l3.yaml +43 -0
- deeptutor/services/memory/consolidator/references.py +388 -0
- deeptutor/services/memory/consolidator/runs.py +405 -0
- deeptutor/services/memory/document.py +235 -0
- deeptutor/services/memory/ids.py +80 -0
- deeptutor/services/memory/ops.py +149 -0
- deeptutor/services/memory/paths.py +104 -0
- deeptutor/services/memory/prompts/en.yaml +156 -0
- deeptutor/services/memory/prompts/zh.yaml +144 -0
- deeptutor/services/memory/recall.py +237 -0
- deeptutor/services/memory/settings.py +198 -0
- deeptutor/services/memory/snapshot/__init__.py +127 -0
- deeptutor/services/memory/snapshot/adapters.py +690 -0
- deeptutor/services/memory/snapshot/diff.py +67 -0
- deeptutor/services/memory/snapshot/entity.py +92 -0
- deeptutor/services/memory/snapshot/store.py +113 -0
- deeptutor/services/memory/store.py +453 -0
- deeptutor/services/memory/trace.py +153 -0
- deeptutor_cli/memory.py +99 -0
- deeptutor_memory-1.7.0.dist-info/METADATA +6 -0
- deeptutor_memory-1.7.0.dist-info/RECORD +50 -0
- deeptutor_memory-1.7.0.dist-info/WHEEL +5 -0
- deeptutor_memory-1.7.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Tolerant JSON parsers shared across the consolidator.
|
|
2
|
+
|
|
3
|
+
Two shapes are supported:
|
|
4
|
+
|
|
5
|
+
* :func:`parse_action` — one-action-per-turn envelope used by the
|
|
6
|
+
agentic loop driver. Returns ``None`` on failure so the loop can
|
|
7
|
+
surface a retry hint instead of crashing.
|
|
8
|
+
* :func:`_parse_ops_response` — legacy ``{"ops": [...]}`` shape, kept
|
|
9
|
+
because the workbench's preview → apply flow round-trips ops
|
|
10
|
+
through this parser (see :mod:`deeptutor.services.memory.store`).
|
|
11
|
+
|
|
12
|
+
Both strip code fences and tolerate prose framing so the model can
|
|
13
|
+
think out loud without invalidating the run.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
import json
|
|
20
|
+
import logging
|
|
21
|
+
import re
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from deeptutor.services.memory.ops import AddOp, DeleteOp, EditOp, Op
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ── Loop-mode action envelope ────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class ParsedAction:
|
|
34
|
+
name: str
|
|
35
|
+
args: dict[str, Any]
|
|
36
|
+
thought: str = ""
|
|
37
|
+
|
|
38
|
+
def arg(self, key: str, default: Any = None) -> Any:
|
|
39
|
+
return self.args.get(key, default)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def parse_action(raw: str) -> ParsedAction | None:
|
|
43
|
+
"""Parse one ``{"thought","action","args"}`` envelope from LLM text.
|
|
44
|
+
|
|
45
|
+
Returns ``None`` on any malformed input; the loop driver renders a
|
|
46
|
+
correction hint to the next turn so the model can self-recover.
|
|
47
|
+
"""
|
|
48
|
+
snippet = _extract_json_object(raw)
|
|
49
|
+
if snippet is None:
|
|
50
|
+
return None
|
|
51
|
+
try:
|
|
52
|
+
data = json.loads(snippet)
|
|
53
|
+
except json.JSONDecodeError:
|
|
54
|
+
return None
|
|
55
|
+
if not isinstance(data, dict):
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
name_raw = data.get("action")
|
|
59
|
+
if not isinstance(name_raw, str) or not name_raw.strip():
|
|
60
|
+
return None
|
|
61
|
+
args_raw = data.get("args")
|
|
62
|
+
args: dict[str, Any] = args_raw if isinstance(args_raw, dict) else {}
|
|
63
|
+
thought_raw = data.get("thought")
|
|
64
|
+
thought = thought_raw.strip() if isinstance(thought_raw, str) else ""
|
|
65
|
+
return ParsedAction(name=name_raw.strip(), args=args, thought=thought)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ── Legacy ops-array shape (kept for apply_ops_payload) ──────────────────
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _parse_ops_response(raw: str) -> list[Op]:
|
|
72
|
+
"""Tolerant parse of a legacy ``{"ops":[...]}`` envelope into ``Op``."""
|
|
73
|
+
snippet = _extract_json_object(raw)
|
|
74
|
+
if snippet is None:
|
|
75
|
+
logger.warning("memory consolidate: no JSON object in payload")
|
|
76
|
+
return []
|
|
77
|
+
try:
|
|
78
|
+
data = json.loads(snippet)
|
|
79
|
+
except json.JSONDecodeError:
|
|
80
|
+
logger.warning("memory consolidate: malformed JSON in payload")
|
|
81
|
+
return []
|
|
82
|
+
if not isinstance(data, dict):
|
|
83
|
+
return []
|
|
84
|
+
ops_raw = data.get("ops")
|
|
85
|
+
if not isinstance(ops_raw, list):
|
|
86
|
+
return []
|
|
87
|
+
|
|
88
|
+
ops: list[Op] = []
|
|
89
|
+
for raw_op in ops_raw:
|
|
90
|
+
op = _parse_one_op(raw_op)
|
|
91
|
+
if op is not None:
|
|
92
|
+
ops.append(op)
|
|
93
|
+
return ops
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _parse_one_op(raw_op: Any) -> Op | None:
|
|
97
|
+
if not isinstance(raw_op, dict):
|
|
98
|
+
return None
|
|
99
|
+
kind = raw_op.get("op")
|
|
100
|
+
try:
|
|
101
|
+
if kind == "add":
|
|
102
|
+
return AddOp(
|
|
103
|
+
section=str(raw_op.get("section", "")).strip(),
|
|
104
|
+
text=str(raw_op.get("text", "")).strip(),
|
|
105
|
+
refs=[str(r) for r in raw_op.get("refs", []) if r],
|
|
106
|
+
)
|
|
107
|
+
if kind == "edit":
|
|
108
|
+
return EditOp(
|
|
109
|
+
target_id=str(raw_op.get("target_id", "")).strip(),
|
|
110
|
+
new_text=str(raw_op.get("new_text", "")).strip(),
|
|
111
|
+
new_refs=[str(r) for r in raw_op.get("new_refs", []) if r],
|
|
112
|
+
)
|
|
113
|
+
if kind == "delete":
|
|
114
|
+
return DeleteOp(
|
|
115
|
+
target_id=str(raw_op.get("target_id", "")).strip(),
|
|
116
|
+
reason=str(raw_op.get("reason", "stale")).strip(),
|
|
117
|
+
)
|
|
118
|
+
except Exception: # noqa: BLE001 — be permissive at the parse layer
|
|
119
|
+
return None
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# ── Shared text extraction ───────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _extract_json_object(raw: str) -> str | None:
|
|
127
|
+
"""Strip code fences and pull out the first top-level JSON object."""
|
|
128
|
+
text = raw.strip()
|
|
129
|
+
text = re.sub(r"^```[a-zA-Z]*\s*", "", text)
|
|
130
|
+
text = re.sub(r"\s*```$", "", text)
|
|
131
|
+
start = text.find("{")
|
|
132
|
+
end = text.rfind("}")
|
|
133
|
+
if start == -1 or end == -1 or end <= start:
|
|
134
|
+
return None
|
|
135
|
+
return text[start : end + 1]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Per-surface and per-slot focus + suggested sections (English).
|
|
2
|
+
# Same data shape used by every mode's prompt template.
|
|
3
|
+
|
|
4
|
+
surfaces:
|
|
5
|
+
chat:
|
|
6
|
+
focus: "Stable misconceptions the user has surfaced, concepts they've demonstrated mastery of, and durable topics they keep returning to. Drop per-turn chatter."
|
|
7
|
+
sections: ["Misconceptions", "Mastery", "Topics"]
|
|
8
|
+
notebook:
|
|
9
|
+
focus: "Recurring note themes, formats the user prefers, and questions they keep coming back to in their notes."
|
|
10
|
+
sections: ["Themes", "Formats", "Open questions"]
|
|
11
|
+
quiz:
|
|
12
|
+
focus: "Error patterns across quiz attempts, topics with low/high success rate, and item types the user struggles with."
|
|
13
|
+
sections: ["Error patterns", "Strong topics", "Struggling topics"]
|
|
14
|
+
kb:
|
|
15
|
+
focus: "Document interests, query patterns into the knowledge base, and gaps in user's library."
|
|
16
|
+
sections: ["Interests", "Frequent queries", "Library gaps"]
|
|
17
|
+
book:
|
|
18
|
+
focus: "Reading pacing, sticking points (pages reopened or paused), and topics the user annotates heavily."
|
|
19
|
+
sections: ["Pacing", "Sticking points", "Annotation themes"]
|
|
20
|
+
partner:
|
|
21
|
+
focus: "Persistent themes across partner conversations and channel-specific behaviours."
|
|
22
|
+
sections: ["Themes", "Channels"]
|
|
23
|
+
cowriter:
|
|
24
|
+
focus: "Writing style preferences, recurring revisions, and topics the user drafts about."
|
|
25
|
+
sections: ["Style", "Recurring revisions", "Topics"]
|
|
26
|
+
|
|
27
|
+
slots:
|
|
28
|
+
recent:
|
|
29
|
+
focus: "A rolling timeline of what the user has been doing across all surfaces over the last 1–4 weeks. Time-anchored, surface-attributed."
|
|
30
|
+
sections: ["This week", "Earlier"]
|
|
31
|
+
profile:
|
|
32
|
+
focus: "Durable identity, learning style, and knowledge level. ONLY claims supported by multiple L2 entries across surfaces. No speculation about traits not directly evidenced."
|
|
33
|
+
sections: ["Identity", "Learning style", "Knowledge level"]
|
|
34
|
+
scope:
|
|
35
|
+
focus: "Concepts and topics the user has demonstrably engaged with, with confidence labels (familiar / practicing / unsure) tied to L2 evidence."
|
|
36
|
+
sections: ["Familiar", "Practicing", "Unsure"]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# L2 audit prompt (English) — line-level edits against raw evidence.
|
|
2
|
+
|
|
3
|
+
system: |
|
|
4
|
+
You are the memory auditor for DeepTutor user {user_label}.
|
|
5
|
+
|
|
6
|
+
ROLE: You are reading a chunk of {surface} L2 memory with each
|
|
7
|
+
entry annotated by its **full original source** (no truncation).
|
|
8
|
+
Your job is to find factual errors and edit the memory so it
|
|
9
|
+
matches the evidence.
|
|
10
|
+
|
|
11
|
+
OUTPUT: A single JSON object — nothing else.
|
|
12
|
+
|
|
13
|
+
{{"edits": [
|
|
14
|
+
{{"op": "replace", "line": <int>, "new_text": "<≤240>",
|
|
15
|
+
"refs": ["<surface>:<id>", ...], "reason": "<short>"}},
|
|
16
|
+
{{"op": "delete", "line_start": <int>, "line_end": <int>,
|
|
17
|
+
"reason": "<short>"}},
|
|
18
|
+
{{"op": "insert", "after_line": <int>, "text": "<≤240>",
|
|
19
|
+
"refs": ["<surface>:<id>", ...], "section": "<optional>",
|
|
20
|
+
"reason": "<short>"}}
|
|
21
|
+
]}}
|
|
22
|
+
|
|
23
|
+
HARD RULES
|
|
24
|
+
- Line numbers refer to the numbered view of the chunk below.
|
|
25
|
+
- Every replace/insert MUST cite refs from the sources shown for
|
|
26
|
+
that line (or for nearby lines in the same chunk).
|
|
27
|
+
- text/new_text ≤ 240 chars.
|
|
28
|
+
- Don't edit lines that are not bullet entries (titles, blanks,
|
|
29
|
+
section headers). Section creation happens implicitly via insert.
|
|
30
|
+
- Banned absolutist phrasing same as update mode.
|
|
31
|
+
- When nothing needs fixing, emit `{{"edits": []}}`. Don't churn.
|
|
32
|
+
- `reason` is short prose describing what evidence drives the edit.
|
|
33
|
+
|
|
34
|
+
Today is {today}.
|
|
35
|
+
|
|
36
|
+
user: |
|
|
37
|
+
# Annotated chunk of {surface} memory (each bullet shows its sources):
|
|
38
|
+
----------------------------------------------------------------
|
|
39
|
+
{chunk}
|
|
40
|
+
----------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
Return JSON. Edit only lines visible above.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# L3 audit prompt (English) — line-level edits against L2 evidence.
|
|
2
|
+
|
|
3
|
+
system: |
|
|
4
|
+
You are the cross-surface memory auditor for DeepTutor user {user_label}.
|
|
5
|
+
|
|
6
|
+
ROLE: You are reading a chunk of {slot} L3 memory annotated with
|
|
7
|
+
the **full L2 entries** it cites. Find claims that are
|
|
8
|
+
overgeneralized, contradicted by L2, or no longer supported.
|
|
9
|
+
|
|
10
|
+
OUTPUT: A single JSON object — nothing else.
|
|
11
|
+
|
|
12
|
+
{{"edits": [
|
|
13
|
+
{{"op": "replace", "line": <int>, "new_text": "<≤240, hedged>",
|
|
14
|
+
"refs": ["m_xxx", ...], "reason": "<short>"}},
|
|
15
|
+
{{"op": "delete", "line_start": <int>, "line_end": <int>,
|
|
16
|
+
"reason": "<short>"}},
|
|
17
|
+
{{"op": "insert", "after_line": <int>, "text": "<≤240, hedged>",
|
|
18
|
+
"refs": ["m_xxx", ...], "section": "<optional>",
|
|
19
|
+
"reason": "<short>"}}
|
|
20
|
+
]}}
|
|
21
|
+
|
|
22
|
+
HARD RULES (same as update_l3 plus audit-specific):
|
|
23
|
+
- replace/insert must cite L2 m_xxx ids visible in the annotated
|
|
24
|
+
sources of this chunk.
|
|
25
|
+
- Hedge: claims of the form "Across N <surface> interactions" or
|
|
26
|
+
"<surface> entries show…".
|
|
27
|
+
- Banned absolutist phrasing.
|
|
28
|
+
- Empty edits is correct when the chunk holds up.
|
|
29
|
+
|
|
30
|
+
Today is {today}.
|
|
31
|
+
|
|
32
|
+
user: |
|
|
33
|
+
# Annotated chunk of {slot} memory (each L3 bullet shows its L2 sources):
|
|
34
|
+
----------------------------------------------------------------
|
|
35
|
+
{chunk}
|
|
36
|
+
----------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
Return JSON.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Dedup prompt (English) — md-only line-level edits.
|
|
2
|
+
|
|
3
|
+
system: |
|
|
4
|
+
You are the memory dedup pass for DeepTutor user {user_label}.
|
|
5
|
+
|
|
6
|
+
ROLE: Read the entire memory document below as a line-numbered view.
|
|
7
|
+
Merge duplicates, collapse near-duplicates by replacing one and
|
|
8
|
+
deleting the others, and rewrite muddled entries.
|
|
9
|
+
|
|
10
|
+
OUTPUT: A single JSON object — nothing else.
|
|
11
|
+
|
|
12
|
+
{{"edits": [
|
|
13
|
+
{{"op": "replace", "line": <int>, "new_text": "<≤240>",
|
|
14
|
+
"refs": ["<existing-or-new-refs>", ...], "reason": "<short>"}},
|
|
15
|
+
{{"op": "delete", "line_start": <int>, "line_end": <int>,
|
|
16
|
+
"reason": "<merged into Lx | duplicate of Ly | low signal>"}}
|
|
17
|
+
]}}
|
|
18
|
+
|
|
19
|
+
HARD RULES
|
|
20
|
+
- You may use ONLY replace and delete — no insert. Dedup never adds.
|
|
21
|
+
- When merging A and B: keep the higher-quality one (replace its
|
|
22
|
+
line with the merged text, union of refs) and delete the other.
|
|
23
|
+
- When two entries restate the same fact verbatim, delete the later
|
|
24
|
+
duplicate (lower-line wins).
|
|
25
|
+
- text ≤ 240. Preserve refs (union when merging).
|
|
26
|
+
- Banned absolutist phrasing (same list as update mode).
|
|
27
|
+
- If nothing needs deduping, emit `{{"edits": []}}`. Don't churn.
|
|
28
|
+
|
|
29
|
+
Today is {today}.
|
|
30
|
+
|
|
31
|
+
user: |
|
|
32
|
+
# Memory document (line-numbered):
|
|
33
|
+
----------------------------------------------------------------
|
|
34
|
+
{doc}
|
|
35
|
+
----------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
Iteration {iteration}/{iterations_total}. Return JSON.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# L2 update prompt (English) — per-chunk fact extraction.
|
|
2
|
+
#
|
|
3
|
+
# Renders into a single LLM call per chunk; the call returns a JSON
|
|
4
|
+
# object {"facts": [...]} that the runtime validates against the
|
|
5
|
+
# chunk-local ref pool, then appends to the L2 doc.
|
|
6
|
+
|
|
7
|
+
system: |
|
|
8
|
+
You are the memory curator for DeepTutor user {user_label}.
|
|
9
|
+
|
|
10
|
+
ROLE: You are reading a chunk of the user's recent {surface} activity
|
|
11
|
+
(raw, untruncated). Extract durable facts about the user.
|
|
12
|
+
|
|
13
|
+
OUTPUT: A single JSON object — nothing else, no prose, no fences.
|
|
14
|
+
|
|
15
|
+
{{"facts": [
|
|
16
|
+
{{"text": "<≤240 chars; one fact per item>",
|
|
17
|
+
"section": "<one of: {sections}>",
|
|
18
|
+
"refs": ["<surface>:<entity_id>", ...]}}
|
|
19
|
+
]}}
|
|
20
|
+
|
|
21
|
+
HARD RULES
|
|
22
|
+
- Every fact must have ≥1 ref. Each ref must come from the
|
|
23
|
+
"Chunk-local citeable refs" list or the "@entity <surface>:<id>"
|
|
24
|
+
markers you see in the chunk below — do NOT invent ids, do NOT cite
|
|
25
|
+
entities outside this chunk.
|
|
26
|
+
- text ≤ 240 chars. Be terse, verb-led ("uses X", "stuck on Y").
|
|
27
|
+
- Banned absolutist phrasing (unless wrapping in "..." or 「...」):
|
|
28
|
+
deeply, truly, mastered, expert, passionate, loves, hates, always,
|
|
29
|
+
never, fully understands.
|
|
30
|
+
- Surface focus: {focus}.
|
|
31
|
+
- If nothing material is in this chunk, emit `{{"facts": []}}` —
|
|
32
|
+
that is a correct, expected answer.
|
|
33
|
+
|
|
34
|
+
Today is {today}.
|
|
35
|
+
|
|
36
|
+
user: |
|
|
37
|
+
# Existing {surface} memory (do not duplicate items already captured here):
|
|
38
|
+
{existing}
|
|
39
|
+
|
|
40
|
+
# Source chunk {chunk_index}/{chunk_total} (chars {chunk_start}..{chunk_end}):
|
|
41
|
+
----------------------------------------------------------------
|
|
42
|
+
{chunk}
|
|
43
|
+
----------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
Return JSON. Cite only refs listed or visible in the chunk above.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# L3 update prompt (English) — per-chunk cross-surface synthesis.
|
|
2
|
+
#
|
|
3
|
+
# Design contract (2026-05-21): L3 cites L2 *files*, not L2 entries. The
|
|
4
|
+
# chunk header lists the surfaces (e.g. ``chat``, ``notebook``) whose
|
|
5
|
+
# blocks are visible in this chunk; the model picks the subset that each
|
|
6
|
+
# fact actually drew from. Refs are bare surface names — never m_xxx,
|
|
7
|
+
# never entry ids.
|
|
8
|
+
|
|
9
|
+
system: |
|
|
10
|
+
You are the cross-surface memory curator for DeepTutor user {user_label}.
|
|
11
|
+
|
|
12
|
+
ROLE: You are reading a chunk of L2 summaries from one or more
|
|
13
|
+
surfaces (chat / notebook / quiz / kb / book / partner / cowriter).
|
|
14
|
+
Synthesize durable, hedged claims about the user.
|
|
15
|
+
|
|
16
|
+
OUTPUT: A single JSON object — nothing else.
|
|
17
|
+
|
|
18
|
+
{{"facts": [
|
|
19
|
+
{{"text": "<≤240 chars, hedged with surface/count>",
|
|
20
|
+
"section": "<one of: {sections}>",
|
|
21
|
+
"refs": ["<surface>", ...]}}
|
|
22
|
+
]}}
|
|
23
|
+
|
|
24
|
+
HARD RULES
|
|
25
|
+
- ``refs`` are **bare surface names** taken from the chunk's
|
|
26
|
+
"Chunk-local citeable refs" list (e.g. ``chat``, ``notebook``).
|
|
27
|
+
Never emit ``m_xxx``, ``surface:id``, or any entry id. One fact
|
|
28
|
+
may cite multiple surfaces if it genuinely synthesizes across them.
|
|
29
|
+
- text ≤ 240 chars. Forced hedge template: claims must be of the
|
|
30
|
+
form "Across N <surface> interactions, the user X" or
|
|
31
|
+
"<surface> entries show the user X" — bind to a surface or count.
|
|
32
|
+
- Banned absolutist phrasing (unless quoting with "..." or 「...」):
|
|
33
|
+
deeply, truly, mastered, expert, passionate, loves, hates, always,
|
|
34
|
+
never, fully understands.
|
|
35
|
+
- Slot focus: {focus}.
|
|
36
|
+
- Empty `{{"facts": []}}` is a correct answer if nothing in this
|
|
37
|
+
chunk warrants a new L3 claim.
|
|
38
|
+
|
|
39
|
+
Today is {today}.
|
|
40
|
+
|
|
41
|
+
user: |
|
|
42
|
+
# Existing {slot} memory (do not duplicate):
|
|
43
|
+
{existing}
|
|
44
|
+
|
|
45
|
+
# L2 chunk {chunk_index}/{chunk_total}:
|
|
46
|
+
----------------------------------------------------------------
|
|
47
|
+
{chunk}
|
|
48
|
+
----------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
Return JSON. Cite only surface names from the "Chunk-local citeable
|
|
51
|
+
refs" list at the top of the chunk.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# 每个 surface / slot 的焦点 + 推荐 section(中文)。
|
|
2
|
+
|
|
3
|
+
surfaces:
|
|
4
|
+
chat:
|
|
5
|
+
focus: "用户呈现出的稳定误解、已经显示出掌握的概念、以及反复回到的持久话题。剔除单轮闲聊。"
|
|
6
|
+
sections: ["误解", "掌握", "话题"]
|
|
7
|
+
notebook:
|
|
8
|
+
focus: "笔记中反复出现的主题、用户偏好的格式、以及反复出现的问题。"
|
|
9
|
+
sections: ["主题", "格式", "悬而未决的问题"]
|
|
10
|
+
quiz:
|
|
11
|
+
focus: "答题中的错误模式、正确率高/低的话题、用户在哪类题型上挣扎。"
|
|
12
|
+
sections: ["错误模式", "强项", "弱项"]
|
|
13
|
+
kb:
|
|
14
|
+
focus: "用户的文档兴趣、对知识库的查询模式、文献库中的空白。"
|
|
15
|
+
sections: ["兴趣", "高频查询", "文献库空白"]
|
|
16
|
+
book:
|
|
17
|
+
focus: "阅读节奏、卡点(页面重复打开或停留)、用户重点标注的主题。"
|
|
18
|
+
sections: ["节奏", "卡点", "标注主题"]
|
|
19
|
+
partner:
|
|
20
|
+
focus: "伙伴对话中反复出现的主题、不同频道的行为差异。"
|
|
21
|
+
sections: ["主题", "频道"]
|
|
22
|
+
cowriter:
|
|
23
|
+
focus: "写作风格偏好、反复修改的模式、用户起草的话题。"
|
|
24
|
+
sections: ["风格", "反复修改", "话题"]
|
|
25
|
+
|
|
26
|
+
slots:
|
|
27
|
+
recent:
|
|
28
|
+
focus: "最近 1-4 周用户跨 surface 的活动滚动时间线。时间锚定、surface 归属。"
|
|
29
|
+
sections: ["本周", "更早"]
|
|
30
|
+
profile:
|
|
31
|
+
focus: "持久的身份、学习风格、知识水平。只接受多个 surface 多个 L2 条目支撑的判断,禁止臆测。"
|
|
32
|
+
sections: ["身份", "学习风格", "知识水平"]
|
|
33
|
+
scope:
|
|
34
|
+
focus: "用户有过实际接触的概念,含信心标签(熟悉 / 练习中 / 不确定),均绑定 L2 证据。"
|
|
35
|
+
sections: ["熟悉", "练习中", "不确定"]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# L2 检查(中文)— 行级编辑。
|
|
2
|
+
|
|
3
|
+
system: |
|
|
4
|
+
你是 DeepTutor 用户 {user_label} 的记忆审计员。
|
|
5
|
+
|
|
6
|
+
任务:阅读一段 {surface} 的 L2 记忆,每个条目下都附上了它引用的
|
|
7
|
+
**完整原始内容**(不截断)。找出与原始内容不一致或事实错误的条目,
|
|
8
|
+
通过编辑修正。
|
|
9
|
+
|
|
10
|
+
输出:唯一一个 JSON 对象。
|
|
11
|
+
|
|
12
|
+
{{"edits": [
|
|
13
|
+
{{"op": "replace", "line": <int>, "new_text": "<≤240>",
|
|
14
|
+
"refs": ["<surface>:<id>", ...], "reason": "<简短>"}},
|
|
15
|
+
{{"op": "delete", "line_start": <int>, "line_end": <int>,
|
|
16
|
+
"reason": "<简短>"}},
|
|
17
|
+
{{"op": "insert", "after_line": <int>, "text": "<≤240>",
|
|
18
|
+
"refs": ["<surface>:<id>", ...], "section": "<可选>",
|
|
19
|
+
"reason": "<简短>"}}
|
|
20
|
+
]}}
|
|
21
|
+
|
|
22
|
+
硬性规则
|
|
23
|
+
- line 必须对应下面带行号的视图。
|
|
24
|
+
- replace/insert 的 refs 必须来自当前条目(或同一 chunk 邻近条目)
|
|
25
|
+
展示的 sources。
|
|
26
|
+
- text/new_text ≤ 240。
|
|
27
|
+
- 不要编辑非 bullet 行(标题、空行、section 标题)。
|
|
28
|
+
- 同 update 的禁词清单。
|
|
29
|
+
- 如果不需要任何修改,返回 `{{"edits": []}}`,不要为改而改。
|
|
30
|
+
- `reason` 用一句话说明依据。
|
|
31
|
+
|
|
32
|
+
今天是 {today}。
|
|
33
|
+
|
|
34
|
+
user: |
|
|
35
|
+
# 带源 trace 注解的 {surface} 记忆 chunk:
|
|
36
|
+
----------------------------------------------------------------
|
|
37
|
+
{chunk}
|
|
38
|
+
----------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
返回 JSON。只编辑上面可见的行号。
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# L3 检查(中文)— 行级编辑。
|
|
2
|
+
|
|
3
|
+
system: |
|
|
4
|
+
你是 DeepTutor 用户 {user_label} 的跨 surface 记忆审计员。
|
|
5
|
+
|
|
6
|
+
任务:阅读一段 {slot} 的 L3 记忆,每个条目都附上了它引用的
|
|
7
|
+
**完整 L2 entry**。找出过度概括、被 L2 反驳、或证据不足的判断,
|
|
8
|
+
通过编辑修正。
|
|
9
|
+
|
|
10
|
+
输出:唯一一个 JSON 对象。
|
|
11
|
+
|
|
12
|
+
{{"edits": [
|
|
13
|
+
{{"op": "replace", "line": <int>, "new_text": "<≤240,已对冲>",
|
|
14
|
+
"refs": ["m_xxx", ...], "reason": "<简短>"}},
|
|
15
|
+
{{"op": "delete", "line_start": <int>, "line_end": <int>,
|
|
16
|
+
"reason": "<简短>"}},
|
|
17
|
+
{{"op": "insert", "after_line": <int>, "text": "<≤240,已对冲>",
|
|
18
|
+
"refs": ["m_xxx", ...], "section": "<可选>",
|
|
19
|
+
"reason": "<简短>"}}
|
|
20
|
+
]}}
|
|
21
|
+
|
|
22
|
+
硬性规则
|
|
23
|
+
- 同 update_l3 的 ref 规则 + L3 对冲模板。
|
|
24
|
+
- 不要为改而改,没问题就回 `{{"edits": []}}`。
|
|
25
|
+
|
|
26
|
+
今天是 {today}。
|
|
27
|
+
|
|
28
|
+
user: |
|
|
29
|
+
# 带 L2 source 注解的 {slot} 记忆 chunk:
|
|
30
|
+
----------------------------------------------------------------
|
|
31
|
+
{chunk}
|
|
32
|
+
----------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
返回 JSON。
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# 去重(中文)— md only 行级编辑。
|
|
2
|
+
|
|
3
|
+
system: |
|
|
4
|
+
你是 DeepTutor 用户 {user_label} 的记忆去重员。
|
|
5
|
+
|
|
6
|
+
任务:完整读取下面带行号的记忆文档,合并重复、删除冗余、改写不清晰的条目。
|
|
7
|
+
|
|
8
|
+
输出:唯一一个 JSON 对象。
|
|
9
|
+
|
|
10
|
+
{{"edits": [
|
|
11
|
+
{{"op": "replace", "line": <int>, "new_text": "<≤240>",
|
|
12
|
+
"refs": ["<原有或新增 ref>", ...], "reason": "<简短>"}},
|
|
13
|
+
{{"op": "delete", "line_start": <int>, "line_end": <int>,
|
|
14
|
+
"reason": "<合并到 Lx | Ly 的重复 | 低信号>"}}
|
|
15
|
+
]}}
|
|
16
|
+
|
|
17
|
+
硬性规则
|
|
18
|
+
- 只允许 replace 和 delete,不允许 insert。
|
|
19
|
+
- 合并 A 和 B 时:保留质量更高的(replace 它的行,refs 取并集),
|
|
20
|
+
删掉另一条。
|
|
21
|
+
- 字面重复时,删掉行号更靠后的(保留靠前的)。
|
|
22
|
+
- text ≤ 240。合并时 refs 取并集。
|
|
23
|
+
- 同 update 的禁词清单。
|
|
24
|
+
- 没什么可去重的就回 `{{"edits": []}}`。
|
|
25
|
+
|
|
26
|
+
今天是 {today}。
|
|
27
|
+
|
|
28
|
+
user: |
|
|
29
|
+
# 记忆文档(带行号):
|
|
30
|
+
----------------------------------------------------------------
|
|
31
|
+
{doc}
|
|
32
|
+
----------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
第 {iteration} / {iterations_total} 轮。返回 JSON。
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# L2 更新(中文)— 按 chunk 抽取事实。
|
|
2
|
+
|
|
3
|
+
system: |
|
|
4
|
+
你是 DeepTutor 用户 {user_label} 的记忆管理员。
|
|
5
|
+
|
|
6
|
+
任务:阅读用户最近 {surface} 活动的一段原始内容(未截断),抽取关于用户
|
|
7
|
+
的稳定事实。
|
|
8
|
+
|
|
9
|
+
输出:唯一一个 JSON 对象 — 不要散文,不要代码块。
|
|
10
|
+
|
|
11
|
+
{{"facts": [
|
|
12
|
+
{{"text": "<≤240 字符;每条一个事实>",
|
|
13
|
+
"section": "<必须取值于:{sections}>",
|
|
14
|
+
"refs": ["<surface>:<entity_id>", ...]}}
|
|
15
|
+
]}}
|
|
16
|
+
|
|
17
|
+
硬性规则
|
|
18
|
+
- 每条事实必须有至少 1 个 ref。ref 必须来自下面 chunk 内的
|
|
19
|
+
"Chunk-local citeable refs" 列表或 "@entity <surface>:<id>" 标记。
|
|
20
|
+
绝对不要编造 id,绝对不要引用 chunk 范围之外的 entity。
|
|
21
|
+
- text ≤ 240 字符。简洁,动词为主(「使用 X」「卡在 Y」)。
|
|
22
|
+
- 禁止绝对化用语(除非用 "..." 或 「...」 引用用户原话):
|
|
23
|
+
深刻、彻底、完美掌握、完全理解、专家、热爱、总是、从来不、
|
|
24
|
+
deeply、truly、mastered、expert、passionate、loves、hates、always、
|
|
25
|
+
never、fully understands。
|
|
26
|
+
- Surface 焦点:{focus}。
|
|
27
|
+
- 如果这个 chunk 里没有值得记录的事实,回 `{{"facts": []}}` 是正常的。
|
|
28
|
+
|
|
29
|
+
今天是 {today}。
|
|
30
|
+
|
|
31
|
+
user: |
|
|
32
|
+
# 已有的 {surface} 记忆(不要重复已经记录过的事实):
|
|
33
|
+
{existing}
|
|
34
|
+
|
|
35
|
+
# 源 chunk {chunk_index}/{chunk_total}(字符 {chunk_start}..{chunk_end}):
|
|
36
|
+
----------------------------------------------------------------
|
|
37
|
+
{chunk}
|
|
38
|
+
----------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
返回 JSON。只能引用上面列出或出现过的 ref。
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# L3 更新(中文)— 跨 surface 综合。
|
|
2
|
+
#
|
|
3
|
+
# 设计契约(2026-05-21):L3 引用的是 L2 的**文件**,不是 L2 的 entry。每个 chunk
|
|
4
|
+
# 开头会列出本 chunk 可见的 surface 名(chat / notebook / quiz / kb / book /
|
|
5
|
+
# partner / cowriter),模型应当为每条 fact 挑出它真正综合到的那些 surface。
|
|
6
|
+
# refs 字段只能写裸的 surface 名,绝不可写 m_xxx 或 entry id。
|
|
7
|
+
|
|
8
|
+
system: |
|
|
9
|
+
你是 DeepTutor 用户 {user_label} 的跨 surface 记忆管理员。
|
|
10
|
+
|
|
11
|
+
任务:阅读若干 surface 的 L2 摘要片段(chat / notebook / quiz / kb / book /
|
|
12
|
+
partner / cowriter),给出关于用户的稳定、对冲过的判断。
|
|
13
|
+
|
|
14
|
+
输出:唯一一个 JSON 对象。
|
|
15
|
+
|
|
16
|
+
{{"facts": [
|
|
17
|
+
{{"text": "<≤240 字符,带 surface/计数对冲>",
|
|
18
|
+
"section": "<必须取值于:{sections}>",
|
|
19
|
+
"refs": ["<surface 名>", ...]}}
|
|
20
|
+
]}}
|
|
21
|
+
|
|
22
|
+
硬性规则
|
|
23
|
+
- ``refs`` 只能是 chunk 顶部 "Chunk-local citeable refs" 列表里的**裸 surface 名**
|
|
24
|
+
(如 ``chat``、``notebook``)。**禁止**写 ``m_xxx`` 或 ``surface:id``
|
|
25
|
+
这类 entry id。一条 fact 如果是跨 surface 综合的,可以同时引用多个 surface。
|
|
26
|
+
- text ≤ 240。强制对冲:判断必须形如
|
|
27
|
+
「在 N 次 <surface> 互动中,用户 X」或「<surface> 记忆显示用户 X」。
|
|
28
|
+
- 禁止绝对化用语(除非 "..." 或 「...」 原话):同 update_l2。
|
|
29
|
+
- Slot 焦点:{focus}。
|
|
30
|
+
- 没有合适的事实就回 `{{"facts": []}}`。
|
|
31
|
+
|
|
32
|
+
今天是 {today}。
|
|
33
|
+
|
|
34
|
+
user: |
|
|
35
|
+
# 已有的 {slot} 记忆(不要重复):
|
|
36
|
+
{existing}
|
|
37
|
+
|
|
38
|
+
# L2 片段 {chunk_index}/{chunk_total}:
|
|
39
|
+
----------------------------------------------------------------
|
|
40
|
+
{chunk}
|
|
41
|
+
----------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
返回 JSON。refs 只能从 chunk 顶部 "Chunk-local citeable refs" 里挑 surface 名。
|