fix: resolve issue #467 - message content validation and Tavily search error handling (#645)

* fix: resolve issue #467 - message content validation and Tavily search error handling

This commit implements a comprehensive fix for issue #467 where the application
crashed with 'Field required: input.messages.3.content' error when generating reports.

## Root Cause Analysis
The issue had multiple interconnected causes:
1. Tavily tool returned mixed types (lists/error strings) instead of consistent JSON
2. background_investigation_node didn't handle error cases properly, returning None
3. Missing message content validation before LLM calls
4. Insufficient error diagnostics for content-related errors

## Changes Made

### Part 1: Fix Tavily Search Tool (tavily_search_results_with_images.py)
- Modified _run() and _arun() methods to return JSON strings instead of mixed types
- Error responses now return JSON: {"error": repr(e)}
- Successful responses return JSON string: json.dumps(cleaned_results)
- Ensures tool results always have valid string content for ToolMessages

### Part 2: Fix background_investigation_node Error Handling (graph/nodes.py)
- Initialize background_investigation_results to empty list instead of None
- Added proper JSON parsing for string responses from Tavily tool
- Handle error responses with explicit error logging
- Always return valid JSON (empty list if error) instead of None

### Part 3: Add Message Content Validation (utils/context_manager.py)
- New validate_message_content() function validates all messages before LLM calls
- Ensures all messages have content attribute and valid string content
- Converts complex types (lists, dicts) to JSON strings
- Provides graceful fallback for messages with issues

### Part 4: Enhanced Error Diagnostics (_execute_agent_step in graph/nodes.py)
- Call message validation before agent invocation
- Add detailed logging for content-related errors
- Log message types, content types, and lengths when validation fails
- Helps with future debugging of similar issues

## Testing
- All unit tests pass (395 tests)
- Python syntax verified for all modified files
- No breaking changes to existing functionality

* test: update tests for issue #467 fixes

Update test expectations to match the new implementation:
- Tavily search tool now returns JSON strings instead of mixed types
- background_investigation_node returns empty list [] for errors instead of None
- All tests updated to verify the new behavior
- All 391 tests pass successfully

* Update src/graph/nodes.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Willem Jiang
2025-10-23 22:08:14 +08:00
committed by GitHub
parent c15c480fe6
commit 052490b116
5 changed files with 114 additions and 14 deletions

View File

@@ -264,3 +264,54 @@ class ContextManager:
"""
# TODO: summary implementation
pass
def validate_message_content(messages: List[BaseMessage]) -> List[BaseMessage]:
"""
Validate and fix all messages to ensure they have valid content before sending to LLM.
This function ensures:
1. All messages have a content field
2. No message has None or empty string content (except for legitimate empty responses)
3. Complex objects (lists, dicts) are converted to JSON strings
Args:
messages: List of messages to validate
Returns:
List of validated messages with fixed content
"""
validated = []
for i, msg in enumerate(messages):
try:
# Check if message has content attribute
if not hasattr(msg, 'content'):
logger.warning(f"Message {i} ({type(msg).__name__}) has no content attribute")
msg.content = ""
# Handle None content
elif msg.content is None:
logger.warning(f"Message {i} ({type(msg).__name__}) has None content, setting to empty string")
msg.content = ""
# Handle complex content types (convert to JSON)
elif isinstance(msg.content, (list, dict)):
logger.debug(f"Message {i} ({type(msg).__name__}) has complex content type {type(msg.content).__name__}, converting to JSON")
msg.content = json.dumps(msg.content, ensure_ascii=False)
# Handle other non-string types
elif not isinstance(msg.content, str):
logger.debug(f"Message {i} ({type(msg).__name__}) has non-string content type {type(msg.content).__name__}, converting to string")
msg.content = str(msg.content)
validated.append(msg)
except Exception as e:
logger.error(f"Error validating message {i}: {e}")
# Create a safe fallback message
if isinstance(msg, ToolMessage):
msg.content = json.dumps({"error": str(e)}, ensure_ascii=False)
else:
msg.content = f"[Error processing message: {str(e)}]"
validated.append(msg)
return validated