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,179 @@
1
+ """模型调用协议。
2
+
3
+ Agent Core 只依赖该协议,不依赖 OpenAI、Anthropic、Gemini 或其他模型 SDK。
4
+ 具体模型适配器由 ``agent_core.model.providers`` 提供,应用也可以注册自定义适配器。
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import AsyncIterator, Callable
10
+ from dataclasses import dataclass, field
11
+ from typing import Any, Protocol, runtime_checkable
12
+
13
+ from agent_core.protocol.messages import Message
14
+
15
+
16
+ @dataclass
17
+ class StreamChunk:
18
+ """模型流式输出的统一数据块。"""
19
+
20
+ text: str = ""
21
+ tool_calls: list[dict[str, Any]] = field(default_factory=list)
22
+ is_tool_call: bool = False
23
+ finish_reason: str | None = None
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class ContextUsage:
28
+ """模型返回的上下文使用量。
29
+
30
+ ``exact`` 用于区分提供商真实计数与本地估算;Core 不规定计数实现,
31
+ 由具体模型适配器决定如何填充。
32
+ """
33
+
34
+ input_tokens: int | None
35
+ context_window_tokens: int | None
36
+ exact: bool
37
+ source: str
38
+
39
+
40
+ @runtime_checkable
41
+ class ModelAdapter(Protocol):
42
+ """Agent Loop 使用的最小模型协议。"""
43
+
44
+ async def chat(
45
+ self,
46
+ messages: list[Message],
47
+ *,
48
+ tools: list[dict[str, Any]] | None = None,
49
+ temperature: float | None = None,
50
+ max_tokens: int | None = None,
51
+ ) -> Message: ...
52
+
53
+ def stream_chat(
54
+ self,
55
+ messages: list[Message],
56
+ *,
57
+ tools: list[dict[str, Any]] | None = None,
58
+ temperature: float | None = None,
59
+ max_tokens: int | None = None,
60
+ ) -> AsyncIterator[StreamChunk]: ...
61
+
62
+ async def context_usage(
63
+ self,
64
+ messages: list[Message],
65
+ *,
66
+ tools: list[dict[str, Any]] | None = None,
67
+ ) -> ContextUsage: ...
68
+
69
+
70
+ # 模型提供商构造器。Core 不关心配置对象的具体类型,应用可以传入
71
+ # Pydantic Settings、字典或自己的配置类。
72
+ ModelProviderFactory = Callable[..., ModelAdapter]
73
+
74
+
75
+ class ModelProviderRegistry:
76
+ """可注册的模型提供商工厂。
77
+
78
+ Core 管理名称到构造器的映射,并内置 OpenAI、Anthropic、Ollama、Gemini
79
+ 适配器;应用可以继续注册自己的适配器。具体 SDK 由 Provider 延迟导入。
80
+ """
81
+
82
+ def __init__(self) -> None:
83
+ self._factories: dict[str, ModelProviderFactory] = {}
84
+
85
+ def register(
86
+ self,
87
+ name: str,
88
+ factory: ModelProviderFactory,
89
+ *,
90
+ replace: bool = False,
91
+ ) -> None:
92
+ """注册一个模型提供商构造器。"""
93
+ normalized = name.strip().lower()
94
+ if not normalized:
95
+ raise ValueError("模型提供商名称不能为空")
96
+ if normalized in self._factories and not replace:
97
+ raise ValueError(f"模型提供商已注册:{normalized}")
98
+ self._factories[normalized] = factory
99
+
100
+ def unregister(self, name: str) -> None:
101
+ """移除一个已注册的提供商。"""
102
+ self._factories.pop(name.strip().lower(), None)
103
+
104
+ def names(self) -> tuple[str, ...]:
105
+ """返回已注册的提供商名称。"""
106
+ return tuple(sorted(self._factories))
107
+
108
+ def create(
109
+ self,
110
+ name: str,
111
+ config: Any,
112
+ *,
113
+ model: str | None = None,
114
+ **kwargs: Any,
115
+ ) -> ModelAdapter:
116
+ """根据提供商名称创建模型适配器。"""
117
+ normalized = name.strip().lower()
118
+ factory = self._factories.get(normalized)
119
+ if factory is None:
120
+ available = ", ".join(self.names()) or "无"
121
+ raise ValueError(f"不支持的模型提供商:{normalized or '<empty>'};已注册:{available}")
122
+ return factory(config, model=model, **kwargs)
123
+
124
+
125
+ default_model_provider_registry = ModelProviderRegistry()
126
+
127
+
128
+ def register_model_provider(
129
+ name: str,
130
+ factory: ModelProviderFactory,
131
+ *,
132
+ replace: bool = False,
133
+ registry: ModelProviderRegistry | None = None,
134
+ ) -> None:
135
+ """向默认或指定注册表注册模型提供商。"""
136
+ (registry or default_model_provider_registry).register(
137
+ name,
138
+ factory,
139
+ replace=replace,
140
+ )
141
+
142
+
143
+ def create_model_provider(
144
+ config: Any,
145
+ *,
146
+ registry: ModelProviderRegistry | None = None,
147
+ provider: str | None = None,
148
+ model: str | None = None,
149
+ **kwargs: Any,
150
+ ) -> ModelAdapter:
151
+ """使用注册表创建模型适配器。
152
+
153
+ ``provider`` 省略时读取配置对象的 ``llm_provider``,默认为 ``openai``。
154
+ 具体密钥校验和 SDK 初始化由对应的 Provider 工厂完成。
155
+ """
156
+ selected = provider or getattr(config, "llm_provider", None) or "openai"
157
+ return (registry or default_model_provider_registry).create(
158
+ str(selected),
159
+ config,
160
+ model=model,
161
+ **kwargs,
162
+ )
163
+
164
+
165
+ def build_tool_definition(
166
+ name: str,
167
+ description: str,
168
+ parameters: dict[str, Any],
169
+ ) -> dict[str, Any]:
170
+ """构造通用的 function-calling 工具定义。"""
171
+
172
+ return {
173
+ "type": "function",
174
+ "function": {
175
+ "name": name,
176
+ "description": description,
177
+ "parameters": parameters,
178
+ },
179
+ }
@@ -0,0 +1,317 @@
1
+ """模型并发、窗口限流、熔断和 fallback 治理。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import time
7
+ from collections import defaultdict, deque
8
+ from collections.abc import AsyncIterator, Callable, Iterator
9
+ from contextlib import asynccontextmanager, contextmanager
10
+ from contextvars import ContextVar
11
+ from dataclasses import dataclass
12
+ from typing import Any
13
+
14
+ from agent_core.errors import (
15
+ ModelInvocationError,
16
+ ModelRateLimitError,
17
+ ModelTimeoutError,
18
+ ModelUnavailableError,
19
+ )
20
+ from agent_core.model.core import ContextUsage, ModelAdapter, StreamChunk
21
+ from agent_core.protocol.messages import Message
22
+
23
+ _CURRENT_SCOPE: ContextVar[str] = ContextVar("agent_core_model_scope", default="default")
24
+
25
+
26
+ @contextmanager
27
+ def model_execution_scope(scope: str) -> Iterator[None]:
28
+ """为当前异步调用链设置模型治理范围,通常使用租户或用户 ID。"""
29
+
30
+ normalized = scope.strip() or "default"
31
+ token = _CURRENT_SCOPE.set(normalized)
32
+ try:
33
+ yield
34
+ finally:
35
+ _CURRENT_SCOPE.reset(token)
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class ModelExecutionPolicy:
40
+ """模型调用治理策略。"""
41
+
42
+ max_concurrency: int = 8
43
+ requests_per_window: int | None = None
44
+ tokens_per_window: int | None = None
45
+ window_seconds: float = 60.0
46
+ queue_timeout_seconds: float | None = 30.0
47
+ call_timeout_seconds: float | None = 120.0
48
+ failure_threshold: int = 5
49
+ recovery_timeout_seconds: float = 30.0
50
+
51
+ def __post_init__(self) -> None:
52
+ if self.max_concurrency <= 0:
53
+ raise ValueError("max_concurrency 必须大于 0")
54
+ if self.requests_per_window is not None and self.requests_per_window <= 0:
55
+ raise ValueError("requests_per_window 必须大于 0")
56
+ if self.tokens_per_window is not None and self.tokens_per_window <= 0:
57
+ raise ValueError("tokens_per_window 必须大于 0")
58
+ if self.window_seconds <= 0:
59
+ raise ValueError("window_seconds 必须大于 0")
60
+ if self.failure_threshold <= 0:
61
+ raise ValueError("failure_threshold 必须大于 0")
62
+
63
+
64
+ class SlidingWindowRateLimiter:
65
+ """按 scope 统计请求数和估算输入 Token 的滑动窗口限流器。"""
66
+
67
+ def __init__(self, policy: ModelExecutionPolicy) -> None:
68
+ self._policy = policy
69
+ self._requests: dict[str, deque[float]] = defaultdict(deque)
70
+ self._tokens: dict[str, deque[tuple[float, int]]] = defaultdict(deque)
71
+ self._lock = asyncio.Lock()
72
+
73
+ async def acquire(self, scope: str, estimated_tokens: int) -> None:
74
+ deadline = (
75
+ None
76
+ if self._policy.queue_timeout_seconds is None
77
+ else time.monotonic() + self._policy.queue_timeout_seconds
78
+ )
79
+ while True:
80
+ async with self._lock:
81
+ now = time.monotonic()
82
+ wait_for = self._reserve_or_wait(scope, estimated_tokens, now)
83
+ if wait_for <= 0:
84
+ return
85
+ if deadline is not None and time.monotonic() + wait_for > deadline:
86
+ raise ModelRateLimitError(f"模型限流队列等待超过 {self._policy.queue_timeout_seconds} 秒")
87
+ await asyncio.sleep(wait_for)
88
+
89
+ def _reserve_or_wait(self, scope: str, estimated_tokens: int, now: float) -> float:
90
+ cutoff = now - self._policy.window_seconds
91
+ requests = self._requests[scope]
92
+ tokens = self._tokens[scope]
93
+ while requests and requests[0] <= cutoff:
94
+ requests.popleft()
95
+ while tokens and tokens[0][0] <= cutoff:
96
+ tokens.popleft()
97
+
98
+ waits: list[float] = []
99
+ request_limit = self._policy.requests_per_window
100
+ if request_limit is not None and len(requests) >= request_limit:
101
+ waits.append(requests[0] + self._policy.window_seconds - now)
102
+ token_limit = self._policy.tokens_per_window
103
+ token_total = sum(item[1] for item in tokens)
104
+ if token_limit is not None and token_total + estimated_tokens > token_limit:
105
+ if estimated_tokens > token_limit:
106
+ raise ModelRateLimitError("单次请求估算 Token 已超过窗口上限")
107
+ waits.append(tokens[0][0] + self._policy.window_seconds - now if tokens else 0.01)
108
+ if waits:
109
+ return max(0.01, min(waits))
110
+ requests.append(now)
111
+ tokens.append((now, max(0, estimated_tokens)))
112
+ return 0.0
113
+
114
+
115
+ class _ScopedConcurrencyLimiter:
116
+ def __init__(self, limit: int) -> None:
117
+ self._limit = limit
118
+ self._items: dict[str, asyncio.Semaphore] = {}
119
+ self._lock = asyncio.Lock()
120
+
121
+ async def _get(self, scope: str) -> asyncio.Semaphore:
122
+ async with self._lock:
123
+ return self._items.setdefault(scope, asyncio.Semaphore(self._limit))
124
+
125
+ @asynccontextmanager
126
+ async def slot(self, scope: str, acquire_timeout: float | None) -> AsyncIterator[None]:
127
+ semaphore = await self._get(scope)
128
+ try:
129
+ if acquire_timeout is None:
130
+ await semaphore.acquire()
131
+ else:
132
+ await asyncio.wait_for(semaphore.acquire(), timeout=acquire_timeout)
133
+ except TimeoutError as exc:
134
+ raise ModelRateLimitError(f"模型并发队列等待超过 {acquire_timeout} 秒") from exc
135
+ try:
136
+ yield
137
+ finally:
138
+ semaphore.release()
139
+
140
+
141
+ @dataclass
142
+ class _CircuitState:
143
+ failures: int = 0
144
+ opened_at: float | None = None
145
+
146
+
147
+ class GovernedModelAdapter:
148
+ """为任意 ModelAdapter 增加限流、超时、熔断和候选模型降级。"""
149
+
150
+ def __init__(
151
+ self,
152
+ primary: ModelAdapter,
153
+ *,
154
+ fallbacks: list[ModelAdapter] | None = None,
155
+ policy: ModelExecutionPolicy | None = None,
156
+ scope_resolver: Callable[[], str] | None = None,
157
+ max_circuit_entries: int = 4096,
158
+ ) -> None:
159
+ self._adapters = [primary, *(fallbacks or [])]
160
+ self._policy = policy or ModelExecutionPolicy()
161
+ self._rate_limiter = SlidingWindowRateLimiter(self._policy)
162
+ self._concurrency = _ScopedConcurrencyLimiter(self._policy.max_concurrency)
163
+ self._scope_resolver = scope_resolver or _CURRENT_SCOPE.get
164
+ self._circuits: dict[tuple[int, str], _CircuitState] = {}
165
+ self._circuit_lock = asyncio.Lock()
166
+ if max_circuit_entries <= 0:
167
+ raise ValueError("max_circuit_entries 必须大于 0")
168
+ self._max_circuit_entries = max_circuit_entries
169
+
170
+ def _evict_circuits_locked(self) -> None:
171
+ """熔断状态按 (adapter, scope) 累积;容量满时先淘汰干净状态,再按插入序淘汰最旧。"""
172
+ if len(self._circuits) < self._max_circuit_entries:
173
+ return
174
+ for key, state in list(self._circuits.items()):
175
+ if len(self._circuits) < self._max_circuit_entries:
176
+ break
177
+ if state.opened_at is None and state.failures == 0:
178
+ self._circuits.pop(key, None)
179
+ while len(self._circuits) >= self._max_circuit_entries:
180
+ self._circuits.pop(next(iter(self._circuits)))
181
+
182
+ @staticmethod
183
+ def _estimate_tokens(messages: list[Message]) -> int:
184
+ return max(1, sum(len(message.content) for message in messages) // 4)
185
+
186
+ async def _allow(self, index: int, scope: str) -> bool:
187
+ async with self._circuit_lock:
188
+ self._evict_circuits_locked()
189
+ state = self._circuits.setdefault((index, scope), _CircuitState())
190
+ if state.opened_at is None:
191
+ return True
192
+ if time.monotonic() - state.opened_at < self._policy.recovery_timeout_seconds:
193
+ return False
194
+ state.opened_at = None
195
+ state.failures = max(0, self._policy.failure_threshold - 1)
196
+ return True
197
+
198
+ async def _success(self, index: int, scope: str) -> None:
199
+ async with self._circuit_lock:
200
+ self._circuits[(index, scope)] = _CircuitState()
201
+
202
+ async def _failure(self, index: int, scope: str) -> None:
203
+ async with self._circuit_lock:
204
+ self._evict_circuits_locked()
205
+ state = self._circuits.setdefault((index, scope), _CircuitState())
206
+ state.failures += 1
207
+ if state.failures >= self._policy.failure_threshold:
208
+ state.opened_at = time.monotonic()
209
+
210
+ async def _any_adapter_available(self, scope: str) -> bool:
211
+ """是否存在至少一个未处于熔断恢复期的候选;短路探测,无副作用。"""
212
+ for index in range(len(self._adapters)):
213
+ if await self._allow(index, scope):
214
+ return True
215
+ return False
216
+
217
+ async def chat(
218
+ self,
219
+ messages: list[Message],
220
+ *,
221
+ tools: list[dict[str, Any]] | None = None,
222
+ temperature: float | None = None,
223
+ max_tokens: int | None = None,
224
+ ) -> Message:
225
+ scope = self._scope_resolver().strip() or "default"
226
+ last_error: Exception | None = None
227
+ # 全部候选处于熔断恢复期时直接失败,不消耗本地限流配额。
228
+ if not await self._any_adapter_available(scope):
229
+ raise ModelUnavailableError("没有可用的模型适配器(全部处于熔断恢复期)")
230
+ # 一个逻辑调用只消耗一次本地限流配额;fallback 属于同一次调用。
231
+ await self._rate_limiter.acquire(scope, self._estimate_tokens(messages))
232
+ async with self._concurrency.slot(scope, self._policy.queue_timeout_seconds):
233
+ for index, adapter in enumerate(self._adapters):
234
+ if not await self._allow(index, scope):
235
+ last_error = ModelUnavailableError(f"第 {index + 1} 个模型适配器熔断中")
236
+ continue
237
+ try:
238
+ async with asyncio.timeout(self._policy.call_timeout_seconds):
239
+ result = await adapter.chat(
240
+ messages,
241
+ tools=tools,
242
+ temperature=temperature,
243
+ max_tokens=max_tokens,
244
+ )
245
+ await self._success(index, scope)
246
+ return result
247
+ except TimeoutError as exc:
248
+ last_error = ModelTimeoutError("模型调用超时")
249
+ last_error.__cause__ = exc
250
+ await self._failure(index, scope)
251
+ except ModelInvocationError as exc:
252
+ last_error = exc
253
+ await self._failure(index, scope)
254
+ if last_error is None:
255
+ raise ModelUnavailableError("没有可用的模型适配器")
256
+ raise last_error
257
+
258
+ async def stream_chat(
259
+ self,
260
+ messages: list[Message],
261
+ *,
262
+ tools: list[dict[str, Any]] | None = None,
263
+ temperature: float | None = None,
264
+ max_tokens: int | None = None,
265
+ ) -> AsyncIterator[StreamChunk]:
266
+ scope = self._scope_resolver().strip() or "default"
267
+ last_error: Exception | None = None
268
+ # 与 chat() 相同:全部候选熔断时直接失败,不消耗本地限流配额。
269
+ if not await self._any_adapter_available(scope):
270
+ raise ModelUnavailableError("没有可用的模型适配器(全部处于熔断恢复期)")
271
+ await self._rate_limiter.acquire(scope, self._estimate_tokens(messages))
272
+ async with self._concurrency.slot(scope, self._policy.queue_timeout_seconds):
273
+ for index, adapter in enumerate(self._adapters):
274
+ emitted = False
275
+ if not await self._allow(index, scope):
276
+ last_error = ModelUnavailableError(f"第 {index + 1} 个模型适配器熔断中")
277
+ continue
278
+ try:
279
+ async with asyncio.timeout(self._policy.call_timeout_seconds):
280
+ async for chunk in adapter.stream_chat(
281
+ messages,
282
+ tools=tools,
283
+ temperature=temperature,
284
+ max_tokens=max_tokens,
285
+ ):
286
+ emitted = True
287
+ yield chunk
288
+ await self._success(index, scope)
289
+ return
290
+ except TimeoutError as exc:
291
+ last_error = ModelTimeoutError("模型流式调用超时")
292
+ last_error.__cause__ = exc
293
+ await self._failure(index, scope)
294
+ except ModelInvocationError as exc:
295
+ last_error = exc
296
+ await self._failure(index, scope)
297
+ if emitted and last_error is not None:
298
+ raise last_error
299
+ if last_error is None:
300
+ raise ModelUnavailableError("没有可用的模型适配器")
301
+ raise last_error
302
+
303
+ async def context_usage(
304
+ self,
305
+ messages: list[Message],
306
+ *,
307
+ tools: list[dict[str, Any]] | None = None,
308
+ ) -> ContextUsage:
309
+ return await self._adapters[0].context_usage(messages, tools=tools)
310
+
311
+
312
+ __all__ = [
313
+ "GovernedModelAdapter",
314
+ "ModelExecutionPolicy",
315
+ "SlidingWindowRateLimiter",
316
+ "model_execution_scope",
317
+ ]
@@ -0,0 +1,43 @@
1
+ """多模型切换:Protocol 抽象 + 各提供商适配器。
2
+
3
+ 使用方式:
4
+ # 开发环境(OpenAI 兼容接口)
5
+ llm = OpenAIProvider(api_key="sk-...", model="gpt-4o-mini")
6
+
7
+ # Anthropic Claude
8
+ llm = AnthropicProvider(api_key="sk-ant-...", model="claude-3-5-sonnet-20241022")
9
+
10
+ # 本地 Ollama
11
+ llm = OllamaProvider(model="qwen2.5:7b")
12
+
13
+ # 从环境变量/配置自动选择
14
+ llm = create_llm_provider(config)
15
+
16
+ 所有提供商均实现 ``ModelAdapter`` 协议,ReActAgent 只依赖该协议,
17
+ 切换提供商无需修改任何 Agent 代码。
18
+
19
+ 模块结构:
20
+ common.py 共享辅助(已知上下文窗口规格、统一 logger)
21
+ openai_compatible.py OpenAI 兼容接口适配器(DashScope/Qwen 复用该实现)
22
+ anthropic.py Anthropic Claude 适配器
23
+ gemini.py Google Gemini 适配器
24
+ ollama.py Ollama 本地模型适配器(复用 OpenAI 兼容接口)
25
+ factory.py 提供商构造器、注册与 create_llm_provider 工厂入口
26
+ """
27
+
28
+ from agent_core.model.providers.anthropic import AnthropicProvider
29
+ from agent_core.model.providers.common import known_context_window
30
+ from agent_core.model.providers.factory import MODEL_PROVIDER_REGISTRY, create_llm_provider
31
+ from agent_core.model.providers.gemini import GeminiProvider
32
+ from agent_core.model.providers.ollama import OllamaProvider
33
+ from agent_core.model.providers.openai_compatible import OpenAIProvider
34
+
35
+ __all__ = [
36
+ "MODEL_PROVIDER_REGISTRY",
37
+ "AnthropicProvider",
38
+ "GeminiProvider",
39
+ "OllamaProvider",
40
+ "OpenAIProvider",
41
+ "create_llm_provider",
42
+ "known_context_window",
43
+ ]