fix: address review feedback - integer key coercion and in-memory cache serializers

- Convert train_iteration to string keys in crew_agent_executor.py,
  experimental/agent_executor.py, and training_handler.py for JSON compatibility
- Revert file_store.py and agent_card.py back to PickleSerializer since they
  use in-memory caches only (no untrusted deserialization risk)
- Add explanatory comments for why PickleSerializer is safe for in-memory caches

Co-Authored-By: João <joao@crewai.com>
This commit is contained in:
Devin AI
2026-03-06 15:26:38 +00:00
parent 614354df4c
commit c1ae3da1cd
5 changed files with 25 additions and 13 deletions

View File

@@ -14,7 +14,7 @@ from typing import TYPE_CHECKING
from a2a.client.errors import A2AClientHTTPError
from a2a.types import AgentCapabilities, AgentCard, AgentSkill
from aiocache import cached # type: ignore[import-untyped]
from aiocache.serializers import JsonSerializer # type: ignore[import-untyped]
from aiocache.serializers import PickleSerializer # type: ignore[import-untyped]
import httpx
from crewai.a2a.auth.client_schemes import APIKeyAuth, HTTPDigestAuth
@@ -220,7 +220,10 @@ def _fetch_agent_card_cached(
return asyncio.run(coro)
@cached(ttl=300, serializer=JsonSerializer()) # type: ignore[untyped-decorator]
# PickleSerializer is safe here: this is an in-memory cache only.
# Data never leaves the process, so there is no untrusted deserialization risk.
# JsonSerializer would break AgentCard (Pydantic model) serialization.
@cached(ttl=300, serializer=PickleSerializer()) # type: ignore[untyped-decorator]
async def _afetch_agent_card_cached(
endpoint: str,
auth_hash: str,

View File

@@ -8,8 +8,8 @@ from __future__ import annotations
import asyncio
from collections.abc import Callable
import contextvars
from concurrent.futures import ThreadPoolExecutor, as_completed
import contextvars
import inspect
import logging
from typing import TYPE_CHECKING, Any, Literal, cast
@@ -1599,16 +1599,19 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
# Initialize or retrieve agent's training data
agent_training_data = training_data.get(agent_id, {})
# Use string key for JSON compatibility (JSON converts int keys to strings)
train_key = str(train_iteration)
if human_feedback is not None:
# Save initial output and human feedback
agent_training_data[train_iteration] = {
agent_training_data[train_key] = {
"initial_output": result.output,
"human_feedback": human_feedback,
}
else:
# Save improved output
if train_iteration in agent_training_data:
agent_training_data[train_iteration]["improved_output"] = result.output
if train_key in agent_training_data:
agent_training_data[train_key]["improved_output"] = result.output
else:
if self.agent.verbose:
self._printer.print(

View File

@@ -1,9 +1,9 @@
from __future__ import annotations
import asyncio
import contextvars
from collections.abc import Callable, Coroutine
from concurrent.futures import ThreadPoolExecutor, as_completed
import contextvars
from datetime import datetime
import inspect
import json
@@ -1492,16 +1492,19 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
# Initialize or retrieve agent's training data
agent_training_data = training_data.get(agent_id, {})
# Use string key for JSON compatibility (JSON converts int keys to strings)
train_key = str(train_iteration)
if human_feedback is not None:
# Save initial output and human feedback
agent_training_data[train_iteration] = {
agent_training_data[train_key] = {
"initial_output": result.output,
"human_feedback": human_feedback,
}
else:
# Save improved output
if train_iteration in agent_training_data:
agent_training_data[train_iteration]["improved_output"] = result.output
if train_key in agent_training_data:
agent_training_data[train_key]["improved_output"] = result.output
else:
train_error = Text()
train_error.append("", style="red bold")

View File

@@ -20,9 +20,12 @@ _file_store: Cache | None = None
try:
from aiocache import Cache
from aiocache.serializers import JsonSerializer
from aiocache.serializers import PickleSerializer
_file_store = Cache(Cache.MEMORY, serializer=JsonSerializer())
# PickleSerializer is safe here: this is an in-memory cache only.
# Data never leaves the process, so there is no untrusted deserialization risk.
# JsonSerializer would break FileInput objects (Pydantic models with IO streams).
_file_store = Cache(Cache.MEMORY, serializer=PickleSerializer())
except ImportError:
logger.debug(
"aiocache is not installed. File store features will be disabled. "

View File

@@ -27,7 +27,7 @@ class CrewTrainingHandler(PickleHandler):
data = self.load()
if agent_id not in data:
data[agent_id] = {}
data[agent_id][train_iteration] = new_data
data[agent_id][str(train_iteration)] = new_data
self.save(data)
def clear(self) -> None: