feat: improve WebUI run recovery
Build / build (push) Has been cancelled
Docker / build (push) Has been cancelled
Lint / ruff (push) Has been cancelled
Test / pytest (ubuntu-latest, 3.11) (push) Has been cancelled
Test / pytest (ubuntu-latest, 3.12) (push) Has been cancelled
Test / pytest (windows-latest, 3.11) (push) Has been cancelled
Test / pytest (windows-latest, 3.12) (push) Has been cancelled
Build / build (push) Has been cancelled
Docker / build (push) Has been cancelled
Lint / ruff (push) Has been cancelled
Test / pytest (ubuntu-latest, 3.11) (push) Has been cancelled
Test / pytest (ubuntu-latest, 3.12) (push) Has been cancelled
Test / pytest (windows-latest, 3.11) (push) Has been cancelled
Test / pytest (windows-latest, 3.12) (push) Has been cancelled
This commit is contained in:
@@ -7,6 +7,7 @@ from __future__ import annotations
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from langchain_core.messages import AIMessage, HumanMessage
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from EvoScientist.config import EvoScientistConfig
|
||||
@@ -161,3 +162,143 @@ def test_ollama_discovery_skipped_when_base_url_absent():
|
||||
{"name": n, "model_id": m, "provider": p}
|
||||
for n, m, p in list_models_by_provider()
|
||||
]
|
||||
|
||||
|
||||
def test_final_answer_extracts_latest_ai_text_blocks():
|
||||
async def fake_metadata(_thread_id):
|
||||
return {"updated_at": "2026-07-06T14:14:53+00:00"}
|
||||
|
||||
async def fake_messages(_thread_id):
|
||||
return [
|
||||
HumanMessage(content="question"),
|
||||
AIMessage(content="old answer"),
|
||||
AIMessage(
|
||||
content=[
|
||||
{"type": "reasoning", "text": "internal"},
|
||||
{"type": "text", "text": "Part A"},
|
||||
{"type": "tool_use", "name": "search"},
|
||||
{"type": "output_text", "text": "Part B"},
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
async def fake_runtime(_request, _thread_id):
|
||||
return {
|
||||
"found": True,
|
||||
"complete": True,
|
||||
"completed_at": "2026-07-06T14:15:00+00:00",
|
||||
}
|
||||
|
||||
with (
|
||||
patch(
|
||||
"EvoScientist.langgraph_dev.http._get_thread_metadata_for_http",
|
||||
new=fake_metadata,
|
||||
),
|
||||
patch(
|
||||
"EvoScientist.langgraph_dev.http._get_thread_messages_for_http",
|
||||
new=fake_messages,
|
||||
),
|
||||
patch(
|
||||
"EvoScientist.langgraph_dev.http._read_thread_runtime_state",
|
||||
new=fake_runtime,
|
||||
),
|
||||
):
|
||||
resp = client.get("/api/threads/thread-1/final-answer")
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert resp.json() == {
|
||||
"content": "Part A\n\nPart B",
|
||||
"completed_at": "2026-07-06T14:15:00+00:00",
|
||||
"complete": True,
|
||||
}
|
||||
|
||||
|
||||
def test_final_answer_skips_tool_selection_json_text():
|
||||
async def fake_metadata(_thread_id):
|
||||
return {"updated_at": "2026-07-06T14:14:53+00:00"}
|
||||
|
||||
async def fake_messages(_thread_id):
|
||||
return [
|
||||
HumanMessage(content="question"),
|
||||
AIMessage(content="stable answer"),
|
||||
AIMessage(
|
||||
content=(
|
||||
'{"tools":["search_papers","get_abstract"]}'
|
||||
'{"tools":["web_search_exa"]}'
|
||||
)
|
||||
),
|
||||
]
|
||||
|
||||
async def fake_runtime(_request, _thread_id):
|
||||
return {
|
||||
"found": True,
|
||||
"complete": True,
|
||||
"completed_at": "2026-07-06T14:15:00+00:00",
|
||||
}
|
||||
|
||||
with (
|
||||
patch(
|
||||
"EvoScientist.langgraph_dev.http._get_thread_metadata_for_http",
|
||||
new=fake_metadata,
|
||||
),
|
||||
patch(
|
||||
"EvoScientist.langgraph_dev.http._get_thread_messages_for_http",
|
||||
new=fake_messages,
|
||||
),
|
||||
patch(
|
||||
"EvoScientist.langgraph_dev.http._read_thread_runtime_state",
|
||||
new=fake_runtime,
|
||||
),
|
||||
):
|
||||
resp = client.get("/api/threads/thread-1/final-answer")
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["content"] == "stable answer"
|
||||
|
||||
|
||||
def test_final_answer_returns_404_for_unknown_thread():
|
||||
async def fake_metadata(_thread_id):
|
||||
return None
|
||||
|
||||
with patch(
|
||||
"EvoScientist.langgraph_dev.http._get_thread_metadata_for_http",
|
||||
new=fake_metadata,
|
||||
):
|
||||
resp = client.get("/api/threads/missing/final-answer")
|
||||
|
||||
assert resp.status_code == 404
|
||||
assert resp.json() == {"error": "thread not found"}
|
||||
|
||||
|
||||
def test_final_answer_does_not_mark_complete_when_runtime_state_fails():
|
||||
async def fake_metadata(_thread_id):
|
||||
return {"updated_at": "2026-07-06T14:14:53+00:00"}
|
||||
|
||||
async def fake_messages(_thread_id):
|
||||
return [AIMessage(content="checkpoint answer")]
|
||||
|
||||
async def fake_runtime(_request, _thread_id):
|
||||
raise RuntimeError("langgraph runtime unavailable")
|
||||
|
||||
with (
|
||||
patch(
|
||||
"EvoScientist.langgraph_dev.http._get_thread_metadata_for_http",
|
||||
new=fake_metadata,
|
||||
),
|
||||
patch(
|
||||
"EvoScientist.langgraph_dev.http._get_thread_messages_for_http",
|
||||
new=fake_messages,
|
||||
),
|
||||
patch(
|
||||
"EvoScientist.langgraph_dev.http._read_thread_runtime_state",
|
||||
new=fake_runtime,
|
||||
),
|
||||
):
|
||||
resp = client.get("/api/threads/thread-1/final-answer")
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert resp.json() == {
|
||||
"content": "checkpoint answer",
|
||||
"completed_at": None,
|
||||
"complete": False,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user