"""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")