"""20261020 工作流的提示词移植与模型决策回归。""" from datetime import datetime, timezone import json from pathlib import Path import pytest from src.agent.collect import CollectReplyError, run_collect from src.agent.llm import FakeLLMService from src.agent.prompts import COLLECTION, load_prompt, render_prompt WORKFLOW = Path(__file__).resolve().parents[2] / "workflow/20261020/事故信息采集2025118.json" NODES = { "collection": "事故信息收集", "single_photo": "单车拍照", "dual_photo": "双车拍照", "single_photo_addon": "单车拍照#6", "dual_photo_addon": "双车拍照#7", "single_confirm": "单车信息确认", "dual_confirm": "双车信息确认", } @pytest.mark.parametrize(("prompt", "node_name"), NODES.items()) def test_archived_prompt_matches_workflow(prompt, node_name): workflow = json.loads(WORKFLOW.read_text(encoding="utf-8-sig")) node = next(n for n in workflow["nodes"] if n["name"] == node_name) original = next(i["value"] for i in node["inputs"] if i["key"] == "systemPrompt") archived = load_prompt(prompt).split("--- 原文开始 ---", 1)[1].split("--- 原文结束 ---", 1)[0] assert archived.strip() == original.strip() def test_render_replaces_time_and_person_variables(): now = datetime(2026, 10, 20, 12, 0, tzinfo=timezone.utc) rendered = render_prompt(COLLECTION, {}, now=now) assert "2026-10-20 12:00:00 +0000" in rendered rendered = render_prompt("dual_confirm", { "person_fields": {"hphm1": "测试A", "hphm2": "测试B", "sfzmwh1": "1234"} }) assert "测试A" in rendered and "测试B" in rendered assert "1234" in rendered assert "{{$" not in rendered assert "未提供" in rendered @pytest.mark.asyncio async def test_short_answer_does_not_override_model_routing(): # “在”可能是明确回答;运行时不得按字数一律拒绝分流。 llm = FakeLLMService(lambda s, u: "2000信息已齐,请拍摄车辆前方。") result = await run_collect({ "stage_code": "1002", "input_text": "在", "clarify_count": 0, "dialogue": [{"role": "assistant", "content": "您还在事故现场吗?"}], }, llm) assert result["stage_code"] == "2000" assert result["clarify_count"] == 0 assert len(llm.calls) == 1 @pytest.mark.asyncio async def test_format_and_stage_errors_share_one_repair_budget(): replies = iter(["缺少前缀", "9999非法阶段"]) llm = FakeLLMService(lambda s, u: next(replies)) with pytest.raises(CollectReplyError): await run_collect({"stage_code": "1002", "input_text": "追尾了"}, llm) assert len(llm.calls) == 2 @pytest.mark.asyncio async def test_collect_sends_history_as_messages(): history = [ {"role": "user", "content": "没有人受伤"}, {"role": "assistant", "content": "1002请问有几辆车?"}, ] llm = FakeLLMService(lambda s, u: "1002请问您还在现场吗?") await run_collect({ "stage_code": "1002", "input_text": "两辆", "dialogue": history, }, llm) assert llm.calls[0]["history"] == history assert llm.calls[0]["user_message"] == "两辆" assert "没有人受伤" not in llm.calls[0]["user_message"] @pytest.mark.asyncio async def test_openai_adapter_preserves_message_roles(): from types import SimpleNamespace from unittest.mock import AsyncMock from src.agent.llm import OpenAILLMService create = AsyncMock(return_value=SimpleNamespace(choices=[ SimpleNamespace(message=SimpleNamespace(content="1002收到。")) ])) llm = OpenAILLMService(model="test", api_key="test", max_retries=0) llm._client = SimpleNamespace(chat=SimpleNamespace(completions=SimpleNamespace(create=create))) history = [{"role": "assistant", "content": "之前的问题"}] await llm.complete("系统提示词", "本轮输入", history=history) assert create.call_args.kwargs["messages"] == [ {"role": "system", "content": "系统提示词"}, {"role": "assistant", "content": "之前的问题"}, {"role": "user", "content": "本轮输入"}, ]