perf(news): scope first-stage Skill prompts to each task
This commit is contained in:
1 parent
f2ef39e99a
commit
1397a076e1
8 files changed
+131
-30
No files matched your search
@@ -4,30 +4,32 @@ from types import SimpleNamespace as N
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.parametrize('stage', ['index', 'research'])
|
||||
@pytest.mark.parametrize('stage', ['index', 'assign', 'research'])
|
||||
@pytest.mark.parametrize('selected', [None, 'low', 'medium', 'high', 'xhigh', 'max'])
|
||||
@pytest.mark.parametrize('inherited', [None, 'medium'])
|
||||
def test_stage_effort_isolated(trading, stage, selected, inherited):
|
||||
from alphapulse.models import AiProvider, SkillNode
|
||||
from alphapulse.news_research.service import model_config
|
||||
from alphapulse.news_research.contracts import task_skill
|
||||
config_stage = "index" if stage == "assign" else stage
|
||||
shared = N(id=1, model='shared', reasoning_effort=inherited, max_tokens=8192, context_window=200000)
|
||||
node = N(id=1, scope='news', kind='skill', enabled=True, parent_id=None, published_version_id=1)
|
||||
version = N(version_no=1, content='Synthetic rules')
|
||||
version = N(version_no=1, content='## 索引规则\nSynthetic index\n## 归属规则\nSynthetic assignment')
|
||||
class Session:
|
||||
async def get(self, model, identifier):
|
||||
if model is AiProvider: return N(**vars(shared))
|
||||
return node if model is SkillNode else version
|
||||
def expunge(self, obj): pass
|
||||
config = {key: {'provider_id': 1, 'model': key+'-model', 'skill_id': 1,
|
||||
'reasoning_effort': selected if key == stage else None} for key in ['index', 'research']}
|
||||
'reasoning_effort': selected if key == config_stage else None} for key in ['index', 'research']}
|
||||
async def check():
|
||||
actual, skill, rules = await model_config(Session(), config, stage)
|
||||
assert actual.model == stage+'-model'
|
||||
assert actual.model == config_stage+'-model'
|
||||
assert actual.reasoning_effort == (selected if selected is not None else inherited)
|
||||
assert rules['model_config']['selected_reasoning_effort'] == selected
|
||||
assert rules['model_config']['reasoning_effort'] == actual.reasoning_effort
|
||||
assert rules['content'] == version.content and skill.endswith(version.content)
|
||||
other = 'research' if stage == 'index' else 'index'
|
||||
assert rules['content'] == version.content and skill.endswith(task_skill(version.content, stage))
|
||||
other = 'research' if config_stage == 'index' else 'index'
|
||||
assert (await model_config(Session(), config, other))[0].reasoning_effort == inherited
|
||||
assert shared.model == 'shared' and shared.reasoning_effort == inherited
|
||||
asyncio.run(check())
|
||||
|
||||
@@ -681,3 +681,49 @@ def test_long_event_compaction_uses_final_research_context(trading, monkeypatch,
|
||||
assert not reader.fits(provider, skill, versions[0].input, ResearchResult)
|
||||
assert not reader.fits(provider, boundary_rules, compressed, ResearchResult)
|
||||
assert reader.fits(provider, skill, compressed, ResearchResult) != needs_compaction
|
||||
|
||||
|
||||
@pytest.mark.parametrize("task", ["index", "assign", "research"])
|
||||
def test_model_config_sends_only_selected_task_and_records_exact_prompt(trading, monkeypatch, task):
|
||||
import asyncio
|
||||
from unittest.mock import AsyncMock
|
||||
from alphapulse.news_research import service
|
||||
from alphapulse.news_research.contracts import INDEX_ASSIGN_SKILL, INDEX_SKILL, ASSIGN_SKILL, RESEARCH_SKILL
|
||||
from alphapulse.news_research.pipeline import BASE
|
||||
from alphapulse.models import SkillNode
|
||||
|
||||
content = RESEARCH_SKILL if task == "research" else INDEX_ASSIGN_SKILL
|
||||
node = N(id=22, scope="news", kind="skill", enabled=True, parent_id=None, published_version_id=61)
|
||||
version = N(version_no=5, content=content)
|
||||
session = N(get=AsyncMock(side_effect=lambda model, *args: node if model is SkillNode else version))
|
||||
provider = N(id=12, model="test", max_tokens=1000, context_window=100000, reasoning_effort="medium")
|
||||
monkeypatch.setattr(service, "provider_for", AsyncMock(return_value=provider))
|
||||
config = {stage: dict(provider_id=12, model="test", skill_id=22) for stage in ("index", "research")}
|
||||
_, prompt, rules = asyncio.run(service.model_config(session, config, task))
|
||||
selected = {"index": INDEX_SKILL, "assign": ASSIGN_SKILL, "research": RESEARCH_SKILL}[task]
|
||||
assert prompt == BASE + "\n" + selected
|
||||
assert rules["system_prompt"] == prompt and rules["content"] == content
|
||||
assert rules["skill_id"] == 22 and rules["version"] == 5
|
||||
|
||||
|
||||
@pytest.mark.parametrize("content", [
|
||||
"legacy unstructured rules",
|
||||
"## 索引规则\nonly one section",
|
||||
"## 索引规则\n\n## 归属规则\nassign",
|
||||
"## 索引规则\nindex\n## 索引规则\nagain",
|
||||
"preamble\n## 索引规则\nindex\n## 归属规则\nassign",
|
||||
"## 索引规则\nindex\n## 归属规则\nassign\n## 其他规则\nextra",
|
||||
"## 索引规则\nindex\n## 归属规则\nassign\n## 索引规则\nagain",
|
||||
])
|
||||
def test_invalid_first_stage_skill_fails_instead_of_sending_full_text(trading, content):
|
||||
from alphapulse.news_research.contracts import task_skill, SkillFormatError
|
||||
for task in ("index", "assign"):
|
||||
with pytest.raises(SkillFormatError):
|
||||
task_skill(content, task)
|
||||
|
||||
|
||||
def test_task_skill_keeps_custom_rules_and_accepts_crlf(trading):
|
||||
from alphapulse.news_research.contracts import task_skill
|
||||
content = "## 归属规则\r\n自定义归属\r\n### 细则\r\n保留规则\r\n## 索引规则\r\n自定义索引"
|
||||
assert task_skill(content, "assign") == "自定义归属\n### 细则\n保留规则"
|
||||
assert task_skill(content, "index") == "自定义索引"
|
||||
@@ -92,7 +92,7 @@ def test_published_skill_retains_base_instructions(trading, monkeypatch):
|
||||
monkeypatch.setattr(service, 'provider_for', AsyncMock(return_value=provider))
|
||||
session = SimpleNamespace(get=AsyncMock(side_effect=[
|
||||
SimpleNamespace(id=9, scope='news', kind='skill', enabled=True, parent_id=None, published_version_id=10),
|
||||
SimpleNamespace(version_no=3, content='PUBLISHED'),
|
||||
SimpleNamespace(version_no=3, content='## 索引规则\nPUBLISHED\n## 归属规则\nASSIGN'),
|
||||
]))
|
||||
_, skill, rules = asyncio.run(service.model_config(session, {'index': {'provider_id': 7, 'model': 'test', 'skill_id': 9}}, 'index'))
|
||||
assert skill == BASE+'\nPUBLISHED' and rules['base'] == BASE
|
||||
|
||||
Reference in new issue
Block a user