优化默认语言模型获取

This commit is contained in:
Azmat@qq.com
2026-09-03 16:00:14 +08:00
parent 6a628b0ca7
commit f53b6e1035
6 changed files with 49 additions and 28 deletions
+3 -18
View File
@@ -520,25 +520,10 @@ def _parse_extracted_entities_response(text: str) -> tuple[list[dict], list[dict
return entities, seg_refs return entities, seg_refs
# 提取步固定锁定豆包 2.0 Pro(与脚本生成同款),不靠 get_default_model 的「最早创建」排序——
# 避免不同环境 DB 创建序漂移把提取路由到别的(中转站)推理模型。取不到再回落默认文本模型。
EXTRACT_TEXT_MODEL_NAME = "doubao-seed-2-0-pro-260215"
def _resolve_extract_model_config(): def _resolve_extract_model_config():
"""提取实体用的文本模型:优先豆包 2.0 Pro(active 且 provider active),否则回落默认文本模型。""" """提取实体用平台后台钦定的默认文本模型(ModelConfig.is_default)。
pinned = ( 未设默认时与 get_default_model 一致:回落最早创建的 active 文本模型。"""
ModelConfig.objects.select_related("provider") return get_default_model(ModelConfig.Capability.TEXT)
.filter(
name=EXTRACT_TEXT_MODEL_NAME,
capability=ModelConfig.Capability.TEXT,
status=ModelConfig.Status.ACTIVE,
provider__status="active",
)
.order_by("created_at")
.first()
)
return pinned or get_default_model(ModelConfig.Capability.TEXT)
def _collect_extract_text( def _collect_extract_text(
@@ -6,7 +6,7 @@ from django.test import TestCase
from apps.accounts.models import Team, TeamMember, User from apps.accounts.models import Team, TeamMember, User
from apps.ai.models import AITask, ModelConfig, ModelProvider from apps.ai.models import AITask, ModelConfig, ModelProvider
from apps.ai.services import EXTRACT_TEXT_MODEL_NAME, run_extract_entities_task, submit_extract_entities from apps.ai.services import run_extract_entities_task, submit_extract_entities
from apps.billing.models import CreditAccount, CreditLedger from apps.billing.models import CreditAccount, CreditLedger
from apps.products.models import Product from apps.products.models import Product
from apps.projects.models import Project, ScriptSegment, ScriptVersion from apps.projects.models import Project, ScriptSegment, ScriptVersion
@@ -66,7 +66,7 @@ class EntityExtractionRoutingTests(TestCase):
metadata={"routing": {"fallback_priority": priority}}, metadata={"routing": {"fallback_priority": priority}},
) )
def model(self, provider, name, *, outbound=True, base_cost="0.50"): def model(self, provider, name, *, outbound=True, base_cost="0.50", is_default=False):
return ModelConfig.objects.create( return ModelConfig.objects.create(
provider=provider, provider=provider,
name=name, name=name,
@@ -75,6 +75,7 @@ class EntityExtractionRoutingTests(TestCase):
endpoint="chat/completions", endpoint="chat/completions",
unit_price=Decimal("10"), unit_price=Decimal("10"),
status=ModelConfig.Status.ACTIVE, status=ModelConfig.Status.ACTIVE,
is_default=is_default,
metadata=_metadata(outbound=outbound, base_cost=base_cost), metadata=_metadata(outbound=outbound, base_cost=base_cost),
) )
@@ -114,7 +115,7 @@ class EntityExtractionRoutingTests(TestCase):
return CreditLedger.objects.filter(task=task, ledger_type=ledger_type).count() return CreditLedger.objects.filter(task=task, ledger_type=ledger_type).count()
def test_first_success_records_streaming_structured_attempt_and_persists_entities(self): def test_first_success_records_streaming_structured_attempt_and_persists_entities(self):
primary = self.model(self.provider("entity-primary", 20), "entity-primary") primary = self.model(self.provider("entity-primary", 20), "entity-primary", is_default=True)
task = self.submit() task = self.submit()
run_extract_entities_task(task_id=str(task.id)) run_extract_entities_task(task_id=str(task.id))
@@ -213,7 +214,7 @@ class EntityExtractionRoutingTests(TestCase):
def test_direct_doubao_primary_retries_but_does_not_switch_when_outbound_disabled(self): def test_direct_doubao_primary_retries_but_does_not_switch_when_outbound_disabled(self):
primary = self.model( primary = self.model(
self.provider("doubao", 10), EXTRACT_TEXT_MODEL_NAME, outbound=False self.provider("doubao", 10), "doubao-seed-2-0-pro-260215", outbound=False
) )
candidate = self.model( candidate = self.model(
self.provider("entity-unused-candidate", 20), "entity-unused", outbound=False self.provider("entity-unused-candidate", 20), "entity-unused", outbound=False
@@ -236,3 +237,22 @@ class EntityExtractionRoutingTests(TestCase):
self.assertTrue(all(attempt.public_model_name == primary.display_name for attempt in attempts)) self.assertTrue(all(attempt.public_model_name == primary.display_name for attempt in attempts))
self.assertEqual(self.ledger_count(task, CreditLedger.Type.RESERVE), 1) self.assertEqual(self.ledger_count(task, CreditLedger.Type.RESERVE), 1)
self.assertEqual(self.ledger_count(task, CreditLedger.Type.RELEASE), 1) self.assertEqual(self.ledger_count(task, CreditLedger.Type.RELEASE), 1)
def test_uses_database_default_text_model_not_older_doubao_pin(self):
older = self.model(self.provider("volcengine-old", 20), "doubao-seed-2-0-pro-260215")
default = self.model(
self.provider("volcengine-default", 10),
"deepseek-v4-pro",
is_default=True,
)
task = self.submit()
run_extract_entities_task(task_id=str(task.id))
task.refresh_from_db()
attempt = task.model_attempts.get()
self.assertEqual(task.status, AITask.Status.SUCCEEDED)
self.assertEqual(attempt.model_config_id, default.id)
self.assertNotEqual(attempt.model_config_id, older.id)
self.assertEqual(task.request_payload["model"], "deepseek-v4-pro")
+1 -1
View File
@@ -1314,7 +1314,7 @@ body.sidebar-collapsed .user::after { display: none; }
.chip.active { border-color: var(--heat-40); color: var(--heat); background: var(--heat-12); } .chip.active { border-color: var(--heat-40); color: var(--heat); background: var(--heat-12); }
.chip svg { width: 12px; height: 12px; } .chip svg { width: 12px; height: 12px; }
/* 清空筛选:幽灵态(无边框/透明底),仅在有筛选时出现,点击重置全部筛选 */ /* 清空筛选:幽灵态(无边框/透明底),仅在有筛选时出现,点击重置全部筛选 */
.chip.chip-clear { border-color: transparent; background: transparent; color: var(--black-alpha-48); padding: 0 8px; } .chip.chip-clear { border-color: transparent; background: transparent; color: var(--black-alpha-48); padding: 0 8px; line-height: 1; white-space: nowrap; }
.chip.chip-clear:hover { background: var(--black-alpha-4); color: var(--accent-black); } .chip.chip-clear:hover { background: var(--black-alpha-4); color: var(--accent-black); }
/* ─── Toolbar ─── */ /* ─── Toolbar ─── */
+1 -1
View File
@@ -26,7 +26,7 @@
.library-page .lib-head { .library-page .lib-head {
display: flex; display: flex;
align-items: flex-start; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 20px; gap: 20px;
margin-bottom: 18px; margin-bottom: 18px;
+5 -1
View File
@@ -25,7 +25,7 @@
.models-page .ml-head { .models-page .ml-head {
display: flex; display: flex;
align-items: flex-start; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 20px; gap: 20px;
margin-bottom: 18px; margin-bottom: 18px;
@@ -91,12 +91,16 @@
.models-page .ml-seg-btn { .models-page .ml-seg-btn {
height: 32px; height: 32px;
padding: 0 12px; padding: 0 12px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 0; border: 0;
border-radius: 8px; border-radius: 8px;
color: #50555d; color: #50555d;
background: transparent; background: transparent;
font: inherit; font: inherit;
font-size: 13px; font-size: 13px;
line-height: 1;
cursor: pointer; cursor: pointer;
} }
.models-page .ml-seg-btn:hover { color: var(--accent-black); } .models-page .ml-seg-btn:hover { color: var(--accent-black); }
+15 -3
View File
@@ -21,7 +21,7 @@
.pl-head { .pl-head {
display: flex; display: flex;
align-items: flex-start; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 20px; gap: 20px;
margin-bottom: 18px; margin-bottom: 18px;
@@ -134,9 +134,9 @@
flex-wrap: wrap; flex-wrap: wrap;
} }
.pl-select { position: relative; width: 150px; flex: 0 0 150px; z-index: 40; } .pl-select { position: relative; width: auto; min-width: 132px; flex: 0 0 auto; z-index: 40; }
.pl-select.open { z-index: 50; } .pl-select.open { z-index: 50; }
.pl-select[data-key="cat"] { width: 168px; flex-basis: 168px; } .pl-select[data-key="cat"] { min-width: 148px; }
.pl-select-trigger { .pl-select-trigger {
width: 100%; width: 100%;
height: 34px; height: 34px;
@@ -226,14 +226,26 @@
.pl-clear { .pl-clear {
height: 34px; height: 34px;
padding: 0 12px; padding: 0 12px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
border: 0; border: 0;
border-radius: 10px; border-radius: 10px;
color: var(--pl-muted); color: var(--pl-muted);
background: transparent; background: transparent;
font: inherit; font: inherit;
font-size: 13px; font-size: 13px;
line-height: 1;
white-space: nowrap;
flex-shrink: 0;
cursor: pointer; cursor: pointer;
} }
.pl-clear svg {
width: 13px;
height: 13px;
flex: 0 0 auto;
}
.pl-clear:hover { color: var(--accent-black); background: rgba(34, 42, 54, 0.06); } .pl-clear:hover { color: var(--accent-black); background: rgba(34, 42, 54, 0.06); }
.pl-view { .pl-view {