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,561 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import threading
|
|
6
|
+
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from typing import Literal
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from openai import AsyncOpenAI
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
from ai_agent.context import ChatMessage
|
|
20
|
+
|
|
21
|
+
from ai_agent.llm_openai import OpenAILLM
|
|
22
|
+
|
|
23
|
+
from ai_agent.memory.compressor import (
|
|
24
|
+
|
|
25
|
+
LLMMemoryCompressor,
|
|
26
|
+
|
|
27
|
+
MemoryCompressor,
|
|
28
|
+
|
|
29
|
+
RuleMemoryCompressor,
|
|
30
|
+
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
from ai_agent.memory.config import MemoryConfig
|
|
34
|
+
|
|
35
|
+
from ai_agent.memory.compression_work import CompressionResult, CompressionWork
|
|
36
|
+
|
|
37
|
+
from ai_agent.memory.context_builder import BuiltMemoryContext, build_memory_context
|
|
38
|
+
|
|
39
|
+
from ai_agent.memory.models import ImportantMemoryEntry, MemoryMessage, MemorySnapshot
|
|
40
|
+
|
|
41
|
+
from ai_agent.memory.snapshot_merge import apply_result, prepare_work
|
|
42
|
+
|
|
43
|
+
from ai_agent.memory.store import MemoryStore
|
|
44
|
+
|
|
45
|
+
from ai_agent.memory.worker import (
|
|
46
|
+
|
|
47
|
+
MemoryTask,
|
|
48
|
+
|
|
49
|
+
MemoryTaskKind,
|
|
50
|
+
|
|
51
|
+
MemoryWorker,
|
|
52
|
+
|
|
53
|
+
expire_old_date_days,
|
|
54
|
+
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
MessageRole = Literal["user", "assistant"]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class MemorySystem:
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
单会话分层记忆入口:短期、日期、长期与重要记忆。
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
Agent 每轮读取已发布视图;压缩在独立线程中执行,完成后发布新视图,不阻塞主推理。
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
|
|
81
|
+
self,
|
|
82
|
+
|
|
83
|
+
storage_dir: Path | str,
|
|
84
|
+
|
|
85
|
+
*,
|
|
86
|
+
|
|
87
|
+
api_key: str,
|
|
88
|
+
|
|
89
|
+
model: str,
|
|
90
|
+
|
|
91
|
+
base_url: str,
|
|
92
|
+
|
|
93
|
+
config: MemoryConfig | None = None,
|
|
94
|
+
|
|
95
|
+
autostart: bool = True,
|
|
96
|
+
|
|
97
|
+
use_llm_compressor: bool = True,
|
|
98
|
+
|
|
99
|
+
compressor: MemoryCompressor | None = None,
|
|
100
|
+
|
|
101
|
+
) -> None:
|
|
102
|
+
|
|
103
|
+
if not api_key.strip():
|
|
104
|
+
|
|
105
|
+
raise ValueError("api_key 不能为空")
|
|
106
|
+
|
|
107
|
+
if not model.strip():
|
|
108
|
+
|
|
109
|
+
raise ValueError("model 不能为空")
|
|
110
|
+
|
|
111
|
+
if not base_url.strip():
|
|
112
|
+
|
|
113
|
+
raise ValueError("base_url 不能为空")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
self._storage_dir = Path(storage_dir)
|
|
118
|
+
|
|
119
|
+
self._config = config or MemoryConfig()
|
|
120
|
+
|
|
121
|
+
self._api_key = api_key.strip()
|
|
122
|
+
|
|
123
|
+
self._model = model.strip()
|
|
124
|
+
|
|
125
|
+
self._base_url = base_url.strip().rstrip("/")
|
|
126
|
+
|
|
127
|
+
self._use_llm = use_llm_compressor
|
|
128
|
+
|
|
129
|
+
self._inject_compressor = compressor
|
|
130
|
+
|
|
131
|
+
self._store = MemoryStore(self._storage_dir)
|
|
132
|
+
|
|
133
|
+
self._lock = threading.RLock()
|
|
134
|
+
|
|
135
|
+
self._live = self._store.load()
|
|
136
|
+
|
|
137
|
+
self._agent_view = self._live.model_copy(deep=True)
|
|
138
|
+
|
|
139
|
+
self._worker = MemoryWorker(
|
|
140
|
+
|
|
141
|
+
config=self._config,
|
|
142
|
+
|
|
143
|
+
compressor_factory=self._make_compressor,
|
|
144
|
+
|
|
145
|
+
prepare=self._worker_prepare,
|
|
146
|
+
|
|
147
|
+
commit=self._worker_commit,
|
|
148
|
+
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if autostart:
|
|
152
|
+
|
|
153
|
+
self._worker.start()
|
|
154
|
+
|
|
155
|
+
self._schedule_expired_dates()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
|
|
161
|
+
def storage_dir(self) -> Path:
|
|
162
|
+
|
|
163
|
+
"""本会话记忆存储目录。"""
|
|
164
|
+
|
|
165
|
+
return self._storage_dir
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
|
|
171
|
+
def config(self) -> MemoryConfig:
|
|
172
|
+
|
|
173
|
+
return self._config
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def append(
|
|
178
|
+
|
|
179
|
+
self,
|
|
180
|
+
|
|
181
|
+
*,
|
|
182
|
+
|
|
183
|
+
speaker: str,
|
|
184
|
+
|
|
185
|
+
role: MessageRole,
|
|
186
|
+
|
|
187
|
+
content: str,
|
|
188
|
+
|
|
189
|
+
at: datetime | None = None,
|
|
190
|
+
|
|
191
|
+
) -> None:
|
|
192
|
+
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
追加一条对话到短期记忆,并在超出上限时触发压缩任务。
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
|
|
201
|
+
speaker: 讲述者显示名(多用户场景区分身份)
|
|
202
|
+
|
|
203
|
+
role: user 或 assistant
|
|
204
|
+
|
|
205
|
+
content: 原文
|
|
206
|
+
|
|
207
|
+
at: 发生时刻,默认 UTC 当前时间
|
|
208
|
+
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
label = speaker.strip()
|
|
212
|
+
|
|
213
|
+
if not label:
|
|
214
|
+
|
|
215
|
+
raise ValueError("speaker 不能为空")
|
|
216
|
+
|
|
217
|
+
text = content.strip()
|
|
218
|
+
|
|
219
|
+
if not text:
|
|
220
|
+
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
stamp = at or datetime.now(timezone.utc)
|
|
224
|
+
|
|
225
|
+
if stamp.tzinfo is None:
|
|
226
|
+
|
|
227
|
+
stamp = stamp.replace(tzinfo=timezone.utc)
|
|
228
|
+
|
|
229
|
+
msg = MemoryMessage(speaker=label, role=role, content=text, at=stamp)
|
|
230
|
+
|
|
231
|
+
with self._lock:
|
|
232
|
+
|
|
233
|
+
self._live.short_term.append(msg)
|
|
234
|
+
|
|
235
|
+
self._sync_agent_short_term()
|
|
236
|
+
|
|
237
|
+
self._store.save_short_term(self._live.short_term)
|
|
238
|
+
|
|
239
|
+
self._maybe_overflow_short_term()
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def remember(self, content: str, *, source: str = "explicit") -> None:
|
|
244
|
+
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
直接写入重要记忆。
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
|
|
253
|
+
content: 要记住的文本
|
|
254
|
+
|
|
255
|
+
source: 来源标记
|
|
256
|
+
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
text = content.strip()
|
|
260
|
+
|
|
261
|
+
if not text:
|
|
262
|
+
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
now = datetime.now(timezone.utc)
|
|
266
|
+
|
|
267
|
+
with self._lock:
|
|
268
|
+
|
|
269
|
+
self._live.important.append(
|
|
270
|
+
|
|
271
|
+
ImportantMemoryEntry(at=now, content=text, source=source),
|
|
272
|
+
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
self._sync_agent_important()
|
|
276
|
+
|
|
277
|
+
self._store.save_important(self._live.important)
|
|
278
|
+
|
|
279
|
+
self._maybe_compress_important()
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def build_context(self) -> BuiltMemoryContext:
|
|
284
|
+
|
|
285
|
+
"""生成供 Agent.run 使用的系统补充与消息列表(读已发布视图,不等待压缩)。"""
|
|
286
|
+
|
|
287
|
+
view = self._copy_agent_view()
|
|
288
|
+
|
|
289
|
+
return build_memory_context(view)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def context_for_agent(
|
|
294
|
+
|
|
295
|
+
self,
|
|
296
|
+
|
|
297
|
+
*,
|
|
298
|
+
|
|
299
|
+
system_prompt: str,
|
|
300
|
+
|
|
301
|
+
) -> tuple[str, list[ChatMessage]]:
|
|
302
|
+
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
合并系统提示与记忆,返回 (完整 system_prompt, messages)。
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
|
|
311
|
+
system_prompt: 调用方原始系统提示
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
|
|
317
|
+
拼接记忆后的系统提示与短期消息列表
|
|
318
|
+
|
|
319
|
+
"""
|
|
320
|
+
|
|
321
|
+
ctx = self.build_context()
|
|
322
|
+
|
|
323
|
+
if ctx.system_supplement:
|
|
324
|
+
|
|
325
|
+
merged = system_prompt.rstrip() + "\n\n" + ctx.system_supplement
|
|
326
|
+
|
|
327
|
+
else:
|
|
328
|
+
|
|
329
|
+
merged = system_prompt
|
|
330
|
+
|
|
331
|
+
return merged, list(ctx.messages)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def flush(self, *, timeout: float = 30.0) -> None:
|
|
336
|
+
|
|
337
|
+
"""等待后台队列中已有压缩任务完成。"""
|
|
338
|
+
|
|
339
|
+
self._worker.drain(timeout=timeout)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def shutdown(self, *, join: bool = True, timeout: float = 5.0) -> None:
|
|
344
|
+
|
|
345
|
+
"""停止后台线程。"""
|
|
346
|
+
|
|
347
|
+
self._worker.stop(join=join, timeout=timeout)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _copy_agent_view(self) -> MemorySnapshot:
|
|
352
|
+
|
|
353
|
+
with self._lock:
|
|
354
|
+
|
|
355
|
+
return self._agent_view.model_copy(deep=True)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _publish_agent_view(self) -> None:
|
|
360
|
+
|
|
361
|
+
self._agent_view = self._live.model_copy(deep=True)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _sync_agent_short_term(self) -> None:
|
|
366
|
+
|
|
367
|
+
self._agent_view.short_term = [
|
|
368
|
+
|
|
369
|
+
m.model_copy(deep=True) for m in self._live.short_term
|
|
370
|
+
|
|
371
|
+
]
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _sync_agent_important(self) -> None:
|
|
376
|
+
|
|
377
|
+
self._agent_view.important = [
|
|
378
|
+
|
|
379
|
+
e.model_copy(deep=True) for e in self._live.important
|
|
380
|
+
|
|
381
|
+
]
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _worker_prepare(self, task: MemoryTask) -> CompressionWork | None:
|
|
386
|
+
|
|
387
|
+
with self._lock:
|
|
388
|
+
|
|
389
|
+
return prepare_work(self._live, task, config=self._config)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _worker_commit(
|
|
394
|
+
|
|
395
|
+
self,
|
|
396
|
+
|
|
397
|
+
task: MemoryTask,
|
|
398
|
+
|
|
399
|
+
work: CompressionWork,
|
|
400
|
+
|
|
401
|
+
result: CompressionResult,
|
|
402
|
+
|
|
403
|
+
) -> None:
|
|
404
|
+
|
|
405
|
+
followups: list[MemoryTask] = []
|
|
406
|
+
|
|
407
|
+
with self._lock:
|
|
408
|
+
|
|
409
|
+
followups = apply_result(
|
|
410
|
+
|
|
411
|
+
self._live,
|
|
412
|
+
|
|
413
|
+
task,
|
|
414
|
+
|
|
415
|
+
work,
|
|
416
|
+
|
|
417
|
+
result,
|
|
418
|
+
|
|
419
|
+
config=self._config,
|
|
420
|
+
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
self._publish_agent_view()
|
|
424
|
+
|
|
425
|
+
self._persist_all_layers()
|
|
426
|
+
|
|
427
|
+
if task.kind == MemoryTaskKind.SHORT_TO_DATE:
|
|
428
|
+
|
|
429
|
+
self._maybe_overflow_short_term()
|
|
430
|
+
|
|
431
|
+
if task.kind == MemoryTaskKind.COMPRESS_IMPORTANT:
|
|
432
|
+
|
|
433
|
+
self._maybe_compress_important()
|
|
434
|
+
|
|
435
|
+
if task.kind == MemoryTaskKind.COMPRESS_LONG:
|
|
436
|
+
|
|
437
|
+
if len(self._live.long_term) > self._config.long_term_max_chunks:
|
|
438
|
+
|
|
439
|
+
followups.append(MemoryTask(MemoryTaskKind.COMPRESS_LONG, {}))
|
|
440
|
+
|
|
441
|
+
for follow in followups:
|
|
442
|
+
|
|
443
|
+
self._worker.enqueue(follow)
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def _make_compressor(self) -> MemoryCompressor:
|
|
448
|
+
|
|
449
|
+
if self._inject_compressor is not None:
|
|
450
|
+
|
|
451
|
+
return self._inject_compressor
|
|
452
|
+
|
|
453
|
+
if not self._use_llm:
|
|
454
|
+
|
|
455
|
+
return RuleMemoryCompressor()
|
|
456
|
+
|
|
457
|
+
client = AsyncOpenAI(api_key=self._api_key, base_url=self._base_url)
|
|
458
|
+
|
|
459
|
+
llm = OpenAILLM(client, model=self._model)
|
|
460
|
+
|
|
461
|
+
return LLMMemoryCompressor(llm)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def _persist_all_layers(self) -> None:
|
|
466
|
+
|
|
467
|
+
self._store.save_short_term(self._live.short_term)
|
|
468
|
+
|
|
469
|
+
active_dates = {day.date for day in self._live.date_days}
|
|
470
|
+
|
|
471
|
+
for day in self._live.date_days:
|
|
472
|
+
|
|
473
|
+
self._store.save_date_day(day)
|
|
474
|
+
|
|
475
|
+
self._store.prune_date_files(active_dates)
|
|
476
|
+
|
|
477
|
+
self._store.save_long_term(self._live.long_term)
|
|
478
|
+
|
|
479
|
+
self._store.save_important(self._live.important)
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def _maybe_overflow_short_term(self) -> None:
|
|
484
|
+
|
|
485
|
+
limit = self._config.short_term_max_messages
|
|
486
|
+
|
|
487
|
+
if len(self._live.short_term) <= limit:
|
|
488
|
+
|
|
489
|
+
return
|
|
490
|
+
|
|
491
|
+
batch = min(
|
|
492
|
+
|
|
493
|
+
self._config.short_term_overflow_batch,
|
|
494
|
+
|
|
495
|
+
len(self._live.short_term),
|
|
496
|
+
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
self._worker.enqueue(
|
|
500
|
+
|
|
501
|
+
MemoryTask(
|
|
502
|
+
|
|
503
|
+
MemoryTaskKind.SHORT_TO_DATE,
|
|
504
|
+
|
|
505
|
+
{"batch_size": batch},
|
|
506
|
+
|
|
507
|
+
),
|
|
508
|
+
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def _maybe_compress_important(self) -> None:
|
|
514
|
+
|
|
515
|
+
if len(self._live.important) <= self._config.important_max_entries:
|
|
516
|
+
|
|
517
|
+
return
|
|
518
|
+
|
|
519
|
+
self._worker.enqueue(
|
|
520
|
+
|
|
521
|
+
MemoryTask(MemoryTaskKind.COMPRESS_IMPORTANT, {}),
|
|
522
|
+
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def _schedule_expired_dates(self) -> None:
|
|
528
|
+
|
|
529
|
+
with self._lock:
|
|
530
|
+
|
|
531
|
+
expired = expire_old_date_days(
|
|
532
|
+
|
|
533
|
+
self._live,
|
|
534
|
+
|
|
535
|
+
config=self._config,
|
|
536
|
+
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
for day_label in expired:
|
|
540
|
+
|
|
541
|
+
self._worker.enqueue(
|
|
542
|
+
|
|
543
|
+
MemoryTask(
|
|
544
|
+
|
|
545
|
+
MemoryTaskKind.DATE_TO_LONG,
|
|
546
|
+
|
|
547
|
+
{"day": day_label},
|
|
548
|
+
|
|
549
|
+
),
|
|
550
|
+
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
if len(self._live.long_term) > self._config.long_term_max_chunks:
|
|
554
|
+
|
|
555
|
+
self._worker.enqueue(
|
|
556
|
+
|
|
557
|
+
MemoryTask(MemoryTaskKind.COMPRESS_LONG, {}),
|
|
558
|
+
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
MessageRole = Literal["user", "assistant"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MemoryMessage(BaseModel):
|
|
13
|
+
"""短期记忆中的单条对话。"""
|
|
14
|
+
|
|
15
|
+
model_config = ConfigDict(extra="forbid")
|
|
16
|
+
|
|
17
|
+
speaker: str = Field(description="讲述者显示名")
|
|
18
|
+
role: MessageRole = Field(description="OpenAI 角色")
|
|
19
|
+
content: str = Field(description="原文")
|
|
20
|
+
at: datetime = Field(description="发生时刻")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DateMemoryEntry(BaseModel):
|
|
24
|
+
"""日期记忆中的压缩条目。"""
|
|
25
|
+
|
|
26
|
+
model_config = ConfigDict(extra="forbid")
|
|
27
|
+
|
|
28
|
+
at: datetime = Field(description="原话发生时刻")
|
|
29
|
+
speaker: str = Field(description="讲述者显示名")
|
|
30
|
+
summary: str = Field(description="压缩摘要")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class DateMemoryDay(BaseModel):
|
|
34
|
+
"""某一天的日期记忆。"""
|
|
35
|
+
|
|
36
|
+
model_config = ConfigDict(extra="forbid")
|
|
37
|
+
|
|
38
|
+
date: str = Field(description="YYYY-MM-DD")
|
|
39
|
+
entries: list[DateMemoryEntry] = Field(default_factory=list)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class LongTermChunk(BaseModel):
|
|
43
|
+
"""长期记忆块;越久越模糊。"""
|
|
44
|
+
|
|
45
|
+
model_config = ConfigDict(extra="forbid")
|
|
46
|
+
|
|
47
|
+
created_at: datetime = Field(description="首次写入时刻")
|
|
48
|
+
updated_at: datetime = Field(description="最近更新时刻")
|
|
49
|
+
summary: str = Field(description="模糊化摘要")
|
|
50
|
+
clarity: float = Field(
|
|
51
|
+
default=1.0,
|
|
52
|
+
ge=0.0,
|
|
53
|
+
le=1.0,
|
|
54
|
+
description="清晰程度,1 为最新,趋近 0 表示已融合",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ImportantMemoryEntry(BaseModel):
|
|
59
|
+
"""需长期保留的事实或偏好。"""
|
|
60
|
+
|
|
61
|
+
model_config = ConfigDict(extra="forbid")
|
|
62
|
+
|
|
63
|
+
at: datetime = Field(description="记录时刻")
|
|
64
|
+
content: str = Field(description="要记住的内容")
|
|
65
|
+
source: str = Field(default="", description="来源说明,如讲述者或压缩任务")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class MemorySnapshot(BaseModel):
|
|
69
|
+
"""单会话记忆快照,对应存储目录根。"""
|
|
70
|
+
|
|
71
|
+
model_config = ConfigDict(extra="forbid")
|
|
72
|
+
|
|
73
|
+
short_term: list[MemoryMessage] = Field(default_factory=list)
|
|
74
|
+
date_days: list[DateMemoryDay] = Field(default_factory=list)
|
|
75
|
+
long_term: list[LongTermChunk] = Field(default_factory=list)
|
|
76
|
+
important: list[ImportantMemoryEntry] = Field(default_factory=list)
|