From 9b97140fffb23acabb69d8e8352f632b06c0cf3b Mon Sep 17 00:00:00 2001 From: Yunxiao Xu Date: Mon, 23 Feb 2026 14:32:43 -0800 Subject: [PATCH] fix(researcher): Handle non-string search results in summarizer node --- .../workers/researcher/nodes/summarizer.py | 10 +++- backend/tests/test_orchestrator_loop.py | 50 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/backend/src/ea_chatbot/graph/workers/researcher/nodes/summarizer.py b/backend/src/ea_chatbot/graph/workers/researcher/nodes/summarizer.py index d1e93ac..8481e52 100644 --- a/backend/src/ea_chatbot/graph/workers/researcher/nodes/summarizer.py +++ b/backend/src/ea_chatbot/graph/workers/researcher/nodes/summarizer.py @@ -17,7 +17,15 @@ def summarizer_node(state: WorkerState) -> dict: callbacks=[LangChainLoggingHandler(logger=logger)] ) - results_str = "\n---\n".join(raw_results) + # Ensure all results are strings (Gemini/OpenAI might return complex content) + processed_results = [] + for res in raw_results: + if isinstance(res, list): + processed_results.append(str(res)) + else: + processed_results.append(str(res)) + + results_str = "\n---\n".join(processed_results) prompt = f"""You are a Research Specialist sub-agent. You have completed a research sub-task. Task: {task} diff --git a/backend/tests/test_orchestrator_loop.py b/backend/tests/test_orchestrator_loop.py index 3c8b73f..e1b9cb8 100644 --- a/backend/tests/test_orchestrator_loop.py +++ b/backend/tests/test_orchestrator_loop.py @@ -84,3 +84,53 @@ def test_orchestrator_full_flow(): assert mock_reflector.called assert mock_synthesizer.called assert "Final synthesized answer" in [m.content for m in final_state["messages"]] + +def test_orchestrator_researcher_flow(): + """Verify that the Orchestrator can route to the researcher worker.""" + + mock_analyzer = MagicMock() + mock_planner = MagicMock() + mock_delegate = MagicMock() + mock_researcher = MagicMock() + mock_reflector = MagicMock() + mock_synthesizer = MagicMock() + + mock_analyzer.return_value = {"next_action": "plan"} + mock_planner.return_value = { + "checklist": [{"task": "Search news", "worker": "researcher"}], + "current_step": 0 + } + mock_delegate.side_effect = [ + {"next_action": "researcher"}, + {"next_action": "summarize"} + ] + mock_researcher.return_value = {"messages": [AIMessage(content="News found")]} + mock_reflector.return_value = {"current_step": 1, "next_action": "delegate"} + mock_synthesizer.return_value = {"messages": [AIMessage(content="Final News Summary")], "next_action": "end"} + + app = create_workflow( + query_analyzer=mock_analyzer, + planner=mock_planner, + delegate=mock_delegate, + researcher_worker=mock_researcher, + reflector=mock_reflector, + synthesizer=mock_synthesizer + ) + + initial_state = AgentState( + messages=[HumanMessage(content="What's the news?")], + question="What's the news?", + analysis={}, + next_action="", + iterations=0, + checklist=[], + current_step=0, + vfs={}, + plots=[], + dfs={} + ) + + final_state = app.invoke(initial_state) + + assert mock_researcher.called + assert "Final News Summary" in [m.content for m in final_state["messages"]]