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

This commit is contained in:
m4
2026-07-10 17:35:44 +08:00
parent df54d8498c
commit e0acc6155e
11 changed files with 988 additions and 5 deletions
+141
View File
@@ -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,
}