agy تنها ارائهدهندهٔ مدل؛ حذف بخش ارائهدهندهها و افزودن زنجیرهٔ انتخاب مدل
- حذف کامل providerهای mock و openai_compatible: دامنه، آداپوترها، رجیستری، کانفیگ، seed و رابطٔ مدیریت - بخش «ارائهدهندهها» از کنسول حذف شد؛ ورود اکانتها فقط از صفحهٔ اکانتهای agy - صفحهٔ مدلها بازطراحی شد: ارائهدهنده → اکانت → مدل (کاتالوگ لحظهای و زندهٔ همان اکانت از Google) → افورت استدلال؛ بدون هیچ ورود دستی نام مدل - مدلها ستون reasoning_effort گرفتند؛ آداپتور آن را به thinkingConfig (بودجهٔ ۱۰۲۴ تا ۳۲۷۶۸) ترجمه میکند و فقط به مدلهای استدلالی میفرستد - مهاجرت c2d3e4f5b6a7: افزودن reasoning_effort و حذف ردیفهای providerهای قدیمی - تستها: ۲۴۶ تست بکاند و ۵۱ تست فرانت سبز؛ ruff و mypy و tsc و eslint تمیز
This commit is contained in:
@@ -91,6 +91,33 @@ SAFETY_SETTINGS: list[dict[str, str]] = [
|
||||
#: manual paste must fit inside it comfortably.
|
||||
CHALLENGE_TTL_SECONDS = 600
|
||||
|
||||
#: agy ``reasoning_effort`` → thinking budget (tokens), mirroring the Antigravity
|
||||
#: client. An unknown value falls back to ``medium`` server-side, and the UI
|
||||
#: only ever offers these five.
|
||||
EFFORT_THINKING_BUDGETS: dict[str, int] = {
|
||||
"minimal": 1024,
|
||||
"low": 4096,
|
||||
"medium": 8192,
|
||||
"high": 16384,
|
||||
"xhigh": 32768,
|
||||
}
|
||||
|
||||
#: Where thinking is honoured. A request must never carry ``thinkingConfig`` to
|
||||
#: a model that does not support it — that is a hard 400 from the backend.
|
||||
_THINKING_MARKERS: tuple[str, ...] = (
|
||||
"thinking",
|
||||
"gemini-3",
|
||||
"gemini-2.5-pro",
|
||||
"gemini-2.5-flash",
|
||||
)
|
||||
|
||||
|
||||
def _supports_thinking(model_name: str) -> bool:
|
||||
lowered = model_name.lower()
|
||||
if "image" in lowered:
|
||||
return False
|
||||
return any(marker in lowered for marker in _THINKING_MARKERS)
|
||||
|
||||
_StatusFn = Callable[..., Awaitable[None]]
|
||||
_LoaderFn = Callable[[], Awaitable[list[ModelCredential]]]
|
||||
|
||||
@@ -603,7 +630,25 @@ class AgyModelAdapter:
|
||||
accounts = [a for a in (_account_of(c) for c in await self._load()) if a.refresh_token]
|
||||
if not accounts:
|
||||
return []
|
||||
token = await self._access_token(accounts[0])
|
||||
return await self._fetch_models(accounts[0])
|
||||
|
||||
async def available_models_for(self, credential: ModelCredential) -> list[str]:
|
||||
"""Live model catalog of exactly one account.
|
||||
|
||||
Called per account selection in the admin console, so the list is
|
||||
fetched fresh from Google every time (the access token is cached, the
|
||||
``fetchAvailableModels`` round-trip is not) and reflects what *this*
|
||||
subscription is entitled to.
|
||||
"""
|
||||
account = _account_of(credential)
|
||||
if not account.refresh_token:
|
||||
raise ProviderUnavailableError(
|
||||
"این اکانت اطلاعات ورود ندارد؛ یک بار دیگر لاگین کنید."
|
||||
)
|
||||
return await self._fetch_models(account)
|
||||
|
||||
async def _fetch_models(self, account: AgyAccount) -> list[str]:
|
||||
token = await self._access_token(account)
|
||||
for base in BASE_URLS:
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
@@ -713,7 +758,8 @@ class AgyModelAdapter:
|
||||
) -> ModelResponse:
|
||||
token = await self._access_token(account)
|
||||
payload = _build_generate_payload(
|
||||
account, model.internal_name, messages, system, temperature, max_tokens
|
||||
account, model.internal_name, messages, system, temperature, max_tokens,
|
||||
reasoning_effort=model.reasoning_effort,
|
||||
)
|
||||
headers = {
|
||||
"Authorization": f"Bearer {token}",
|
||||
@@ -775,6 +821,7 @@ def _build_generate_payload(
|
||||
system: str,
|
||||
temperature: float,
|
||||
max_tokens: int,
|
||||
reasoning_effort: str = "",
|
||||
) -> dict:
|
||||
"""Translate the port's OpenAI-shaped call into the Antigravity envelope."""
|
||||
system_parts: list[str] = [system] if system else []
|
||||
@@ -786,13 +833,27 @@ def _build_generate_payload(
|
||||
role = "model" if message.role == "assistant" else "user"
|
||||
contents.append({"role": role, "parts": [{"text": message.content}]})
|
||||
|
||||
generation: dict = {
|
||||
"temperature": temperature,
|
||||
"maxOutputTokens": max_tokens,
|
||||
}
|
||||
# Reasoning effort is only sent to models that can think; the backend
|
||||
# rejects ``thinkingConfig`` everywhere else with a 400.
|
||||
if reasoning_effort and _supports_thinking(model_name):
|
||||
budget = EFFORT_THINKING_BUDGETS.get(reasoning_effort, 8192)
|
||||
generation["thinkingConfig"] = {
|
||||
"thinkingBudget": budget,
|
||||
"includeThoughts": True,
|
||||
}
|
||||
if max_tokens <= budget:
|
||||
# The budget must fit inside the answer window or the thinking
|
||||
# model has nowhere to emit its final answer.
|
||||
generation["maxOutputTokens"] = budget + 8000
|
||||
|
||||
request: dict = {
|
||||
"contents": contents,
|
||||
"sessionId": _random_session_id(),
|
||||
"generationConfig": {
|
||||
"temperature": temperature,
|
||||
"maxOutputTokens": max_tokens,
|
||||
},
|
||||
"generationConfig": generation,
|
||||
"toolConfig": {"functionCallingConfig": {"mode": "VALIDATED"}},
|
||||
"safetySettings": SAFETY_SETTINGS,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user