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
Empty file added eval "$(ssh-agent -s)"
Empty file.
Empty file added eval "$(ssh-agent -s)".pub
Empty file.
1 change: 0 additions & 1 deletion src/agent/browser_use/browser_use_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
os.environ.get("SKIP_LLM_API_KEY_VERIFICATION", "false").lower()[0] in "ty1"
)


class BrowserUseAgent(Agent):
def _set_tool_calling_method(self) -> ToolCallingMethod | None:
tool_calling_method = self.settings.tool_calling_method
Expand Down
222 changes: 215 additions & 7 deletions src/agent/deep_research/deep_research_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@
from src.browser.custom_browser import CustomBrowser
from src.controller.custom_controller import CustomController
from src.utils.mcp_client import setup_mcp_client_and_tools
from src.agent.intent_classifier.agent import IntentClassifierAgent
from src.webpage.webpage_checker import WebpageChecker
from src.agent.snippet_extractor.agent import SnippetExtractorAgent
from src.agent.qa_possibilty_checker.agent import QAPossibilityChecker
from src.agent.prompt_enahncer.agent import PromptEnhancerAgent


logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -300,6 +306,25 @@ def create_browser_search_tool(

# --- Langgraph State Definition ---

# class InitialStepsState(TypedDict): #this class will act as out State storing all the output values from the agents
# user_query: str = " "
# url: str = " "

# intent_check: bool = False #true if the intent is QA related
# inten_agent_msg: str = ""

# webpage_check: bool = False #true if the webpage is valid
# webpage_msg: str = ""

# extracted_snippet_agent_msg: str = ""
# extracted_snippet: str = " "
# snippet_check: bool = False #true if the snippet is available

# QA_possibility_agent_msg: str = ""
# QA_possibility_check: bool = False #true if the QA is possible

# enhanced_prompt_agent_msg: str = ""
# enhanced_prompt: str = " "

class ResearchTaskItem(TypedDict):
# step: int # Maybe step within category, or just implicit by order
Expand All @@ -318,6 +343,27 @@ class ResearchCategoryItem(TypedDict):
class DeepResearchState(TypedDict):
task_id: str
topic: str
user_query: str
url: str

#--- Initial Steps State ---
intent_check: bool = False #true if the intent is QA related
inten_agent_msg: str = ""

webpage_check: bool = False #true if the webpage is valid
webpage_msg: str = ""

extracted_snippet_agent_msg: str = ""
extracted_snippet: str = " "
snippet_check: bool = False #true if the snippet is available

QA_possibility_agent_msg: str = ""
QA_possibility_check: bool = False #true if the QA is possible

enhanced_prompt_agent_msg: str = ""
enhanced_prompt: str = " "
#--- End of Initial Steps State ---

research_plan: List[ResearchCategoryItem] # CHANGED
search_results: List[Dict[str, Any]]
llm: Any
Expand All @@ -334,6 +380,100 @@ class DeepResearchState(TypedDict):

# --- Langgraph Nodes ---

def intent_classifier(state: DeepResearchState) -> DeepResearchState:
logger.info("\n\n INTENT CLASSIFIER NODE...\n")

output = IntentClassifierAgent(state["user_query"]).run_agent()
state["intent_check"] = output.intent
state["inten_agent_msg"] = output.agent_msg

return state # return the states values to continue the graph

def webpage_checker(state: DeepResearchState) -> DeepResearchState:
logger.info("\n\n WEBPAGE CHECKER NODE...\n")
output = WebpageChecker(state["url"]).exists()

if output:
logger.info("Webpage exists and is valid")
state['webpage_msg'] = "Webpage exists and is valid"
state["webpage_check"] = True
else:
logger.error("Webpage does not exist or is invalid")
state['webpage_msg'] = "Webpage does not exists or is invalid"
state["webpage_check"] = False

return state # return the state to continue the graph


def _get_output_value(output, key, default=None):
if isinstance(output, dict):
return output.get(key, default)
return getattr(output, key, default)

def snippet_extractor(state: DeepResearchState) -> DeepResearchState:
logger.info("\n\n SNIPPET EXTRACTOR NODE...\n")

output = SnippetExtractorAgent(user_prompt=state["user_query"], url=state["url"]).run_agent()
state['extracted_snippet_agent_msg'] = _get_output_value(output, "agent_msg", "")
state['snippet_check'] = _get_output_value(output, "snippet_check", False)
state['extracted_snippet'] = _get_output_value(output, "extracted_snippet", "")

return state

def QA_possibility(state: DeepResearchState) -> DeepResearchState:
logger.info("\n\n QA POSSIBILTY CHECKER AGENT...\n")

output = QAPossibilityChecker(user_prompt=state["user_query"], extracted_snippet=state['extracted_snippet']).run_agent()
state["QA_possibility_agent_msg"] = output.agent_msg
state["QA_possibility_check"] = output.qa_possibilty

return state

def prompt_enhancer(state: DeepResearchState) -> DeepResearchState:
logger.info("\n\n PROMPT ENHANCER AGENT...\n")

output = PromptEnhancerAgent(state["user_query"], extracted_snippet=state['extracted_snippet']).run_agent()
state['enhanced_prompt_agent_msg'] = output.agent_msg
state['enhanced_prompt'] = output.enhanced_prompt

return state

# --- langgraph edges ---

def _intent_condition(state: DeepResearchState):
# this is our intent conditional edge
intent_check = state.get("intent_check")
if intent_check: # if true then we continue to snippet_extractor
return "webpage_checker" # if intent is not QA related, check webpage
else:
return "end_run" # if intent is not QA related, end the graph


def _webpage_condition(state: DeepResearchState):
# this is our intent conditional edge
webpage_check = state.get("webpage_check")
if webpage_check: # if true then we continue to snippet_extractor
return "snippet_extractor" # if intent is not QA related, check webpage
else:
return "end_run" # if intent is not QA related, end the graph

def _snippet_condition(state: DeepResearchState):
#this is our snippet conditional edge
snippet_check = state.get("snippet_check")
if snippet_check:
return "QA_possibility"

return "end_run" # if snippet is not available, end the graph

def _QA_possibility_condition(self, state: DeepResearchState):
# this is our possibility conditional edge
possibility_check = state.get("QA_possibility_check")
if possibility_check:
return "prompt_enhancer"

return "end_run" # if qa possibility is not valid, end the graph



def _load_previous_state(task_id: str, output_dir: str) -> Dict[str, Any]:
state_updates = {}
Expand Down Expand Up @@ -1052,6 +1192,12 @@ def _compile_graph(self) -> StateGraph:
workflow = StateGraph(DeepResearchState)

# Add nodes
workflow.add_node("intent_classifier", intent_classifier)
workflow.add_node("webpage_checker", webpage_checker)
workflow.add_node("snippet_extractor", snippet_extractor)
workflow.add_node("QA_possibility", QA_possibility)
workflow.add_node("prompt_enhancer", prompt_enhancer)

workflow.add_node("plan_research", planning_node)
workflow.add_node("execute_research", research_execution_node)
workflow.add_node("synthesize_report", synthesis_node)
Expand All @@ -1060,7 +1206,45 @@ def _compile_graph(self) -> StateGraph:
) # Simple end node

# Define edges
workflow.set_entry_point("plan_research")
workflow.set_entry_point("intent_classifier")

workflow.add_conditional_edges(
"intent_classifier",
_intent_condition,
{
"webpage_checker": "webpage_checker",
"end_run": "end_run"
}
)

workflow.add_conditional_edges(
"webpage_checker",
_webpage_condition,
{
"snippet_extractor": "snippet_extractor",
"end_run": "end_run"
}

)
workflow.add_conditional_edges(
"snippet_extractor",
_snippet_condition,
{
"QA_possibility": "QA_possibility",
"end_run": "end_run"
}
)

workflow.add_conditional_edges(
"QA_possibility",
_QA_possibility_condition,
{
"prompt_enhancer": "prompt_enhancer",
"end_run": "end_run"
}
)

workflow.add_edge("prompt_enhancer", "plan_research")

workflow.add_edge(
"plan_research", "execute_research"
Expand All @@ -1085,20 +1269,28 @@ def _compile_graph(self) -> StateGraph:
async def run(
self,
topic: str,
user_query: str,
url: str,
task_id: Optional[str] = None,
save_dir: str = "./tmp/deep_research",
max_parallel_browsers: int = 1,
) -> Dict[str, Any]:
"""
Starts the deep research process (Async Generator Version).
Starts the deep research process.

Args:
topic: The research topic.
user_query: The user's specific query.
url: The URL to analyze.
task_id: Optional existing task ID to resume. If None, a new ID is generated.
save_dir: Directory to save research results.
max_parallel_browsers: Maximum number of parallel browser instances.

Yields:
Intermediate state updates or messages during execution.
Returns:
Dictionary containing the research results and status.
"""

print("\n\n\n\n\n\n DEEP RESEARCGH AFEBNT CALLED\n\n\n\n\n\n")
if self.runner and not self.runner.done():
logger.warning(
"Agent is already running. Please stop the current task first."
Expand All @@ -1114,9 +1306,10 @@ async def run(
output_dir = os.path.join(save_dir, self.current_task_id)
os.makedirs(output_dir, exist_ok=True)

logger.info(
f"[AsyncGen] Starting research task ID: {self.current_task_id} for topic: '{topic}'"
)
logger.info(f"[AsyncGen] Starting research task ID: {self.current_task_id}")
logger.info(f"[AsyncGen] Topic: {topic}")
logger.info(f"[AsyncGen] User Query: {user_query}")
logger.info(f"[AsyncGen] URL: {url}")
logger.info(f"[AsyncGen] Output directory: {output_dir}")

self.stop_event = threading.Event()
Expand All @@ -1127,6 +1320,8 @@ async def run(
initial_state: DeepResearchState = {
"task_id": self.current_task_id,
"topic": topic,
"user_query": user_query,
"url": url,
"research_plan": [],
"search_results": [],
"messages": [],
Expand All @@ -1139,6 +1334,19 @@ async def run(
"current_task_index_in_category": 0,
"stop_requested": False,
"error_message": None,

# Initialize the new fields
"intent_check": False,
"inten_agent_msg": "",
"webpage_check": False,
"webpage_msg": "",
"extracted_snippet_agent_msg": "",
"extracted_snippet": "",
"snippet_check": False,
"QA_possibility_agent_msg": "",
"QA_possibility_check": False,
"enhanced_prompt_agent_msg": "",
"enhanced_prompt": ""
}

if task_id:
Expand Down
68 changes: 68 additions & 0 deletions src/agent/intent_classifier/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@

The Intent Classifier Agent is the first component in our AI-based QA automation system. Its main role is to decide whether a user's request is about testing a webpage or completely unrelated. This helps the system ignore non-testing prompts early, saving time and resources.

What This Agent Does
- When a user sends a message like: "Check if the login button works"
- The Intent Classifier Agent will recognize this as a valid QA task and pass it on to the next agent in the chain.

- But if the user says: "How do I use ChatGPT to write an essay?"
- Then the agent will mark it as not related to QA, and the system will stop there.

The agent gives a simple true or false answer in this format:

{ "intent": true }
or
{ "intent": false }

How It Works – File by File

1. agent.py – The Agent Logic
This file contains the class IntentClassifierAgent, which manages the entire logic. It does three main things:

a. __init__(self, user_prompt: str):
This function is called when we first create the agent. It:
- Stores the user’s input (their message)
- Loads the classification prompt
- Tells the system which output structure to expect (a Pydantic class)

b. run_agent(self) -> IntentClassifierOutput:
This function starts the classification process. It:
- Logs that the agent is running
- Sends everything (prompt, input, expected output format) to a helper function called run_main_agent which handles actual interaction with the AI model
- Returns the result (either true or false)


2. prompt.py – The Instruction for the AI
This file contains a variable called agents_prompt, which is the natural-language instruction given to the AI model. It includes:

- Clear criteria for what counts as a QA-related task
- A few examples of QA prompts vs. non-QA prompts (few shot learning technique)
- A placeholder {input} where the user’s query is inserted
- This prompt ensures the AI is focused on classifying only QA-relevant commands.

3. output.py – The Output Schema
This file defines a simple Pydantic model called IntentClassifierOutput:

- class IntentClassifierOutput(BaseModel):
intent: bool

- This means the final answer must look like this:
{ "intent": true }
or
{ "intent": false }

This structured format makes it easy for the next parts of the system to read and act on.

How It Fits in the Big Picture
This agent is the first step in the AI pipeline.

If the user request is QA-related (intent: true), we move on to the Snippet Extractor Agent.
If it’s not QA-related (intent: false), the workflow stops early, saving time and computation.
This makes our system more intelligent, cost-efficient, and focused.

Summary
Goal: Filter out non-QA requests
Input: A single user message
Output: { "intent": true } or { "intent": false }
Key Files: agent.py, prompt.py, output.py
Next Step: Trigger next agent only if intent is true
Loading