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,360 @@
1
+ """用户/租户级模型选择存储。
2
+
3
+ 模型选择属于 Agent Core 的运行配置,不应放在前端 localStorage 或进程全局变量中。
4
+ 开发环境使用 SQLite,测试使用内存,生产环境使用 PostgreSQL。
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ import copy
11
+ import sqlite3
12
+ from collections.abc import Generator
13
+ from contextlib import contextmanager
14
+ from datetime import UTC, datetime
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ from agent_core.ports.model_selection import (
19
+ ModelSelection,
20
+ ModelSelectionScope,
21
+ ModelSelectionStore,
22
+ )
23
+ from agent_core.storage._sqlite_utils import (
24
+ _run_in_thread,
25
+ run_sync_in_thread,
26
+ sqlite_connection,
27
+ )
28
+
29
+
30
+ def _now() -> str:
31
+ return datetime.now(UTC).isoformat()
32
+
33
+
34
+ def _validate_scope(scope: ModelSelectionScope, user_id: str | None, tenant_id: str | None) -> None:
35
+ if scope == "user" and not user_id:
36
+ raise ValueError("用户级模型选择需要 user_id")
37
+ if not tenant_id:
38
+ raise ValueError("模型选择需要 tenant_id")
39
+
40
+
41
+ def _parse_scope(value: object) -> ModelSelectionScope:
42
+ """校验持久化层返回的 scope,避免非法值进入领域对象。"""
43
+ scope = str(value)
44
+ if scope == "user":
45
+ return "user"
46
+ if scope == "tenant":
47
+ return "tenant"
48
+ raise ValueError(f"无效的模型选择范围:{scope}")
49
+
50
+
51
+ class MemoryModelSelectionStore:
52
+ """测试环境内存实现。"""
53
+
54
+ def __init__(self) -> None:
55
+ self._data: dict[tuple[str, str | None, str], ModelSelection] = {}
56
+ self._lock = asyncio.Lock()
57
+
58
+ async def resolve(self, *, user_id: str | None, tenant_id: str | None) -> ModelSelection | None:
59
+ async with self._lock:
60
+ if user_id and tenant_id:
61
+ value = self._data.get(("user", user_id, tenant_id))
62
+ if value:
63
+ return copy.deepcopy(value)
64
+ if tenant_id:
65
+ value = self._data.get(("tenant", "", tenant_id))
66
+ if value:
67
+ return copy.deepcopy(value)
68
+ return None
69
+
70
+ async def save(
71
+ self,
72
+ provider: str,
73
+ model: str,
74
+ *,
75
+ scope: ModelSelectionScope,
76
+ user_id: str | None,
77
+ tenant_id: str | None,
78
+ ) -> ModelSelection:
79
+ _validate_scope(scope, user_id, tenant_id)
80
+ async with self._lock:
81
+ key = (scope, user_id if scope == "user" else "", tenant_id or "")
82
+ previous = self._data.get(key)
83
+ value = ModelSelection(
84
+ provider=provider.strip().lower(),
85
+ model=model.strip(),
86
+ scope=scope,
87
+ user_id=user_id if scope == "user" else None,
88
+ tenant_id=tenant_id,
89
+ version=(previous.version + 1) if previous else 1,
90
+ updated_at=_now(),
91
+ )
92
+ self._data[key] = value
93
+ return copy.deepcopy(value)
94
+
95
+ async def clear(self, *, scope: ModelSelectionScope, user_id: str | None, tenant_id: str | None) -> None:
96
+ _validate_scope(scope, user_id, tenant_id)
97
+ async with self._lock:
98
+ self._data.pop((scope, user_id if scope == "user" else "", tenant_id or ""), None)
99
+
100
+
101
+ class SqliteModelSelectionStore:
102
+ """开发环境 SQLite 实现。"""
103
+
104
+ def __init__(self, db_path: str | Path = "./sessions.db") -> None:
105
+ self._db_path = str(Path(db_path).resolve())
106
+ # 建表/建索引 DDL 初始化同样放入工作线程执行,与 async 方法共用同一连接参数规范。
107
+ run_sync_in_thread(self._init_db)
108
+
109
+ def _init_db(self) -> None:
110
+ with self._connect() as conn:
111
+ conn.execute(
112
+ """CREATE TABLE IF NOT EXISTS agent_model_selections (
113
+ scope TEXT NOT NULL CHECK(scope IN ('user', 'tenant')),
114
+ user_id TEXT,
115
+ tenant_id TEXT NOT NULL,
116
+ provider TEXT NOT NULL,
117
+ model TEXT NOT NULL,
118
+ version INTEGER NOT NULL DEFAULT 1,
119
+ updated_at TEXT NOT NULL,
120
+ PRIMARY KEY (scope, user_id, tenant_id)
121
+ )"""
122
+ )
123
+ conn.execute(
124
+ "CREATE INDEX IF NOT EXISTS idx_agent_model_selection_tenant "
125
+ "ON agent_model_selections(tenant_id)"
126
+ )
127
+
128
+ @contextmanager
129
+ def _connect(self) -> Generator[sqlite3.Connection]:
130
+ """获取 SQLite 连接(同步,自动提交/回滚;统一 timeout 与 WAL 规范)。"""
131
+ with sqlite_connection(self._db_path) as conn:
132
+ yield conn
133
+
134
+ @staticmethod
135
+ def _row(row: sqlite3.Row | tuple[Any, ...] | None) -> ModelSelection | None:
136
+ if row is None:
137
+ return None
138
+ values = tuple(row)
139
+ return ModelSelection(
140
+ provider=str(values[3]),
141
+ model=str(values[4]),
142
+ scope=_parse_scope(values[0]),
143
+ user_id=values[1] or None,
144
+ tenant_id=values[2],
145
+ version=int(values[5]),
146
+ updated_at=str(values[6]),
147
+ )
148
+
149
+ async def resolve(self, *, user_id: str | None, tenant_id: str | None) -> ModelSelection | None:
150
+ def _op() -> ModelSelection | None:
151
+ with self._connect() as conn:
152
+ if user_id and tenant_id:
153
+ row = conn.execute(
154
+ "SELECT scope,user_id,tenant_id,provider,model,version,updated_at "
155
+ "FROM agent_model_selections WHERE scope='user' AND user_id=? AND tenant_id=?",
156
+ (user_id, tenant_id),
157
+ ).fetchone()
158
+ value = self._row(row)
159
+ if value:
160
+ return value
161
+ if tenant_id:
162
+ row = conn.execute(
163
+ "SELECT scope,user_id,tenant_id,provider,model,version,updated_at "
164
+ "FROM agent_model_selections WHERE scope='tenant' AND tenant_id=?",
165
+ (tenant_id,),
166
+ ).fetchone()
167
+ return self._row(row)
168
+ return None
169
+
170
+ return await _run_in_thread(_op)
171
+
172
+ async def save(
173
+ self,
174
+ provider: str,
175
+ model: str,
176
+ *,
177
+ scope: ModelSelectionScope,
178
+ user_id: str | None,
179
+ tenant_id: str | None,
180
+ ) -> ModelSelection:
181
+ _validate_scope(scope, user_id, tenant_id)
182
+ key_user = user_id if scope == "user" else ""
183
+
184
+ def _op() -> ModelSelection:
185
+ with self._connect() as conn:
186
+ row = conn.execute(
187
+ "SELECT version FROM agent_model_selections "
188
+ "WHERE scope=? AND user_id IS ? AND tenant_id=?",
189
+ (scope, key_user, tenant_id),
190
+ ).fetchone()
191
+ version = int(row[0]) + 1 if row else 1
192
+ value = ModelSelection(
193
+ provider.strip().lower(), model.strip(), scope, key_user, tenant_id, version, _now()
194
+ )
195
+ conn.execute(
196
+ """INSERT INTO agent_model_selections(
197
+ scope,user_id,tenant_id,provider,model,version,updated_at
198
+ )
199
+ VALUES (?,?,?,?,?,?,?)
200
+ ON CONFLICT(scope,user_id,tenant_id) DO UPDATE SET
201
+ provider=excluded.provider, model=excluded.model, version=excluded.version,
202
+ updated_at=excluded.updated_at""",
203
+ (
204
+ scope,
205
+ key_user,
206
+ tenant_id,
207
+ value.provider,
208
+ value.model,
209
+ value.version,
210
+ value.updated_at,
211
+ ),
212
+ )
213
+ return value
214
+
215
+ return await _run_in_thread(_op)
216
+
217
+ async def clear(self, *, scope: ModelSelectionScope, user_id: str | None, tenant_id: str | None) -> None:
218
+ _validate_scope(scope, user_id, tenant_id)
219
+
220
+ def _op() -> None:
221
+ with self._connect() as conn:
222
+ conn.execute(
223
+ "DELETE FROM agent_model_selections WHERE scope=? AND user_id IS ? AND tenant_id=?",
224
+ (scope, user_id if scope == "user" else "", tenant_id),
225
+ )
226
+
227
+ await _run_in_thread(_op)
228
+
229
+
230
+ class PostgresModelSelectionStore:
231
+ """生产环境 PostgreSQL 实现。"""
232
+
233
+ def __init__(self, dsn: str) -> None:
234
+ self._dsn = dsn.replace("postgresql+asyncpg://", "postgresql://")
235
+ self._pool: Any = None
236
+
237
+ async def initialize(self) -> None:
238
+ try:
239
+ import asyncpg
240
+ except ImportError as exc:
241
+ raise ImportError("PostgresModelSelectionStore 需要 asyncpg") from exc
242
+ self._pool = await asyncpg.create_pool(self._dsn, min_size=2, max_size=10)
243
+ async with self._pool.acquire() as conn:
244
+ await conn.execute("""CREATE TABLE IF NOT EXISTS agent_model_selections (
245
+ scope TEXT NOT NULL CHECK(scope IN ('user', 'tenant')),
246
+ user_id TEXT, tenant_id TEXT NOT NULL, provider TEXT NOT NULL, model TEXT NOT NULL,
247
+ version INTEGER NOT NULL DEFAULT 1, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
248
+ PRIMARY KEY(scope, user_id, tenant_id)
249
+ )""")
250
+
251
+ async def close(self) -> None:
252
+ if self._pool:
253
+ await self._pool.close()
254
+
255
+ def _check(self) -> None:
256
+ if self._pool is None:
257
+ raise RuntimeError("PostgresModelSelectionStore 未初始化")
258
+
259
+ @staticmethod
260
+ def _value(row: Any) -> ModelSelection:
261
+ return ModelSelection(
262
+ provider=str(row["provider"]),
263
+ model=str(row["model"]),
264
+ scope=_parse_scope(row["scope"]),
265
+ user_id=row["user_id"] or None,
266
+ tenant_id=row["tenant_id"],
267
+ version=int(row["version"]),
268
+ updated_at=str(row["updated_at"]),
269
+ )
270
+
271
+ async def resolve(self, *, user_id: str | None, tenant_id: str | None) -> ModelSelection | None:
272
+ self._check()
273
+ async with self._pool.acquire() as conn:
274
+ if user_id and tenant_id:
275
+ row = await conn.fetchrow(
276
+ "SELECT * FROM agent_model_selections WHERE scope='user' AND user_id=$1 AND tenant_id=$2",
277
+ user_id,
278
+ tenant_id,
279
+ )
280
+ if row:
281
+ return self._value(row)
282
+ if tenant_id:
283
+ row = await conn.fetchrow(
284
+ "SELECT * FROM agent_model_selections WHERE scope='tenant' AND tenant_id=$1",
285
+ tenant_id,
286
+ )
287
+ return self._value(row) if row else None
288
+ return None
289
+
290
+ async def save(
291
+ self,
292
+ provider: str,
293
+ model: str,
294
+ *,
295
+ scope: ModelSelectionScope,
296
+ user_id: str | None,
297
+ tenant_id: str | None,
298
+ ) -> ModelSelection:
299
+ _validate_scope(scope, user_id, tenant_id)
300
+ self._check()
301
+ key_user = user_id if scope == "user" else ""
302
+ async with self._pool.acquire() as conn:
303
+ row = await conn.fetchrow(
304
+ """INSERT INTO agent_model_selections(
305
+ scope,user_id,tenant_id,provider,model,version,updated_at
306
+ )
307
+ VALUES($1,$2,$3,$4,$5,1,NOW())
308
+ ON CONFLICT(scope,user_id,tenant_id) DO UPDATE SET
309
+ provider=EXCLUDED.provider,
310
+ model=EXCLUDED.model,
311
+ version=agent_model_selections.version + 1,
312
+ updated_at=NOW()
313
+ RETURNING scope,user_id,tenant_id,provider,model,version,updated_at""",
314
+ scope,
315
+ key_user,
316
+ tenant_id,
317
+ provider.strip().lower(),
318
+ model.strip(),
319
+ )
320
+ return self._value(row)
321
+
322
+ async def clear(self, *, scope: ModelSelectionScope, user_id: str | None, tenant_id: str | None) -> None:
323
+ _validate_scope(scope, user_id, tenant_id)
324
+ self._check()
325
+ async with self._pool.acquire() as conn:
326
+ await conn.execute(
327
+ """DELETE FROM agent_model_selections
328
+ WHERE scope=$1 AND user_id IS NOT DISTINCT FROM $2 AND tenant_id=$3""",
329
+ scope,
330
+ user_id if scope == "user" else "",
331
+ tenant_id,
332
+ )
333
+
334
+
335
+ def create_model_selection_store(
336
+ env: str = "development",
337
+ *,
338
+ sqlite_path: str = "./sessions.db",
339
+ postgres_url: str | None = None,
340
+ ) -> ModelSelectionStore:
341
+ """按环境创建模型选择存储。"""
342
+ normalized = env.lower()
343
+ if normalized in {"test", "testing"}:
344
+ return MemoryModelSelectionStore()
345
+ if normalized in {"production", "prod"}:
346
+ if not postgres_url:
347
+ raise ValueError("生产环境需要配置 PostgreSQL 连接地址")
348
+ return PostgresModelSelectionStore(postgres_url)
349
+ return SqliteModelSelectionStore(sqlite_path)
350
+
351
+
352
+ __all__ = [
353
+ "MemoryModelSelectionStore",
354
+ "ModelSelection",
355
+ "ModelSelectionScope",
356
+ "ModelSelectionStore",
357
+ "PostgresModelSelectionStore",
358
+ "SqliteModelSelectionStore",
359
+ "create_model_selection_store",
360
+ ]