From 9caf332e9ed9c916a6091e524204dd3bfb4ce103 Mon Sep 17 00:00:00 2001 From: chiguyong Date: Tue, 16 Jun 2026 08:01:43 +0800 Subject: [PATCH] fix: ensure agent never returns empty result to user --- src/agentkit/core/react.py | 26 +++++++++++++++ src/agentkit/server/routes/chat.py | 27 ++++++++------- src/agentkit/server/routes/portal.py | 50 +++++++++++++++++----------- src/agentkit/tools/shell.py | 3 ++ src/agentkit/tools/web_crawl.py | 4 +++ src/agentkit/tools/web_search.py | 7 +++- 6 files changed, 85 insertions(+), 32 deletions(-) diff --git a/src/agentkit/core/react.py b/src/agentkit/core/react.py index 1ecbf9d..6e1025a 100644 --- a/src/agentkit/core/react.py +++ b/src/agentkit/core/react.py @@ -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: diff --git a/src/agentkit/server/routes/chat.py b/src/agentkit/server/routes/chat.py index 30f7eaf..3587653 100644 --- a/src/agentkit/server/routes/chat.py +++ b/src/agentkit/server/routes/chat.py @@ -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, diff --git a/src/agentkit/server/routes/portal.py b/src/agentkit/server/routes/portal.py index bd949aa..9ad5a3b 100644 --- a/src/agentkit/server/routes/portal.py +++ b/src/agentkit/server/routes/portal.py @@ -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", diff --git a/src/agentkit/tools/shell.py b/src/agentkit/tools/shell.py index f5fc230..ff17f8b 100644 --- a/src/agentkit/tools/shell.py +++ b/src/agentkit/tools/shell.py @@ -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 diff --git a/src/agentkit/tools/web_crawl.py b/src/agentkit/tools/web_crawl.py index cac5c91..aa466b7 100644 --- a/src/agentkit/tools/web_crawl.py +++ b/src/agentkit/tools/web_crawl.py @@ -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: diff --git a/src/agentkit/tools/web_search.py b/src/agentkit/tools/web_search.py index fb55b14..87f0309 100644 --- a/src/agentkit/tools/web_search.py +++ b/src/agentkit/tools/web_search.py @@ -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: