python-library-ai-agent 0.1.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.
- ai_agent/__init__.py +66 -0
- ai_agent/agent.py +122 -0
- ai_agent/app/__init__.py +10 -0
- ai_agent/app/_workspace.py +127 -0
- ai_agent/app/app.py +321 -0
- ai_agent/app/harness_io.py +109 -0
- ai_agent/app/output_format.py +77 -0
- ai_agent/app/packet.py +39 -0
- ai_agent/app/session.py +742 -0
- ai_agent/app/session_store.py +85 -0
- ai_agent/builtin_tools/__init__.py +18 -0
- ai_agent/builtin_tools/current_time.py +39 -0
- ai_agent/builtin_tools/pack.py +20 -0
- ai_agent/builtin_tools/prefix.py +11 -0
- ai_agent/context.py +151 -0
- ai_agent/harness/__init__.py +3 -0
- ai_agent/harness/current_time.py +25 -0
- ai_agent/harness/harness.py +324 -0
- ai_agent/harness/process.py +115 -0
- ai_agent/harness/prompts.py +38 -0
- ai_agent/harness/sandbox.py +139 -0
- ai_agent/json_extract.py +70 -0
- ai_agent/listener.py +172 -0
- ai_agent/llm.py +39 -0
- ai_agent/llm_openai.py +117 -0
- ai_agent/loop.py +124 -0
- ai_agent/mcp_config.py +54 -0
- ai_agent/mcp_loader.py +110 -0
- ai_agent/memory/__init__.py +9 -0
- ai_agent/memory/compression_work.py +71 -0
- ai_agent/memory/compressor.py +339 -0
- ai_agent/memory/config.py +40 -0
- ai_agent/memory/context_builder.py +57 -0
- ai_agent/memory/memory_system.py +561 -0
- ai_agent/memory/models.py +76 -0
- ai_agent/memory/snapshot_merge.py +158 -0
- ai_agent/memory/store.py +107 -0
- ai_agent/memory/worker.py +227 -0
- ai_agent/plan/__init__.py +15 -0
- ai_agent/plan/complete.py +64 -0
- ai_agent/plan/delivery.py +41 -0
- ai_agent/plan/display.py +46 -0
- ai_agent/plan/models.py +44 -0
- ai_agent/plan/parse.py +39 -0
- ai_agent/plan/planner.py +204 -0
- ai_agent/plan/runner.py +281 -0
- ai_agent/react_tool_turn.py +39 -0
- ai_agent/rule/__init__.py +3 -0
- ai_agent/rule/rules.py +36 -0
- ai_agent/skill/__init__.py +5 -0
- ai_agent/skill/builtin_registry.py +56 -0
- ai_agent/skill/catalog.py +104 -0
- ai_agent/skill/frontmatter.py +83 -0
- ai_agent/skill/manager.py +486 -0
- ai_agent/skill/models.py +31 -0
- ai_agent/skill/roots.py +150 -0
- ai_agent/skill/skill_kit.py +80 -0
- ai_agent/skill/tool_declarations.py +68 -0
- ai_agent/tools.py +123 -0
- python_library_ai_agent-0.1.0.dist-info/METADATA +10 -0
- python_library_ai_agent-0.1.0.dist-info/RECORD +62 -0
- python_library_ai_agent-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from typing import Any, Protocol
|
|
7
|
+
|
|
8
|
+
from ai_agent.context import ChatMessage, RunContext
|
|
9
|
+
from ai_agent.llm import LLMClient, StreamKind
|
|
10
|
+
from ai_agent.memory.models import (
|
|
11
|
+
DateMemoryEntry,
|
|
12
|
+
ImportantMemoryEntry,
|
|
13
|
+
LongTermChunk,
|
|
14
|
+
MemoryMessage,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MemoryCompressor(Protocol):
|
|
19
|
+
"""记忆压缩接口;默认由语言模型实现,测试可替换。"""
|
|
20
|
+
|
|
21
|
+
async def compress_to_date_entries(
|
|
22
|
+
self,
|
|
23
|
+
messages: list[MemoryMessage],
|
|
24
|
+
) -> tuple[list[DateMemoryEntry], list[str]]:
|
|
25
|
+
"""
|
|
26
|
+
将一批短期消息压缩为日期记忆条目,并提取重要事实。
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
(日期条目, 重要事实文本列表)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
async def merge_date_to_long_term(
|
|
33
|
+
self,
|
|
34
|
+
day_label: str,
|
|
35
|
+
entries: list[DateMemoryEntry],
|
|
36
|
+
) -> LongTermChunk:
|
|
37
|
+
"""将一整天的日期记忆合并为一块长期记忆。"""
|
|
38
|
+
|
|
39
|
+
async def merge_long_term_chunks(
|
|
40
|
+
self,
|
|
41
|
+
chunks: list[LongTermChunk],
|
|
42
|
+
) -> list[LongTermChunk]:
|
|
43
|
+
"""合并较旧的长期记忆块并降低清晰度。"""
|
|
44
|
+
|
|
45
|
+
async def reconcile_important(
|
|
46
|
+
self,
|
|
47
|
+
entries: list[ImportantMemoryEntry],
|
|
48
|
+
) -> list[ImportantMemoryEntry]:
|
|
49
|
+
"""压缩重要记忆并调和矛盾。"""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class LLMMemoryCompressor:
|
|
53
|
+
"""用语言模型执行各层压缩。"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, llm: LLMClient) -> None:
|
|
56
|
+
self._llm = llm
|
|
57
|
+
|
|
58
|
+
async def compress_to_date_entries(
|
|
59
|
+
self,
|
|
60
|
+
messages: list[MemoryMessage],
|
|
61
|
+
) -> tuple[list[DateMemoryEntry], list[str]]:
|
|
62
|
+
if not messages:
|
|
63
|
+
return [], []
|
|
64
|
+
lines = []
|
|
65
|
+
for msg in messages:
|
|
66
|
+
stamp = msg.at.isoformat()
|
|
67
|
+
lines.append(f"[{stamp}] {msg.speaker} ({msg.role}): {msg.content}")
|
|
68
|
+
user_text = "\n".join(lines)
|
|
69
|
+
system = (
|
|
70
|
+
"你是会话记忆整理助手。输入是一批带时间与讲述者的对话原文。"
|
|
71
|
+
"过滤寒暄与重复,保留事实、决定、偏好与待办。"
|
|
72
|
+
"输出 JSON 对象,字段:"
|
|
73
|
+
"entries 为数组,每项含 at(ISO8601)、speaker、summary;"
|
|
74
|
+
"important 为字符串数组,列出需长期记住的事实。"
|
|
75
|
+
"只输出 JSON,不要 markdown。"
|
|
76
|
+
)
|
|
77
|
+
raw = await _complete(self._llm, system, user_text)
|
|
78
|
+
data = _parse_json_object(raw)
|
|
79
|
+
entries: list[DateMemoryEntry] = []
|
|
80
|
+
for item in data.get("entries", []):
|
|
81
|
+
if not isinstance(item, dict):
|
|
82
|
+
continue
|
|
83
|
+
summary = str(item.get("summary", "")).strip()
|
|
84
|
+
speaker = str(item.get("speaker", "")).strip() or "unknown"
|
|
85
|
+
at_raw = item.get("at")
|
|
86
|
+
at = _parse_datetime(at_raw, fallback=messages[0].at)
|
|
87
|
+
if summary:
|
|
88
|
+
entries.append(
|
|
89
|
+
DateMemoryEntry(at=at, speaker=speaker, summary=summary),
|
|
90
|
+
)
|
|
91
|
+
important = [
|
|
92
|
+
str(x).strip()
|
|
93
|
+
for x in data.get("important", [])
|
|
94
|
+
if str(x).strip()
|
|
95
|
+
]
|
|
96
|
+
if not entries:
|
|
97
|
+
entries = _fallback_date_entries(messages)
|
|
98
|
+
return entries, important
|
|
99
|
+
|
|
100
|
+
async def merge_date_to_long_term(
|
|
101
|
+
self,
|
|
102
|
+
day_label: str,
|
|
103
|
+
entries: list[DateMemoryEntry],
|
|
104
|
+
) -> LongTermChunk:
|
|
105
|
+
if not entries:
|
|
106
|
+
now = datetime.now(timezone.utc)
|
|
107
|
+
return LongTermChunk(
|
|
108
|
+
created_at=now,
|
|
109
|
+
updated_at=now,
|
|
110
|
+
summary=f"{day_label} 无有效记忆",
|
|
111
|
+
clarity=0.6,
|
|
112
|
+
)
|
|
113
|
+
lines = []
|
|
114
|
+
for entry in entries:
|
|
115
|
+
stamp = entry.at.isoformat()
|
|
116
|
+
lines.append(f"[{stamp}] {entry.speaker}: {entry.summary}")
|
|
117
|
+
system = (
|
|
118
|
+
"将一天的日期记忆压缩为一段长期记忆摘要。"
|
|
119
|
+
"保留关键事实但允许适度模糊;输出 JSON:"
|
|
120
|
+
'{"summary":"..."}。只输出 JSON。'
|
|
121
|
+
)
|
|
122
|
+
raw = await _complete(self._llm, system, "\n".join(lines))
|
|
123
|
+
data = _parse_json_object(raw)
|
|
124
|
+
summary = str(data.get("summary", "")).strip()
|
|
125
|
+
if not summary:
|
|
126
|
+
summary = _fallback_day_summary(day_label, entries)
|
|
127
|
+
now = datetime.now(timezone.utc)
|
|
128
|
+
return LongTermChunk(
|
|
129
|
+
created_at=now,
|
|
130
|
+
updated_at=now,
|
|
131
|
+
summary=summary,
|
|
132
|
+
clarity=0.75,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
async def merge_long_term_chunks(
|
|
136
|
+
self,
|
|
137
|
+
chunks: list[LongTermChunk],
|
|
138
|
+
) -> list[LongTermChunk]:
|
|
139
|
+
if len(chunks) <= 1:
|
|
140
|
+
return chunks
|
|
141
|
+
ordered = sorted(chunks, key=lambda c: c.created_at)
|
|
142
|
+
merge_count = max(1, len(ordered) // 3)
|
|
143
|
+
old_batch = ordered[:merge_count]
|
|
144
|
+
rest = ordered[merge_count:]
|
|
145
|
+
lines = []
|
|
146
|
+
for chunk in old_batch:
|
|
147
|
+
lines.append(f"(clarity={chunk.clarity:.2f}) {chunk.summary}")
|
|
148
|
+
system = (
|
|
149
|
+
"将多段较旧的长期记忆融合为更少、更模糊的摘要块。"
|
|
150
|
+
"输出 JSON:chunks 为数组,每项含 summary 与 clarity(0~1,越低越模糊)。"
|
|
151
|
+
"只输出 JSON。"
|
|
152
|
+
)
|
|
153
|
+
raw = await _complete(self._llm, system, "\n".join(lines))
|
|
154
|
+
data = _parse_json_object(raw)
|
|
155
|
+
merged: list[LongTermChunk] = []
|
|
156
|
+
now = datetime.now(timezone.utc)
|
|
157
|
+
for item in data.get("chunks", []):
|
|
158
|
+
if not isinstance(item, dict):
|
|
159
|
+
continue
|
|
160
|
+
summary = str(item.get("summary", "")).strip()
|
|
161
|
+
if not summary:
|
|
162
|
+
continue
|
|
163
|
+
clarity = float(item.get("clarity", 0.4))
|
|
164
|
+
clarity = max(0.0, min(1.0, clarity))
|
|
165
|
+
merged.append(
|
|
166
|
+
LongTermChunk(
|
|
167
|
+
created_at=old_batch[0].created_at,
|
|
168
|
+
updated_at=now,
|
|
169
|
+
summary=summary,
|
|
170
|
+
clarity=clarity,
|
|
171
|
+
),
|
|
172
|
+
)
|
|
173
|
+
if not merged:
|
|
174
|
+
merged = [_fallback_merge_chunks(old_batch)]
|
|
175
|
+
return merged + rest
|
|
176
|
+
|
|
177
|
+
async def reconcile_important(
|
|
178
|
+
self,
|
|
179
|
+
entries: list[ImportantMemoryEntry],
|
|
180
|
+
) -> list[ImportantMemoryEntry]:
|
|
181
|
+
if not entries:
|
|
182
|
+
return []
|
|
183
|
+
lines = []
|
|
184
|
+
for entry in entries:
|
|
185
|
+
stamp = entry.at.isoformat()
|
|
186
|
+
src = entry.source or "unknown"
|
|
187
|
+
lines.append(f"[{stamp}] ({src}) {entry.content}")
|
|
188
|
+
system = (
|
|
189
|
+
"整理重要记忆列表:合并重复、调和矛盾(以最新或更可信者为准并说明取舍)。"
|
|
190
|
+
"输出 JSON:entries 为数组,每项含 content 与 source。"
|
|
191
|
+
"只输出 JSON。"
|
|
192
|
+
)
|
|
193
|
+
raw = await _complete(self._llm, system, "\n".join(lines))
|
|
194
|
+
data = _parse_json_object(raw)
|
|
195
|
+
now = datetime.now(timezone.utc)
|
|
196
|
+
out: list[ImportantMemoryEntry] = []
|
|
197
|
+
for item in data.get("entries", []):
|
|
198
|
+
if not isinstance(item, dict):
|
|
199
|
+
continue
|
|
200
|
+
content = str(item.get("content", "")).strip()
|
|
201
|
+
if not content:
|
|
202
|
+
continue
|
|
203
|
+
source = str(item.get("source", "")).strip()
|
|
204
|
+
out.append(
|
|
205
|
+
ImportantMemoryEntry(at=now, content=content, source=source),
|
|
206
|
+
)
|
|
207
|
+
if not out:
|
|
208
|
+
out = entries[-max(1, len(entries) // 2):]
|
|
209
|
+
return out
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class RuleMemoryCompressor:
|
|
213
|
+
"""无语言模型时的规则压缩,供测试或离线降级。"""
|
|
214
|
+
|
|
215
|
+
async def compress_to_date_entries(
|
|
216
|
+
self,
|
|
217
|
+
messages: list[MemoryMessage],
|
|
218
|
+
) -> tuple[list[DateMemoryEntry], list[str]]:
|
|
219
|
+
entries = _fallback_date_entries(messages)
|
|
220
|
+
important: list[str] = []
|
|
221
|
+
for msg in messages:
|
|
222
|
+
text = msg.content.strip()
|
|
223
|
+
if len(text) >= 40 and "?" not in text[:8]:
|
|
224
|
+
important.append(f"{msg.speaker}: {text[:120]}")
|
|
225
|
+
return entries, important[:3]
|
|
226
|
+
|
|
227
|
+
async def merge_date_to_long_term(
|
|
228
|
+
self,
|
|
229
|
+
day_label: str,
|
|
230
|
+
entries: list[DateMemoryEntry],
|
|
231
|
+
) -> LongTermChunk:
|
|
232
|
+
now = datetime.now(timezone.utc)
|
|
233
|
+
summary = _fallback_day_summary(day_label, entries)
|
|
234
|
+
return LongTermChunk(
|
|
235
|
+
created_at=now,
|
|
236
|
+
updated_at=now,
|
|
237
|
+
summary=summary,
|
|
238
|
+
clarity=0.7,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
async def merge_long_term_chunks(
|
|
242
|
+
self,
|
|
243
|
+
chunks: list[LongTermChunk],
|
|
244
|
+
) -> list[LongTermChunk]:
|
|
245
|
+
if len(chunks) <= 1:
|
|
246
|
+
return chunks
|
|
247
|
+
ordered = sorted(chunks, key=lambda c: c.created_at)
|
|
248
|
+
merge_count = max(1, len(ordered) // 3)
|
|
249
|
+
merged = _fallback_merge_chunks(ordered[:merge_count])
|
|
250
|
+
return [merged] + ordered[merge_count:]
|
|
251
|
+
|
|
252
|
+
async def reconcile_important(
|
|
253
|
+
self,
|
|
254
|
+
entries: list[ImportantMemoryEntry],
|
|
255
|
+
) -> list[ImportantMemoryEntry]:
|
|
256
|
+
seen: set[str] = set()
|
|
257
|
+
out: list[ImportantMemoryEntry] = []
|
|
258
|
+
for entry in reversed(entries):
|
|
259
|
+
key = entry.content.strip().lower()
|
|
260
|
+
if key in seen:
|
|
261
|
+
continue
|
|
262
|
+
seen.add(key)
|
|
263
|
+
out.append(entry)
|
|
264
|
+
out.reverse()
|
|
265
|
+
return out[: max(1, len(entries) // 2 + 1)]
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
async def _complete(llm: LLMClient, system: str, user: str) -> str:
|
|
269
|
+
run = RunContext(
|
|
270
|
+
system_prompt=system,
|
|
271
|
+
messages=[ChatMessage(role="user", content=user)],
|
|
272
|
+
)
|
|
273
|
+
parts: list[str] = []
|
|
274
|
+
async for chunk in llm.stream(run):
|
|
275
|
+
if chunk.kind == StreamKind.TEXT:
|
|
276
|
+
parts.append(chunk.delta)
|
|
277
|
+
return "".join(parts).strip()
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _parse_json_object(raw: str) -> dict[str, Any]:
|
|
281
|
+
text = raw.strip()
|
|
282
|
+
if not text:
|
|
283
|
+
return {}
|
|
284
|
+
fence = re.search(r"```(?:json)?\s*([\s\S]*?)```", text)
|
|
285
|
+
if fence:
|
|
286
|
+
text = fence.group(1).strip()
|
|
287
|
+
try:
|
|
288
|
+
data = json.loads(text)
|
|
289
|
+
except json.JSONDecodeError:
|
|
290
|
+
return {}
|
|
291
|
+
return data if isinstance(data, dict) else {}
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _parse_datetime(value: Any, *, fallback: datetime) -> datetime:
|
|
295
|
+
if isinstance(value, datetime):
|
|
296
|
+
return value
|
|
297
|
+
if isinstance(value, str) and value.strip():
|
|
298
|
+
try:
|
|
299
|
+
return datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
300
|
+
except ValueError:
|
|
301
|
+
pass
|
|
302
|
+
return fallback
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _fallback_date_entries(messages: list[MemoryMessage]) -> list[DateMemoryEntry]:
|
|
306
|
+
entries: list[DateMemoryEntry] = []
|
|
307
|
+
for msg in messages:
|
|
308
|
+
text = msg.content.strip()
|
|
309
|
+
if not text:
|
|
310
|
+
continue
|
|
311
|
+
summary = text if len(text) <= 160 else text[:157] + "..."
|
|
312
|
+
entries.append(
|
|
313
|
+
DateMemoryEntry(at=msg.at, speaker=msg.speaker, summary=summary),
|
|
314
|
+
)
|
|
315
|
+
return entries
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _fallback_day_summary(day_label: str, entries: list[DateMemoryEntry]) -> str:
|
|
319
|
+
if not entries:
|
|
320
|
+
return f"{day_label} 无记录"
|
|
321
|
+
parts = [f"{e.speaker}: {e.summary}" for e in entries[:8]]
|
|
322
|
+
tail = f" 等共 {len(entries)} 条" if len(entries) > 8 else ""
|
|
323
|
+
return f"{day_label} — " + ";".join(parts) + tail
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _fallback_merge_chunks(chunks: list[LongTermChunk]) -> LongTermChunk:
|
|
327
|
+
now = datetime.now(timezone.utc)
|
|
328
|
+
texts = [c.summary for c in chunks if c.summary.strip()]
|
|
329
|
+
joined = " / ".join(texts[:5])
|
|
330
|
+
if len(texts) > 5:
|
|
331
|
+
joined += f" …(+{len(texts) - 5})"
|
|
332
|
+
clarity_vals = [c.clarity for c in chunks]
|
|
333
|
+
clarity = min(clarity_vals) * 0.7 if clarity_vals else 0.35
|
|
334
|
+
return LongTermChunk(
|
|
335
|
+
created_at=chunks[0].created_at,
|
|
336
|
+
updated_at=now,
|
|
337
|
+
summary=joined,
|
|
338
|
+
clarity=max(0.1, clarity),
|
|
339
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MemoryConfig(BaseModel):
|
|
7
|
+
"""记忆系统容量与保留策略。"""
|
|
8
|
+
|
|
9
|
+
model_config = ConfigDict(extra="forbid")
|
|
10
|
+
|
|
11
|
+
short_term_max_messages: int = Field(
|
|
12
|
+
default=20,
|
|
13
|
+
ge=1,
|
|
14
|
+
description="短期记忆条数上限,超出后最旧的一批转入日期记忆",
|
|
15
|
+
)
|
|
16
|
+
short_term_overflow_batch: int = Field(
|
|
17
|
+
default=5,
|
|
18
|
+
ge=1,
|
|
19
|
+
description="每次从短期弹出的消息条数",
|
|
20
|
+
)
|
|
21
|
+
date_memory_days: int = Field(
|
|
22
|
+
default=7,
|
|
23
|
+
ge=1,
|
|
24
|
+
description="日期记忆保留天数,超出后转入长期记忆",
|
|
25
|
+
)
|
|
26
|
+
date_memory_max_entries_per_day: int = Field(
|
|
27
|
+
default=50,
|
|
28
|
+
ge=1,
|
|
29
|
+
description="单日日期记忆条目上限,超出后触发压缩",
|
|
30
|
+
)
|
|
31
|
+
long_term_max_chunks: int = Field(
|
|
32
|
+
default=30,
|
|
33
|
+
ge=1,
|
|
34
|
+
description="长期记忆块数上限,超出后合并较旧条目",
|
|
35
|
+
)
|
|
36
|
+
important_max_entries: int = Field(
|
|
37
|
+
default=20,
|
|
38
|
+
ge=1,
|
|
39
|
+
description="重要记忆条目上限,超出后压缩并调和矛盾",
|
|
40
|
+
)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from ai_agent.context import ChatMessage
|
|
6
|
+
from ai_agent.memory.models import MemorySnapshot
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class BuiltMemoryContext:
|
|
11
|
+
"""供 Agent 使用的记忆上下文。"""
|
|
12
|
+
|
|
13
|
+
system_supplement: str
|
|
14
|
+
messages: list[ChatMessage]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def build_memory_context(snapshot: MemorySnapshot) -> BuiltMemoryContext:
|
|
18
|
+
"""
|
|
19
|
+
将四层记忆格式化为系统补充与短期消息列表。
|
|
20
|
+
|
|
21
|
+
短期记忆转为带 name 的 ChatMessage,便于模型区分讲述者。
|
|
22
|
+
"""
|
|
23
|
+
sections: list[str] = []
|
|
24
|
+
if snapshot.important:
|
|
25
|
+
lines = [f"- {entry.content}" for entry in snapshot.important]
|
|
26
|
+
sections.append("【重要记忆】\n" + "\n".join(lines))
|
|
27
|
+
if snapshot.long_term:
|
|
28
|
+
lines = []
|
|
29
|
+
for chunk in sorted(snapshot.long_term, key=lambda c: c.created_at):
|
|
30
|
+
lines.append(f"- ({chunk.clarity:.1f}) {chunk.summary}")
|
|
31
|
+
sections.append("【长期记忆】\n" + "\n".join(lines))
|
|
32
|
+
if snapshot.date_days:
|
|
33
|
+
day_blocks: list[str] = []
|
|
34
|
+
for day in sorted(snapshot.date_days, key=lambda d: d.date):
|
|
35
|
+
if not day.entries:
|
|
36
|
+
continue
|
|
37
|
+
entry_lines = []
|
|
38
|
+
for entry in day.entries:
|
|
39
|
+
stamp = entry.at.strftime("%H:%M")
|
|
40
|
+
entry_lines.append(f" {stamp} {entry.speaker}: {entry.summary}")
|
|
41
|
+
day_blocks.append(f"{day.date}\n" + "\n".join(entry_lines))
|
|
42
|
+
if day_blocks:
|
|
43
|
+
sections.append("【日期记忆】\n" + "\n\n".join(day_blocks))
|
|
44
|
+
supplement = ""
|
|
45
|
+
if sections:
|
|
46
|
+
supplement = (
|
|
47
|
+
"以下是与本会话相关的分层记忆;回答时须区分各讲述者,"
|
|
48
|
+
"勿把不同人的话混为同一人。\n\n"
|
|
49
|
+
+ "\n\n".join(sections)
|
|
50
|
+
)
|
|
51
|
+
messages: list[ChatMessage] = []
|
|
52
|
+
for msg in snapshot.short_term:
|
|
53
|
+
name = msg.speaker
|
|
54
|
+
messages.append(
|
|
55
|
+
ChatMessage(role=msg.role, content=msg.content, name=name),
|
|
56
|
+
)
|
|
57
|
+
return BuiltMemoryContext(system_supplement=supplement, messages=messages)
|