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,411 @@
1
+ """工作流和宏执行实例存储。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ import sqlite3
8
+ import uuid
9
+ from abc import ABC, abstractmethod
10
+ from collections.abc import Generator
11
+ from contextlib import contextmanager
12
+ from dataclasses import dataclass, field
13
+ from datetime import UTC, datetime
14
+ from pathlib import Path
15
+ from typing import Any
16
+
17
+ from agent_core.storage._sqlite_utils import (
18
+ _run_in_thread,
19
+ run_sync_in_thread,
20
+ sqlite_connection,
21
+ )
22
+
23
+
24
+ def _now() -> str:
25
+ return datetime.now(UTC).isoformat()
26
+
27
+
28
+ class WorkflowConcurrencyError(RuntimeError):
29
+ """工作流执行实例版本冲突。"""
30
+
31
+
32
+ def _json_value(value: Any, default: dict[str, Any] | list[dict[str, Any]]) -> Any:
33
+ """统一 SQLite 文本和 asyncpg JSONB 的返回形态。"""
34
+ if value is None or value == "":
35
+ return default
36
+ return json.loads(value) if isinstance(value, str) else value
37
+
38
+
39
+ def _postgres_datetime(value: str) -> datetime:
40
+ parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
41
+ return parsed if parsed.tzinfo is not None else parsed.replace(tzinfo=UTC)
42
+
43
+
44
+ @dataclass
45
+ class WorkflowExecution:
46
+ execution_id: str = field(default_factory=lambda: str(uuid.uuid4()))
47
+ definition_id: str = ""
48
+ execution_type: str = "workflow"
49
+ user_id: str | None = None
50
+ tenant_id: str | None = None
51
+ status: str = "queued"
52
+ current_step: str | None = None
53
+ steps_completed: int = 0
54
+ total_steps: int = 0
55
+ input_data: dict[str, Any] = field(default_factory=dict)
56
+ result_data: dict[str, Any] = field(default_factory=dict)
57
+ events: list[dict[str, Any]] = field(default_factory=list)
58
+ version: int = 0
59
+ created_at: str = field(default_factory=_now)
60
+ updated_at: str = field(default_factory=_now)
61
+
62
+ def to_dict(self) -> dict[str, Any]:
63
+ return {
64
+ "execution_id": self.execution_id,
65
+ "definition_id": self.definition_id,
66
+ "execution_type": self.execution_type,
67
+ "user_id": self.user_id,
68
+ "tenant_id": self.tenant_id,
69
+ "status": self.status,
70
+ "current_step": self.current_step,
71
+ "steps_completed": self.steps_completed,
72
+ "total_steps": self.total_steps,
73
+ "input_data": self.input_data,
74
+ "result_data": self.result_data,
75
+ "events": self.events,
76
+ "version": self.version,
77
+ "created_at": self.created_at,
78
+ "updated_at": self.updated_at,
79
+ }
80
+
81
+
82
+ class WorkflowExecutionStore(ABC):
83
+ @abstractmethod
84
+ async def save(self, execution: WorkflowExecution) -> None: ...
85
+
86
+ @abstractmethod
87
+ async def load(self, execution_id: str) -> WorkflowExecution | None: ...
88
+
89
+ @abstractmethod
90
+ async def list(self, definition_id: str, tenant_id: str | None = None) -> list[WorkflowExecution]: ...
91
+
92
+ # 统一端口名称,同时保留工作流模块已有的 save/load/list 调用。
93
+ async def save_execution(self, execution: WorkflowExecution) -> None:
94
+ await self.save(execution)
95
+
96
+ async def load_execution(self, execution_id: str) -> WorkflowExecution | None:
97
+ return await self.load(execution_id)
98
+
99
+
100
+ def _from_dict(value: dict[str, Any]) -> WorkflowExecution:
101
+ input_data = _json_value(value.get("input_data"), {})
102
+ result_data = _json_value(value.get("result_data"), {})
103
+ events = _json_value(value.get("events"), [])
104
+ return WorkflowExecution(
105
+ execution_id=str(value["execution_id"]),
106
+ definition_id=str(value.get("definition_id", "")),
107
+ execution_type=str(value.get("execution_type", "workflow")),
108
+ user_id=value.get("user_id"),
109
+ tenant_id=value.get("tenant_id"),
110
+ status=str(value.get("status", "queued")),
111
+ current_step=value.get("current_step"),
112
+ steps_completed=int(value.get("steps_completed", 0)),
113
+ total_steps=int(value.get("total_steps", 0)),
114
+ input_data=dict(input_data),
115
+ result_data=dict(result_data),
116
+ events=list(events),
117
+ version=int(value.get("version", 0)),
118
+ created_at=str(value.get("created_at", _now())),
119
+ updated_at=str(value.get("updated_at", _now())),
120
+ )
121
+
122
+
123
+ class MemoryWorkflowExecutionStore(WorkflowExecutionStore):
124
+ def __init__(self) -> None:
125
+ self._items: dict[str, dict[str, Any]] = {}
126
+ self._lock = asyncio.Lock()
127
+
128
+ async def save(self, execution: WorkflowExecution) -> None:
129
+ async with self._lock:
130
+ previous = self._items.get(execution.execution_id)
131
+ stored_version = int(previous.get("version", 0)) if previous else 0
132
+ if previous is not None and stored_version != execution.version:
133
+ raise WorkflowConcurrencyError(f"工作流 {execution.execution_id} 版本冲突")
134
+ execution.version = stored_version + 1
135
+ execution.updated_at = _now()
136
+ self._items[execution.execution_id] = execution.to_dict()
137
+
138
+ async def load(self, execution_id: str) -> WorkflowExecution | None:
139
+ value = self._items.get(execution_id)
140
+ return _from_dict(value) if value else None
141
+
142
+ async def list(self, definition_id: str, tenant_id: str | None = None) -> list[WorkflowExecution]:
143
+ return [
144
+ _from_dict(value)
145
+ for value in self._items.values()
146
+ if value.get("definition_id") == definition_id
147
+ and (tenant_id is None or value.get("tenant_id") == tenant_id)
148
+ ]
149
+
150
+
151
+ class SqliteWorkflowExecutionStore(WorkflowExecutionStore):
152
+ DDL = """
153
+ CREATE TABLE IF NOT EXISTS workflow_executions (
154
+ execution_id TEXT PRIMARY KEY,
155
+ definition_id TEXT NOT NULL,
156
+ execution_type TEXT NOT NULL,
157
+ user_id TEXT,
158
+ tenant_id TEXT,
159
+ status TEXT NOT NULL,
160
+ current_step TEXT,
161
+ steps_completed INTEGER NOT NULL DEFAULT 0,
162
+ total_steps INTEGER NOT NULL DEFAULT 0,
163
+ input_data TEXT NOT NULL DEFAULT '{}',
164
+ result_data TEXT NOT NULL DEFAULT '{}',
165
+ events TEXT NOT NULL DEFAULT '[]',
166
+ version INTEGER NOT NULL DEFAULT 0,
167
+ created_at TEXT NOT NULL,
168
+ updated_at TEXT NOT NULL
169
+ );
170
+ CREATE INDEX IF NOT EXISTS idx_workflow_execution_definition
171
+ ON workflow_executions(definition_id, tenant_id, updated_at);
172
+ """
173
+
174
+ def __init__(self, db_path: str | Path = "./sessions.db") -> None:
175
+ self._db_path = str(Path(db_path).resolve())
176
+ # 建表 DDL/PRAGMA 初始化同样放入工作线程执行,与 async 方法共用同一连接参数规范。
177
+ run_sync_in_thread(self._init_db)
178
+
179
+ def _init_db(self) -> None:
180
+ with self._connect() as conn:
181
+ conn.executescript(self.DDL)
182
+
183
+ @contextmanager
184
+ def _connect(self) -> Generator[sqlite3.Connection]:
185
+ """获取 SQLite 连接(同步,自动提交/回滚;统一 timeout 与 WAL 规范)。"""
186
+ with sqlite_connection(self._db_path) as conn:
187
+ yield conn
188
+
189
+ @staticmethod
190
+ def _row(row: tuple[Any, ...]) -> WorkflowExecution:
191
+ keys = [
192
+ "execution_id",
193
+ "definition_id",
194
+ "execution_type",
195
+ "user_id",
196
+ "tenant_id",
197
+ "status",
198
+ "current_step",
199
+ "steps_completed",
200
+ "total_steps",
201
+ "input_data",
202
+ "result_data",
203
+ "events",
204
+ "version",
205
+ "created_at",
206
+ "updated_at",
207
+ ]
208
+ value = dict(zip(keys, row, strict=True))
209
+ for key in ("input_data", "result_data", "events"):
210
+ value[key] = json.loads(value[key]) if value[key] else ({} if key != "events" else [])
211
+ return _from_dict(value)
212
+
213
+ async def save(self, execution: WorkflowExecution) -> None:
214
+ def _op() -> tuple[int, str]:
215
+ with self._connect() as conn:
216
+ conn.execute("BEGIN IMMEDIATE")
217
+ row = conn.execute(
218
+ "SELECT version FROM workflow_executions WHERE execution_id=?",
219
+ (execution.execution_id,),
220
+ ).fetchone()
221
+ stored_version = int(row[0]) if row else 0
222
+ if row is not None and stored_version != execution.version:
223
+ raise WorkflowConcurrencyError(f"工作流 {execution.execution_id} 版本冲突")
224
+ next_version = stored_version + 1
225
+ updated_at = _now()
226
+ value = execution.to_dict()
227
+ cursor = conn.execute(
228
+ """INSERT INTO workflow_executions
229
+ (execution_id,definition_id,execution_type,user_id,tenant_id,status,current_step,
230
+ steps_completed,total_steps,input_data,result_data,events,version,created_at,updated_at)
231
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
232
+ ON CONFLICT(execution_id) DO UPDATE SET status=excluded.status,
233
+ current_step=excluded.current_step, steps_completed=excluded.steps_completed,
234
+ total_steps=excluded.total_steps, result_data=excluded.result_data,
235
+ events=excluded.events, version=excluded.version, updated_at=excluded.updated_at
236
+ WHERE workflow_executions.version=?""",
237
+ (
238
+ execution.execution_id,
239
+ execution.definition_id,
240
+ execution.execution_type,
241
+ execution.user_id,
242
+ execution.tenant_id,
243
+ execution.status,
244
+ execution.current_step,
245
+ execution.steps_completed,
246
+ execution.total_steps,
247
+ json.dumps(value["input_data"], ensure_ascii=False),
248
+ json.dumps(value["result_data"], ensure_ascii=False),
249
+ json.dumps(value["events"], ensure_ascii=False),
250
+ next_version,
251
+ execution.created_at,
252
+ updated_at,
253
+ stored_version,
254
+ ),
255
+ )
256
+ if cursor.rowcount != 1:
257
+ raise WorkflowConcurrencyError(f"工作流 {execution.execution_id} 保存时发生并发冲突")
258
+ return next_version, updated_at
259
+
260
+ next_version, updated_at = await _run_in_thread(_op)
261
+ execution.version = next_version
262
+ execution.updated_at = updated_at
263
+
264
+ async def load(self, execution_id: str) -> WorkflowExecution | None:
265
+ def _op() -> Any:
266
+ with self._connect() as conn:
267
+ return conn.execute(
268
+ "SELECT execution_id,definition_id,execution_type,user_id,tenant_id,status,current_step,"
269
+ "steps_completed,total_steps,input_data,result_data,events,version,created_at,updated_at "
270
+ "FROM workflow_executions WHERE execution_id=?",
271
+ (execution_id,),
272
+ ).fetchone()
273
+
274
+ row = await _run_in_thread(_op)
275
+ return self._row(row) if row else None
276
+
277
+ async def list(self, definition_id: str, tenant_id: str | None = None) -> list[WorkflowExecution]:
278
+ query = (
279
+ "SELECT execution_id,definition_id,execution_type,user_id,tenant_id,"
280
+ "status,current_step,steps_completed,total_steps,input_data,result_data,"
281
+ "events,version,created_at,updated_at FROM workflow_executions "
282
+ "WHERE definition_id=?"
283
+ )
284
+ args: list[Any] = [definition_id]
285
+ if tenant_id is not None:
286
+ query += " AND tenant_id=?"
287
+ args.append(tenant_id)
288
+ query += " ORDER BY updated_at DESC"
289
+
290
+ def _op() -> list[Any]:
291
+ with self._connect() as conn:
292
+ return conn.execute(query, args).fetchall()
293
+
294
+ rows = await _run_in_thread(_op)
295
+ return [self._row(row) for row in rows]
296
+
297
+
298
+ class PostgresWorkflowExecutionStore(WorkflowExecutionStore):
299
+ def __init__(self, dsn: str) -> None:
300
+ self._dsn = dsn.replace("postgresql+asyncpg://", "postgresql://")
301
+ self._pool: Any = None
302
+
303
+ async def initialize(self) -> None:
304
+ try:
305
+ import asyncpg
306
+ except ImportError as exc:
307
+ raise ImportError("PostgresWorkflowExecutionStore 需要 asyncpg") from exc
308
+ self._pool = await asyncpg.create_pool(self._dsn, min_size=2, max_size=10)
309
+ async with self._pool.acquire() as conn:
310
+ await conn.execute("""
311
+ CREATE TABLE IF NOT EXISTS workflow_executions (
312
+ execution_id TEXT PRIMARY KEY, definition_id TEXT NOT NULL,
313
+ execution_type TEXT NOT NULL, user_id TEXT, tenant_id TEXT,
314
+ status TEXT NOT NULL, current_step TEXT,
315
+ steps_completed INTEGER NOT NULL DEFAULT 0, total_steps INTEGER NOT NULL DEFAULT 0,
316
+ input_data JSONB NOT NULL DEFAULT '{}'::jsonb,
317
+ result_data JSONB NOT NULL DEFAULT '{}'::jsonb,
318
+ events JSONB NOT NULL DEFAULT '[]'::jsonb,
319
+ version INTEGER NOT NULL DEFAULT 0,
320
+ created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL
321
+ )
322
+ """)
323
+
324
+ async def close(self) -> None:
325
+ if self._pool:
326
+ await self._pool.close()
327
+
328
+ def _check(self) -> None:
329
+ if self._pool is None:
330
+ raise RuntimeError("PostgresWorkflowExecutionStore 未初始化")
331
+
332
+ async def save(self, execution: WorkflowExecution) -> None:
333
+ self._check()
334
+ async with self._pool.acquire() as conn:
335
+ async with conn.transaction():
336
+ previous = await conn.fetchval(
337
+ "SELECT version FROM workflow_executions WHERE execution_id=$1 FOR UPDATE",
338
+ execution.execution_id,
339
+ )
340
+ stored_version = int(previous) if previous is not None else 0
341
+ if previous is not None and stored_version != execution.version:
342
+ raise WorkflowConcurrencyError(f"工作流 {execution.execution_id} 版本冲突")
343
+ next_version = stored_version + 1
344
+ updated_at = _now()
345
+ value = execution.to_dict()
346
+ command = await conn.execute(
347
+ """INSERT INTO workflow_executions
348
+ (execution_id,definition_id,execution_type,user_id,tenant_id,status,
349
+ current_step,steps_completed,total_steps,input_data,result_data,
350
+ events,version,created_at,updated_at)
351
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10::jsonb,$11::jsonb,$12::jsonb,$13,$14,$15)
352
+ ON CONFLICT(execution_id) DO UPDATE SET
353
+ status=$6,current_step=$7,steps_completed=$8,total_steps=$9,
354
+ result_data=$11::jsonb,events=$12::jsonb,version=$13,updated_at=$15
355
+ WHERE workflow_executions.version=$16""",
356
+ execution.execution_id,
357
+ execution.definition_id,
358
+ execution.execution_type,
359
+ execution.user_id,
360
+ execution.tenant_id,
361
+ execution.status,
362
+ execution.current_step,
363
+ execution.steps_completed,
364
+ execution.total_steps,
365
+ json.dumps(value["input_data"]),
366
+ json.dumps(value["result_data"]),
367
+ json.dumps(value["events"]),
368
+ next_version,
369
+ _postgres_datetime(execution.created_at),
370
+ _postgres_datetime(updated_at),
371
+ stored_version,
372
+ )
373
+ expected = "INSERT 0 1" if previous is None else "UPDATE 1"
374
+ if command != expected:
375
+ raise WorkflowConcurrencyError(f"工作流 {execution.execution_id} 保存时发生并发冲突")
376
+ execution.version = next_version
377
+ execution.updated_at = updated_at
378
+
379
+ async def load(self, execution_id: str) -> WorkflowExecution | None:
380
+ self._check()
381
+ async with self._pool.acquire() as conn:
382
+ row = await conn.fetchrow("SELECT * FROM workflow_executions WHERE execution_id=$1", execution_id)
383
+ return _from_dict(dict(row)) if row else None
384
+
385
+ async def list(self, definition_id: str, tenant_id: str | None = None) -> list[WorkflowExecution]:
386
+ self._check()
387
+ async with self._pool.acquire() as conn:
388
+ rows = await conn.fetch(
389
+ """SELECT * FROM workflow_executions
390
+ WHERE definition_id=$1 AND ($2::text IS NULL OR tenant_id=$2)
391
+ ORDER BY updated_at DESC""",
392
+ definition_id,
393
+ tenant_id,
394
+ )
395
+ return [_from_dict(dict(row)) for row in rows]
396
+
397
+
398
+ def create_workflow_execution_store(
399
+ env: str = "development",
400
+ *,
401
+ sqlite_path: str = "./sessions.db",
402
+ postgres_url: str | None = None,
403
+ ) -> WorkflowExecutionStore:
404
+ normalized = env.lower()
405
+ if normalized in {"test", "testing"}:
406
+ return MemoryWorkflowExecutionStore()
407
+ if normalized in {"production", "prod"}:
408
+ if not postgres_url:
409
+ raise ValueError("生产环境需要 PostgreSQL 连接地址")
410
+ return PostgresWorkflowExecutionStore(postgres_url)
411
+ return SqliteWorkflowExecutionStore(sqlite_path)
@@ -0,0 +1,5 @@
1
+ """工具注册与执行。"""
2
+
3
+ from agent_core.tools.executor import ToolExecutor, ToolRegistry, ToolSpec
4
+
5
+ __all__ = ["ToolExecutor", "ToolRegistry", "ToolSpec"]