Skip to content

Commit d18c2e2

Browse files
Aviral DuaCopilot
andcommitted
fix(deep_research): resolve DeepResearchAgent returning None
When DeepResearchTool's inner initiate_chat() calls terminate via tool execution (confirm_summary, generate_subquestions, confirm_answer), the default _last_msg_as_summary extracts content from the last sender->recipient message, which is a tool_call with content=None. This causes result.summary to return an empty string, making the DeepResearchAgent return None to the user. Add _extract_last_result_with_prefix() static method that walks chat_result.chat_history in reverse to find the actual answer message by its expected prefix, falling back to result.summary when no match is found. Replace all 3 bare return result.summary calls with this helper. Includes 5 unit tests covering: prefix extraction, multiple matches, fallback to summary, None content handling, and empty history. Fixes #1770 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 78b245f commit d18c2e2

File tree

2 files changed

+87
-9
lines changed

2 files changed

+87
-9
lines changed

autogen/tools/experimental/deep_research/deep_research.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,22 @@ class DeepResearchTool(Tool):
7676

7777
ANSWER_CONFIRMED_PREFIX = "Answer confirmed:"
7878

79+
@staticmethod
80+
def _extract_last_result_with_prefix(chat_result: Any, prefix: str) -> str:
81+
"""Extract the last message starting with the given prefix from a ChatResult's history.
82+
83+
When a chat terminates via a tool call (e.g. confirm_summary), the default
84+
``last_msg`` summary method returns empty because the last sender→recipient
85+
message is a tool_call with ``content=None``. This helper walks the chat
86+
history in reverse and returns the first message whose content starts with
87+
*prefix*, falling back to ``chat_result.summary``.
88+
"""
89+
for msg in reversed(chat_result.chat_history):
90+
content = msg.get("content") or ""
91+
if isinstance(content, str) and content.startswith(prefix):
92+
return content
93+
return chat_result.summary
94+
7995
def __init__(
8096
self,
8197
llm_config: LLMConfig | dict[str, Any],
@@ -132,8 +148,10 @@ def delegate_research_task(
132148
str: The answer to the research task.
133149
"""
134150

135-
@self.summarizer_agent.register_for_execution()
136-
@self.critic_agent.register_for_llm(description="Call this method to confirm the final answer.")
151+
@self.summarizer_agent.register_for_execution(silent_override=True)
152+
@self.critic_agent.register_for_llm(
153+
description="Call this method to confirm the final answer.", silent_override=True
154+
)
137155
def confirm_summary(answer: str, reasoning: str) -> str:
138156
return f"{self.ANSWER_CONFIRMED_PREFIX}" + answer + "\nReasoning: " + reasoning
139157

@@ -142,10 +160,10 @@ def confirm_summary(answer: str, reasoning: str) -> str:
142160
max_web_steps=max_web_steps,
143161
)
144162

145-
self.summarizer_agent.register_for_llm(description="Split the question into subquestions and get answers.")(
146-
split_question_and_answer_subquestions
147-
)
148-
self.critic_agent.register_for_execution()(split_question_and_answer_subquestions)
163+
self.summarizer_agent.register_for_llm(
164+
description="Split the question into subquestions and get answers.", silent_override=True
165+
)(split_question_and_answer_subquestions)
166+
self.critic_agent.register_for_execution(silent_override=True)(split_question_and_answer_subquestions)
149167

150168
result = self.critic_agent.initiate_chat(
151169
self.summarizer_agent,
@@ -154,7 +172,7 @@ def confirm_summary(answer: str, reasoning: str) -> str:
154172
clear_history=False,
155173
)
156174

157-
return result.summary
175+
return DeepResearchTool._extract_last_result_with_prefix(result, self.ANSWER_CONFIRMED_PREFIX)
158176

159177
super().__init__(
160178
name=delegate_research_task.__name__,
@@ -228,7 +246,7 @@ def split_question_and_answer_subquestions(
228246
message="Analyse and gather subqestions for the following question: " + question,
229247
)
230248

231-
return result.summary
249+
return DeepResearchTool._extract_last_result_with_prefix(result, DeepResearchTool.SUBQUESTIONS_ANSWER_PREFIX)
232250

233251
return split_question_and_answer_subquestions
234252

@@ -326,4 +344,4 @@ def confirm_answer(answer: str) -> str:
326344
message="Please find the answer to this question: " + question,
327345
)
328346

329-
return result.summary
347+
return DeepResearchTool._extract_last_result_with_prefix(result, DeepResearchTool.ANSWER_CONFIRMED_PREFIX)

test/tools/experimental/deep_research/test_deep_research.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,72 @@
88

99
from autogen import LLMConfig
1010
from autogen.agentchat import AssistantAgent
11+
from autogen.agentchat.chat import ChatResult
1112
from autogen.import_utils import run_for_optional_imports
1213
from autogen.tools.dependency_injection import Depends, on
1314
from autogen.tools.experimental import DeepResearchTool
1415
from test.credentials import Credentials
1516

1617

18+
class TestExtractLastResultWithPrefix:
19+
"""Unit tests for _extract_last_result_with_prefix (issue #1770)."""
20+
21+
def test_extracts_answer_from_tool_result_message(self) -> None:
22+
"""When chat terminates via tool call, the confirmed answer is in the history."""
23+
chat_result = ChatResult(
24+
chat_history=[
25+
{"role": "assistant", "content": "Let me research that."},
26+
{"role": "assistant", "content": None, "tool_calls": [{"function": {"name": "confirm_summary"}}]},
27+
{"role": "tool", "content": "Answer confirmed: Brussels is the capital of Belgium"},
28+
],
29+
summary="",
30+
)
31+
result = DeepResearchTool._extract_last_result_with_prefix(chat_result, "Answer confirmed:")
32+
assert result == "Answer confirmed: Brussels is the capital of Belgium"
33+
34+
def test_returns_last_match_when_multiple_prefixed_messages(self) -> None:
35+
"""When multiple messages match the prefix, returns the last one."""
36+
chat_result = ChatResult(
37+
chat_history=[
38+
{"role": "tool", "content": "Answer confirmed: First attempt"},
39+
{"role": "assistant", "content": "That needs improvement."},
40+
{"role": "tool", "content": "Answer confirmed: Refined answer"},
41+
],
42+
summary="",
43+
)
44+
result = DeepResearchTool._extract_last_result_with_prefix(chat_result, "Answer confirmed:")
45+
assert result == "Answer confirmed: Refined answer"
46+
47+
def test_falls_back_to_summary_when_no_prefix_found(self) -> None:
48+
"""When no message matches, falls back to chat_result.summary."""
49+
chat_result = ChatResult(
50+
chat_history=[
51+
{"role": "assistant", "content": "Some other message"},
52+
],
53+
summary="fallback summary",
54+
)
55+
result = DeepResearchTool._extract_last_result_with_prefix(chat_result, "Answer confirmed:")
56+
assert result == "fallback summary"
57+
58+
def test_handles_none_content_gracefully(self) -> None:
59+
"""Messages with content=None (tool calls) should not crash."""
60+
chat_result = ChatResult(
61+
chat_history=[
62+
{"role": "assistant", "content": None},
63+
{"role": "assistant"},
64+
],
65+
summary="",
66+
)
67+
result = DeepResearchTool._extract_last_result_with_prefix(chat_result, "Answer confirmed:")
68+
assert result == ""
69+
70+
def test_handles_empty_chat_history(self) -> None:
71+
"""Empty chat history falls back to summary."""
72+
chat_result = ChatResult(chat_history=[], summary="")
73+
result = DeepResearchTool._extract_last_result_with_prefix(chat_result, "Answer confirmed:")
74+
assert result == ""
75+
76+
1777
@run_for_optional_imports(
1878
["langchain_openai", "browser_use"],
1979
"browser-use",

0 commit comments

Comments
 (0)