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,17 @@
1
+ """Agent 生命周期回调。"""
2
+
3
+ from agent_core.callbacks.manager import (
4
+ BaseCallbackHandler,
5
+ CallableCallbackHandler,
6
+ CallbackFailure,
7
+ CallbackHandler,
8
+ CallbackManager,
9
+ )
10
+
11
+ __all__ = [
12
+ "BaseCallbackHandler",
13
+ "CallableCallbackHandler",
14
+ "CallbackFailure",
15
+ "CallbackHandler",
16
+ "CallbackManager",
17
+ ]
@@ -0,0 +1,105 @@
1
+ """进程内生命周期回调管理器。
2
+
3
+ Callback 只观察已经发生的事件。需要修改、重试或阻止执行时,应使用 Middleware。
4
+ EventSink 负责把事件发往进程外;CallbackManager 负责进程内订阅,两者消费同一个 RunEvent。
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import inspect
10
+ import logging
11
+ from collections.abc import Awaitable, Callable, Iterable
12
+ from dataclasses import dataclass
13
+ from typing import Protocol, runtime_checkable
14
+
15
+ from agent_core.protocol.runtime import RunEvent
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ CallbackFunction = Callable[[RunEvent], Awaitable[None] | None]
20
+
21
+
22
+ @runtime_checkable
23
+ class CallbackHandler(Protocol):
24
+ """生命周期事件处理器协议。"""
25
+
26
+ async def on_event(self, event: RunEvent) -> None: ...
27
+
28
+
29
+ class BaseCallbackHandler:
30
+ """可按需继承的空回调基类。"""
31
+
32
+ async def on_event(self, event: RunEvent) -> None:
33
+ del event
34
+
35
+
36
+ class CallableCallbackHandler(BaseCallbackHandler):
37
+ """把同步或异步函数包装为 CallbackHandler。"""
38
+
39
+ def __init__(
40
+ self,
41
+ callback: CallbackFunction,
42
+ *,
43
+ event_types: Iterable[str] | None = None,
44
+ ) -> None:
45
+ self._callback = callback
46
+ self._event_types = frozenset(event_types or ())
47
+
48
+ async def on_event(self, event: RunEvent) -> None:
49
+ if self._event_types and event.event_type not in self._event_types:
50
+ return
51
+ result = self._callback(event)
52
+ if inspect.isawaitable(result):
53
+ await result
54
+
55
+
56
+ @dataclass(frozen=True)
57
+ class CallbackFailure:
58
+ """一次回调分发失败,不改变 Agent 主流程。"""
59
+
60
+ handler_name: str
61
+ event_type: str
62
+ error: Exception
63
+
64
+
65
+ class CallbackManager:
66
+ """按注册顺序分发生命周期事件并隔离订阅者故障。"""
67
+
68
+ def __init__(self, handlers: Iterable[CallbackHandler] | None = None) -> None:
69
+ self._handlers = list(handlers or ())
70
+
71
+ @property
72
+ def handlers(self) -> tuple[CallbackHandler, ...]:
73
+ return tuple(self._handlers)
74
+
75
+ def add(self, handler: CallbackHandler) -> None:
76
+ if handler not in self._handlers:
77
+ self._handlers.append(handler)
78
+
79
+ def remove(self, handler: CallbackHandler) -> bool:
80
+ try:
81
+ self._handlers.remove(handler)
82
+ except ValueError:
83
+ return False
84
+ return True
85
+
86
+ async def dispatch(self, event: RunEvent) -> tuple[CallbackFailure, ...]:
87
+ """向全部订阅者分发事件;单个订阅者失败不会中断其他订阅者。"""
88
+
89
+ failures: list[CallbackFailure] = []
90
+ for handler in tuple(self._handlers):
91
+ try:
92
+ await handler.on_event(event)
93
+ except Exception as exc:
94
+ failure = CallbackFailure(
95
+ handler_name=type(handler).__name__,
96
+ event_type=event.event_type,
97
+ error=exc,
98
+ )
99
+ failures.append(failure)
100
+ logger.exception(
101
+ "生命周期回调失败:handler=%s event=%s",
102
+ failure.handler_name,
103
+ event.event_type,
104
+ )
105
+ return tuple(failures)
@@ -0,0 +1,19 @@
1
+ """Checkpoint 和历史版本管理。"""
2
+
3
+ from agent_core.checkpoint.store import (
4
+ Checkpointer,
5
+ FileCheckpointer,
6
+ MemoryCheckpointer,
7
+ )
8
+ from agent_core.checkpoint.travel import (
9
+ CheckpointVersion,
10
+ TimeTravelCheckpointer,
11
+ )
12
+
13
+ __all__ = [
14
+ "CheckpointVersion",
15
+ "Checkpointer",
16
+ "FileCheckpointer",
17
+ "MemoryCheckpointer",
18
+ "TimeTravelCheckpointer",
19
+ ]
@@ -0,0 +1,93 @@
1
+ """checkpoint 文件名清洗与原子写入的底层工具。
2
+
3
+ 职责:
4
+ - thread_id 与文件名之间的严格双向映射(`_sanitize_thread_id` / `_restore_thread_id`);
5
+ - 相关常量(`_FILENAME_*` / `_WINDOWS_RESERVED` / `_HEX_ESCAPE`);
6
+ - JSON 文件的临时文件 + 原子替换写入(`_atomic_write_json`)。
7
+
8
+ 本模块不依赖包内其他模块,供 `store` 与 `travel` 复用。
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import os
15
+ import re
16
+ import tempfile
17
+ from pathlib import Path
18
+ from typing import Any
19
+
20
+ # 文件名严格清洗:白名单字符原样保留(可读性),其余按 UTF-8 字节转义为 %XX。
21
+ # Windows 非法字符 <>:"/\|?*、``%``(转义前缀,必须一并转义保证可逆)和控制符都要处理。
22
+ _FILENAME_ILLEGAL_CHARS = frozenset('<>:"/\\|?*%')
23
+ _FILENAME_ESCAPE_CHARS = _FILENAME_ILLEGAL_CHARS | {chr(code) for code in range(0x20)}
24
+ # Windows 保留设备名(不区分大小写,且无论后缀如何都保留):只检查文件名第一个点之前的主干。
25
+ _WINDOWS_RESERVED = re.compile(r"CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9]", re.IGNORECASE)
26
+ _HEX_ESCAPE = re.compile(r"%([0-9A-Fa-f]{2})")
27
+
28
+
29
+ def _escape_char(char: str) -> str:
30
+ """把单个字符转义为 UTF-8 字节的 %XX 序列。"""
31
+ return "".join(f"%{byte:02X}" for byte in char.encode("utf-8", errors="surrogatepass"))
32
+
33
+
34
+ def _sanitize_thread_id(thread_id: str) -> str:
35
+ """把 thread_id 清洗为跨平台安全且可逆的文件名主干。
36
+
37
+ 规则:
38
+ - 白名单(可打印且不属于非法字符集)原样保留,保证可读性;
39
+ - 其余字符(Windows 非法字符 ``<>:"/\\|?*``、``%``、控制符、孤立代理对等)
40
+ 按 UTF-8 字节转义为 ``%XX``。``%`` 本身也被转义,因此清洗是单射:
41
+ ``a/b`` 与 ``a_b`` 会得到不同文件名,不再互相覆盖;
42
+ - 以点/空格结尾或命中 Windows 保留设备名(CON、PRN、COM1 等)时,
43
+ 对首个点之前主干的末字符转义规避系统特殊处理,映射仍然可逆。
44
+ """
45
+ sanitized = "".join(
46
+ char if char.isprintable() and char not in _FILENAME_ESCAPE_CHARS else _escape_char(char)
47
+ for char in thread_id
48
+ )
49
+ if sanitized.endswith((".", " ")):
50
+ sanitized = sanitized[:-1] + _escape_char(sanitized[-1])
51
+ base_name = sanitized.split(".", 1)[0]
52
+ if _WINDOWS_RESERVED.fullmatch(base_name):
53
+ sanitized = sanitized[:-1] + _escape_char(sanitized[-1])
54
+ return sanitized
55
+
56
+
57
+ def _restore_thread_id(file_stem: str) -> str:
58
+ """`_sanitize_thread_id` 的逆映射;无法还原的历史文件名原样返回。"""
59
+ if "%" not in file_stem:
60
+ return file_stem
61
+ raw = bytearray()
62
+ index = 0
63
+ while index < len(file_stem):
64
+ match = _HEX_ESCAPE.match(file_stem, index)
65
+ if match is None:
66
+ raw.extend(file_stem[index].encode("utf-8", errors="surrogatepass"))
67
+ index += 1
68
+ else:
69
+ raw.append(int(match.group(1), 16))
70
+ index = match.end()
71
+ return raw.decode("utf-8", errors="surrogatepass")
72
+
73
+
74
+ def _atomic_write_json(file_path: Path, value: Any) -> None:
75
+ """在目标目录写临时文件并原子替换,避免崩溃留下截断 JSON。"""
76
+ temporary_path: Path | None = None
77
+ try:
78
+ with tempfile.NamedTemporaryFile(
79
+ mode="w",
80
+ encoding="utf-8",
81
+ dir=file_path.parent,
82
+ prefix=f".{file_path.name}.",
83
+ suffix=".tmp",
84
+ delete=False,
85
+ ) as temporary:
86
+ temporary_path = Path(temporary.name)
87
+ json.dump(value, temporary, ensure_ascii=False, indent=2)
88
+ temporary.flush()
89
+ os.fsync(temporary.fileno())
90
+ os.replace(temporary_path, file_path)
91
+ finally:
92
+ if temporary_path is not None and temporary_path.exists():
93
+ temporary_path.unlink(missing_ok=True)
@@ -0,0 +1,186 @@
1
+ """手写 checkpoint 持久化。
2
+
3
+ 功能:
4
+ - 保存和加载会话状态(state)
5
+ - 支持按 thread_id 隔离不同会话
6
+ - 简化实现:内存存储 + 可选的 JSON 文件持久化
7
+
8
+ 包内职责划分:
9
+ - `naming`:文件名清洗与原子写入(无依赖的底层工具);
10
+ - 本模块(`store`):当前快照的 `Checkpointer` 协议与内存/文件两个实现;
11
+ - `travel`:`CheckpointVersion` 与时间旅行多版本存储。
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import copy
17
+ import json
18
+ import logging
19
+ from pathlib import Path
20
+ from typing import Any, Protocol
21
+
22
+ from agent_core.checkpoint.naming import _atomic_write_json, _restore_thread_id, _sanitize_thread_id
23
+ from agent_core.errors import CheckpointError
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ class Checkpointer(Protocol):
29
+ """Agent Loop 所需的最小 checkpoint 存储协议。
30
+
31
+ 具体实现可以是内存、文件、SQLite 或 PostgreSQL;Core 只依赖这个协议,
32
+ 不把数据库驱动和业务项目耦合进来。
33
+ """
34
+
35
+ async def save(
36
+ self,
37
+ thread_id: str,
38
+ state: dict[str, Any],
39
+ metadata: dict[str, Any] | None = None,
40
+ ) -> str | None:
41
+ """保存一个会话快照,返回实现生成的版本 ID(如有)。"""
42
+ ...
43
+
44
+ async def load(self, thread_id: str) -> dict[str, Any] | None:
45
+ """读取会话当前快照。"""
46
+ ...
47
+
48
+ async def delete(self, thread_id: str) -> None:
49
+ """删除会话当前快照。"""
50
+ ...
51
+
52
+
53
+ class MemoryCheckpointer:
54
+ """内存 checkpoint 存储(进程内有效)。"""
55
+
56
+ def __init__(self) -> None:
57
+ # thread_id -> state
58
+ self._storage: dict[str, dict[str, Any]] = {}
59
+
60
+ async def save(
61
+ self,
62
+ thread_id: str,
63
+ state: dict[str, Any],
64
+ metadata: dict[str, Any] | None = None,
65
+ ) -> None:
66
+ """保存会话状态。
67
+
68
+ Args:
69
+ thread_id: 会话 ID。
70
+ state: 状态字典。
71
+ """
72
+ # metadata 由版本型实现使用;内存当前快照保持原有 state 结构不变。
73
+ del metadata
74
+ self._storage[thread_id] = copy.deepcopy(state)
75
+ logger.debug("Saved checkpoint for thread %r (keys: %d)", thread_id, len(state))
76
+
77
+ async def load(self, thread_id: str) -> dict[str, Any] | None:
78
+ """加载会话状态。
79
+
80
+ Args:
81
+ thread_id: 会话 ID。
82
+
83
+ Returns:
84
+ 状态字典,如果不存在则返回 None。
85
+ """
86
+ state = self._storage.get(thread_id)
87
+ if state is not None:
88
+ logger.debug("Loaded checkpoint for thread %r (keys: %d)", thread_id, len(state))
89
+ return copy.deepcopy(state) if state is not None else None
90
+
91
+ async def delete(self, thread_id: str) -> None:
92
+ """删除会话状态。
93
+
94
+ Args:
95
+ thread_id: 会话 ID。
96
+ """
97
+ if thread_id in self._storage:
98
+ del self._storage[thread_id]
99
+ logger.debug("Deleted checkpoint for thread %r", thread_id)
100
+
101
+ def list_threads(self) -> list[str]:
102
+ """列出所有会话 ID。"""
103
+ return list(self._storage.keys())
104
+
105
+
106
+ class FileCheckpointer:
107
+ """文件系统 checkpoint 存储(JSON 格式)。
108
+
109
+ 每个 thread 存储为独立的 JSON 文件。
110
+ """
111
+
112
+ def __init__(self, storage_dir: str | Path) -> None:
113
+ """
114
+ Args:
115
+ storage_dir: 存储目录路径。
116
+ """
117
+ self._storage_dir = Path(storage_dir)
118
+ self._storage_dir.mkdir(parents=True, exist_ok=True)
119
+
120
+ def _get_file_path(self, thread_id: str) -> Path:
121
+ """获取 thread 对应的文件路径。
122
+
123
+ 文件名经 `_sanitize_thread_id` 严格清洗:单射且可逆,含 ``/``、``\\``、
124
+ ``:`` 等 Windows 非法字符的 thread_id 不会互相覆盖。
125
+ """
126
+ return self._storage_dir / f"{_sanitize_thread_id(thread_id)}.json"
127
+
128
+ async def save(
129
+ self,
130
+ thread_id: str,
131
+ state: dict[str, Any],
132
+ metadata: dict[str, Any] | None = None,
133
+ ) -> None:
134
+ """保存会话状态到文件。
135
+
136
+ Args:
137
+ thread_id: 会话 ID。
138
+ state: 状态字典。
139
+ """
140
+ # 文件快照接口与内存实现保持一致,metadata 由需要版本历史的实现使用。
141
+ del metadata
142
+ file_path = self._get_file_path(thread_id)
143
+ try:
144
+ _atomic_write_json(file_path, state)
145
+ logger.debug("Saved checkpoint for thread %r to %s", thread_id, file_path)
146
+ except Exception as exc:
147
+ raise CheckpointError(f"Failed to save checkpoint for thread {thread_id!r}: {exc}") from exc
148
+
149
+ async def load(self, thread_id: str) -> dict[str, Any] | None:
150
+ """从文件加载会话状态。
151
+
152
+ Args:
153
+ thread_id: 会话 ID。
154
+
155
+ Returns:
156
+ 状态字典,如果文件不存在则返回 None。
157
+ """
158
+ file_path = self._get_file_path(thread_id)
159
+ if not file_path.exists():
160
+ return None
161
+
162
+ try:
163
+ with file_path.open("r", encoding="utf-8") as f:
164
+ raw_state = json.load(f)
165
+ if not isinstance(raw_state, dict):
166
+ raise CheckpointError(f"Checkpoint {thread_id!r} 的根节点必须是对象")
167
+ state: dict[str, Any] = {str(key): value for key, value in raw_state.items()}
168
+ logger.debug("Loaded checkpoint for thread %r from %s", thread_id, file_path)
169
+ return state
170
+ except Exception as exc:
171
+ raise CheckpointError(f"Failed to load checkpoint for thread {thread_id!r}: {exc}") from exc
172
+
173
+ async def delete(self, thread_id: str) -> None:
174
+ """删除会话状态文件。
175
+
176
+ Args:
177
+ thread_id: 会话 ID。
178
+ """
179
+ file_path = self._get_file_path(thread_id)
180
+ if file_path.exists():
181
+ file_path.unlink()
182
+ logger.debug("Deleted checkpoint for thread %r at %s", thread_id, file_path)
183
+
184
+ def list_threads(self) -> list[str]:
185
+ """列出所有会话 ID(文件名主干经 `_restore_thread_id` 逆映射还原)。"""
186
+ return [_restore_thread_id(file_path.stem) for file_path in self._storage_dir.glob("*.json")]