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,273 @@
1
+ """适配器 3:Gemini。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import uuid
6
+ from collections.abc import AsyncIterator
7
+ from typing import Any
8
+
9
+ from agent_core.errors import ModelInvocationError
10
+ from agent_core.model.core import ContextUsage, StreamChunk
11
+ from agent_core.model.providers.common import logger
12
+ from agent_core.protocol.messages import Message, assistant_message
13
+
14
+
15
+ class GeminiProvider:
16
+ """Google Gemini 模型适配器。
17
+
18
+ 使用官方 ``google-genai`` SDK,安装方式:``uv add google-genai``。
19
+ 该 SDK 只在选择 Gemini 时延迟导入,不影响现有提供商。
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ *,
25
+ api_key: str,
26
+ model: str = "gemini-2.5-flash",
27
+ temperature: float = 0.0,
28
+ max_tokens: int = 4096,
29
+ context_window_tokens: int | None = None,
30
+ ) -> None:
31
+ try:
32
+ from google import genai
33
+ from google.genai import types
34
+ except ImportError as exc:
35
+ raise ImportError("GeminiProvider 需要 google-genai 包,请执行:uv add google-genai") from exc
36
+
37
+ self._genai_types = types
38
+ self._client = genai.Client(api_key=api_key)
39
+ self.model = model
40
+ self._temperature = temperature
41
+ self._max_tokens = max_tokens
42
+ self._context_window_tokens = context_window_tokens
43
+ self.provider_name = "gemini"
44
+
45
+ # ──────────────────────────────────────────────
46
+ # 调用 Gemini API 进行上下文使用
47
+ # ──────────────────────────────────────────────
48
+ async def context_usage(
49
+ self,
50
+ messages: list[Message],
51
+ *,
52
+ tools: list[dict[str, Any]] | None = None,
53
+ ) -> ContextUsage:
54
+ _system_prompt, contents = self._to_gemini_contents(messages)
55
+ try:
56
+ response = await self._client.aio.models.count_tokens(
57
+ model=self.model,
58
+ contents=contents,
59
+ )
60
+ input_tokens = getattr(response, "total_tokens", None)
61
+ if isinstance(input_tokens, int):
62
+ model = await self._client.aio.models.get(model=self.model)
63
+ model_limit = getattr(model, "input_token_limit", None)
64
+ return ContextUsage(
65
+ input_tokens=input_tokens,
66
+ context_window_tokens=(
67
+ self._context_window_tokens or (model_limit if isinstance(model_limit, int) else None)
68
+ ),
69
+ exact=True,
70
+ source="provider_tokenizer",
71
+ )
72
+ except Exception as exc:
73
+ logger.debug("Gemini tokenizer 不可用:%s", exc)
74
+ return ContextUsage(
75
+ input_tokens=None,
76
+ context_window_tokens=self._context_window_tokens,
77
+ exact=False,
78
+ source="unavailable",
79
+ )
80
+
81
+ # ──────────────────────────────────────────────
82
+ # 转换为 Gemini API 格式消息
83
+ # ──────────────────────────────────────────────
84
+ def _to_gemini_contents(self, messages: list[Message]) -> tuple[str | None, list[Any]]:
85
+ """把内部消息转换为 Gemini Content/Part。"""
86
+ types = self._genai_types
87
+ system_instruction: str | None = None
88
+ contents: list[Any] = []
89
+
90
+ for message in messages:
91
+ if message.role == "system":
92
+ system_instruction = (
93
+ f"{system_instruction}\n\n{message.content}" if system_instruction else message.content
94
+ )
95
+ continue
96
+
97
+ if message.role == "user":
98
+ contents.append(
99
+ types.Content(
100
+ role="user",
101
+ parts=[types.Part.from_text(text=message.content)],
102
+ )
103
+ )
104
+ continue
105
+
106
+ if message.role == "assistant":
107
+ parts: list[Any] = []
108
+ if message.content:
109
+ parts.append(types.Part.from_text(text=message.content))
110
+ for call in message.tool_calls:
111
+ parts.append(
112
+ types.Part.from_function_call(
113
+ name=str(call.get("name", "")),
114
+ args=dict(call.get("args") or {}),
115
+ )
116
+ )
117
+ if parts:
118
+ contents.append(types.Content(role="model", parts=parts))
119
+ continue
120
+
121
+ if message.role == "tool":
122
+ # Gemini 使用 function_response 表达工具结果。
123
+ contents.append(
124
+ types.Content(
125
+ role="user",
126
+ parts=[
127
+ types.Part.from_function_response(
128
+ name=message.name or "tool",
129
+ response={"result": message.content},
130
+ )
131
+ ],
132
+ )
133
+ )
134
+
135
+ return system_instruction, contents
136
+
137
+ # ──────────────────────────────────────────────
138
+ # 转换为 Gemini API 格式工具
139
+ # ──────────────────────────────────────────────
140
+ def _to_gemini_tools(self, tools: list[dict[str, Any]]) -> list[Any]:
141
+ """把 OpenAI function schema 转换为 Gemini function declarations。"""
142
+ types = self._genai_types
143
+ declarations = []
144
+ for tool in tools:
145
+ function = tool.get("function", tool)
146
+ declarations.append(
147
+ types.FunctionDeclaration(
148
+ name=function["name"],
149
+ description=function.get("description", ""),
150
+ parameters=function.get("parameters", {"type": "object", "properties": {}}),
151
+ )
152
+ )
153
+ return [types.Tool(function_declarations=declarations)] if declarations else []
154
+
155
+ # ──────────────────────────────────────────────
156
+ # 构造 Gemini 请求配置
157
+ # ──────────────────────────────────────────────
158
+ def _config(
159
+ self,
160
+ *,
161
+ system_instruction: str | None,
162
+ tools: list[dict[str, Any]] | None,
163
+ temperature: float | None,
164
+ max_tokens: int | None,
165
+ ) -> Any:
166
+ """构造 Gemini 请求配置。"""
167
+ kwargs: dict[str, Any] = {
168
+ "temperature": temperature if temperature is not None else self._temperature,
169
+ "max_output_tokens": max_tokens if max_tokens is not None else self._max_tokens,
170
+ }
171
+ if system_instruction:
172
+ kwargs["system_instruction"] = system_instruction
173
+ if tools:
174
+ kwargs["tools"] = self._to_gemini_tools(tools)
175
+ return self._genai_types.GenerateContentConfig(**kwargs)
176
+
177
+ @staticmethod
178
+ def _response_message(response: Any) -> Message:
179
+ """从 Gemini 响应中提取文本和函数调用。"""
180
+ content = ""
181
+ tool_calls: list[dict[str, Any]] = []
182
+ candidates = getattr(response, "candidates", None) or []
183
+ parts = getattr(getattr(candidates[0], "content", None), "parts", []) if candidates else []
184
+ for part in parts or []:
185
+ text = getattr(part, "text", None)
186
+ if text:
187
+ content += text
188
+ function_call = getattr(part, "function_call", None)
189
+ if function_call is not None:
190
+ tool_calls.append(
191
+ {
192
+ "id": str(getattr(function_call, "id", "") or uuid.uuid4()),
193
+ "type": "function",
194
+ "name": str(getattr(function_call, "name", "")),
195
+ "args": dict(getattr(function_call, "args", {}) or {}),
196
+ }
197
+ )
198
+ return assistant_message(content, tool_calls=tool_calls)
199
+
200
+ # ──────────────────────────────────────────────
201
+ # 调用 Gemini API 进行聊天
202
+ # ──────────────────────────────────────────────
203
+ async def chat(
204
+ self,
205
+ messages: list[Message],
206
+ *,
207
+ tools: list[dict[str, Any]] | None = None,
208
+ temperature: float | None = None,
209
+ max_tokens: int | None = None,
210
+ ) -> Message:
211
+ system_instruction, contents = self._to_gemini_contents(messages)
212
+ config = self._config(
213
+ system_instruction=system_instruction,
214
+ tools=tools,
215
+ temperature=temperature,
216
+ max_tokens=max_tokens,
217
+ )
218
+ try:
219
+ response = await self._client.aio.models.generate_content(
220
+ model=self.model, contents=contents, config=config
221
+ )
222
+ except Exception as exc:
223
+ raise ModelInvocationError(f"Gemini API call failed: {exc}") from exc
224
+ return self._response_message(response)
225
+
226
+ # ──────────────────────────────────────────────
227
+ # 调用 Gemini API 进行流式聊天
228
+ # ──────────────────────────────────────────────
229
+ async def stream_chat(
230
+ self,
231
+ messages: list[Message],
232
+ *,
233
+ tools: list[dict[str, Any]] | None = None,
234
+ temperature: float | None = None,
235
+ max_tokens: int | None = None,
236
+ ) -> AsyncIterator[StreamChunk]:
237
+ system_instruction, contents = self._to_gemini_contents(messages)
238
+ config = self._config(
239
+ system_instruction=system_instruction,
240
+ tools=tools,
241
+ temperature=temperature,
242
+ max_tokens=max_tokens,
243
+ )
244
+ tool_call_map: dict[str, dict[str, Any]] = {}
245
+ try:
246
+ stream = await self._client.aio.models.generate_content_stream(
247
+ model=self.model, contents=contents, config=config
248
+ )
249
+ async for chunk in stream:
250
+ message = self._response_message(chunk)
251
+ if message.content:
252
+ yield StreamChunk(text=message.content)
253
+ if message.tool_calls:
254
+ for call_index, call in enumerate(message.tool_calls):
255
+ # 同一轮允许多次调用同名函数,按位置而不是名称归并增量。
256
+ key = f"{call_index}:{call['name']}"
257
+ current = tool_call_map.setdefault(
258
+ key,
259
+ {
260
+ "id": call["id"],
261
+ "type": "function",
262
+ "name": call["name"],
263
+ "args": {},
264
+ },
265
+ )
266
+ current["args"].update(call.get("args") or {})
267
+ except Exception as exc:
268
+ raise ModelInvocationError(f"Gemini stream failed: {exc}") from exc
269
+
270
+ if tool_call_map:
271
+ yield StreamChunk(tool_calls=list(tool_call_map.values()), is_tool_call=True)
272
+ else:
273
+ yield StreamChunk()
@@ -0,0 +1,80 @@
1
+ """适配器 4:Ollama(本地模型)。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import AsyncIterator
6
+ from typing import Any
7
+
8
+ from agent_core.model.core import ContextUsage, StreamChunk
9
+ from agent_core.model.providers.openai_compatible import OpenAIProvider
10
+ from agent_core.protocol.messages import Message
11
+
12
+
13
+ class OllamaProvider:
14
+ """Ollama 本地模型提供商(复用 OpenAI 兼容接口)。
15
+
16
+ Ollama 提供 OpenAI 兼容的 API,直接复用 OpenAIProvider。
17
+
18
+ 需要先启动 Ollama:ollama serve
19
+ 支持的模型:qwen2.5, llama3.2, mistral, deepseek-r1 等
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ *,
25
+ model: str = "qwen2.5:7b",
26
+ base_url: str = "http://localhost:11434/v1",
27
+ temperature: float = 0.0,
28
+ max_tokens: int = 4096,
29
+ context_window_tokens: int | None = None,
30
+ ) -> None:
31
+ self._inner = OpenAIProvider(
32
+ api_key="ollama", # Ollama 不需要真实 key
33
+ base_url=base_url,
34
+ model=model,
35
+ temperature=temperature,
36
+ max_tokens=max_tokens,
37
+ context_window_tokens=context_window_tokens,
38
+ )
39
+ self.model = model
40
+ self.provider_name = "ollama"
41
+
42
+ # ──────────────────────────────────────────────
43
+ # 获取上下文使用统计
44
+ # ──────────────────────────────────────────────
45
+ async def context_usage(
46
+ self,
47
+ messages: list[Message],
48
+ *,
49
+ tools: list[dict[str, Any]] | None = None,
50
+ ) -> ContextUsage:
51
+ return await self._inner.context_usage(messages, tools=tools)
52
+
53
+ # ──────────────────────────────────────────────
54
+ # 调用 Ollama API 进行聊天
55
+ # ──────────────────────────────────────────────
56
+ async def chat(
57
+ self,
58
+ messages: list[Message],
59
+ *,
60
+ tools: list[dict[str, Any]] | None = None,
61
+ temperature: float | None = None,
62
+ max_tokens: int | None = None,
63
+ ) -> Message:
64
+ return await self._inner.chat(messages, tools=tools, temperature=temperature, max_tokens=max_tokens)
65
+
66
+ # ──────────────────────────────────────────────
67
+ # 调用 Ollama API 进行流式聊天
68
+ # ──────────────────────────────────────────────
69
+ async def stream_chat(
70
+ self,
71
+ messages: list[Message],
72
+ *,
73
+ tools: list[dict[str, Any]] | None = None,
74
+ temperature: float | None = None,
75
+ max_tokens: int | None = None,
76
+ ) -> AsyncIterator[StreamChunk]:
77
+ async for chunk in self._inner.stream_chat(
78
+ messages, tools=tools, temperature=temperature, max_tokens=max_tokens
79
+ ):
80
+ yield chunk