Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,8 @@ marimo/_static/
marimo/_lsp/
__marimo__/

agent_evals
agent_evals

my_knowledge.db
response.md
tmp
39 changes: 28 additions & 11 deletions devops_agent/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

console = Console()


@click.group()
@click.version_option(version="0.1.0")
def cli():
Expand All @@ -20,23 +21,36 @@ def default_provider() -> str:
return "openai"


def default_model() -> str:
return "gpt-4o"


@cli.command()
@click.option('--log-file', type=click.Path(exists=True), help='Path to log file to analyze')
@click.option('--provider', type=str, help='Configure the agent with one of the enterprise grade providers like OpenAI, Anthropic, Gemini')
@click.option('--provider', type=str,
help='Configure the agent with one of the enterprise grade providers like OpenAI, Anthropic, Gemini')
@click.option('--model', type=str,
help='Configure the model name in accordance with the provider selected like gpt-4o, gemini-flash-2.5, etc.')
@click.option('--output', type=click.Path(exists=True), help='Path to output file')
@click.option('--query', type=str, help='Query to ask the DevOps agent')
@click.option('--output', type=click.Path(), help='Output file path (optional)')
@click.option('--format', type=click.Choice(['text', 'json', 'markdown']), default='text', help='Output format')
@click.option('--interactive', '-i', is_flag=True, help='Run in interactive mode')
def run(log_file, provider, query, output, format, interactive):
@click.option('--debug_mode', help='Run all agents in debug mode, don\'t use in production')
def run(log_file, provider, model, query, output, format, interactive, debug_mode):
"""Run the DevOps agent with specified options"""

if not provider:
console.print("[yellow]No provider specified, defaulting to openai[/yellow]")
provider = default_provider()

if not model:
console.print("[yellow]No model specified, defaulting to gpt-4o[/yellow]")
provider = default_model()

# Interactive mode
if interactive:
run_interactive_mode(provider, output, format)
run_interactive_mode(provider, model, output, format, debug_mode)
return

# Single query mode (original behavior)
Expand All @@ -57,7 +71,8 @@ def run(log_file, provider, query, output, format, interactive):
console.print(f"[yellow]Analyzing log file:[/yellow] {log_file}")
try:
file_path = Path(__file__).parent.joinpath(log_file)
response = execute_log_analysis_agent(provider=provider, log_file=file_path)
response = execute_log_analysis_agent(provider=provider, model=model, log_file=file_path,
debug_mode=debug_mode)
console.print(Panel.fit(
f"[bold yellow]Assistant:[/bold yellow] [dim]{response}[/dim]",
border_style="yellow"
Expand All @@ -71,10 +86,10 @@ def run(log_file, provider, query, output, format, interactive):
console.print(f"\n[red]Error:[/red] {str(e)}")

if query:
process_query(provider, query, output, format)
process_query(provider, query, output, format, debug_mode)


def run_interactive_mode(provider: str, output: str = None, format: str = 'text'):
def run_interactive_mode(provider: str, model: str, output: str = None, format: str = 'text', debug_mode: bool = False):
"""Run the agent in interactive mode with continuous conversation"""

console.print(Panel.fit(
Expand Down Expand Up @@ -106,7 +121,8 @@ def run_interactive_mode(provider: str, output: str = None, format: str = 'text'
))

try:
response = execute_master_agent(provider=provider, user_query=user_input)
response = execute_master_agent(provider=provider, model_str=model, user_query=user_input,
debug_mode=debug_mode)
console.print(Panel.fit(
f"[bold yellow]Assistant:[/bold yellow] [dim]{response}[/dim]",
border_style="yellow"
Expand All @@ -128,7 +144,8 @@ def run_interactive_mode(provider: str, output: str = None, format: str = 'text'
break


def process_query(provider: str, query: str, output: str = None, format: str = 'text'):
def process_query(provider: str, model: str, query: str, output: str = None, format: str = 'text',
debug_mode: bool = False):
"""Process a single query"""
console.print(f"[yellow]Processing query:[/yellow] {query}")
console.print(Panel.fit(
Expand All @@ -137,7 +154,7 @@ def process_query(provider: str, query: str, output: str = None, format: str = '
))

try:
response = execute_master_agent(provider=provider, user_query=query)
response = execute_master_agent(provider=provider, model_str=model, user_query=query, debug_mode=debug_mode)
console.print(Panel.fit(
f"[bold yellow]Assistant:[/bold yellow] [dim]{response}[/dim]",
border_style="yellow"
Expand Down Expand Up @@ -172,7 +189,7 @@ def save_to_file(filepath: str, query: str, response: str, format: str):
mode = 'a' if output_path.exists() else 'w'
with open(output_path, mode) as f:
if mode == 'a':
f.write("\n" + "="*50 + "\n\n")
f.write("\n" + "=" * 50 + "\n\n")
f.write(content)


Expand All @@ -194,4 +211,4 @@ def main():


if __name__ == '__main__':
main()
main()
11 changes: 6 additions & 5 deletions devops_agent/core/devops_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,20 @@

console = Console()

def execute_devops_agent(provider: str) -> Agent:
def execute_devops_agent(provider: str, model: str, debug_mode: bool = False) -> Agent:
console.print(Panel.fit(
"[bold cyan]DevOps Agent Invoking...[/bold cyan]",
border_style="cyan"
))
llm_provider = provider.lower().strip()
if llm_provider == 'openai':
model = OpenAIChat(id="gpt-4o", api_key=os.environ.get('OPENAI_API_KEY'))
model = OpenAIChat(id=model, api_key=os.environ.get('OPENAI_API_KEY'))
elif llm_provider == 'anthropic':
model = Claude(id="claude-sonnet-4-5-20250929", temperature=0.6, api_key=os.environ.get('ANTHROPIC_API_KEY'))
model = Claude(id=model, temperature=0.6, api_key=os.environ.get('ANTHROPIC_API_KEY'))
elif llm_provider == 'google':
model = Gemini(id="gemini-2.5-flash", temperature=0.6, api_key=os.environ.get('GEMINI_API_KEY'))
model = Gemini(id=model, temperature=0.6, api_key=os.environ.get('GEMINI_API_KEY'))
else:
model = OpenAIChat(id="gpt-5-mini"), #default
model = OpenAIChat(id=model), #default

devops_assist = Agent(
name="DevOps Agent",
Expand All @@ -52,6 +52,7 @@ def execute_devops_agent(provider: str) -> Agent:
"""),
stream_intermediate_steps=True,
markdown=True,
debug_mode=debug_mode,
)

return devops_assist
38 changes: 7 additions & 31 deletions devops_agent/core/kubernetes_agent.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,20 @@
import asyncio
import os
from textwrap import dedent

from agno.agent import Agent
from agno.knowledge import Knowledge
from agno.models.openai import OpenAIChat
from agno.models.anthropic import Claude
from agno.models.google.gemini import Gemini
from agno.vectordb.qdrant import Qdrant
from agno.knowledge.embedder.fastembed import FastEmbedEmbedder
from qdrant_client.http.models import VectorParams, Distance
from rich.console import Console
from rich.panel import Panel

from devops_agent.utils.prompt_generator_from_poml import prompt_from_poml
from qdrant_client.qdrant_client import QdrantClient

k8s_prompt = prompt_from_poml('kubernetes.poml')

# qclient = QdrantClient(url=os.environ.get('QDRANT_URL'), api_key=os.environ.get('QDRANT_API_KEY'))
# if not qclient.collection_exists("devops-memory"):
# qclient.create_collection(collection_name="devops-memory",
# vectors_config=VectorParams(size=768, distance=Distance.COSINE))
#
# vector_db = Qdrant(collection="devops-memory", url=os.environ.get('QDRANT_URL'),
# api_key=os.environ.get('QDRANT_API_KEY'),
# embedder=FastEmbedEmbedder(id="snowflake/snowflake-arctic-embed-m"))
#
# # Create knowledge base
# knowledge = Knowledge(vector_db=vector_db)

console = Console()

def execute_k8s_agent(provider: str, user_query: str = None) -> Agent:
def execute_k8s_agent(provider: str, model:str, debug_mode: bool=False) -> Agent:

console.print(Panel.fit(
"[bold cyan]Kubernetes Agent Invoking...[/bold cyan]",
Expand All @@ -41,18 +23,19 @@ def execute_k8s_agent(provider: str, user_query: str = None) -> Agent:

llm_provider = provider.lower().strip()
if llm_provider == 'openai':
model = OpenAIChat(id="gpt-4o", api_key=os.environ.get('OPENAI_API_KEY'))
model = OpenAIChat(id=model, api_key=os.environ.get('OPENAI_API_KEY'))
elif llm_provider == 'anthropic':
model = Claude(id="claude-sonnet-4-5-20250929", temperature=0.6, api_key=os.environ.get('ANTHROPIC_API_KEY'))
model = Claude(id=model, temperature=0.6, api_key=os.environ.get('ANTHROPIC_API_KEY'))
elif llm_provider == 'google':
model = Gemini(id="gemini-2.5-flash", temperature=0.6, api_key=os.environ.get('GEMINI_API_KEY'))
model = Gemini(id=model, temperature=0.6, api_key=os.environ.get('GEMINI_API_KEY'))
else:
model = OpenAIChat(id="gpt-5-mini"), # default

k8s_assist = Agent(
name="Kubernetes Agent",
model=model,
description="You help answer questions about the application with kubernetes design and implementation domain of any infrastructure like Azure(AKS), AWS(EKS), and GCP(GKS)",
description="You help answer questions about the application with kubernetes design and implementation domain of"
" any infrastructure like Azure(AKS), AWS(EKS), and GCP(GKS)",
instructions=k8s_prompt,
additional_input=dedent("""\
Instruction: You should always answer scenarios like below (few examples as below).
Expand All @@ -67,14 +50,7 @@ def execute_k8s_agent(provider: str, user_query: str = None) -> Agent:
"""),
stream_intermediate_steps=True,
markdown=True,
debug_mode=debug_mode
)

# response = k8s_assist.run(user_query, stream_intermediate_steps=True, retry=3)
#
# asyncio.run(
# knowledge.add_content_async(text_content=response.content,
# metadata={"agent_id": response.agent_id, "session_id": response.session_id})
# )
# return response.content

return k8s_assist
14 changes: 8 additions & 6 deletions devops_agent/core/log_analysis_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@

console = Console()

def execute_log_analysis_agent(provider: str, log_file: Path) -> Agent:
def execute_log_analysis_agent(provider: str, model: str, log_file: Path, debug_mode: bool= False) -> Agent:
console.print(Panel.fit(
"[bold cyan]Log Analysis Agent Invoking...[/bold cyan]",
border_style="cyan"
))
llm_provider = provider.lower().strip()
if llm_provider == 'openai':
model = OpenAIChat(id="gtp-4o", api_key=os.environ.get('OPENAI_API_KEY'))
model = OpenAIChat(id=model, api_key=os.environ.get('OPENAI_API_KEY'))
elif llm_provider == 'anthropic':
model = Claude(id="claude-sonnet-4-5-20250929", temperature=0.6, api_key=os.environ.get('ANTHROPIC_API_KEY'))
model = Claude(id=model, temperature=0.6, api_key=os.environ.get('ANTHROPIC_API_KEY'))
elif llm_provider == 'google':
model = Gemini(id="gemini-2.5-flash", temperature=0.6, api_key=os.environ.get('GEMINI_API_KEY'))
model = Gemini(id=model, temperature=0.6, api_key=os.environ.get('GEMINI_API_KEY'))
else:
model = OpenAIChat(id="gpt-5-mini"), #default
model = OpenAIChat(id=model), #default

file_analysis_agent = Agent(
name="LogFile Analysis Agent",
Expand All @@ -35,10 +35,12 @@ def execute_log_analysis_agent(provider: str, log_file: Path) -> Agent:
"You are an AI agent that can analyze log files.",
"You are given a log file and you need to analyse and give detailed answer to the question from the user.",
],
debug_mode=debug_mode
)

print("executing the log analysis")
user_query = 'analyse and give all the insights such as critical errors, patterns, anomalies, or any other significant findings'
user_query = ('analyse and give all the insights such as critical errors, patterns, anomalies, or any other '
'significant findings')
response = file_analysis_agent.run(user_query, files=[File(filepath=log_file)])

return response.content
62 changes: 40 additions & 22 deletions devops_agent/core/master_agent.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import asyncio
import os
from pathlib import Path

from agno.knowledge import Knowledge
from agno.models.openai import OpenAIChat
Expand All @@ -10,9 +9,11 @@
from agno.tools.reasoning import ReasoningTools
from agno.vectordb.qdrant import Qdrant
from agno.db.in_memory import InMemoryDb
from agno.db.sqlite import SqliteDb
from agno.vectordb.chroma import ChromaDb
from agno.knowledge.embedder.fastembed import FastEmbedEmbedder
from qdrant_client import QdrantClient
from qdrant_client.http.models import VectorParams, Distance
from qdrant_client.http.models import VectorParams, Distance, models

from devops_agent.core.devops_agent import execute_devops_agent
from devops_agent.core.kubernetes_agent import execute_k8s_agent
Expand All @@ -25,37 +26,56 @@

console = Console()

qclient = QdrantClient(url=os.environ.get('QDRANT_URL'), api_key=os.environ.get('QDRANT_API_KEY'))
if not qclient.collection_exists("devops-memory"):
qclient.create_collection(collection_name="devops-memory",
vectors_config=VectorParams(size=768, distance=Distance.COSINE))
try:
qclient = QdrantClient(url=os.environ.get('QDRANT_URL'), api_key=os.environ.get('QDRANT_API_KEY'))
if not qclient.collection_exists("devops-memory"):
qclient.create_collection(collection_name="devops-memory",
vectors_config=VectorParams(size=768, distance=Distance.COSINE))

vector_db = Qdrant(collection="devops-memory", url=os.environ.get('QDRANT_URL'),
api_key=os.environ.get('QDRANT_API_KEY'),
embedder=FastEmbedEmbedder(id="snowflake/snowflake-arctic-embed-m"))
# Create vector_db with remote connection
vector_db = Qdrant(collection="devops-memory",
url=os.environ.get('QDRANT_URL'),
api_key=os.environ.get('QDRANT_API_KEY'),
embedder=FastEmbedEmbedder(id="snowflake/snowflake-arctic-embed-m"))

# Create knowledge base
knowledge = Knowledge(vector_db=vector_db)
# Create knowledge base
knowledge = Knowledge(vector_db=vector_db)

except Exception as e:
console.print(f"[yellow]Warning: Could not connect to remote Qdrant, falling back to in-memory mode: {e}[/yellow]")

def execute_master_agent(provider: str, user_query: str = None) -> str:
# SQLite for content tracking
contents_db = SqliteDb(db_file="my_knowledge.db")

# Create Knowledge with SQLite contents DB and ChromaDB
knowledge = Knowledge(
name="Basic SDK Knowledge Base",
description="Agno 2.0 Knowledge Implementation with ChromaDB",
contents_db=contents_db,
vector_db=ChromaDb(
collection="vectors", path="tmp/chromadb", persistent_client=True,
embedder=FastEmbedEmbedder(id="snowflake/snowflake-arctic-embed-m")
),
)

def execute_master_agent(provider: str, model_str: str, user_query: str = None, debug_mode: bool=False) -> str:
llm_provider = provider.lower().strip()
if llm_provider == 'openai':
model = OpenAIChat(id="gpt-4o", api_key=os.environ.get('OPENAI_API_KEY'))
model = OpenAIChat(id=model_str, api_key=os.environ.get('OPENAI_API_KEY'))
elif llm_provider == 'anthropic':
model = Claude(id="claude-sonnet-4-5-20250929", temperature=0.6, api_key=os.environ.get('ANTHROPIC_API_KEY'))
model = Claude(id=model_str, temperature=0.6, api_key=os.environ.get('ANTHROPIC_API_KEY'))
elif llm_provider == 'google':
model = Gemini(id="gemini-2.5-flash", temperature=0.6, api_key=os.environ.get('GEMINI_API_KEY'))
model = Gemini(id=model_str, temperature=0.6, api_key=os.environ.get('GEMINI_API_KEY'))
else:
model = OpenAIChat(id="gpt-5-mini"), # default
model = OpenAIChat(id=model_str), # default

devops_team = Team(
name="Multi Cloud and Devops Team",
model=model,
members=[
execute_devops_agent(provider=provider),
execute_k8s_agent(provider=provider),
execute_terraform_agent(provider=provider)
execute_devops_agent(provider=provider, model=model_str, debug_mode=debug_mode),
execute_k8s_agent(provider=provider, model=model_str, debug_mode=debug_mode),
execute_terraform_agent(provider=provider, model=model_str, debug_mode=debug_mode),
],
instructions=[
"You are a intelligent router that directs questions to the appropriate agent.",
Expand Down Expand Up @@ -98,7 +118,5 @@ def execute_master_agent(provider: str, user_query: str = None) -> str:
# saved the response to knowledge in async mode
asyncio.run(
knowledge.add_content_async(text_content=f"question: {user_query}, Assistant: {response}",
skip_if_exists=False)

)
skip_if_exists=True))
return response
Loading