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.
- agent_core/__init__.py +525 -0
- agent_core/access/__init__.py +6 -0
- agent_core/access/context.py +45 -0
- agent_core/access/guard.py +40 -0
- agent_core/acp/README.md +236 -0
- agent_core/acp/__init__.py +20 -0
- agent_core/acp/server.py +351 -0
- agent_core/acp/types.py +86 -0
- agent_core/callbacks/__init__.py +17 -0
- agent_core/callbacks/manager.py +105 -0
- agent_core/checkpoint/__init__.py +19 -0
- agent_core/checkpoint/naming.py +93 -0
- agent_core/checkpoint/store.py +186 -0
- agent_core/checkpoint/travel.py +448 -0
- agent_core/compaction/__init__.py +57 -0
- agent_core/compaction/middleware.py +428 -0
- agent_core/compaction/prompts.py +32 -0
- agent_core/compaction/pruner.py +121 -0
- agent_core/compaction/region.py +145 -0
- agent_core/compaction/spill.py +81 -0
- agent_core/compaction/spill_tool.py +42 -0
- agent_core/compaction/summarizer.py +95 -0
- agent_core/compaction/token_limit.py +74 -0
- agent_core/compaction/types.py +93 -0
- agent_core/docs.py +15 -0
- agent_core/documents/__init__.py +23 -0
- agent_core/documents/loaders.py +79 -0
- agent_core/documents/splitters.py +94 -0
- agent_core/documents/types.py +197 -0
- agent_core/errors.py +81 -0
- agent_core/guardrails/__init__.py +19 -0
- agent_core/guardrails/core.py +186 -0
- agent_core/hitl/__init__.py +15 -0
- agent_core/hitl/core.py +517 -0
- agent_core/mcp/__init__.py +14 -0
- agent_core/mcp/client.py +212 -0
- agent_core/mcp/errors.py +26 -0
- agent_core/middleware/__init__.py +26 -0
- agent_core/middleware/base.py +283 -0
- agent_core/model/__init__.py +73 -0
- agent_core/model/catalog.py +73 -0
- agent_core/model/core.py +179 -0
- agent_core/model/governance.py +317 -0
- agent_core/model/providers/__init__.py +43 -0
- agent_core/model/providers/anthropic.py +294 -0
- agent_core/model/providers/common.py +28 -0
- agent_core/model/providers/factory.py +107 -0
- agent_core/model/providers/gemini.py +273 -0
- agent_core/model/providers/ollama.py +80 -0
- agent_core/model/providers/openai_compatible.py +526 -0
- agent_core/model/structured.py +200 -0
- agent_core/observability/__init__.py +21 -0
- agent_core/observability/core.py +311 -0
- agent_core/ports/__init__.py +31 -0
- agent_core/ports/memory.py +130 -0
- agent_core/ports/model_selection.py +64 -0
- agent_core/ports/storage.py +114 -0
- agent_core/prompts/__init__.py +19 -0
- agent_core/prompts/registry.py +236 -0
- agent_core/prompts/templates.py +166 -0
- agent_core/protocol/__init__.py +31 -0
- agent_core/protocol/capabilities.py +49 -0
- agent_core/protocol/messages.py +100 -0
- agent_core/protocol/runtime.py +293 -0
- agent_core/py.typed +1 -0
- agent_core/retrieval/__init__.py +40 -0
- agent_core/retrieval/_common.py +121 -0
- agent_core/retrieval/chroma.py +269 -0
- agent_core/retrieval/core.py +63 -0
- agent_core/retrieval/factory.py +72 -0
- agent_core/retrieval/memory.py +232 -0
- agent_core/retrieval/pgvector.py +239 -0
- agent_core/retrieval/types.py +95 -0
- agent_core/runtime/__init__.py +28 -0
- agent_core/runtime/compat.py +98 -0
- agent_core/runtime/react.py +888 -0
- agent_core/runtime/service.py +557 -0
- agent_core/runtime/store.py +55 -0
- agent_core/serialization/__init__.py +21 -0
- agent_core/serialization/registry.py +318 -0
- agent_core/skills/__init__.py +24 -0
- agent_core/skills/errors.py +27 -0
- agent_core/skills/loader.py +258 -0
- agent_core/skills/registry.py +205 -0
- agent_core/skills/types.py +56 -0
- agent_core/storage/__init__.py +107 -0
- agent_core/storage/_sqlite_utils.py +92 -0
- agent_core/storage/context.py +382 -0
- agent_core/storage/lease.py +435 -0
- agent_core/storage/memory.py +604 -0
- agent_core/storage/model_selection.py +360 -0
- agent_core/storage/runtime.py +899 -0
- agent_core/storage/session.py +904 -0
- agent_core/storage/workflow.py +411 -0
- agent_core/tools/__init__.py +5 -0
- agent_core/tools/executor.py +306 -0
- agent_core/workflow/__init__.py +25 -0
- agent_core/workflow/durable.py +175 -0
- agent_core/workflow/state_machine.py +427 -0
- zero_agent_core-0.2.0.dist-info/METADATA +237 -0
- zero_agent_core-0.2.0.dist-info/RECORD +104 -0
- zero_agent_core-0.2.0.dist-info/WHEEL +4 -0
- zero_agent_core-0.2.0.dist-info/entry_points.txt +3 -0
- zero_agent_core-0.2.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""适配器 2:Anthropic Claude。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
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 known_context_window, logger
|
|
12
|
+
from agent_core.protocol.messages import Message, assistant_message
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AnthropicProvider:
|
|
16
|
+
"""Anthropic Claude 提供商。
|
|
17
|
+
|
|
18
|
+
需要安装:uv add anthropic
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
*,
|
|
24
|
+
api_key: str,
|
|
25
|
+
model: str = "claude-3-5-sonnet-20241022",
|
|
26
|
+
temperature: float = 0.0,
|
|
27
|
+
max_tokens: int = 4096,
|
|
28
|
+
context_window_tokens: int | None = None,
|
|
29
|
+
) -> None:
|
|
30
|
+
try:
|
|
31
|
+
import anthropic
|
|
32
|
+
|
|
33
|
+
self._client = anthropic.AsyncAnthropic(api_key=api_key)
|
|
34
|
+
except ImportError as exc:
|
|
35
|
+
raise ImportError("AnthropicProvider 需要 anthropic 包,请执行:uv add anthropic") from exc
|
|
36
|
+
|
|
37
|
+
self.model = model
|
|
38
|
+
self._temperature = temperature
|
|
39
|
+
self._max_tokens = max_tokens
|
|
40
|
+
self._context_window_tokens = context_window_tokens
|
|
41
|
+
self.provider_name = "anthropic"
|
|
42
|
+
|
|
43
|
+
# ──────────────────────────────────────────────
|
|
44
|
+
# 上下文使用
|
|
45
|
+
# ──────────────────────────────────────────────
|
|
46
|
+
async def context_usage(
|
|
47
|
+
self,
|
|
48
|
+
messages: list[Message],
|
|
49
|
+
*,
|
|
50
|
+
tools: list[dict[str, Any]] | None = None,
|
|
51
|
+
) -> ContextUsage:
|
|
52
|
+
system_prompt, anthropic_msgs = self._to_anthropic_messages(messages)
|
|
53
|
+
try:
|
|
54
|
+
count_tokens = self._client.messages.count_tokens
|
|
55
|
+
kwargs: dict[str, Any] = {
|
|
56
|
+
"model": self.model,
|
|
57
|
+
"messages": anthropic_msgs,
|
|
58
|
+
}
|
|
59
|
+
if system_prompt:
|
|
60
|
+
kwargs["system"] = system_prompt
|
|
61
|
+
if tools:
|
|
62
|
+
kwargs["tools"] = self._to_anthropic_tools(tools)
|
|
63
|
+
response = await count_tokens(**kwargs)
|
|
64
|
+
input_tokens = getattr(response, "input_tokens", None)
|
|
65
|
+
if isinstance(input_tokens, int):
|
|
66
|
+
return ContextUsage(
|
|
67
|
+
input_tokens=input_tokens,
|
|
68
|
+
context_window_tokens=(self._context_window_tokens or known_context_window(self.model)),
|
|
69
|
+
exact=True,
|
|
70
|
+
source="provider_tokenizer",
|
|
71
|
+
)
|
|
72
|
+
except Exception as exc:
|
|
73
|
+
logger.debug("Anthropic tokenizer 不可用:%s", exc)
|
|
74
|
+
return ContextUsage(
|
|
75
|
+
input_tokens=None,
|
|
76
|
+
context_window_tokens=self._context_window_tokens or known_context_window(self.model),
|
|
77
|
+
exact=False,
|
|
78
|
+
source="unavailable",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# ──────────────────────────────────────────────
|
|
82
|
+
# 转换为 Anthropic API 格式
|
|
83
|
+
# ──────────────────────────────────────────────
|
|
84
|
+
def _to_anthropic_messages(self, messages: list[Message]) -> tuple[str | None, list[dict[str, Any]]]:
|
|
85
|
+
"""将 Message 列表转为 Anthropic API 格式。
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
(system_prompt, messages_list)
|
|
89
|
+
"""
|
|
90
|
+
system_parts: list[str] = []
|
|
91
|
+
anthropic_msgs: list[dict[str, Any]] = []
|
|
92
|
+
|
|
93
|
+
def append_user_content(content: str | list[dict[str, Any]]) -> None:
|
|
94
|
+
"""合并连续 user 消息,满足 Anthropic 的消息交替约束。"""
|
|
95
|
+
if anthropic_msgs and anthropic_msgs[-1]["role"] == "user":
|
|
96
|
+
existing = anthropic_msgs[-1]["content"]
|
|
97
|
+
if isinstance(existing, str):
|
|
98
|
+
existing = [{"type": "text", "text": existing}]
|
|
99
|
+
anthropic_msgs[-1]["content"] = existing
|
|
100
|
+
if isinstance(content, str):
|
|
101
|
+
existing.append({"type": "text", "text": content})
|
|
102
|
+
else:
|
|
103
|
+
existing.extend(content)
|
|
104
|
+
return
|
|
105
|
+
anthropic_msgs.append({"role": "user", "content": content})
|
|
106
|
+
|
|
107
|
+
for msg in messages:
|
|
108
|
+
if msg.role == "system":
|
|
109
|
+
if msg.content:
|
|
110
|
+
system_parts.append(msg.content)
|
|
111
|
+
elif msg.role == "user":
|
|
112
|
+
append_user_content(msg.content)
|
|
113
|
+
elif msg.role == "assistant":
|
|
114
|
+
content: list[dict[str, Any]] = []
|
|
115
|
+
if msg.content:
|
|
116
|
+
content.append({"type": "text", "text": msg.content})
|
|
117
|
+
if msg.tool_calls:
|
|
118
|
+
for tc in msg.tool_calls:
|
|
119
|
+
content.append(
|
|
120
|
+
{
|
|
121
|
+
"type": "tool_use",
|
|
122
|
+
"id": tc["id"],
|
|
123
|
+
"name": tc["name"],
|
|
124
|
+
"input": tc.get("args", {}),
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
if content:
|
|
128
|
+
anthropic_msgs.append({"role": "assistant", "content": content})
|
|
129
|
+
elif msg.role == "tool":
|
|
130
|
+
append_user_content(
|
|
131
|
+
[
|
|
132
|
+
{
|
|
133
|
+
"type": "tool_result",
|
|
134
|
+
"tool_use_id": msg.tool_call_id,
|
|
135
|
+
"content": msg.content,
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return "\n\n".join(system_parts) or None, anthropic_msgs
|
|
141
|
+
|
|
142
|
+
# ──────────────────────────────────────────────
|
|
143
|
+
# 转换为 Anthropic API 格式工具定义
|
|
144
|
+
# ──────────────────────────────────────────────
|
|
145
|
+
def _to_anthropic_tools(self, tools: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
146
|
+
"""将 OpenAI 格式工具定义转为 Anthropic 格式。"""
|
|
147
|
+
result = []
|
|
148
|
+
for t in tools:
|
|
149
|
+
fn = t.get("function", t)
|
|
150
|
+
result.append(
|
|
151
|
+
{
|
|
152
|
+
"name": fn["name"],
|
|
153
|
+
"description": fn.get("description", ""),
|
|
154
|
+
"input_schema": fn.get("parameters", {"type": "object", "properties": {}}),
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
return result
|
|
158
|
+
|
|
159
|
+
# ──────────────────────────────────────────────
|
|
160
|
+
# 调用 Anthropic API 进行 chat completion
|
|
161
|
+
# ──────────────────────────────────────────────
|
|
162
|
+
async def chat(
|
|
163
|
+
self,
|
|
164
|
+
messages: list[Message],
|
|
165
|
+
*,
|
|
166
|
+
tools: list[dict[str, Any]] | None = None,
|
|
167
|
+
temperature: float | None = None,
|
|
168
|
+
max_tokens: int | None = None,
|
|
169
|
+
) -> Message:
|
|
170
|
+
system_prompt, anthropic_msgs = self._to_anthropic_messages(messages)
|
|
171
|
+
|
|
172
|
+
kwargs: dict[str, Any] = {
|
|
173
|
+
"model": self.model,
|
|
174
|
+
"max_tokens": max_tokens or self._max_tokens,
|
|
175
|
+
"temperature": temperature if temperature is not None else self._temperature,
|
|
176
|
+
"messages": anthropic_msgs,
|
|
177
|
+
}
|
|
178
|
+
if system_prompt:
|
|
179
|
+
kwargs["system"] = system_prompt
|
|
180
|
+
if tools:
|
|
181
|
+
kwargs["tools"] = self._to_anthropic_tools(tools)
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
response = await self._client.messages.create(**kwargs)
|
|
185
|
+
except Exception as exc:
|
|
186
|
+
raise ModelInvocationError(f"Anthropic API call failed: {exc}") from exc
|
|
187
|
+
|
|
188
|
+
# 解析响应
|
|
189
|
+
content_text = ""
|
|
190
|
+
tool_calls: list[dict[str, Any]] = []
|
|
191
|
+
|
|
192
|
+
for block in response.content:
|
|
193
|
+
if block.type == "text":
|
|
194
|
+
content_text += block.text
|
|
195
|
+
elif block.type == "tool_use":
|
|
196
|
+
tool_calls.append(
|
|
197
|
+
{
|
|
198
|
+
"id": block.id,
|
|
199
|
+
"type": "function",
|
|
200
|
+
"name": block.name,
|
|
201
|
+
"args": block.input,
|
|
202
|
+
}
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
logger.debug(
|
|
206
|
+
"Anthropic response: content_len=%d, tool_calls=%d, stop=%s",
|
|
207
|
+
len(content_text),
|
|
208
|
+
len(tool_calls),
|
|
209
|
+
response.stop_reason,
|
|
210
|
+
)
|
|
211
|
+
return assistant_message(content_text, tool_calls=tool_calls)
|
|
212
|
+
|
|
213
|
+
# ──────────────────────────────────────────────
|
|
214
|
+
# 调用 Anthropic API 进行流式 chat completion
|
|
215
|
+
# ──────────────────────────────────────────────
|
|
216
|
+
async def stream_chat(
|
|
217
|
+
self,
|
|
218
|
+
messages: list[Message],
|
|
219
|
+
*,
|
|
220
|
+
tools: list[dict[str, Any]] | None = None,
|
|
221
|
+
temperature: float | None = None,
|
|
222
|
+
max_tokens: int | None = None,
|
|
223
|
+
) -> AsyncIterator[StreamChunk]:
|
|
224
|
+
system_prompt, anthropic_msgs = self._to_anthropic_messages(messages)
|
|
225
|
+
|
|
226
|
+
kwargs: dict[str, Any] = {
|
|
227
|
+
"model": self.model,
|
|
228
|
+
"max_tokens": max_tokens or self._max_tokens,
|
|
229
|
+
"temperature": temperature if temperature is not None else self._temperature,
|
|
230
|
+
"messages": anthropic_msgs,
|
|
231
|
+
}
|
|
232
|
+
if system_prompt:
|
|
233
|
+
kwargs["system"] = system_prompt
|
|
234
|
+
if tools:
|
|
235
|
+
kwargs["tools"] = self._to_anthropic_tools(tools)
|
|
236
|
+
|
|
237
|
+
tool_call_accum: dict[str, dict[str, Any]] = {}
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
async with self._client.messages.stream(**kwargs) as stream:
|
|
241
|
+
async for event in stream:
|
|
242
|
+
# 不依赖 SDK 私有事件类名,兼容 anthropic 不同版本的事件对象。
|
|
243
|
+
if hasattr(event, "content_block"):
|
|
244
|
+
block = event.content_block
|
|
245
|
+
if getattr(block, "type", None) == "tool_use":
|
|
246
|
+
block_id = str(getattr(event, "index", len(tool_call_accum)))
|
|
247
|
+
tool_call_accum[block_id] = {
|
|
248
|
+
"id": getattr(block, "id", ""),
|
|
249
|
+
"name": getattr(block, "name", ""),
|
|
250
|
+
"args_json": "",
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if hasattr(event, "delta"):
|
|
254
|
+
delta = event.delta
|
|
255
|
+
# 取值经 getattr 而非属性直访:delta 在不同 SDK 版本下的
|
|
256
|
+
# 类型联合不同(mypy 按 union-attr 检查),鸭子类型取值
|
|
257
|
+
# 既保持跨版本兼容,也让类型检查可静态通过。
|
|
258
|
+
delta_text = getattr(delta, "text", None)
|
|
259
|
+
partial_json = getattr(delta, "partial_json", None)
|
|
260
|
+
if delta_text:
|
|
261
|
+
yield StreamChunk(text=delta_text)
|
|
262
|
+
elif partial_json is not None:
|
|
263
|
+
# 工具调用参数增量
|
|
264
|
+
block_id = str(getattr(event, "index", len(tool_call_accum)))
|
|
265
|
+
if block_id not in tool_call_accum:
|
|
266
|
+
tool_call_accum[block_id] = {"args_json": ""}
|
|
267
|
+
tool_call_accum[block_id]["args_json"] += partial_json
|
|
268
|
+
|
|
269
|
+
except Exception as exc:
|
|
270
|
+
raise ModelInvocationError(f"Anthropic stream failed: {exc}") from exc
|
|
271
|
+
|
|
272
|
+
# 流结束后发送工具调用
|
|
273
|
+
if tool_call_accum:
|
|
274
|
+
tool_calls = []
|
|
275
|
+
for acc in tool_call_accum.values():
|
|
276
|
+
try:
|
|
277
|
+
args = json.loads(acc.get("args_json", "{}") or "{}")
|
|
278
|
+
except json.JSONDecodeError as exc:
|
|
279
|
+
raise ModelInvocationError(
|
|
280
|
+
f"Anthropic 流式工具 {acc.get('name')!r} 参数不是有效 JSON:{exc}"
|
|
281
|
+
) from exc
|
|
282
|
+
if not isinstance(args, dict):
|
|
283
|
+
raise ModelInvocationError(f"Anthropic 流式工具 {acc.get('name')!r} 参数必须是 JSON 对象")
|
|
284
|
+
tool_calls.append(
|
|
285
|
+
{
|
|
286
|
+
"id": acc.get("id", ""),
|
|
287
|
+
"type": "function",
|
|
288
|
+
"name": acc.get("name", ""),
|
|
289
|
+
"args": args,
|
|
290
|
+
}
|
|
291
|
+
)
|
|
292
|
+
yield StreamChunk(tool_calls=tool_calls, is_tool_call=True)
|
|
293
|
+
else:
|
|
294
|
+
yield StreamChunk()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Provider 共享辅助:已公开模型规格与统一 logger。
|
|
2
|
+
|
|
3
|
+
具体厂商适配器见 ``openai_compatible``、``anthropic``、``gemini``、``ollama``;
|
|
4
|
+
工厂与注册逻辑见 ``factory``。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
# 保留拆分前单文件模块的 logger 名,日志过滤行为保持不变。
|
|
12
|
+
logger = logging.getLogger("agent_core.model.providers")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# ── token / 已公开模型规格辅助函数 ────────────────────────────────
|
|
16
|
+
def known_context_window(model: str) -> int | None:
|
|
17
|
+
"""已公开模型规格;未知模型必须由 provider metadata 或配置提供。"""
|
|
18
|
+
normalized = model.lower()
|
|
19
|
+
known_windows = {
|
|
20
|
+
"gpt-4o": 128_000,
|
|
21
|
+
"gpt-4o-mini": 128_000,
|
|
22
|
+
"gpt-4.1": 1_047_576,
|
|
23
|
+
"gpt-4.1-mini": 1_047_576,
|
|
24
|
+
"gpt-4.1-nano": 1_047_576,
|
|
25
|
+
"qwen3.7-plus": 1_000_000,
|
|
26
|
+
"qwen3-max": 262_144,
|
|
27
|
+
}
|
|
28
|
+
return known_windows.get(normalized)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""提供商构造器与注册:统一模型工厂入口。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from agent_core.model.core import (
|
|
8
|
+
ModelAdapter,
|
|
9
|
+
create_model_provider,
|
|
10
|
+
default_model_provider_registry,
|
|
11
|
+
)
|
|
12
|
+
from agent_core.model.providers.anthropic import AnthropicProvider
|
|
13
|
+
from agent_core.model.providers.common import logger
|
|
14
|
+
from agent_core.model.providers.gemini import GeminiProvider
|
|
15
|
+
from agent_core.model.providers.ollama import OllamaProvider
|
|
16
|
+
from agent_core.model.providers.openai_compatible import OpenAIProvider
|
|
17
|
+
|
|
18
|
+
# 模型提供商注册表和具体适配器都属于 agent-core;第三方 SDK 只在实例化对应
|
|
19
|
+
# 提供商时延迟导入,未使用的模型不会影响 Core 的基础导入。
|
|
20
|
+
MODEL_PROVIDER_REGISTRY = default_model_provider_registry
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _model_name(config: Any, model: str | None) -> str:
|
|
24
|
+
"""读取统一模型名称,并保留旧配置对象的访问方式。"""
|
|
25
|
+
return model or str(getattr(config, "model_name", ""))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _build_openai(config: Any, *, model: str | None = None, **_: Any) -> ModelAdapter:
|
|
29
|
+
"""构造 OpenAI 及其兼容接口适配器。"""
|
|
30
|
+
api_key = getattr(config, "openai_api_key", None)
|
|
31
|
+
if not api_key:
|
|
32
|
+
raise ValueError("使用 OpenAI 兼容接口需要配置 AGENT_OPENAI_API_KEY")
|
|
33
|
+
model_name = _model_name(config, model)
|
|
34
|
+
logger.info("LLM Provider: OpenAI-compatible (%s)", model_name)
|
|
35
|
+
return OpenAIProvider(
|
|
36
|
+
api_key=api_key,
|
|
37
|
+
base_url=getattr(config, "openai_base_url", "https://api.openai.com/v1"),
|
|
38
|
+
model=model_name,
|
|
39
|
+
temperature=getattr(config, "temperature", 0.0),
|
|
40
|
+
max_tokens=getattr(config, "max_tokens", 4096),
|
|
41
|
+
context_window_tokens=getattr(config, "context_window_tokens", None),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _build_anthropic(config: Any, *, model: str | None = None, **_: Any) -> ModelAdapter:
|
|
46
|
+
"""构造 Anthropic 适配器。"""
|
|
47
|
+
api_key = getattr(config, "anthropic_api_key", None)
|
|
48
|
+
if not api_key:
|
|
49
|
+
raise ValueError("使用 Anthropic 需要配置 AGENT_ANTHROPIC_API_KEY")
|
|
50
|
+
model_name = _model_name(config, model)
|
|
51
|
+
logger.info("LLM Provider: Anthropic (%s)", model_name)
|
|
52
|
+
return AnthropicProvider(
|
|
53
|
+
api_key=api_key,
|
|
54
|
+
model=model_name,
|
|
55
|
+
temperature=getattr(config, "temperature", 0.0),
|
|
56
|
+
max_tokens=getattr(config, "max_tokens", 4096),
|
|
57
|
+
context_window_tokens=getattr(config, "context_window_tokens", None),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _build_ollama(config: Any, *, model: str | None = None, **_: Any) -> ModelAdapter:
|
|
62
|
+
"""构造 Ollama 本地模型适配器。"""
|
|
63
|
+
model_name = _model_name(config, model)
|
|
64
|
+
base_url = getattr(config, "ollama_base_url", "http://localhost:11434/v1")
|
|
65
|
+
logger.info("LLM Provider: Ollama (%s @ %s)", model_name, base_url)
|
|
66
|
+
return OllamaProvider(
|
|
67
|
+
model=model_name,
|
|
68
|
+
base_url=base_url,
|
|
69
|
+
temperature=getattr(config, "temperature", 0.0),
|
|
70
|
+
max_tokens=getattr(config, "max_tokens", 4096),
|
|
71
|
+
context_window_tokens=getattr(config, "context_window_tokens", None),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _build_gemini(config: Any, *, model: str | None = None, **_: Any) -> ModelAdapter:
|
|
76
|
+
"""构造 Google Gemini 适配器。"""
|
|
77
|
+
api_key = getattr(config, "gemini_api_key", None)
|
|
78
|
+
if not api_key:
|
|
79
|
+
raise ValueError("使用 Gemini 需要配置 AGENT_GEMINI_API_KEY")
|
|
80
|
+
model_name = _model_name(config, model)
|
|
81
|
+
logger.info("LLM Provider: Gemini (%s)", model_name)
|
|
82
|
+
return GeminiProvider(
|
|
83
|
+
api_key=api_key,
|
|
84
|
+
model=model_name,
|
|
85
|
+
temperature=getattr(config, "temperature", 0.0),
|
|
86
|
+
max_tokens=getattr(config, "max_tokens", 4096),
|
|
87
|
+
context_window_tokens=getattr(config, "context_window_tokens", None),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
MODEL_PROVIDER_REGISTRY.register("openai", _build_openai)
|
|
92
|
+
MODEL_PROVIDER_REGISTRY.register("anthropic", _build_anthropic)
|
|
93
|
+
MODEL_PROVIDER_REGISTRY.register("ollama", _build_ollama)
|
|
94
|
+
MODEL_PROVIDER_REGISTRY.register("gemini", _build_gemini)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def create_llm_provider(config: Any, *, model: str | None = None) -> ModelAdapter:
|
|
98
|
+
"""兼容旧调用方的模型工厂入口。
|
|
99
|
+
|
|
100
|
+
选择、注册和具体 SDK 适配器都由 ``agent_core`` 提供。
|
|
101
|
+
旧代码仍可继续调用 ``create_llm_provider(config, model=...)``。
|
|
102
|
+
"""
|
|
103
|
+
return create_model_provider(
|
|
104
|
+
config,
|
|
105
|
+
provider=getattr(config, "llm_provider", "openai"),
|
|
106
|
+
model=model,
|
|
107
|
+
)
|