fix: ensure agent never returns empty result to user
This commit is contained in:
parent
87c59bb3e2
commit
9caf332e9e
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in New Issue