mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Fix MCP tool result serialization for list[TextContent] (#2523)
* Fix MCP tool result serialization for list[TextContent] When MCP tools return results containing list[TextContent], they were incorrectly serialized to object repr strings like: '[<agent_framework._types.TextContent object at 0x...>]' This fix properly extracts text content from list items by: 1. Checking if items have a 'text' attribute (TextContent) 2. Using model_dump() for items that support it 3. Falling back to str() for other types 4. Joining single items as plain text, multiple items as JSON array Fixes #2509 * Address PR review feedback for MCP tool result serialization - Extract serialize_content_result() to shared _utils.py - Fix logic: use texts[0] instead of join for single item - Add type annotation: texts: list[str] = [] - Return empty string for empty list instead of '[]' - Move import json to file top level - Add comprehensive unit tests for serialization * Address PR review feedback: fix type checking and double serialization - Add isinstance(item.text, str) check to ensure text attribute is a string - Fix double-serialization issue by keeping model_dump results as dicts until final json.dumps (removes escaped JSON strings in arrays) - Improve docstring with detailed return value documentation - Add test for non-string text attribute handling - Add tests for list type tool results in _events.py path * Simplify PR: minimal changes to fix MCP tool result serialization Addresses reviewer feedback about excessive refactoring: - Reset _events.py to original structure - Only add import and use serialize_content_result in one location - All review comments addressed in serialize_content_result(): - Added isinstance(item.text, str) check - Use model_dump(mode="json") to avoid double-serialization - Improved docstring with explicit return value documentation - Empty list returns "" instead of "[]" * Refactor: Move MCP TextContent serialization to core prepare_function_call_results Per reviewer feedback, moved the TextContent serialization logic from ag-ui's serialize_content_result to the core package's prepare_function_call_results function. Changes: - Added handling for objects with 'text' attribute (like MCP TextContent) in _prepare_function_call_results_as_dumpable - Removed serialize_content_result from ag-ui/_utils.py - Updated _events.py and _message_adapters.py to use prepare_function_call_results from core package - Updated tests to match the core function's behavior * Fix failing tests for prepare_function_call_results behavior - test_tool_result_with_none: Update expected value to 'null' (JSON serialization of None) - test_tool_result_with_model_dump_objects: Use Pydantic BaseModel instead of plain class * Fix B903 linter error: Convert MockTextContent to dataclass The ruff linter was reporting B903 (class could be dataclass or namedtuple) for the MockTextContent test helper classes. This commit converts them to dataclasses to satisfy the linter check.
This commit is contained in:
committed by
GitHub
Unverified
parent
f49e537721
commit
db283cd396
@@ -1869,6 +1869,9 @@ def _prepare_function_call_results_as_dumpable(content: Contents | Any | list[Co
|
||||
return content.model_dump()
|
||||
if hasattr(content, "to_dict"):
|
||||
return content.to_dict(exclude={"raw_representation", "additional_properties"})
|
||||
# Handle objects with text attribute (e.g., MCP TextContent)
|
||||
if hasattr(content, "text") and isinstance(content.text, str):
|
||||
return content.text
|
||||
return content
|
||||
|
||||
|
||||
|
||||
@@ -2133,3 +2133,55 @@ def test_prepare_function_call_results_nested_pydantic_model():
|
||||
assert "Seattle" in json_result
|
||||
assert "rainy" in json_result
|
||||
assert "18.0" in json_result or "18" in json_result
|
||||
|
||||
|
||||
# region prepare_function_call_results with MCP TextContent-like objects
|
||||
|
||||
|
||||
def test_prepare_function_call_results_text_content_single():
|
||||
"""Test that objects with text attribute (like MCP TextContent) are properly handled."""
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class MockTextContent:
|
||||
text: str
|
||||
|
||||
result = [MockTextContent("Hello from MCP tool!")]
|
||||
json_result = prepare_function_call_results(result)
|
||||
|
||||
# Should extract text and serialize as JSON array of strings
|
||||
assert isinstance(json_result, str)
|
||||
assert json_result == '["Hello from MCP tool!"]'
|
||||
|
||||
|
||||
def test_prepare_function_call_results_text_content_multiple():
|
||||
"""Test that multiple TextContent-like objects are serialized correctly."""
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class MockTextContent:
|
||||
text: str
|
||||
|
||||
result = [MockTextContent("First result"), MockTextContent("Second result")]
|
||||
json_result = prepare_function_call_results(result)
|
||||
|
||||
# Should extract text from each and serialize as JSON array
|
||||
assert isinstance(json_result, str)
|
||||
assert json_result == '["First result", "Second result"]'
|
||||
|
||||
|
||||
def test_prepare_function_call_results_text_content_with_non_string_text():
|
||||
"""Test that objects with non-string text attribute are not treated as TextContent."""
|
||||
|
||||
class BadTextContent:
|
||||
def __init__(self):
|
||||
self.text = 12345 # Not a string!
|
||||
|
||||
result = [BadTextContent()]
|
||||
json_result = prepare_function_call_results(result)
|
||||
|
||||
# Should not extract text since it's not a string, will serialize the object
|
||||
assert isinstance(json_result, str)
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
Reference in New Issue
Block a user