Skip to content
13 changes: 13 additions & 0 deletions backend/apps/ai/flows/assistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from apps.ai.agents.rag import create_rag_agent
from apps.ai.common.constants import DEFAULT_VISION_MODEL, DELIMITER
from apps.ai.common.intent import Intent
from apps.ai.query_analyzer import analyze_query
from apps.ai.router import route
from apps.common.open_ai import OpenAi
from apps.slack.constants import (
Expand Down Expand Up @@ -104,6 +105,18 @@ def process_query( # noqa: PLR0911
},
)

# TODO(rudransh-shrivastava): Use analysis results for multi-agent orchestration
query_analysis = analyze_query(query)
Comment thread
rudransh-shrivastava marked this conversation as resolved.
Outdated
logger.warning(
"Query analyzed",
extra={
"is_simple": query_analysis["is_simple"],
"sub_queries": query_analysis["sub_queries"],
"required_agents": query_analysis["required_agents"],
"query": query,
},
)
Comment thread
rudransh-shrivastava marked this conversation as resolved.
Outdated

# Step 2: Handle queries in owasp-community channel - suggest channels
# If query is in owasp-community channel, ALWAYS route to community agent
# for channel suggestions regardless of intent
Expand Down
116 changes: 116 additions & 0 deletions backend/apps/ai/query_analyzer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"""Query analyzer for determining query complexity."""
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.

import contextlib
import logging

from crewai import Agent, Crew, Task

from apps.ai.common.llm_config import get_llm
from apps.ai.template_loader import env

logger = logging.getLogger(__name__)

AGENT_DESCRIPTIONS = {
"channel": "Routes users to appropriate Slack channels for their questions",
"chapter": "Finds OWASP chapters, chapter leaders, and chapter activities worldwide",
"community": "Finds community leaders, committees, and entity Slack channels",
"contribution": "Helps find contribution opportunities and GSoC program information",
"project": "Finds OWASP projects by topic, maturity level, or specific needs",
"rag": "Searches OWASP documentation, policies, and repositories for general information",
}


def create_query_analyzer_agent() -> Agent:
"""Create query analyzer agent.

Returns:
Agent: Query analyzer agent configured for complexity analysis.

"""
return Agent(
role="Query Analyzer",
goal=(
"Analyze user queries to determine complexity, identify required expert "
"agents, and decompose complex queries into sub-queries when needed."
),
backstory=env.get_template("query_analyzer/backstory.jinja")
.render(agent_names=", ".join(AGENT_DESCRIPTIONS)) # nosemgrep: direct-use-of-jinja2
.strip(),
llm=get_llm(),
verbose=True,
allow_delegation=False,
memory=False,
)


def analyze_query(query: str) -> dict:
"""Analyze query to determine complexity and required agents.

Args:
query (str): User's question.

Returns:
dict: Analysis with 'is_simple', 'sub_queries', and 'required_agents'.

"""
try:
analyzer_agent = create_query_analyzer_agent()

task_template = env.get_template("query_analyzer/tasks/analyze.jinja")
agents = [{"name": name, "description": desc} for name, desc in AGENT_DESCRIPTIONS.items()]
task_description = task_template.render( # nosemgrep: direct-use-of-jinja2
query=query, agents=agents
).strip()

analysis_task = Task(
description=task_description,
agent=analyzer_agent,
expected_output="Query analysis with complexity assessment and required agents",
)

crew = Crew(
agents=[analyzer_agent],
tasks=[analysis_task],
verbose=True,
max_iter=5,
max_rpm=10,
)
result = crew.kickoff()
except (RuntimeError, ValueError):
logger.exception("Query analysis failed for: %s", query)
Comment thread
rudransh-shrivastava marked this conversation as resolved.
Outdated
return {
"is_simple": True,
"sub_queries": [query],
"required_agents": [],
}

result_str = str(result)
Comment thread
rudransh-shrivastava marked this conversation as resolved.
is_simple = True
sub_queries = [query]
required_agents = []

for line in result_str.split("\n"):
line_lower = line.lower().strip()
if line_lower.startswith("issimple:"):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe IS_SIMPLE in the source would be more readable here (if we can name it as we want)

with contextlib.suppress(ValueError):
value = line.split(":", 1)[1].strip().lower()
is_simple = value in ("true", "yes", "1")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
is_simple = value in ("true", "yes", "1")
is_simple = value in {"true", "yes", "1"}

elif line_lower.startswith("subqueries:"):
value = line.split(":", 1)[1].strip()
if value and value.lower() != "none":
sub_queries = [q.strip() for q in value.split("|") if q.strip()]
elif line_lower.startswith("requiredagents:"):
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
value = line.split(":", 1)[1].strip().lower()
if value and value != "none":
required_agents = [
a.strip() for a in value.split(",") if a.strip() in AGENT_DESCRIPTIONS
]

if not required_agents:
logger.warning("Query analysis returned no valid agents for: %s", query)

return {
"is_simple": is_simple,
"sub_queries": sub_queries,
"required_agents": required_agents,
}
1 change: 1 addition & 0 deletions backend/apps/ai/templates/query_analyzer/backstory.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
You are an expert at understanding query complexity and determining what resources are needed to answer questions about OWASP. You analyze queries to identify whether they are simple (answerable by one specialist) or complex (requiring multiple specialists). For complex queries, you decompose them into focused sub-queries that can be handled independently. You understand the capabilities of available agents ({{ agent_names }}), allowing you to determine which expert agents are best suited to answer each part of a query.
19 changes: 19 additions & 0 deletions backend/apps/ai/templates/query_analyzer/tasks/analyze.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Analyze this query: {{ query }}
Comment thread
rudransh-shrivastava marked this conversation as resolved.

Determine the complexity of this query and what resources are needed to answer it.

Available expert agents:
{% for agent in agents %}
- {{ agent.name }}: {{ agent.description }}
{% endfor %}

A query is SIMPLE if it has a single clear intent and can be fully answered by ONE expert agent.

A query is COMPLEX if it combines multiple topics, requires information from multiple agents, or asks compound questions that need different expertise.

For complex queries, break them into independent sub-queries where each sub-query is answerable by a single agent.

Format your response as:
IsSimple: true or false
SubQueries: sub-query 1 | sub-query 2 | sub-query 3 (use pipe separator, or just the original query if simple)
RequiredAgents: agent1, agent2 (comma-separated list of agent names)
2 changes: 2 additions & 0 deletions cspell/custom-dict.txt
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,15 @@ repositorycontributor
requirepass
rqworker
rsc
rudransh
saft
sakanashi
samm
sbom
schemathesis
semgrep
seo
shrivastava
skillstruck
slackbot
slideshare
Expand Down
Loading