-
Notifications
You must be signed in to change notification settings - Fork 3.1k
add sre-agent-code #147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
add sre-agent-code #147
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
OPENAI_API_KEY="your-openai-key" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
# Junior SRE-Agent | ||
|
||
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: | ||
|
||
* **Local CLI** for ad-hoc queries | ||
* **Event Listener** for streaming chat events via Xpander Chat UI | ||
|
||
We use the following tech stack: | ||
|
||
* Agno's Agent framework | ||
* Xpander for event streaming & agent management | ||
* Kubernetes (kubectl CLI) | ||
|
||
--- | ||
|
||
## Setup and Installation | ||
|
||
Ensure you have Python 3.12 or later installed on your system. | ||
|
||
### Clone the repository | ||
|
||
```bash | ||
git clone <your-repository-url> | ||
cd sre-agent-xpander.ai | ||
``` | ||
|
||
### Create & activate virtual environment | ||
|
||
```bash | ||
python3.12 -m venv .venv | ||
source .venv/bin/activate # macOS/Linux | ||
.venv\Scripts\activate # Windows | ||
``` | ||
|
||
### Install dependencies | ||
|
||
```bash | ||
pip install -r requirements.txt | ||
``` | ||
|
||
### Configure credentials | ||
|
||
```bash | ||
cp .env.example | ||
cp xpander_config.json.example | ||
``` | ||
|
||
**Configure `.env` file for OpenAI:** | ||
```bash | ||
OPENAI_API_KEY=your_openai_api_key_here | ||
``` | ||
|
||
**Configure `xpander_config.json` for Xpander credentials:** | ||
```json | ||
{ | ||
"agent_id": "your_xpander_agent_id", | ||
"api_key": "your_xpander_api_key", | ||
"org_id": "your_xpander_org_id", | ||
"base_url": "https://agent-controller.xpander.ai" | ||
} | ||
|
||
## Xpander Agent Configuration | ||
|
||
Follow these steps to configure your Xpander agent: | ||
|
||
1. Sign in to the Xpander dashboard at [https://app.xpander.ai](https://app.xpander.ai) | ||
2. Create a new agent (or select an existing one) and note its **Agent ID** and **Organization ID** | ||
3. Go to the **API Keys** section and generate a new API key or use default | ||
4. Copy the key and update your `xpander_config.json` file: | ||
|
||
--- | ||
|
||
## Run the project | ||
|
||
### CLI Mode | ||
|
||
```bash | ||
python sre_agent.py | ||
``` | ||
|
||
Type your queries at the `➜ ` prompt and enter `exit` or `quit` to stop. | ||
|
||
### Event Listener Mode | ||
|
||
```bash | ||
python xpander_handler.py | ||
``` | ||
|
||
Incoming messages will be forwarded to the SREAgent, any detected `kubectl` commands run live, and responses streamed back via SSE. | ||
|
||
--- | ||
|
||
## 📬 Stay Updated with Our Newsletter! | ||
|
||
**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) | ||
|
||
[](https://join.dailydoseofds.com) | ||
|
||
--- | ||
|
||
## Contribution | ||
|
||
Contributions are welcome! Please fork the repository, create a feature branch, and submit a Pull Request with your improvements. |
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,4 @@ | ||||||||||||||||||
xpander-utils | ||||||||||||||||||
agno | ||||||||||||||||||
openai | ||||||||||||||||||
python-dotenv | ||||||||||||||||||
Comment on lines
+1
to
+4
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Pin dependency versions for security and reproducibility. Consider pinning specific versions to ensure consistent builds and prevent potential security vulnerabilities from automatic updates. -xpander-utils
-agno
-openai
-python-dotenv
+xpander-utils>=1.0.0,<2.0.0
+agno>=1.0.0,<2.0.0
+openai>=1.0.0,<2.0.0
+python-dotenv>=1.0.0,<2.0.0 Alternatively, use exact versions if you need strict reproducibility: -xpander-utils
-agno
-openai
-python-dotenv
+xpander-utils==1.2.3
+agno==2.1.0
+openai==1.12.0
+python-dotenv==1.0.0 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,108 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import asyncio | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import json | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import logging | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import re | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import subprocess | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from pathlib import Path | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from typing import Optional, Any | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+1
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainVerify shlex module availability for security fix. The security fix for command injection requires the 🏁 Script executed: #!/bin/bash
# Description: Verify that shlex is part of Python standard library and check for its usage in the codebase
# Test 1: Check if shlex is mentioned in any Python files
echo "Checking for existing shlex usage in the codebase:"
rg -l "import shlex|from shlex import" --type py
# Test 2: Verify shlex is available in Python standard library
python3 -c "import shlex; print('shlex module is available')" Length of output: 257 Add shlex import for secure command handling and validate kubectl commands Since Suggested changes in import subprocess
+import shlex
from pathlib import Path
from typing import Optional, Any • Add 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from agno.agent import Agent | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from agno.models.openai import OpenAIChat | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from agno.tools.mcp import MultiMCPTools | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from agno.tools.thinking import ThinkingTools | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from dotenv import load_dotenv | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from xpander_utils.sdk.adapters import AgnoAdapter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# set up logging | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
t_logger = logging.getLogger(__name__) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# look for any kubectl command and strip code fences | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
KUBECTL = re.compile(r"kubectl\s+(.+)", re.IGNORECASE) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
FENCE = re.compile(r"```[\s\S]*?```", re.MULTILINE) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
class LocalKubectlTool(MultiMCPTools): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
name = "kubectl" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def __init__(self) -> None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
super().__init__([self.name], env={}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# capture context once | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.ctx = subprocess.run( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
["kubectl","config","current-context"], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
capture_output=True, text=True, check=False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
).stdout.strip() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+26
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for kubectl context retrieval. The kubectl config command could fail if kubectl is not installed or configured properly. Consider handling this gracefully. def __init__(self) -> None:
super().__init__([self.name], env={})
# capture context once
- self.ctx = subprocess.run(
- ["kubectl","config","current-context"],
- capture_output=True, text=True, check=False
- ).stdout.strip()
+ try:
+ result = subprocess.run(
+ ["kubectl", "config", "current-context"],
+ capture_output=True, text=True, check=False
+ )
+ self.ctx = result.stdout.strip() if result.returncode == 0 else ""
+ except FileNotFoundError:
+ t_logger.warning("kubectl not found in PATH")
+ self.ctx = "" 📝 Committable suggestion
Suggested change
🧰 Tools🪛 Flake8 (7.2.0)[error] 30-30: missing whitespace after ',' (E231) [error] 30-30: missing whitespace after ',' (E231) 🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def kubectl(self, flags: str) -> str: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# run kubectl with saved context | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
cmd = ["kubectl"] + (["--context", self.ctx] if self.ctx else []) + flags.split() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
p = subprocess.run(cmd, capture_output=True, text=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return p.stdout if p.returncode == 0 else p.stderr | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
class SREAgent: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def __init__(self, adapter: AgnoAdapter) -> None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.adapter = adapter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.agent: Optional[Agent] = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.ktool = LocalKubectlTool() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
async def run( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
message: str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
user_id: str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
session_id: str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
cli: bool = False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) -> Any: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# initialize LLM agent if needed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if not self.agent: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.agent = self.build_agent() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# get AI response | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
resp = await ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
self.agent.aprint_response(message, user_id, session_id) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if cli | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
else self.agent.arun(message, user_id=user_id, session_id=session_id) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# remove code fences | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
clean = FENCE.sub( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
lambda m: "\n".join(m.group(0).splitlines()[1:-1]), resp.content | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# search anywhere for kubectl | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
m = KUBECTL.search(clean) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if m: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
flags = m.group(1).splitlines()[0].strip() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
resp.content = self.ktool.kubectl(flags) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
t_logger.info("ran kubectl %s", flags) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return resp | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+65
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Preserve AI response context when executing kubectl commands. The current implementation replaces the entire response content with just the kubectl output, losing the AI's explanation and context. Consider appending the output instead. # remove code fences
clean = FENCE.sub(
lambda m: "\n".join(m.group(0).splitlines()[1:-1]), resp.content
)
# search anywhere for kubectl
m = KUBECTL.search(clean)
if m:
flags = m.group(1).splitlines()[0].strip()
- resp.content = self.ktool.kubectl(flags)
+ kubectl_output = self.ktool.kubectl(flags)
+ resp.content = f"{resp.content}\n\n**Command output:**\n```\n{kubectl_output}\n```"
t_logger.info("ran kubectl %s", flags)
return resp 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def build_agent(self) -> Agent: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# set up the Agno agent with kubectl tool | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
prompt = self.adapter.get_system_prompt() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
instr = ([prompt] if isinstance(prompt, str) else list(prompt)) + [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"When user asks about Kubernetes, reply with a kubectl command.", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"Always run commands to fetch live data." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return Agent( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
model=OpenAIChat(id="gpt-4o"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
tools=[ThinkingTools(add_instructions=True), self.ktool], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
instructions=instr, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
storage=self.adapter.storage, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
markdown=True, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
add_history_to_messages=True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
async def _cli() -> None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
load_dotenv() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
cfg = json.loads(Path("xpander_config.json").read_text()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
backend = await asyncio.to_thread( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
AgnoAdapter, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
agent_id=cfg["agent_id"], api_key=cfg["api_key"], base_url=cfg.get("base_url") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
agent = SREAgent(backend) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
while True: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
text = input("➜ ").strip() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if text.lower() in {"exit","quit"}: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
break | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
print((await agent.run(text, user_id="cli", session_id="dev", cli=True)).content) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+93
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for configuration loading. The code assumes async def _cli() -> None:
load_dotenv()
- cfg = json.loads(Path("xpander_config.json").read_text())
+ try:
+ cfg = json.loads(Path("xpander_config.json").read_text())
+ except (FileNotFoundError, json.JSONDecodeError) as e:
+ print(f"Error loading configuration: {e}")
+ return
+
backend = await asyncio.to_thread(
AgnoAdapter,
agent_id=cfg["agent_id"], api_key=cfg["api_key"], base_url=cfg.get("base_url")
)
agent = SREAgent(backend)
while True:
text = input("➜ ").strip()
- if text.lower() in {"exit","quit"}:
+ if text.lower() in {"exit", "quit"}:
break
print((await agent.run(text, user_id="cli", session_id="dev", cli=True)).content) 📝 Committable suggestion
Suggested change
🧰 Tools🪛 Flake8 (7.2.0)[error] 93-93: expected 2 blank lines, found 1 (E302) [error] 103-103: missing whitespace after ',' (E231) 🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if __name__ == "__main__": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
asyncio.run(_cli()) |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,8 @@ | ||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||
"base_url": "https://agent-controller.xpander.ai", | ||||||||||||||||||||||||||||||
"org_id": "your-xpander-org-id", | ||||||||||||||||||||||||||||||
"agent_id": "your-xpander-agent-id", | ||||||||||||||||||||||||||||||
"api_key": "your-xpander-api-key", | ||||||||||||||||||||||||||||||
"controller_url": "https://agent-controller.xpander.ai" | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
Comment on lines
+1
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Fix JSON formatting and remove redundant configuration. The configuration has formatting inconsistencies and contains duplicate URL fields that may confuse users. -{
- "base_url": "https://agent-controller.xpander.ai",
- "org_id": "your-xpander-org-id",
- "agent_id": "your-xpander-agent-id",
- "api_key": "your-xpander-api-key",
- "controller_url": "https://agent-controller.xpander.ai"
- }
+{
+ "base_url": "https://agent-controller.xpander.ai",
+ "org_id": "your-xpander-org-id",
+ "agent_id": "your-xpander-agent-id",
+ "api_key": "your-xpander-api-key"
+} The 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,61 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import asyncio | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import json | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import logging | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from pathlib import Path | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from dotenv import load_dotenv | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Disable UNIX-only signal handlers on Windows | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import asyncio as _asyncio | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_asyncio.AbstractEventLoop.add_signal_handler = lambda *a, **k: None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+7
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace signal handler monkey patching with proper cross-platform solution. The current approach of monkey patching Consider using a more robust cross-platform approach: -# Disable UNIX-only signal handlers on Windows
-import asyncio as _asyncio
-_asyncio.AbstractEventLoop.add_signal_handler = lambda *a, **k: None
+import sys
+
+# Check platform compatibility for signal handlers
+if sys.platform == "win32":
+ import signal
+ # Windows-specific signal handling if needed
+ pass Or use a library like 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from xpander_utils.events import XpanderEventListener, AgentExecutionResult, AgentExecution | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from xpander_utils.sdk.adapters import AgnoAdapter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from sre_agent import SREAgent | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+11
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Fix import order to comply with PEP 8. Imports should be grouped and ordered: standard library, third-party, local imports. import asyncio
import json
import logging
+import sys
from pathlib import Path
-from dotenv import load_dotenv
-# Disable UNIX-only signal handlers on Windows
-import asyncio as _asyncio
-_asyncio.AbstractEventLoop.add_signal_handler = lambda *a, **k: None
-
+from dotenv import load_dotenv
from xpander_utils.events import XpanderEventListener, AgentExecutionResult, AgentExecution
from xpander_utils.sdk.adapters import AgnoAdapter
+
from sre_agent import SREAgent
🧰 Tools🪛 Flake8 (7.2.0)[error] 11-11: module level import not at top of file (E402) [error] 12-12: module level import not at top of file (E402) [error] 13-13: module level import not at top of file (E402) 🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Init | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
load_dotenv() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logging.basicConfig(level=logging.INFO) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger = logging.getLogger(__name__) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
cfg = json.loads(Path("xpander_config.json").read_text()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add error handling for configuration file loading. The current implementation will raise an unhandled exception if the configuration file is missing or malformed. -cfg = json.loads(Path("xpander_config.json").read_text())
+try:
+ cfg = json.loads(Path("xpander_config.json").read_text())
+except FileNotFoundError:
+ logger.error("Configuration file 'xpander_config.json' not found")
+ raise
+except json.JSONDecodeError as e:
+ logger.error("Invalid JSON in configuration file: %s", e)
+ raise
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Management-API client | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
xp_adapter = asyncio.run( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
asyncio.to_thread( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
AgnoAdapter, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
agent_id=cfg["agent_id"], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
api_key=cfg["api_key"], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+20
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid blocking operations at module level. Using -cfg = json.loads(Path("xpander_config.json").read_text())
-
-# Management-API client
-xp_adapter = asyncio.run(
- asyncio.to_thread(
- AgnoAdapter,
- agent_id=cfg["agent_id"],
- api_key=cfg["api_key"],
- )
-)
-
-agent = SREAgent(xp_adapter)
+async def initialize_components():
+ """Initialize configuration and components."""
+ cfg = json.loads(Path("xpander_config.json").read_text())
+
+ # Management-API client
+ xp_adapter = await asyncio.to_thread(
+ AgnoAdapter,
+ agent_id=cfg["agent_id"],
+ api_key=cfg["api_key"],
+ )
+
+ agent = SREAgent(xp_adapter)
+ return cfg, xp_adapter, agent Then call this function in a proper async context or main function. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
agent = SREAgent(xp_adapter) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Execution callback (forward only) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
async def handle_execution_request(task: AgentExecution) -> AgentExecutionResult: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Optional: register task for Xpander metrics | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
await asyncio.to_thread( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
xp_adapter.agent.init_task, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
execution=task.model_dump() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
resp = await agent.run( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
message=task.input.text, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
user_id=task.input.user.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
session_id=task.memory_thread_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
cli=False, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return AgentExecutionResult(result=resp.content, is_success=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
except Exception as exc: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.exception("Error handling execution request") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return AgentExecutionResult(result=str(exc), is_success=False) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+34
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add missing blank lines before function definition. PEP 8 requires 2 blank lines before top-level function definitions. agent = SREAgent(xp_adapter)
+
# Execution callback (forward only)
async def handle_execution_request(task: AgentExecution) -> AgentExecutionResult: 📝 Committable suggestion
Suggested change
🧰 Tools🪛 Flake8 (7.2.0)[error] 34-34: expected 2 blank lines, found 1 (E302) 🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
# Start SSE listener | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
listener = XpanderEventListener( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
api_key = cfg["api_key"], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
organization_id = cfg["org_id"], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
agent_id = cfg["agent_id"], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
base_url = cfg["base_url"], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+55
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Fix formatting issues in function call. The parameter alignment has inconsistent spacing that violates PEP 8. listener = XpanderEventListener(
- api_key = cfg["api_key"],
- organization_id = cfg["org_id"],
- agent_id = cfg["agent_id"],
- base_url = cfg["base_url"],
+ api_key=cfg["api_key"],
+ organization_id=cfg["org_id"],
+ agent_id=cfg["agent_id"],
+ base_url=cfg["base_url"],
) 📝 Committable suggestion
Suggested change
🧰 Tools🪛 Flake8 (7.2.0)[error] 56-56: unexpected spaces around keyword / parameter equals (E251) [error] 56-56: multiple spaces before operator (E221) [error] 56-56: unexpected spaces around keyword / parameter equals (E251) [error] 57-57: unexpected spaces around keyword / parameter equals (E251) [error] 57-57: unexpected spaces around keyword / parameter equals (E251) [error] 58-58: unexpected spaces around keyword / parameter equals (E251) [error] 58-58: multiple spaces before operator (E221) [error] 58-58: unexpected spaces around keyword / parameter equals (E251) [error] 59-59: unexpected spaces around keyword / parameter equals (E251) [error] 59-59: multiple spaces before operator (E221) [error] 59-59: unexpected spaces around keyword / parameter equals (E251) 🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
listener.register(on_execution_request=handle_execution_request) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix incomplete copy commands in setup instructions.
The copy commands are missing destination filenames, which will cause errors when users follow the instructions.
In sre-agent-xpander.ai/README.md around lines 43 to 46, the copy commands are
incomplete because they lack destination filenames. Update the commands to
specify the target filenames explicitly: change "cp .env.example" to "cp
.env.example .env" and "cp xpander_config.json.example" to "cp
xpander_config.json.example xpander_config.json" to ensure the files are copied
correctly during setup.