mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Fixed Redis context provider and samples (#4030)
* Removed session_id filtering in Mem0 implementation * Fixed redis samples * Resolved comments
This commit is contained in:
committed by
GitHub
Unverified
parent
f087b864fb
commit
b0fd4946e6
@@ -107,7 +107,7 @@ class Mem0ContextProvider(BaseContextProvider):
|
||||
if not input_text.strip():
|
||||
return
|
||||
|
||||
filters = self._build_filters(session_id=context.session_id)
|
||||
filters = self._build_filters()
|
||||
|
||||
# AsyncMemory (OSS) expects user_id/agent_id/run_id as direct kwargs
|
||||
# AsyncMemoryClient (Platform) expects them in a filters dict
|
||||
@@ -164,7 +164,6 @@ class Mem0ContextProvider(BaseContextProvider):
|
||||
messages=messages,
|
||||
user_id=self.user_id,
|
||||
agent_id=self.agent_id,
|
||||
run_id=context.session_id,
|
||||
metadata={"application_id": self.application_id},
|
||||
)
|
||||
|
||||
@@ -177,15 +176,13 @@ class Mem0ContextProvider(BaseContextProvider):
|
||||
"At least one of the filters: agent_id, user_id, or application_id is required."
|
||||
)
|
||||
|
||||
def _build_filters(self, *, session_id: str | None = None) -> dict[str, Any]:
|
||||
def _build_filters(self) -> dict[str, Any]:
|
||||
"""Build search filters from initialization parameters."""
|
||||
filters: dict[str, Any] = {}
|
||||
if self.user_id:
|
||||
filters["user_id"] = self.user_id
|
||||
if self.agent_id:
|
||||
filters["agent_id"] = self.agent_id
|
||||
if session_id:
|
||||
filters["run_id"] = session_id
|
||||
if self.application_id:
|
||||
filters["app_id"] = self.application_id
|
||||
return filters
|
||||
|
||||
@@ -255,7 +255,7 @@ class TestAfterRun:
|
||||
{"role": "assistant", "content": "answer"},
|
||||
]
|
||||
assert call_kwargs["user_id"] == "u1"
|
||||
assert call_kwargs["run_id"] == "s1"
|
||||
assert "run_id" not in call_kwargs
|
||||
|
||||
async def test_only_stores_user_assistant_system(self, mock_mem0_client: AsyncMock) -> None:
|
||||
"""Only stores user/assistant/system messages with text."""
|
||||
@@ -298,8 +298,8 @@ class TestAfterRun:
|
||||
|
||||
mock_mem0_client.add.assert_not_awaited()
|
||||
|
||||
async def test_uses_session_id_as_run_id(self, mock_mem0_client: AsyncMock) -> None:
|
||||
"""Uses session_id as run_id."""
|
||||
async def test_no_run_id_in_storage(self, mock_mem0_client: AsyncMock) -> None:
|
||||
"""run_id is not passed to mem0 add, so memories are not scoped to sessions."""
|
||||
provider = Mem0ContextProvider(source_id="mem0", mem0_client=mock_mem0_client, user_id="u1")
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", text="hi")], session_id="my-session")
|
||||
@@ -309,7 +309,7 @@ class TestAfterRun:
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
) # type: ignore[arg-type]
|
||||
|
||||
assert mock_mem0_client.add.call_args.kwargs["run_id"] == "my-session"
|
||||
assert "run_id" not in mock_mem0_client.add.call_args.kwargs
|
||||
|
||||
async def test_validates_filters(self, mock_mem0_client: AsyncMock) -> None:
|
||||
"""Raises ServiceInitializationError when no filters."""
|
||||
@@ -381,10 +381,9 @@ class TestBuildFilters:
|
||||
agent_id="a1",
|
||||
application_id="app1",
|
||||
)
|
||||
assert provider._build_filters(session_id="sess1") == {
|
||||
assert provider._build_filters() == {
|
||||
"user_id": "u1",
|
||||
"agent_id": "a1",
|
||||
"run_id": "sess1",
|
||||
"app_id": "app1",
|
||||
}
|
||||
|
||||
@@ -395,10 +394,11 @@ class TestBuildFilters:
|
||||
assert "run_id" not in filters
|
||||
assert "app_id" not in filters
|
||||
|
||||
def test_session_id_mapped_to_run_id(self, mock_mem0_client: AsyncMock) -> None:
|
||||
def test_no_run_id_in_search_filters(self, mock_mem0_client: AsyncMock) -> None:
|
||||
"""run_id is excluded from search filters so memories work across sessions."""
|
||||
provider = Mem0ContextProvider(source_id="mem0", mem0_client=mock_mem0_client, user_id="u1")
|
||||
filters = provider._build_filters(session_id="s99")
|
||||
assert filters["run_id"] == "s99"
|
||||
filters = provider._build_filters()
|
||||
assert "run_id" not in filters
|
||||
|
||||
def test_empty_when_no_params(self, mock_mem0_client: AsyncMock) -> None:
|
||||
provider = Mem0ContextProvider(source_id="mem0", mem0_client=mock_mem0_client)
|
||||
|
||||
@@ -129,7 +129,7 @@ class RedisContextProvider(BaseContextProvider):
|
||||
if not input_text.strip():
|
||||
return
|
||||
|
||||
memories = await self._redis_search(text=input_text, session_id=context.session_id)
|
||||
memories = await self._redis_search(text=input_text)
|
||||
line_separated_memories = "\n".join(
|
||||
str(memory.get("content", "")) for memory in memories if memory.get("content")
|
||||
)
|
||||
@@ -337,7 +337,7 @@ class RedisContextProvider(BaseContextProvider):
|
||||
filter_expression: Any | None = None,
|
||||
return_fields: list[str] | None = None,
|
||||
num_results: int = 10,
|
||||
alpha: float = 0.7,
|
||||
linear_alpha: float = 0.7,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Runs a text or hybrid vector-text search with optional filters."""
|
||||
await self._ensure_index()
|
||||
@@ -374,7 +374,7 @@ class RedisContextProvider(BaseContextProvider):
|
||||
vector_field_name=self.vector_field_name,
|
||||
text_scorer=text_scorer,
|
||||
filter_expression=combined_filter,
|
||||
alpha=alpha,
|
||||
linear_alpha=linear_alpha,
|
||||
dtype=self.redis_vectorizer.dtype,
|
||||
num_results=num_results,
|
||||
return_fields=return_fields,
|
||||
|
||||
@@ -170,6 +170,26 @@ class TestRedisContextProviderBeforeRun:
|
||||
mock_index.query.assert_not_called()
|
||||
assert "ctx" not in ctx.context_messages
|
||||
|
||||
async def test_before_run_searches_without_session_id(
|
||||
self,
|
||||
mock_index: AsyncMock,
|
||||
patch_index_from_dict: MagicMock, # noqa: ARG002
|
||||
):
|
||||
"""Verify that before_run performs cross-session retrieval (no session_id filter)."""
|
||||
mock_index.query = AsyncMock(return_value=[{"content": "Memory"}])
|
||||
provider = RedisContextProvider(source_id="ctx", user_id="u1")
|
||||
session = AgentSession(session_id="test-session")
|
||||
ctx = SessionContext(input_messages=[Message(role="user", contents=["test query"])], session_id="s1")
|
||||
|
||||
with patch.object(provider, "_redis_search", wraps=provider._redis_search) as spy:
|
||||
await provider.before_run(
|
||||
agent=None, session=session, context=ctx, state=session.state.setdefault(provider.source_id, {})
|
||||
) # type: ignore[arg-type]
|
||||
|
||||
spy.assert_called_once()
|
||||
# session_id should not be passed to _redis_search (cross-session retrieval)
|
||||
assert "session_id" not in spy.call_args.kwargs
|
||||
|
||||
async def test_empty_results_no_messages(
|
||||
self,
|
||||
mock_index: AsyncMock,
|
||||
|
||||
Reference in New Issue
Block a user