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/types.py
ADDED
|
@@ -0,0 +1,1003 @@
|
|
|
1
|
+
"""Core data types for pi-ai.
|
|
2
|
+
|
|
3
|
+
Every type mirrors its counterpart in ``@earendil-works/pi-ai`` (``src/types.ts``).
|
|
4
|
+
Python attribute names are snake_case; :mod:`pi_ai.serialization` converts to and
|
|
5
|
+
from pi's camelCase JSON so transcripts, models and contexts stay interchangeable
|
|
6
|
+
with the TypeScript implementation.
|
|
7
|
+
|
|
8
|
+
All data classes use ``__slots__`` for low memory use and fast attribute access.
|
|
9
|
+
Discriminators (``type`` on content blocks, events and models, ``role`` on
|
|
10
|
+
messages) are class-level constants, so they can be read as ``block.type`` and
|
|
11
|
+
matched structurally (``match event: case TextDeltaEvent(delta=d): ...``).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import time
|
|
17
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
18
|
+
from dataclasses import KW_ONLY, dataclass, field
|
|
19
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Literal, Protocol, TypeAlias, TypedDict
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
import httpx
|
|
23
|
+
|
|
24
|
+
from .abort import AbortSignal
|
|
25
|
+
from .event_stream import AssistantMessageEventStream
|
|
26
|
+
from .utils.diagnostics import AssistantMessageDiagnostic
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
# literal aliases
|
|
30
|
+
"KnownApi",
|
|
31
|
+
"Api",
|
|
32
|
+
"KnownImageApi",
|
|
33
|
+
"ImageApi",
|
|
34
|
+
"KnownClassifierApi",
|
|
35
|
+
"ClassifierApi",
|
|
36
|
+
"KnownProvider",
|
|
37
|
+
"ProviderId",
|
|
38
|
+
"StopReason",
|
|
39
|
+
"ThinkingLevel",
|
|
40
|
+
"ModelThinkingLevel",
|
|
41
|
+
"THINKING_LEVELS",
|
|
42
|
+
"MODEL_THINKING_LEVELS",
|
|
43
|
+
"CacheRetention",
|
|
44
|
+
"Transport",
|
|
45
|
+
"ToolChoice",
|
|
46
|
+
"ModelType",
|
|
47
|
+
"ModelInputModality",
|
|
48
|
+
"JsonValue",
|
|
49
|
+
"JsonObject",
|
|
50
|
+
"ProviderEnv",
|
|
51
|
+
"ProviderHeaders",
|
|
52
|
+
"SamplingParams",
|
|
53
|
+
# content
|
|
54
|
+
"TextContent",
|
|
55
|
+
"ThinkingContent",
|
|
56
|
+
"ImageContent",
|
|
57
|
+
"ToolCall",
|
|
58
|
+
"TextSignatureV1",
|
|
59
|
+
"AssistantContent",
|
|
60
|
+
"UserContent",
|
|
61
|
+
"ToolResultContent",
|
|
62
|
+
# usage
|
|
63
|
+
"UsageCost",
|
|
64
|
+
"Usage",
|
|
65
|
+
# tools
|
|
66
|
+
"GrammarFormat",
|
|
67
|
+
"GrammarVariants",
|
|
68
|
+
"ConstrainedSamplingConfig",
|
|
69
|
+
"Tool",
|
|
70
|
+
"ToolReference",
|
|
71
|
+
# messages
|
|
72
|
+
"DeferredHandle",
|
|
73
|
+
"SystemMessage",
|
|
74
|
+
"UserMessage",
|
|
75
|
+
"AssistantMessage",
|
|
76
|
+
"NestedToolCallRecord",
|
|
77
|
+
"NestedToolCalls",
|
|
78
|
+
"ToolResultMessage",
|
|
79
|
+
"Message",
|
|
80
|
+
"Context",
|
|
81
|
+
"TranscriptContext",
|
|
82
|
+
# events
|
|
83
|
+
"StartEvent",
|
|
84
|
+
"TextStartEvent",
|
|
85
|
+
"TextDeltaEvent",
|
|
86
|
+
"TextEndEvent",
|
|
87
|
+
"ThinkingStartEvent",
|
|
88
|
+
"ThinkingDeltaEvent",
|
|
89
|
+
"ThinkingEndEvent",
|
|
90
|
+
"ToolCallStartEvent",
|
|
91
|
+
"ToolCallDeltaEvent",
|
|
92
|
+
"ToolCallEndEvent",
|
|
93
|
+
"DoneEvent",
|
|
94
|
+
"ErrorEvent",
|
|
95
|
+
"AssistantMessageEvent",
|
|
96
|
+
# models
|
|
97
|
+
"ModelCostRates",
|
|
98
|
+
"ModelCostTier",
|
|
99
|
+
"ModelCost",
|
|
100
|
+
"ModelImageResizeOptions",
|
|
101
|
+
"ModelImageInputLimits",
|
|
102
|
+
"ModelInputLimits",
|
|
103
|
+
"BaseModel",
|
|
104
|
+
"Model",
|
|
105
|
+
"ImageModel",
|
|
106
|
+
"ClassifierModel",
|
|
107
|
+
"AnyModel",
|
|
108
|
+
# images
|
|
109
|
+
"ImagesContext",
|
|
110
|
+
"AssistantImages",
|
|
111
|
+
# classifier
|
|
112
|
+
"ClassifierChoiceQuestion",
|
|
113
|
+
"ClassifierScoreQuestion",
|
|
114
|
+
"ClassifierBoolQuestion",
|
|
115
|
+
"ClassifierQuestion",
|
|
116
|
+
"ClassifierContext",
|
|
117
|
+
"ClassifierChoiceAnswer",
|
|
118
|
+
"ClassifierScoreAnswer",
|
|
119
|
+
"ClassifierBoolAnswer",
|
|
120
|
+
"ClassifierAnswer",
|
|
121
|
+
"ClassifierResult",
|
|
122
|
+
# options
|
|
123
|
+
"ProviderResponse",
|
|
124
|
+
"ThinkingBudgets",
|
|
125
|
+
"ProviderRequestOptions",
|
|
126
|
+
"StreamOptions",
|
|
127
|
+
"SimpleStreamOptions",
|
|
128
|
+
"DeferredFetchOptions",
|
|
129
|
+
"DeferredCancelOptions",
|
|
130
|
+
"ImagesOptions",
|
|
131
|
+
"ClassifierOptions",
|
|
132
|
+
# implementation contracts
|
|
133
|
+
"ProviderStreams",
|
|
134
|
+
"ProviderImages",
|
|
135
|
+
"ProviderClassifier",
|
|
136
|
+
"StreamFunction",
|
|
137
|
+
"now_ms",
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def now_ms() -> int:
|
|
142
|
+
"""Current Unix time in milliseconds (the timestamp unit used throughout pi)."""
|
|
143
|
+
return time.time_ns() // 1_000_000
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# ---------------------------------------------------------------------------
|
|
147
|
+
# Literal aliases
|
|
148
|
+
# ---------------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
KnownApi: TypeAlias = Literal[
|
|
151
|
+
"openai-completions",
|
|
152
|
+
"mistral-conversations",
|
|
153
|
+
"openai-responses",
|
|
154
|
+
"azure-openai-responses",
|
|
155
|
+
"openai-codex-responses",
|
|
156
|
+
"anthropic-messages",
|
|
157
|
+
"bedrock-converse-stream",
|
|
158
|
+
"google-generative-ai",
|
|
159
|
+
"google-vertex",
|
|
160
|
+
"pi-messages",
|
|
161
|
+
]
|
|
162
|
+
#: Any API id. Known ids are listed in :data:`KnownApi`; custom providers may use their own.
|
|
163
|
+
Api: TypeAlias = str
|
|
164
|
+
|
|
165
|
+
KnownImageApi: TypeAlias = Literal["openrouter-images"]
|
|
166
|
+
ImageApi: TypeAlias = str
|
|
167
|
+
|
|
168
|
+
KnownClassifierApi: TypeAlias = Literal[
|
|
169
|
+
"typesafe-system-one",
|
|
170
|
+
"cloudflare-workers-ai-system-one",
|
|
171
|
+
"llama-cpp-classify",
|
|
172
|
+
"openai-decisions",
|
|
173
|
+
]
|
|
174
|
+
ClassifierApi: TypeAlias = str
|
|
175
|
+
|
|
176
|
+
KnownProvider: TypeAlias = Literal[
|
|
177
|
+
"amazon-bedrock",
|
|
178
|
+
"ant-ling",
|
|
179
|
+
"anthropic",
|
|
180
|
+
"google",
|
|
181
|
+
"google-vertex",
|
|
182
|
+
"openai",
|
|
183
|
+
"azure",
|
|
184
|
+
"openai-codex",
|
|
185
|
+
"radius",
|
|
186
|
+
"typesafe",
|
|
187
|
+
"nvidia",
|
|
188
|
+
"deepseek",
|
|
189
|
+
"github-copilot",
|
|
190
|
+
"xai",
|
|
191
|
+
"groq",
|
|
192
|
+
"cerebras",
|
|
193
|
+
"openrouter",
|
|
194
|
+
"vercel-ai-gateway",
|
|
195
|
+
"zai",
|
|
196
|
+
"zai-coding-cn",
|
|
197
|
+
"mistral",
|
|
198
|
+
"minimax",
|
|
199
|
+
"minimax-cn",
|
|
200
|
+
"moonshotai",
|
|
201
|
+
"moonshotai-cn",
|
|
202
|
+
"huggingface",
|
|
203
|
+
"fireworks",
|
|
204
|
+
"together",
|
|
205
|
+
"baseten",
|
|
206
|
+
"opencode",
|
|
207
|
+
"opencode-go",
|
|
208
|
+
"kimi-coding",
|
|
209
|
+
"meta",
|
|
210
|
+
"cloudflare-workers-ai",
|
|
211
|
+
"cloudflare-ai-gateway",
|
|
212
|
+
"qwen-token-plan",
|
|
213
|
+
"qwen-token-plan-cn",
|
|
214
|
+
"qwen-token-plan-individual",
|
|
215
|
+
"xiaomi",
|
|
216
|
+
"xiaomi-token-plan-cn",
|
|
217
|
+
"xiaomi-token-plan-ams",
|
|
218
|
+
"xiaomi-token-plan-sgp",
|
|
219
|
+
]
|
|
220
|
+
ProviderId: TypeAlias = str
|
|
221
|
+
|
|
222
|
+
StopReason: TypeAlias = Literal["pending", "stop", "length", "toolUse", "error", "aborted", "deferred"]
|
|
223
|
+
|
|
224
|
+
#: Thinking levels accepted by ``stream_simple(reasoning=...)``.
|
|
225
|
+
ThinkingLevel: TypeAlias = Literal["minimal", "low", "medium", "high", "xhigh", "max"]
|
|
226
|
+
#: Thinking levels including ``"off"``.
|
|
227
|
+
ModelThinkingLevel: TypeAlias = Literal["off", "minimal", "low", "medium", "high", "xhigh", "max"]
|
|
228
|
+
THINKING_LEVELS: tuple[ThinkingLevel, ...] = ("minimal", "low", "medium", "high", "xhigh", "max")
|
|
229
|
+
MODEL_THINKING_LEVELS: tuple[ModelThinkingLevel, ...] = ("off", *THINKING_LEVELS)
|
|
230
|
+
|
|
231
|
+
CacheRetention: TypeAlias = Literal["none", "short", "long"]
|
|
232
|
+
Transport: TypeAlias = Literal["sse", "websocket", "websocket-cached", "auto"]
|
|
233
|
+
ToolChoice: TypeAlias = Literal["auto", "none"]
|
|
234
|
+
ModelType: TypeAlias = Literal["chat", "image", "classifier"]
|
|
235
|
+
ModelInputModality: TypeAlias = Literal["text", "image"]
|
|
236
|
+
|
|
237
|
+
JsonValue: TypeAlias = Any
|
|
238
|
+
JsonObject: TypeAlias = dict[str, Any]
|
|
239
|
+
#: Provider-scoped environment overrides. Values take precedence over ``os.environ``.
|
|
240
|
+
ProviderEnv: TypeAlias = dict[str, str]
|
|
241
|
+
#: Request headers. A ``None`` value suppresses a provider/API default header with the same name.
|
|
242
|
+
ProviderHeaders: TypeAlias = dict[str, "str | None"]
|
|
243
|
+
SamplingParams: TypeAlias = dict[str, Any]
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# ---------------------------------------------------------------------------
|
|
247
|
+
# Content blocks
|
|
248
|
+
# ---------------------------------------------------------------------------
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class TextSignatureV1(TypedDict, total=False):
|
|
252
|
+
v: Literal[1]
|
|
253
|
+
id: str
|
|
254
|
+
phase: Literal["commentary", "final_answer"]
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@dataclass(slots=True)
|
|
258
|
+
class TextContent:
|
|
259
|
+
"""Plain text."""
|
|
260
|
+
|
|
261
|
+
type: ClassVar[Literal["text"]] = "text"
|
|
262
|
+
text: str = ""
|
|
263
|
+
_: KW_ONLY
|
|
264
|
+
#: Provider message metadata, e.g. OpenAI Responses message ids (legacy id string or TextSignatureV1 JSON).
|
|
265
|
+
text_signature: str | None = None
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@dataclass(slots=True)
|
|
269
|
+
class ThinkingContent:
|
|
270
|
+
"""Model reasoning. ``thinking_signature`` carries provider replay data."""
|
|
271
|
+
|
|
272
|
+
type: ClassVar[Literal["thinking"]] = "thinking"
|
|
273
|
+
thinking: str = ""
|
|
274
|
+
_: KW_ONLY
|
|
275
|
+
thinking_signature: str | None = None
|
|
276
|
+
#: True when the reasoning was redacted by safety filters; the encrypted payload is in ``thinking_signature``.
|
|
277
|
+
redacted: bool | None = None
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@dataclass(slots=True)
|
|
281
|
+
class ImageContent:
|
|
282
|
+
"""Base64-encoded image data."""
|
|
283
|
+
|
|
284
|
+
type: ClassVar[Literal["image"]] = "image"
|
|
285
|
+
data: str
|
|
286
|
+
mime_type: str
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@dataclass(slots=True)
|
|
290
|
+
class ToolCall:
|
|
291
|
+
"""A tool call requested by the model."""
|
|
292
|
+
|
|
293
|
+
type: ClassVar[Literal["toolCall"]] = "toolCall"
|
|
294
|
+
id: str
|
|
295
|
+
name: str
|
|
296
|
+
arguments: dict[str, Any] = field(default_factory=dict)
|
|
297
|
+
_: KW_ONLY
|
|
298
|
+
#: Google-specific opaque signature for reusing thought context.
|
|
299
|
+
thought_signature: str | None = None
|
|
300
|
+
#: OpenAI Responses namespace for calls to dynamically loaded or namespaced tools.
|
|
301
|
+
namespace: str | None = None
|
|
302
|
+
#: Set when the model's complete arguments text was not a valid JSON object; ``arguments`` is then ``{}``.
|
|
303
|
+
arguments_error: str | None = None
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
AssistantContent: TypeAlias = TextContent | ThinkingContent | ToolCall
|
|
307
|
+
UserContent: TypeAlias = TextContent | ImageContent
|
|
308
|
+
ToolResultContent: TypeAlias = TextContent | ImageContent
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
# ---------------------------------------------------------------------------
|
|
312
|
+
# Usage
|
|
313
|
+
# ---------------------------------------------------------------------------
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@dataclass(slots=True)
|
|
317
|
+
class UsageCost:
|
|
318
|
+
input: float = 0.0
|
|
319
|
+
output: float = 0.0
|
|
320
|
+
cache_read: float = 0.0
|
|
321
|
+
cache_write: float = 0.0
|
|
322
|
+
total: float = 0.0
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@dataclass(slots=True)
|
|
326
|
+
class Usage:
|
|
327
|
+
"""Token counts and their cost in USD."""
|
|
328
|
+
|
|
329
|
+
input: int = 0
|
|
330
|
+
output: int = 0
|
|
331
|
+
cache_read: int = 0
|
|
332
|
+
cache_write: int = 0
|
|
333
|
+
total_tokens: int = 0
|
|
334
|
+
cost: UsageCost = field(default_factory=UsageCost)
|
|
335
|
+
_: KW_ONLY
|
|
336
|
+
#: Subset of ``cache_write`` written with 1h retention. Only Anthropic reports this split.
|
|
337
|
+
cache_write_1h: int | None = None
|
|
338
|
+
#: Reasoning tokens, a subset of ``output``, when the provider reports them.
|
|
339
|
+
reasoning: int | None = None
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
# ---------------------------------------------------------------------------
|
|
343
|
+
# Tools
|
|
344
|
+
# ---------------------------------------------------------------------------
|
|
345
|
+
|
|
346
|
+
GrammarFormat: TypeAlias = Literal["openai_lark", "openai_regex"]
|
|
347
|
+
GrammarVariants: TypeAlias = dict[str, str]
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
class ConstrainedSamplingConfig(TypedDict, total=False):
|
|
351
|
+
"""Provider-side constrained sampling for a tool.
|
|
352
|
+
|
|
353
|
+
``{"type": "json_schema", "strict": "prefer" | "require"}`` or
|
|
354
|
+
``{"type": "grammar", "variants": {"openai_lark": "..."}}``.
|
|
355
|
+
"""
|
|
356
|
+
|
|
357
|
+
type: Literal["json_schema", "grammar"]
|
|
358
|
+
strict: Literal["prefer", "require"]
|
|
359
|
+
variants: GrammarVariants
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@dataclass(slots=True)
|
|
363
|
+
class Tool:
|
|
364
|
+
"""A tool the model may call.
|
|
365
|
+
|
|
366
|
+
``parameters`` is a JSON Schema object (build it with :class:`pi_ai.Type`) or a
|
|
367
|
+
Pydantic model class. Pydantic models are converted to JSON Schema for the
|
|
368
|
+
provider and validated arguments are returned as model instances.
|
|
369
|
+
"""
|
|
370
|
+
|
|
371
|
+
name: str
|
|
372
|
+
description: str = ""
|
|
373
|
+
parameters: Any = field(default_factory=lambda: {"type": "object", "properties": {}})
|
|
374
|
+
_: KW_ONLY
|
|
375
|
+
constrained_sampling: ConstrainedSamplingConfig | Literal[False] | None = None
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
@dataclass(slots=True)
|
|
379
|
+
class ToolReference:
|
|
380
|
+
name: str
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
# ---------------------------------------------------------------------------
|
|
384
|
+
# Messages
|
|
385
|
+
# ---------------------------------------------------------------------------
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
@dataclass(slots=True)
|
|
389
|
+
class DeferredHandle:
|
|
390
|
+
provider: str
|
|
391
|
+
model_id: str
|
|
392
|
+
api: str
|
|
393
|
+
#: Provider token, such as a response id or batch id plus row id.
|
|
394
|
+
id: str
|
|
395
|
+
_: KW_ONLY
|
|
396
|
+
expires_at: int | None = None
|
|
397
|
+
poll_after_ms: int | None = None
|
|
398
|
+
#: Provider conversion data required to reconstruct the final assistant message.
|
|
399
|
+
data: Any = None
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
@dataclass(slots=True)
|
|
403
|
+
class SystemMessage:
|
|
404
|
+
"""System instructions and tool declarations at one point in the transcript.
|
|
405
|
+
|
|
406
|
+
The leading system message is the system prompt. Later system messages change it:
|
|
407
|
+
``content`` adds instructions from that point on, ``sections`` replace or remove
|
|
408
|
+
named prompt sections (``None`` removes one), and ``tools_added``/``tools_removed``
|
|
409
|
+
change the tool set. Replaying every system message in order yields the current
|
|
410
|
+
prompt and tools.
|
|
411
|
+
"""
|
|
412
|
+
|
|
413
|
+
role: ClassVar[Literal["system"]] = "system"
|
|
414
|
+
content: str | list[TextContent] = ""
|
|
415
|
+
_: KW_ONLY
|
|
416
|
+
sections: dict[str, str | None] | None = None
|
|
417
|
+
tools_added: list[Tool] | None = None
|
|
418
|
+
tools_removed: list[ToolReference] | None = None
|
|
419
|
+
timestamp: int = field(default_factory=now_ms)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@dataclass(slots=True)
|
|
423
|
+
class UserMessage:
|
|
424
|
+
role: ClassVar[Literal["user"]] = "user"
|
|
425
|
+
content: str | list[TextContent | ImageContent] = ""
|
|
426
|
+
_: KW_ONLY
|
|
427
|
+
timestamp: int = field(default_factory=now_ms)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
@dataclass(slots=True)
|
|
431
|
+
class AssistantMessage:
|
|
432
|
+
role: ClassVar[Literal["assistant"]] = "assistant"
|
|
433
|
+
content: list[TextContent | ThinkingContent | ToolCall] = field(default_factory=list)
|
|
434
|
+
_: KW_ONLY
|
|
435
|
+
api: str
|
|
436
|
+
provider: str
|
|
437
|
+
model: str
|
|
438
|
+
usage: Usage = field(default_factory=Usage)
|
|
439
|
+
stop_reason: StopReason = "stop"
|
|
440
|
+
#: Unix timestamp in milliseconds when the request started.
|
|
441
|
+
timestamp: int = field(default_factory=now_ms)
|
|
442
|
+
#: Concrete model reported by the provider when different from the requested ``model``.
|
|
443
|
+
response_model: str | None = None
|
|
444
|
+
#: Provider-specific response/message identifier when the upstream API exposes one.
|
|
445
|
+
response_id: str | None = None
|
|
446
|
+
#: Exact provider-native effort level used for this response.
|
|
447
|
+
provider_thinking_level: str | None = None
|
|
448
|
+
#: Pi thinking level the agent loop requested for this response.
|
|
449
|
+
thinking_level: ModelThinkingLevel | None = None
|
|
450
|
+
diagnostics: list[AssistantMessageDiagnostic] | None = None
|
|
451
|
+
deferred: DeferredHandle | None = None
|
|
452
|
+
error_message: str | None = None
|
|
453
|
+
raw_stop_reason: str | None = None
|
|
454
|
+
#: Provider indication of whether the model explicitly ended its turn.
|
|
455
|
+
end_turn: bool | None = None
|
|
456
|
+
#: Milliseconds from ``timestamp`` until the response ended (monotonic clock).
|
|
457
|
+
duration_ms: int | None = None
|
|
458
|
+
|
|
459
|
+
def shallow_copy(self) -> AssistantMessage:
|
|
460
|
+
"""Copy the message object (not its content blocks), like ``{ ...message }`` in TypeScript."""
|
|
461
|
+
copied = object.__new__(AssistantMessage)
|
|
462
|
+
for name in _ASSISTANT_SLOTS:
|
|
463
|
+
object.__setattr__(copied, name, getattr(self, name))
|
|
464
|
+
copied.content = list(self.content)
|
|
465
|
+
return copied
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
_ASSISTANT_SLOTS: tuple[str, ...] = AssistantMessage.__slots__ # type: ignore[attr-defined]
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
@dataclass(slots=True)
|
|
472
|
+
class NestedToolCallRecord:
|
|
473
|
+
"""A tool call that another tool made while it ran (for example from a codemode script)."""
|
|
474
|
+
|
|
475
|
+
id: str
|
|
476
|
+
name: str
|
|
477
|
+
status: Literal["ok", "error", "unfinished"]
|
|
478
|
+
_: KW_ONLY
|
|
479
|
+
arguments: dict[str, Any] | None = None
|
|
480
|
+
arguments_bytes: int | None = None
|
|
481
|
+
duration_ms: int | None = None
|
|
482
|
+
error: str | None = None
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
@dataclass(slots=True)
|
|
486
|
+
class NestedToolCalls:
|
|
487
|
+
calls: list[NestedToolCallRecord] = field(default_factory=list)
|
|
488
|
+
complete: bool = True
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
@dataclass(slots=True)
|
|
492
|
+
class ToolResultMessage:
|
|
493
|
+
role: ClassVar[Literal["toolResult"]] = "toolResult"
|
|
494
|
+
tool_call_id: str
|
|
495
|
+
tool_name: str
|
|
496
|
+
content: list[TextContent | ImageContent] = field(default_factory=list)
|
|
497
|
+
is_error: bool = False
|
|
498
|
+
_: KW_ONLY
|
|
499
|
+
details: Any = None
|
|
500
|
+
#: Usage from the tool execution itself. Not part of the main LLM context accounting.
|
|
501
|
+
usage: Usage | None = None
|
|
502
|
+
#: Calls this tool made to other tools. Kept for the session record; not sent to the model.
|
|
503
|
+
nested_calls: NestedToolCalls | None = None
|
|
504
|
+
timestamp: int = field(default_factory=now_ms)
|
|
505
|
+
#: Milliseconds the tool's execution took.
|
|
506
|
+
duration_ms: int | None = None
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
Message: TypeAlias = SystemMessage | UserMessage | AssistantMessage | ToolResultMessage
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
@dataclass(slots=True)
|
|
513
|
+
class Context:
|
|
514
|
+
"""Request input accepted by the public stream entry points.
|
|
515
|
+
|
|
516
|
+
``system_prompt`` and ``tools`` are shorthand for a leading system message;
|
|
517
|
+
:func:`pi_ai.normalize_context` folds them into one before the request reaches
|
|
518
|
+
a provider.
|
|
519
|
+
"""
|
|
520
|
+
|
|
521
|
+
messages: list[Message] = field(default_factory=list)
|
|
522
|
+
system_prompt: str | None = None
|
|
523
|
+
tools: list[Tool] | None = None
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
class TranscriptContext:
|
|
527
|
+
"""Normalized request context passed to providers and API implementations.
|
|
528
|
+
|
|
529
|
+
The prompt and tool declarations are carried by the transcript's system
|
|
530
|
+
messages. Only :func:`pi_ai.normalize_context` (and the transcript helpers)
|
|
531
|
+
produce this type, so a raw :class:`Context` cannot reach provider code by
|
|
532
|
+
accident.
|
|
533
|
+
"""
|
|
534
|
+
|
|
535
|
+
__slots__ = ("messages",)
|
|
536
|
+
|
|
537
|
+
def __init__(self, messages: list[Message]) -> None:
|
|
538
|
+
self.messages = messages
|
|
539
|
+
|
|
540
|
+
def __repr__(self) -> str:
|
|
541
|
+
return f"TranscriptContext(messages={self.messages!r})"
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
# ---------------------------------------------------------------------------
|
|
545
|
+
# Stream events
|
|
546
|
+
# ---------------------------------------------------------------------------
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
@dataclass(slots=True)
|
|
550
|
+
class StartEvent:
|
|
551
|
+
type: ClassVar[Literal["start"]] = "start"
|
|
552
|
+
partial: AssistantMessage
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
@dataclass(slots=True)
|
|
556
|
+
class TextStartEvent:
|
|
557
|
+
type: ClassVar[Literal["text_start"]] = "text_start"
|
|
558
|
+
content_index: int
|
|
559
|
+
partial: AssistantMessage
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
@dataclass(slots=True)
|
|
563
|
+
class TextDeltaEvent:
|
|
564
|
+
type: ClassVar[Literal["text_delta"]] = "text_delta"
|
|
565
|
+
content_index: int
|
|
566
|
+
delta: str
|
|
567
|
+
partial: AssistantMessage
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
@dataclass(slots=True)
|
|
571
|
+
class TextEndEvent:
|
|
572
|
+
type: ClassVar[Literal["text_end"]] = "text_end"
|
|
573
|
+
content_index: int
|
|
574
|
+
content: str
|
|
575
|
+
partial: AssistantMessage
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
@dataclass(slots=True)
|
|
579
|
+
class ThinkingStartEvent:
|
|
580
|
+
type: ClassVar[Literal["thinking_start"]] = "thinking_start"
|
|
581
|
+
content_index: int
|
|
582
|
+
partial: AssistantMessage
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
@dataclass(slots=True)
|
|
586
|
+
class ThinkingDeltaEvent:
|
|
587
|
+
type: ClassVar[Literal["thinking_delta"]] = "thinking_delta"
|
|
588
|
+
content_index: int
|
|
589
|
+
delta: str
|
|
590
|
+
partial: AssistantMessage
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
@dataclass(slots=True)
|
|
594
|
+
class ThinkingEndEvent:
|
|
595
|
+
type: ClassVar[Literal["thinking_end"]] = "thinking_end"
|
|
596
|
+
content_index: int
|
|
597
|
+
content: str
|
|
598
|
+
partial: AssistantMessage
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
@dataclass(slots=True)
|
|
602
|
+
class ToolCallStartEvent:
|
|
603
|
+
type: ClassVar[Literal["toolcall_start"]] = "toolcall_start"
|
|
604
|
+
content_index: int
|
|
605
|
+
partial: AssistantMessage
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
@dataclass(slots=True)
|
|
609
|
+
class ToolCallDeltaEvent:
|
|
610
|
+
type: ClassVar[Literal["toolcall_delta"]] = "toolcall_delta"
|
|
611
|
+
content_index: int
|
|
612
|
+
delta: str
|
|
613
|
+
partial: AssistantMessage
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
@dataclass(slots=True)
|
|
617
|
+
class ToolCallEndEvent:
|
|
618
|
+
type: ClassVar[Literal["toolcall_end"]] = "toolcall_end"
|
|
619
|
+
content_index: int
|
|
620
|
+
tool_call: ToolCall
|
|
621
|
+
partial: AssistantMessage
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
@dataclass(slots=True)
|
|
625
|
+
class DoneEvent:
|
|
626
|
+
type: ClassVar[Literal["done"]] = "done"
|
|
627
|
+
reason: Literal["stop", "length", "toolUse", "deferred"]
|
|
628
|
+
message: AssistantMessage
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
@dataclass(slots=True)
|
|
632
|
+
class ErrorEvent:
|
|
633
|
+
type: ClassVar[Literal["error"]] = "error"
|
|
634
|
+
reason: Literal["aborted", "error"]
|
|
635
|
+
error: AssistantMessage
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
AssistantMessageEvent: TypeAlias = (
|
|
639
|
+
StartEvent
|
|
640
|
+
| TextStartEvent
|
|
641
|
+
| TextDeltaEvent
|
|
642
|
+
| TextEndEvent
|
|
643
|
+
| ThinkingStartEvent
|
|
644
|
+
| ThinkingDeltaEvent
|
|
645
|
+
| ThinkingEndEvent
|
|
646
|
+
| ToolCallStartEvent
|
|
647
|
+
| ToolCallDeltaEvent
|
|
648
|
+
| ToolCallEndEvent
|
|
649
|
+
| DoneEvent
|
|
650
|
+
| ErrorEvent
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
# ---------------------------------------------------------------------------
|
|
655
|
+
# Models
|
|
656
|
+
# ---------------------------------------------------------------------------
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
@dataclass(slots=True)
|
|
660
|
+
class ModelCostRates:
|
|
661
|
+
"""USD per million tokens."""
|
|
662
|
+
|
|
663
|
+
input: float = 0.0
|
|
664
|
+
output: float = 0.0
|
|
665
|
+
cache_read: float = 0.0
|
|
666
|
+
cache_write: float = 0.0
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
@dataclass(slots=True)
|
|
670
|
+
class ModelCostTier:
|
|
671
|
+
"""Request-wide pricing tier used when total request input exceeds ``input_tokens_above``."""
|
|
672
|
+
|
|
673
|
+
input_tokens_above: int
|
|
674
|
+
input: float = 0.0
|
|
675
|
+
output: float = 0.0
|
|
676
|
+
cache_read: float = 0.0
|
|
677
|
+
cache_write: float = 0.0
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
@dataclass(slots=True)
|
|
681
|
+
class ModelCost:
|
|
682
|
+
"""USD per million tokens, with optional request-wide pricing tiers."""
|
|
683
|
+
|
|
684
|
+
input: float = 0.0
|
|
685
|
+
output: float = 0.0
|
|
686
|
+
cache_read: float = 0.0
|
|
687
|
+
cache_write: float = 0.0
|
|
688
|
+
tiers: list[ModelCostTier] | None = None
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
@dataclass(slots=True)
|
|
692
|
+
class ModelImageResizeOptions:
|
|
693
|
+
max_width: int | None = None
|
|
694
|
+
max_height: int | None = None
|
|
695
|
+
#: Maximum base64-encoded payload size in bytes.
|
|
696
|
+
max_bytes: int | None = None
|
|
697
|
+
jpeg_quality: int | None = None
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
@dataclass(slots=True)
|
|
701
|
+
class ModelImageInputLimits:
|
|
702
|
+
resize: ModelImageResizeOptions | None = None
|
|
703
|
+
max_per_message: int | None = None
|
|
704
|
+
max_per_request: int | None = None
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
@dataclass(slots=True)
|
|
708
|
+
class ModelInputLimits:
|
|
709
|
+
#: Maximum serialized provider request size in bytes.
|
|
710
|
+
max_request_bytes: int | None = None
|
|
711
|
+
images: ModelImageInputLimits | None = None
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
@dataclass(slots=True, kw_only=True)
|
|
715
|
+
class BaseModel:
|
|
716
|
+
"""Fields shared by every catalog entry, regardless of what you can do with it."""
|
|
717
|
+
|
|
718
|
+
type: ClassVar[ModelType] = "chat"
|
|
719
|
+
id: str
|
|
720
|
+
name: str = ""
|
|
721
|
+
api: str
|
|
722
|
+
provider: str
|
|
723
|
+
base_url: str = ""
|
|
724
|
+
input: list[str] = field(default_factory=lambda: ["text"])
|
|
725
|
+
cost: ModelCost = field(default_factory=ModelCost)
|
|
726
|
+
input_limits: ModelInputLimits | None = None
|
|
727
|
+
headers: dict[str, str] | None = None
|
|
728
|
+
|
|
729
|
+
def __post_init__(self) -> None:
|
|
730
|
+
if not self.name:
|
|
731
|
+
self.name = self.id
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
@dataclass(slots=True, kw_only=True)
|
|
735
|
+
class Model(BaseModel):
|
|
736
|
+
"""Chat model: usable with ``stream()`` and friends.
|
|
737
|
+
|
|
738
|
+
``compat`` holds API-specific compatibility overrides as a plain dict using pi's
|
|
739
|
+
JSON keys (``{"supportsDeveloperRole": False}``), so ``models.json`` configuration
|
|
740
|
+
is interchangeable with the TypeScript implementation.
|
|
741
|
+
"""
|
|
742
|
+
|
|
743
|
+
type: ClassVar[Literal["chat"]] = "chat"
|
|
744
|
+
reasoning: bool = False
|
|
745
|
+
context_window: int = 128_000
|
|
746
|
+
max_tokens: int = 16_384
|
|
747
|
+
#: Maps pi thinking levels to provider/model-specific values. ``None`` marks a level unsupported.
|
|
748
|
+
thinking_level_map: dict[str, str | None] | None = None
|
|
749
|
+
#: Prompt cache lifetimes in seconds per retention tier (``{"short": 300, "long": 3600}``).
|
|
750
|
+
prompt_cache: dict[str, float] | None = None
|
|
751
|
+
#: Default sampling parameters; per-request ``sampling_params`` override these per key.
|
|
752
|
+
sampling_params: SamplingParams | None = None
|
|
753
|
+
#: Sampling parameter overrides selected by the effective pi thinking level.
|
|
754
|
+
sampling_params_by_thinking_level: dict[str, SamplingParams] | None = None
|
|
755
|
+
#: Compatibility overrides for the model's API. Unset fields are auto-detected.
|
|
756
|
+
compat: dict[str, Any] | None = None
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
@dataclass(slots=True, kw_only=True)
|
|
760
|
+
class ImageModel(BaseModel):
|
|
761
|
+
"""Image-generation model: usable with ``generate_images()`` only."""
|
|
762
|
+
|
|
763
|
+
type: ClassVar[Literal["image"]] = "image"
|
|
764
|
+
#: Output modalities. Always includes ``"image"``; ``"text"`` means the model can also return text blocks.
|
|
765
|
+
output: list[str] = field(default_factory=lambda: ["image"])
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
@dataclass(slots=True, kw_only=True)
|
|
769
|
+
class ClassifierModel(BaseModel):
|
|
770
|
+
"""Structured classifier model: usable with ``classify()`` only."""
|
|
771
|
+
|
|
772
|
+
type: ClassVar[Literal["classifier"]] = "classifier"
|
|
773
|
+
context_window: int = 0
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
AnyModel: TypeAlias = Model | ImageModel | ClassifierModel
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
# ---------------------------------------------------------------------------
|
|
780
|
+
# Image generation
|
|
781
|
+
# ---------------------------------------------------------------------------
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
@dataclass(slots=True)
|
|
785
|
+
class ImagesContext:
|
|
786
|
+
input: list[TextContent | ImageContent] = field(default_factory=list)
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
@dataclass(slots=True, kw_only=True)
|
|
790
|
+
class AssistantImages:
|
|
791
|
+
api: str
|
|
792
|
+
provider: str
|
|
793
|
+
model: str
|
|
794
|
+
output: list[TextContent | ImageContent] = field(default_factory=list)
|
|
795
|
+
response_id: str | None = None
|
|
796
|
+
usage: Usage | None = None
|
|
797
|
+
stop_reason: Literal["stop", "error", "aborted"] = "stop"
|
|
798
|
+
error_message: str | None = None
|
|
799
|
+
timestamp: int = field(default_factory=now_ms)
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
# ---------------------------------------------------------------------------
|
|
803
|
+
# Classification
|
|
804
|
+
# ---------------------------------------------------------------------------
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
@dataclass(slots=True)
|
|
808
|
+
class ClassifierChoiceQuestion:
|
|
809
|
+
type: ClassVar[Literal["choice"]] = "choice"
|
|
810
|
+
instructions: str
|
|
811
|
+
criteria: dict[str, str]
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
@dataclass(slots=True)
|
|
815
|
+
class ClassifierScoreQuestion:
|
|
816
|
+
type: ClassVar[Literal["score"]] = "score"
|
|
817
|
+
instructions: str
|
|
818
|
+
criteria: list[str]
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
@dataclass(slots=True)
|
|
822
|
+
class ClassifierBoolQuestion:
|
|
823
|
+
type: ClassVar[Literal["bool"]] = "bool"
|
|
824
|
+
instructions: str
|
|
825
|
+
#: ``{"true": "...", "false": "..."}``
|
|
826
|
+
criteria: dict[str, str]
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
ClassifierQuestion: TypeAlias = ClassifierChoiceQuestion | ClassifierScoreQuestion | ClassifierBoolQuestion
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
@dataclass(slots=True)
|
|
833
|
+
class ClassifierContext:
|
|
834
|
+
state: dict[str, Any]
|
|
835
|
+
questions: dict[str, ClassifierQuestion]
|
|
836
|
+
_: KW_ONLY
|
|
837
|
+
#: Images judged together with ``state``. Only models whose ``input`` includes ``"image"`` accept them.
|
|
838
|
+
images: list[ImageContent] | None = None
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
@dataclass(slots=True)
|
|
842
|
+
class ClassifierChoiceAnswer:
|
|
843
|
+
type: ClassVar[Literal["choice"]] = "choice"
|
|
844
|
+
choice: str
|
|
845
|
+
probabilities: dict[str, float]
|
|
846
|
+
confidence: float
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
@dataclass(slots=True)
|
|
850
|
+
class ClassifierScoreAnswer:
|
|
851
|
+
type: ClassVar[Literal["score"]] = "score"
|
|
852
|
+
score: float
|
|
853
|
+
confidence: float
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
@dataclass(slots=True)
|
|
857
|
+
class ClassifierBoolAnswer:
|
|
858
|
+
type: ClassVar[Literal["bool"]] = "bool"
|
|
859
|
+
probability: float
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
ClassifierAnswer: TypeAlias = ClassifierChoiceAnswer | ClassifierScoreAnswer | ClassifierBoolAnswer
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
@dataclass(slots=True, kw_only=True)
|
|
866
|
+
class ClassifierResult:
|
|
867
|
+
api: str
|
|
868
|
+
provider: str
|
|
869
|
+
model: str
|
|
870
|
+
answers: dict[str, ClassifierAnswer] = field(default_factory=dict)
|
|
871
|
+
usage: Usage | None = None
|
|
872
|
+
stop_reason: Literal["stop", "error", "aborted"] = "stop"
|
|
873
|
+
error_message: str | None = None
|
|
874
|
+
timestamp: int = field(default_factory=now_ms)
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
# ---------------------------------------------------------------------------
|
|
878
|
+
# Options
|
|
879
|
+
# ---------------------------------------------------------------------------
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
@dataclass(slots=True)
|
|
883
|
+
class ProviderResponse:
|
|
884
|
+
status: int
|
|
885
|
+
headers: dict[str, str]
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
class ThinkingBudgets(TypedDict, total=False):
|
|
889
|
+
"""Token budgets for each thinking level (token-based providers only)."""
|
|
890
|
+
|
|
891
|
+
minimal: int
|
|
892
|
+
low: int
|
|
893
|
+
medium: int
|
|
894
|
+
high: int
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
MaybeAwaitable: TypeAlias = "Any | Awaitable[Any]"
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
class ProviderRequestOptions(TypedDict, total=False):
|
|
901
|
+
"""Authentication, HTTP transport, and lifecycle callbacks shared by provider requests.
|
|
902
|
+
|
|
903
|
+
Every public entry point also accepts these as keyword arguments.
|
|
904
|
+
"""
|
|
905
|
+
|
|
906
|
+
signal: AbortSignal
|
|
907
|
+
api_key: str
|
|
908
|
+
#: Custom ``httpx.AsyncClient`` for provider HTTP requests (the analog of pi's ``fetch`` option).
|
|
909
|
+
http_client: httpx.AsyncClient
|
|
910
|
+
#: Provider-scoped environment values; take precedence over ``os.environ``.
|
|
911
|
+
env: ProviderEnv
|
|
912
|
+
#: Inspect or replace the provider payload before sending. Return ``None`` to keep it unchanged.
|
|
913
|
+
on_payload: Callable[[Any, Any], Any]
|
|
914
|
+
#: Called after an HTTP response is received, before its body is consumed.
|
|
915
|
+
on_response: Callable[[ProviderResponse, Any], Any]
|
|
916
|
+
#: Extra headers merged over provider defaults. ``None`` values suppress a default header.
|
|
917
|
+
headers: ProviderHeaders
|
|
918
|
+
#: HTTP request timeout in milliseconds.
|
|
919
|
+
timeout_ms: float
|
|
920
|
+
#: Maximum client-side retry attempts for retryable HTTP failures (default 0 at this layer).
|
|
921
|
+
max_retries: int
|
|
922
|
+
#: Fail instead of waiting when the server requests a retry delay above this (default 60000; 0 disables).
|
|
923
|
+
max_retry_delay_ms: float
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
class StreamOptions(ProviderRequestOptions, total=False):
|
|
927
|
+
#: Observe each parsed provider stream event before pi normalizes it. Treat as read-only.
|
|
928
|
+
on_provider_stream_event: Callable[[Any, Any], Any]
|
|
929
|
+
temperature: float
|
|
930
|
+
#: Extra sampling parameters merged into OpenAI-compatible request bodies (``top_p``, ``top_k``, ...).
|
|
931
|
+
sampling_params: SamplingParams
|
|
932
|
+
max_tokens: int
|
|
933
|
+
transport: Transport
|
|
934
|
+
#: Prompt cache retention preference. Default ``"short"``.
|
|
935
|
+
cache_retention: CacheRetention
|
|
936
|
+
#: Session identifier for session-based caching and routing.
|
|
937
|
+
session_id: str
|
|
938
|
+
websocket_connect_timeout_ms: float
|
|
939
|
+
#: Request metadata. Providers extract the fields they understand (e.g. Anthropic ``user_id``).
|
|
940
|
+
metadata: dict[str, Any]
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
class SimpleStreamOptions(StreamOptions, total=False):
|
|
944
|
+
#: Provider-neutral tool selection.
|
|
945
|
+
tool_choice: ToolChoice
|
|
946
|
+
reasoning: ThinkingLevel
|
|
947
|
+
#: Ask a capable provider to return a durable handle and continue asynchronously.
|
|
948
|
+
deferred: bool | dict[str, str]
|
|
949
|
+
thinking_budgets: ThinkingBudgets
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
class DeferredFetchOptions(ProviderRequestOptions, total=False):
|
|
953
|
+
#: Maximum provider long-poll duration in milliseconds. 0 performs one status check.
|
|
954
|
+
wait: float
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
DeferredCancelOptions: TypeAlias = ProviderRequestOptions
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
class ImagesOptions(ProviderRequestOptions, total=False):
|
|
961
|
+
metadata: dict[str, Any]
|
|
962
|
+
|
|
963
|
+
|
|
964
|
+
class ClassifierOptions(ProviderRequestOptions, total=False):
|
|
965
|
+
#: Divides answer logits before normalization. Values above 1 soften the distribution.
|
|
966
|
+
temperature: float
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
# ---------------------------------------------------------------------------
|
|
970
|
+
# Implementation contracts
|
|
971
|
+
# ---------------------------------------------------------------------------
|
|
972
|
+
|
|
973
|
+
StreamFunction: TypeAlias = Callable[
|
|
974
|
+
[Model, TranscriptContext, "Mapping[str, Any] | None"], "AssistantMessageEventStream"
|
|
975
|
+
]
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
class ProviderStreams(Protocol):
|
|
979
|
+
"""The stream contract of an API implementation module.
|
|
980
|
+
|
|
981
|
+
Every module under ``pi_ai.api`` exposes ``stream`` and ``stream_simple``; capable
|
|
982
|
+
modules also expose ``fetch_deferred`` and ``cancel_deferred``.
|
|
983
|
+
"""
|
|
984
|
+
|
|
985
|
+
def stream(
|
|
986
|
+
self, model: Model, context: TranscriptContext, options: Mapping[str, Any] | None = None
|
|
987
|
+
) -> AssistantMessageEventStream: ...
|
|
988
|
+
|
|
989
|
+
def stream_simple(
|
|
990
|
+
self, model: Model, context: TranscriptContext, options: Mapping[str, Any] | None = None
|
|
991
|
+
) -> AssistantMessageEventStream: ...
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
class ProviderImages(Protocol):
|
|
995
|
+
async def generate_images(
|
|
996
|
+
self, model: ImageModel, context: ImagesContext, options: Mapping[str, Any] | None = None
|
|
997
|
+
) -> AssistantImages: ...
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
class ProviderClassifier(Protocol):
|
|
1001
|
+
async def classify(
|
|
1002
|
+
self, model: ClassifierModel, context: ClassifierContext, options: Mapping[str, Any] | None = None
|
|
1003
|
+
) -> ClassifierResult: ...
|