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,427 @@
1
+ """手写状态机调度器。
2
+
3
+ 核心概念:
4
+ - State: 字典,存储所有运行时状态
5
+ - Node: 可调用函数,接受 state,返回更新字典
6
+ - Edge: 节点间的转移规则(固定边 / 条件边)
7
+ - Execution: 从 START 开始,按边的规则依次执行节点,直到 END
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+ import logging
14
+ from collections.abc import Awaitable, Callable
15
+ from contextvars import ContextVar
16
+ from copy import deepcopy
17
+ from dataclasses import dataclass
18
+ from inspect import isawaitable
19
+ from typing import Any
20
+
21
+ from agent_core.errors import StateMachineError
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ # 特殊节点名称
26
+ START = "__start__"
27
+ END = "__end__"
28
+
29
+ # 节点函数签名
30
+ NodeFunc = Callable[[dict[str, Any]], dict[str, Any] | Awaitable[dict[str, Any]]]
31
+ # 条件路由函数签名:返回下一个节点名称
32
+ ConditionalRouteFunc = Callable[[dict[str, Any]], str | Awaitable[str]]
33
+ StepCallback = Callable[
34
+ [str, str, dict[str, Any], int],
35
+ Awaitable[None] | None,
36
+ ]
37
+ NodeStartCallback = Callable[["NodeExecutionContext", dict[str, Any]], Awaitable[None] | None]
38
+ IdempotencyKeyFactory = Callable[[str, int], str]
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class NodeExecutionPolicy:
43
+ """单个工作流节点的超时和重试策略。"""
44
+
45
+ timeout_seconds: float | None = None
46
+ max_attempts: int = 1
47
+ backoff_initial_seconds: float = 0.0
48
+ backoff_multiplier: float = 2.0
49
+ backoff_max_seconds: float = 30.0
50
+ retry_exceptions: tuple[type[Exception], ...] = (Exception,)
51
+ idempotent: bool = False
52
+
53
+ def __post_init__(self) -> None:
54
+ if self.timeout_seconds is not None and self.timeout_seconds <= 0:
55
+ raise ValueError("timeout_seconds 必须大于 0")
56
+ if self.max_attempts <= 0:
57
+ raise ValueError("max_attempts 必须大于 0")
58
+ if self.max_attempts > 1 and not self.idempotent:
59
+ raise ValueError("开启节点重试前必须声明 idempotent=True")
60
+ if self.backoff_initial_seconds < 0 or self.backoff_max_seconds < 0:
61
+ raise ValueError("退避时间不能小于 0")
62
+ if self.backoff_multiplier < 1:
63
+ raise ValueError("backoff_multiplier 不能小于 1")
64
+
65
+
66
+ @dataclass(frozen=True)
67
+ class NodeExecutionContext:
68
+ """当前节点调用可读取的执行上下文。"""
69
+
70
+ node_name: str
71
+ attempt: int
72
+ idempotency_key: str
73
+
74
+
75
+ _CURRENT_NODE_EXECUTION: ContextVar[NodeExecutionContext | None] = ContextVar(
76
+ "agent_core_node_execution",
77
+ default=None,
78
+ )
79
+
80
+
81
+ def current_node_execution() -> NodeExecutionContext | None:
82
+ """返回当前节点执行上下文,供外部副作用传递幂等键。"""
83
+
84
+ return _CURRENT_NODE_EXECUTION.get()
85
+
86
+
87
+ class WorkflowPause(Exception):
88
+ """节点主动暂停工作流,等待外部条件满足后恢复。"""
89
+
90
+ def __init__(self, reason: str, data: dict[str, Any] | None = None) -> None:
91
+ super().__init__(reason)
92
+ self.reason = reason
93
+ self.data = dict(data or {})
94
+
95
+
96
+ class StateMachine:
97
+ """手写状态机调度器。
98
+
99
+ 功能:
100
+ 1. 注册节点(node)和边(edge)
101
+ 2. 从 START 开始执行,按照边的规则转移
102
+ 3. 每执行一个节点,将返回值合并到 state
103
+ 4. 条件边根据当前 state 动态决定下一个节点
104
+ 5. 到达 END 或超过最大步数时停止
105
+ """
106
+
107
+ def __init__(self, *, max_steps: int = 50) -> None:
108
+ """
109
+ Args:
110
+ max_steps: 最大执行步数,防止死循环。
111
+ """
112
+ self._nodes: dict[str, NodeFunc] = {}
113
+ self._node_policies: dict[str, NodeExecutionPolicy] = {}
114
+ # 固定边:node_name -> next_node_name
115
+ self._edges: dict[str, str] = {}
116
+ # 条件边:node_name -> route_func
117
+ self._conditional_edges: dict[str, ConditionalRouteFunc] = {}
118
+ self._conditional_targets: dict[str, tuple[str, ...]] = {}
119
+ self._max_steps = max_steps
120
+
121
+ def add_node(
122
+ self,
123
+ name: str,
124
+ func: NodeFunc,
125
+ *,
126
+ policy: NodeExecutionPolicy | None = None,
127
+ ) -> None:
128
+ """注册一个节点。
129
+
130
+ Args:
131
+ name: 节点名称(全局唯一)。
132
+ func: 节点函数,接受 state,返回更新字典。
133
+ """
134
+ if name in self._nodes:
135
+ raise StateMachineError(f"Node {name!r} already registered.")
136
+ if name in (START, END):
137
+ raise StateMachineError(f"Cannot register reserved node name {name!r}.")
138
+ self._nodes[name] = func
139
+ self._node_policies[name] = policy or NodeExecutionPolicy()
140
+
141
+ def add_edge(self, from_node: str, to_node: str) -> None:
142
+ """注册一条固定边:from_node 执行完毕后自动转移到 to_node。
143
+
144
+ Args:
145
+ from_node: 源节点名称。
146
+ to_node: 目标节点名称。
147
+ """
148
+ if from_node in self._conditional_edges:
149
+ raise StateMachineError(f"Node {from_node!r} already has a conditional edge.")
150
+ self._edges[from_node] = to_node
151
+
152
+ def add_conditional_edges(
153
+ self,
154
+ from_node: str,
155
+ route_func: ConditionalRouteFunc,
156
+ *,
157
+ possible_targets: list[str] | tuple[str, ...] | None = None,
158
+ ) -> None:
159
+ """注册一条条件边:from_node 执行完毕后,调用 route_func(state) 决定下一个节点。
160
+
161
+ Args:
162
+ from_node: 源节点名称。
163
+ route_func: 路由函数,接受 state,返回下一个节点名称。
164
+ """
165
+ if from_node in self._edges:
166
+ raise StateMachineError(f"Node {from_node!r} already has a fixed edge.")
167
+ self._conditional_edges[from_node] = route_func
168
+ self._conditional_targets[from_node] = tuple(possible_targets or ())
169
+
170
+ async def ainvoke(self, initial_state: dict[str, Any]) -> dict[str, Any]:
171
+ """异步执行状态机。
172
+
173
+ Args:
174
+ initial_state: 初始状态字典。
175
+
176
+ Returns:
177
+ 执行完毕后的最终状态。
178
+
179
+ Raises:
180
+ StateMachineError: 节点不存在、超过最大步数等错误。
181
+ """
182
+ return await self.ainvoke_from(initial_state, start_node=START)
183
+
184
+ async def ainvoke_from(
185
+ self,
186
+ initial_state: dict[str, Any],
187
+ *,
188
+ start_node: str = START,
189
+ on_step: StepCallback | None = None,
190
+ on_node_start: NodeStartCallback | None = None,
191
+ idempotency_key_factory: IdempotencyKeyFactory | None = None,
192
+ ) -> dict[str, Any]:
193
+ """从指定节点执行,并在每个节点完成后发布可持久化快照。"""
194
+ if start_node != START and start_node != END and start_node not in self._nodes:
195
+ raise StateMachineError(f"Node {start_node!r} not found.")
196
+ state = deepcopy(initial_state)
197
+ current_node = start_node
198
+ steps = 0
199
+ completed_nodes = 0
200
+
201
+ logger.info("StateMachine: starting execution from %s", current_node)
202
+
203
+ while current_node != END:
204
+ steps += 1
205
+ if steps > self._max_steps:
206
+ raise StateMachineError(
207
+ f"StateMachine exceeded max steps ({self._max_steps}). Last node: {current_node!r}."
208
+ )
209
+
210
+ # 执行当前节点(START 和 END 是虚拟节点,不执行)
211
+ if current_node != START:
212
+ node_func = self._nodes.get(current_node)
213
+ if node_func is None:
214
+ raise StateMachineError(f"Node {current_node!r} not found.")
215
+
216
+ logger.debug("StateMachine: executing node %r (step %d)", current_node, steps)
217
+ try:
218
+ idempotency_key = (
219
+ idempotency_key_factory(current_node, completed_nodes)
220
+ if idempotency_key_factory is not None
221
+ else f"{current_node}:{completed_nodes + 1}"
222
+ )
223
+ updates = await self._execute_node(
224
+ current_node,
225
+ node_func,
226
+ state,
227
+ idempotency_key=idempotency_key,
228
+ on_node_start=on_node_start,
229
+ )
230
+ if updates:
231
+ state.update(updates)
232
+ completed_nodes += 1
233
+ except WorkflowPause:
234
+ raise
235
+ except Exception as exc:
236
+ raise StateMachineError(f"Node {current_node!r} raised exception: {exc}") from exc
237
+
238
+ # 决定下一个节点
239
+ next_node = await self._get_next_node(current_node, state)
240
+ if on_step is not None:
241
+ checkpoint = on_step(current_node, next_node, deepcopy(state), completed_nodes)
242
+ if isawaitable(checkpoint):
243
+ await checkpoint
244
+ logger.debug("StateMachine: %r -> %r", current_node, next_node)
245
+ current_node = next_node
246
+
247
+ logger.info("StateMachine: execution completed in %d steps", steps)
248
+ return state
249
+
250
+ @property
251
+ def node_count(self) -> int:
252
+ """返回已注册的实际节点数量。"""
253
+ return len(self._nodes)
254
+
255
+ async def _execute_node(
256
+ self,
257
+ node_name: str,
258
+ node_func: NodeFunc,
259
+ state: dict[str, Any],
260
+ *,
261
+ idempotency_key: str,
262
+ on_node_start: NodeStartCallback | None,
263
+ ) -> dict[str, Any]:
264
+ policy = self._node_policies[node_name]
265
+ delay = policy.backoff_initial_seconds
266
+ for attempt in range(1, policy.max_attempts + 1):
267
+ context = NodeExecutionContext(
268
+ node_name=node_name,
269
+ attempt=attempt,
270
+ idempotency_key=idempotency_key,
271
+ )
272
+ if on_node_start is not None:
273
+ pending = on_node_start(context, deepcopy(state))
274
+ if isawaitable(pending):
275
+ await pending
276
+ token = _CURRENT_NODE_EXECUTION.set(context)
277
+ try:
278
+ result = node_func(state)
279
+ if isawaitable(result):
280
+ if policy.timeout_seconds is None:
281
+ result = await result
282
+ else:
283
+ result = await asyncio.wait_for(result, timeout=policy.timeout_seconds)
284
+ return result
285
+ except WorkflowPause:
286
+ raise
287
+ except policy.retry_exceptions:
288
+ if attempt >= policy.max_attempts:
289
+ raise
290
+ if delay > 0:
291
+ await asyncio.sleep(delay)
292
+ delay = min(
293
+ policy.backoff_max_seconds,
294
+ max(delay, 0.001) * policy.backoff_multiplier,
295
+ )
296
+ finally:
297
+ _CURRENT_NODE_EXECUTION.reset(token)
298
+ raise StateMachineError(f"Node {node_name!r} exhausted retry policy.")
299
+
300
+ def describe(self) -> dict[str, Any]:
301
+ """返回可序列化的状态机结构,用于调试和可视化。"""
302
+
303
+ return {
304
+ "nodes": [
305
+ {
306
+ "name": name,
307
+ "policy": {
308
+ "timeout_seconds": policy.timeout_seconds,
309
+ "max_attempts": policy.max_attempts,
310
+ "idempotent": policy.idempotent,
311
+ },
312
+ }
313
+ for name, policy in self._node_policies.items()
314
+ ],
315
+ "edges": [
316
+ {"from": source, "to": target, "kind": "fixed"} for source, target in self._edges.items()
317
+ ],
318
+ "conditional_edges": [
319
+ {
320
+ "from": source,
321
+ "targets": list(self._conditional_targets.get(source, ())),
322
+ "kind": "conditional",
323
+ }
324
+ for source in self._conditional_edges
325
+ ],
326
+ "max_steps": self._max_steps,
327
+ }
328
+
329
+ def to_mermaid(self, *, direction: str = "TD") -> str:
330
+ """把状态机导出为 Mermaid flowchart。"""
331
+
332
+ normalized_direction = direction.upper()
333
+ if normalized_direction not in {"TD", "TB", "BT", "LR", "RL"}:
334
+ raise ValueError(f"不支持的 Mermaid 方向:{direction}")
335
+ names = [START, *self._nodes, END]
336
+ node_ids = {name: f"N{index}" for index, name in enumerate(names)}
337
+
338
+ def label(value: str) -> str:
339
+ return value.replace("\\", "\\\\").replace('"', "&quot;")
340
+
341
+ lines = [f"flowchart {normalized_direction}"]
342
+ lines.append(f' {node_ids[START]}(["START"])')
343
+ for name in self._nodes:
344
+ lines.append(f' {node_ids[name]}["{label(name)}"]')
345
+ lines.append(f' {node_ids[END]}(["END"])')
346
+ for source, target in self._edges.items():
347
+ if source in node_ids and target in node_ids:
348
+ lines.append(f" {node_ids[source]} --> {node_ids[target]}")
349
+ for index, source in enumerate(self._conditional_edges):
350
+ targets = self._conditional_targets.get(source, ())
351
+ if targets:
352
+ for target in targets:
353
+ if target in node_ids:
354
+ lines.append(f' {node_ids[source]} -. "条件" .-> {node_ids[target]}')
355
+ else:
356
+ dynamic_id = f"D{index}"
357
+ lines.append(f' {dynamic_id}{{"动态路由"}}')
358
+ lines.append(f' {node_ids[source]} -. "条件" .-> {dynamic_id}')
359
+ return "\n".join(lines)
360
+
361
+ async def _get_next_node(self, current_node: str, state: dict[str, Any]) -> str:
362
+ """根据边的规则决定下一个节点。
363
+
364
+ 优先级:
365
+ 1. 条件边(如果存在)
366
+ 2. 固定边(如果存在)
367
+ 3. 默认:END(终止执行)
368
+ """
369
+ # 条件边
370
+ if current_node in self._conditional_edges:
371
+ route_func = self._conditional_edges[current_node]
372
+ try:
373
+ next_node = route_func(state)
374
+ return await next_node if isawaitable(next_node) else next_node
375
+ except Exception as exc:
376
+ raise StateMachineError(
377
+ f"Conditional route from {current_node!r} raised exception: {exc}"
378
+ ) from exc
379
+
380
+ # 固定边
381
+ if current_node in self._edges:
382
+ return self._edges[current_node]
383
+
384
+ # 默认终止
385
+ return END
386
+
387
+
388
+ class StateMachineBuilder:
389
+ """状态机构建器(类似 StateGraph 的接口风格)。"""
390
+
391
+ def __init__(self, *, max_steps: int = 50) -> None:
392
+ self._machine = StateMachine(max_steps=max_steps)
393
+
394
+ def add_node(
395
+ self,
396
+ name: str,
397
+ func: NodeFunc,
398
+ *,
399
+ policy: NodeExecutionPolicy | None = None,
400
+ ) -> StateMachineBuilder:
401
+ """添加节点(链式调用)。"""
402
+ self._machine.add_node(name, func, policy=policy)
403
+ return self
404
+
405
+ def add_edge(self, from_node: str, to_node: str) -> StateMachineBuilder:
406
+ """添加固定边(链式调用)。"""
407
+ self._machine.add_edge(from_node, to_node)
408
+ return self
409
+
410
+ def add_conditional_edges(
411
+ self,
412
+ from_node: str,
413
+ route_func: ConditionalRouteFunc,
414
+ *,
415
+ possible_targets: list[str] | tuple[str, ...] | None = None,
416
+ ) -> StateMachineBuilder:
417
+ """添加条件边(链式调用)。"""
418
+ self._machine.add_conditional_edges(
419
+ from_node,
420
+ route_func,
421
+ possible_targets=possible_targets,
422
+ )
423
+ return self
424
+
425
+ def build(self) -> StateMachine:
426
+ """构建并返回状态机实例。"""
427
+ return self._machine
@@ -0,0 +1,237 @@
1
+ Metadata-Version: 2.5
2
+ Name: zero-agent-core
3
+ Version: 0.2.0
4
+ Summary: 零外部平台绑定的 Python Agent 运行时内核
5
+ Project-URL: Homepage, https://github.com/qiuzixu/agent-core
6
+ Project-URL: Documentation, https://github.com/qiuzixu/agent-core/tree/main/docs
7
+ Project-URL: Repository, https://github.com/qiuzixu/agent-core
8
+ Project-URL: Issues, https://github.com/qiuzixu/agent-core/issues
9
+ Author: qiuzixu
10
+ License-Expression: Apache-2.0
11
+ License-File: LICENSE
12
+ Keywords: acp,agent,function-calling,llm,mcp,react
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: <3.14,>=3.13
20
+ Provides-Extra: anthropic
21
+ Requires-Dist: anthropic<1,>=0.40; extra == 'anthropic'
22
+ Provides-Extra: chroma
23
+ Requires-Dist: chromadb<2,>=1.0; extra == 'chroma'
24
+ Provides-Extra: dev
25
+ Requires-Dist: mkdocs-material<10,>=9.7; extra == 'dev'
26
+ Requires-Dist: mypy<2,>=1.18; extra == 'dev'
27
+ Requires-Dist: pytest-asyncio<2,>=1.3; extra == 'dev'
28
+ Requires-Dist: pytest<10,>=9; extra == 'dev'
29
+ Requires-Dist: ruff<1,>=0.14; extra == 'dev'
30
+ Provides-Extra: gemini
31
+ Requires-Dist: google-genai<2,>=1.0; extra == 'gemini'
32
+ Provides-Extra: mcp
33
+ Requires-Dist: mcp<2,>=1.27; extra == 'mcp'
34
+ Provides-Extra: models
35
+ Requires-Dist: anthropic<1,>=0.40; extra == 'models'
36
+ Requires-Dist: dashscope<2,>=1.27; extra == 'models'
37
+ Requires-Dist: google-genai<2,>=1.0; extra == 'models'
38
+ Requires-Dist: httpx<1,>=0.28; extra == 'models'
39
+ Requires-Dist: openai<3,>=1.0; extra == 'models'
40
+ Provides-Extra: openai
41
+ Requires-Dist: httpx<1,>=0.28; extra == 'openai'
42
+ Requires-Dist: openai<3,>=1.0; extra == 'openai'
43
+ Provides-Extra: pgvector
44
+ Requires-Dist: asyncpg<1,>=0.30; extra == 'pgvector'
45
+ Provides-Extra: production
46
+ Requires-Dist: asyncpg<1,>=0.30; extra == 'production'
47
+ Provides-Extra: qwen
48
+ Requires-Dist: dashscope<2,>=1.27; extra == 'qwen'
49
+ Description-Content-Type: text/markdown
50
+
51
+ # Handwritten Agent Core
52
+
53
+ <!-- PyPI 徽章(发布到 PyPI 后取消此行注释即生效):
54
+ [![PyPI version](https://img.shields.io/pypi/v/zero-agent-core)](https://pypi.org/project/zero-agent-core/)
55
+ [![Python versions](https://img.shields.io/pypi/pyversions/zero-agent-core)](https://pypi.org/project/zero-agent-core/)
56
+ -->
57
+ [![Core CI](https://github.com/qiuzixu/agent-core/actions/workflows/ci.yml/badge.svg)](https://github.com/qiuzixu/agent-core/actions/workflows/ci.yml)
58
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](LICENSE)
59
+ [![Python 3.13](https://img.shields.io/badge/Python-3.13-blue.svg)](https://www.python.org/)
60
+
61
+ 一个零外部平台绑定的 Python Agent 运行时内核。项目从头实现 Agent Loop、
62
+ 工具调用、MCP、Skill、上下文压缩、持久化恢复、工作流、HITL、多模型适配和 ACP 协议。
63
+
64
+ > 当前版本为 `0.2.0` Alpha,要求 Python 3.13。公开 API 在 `0.x` 阶段仍可能调整。
65
+
66
+ ## 主要能力
67
+
68
+ - 普通和流式 ReAct Agent Loop,本轮多个工具并发执行;
69
+ - Function Calling、MCP stdio 客户端和声明式 Skill;
70
+ - OpenAI、Anthropic、Gemini、Ollama,以及自定义模型 Provider;
71
+ - JSON Schema 结构化输出,以及模型限流、并发、超时、熔断和 fallback;
72
+ - Middleware、Guardrails、HITL、调用统计和可选 OpenTelemetry;
73
+ - 生命周期 Callback、统一 RunEvent 和流式增量事件;
74
+ - Document/Blob、文本加载与切分、Embedding/Retriever/VectorStore 协议;
75
+ - 可选 `zero-agent-core-mineru` 扩展包解析 PDF、扫描件和复杂版面;
76
+ - 可选 `zero-agent-core-embeddings` 扩展包接入 OpenAI、Gemini 和 Ollama;
77
+ - 可选 `zero-agent-core-multi-agent` 扩展包编排多个独立 Agent;
78
+ - 内存、Chroma、pgvector 向量存储,关键词检索以及用户/租户/namespace 隔离;
79
+ - 带类型白名单、Schema 版本和迁移函数的安全 JSON 序列化;
80
+ - 安全文本/聊天 Prompt 模板、消息占位符、partial 和版本管理;
81
+ - 基于模型上下文用量的摘要压缩、工具结果瘦身和 spill;
82
+ - Run、事件、幂等键、Worker 租约、心跳、重启恢复和取消;
83
+ - Checkpoint 历史、回滚、节点重试/幂等/pending write 和 Mermaid 状态机导出;
84
+ - 会话、长期上下文、审批、模型选择的内存、SQLite、PostgreSQL 存储;
85
+ - 跨会话长期记忆、TTL、版本冲突、关键词检索和访问隔离;
86
+ - 用户、租户和管理员访问上下文;
87
+ - ACP JSON-RPC/stdio 服务端适配。
88
+
89
+ 完整边界见[已实现能力](docs/CAPABILITIES.md),模块和应用调用关系见
90
+ [系统架构](docs/ARCHITECTURE.md)。
91
+
92
+ ## 快速开始
93
+
94
+ 从 PyPI 安装(导入名是 `agent_core`,不受发行名更名影响):
95
+
96
+ ```bash
97
+ pip install zero-agent-core
98
+ ```
99
+
100
+ 推荐使用 [uv](https://docs.astral.sh/uv/)(与仓库其它项目一致,自动创建 `.venv`):
101
+
102
+ ```bash
103
+ cd agent-core
104
+ uv sync --all-extras
105
+ uv run python examples/starter/starter_agent.py # 五分钟上手:单文件最小完整示例
106
+ uv run python examples/basic_agent.py # 更小的最小示例
107
+ ```
108
+
109
+ `uv sync` 默认安装项目本体与 `dev` 依赖组;按需追加 extra:
110
+
111
+ ```bash
112
+ uv sync --extra openai # 单个 extra
113
+ uv sync --extra models,mcp,production
114
+ uv sync --all-extras # 全部可选依赖
115
+ ```
116
+
117
+ 不用 uv 时也可以用标准 venv + pip:
118
+
119
+ ```bash
120
+ cd agent-core
121
+ python -m venv .venv
122
+ python -m pip install -e ".[dev]"
123
+ python examples/starter/starter_agent.py
124
+ ```
125
+
126
+ `examples/starter/starter_agent.py` 与 `examples/basic_agent.py` 都使用离线演示模型,
127
+ 不需要 API Key 或外部服务。五分钟上手路径(安装 → 运行 → 换真实模型 → 加工具 →
128
+ 加工作流)见 [examples/starter/README.md](examples/starter/README.md)。
129
+
130
+ 真实模型按需安装(uv 用户把 `pip install` 换成 `uv pip install`,或用上面的
131
+ `uv sync --extra ...`):
132
+
133
+ ```bash
134
+ pip install "zero-agent-core[openai]"
135
+ pip install "zero-agent-core[anthropic]"
136
+ pip install "zero-agent-core[gemini]"
137
+ pip install "zero-agent-core[models,mcp,production]"
138
+ pip install "zero-agent-core[chroma]" # 本地持久化向量存储
139
+ pip install "zero-agent-core[pgvector]" # PostgreSQL 向量存储
140
+ pip install zero-agent-core-mineru # 独立 MinerU 文档解析扩展包
141
+ pip install "zero-agent-core-embeddings[openai]"
142
+ pip install "zero-agent-core-embeddings[gemini]"
143
+ pip install "zero-agent-core-embeddings[ollama]"
144
+ pip install zero-agent-core-multi-agent # 独立多 Agent 编排扩展包
145
+ ```
146
+
147
+ 最小组装方式:
148
+
149
+ ```python
150
+ from agent_core import ReActAgent, ToolExecutor, ToolRegistry, create_model_provider
151
+
152
+ model = create_model_provider(settings)
153
+
154
+ tools = ToolRegistry()
155
+ tools.register("lookup", lookup, "查询数据")
156
+
157
+ agent = ReActAgent(
158
+ llm=model,
159
+ tool_executor=ToolExecutor(tools),
160
+ system_prompt="你是一个助手。",
161
+ tool_definitions=tools.build_tool_definitions(),
162
+ )
163
+
164
+ answer = await agent.run("查询数据")
165
+ ```
166
+
167
+ 应用可以通过路径依赖使用尚未发布的 Core。**uv 0.11 起 path 源默认非 editable**,
168
+ 不加 `editable = true` 时 site-packages 里是同步时刻的快照,Core 源码更新后应用
169
+ 会用到旧代码(症状是新符号 ImportError):
170
+
171
+ ```toml
172
+ [project]
173
+ dependencies = ["zero-agent-core"]
174
+
175
+ [tool.uv.sources]
176
+ zero-agent-core = { path = "../agent-core", editable = true }
177
+ ```
178
+
179
+ 随后在应用目录执行 `uv sync`(多包仓库在 workspace 根执行
180
+ `uv sync --all-packages --all-extras`)完成安装。
181
+
182
+ ## 文档站
183
+
184
+ 文档源文件位于 `docs/`,使用 Material for MkDocs:
185
+
186
+ ```bash
187
+ python -m pip install mkdocs-material
188
+ python -m mkdocs serve # 本地预览 http://127.0.0.1:8000
189
+ python -m mkdocs build # 构建到 site/
190
+ ```
191
+
192
+ 子路径部署时设置 `DOCS_BASE`,例如 `/zero-agent-core/`。文档入口为
193
+ [docs/index.md](docs/index.md),包含快速开始、核心概念、模型、工具、MCP、Skill、存储、
194
+ 工作流、ACP 和公共 API 参考。
195
+
196
+ ## 项目边界
197
+
198
+ Core 只提供框架通用能力(Agent Loop、模型适配、工具执行、会话与存储等),
199
+ 不包含任何特定业务。应用侧仍负责:
200
+
201
+ - HTTP、WebSocket 或 SSE 等 API 接入;
202
+ - 身份认证、业务授权和密钥读取;
203
+ - 业务提示词、业务工具和业务数据模型;
204
+ - 前端通信与外部系统集成;
205
+ - 部署编排、监控后端和数据库运维。
206
+
207
+ 新 Agent 直接依赖 `agent_core`。Core 不感知、也不依赖任何具体业务域。
208
+
209
+ ## 开发
210
+
211
+ ```bash
212
+ uv run ruff check src tests examples
213
+ uv run pytest
214
+ uv run mkdocs build
215
+ uv build
216
+ ```
217
+
218
+ 不使用 uv 时:
219
+
220
+ ```bash
221
+ python -m pip install build
222
+ ruff check src tests examples
223
+ pytest
224
+ python -m mkdocs build
225
+ python -m build
226
+ ```
227
+
228
+ 贡献前请阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 和 [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)。
229
+ 安全问题按 [SECURITY.md](SECURITY.md) 私密报告。首次发布前的外部配置和决策记录在
230
+ [OPEN_SOURCE_CHECKLIST.md](OPEN_SOURCE_CHECKLIST.md)。
231
+
232
+ ## 许可证
233
+
234
+ 项目按 [Apache License 2.0](LICENSE) 发布。
235
+
236
+
237
+ <!-- 嵌入式 Agent 内核 这个名字备选 -->