zero-agent-core 0.2.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.
Files changed (104) hide show
  1. agent_core/__init__.py +525 -0
  2. agent_core/access/__init__.py +6 -0
  3. agent_core/access/context.py +45 -0
  4. agent_core/access/guard.py +40 -0
  5. agent_core/acp/README.md +236 -0
  6. agent_core/acp/__init__.py +20 -0
  7. agent_core/acp/server.py +351 -0
  8. agent_core/acp/types.py +86 -0
  9. agent_core/callbacks/__init__.py +17 -0
  10. agent_core/callbacks/manager.py +105 -0
  11. agent_core/checkpoint/__init__.py +19 -0
  12. agent_core/checkpoint/naming.py +93 -0
  13. agent_core/checkpoint/store.py +186 -0
  14. agent_core/checkpoint/travel.py +448 -0
  15. agent_core/compaction/__init__.py +57 -0
  16. agent_core/compaction/middleware.py +428 -0
  17. agent_core/compaction/prompts.py +32 -0
  18. agent_core/compaction/pruner.py +121 -0
  19. agent_core/compaction/region.py +145 -0
  20. agent_core/compaction/spill.py +81 -0
  21. agent_core/compaction/spill_tool.py +42 -0
  22. agent_core/compaction/summarizer.py +95 -0
  23. agent_core/compaction/token_limit.py +74 -0
  24. agent_core/compaction/types.py +93 -0
  25. agent_core/docs.py +15 -0
  26. agent_core/documents/__init__.py +23 -0
  27. agent_core/documents/loaders.py +79 -0
  28. agent_core/documents/splitters.py +94 -0
  29. agent_core/documents/types.py +197 -0
  30. agent_core/errors.py +81 -0
  31. agent_core/guardrails/__init__.py +19 -0
  32. agent_core/guardrails/core.py +186 -0
  33. agent_core/hitl/__init__.py +15 -0
  34. agent_core/hitl/core.py +517 -0
  35. agent_core/mcp/__init__.py +14 -0
  36. agent_core/mcp/client.py +212 -0
  37. agent_core/mcp/errors.py +26 -0
  38. agent_core/middleware/__init__.py +26 -0
  39. agent_core/middleware/base.py +283 -0
  40. agent_core/model/__init__.py +73 -0
  41. agent_core/model/catalog.py +73 -0
  42. agent_core/model/core.py +179 -0
  43. agent_core/model/governance.py +317 -0
  44. agent_core/model/providers/__init__.py +43 -0
  45. agent_core/model/providers/anthropic.py +294 -0
  46. agent_core/model/providers/common.py +28 -0
  47. agent_core/model/providers/factory.py +107 -0
  48. agent_core/model/providers/gemini.py +273 -0
  49. agent_core/model/providers/ollama.py +80 -0
  50. agent_core/model/providers/openai_compatible.py +526 -0
  51. agent_core/model/structured.py +200 -0
  52. agent_core/observability/__init__.py +21 -0
  53. agent_core/observability/core.py +311 -0
  54. agent_core/ports/__init__.py +31 -0
  55. agent_core/ports/memory.py +130 -0
  56. agent_core/ports/model_selection.py +64 -0
  57. agent_core/ports/storage.py +114 -0
  58. agent_core/prompts/__init__.py +19 -0
  59. agent_core/prompts/registry.py +236 -0
  60. agent_core/prompts/templates.py +166 -0
  61. agent_core/protocol/__init__.py +31 -0
  62. agent_core/protocol/capabilities.py +49 -0
  63. agent_core/protocol/messages.py +100 -0
  64. agent_core/protocol/runtime.py +293 -0
  65. agent_core/py.typed +1 -0
  66. agent_core/retrieval/__init__.py +40 -0
  67. agent_core/retrieval/_common.py +121 -0
  68. agent_core/retrieval/chroma.py +269 -0
  69. agent_core/retrieval/core.py +63 -0
  70. agent_core/retrieval/factory.py +72 -0
  71. agent_core/retrieval/memory.py +232 -0
  72. agent_core/retrieval/pgvector.py +239 -0
  73. agent_core/retrieval/types.py +95 -0
  74. agent_core/runtime/__init__.py +28 -0
  75. agent_core/runtime/compat.py +98 -0
  76. agent_core/runtime/react.py +888 -0
  77. agent_core/runtime/service.py +557 -0
  78. agent_core/runtime/store.py +55 -0
  79. agent_core/serialization/__init__.py +21 -0
  80. agent_core/serialization/registry.py +318 -0
  81. agent_core/skills/__init__.py +24 -0
  82. agent_core/skills/errors.py +27 -0
  83. agent_core/skills/loader.py +258 -0
  84. agent_core/skills/registry.py +205 -0
  85. agent_core/skills/types.py +56 -0
  86. agent_core/storage/__init__.py +107 -0
  87. agent_core/storage/_sqlite_utils.py +92 -0
  88. agent_core/storage/context.py +382 -0
  89. agent_core/storage/lease.py +435 -0
  90. agent_core/storage/memory.py +604 -0
  91. agent_core/storage/model_selection.py +360 -0
  92. agent_core/storage/runtime.py +899 -0
  93. agent_core/storage/session.py +904 -0
  94. agent_core/storage/workflow.py +411 -0
  95. agent_core/tools/__init__.py +5 -0
  96. agent_core/tools/executor.py +306 -0
  97. agent_core/workflow/__init__.py +25 -0
  98. agent_core/workflow/durable.py +175 -0
  99. agent_core/workflow/state_machine.py +427 -0
  100. zero_agent_core-0.2.0.dist-info/METADATA +237 -0
  101. zero_agent_core-0.2.0.dist-info/RECORD +104 -0
  102. zero_agent_core-0.2.0.dist-info/WHEEL +4 -0
  103. zero_agent_core-0.2.0.dist-info/entry_points.txt +3 -0
  104. zero_agent_core-0.2.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,448 @@
1
+ """时间旅行 checkpoint:版本管理与回滚。
2
+
3
+ 职责:
4
+ - `CheckpointVersion`:单个 checkpoint 版本(state + metadata)的值对象;
5
+ - `TimeTravelCheckpointer`:多版本存储,支持保存/加载/回滚/对比/删除版本,
6
+ 每次保存创建新版本,可选 JSON 文件持久化,版本数量有上限。
7
+
8
+ 依赖方向:本模块仅依赖 `naming`(文件名清洗与原子写入),不反向依赖 `store`。
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import copy
14
+ import json
15
+ import logging
16
+ import uuid
17
+ from datetime import UTC, datetime
18
+ from pathlib import Path
19
+ from typing import Any
20
+
21
+ from agent_core.checkpoint.naming import _atomic_write_json, _sanitize_thread_id
22
+ from agent_core.errors import CheckpointError
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class CheckpointVersion:
28
+ """Checkpoint 版本信息。"""
29
+
30
+ def __init__(
31
+ self,
32
+ version_id: str,
33
+ timestamp: str,
34
+ state: dict[str, Any],
35
+ metadata: dict[str, Any] | None = None,
36
+ ) -> None:
37
+ """初始化。
38
+
39
+ Args:
40
+ version_id: 版本 ID。
41
+ timestamp: 时间戳(ISO 格式)。
42
+ state: 状态字典。
43
+ metadata: 元数据(可选)。
44
+ """
45
+ self.version_id = version_id
46
+ self.timestamp = timestamp
47
+ self.state = state
48
+ self.metadata = metadata or {}
49
+
50
+ def to_dict(self) -> dict[str, Any]:
51
+ """转换为字典。"""
52
+ return {
53
+ "version_id": self.version_id,
54
+ "timestamp": self.timestamp,
55
+ "state": self.state,
56
+ "metadata": self.metadata,
57
+ }
58
+
59
+ @classmethod
60
+ def from_dict(cls, data: dict[str, Any]) -> CheckpointVersion:
61
+ """从字典创建。"""
62
+ return cls(
63
+ version_id=data["version_id"],
64
+ timestamp=data["timestamp"],
65
+ state=data["state"],
66
+ metadata=data.get("metadata"),
67
+ )
68
+
69
+
70
+ class TimeTravelCheckpointer:
71
+ """支持时间旅行的 Checkpointer。
72
+
73
+ 功能:
74
+ 1. 保存多个版本(每次保存创建新版本)
75
+ 2. 查看历史版本列表
76
+ 3. 回滚到任意版本
77
+ 4. 对比两个版本
78
+ 5. 每个 thread 的版本数量有上限,超过后淘汰最旧版本,避免无限膨胀
79
+ """
80
+
81
+ def __init__(
82
+ self,
83
+ storage_dir: str | Path | None = None,
84
+ *,
85
+ max_versions_per_thread: int = 100,
86
+ ) -> None:
87
+ """初始化。
88
+
89
+ Args:
90
+ storage_dir: 存储目录(可选,None 表示仅内存存储)。
91
+ max_versions_per_thread: 每个 thread 保留的最大版本数,
92
+ 追加超过上限时淘汰最旧版本(默认 100)。
93
+ """
94
+ if max_versions_per_thread < 1:
95
+ raise ValueError("max_versions_per_thread 必须大于 0")
96
+ self._storage_dir = Path(storage_dir) if storage_dir else None
97
+ if self._storage_dir:
98
+ self._storage_dir.mkdir(parents=True, exist_ok=True)
99
+
100
+ # thread_id -> list of CheckpointVersion
101
+ self._versions: dict[str, list[CheckpointVersion]] = {}
102
+
103
+ # 当前版本索引
104
+ self._current_version: dict[str, int] = {}
105
+
106
+ # 每个 thread 的版本数量上限
107
+ self._max_versions_per_thread = max_versions_per_thread
108
+
109
+ def _get_version_file(self, thread_id: str) -> Path | None:
110
+ """获取版本文件路径(文件名经 `_sanitize_thread_id` 严格清洗)。"""
111
+ if not self._storage_dir:
112
+ return None
113
+ safe_id = _sanitize_thread_id(thread_id)
114
+ return self._storage_dir / f"{safe_id}_versions.json"
115
+
116
+ async def save(
117
+ self,
118
+ thread_id: str,
119
+ state: dict[str, Any],
120
+ metadata: dict[str, Any] | None = None,
121
+ ) -> str:
122
+ """保存新版本。
123
+
124
+ Args:
125
+ thread_id: 会话 ID。
126
+ state: 状态字典。
127
+ metadata: 元数据(可选)。
128
+
129
+ Returns:
130
+ 版本 ID。
131
+ """
132
+ # 创建版本
133
+ version_id = str(uuid.uuid4())
134
+ timestamp = datetime.now(UTC).isoformat()
135
+
136
+ version = CheckpointVersion(
137
+ version_id=version_id,
138
+ timestamp=timestamp,
139
+ state=copy.deepcopy(state),
140
+ metadata=metadata or {},
141
+ )
142
+
143
+ # 添加到版本列表(超过容量上限时淘汰最旧版本,指针随淘汰前移)
144
+ if thread_id not in self._versions:
145
+ self._versions[thread_id] = []
146
+ versions = self._versions[thread_id]
147
+
148
+ # 记录保存前的当前指针:持久化失败时按版本身份恢复(淘汰会改变索引)。
149
+ previous_index = self._current_version.get(thread_id)
150
+ previous_current: CheckpointVersion | None = None
151
+ if previous_index is not None and 0 <= previous_index < len(versions):
152
+ previous_current = versions[previous_index]
153
+
154
+ versions.append(version)
155
+ self._trim_versions(thread_id)
156
+ self._current_version[thread_id] = len(versions) - 1
157
+
158
+ # 持久化到文件
159
+ try:
160
+ await self._persist_versions(thread_id)
161
+ except Exception:
162
+ # 按身份移除本次追加的版本;并发 save_version 时列表末尾可能是其他调用者的版本。
163
+ for position in range(len(versions) - 1, -1, -1):
164
+ if versions[position] is version:
165
+ del versions[position]
166
+ break
167
+ self._restore_current_pointer(thread_id, previous_current)
168
+ raise
169
+
170
+ logger.info(
171
+ "Saved checkpoint version %s for thread %r (total versions: %d)",
172
+ version_id,
173
+ thread_id,
174
+ len(self._versions[thread_id]),
175
+ )
176
+
177
+ return version_id
178
+
179
+ async def load(self, thread_id: str, version_id: str | None = None) -> dict[str, Any] | None:
180
+ """加载指定版本的 state。
181
+
182
+ Args:
183
+ thread_id: 会话 ID。
184
+ version_id: 版本 ID(None 表示最新版本)。
185
+
186
+ Returns:
187
+ 状态字典,如果不存在则返回 None。
188
+ """
189
+ # 从文件加载(如果需要)
190
+ await self._load_versions_from_file(thread_id)
191
+
192
+ versions = self._versions.get(thread_id, [])
193
+ if not versions:
194
+ return None
195
+
196
+ # 查找版本
197
+ if version_id:
198
+ for v in versions:
199
+ if v.version_id == version_id:
200
+ logger.info(
201
+ "Loaded checkpoint version %s for thread %r",
202
+ version_id,
203
+ thread_id,
204
+ )
205
+ return copy.deepcopy(v.state)
206
+ logger.warning(
207
+ "Checkpoint version %s not found for thread %r",
208
+ version_id,
209
+ thread_id,
210
+ )
211
+ return None
212
+
213
+ # 没有显式版本时读取当前指针。回滚后不能意外跳回最新版本。
214
+ current_index = self._current_version.get(thread_id, len(versions) - 1)
215
+ current_index = max(0, min(current_index, len(versions) - 1))
216
+ latest = versions[current_index]
217
+ logger.info(
218
+ "Loaded current checkpoint version %s for thread %r",
219
+ latest.version_id,
220
+ thread_id,
221
+ )
222
+ return copy.deepcopy(latest.state)
223
+
224
+ async def list_versions(self, thread_id: str) -> list[dict[str, Any]]:
225
+ """列出所有版本。
226
+
227
+ Args:
228
+ thread_id: 会话 ID。
229
+
230
+ Returns:
231
+ 版本信息列表(每个元素包含 version_id, timestamp, metadata)。
232
+ """
233
+ await self._load_versions_from_file(thread_id)
234
+
235
+ versions = self._versions.get(thread_id, [])
236
+ return [
237
+ {
238
+ "version_id": v.version_id,
239
+ "timestamp": v.timestamp,
240
+ "metadata": v.metadata,
241
+ }
242
+ for v in versions
243
+ ]
244
+
245
+ async def rollback(self, thread_id: str, version_id: str) -> bool:
246
+ """回滚到指定版本。
247
+
248
+ 注意:这不会删除后续版本,只是将当前指针移动到指定版本。
249
+
250
+ Args:
251
+ thread_id: 会话 ID。
252
+ version_id: 目标版本 ID。
253
+
254
+ Returns:
255
+ 是否成功回滚。
256
+ """
257
+ await self._load_versions_from_file(thread_id)
258
+
259
+ versions = self._versions.get(thread_id, [])
260
+ if not versions:
261
+ logger.warning("No versions found for thread %r", thread_id)
262
+ return False
263
+
264
+ # 查找版本索引
265
+ for i, v in enumerate(versions):
266
+ if v.version_id == version_id:
267
+ self._current_version[thread_id] = i
268
+ # 当前版本指针也属于时间旅行状态;必须和版本列表一起持久化,
269
+ # 否则进程重启后会再次指向最新版本。
270
+ await self._persist_versions(thread_id)
271
+ logger.info(
272
+ "Rolled back to version %s for thread %r (index: %d)",
273
+ version_id,
274
+ thread_id,
275
+ i,
276
+ )
277
+ return True
278
+
279
+ logger.warning(
280
+ "Version %s not found for thread %r",
281
+ version_id,
282
+ thread_id,
283
+ )
284
+ return False
285
+
286
+ async def delete_version(self, thread_id: str, version_id: str) -> bool:
287
+ """删除指定版本。
288
+
289
+ Args:
290
+ thread_id: 会话 ID。
291
+ version_id: 版本 ID。
292
+
293
+ Returns:
294
+ 是否成功删除。
295
+ """
296
+ await self._load_versions_from_file(thread_id)
297
+
298
+ versions = self._versions.get(thread_id, [])
299
+ if not versions:
300
+ return False
301
+
302
+ # 查找并删除
303
+ for i, v in enumerate(versions):
304
+ if v.version_id == version_id:
305
+ versions.pop(i)
306
+ if versions:
307
+ current = self._current_version.get(thread_id, len(versions) - 1)
308
+ if i < current:
309
+ current -= 1
310
+ self._current_version[thread_id] = max(0, min(current, len(versions) - 1))
311
+ else:
312
+ self._current_version.pop(thread_id, None)
313
+ await self._persist_versions(thread_id)
314
+ logger.info(
315
+ "Deleted version %s for thread %r",
316
+ version_id,
317
+ thread_id,
318
+ )
319
+ return True
320
+
321
+ return False
322
+
323
+ async def delete(self, thread_id: str) -> None:
324
+ """删除一个会话的全部版本,并满足 Agent Loop 的 Checkpointer 协议。"""
325
+ self._versions.pop(thread_id, None)
326
+ self._current_version.pop(thread_id, None)
327
+ file_path = self._get_version_file(thread_id)
328
+ if file_path is not None:
329
+ try:
330
+ file_path.unlink(missing_ok=True)
331
+ except OSError as exc:
332
+ raise CheckpointError(f"删除会话 {thread_id!r} 的 checkpoint 失败:{exc}") from exc
333
+
334
+ async def compare_versions(
335
+ self,
336
+ thread_id: str,
337
+ version_id_1: str,
338
+ version_id_2: str,
339
+ ) -> dict[str, Any]:
340
+ """对比两个版本。
341
+
342
+ Args:
343
+ thread_id: 会话 ID。
344
+ version_id_1: 版本 1 ID。
345
+ version_id_2: 版本 2 ID。
346
+
347
+ Returns:
348
+ 对比结果(包含差异信息)。
349
+ """
350
+ state1 = await self.load(thread_id, version_id_1)
351
+ state2 = await self.load(thread_id, version_id_2)
352
+
353
+ if state1 is None or state2 is None:
354
+ return {"error": "One or both versions not found"}
355
+
356
+ # 简单对比(实际项目可以使用更复杂的 diff 算法)
357
+ diff = {
358
+ "version_1": version_id_1,
359
+ "version_2": version_id_2,
360
+ "keys_only_in_v1": list(set(state1.keys()) - set(state2.keys())),
361
+ "keys_only_in_v2": list(set(state2.keys()) - set(state1.keys())),
362
+ "common_keys": list(set(state1.keys()) & set(state2.keys())),
363
+ }
364
+
365
+ return diff
366
+
367
+ def _trim_versions(self, thread_id: str) -> int:
368
+ """把版本数量收敛到 `_max_versions_per_thread` 内,返回淘汰的最旧版本数。
369
+
370
+ 淘汰只从列表头部(最旧)进行;当前指针所指版本被淘汰时,指针前移到
371
+ 它的继任版本(最旧存活版本),未被淘汰时指针仍指向同一版本。
372
+ """
373
+ versions = self._versions.get(thread_id)
374
+ if not versions:
375
+ return 0
376
+ excess = len(versions) - self._max_versions_per_thread
377
+ if excess <= 0:
378
+ return 0
379
+ del versions[:excess]
380
+ current = self._current_version.get(thread_id)
381
+ if current is not None:
382
+ self._current_version[thread_id] = max(current - excess, 0)
383
+ logger.info(
384
+ "Evicted %d oldest checkpoint versions for thread %r (limit: %d)",
385
+ excess,
386
+ thread_id,
387
+ self._max_versions_per_thread,
388
+ )
389
+ return excess
390
+
391
+ def _restore_current_pointer(self, thread_id: str, previous: CheckpointVersion | None) -> None:
392
+ """按版本身份恢复保存前的当前指针(容量淘汰会改变索引)。"""
393
+ versions = self._versions.get(thread_id, [])
394
+ if previous is None:
395
+ self._current_version.pop(thread_id, None)
396
+ return
397
+ position = next((i for i, item in enumerate(versions) if item is previous), None)
398
+ if position is not None:
399
+ self._current_version[thread_id] = position
400
+ elif versions:
401
+ # 保存前的指针版本已被容量淘汰:指针前移到最旧存活版本。
402
+ self._current_version[thread_id] = 0
403
+ else:
404
+ self._current_version.pop(thread_id, None)
405
+
406
+ async def _persist_versions(self, thread_id: str) -> None:
407
+ """持久化版本列表到文件。"""
408
+ if not self._storage_dir:
409
+ return
410
+
411
+ file_path = self._get_version_file(thread_id)
412
+ if not file_path:
413
+ return
414
+
415
+ versions = self._versions.get(thread_id, [])
416
+ data = {
417
+ "thread_id": thread_id,
418
+ "versions": [v.to_dict() for v in versions],
419
+ "current_version": self._current_version.get(thread_id, 0),
420
+ }
421
+
422
+ try:
423
+ _atomic_write_json(file_path, data)
424
+ except Exception as exc:
425
+ raise CheckpointError(f"持久化会话 {thread_id!r} 的 checkpoint 版本失败:{exc}") from exc
426
+
427
+ async def _load_versions_from_file(self, thread_id: str) -> None:
428
+ """从文件加载版本列表。"""
429
+ if thread_id in self._versions:
430
+ return # 已加载
431
+
432
+ if not self._storage_dir:
433
+ return
434
+
435
+ file_path = self._get_version_file(thread_id)
436
+ if not file_path or not file_path.exists():
437
+ return
438
+
439
+ try:
440
+ with file_path.open("r", encoding="utf-8") as f:
441
+ data = json.load(f)
442
+
443
+ self._versions[thread_id] = [CheckpointVersion.from_dict(v) for v in data.get("versions", [])]
444
+ self._current_version[thread_id] = data.get("current_version", 0)
445
+ # 历史文件中的版本数可能超过当前上限:加载后同样收敛,指针随淘汰前移。
446
+ self._trim_versions(thread_id)
447
+ except Exception as exc:
448
+ raise CheckpointError(f"加载会话 {thread_id!r} 的 checkpoint 版本失败:{exc}") from exc
@@ -0,0 +1,57 @@
1
+ """通用上下文压缩能力。"""
2
+
3
+ from agent_core.compaction.middleware import (
4
+ OVERFLOW_RETRY_KEY,
5
+ CompactionMiddleware,
6
+ is_context_overflow_error,
7
+ )
8
+ from agent_core.compaction.prompts import DEFAULT_COMPACTION_INSTRUCTION, DEFAULT_SUMMARY_SECTIONS
9
+ from agent_core.compaction.pruner import PRUNE_MARKER, PruneConfig, prune_text, prune_tool_results
10
+ from agent_core.compaction.region import (
11
+ balanced_cut,
12
+ build_checkpoint,
13
+ estimate_message_tokens,
14
+ estimate_tokens,
15
+ extract_previous_summary,
16
+ rebuild,
17
+ select_cut,
18
+ )
19
+ from agent_core.compaction.spill import SpillStore
20
+ from agent_core.compaction.spill_tool import build_spill_tool
21
+ from agent_core.compaction.token_limit import TokenLimitMiddleware
22
+ from agent_core.compaction.types import (
23
+ SUMMARY_CLOSE,
24
+ SUMMARY_OPEN,
25
+ SUMMARY_SOURCE,
26
+ CompactionConfig,
27
+ CompactionError,
28
+ CompactionResult,
29
+ )
30
+
31
+ __all__ = [
32
+ "DEFAULT_COMPACTION_INSTRUCTION",
33
+ "DEFAULT_SUMMARY_SECTIONS",
34
+ "OVERFLOW_RETRY_KEY",
35
+ "PRUNE_MARKER",
36
+ "SUMMARY_CLOSE",
37
+ "SUMMARY_OPEN",
38
+ "SUMMARY_SOURCE",
39
+ "CompactionConfig",
40
+ "CompactionError",
41
+ "CompactionMiddleware",
42
+ "CompactionResult",
43
+ "PruneConfig",
44
+ "SpillStore",
45
+ "TokenLimitMiddleware",
46
+ "balanced_cut",
47
+ "build_checkpoint",
48
+ "build_spill_tool",
49
+ "estimate_message_tokens",
50
+ "estimate_tokens",
51
+ "extract_previous_summary",
52
+ "is_context_overflow_error",
53
+ "prune_text",
54
+ "prune_tool_results",
55
+ "rebuild",
56
+ "select_cut",
57
+ ]