fix: coerce JSON-string tags to list in MemoryItem and MCP tools (#682)
MCP tool bridges sometimes serialize JSON arrays as strings during transport, e.g. '["a", "b"]' arrives as the literal string '["a", "b"]' instead of a native JSON array. This causes Pydantic to reject the input with a validation error. Add defensive coercion at two layers: 1. HTTP API (http.py): Pydantic field_validator on MemoryItem.tags with mode="before" that parses JSON strings back into lists. 2. MCP tools (mcp_tools.py): Same coercion in build_content_dict before tags reach the Pydantic model. A plain non-JSON string is wrapped in a single-element list. Correctly-formatted input is passed through unchanged. Co-authored-by: Philipp <philipp@Philipps-MacBook-Pro.local>
This commit is contained in:
parent
4285e94406
commit
c5273f5fd4
2 changed files with 34 additions and 0 deletions
|
|
@ -424,6 +424,27 @@ class MemoryItem(BaseModel):
|
|||
default=None,
|
||||
description="Optional tags for visibility scoping. Memories with tags can be filtered during recall.",
|
||||
)
|
||||
|
||||
@field_validator("tags", mode="before")
|
||||
@classmethod
|
||||
def coerce_tags(cls, v):
|
||||
"""Coerce JSON-string tags to list.
|
||||
|
||||
MCP tool bridges sometimes serialize JSON arrays as strings during
|
||||
transport, e.g. '["a", "b"]' instead of ["a", "b"]. This validator
|
||||
parses such strings back into lists so the retain call succeeds.
|
||||
A plain non-JSON string is wrapped in a single-element list.
|
||||
"""
|
||||
if isinstance(v, str):
|
||||
try:
|
||||
parsed = json.loads(v)
|
||||
if isinstance(parsed, list):
|
||||
return parsed
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
return [v]
|
||||
return v
|
||||
|
||||
observation_scopes: Literal["per_tag", "combined", "all_combinations"] | list[list[str]] | None = Field(
|
||||
default=None,
|
||||
title="ObservationScopes",
|
||||
|
|
|
|||
|
|
@ -117,6 +117,19 @@ def build_content_dict(
|
|||
Returns:
|
||||
Tuple of (content_dict, error_message). error_message is None if successful.
|
||||
"""
|
||||
# Coerce tags from JSON string to list if needed.
|
||||
# MCP tool bridges sometimes serialize JSON arrays as strings during
|
||||
# transport, e.g. '["a", "b"]' instead of ["a", "b"].
|
||||
if isinstance(tags, str):
|
||||
try:
|
||||
parsed = json.loads(tags)
|
||||
if isinstance(parsed, list):
|
||||
tags = parsed
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
if isinstance(tags, str):
|
||||
tags = [tags]
|
||||
|
||||
content_dict: dict[str, Any] = {"content": content, "context": context}
|
||||
|
||||
if timestamp:
|
||||
|
|
|
|||
Loading…
Reference in a new issue