slimx 0.5.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.
slimx/__init__.py ADDED
@@ -0,0 +1,99 @@
1
+ """SlimX — a slim, intuitive, lightweight library for calling LLMs.
2
+
3
+ This top-level module keeps imports *lazy* to:
4
+ - speed up imports
5
+ - avoid provider bootstrapping side-effects at import time
6
+ - prevent circular imports across `high`, `low`, and `providers`
7
+
8
+ You can still write:
9
+
10
+ from slimx import llm, tool, Message
11
+
12
+ The symbols resolve on first access.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from importlib import import_module
18
+ from typing import Any, TYPE_CHECKING
19
+
20
+ __version__ = "0.5.0"
21
+
22
+ _LAZY: dict[str, tuple[str, str]] = {
23
+ # High-level
24
+ "llm": ("slimx.high.api", "llm"),
25
+ "allm": ("slimx.high.api", "allm"),
26
+ "Model": ("slimx.high.api", "Model"),
27
+ "AsyncModel": ("slimx.high.api", "AsyncModel"),
28
+
29
+ # Tooling
30
+ "tool": ("slimx.tooling", "tool"),
31
+ "ToolSpec": ("slimx.tooling", "ToolSpec"),
32
+
33
+ # Messages & core types
34
+ "Message": ("slimx.messages", "Message"),
35
+ "Result": ("slimx.types", "Result"),
36
+ "StreamEvent": ("slimx.types", "StreamEvent"),
37
+ "Usage": ("slimx.types", "Usage"),
38
+ "ToolCall": ("slimx.types", "ToolCall"),
39
+
40
+ # Low-level
41
+ "Client": ("slimx.low.client", "Client"),
42
+ "ChatRequest": ("slimx.low.types", "ChatRequest"),
43
+
44
+ # Providers
45
+ "get_provider": ("slimx.providers.registry", "get_provider"),
46
+ "list_providers": ("slimx.providers.registry", "list_providers"),
47
+ }
48
+
49
+ __all__ = [
50
+ # High-level
51
+ "llm",
52
+ "allm",
53
+ "Model",
54
+ "AsyncModel",
55
+
56
+ # Tooling
57
+ "tool",
58
+ "ToolSpec",
59
+
60
+ # Messages & core types
61
+ "Message",
62
+ "Result",
63
+ "StreamEvent",
64
+ "Usage",
65
+ "ToolCall",
66
+
67
+ # Low-level
68
+ "Client",
69
+ "ChatRequest",
70
+
71
+ # Providers
72
+ "get_provider",
73
+ "list_providers",
74
+
75
+ "__version__",
76
+ ]
77
+
78
+
79
+ if TYPE_CHECKING:
80
+ # These imports are for type checkers only; runtime is lazy.
81
+ from slimx.high.api import AsyncModel, Model, allm, llm
82
+ from slimx.low.client import Client
83
+ from slimx.low.types import ChatRequest
84
+ from slimx.messages import Message
85
+ from slimx.providers.registry import get_provider, list_providers
86
+ from slimx.tooling import ToolSpec, tool
87
+ from slimx.types import Result, StreamEvent, ToolCall, Usage
88
+
89
+
90
+ def __getattr__(name: str) -> Any:
91
+ if name in _LAZY:
92
+ module_name, attr = _LAZY[name]
93
+ mod = import_module(module_name)
94
+ return getattr(mod, attr)
95
+ raise AttributeError(f"module 'slimx' has no attribute {name!r}")
96
+
97
+
98
+ def __dir__() -> list[str]:
99
+ return sorted(list(globals().keys()) + list(_LAZY.keys()))
slimx/errors.py ADDED
@@ -0,0 +1,7 @@
1
+ class SlimXError(Exception): ...
2
+ class ProviderError(SlimXError): ...
3
+ class ProviderAuthError(ProviderError): ...
4
+ class ProviderRateLimitError(ProviderError): ...
5
+ class ProviderTimeoutError(ProviderError): ...
6
+ class ToolExecutionError(SlimXError): ...
7
+ class SchemaError(SlimXError): ...
slimx/high/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ from .api import llm, allm, Model, AsyncModel
2
+ __all__=['llm','allm','Model','AsyncModel']
slimx/high/api.py ADDED
@@ -0,0 +1,148 @@
1
+ from typing import Any, Dict, Iterable, Optional, Sequence
2
+
3
+ from ..messages import Message
4
+ from ..types import Result, StreamEvent
5
+ from ..schema import parse_json, schema_for, coerce_dataclass
6
+ from ..tooling import ToolSpec
7
+ from ..providers import get_provider
8
+ from ..low import Client, ChatRequest
9
+
10
+
11
+ def _parse_model(model: str):
12
+ if ":" in model:
13
+ p, m = model.split(":", 1)
14
+ return p.strip(), m.strip()
15
+ return "openai", model.strip()
16
+
17
+
18
+ class Model:
19
+ def __init__(
20
+ self,
21
+ model: str,
22
+ *,
23
+ temperature: Optional[float] = None,
24
+ max_tokens: Optional[int] = None,
25
+ tools: Optional[Sequence[ToolSpec]] = None,
26
+ tool_runtime: str = "none",
27
+ timeout: Optional[float] = None,
28
+ retries: int = 2,
29
+ provider_kwargs: Optional[Dict[str, Any]] = None,
30
+ ):
31
+ provider_name, model_name = _parse_model(model)
32
+ provider = get_provider(provider_name, async_mode=False, **(provider_kwargs or {}))
33
+ self._client = Client(provider, timeout=timeout, retries=retries)
34
+ self._model = model_name
35
+ self._temperature = temperature
36
+ self._max_tokens = max_tokens
37
+ self._tools = list(tools or [])
38
+ self._tool_runtime = tool_runtime
39
+
40
+ def __call__(self, prompt: str, **overrides: Any) -> Result:
41
+ req = ChatRequest(
42
+ model=self._model,
43
+ messages=[Message.user(prompt)],
44
+ temperature=overrides.get("temperature", self._temperature),
45
+ max_tokens=overrides.get("max_tokens", self._max_tokens),
46
+ )
47
+ return self._client.chat(req, tools=self._tools, tool_runtime=self._tool_runtime)
48
+
49
+ def stream(self, prompt: str, **overrides: Any) -> Iterable[StreamEvent]:
50
+ req = ChatRequest(
51
+ model=self._model,
52
+ messages=[Message.user(prompt)],
53
+ temperature=overrides.get("temperature", self._temperature),
54
+ max_tokens=overrides.get("max_tokens", self._max_tokens),
55
+ )
56
+ return self._client.stream(req, tools=self._tools)
57
+
58
+ def json(self, prompt: str, *, schema: Any, **overrides: Any) -> Result:
59
+ if isinstance(schema, dict):
60
+ schema_dict = schema
61
+ schema_type = None
62
+ else:
63
+ schema_type = schema
64
+ schema_dict = schema_for(schema)
65
+
66
+ sys = "Return ONLY valid JSON (no markdown). Match this JSON Schema exactly: " + str(schema_dict)
67
+ req = ChatRequest(
68
+ model=self._model,
69
+ messages=[Message.system(sys), Message.user(prompt)],
70
+ temperature=overrides.get("temperature", self._temperature),
71
+ max_tokens=overrides.get("max_tokens", self._max_tokens),
72
+ response_format="json_object",
73
+ )
74
+ res = self._client.chat(req, tools=self._tools, tool_runtime=self._tool_runtime)
75
+ obj = parse_json(res.text)
76
+ res.data = coerce_dataclass(schema_type, obj) if schema_type else obj
77
+ return res
78
+
79
+
80
+ class AsyncModel:
81
+ def __init__(
82
+ self,
83
+ model: str,
84
+ *,
85
+ temperature: Optional[float] = None,
86
+ max_tokens: Optional[int] = None,
87
+ tools: Optional[Sequence[ToolSpec]] = None,
88
+ tool_runtime: str = "none",
89
+ timeout: Optional[float] = None,
90
+ retries: int = 2,
91
+ provider_kwargs: Optional[Dict[str, Any]] = None,
92
+ ):
93
+ provider_name, model_name = _parse_model(model)
94
+ provider = get_provider(provider_name, async_mode=True, **(provider_kwargs or {}))
95
+ self._client = Client(provider, timeout=timeout, retries=retries)
96
+ self._model = model_name
97
+ self._temperature = temperature
98
+ self._max_tokens = max_tokens
99
+ self._tools = list(tools or [])
100
+ self._tool_runtime = tool_runtime
101
+
102
+ async def __call__(self, prompt: str, **overrides: Any) -> Result:
103
+ req = ChatRequest(
104
+ model=self._model,
105
+ messages=[Message.user(prompt)],
106
+ temperature=overrides.get("temperature", self._temperature),
107
+ max_tokens=overrides.get("max_tokens", self._max_tokens),
108
+ )
109
+ return await self._client.achat(req, tools=self._tools, tool_runtime=self._tool_runtime)
110
+
111
+ async def astream(self, prompt: str, **overrides: Any):
112
+ req = ChatRequest(
113
+ model=self._model,
114
+ messages=[Message.user(prompt)],
115
+ temperature=overrides.get("temperature", self._temperature),
116
+ max_tokens=overrides.get("max_tokens", self._max_tokens),
117
+ )
118
+ async for ev in self._client.astream(req, tools=self._tools):
119
+ yield ev
120
+
121
+ async def json(self, prompt: str, *, schema: Any, **overrides: Any) -> Result:
122
+ if isinstance(schema, dict):
123
+ schema_dict = schema
124
+ schema_type = None
125
+ else:
126
+ schema_type = schema
127
+ schema_dict = schema_for(schema)
128
+
129
+ sys = "Return ONLY valid JSON (no markdown). Match this JSON Schema exactly: " + str(schema_dict)
130
+ req = ChatRequest(
131
+ model=self._model,
132
+ messages=[Message.system(sys), Message.user(prompt)],
133
+ temperature=overrides.get("temperature", self._temperature),
134
+ max_tokens=overrides.get("max_tokens", self._max_tokens),
135
+ response_format="json_object",
136
+ )
137
+ res = await self._client.achat(req, tools=self._tools, tool_runtime=self._tool_runtime)
138
+ obj = parse_json(res.text)
139
+ res.data = coerce_dataclass(schema_type, obj) if schema_type else obj
140
+ return res
141
+
142
+
143
+ def llm(model: str, **kwargs: Any) -> Model:
144
+ return Model(model, **kwargs)
145
+
146
+
147
+ def allm(model: str, **kwargs: Any) -> AsyncModel:
148
+ return AsyncModel(model, **kwargs)
slimx/low/__init__.py ADDED
@@ -0,0 +1,32 @@
1
+ """Low-level SlimX API.
2
+
3
+ This module is intentionally lazy to avoid circular imports.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from importlib import import_module
9
+ from typing import Any, TYPE_CHECKING
10
+
11
+ _LAZY: dict[str, tuple[str, str]] = {
12
+ "Client": ("slimx.low.client", "Client"),
13
+ "ChatRequest": ("slimx.low.types", "ChatRequest"),
14
+ }
15
+
16
+ __all__ = ["Client", "ChatRequest"]
17
+
18
+
19
+ if TYPE_CHECKING:
20
+ from .client import Client
21
+ from .types import ChatRequest
22
+
23
+
24
+ def __getattr__(name: str) -> Any:
25
+ if name in _LAZY:
26
+ module_name, attr = _LAZY[name]
27
+ return getattr(import_module(module_name), attr)
28
+ raise AttributeError(name)
29
+
30
+
31
+ def __dir__() -> list[str]:
32
+ return sorted(list(globals().keys()) + list(_LAZY.keys()))
slimx/low/client.py ADDED
@@ -0,0 +1,137 @@
1
+ import json
2
+ import time
3
+ from typing import Iterable, Optional, Sequence
4
+ from ..messages import Message
5
+ from ..types import Result, StreamEvent
6
+ from ..tooling import ToolSpec, execute_tool
7
+ from ..utils.retry import retry
8
+ from ..providers.base import Provider
9
+ from .types import ChatRequest
10
+
11
+ class Client:
12
+ def __init__(self, provider: Provider, *, timeout: Optional[float]=None, retries: int=2):
13
+ self.provider = provider
14
+ self.timeout = timeout
15
+ self.retries = retries
16
+ self.provider_name = getattr(provider, "name", "provider")
17
+
18
+ def chat(self, req: ChatRequest, *, tools: Sequence[ToolSpec]=(), tool_runtime: str="none", max_steps: int=6) -> Result:
19
+ tool_map = {t.name: t for t in tools}
20
+ started = time.perf_counter()
21
+
22
+ res = retry(lambda: self.provider.chat(req, tools=tools, timeout=self.timeout), retries=self.retries)
23
+
24
+ if tool_runtime != "auto" or not res.tool_calls or not tool_map:
25
+ self._attach_trace(res, req=req, started=started, steps=0)
26
+ return res
27
+
28
+ # Auto tool loop (best-effort cross-provider)
29
+ messages = list(req.messages)
30
+ steps = 0
31
+ while res.tool_calls and steps < max_steps:
32
+ steps += 1
33
+ messages.append(Message.assistant("", tool_calls=[_tool_call_to_provider_dict(tc) for tc in res.tool_calls]))
34
+ for tc in res.tool_calls:
35
+ spec = tool_map.get(tc.name)
36
+ if not spec:
37
+ continue
38
+ out = execute_tool(spec, tc.arguments)
39
+ messages.append(Message.tool(content=json.dumps(out), tool_call_id=tc.id or tc.name))
40
+
41
+ req = ChatRequest(
42
+ model=req.model,
43
+ messages=messages,
44
+ temperature=req.temperature,
45
+ max_tokens=req.max_tokens,
46
+ response_format=req.response_format,
47
+ extra=req.extra,
48
+ )
49
+ res = retry(lambda: self.provider.chat(req, tools=tools, timeout=self.timeout), retries=self.retries)
50
+ if not res.tool_calls:
51
+ break
52
+ self._attach_trace(res, req=req, started=started, steps=steps)
53
+ return res
54
+
55
+ def stream(self, req: ChatRequest, *, tools: Sequence[ToolSpec]=()) -> Iterable[StreamEvent]:
56
+ return self.provider.stream(req, tools=tools, timeout=self.timeout)
57
+
58
+ async def achat(self, req: ChatRequest, *, tools: Sequence[ToolSpec]=(), tool_runtime: str="none", max_steps: int=6) -> Result:
59
+ started = time.perf_counter()
60
+ # async retry
61
+ last = None
62
+ for i in range(self.retries + 1):
63
+ try:
64
+ res = await self.provider.achat(req, tools=tools, timeout=self.timeout)
65
+ break
66
+ except Exception as e:
67
+ last = e
68
+ if i >= self.retries:
69
+ raise
70
+ else:
71
+ raise last # type: ignore[misc]
72
+
73
+ tool_map = {t.name: t for t in tools}
74
+ if tool_runtime != "auto" or not res.tool_calls or not tool_map:
75
+ self._attach_trace(res, req=req, started=started, steps=0)
76
+ return res
77
+
78
+ messages = list(req.messages)
79
+ steps = 0
80
+ while res.tool_calls and steps < max_steps:
81
+ steps += 1
82
+ messages.append(Message.assistant("", tool_calls=[_tool_call_to_provider_dict(tc) for tc in res.tool_calls]))
83
+ for tc in res.tool_calls:
84
+ spec = tool_map.get(tc.name)
85
+ if not spec:
86
+ continue
87
+ out = execute_tool(spec, tc.arguments)
88
+ messages.append(Message.tool(content=json.dumps(out), tool_call_id=tc.id or tc.name))
89
+
90
+ req = ChatRequest(
91
+ model=req.model,
92
+ messages=messages,
93
+ temperature=req.temperature,
94
+ max_tokens=req.max_tokens,
95
+ response_format=req.response_format,
96
+ extra=req.extra,
97
+ )
98
+
99
+ last = None
100
+ for i in range(self.retries + 1):
101
+ try:
102
+ res = await self.provider.achat(req, tools=tools, timeout=self.timeout)
103
+ break
104
+ except Exception as e:
105
+ last = e
106
+ if i >= self.retries:
107
+ raise
108
+ else:
109
+ raise last # type: ignore[misc]
110
+
111
+ if not res.tool_calls:
112
+ break
113
+ self._attach_trace(res, req=req, started=started, steps=steps)
114
+ return res
115
+
116
+ async def astream(self, req: ChatRequest, *, tools: Sequence[ToolSpec]=()):
117
+ async for ev in self.provider.astream(req, tools=tools, timeout=self.timeout):
118
+ yield ev
119
+
120
+ def _attach_trace(self, res: Result, *, req: ChatRequest, started: float, steps: int) -> None:
121
+ res.trace.update({
122
+ "provider": self.provider_name,
123
+ "model": req.model,
124
+ "elapsed_ms": int((time.perf_counter() - started) * 1000),
125
+ "retries": self.retries,
126
+ "tool_steps": steps,
127
+ "tool_call_count": len(res.tool_calls or []),
128
+ "timeout": self.timeout,
129
+ })
130
+
131
+
132
+ def _tool_call_to_provider_dict(tc) -> dict:
133
+ return {
134
+ "id": tc.id or tc.name,
135
+ "type": "function",
136
+ "function": {"name": tc.name, "arguments": tc.arguments_json},
137
+ }
@@ -0,0 +1,6 @@
1
+ # Backwards-compatible import path for older code:
2
+ from ...providers.openai import OpenAIProvider
3
+ from ...providers.anthropic import AnthropicProvider
4
+ from ...providers.ollama import OllamaProvider
5
+
6
+ __all__ = ["OpenAIProvider", "AnthropicProvider", "OllamaProvider"]
slimx/low/types.py ADDED
@@ -0,0 +1,24 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any, Dict, List, Optional
3
+ from ..messages import Message
4
+
5
+ @dataclass
6
+ class ChatRequest:
7
+ model: str
8
+ messages: List[Message]
9
+ temperature: Optional[float] = None
10
+ max_tokens: Optional[int] = None
11
+ response_format: Optional[str] = None
12
+ extra: Optional[Dict[str, Any]] = None
13
+
14
+ def to_dict(self) -> Dict[str, Any]:
15
+ d: Dict[str, Any] = {"model": self.model, "messages": [m.to_dict() for m in self.messages]}
16
+ if self.temperature is not None:
17
+ d["temperature"] = self.temperature
18
+ if self.max_tokens is not None:
19
+ d["max_tokens"] = self.max_tokens
20
+ if self.response_format:
21
+ d["response_format"] = self.response_format
22
+ if self.extra:
23
+ d.update(self.extra)
24
+ return d
slimx/messages.py ADDED
@@ -0,0 +1,89 @@
1
+ # slimx/messages.py
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass, field
5
+ from typing import Any, Dict, List, Optional
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class Message:
10
+ """
11
+ SlimX canonical message.
12
+
13
+ - `role`: system | user | assistant | tool
14
+ - `content`: message content (text)
15
+ - `name`: optional participant name
16
+ - `tool_call_id`: provider tool call identifier (OpenAI/Anthropic)
17
+ - `tool_name`: required by some providers for tool result messages (e.g., Ollama)
18
+ - `metadata`: extension point for provider-specific or app-specific fields
19
+ """
20
+ role: str
21
+ content: str
22
+ name: Optional[str] = None
23
+
24
+ # Tool message fields
25
+ tool_call_id: Optional[str] = None
26
+ tool_name: Optional[str] = None
27
+ tool_calls: List[Dict[str, Any]] = field(default_factory=list)
28
+
29
+ metadata: Dict[str, Any] = field(default_factory=dict)
30
+
31
+ @staticmethod
32
+ def system(content: str, *, name: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> "Message":
33
+ return Message("system", content, name=name, metadata=metadata or {})
34
+
35
+ @staticmethod
36
+ def user(content: str, *, name: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> "Message":
37
+ return Message("user", content, name=name, metadata=metadata or {})
38
+
39
+ @staticmethod
40
+ def assistant(
41
+ content: str,
42
+ *,
43
+ name: Optional[str] = None,
44
+ tool_calls: Optional[List[Dict[str, Any]]] = None,
45
+ metadata: Optional[Dict[str, Any]] = None,
46
+ ) -> "Message":
47
+ return Message("assistant", content, name=name, tool_calls=tool_calls or [], metadata=metadata or {})
48
+
49
+ @staticmethod
50
+ def tool(
51
+ content: str,
52
+ *,
53
+ tool_call_id: Optional[str] = None,
54
+ tool_name: Optional[str] = None,
55
+ metadata: Optional[Dict[str, Any]] = None,
56
+ ) -> "Message":
57
+ # tool_call_id: OpenAI/Anthropic
58
+ # tool_name: Ollama and some adapters
59
+ return Message(
60
+ "tool",
61
+ content,
62
+ tool_call_id=tool_call_id,
63
+ tool_name=tool_name,
64
+ metadata=metadata or {},
65
+ )
66
+
67
+ def to_dict(self) -> Dict[str, Any]:
68
+ """
69
+ Best-effort provider-agnostic serialization.
70
+ Providers may ignore unknown keys; adapters/providers can override if needed.
71
+ """
72
+ d: Dict[str, Any] = {"role": self.role, "content": self.content}
73
+
74
+ if self.name:
75
+ d["name"] = self.name
76
+
77
+ # Tool-related fields (only set if present)
78
+ if self.tool_call_id:
79
+ d["tool_call_id"] = self.tool_call_id
80
+ if self.tool_name:
81
+ d["tool_name"] = self.tool_name
82
+ if self.tool_calls:
83
+ d["tool_calls"] = self.tool_calls
84
+
85
+ # Optional extra fields
86
+ if self.metadata:
87
+ d["metadata"] = self.metadata
88
+
89
+ return d
@@ -0,0 +1,13 @@
1
+ """Providers package.
2
+
3
+ Provider implementations live under `slimx.providers.*`.
4
+
5
+ Important: we do *not* import/register built-in providers at import time.
6
+ Defaults are registered lazily in `slimx.providers.registry` when you call
7
+ `list_providers()` or `get_provider()`.
8
+ """
9
+
10
+ from .base import ProviderCapabilities
11
+ from .registry import get_provider, list_providers, load_plugins, register
12
+
13
+ __all__ = ["register", "get_provider", "load_plugins", "list_providers", "ProviderCapabilities"]
@@ -0,0 +1,100 @@
1
+ """Built-in provider factories.
2
+
3
+ We keep provider imports inside factories so importing SlimX doesn't pull in
4
+ provider code unless you actually select/use a provider.
5
+
6
+ Factories accept keyword overrides where possible:
7
+ - OpenAI: api_key, base_url
8
+ - Anthropic: api_key, base_url, version
9
+ - Ollama: base_url
10
+ - Google: api_key, base_url
11
+
12
+ Async selection is controlled via `async_mode=True`.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import os
18
+ from typing import Any, Callable, Dict
19
+
20
+ from ..errors import ProviderAuthError
21
+ from .base import Provider
22
+
23
+ ProviderFactory = Callable[..., Provider]
24
+
25
+
26
+ def openai_factory(*, async_mode: bool = False, **kwargs: Any) -> Provider:
27
+ api_key = kwargs.pop("api_key", None) or os.environ.get("OPENAI_API_KEY")
28
+ base_url = kwargs.pop("base_url", None) or os.environ.get(
29
+ "OPENAI_BASE_URL", "https://api.openai.com/v1"
30
+ )
31
+
32
+ if async_mode:
33
+ from .openai_async import OpenAIAsyncProvider as P
34
+ else:
35
+ from .openai import OpenAIProvider as P
36
+
37
+ if not api_key:
38
+ raise ProviderAuthError("OPENAI_API_KEY is not set")
39
+
40
+ return P(api_key=api_key, base_url=base_url)
41
+
42
+
43
+ def anthropic_factory(*, async_mode: bool = False, **kwargs: Any) -> Provider:
44
+ api_key = kwargs.pop("api_key", None) or os.environ.get("ANTHROPIC_API_KEY")
45
+ base_url = kwargs.pop("base_url", None) or os.environ.get(
46
+ "ANTHROPIC_BASE_URL", "https://api.anthropic.com"
47
+ )
48
+ version = kwargs.pop("version", None) or os.environ.get("ANTHROPIC_VERSION", "2023-06-01")
49
+
50
+ if async_mode:
51
+ from .anthropic_async import AnthropicAsyncProvider as P
52
+ else:
53
+ from .anthropic import AnthropicProvider as P
54
+
55
+ if not api_key:
56
+ raise ProviderAuthError("ANTHROPIC_API_KEY is not set")
57
+
58
+ return P(api_key=api_key, base_url=base_url, version=version)
59
+
60
+
61
+ def ollama_factory(*, async_mode: bool = False, **kwargs: Any) -> Provider:
62
+ base_url = kwargs.pop("base_url", None) or os.environ.get(
63
+ "OLLAMA_BASE_URL", "http://localhost:11434"
64
+ )
65
+
66
+ if async_mode:
67
+ from .ollama_async import OllamaAsyncProvider as P
68
+ else:
69
+ from .ollama import OllamaProvider as P
70
+
71
+ return P(base_url=base_url)
72
+
73
+
74
+ def google_factory(*, async_mode: bool = False, **kwargs: Any) -> Provider:
75
+ api_key = (
76
+ kwargs.pop("api_key", None)
77
+ or os.environ.get("GOOGLE_API_KEY")
78
+ or os.environ.get("GEMINI_API_KEY")
79
+ )
80
+ base_url = kwargs.pop("base_url", None) or os.environ.get(
81
+ "GOOGLE_BASE_URL", "https://generativelanguage.googleapis.com/v1beta"
82
+ )
83
+
84
+ if async_mode:
85
+ from .google_async import GoogleAsyncProvider as P
86
+ else:
87
+ from .google import GoogleProvider as P
88
+
89
+ if not api_key:
90
+ raise ProviderAuthError("GOOGLE_API_KEY or GEMINI_API_KEY is not set")
91
+
92
+ return P(api_key=api_key, base_url=base_url)
93
+
94
+
95
+ DEFAULT_FACTORIES: Dict[str, ProviderFactory] = {
96
+ "openai": openai_factory,
97
+ "anthropic": anthropic_factory,
98
+ "ollama": ollama_factory,
99
+ "google": google_factory,
100
+ }