Skip to content

Commit 490f466

Browse files
committed
fix: Query with an instance now sends instance details to agent
1 parent b10db8c commit 490f466

File tree

4 files changed

+139
-5
lines changed

4 files changed

+139
-5
lines changed

redis_sre_agent/agent/knowledge_agent.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ async def process_query(
419419
session_id: Session identifier
420420
user_id: User identifier
421421
max_iterations: Maximum number of agent iterations
422-
context: Additional context (ignored for knowledge-only agent)
422+
context: Additional context (currently ignored for knowledge-only agent)
423423
progress_callback: Optional callback for progress updates
424424
conversation_history: Optional list of previous messages for context
425425
@@ -432,9 +432,9 @@ async def process_query(
432432
if progress_callback:
433433
self.progress_callback = progress_callback
434434

435-
# Create ToolManager with only knowledge tools (no redis_instance)
435+
# Create ToolManager with Redis instance-independent tools
436436
async with ToolManager(redis_instance=None) as tool_mgr:
437-
logger.info(f"Loaded {len(tool_mgr.get_tools())} knowledge tools")
437+
logger.info(f"Loaded {len(tool_mgr.get_tools())} usable without Redis instance details")
438438

439439
# Build workflow with tools
440440
workflow = self._build_workflow(tool_mgr)

redis_sre_agent/agent/langgraph_agent.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1911,11 +1911,11 @@ async def _retry_with_backoff(
19111911
raise last_exception
19121912

19131913

1914-
def get_sre_agent() -> SRELangGraphAgent:
1914+
def get_sre_agent(*args, **kwargs) -> SRELangGraphAgent:
19151915
"""Create a new SRE agent instance for each task to prevent cross-contamination.
19161916
19171917
Previously this was a singleton, but that caused cross-contamination between
19181918
different tasks/threads when multiple tasks ran concurrently. Each task now
19191919
gets its own isolated agent instance.
19201920
"""
1921-
return SRELangGraphAgent()
1921+
return SRELangGraphAgent(*args, **kwargs)

redis_sre_agent/cli/query.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@ async def _query():
3434
agent = get_knowledge_agent()
3535

3636
try:
37+
context = {"instance_id": instance.id} if instance else None
3738
response = await agent.process_query(
3839
query,
3940
session_id="cli",
4041
user_id="cli_user",
4142
max_iterations=settings.max_iterations,
43+
context=context,
4244
)
4345

4446
from rich.console import Console

tests/unit/cli/test_cli_query.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
"""Tests for the `query` CLI command."""
2+
3+
from unittest.mock import AsyncMock, MagicMock, patch
4+
5+
from click.testing import CliRunner
6+
7+
from redis_sre_agent.cli.query import query
8+
9+
10+
def test_query_cli_help_shows_instance_option():
11+
runner = CliRunner()
12+
result = runner.invoke(query, ["--help"])
13+
14+
assert result.exit_code == 0
15+
assert "--redis-instance-id" in result.output
16+
assert "-r" in result.output
17+
18+
19+
def test_query_without_instance_uses_knowledge_agent():
20+
runner = CliRunner()
21+
22+
mock_agent = MagicMock()
23+
mock_agent.process_query = AsyncMock(return_value="ok")
24+
25+
with (
26+
patch("redis_sre_agent.cli.query.get_knowledge_agent", return_value=mock_agent)
27+
as mock_get_knowledge,
28+
patch("redis_sre_agent.cli.query.get_sre_agent") as mock_get_sre,
29+
patch(
30+
"redis_sre_agent.cli.query.get_instance_by_id",
31+
new=AsyncMock(),
32+
) as mock_get_instance,
33+
):
34+
result = runner.invoke(query, ["What is Redis SRE?"])
35+
36+
assert result.exit_code == 0, result.output
37+
mock_get_knowledge.assert_called_once()
38+
mock_get_sre.assert_not_called()
39+
mock_get_instance.assert_not_awaited()
40+
mock_agent.process_query.assert_awaited_once()
41+
42+
43+
def test_query_with_instance_uses_sre_agent_and_passes_instance_context():
44+
runner = CliRunner()
45+
46+
class DummyInstance:
47+
def __init__(self, id: str, name: str): # noqa: A003 - keep click-style arg name
48+
self.id = id
49+
self.name = name
50+
51+
instance = DummyInstance("redis-prod-123", "Haink Production")
52+
53+
mock_sre_agent = MagicMock()
54+
mock_sre_agent.process_query = AsyncMock(return_value="ok")
55+
56+
with (
57+
patch(
58+
"redis_sre_agent.cli.query.get_instance_by_id",
59+
new=AsyncMock(return_value=instance),
60+
) as mock_get_instance,
61+
patch(
62+
"redis_sre_agent.cli.query.get_sre_agent", return_value=mock_sre_agent
63+
) as mock_get_sre,
64+
patch("redis_sre_agent.cli.query.get_knowledge_agent") as mock_get_knowledge,
65+
):
66+
# Use -r / --redis-instance-id option to select instance
67+
result = runner.invoke(
68+
query,
69+
[
70+
"Should I scale this instance yet?",
71+
"-r",
72+
instance.id,
73+
],
74+
)
75+
76+
assert result.exit_code == 0, result.output
77+
78+
# Instance lookup should happen once with the provided ID
79+
mock_get_instance.assert_awaited_once_with(instance.id)
80+
81+
# SRE agent should be used (not the knowledge agent)
82+
mock_get_sre.assert_called_once()
83+
mock_get_knowledge.assert_not_called()
84+
85+
# The agent should be called exactly once
86+
mock_sre_agent.process_query.assert_awaited_once()
87+
_, kwargs = mock_sre_agent.process_query.call_args
88+
89+
# Critical behavior: CLI must pass instance context through to the agent
90+
assert kwargs.get("context") == {"instance_id": instance.id}
91+
92+
93+
def test_query_with_unknown_instance_falls_back_to_knowledge_agent():
94+
runner = CliRunner()
95+
96+
mock_agent = MagicMock()
97+
mock_agent.process_query = AsyncMock(return_value="ok")
98+
99+
missing_id = "nonexistent-instance-id"
100+
101+
with (
102+
patch(
103+
"redis_sre_agent.cli.query.get_instance_by_id",
104+
new=AsyncMock(return_value=None),
105+
) as mock_get_instance,
106+
patch(
107+
"redis_sre_agent.cli.query.get_knowledge_agent", return_value=mock_agent
108+
) as mock_get_knowledge,
109+
patch("redis_sre_agent.cli.query.get_sre_agent") as mock_get_sre,
110+
):
111+
result = runner.invoke(
112+
query,
113+
[
114+
"-r",
115+
missing_id,
116+
"Check the health of this instance",
117+
],
118+
)
119+
120+
assert result.exit_code == 0, result.output
121+
122+
# We attempted to resolve the instance ID
123+
mock_get_instance.assert_awaited_once_with(missing_id)
124+
125+
# Without a resolved instance, we should route to the knowledge agent
126+
mock_get_knowledge.assert_called_once()
127+
mock_get_sre.assert_not_called()
128+
129+
# Knowledge agent is called once; it should not receive a non-None instance context
130+
mock_agent.process_query.assert_awaited_once()
131+
_, kwargs = mock_agent.process_query.call_args
132+
assert kwargs.get("context") is None

0 commit comments

Comments
 (0)