Skip to content

Commit b5e393f

Browse files
committed
add sre-agent-code
1 parent b235a60 commit b5e393f

File tree

6 files changed

+285
-0
lines changed

6 files changed

+285
-0
lines changed

sre-agent-xpander.ai/.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
OPENAI_API_KEY="your-openai-key"

sre-agent-xpander.ai/README.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Junior SRE-Agent
2+
3+
This project demonstrates how to build a Site Reliability Engineer (SRE) agent using Agno's Agent framework and Xpander's event streaming. It supports two modes:
4+
5+
* **Local CLI** for ad-hoc queries
6+
* **Event Listener** for streaming chat events via Xpander Chat UI
7+
8+
We use the following tech stack:
9+
10+
* Agno's Agent framework
11+
* Xpander for event streaming & agent management
12+
* Kubernetes (kubectl CLI)
13+
14+
---
15+
16+
## Setup and Installation
17+
18+
Ensure you have Python 3.12 or later installed on your system.
19+
20+
### Clone the repository
21+
22+
```bash
23+
git clone <your-repository-url>
24+
cd sre-agent-xpander.ai
25+
```
26+
27+
### Create & activate virtual environment
28+
29+
```bash
30+
python3.12 -m venv .venv
31+
source .venv/bin/activate # macOS/Linux
32+
.venv\Scripts\activate # Windows
33+
```
34+
35+
### Install dependencies
36+
37+
```bash
38+
pip install -r requirements.txt
39+
```
40+
41+
### Configure credentials
42+
43+
```bash
44+
cp .env.example
45+
cp xpander_config.json.example
46+
```
47+
48+
**Configure `.env` file for OpenAI:**
49+
```bash
50+
OPENAI_API_KEY=your_openai_api_key_here
51+
```
52+
53+
**Configure `xpander_config.json` for Xpander credentials:**
54+
```json
55+
{
56+
"agent_id": "your_xpander_agent_id",
57+
"api_key": "your_xpander_api_key",
58+
"org_id": "your_xpander_org_id",
59+
"base_url": "https://agent-controller.xpander.ai"
60+
}
61+
62+
## Xpander Agent Configuration
63+
64+
Follow these steps to configure your Xpander agent:
65+
66+
1. Sign in to the Xpander dashboard at [https://app.xpander.ai](https://app.xpander.ai)
67+
2. Create a new agent (or select an existing one) and note its **Agent ID** and **Organization ID**
68+
3. Go to the **API Keys** section and generate a new API key or use default
69+
4. Copy the key and update your `xpander_config.json` file:
70+
71+
---
72+
73+
## Run the project
74+
75+
### CLI Mode
76+
77+
```bash
78+
python sre_agent.py
79+
```
80+
81+
Type your queries at the `` prompt and enter `exit` or `quit` to stop.
82+
83+
### Event Listener Mode
84+
85+
```bash
86+
python xpander_handler.py
87+
```
88+
89+
Incoming messages will be forwarded to the SREAgent, any detected `kubectl` commands run live, and responses streamed back via SSE.
90+
91+
---
92+
93+
## 📬 Stay Updated with Our Newsletter!
94+
95+
**Get a FREE Data Science eBook** 📖 with 150+ essential lessons in Data Science when you subscribe to our newsletter! Stay in the loop with the latest tutorials, insights, and exclusive resources. [Subscribe now!](https://join.dailydoseofds.com)
96+
97+
[![Daily Dose of Data Science Newsletter](https://github.com/patchy631/ai-engineering/blob/main/resources/join_ddods.png)](https://join.dailydoseofds.com)
98+
99+
---
100+
101+
## Contribution
102+
103+
Contributions are welcome! Please fork the repository, create a feature branch, and submit a Pull Request with your improvements.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
xpander-utils
2+
agno
3+
openai
4+
python-dotenv

sre-agent-xpander.ai/sre_agent.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import asyncio
2+
import json
3+
import logging
4+
import re
5+
import subprocess
6+
from pathlib import Path
7+
from typing import Optional, Any
8+
9+
from agno.agent import Agent
10+
from agno.models.openai import OpenAIChat
11+
from agno.tools.mcp import MultiMCPTools
12+
from agno.tools.thinking import ThinkingTools
13+
from dotenv import load_dotenv
14+
from xpander_utils.sdk.adapters import AgnoAdapter
15+
16+
# set up logging
17+
t_logger = logging.getLogger(__name__)
18+
19+
# look for any kubectl command and strip code fences
20+
KUBECTL = re.compile(r"kubectl\s+(.+)", re.IGNORECASE)
21+
FENCE = re.compile(r"```[\s\S]*?```", re.MULTILINE)
22+
23+
class LocalKubectlTool(MultiMCPTools):
24+
name = "kubectl"
25+
26+
def __init__(self) -> None:
27+
super().__init__([self.name], env={})
28+
# capture context once
29+
self.ctx = subprocess.run(
30+
["kubectl","config","current-context"],
31+
capture_output=True, text=True, check=False
32+
).stdout.strip()
33+
34+
def kubectl(self, flags: str) -> str:
35+
# run kubectl with saved context
36+
cmd = ["kubectl"] + (["--context", self.ctx] if self.ctx else []) + flags.split()
37+
p = subprocess.run(cmd, capture_output=True, text=True)
38+
return p.stdout if p.returncode == 0 else p.stderr
39+
40+
class SREAgent:
41+
def __init__(self, adapter: AgnoAdapter) -> None:
42+
self.adapter = adapter
43+
self.agent: Optional[Agent] = None
44+
self.ktool = LocalKubectlTool()
45+
46+
async def run(
47+
self,
48+
message: str,
49+
*,
50+
user_id: str,
51+
session_id: str,
52+
cli: bool = False
53+
) -> Any:
54+
# initialize LLM agent if needed
55+
if not self.agent:
56+
self.agent = self.build_agent()
57+
58+
# get AI response
59+
resp = await (
60+
self.agent.aprint_response(message, user_id, session_id)
61+
if cli
62+
else self.agent.arun(message, user_id=user_id, session_id=session_id)
63+
)
64+
65+
# remove code fences
66+
clean = FENCE.sub(
67+
lambda m: "\n".join(m.group(0).splitlines()[1:-1]), resp.content
68+
)
69+
# search anywhere for kubectl
70+
m = KUBECTL.search(clean)
71+
if m:
72+
flags = m.group(1).splitlines()[0].strip()
73+
resp.content = self.ktool.kubectl(flags)
74+
t_logger.info("ran kubectl %s", flags)
75+
return resp
76+
77+
def build_agent(self) -> Agent:
78+
# set up the Agno agent with kubectl tool
79+
prompt = self.adapter.get_system_prompt()
80+
instr = ([prompt] if isinstance(prompt, str) else list(prompt)) + [
81+
"When user asks about Kubernetes, reply with a kubectl command.",
82+
"Always run commands to fetch live data."
83+
]
84+
return Agent(
85+
model=OpenAIChat(id="gpt-4o"),
86+
tools=[ThinkingTools(add_instructions=True), self.ktool],
87+
instructions=instr,
88+
storage=self.adapter.storage,
89+
markdown=True,
90+
add_history_to_messages=True
91+
)
92+
93+
async def _cli() -> None:
94+
load_dotenv()
95+
cfg = json.loads(Path("xpander_config.json").read_text())
96+
backend = await asyncio.to_thread(
97+
AgnoAdapter,
98+
agent_id=cfg["agent_id"], api_key=cfg["api_key"], base_url=cfg.get("base_url")
99+
)
100+
agent = SREAgent(backend)
101+
while True:
102+
text = input("➜ ").strip()
103+
if text.lower() in {"exit","quit"}:
104+
break
105+
print((await agent.run(text, user_id="cli", session_id="dev", cli=True)).content)
106+
107+
if __name__ == "__main__":
108+
asyncio.run(_cli())
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"base_url": "https://agent-controller.xpander.ai",
3+
"org_id": "your-xpander-org-id",
4+
"agent_id": "your-xpander-agent-id",
5+
"api_key": "your-xpander-api-key",
6+
"controller_url": "https://agent-controller.xpander.ai"
7+
}
8+
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import asyncio
2+
import json
3+
import logging
4+
from pathlib import Path
5+
from dotenv import load_dotenv
6+
7+
# Disable UNIX-only signal handlers on Windows
8+
import asyncio as _asyncio
9+
_asyncio.AbstractEventLoop.add_signal_handler = lambda *a, **k: None
10+
11+
from xpander_utils.events import XpanderEventListener, AgentExecutionResult, AgentExecution
12+
from xpander_utils.sdk.adapters import AgnoAdapter
13+
from sre_agent import SREAgent
14+
15+
# Init
16+
load_dotenv()
17+
logging.basicConfig(level=logging.INFO)
18+
logger = logging.getLogger(__name__)
19+
20+
cfg = json.loads(Path("xpander_config.json").read_text())
21+
22+
# Management-API client
23+
xp_adapter = asyncio.run(
24+
asyncio.to_thread(
25+
AgnoAdapter,
26+
agent_id=cfg["agent_id"],
27+
api_key=cfg["api_key"],
28+
)
29+
)
30+
31+
agent = SREAgent(xp_adapter)
32+
33+
# Execution callback (forward only)
34+
async def handle_execution_request(task: AgentExecution) -> AgentExecutionResult:
35+
try:
36+
# Optional: register task for Xpander metrics
37+
await asyncio.to_thread(
38+
xp_adapter.agent.init_task,
39+
execution=task.model_dump()
40+
)
41+
42+
resp = await agent.run(
43+
message=task.input.text,
44+
user_id=task.input.user.id,
45+
session_id=task.memory_thread_id,
46+
cli=False,
47+
)
48+
return AgentExecutionResult(result=resp.content, is_success=True)
49+
50+
except Exception as exc:
51+
logger.exception("Error handling execution request")
52+
return AgentExecutionResult(result=str(exc), is_success=False)
53+
54+
# Start SSE listener
55+
listener = XpanderEventListener(
56+
api_key = cfg["api_key"],
57+
organization_id = cfg["org_id"],
58+
agent_id = cfg["agent_id"],
59+
base_url = cfg["base_url"],
60+
)
61+
listener.register(on_execution_request=handle_execution_request)

0 commit comments

Comments
 (0)