assistant/tests/test_preflight_feature_flag...

105 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Disabled reply experiments cannot choose execution or delivery paths."""
from copy import deepcopy
from unittest.mock import AsyncMock, Mock
import pytest
from backend.config import settings
from backend.prospecting import orchestrator as subject, preflight, prompts, request_routing
from backend.prospecting.contracts import IntentPlan, KNOWLEDGE_ID, ProspectingBrief
from backend.prospecting.delivery import project
REQUEST = '独立做一轮只读预检,不保存来源,最多查看2篇新笔记,不分析名单。'
@pytest.fixture(autouse=True)
def disable_reply_relation_preflight(monkeypatch):
monkeypatch.setattr(settings, 'reply_relation_preflight_enabled', False)
def forbidden(*_args, **_kwargs):
raise AssertionError('Disabled preflight must not parse the request or project results')
for module in (subject, prompts, request_routing):
monkeypatch.setattr(module, 'compile_preflight_policy', forbidden)
monkeypatch.setattr(preflight, 'delivery_projection', forbidden)
@pytest.fixture
def memory(monkeypatch, tmp_path):
brief = ProspectingBrief(intent='discover', target_candidates=10, max_notes=9,
keywords=['用户已给关键词'], user_notes=REQUEST)
row = {'id': 'preflight-disabled-p0', 'knowledge_id': KNOWLEDGE_ID, 'status': 'running',
'current_step': 'search_read', 'browser_profile_path': '/fixture',
'inputs': {**brief.model_dump(), 'goal': REQUEST, '_goal_version': 1,
'_execution_policy': subject.EXECUTION_POLICY, '_needs_interpretation': False},
'artifacts': {'sources': [], 'messages': [], 'analysis_pending': False, 'plan': {'missing': []}}}
monkeypatch.setattr(subject.db, 'get_task', lambda _tid: deepcopy(row))
def update(_tid, **fields):
row.update(deepcopy(fields))
return deepcopy(row)
monkeypatch.setattr(subject.db, 'update_task', update)
monkeypatch.setattr(subject, 'append_progress', lambda *_args, **_kwargs: None)
monkeypatch.setattr(subject, 'write_summary', lambda *_args, **_kwargs: None)
monkeypatch.setattr(subject, 'task_doc_dir', lambda _tid: tmp_path)
monkeypatch.setattr(subject, 'get_llm_config', lambda: {'api_key': 'fixture-only'})
monkeypatch.setattr(subject.runner, 'has_live_session', lambda _tid: False)
engine = subject.ProspectingOrchestrator()
review = Mock(side_effect=lambda _tid, artifacts: artifacts)
monkeypatch.setattr(engine, '_review', review)
monkeypatch.setattr(engine, '_learn', Mock())
return row, engine, review
def test_disabled_preflight_does_not_change_intent_or_note_limit():
plan = IntentPlan(brief=ProspectingBrief(intent='discover', target_candidates=10, max_notes=9),
summary='模型抽取的任务', stages=['搜索', '读取'], reuse_existing_sources=True)
assert request_routing.ground_execution_plan(plan, REQUEST) == plan
# This phrasing previously enabled a current-page recheck solely through
# preflight regex, without an independent browser verification instruction.
assert request_routing.browser_recheck_requested('只读预检当前页面,不保存来源。') is False
@pytest.mark.parametrize('message', [REQUEST, '请在当前网页核对现有原话,不新增来源。'])
def test_default_prompt_has_no_disabled_tool_or_parent_repair_instructions(message):
prompt = prompts.browser_prompt(ProspectingBrief(intent='discover', target_candidates=10, user_notes=message), [])
assert 'inspect_reply_relations' not in prompt and 'resolve_reply_target' not in prompt
assert '先按实际给出的父/根定位保存缺失来源' not in prompt
assert '不为补关系重存回复' in prompt
async def test_prepare_does_not_rewrite_a_model_discover_intent_from_preflight_regex(memory, monkeypatch):
row, engine, _review = memory
execute = AsyncMock()
monkeypatch.setattr(engine, '_run', execute)
result = await engine._prepare(deepcopy(row))
await engine.jobs[row['id']]
assert result['inputs']['intent'] == 'discover' and result['inputs']['max_notes'] == 9
brief = execute.await_args.args[1]
assert brief.intent == 'discover' and brief.max_notes == 9
async def test_scoped_execution_uses_normal_tools_and_delivery_when_flag_is_off(memory, monkeypatch):
row, engine, review = memory
ranking = Mock(return_value=[])
monkeypatch.setattr(subject, 'ranked_experience', ranking)
browser = AsyncMock(return_value={'verdict': 'success', 'summary': 'fixture',
'history': {'is_successful': True}, 'artifacts': {}})
monkeypatch.setattr(subject.runner, 'run_browser_step', browser)
await engine._run_scoped(row['id'], engine._brief(row), '')
args = browser.await_args.kwargs
assert args['preflight_policy'] is None and args['keep_session'] is False
assert args['prospecting_limits']['max_notes'] == 9
assert '本轮交付目标是10位去重候选' in args['agent_task']
assert row['current_step'] == 'stage_results'
assert row['artifacts']['experience_injected'] is True
ranking.assert_called_once()
review.assert_called_once()
def test_default_project_does_not_activate_preflight_even_with_old_artifacts(memory):
row, _engine, _review = memory
row['artifacts']['preflight_run'] = {'run_id': 'a'*32, 'goal_version': 1}
before = deepcopy(row)
result = project(row)
assert 'preflight' not in result
assert row == before