From b07c1439a34b965f3e1813b9638768620242e63d Mon Sep 17 00:00:00 2001 From: Greyson LaLonde Date: Thu, 21 May 2026 00:22:53 +0800 Subject: [PATCH] fix: backfill legacy discriminators and add source validation context --- lib/crewai/src/crewai/knowledge/knowledge.py | 10 ++++- lib/crewai/src/crewai/state/runtime.py | 40 +++++++++++++++++++- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/lib/crewai/src/crewai/knowledge/knowledge.py b/lib/crewai/src/crewai/knowledge/knowledge.py index 623fa3cef..c923c86fb 100644 --- a/lib/crewai/src/crewai/knowledge/knowledge.py +++ b/lib/crewai/src/crewai/knowledge/knowledge.py @@ -38,14 +38,20 @@ def _resolve_knowledge_sources(value: Any) -> Any: if not isinstance(value, list): return value resolved: list[Any] = [] - for item in value: + for idx, item in enumerate(value): if isinstance(item, dict): tag = item.get("source_type") cls = _KNOWN_SOURCES.get(tag) if isinstance(tag, str) else None if cls is None: resolved.append(item) else: - resolved.append(cls.model_validate(item)) + try: + resolved.append(cls.model_validate(item)) + except Exception as exc: + raise ValueError( + f"Failed to validate knowledge source at index {idx} " + f"with source_type={tag!r}: {exc}" + ) from exc else: resolved.append(item) return resolved diff --git a/lib/crewai/src/crewai/state/runtime.py b/lib/crewai/src/crewai/state/runtime.py index 2662266d2..8b08632d9 100644 --- a/lib/crewai/src/crewai/state/runtime.py +++ b/lib/crewai/src/crewai/state/runtime.py @@ -113,12 +113,48 @@ def _migrate(data: dict[str, Any]) -> dict[str, Any]: ) # --- migrations in version order --- - # if stored < Version("X.Y.Z"): - # data.setdefault("some_field", "default") + if stored < Version("1.14.6"): + for entity in data.get("entities") or []: + _backfill_discriminators(entity) return data +def _backfill_memory_kind(value: Any) -> None: + """Infer ``memory_kind`` from structural fields on legacy memory dicts.""" + if not isinstance(value, dict) or "memory_kind" in value: + return + if "scopes" in value: + value["memory_kind"] = "slice" + elif "root_path" in value: + value["memory_kind"] = "scope" + else: + value["memory_kind"] = "memory" + + +def _backfill_source_type(source: Any) -> None: + """Infer ``source_type`` for legacy knowledge source dicts when possible.""" + if not isinstance(source, dict) or "source_type" in source: + return + if "content" in source: + source["source_type"] = "string" + + +def _backfill_discriminators(entity: Any) -> None: + """Walk an entity dict and backfill discriminator fields added in 1.14.6.""" + if not isinstance(entity, dict): + return + _backfill_memory_kind(entity.get("memory")) + for agent in entity.get("agents") or []: + _backfill_memory_kind(agent.get("memory") if isinstance(agent, dict) else None) + for container in (entity.get("knowledge"), entity): + if isinstance(container, dict): + for src in ( + container.get("sources") or container.get("knowledge_sources") or [] + ): + _backfill_source_type(src) + + class RuntimeState(RootModel): # type: ignore[type-arg] root: list[Entity] _provider: BaseProvider = PrivateAttr(default_factory=JsonProvider)