Skip to content

Commit a2c397c

Browse files
committed
add interactive AI-powered deubgging tool az aks agent
1 parent 7fd5e48 commit a2c397c

File tree

4 files changed

+168
-3
lines changed

4 files changed

+168
-3
lines changed

src/aks-preview/azext_aks_preview/_params.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
validate_nat_gateway_idle_timeout,
2424
validate_nat_gateway_managed_outbound_ip_count,
2525
)
26+
from azure.cli.core.api import get_config_dir
2627
from azure.cli.core.commands.parameters import (
2728
edge_zone_type,
2829
file_type,
@@ -2744,6 +2745,55 @@ def load_arguments(self, _):
27442745
help="Name of the load balancer configuration. Required.",
27452746
)
27462747

2748+
with self.argument_context("aks agent") as c:
2749+
c.positional("prompt", help="Ask any question and answer using available tools.")
2750+
c.argument(
2751+
"api_key",
2752+
default=None,
2753+
required=False,
2754+
help="API key to use for the LLM (if not given, uses environment variables AZURE_API_KEY, OPENAI_API_KEY).",
2755+
)
2756+
c.argument(
2757+
"model",
2758+
default=None,
2759+
required=False,
2760+
help="Model to use for the LLM. For example, azure/<deployment_name> for Azure OpenAI, or <model_name> for OpenAI.",
2761+
)
2762+
c.argument(
2763+
"interactive",
2764+
type=bool,
2765+
default=True,
2766+
required=False,
2767+
help="Enabled interactive mode. If set to false, the agent will not prompt for input and will run in batch mode.",
2768+
)
2769+
c.argument(
2770+
"max_steps",
2771+
type=int,
2772+
default=10,
2773+
required=False,
2774+
help="Maximum number of steps the LLM can take to investigate the issue.",
2775+
)
2776+
c.argument(
2777+
"echo",
2778+
type=bool,
2779+
default=True,
2780+
required=False,
2781+
help="Echo back the question provided to HolmesGPT in the output.",
2782+
)
2783+
c.argument(
2784+
"show_tool_output",
2785+
type=bool,
2786+
default=True,
2787+
required=False,
2788+
help="Show the output of each tool that was called.",
2789+
)
2790+
c.argument(
2791+
"config_file",
2792+
type=bool,
2793+
default=os.path.join(get_config_dir(), "agent.config"),
2794+
required=False,
2795+
help="Show the output of each tool that was called.",
2796+
)
27472797

27482798
def _get_default_install_location(exe_name):
27492799
system = platform.system()

src/aks-preview/azext_aks_preview/commands.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ def load_command_table(self, _):
186186
g.custom_command(
187187
"operation-abort", "aks_operation_abort", supports_no_wait=True
188188
)
189+
g.custom_command("agent", "aks_agent")
189190

190191
# AKS maintenance configuration commands
191192
with self.command_group(

src/aks-preview/azext_aks_preview/custom.py

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,26 @@
88
import json
99
import os
1010
import os.path
11+
from pathlib import Path
1112
import platform
12-
import ssl
13+
import socket
1314
import sys
1415
import threading
1516
import time
17+
import typer
18+
import uuid
1619
import webbrowser
1720

21+
from holmes.config import Config
22+
from holmes.core.prompt import build_initial_ask_messages
23+
from holmes.interactive import run_interactive_loop
24+
from holmes.logging import init_logging
25+
from holmes.plugins.interfaces import Issue
26+
from holmes.plugins.prompts import load_and_render_prompt
27+
from holmes.utils.console.logging import init_logging
28+
from holmes.utils.console.result import handle_result
29+
from holmes.plugins.destinations import DestinationType
30+
1831
from azext_aks_preview._client_factory import (
1932
CUSTOM_MGMT_AKS_PREVIEW,
2033
cf_agent_pools,
@@ -4333,3 +4346,102 @@ def aks_loadbalancer_rebalance_nodes(
43334346
}
43344347

43354348
return aks_loadbalancer_rebalance_internal(managed_clusters_client, parameters)
4349+
4350+
4351+
def aks_agent(
4352+
prompt,
4353+
api_key,
4354+
model,
4355+
interactive,
4356+
max_steps,
4357+
echo,
4358+
show_tool_output,
4359+
config_file,
4360+
):
4361+
4362+
# TODO: make log verbose configurable, currently disbled by [].
4363+
console = init_logging([])
4364+
4365+
# Detect and read piped input
4366+
piped_data = None
4367+
if not sys.stdin.isatty():
4368+
piped_data = sys.stdin.read().strip()
4369+
if interactive:
4370+
console.print(
4371+
"[bold yellow]Interactive mode disabled when reading piped input[/bold yellow]"
4372+
)
4373+
interactive = False
4374+
4375+
config_file = Path(config_file)
4376+
config = Config.load_from_file(
4377+
config_file,
4378+
api_key=api_key,
4379+
model=model,
4380+
max_steps=max_steps,
4381+
)
4382+
4383+
ai = config.create_console_toolcalling_llm(
4384+
dal=None
4385+
)
4386+
template_context = {
4387+
"toolsets": ai.tool_executor.toolsets,
4388+
"runbooks": config.get_runbook_catalog(),
4389+
}
4390+
# TODO: extend the system prompt with AKS context
4391+
system_prompt= "builtin://generic_ask.jinja2"
4392+
system_prompt_rendered = load_and_render_prompt(system_prompt, template_context) # type: ignore
4393+
4394+
if not prompt and not interactive and not piped_data:
4395+
raise typer.BadParameter(
4396+
"Either the 'prompt' argument must be provided (unless using --interactive mode)."
4397+
)
4398+
4399+
# Handle piped data
4400+
if piped_data:
4401+
if prompt:
4402+
# User provided both piped data and a prompt
4403+
prompt = f"Here's some piped output:\n\n{piped_data}\n\n{prompt}"
4404+
else:
4405+
# Only piped data, no prompt - ask what to do with it
4406+
prompt = f"Here's some piped output:\n\n{piped_data}\n\nWhat can you tell me about this output?"
4407+
4408+
if echo and not interactive and prompt:
4409+
console.print("[bold yellow]User:[/bold yellow] " + prompt)
4410+
4411+
# TODO: add refresh-toolset to refresh the toolset if it has changed
4412+
if interactive:
4413+
run_interactive_loop(
4414+
ai,
4415+
console,
4416+
system_prompt_rendered,
4417+
prompt,
4418+
)
4419+
return
4420+
4421+
messages = build_initial_ask_messages(
4422+
console,
4423+
system_prompt_rendered,
4424+
prompt, # type: ignore
4425+
)
4426+
4427+
response = ai.call(messages)
4428+
4429+
4430+
messages = response.messages # type: ignore # Update messages with the full history
4431+
4432+
issue = Issue(
4433+
id=str(uuid.uuid4()),
4434+
name=prompt,
4435+
source_type="holmes-ask",
4436+
raw={"prompt": prompt, "full_conversation": messages},
4437+
source_instance_id=socket.gethostname(),
4438+
)
4439+
handle_result(
4440+
response,
4441+
console,
4442+
DestinationType.CLI,
4443+
config,
4444+
issue,
4445+
show_tool_output,
4446+
False,
4447+
)

src/aks-preview/setup.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from codecs import open as open1
99

10-
from setuptools import setup, find_packages
10+
from setuptools import find_packages, setup
1111

1212
VERSION = "18.0.0b18"
1313

@@ -23,7 +23,9 @@
2323
"License :: OSI Approved :: MIT License",
2424
]
2525

26-
DEPENDENCIES = []
26+
DEPENDENCIES = [
27+
"holmesgpt @ git+ssh://[email protected]/robusta-dev/holmesgpt@create-console-package", # will use a official pypi package once available
28+
]
2729

2830
with open1("README.rst", "r", encoding="utf-8") as f:
2931
README = f.read()

0 commit comments

Comments
 (0)