"""Derived run summaries exclude leading reasoning without rewriting messages.""" from uuid import uuid4 import httpx import pytest from langchain_core.messages import AIMessage, HumanMessage from langchain_core.outputs import ChatGeneration, LLMResult from langchain_openai import ChatOpenAI from app.scheduler.notification_text import render_notification_text from deerflow.persistence.scheduled_task_runs.sql import run_summary from deerflow.runtime.events.store.memory import MemoryRunEventStore from deerflow.runtime.journal import RunJournal ANSWER = "The release checklist is complete." @pytest.mark.anyio @pytest.mark.parametrize( ("content", "expected"), [ (f"Compare candidates.{ANSWER}", ANSWER), (f"\n\nCompare candidates.\n\n{ANSWER}", ANSWER), (f'Compare candidates.\n{ANSWER}', ANSWER), (f"First.\nSecond.\n{ANSWER}", ANSWER), (f"First. Second.{ANSWER}", ANSWER), ("" + "r" * 2400 + f"\n{ANSWER}", ANSWER), ("Only reasoning.", None), ("Unfinished reasoning.", None), ("literal example\n```", "```xml\nliteral example\n```"), ("`literal example`", "`literal example`"), ("Use literal example in this document.", "Use literal example in this document."), (" indented code", "indented code"), ("ordinary XML", "ordinary XML"), ("Reasoning.\n`literal example`", "`literal example`"), ([{"type": "text", "text": "Reasoning."}, {"type": "text", "text": ANSWER}], ANSWER), (f"Reasoning.{'a' * 2500}", "a" * 2000), ], ids=[ "short", "multiline", "attributes", "two-blocks", "same-line-blocks", "long-reasoning", "only-reasoning", "unclosed", "partial-tag", "plain", "fenced-literal", "inline-literal", "prose-literal", "indented-literal", "not-tag", "literal-after-reasoning", "content-blocks", "long-answer", ], ) async def test_visible_summary_preserves_original_response(content, expected): store = MemoryRunEventStore() journal = RunJournal("run-visible", "thread-visible", store) message = AIMessage(content=content, usage_metadata={"input_tokens": 10, "output_tokens": 5, "total_tokens": 15}) original = message.model_dump() journal.on_llm_end(LLMResult(generations=[[ChatGeneration(message=message)]]), run_id=uuid4(), tags=["lead_agent"]) await journal.flush() completion = journal.get_completion_data() assert completion["last_ai_message"] == expected assert completion["message_count"] == 1 assert completion["total_tokens"] == 15 events = await store.list_events("thread-visible", "run-visible") response = next(event for event in events if event["event_type"] == "llm.ai.response") assert response["content"]["content"] == content assert message.model_dump() == original @pytest.mark.anyio async def test_thought_only_response_keeps_previous_useful_summary(): journal = RunJournal("run-visible", "thread-visible", MemoryRunEventStore()) for content in (ANSWER, "Unfinished reasoning."): journal.on_llm_end(LLMResult(generations=[[ChatGeneration(message=AIMessage(content=content))]]), run_id=uuid4(), tags=["lead_agent"]) await journal.flush() assert journal.get_completion_data()["last_ai_message"] == ANSWER assert journal.get_completion_data()["message_count"] == 2 @pytest.mark.anyio async def test_openai_compatible_reply_reaches_notice_without_inline_reasoning(): content = "" + "r" * 2400 + f"\n{ANSWER}" requests = [] def respond(request): assert request.method == "POST" and request.url.path == "/v1/chat/completions" requests.append(request) return httpx.Response( 200, json={ "id": "offline-response", "object": "chat.completion", "created": 0, "model": "reasoning-model", "choices": [{"index": 0, "finish_reason": "stop", "message": {"role": "assistant", "content": content}}], "usage": {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15}, }, ) store = MemoryRunEventStore() journal = RunJournal("run-visible", "thread-visible", store) async with httpx.AsyncClient(transport=httpx.MockTransport(respond)) as client: model = ChatOpenAI(model="reasoning-model", api_key="offline-test-key", base_url="https://model.invalid/v1", http_async_client=client) message = await model.ainvoke([HumanMessage(content="Check the release checklist")], config={"callbacks": [journal], "tags": ["lead_agent"]}) await journal.flush() summary = journal.get_completion_data()["last_ai_message"] notice = render_notification_text({"event": "run_completed", "run_id": "run-visible", "payload": {"run_status": "success", "task_title": "Check release", "result_summary": run_summary(summary)}}) assert len(requests) == 1 assert message.content == content assert run_summary(summary) == ANSWER assert f"Result: {ANSWER}" in notice assert "" not in notice and "rrrr" not in notice events = await store.list_events("thread-visible", "run-visible") assert any(event["content"].get("content") == content for event in events if event["event_type"] == "llm.ai.response")