PMFlow/backend/tests/test_ai_pmo_ingress_mainlin...

1575 lines
57 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.

from datetime import datetime, timedelta
from types import SimpleNamespace
import pytest
from fastapi import HTTPException
from app.api.ai_pmo_api import routes_core
from app.api.ai_pmo_api import helpers_inbox_filters
from app.api.ai_pmo_api import helpers_inbox_payload
from app.api.ai_pmo_api.schemas import PMOProcessTextRequest
from app.services.ai_pmo.consultant_dispatch import build_consultant_dispatch_contract
from app.services.ai_pmo.ai_pmo_kernel.mixins.intent.ai_pmo_kernel_mixins_intent_resolve import (
KernelIntentResolveMixin,
)
from app.services.ai_pmo.ai_pmo_kernel.mixins.intent.ai_pmo_kernel_mixins_intent_llm import (
KernelIntentLLMMixin,
)
from app.services.ai_pmo.ai_pmo_kernel.mixins.intent.ai_pmo_kernel_mixins_intent_rules import (
KernelIntentRulesMixin,
)
from app.services.ai_pmo.ai_pmo_kernel.ai_agent_kernel import gate_execution
from app.services.ai_pmo.ai_pmo_kernel.ai_pmo_reply_renderer import ReplyRenderer
from app.services.ai_pmo.ai_pmo_kernel import ai_pmo_typed_executor_mixins_parse
from app.services.ai_pmo.ai_pmo_kernel_ingress_flow import mixin_entry
from app.services.ai_pmo.ai_pmo_kernel_ingress_flow import mixin_prepare_manual
from app.services.ai_pmo.ai_pmo_kernel_world_model_core import mixin_project
from app.services.ai_pmo.ai_pmo_typed_executor_execute import mixin_execute_query_project
from app.services.ai_pmo.ai_pmo_intent import CanonicalIntent
from app.services.ai_pmo.draft_confirm_handler import DraftConfirmHandler
from app.services.llm_service import ChatCompletionResult, StructuredExtractionResult
from app.services.llm_service import mixin_tier_escalation
from app.services.llm_service.mixin_tier_escalation import LLMTierEscalationMixin
from app.utils.thread_context import build_feishu_conversation_scope_key, build_feishu_thread_key
@pytest.mark.asyncio
async def test_process_text_route_uses_pmflow_manual_ingress(monkeypatch) -> None:
captured: dict[str, object] = {}
class FakeKernel:
def __init__(self, db, tenant_id: int):
captured["init_db"] = db
captured["init_tenant_id"] = tenant_id
async def process_ingress(self, **kwargs):
captured["ingress_kwargs"] = kwargs
return {
"handled": True,
"status": "answered",
"classification": "question",
"direct_reply_required": True,
"direct_reply_text": "当前主链正常。",
}
monkeypatch.setattr(routes_core, "AIPMOKernelService", FakeKernel)
request = PMOProcessTextRequest(
message_text="现在进展怎么样?",
sender="产品经理A",
source_message_id="manual-1",
project_id=11,
)
current_user = SimpleNamespace(name="ignored", username="ignored")
response = await routes_core.process_text(
request=request,
current_user=current_user,
db=object(),
tenant_id=5,
)
assert response["code"] == 0
data = response["data"]
assert data["status"] == "answered"
kwargs = captured["ingress_kwargs"]
assert isinstance(kwargs, dict)
assert kwargs["source_system"] == "pmflow_manual"
assert kwargs["source_message_id"] == "manual-1"
assert kwargs["message_text"] == "现在进展怎么样?"
assert kwargs["sender"] == "产品经理A"
assert kwargs["channel_ref"] == "pmflow_manual"
assert kwargs["chat_type"] == "manual"
assert kwargs["project_hint_id"] == 11
@pytest.mark.asyncio
async def test_process_text_route_rejects_unhandled_result(monkeypatch) -> None:
class FakeKernel:
def __init__(self, db, tenant_id: int):
_ = (db, tenant_id)
async def process_ingress(self, **kwargs):
_ = kwargs
return {"handled": False, "reason": "kernel_not_handled"}
monkeypatch.setattr(routes_core, "AIPMOKernelService", FakeKernel)
request = PMOProcessTextRequest(message_text="hello")
current_user = SimpleNamespace(name="u", username="u")
with pytest.raises(HTTPException) as exc_info:
await routes_core.process_text(
request=request,
current_user=current_user,
db=object(),
tenant_id=1,
)
assert exc_info.value.status_code == 400
assert str(exc_info.value.detail) == "kernel_not_handled"
@pytest.mark.asyncio
async def test_prepare_mixed_sidecar_context_passes_mixed_contract(monkeypatch) -> None:
captured: dict[str, object] = {}
class FakeAdaptiveRAGService:
def __init__(self, db, tenant_id: int):
captured["tenant_id"] = tenant_id
_ = db
async def classify_intent(self, text: str):
captured["classified_text"] = text
return {
"intent": "mixed",
"question_part": "现在进展如何",
"statement_part": "新增任务确认流",
}
async def answer_question(self, **kwargs):
captured["answer_kwargs"] = kwargs
return {"answer": "[回答] 进展稳定\n[已收到需求] 新增任务确认流"}
class FakeKernel(mixin_entry.KernelIngressFlowEntryMixin):
def __init__(self) -> None:
self.db = object()
self.tenant_id = 9
monkeypatch.setattr(mixin_entry, "AdaptiveRAGService", FakeAdaptiveRAGService)
kernel = FakeKernel()
clean_text, mixed_meta = await kernel._prepare_mixed_sidecar_context(
source_system="pmflow_manual",
source_message_id="manual-msg-2",
channel_ref="pmflow-chat",
source_sender_open_id="ou-pmflow-1",
thread_context={"reply_to_message_id": "reply-1"},
message_text="顺便问下现在进展如何,并且新增任务确认流",
project_hint_id=22,
)
assert clean_text == "新增任务确认流"
assert mixed_meta["mixed_intent"] is True
assert mixed_meta["mixed_question_part"] == "现在进展如何"
assert mixed_meta["mixed_signal_part"] == "新增任务确认流"
assert "[回答] 进展稳定" in str(mixed_meta.get("mixed_sidecar_reply") or "")
answer_kwargs = captured["answer_kwargs"]
assert isinstance(answer_kwargs, dict)
assert answer_kwargs["asked_via"] == "pmflow_manual"
assert answer_kwargs["project_id"] is None
assert answer_kwargs["message_intent"] == "mixed"
assert answer_kwargs["source_sender_open_id"] == "ou-pmflow-1"
assert answer_kwargs["thread_context"] == {"reply_to_message_id": "reply-1"}
assert answer_kwargs["mixed_signal_part"] == "新增任务确认流"
assert answer_kwargs["conversation_key"] == "manual-msg-2"
@pytest.mark.asyncio
async def test_process_ingress_prioritizes_draft_pending_confirm(monkeypatch) -> None:
class FakeKernel(mixin_entry.KernelIngressFlowEntryMixin):
def __init__(self) -> None:
self.db = object()
self.tenant_id = 1
self.draft_called = False
async def _prepare_ingress_envelope(self, **kwargs):
_ = kwargs
return {
"early_result": None,
"clean_text": "确认",
"synthetic_message_id": "s-msg-1",
"closure_payload": {},
"ingest_event": SimpleNamespace(id=100),
"operator": SimpleNamespace(id=8),
"work_item": SimpleNamespace(id=55),
"session": SimpleNamespace(last_intent=mixin_entry.STATE_DRAFT_PENDING_CONFIRM),
}
async def _maybe_handle_draft_confirmation_ingress(self, **kwargs):
self.draft_called = True
_ = kwargs
return {
"handled": True,
"status": "answered",
"classification": "command",
"direct_reply_required": True,
"direct_reply_text": "已确认草稿并继续执行。",
}
def _is_short_confirmation_message(self, **kwargs):
_ = kwargs
return False
async def _prepare_mixed_sidecar_context(self, **kwargs):
_ = kwargs
raise AssertionError("draft_pending_confirm flow should bypass mixed sidecar precheck")
kernel = FakeKernel()
result = await kernel.process_ingress(
source_system="feishu",
source_message_id="feishu-1",
message_text="确认",
sender="tester",
channel_ref="chat-1",
source_sender_open_id="ou_xxx",
ingress_options={"thread_context": {"reply_to_message_id": "msg-root-1"}},
)
assert kernel.draft_called is True
assert result["status"] == "answered"
assert "草稿" in result["direct_reply_text"]
def test_should_route_to_draft_confirmation_requires_anchor_or_explicit_id() -> None:
session = SimpleNamespace(last_intent=mixin_entry.STATE_DRAFT_PENDING_CONFIRM)
assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation(
session=session,
message_text="确认",
ingress_options=None,
) is False
assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation(
session=session,
message_text="确认",
ingress_options={"thread_context": {"thread_id": "th_123"}},
) is False
assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation(
session=session,
message_text="确认",
ingress_options={"thread_context": {"root_message_id": "root_123"}},
) is False
assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation(
session=session,
message_text="创建时间是多少?",
ingress_options={"thread_context": {"reply_to_message_id": "msg-123"}},
) is False
assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation(
session=session,
message_text="确认",
ingress_options={"thread_context": {"reply_to_message_id": "msg-123"}},
) is True
assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation(
session=session,
message_text="工作项ID: 88 创建时间是多少?",
ingress_options=None,
) is True
def test_short_confirmation_requires_reply_anchor() -> None:
session = SimpleNamespace(
last_intent=mixin_entry.STATE_DRAFT_PENDING_CONFIRM,
last_active_at=datetime.now(),
)
sent_at = datetime.now() + timedelta(seconds=1)
assert (
KernelIntentRulesMixin._is_short_confirmation_message(
message_text="确认",
session=session,
message_sent_at=sent_at,
session_snapshot=None,
reply_anchor_present=False,
)
is False
)
assert (
KernelIntentRulesMixin._is_short_confirmation_message(
message_text="确认",
session=session,
message_sent_at=sent_at,
session_snapshot=None,
reply_anchor_present=True,
)
is True
)
def test_natural_revision_routes_to_draft_confirmation_without_reply_anchor() -> None:
session = SimpleNamespace(last_intent=mixin_entry.STATE_DRAFT_PENDING_CONFIRM)
assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation(
session=session,
message_text="第一版先做直播录屏和评论回放切片,再加亮点和掉点总结,最后给一份复盘清单。",
ingress_options=None,
) is True
def test_scope_narrowing_revision_routes_to_draft_confirmation_without_reply_anchor() -> None:
session = SimpleNamespace(last_intent=mixin_entry.STATE_DRAFT_PENDING_CONFIRM)
assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation(
session=session,
message_text="第一版先别做太大,先把三件事做好就行:直播录屏和评论回放切片、亮点和掉点自动总结、给团队一份能直接执行的复盘清单。",
ingress_options=None,
) is True
def test_smalltalk_does_not_route_to_draft_confirmation_without_reply_anchor() -> None:
session = SimpleNamespace(last_intent=mixin_entry.STATE_DRAFT_PENDING_CONFIRM)
assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation(
session=session,
message_text="收到,辛苦了",
ingress_options=None,
) is False
def test_new_project_statement_does_not_route_to_existing_draft_without_anchor() -> None:
session = SimpleNamespace(last_intent=mixin_entry.STATE_DRAFT_PENDING_CONFIRM)
assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation(
session=session,
message_text="最近直播复盘太慢了,想做个给运营团队用的小工具,这个新需求先叫 FeishuWebFlow-abc。",
ingress_options=None,
) is False
def test_explicit_named_project_create_skips_existing_project_name_binding() -> None:
assert mixin_project.KernelWorldModelCoreProjectMixin._should_skip_implicit_project_binding(
message_text=(
"最近我们做跨境直播复盘太慢了,想做个给运营团队用的小工具,"
"这个需求先叫 FeishuWebFlow-1775301894322。"
),
) is True
def test_project_lookup_question_keeps_existing_project_name_binding() -> None:
assert mixin_project.KernelWorldModelCoreProjectMixin._should_skip_implicit_project_binding(
message_text="帮我看下跨境直播复盘这个项目现在怎么样?",
) is False
@pytest.mark.asyncio
async def test_draft_confirm_handler_prefers_latest_sender_scope_candidate_without_anchor() -> None:
latest_work_item = SimpleNamespace(
id=202,
state="draft_pending_confirm",
source_message_id="msg-latest",
)
old_work_item = SimpleNamespace(
id=101,
state="draft_pending_confirm",
source_message_id="msg-old",
)
ingest_payload = {
"sender": "tester",
"channel_ref": "chat-1",
"source_sender_open_id": "ou_123",
}
rows = [
(latest_work_item, SimpleNamespace(payload=dict(ingest_payload))),
(old_work_item, SimpleNamespace(payload=dict(ingest_payload))),
]
class FakeResult:
def __init__(self, value):
self._value = value
def all(self):
return list(self._value)
class FakeDB:
async def execute(self, _stmt):
return FakeResult(rows)
handler = DraftConfirmHandler(SimpleNamespace(db=FakeDB(), tenant_id=1))
target = await handler._resolve_pending_draft(
sender="tester",
source_system="feishu",
channel_ref="chat-1",
source_sender_open_id="ou_123",
explicit_work_item_id=None,
thread_context=None,
)
assert target == rows[0]
@pytest.mark.asyncio
async def test_draft_confirm_handler_skips_followup_wrapper_when_resolving_latest_draft() -> None:
followup_wrapper = SimpleNamespace(
id=303,
state="draft_pending_confirm",
source_message_id="msg-followup",
intent="followup.confirm",
)
latest_project_draft = SimpleNamespace(
id=202,
state="draft_pending_confirm",
source_message_id="msg-project",
intent="project_create.create",
)
ingest_payload = {
"sender": "tester",
"channel_ref": "chat-1",
"source_sender_open_id": "ou_123",
}
rows = [
(followup_wrapper, SimpleNamespace(payload=dict(ingest_payload))),
(latest_project_draft, SimpleNamespace(payload=dict(ingest_payload))),
]
class FakeResult:
def __init__(self, value):
self._value = value
def all(self):
return list(self._value)
class FakeDB:
async def execute(self, _stmt):
return FakeResult(rows)
handler = DraftConfirmHandler(SimpleNamespace(db=FakeDB(), tenant_id=1))
target = await handler._resolve_pending_draft(
sender="tester",
source_system="feishu",
channel_ref="chat-1",
source_sender_open_id="ou_123",
explicit_work_item_id=None,
thread_context=None,
)
assert target == rows[1]
def test_build_feishu_thread_key_prefers_actor_scope_without_anchor() -> None:
assert build_feishu_thread_key(
source_chat_id="chat-1",
source_sender_open_id="ou_dm_1",
sender="产品经理A",
source_message_id="om_current_message",
thread_context=None,
) == "feishu:chat-1:ou_dm_1"
def test_low_value_closure_payload_helper_handles_summary_text() -> None:
assert helpers_inbox_filters._is_low_value_closure_payload(
{"closure_summary": "待补充核心痛点"}
) is False
def test_serialize_work_item_summary_handles_message_text_without_nameerror() -> None:
row = SimpleNamespace(
id=9,
source_system="pmflow_manual",
source_message_id="msg-9",
draft_content={},
state="received",
review_summary={},
action_bundle={},
intent="query.read_only",
project_id=None,
signal_id=None,
reply_template_key=None,
execution_result={},
draft_expires_at=None,
draft_version=0,
confirm_message_id=None,
created_at=None,
updated_at=None,
decision_level="portfolio",
)
ingest = SimpleNamespace(payload={"message_text": " 最近 状态 怎么样 "})
payload = helpers_inbox_payload._serialize_work_item_summary(
row=row,
ingest=ingest,
project=None,
)
assert payload["source_message_text"] == "最近 状态 怎么样"
def test_coerce_rag_business_review_scene_keeps_rag_ask_on_query_chain() -> None:
class FakeKernel(mixin_entry.KernelIngressFlowEntryMixin):
def __init__(self) -> None:
self.tenant_id = 1
kernel = FakeKernel()
original_intent = mixin_entry.CanonicalIntent(
name="signal.create",
confidence=0.91,
project_id=None,
signal_title="大模型 API 聚合平台",
signal_summary="我现在要做一个大模型api聚合平台,你帮我分析下要怎么做,给出mvp需求",
metadata={"llm_function": "classify_intent_ai_pmo_kernel"},
)
coerced = kernel._coerce_intent_for_query_scene(
intent=original_intent,
source_system="pmflow_manual",
message_text="我现在要做一个大模型api聚合平台,你帮我分析下要怎么做,给出mvp需求",
rag_request_context={
"asked_via": "pmflow_manual",
"action_mode": "auto",
},
semantic_bundle=None,
world_model={},
)
assert coerced.name == "query.read_only"
assert coerced.metadata["message_intent"] == "business_review"
assert coerced.metadata["consultant_mode_preference"] == "clarify"
assert coerced.metadata["workflow_route"] == "query.answer"
semantic_bundle = coerced.metadata["semantic_bundle"]
assert semantic_bundle["diagnostics"]["message_intent"] == "business_review"
assert semantic_bundle["diagnostics"]["workflow_route"] == "query.answer"
def test_coerce_feishu_semantic_business_review_bundle_keeps_query_chain() -> None:
class FakeKernel(mixin_entry.KernelIngressFlowEntryMixin):
def __init__(self) -> None:
self.tenant_id = 1
kernel = FakeKernel()
original_intent = mixin_entry.CanonicalIntent(
name="signal.create",
confidence=0.93,
project_id=None,
signal_title="大模型 API 聚合平台",
signal_summary="我现在要创建需求为一个大模型api聚合平台,你帮我分析下要怎么做,",
metadata={"llm_function": "classify_intent_ai_pmo_kernel"},
)
coerced = kernel._coerce_intent_for_query_scene(
intent=original_intent,
source_system="feishu",
message_text="我现在要创建需求为一个大模型api聚合平台,你帮我分析下要怎么做,",
rag_request_context=None,
semantic_bundle={
"primary_workflow": "query_flow",
"diagnostics": {
"message_intent": "business_review",
"review_framework": "five_lenses",
"consultant_mode_preference": "clarify",
},
},
world_model={},
)
assert coerced.name == "query.read_only"
assert coerced.metadata["message_intent"] == "business_review"
assert coerced.metadata["workflow_route"] == "query.answer"
@pytest.mark.asyncio
async def test_kernel_intent_resolve_inherits_recent_feishu_session_context_without_reply_anchor() -> None:
captured: dict[str, object] = {}
class FakeResolver(KernelIntentResolveMixin):
def __init__(self) -> None:
self.tenant_id = 1
def _is_short_confirmation_message(self, **kwargs):
_ = kwargs
return False
@staticmethod
def _intent_llm_enabled() -> bool:
return True
async def _resolve_intent_with_llm(self, **kwargs):
captured.update(kwargs)
return CanonicalIntent(
name="query.read_only",
confidence=0.93,
project_id=21,
signal_title="",
signal_summary="",
metadata={},
)
resolver = FakeResolver()
session = SimpleNamespace(
sender="feishu:chat-1:ou_dm_1",
last_intent="query.read_only",
current_project_id=21,
last_active_at=datetime.now(),
)
_, source = await resolver._resolve_intent(
message_text="我需要对应的需求mvp",
session=session,
world_model={},
thread_context=None,
)
assert source == "llm_only"
assert captured["session_last_intent_override"] == "query.read_only"
assert captured["session_project_id_override"] == 21
@pytest.mark.asyncio
async def test_kernel_intent_llm_prompt_guides_natural_problem_statement_to_project_create(monkeypatch) -> None:
captured: dict[str, object] = {}
class FakeResolver(KernelIntentLLMMixin):
def __init__(self) -> None:
self.tenant_id = 1
self.db = object()
def _build_canonical_intent(self, *, intent_name, message_text, project_id, confidence, metadata): # noqa: ANN001
return CanonicalIntent(
name=intent_name,
confidence=confidence,
project_id=project_id,
signal_title="",
signal_summary=message_text[:300],
metadata=metadata,
)
async def fake_generate_structured_object(prompt: str, **kwargs): # noqa: ANN001
captured["prompt"] = prompt
captured["kwargs"] = kwargs
return StructuredExtractionResult(
payload={
"family": "project_create",
"action": "create",
"intent": "project.create",
"confidence": 0.93,
"project_id": None,
"requested_count": 1,
"requires_confirmation": False,
"parameters": {},
"reasoning": "用户在描述真实业务痛点,并明确表达想做一个工具解决。",
},
source_field="generated_text",
provider="mock",
model="mock-model",
raw_text='{"intent":"project.create"}',
validation="ok",
)
monkeypatch.setattr(
"app.services.ai_pmo.ai_pmo_kernel.mixins.intent.ai_pmo_kernel_mixins_intent_llm.llm_service.generate_structured_object",
fake_generate_structured_object,
)
resolver = FakeResolver()
intent = await resolver._resolve_intent_with_llm(
message_text="最近直播复盘太慢了,我想做个给运营用的小工具,能自动切片、总结亮点掉点。",
session=None,
world_model={},
rule_intent=None,
)
assert intent is not None
assert intent.name == "project.create"
prompt = str(captured["prompt"] or "")
assert "自然语言" in prompt
assert "想做个工具" in prompt
assert "先帮我分析" in prompt
def test_reply_renderer_formats_project_delete_pending_and_success() -> None:
renderer = ReplyRenderer(
state_waiting_manual="waiting_manual",
state_failed="failed",
receipt_phase_action="action",
receipt_phase_pending="pending",
receipt_phase_error="error",
)
intent = CanonicalIntent(
name="project.delete",
confidence=0.96,
project_id=None,
signal_title="",
signal_summary="",
metadata={"llm_parameters": {"project_id": 12, "project_title": "重复项目"}},
)
pending = renderer.render(
work_state="waiting_manual",
intent=intent,
decision_record={"approval_requirement": {"message": "检测到删除或归档类写操作,必须进入显式审批/人工确认。"}},
execution_result={
"status": "waiting_manual",
"action_contract": {
"operation_code": "project.delete",
"params": {"project_id": 12, "project_title": "重复项目"},
},
},
)
assert pending["template_key"] == "pending_manual"
assert "重复项目" in pending["text"]
assert "人工确认" in pending["text"]
success = renderer.render(
work_state="succeeded",
intent=intent,
decision_record={"approval_requirement": {}},
execution_result={"status": "succeeded", "operation_code": "project.delete", "project_id": 12},
)
assert success["template_key"] == "action_succeeded"
assert "已删除项目#12《重复项目》" in success["text"]
@pytest.mark.asyncio
async def test_finalize_waiting_manual_passes_execution_result_to_renderer(monkeypatch) -> None:
captured: dict[str, object] = {}
class FakeReviewService:
async def review_work_item(self, **kwargs):
_ = kwargs
return {"verified": False, "issues": ["waiting_manual"], "next_step": "请确认"}
class FakeRenderer:
def render(self, **kwargs):
captured["render_kwargs"] = kwargs
return {
"receipt_phase": "pending",
"template_key": "pending_manual",
"payload": {"intent": kwargs["intent"].name},
"text": "ok",
}
class FakeKernel(mixin_prepare_manual.KernelIngressFlowPrepareManualMixin):
def __init__(self) -> None:
class _FakeDB:
async def commit(self):
return None
self.db = _FakeDB()
self.tenant_id = 7
self.review_service = FakeReviewService()
self.reply_renderer = FakeRenderer()
async def _maybe_emit_risk_cadence_snapshot(self, **kwargs):
_ = kwargs
async def _claim_reply_receipt(self, **kwargs):
_ = kwargs
return True
async def _write_processing_log(self, **kwargs):
_ = kwargs
async def _update_session(self, **kwargs):
_ = kwargs
async def _upsert_operation_execution_ledger(self, **kwargs):
_ = kwargs
return None
def _extract_execution_operation_code(self, execution_result):
if not isinstance(execution_result, dict):
return None
action_contract = execution_result.get("action_contract") if isinstance(execution_result.get("action_contract"), dict) else {}
if isinstance(action_contract, dict):
operation_code = str(action_contract.get("operation_code") or "").strip().lower()
if operation_code:
return operation_code
operation_code = str(execution_result.get("operation_code") or "").strip().lower()
return operation_code or None
def _normalize_execution_ledger_status(self, *, work_item_state, execution_status):
_ = execution_status
return "pending_manual" if str(work_item_state or "").strip().lower() == "waiting_manual" else "processing"
async def _project_waiting_manual(self, **kwargs):
captured["project_waiting_manual"] = kwargs
def _apply_execution_action_contract(self, **kwargs):
execution_result = dict(kwargs["execution_result"])
execution_result["action_contract"] = {
"operation_code": "project.delete",
"params": {"project_id": 12, "project_title": "重复项目"},
}
return execution_result
def _build_capture_result(self, *, work_item, intent, execution_result, render, direct_reply_required, status, classification):
_ = (work_item, intent, execution_result, render, direct_reply_required, classification)
return {"status": status, "render": render}
kernel = FakeKernel()
work_item = SimpleNamespace(
state=None,
action_bundle=None,
execution_result=None,
review_summary=None,
reply_template_key=None,
id=99,
project_id=12,
signal_id=None,
intent="project.delete",
source_message_id="msg-1",
draft_version=None,
draft_expires_at=None,
)
session = SimpleNamespace()
ingest_event = SimpleNamespace()
decision_payload = {
"policy": {"approval_requirement": {"message": "检测到删除或归档类写操作,必须进入显式审批/人工确认。"}},
"execution_plan": {"actions": []},
"simulation": {},
}
workflow_route = SimpleNamespace(key="project.delete", action="delete")
intent = CanonicalIntent(
name="project.delete",
confidence=0.94,
project_id=12,
signal_title="",
signal_summary="",
metadata={"llm_parameters": {"project_id": 12, "project_title": "重复项目"}},
)
result = await kernel._finalize_waiting_manual_ingress(
work_item=work_item,
session=session,
intent=intent,
world_model={},
decision_payload=decision_payload,
workflow_route=workflow_route,
decision_trace={"reason": "test"},
source_system="feishu",
source_message_id="msg-1",
message_text="删除重复项目",
sender="tester",
ingest_event=ingest_event,
started=0.0,
)
assert result["status"] == "captured"
render_kwargs = captured["render_kwargs"]
assert isinstance(render_kwargs, dict)
assert render_kwargs["execution_result"]["action_contract"]["operation_code"] == "project.delete"
assert render_kwargs["execution_result"]["action_contract"]["params"]["project_title"] == "重复项目"
def test_gate_execution_ignores_low_confidence_but_keeps_risk_review() -> None:
low_confidence = gate_execution(confidence=0.02, risk_level="medium")
assert low_confidence.action == "auto_execute"
assert low_confidence.auto_execute is True
assert low_confidence.gate_code == "auto_execute_allowed"
assert "置信度" not in low_confidence.reason
high_risk = gate_execution(confidence=0.02, risk_level="high")
assert high_risk.action == "auto_execute"
assert high_risk.auto_execute is True
assert high_risk.gate_code == "auto_execute_allowed"
assert "人工确认" not in high_risk.reason
@pytest.mark.asyncio
async def test_execute_query_intent_honors_rag_request_context(monkeypatch) -> None:
captured: dict[str, object] = {}
class FakeAdaptiveRAGService:
def __init__(self, db, tenant_id: int):
_ = db
captured["tenant_id"] = tenant_id
async def answer_question(self, **kwargs):
captured["answer_kwargs"] = kwargs
return {"answer": "来自统一 ingress 的回答", "session_id": 7}
class FakeExecutor(mixin_execute_query_project.ExecuteQueryProjectMixin):
def __init__(self) -> None:
self.db = object()
self.tenant_id = 3
monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService)
executor = FakeExecutor()
intent = SimpleNamespace(
project_id=12,
metadata={
"rag_request_context": {
"asked_by": 101,
"asked_via": "pmflow_manual",
"project_id": 19,
"source_chat_id": "chat-abc",
"session_id": 55,
"force_new_session": True,
"thinking_mode": "deep",
"action_mode": "read_write",
"sync_requirement": True,
"message_intent": "mixed",
"mixed_signal_part": "新增任务确认流",
"consultant_mode_preference": "challenge",
}
},
)
result = await executor._execute_query_intent(
intent=intent,
message_text="请同步更新状态",
operator_user_id=999,
source_system="feishu",
source_message_id="msg-1",
)
assert result["status"] == "succeeded"
assert "统一 ingress" in result["answer"]
answer_kwargs = captured["answer_kwargs"]
assert isinstance(answer_kwargs, dict)
assert answer_kwargs["asked_by"] == 101
assert answer_kwargs["asked_via"] == "pmflow_manual"
assert answer_kwargs["project_id"] == 19
assert answer_kwargs["source_chat_id"] == "chat-abc"
assert answer_kwargs["session_id"] == 55
assert answer_kwargs["force_new_session"] is True
assert answer_kwargs["thinking_mode"] == "deep"
assert answer_kwargs["action_mode"] == "read_write"
assert answer_kwargs["sync_requirement"] is True
assert answer_kwargs["message_intent"] == "mixed"
assert answer_kwargs["mixed_signal_part"] == "新增任务确认流"
assert answer_kwargs["consultant_mode_preference"] == "challenge"
@pytest.mark.asyncio
async def test_execute_query_intent_feishu_fallback_uses_thread_key(monkeypatch) -> None:
captured: dict[str, object] = {}
class FakeAdaptiveRAGService:
def __init__(self, db, tenant_id: int):
_ = db
captured["tenant_id"] = tenant_id
async def answer_question(self, **kwargs):
captured["answer_kwargs"] = kwargs
return {"answer": "fallback-answer", "session_id": 7}
class FakeExecutor(mixin_execute_query_project.ExecuteQueryProjectMixin):
def __init__(self) -> None:
self.db = object()
self.tenant_id = 3
monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService)
executor = FakeExecutor()
intent = SimpleNamespace(
project_id=None,
metadata={
"thread_context": {"root_message_id": "root-1"},
"source_sender_open_id": "ou-feishu-99",
},
)
result = await executor._execute_query_intent(
intent=intent,
message_text="最近AI行业有什么动态",
operator_user_id=999,
source_system="feishu",
source_message_id="msg-feishu-1",
)
assert result["status"] == "succeeded"
answer_kwargs = captured["answer_kwargs"]
assert isinstance(answer_kwargs, dict)
assert answer_kwargs["asked_by"] is None
assert answer_kwargs["asked_via"] == "feishu"
assert answer_kwargs["source_chat_id"] is None
assert answer_kwargs["source_sender_open_id"] == "ou-feishu-99"
assert answer_kwargs["thread_context"] == {"root_message_id": "root-1"}
assert answer_kwargs["conversation_key"] == build_feishu_conversation_scope_key(
source_chat_id=None,
source_sender_open_id="ou-feishu-99",
sender=None,
)
@pytest.mark.asyncio
async def test_execute_query_intent_uses_semantic_bundle_rag_context_when_no_explicit_rag_request(monkeypatch) -> None:
captured: dict[str, object] = {}
class FakeAdaptiveRAGService:
def __init__(self, db, tenant_id: int):
_ = db
captured["tenant_id"] = tenant_id
async def answer_question(self, **kwargs):
captured["answer_kwargs"] = kwargs
return {"answer": "corrected-answer", "session_id": 9}
class FakeExecutor(mixin_execute_query_project.ExecuteQueryProjectMixin):
def __init__(self) -> None:
self.db = object()
self.tenant_id = 3
monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService)
executor = FakeExecutor()
intent = SimpleNamespace(
project_id=None,
metadata={
"source_chat_id": "chat-dm-9",
"source_sender_open_id": "ou-feishu-9",
"semantic_bundle": {
"diagnostics": {
"message_intent": "correction_escalation",
"consultant_mode_preference": "challenge",
"force_thinking_mode": "deep",
}
},
},
)
result = await executor._execute_query_intent(
intent=intent,
message_text="不对,调用高级模型来回答我的问题",
operator_user_id=999,
source_system="feishu",
source_message_id="msg-feishu-9",
)
assert result["status"] == "succeeded"
answer_kwargs = captured["answer_kwargs"]
assert isinstance(answer_kwargs, dict)
assert answer_kwargs["asked_by"] is None
assert answer_kwargs["asked_via"] == "feishu"
assert answer_kwargs["source_chat_id"] == "chat-dm-9"
assert answer_kwargs["source_sender_open_id"] == "ou-feishu-9"
assert answer_kwargs["message_intent"] == "correction_escalation"
assert answer_kwargs["consultant_mode_preference"] == "challenge"
assert answer_kwargs["thinking_mode"] == "deep"
assert answer_kwargs["conversation_key"] == build_feishu_conversation_scope_key(
source_chat_id="chat-dm-9",
source_sender_open_id="ou-feishu-9",
sender=None,
)
@pytest.mark.asyncio
async def test_execute_query_intent_rolls_back_failed_rag_transaction(monkeypatch) -> None:
class FakeAdaptiveRAGService:
def __init__(self, db, tenant_id: int):
_ = (db, tenant_id)
async def answer_question(self, **kwargs):
_ = kwargs
raise RuntimeError("rag failed before response")
class FakeDB:
def __init__(self) -> None:
self.rollback_calls = 0
async def rollback(self) -> None:
self.rollback_calls += 1
class FakeExecutor(mixin_execute_query_project.ExecuteQueryProjectMixin):
def __init__(self) -> None:
self.db = FakeDB()
self.tenant_id = 3
@staticmethod
def _build_query_error_message(exc: Exception) -> str:
return f"query failed: {exc}"
monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService)
executor = FakeExecutor()
intent = SimpleNamespace(project_id=12, metadata={})
result = await executor._execute_query_intent(
intent=intent,
message_text="现在完整调用链是什么?",
operator_user_id=999,
source_system="pmflow_manual",
source_message_id="msg-rollback-1",
)
assert result["status"] == "failed_safe"
assert "rag failed" in str(result["error_message"] or "")
assert executor.db.rollback_calls == 1
def test_typed_executor_common_exports_regex_helpers_to_query_mixins() -> None:
assert mixin_execute_query_project.ExecuteQueryProjectMixin._extract_task_priority("p0 紧急任务") == "high"
assert (
ai_pmo_typed_executor_mixins_parse.TypedExecutorParseMixin._extract_requested_signal_count(
message_text="请帮我分析 3 个信号",
metadata={},
)
== 3
)
assert (
ai_pmo_typed_executor_mixins_parse.TypedExecutorParseMixin._requires_strict_signal_analysis(
message_text="先分析后入库 2 个信号",
requested_count=2,
)
is True
)
def test_consultant_dispatch_uses_short_next_action_for_query_answers() -> None:
dispatch = build_consultant_dispatch_contract(
intent_name="query.answer",
message_text="PMFlow 现在完整调用链是什么?",
source_system="pmflow_manual",
status="answered",
classification="question",
primary_operation_code="query.answer",
risk_level="low",
missing_fields=[],
direct_reply_text=(
"结论:PMFlow 当前完整调用链无法完整还原。\n\n"
"依据:前端 `/ai-steward` 页面通过 `/rag/ask` 发起请求,"
"并继续进入 `AdaptiveRAGService.upsert_pmflow_record_async`。"
),
)
assert dispatch["allow_execute"] is False
assert dispatch["mode"] == "advise"
assert dispatch["next_action"] == "继续追问具体模块、入口函数或文件路径,我会把调用链收窄到那一段。"
assert "结论:" not in dispatch["next_action"]
assert "/rag/ask" not in dispatch["next_action"]
@pytest.mark.asyncio
async def test_low_tier_rag_failure_retries_high_tier(monkeypatch) -> None:
captured: dict[str, object] = {"logs": []}
monkeypatch.setattr(
mixin_tier_escalation,
"_runtime_log",
lambda scope, message: captured["logs"].append((scope, message)),
)
low_engine = {
"id": "low-1",
"provider": "glm",
"model": "qwen-plus",
"base_url": "https://open.bigmodel.cn/api/paas/v4",
"api_key": "low-key",
}
high_engine = {
"id": "high-1",
"provider": "openai",
"model": "GLM-5",
"base_url": "https://api.openai.com/v1",
"api_key": "high-key",
}
bundle = {
"settings": {},
"active_engine_id": "low-1",
"engines": [low_engine, high_engine],
}
openclaw_bundle = {
"rules": [
{
"name": "engine_high_reasoning_glm5",
"enabled": True,
"priority": 220,
"tier": "high",
"provider": "openai",
"model": "GLM-5",
"api_env": "OPENAI_API_KEY",
"match_any": ["adaptive_rag_answer"],
}
]
}
class FakeLLM(LLMTierEscalationMixin):
def _low_to_high_escalation_enabled(self) -> bool:
return True
def _is_low_tier_function(self, function_name: str) -> bool:
return function_name == "adaptive_rag_answer_normal"
@staticmethod
def _rule_matches_function(rule, function_name: str) -> bool:
fn = str(function_name or "").strip().lower()
if not fn:
return False
if bool(rule.get("match_all", False)):
return True
exact = str(rule.get("function_exact") or "").strip().lower()
if exact and fn == exact:
return True
prefix = str(rule.get("function_prefix") or "").strip().lower()
if prefix and fn.startswith(prefix):
return True
match_any = rule.get("match_any")
if isinstance(match_any, list):
for hint in match_any:
token = str(hint or "").strip().lower()
if token and token in fn:
return True
return False
async def _get_or_init_bundle(self, db, *, tenant_id: int):
_ = (db, tenant_id)
return SimpleNamespace(), bundle
def _resolve_active_engine_from_bundle(self, bundle_value):
_ = bundle_value
return dict(low_engine)
def _load_openclaw_routing_bundle(self):
return dict(openclaw_bundle)
def _resolve_engine_from_rule(self, *, rule, engines, active_engine):
_ = active_engine
for item in engines:
if (
str(item.get("provider") or "").strip().lower() == str(rule.get("provider") or "").strip().lower()
and str(item.get("model") or "").strip() == str(rule.get("model") or "").strip()
):
return dict(item)
return None
def _ensure_openclaw_control_plane_engine(self, engine):
return dict(engine)
def _normalize_engine_settings(self, raw):
_ = raw
return {}
def _ensure_engine_allowed_for_function(self, *, engine, function_name, settings, engine_label):
_ = (function_name, settings, engine_label)
return dict(engine)
def _is_invalid_structured_output(self, *, function_name, response_text):
_ = (function_name, response_text)
return False
async def _chat_completion(self, **kwargs):
captured["chat_kwargs"] = kwargs
return "GLM-5 answer"
llm = FakeLLM()
text, resolved_engine = await llm._retry_with_high_tier_after_low_model_failure(
db=object(),
tenant_id=1,
function_name="adaptive_rag_answer_normal",
failed_engine=dict(low_engine),
prompt="请回答一个问题",
system_prompt="system prompt",
temperature=0.2,
max_tokens=512,
response_mode="complete",
error=ValueError("LLM 返回空内容"),
)
assert text == "GLM-5 answer"
assert isinstance(resolved_engine, dict)
resolved_meta = resolved_engine["_routing_meta"]
assert resolved_meta["planned_tier"] == "low"
assert resolved_meta["executed_tier"] == "high"
assert resolved_meta["routing_decision"] == "low_to_high_escalated"
assert captured["chat_kwargs"]["provider"] == "openai"
assert captured["chat_kwargs"]["model"] == "GLM-5"
log_text = "\n".join(message for _, message in captured["logs"])
assert "高阶升级成功" in log_text
@pytest.mark.asyncio
async def test_low_tier_intent_failure_retries_high_tier(monkeypatch) -> None:
captured: dict[str, object] = {"logs": []}
monkeypatch.setattr(
mixin_tier_escalation,
"_runtime_log",
lambda scope, message: captured["logs"].append((scope, message)),
)
low_engine = {
"id": "low-1",
"provider": "glm",
"model": "qwen-plus",
"base_url": "https://open.bigmodel.cn/api/paas/v4",
"api_key": "low-key",
}
alternate_high_engine = {
"id": "high-deepseek-1",
"provider": "deepseek",
"model": "deepseek-chat",
"base_url": "https://api.deepseek.com/v1",
"api_key": "deepseek-key",
}
glm5_engine = {
"id": "high-glm5-1",
"provider": "openai",
"model": "GLM-5",
"base_url": "https://api.openai.com/v1",
"api_key": "glm5-key",
}
bundle = {
"settings": {},
"active_engine_id": "low-1",
"engines": [low_engine, alternate_high_engine, glm5_engine],
}
openclaw_bundle = {
"rules": [
{
"name": "engine_low_intent_kernel_exact",
"enabled": True,
"priority": 310,
"tier": "low",
"provider": "glm",
"model": "qwen-plus",
"api_env": "DASHSCOPE_API_KEY",
"function_exact": "classify_intent_ai_pmo_kernel",
}
]
}
class FakeLLM(LLMTierEscalationMixin):
def _low_to_high_escalation_enabled(self) -> bool:
return True
def _is_low_tier_function(self, function_name: str) -> bool:
return function_name == "classify_intent_ai_pmo_kernel"
@staticmethod
def _rule_matches_function(rule, function_name: str) -> bool:
fn = str(function_name or "").strip().lower()
if not fn:
return False
if bool(rule.get("match_all", False)):
return True
exact = str(rule.get("function_exact") or "").strip().lower()
if exact and fn == exact:
return True
prefix = str(rule.get("function_prefix") or "").strip().lower()
if prefix and fn.startswith(prefix):
return True
match_any = rule.get("match_any")
if isinstance(match_any, list):
for hint in match_any:
token = str(hint or "").strip().lower()
if token and token in fn:
return True
return False
async def _get_or_init_bundle(self, db, *, tenant_id: int):
_ = (db, tenant_id)
return SimpleNamespace(), bundle
def _resolve_active_engine_from_bundle(self, bundle_value):
_ = bundle_value
return dict(low_engine)
def _load_openclaw_routing_bundle(self):
return dict(openclaw_bundle)
def _resolve_engine_from_rule(self, *, rule, engines, active_engine):
_ = active_engine
for item in engines:
if (
str(item.get("provider") or "").strip().lower() == str(rule.get("provider") or "").strip().lower()
and str(item.get("model") or "").strip() == str(rule.get("model") or "").strip()
):
return dict(item)
return None
def _resolve_tier_engine(self, *, tier, engines, active_engine):
_ = (engines, active_engine)
if str(tier).strip().lower() == "high":
return dict(alternate_high_engine)
return dict(low_engine)
def _ensure_openclaw_control_plane_engine(self, engine):
return dict(engine)
def _normalize_engine_settings(self, raw):
_ = raw
return {}
def _ensure_engine_allowed_for_function(self, *, engine, function_name, settings, engine_label):
_ = (function_name, settings, engine_label)
return dict(engine)
def _is_invalid_structured_output(self, *, function_name, response_text):
_ = (function_name, response_text)
return False
async def _chat_completion(self, **kwargs):
captured["chat_kwargs"] = kwargs
return "GLM-5 answer"
llm = FakeLLM()
text, resolved_engine = await llm._retry_with_high_tier_after_low_model_failure(
db=object(),
tenant_id=1,
function_name="classify_intent_ai_pmo_kernel",
failed_engine=dict(low_engine),
prompt="请返回结构化意图",
system_prompt="system prompt",
temperature=0.2,
max_tokens=512,
response_mode="complete",
error=ValueError("LLM 返回空内容"),
)
assert text == "GLM-5 answer"
assert isinstance(resolved_engine, dict)
resolved_meta = resolved_engine["_routing_meta"]
assert resolved_meta["planned_tier"] == "low"
assert resolved_meta["executed_tier"] == "high"
assert resolved_meta["routing_decision"] == "low_to_high_escalated"
assert captured["chat_kwargs"]["provider"] == "openai"
assert captured["chat_kwargs"]["model"] == "GLM-5"
log_text = "\n".join(message for _, message in captured["logs"])
assert "高阶升级成功" in log_text
@pytest.mark.asyncio
async def test_low_tier_structured_failure_retries_high_tier(monkeypatch) -> None:
captured: dict[str, object] = {"logs": []}
monkeypatch.setattr(
mixin_tier_escalation,
"_runtime_log",
lambda scope, message: captured["logs"].append((scope, message)),
)
low_engine = {
"id": "low-1",
"provider": "glm",
"model": "qwen-plus",
"base_url": "https://open.bigmodel.cn/api/paas/v4",
"api_key": "low-key",
}
alternate_high_engine = {
"id": "high-deepseek-1",
"provider": "deepseek",
"model": "deepseek-chat",
"base_url": "https://api.deepseek.com/v1",
"api_key": "deepseek-key",
}
glm5_engine = {
"id": "high-glm5-1",
"provider": "openai",
"model": "GLM-5",
"base_url": "https://api.openai.com/v1",
"api_key": "glm5-key",
}
bundle = {
"settings": {},
"active_engine_id": "low-1",
"engines": [low_engine, alternate_high_engine, glm5_engine],
}
openclaw_bundle = {
"rules": [
{
"name": "engine_low_intent_kernel_exact",
"enabled": True,
"priority": 310,
"tier": "low",
"provider": "glm",
"model": "qwen-plus",
"api_env": "DASHSCOPE_API_KEY",
"function_exact": "classify_intent_ai_pmo_kernel",
}
]
}
class FakeLLM(LLMTierEscalationMixin):
def _low_to_high_escalation_enabled(self) -> bool:
return True
def _is_low_tier_function(self, function_name: str) -> bool:
return function_name == "classify_intent_ai_pmo_kernel"
@staticmethod
def _rule_matches_function(rule, function_name: str) -> bool:
fn = str(function_name or "").strip().lower()
if not fn:
return False
if bool(rule.get("match_all", False)):
return True
exact = str(rule.get("function_exact") or "").strip().lower()
if exact and fn == exact:
return True
prefix = str(rule.get("function_prefix") or "").strip().lower()
if prefix and fn.startswith(prefix):
return True
match_any = rule.get("match_any")
if isinstance(match_any, list):
for hint in match_any:
token = str(hint or "").strip().lower()
if token and token in fn:
return True
return False
async def _get_or_init_bundle(self, db, *, tenant_id: int):
_ = (db, tenant_id)
return SimpleNamespace(), bundle
def _resolve_active_engine_from_bundle(self, bundle_value):
_ = bundle_value
return dict(low_engine)
def _load_openclaw_routing_bundle(self):
return dict(openclaw_bundle)
def _resolve_engine_from_rule(self, *, rule, engines, active_engine):
_ = active_engine
for item in engines:
if (
str(item.get("provider") or "").strip().lower() == str(rule.get("provider") or "").strip().lower()
and str(item.get("model") or "").strip() == str(rule.get("model") or "").strip()
):
return dict(item)
return None
def _resolve_tier_engine(self, *, tier, engines, active_engine):
_ = (engines, active_engine)
if str(tier).strip().lower() == "high":
return dict(alternate_high_engine)
return dict(low_engine)
def _ensure_openclaw_control_plane_engine(self, engine):
return dict(engine)
def _normalize_engine_settings(self, raw):
_ = raw
return {}
def _ensure_engine_allowed_for_function(self, *, engine, function_name, settings, engine_label):
_ = (function_name, settings, engine_label)
return dict(engine)
def _is_invalid_structured_output(self, *, function_name, response_text):
_ = (function_name, response_text)
return False
def _extract_structured_object_from_response(
self,
*,
function_name,
response,
fallback_text,
provider,
model,
):
_ = (function_name, response, fallback_text)
return StructuredExtractionResult(
payload={"confidence": 0.91, "reasoning": "ok", "intent": "query.read_only"},
source_field="generated_text",
provider=provider,
model=model,
raw_text='{"confidence": 0.91, "reasoning": "ok", "intent": "query.read_only"}',
validation="ok",
)
async def _chat_completion_raw(self, **kwargs):
captured["chat_kwargs"] = kwargs
raw_text = '{"confidence": 0.91, "reasoning": "ok", "intent": "query.read_only"}'
return ChatCompletionResult(text=raw_text, raw_response=raw_text)
llm = FakeLLM()
result, resolved_engine = await llm._retry_structured_with_high_tier_after_low_model_failure(
db=object(),
tenant_id=1,
function_name="classify_intent_ai_pmo_kernel",
failed_engine=dict(low_engine),
prompt="请返回结构化意图",
system_prompt="system prompt",
temperature=0.2,
max_tokens=512,
response_mode="complete",
error=ValueError("LLM 返回空内容"),
)
assert result is not None
assert result.payload["intent"] == "query.read_only"
assert isinstance(resolved_engine, dict)
resolved_meta = resolved_engine["_routing_meta"]
assert resolved_meta["planned_tier"] == "low"
assert resolved_meta["executed_tier"] == "high"
assert resolved_meta["routing_decision"] == "low_to_high_escalated"
assert captured["chat_kwargs"]["provider"] == "openai"
assert captured["chat_kwargs"]["model"] == "GLM-5"
log_text = "\n".join(message for _, message in captured["logs"])
assert "高阶结构化升级成功" in log_text