fix: ensure agent never returns empty result to user

This commit is contained in:
chiguyong 2026-06-16 08:01:43 +08:00
parent 87c59bb3e2
commit 9caf332e9e
6 changed files with 85 additions and 32 deletions

View File

@ -596,6 +596,14 @@ class ReActEngine:
else:
output = response.content or ""
# 兜底:确保 output 永远不为空字符串
if not output or not output.strip():
output = (
"抱歉,我暂时无法生成有效的回复。请尝试换一种方式描述你的需求,"
"或者稍后再试。"
)
trace_outcome = "empty_fallback"
# 结束轨迹记录
if trace_recorder is not None:
trace_recorder.end_trace(outcome=trace_outcome)
@ -1116,6 +1124,24 @@ class ReActEngine:
"max_steps_reached": True,
},
)
# 兜底:确保 output 永远不为空字符串
if not output or not output.strip():
output = (
"抱歉,我暂时无法生成有效的回复。请尝试换一种方式描述你的需求,"
"或者稍后再试。"
)
trace_outcome = "empty_fallback"
yield ReActEvent(
event_type="final_answer",
step=step,
data={
"output": output,
"total_steps": len(trajectory),
"total_tokens": total_tokens,
"empty_fallback": True,
},
)
finally:
# 结束轨迹记录 — always runs even if consumer doesn't fully iterate
if trace_recorder is not None:

View File

@ -514,18 +514,19 @@ async def _handle_chat_message(
task_type="chat",
)
final_content = response.content or ""
if final_content:
await websocket.send_json({
"type": "final_answer",
"content": final_content,
"is_final": True,
})
await sm.append_message(
session_id=session_id,
role=MessageRole.ASSISTANT,
content=final_content,
agent_name=agent.name,
)
if not final_content or not final_content.strip():
final_content = "抱歉,我暂时无法生成有效的回复。请尝试换一种方式描述你的需求,或者稍后再试。"
await websocket.send_json({
"type": "final_answer",
"content": final_content,
"is_final": True,
})
await sm.append_message(
session_id=session_id,
role=MessageRole.ASSISTANT,
content=final_content,
agent_name=agent.name,
)
except Exception as e:
logger.error(f"Chat DIRECT_CHAT error for session {session_id}: {e}")
await websocket.send_json({"type": "error", "data": {"message": str(e)[:200]}})
@ -611,6 +612,8 @@ async def _handle_chat_message(
token_buffer.clear()
# Then send final answer
final_content = event.data.get("output", "")
if not final_content or not final_content.strip():
final_content = "抱歉,我暂时无法生成有效的回复。请尝试换一种方式描述你的需求,或者稍后再试。"
await websocket.send_json({
"type": "final_answer",
"content": final_content,

View File

@ -41,6 +41,17 @@ router = APIRouter(tags=["portal"])
_api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
_api_key_query = APIKeyQuery(name="api_key", auto_error=False)
_EMPTY_RESPONSE_FALLBACK = (
"抱歉,我暂时无法生成有效的回复。请尝试换一种方式描述你的需求,或者稍后再试。"
)
def _ensure_non_empty(text: str | None) -> str:
"""Ensure response text is never empty or whitespace-only."""
if text and text.strip():
return text
return _EMPTY_RESPONSE_FALLBACK
async def _verify_api_key(
request: Request,
@ -390,7 +401,7 @@ async def chat(request: ChatRequest, req: Request, _auth: None = Depends(_verify
agent_name="default",
task_type="chat",
)
response_text = response.content or ""
response_text = _ensure_non_empty(response.content)
else:
# REACT / SKILL_REACT / REWOO / REFLEXION / PLAN_EXEC / TEAM_COLLAB
# Advanced modes (REWOO, REFLEXION, PLAN_EXEC, TEAM_COLLAB) currently
@ -437,9 +448,9 @@ async def chat(request: ChatRequest, req: Request, _auth: None = Depends(_verify
if event.event_type == "final_answer":
collected_output.append(event.data.get("output", ""))
except Exception as e:
response_text = f"Error: {e}"
response_text = f"执行出错: {e}"
else:
response_text = "".join(collected_output) if collected_output else ""
response_text = _ensure_non_empty("".join(collected_output) if collected_output else None)
await _conversation_store.add_message(conv.id, "assistant", response_text)
@ -495,9 +506,8 @@ async def chat_stream(request: ChatRequest, req: Request, _auth: None = Depends(
agent_name="default",
task_type="chat",
)
response_text = response.content or ""
if response_text:
await _conversation_store.add_message(conv.id, "assistant", response_text)
response_text = _ensure_non_empty(response.content)
await _conversation_store.add_message(conv.id, "assistant", response_text)
yield {
"event": "final_answer",
"data": json.dumps(
@ -561,9 +571,10 @@ async def chat_stream(request: ChatRequest, req: Request, _auth: None = Depends(
}
return
response_text = "".join(collected_output) if collected_output else ""
if response_text:
await _conversation_store.add_message(conv.id, "assistant", response_text)
response_text = _ensure_non_empty(
"".join(collected_output) if collected_output else None
)
await _conversation_store.add_message(conv.id, "assistant", response_text)
return EventSourceResponse(event_generator())
@ -823,14 +834,14 @@ async def portal_websocket(websocket: WebSocket):
task_type="chat",
)
# Store assistant reply for multi-turn context continuity
if response.content:
await _conversation_store.add_message(conv.id, "assistant", response.content)
response_content = _ensure_non_empty(response.content)
await _conversation_store.add_message(conv.id, "assistant", response_content)
await websocket.send_json(
{
"type": "result",
"data": {
"status": "completed",
"content": response.content,
"content": response_content,
"timestamp": datetime.now(timezone.utc).isoformat(),
},
}
@ -887,14 +898,14 @@ async def portal_websocket(websocket: WebSocket):
task_type="chat",
)
# Store assistant reply for multi-turn context continuity
if response.content:
await _conversation_store.add_message(conv.id, "assistant", response.content)
response_content = _ensure_non_empty(response.content)
await _conversation_store.add_message(conv.id, "assistant", response_content)
await websocket.send_json(
{
"type": "result",
"data": {
"status": "completed",
"content": response.content,
"content": response_content,
"timestamp": datetime.now(timezone.utc).isoformat(),
},
}
@ -959,11 +970,12 @@ async def portal_websocket(websocket: WebSocket):
await websocket.send_json({"type": "error", "data": {"message": str(e)}})
continue
response_text = "".join(collected_output) if collected_output else ""
if response_text:
await _conversation_store.add_message(conv.id, "assistant", response_text)
response_text = _ensure_non_empty(
"".join(collected_output) if collected_output else None
)
await _conversation_store.add_message(conv.id, "assistant", response_text)
outcome = "success" if response_text else "failure"
outcome = "success" if response_text != _EMPTY_RESPONSE_FALLBACK else "failure"
await websocket.send_json(
{
"type": "result",

View File

@ -404,6 +404,9 @@ class ShellTool(Tool):
else:
output = stdout.decode("utf-8", errors="replace") if stdout else ""
exit_code = proc.returncode if proc.returncode is not None else 0
# Ensure non-empty output for successful commands
if exit_code == 0 and not output.strip():
output = "[命令执行成功,无输出内容]"
except Exception as e:
output = str(e)
exit_code = -1

View File

@ -131,6 +131,10 @@ class WebCrawlTool(Tool):
else:
content = result.markdown or ""
# Ensure non-empty content with helpful message
if not content.strip():
content = f"[页面抓取成功但内容为空。URL: {url}。可能原因1) 页面需要 JavaScript 渲染2) 页面内容为动态加载3) 页面被反爬虫拦截]"
# 提取链接
links: list[str] = []
if hasattr(result, "links") and result.links:

View File

@ -208,7 +208,12 @@ class WebSearchTool(Tool):
if result.get("success") and result.get("total", 0) > 0:
return result
# Return whatever we have (may be empty)
# Return whatever we have, with helpful fallback if empty
if not result.get("success") or result.get("total", 0) == 0:
result["fallback_message"] = (
f"未找到与 '{query}' 相关的搜索结果。建议1) 尝试不同的关键词;"
"2) 检查网络连接3) 稍后重试"
)
return result
except Exception as e: