agy تنها ارائهدهندهٔ مدل؛ حذف بخش ارائهدهندهها و افزودن زنجیرهٔ انتخاب مدل
- حذف کامل providerهای mock و openai_compatible: دامنه، آداپوترها، رجیستری، کانفیگ، seed و رابطٔ مدیریت - بخش «ارائهدهندهها» از کنسول حذف شد؛ ورود اکانتها فقط از صفحهٔ اکانتهای agy - صفحهٔ مدلها بازطراحی شد: ارائهدهنده → اکانت → مدل (کاتالوگ لحظهای و زندهٔ همان اکانت از Google) → افورت استدلال؛ بدون هیچ ورود دستی نام مدل - مدلها ستون reasoning_effort گرفتند؛ آداپتور آن را به thinkingConfig (بودجهٔ ۱۰۲۴ تا ۳۲۷۶۸) ترجمه میکند و فقط به مدلهای استدلالی میفرستد - مهاجرت c2d3e4f5b6a7: افزودن reasoning_effort و حذف ردیفهای providerهای قدیمی - تستها: ۲۴۶ تست بکاند و ۵۱ تست فرانت سبز؛ ruff و mypy و tsc و eslint تمیز
This commit is contained in:
+34
-88
@@ -2,9 +2,9 @@
|
||||
|
||||
Run with ``pnpm db:seed``. Creates a platform owner, a demo user, a sample
|
||||
organization that both of them own, the system agent templates, routing
|
||||
policies, rate cards and a mock provider with two models. The agy (Google
|
||||
Antigravity) provider and its model catalog are seeded too; agy models become
|
||||
routable only when ``AI_DEFAULT_PROVIDER=agy``.
|
||||
policies, rate cards and the agy (Google Antigravity) provider with its model
|
||||
catalog. agy is the only model provider the platform talks to; accounts are
|
||||
added from the admin console.
|
||||
|
||||
Safe to run repeatedly: every step checks for existing rows first.
|
||||
|
||||
@@ -20,7 +20,6 @@ import logging
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.database import Base, SessionFactory, engine
|
||||
from app.core.security import hash_password
|
||||
from app.domain.ai_control.models import (
|
||||
@@ -64,14 +63,17 @@ DEMO_ORG_NAME = "سازمان نمونه"
|
||||
DEMO_ORG_SLUG = "sample-org"
|
||||
|
||||
#: The agy (Google Antigravity) model catalog. ``internal_name`` is the model id
|
||||
#: the Antigravity backend expects; the display name is admin-only. Seeded for
|
||||
#: every deployment, activated for routing only when agy is the default
|
||||
#: provider — see the seed step that consumes this table.
|
||||
AGY_MODELS: list[tuple[str, str, list[Capability]]] = [
|
||||
#: the Antigravity backend expects; the display name is admin-only. The 4th
|
||||
#: element is the reasoning effort (empty = provider default) — the console can
|
||||
#: change it per model, and the adapter only sends it to thinking-capable
|
||||
#: models. Seeded for every deployment and reconciled on *every* run, so a
|
||||
#: database created by an older version converges without being rebuilt.
|
||||
AGY_MODELS: list[tuple[str, str, list[Capability], str]] = [
|
||||
(
|
||||
"gemini-3-flash",
|
||||
"Gemini 3 Flash",
|
||||
[Capability.TEXT, Capability.CODE, Capability.LONG_CONTEXT],
|
||||
"",
|
||||
),
|
||||
(
|
||||
"gemini-3-pro-high",
|
||||
@@ -80,23 +82,27 @@ AGY_MODELS: list[tuple[str, str, list[Capability]]] = [
|
||||
Capability.TEXT, Capability.REASONING, Capability.CODE,
|
||||
Capability.VISION, Capability.LONG_CONTEXT,
|
||||
],
|
||||
"high",
|
||||
),
|
||||
(
|
||||
"claude-sonnet-4-5",
|
||||
"Claude Sonnet 4.5",
|
||||
[Capability.TEXT, Capability.CODE, Capability.TOOLS],
|
||||
"",
|
||||
),
|
||||
(
|
||||
"claude-sonnet-4-5-thinking",
|
||||
"Claude Sonnet 4.5 (Thinking)",
|
||||
[Capability.TEXT, Capability.CODE, Capability.REASONING],
|
||||
"high",
|
||||
),
|
||||
(
|
||||
"claude-opus-4-5-thinking",
|
||||
"Claude Opus 4.5 (Thinking)",
|
||||
[Capability.TEXT, Capability.REASONING, Capability.CODE, Capability.LONG_CONTEXT],
|
||||
"high",
|
||||
),
|
||||
("gpt-oss-120b-medium", "GPT-OSS 120B Medium", [Capability.TEXT, Capability.CODE]),
|
||||
("gpt-oss-120b-medium", "GPT-OSS 120B Medium", [Capability.TEXT, Capability.CODE], ""),
|
||||
]
|
||||
|
||||
AGENT_TEMPLATES: list[tuple[str, RoleKey, str, list[str]]] = [
|
||||
@@ -270,51 +276,11 @@ async def seed() -> None:
|
||||
# as either one lands on a populated dashboard rather than an empty one.
|
||||
await _ensure_org(session, members=[owner, demo])
|
||||
|
||||
if not (await session.execute(select(AiProviderRow))).scalars().first():
|
||||
provider = Provider(name="mock", type=ProviderType.MOCK, base_url="")
|
||||
session.add(
|
||||
AiProviderRow(
|
||||
id=provider.id, name=provider.name, type=provider.type.value,
|
||||
base_url=provider.base_url, is_active=True,
|
||||
)
|
||||
)
|
||||
await session.flush()
|
||||
for internal, display, caps in (
|
||||
("mock-fast", "Mock Fast", [Capability.TEXT]),
|
||||
(
|
||||
"mock-reasoning",
|
||||
"Mock Reasoning",
|
||||
[Capability.TEXT, Capability.REASONING, Capability.CODE],
|
||||
),
|
||||
):
|
||||
model = AiModel(
|
||||
provider_id=provider.id,
|
||||
internal_name=internal,
|
||||
display_name_for_admin=display,
|
||||
capabilities=[str(c) for c in caps],
|
||||
input_price_per_1m=0.3,
|
||||
output_price_per_1m=1.2,
|
||||
)
|
||||
session.add(
|
||||
AiModelRow(
|
||||
id=model.id, provider_id=model.provider_id,
|
||||
internal_name=model.internal_name,
|
||||
display_name_for_admin=model.display_name_for_admin,
|
||||
capabilities=model.capabilities,
|
||||
input_price_per_1m=model.input_price_per_1m,
|
||||
output_price_per_1m=model.output_price_per_1m,
|
||||
is_active=True,
|
||||
)
|
||||
)
|
||||
logger.info("created mock provider + 2 models")
|
||||
|
||||
# The agy catalog (Google Antigravity). It is seeded for every
|
||||
# deployment — the admin console needs the provider and its models to
|
||||
# exist before any account is logged in. Routing activation is
|
||||
# reconciled on *every* seed run below, not only at insert time, so
|
||||
# flipping AI_DEFAULT_PROVIDER and re-seeding reshapes routing without
|
||||
# a fresh database.
|
||||
agy_is_default = settings.ai_default_provider.strip().lower() == "agy"
|
||||
# The agy catalog (Google Antigravity). agy is the only model provider,
|
||||
# so the provider row and its models are created on a first run and
|
||||
# reconciled on every later one: display names, capabilities, efforts
|
||||
# and activation follow this table, and a model removed here stays in
|
||||
# the database (neither table nor row deletion is safe for a seed).
|
||||
agy_provider_row = (
|
||||
await session.execute(
|
||||
select(AiProviderRow).where(AiProviderRow.name == "agy")
|
||||
@@ -328,7 +294,7 @@ async def seed() -> None:
|
||||
)
|
||||
session.add(agy_provider_row)
|
||||
await session.flush()
|
||||
for internal, display, caps in AGY_MODELS:
|
||||
for internal, display, caps, effort in AGY_MODELS:
|
||||
existing = (
|
||||
await session.execute(
|
||||
select(AiModelRow).where(
|
||||
@@ -338,6 +304,12 @@ async def seed() -> None:
|
||||
)
|
||||
).scalars().first()
|
||||
if existing is not None:
|
||||
# Reconcile: an older database has these rows with different
|
||||
# metadata (efforts did not exist, capabilities moved).
|
||||
existing.display_name_for_admin = display
|
||||
existing.capabilities = [str(c) for c in caps]
|
||||
existing.reasoning_effort = effort
|
||||
existing.is_active = True
|
||||
continue
|
||||
model = AiModel(
|
||||
provider_id=agy_provider_row.id,
|
||||
@@ -349,6 +321,7 @@ async def seed() -> None:
|
||||
# customer-facing price on the rate card.
|
||||
input_price_per_1m=0.0,
|
||||
output_price_per_1m=0.0,
|
||||
reasoning_effort=effort,
|
||||
)
|
||||
session.add(
|
||||
AiModelRow(
|
||||
@@ -358,41 +331,14 @@ async def seed() -> None:
|
||||
capabilities=model.capabilities,
|
||||
input_price_per_1m=model.input_price_per_1m,
|
||||
output_price_per_1m=model.output_price_per_1m,
|
||||
is_active=False,
|
||||
reasoning_effort=model.reasoning_effort,
|
||||
is_active=True,
|
||||
)
|
||||
)
|
||||
# Reconcile routing with the configured default on every run: agy models
|
||||
# are routable only when agy is the default, and the mock catalog must
|
||||
# not win when it is not — a model without accounts fails at call time.
|
||||
agy_model_rows = (
|
||||
await session.execute(
|
||||
select(AiModelRow).where(
|
||||
AiModelRow.provider_id == agy_provider_row.id
|
||||
)
|
||||
)
|
||||
).scalars()
|
||||
for row in agy_model_rows:
|
||||
row.is_active = agy_is_default
|
||||
if agy_is_default:
|
||||
mock_provider_row = (
|
||||
await session.execute(
|
||||
select(AiProviderRow).where(AiProviderRow.name == "mock")
|
||||
)
|
||||
).scalars().first()
|
||||
if mock_provider_row is not None:
|
||||
mock_model_rows = (
|
||||
await session.execute(
|
||||
select(AiModelRow).where(
|
||||
AiModelRow.provider_id == mock_provider_row.id
|
||||
)
|
||||
)
|
||||
).scalars()
|
||||
for row in mock_model_rows:
|
||||
row.is_active = False
|
||||
logger.info(
|
||||
"agy is the default provider; agy catalog activated "
|
||||
"(accounts are added from the admin console)"
|
||||
)
|
||||
logger.info(
|
||||
"agy is the only model provider; catalog active "
|
||||
"(accounts are added from the admin console)"
|
||||
)
|
||||
|
||||
if not (await session.execute(select(AgentTemplateRow))).scalars().first():
|
||||
for name, role_key, description, tools in AGENT_TEMPLATES:
|
||||
|
||||
Reference in New Issue
Block a user