pi-ai-python 1.3.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.
- pi_agent/__init__.py +127 -0
- pi_agent/agent.py +617 -0
- pi_agent/agent_loop.py +820 -0
- pi_agent/proxy.py +269 -0
- pi_agent/py.typed +0 -0
- pi_agent/stream_fn.py +27 -0
- pi_agent/tools.py +327 -0
- pi_agent/types.py +394 -0
- pi_ai/__init__.py +263 -0
- pi_ai/abort.py +315 -0
- pi_ai/api/__init__.py +1 -0
- pi_ai/api/anthropic_messages.py +1782 -0
- pi_ai/api/azure_openai_config.py +138 -0
- pi_ai/api/azure_openai_responses.py +245 -0
- pi_ai/api/bedrock_converse_stream.py +1598 -0
- pi_ai/api/classifier_shared.py +211 -0
- pi_ai/api/cloudflare.py +36 -0
- pi_ai/api/cloudflare_workers_ai_system_one.py +80 -0
- pi_ai/api/constrained_sampling.py +279 -0
- pi_ai/api/github_copilot_headers.py +30 -0
- pi_ai/api/google_generative_ai.py +138 -0
- pi_ai/api/google_shared.py +927 -0
- pi_ai/api/google_vertex.py +234 -0
- pi_ai/api/lazy.py +156 -0
- pi_ai/api/llama_cpp_classify.py +449 -0
- pi_ai/api/mistral_conversations.py +756 -0
- pi_ai/api/openai_codex_responses.py +1545 -0
- pi_ai/api/openai_completions.py +1594 -0
- pi_ai/api/openai_decisions.py +210 -0
- pi_ai/api/openai_prompt_cache.py +19 -0
- pi_ai/api/openai_responses.py +418 -0
- pi_ai/api/openai_responses_shared.py +840 -0
- pi_ai/api/openai_sdk.py +204 -0
- pi_ai/api/openrouter_images.py +153 -0
- pi_ai/api/pi_messages.py +449 -0
- pi_ai/api/registry.py +103 -0
- pi_ai/api/simple_options.py +119 -0
- pi_ai/api/system_one_shared.py +148 -0
- pi_ai/api/transform_messages.py +211 -0
- pi_ai/api/typesafe_system_one.py +43 -0
- pi_ai/auth/__init__.py +1 -0
- pi_ai/auth/context.py +47 -0
- pi_ai/auth/credential_store.py +208 -0
- pi_ai/auth/helpers.py +123 -0
- pi_ai/auth/oauth/__init__.py +5 -0
- pi_ai/auth/oauth/_common.py +454 -0
- pi_ai/auth/oauth/anthropic.py +283 -0
- pi_ai/auth/oauth/callback_server.py +339 -0
- pi_ai/auth/oauth/device_code.py +127 -0
- pi_ai/auth/oauth/github_copilot.py +508 -0
- pi_ai/auth/oauth/kimi_coding.py +283 -0
- pi_ai/auth/oauth/load.py +64 -0
- pi_ai/auth/oauth/meta.py +238 -0
- pi_ai/auth/oauth/openai_chatgpt.py +321 -0
- pi_ai/auth/oauth/openai_codex.py +391 -0
- pi_ai/auth/oauth/openrouter.py +173 -0
- pi_ai/auth/oauth/pkce.py +23 -0
- pi_ai/auth/oauth/radius.py +311 -0
- pi_ai/auth/oauth/xai.py +220 -0
- pi_ai/auth/resolve.py +190 -0
- pi_ai/auth/types.py +292 -0
- pi_ai/cli.py +265 -0
- pi_ai/compat.py +651 -0
- pi_ai/env_api_keys.py +137 -0
- pi_ai/event_stream.py +329 -0
- pi_ai/models.py +1191 -0
- pi_ai/models_store.py +121 -0
- pi_ai/oauth.py +90 -0
- pi_ai/providers/__init__.py +1 -0
- pi_ai/providers/all.py +154 -0
- pi_ai/providers/amazon_bedrock.py +116 -0
- pi_ai/providers/ant_ling.py +23 -0
- pi_ai/providers/anthropic.py +105 -0
- pi_ai/providers/azure.py +103 -0
- pi_ai/providers/baseten.py +23 -0
- pi_ai/providers/catalog.py +167 -0
- pi_ai/providers/cerebras.py +23 -0
- pi_ai/providers/cloudflare_ai_gateway.py +30 -0
- pi_ai/providers/cloudflare_auth.py +112 -0
- pi_ai/providers/cloudflare_stream.py +84 -0
- pi_ai/providers/cloudflare_workers_ai.py +26 -0
- pi_ai/providers/data/amazon-bedrock.json +1 -0
- pi_ai/providers/data/ant-ling.json +1 -0
- pi_ai/providers/data/anthropic.json +1 -0
- pi_ai/providers/data/azure.json +1 -0
- pi_ai/providers/data/baseten.json +1 -0
- pi_ai/providers/data/cerebras.json +1 -0
- pi_ai/providers/data/cloudflare-ai-gateway.json +1 -0
- pi_ai/providers/data/cloudflare-workers-ai.json +1 -0
- pi_ai/providers/data/deepseek.json +1 -0
- pi_ai/providers/data/fireworks.json +1 -0
- pi_ai/providers/data/github-copilot.json +1 -0
- pi_ai/providers/data/google-vertex.json +1 -0
- pi_ai/providers/data/google.json +1 -0
- pi_ai/providers/data/groq.json +1 -0
- pi_ai/providers/data/huggingface.json +1 -0
- pi_ai/providers/data/kimi-coding.json +1 -0
- pi_ai/providers/data/manifest.json +1 -0
- pi_ai/providers/data/meta.json +1 -0
- pi_ai/providers/data/minimax-cn.json +1 -0
- pi_ai/providers/data/minimax.json +1 -0
- pi_ai/providers/data/mistral.json +1 -0
- pi_ai/providers/data/moonshotai-cn.json +1 -0
- pi_ai/providers/data/moonshotai.json +1 -0
- pi_ai/providers/data/nvidia.json +1 -0
- pi_ai/providers/data/openai-codex.json +1 -0
- pi_ai/providers/data/openai.json +1 -0
- pi_ai/providers/data/opencode-go.json +1 -0
- pi_ai/providers/data/opencode.json +1 -0
- pi_ai/providers/data/openrouter.json +1 -0
- pi_ai/providers/data/qwen-token-plan-cn.json +1 -0
- pi_ai/providers/data/qwen-token-plan-individual.json +1 -0
- pi_ai/providers/data/qwen-token-plan.json +1 -0
- pi_ai/providers/data/radius.json +1 -0
- pi_ai/providers/data/together.json +1 -0
- pi_ai/providers/data/typesafe.json +1 -0
- pi_ai/providers/data/vercel-ai-gateway.json +1 -0
- pi_ai/providers/data/xai.json +1 -0
- pi_ai/providers/data/xiaomi-token-plan-ams.json +1 -0
- pi_ai/providers/data/xiaomi-token-plan-cn.json +1 -0
- pi_ai/providers/data/xiaomi-token-plan-sgp.json +1 -0
- pi_ai/providers/data/xiaomi.json +1 -0
- pi_ai/providers/data/zai-coding-cn.json +1 -0
- pi_ai/providers/data/zai.json +1 -0
- pi_ai/providers/deepseek.py +23 -0
- pi_ai/providers/faux.py +680 -0
- pi_ai/providers/fireworks.py +26 -0
- pi_ai/providers/github_copilot.py +46 -0
- pi_ai/providers/google.py +23 -0
- pi_ai/providers/google_vertex.py +131 -0
- pi_ai/providers/groq.py +23 -0
- pi_ai/providers/huggingface.py +23 -0
- pi_ai/providers/kimi_coding.py +32 -0
- pi_ai/providers/meta.py +32 -0
- pi_ai/providers/minimax.py +23 -0
- pi_ai/providers/minimax_cn.py +23 -0
- pi_ai/providers/mistral.py +23 -0
- pi_ai/providers/moonshotai.py +23 -0
- pi_ai/providers/moonshotai_cn.py +23 -0
- pi_ai/providers/nvidia.py +23 -0
- pi_ai/providers/openai.py +44 -0
- pi_ai/providers/openai_codex.py +26 -0
- pi_ai/providers/opencode.py +36 -0
- pi_ai/providers/opencode_go.py +33 -0
- pi_ai/providers/opencode_headers.py +60 -0
- pi_ai/providers/openrouter.py +145 -0
- pi_ai/providers/openrouter_api.py +122 -0
- pi_ai/providers/qwen_token_plan.py +23 -0
- pi_ai/providers/qwen_token_plan_cn.py +23 -0
- pi_ai/providers/qwen_token_plan_individual.py +23 -0
- pi_ai/providers/radius.py +146 -0
- pi_ai/providers/radius_config.py +136 -0
- pi_ai/providers/together.py +23 -0
- pi_ai/providers/typesafe.py +22 -0
- pi_ai/providers/vercel_ai_gateway.py +25 -0
- pi_ai/providers/xai.py +32 -0
- pi_ai/providers/xiaomi.py +23 -0
- pi_ai/providers/xiaomi_token_plan_ams.py +23 -0
- pi_ai/providers/xiaomi_token_plan_cn.py +23 -0
- pi_ai/providers/xiaomi_token_plan_sgp.py +23 -0
- pi_ai/providers/zai.py +23 -0
- pi_ai/providers/zai_coding_cn.py +23 -0
- pi_ai/py.typed +0 -0
- pi_ai/serialization.py +477 -0
- pi_ai/session_resources.py +33 -0
- pi_ai/types.py +1003 -0
- pi_ai/utils/__init__.py +1 -0
- pi_ai/utils/callbacks.py +15 -0
- pi_ai/utils/diagnostics.py +70 -0
- pi_ai/utils/error_body.py +89 -0
- pi_ai/utils/estimate.py +125 -0
- pi_ai/utils/fastjson.py +63 -0
- pi_ai/utils/http.py +409 -0
- pi_ai/utils/json_parse.py +424 -0
- pi_ai/utils/misc.py +193 -0
- pi_ai/utils/model_operations.py +73 -0
- pi_ai/utils/models_error.py +35 -0
- pi_ai/utils/oauth_page.py +121 -0
- pi_ai/utils/overflow.py +87 -0
- pi_ai/utils/retry.py +193 -0
- pi_ai/utils/schema.py +283 -0
- pi_ai/utils/text.py +65 -0
- pi_ai/utils/transcript.py +271 -0
- pi_ai/utils/validation.py +813 -0
- pi_ai_python-1.3.0.dist-info/METADATA +167 -0
- pi_ai_python-1.3.0.dist-info/RECORD +189 -0
- pi_ai_python-1.3.0.dist-info/WHEEL +4 -0
- pi_ai_python-1.3.0.dist-info/entry_points.txt +2 -0
- pi_ai_python-1.3.0.dist-info/licenses/LICENSE +22 -0
pi_ai/compat.py
ADDED
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
"""Compatibility entry point preserving the old global pi-ai API surface.
|
|
2
|
+
|
|
3
|
+
API-dispatch :func:`stream`/:func:`complete` with env API key injection, the API
|
|
4
|
+
registry, generated catalog reads (:func:`get_model`/:func:`get_models`/
|
|
5
|
+
:func:`get_providers`), per-API lazy stream wrappers, and image generation.
|
|
6
|
+
|
|
7
|
+
Existing apps switch imports from ``pi_ai`` to ``pi_ai.compat`` unchanged (this
|
|
8
|
+
module re-exports the whole root API); new code uses :func:`pi_ai.create_models`
|
|
9
|
+
and the provider factories in :mod:`pi_ai.providers`.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import random
|
|
15
|
+
import string
|
|
16
|
+
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import Any, TypeAlias
|
|
19
|
+
|
|
20
|
+
from . import * # noqa: F403 (the compat entry point re-exports the root API)
|
|
21
|
+
from . import __all__ as _root_all
|
|
22
|
+
from .api.lazy import LazyApi
|
|
23
|
+
from .api.registry import (
|
|
24
|
+
anthropic_messages_api,
|
|
25
|
+
azure_openai_responses_api,
|
|
26
|
+
bedrock_converse_stream_api,
|
|
27
|
+
google_generative_ai_api,
|
|
28
|
+
google_vertex_api,
|
|
29
|
+
mistral_conversations_api,
|
|
30
|
+
openai_codex_responses_api,
|
|
31
|
+
openai_completions_api,
|
|
32
|
+
openai_responses_api,
|
|
33
|
+
openrouter_images_api,
|
|
34
|
+
pi_messages_api,
|
|
35
|
+
)
|
|
36
|
+
from .env_api_keys import (
|
|
37
|
+
ANTHROPIC_API_KEY_ENV,
|
|
38
|
+
ANTHROPIC_AUTH_TOKEN_ENV,
|
|
39
|
+
ANTHROPIC_FEDERATION_RULE_ID_ENV,
|
|
40
|
+
ANTHROPIC_IDENTITY_TOKEN_FILE_ENV,
|
|
41
|
+
ANTHROPIC_OAUTH_TOKEN_ENV,
|
|
42
|
+
ANTHROPIC_ORGANIZATION_ID_ENV,
|
|
43
|
+
ANTHROPIC_SERVICE_ACCOUNT_ID_ENV,
|
|
44
|
+
ANTHROPIC_WORKSPACE_ID_ENV,
|
|
45
|
+
PROVIDER_API_KEY_ENV_VARS,
|
|
46
|
+
find_env_keys,
|
|
47
|
+
get_env_api_key,
|
|
48
|
+
)
|
|
49
|
+
from .event_stream import AssistantMessageEventStream
|
|
50
|
+
from .models import Models, Provider
|
|
51
|
+
from .providers.all import (
|
|
52
|
+
BuiltinProvider,
|
|
53
|
+
builtin_models,
|
|
54
|
+
get_builtin_image_model,
|
|
55
|
+
get_builtin_image_models,
|
|
56
|
+
get_builtin_model,
|
|
57
|
+
get_builtin_models,
|
|
58
|
+
get_builtin_providers,
|
|
59
|
+
)
|
|
60
|
+
from .providers.catalog import BUILTIN_PROVIDERS, load_catalog
|
|
61
|
+
from .providers.faux import FauxModelDefinition, FauxProviderState, FauxResponseStep, create_faux_core
|
|
62
|
+
from .types import (
|
|
63
|
+
AssistantImages,
|
|
64
|
+
AssistantMessage,
|
|
65
|
+
Context,
|
|
66
|
+
ImageModel,
|
|
67
|
+
ImagesContext,
|
|
68
|
+
Model,
|
|
69
|
+
TranscriptContext,
|
|
70
|
+
now_ms,
|
|
71
|
+
)
|
|
72
|
+
from .utils.diagnostics import format_thrown_value
|
|
73
|
+
from .utils.transcript import normalize_context
|
|
74
|
+
|
|
75
|
+
__all__ = [
|
|
76
|
+
*_root_all,
|
|
77
|
+
# lazy API wrappers
|
|
78
|
+
"anthropic_messages_api",
|
|
79
|
+
"azure_openai_responses_api",
|
|
80
|
+
"bedrock_converse_stream_api",
|
|
81
|
+
"google_generative_ai_api",
|
|
82
|
+
"google_vertex_api",
|
|
83
|
+
"mistral_conversations_api",
|
|
84
|
+
"openai_codex_responses_api",
|
|
85
|
+
"openai_completions_api",
|
|
86
|
+
"openai_responses_api",
|
|
87
|
+
"pi_messages_api",
|
|
88
|
+
# env API keys
|
|
89
|
+
"ANTHROPIC_AUTH_TOKEN_ENV",
|
|
90
|
+
"ANTHROPIC_OAUTH_TOKEN_ENV",
|
|
91
|
+
"ANTHROPIC_API_KEY_ENV",
|
|
92
|
+
"ANTHROPIC_FEDERATION_RULE_ID_ENV",
|
|
93
|
+
"ANTHROPIC_ORGANIZATION_ID_ENV",
|
|
94
|
+
"ANTHROPIC_SERVICE_ACCOUNT_ID_ENV",
|
|
95
|
+
"ANTHROPIC_IDENTITY_TOKEN_FILE_ENV",
|
|
96
|
+
"ANTHROPIC_WORKSPACE_ID_ENV",
|
|
97
|
+
"PROVIDER_API_KEY_ENV_VARS",
|
|
98
|
+
"find_env_keys",
|
|
99
|
+
"get_env_api_key",
|
|
100
|
+
# static catalog reads
|
|
101
|
+
"BuiltinProvider",
|
|
102
|
+
"BuiltinImageProvider",
|
|
103
|
+
"get_model",
|
|
104
|
+
"get_models",
|
|
105
|
+
"get_providers",
|
|
106
|
+
"get_image_model",
|
|
107
|
+
"get_image_models",
|
|
108
|
+
"get_image_providers",
|
|
109
|
+
# API registry
|
|
110
|
+
"ApiStreamFunction",
|
|
111
|
+
"ApiStreamSimpleFunction",
|
|
112
|
+
"ApiProvider",
|
|
113
|
+
"register_api_provider",
|
|
114
|
+
"get_api_provider",
|
|
115
|
+
"get_api_providers",
|
|
116
|
+
"unregister_api_providers",
|
|
117
|
+
"register_builtin_api_providers",
|
|
118
|
+
"reset_api_providers",
|
|
119
|
+
"FauxProviderRegistration",
|
|
120
|
+
"register_faux_provider",
|
|
121
|
+
# api dispatch
|
|
122
|
+
"stream",
|
|
123
|
+
"complete",
|
|
124
|
+
"stream_simple",
|
|
125
|
+
"complete_simple",
|
|
126
|
+
# images
|
|
127
|
+
"ImagesApiFunction",
|
|
128
|
+
"ImagesApiProvider",
|
|
129
|
+
"register_images_api_provider",
|
|
130
|
+
"get_images_api_provider",
|
|
131
|
+
"generate_images",
|
|
132
|
+
"generate_images_openrouter",
|
|
133
|
+
"register_builtin_images_api_providers",
|
|
134
|
+
# legacy per-API aliases
|
|
135
|
+
"stream_anthropic",
|
|
136
|
+
"stream_simple_anthropic",
|
|
137
|
+
"stream_azure_openai_responses",
|
|
138
|
+
"stream_simple_azure_openai_responses",
|
|
139
|
+
"stream_google",
|
|
140
|
+
"stream_simple_google",
|
|
141
|
+
"stream_google_vertex",
|
|
142
|
+
"stream_simple_google_vertex",
|
|
143
|
+
"stream_mistral",
|
|
144
|
+
"stream_simple_mistral",
|
|
145
|
+
"stream_openai_codex_responses",
|
|
146
|
+
"stream_simple_openai_codex_responses",
|
|
147
|
+
"stream_openai_completions",
|
|
148
|
+
"stream_simple_openai_completions",
|
|
149
|
+
"stream_openai_responses",
|
|
150
|
+
"stream_simple_openai_responses",
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _merge_options(options: Mapping[str, Any] | None, kwargs: Mapping[str, Any]) -> dict[str, Any] | None:
|
|
155
|
+
if not kwargs:
|
|
156
|
+
return dict(options) if options is not None else None
|
|
157
|
+
return {**(options or {}), **kwargs}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# ---------------------------------------------------------------------------
|
|
161
|
+
# Static catalog reads
|
|
162
|
+
# ---------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
#: Deprecated static catalog read. Use ``pi_ai.providers.all.get_builtin_model`` or ``Models.get_model()``.
|
|
165
|
+
get_model = get_builtin_model
|
|
166
|
+
|
|
167
|
+
#: Deprecated static catalog read. Use ``pi_ai.providers.all.get_builtin_models`` or ``Models.get_models()``.
|
|
168
|
+
get_models = get_builtin_models
|
|
169
|
+
|
|
170
|
+
#: Deprecated static catalog read. Use ``pi_ai.providers.all.get_builtin_providers`` or ``Models.get_providers()``.
|
|
171
|
+
get_providers = get_builtin_providers
|
|
172
|
+
|
|
173
|
+
#: Built-in providers with at least one image model in the generated catalog.
|
|
174
|
+
BuiltinImageProvider: TypeAlias = str
|
|
175
|
+
|
|
176
|
+
_image_providers: list[str] | None = None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def get_image_model(provider: str, model_id: str) -> ImageModel | None:
|
|
180
|
+
"""Deprecated static catalog read. Use ``get_builtin_image_model`` or ``Models.get_model_of_type("image", ...)``."""
|
|
181
|
+
return get_builtin_image_model(provider, model_id)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def get_image_providers() -> list[str]:
|
|
185
|
+
"""Deprecated static catalog read. Use ``Models.get_providers()``."""
|
|
186
|
+
global _image_providers
|
|
187
|
+
if _image_providers is None:
|
|
188
|
+
_image_providers = [provider for provider in BUILTIN_PROVIDERS if load_catalog(provider)["image"]]
|
|
189
|
+
return list(_image_providers)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def get_image_models(provider: str) -> list[ImageModel]:
|
|
193
|
+
"""Deprecated static catalog read. Use ``get_builtin_image_models`` or ``Models.get_models_of_type("image")``."""
|
|
194
|
+
return get_builtin_image_models(provider)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# ---------------------------------------------------------------------------
|
|
198
|
+
# API registry
|
|
199
|
+
# ---------------------------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
ApiStreamFunction: TypeAlias = Callable[
|
|
202
|
+
[Model, TranscriptContext, "Mapping[str, Any] | None"], AssistantMessageEventStream
|
|
203
|
+
]
|
|
204
|
+
ApiStreamSimpleFunction: TypeAlias = ApiStreamFunction
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@dataclass(slots=True)
|
|
208
|
+
class ApiProvider:
|
|
209
|
+
"""A chat API implementation registered under its ``api`` id."""
|
|
210
|
+
|
|
211
|
+
api: str
|
|
212
|
+
stream: ApiStreamFunction
|
|
213
|
+
stream_simple: ApiStreamSimpleFunction
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@dataclass(slots=True)
|
|
217
|
+
class _RegisteredApiProvider:
|
|
218
|
+
provider: ApiProvider
|
|
219
|
+
source_id: str | None
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
_api_provider_registry: dict[str, _RegisteredApiProvider] = {}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _field(provider: Any, name: str) -> Any:
|
|
226
|
+
return provider[name] if isinstance(provider, Mapping) else getattr(provider, name)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _wrap_stream(api: str, stream_fn: Callable[..., AssistantMessageEventStream]) -> ApiStreamFunction:
|
|
230
|
+
def wrapped(
|
|
231
|
+
model: Model, context: TranscriptContext, options: Mapping[str, Any] | None = None
|
|
232
|
+
) -> AssistantMessageEventStream:
|
|
233
|
+
if model.api != api:
|
|
234
|
+
raise ValueError(f"Mismatched api: {model.api} expected {api}")
|
|
235
|
+
return stream_fn(model, context, options)
|
|
236
|
+
|
|
237
|
+
return wrapped
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def register_api_provider(provider: ApiProvider | Mapping[str, Any] | Any, source_id: str | None = None) -> None:
|
|
241
|
+
"""Register (or replace) the implementation for ``provider.api``.
|
|
242
|
+
|
|
243
|
+
``provider`` is an :class:`ApiProvider`, any object with ``api``/``stream``/``stream_simple``
|
|
244
|
+
attributes, or a mapping with those keys. ``source_id`` groups registrations for
|
|
245
|
+
:func:`unregister_api_providers`.
|
|
246
|
+
"""
|
|
247
|
+
api = _field(provider, "api")
|
|
248
|
+
_api_provider_registry[api] = _RegisteredApiProvider(
|
|
249
|
+
ApiProvider(
|
|
250
|
+
api=api,
|
|
251
|
+
stream=_wrap_stream(api, _field(provider, "stream")),
|
|
252
|
+
stream_simple=_wrap_stream(api, _field(provider, "stream_simple")),
|
|
253
|
+
),
|
|
254
|
+
source_id,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def get_api_provider(api: str) -> ApiProvider | None:
|
|
259
|
+
entry = _api_provider_registry.get(api)
|
|
260
|
+
return entry.provider if entry is not None else None
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def get_api_providers() -> list[ApiProvider]:
|
|
264
|
+
return [entry.provider for entry in _api_provider_registry.values()]
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def unregister_api_providers(source_id: str) -> None:
|
|
268
|
+
"""Remove every registration made with ``source_id``."""
|
|
269
|
+
for api in [api for api, entry in _api_provider_registry.items() if entry.source_id == source_id]:
|
|
270
|
+
del _api_provider_registry[api]
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _clear_api_providers() -> None:
|
|
274
|
+
_api_provider_registry.clear()
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class FauxProviderRegistration:
|
|
278
|
+
"""Handle returned by :func:`register_faux_provider`."""
|
|
279
|
+
|
|
280
|
+
def __init__(self, core: Any, source_id: str) -> None:
|
|
281
|
+
self._core = core
|
|
282
|
+
self._source_id = source_id
|
|
283
|
+
self.api: str = core.api
|
|
284
|
+
self.models: list[Model] = core.models
|
|
285
|
+
self.state: FauxProviderState = core.state
|
|
286
|
+
|
|
287
|
+
def get_model(self, model_id: str | None = None) -> Model | None:
|
|
288
|
+
"""The first faux model, or the one with ``model_id`` (``None`` if unknown)."""
|
|
289
|
+
return self._core.get_model(model_id)
|
|
290
|
+
|
|
291
|
+
def set_responses(self, responses: Sequence[FauxResponseStep]) -> None:
|
|
292
|
+
self._core.set_responses(responses)
|
|
293
|
+
|
|
294
|
+
def append_responses(self, responses: Sequence[FauxResponseStep]) -> None:
|
|
295
|
+
self._core.append_responses(responses)
|
|
296
|
+
|
|
297
|
+
def get_pending_response_count(self) -> int:
|
|
298
|
+
return self._core.get_pending_response_count()
|
|
299
|
+
|
|
300
|
+
def unregister(self) -> None:
|
|
301
|
+
unregister_api_providers(self._source_id)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _random_base36(length: int) -> str:
|
|
305
|
+
return "".join(random.choices(string.digits + string.ascii_lowercase, k=length))
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def register_faux_provider(
|
|
309
|
+
*,
|
|
310
|
+
api: str | None = None,
|
|
311
|
+
provider: str | None = None,
|
|
312
|
+
models: Sequence[FauxModelDefinition | Mapping[str, Any]] | None = None,
|
|
313
|
+
deferred: Mapping[str, Any] | None = None,
|
|
314
|
+
tokens_per_second: float | None = None,
|
|
315
|
+
token_size: Mapping[str, int] | None = None,
|
|
316
|
+
) -> FauxProviderRegistration:
|
|
317
|
+
"""Register a scripted in-memory API implementation in the global API registry."""
|
|
318
|
+
core = create_faux_core(
|
|
319
|
+
api=api,
|
|
320
|
+
provider=provider,
|
|
321
|
+
models=models,
|
|
322
|
+
deferred=deferred,
|
|
323
|
+
tokens_per_second=tokens_per_second,
|
|
324
|
+
token_size=token_size,
|
|
325
|
+
)
|
|
326
|
+
source_id = f"faux-provider-{_random_base36(8)}"
|
|
327
|
+
register_api_provider(ApiProvider(api=core.api, stream=core.stream, stream_simple=core.stream_simple), source_id)
|
|
328
|
+
return FauxProviderRegistration(core, source_id)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
_BUILTIN_APIS: tuple[tuple[str, LazyApi], ...] = (
|
|
332
|
+
("anthropic-messages", anthropic_messages_api()),
|
|
333
|
+
("openai-completions", openai_completions_api()),
|
|
334
|
+
("openai-responses", openai_responses_api()),
|
|
335
|
+
("openai-codex-responses", openai_codex_responses_api()),
|
|
336
|
+
("azure-openai-responses", azure_openai_responses_api()),
|
|
337
|
+
("google-generative-ai", google_generative_ai_api()),
|
|
338
|
+
("google-vertex", google_vertex_api()),
|
|
339
|
+
("mistral-conversations", mistral_conversations_api()),
|
|
340
|
+
("bedrock-converse-stream", bedrock_converse_stream_api()),
|
|
341
|
+
("pi-messages", pi_messages_api()),
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
_builtin_api_provider_instances: dict[str, ApiProvider | None] = {}
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def register_builtin_api_providers() -> None:
|
|
348
|
+
"""Register the built-in API implementations without clobbering existing entries.
|
|
349
|
+
|
|
350
|
+
Compat may load after a test or extension has already registered an override for
|
|
351
|
+
a built-in api id.
|
|
352
|
+
"""
|
|
353
|
+
for api, streams in _BUILTIN_APIS:
|
|
354
|
+
if get_api_provider(api) is None:
|
|
355
|
+
register_api_provider(ApiProvider(api=api, stream=streams.stream, stream_simple=streams.stream_simple))
|
|
356
|
+
_builtin_api_provider_instances[api] = get_api_provider(api)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def reset_api_providers() -> None:
|
|
360
|
+
"""Drop every registration and re-register the built-in API implementations."""
|
|
361
|
+
_clear_api_providers()
|
|
362
|
+
_builtin_api_provider_instances.clear()
|
|
363
|
+
register_builtin_api_providers()
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
register_builtin_api_providers()
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
# ---------------------------------------------------------------------------
|
|
370
|
+
# API dispatch with env API key injection
|
|
371
|
+
# ---------------------------------------------------------------------------
|
|
372
|
+
|
|
373
|
+
_AMBIENT_AUTH_MARKER = "<authenticated>"
|
|
374
|
+
_compat_models_instance: Models | None = None
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _compat_models() -> Models:
|
|
378
|
+
global _compat_models_instance
|
|
379
|
+
if _compat_models_instance is None:
|
|
380
|
+
_compat_models_instance = builtin_models()
|
|
381
|
+
return _compat_models_instance
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _has_explicit_api_key(api_key: Any) -> bool:
|
|
385
|
+
return isinstance(api_key, str) and len(api_key.strip()) > 0
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _with_env_api_key(model: Model, options: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
389
|
+
if options is not None and _has_explicit_api_key(options.get("api_key")):
|
|
390
|
+
return options
|
|
391
|
+
api_key = get_env_api_key(model.provider, options.get("env") if options else None)
|
|
392
|
+
if not api_key or api_key == _AMBIENT_AUTH_MARKER:
|
|
393
|
+
return options
|
|
394
|
+
return {**(options or {}), "api_key": api_key}
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def _has_resolved_cloudflare_auth(options: Mapping[str, Any] | None) -> bool:
|
|
398
|
+
if not options:
|
|
399
|
+
return False
|
|
400
|
+
headers = options.get("headers") or {}
|
|
401
|
+
return _has_explicit_api_key(options.get("api_key")) or isinstance(headers.get("cf-aig-authorization"), str)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def _get_builtin_provider_for_model(model: Model) -> Provider | None:
|
|
405
|
+
if get_api_provider(model.api) is not _builtin_api_provider_instances.get(model.api):
|
|
406
|
+
return None
|
|
407
|
+
provider = _compat_models().get_provider(model.provider)
|
|
408
|
+
if provider is None:
|
|
409
|
+
return None
|
|
410
|
+
return provider if any(candidate.api == model.api for candidate in provider.get_models()) else None
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _resolve_api_provider(api: str) -> ApiProvider:
|
|
414
|
+
provider = get_api_provider(api)
|
|
415
|
+
if provider is None:
|
|
416
|
+
raise ValueError(f"No API provider registered for api: {api}")
|
|
417
|
+
return provider
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def stream(
|
|
421
|
+
model: Model,
|
|
422
|
+
context: Context | Mapping[str, Any] | TranscriptContext,
|
|
423
|
+
options: Mapping[str, Any] | None = None,
|
|
424
|
+
/,
|
|
425
|
+
**kwargs: Any,
|
|
426
|
+
) -> AssistantMessageEventStream:
|
|
427
|
+
"""Stream through the implementation registered for ``model.api``.
|
|
428
|
+
|
|
429
|
+
Built-in providers' models go through their provider (so provider request shaping
|
|
430
|
+
applies); the API key comes from ``options["api_key"]`` or the provider's env var.
|
|
431
|
+
Raises :class:`ValueError` when no implementation is registered for ``model.api``.
|
|
432
|
+
"""
|
|
433
|
+
transcript = normalize_context(context)
|
|
434
|
+
opts = _merge_options(options, kwargs)
|
|
435
|
+
builtin_provider = _get_builtin_provider_for_model(model)
|
|
436
|
+
if builtin_provider is not None:
|
|
437
|
+
if model.provider.startswith("cloudflare-") and not _has_resolved_cloudflare_auth(opts):
|
|
438
|
+
return _compat_models().stream(model, transcript, opts)
|
|
439
|
+
return builtin_provider.stream(model, transcript, _with_env_api_key(model, opts))
|
|
440
|
+
provider = _resolve_api_provider(model.api)
|
|
441
|
+
return provider.stream(model, transcript, _with_env_api_key(model, opts))
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
async def complete(
|
|
445
|
+
model: Model,
|
|
446
|
+
context: Context | Mapping[str, Any] | TranscriptContext,
|
|
447
|
+
options: Mapping[str, Any] | None = None,
|
|
448
|
+
/,
|
|
449
|
+
**kwargs: Any,
|
|
450
|
+
) -> AssistantMessage:
|
|
451
|
+
return await stream(model, context, options, **kwargs).result()
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def stream_simple(
|
|
455
|
+
model: Model,
|
|
456
|
+
context: Context | Mapping[str, Any] | TranscriptContext,
|
|
457
|
+
options: Mapping[str, Any] | None = None,
|
|
458
|
+
/,
|
|
459
|
+
**kwargs: Any,
|
|
460
|
+
) -> AssistantMessageEventStream:
|
|
461
|
+
"""Like :func:`stream` with provider-neutral options (``reasoning="medium"``, ``tool_choice``...)."""
|
|
462
|
+
transcript = normalize_context(context)
|
|
463
|
+
opts = _merge_options(options, kwargs)
|
|
464
|
+
builtin_provider = _get_builtin_provider_for_model(model)
|
|
465
|
+
if builtin_provider is not None:
|
|
466
|
+
if model.provider.startswith("cloudflare-") and not _has_resolved_cloudflare_auth(opts):
|
|
467
|
+
return _compat_models().stream_simple(model, transcript, opts)
|
|
468
|
+
return builtin_provider.stream_simple(model, transcript, _with_env_api_key(model, opts))
|
|
469
|
+
provider = _resolve_api_provider(model.api)
|
|
470
|
+
return provider.stream_simple(model, transcript, _with_env_api_key(model, opts))
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
async def complete_simple(
|
|
474
|
+
model: Model,
|
|
475
|
+
context: Context | Mapping[str, Any] | TranscriptContext,
|
|
476
|
+
options: Mapping[str, Any] | None = None,
|
|
477
|
+
/,
|
|
478
|
+
**kwargs: Any,
|
|
479
|
+
) -> AssistantMessage:
|
|
480
|
+
return await stream_simple(model, context, options, **kwargs).result()
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
# ---------------------------------------------------------------------------
|
|
484
|
+
# Image generation registry
|
|
485
|
+
# ---------------------------------------------------------------------------
|
|
486
|
+
|
|
487
|
+
ImagesApiFunction: TypeAlias = Callable[
|
|
488
|
+
[ImageModel, ImagesContext, "Mapping[str, Any] | None"], Awaitable[AssistantImages]
|
|
489
|
+
]
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
@dataclass(slots=True)
|
|
493
|
+
class ImagesApiProvider:
|
|
494
|
+
"""An image-generation implementation registered under its ``api`` id."""
|
|
495
|
+
|
|
496
|
+
api: str
|
|
497
|
+
generate_images: ImagesApiFunction
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
@dataclass(slots=True)
|
|
501
|
+
class _RegisteredImagesApiProvider:
|
|
502
|
+
provider: ImagesApiProvider
|
|
503
|
+
source_id: str | None
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
_images_api_provider_registry: dict[str, _RegisteredImagesApiProvider] = {}
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def _wrap_generate_images(api: str, generate: Callable[..., Awaitable[AssistantImages]]) -> ImagesApiFunction:
|
|
510
|
+
async def wrapped(
|
|
511
|
+
model: ImageModel, context: ImagesContext, options: Mapping[str, Any] | None = None
|
|
512
|
+
) -> AssistantImages:
|
|
513
|
+
if model.api != api:
|
|
514
|
+
raise ValueError(f"Mismatched api: {model.api} expected {api}")
|
|
515
|
+
return await generate(model, context, options)
|
|
516
|
+
|
|
517
|
+
return wrapped
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def register_images_api_provider(
|
|
521
|
+
provider: ImagesApiProvider | Mapping[str, Any] | Any, source_id: str | None = None
|
|
522
|
+
) -> None:
|
|
523
|
+
"""Register (or replace) the image implementation for ``provider.api``."""
|
|
524
|
+
api = _field(provider, "api")
|
|
525
|
+
_images_api_provider_registry[api] = _RegisteredImagesApiProvider(
|
|
526
|
+
ImagesApiProvider(api=api, generate_images=_wrap_generate_images(api, _field(provider, "generate_images"))),
|
|
527
|
+
source_id,
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def get_images_api_provider(api: str) -> ImagesApiProvider | None:
|
|
532
|
+
entry = _images_api_provider_registry.get(api)
|
|
533
|
+
return entry.provider if entry is not None else None
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
_openrouter_images = openrouter_images_api()
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
async def generate_images_openrouter(
|
|
540
|
+
model: ImageModel, context: ImagesContext, options: Mapping[str, Any] | None = None
|
|
541
|
+
) -> AssistantImages:
|
|
542
|
+
"""OpenRouter image generation, loaded on first use. Load and request failures become error results."""
|
|
543
|
+
try:
|
|
544
|
+
return await _openrouter_images.generate_images(model, context, options)
|
|
545
|
+
except Exception as error:
|
|
546
|
+
return AssistantImages(
|
|
547
|
+
api=model.api,
|
|
548
|
+
provider=model.provider,
|
|
549
|
+
model=model.id,
|
|
550
|
+
output=[],
|
|
551
|
+
stop_reason="error",
|
|
552
|
+
error_message=format_thrown_value(error),
|
|
553
|
+
timestamp=now_ms(),
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def register_builtin_images_api_providers() -> None:
|
|
558
|
+
register_images_api_provider(ImagesApiProvider(api="openrouter-images", generate_images=generate_images_openrouter))
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
register_builtin_images_api_providers()
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
async def generate_images(
|
|
565
|
+
model: ImageModel,
|
|
566
|
+
context: ImagesContext,
|
|
567
|
+
options: Mapping[str, Any] | None = None,
|
|
568
|
+
/,
|
|
569
|
+
**kwargs: Any,
|
|
570
|
+
) -> AssistantImages:
|
|
571
|
+
"""Global image generation dispatched on ``model.api`` through the images API registry.
|
|
572
|
+
|
|
573
|
+
Auth must be passed explicitly via ``api_key``; prefer ``Models.generate_images()``,
|
|
574
|
+
which resolves provider auth. Raises :class:`ValueError` for an unregistered api.
|
|
575
|
+
"""
|
|
576
|
+
provider = get_images_api_provider(model.api)
|
|
577
|
+
if provider is None:
|
|
578
|
+
raise ValueError(f"No API provider registered for api: {model.api}")
|
|
579
|
+
return await provider.generate_images(model, context, _merge_options(options, kwargs))
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
# ---------------------------------------------------------------------------
|
|
583
|
+
# Legacy per-API stream aliases
|
|
584
|
+
# ---------------------------------------------------------------------------
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def _legacy_alias(streams: LazyApi, simple: bool, name: str, module: str) -> ApiStreamFunction:
|
|
588
|
+
method = streams.stream_simple if simple else streams.stream
|
|
589
|
+
target = "stream_simple" if simple else "stream"
|
|
590
|
+
|
|
591
|
+
def alias(
|
|
592
|
+
model: Model,
|
|
593
|
+
context: Context | Mapping[str, Any] | TranscriptContext,
|
|
594
|
+
options: Mapping[str, Any] | None = None,
|
|
595
|
+
/,
|
|
596
|
+
**kwargs: Any,
|
|
597
|
+
) -> AssistantMessageEventStream:
|
|
598
|
+
return method(model, normalize_context(context), _merge_options(options, kwargs))
|
|
599
|
+
|
|
600
|
+
alias.__name__ = alias.__qualname__ = name
|
|
601
|
+
alias.__doc__ = f"Deprecated: use ``pi_ai.api.{module}.{target}`` or ``{module}_api().{target}``."
|
|
602
|
+
return alias
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
_anthropic_messages_streams = anthropic_messages_api()
|
|
606
|
+
_azure_openai_responses_streams = azure_openai_responses_api()
|
|
607
|
+
_google_generative_ai_streams = google_generative_ai_api()
|
|
608
|
+
_google_vertex_streams = google_vertex_api()
|
|
609
|
+
_mistral_conversations_streams = mistral_conversations_api()
|
|
610
|
+
_openai_codex_responses_streams = openai_codex_responses_api()
|
|
611
|
+
_openai_completions_streams = openai_completions_api()
|
|
612
|
+
_openai_responses_streams = openai_responses_api()
|
|
613
|
+
|
|
614
|
+
stream_anthropic = _legacy_alias(_anthropic_messages_streams, False, "stream_anthropic", "anthropic_messages")
|
|
615
|
+
stream_simple_anthropic = _legacy_alias(
|
|
616
|
+
_anthropic_messages_streams, True, "stream_simple_anthropic", "anthropic_messages"
|
|
617
|
+
)
|
|
618
|
+
stream_azure_openai_responses = _legacy_alias(
|
|
619
|
+
_azure_openai_responses_streams, False, "stream_azure_openai_responses", "azure_openai_responses"
|
|
620
|
+
)
|
|
621
|
+
stream_simple_azure_openai_responses = _legacy_alias(
|
|
622
|
+
_azure_openai_responses_streams, True, "stream_simple_azure_openai_responses", "azure_openai_responses"
|
|
623
|
+
)
|
|
624
|
+
stream_google = _legacy_alias(_google_generative_ai_streams, False, "stream_google", "google_generative_ai")
|
|
625
|
+
stream_simple_google = _legacy_alias(
|
|
626
|
+
_google_generative_ai_streams, True, "stream_simple_google", "google_generative_ai"
|
|
627
|
+
)
|
|
628
|
+
stream_google_vertex = _legacy_alias(_google_vertex_streams, False, "stream_google_vertex", "google_vertex")
|
|
629
|
+
stream_simple_google_vertex = _legacy_alias(
|
|
630
|
+
_google_vertex_streams, True, "stream_simple_google_vertex", "google_vertex"
|
|
631
|
+
)
|
|
632
|
+
stream_mistral = _legacy_alias(_mistral_conversations_streams, False, "stream_mistral", "mistral_conversations")
|
|
633
|
+
stream_simple_mistral = _legacy_alias(
|
|
634
|
+
_mistral_conversations_streams, True, "stream_simple_mistral", "mistral_conversations"
|
|
635
|
+
)
|
|
636
|
+
stream_openai_codex_responses = _legacy_alias(
|
|
637
|
+
_openai_codex_responses_streams, False, "stream_openai_codex_responses", "openai_codex_responses"
|
|
638
|
+
)
|
|
639
|
+
stream_simple_openai_codex_responses = _legacy_alias(
|
|
640
|
+
_openai_codex_responses_streams, True, "stream_simple_openai_codex_responses", "openai_codex_responses"
|
|
641
|
+
)
|
|
642
|
+
stream_openai_completions = _legacy_alias(
|
|
643
|
+
_openai_completions_streams, False, "stream_openai_completions", "openai_completions"
|
|
644
|
+
)
|
|
645
|
+
stream_simple_openai_completions = _legacy_alias(
|
|
646
|
+
_openai_completions_streams, True, "stream_simple_openai_completions", "openai_completions"
|
|
647
|
+
)
|
|
648
|
+
stream_openai_responses = _legacy_alias(_openai_responses_streams, False, "stream_openai_responses", "openai_responses")
|
|
649
|
+
stream_simple_openai_responses = _legacy_alias(
|
|
650
|
+
_openai_responses_streams, True, "stream_simple_openai_responses", "openai_responses"
|
|
651
|
+
)
|