Repository: potpie-ai/potpie
Pull request #303: Feat/pydantic ai
Author: login: nndn
Created: 2025-03-10T06:11:44Z | Merged: 2025-03-15T15:03:13Z

Feat: use pydantic ai agents

Improvements:
1. Message streaming support
2. Stream metadata like tool calls, responses etc
3. Time to respond get's reduced significantly

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Chat responses now include an additional field for tracking tool interactions.
  - Enhanced agent functionality dynamically selects between improved models, enabling better streaming and output quality.
  - Structured response formats have been integrated for a range of tools such as code queries, web extraction, and change detection.
  - Support for a new AI provider, enhancing model handling capabilities.
  - New methods added to check model support and retrieve model instances based on provider configuration.

- **Bug Fixes**
  - Refinements to classification and error handling improve overall system stability.

- **Chores**
  - Updated dependencies, including a version upgrade for the `openai` package and the addition of `pydantic_ai`, optimizing performance and maintainability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/app/modules/conversations/conversation/conversation_schema.py b/app/modules/conversations/conversation/conversation_schema.py
index 3a6261df1..128bf2699 100644
--- a/app/modules/conversations/conversation/conversation_schema.py
+++ b/app/modules/conversations/conversation/conversation_schema.py
@@ -1,6 +1,6 @@
 from datetime import datetime
 from enum import Enum
-from typing import List, Optional
+from typing import Any, List, Optional
 
 from pydantic import BaseModel
 
@@ -54,6 +54,7 @@ class Config:
 class ChatMessageResponse(BaseModel):
     message: str
     citations: List[str]
+    tool_calls: List[Any]
 
 
 # Resolve forward references
diff --git a/app/modules/conversations/conversation/conversation_service.py b/app/modules/conversations/conversation/conversation_service.py
index e2d9e09e9..da05ecf05 100644
--- a/app/modules/conversations/conversation/conversation_service.py
+++ b/app/modules/conversations/conversation/conversation_service.py
@@ -308,7 +308,7 @@ async def store_message(
                         all_citations = all_citations + chunk.citations
 
                     yield ChatMessageResponse(
-                        message=full_message, citations=all_citations
+                        message=full_message, citations=all_citations, tool_calls=[]
                     )
 
         except AccessTypeReadError:
@@ -417,7 +417,9 @@ async def regenerate_last_message(
                     full_message += chunk.message
                     all_citations = all_citations + chunk.citations
 
-                yield ChatMessageResponse(message=full_message, citations=all_citations)
+                yield ChatMessageResponse(
+                    message=full_message, citations=all_citations, tool_calls=[]
+                )
 
         except AccessTypeReadError:
             raise
@@ -478,8 +480,11 @@ def parse_str_to_message(self, chunk: str) -> ChatMessageResponse:
         # Extract the 'message' and 'citations'
         message: str = data.get("message", "")
         citations: List[str] = data.get("citations", [])
+        tool_calls: List[dict] = data.get("tool_calls", [])
 
-        return ChatMessageResponse(message=message, citations=citations)
+        return ChatMessageResponse(
+            message=message, citations=citations, tool_calls=tool_calls
+        )
 
     async def _generate_and_stream_ai_response(
         self,
@@ -524,7 +529,9 @@ async def _generate_and_stream_ai_response(
                         agent_id, user_id, query, node_ids, project_id, conversation.id
                     )
                 )
-                yield ChatMessageResponse(message=response["message"], citations=[])
+                yield ChatMessageResponse(
+                    message=response["message"], citations=[], tool_calls=[]
+                )
             else:
                 res = self.agent_service.execute_stream(
                     ChatContext(
@@ -544,7 +551,12 @@ async def _generate_and_stream_ai_response(
                         citations=chunk.citations,
                     )
                     yield ChatMessageResponse(
-                        message=chunk.response, citations=chunk.citations
+                        message=chunk.response,
+                        citations=chunk.citations,
+                        tool_calls=[
+                            tool_call.model_dump_json()
+                            for tool_call in chunk.tool_calls
+                        ],
                     )
                 self.history_manager.flush_message_buffer(
                     conversation_id, MessageType.AI_GENERATED
diff --git a/app/modules/intelligence/agents/chat_agent.py b/app/modules/intelligence/agents/chat_agent.py
index 58c200acf..c2e598898 100644
--- a/app/modules/intelligence/agents/chat_agent.py
+++ b/app/modules/intelligence/agents/chat_agent.py
@@ -1,13 +1,40 @@
 from abc import ABC, abstractmethod
-from typing import AsyncGenerator, List, Optional
+from enum import Enum
+from typing import Any, AsyncGenerator, Dict, List, Optional
 from pydantic import BaseModel, Field
 
 
+class ToolCallEventType(Enum):
+    CALL = "call"
+    RESULT = "result"
+
+
+class ToolCallResponse(BaseModel):
+    call_id: str = Field(
+        ...,
+        description="ID of the tool call",
+    )
+    event_type: ToolCallEventType = Field(..., description="Type of the event")
+    tool_name: str = Field(
+        ...,
+        description="Name of the tool",
+    )
+    tool_response: str = Field(
+        ...,
+        description="Response from the tool",
+    )
+    tool_call_details: Dict[str, Any] = Field(
+        ...,
+        description="Details of the tool call",
+    )
+
+
 class ChatAgentResponse(BaseModel):
     response: str = Field(
         ...,
         description="Full response to the query",
     )
+    tool_calls: List[ToolCallResponse] = Field([], description="List of tool calls")
     citations: List[str] = Field(
         ...,
         description="List of file names extracted from context and referenced in the response",
diff --git a/app/modules/intelligence/agents/chat_agents/adaptive_agent.py b/app/modules/intelligence/agents/chat_agents/adaptive_agent.py
index 0495af9e3..b95ee9276 100644
--- a/app/modules/intelligence/agents/chat_agents/adaptive_agent.py
+++ b/app/modules/intelligence/agents/chat_agents/adaptive_agent.py
@@ -125,4 +125,4 @@ async def run_stream(
         # build llm response
         messages = await self._get_messages(ctx)
         async for chunk in await self.llm_provider.call_llm(messages=messages, stream=True):  # type: ignore
-            yield ChatAgentResponse(response=chunk, citations=[])
+            yield ChatAgentResponse(response=chunk, citations=[], tool_calls=[])
diff --git a/app/modules/intelligence/agents/chat_agents/auto_router_agent.py b/app/modules/intelligence/agents/chat_agents/auto_router_agent.py
index 199052225..580375b54 100644
--- a/app/modules/intelligence/agents/chat_agents/auto_router_agent.py
+++ b/app/modules/intelligence/agents/chat_agents/auto_router_agent.py
@@ -49,12 +49,13 @@ async def _run_classification(
         ]
 
         try:
-            classification: ClassificationRespone = (
+            classification: ClassificationResponse = (
                 await self.llm_provider.call_llm_with_structured_output(
                     messages=messages,
-                    output_schema=ClassificationRespone,  # type: ignore
+                    output_schema=ClassificationResponse,  # type: ignore
                 )
             )
+
             agent_id = classification.agent_id
             confidence = float(classification.confidence_score)
             selected_agent_id = (
@@ -81,7 +82,7 @@ async def run_stream(
             yield chunk
 
 
-class ClassificationRespone(BaseModel):
+class ClassificationResponse(BaseModel):
     agent_id: str = Field(description="agent_id of the best matching agent")
     confidence_score: str = Field(
         description="confidence score of the best matching agent, should be the maximum confidence score and be a valid floating point number"
diff --git a/app/modules/intelligence/agents/chat_agents/crewai_agent.py b/app/modules/intelligence/agents/chat_agents/crewai_agent.py
index e25ebe8ab..9ccb9cb50 100644
--- a/app/modules/intelligence/agents/chat_agents/crewai_agent.py
+++ b/app/modules/intelligence/agents/chat_agents/crewai_agent.py
@@ -6,7 +6,7 @@
     AgentProvider,
 )
 from crewai import Agent, Crew, Process, Task
-from pydantic import BaseModel
+from pydantic import BaseModel, Field
 from app.modules.utils.logger import setup_logger
 from ..chat_agent import ChatAgent, ChatAgentResponse, ChatContext
 
@@ -30,6 +30,17 @@ class AgentConfig(BaseModel):
     max_iter: int = 15
 
 
+class CrewAIResponse(BaseModel):
+    response: str = Field(
+        ...,
+        description="Full response to the query",
+    )
+    citations: List[str] = Field(
+        ...,
+        description="List of file names extracted from context and referenced in the response",
+    )
+
+
 class CrewAIAgent(ChatAgent):
     def __init__(
         self,
@@ -128,7 +139,7 @@ async def _create_task(self, task_config: TaskConfig, ctx: ChatContext) -> Task:
             description=task_description,
             agent=self.agent,
             expected_output="Markdown formatted response with code context and explanations",
-            output_pydantic=ChatAgentResponse,
+            output_pydantic=CrewAIResponse,
         )
 
         return task
@@ -136,6 +147,7 @@ async def _create_task(self, task_config: TaskConfig, ctx: ChatContext) -> Task:
     async def run(self, ctx: ChatContext) -> ChatAgentResponse:
         """Main execution flow"""
         try:
+            logger.info("running crew-ai agent")
             # agentops.init(
             #     os.getenv("AGENTOPS_API_KEY"), default_tags=["openai-gpt-notebook"]
             # )
@@ -156,9 +168,13 @@ async def run(self, ctx: ChatContext) -> ChatAgentResponse:
 
             logger.info(f"Starting Crew AI kickoff with {len(tasks)} tasks")
             result = await crew.kickoff_async()
-            response: ChatAgentResponse = result.tasks_output[0].pydantic
+            response: CrewAIResponse = result.tasks_output[0].pydantic
             # agentops.end_session("success")
-            return response
+            return ChatAgentResponse(
+                response=response.response,
+                tool_calls=[],
+                citations=response.citations,
+            )
 
         except Exception as e:
             logger.error(f"Error in run method: {str(e)}", exc_info=True)
@@ -167,4 +183,5 @@ async def run(self, ctx: ChatContext) -> ChatAgentResponse:
     async def run_stream(
         self, ctx: ChatContext
     ) -> AsyncGenerator[ChatAgentResponse, None]:
+        logger.info("running crew-ai agent stream")
         yield await self.run(ctx)  # CrewAI doesn't support streaming response
diff --git a/app/modules/intelligence/agents/chat_agents/langchain_agent.py b/app/modules/intelligence/agents/chat_agents/langchain_agent.py
index 815ff8244..fe53e3510 100644
--- a/app/modules/intelligence/agents/chat_agents/langchain_agent.py
+++ b/app/modules/intelligence/agents/chat_agents/langchain_agent.py
@@ -41,7 +41,7 @@ def __init__(
             tools[i].name = re.sub(r" ", "", tool.name)
 
         self.agent = create_react_agent(
-            model=llm_provider.get_large_llm(AgentProvider.LANGCHAIN),
+            model=llm_provider.get_large_llm(AgentProvider.LANGCHAIN),  # type: ignore
             tools=ToolExecutor(tools),
             debug=True,
         )
@@ -118,8 +118,7 @@ async def run(self, ctx: ChatContext) -> ChatAgentResponse:
             # session and session.end_session("Success")
 
             return ChatAgentResponse(
-                response=res["messages"][-1].content,
-                citations=[],
+                response=res["messages"][-1].content, citations=[], tool_calls=[]
             )
 
         except Exception as e:
@@ -145,6 +144,7 @@ async def run_stream(
                     yield ChatAgentResponse(
                         response=str(chunk["agent"]["messages"][0].content),
                         citations=[],
+                        tool_calls=[],
                     )
 
         except Exception as e:
diff --git a/app/modules/intelligence/agents/chat_agents/pydantic_agent.py b/app/modules/intelligence/agents/chat_agents/pydantic_agent.py
new file mode 100644
index 000000000..1b87d1c8e
--- /dev/null
+++ b/app/modules/intelligence/agents/chat_agents/pydantic_agent.py
@@ -0,0 +1,249 @@
+import re
+from typing import List, AsyncGenerator
+
+from app.modules.intelligence.provider.provider_service import (
+    ProviderService,
+)
+from .crewai_agent import AgentConfig, TaskConfig
+from app.modules.utils.logger import setup_logger
+
+from ..chat_agent import (
+    ChatAgent,
+    ChatAgentResponse,
+    ChatContext,
+    ToolCallEventType,
+    ToolCallResponse,
+)
+
+from pydantic_ai import Agent, Tool
+from pydantic_ai.messages import (
+    FunctionToolCallEvent,
+    FunctionToolResultEvent,
+    PartDeltaEvent,
+    TextPartDelta,
+)
+from langchain_core.tools import StructuredTool
+
+logger = setup_logger(__name__)
+
+
+def get_tool_run_message(tool_name: str):
+    match tool_name:
+        case "GetCodeanddocstringFromProbableNodeName":
+            return "Retrieving code from referenced names"
+        case "Getcodechanges":
+            return "Fetching code changes from your repo"
+        case "GetNodesfromTags":
+            return "Fetching nodes from tags"
+        case "AskKnowledgeGraphQueries":
+            return "Traversing the knowledge graph"
+        case "GetCodeanddocstringFromMultipleNodeIDs":
+            return "Fetching code and docstrings"
+        case "get_code_file_structure":
+            return "Loading the file structure of the repo"
+        case "GetNodeNeighboursFromNodeID":
+            return "Identifying referenced code"
+        case "WebpageContentExtractor":
+            return "Querying information from the web"
+        case "GitHubContentFetcher":
+            return "Fetching content from github"
+        case _:
+            return "Fetching code"
+
+
+def get_tool_response_message(tool_name: str):
+    match tool_name:
+        case "Getcodechanges":
+            return "Code changes fetched successfully"
+        case "GetNodesfromTags":
+            return "Fetched nodes from tags"
+        case "AskKnowledgeGraphQueries":
+            return "Knowledge graph queries successful"
+        case "GetCodeanddocstringFromMultipleNodeIDs":
+            return "Fetched code and docstrings"
+        case "get_code_file_structure":
+            return "File structure of the repo loaded successfully"
+        case "GetNodeNeighboursFromNodeID":
+            return "Fetched referenced code"
+        case "WebpageContentExtractor":
+            return "Information retrieved from web"
+        case "GitHubContentFetcher":
+            return "File contents fetched from github"
+        case _:
+            return "Code fetched"
+
+
+class PydanticRagAgent(ChatAgent):
+    def __init__(
+        self,
+        llm_provider: ProviderService,
+        config: AgentConfig,
+        tools: List[StructuredTool],
+    ):
+        """Initialize the agent with configuration and tools"""
+
+        self.tasks = config.tasks
+        self.max_iter = config.max_iter
+
+        # tool name can't have spaces for langgraph agents
+        for i, tool in enumerate(tools):
+            tools[i].name = re.sub(r" ", "", tool.name)
+
+        self.agent = Agent(
+            model=llm_provider.get_pydantic_model(),
+            # model="gpt-4o-mini",
+            tools=[
+                Tool(
+                    name=tool.name,
+                    description=tool.description,
+                    function=tool.func,  # type: ignore
+                )
+                for tool in tools
+            ],
+            system_prompt=f"Role: {config.role}\nGoal: {config.goal}\nBackstory: {config.backstory}. Respond to the user query",
+            result_type=str,
+            retries=3,
+            result_retries=3,
+            model_settings={
+                "parallel_tool_calls": True,
+            },
+        )
+
+    def _create_task_description(
+        self,
+        task_config: TaskConfig,
+        ctx: ChatContext,
+    ) -> str:
+        """Create a task description from task configuration"""
+        if ctx.node_ids is None:
+            ctx.node_ids = []
+        if isinstance(ctx.node_ids, str):
+            ctx.node_ids = [ctx.node_ids]
+
+        return f"""
+                CONTEXT:
+                User Query: {ctx.query}
+                Project ID: {ctx.project_id}
+                Node IDs: {" ,".join(ctx.node_ids)}
+
+                Consider the chat history for any specific instructions or context: {" ,".join(ctx.history) if len(ctx.history) > 0 else "no chat history"}
+
+                Additional Context:
+                {ctx.additional_context if ctx.additional_context != "" else "no additional context"}
+
+                TASK:
+                {task_config.description}
+
+                Expected Output:
+                {task_config.expected_output}
+
+                INSTRUCTIONS:
+                1. Use the available tools to gather information
+                2. Process and synthesize the gathered information
+                3. Format your response in markdown, make sure it's well formatted
+                4. Include relevant code snippets and file references
+                5. Provide clear explanations
+                6. Verify your output before submitting
+
+                IMPORTANT:
+                - Use tools efficiently and avoid unnecessary API calls
+                - Only use the tools listed below
+
+                With above information answer the user query: {ctx.query}
+            """
+
+    async def run(self, ctx: ChatContext) -> ChatAgentResponse:
+        """Main execution flow"""
+        logger.info("running pydantic-ai agent")
+        try:
+            # agentops.init(
+            #     os.getenv("AGENTOPS_API_KEY"), default_tags=["openai-gpt-notebook"]
+            # )
+            # session = agentops.start_session()
+            # Create all tasks
+
+            task = self._create_task_description(self.tasks[0], ctx)
+
+            resp = await self.agent.run(user_prompt=task)
+
+            # session and session.end_session("Success")
+
+            return ChatAgentResponse(
+                response=resp.data,
+                tool_calls=[],
+                citations=[],
+            )
+
+        except Exception as e:
+            logger.error(f"Error in run method: {str(e)}", exc_info=True)
+            raise Exception from e
+
+    async def run_stream(
+        self, ctx: ChatContext
+    ) -> AsyncGenerator[ChatAgentResponse, None]:
+        logger.info("running pydantic-ai agent stream")
+        task = self._create_task_description(self.tasks[0], ctx)
+        try:
+            async with self.agent.iter(
+                user_prompt=task,
+            ) as run:
+                async for node in run:
+                    if Agent.is_model_request_node(node):
+                        # A model request node => We can stream tokens from the model's request
+                        async with node.stream(run.ctx) as request_stream:
+                            async for event in request_stream:
+
+                                if isinstance(event, PartDeltaEvent) and isinstance(
+                                    event.delta, TextPartDelta
+                                ):
+                                    yield ChatAgentResponse(
+                                        response=event.delta.content_delta,
+                                        tool_calls=[],
+                                        citations=[],
+                                    )
+                    elif Agent.is_call_tools_node(node):
+                        async with node.stream(run.ctx) as handle_stream:
+                            async for event in handle_stream:
+                                if isinstance(event, FunctionToolCallEvent):
+                                    yield ChatAgentResponse(
+                                        # response=f"\n[Tools] The LLM calls tool={event.part.tool_name!r} {event.part.args_as_json_str}\n",
+                                        response="",
+                                        tool_calls=[
+                                            ToolCallResponse(
+                                                call_id=event.part.tool_call_id or "",
+                                                event_type=ToolCallEventType.CALL,
+                                                tool_name=event.part.tool_name,
+                                                tool_response=get_tool_run_message(
+                                                    event.part.tool_name
+                                                ),
+                                                tool_call_details={},
+                                            )
+                                        ],
+                                        citations=[],
+                                    )
+                                if isinstance(event, FunctionToolResultEvent):
+                                    yield ChatAgentResponse(
+                                        # response=f"\n[Tools] Tool call {event.result.tool_name!r}\n",
+                                        response="",
+                                        tool_calls=[
+                                            ToolCallResponse(
+                                                call_id=event.result.tool_call_id or "",
+                                                event_type=ToolCallEventType.RESULT,
+                                                tool_name=event.result.tool_name
+                                                or "unknown tool",
+                                                tool_response=get_tool_response_message(
+                                                    event.result.tool_name
+                                                    or "unknown tool"
+                                                ),
+                                                tool_call_details={},
+                                            )
+                                        ],
+                                        citations=[],
+                                    )
+
+                    elif Agent.is_end_node(node):
+                        logger.info("result streamed successfully!!")
+
+        except Exception as e:
+            logger.error(f"Error in run method: {str(e)}", exc_info=True)
+            raise Exception from e
diff --git a/app/modules/intelligence/agents/chat_agents/system_agents/blast_radius_agent.py b/app/modules/intelligence/agents/chat_agents/system_agents/blast_radius_agent.py
index 1e4ec23f3..743dfb66d 100644
--- a/app/modules/intelligence/agents/chat_agents/system_agents/blast_radius_agent.py
+++ b/app/modules/intelligence/agents/chat_agents/system_agents/blast_radius_agent.py
@@ -1,8 +1,9 @@
+from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
 from app.modules.intelligence.provider.provider_service import (
     ProviderService,
 )
 from app.modules.intelligence.tools.tool_service import ToolService
-from ..crewai_agent import CrewAIAgent, AgentConfig, TaskConfig
+from ..crewai_agent import AgentConfig, CrewAIAgent, TaskConfig
 from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
 from typing import AsyncGenerator
 
@@ -17,30 +18,31 @@ def __init__(
         self.llm_provider = llm_provider
 
     def _build_agent(self):
-        return CrewAIAgent(
-            self.llm_provider,
-            AgentConfig(
-                role="Blast Radius Analyzer",
-                goal="Analyze the impact of code changes",
-                backstory="You are an AI expert in analyzing how code changes affect the rest of the codebase.",
-                tasks=[
-                    TaskConfig(
-                        description=blast_radius_task_prompt,
-                        expected_output="Comprehensive impact analysis of the code changes on the codebase and answers to the users query about them.",
-                    )
-                ],
-            ),
-            tools=self.tools_provider.get_tools(
-                [
-                    "get_nodes_from_tags",
-                    "ask_knowledge_graph_queries",
-                    "get_code_from_multiple_node_ids",
-                    "change_detection",
-                    "webpage_extractor",
-                    "github_tool",
-                ]
-            ),
+        agent_config = AgentConfig(
+            role="Blast Radius Analyzer",
+            goal="Analyze the impact of code changes",
+            backstory="You are an AI expert in analyzing how code changes affect the rest of the codebase.",
+            tasks=[
+                TaskConfig(
+                    description=blast_radius_task_prompt,
+                    expected_output="Comprehensive impact analysis of the code changes on the codebase and answers to the users query about them.",
+                )
+            ],
         )
+        tools = self.tools_provider.get_tools(
+            [
+                "get_nodes_from_tags",
+                "ask_knowledge_graph_queries",
+                "get_code_from_multiple_node_ids",
+                "change_detection",
+                "webpage_extractor",
+                "github_tool",
+            ]
+        )
+        if self.llm_provider.is_current_model_supported_by_pydanticai():
+            return PydanticRagAgent(self.llm_provider, agent_config, tools)
+        else:
+            return CrewAIAgent(self.llm_provider, agent_config, tools)
 
     async def run(self, ctx: ChatContext) -> ChatAgentResponse:
         return await self._build_agent().run(ctx)
diff --git a/app/modules/intelligence/agents/chat_agents/system_agents/code_gen_agent.py b/app/modules/intelligence/agents/chat_agents/system_agents/code_gen_agent.py
index 9739633b7..26ae5d808 100644
--- a/app/modules/intelligence/agents/chat_agents/system_agents/code_gen_agent.py
+++ b/app/modules/intelligence/agents/chat_agents/system_agents/code_gen_agent.py
@@ -1,8 +1,9 @@
+from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
 from app.modules.intelligence.provider.provider_service import (
     ProviderService,
 )
 from app.modules.intelligence.tools.tool_service import ToolService
-from ..crewai_agent import CrewAIAgent, AgentConfig, TaskConfig
+from ..crewai_agent import AgentConfig, CrewAIAgent, TaskConfig
 from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
 from typing import AsyncGenerator
 
@@ -17,12 +18,10 @@ def __init__(
         self.tools_provider = tools_provider
 
     def _build_agent(self) -> ChatAgent:
-        return CrewAIAgent(
-            self.llm_provider,
-            AgentConfig(
-                role="Code Generation Agent",
-                goal="Generate precise, copy-paste ready code modifications that maintain project consistency and handle all dependencies",
-                backstory="""
+        agent_config = AgentConfig(
+            role="Code Generation Agent",
+            goal="Generate precise, copy-paste ready code modifications that maintain project consistency and handle all dependencies",
+            backstory="""
                     You are an expert code generation agent specialized in creating production-ready,
                     immediately usable code modifications. Your primary responsibilities include:
                     1. Analyzing existing codebase context and understanding dependencies
@@ -38,26 +37,29 @@ def _build_agent(self) -> ChatAgent:
                     - Maintain exact indentation and spacing patterns from original code
                     - Include clear section markers for where code should be inserted/modified
                 """,
-                tasks=[
-                    TaskConfig(
-                        description=code_gen_task_prompt,
-                        expected_output="User-friendly, clearly structured code changes with comprehensive dependency analysis, implementation details for ALL impacted files, and complete verification steps",
-                    )
-                ],
-            ),
-            tools=self.tools_provider.get_tools(
-                [
-                    "get_code_from_multiple_node_ids",
-                    "get_node_neighbours_from_node_id",
-                    "get_code_from_probable_node_name",
-                    "ask_knowledge_graph_queries",
-                    "get_nodes_from_tags",
-                    "get_code_file_structure",
-                    "webpage_extractor",
-                    "github_tool",
-                ]
-            ),
+            tasks=[
+                TaskConfig(
+                    description=code_gen_task_prompt,
+                    expected_output="User-friendly, clearly structured code changes with comprehensive dependency analysis, implementation details for ALL impacted files, and complete verification steps",
+                )
+            ],
         )
+        tools = self.tools_provider.get_tools(
+            [
+                "get_code_from_multiple_node_ids",
+                "get_node_neighbours_from_node_id",
+                "get_code_from_probable_node_name",
+                "ask_knowledge_graph_queries",
+                "get_nodes_from_tags",
+                "get_code_file_structure",
+                "webpage_extractor",
+                "github_tool",
+            ]
+        )
+        if self.llm_provider.is_current_model_supported_by_pydanticai():
+            return PydanticRagAgent(self.llm_provider, agent_config, tools)
+        else:
+            return CrewAIAgent(self.llm_provider, agent_config, tools)
 
     async def _enriched_context(self, ctx: ChatContext) -> ChatContext:
         if ctx.node_ids and len(ctx.node_ids) > 0:
diff --git a/app/modules/intelligence/agents/chat_agents/system_agents/debug_agent.py b/app/modules/intelligence/agents/chat_agents/system_agents/debug_agent.py
index 6ec7c0f2f..1510f114b 100644
--- a/app/modules/intelligence/agents/chat_agents/system_agents/debug_agent.py
+++ b/app/modules/intelligence/agents/chat_agents/system_agents/debug_agent.py
@@ -1,8 +1,9 @@
+from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
 from app.modules.intelligence.provider.provider_service import (
     ProviderService,
 )
 from app.modules.intelligence.tools.tool_service import ToolService
-from ..crewai_agent import CrewAIAgent, AgentConfig, TaskConfig
+from ..crewai_agent import AgentConfig, CrewAIAgent, TaskConfig
 from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
 from typing import AsyncGenerator
 
@@ -14,12 +15,13 @@ def __init__(
         tools_provider: ToolService,
     ):
         self.tools_provider = tools_provider
-        self.rag_agent = CrewAIAgent(
-            llm_provider,
-            AgentConfig(
-                role="Context curation agent",
-                goal="Handle querying the knowledge graph and refining the results to provide accurate and contextually rich responses.",
-                backstory="""
+        self.llm_provider = llm_provider
+
+    def _build_agent(self) -> ChatAgent:
+        agent_config = AgentConfig(
+            role="Context curation agent",
+            goal="Handle querying the knowledge graph and refining the results to provide accurate and contextually rich responses.",
+            backstory="""
                     You are a highly efficient and intelligent RAG agent capable of querying complex knowledge graphs and refining the results to generate precise and comprehensive responses.
                     Your tasks include:
                     1. Analyzing the user's query and formulating an effective strategy to extract relevant information from the code knowledge graph.
@@ -28,27 +30,31 @@ def __init__(
                     4. Maintaining traceability by including relevant citations and references in your output.
                     5. Including relevant citations in the response.
                 """,
-                tasks=[
-                    TaskConfig(
-                        description=code_gen_task_prompt,
-                        expected_output="Markdown formatted chat response to user's query grounded in provided code context and tool results",
-                    )
-                ],
-            ),
-            tools=self.tools_provider.get_tools(
-                [
-                    "get_code_from_multiple_node_ids",
-                    "get_node_neighbours_from_node_id",
-                    "get_code_from_probable_node_name",
-                    "ask_knowledge_graph_queries",
-                    "get_nodes_from_tags",
-                    "get_code_file_structure",
-                    "webpage_extractor",
-                    "github_tool",
-                ]
-            ),
+            tasks=[
+                TaskConfig(
+                    description=code_gen_task_prompt,
+                    expected_output="Markdown formatted chat response to user's query grounded in provided code context and tool results",
+                )
+            ],
+        )
+        tools = self.tools_provider.get_tools(
+            [
+                "get_code_from_multiple_node_ids",
+                "get_node_neighbours_from_node_id",
+                "get_code_from_probable_node_name",
+                "ask_knowledge_graph_queries",
+                "get_nodes_from_tags",
+                "get_code_file_structure",
+                "webpage_extractor",
+                "github_tool",
+            ]
         )
 
+        if self.llm_provider.is_current_model_supported_by_pydanticai():
+            return PydanticRagAgent(self.llm_provider, agent_config, tools)
+        else:
+            return CrewAIAgent(self.llm_provider, agent_config, tools)
+
     async def _enriched_context(self, ctx: ChatContext) -> ChatContext:
         if ctx.node_ids and len(ctx.node_ids) > 0:
             code_results = await self.tools_provider.get_code_from_multiple_node_ids_tool.run_multiple(
@@ -60,12 +66,14 @@ async def _enriched_context(self, ctx: ChatContext) -> ChatContext:
         return ctx
 
     async def run(self, ctx: ChatContext) -> ChatAgentResponse:
-        return await self.rag_agent.run(ctx)
+        ctx = await self._enriched_context(ctx)
+        return await self._build_agent().run(ctx)
 
     async def run_stream(
         self, ctx: ChatContext
     ) -> AsyncGenerator[ChatAgentResponse, None]:
-        async for chunk in self.rag_agent.run_stream(ctx):
+        ctx = await self._enriched_context(ctx)
+        async for chunk in self._build_agent().run_stream(ctx):
             yield chunk
 
 
diff --git a/app/modules/intelligence/agents/chat_agents/system_agents/integration_test_agent.py b/app/modules/intelligence/agents/chat_agents/system_agents/integration_test_agent.py
index 0cfbfe8ec..c3ed7e419 100644
--- a/app/modules/intelligence/agents/chat_agents/system_agents/integration_test_agent.py
+++ b/app/modules/intelligence/agents/chat_agents/system_agents/integration_test_agent.py
@@ -1,9 +1,10 @@
 import json
+from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
 from app.modules.intelligence.provider.provider_service import (
     ProviderService,
 )
 from app.modules.intelligence.tools.tool_service import ToolService
-from ..crewai_agent import CrewAIAgent, AgentConfig, TaskConfig
+from ..crewai_agent import AgentConfig, CrewAIAgent, TaskConfig
 from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
 from typing import AsyncGenerator, List
 from pydantic import BaseModel
@@ -22,30 +23,34 @@ def __init__(
     ):
         self.llm_provider = llm_provider
         self.tools_provider = tools_provider
-        self.rag_agent = CrewAIAgent(
-            llm_provider,
-            AgentConfig(
-                role="Integration Test Writer",
-                goal="Create a comprehensive integration test suite for the provided codebase. Analyze the code, determine the appropriate testing language and framework, and write tests that cover all major integration points.",
-                backstory="""
+
+    def _build_agent(self) -> ChatAgent:
+        agent_config = AgentConfig(
+            role="Integration Test Writer",
+            goal="Create a comprehensive integration test suite for the provided codebase. Analyze the code, determine the appropriate testing language and framework, and write tests that cover all major integration points.",
+            backstory="""
                     You are an expert in writing unit tests for code using latest features of the popular testing libraries for the given programming language.
                 """,
-                tasks=[
-                    TaskConfig(
-                        description=integration_test_task_prompt,
-                        expected_output="Write COMPLETE CODE for integration tests for each node based on the test plan.",
-                    )
-                ],
-            ),
-            tools=self.tools_provider.get_tools(
-                [
-                    "get_code_from_multiple_node_ids",
-                    "get_code_from_probable_node_name",
-                    "webpage_extractor",
-                    "github_tool",
-                ]
-            ),
+            tasks=[
+                TaskConfig(
+                    description=integration_test_task_prompt,
+                    expected_output="Write COMPLETE CODE for integration tests for each node based on the test plan.",
+                )
+            ],
         )
+        tools = self.tools_provider.get_tools(
+            [
+                "get_code_from_multiple_node_ids",
+                "get_code_from_probable_node_name",
+                "webpage_extractor",
+                "github_tool",
+            ]
+        )
+
+        if self.llm_provider.is_current_model_supported_by_pydanticai():
+            return PydanticRagAgent(self.llm_provider, agent_config, tools)
+        else:
+            return CrewAIAgent(self.llm_provider, agent_config, tools)
 
     def _enriched_context(self, ctx: ChatContext) -> ChatContext:
         if not ctx.node_ids or len(ctx.node_ids) == 0:
@@ -110,13 +115,13 @@ def extract_unique_node_contexts(node, visited=None):
         return ctx
 
     async def run(self, ctx: ChatContext) -> ChatAgentResponse:
-        return await self.rag_agent.run(self._enriched_context(ctx))
+        return await self._build_agent().run(self._enriched_context(ctx))
 
     async def run_stream(
         self, ctx: ChatContext
     ) -> AsyncGenerator[ChatAgentResponse, None]:
         ctx = self._enriched_context(ctx)
-        async for chunk in self.rag_agent.run_stream(ctx):
+        async for chunk in self._build_agent().run_stream(ctx):
             yield chunk
 
 
diff --git a/app/modules/intelligence/agents/chat_agents/system_agents/low_level_design_agent.py b/app/modules/intelligence/agents/chat_agents/system_agents/low_level_design_agent.py
index 8df76eb9f..a474d312a 100644
--- a/app/modules/intelligence/agents/chat_agents/system_agents/low_level_design_agent.py
+++ b/app/modules/intelligence/agents/chat_agents/system_agents/low_level_design_agent.py
@@ -1,8 +1,9 @@
+from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
 from app.modules.intelligence.provider.provider_service import (
     ProviderService,
 )
 from app.modules.intelligence.tools.tool_service import ToolService
-from ..crewai_agent import CrewAIAgent, AgentConfig, TaskConfig
+from ..crewai_agent import AgentConfig, CrewAIAgent, TaskConfig
 from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
 from typing import AsyncGenerator
 
@@ -17,35 +18,37 @@ def __init__(
         self.tools_provider = tools_provider
 
     def _build_agent(self) -> ChatAgent:
-        return CrewAIAgent(
-            self.llm_provider,
-            AgentConfig(
-                role="Design Planner",
-                goal="Create a detailed low-level design plan for implementing new features",
-                backstory="""
+        agent_config = AgentConfig(
+            role="Design Planner",
+            goal="Create a detailed low-level design plan for implementing new features",
+            backstory="""
                     You are a senior software architect specializing in creating detailed,
                     actionable design plans. Your expertise lies in breaking down complex features into
                     manageable steps and providing clear guidance for implementation.
                 """,
-                tasks=[
-                    TaskConfig(
-                        description=lld_task_prompt,
-                        expected_output="Low-level design plan for implementing the new feature",
-                    )
-                ],
-            ),
-            tools=self.tools_provider.get_tools(
-                [
-                    "get_code_from_multiple_node_ids",
-                    "get_node_neighbours_from_node_id",
-                    "get_code_from_probable_node_name",
-                    "ask_knowledge_graph_queries",
-                    "get_nodes_from_tags",
-                    "webpage_extractor",
-                    "github_tool",
-                ]
-            ),
+            tasks=[
+                TaskConfig(
+                    description=lld_task_prompt,
+                    expected_output="Low-level design plan for implementing the new feature",
+                )
+            ],
         )
+        tools = self.tools_provider.get_tools(
+            [
+                "get_code_from_multiple_node_ids",
+                "get_node_neighbours_from_node_id",
+                "get_code_from_probable_node_name",
+                "ask_knowledge_graph_queries",
+                "get_nodes_from_tags",
+                "webpage_extractor",
+                "github_tool",
+            ]
+        )
+
+        if self.llm_provider.is_current_model_supported_by_pydanticai():
+            return PydanticRagAgent(self.llm_provider, agent_config, tools)
+        else:
+            return CrewAIAgent(self.llm_provider, agent_config, tools)
 
     async def _enriched_context(self, ctx: ChatContext) -> ChatContext:
         if ctx.node_ids and len(ctx.node_ids) > 0:
diff --git a/app/modules/intelligence/agents/chat_agents/system_agents/qna_agent.py b/app/modules/intelligence/agents/chat_agents/system_agents/qna_agent.py
index c60ee1932..db8385239 100644
--- a/app/modules/intelligence/agents/chat_agents/system_agents/qna_agent.py
+++ b/app/modules/intelligence/agents/chat_agents/system_agents/qna_agent.py
@@ -1,8 +1,9 @@
+from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
 from app.modules.intelligence.provider.provider_service import (
     ProviderService,
 )
 from app.modules.intelligence.tools.tool_service import ToolService
-from ..crewai_agent import CrewAIAgent, AgentConfig, TaskConfig
+from ..crewai_agent import AgentConfig, CrewAIAgent, TaskConfig
 from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
 from typing import AsyncGenerator
 
@@ -17,12 +18,10 @@ def __init__(
         self.tools_provider = tools_provider
 
     def _build_agent(self) -> ChatAgent:
-        return CrewAIAgent(
-            self.llm_provider,
-            AgentConfig(
-                role="QNA Agent",
-                goal="Answer queries of the repo in a detail fashion",
-                backstory="""
+        agent_config = AgentConfig(
+            role="QNA Agent",
+            goal="Answer queries of the repo in a detail fashion",
+            backstory="""
                     You are a highly efficient and intelligent RAG agent capable of querying complex knowledge graphs and refining the results to generate precise and comprehensive responses.
                     Your tasks include:
                     1. Analyzing the user's query and formulating an effective strategy to extract relevant information from the code knowledge graph.
@@ -31,26 +30,30 @@ def _build_agent(self) -> ChatAgent:
                     4. Maintaining traceability by including relevant citations and references in your output.
                     5. Including relevant citations in the response.
                 """,
-                tasks=[
-                    TaskConfig(
-                        description=qna_task_prompt,
-                        expected_output="Markdown formatted chat response to user's query grounded in provided code context and tool results",
-                    )
-                ],
-            ),
-            tools=self.tools_provider.get_tools(
-                [
-                    "get_code_from_multiple_node_ids",
-                    "get_node_neighbours_from_node_id",
-                    "get_code_from_probable_node_name",
-                    "ask_knowledge_graph_queries",
-                    "get_nodes_from_tags",
-                    "get_code_file_structure",
-                    "webpage_extractor",
-                    "github_tool",
-                ]
-            ),
+            tasks=[
+                TaskConfig(
+                    description=qna_task_prompt,
+                    expected_output="Markdown formatted chat response to user's query grounded in provided code context and tool results",
+                )
+            ],
         )
+        tools = self.tools_provider.get_tools(
+            [
+                "get_code_from_multiple_node_ids",
+                "get_node_neighbours_from_node_id",
+                "get_code_from_probable_node_name",
+                "ask_knowledge_graph_queries",
+                "get_nodes_from_tags",
+                "get_code_file_structure",
+                "webpage_extractor",
+                "github_tool",
+            ]
+        )
+
+        if self.llm_provider.is_current_model_supported_by_pydanticai():
+            return PydanticRagAgent(self.llm_provider, agent_config, tools)
+        else:
+            return CrewAIAgent(self.llm_provider, agent_config, tools)
 
     async def _enriched_context(self, ctx: ChatContext) -> ChatContext:
         if ctx.node_ids and len(ctx.node_ids) > 0:
diff --git a/app/modules/intelligence/agents/chat_agents/system_agents/unit_test_agent.py b/app/modules/intelligence/agents/chat_agents/system_agents/unit_test_agent.py
index 93d2cceb6..065b5f3f7 100644
--- a/app/modules/intelligence/agents/chat_agents/system_agents/unit_test_agent.py
+++ b/app/modules/intelligence/agents/chat_agents/system_agents/unit_test_agent.py
@@ -1,8 +1,9 @@
+from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
 from app.modules.intelligence.provider.provider_service import (
     ProviderService,
 )
 from app.modules.intelligence.tools.tool_service import ToolService
-from ..crewai_agent import CrewAIAgent, AgentConfig, TaskConfig
+from ..crewai_agent import AgentConfig, CrewAIAgent, TaskConfig
 from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
 from typing import AsyncGenerator
 
@@ -17,29 +18,30 @@ def __init__(
         self.tools_provider = tools_provider
 
     def _build_agent(self) -> ChatAgent:
-        return CrewAIAgent(
-            self.llm_provider,
-            AgentConfig(
-                role="Test Plan and Unit Test Expert",
-                goal="Create test plans and write unit tests based on user requirements",
-                backstory="""You are a seasoned AI test engineer specializing in creating robust test plans and unit tests.
+        agent_config = AgentConfig(
+            role="Test Plan and Unit Test Expert",
+            goal="Create test plans and write unit tests based on user requirements",
+            backstory="""You are a seasoned AI test engineer specializing in creating robust test plans and unit tests.
                         You aim to assist users effectively in generating and refining test plans and unit tests, ensuring they are comprehensive and tailored to the user's project requirements.""",
-                tasks=[
-                    TaskConfig(
-                        description=qna_task_prompt,
-                        expected_output="Outline the test plan and write unit tests for each node based on the test plan.",
-                    )
-                ],
-            ),
-            tools=self.tools_provider.get_tools(
-                [
-                    "get_code_from_node_id",
-                    "get_code_from_probable_node_name",
-                    "webpage_extractor",
-                    "github_tool",
-                ]
-            ),
+            tasks=[
+                TaskConfig(
+                    description=qna_task_prompt,
+                    expected_output="Outline the test plan and write unit tests for each node based on the test plan.",
+                )
+            ],
         )
+        tools = self.tools_provider.get_tools(
+            [
+                "get_code_from_node_id",
+                "get_code_from_probable_node_name",
+                "webpage_extractor",
+                "github_tool",
+            ]
+        )
+        if self.llm_provider.is_current_model_supported_by_pydanticai():
+            return PydanticRagAgent(self.llm_provider, agent_config, tools)
+        else:
+            return CrewAIAgent(self.llm_provider, agent_config, tools)
 
     async def _enriched_context(self, ctx: ChatContext) -> ChatContext:
         if ctx.node_ids and len(ctx.node_ids) > 0:
diff --git a/app/modules/intelligence/provider/provider_service.py b/app/modules/intelligence/provider/provider_service.py
index 89907cf2b..1b845df5d 100644
--- a/app/modules/intelligence/provider/provider_service.py
+++ b/app/modules/intelligence/provider/provider_service.py
@@ -3,10 +3,13 @@
 from enum import Enum
 from typing import List, Dict, Any, Union, AsyncGenerator, Optional
 import uuid
+from anthropic import AsyncAnthropic
 from crewai import LLM
 from pydantic import BaseModel
+from pydantic_ai.models import Model
 from litellm import litellm, AsyncOpenAI, acompletion
 import instructor
+import httpx
 from portkey_ai import createHeaders, PORTKEY_GATEWAY_URL
 
 from app.modules.key_management.secret_manager import SecretManager
@@ -15,10 +18,15 @@
 
 from .provider_schema import ProviderInfo, GetProviderResponse
 
+from pydantic_ai.models.openai import OpenAIModel
+from pydantic_ai.models.anthropic import AnthropicModel
+from pydantic_ai.providers.openai import OpenAIProvider
+
 
 class AgentProvider(Enum):
     CREWAI = "CREWAI"
     LANGCHAIN = "LANGCHAIN"
+    PYDANTICAI = "PYDANTICAI"
 
 
 PLATFORM_PROVIDERS = [
@@ -403,17 +411,17 @@ def _initialize_llm(self, provider: str, size: str, agent_type: AgentProvider):
         """
         params = self._build_llm_params(provider, size)
         routing_provider = params.pop("routing_provider", None)
+        headers = createHeaders(
+            api_key=self.portkey_api_key,
+            provider=routing_provider,
+            trace_id=str(uuid.uuid4())[:8],
+        )
         if agent_type == AgentProvider.CREWAI:
             crewai_params = {"model": params["model"], **params}
             if "default_headers" in params:
                 crewai_params["headers"] = params["default_headers"]
             if self.portkey_api_key and routing_provider != "ollama":
                 # ollama + portkey is not supported currently
-                headers = createHeaders(
-                    api_key=self.portkey_api_key,
-                    provider=routing_provider,
-                    trace_id=str(uuid.uuid4())[:8],
-                )
                 crewai_params["extra_headers"] = headers
                 crewai_params["base_url"] = PORTKEY_GATEWAY_URL
             return LLM(**crewai_params)
@@ -474,3 +482,88 @@ async def get_global_ai_provider(self, user_id: str) -> GetProviderResponse:
                 high_reasoning_model if high_reasoning_model else default_large_model
             ),
         )
+
+    def is_current_model_supported_by_pydanticai(self) -> bool:
+        provider = self._get_provider_config("large")
+        if provider in [
+            "openai",
+            "anthropic",
+        ]:
+            return True
+        return False
+
+    def get_pydantic_model(self) -> Model | None:
+        provider = self._get_provider_config("large")
+        params = self._build_llm_params(provider, "large")
+        routing_provider = params.pop("routing_provider", None)
+        api_key = params["api_key"]
+
+        # pick model name => convert "openai/gpt-4o" to "gpt-4o"
+        model_name: str = params["model"]
+        model = model_name.split("/")[-1]
+
+        # if portkey is enabled
+        if self.portkey_api_key:
+            match provider:
+                case "openai":
+                    return OpenAIModel(
+                        model_name=model,
+                        provider=OpenAIProvider(
+                            api_key=api_key,
+                            base_url=PORTKEY_GATEWAY_URL,
+                            http_client=httpx.AsyncClient(
+                                headers=createHeaders(
+                                    api_key=self.portkey_api_key,
+                                    provider=routing_provider,
+                                    trace_id=str(uuid.uuid4())[:8],
+                                ),
+                            ),
+                        ),
+                    )
+                case "anthropic":
+                    return AnthropicModel(
+                        model_name=model,
+                        anthropic_client=AsyncAnthropic(
+                            base_url=PORTKEY_GATEWAY_URL,
+                            api_key=api_key,
+                            default_headers=createHeaders(
+                                api_key=self.portkey_api_key,
+                                provider=routing_provider,
+                                trace_id=str(uuid.uuid4())[:8],
+                            ),
+                        ),
+                    )
+                # case "deepseek":
+                #     model_name: str = params["model"]
+                #     model_name = model_name.split("/", 1)[1]
+                #     return OpenAIModel(
+                #         model_name=model_name if model_name else model,
+                #         # model_name="gpt-4o-mini",
+                #         provider=OpenAIProvider(
+                #             api_key=api_key,
+                #             base_url="https://openrouter.ai/api/v1",
+                #             http_client=httpx.AsyncClient(
+                #                 headers=createHeaders(
+                #                     # api_key=self.portkey_api_key,
+                #                     provider=routing_provider,
+                #                     trace_id=str(uuid.uuid4())[:8],
+                #                 ),
+                #             ),
+                #         ),
+                #     )
+
+        match provider:
+            case "openai":
+                return OpenAIModel(
+                    model_name=model,
+                    provider=OpenAIProvider(
+                        api_key=api_key,
+                    ),
+                )
+            case "anthropic":
+                return AnthropicModel(
+                    model_name=model,
+                    anthropic_client=AsyncAnthropic(
+                        api_key=api_key,
+                    ),
+                )
diff --git a/app/modules/intelligence/tools/change_detection/change_detection_tool.py b/app/modules/intelligence/tools/change_detection/change_detection_tool.py
index c69afb4ad..0e8586282 100644
--- a/app/modules/intelligence/tools/change_detection/change_detection_tool.py
+++ b/app/modules/intelligence/tools/change_detection/change_detection_tool.py
@@ -3,7 +3,7 @@
 from typing import Dict, List
 
 from fastapi import HTTPException
-from langchain_core.tools import StructuredTool, Tool
+from langchain_core.tools import StructuredTool
 from pydantic import BaseModel, Field
 from tree_sitter_languages import get_parser
 
@@ -304,7 +304,7 @@ def run(self, project_id: str) -> str:
         return asyncio.run(self.get_code_changes(project_id))
 
 
-def get_change_detection_tool(user_id: str) -> Tool:
+def get_change_detection_tool(user_id: str) -> StructuredTool:
     """
     Get a list of LangChain Tool objects for use in agents.
     """
diff --git a/app/modules/intelligence/tools/code_query_tools/get_code_from_node_name_tool.py b/app/modules/intelligence/tools/code_query_tools/get_code_from_node_name_tool.py
index 1b67c410d..ed1919df9 100644
--- a/app/modules/intelligence/tools/code_query_tools/get_code_from_node_name_tool.py
+++ b/app/modules/intelligence/tools/code_query_tools/get_code_from_node_name_tool.py
@@ -3,7 +3,7 @@
 from typing import Any, Dict
 
 from fastapi import HTTPException
-from langchain_core.tools import StructuredTool, Tool
+from langchain_core.tools import StructuredTool
 from neo4j import GraphDatabase
 from sqlalchemy.orm import Session
 
@@ -142,7 +142,7 @@ def __del__(self):
             self.neo4j_driver.close()
 
 
-def get_code_from_node_name_tool(sql_db: Session, user_id: str) -> Tool:
+def get_code_from_node_name_tool(sql_db: Session, user_id: str) -> StructuredTool:
     tool_instance = GetCodeFromNodeNameTool(sql_db, user_id)
     return StructuredTool.from_function(
         coroutine=tool_instance.arun,
diff --git a/app/modules/intelligence/tools/code_query_tools/get_code_graph_from_node_id_tool.py b/app/modules/intelligence/tools/code_query_tools/get_code_graph_from_node_id_tool.py
index c6d9752eb..ea4e08743 100644
--- a/app/modules/intelligence/tools/code_query_tools/get_code_graph_from_node_id_tool.py
+++ b/app/modules/intelligence/tools/code_query_tools/get_code_graph_from_node_id_tool.py
@@ -2,7 +2,7 @@
 import logging
 from typing import Any, Dict, List, Optional
 
-from langchain_core.tools import StructuredTool, Tool
+from langchain_core.tools import StructuredTool
 from neo4j import GraphDatabase
 from sqlalchemy.orm import Session
 
@@ -208,7 +208,7 @@ def __del__(self):
             self.neo4j_driver.close()
 
 
-def get_code_graph_from_node_id_tool(sql_db: Session) -> Tool:
+def get_code_graph_from_node_id_tool(sql_db: Session) -> StructuredTool:
     tool_instance = GetCodeGraphFromNodeIdTool(sql_db)
     return StructuredTool.from_function(
         coroutine=tool_instance.arun,
diff --git a/app/modules/intelligence/tools/code_query_tools/get_code_graph_from_node_name_tool.py b/app/modules/intelligence/tools/code_query_tools/get_code_graph_from_node_name_tool.py
index 21c803d90..9284d02f0 100644
--- a/app/modules/intelligence/tools/code_query_tools/get_code_graph_from_node_name_tool.py
+++ b/app/modules/intelligence/tools/code_query_tools/get_code_graph_from_node_name_tool.py
@@ -2,7 +2,7 @@
 import logging
 from typing import Any, Dict, List, Optional
 
-from langchain_core.tools import StructuredTool, Tool
+from langchain_core.tools import StructuredTool
 from neo4j import GraphDatabase
 from sqlalchemy.orm import Session
 
@@ -210,7 +210,7 @@ def __del__(self):
             self.neo4j_driver.close()
 
 
-def get_code_graph_from_node_name_tool(sql_db: Session) -> Tool:
+def get_code_graph_from_node_name_tool(sql_db: Session) -> StructuredTool:
     tool_instance = GetCodeGraphFromNodeNameTool(sql_db)
     return StructuredTool.from_function(
         coroutine=tool_instance.arun,
diff --git a/app/modules/intelligence/tools/code_query_tools/get_node_neighbours_from_node_id_tool.py b/app/modules/intelligence/tools/code_query_tools/get_node_neighbours_from_node_id_tool.py
index 50772046b..0783af31c 100644
--- a/app/modules/intelligence/tools/code_query_tools/get_node_neighbours_from_node_id_tool.py
+++ b/app/modules/intelligence/tools/code_query_tools/get_node_neighbours_from_node_id_tool.py
@@ -2,7 +2,7 @@
 import logging
 from typing import Any, Dict, List, Optional
 
-from langchain_core.tools import StructuredTool, Tool
+from langchain_core.tools import StructuredTool
 from neo4j import GraphDatabase
 from pydantic import BaseModel, Field
 from sqlalchemy.orm import Session
@@ -117,7 +117,7 @@ def __del__(self):
             self.neo4j_driver.close()
 
 
-def get_node_neighbours_from_node_id_tool(sql_db: Session) -> Tool:
+def get_node_neighbours_from_node_id_tool(sql_db: Session) -> StructuredTool:
     tool_instance = GetNodeNeighboursFromNodeIdTool(sql_db)
     return StructuredTool.from_function(
         coroutine=tool_instance.arun,
diff --git a/app/modules/intelligence/tools/kg_based_tools/get_code_from_multiple_node_ids_tool.py b/app/modules/intelligence/tools/kg_based_tools/get_code_from_multiple_node_ids_tool.py
index 3945be6a1..018489491 100644
--- a/app/modules/intelligence/tools/kg_based_tools/get_code_from_multiple_node_ids_tool.py
+++ b/app/modules/intelligence/tools/kg_based_tools/get_code_from_multiple_node_ids_tool.py
@@ -2,7 +2,7 @@
 import logging
 from typing import Any, Dict, List
 
-from langchain_core.tools import StructuredTool, Tool
+from langchain_core.tools import StructuredTool
 from neo4j import GraphDatabase
 from pydantic import BaseModel, Field
 from sqlalchemy.orm import Session
@@ -154,7 +154,9 @@ def __del__(self):
             self.neo4j_driver.close()
 
 
-def get_code_from_multiple_node_ids_tool(sql_db: Session, user_id: str) -> Tool:
+def get_code_from_multiple_node_ids_tool(
+    sql_db: Session, user_id: str
+) -> StructuredTool:
     tool_instance = GetCodeFromMultipleNodeIdsTool(sql_db, user_id)
     return StructuredTool.from_function(
         coroutine=tool_instance.arun,
diff --git a/app/modules/intelligence/tools/kg_based_tools/get_code_from_node_id_tool.py b/app/modules/intelligence/tools/kg_based_tools/get_code_from_node_id_tool.py
index dad643b2c..39b62a0ed 100644
--- a/app/modules/intelligence/tools/kg_based_tools/get_code_from_node_id_tool.py
+++ b/app/modules/intelligence/tools/kg_based_tools/get_code_from_node_id_tool.py
@@ -2,7 +2,7 @@
 import logging
 from typing import Any, Dict
 
-from langchain_core.tools import StructuredTool, Tool
+from langchain_core.tools import StructuredTool
 from neo4j import GraphDatabase
 from pydantic import BaseModel, Field
 from sqlalchemy.orm import Session
@@ -135,7 +135,7 @@ def __del__(self):
             self.neo4j_driver.close()
 
 
-def get_code_from_node_id_tool(sql_db: Session, user_id: str) -> Tool:
+def get_code_from_node_id_tool(sql_db: Session, user_id: str) -> StructuredTool:
     tool_instance = GetCodeFromNodeIdTool(sql_db, user_id)
     return StructuredTool.from_function(
         coroutine=tool_instance.arun,
diff --git a/app/modules/intelligence/tools/kg_based_tools/get_code_from_probable_node_name_tool.py b/app/modules/intelligence/tools/kg_based_tools/get_code_from_probable_node_name_tool.py
index edfb7123c..7becebe63 100644
--- a/app/modules/intelligence/tools/kg_based_tools/get_code_from_probable_node_name_tool.py
+++ b/app/modules/intelligence/tools/kg_based_tools/get_code_from_probable_node_name_tool.py
@@ -2,7 +2,7 @@
 import logging
 from typing import Any, Dict, List
 
-from langchain_core.tools import StructuredTool, Tool
+from langchain_core.tools import StructuredTool
 from neo4j import GraphDatabase
 from pydantic import BaseModel, Field
 from sqlalchemy.orm import Session
@@ -205,7 +205,9 @@ def __del__(self):
             self.neo4j_driver.close()
 
 
-def get_code_from_probable_node_name_tool(sql_db: Session, user_id: str) -> Tool:
+def get_code_from_probable_node_name_tool(
+    sql_db: Session, user_id: str
+) -> StructuredTool:
     tool_instance = GetCodeFromProbableNodeNameTool(sql_db, user_id)
     return StructuredTool.from_function(
         coroutine=tool_instance.arun,
diff --git a/app/modules/intelligence/tools/tool_service.py b/app/modules/intelligence/tools/tool_service.py
index 43b41735b..ca0d4bd08 100644
--- a/app/modules/intelligence/tools/tool_service.py
+++ b/app/modules/intelligence/tools/tool_service.py
@@ -1,4 +1,4 @@
-from typing import Any, Dict, List
+from typing import Dict, List
 
 from sqlalchemy.orm import Session
 
@@ -43,6 +43,7 @@
 from app.modules.intelligence.tools.web_tools.webpage_extractor_tool import (
     webpage_extractor_tool,
 )
+from langchain_core.tools import StructuredTool
 
 
 class ToolService:
@@ -58,7 +59,7 @@ def __init__(self, db: Session, user_id: str):
         self.file_structure_tool = GetCodeFileStructureTool(db)
         self.tools = self._initialize_tools()
 
-    def get_tools(self, tool_names: List[str]) -> List[Any]:
+    def get_tools(self, tool_names: List[str]) -> List[StructuredTool]:
         """get tools if exists"""
         tools = []
         for tool_name in tool_names:
@@ -66,7 +67,7 @@ def get_tools(self, tool_names: List[str]) -> List[Any]:
                 tools.append(self.tools[tool_name])
         return tools
 
-    def _initialize_tools(self) -> Dict[str, Any]:
+    def _initialize_tools(self) -> Dict[str, StructuredTool]:
         tools = {
             "get_code_from_probable_node_name": get_code_from_probable_node_name_tool(
                 self.db, self.user_id
diff --git a/app/modules/intelligence/tools/web_tools/github_tool.py b/app/modules/intelligence/tools/web_tools/github_tool.py
index 49a411410..b61210188 100644
--- a/app/modules/intelligence/tools/web_tools/github_tool.py
+++ b/app/modules/intelligence/tools/web_tools/github_tool.py
@@ -8,7 +8,7 @@
 from github import Github
 from github.Auth import AppAuth
 from github.GithubException import UnknownObjectException
-from langchain_core.tools import StructuredTool, Tool
+from langchain_core.tools import StructuredTool
 from pydantic import BaseModel, Field
 from sqlalchemy.orm import Session
 
@@ -233,7 +233,7 @@ def _fetch_github_content(
             return None
 
 
-def github_tool(sql_db: Session, user_id: str) -> Optional[Tool]:
+def github_tool(sql_db: Session, user_id: str) -> Optional[StructuredTool]:
     if not os.getenv("GITHUB_APP_ID") or not config_provider.get_github_key():
         logging.warning(
             "GitHub app credentials not set, GitHub tool will not be initialized"
diff --git a/app/modules/intelligence/tools/web_tools/webpage_extractor_tool.py b/app/modules/intelligence/tools/web_tools/webpage_extractor_tool.py
index bea9678c1..2e74da11f 100644
--- a/app/modules/intelligence/tools/web_tools/webpage_extractor_tool.py
+++ b/app/modules/intelligence/tools/web_tools/webpage_extractor_tool.py
@@ -4,7 +4,7 @@
 from typing import Any, Dict, Optional
 
 from firecrawl import FirecrawlApp
-from langchain_core.tools import StructuredTool, Tool
+from langchain_core.tools import StructuredTool
 from pydantic import BaseModel, Field
 from sqlalchemy.orm import Session
 
@@ -86,7 +86,7 @@ def _extract_content(self, url: str) -> Optional[Dict[str, Any]]:
         }
 
 
-def webpage_extractor_tool(sql_db: Session, user_id: str) -> Optional[Tool]:
+def webpage_extractor_tool(sql_db: Session, user_id: str) -> Optional[StructuredTool]:
     if not os.getenv("FIRECRAWL_API_KEY"):
         logging.warning(
             "FIRECRAWL_API_KEY not set, webpage extractor tool will not be initialized"
diff --git a/requirements.txt b/requirements.txt
index 874b92352..b626a522d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -22,7 +22,7 @@ grep-ast==0.4.1
 pygments==2.18.0
 networkx==3.4.2
 blar-graph==1.1.6
-openai==1.60.2
+openai==1.66.3
 uuid6==2024.7.10
 aiohttp==3.11.9
 langchain==0.3.16
@@ -63,3 +63,4 @@ tiktoken==0.7.0
 agentops==0.3.26
 pydantic[email]==2.10.3
 firecrawl-py==1.11.1
+pydantic_ai==0.0.39


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 7**

<details>
<summary>🧹 Nitpick comments (57)</summary><blockquote>

<details>
<summary>app/modules/intelligence/agents/chat_agents/auto_router_agent.py (1)</summary><blockquote>

`1-16`: **Check variable naming clarity.**
Overall, imports and logging initialization are fine. Consider renaming the dictionary key variable `id` to something like `agent_key` inside the list comprehension to avoid overshadowing Python’s built-in `id()`.

```diff
- f"agent_id: {agents[id].id}\n description: {agents[id].description}\n\n"
+ f"agent_id: {agents[agent_key].id}\n description: {agents[agent_key].description}\n\n"
  for agent_key in agents
```

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agent.py (3)</summary><blockquote>

`1-5`: **Consider clarifying async usage vs. docstring references to synchronous behavior.**

The file’s imports suggest asynchronous operations, yet the docstring text references "synchronous" behavior for `run`. You might want to align docstrings and method signatures to avoid confusion about how the agent processes requests.

---

`26-38`: **Synchronize method naming with asynchronous execution in `ChatAgent`.**

The `run` method is marked `async def`, so clarifying that it is still asynchronous would help. Consider renaming it or updating its docstring to reflect asynchronous usage, e.g. "Run the agent once and return a complete response."

---

`40-45`: **Add docstrings for better maintainability.**

`AgentWithInfo` could benefit from a brief docstring explaining its purpose, particularly how it integrates with `ChatAgent` and where an instance might be used.

</blockquote></details>
<details>
<summary>app/modules/intelligence/tools/tool_service.py (3)</summary><blockquote>

`55-59`: **Check lazy or on-demand initialization feasibility.**

Immediately instantiating `get_code_from_multiple_node_ids_tool`, `get_code_graph_from_node_id_tool`, and `file_structure_tool` might be memory-intensive if not always used. Consider lazy loading if they are only needed in specific scenarios.

---

`62-69`: **Efficient lookup in `get_tools`.**

This logic is straightforward. However, a small optimization or alternative approach could be using a list comprehension with conditional checks:
```diff
-def get_tools(self, tool_names: List[str]) -> List[StructuredTool]:
-    tools = []
-    for tool_name in tool_names:
-        if self.tools.get(tool_name) is not None:
-            tools.append(self.tools[tool_name])
-    return tools
+def get_tools(self, tool_names: List[str]) -> List[StructuredTool]:
+    return [
+        self.tools[name] for name in tool_names
+        if name in self.tools
+    ]
```

---

`70-70`: **Ensure consistency in initialization methods.**

Renaming `_initialize_tools` to something that better reflects its new role like `_build_tool_dict` or `_create_tool_mapping` helps maintain clarity that it returns a dictionary of `StructuredTool`.

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/langchain_agent.py (5)</summary><blockquote>

`55-59`: **Remove the unused local variable `node_ids`.**

A local variable `node_ids` is assigned but never used, as flagged by static analysis (F841). Removing it helps maintain cleaner code.


```diff
- if isinstance(ctx.node_ids, str):
-     node_ids = [ctx.node_ids]
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

58-58: Local variable `node_ids` is assigned to but never used

Remove assignment to unused variable `node_ids`

(F841)

</details>

</details>

---

`27-48`: **Consider making `debug` mode configurable**  

`debug=True` is currently hardcoded in `create_react_agent`. Exposing this value via a configuration entry or an environment variable can help you toggle debug mode easily in different environments.


```diff
- self.agent = create_react_agent(
-     model=llm_provider.get_large_llm(AgentProvider.LANGCHAIN),
-     tools=ToolExecutor(tools),
-     debug=True,
- )
+ import os
+ ...
+ debug_setting = bool(os.getenv("AGENT_DEBUG", ""))
+ self.agent = create_react_agent(
+     model=llm_provider.get_large_llm(AgentProvider.LANGCHAIN),
+     tools=ToolExecutor(tools),
+     debug=debug_setting,
+ )
```

---

`49-101`: **Consider using a templating approach for maintainability**  

The multi-line f-string approach is workable, but consider extracting it into a template-based approach (e.g., Jinja2) for cleaner separation of logic and presentation.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

58-58: Local variable `node_ids` is assigned to but never used

Remove assignment to unused variable `node_ids`

(F841)

</details>

</details>

---

`102-128`: **Customize or log exceptions for better traceability**  

Re-raising a generic `Exception` can obscure contextual information. Consider creating a custom exception subclass or attaching relevant details to aid debugging.

---

`129-153`: **Consider validating streamed partial responses**  

When streaming partial replies, ensure that chunk data is sanitized or validated to avoid sending incomplete or incorrect output to clients.

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/pydantic_agent.py (5)</summary><blockquote>

`31-64`: **Make the model name configurable**  

The agent is currently initialized with `model="openai:gpt-4o"`. Consider exposing this as a configuration or environment variable to facilitate experimentation and versioning.

---

`74-76`: **Remove the unused local variable `node_ids`.**

Like in other parts of the codebase, this variable is assigned but never used. Eliminating it keeps the code simpler.


```diff
- if isinstance(ctx.node_ids, str):
-     node_ids = [ctx.node_ids]
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

75-75: Local variable `node_ids` is assigned to but never used

Remove assignment to unused variable `node_ids`

(F841)

</details>

</details>

---

`66-108`: **Consider using a templating solution**  

A templating library (e.g., Jinja2) could make the task description more maintainable and easier to adjust compared to a large multi-line string literal.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

75-75: Local variable `node_ids` is assigned to but never used

Remove assignment to unused variable `node_ids`

(F841)

</details>

</details>

---

`109-132`: **Provide specific exception handling**  

Catching and re-raising a bare `Exception` can lead to generic error messages. Using a more descriptive exception type or adding contextual information will simplify troubleshooting.

---

`133-178`: **Implement error checks or feedback in streamed events**  

When streaming partial content, consider verifying that each chunk meets expected structure or content guidelines before yielding it to the client.

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/agents_router.py (1)</summary><blockquote>

`28-31`: **Consider removing or enhancing the empty constructor**  

The constructor is entirely empty. If no initialization is required, you could remove the constructor or at least provide a docstring explaining why it's left empty.

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/adaptive_agent.py (4)</summary><blockquote>

`47-47`: **Use Pythonic `is None` instead of `== None`.**
Replacing equality comparison with identity comparison improves readability and aligns with common best practices.  


```diff
- if system_prompt == None:
+ if system_prompt is None:
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

47-47: Comparison to `None` should be `cond is None`

Replace with `cond is None`

(E711)

</details>

</details>

---

`39-72`: **Consider adding docstrings or inline comments.**
These methods are central to your agent’s message-building logic. Including docstrings clarifies the purpose of each method and helps maintainers quickly understand the approach to prompt retrieval and query formatting.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

47-47: Comparison to `None` should be `cond is None`

Replace with `cond is None`

(E711)

</details>

</details>

---

`73-99`: **Add robustness for classification errors.**
Currently, if classification fails, you default to `ClassificationResult.AGENT_REQUIRED`. Ensure this fallback is desired. In some cases, you may want a different fallback or more granular error handling so that you don't incorrectly delegate queries that only partially failed classification.

---

`104-110`: **Provide an option to capture citations for LLM responses.**
When returning an LLM response directly, you currently return an empty citations list. Consider capturing any references or relevant context from the LLM to maintain parity with the RAG approach.  

Would you like help implementing a citation extraction strategy for these direct LLM calls?

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/blast_radius_agent.py (3)</summary><blockquote>

`11-11`: **Add a class-level docstring.**
While your constructor and method naming is clear, a concise class docstring describing the agent’s functionality improves readability, especially for new contributors.

---

`20-45`: **Reuse the built agent to avoid overhead.**
Each call to `run`/`run_stream` rebuilds the agent. Consider storing it during initialization if the agent's configuration does not change at runtime to reduce repeated object construction overhead.  


```diff
+ class BlastRadiusAgent(ChatAgent):
+     def __init__(self, llm_provider: ProviderService, tools_provider: ToolService):
+         self.tools_provider = tools_provider
+         self.llm_provider = llm_provider
+         self._agent = self._build_agent()
+
      def _build_agent(self):
          return PydanticRagAgent(
              ...
          )

      async def run(self, ctx: ChatContext) -> ChatAgentResponse:
-         return await self._build_agent().run(ctx)
+         return await self._agent.run(ctx)
```

---

`46-94`: **Add tests for the new agent’s behavior.**
Ensure you have direct tests verifying the correctness of the returned analysis and coverage for various code change scenarios.

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/debug_agent.py (1)</summary><blockquote>

`11-31`: **Add a class-level docstring or inline documentation.**
Although the class name is descriptive, an explicit docstring detailing the agent’s objective will help future maintainers, ensuring they immediately understand the agent’s role in debugging.

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/unit_test_agent.py (3)</summary><blockquote>

`11-19`: **Consider calling the base class constructor.**
If the `ChatAgent` base class has initialization logic, calling `super().__init__()` ensures proper setup. Otherwise, clarify if it's deliberately omitted.  

```diff
 class UnitTestAgent(ChatAgent):
     def __init__(
         self,
         llm_provider: ProviderService,
         tools_provider: ToolService,
     ):
+        super().__init__()
         self.llm_provider = llm_provider
         self.tools_provider = tools_provider
```

---

`45-54`: **Handle tool execution errors.**
In `_enriched_context`, if `get_code_from_multiple_node_ids_tool` fails or returns invalid data, the code appends an empty or invalid string. Consider defensive checks or error handling.

---

`66-104`: **Consider externalizing the prompt.**
While embedding the `qna_task_prompt` in code is convenient for prototyping, externalizing it (e.g., storing in a dedicated prompt file) might improve maintainability, especially if the prompt grows or changes frequently.

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/low_level_design_agent.py (3)</summary><blockquote>

`11-19`: **Consider calling the parent constructor.**
Just like in the UnitTestAgent, verify if the `ChatAgent` base class requires initialization via `super().__init__()`.

---

`51-68`: **Add error handling for file structure retrieval.**
If `file_structure_tool.fetch_repo_structure` fails, consider logging or handling the exception so that partial context doesn't lead to incorrect design plans.

---

`69-78`: **Potential fallback for missing context.**
`run` and `run_stream` rely on `_enriched_context`. If `_enriched_context` raises an error or returns incomplete data, consider providing a fallback design approach or user-friendly error messages.

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/integration_test_agent.py (4)</summary><blockquote>

`13-16`: **Document the NodeContext model.**
The `NodeContext` class could benefit from a brief docstring explaining node IDs and names.

---

`18-26`: **Initialize the base agent if needed.**
Similar to the other agents, confirm whether `ChatAgent` base class initialization is required.

---

`113-115`: **Consider extra error handling.**
A failure in the RAG agent call might propagate. Wrap calls in try-except blocks to maintain stability.

---

`116-122`: **Assess concurrency in run_stream.**
As this function streams responses asynchronously, consider safe concurrency to prevent partial data if an exception occurs.

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/qna_agent.py (3)</summary><blockquote>

`11-19`: **Consider adding docstrings and type hints in the constructor.**

Documenting the interface and explaining each constructor parameter will make the class more self-explanatory for future maintainers.

---

`20-55`: **Review tool selection carefully.**

While it's beneficial to include a broad set of tools, consider whether all listed tools are truly necessary for QnA functionality. Maintaining a minimal toolset improves performance and reduces complexity.

---

`85-167`: **Ensure the lengthy prompt text remains maintainable.**

This multiline string is core to the Q&A logic. Consider extracting it into a dedicated, documented module or a constant to prevent accidental edits and to improve readability.

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/agents_service.py (5)</summary><blockquote>

`35-40`: **Add docstring for AgentInfo.**

Given that `AgentInfo` is a central data model, adding a short docstring about each field will help developers understand the purpose and constraints of each attribute.

---

`43-55`: **Add a docstring to the AgentsService constructor.**

Along with parameters like `db`, `llm_provider`, and others, a concise docstring explaining their relevance and usage enables easier onboarding for new contributors.

---

`59-66`: **Consider naming or docs for `_system_agents`.**

The `_system_agents` method returns a dictionary of system agent instances. A short docstring or a clearer method name (e.g., `_build_system_agents`) can improve readability.

---

`161-197`: **Add unit tests for `list_available_agents`.**

The logic that merges system and custom agents can break if new agent types or statuses are introduced. Reinforce reliability with dedicated test coverage.

---

`198-204`: **Ensure consistent error handling in `validate_agent_id`.**

When a user provides an invalid `agent_id`, consider returning a more descriptive error or logging a warning with additional context.

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/code_gen_agent.py (3)</summary><blockquote>

`11-19`: **Add docstrings and explicit type annotations to clarify inputs.**

Explain the `CodeGenAgent` constructor parameters. This ensures future maintainers understand the dependencies needed for code generation.

---

`63-71`: **Consider chunking or truncating large code content.**

If `ctx.node_ids` refer to large code sections, appending them in full to `ctx.additional_context` might degrade performance. Evaluate whether chunking or summarization is necessary.

---

`84-230`: **Keep the prompt organized and well-commented.**

While the extensive prompt ensures comprehensive code generation, maintaining it in a structured, dedicated resource (e.g., a separate constants file) can improve readability and reduce accidental edits.

</blockquote></details>
<details>
<summary>app/modules/conversations/conversation/conversation_service.py (4)</summary><blockquote>

`82-84`: **Spelling mismatch in `promt_service`.**  
Rename to keep naming consistent with “prompt_service.”  

```diff
-        promt_service: PromptService,
+        prompt_service: PromptService,
```

---

`93-95`: **Ensure consistency in attribute naming.**  
This property assignment keeps the same spelling discrepancy. Consider updating to reflect the corrected parameter name:  

```diff
-        self.prompt_service = promt_service
+        self.prompt_service = prompt_service
```

---

`366-367`: **Avoid unchecked type ignore.**  
Please clarify the need for `# type: ignore`. Removing it or improving stubs might help maintain type safety.

---

`512-515`: **Avoid overshadowing the built-in name `type`.**  
Using “type” as a variable name can be confusing. Rename for better clarity.  

```diff
-            type = await self.agent_service.validate_agent_id(user_id, str(agent_id))
+            agent_type = await self.agent_service.validate_agent_id(user_id, str(agent_id))
```

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/crewai_agent.py (6)</summary><blockquote>

`23-31`: **AgentConfig model with max_iter.**  
Consider validating `max_iter` to avoid zero or negative iteration scenarios if unsupported.

---

`40-57`: **Creating the underlying `Agent` with `verbose=True`.**  
Consider turning off verbosity in production to reduce potentially sensitive logs.  

```diff
-            verbose=True,
+            verbose=False,  # or configurable based on environment
```

---

`59-69`: **Tools description generator.**  
If tool name or args schema can contain user content, consider validation to prevent injection or malformed strings.

---

`121-135`: **Task creation with `_create_task_description`.**  
Optional logging of the final description might help debug complex tasks.

---

`136-162`: **Raises a plain `Exception` on errors.**  
Using a more specific exception could improve error handling.  

```diff
-            raise Exception from e
+            raise CrewAIAgentError("Run method failed") from e
```

---

`167-171`: **`run_stream` yields only once.**  
To truly stream, consider chunking or partial results.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 75b4a1e923e48bf888c94d36db76f6eea3a2afe4 and 8d941526c3ecd91bcf8a2942695668c6a0bacb86.

</details>

<details>
<summary>📒 Files selected for processing (49)</summary>

* `app/core/models.py` (1 hunks)
* `app/modules/conversations/conversation/conversation_service.py` (9 hunks)
* `app/modules/intelligence/agents/agent_base.py` (0 hunks)
* `app/modules/intelligence/agents/agent_injector_service.py` (0 hunks)
* `app/modules/intelligence/agents/agents/blast_radius_agent.py` (0 hunks)
* `app/modules/intelligence/agents/agents/code_gen_agent.py` (0 hunks)
* `app/modules/intelligence/agents/agents/debug_rag_agent.py` (0 hunks)
* `app/modules/intelligence/agents/agents/integration_test_agent.py` (0 hunks)
* `app/modules/intelligence/agents/agents/low_level_design_agent.py` (0 hunks)
* `app/modules/intelligence/agents/agents/rag_agent.py` (0 hunks)
* `app/modules/intelligence/agents/agents/unit_test_agent.py` (0 hunks)
* `app/modules/intelligence/agents/agents_controller.py` (1 hunks)
* `app/modules/intelligence/agents/agents_router.py` (3 hunks)
* `app/modules/intelligence/agents/agents_schema.py` (0 hunks)
* `app/modules/intelligence/agents/agents_service.py` (2 hunks)
* `app/modules/intelligence/agents/base_agent_service.py` (0 hunks)
* `app/modules/intelligence/agents/chat_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/adaptive_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/auto_router_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/code_changes_chat_agent.py` (0 hunks)
* `app/modules/intelligence/agents/chat_agents/code_gen_chat_agent.py` (0 hunks)
* `app/modules/intelligence/agents/chat_agents/crewai_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/debugging_chat_agent.py` (0 hunks)
* `app/modules/intelligence/agents/chat_agents/integration_test_chat_agent.py` (0 hunks)
* `app/modules/intelligence/agents/chat_agents/langchain_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/lld_chat_agent.py` (0 hunks)
* `app/modules/intelligence/agents/chat_agents/pydantic_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/qna_chat_agent.py` (0 hunks)
* `app/modules/intelligence/agents/chat_agents/supervisor_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/blast_radius_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/code_gen_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/debug_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/integration_test_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/low_level_design_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/qna_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/unit_test_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/unit_test_chat_agent.py` (0 hunks)
* `app/modules/intelligence/agents/custom_agents/custom_agents_service.py` (1 hunks)
* `app/modules/intelligence/tools/change_detection/change_detection_tool.py` (2 hunks)
* `app/modules/intelligence/tools/code_query_tools/get_code_from_node_name_tool.py` (2 hunks)
* `app/modules/intelligence/tools/code_query_tools/get_code_graph_from_node_id_tool.py` (2 hunks)
* `app/modules/intelligence/tools/code_query_tools/get_code_graph_from_node_name_tool.py` (2 hunks)
* `app/modules/intelligence/tools/code_query_tools/get_node_neighbours_from_node_id_tool.py` (2 hunks)
* `app/modules/intelligence/tools/kg_based_tools/get_code_from_multiple_node_ids_tool.py` (2 hunks)
* `app/modules/intelligence/tools/kg_based_tools/get_code_from_node_id_tool.py` (2 hunks)
* `app/modules/intelligence/tools/kg_based_tools/get_code_from_probable_node_name_tool.py` (2 hunks)
* `app/modules/intelligence/tools/tool_service.py` (5 hunks)
* `app/modules/intelligence/tools/web_tools/github_tool.py` (2 hunks)
* `app/modules/intelligence/tools/web_tools/webpage_extractor_tool.py` (2 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (18)</summary>

* app/modules/intelligence/agents/agent_base.py
* app/modules/intelligence/agents/agents_schema.py
* app/modules/intelligence/agents/agent_injector_service.py
* app/modules/intelligence/agents/chat_agents/code_changes_chat_agent.py
* app/modules/intelligence/agents/chat_agents/debugging_chat_agent.py
* app/modules/intelligence/agents/base_agent_service.py
* app/modules/intelligence/agents/chat_agents/lld_chat_agent.py
* app/modules/intelligence/agents/chat_agents/qna_chat_agent.py
* app/modules/intelligence/agents/agents/low_level_design_agent.py
* app/modules/intelligence/agents/agents/blast_radius_agent.py
* app/modules/intelligence/agents/agents/rag_agent.py
* app/modules/intelligence/agents/agents/debug_rag_agent.py
* app/modules/intelligence/agents/chat_agents/integration_test_chat_agent.py
* app/modules/intelligence/agents/agents/code_gen_agent.py
* app/modules/intelligence/agents/agents/integration_test_agent.py
* app/modules/intelligence/agents/agents/unit_test_agent.py
* app/modules/intelligence/agents/chat_agents/code_gen_chat_agent.py
* app/modules/intelligence/agents/chat_agents/unit_test_chat_agent.py

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

<details>
<summary>app/modules/intelligence/agents/chat_agents/adaptive_agent.py</summary>

47-47: Comparison to `None` should be `cond is None`

Replace with `cond is None`

(E711)

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/pydantic_agent.py</summary>

75-75: Local variable `node_ids` is assigned to but never used

Remove assignment to unused variable `node_ids`

(F841)

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/langchain_agent.py</summary>

58-58: Local variable `node_ids` is assigned to but never used

Remove assignment to unused variable `node_ids`

(F841)

</details>
<details>
<summary>app/modules/conversations/conversation/conversation_service.py</summary>

509-509: Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling

(B904)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (61)</summary><blockquote>

<details>
<summary>app/modules/intelligence/tools/web_tools/webpage_extractor_tool.py (2)</summary>

`7-7`: **Import updated to match actual tool type usage.**

The import has been correctly updated from `Tool` to `StructuredTool` to match the tool's actual implementation, which already uses `StructuredTool.from_function()` at line 97.

---

`89-89`: **Function signature now aligns with implementation.**

The return type annotation has been properly updated from `Optional[Tool]` to `Optional[StructuredTool]`, which accurately reflects what the function returns at line 97. This improves type consistency and helps with static type checking.

</details>
<details>
<summary>app/core/models.py (1)</summary>

`9-11`: **Reintroduced import appears unchanged.**  
No functional issues are apparent for this import.

</details>
<details>
<summary>app/modules/intelligence/agents/custom_agents/custom_agents_service.py (4)</summary>

`19-21`: **New import for `RuntimeAgent`**  
The import is used properly downstream for the runtime logic. No concerns here.

---

`26-26`: **New import for `SecretManager`**  
The `SecretManager` is instantiated later to store secrets. Implementation looks appropriate.

---

`34-35`: **Direct assignment of `db` and `secret_manager`**  
The new initialization is concise and avoids `BaseAgentService` usage. Looks good.

---

`32-32`: <details>
<summary>❓ Verification inconclusive</summary>

**Removed `BaseAgentService` inheritance**  
Verify that removing the base class does not break any inherited methods or references.  
  
Run the following script to confirm no residual references to `BaseAgentService` remain:

---

🏁 Script executed:

```shell
#!/bin/bash
# Description: Search for any references to BaseAgentService
rg -A 3 "BaseAgentService"
```

Length of output: 26

---



</details>

**Attention: Verify Removal of BaseAgentService References**

The initial search using `rg -A 3 "BaseAgentService"` returned no output, indicating that no explicit references to `BaseAgentService` were detected. However, given the low-quality inference from the command output, please perform a manual verification to ensure that no inherited methods or properties from the removed base class are impacting functionality.

- Double-check that no residual usage of `BaseAgentService` exists in related modules or tests.
- Confirm that any methods originally provided by `BaseAgentService` are either re-implemented or no longer required.
- Manually review integration tests around agent functionality for potential breaking changes.

</details>
<details>
<summary>app/modules/intelligence/tools/code_query_tools/get_code_from_node_name_tool.py (2)</summary>

`6-6`: **Import change from Tool to StructuredTool**

The change from importing `Tool` to `StructuredTool` is part of the broader refactoring to standardize tool types across the codebase.

---

`145-145`: **Function return type updated to StructuredTool**

The return type has been updated from `Tool` to `StructuredTool`, providing better type specificity while maintaining the same functionality.

</details>
<details>
<summary>app/modules/intelligence/tools/code_query_tools/get_node_neighbours_from_node_id_tool.py (2)</summary>

`5-5`: **Import change from Tool to StructuredTool**

The change from importing `Tool` to `StructuredTool` is part of the standardization effort across the codebase.

---

`120-120`: **Function return type updated to StructuredTool**

The return type has been updated from `Tool` to `StructuredTool`, which improves type specificity while maintaining the existing functionality.

</details>
<details>
<summary>app/modules/intelligence/tools/code_query_tools/get_code_graph_from_node_id_tool.py (2)</summary>

`5-5`: **Import change from Tool to StructuredTool**

Updated import statement to use `StructuredTool` instead of `Tool`, consistent with other changes in this PR.

---

`211-211`: **Function return type updated to StructuredTool**

The return type has been updated from `Tool` to `StructuredTool`, maintaining consistent type specificity across the codebase.

</details>
<details>
<summary>app/modules/intelligence/tools/web_tools/github_tool.py (2)</summary>

`11-11`: **Import change from Tool to StructuredTool**

The change from importing `Tool` to `StructuredTool` aligns with the overall refactoring strategy.

---

`236-236`: **Function return type updated to Optional[StructuredTool]**

The return type has been updated from `Optional[Tool]` to `Optional[StructuredTool]`, maintaining the optional nature of the return value while improving type specificity.

</details>
<details>
<summary>app/modules/intelligence/tools/kg_based_tools/get_code_from_node_id_tool.py (2)</summary>

`5-5`: **Updated import to use StructuredTool instead of Tool**

The import has been updated to specifically use `StructuredTool` from `langchain_core.tools` instead of the generic `Tool` type, improving type specificity.

---

`138-138`: **Return type improved for better type safety**

The return type annotation has been changed from `Tool` to `StructuredTool`, which properly aligns with the actual return value from `StructuredTool.from_function()`. This improves type specificity and enables better IDE support and static analysis.

</details>
<details>
<summary>app/modules/intelligence/tools/code_query_tools/get_code_graph_from_node_name_tool.py (2)</summary>

`5-5`: **Updated import to use StructuredTool instead of Tool**

The import has been updated to specifically use `StructuredTool` from `langchain_core.tools` instead of the generic `Tool` type, improving type specificity.

---

`213-213`: **Return type improved for better type safety**

The return type annotation has been changed from `Tool` to `StructuredTool`, which properly aligns with the actual return value from `StructuredTool.from_function()`. This improves type specificity and enables better IDE support and static analysis.

</details>
<details>
<summary>app/modules/intelligence/tools/change_detection/change_detection_tool.py (2)</summary>

`6-6`: **Updated import to use StructuredTool instead of Tool**

The import has been updated to specifically use `StructuredTool` from `langchain_core.tools` instead of the generic `Tool` type, improving type specificity.

---

`307-307`: **Return type improved for better type safety**

The return type annotation has been changed from `Tool` to `StructuredTool`, which properly aligns with the actual return value from `StructuredTool.from_function()`. This improves type specificity and enables better IDE support and static analysis.

</details>
<details>
<summary>app/modules/intelligence/tools/kg_based_tools/get_code_from_probable_node_name_tool.py (2)</summary>

`5-5`: **Updated import to use StructuredTool instead of Tool**

The import has been updated to specifically use `StructuredTool` from `langchain_core.tools` instead of the generic `Tool` type, improving type specificity.

---

`208-210`: **Improved function signature formatting and type specificity**

The function signature has been reformatted to a multi-line style for better readability and the return type has been updated from `Tool` to `StructuredTool`. This improves both code formatting and type specificity.

</details>
<details>
<summary>app/modules/intelligence/tools/kg_based_tools/get_code_from_multiple_node_ids_tool.py (2)</summary>

`5-5`: **Remove unused import is correct.**
No references to `Tool` remain in this file, so removing that import is appropriate and avoids clutter.

---

`157-159`: <details>
<summary>✅ Verification successful</summary>

**Confirm consistency of the new return type.**
Switching the return type from `Tool` to `StructuredTool` aligns well with the new structured approach. Ensure that any consumers of this function are updated to accept a `StructuredTool`.

Please verify usage across the codebase by searching for calls to `get_code_from_multiple_node_ids_tool` and confirming they expect a `StructuredTool`:

---

🏁 Script executed:

```shell
#!/bin/bash
rg -A 3 "get_code_from_multiple_node_ids_tool"
```

Length of output: 4419

---



</details>

**Verification Complete: Consumers are Consistent with the New Return Type**

The search results confirm that all locations invoking `get_code_from_multiple_node_ids_tool`—including in the tool service and various agents (qna, unit_test, low_level_design, debug, and code_gen)—are using the returned object correctly (e.g., by calling `run_multiple`). There is no evidence of any consumer expecting the old `Tool` type.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/auto_router_agent.py (4)</summary>

`17-32`: **Constructor logic looks good.**
The constructor properly initializes the `llm_provider`, `agents`, and compiles agent descriptions. No apparent issues with concurrency or error handling here.

---

`85-90`: **Classification model strongly typed.**
Defining `ClassificationRespone` with pydantic ensures consistent structured input from the LLM. This clarity helps reduce parsing errors.

---

`92-124`: **Well-defined completion prompt.**
The multi-step classification guidelines are clearly outlined, aiding the LLM in agent selection. Overall, this is good documentation for future maintainers.

---

`34-82`: <details>
<summary>❓ Verification inconclusive</summary>

**Robust classification flow with fallback mechanism.**
1. The `_run_classification` method elegantly handles classification errors by falling back to the current agent.  
2. Confidence threshold is set at 0.5, which is reasonable; consider clamping or validating extreme confidence values in certain edge cases.  
3. Logging appropriately captures classification outcomes.  

Ensure that the code has suitable test coverage:

---

🏁 Script executed:

```shell
#!/bin/bash
rg -A 5 "test_auto_router_agent" 
```

Length of output: 32

---



</details>

**Attention: Verification of Test Coverage Required**

The classification flow and fallback mechanism in `_run_classification` are well implemented with appropriate logging and error handling. However, the automated search for tests associated with `auto_router_agent` (using the pattern "test_auto_router_agent") returned no results. This raises concerns about the existence or discoverability of corresponding tests.

- The error handling and fallback to the current agent are robust.
- The confidence threshold of 0.5 appears adequate; still consider validating or clamping extreme confidence values in specific edge cases.
- Logging for both successful classifications and errors is implemented appropriately.
- **Action Item:** Please manually verify or add test coverage for the auto router agent functionality to ensure that all classification paths, especially edge cases, are properly exercised.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/supervisor_agent.py (2)</summary>

`1-8`: **Imports and references are straightforward.**
All imported symbols appear to be used. This neatly sets up access to `AutoRouterAgent` and relevant classes.

---

`9-25`: **Streamlined supervisor logic.**
1. The `SupervisorAgent` delegates requests to `AutoRouterAgent`, neatly separating responsibilities.
2. `run` and `run_stream` methods cleanly forward context and generate responses.
No concerns found.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agent.py (2)</summary>

`6-15`: **Looks good overall.**

The `ChatAgentResponse` model appears concise and well-defined. The usage of `pydantic.Field` for descriptions is a thoughtful touch, ensuring clarity in generated schemas and tooltips.

---

`17-24`: **Check the handling of optional fields in `ChatContext`.**

The optional `node_ids` field and empty string `additional_context` field seem fine for typical usage, but verify that the downstream code properly accounts for missing or empty values in these fields to prevent unintended behavior (e.g., key errors, missed validations).

</details>
<details>
<summary>app/modules/intelligence/tools/tool_service.py (3)</summary>

`1-1`: **LGTM.**

Importing `Dict` and `List` is appropriate for the new method signatures.

---

`10-30`: **Consistent naming and usage of specialized tool classes.**

Good job importing `GetCodeFileStructureTool`, `GetCodeGraphFromNodeIdTool`, and `GetCodeFromMultipleNodeIdsTool`. Ensuring a standardized naming approach across all tool classes helps maintain clarity.

---

`46-46`: **Validate `StructuredTool` usage.**

The introduction of `StructuredTool` is a great approach, but confirm that all user-defined arguments and return types in custom tools comply with the `StructuredTool` contracts (arguments schema, etc.) to avoid runtime issues.

</details>
<details>
<summary>app/modules/intelligence/agents/agents_controller.py (2)</summary>

`6-12`: **Imports and refactoring appear consistent.**

The updated imports for `AgentInfo`, `ProviderService`, `PromptService`, and `ToolService` neatly organize dependencies, reflecting a more modular approach.

---

`16-23`: **Constructor injection is a good step toward better testability.**

Injecting `llm_provider`, `prompt_provider`, and `tools_provider` promotes more flexible and maintainable design, allowing easier mocking or swapping of dependencies in tests.

</details>
<details>
<summary>app/modules/intelligence/agents/agents_router.py (1)</summary>

`42-46`: **Validate service initialization flow**  

Multiple providers (`ProviderService`, `ToolService`, `PromptService`) depend on a valid `user_id`. Ensure that error handling is in place for unexpected `user_id` values or database session issues.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/debug_agent.py (1)</summary>

`1-52`: **Consider test coverage for complex queries.**
This agent leverages multiple tools. To prevent regressions, add tests that ensure each tool’s usage is triggered appropriately and that code references in `node_ids` remain reliable.  

If you would like to confirm coverage for these interactions, you can run tests or request a script to parse your test files and check for coverage references to DebugAgent.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/unit_test_agent.py (1)</summary>

`1-9`: **Imports look good.**
No obvious issues. The chosen imports are appropriate, and the code is neatly organized.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/low_level_design_agent.py (3)</summary>

`1-9`: **Imports are suitable.**
They align with the usage within this new agent. No concerns.

---

`20-50`: **Smooth agent build configuration.**
The `_build_agent` method clearly defines its tasks and tools. Ensure that references to `lld_task_prompt` are loaded correctly at runtime since it is defined later in the file.

---

`80-93`: **Prompt usage is clear.**
The `lld_task_prompt` is well structured and thorough. Similar to other prompts, storing it externally could help version control and updates.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/integration_test_agent.py (3)</summary>

`1-11`: **Import statements and basic definitions.**
All imports appear relevant. The early model definition setup is clear and logically placed.

---

`124-176`: **Integration test prompt clarity.**
The workflow is comprehensive. For complex systems, consider sub-prompts or referencing external docstrings to improve clarity.

---

`27-49`: <details>
<summary>✅ Verification successful</summary>

**Ensure that the retrieved tools exist.**
The `tools_provider.get_tools(...)` call depends on the same tools as in other agents. If any are missing, an exception might occur.

---

🏁 Script executed:

```shell
#!/bin/bash
# Verify presence of all listed tools in the codebase
rg -A 3 'def get_tools' 
```

Length of output: 375

---



</details>

**Verified: Tools Retrieval Configuration Looks Correct**

I confirmed that the `get_tools` method is implemented (see `app/modules/intelligence/tools/tool_service.py`), which means that the call in the integration test agent should safely retrieve tools as expected. However, please double-check that each tool in the list  
- `"get_code_from_multiple_node_ids"`  
- `"get_code_from_probable_node_name"`  
- `"webpage_extractor"`  
- `"github_tool"`

is registered globally in the same manner as used in other agents to prevent any runtime exceptions.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/qna_agent.py (2)</summary>

`56-72`: **Handle potential exceptions when fetching code and file structure.**

Calls to `run_multiple` and `fetch_repo_structure` can fail if the project ID is invalid or the service is temporarily unavailable. Wrap these calls in appropriate try/except blocks or handle errors at a higher level to avoid unexpected runtime failures.

---

`74-83`: **Confirm runtime error handling in run methods.**

Both `run` and `run_stream` rely on asynchronous calls but do not provide dedicated error handling. Confirm that exceptions thrown from `_enriched_context` or `_build_agent` are properly caught and managed upstream.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/code_gen_agent.py (1)</summary>

`73-82`: **Add robust error handling in run methods.**

Asynchronous operations can fail unexpectedly. Confirm upstream or within these methods that errors from `_enriched_context` or `_build_agent` are consistently managed.

</details>
<details>
<summary>app/modules/conversations/conversation/conversation_service.py (7)</summary>

`5-5`: **Imported AsyncGenerator and List successfully.**  
No issues found; these imports are correct for asynchronous streaming and list usage.

---

`36-37`: **New imports for AgentsService and ChatContext.**  
These additions appear necessary for agent validation and contextual chat handling.

---

`45-48`: **Adaptive agent and tool service imports introduced.**  
Successfully brings in new dependencies for prompt and tool-based AI services.

---

`103-107`: **Initialization of newly injected services.**  
The creation of `ToolService`, `PromptService`, and `AgentsService` aligns with the new injection pattern.

---

`116-118`: **Properly wiring services in the factory method.**  
Ensures the conversation service includes the necessary references to tool, prompt, and agent services.

---

`163-168`: **Agent ID validation for conversation creation.**  
This check helps ensure only valid agents are used. Looks good.

---

`520-551`: **Branching logic between custom and standard agents.**  
Clean separation ensures streaming is bypassed for custom agents. Code structure is coherent.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/crewai_agent.py (4)</summary>

`1-2`: **New imports are in line with the file’s needs.**  
The use of `json`, `Any`, `List`, and `AsyncGenerator` is appropriate.

---

`4-7`: **ProviderService and AgentProvider imports.**  
Essential for retrieving the LLM from the provider. No issues observed.

---

`16-21`: **TaskConfig Pydantic model.**  
Clear structure for task description and expected output.

---

`33-39`: **Initialization of CrewAIAgent.**  
Sets up tasks, max iterations, and tool references. No immediate concerns.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T06:19:23Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (3)</summary><blockquote>

<details>
<summary>app/modules/conversations/conversation/conversation_service.py (1)</summary><blockquote>

`502-510`: _⚠️ Potential issue_

**Fix exception chaining for clarity.**

Raising a new exception without chaining can mask the original cause.



```diff
-        except Exception:
-            raise ConversationServiceError("Failed to get chat history")
+        except Exception as e:
+            raise ConversationServiceError("Failed to get chat history") from e
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

509-509: Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling

(B904)

</details>

</details>

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/debug_agent.py (1)</summary><blockquote>

`63-65`: _🛠️ Refactor suggestion_

**Consider incorporating `_enriched_context` here.**

The `run` method (lines 63–65) calls `self.rag_agent.run(ctx)` directly. If `_enriched_context` is intended to enrich the user’s query context, consider invoking `_enriched_context(ctx)` first.

```diff
 async def run(self, ctx: ChatContext) -> ChatAgentResponse:
-    return await self.rag_agent.run(ctx)
+    enriched = await self._enriched_context(ctx)
+    return await self.rag_agent.run(enriched)
```

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/integration_test_agent.py (1)</summary><blockquote>

`51-112`: _🛠️ Refactor suggestion_

**Handle potential missing or invalid graph structure.**

In `_enriched_context`, the code assumes `graph["graph"]["root_node"]` exists, and that each node has `"children"`. If the tool’s output is malformed or missing fields, this might raise exceptions. Consider adding defensive checks or try/except blocks to avoid runtime errors.

```diff
+            if "graph" not in graph or "root_node" not in graph["graph"]:
+                logger.warning(f"Graph structure missing root_node for {node_id}")
+                continue
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (16)</summary><blockquote>

<details>
<summary>app/modules/intelligence/tools/tool_service.py (3)</summary><blockquote>

`55-59`: **Consider potential duplication with _initialize_tools method**

The tool instances created here (like `get_code_from_multiple_node_ids_tool`) are also created via function calls in `_initialize_tools` (line 76-78). This could lead to inconsistencies if implementations diverge.


Consider refactoring to reuse these instances:

```diff
def _initialize_tools(self) -> Dict[str, StructuredTool]:
    tools = {
        "get_code_from_probable_node_name": get_code_from_probable_node_name_tool(
            self.db, self.user_id
        ),
        "get_code_from_node_id": get_code_from_node_id_tool(self.db, self.user_id),
-       "get_code_from_multiple_node_ids": get_code_from_multiple_node_ids_tool(
-           self.db, self.user_id
-       ),
+       "get_code_from_multiple_node_ids": self.get_code_from_multiple_node_ids_tool,
        # Similar changes for other duplicated tool initializations
        "get_code_graph_from_node_id": get_code_graph_from_node_id_tool(self.db),
        "get_code_file_structure": get_code_file_structure_tool(self.db),
    }
```

---

`62-69`: **Consider adding error handling for missing tools**

The `get_tools` method silently ignores tool names that don't exist in the tools dictionary. Consider adding logging or error handling to provide feedback about missing tools.


```diff
def get_tools(self, tool_names: List[str]) -> List[StructuredTool]:
    """get tools if exists"""
    tools = []
+   missing_tools = []
    for tool_name in tool_names:
        if self.tools.get(tool_name) is not None:
            tools.append(self.tools[tool_name])
+       else:
+           missing_tools.append(tool_name)
+   if missing_tools:
+       logging.warning(f"Requested tools not found: {missing_tools}")
    return tools
```

---

`62-69`: **Consider standardizing tool initialization patterns**

The codebase currently uses two different patterns for tool initialization:
1. Direct class instantiation (e.g., `GetCodeFromMultipleNodeIdsTool(...)`)
2. Factory functions (e.g., `get_code_from_node_id_tool(...)`)

This inconsistency might make the code harder to maintain. Consider standardizing on one approach.



Also applies to: 70-103

</blockquote></details>
<details>
<summary>app/modules/conversations/conversation/conversation_service.py (3)</summary><blockquote>

`82-84`: **Good dependency injection for the new services.**

The addition of `tools_service`, `promt_service`, and `agent_service` as dependencies properly integrates the new framework into the conversation service.


However, there's a typo in parameter name `promt_service` (missing 'p' in "prompt").


```diff
-        promt_service: PromptService,
+        prompt_service: PromptService,
```


Also applies to: 93-96

---

`365-367`: **Type annotation improvement for generated_title.**

Adding type annotation for `generated_title` improves code clarity and type safety, though the `# type: ignore` suggests there might be a type mismatch that should be addressed more permanently.



Consider updating the `provider_service.call_llm` method's return type to avoid needing the `# type: ignore` comment.

---

`529-537`: **LGTM: Improved streaming implementation.**

The new implementation using `ChatContext` is well-structured and provides a cleaner interface for agent execution.


However, the history is limited to only the last 5 messages, which might lose context in longer conversations.


Consider making the history limit configurable or using a more context-aware approach to determine which messages to include.

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/custom_agents/custom_agents_service.py (1)</summary><blockquote>

`37-38`: **Consider dependency injection for SecretManager.**

While the direct initialization of `db` is good, the `secret_manager` is tightly coupled by being instantiated directly within the constructor. This could impact testability.


Consider using dependency injection for SecretManager similar to how the database session is handled:

```diff
-    def __init__(self, db: Session):
-        self.db = db
-        self.secret_manager = SecretManager()
+    def __init__(self, db: Session, secret_manager: Optional[SecretManager] = None):
+        self.db = db
+        self.secret_manager = secret_manager or SecretManager()
```

This would make unit testing easier by allowing a mock SecretManager to be injected.

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/pydantic_agent.py (3)</summary><blockquote>

`31-47`: **Clarify potential naming collisions when removing spaces from tool names.**

In the constructor, each tool’s name is sanitized with `re.sub(r" ", "", tool.name)` (lines 44–45). If two tools differ only by spaces, this may result in collisions. Consider adding additional disambiguation logic or verifying uniqueness after removing spaces.

---

`72-76`: **Remove unused local variable.**

The local variable `node_ids` is assigned at line 75 but never used. This might cause confusion.

Apply this diff to remove the unused assignment:

```diff
-        if isinstance(ctx.node_ids, str):
-            node_ids = [ctx.node_ids]
+        if isinstance(ctx.node_ids, str):
+            ctx.node_ids = [ctx.node_ids]
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

75-75: Local variable `node_ids` is assigned to but never used

Remove assignment to unused variable `node_ids`

(F841)

</details>

</details>

---

`109-132`: **Adhere to best practices when handling exceptions.**

The `run` method (lines 109–132) uses a broad `except Exception as e:` clause, then re-raises a generic `Exception`. While it’s helpful for logging, consider wrapping known failure points in more specific exception handling when practical.

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/debug_agent.py (2)</summary><blockquote>

`11-52`: **Possible forward reference for variable `code_gen_task_prompt`.**

The constructor references `code_gen_task_prompt` (lines 34–35) before it’s declared at line 73. In some Python environments, this may lead to a `NameError`. Consider defining `code_gen_task_prompt` earlier or wrapping the agent initialization in a function that ensures the variable is available beforehand.

---

`66-71`: **Confirm streaming usage of debug info.**

The `run_stream` method streams output from `rag_agent`, but currently doesn’t provide additional debugging logs or context. If debugging details are essential, consider capturing or injecting them into each streamed chunk’s response.

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/code_gen_agent.py (4)</summary><blockquote>

`63-71`: **Consider adding error handling for tool execution.**

The `_enriched_context` method doesn't handle potential exceptions from the tool execution, which could cause failures during runtime.


```diff
async def _enriched_context(self, ctx: ChatContext) -> ChatContext:
    if ctx.node_ids and len(ctx.node_ids) > 0:
+       try:
            code_results = await self.tools_provider.get_code_from_multiple_node_ids_tool.run_multiple(
                ctx.project_id, ctx.node_ids
            )
            ctx.additional_context += (
                f"Code referred to in the query:\n {code_results}\n"
            )
+       except Exception as e:
+           # Log the error but continue without enrichment
+           ctx.additional_context += f"Note: Could not retrieve code from node IDs due to an error: {str(e)}\n"
    return ctx
```

---

`73-75`: **Consider caching the agent instance.**

Building the agent for each run could be inefficient. Consider caching the agent instance for better performance.


Instead of building the agent for each run, you could build it once in the constructor and reuse it:

```diff
def __init__(
    self,
    llm_provider: ProviderService,
    tools_provider: ToolService,
):
    self.llm_provider = llm_provider
    self.tools_provider = tools_provider
+   self._agent = None

+ def _get_agent(self) -> ChatAgent:
+     if self._agent is None:
+         self._agent = self._build_agent()
+     return self._agent

async def run(self, ctx: ChatContext) -> ChatAgentResponse:
-   return await self._build_agent().run(await self._enriched_context(ctx))
+   return await self._get_agent().run(await self._enriched_context(ctx))
```

---

`84-230`: **Consider moving the prompt to a dedicated configuration file.**

The `code_gen_task_prompt` is very detailed and occupies a significant portion of the file. For better maintainability, consider moving it to a dedicated configuration or prompts file.


Create a new file `app/modules/intelligence/agents/chat_agents/system_agents/prompts.py`:

```python
CODE_GEN_TASK_PROMPT = """
    Work to generate copy-paste ready code:
    # ... [rest of the prompt]
"""
```

Then import it in this file:

```diff
from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
from app.modules.intelligence.provider.provider_service import (
    ProviderService,
)
from app.modules.intelligence.tools.tool_service import ToolService
from ..crewai_agent import AgentConfig, TaskConfig
from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
from typing import AsyncGenerator
+ from app.modules.intelligence.agents.chat_agents.system_agents.prompts import CODE_GEN_TASK_PROMPT

# ...

                tasks=[
                    TaskConfig(
-                       description=code_gen_task_prompt,
+                       description=CODE_GEN_TASK_PROMPT,
                        expected_output="User-friendly, clearly structured code changes with comprehensive dependency analysis, implementation details for ALL impacted files, and complete verification steps",
                    )
                ],
            ),
# ...

- code_gen_task_prompt = """
-     Work to generate copy-paste ready code:
-     # ... [rest of the prompt]
- """
```

---

`49-60`: **Consider using constants for tool names.**

The tool names are hardcoded strings, which could lead to typo-related bugs. Consider using constants or enums for better maintainability.


Create a new file for tool constants:

```python
# app/modules/intelligence/tools/tool_constants.py

class ToolNames:
    GET_CODE_FROM_MULTIPLE_NODE_IDS = "get_code_from_multiple_node_ids"
    GET_NODE_NEIGHBOURS_FROM_NODE_ID = "get_node_neighbours_from_node_id"
    GET_CODE_FROM_PROBABLE_NODE_NAME = "get_code_from_probable_node_name"
    ASK_KNOWLEDGE_GRAPH_QUERIES = "ask_knowledge_graph_queries"
    GET_NODES_FROM_TAGS = "get_nodes_from_tags"
    GET_CODE_FILE_STRUCTURE = "get_code_file_structure"
    WEBPAGE_EXTRACTOR = "webpage_extractor"
    GITHUB_TOOL = "github_tool"
```

Then update the code:

```diff
from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
from app.modules.intelligence.provider.provider_service import (
    ProviderService,
)
from app.modules.intelligence.tools.tool_service import ToolService
+ from app.modules.intelligence.tools.tool_constants import ToolNames
from ..crewai_agent import AgentConfig, TaskConfig
from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
from typing import AsyncGenerator

# ...

            tools=self.tools_provider.get_tools(
                [
-                   "get_code_from_multiple_node_ids",
-                   "get_node_neighbours_from_node_id",
-                   "get_code_from_probable_node_name",
-                   "ask_knowledge_graph_queries",
-                   "get_nodes_from_tags",
-                   "get_code_file_structure",
-                   "webpage_extractor",
-                   "github_tool",
+                   ToolNames.GET_CODE_FROM_MULTIPLE_NODE_IDS,
+                   ToolNames.GET_NODE_NEIGHBOURS_FROM_NODE_ID,
+                   ToolNames.GET_CODE_FROM_PROBABLE_NODE_NAME,
+                   ToolNames.ASK_KNOWLEDGE_GRAPH_QUERIES,
+                   ToolNames.GET_NODES_FROM_TAGS,
+                   ToolNames.GET_CODE_FILE_STRUCTURE,
+                   ToolNames.WEBPAGE_EXTRACTOR,
+                   ToolNames.GITHUB_TOOL,
                ]
            ),
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 8d941526c3ecd91bcf8a2942695668c6a0bacb86 and b503dcaf0198bc057a5d4afc3136bdf27433da10.

</details>

<details>
<summary>📒 Files selected for processing (23)</summary>

* `app/modules/code_provider/github/github_service.py` (1 hunks)
* `app/modules/conversations/conversation/conversation_service.py` (9 hunks)
* `app/modules/intelligence/agents/agents_service.py` (2 hunks)
* `app/modules/intelligence/agents/chat_agents/pydantic_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/blast_radius_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/code_gen_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/debug_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/integration_test_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/low_level_design_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/qna_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/unit_test_agent.py` (1 hunks)
* `app/modules/intelligence/agents/custom_agents/custom_agents_service.py` (1 hunks)
* `app/modules/intelligence/tools/change_detection/change_detection_tool.py` (2 hunks)
* `app/modules/intelligence/tools/code_query_tools/get_code_from_node_name_tool.py` (2 hunks)
* `app/modules/intelligence/tools/code_query_tools/get_code_graph_from_node_id_tool.py` (2 hunks)
* `app/modules/intelligence/tools/code_query_tools/get_code_graph_from_node_name_tool.py` (2 hunks)
* `app/modules/intelligence/tools/code_query_tools/get_node_neighbours_from_node_id_tool.py` (2 hunks)
* `app/modules/intelligence/tools/kg_based_tools/get_code_from_multiple_node_ids_tool.py` (2 hunks)
* `app/modules/intelligence/tools/kg_based_tools/get_code_from_node_id_tool.py` (2 hunks)
* `app/modules/intelligence/tools/kg_based_tools/get_code_from_probable_node_name_tool.py` (2 hunks)
* `app/modules/intelligence/tools/tool_service.py` (5 hunks)
* `app/modules/intelligence/tools/web_tools/github_tool.py` (2 hunks)
* `app/modules/intelligence/tools/web_tools/webpage_extractor_tool.py` (2 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* app/modules/code_provider/github/github_service.py

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (14)</summary>

* app/modules/intelligence/tools/code_query_tools/get_code_graph_from_node_name_tool.py
* app/modules/intelligence/tools/code_query_tools/get_code_graph_from_node_id_tool.py
* app/modules/intelligence/tools/code_query_tools/get_node_neighbours_from_node_id_tool.py
* app/modules/intelligence/tools/code_query_tools/get_code_from_node_name_tool.py
* app/modules/intelligence/tools/web_tools/github_tool.py
* app/modules/intelligence/tools/kg_based_tools/get_code_from_node_id_tool.py
* app/modules/intelligence/tools/change_detection/change_detection_tool.py
* app/modules/intelligence/tools/kg_based_tools/get_code_from_multiple_node_ids_tool.py
* app/modules/intelligence/tools/web_tools/webpage_extractor_tool.py
* app/modules/intelligence/tools/kg_based_tools/get_code_from_probable_node_name_tool.py
* app/modules/intelligence/agents/chat_agents/system_agents/unit_test_agent.py
* app/modules/intelligence/agents/chat_agents/system_agents/qna_agent.py
* app/modules/intelligence/agents/chat_agents/system_agents/low_level_design_agent.py
* app/modules/intelligence/agents/chat_agents/system_agents/blast_radius_agent.py

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

<details>
<summary>app/modules/conversations/conversation/conversation_service.py</summary>

509-509: Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling

(B904)

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/pydantic_agent.py</summary>

75-75: Local variable `node_ids` is assigned to but never used

Remove assignment to unused variable `node_ids`

(F841)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (33)</summary><blockquote>

<details>
<summary>app/modules/intelligence/tools/tool_service.py (3)</summary>

`1-1`: **Improved type specificity with StructuredTool**

The import changes from generic `Any` to specific `StructuredTool` improves type safety and makes the code more self-documenting.



Also applies to: 46-46

---

`10-10`: **Good addition of specific tool class imports**

These imports support the new tool instances that are directly initialized in the constructor.



Also applies to: 17-17, 30-30

---

`70-70`: **Improved type specificity in method signature**

Changing from `Any` to `StructuredTool` enhances type safety and makes the code more self-documenting.

</details>
<details>
<summary>app/modules/conversations/conversation/conversation_service.py (8)</summary>

`5-5`: **Appropriate import change.**

Updated imports to include AsyncGenerator and List types, which are used in the type hints throughout the code.

---

`36-38`: **LGTM: Import changes align with the new agent framework.**

The addition of imports for `AgentsService`, `ChatContext`, `PromptService`, and `ToolService` properly supports the new structured tools and chat agent framework that replaces the legacy agent system.



Also applies to: 45-48

---

`103-107`: **LGTM: Proper service initialization.**

The creation and initialization of the new services (`tool_service`, `prompt_service`, and `agent_service`) is correctly implemented in the `create` class method.

---

`116-119`: **LGTM: Updated dependency passing.**

The newly created services are correctly passed to the class constructor.

---

`163-164`: **LGTM: Improved agent validation.**

Agent validation now uses the `agent_service.validate_agent_id` method, which aligns with the broader architectural changes.

---

`512-515`: **LGTM: Improved agent validation.**

The refactored code now validates the agent ID and checks if the agent type is None, providing better error handling.

---

`520-526`: **LGTM: Clean custom agent handling.**

The updated code for handling custom agents now uses the agent_service to execute the agent runtime, which is consistent with the architectural changes.

---

`539-551`: **LGTM: Effective streaming implementation.**

The streaming implementation correctly handles chunks, adds them to the message history, and yields them as responses. The final flush ensures all content is properly stored.

</details>
<details>
<summary>app/modules/intelligence/agents/custom_agents/custom_agents_service.py (3)</summary>

`22-24`: **Import formatting improved.**

The import for RuntimeAgent has been reformatted to use a cleaner multi-line format, improving readability.

---

`29-29`: **New dependency added: SecretManager.**

The addition of SecretManager import supports the new standalone nature of this class.

---

`35-35`: **Class decoupled from BaseAgentService.**

The CustomAgentService class has been modified to operate as a standalone class rather than inheriting from BaseAgentService. This change is consistent with the overall restructuring of the agent framework.

</details>
<details>
<summary>app/modules/intelligence/agents/agents_service.py (6)</summary>

`38-44`: **Good addition of structured AgentInfo model.**

Using Pydantic's BaseModel for AgentInfo provides type validation and better structure for agent metadata.

---

`47-62`: **Improved dependency injection in constructor.**

The constructor now properly accepts external dependencies (llm_provider, prompt_provider, tools_provider), which is a significant improvement for testability and flexibility.


However, note that the PromptService is still being instantiated directly within the constructor on line 56. Consider receiving this as a parameter as well for complete dependency injection.

---

`63-157`: **Well-structured system agents creation.**

Extracting system agent creation into a dedicated method with proper dependency injection improves code organization and maintainability.


However, there are a few concerns:

1. There's a TODO comment on line 128 for the LLD agent using an incorrect agent type (QNA instead of a specific LLD type).

Please check if this TODO needs to be addressed before merging this PR, as it might affect the behavior of the LowLevelDesignAgent.

---

`158-164`: **New streaming support for agent execution.**

The addition of both regular and streaming execution methods is a good improvement that enables more flexible client interactions.

---

`165-201`: **Refactored list_available_agents method.**

The method now properly uses the new system agents structure to create a consistent list of both system and custom agents.

---

`203-209`: **Added agent validation method.**

The `validate_agent_id` method provides a clear way to check agent validity across both system and custom agents, which is helpful for API endpoints that need to work with both types.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/pydantic_agent.py (2)</summary>

`66-108`: **Validate multi-line string formatting.**

The `_create_task_description` method builds a multi-line string that incorporates newlines and indentation. Although this appears correct, consider ensuring these whitespace choices are intentional and that no extra leading spaces break downstream formatting.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

75-75: Local variable `node_ids` is assigned to but never used

Remove assignment to unused variable `node_ids`

(F841)

</details>

</details>

---

`133-178`: **Confirm streaming event handling covers all edge cases.**

The `run_stream` method provides comprehensive handling of token deltas, tool call events, and final node closure. Verify that all potential event types (e.g., error events, user cancellation events) are handled to avoid unhandled exceptions during streaming.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/debug_agent.py (2)</summary>

`53-62`: **`_enriched_context` remains unused in this workflow.**


A similar comment was made previously about calling `_enriched_context` in either `run` or `run_stream`. Currently, `_enriched_context` is defined but never invoked, so any code appended to `ctx.additional_context` is never used in responses.

---

`73-155`: **High-level test generation instructions validated.**

The `code_gen_task_prompt` (lines 73–155) offers a methodical strategy for analyzing project structures and retrieving relevant code. This is comprehensive and well-structured, so no immediate changes are recommended.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/integration_test_agent.py (3)</summary>

`1-50`: **Initialize agent with comprehensive roles and tools.**

The constructor correctly sets up the `IntegrationTestAgent` with a `PydanticRagAgent` and specialized tasks for integration test generation. This approach seems appropriate given the PR's purpose of leveraging Pydantic-based AI tooling.

---

`113-122`: **Enrich context before streaming.**

Both `run` and `run_stream` call `_enriched_context` to ensure the context includes code structure. This design is consistent and ensures all data is present. Verify that the overhead of repeated calls is acceptable if `run_stream` might scale.

---

`124-176`: **In-depth integration test prompt.**

The `integration_test_task_prompt` is thoroughly detailed. It covers typical integration testing aspects, including coverage, environment setup, and iteration. Well done.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/code_gen_agent.py (6)</summary>

`1-9`: **Clean import structure.**

The imports are well-organized, with a clear separation between external and internal dependencies. This makes the code more maintainable and highlights the module's dependencies.

---

`11-19`: **Well-structured agent class with clean dependency injection.**

The `CodeGenAgent` class extends the `ChatAgent` base class and uses dependency injection for its required services, making it testable and maintainable.

---

`20-62`: **LGTM - Comprehensive agent configuration.**

The agent configuration is well-structured with clear role definition, specific goals, and a detailed backstory. The agent is equipped with numerous tools to support code generation tasks, which aligns with the PR objective of implementing Pydantic AI agents.

---

`76-81`: **Streaming implementation is aligned with PR objectives.**

The `run_stream` method enables streaming responses, which aligns perfectly with the PR objective of supporting message streaming. This is a key improvement highlighted in the PR summary.

---

`76-81`: **The streaming implementation supports the PR objective.**

The implementation of `run_stream` directly supports the PR objective of "Support for message streaming" and "stream metadata" as mentioned in the PR summary. This is an excellent addition that allows for more dynamic interactions.

---

`63-71`: <details>
<summary>✅ Verification successful</summary>

**Verify that the `get_code_from_multiple_node_ids_tool` attribute exists.**

The code assumes that `tools_provider` has a `get_code_from_multiple_node_ids_tool` attribute, but there's no validation to ensure it exists.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if the ToolService class has a get_code_from_multiple_node_ids_tool attribute or method

# First, find the ToolService class definition
echo "Searching for ToolService class definition..."
rg -A 10 "class ToolService" --type py
echo "-------------------"

# Next, check if there's a getter method or property for the tool
echo "Checking for get_code_from_multiple_node_ids_tool attribute or method..."
rg "get_code_from_multiple_node_ids_tool" --type py
echo "-------------------"

# Look for the implementation of get_tools to see if it returns the required tool
echo "Checking get_tools implementation..."
rg "def get_tools" --type py -A 10
```

Length of output: 4097

---



</details>

**Tool Attribute Verification Confirmed**

After verifying the codebase, we can confirm that the `tools_provider` indeed has the `get_code_from_multiple_node_ids_tool` attribute. It is correctly instantiated within the `ToolService` class in `app/modules/intelligence/tools/tool_service.py` and consistently referenced across multiple agents. No additional validation is required in the `code_gen_agent.py` file.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T04:15:47Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (9)</summary><blockquote>

<details>
<summary>app/modules/intelligence/agents/chat_agent.py (2)</summary><blockquote>

`44-51`: **`ChatContext` modeling is solid.**

Having `additional_context` and optional `node_ids` is flexible. Optionally, you could default `node_ids` to an empty list if that helps avoid `None` checks downstream, but this is a minor choice of style.

---

`53-65`: **Abstract methods are appropriately defined.**

`ChatAgent` sets a clear contract for synchronous vs. streaming responses. Consider adding docstrings if you anticipate complex logic in subclasses.

</blockquote></details>
<details>
<summary>app/modules/conversations/conversation/conversation_schema.py (1)</summary><blockquote>

`57-57`: **Use a more specific type for `tool_calls`.**

Currently, `tool_calls` is declared as `List[Any]`. For stronger type-checking and maintainability, consider defining a more descriptive type or model if the structure of these calls is known.

</blockquote></details>
<details>
<summary>app/core/config_provider.py (1)</summary><blockquote>

`16-16`: **Consider renaming the environment variable for clarity.**

Static analysis suggests using uppercase environment variable naming (e.g., `ISDEVELOPMENTMODE`). Adopting a conventional naming scheme helps readability and consistency across codebases.

```diff
- self.is_development_mode = os.getenv("isDevelopmentMode", "disabled")
+ self.is_development_mode = os.getenv("ISDEVELOPMENTMODE", "disabled")
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

16-16: Use capitalized environment variable `ISDEVELOPMENTMODE` instead of `isDevelopmentMode`

(SIM112)

</details>

</details>

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/pydantic_agent.py (1)</summary><blockquote>

`158-200`: **Optimize unused variable `node_ids`.**

Inside `_create_task_description`, `node_ids` is assigned but never used. This is flagged by static analysis. You can safely remove or use it if needed.  

```diff
-        if isinstance(ctx.node_ids, str):
-            node_ids = [ctx.node_ids]
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

167-167: Local variable `node_ids` is assigned to but never used

Remove assignment to unused variable `node_ids`

(F841)

</details>

</details>

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/adaptive_agent.py (1)</summary><blockquote>

`39-72`: **Use identity comparison for None.**

At line 47, replace `== None` with `is None` to adhere to PEP 8 best practices.  

```diff
-        if system_prompt == None:
+        if system_prompt is None:
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

47-47: Comparison to `None` should be `cond is None`

Replace with `cond is None`

(E711)

</details>

</details>

</blockquote></details>
<details>
<summary>app/modules/conversations/conversation/conversation_service.py (2)</summary><blockquote>

`82-84`: **Potential naming consistency.**

Renaming `promt_service` to `prompt_service` may improve clarity. Currently it's spelled with a missing “p”.  

```diff
-        promt_service: PromptService,
+        prompt_service: PromptService,
```

---

`525-535`: **Fallback path for custom agents.**

For `CUSTOM_AGENT`, returning a single chunk response fits the existing architecture. Consider streaming if future updates require large outputs.

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/crewai_agent.py (1)</summary><blockquote>

`178-180`: **Use a more descriptive way to re-raise exceptions.**

Re-raising with `raise Exception from e` obscures the original exception type. Instead, you can re-raise with `raise e` to preserve the original traceback. Also, consider guarding against empty task lists, which could cause an index error at `result.tasks_output[0]`.  

Possible fix:
```diff
- except Exception as e:
-     logger.error(f"Error in run method: {str(e)}", exc_info=True)
-     raise Exception from e
+ except Exception as e:
+     logger.error(f"Error in run method: {e}", exc_info=True)
+     raise e
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b503dcaf0198bc057a5d4afc3136bdf27433da10 and 0c3b08e9ed0a5f94cbf5c568d1d88f052db0410d.

</details>

<details>
<summary>📒 Files selected for processing (8)</summary>

* `app/core/config_provider.py` (2 hunks)
* `app/modules/code_provider/github/github_service.py` (1 hunks)
* `app/modules/conversations/conversation/conversation_schema.py` (2 hunks)
* `app/modules/conversations/conversation/conversation_service.py` (11 hunks)
* `app/modules/intelligence/agents/chat_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/adaptive_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/crewai_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/pydantic_agent.py` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* app/modules/code_provider/github/github_service.py

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

<details>
<summary>app/core/config_provider.py</summary>

16-16: Use capitalized environment variable `ISDEVELOPMENTMODE` instead of `isDevelopmentMode`

(SIM112)

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/adaptive_agent.py</summary>

47-47: Comparison to `None` should be `cond is None`

Replace with `cond is None`

(E711)

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/pydantic_agent.py</summary>

167-167: Local variable `node_ids` is assigned to but never used

Remove assignment to unused variable `node_ids`

(F841)

</details>
<details>
<summary>app/modules/conversations/conversation/conversation_service.py</summary>

514-514: Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling

(B904)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (27)</summary><blockquote>

<details>
<summary>app/modules/intelligence/agents/chat_agent.py (5)</summary>

`1-5`: **Imports look sound.**

No issues found with these imports. They lay the groundwork for enums, ABCs, typing, and Pydantic nicely.

---

`7-10`: **Enum usage is clear.**

The `ToolCallEventType` Enum is straightforward and well-defined for tool call lifecycle events.

---

`12-30`: **Structured fields in `ToolCallResponse` are well-defined.**

All fields have meaningful descriptions, and using `Dict[str, Any]` for tool call details is flexible. This is good for capturing arbitrary metadata.

---

`32-42`: **`ChatAgentResponse` looks good.**

Your response structure adequately captures the agent’s textual output, tool calls, and citations. The use of a list of `ToolCallResponse` provides strong type safety for tool interactions.

---

`67-73`: **`AgentWithInfo` is appropriately minimal.**

Storing the agent and its metadata in one place should streamline agent lookup. No issues found.

</details>
<details>
<summary>app/core/config_provider.py (1)</summary>

`113-113`: **Boolean return logic is clear.**

Returning `self.is_development_mode == "enabled"` is concise and unambiguous. This simplifies downstream checks.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/pydantic_agent.py (5)</summary>

`76-98`: **No concerns; match statements look fine.**

These match statements for tool-run messages are straightforward and correctly use Python 3.10+ pattern matching. No issues noted.

---

`100-120`: **No concerns; match statements look fine.**

These match statements for tool-response messages are also straightforward, with no major concerns.

---

`122-156`: **Initialization clarity.**

The constructor correctly reassigns tool names to remove spaces, sets up the new agent, and passes the `model_settings` with parallel tool calls and retries. Implementation appears structurally sound.

---

`201-225`: **Robust exception handling.**

The `run` method includes proper error handling, returning a custom exception on failure. Implementation looks good.

---

`226-294`: **Effective streaming approach.**

The `run_stream` method cleanly streams parts from the agent, differentiating model request nodes, tool calls, and end nodes. Logic is well structured, though if you anticipate large-scale usage, consider potential memory constraints or concurrency issues.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/adaptive_agent.py (4)</summary>

`23-25`: **Class documentation is concise.**

The docstring for `AdaptiveAgent` effectively spells out its role, switching to a RAG agent when needed.

---

`73-99`: **Classification fallback.**

The `_run_classification` method sensibly defaults to `ClassificationResult.AGENT_REQUIRED` on exception. This graceful fallback looks good, minimizing impact on end-user.

---

`100-111`: **Selective agent usage.**

In `run`, switching to `rag_agent` vs. direct LLM usage is carried out logically. Return structure is straightforward, no issues noted.

---

`112-129`: **Streaming logic is clear.**

`run_stream` effectively yields from either `rag_agent` or the local LLM. This is concise and fits well with the `AdaptiveAgent` design goals.

</details>
<details>
<summary>app/modules/conversations/conversation/conversation_service.py (9)</summary>

`36-37`: **Integration of AgentsService.**

Lines 36–37 show injection of the new `agent_service`. Good step for centralizing agent usage.

---

`45-48`: **PromptService and ToolService usage.**

These additions align with the overall design, ensuring conversation logic can leverage your prompt and tool features.

---

`103-107`: **AgentService instantiation.**

Injecting the `provider_service`, `prompt_service`, and `tool_service` ensures the `AgentsService` can manage or create all agent types. No issues found.

---

`159-168`: **Agent ID validation.**

Lines 163–168 ensure we only proceed if the provided agent ID is valid. This leverages `agent_service.validate_agent_id(...)`. Good practice.

---

`311-312`: **Message composition and citations.**

Lines 311–312 build the final user-facing message and citations. No issues noted; it effectively aggregates partial responses.

---

`483-487`: **Data extraction for ChatMessageResponse.**

Consolidating references to `message`, `citations`, and `tool_calls` into a single response is consistent and clear.

---

`507-515`: **Chain the raised exception to preserve context.**

This code is identical to a previously flagged issue about exception chaining.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

514-514: Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling

(B904)

</details>

</details>

---

`517-520`: **Agent ID revalidation.**

Ensuring the `validate_agent_id` does not return None is a good safety-check for potential stale or invalid data.

---

`557-560`: **Serializing tool calls.**

Calling `tool_call.model_dump_json()` is a convenient approach for serializing calls into the final response. This ensures integration with your front-end.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/crewai_agent.py (3)</summary>

`8-8`: **Revisit the pinned version for the `crewai` package.**

This import may fail at runtime if the environment lacks `crewai`, or if incompatible versions are installed. Ensuring a pinned version in your dependency manifest or `requirements.txt` helps avoid `ModuleNotFoundError` or version conflicts.

---

`44-69`: **Verify if parent constructor calls are needed.**

The parent `ChatAgent` class initialization may include specifications or logic that should be inherited. Consider calling `super().__init__()` if necessary. Otherwise, confirm no relevant superclass state is omitted.

---

`81-130`: **Ensure proper sanitization of user inputs when constructing the multiline task description.**

User-supplied strings (`ctx.query`, `ctx.history`, etc.) are directly interpolated, potentially allowing injection or formatting issues if malicious input is passed. Consider sanitizing or escaping user inputs and validating length/structure before building the final message.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T05:34:09Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 9**

<details>
<summary>♻️ Duplicate comments (4)</summary><blockquote>

<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/unit_test_agent.py (2)</summary><blockquote>

`56-57`: _⚠️ Potential issue_

**Add robust exception handling.**

The `run` method delegates to `_build_agent()` and `_enriched_context()` without error handling. If either method fails, the error would propagate without any context or graceful recovery.


```diff
async def run(self, ctx: ChatContext) -> ChatAgentResponse:
-    return await self._build_agent().run(await self._enriched_context(ctx))
+    try:
+        enriched_ctx = await self._enriched_context(ctx)
+        agent = self._build_agent()
+        return await agent.run(enriched_ctx)
+    except Exception as e:
+        # Log the error and provide context
+        raise Exception(f"Failed to run UnitTestAgent: {str(e)}") from e
```

---

`59-64`: _⚠️ Potential issue_

**Add similar exception handling to the streaming method.**

The `run_stream` method has the same issue as `run` - it lacks error handling for `_enriched_context()` and `_build_agent()`.


```diff
async def run_stream(
    self, ctx: ChatContext
) -> AsyncGenerator[ChatAgentResponse, None]:
-    ctx = await self._enriched_context(ctx)
-    async for chunk in self._build_agent().run_stream(ctx):
-        yield chunk
+    try:
+        ctx = await self._enriched_context(ctx)
+        agent = self._build_agent()
+        async for chunk in agent.run_stream(ctx):
+            yield chunk
+    except Exception as e:
+        # Log the error and provide context
+        raise Exception(f"Failed to run UnitTestAgent stream: {str(e)}") from e
```

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/integration_test_agent.py (2)</summary><blockquote>

`68-85`: _⚠️ Potential issue_

**Check for potential None or invalid graph data.**

The node extraction logic assumes a specific graph structure with `node["id"]`, `node["name"]`, and `node["children"]`. If any of these keys are missing, it will raise KeyError exceptions. Similarly, line 84 assumes `graph["graph"]["root_node"]` exists without validation.


```diff
def extract_unique_node_contexts(node, visited=None):
    if visited is None:
        visited = set()
    node_contexts: List[NodeContext] = []
-    if node["id"] not in visited:
-        visited.add(node["id"])
-        node_contexts.append(
-            NodeContext(node_id=node["id"], name=node["name"])
-        )
-        for child in node.get("children", []):
-            node_contexts.extend(
-                extract_unique_node_contexts(child, visited)
-            )
+    if not node or not isinstance(node, dict):
+        return node_contexts
+    
+    node_id = node.get("id")
+    if not node_id or node_id in visited:
+        return node_contexts
+        
+    visited.add(node_id)
+    node_contexts.append(
+        NodeContext(node_id=node_id, name=node.get("name", "Unknown"))
+    )
+    for child in node.get("children", []):
+        node_contexts.extend(
+            extract_unique_node_contexts(child, visited)
+        )
    return node_contexts

# Extract related nodes from each graph
-node_contexts = extract_unique_node_contexts(graph["graph"]["root_node"])
+if graph and isinstance(graph, dict) and "graph" in graph and "root_node" in graph["graph"]:
+    node_contexts = extract_unique_node_contexts(graph["graph"]["root_node"])
+else:
+    node_contexts = []
```

---

`96-108`: _⚠️ Potential issue_

**Add null checks when formatting graphs.**

The code to format graphs is accessing nested properties without validating their existence, which could lead to KeyError or AttributeError exceptions.


```diff
# Format graphs for better readability in the prompt
formatted_graphs = {}
for node_id, graph in graphs.items():
-    formatted_graphs[node_id] = {
-        "name": next(
-            (
-                node.name
-                for node in unique_node_contexts
-                if node.node_id == node_id
-            ),
-            "Unknown",
-        ),
-        "structure": graph["graph"]["root_node"],
-    }
+    try:
+        matching_nodes = [node for node in unique_node_contexts if node.node_id == node_id]
+        node_name = matching_nodes[0].name if matching_nodes else "Unknown"
+        
+        structure = (
+            graph.get("graph", {}).get("root_node", {})
+            if isinstance(graph, dict) else {}
+        )
+        
+        formatted_graphs[node_id] = {
+            "name": node_name,
+            "structure": structure
+        }
+    except Exception as e:
+        # Log error but continue with partial data
+        formatted_graphs[node_id] = {
+            "name": "Error retrieving name",
+            "structure": {},
+            "error": str(e)
+        }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (15)</summary><blockquote>

<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/low_level_design_agent.py (3)</summary><blockquote>

`20-52`: **Good implementation of agent selection logic**

The method appropriately builds either a PydanticRagAgent or CrewAIAgent based on model support, allowing for runtime flexibility. Consider adding a brief comment explaining the condition for choosing between agent implementations.


```diff
   def _build_agent(self) -> ChatAgent:
+      # Configure agent with role, goal and tools then select the appropriate
+      # implementation based on whether the current model supports PydanticAI
       agent_config = AgentConfig(
```

---

`36-46`: **Consider making tool selection more maintainable**

The hardcoded list of tools could become difficult to maintain as the system evolves.

Consider extracting this to a constant or configuration setting:

```diff
-        tools = self.tools_provider.get_tools(
-            [
-                "get_code_from_multiple_node_ids",
-                "get_node_neighbours_from_node_id",
-                "get_code_from_probable_node_name",
-                "ask_knowledge_graph_queries",
-                "get_nodes_from_tags",
-                "webpage_extractor",
-                "github_tool",
-            ]
-        )
+        LLD_AGENT_TOOLS = [
+            "get_code_from_multiple_node_ids",
+            "get_node_neighbours_from_node_id",
+            "get_code_from_probable_node_name",
+            "ask_knowledge_graph_queries",
+            "get_nodes_from_tags",
+            "webpage_extractor",
+            "github_tool",
+        ]
+        tools = self.tools_provider.get_tools(LLD_AGENT_TOOLS)
```

---

`71-73`: **Consider adding error handling**

The run method delegates to the built agent but doesn't handle potential exceptions.

Consider implementing error handling:

```diff
 async def run(self, ctx: ChatContext) -> ChatAgentResponse:
-    return await self._build_agent().run(await self._enriched_context(ctx))
+    try:
+        return await self._build_agent().run(await self._enriched_context(ctx))
+    except Exception as e:
+        # Log the error and return an appropriate response
+        logger.error(f"Error running LowLevelDesignAgent: {e}")
+        return ChatAgentResponse(response="Error generating low-level design plan.")
```

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/auto_router_agent.py (1)</summary><blockquote>

`20-32`: **Consider adding docstrings for clarity.**

While the constructor effectively initializes the `AutoRouterAgent`, adding docstrings can improve maintainability by describing expected parameter types and usage details for `agents` and `llm_provider`.

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/pydantic_agent.py (1)</summary><blockquote>

`112-122`: **Remove or utilize unused variable `node_ids`.**

The local variable `node_ids` is assigned but never used, causing a potential dead store. To clean up the code, consider removing it or updating references to ensure the variable serves a purpose.


```diff
- if isinstance(ctx.node_ids, str):
-     node_ids = [ctx.node_ids]
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

121-121: Local variable `node_ids` is assigned to but never used

Remove assignment to unused variable `node_ids`

(F841)

</details>

</details>

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/langchain_agent.py (1)</summary><blockquote>

`49-59`: **Remove or utilize unused variable assignment.**

The assignment to `node_ids` is never used. This generates a warning and can confuse future readers. Consider removing it to keep the codebase clean.


```diff
-if isinstance(ctx.node_ids, str):
-    node_ids = [ctx.node_ids]
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

58-58: Local variable `node_ids` is assigned to but never used

Remove assignment to unused variable `node_ids`

(F841)

</details>

</details>

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/crewai_agent.py (3)</summary><blockquote>

`23-31`: **Consider adding validation constraints to AgentConfig.**

"role", "goal", and "backstory" fields accept arbitrary user input, which might lead to unreasonably large or invalid values. Adding field constraints (e.g., max length, regex validation) can help maintain data integrity and security.

---

`179-181`: **Use a more descriptive or original exception type when re-raising.**

Raising a generic `Exception` can obscure debugging and error handling. Consider re-raising the original exception or substituting a custom exception class (e.g., `CrewAIException`) for better clarity and maintainability.

---

`183-187`: **Rename or remove `run_stream` if streaming isn't supported.**

Since CrewAI doesn't actually stream data, this method yields only a single response. Renaming or removing it might help reduce confusion about its intended behavior.

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/qna_agent.py (2)</summary><blockquote>

`11-19`: **Consider adding docstrings for improved readability**  
Providing method docstrings (particularly for `__init__`) can clarify usage and expected behavior, facilitating maintainability.

---

`87-169`: **Remove extraneous backtick and consider minor formatting improvements**  
Line 109 contains a trailing backtick after "step". This may be a typographical oversight. Removing it will improve readability.  

```diff
-        - If code results are not relevant move to next step`
+        - If code results are not relevant move to next step
```

</blockquote></details>
<details>
<summary>app/modules/intelligence/provider/provider_service.py (1)</summary><blockquote>

`485-489`: **Simplify Boolean expression**

This conditional check can be simplified by directly returning the comparison result.


```diff
def is_current_model_supported_by_pydanticai(self) -> bool:
    provider = self._get_provider_config("small")
-   if provider in ["openai", "anthropic"]:
-       return True
-   return False
+   return provider in ["openai", "anthropic"]
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

487-489: Return the condition `provider in ["openai", "anthropic"]` directly

Replace with `return provider in ["openai", "anthropic"]`

(SIM103)

</details>

</details>

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/debug_agent.py (1)</summary><blockquote>

`65-72`: **Add enrichment to run methods**

Both run methods should call `_enriched_context` before delegating to the rag_agent if the enrichment is needed.


```diff
async def run(self, ctx: ChatContext) -> ChatAgentResponse:
+    ctx = await self._enriched_context(ctx)
    return await self.rag_agent.run(ctx)

async def run_stream(
    self, ctx: ChatContext
) -> AsyncGenerator[ChatAgentResponse, None]:
+    ctx = await self._enriched_context(ctx)
    async for chunk in self.rag_agent.run_stream(ctx):
        yield chunk
```

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/integration_test_agent.py (1)</summary><blockquote>

`49-52`: **Consider more consistent agent creation pattern.**

The `IntegrationTestAgent` creates and stores agent instances in the constructor, while other agent classes (like `UnitTestAgent`) create them on-demand in a `_build_agent` method. This inconsistency makes the codebase harder to maintain.


```diff
- self.rag_agent = CrewAIAgent(llm_provider, agent_config, tools)
- if llm_provider.is_current_model_supported_by_pydanticai():
-     self.rag_agent = PydanticRagAgent(llm_provider, agent_config, tools)
+ # Store configuration for later use
+ self.agent_config = agent_config
+ self.tools = tools
```

Then add a `_build_agent` method:

```python
def _build_agent(self) -> ChatAgent:
    if self.llm_provider.is_current_model_supported_by_pydanticai():
        return PydanticRagAgent(self.llm_provider, self.agent_config, self.tools)
    else:
        return CrewAIAgent(self.llm_provider, self.agent_config, self.tools)
```

And update `run` and `run_stream` methods to use it:

```diff
async def run(self, ctx: ChatContext) -> ChatAgentResponse:
-    return await self.rag_agent.run(self._enriched_context(ctx))
+    return await self._build_agent().run(self._enriched_context(ctx))
```

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/code_gen_agent.py (1)</summary><blockquote>

`20-44`: **Consider caching agent creation for performance.**

The `_build_agent` method creates a new agent instance on each call, which might be inefficient for repeated operations. Consider caching the agent instance based on the current model.


```diff
+ def __init__(
+     self,
+     llm_provider: ProviderService,
+     tools_provider: ToolService,
+ ):
+     self.llm_provider = llm_provider
+     self.tools_provider = tools_provider
+     self._agent_cache = {}

def _build_agent(self) -> ChatAgent:
+     # Check if we already built an agent for the current model
+     current_model = self.llm_provider.get_current_model()
+     if current_model in self._agent_cache:
+         return self._agent_cache[current_model]
+     
    agent_config = AgentConfig(
        # ... existing configuration ...
    )
    tools = self.tools_provider.get_tools(
        # ... existing tools list ...
    )
+     
+     # Create and cache the agent
+     agent = None
    if self.llm_provider.is_current_model_supported_by_pydanticai():
-        return PydanticRagAgent(self.llm_provider, agent_config, tools)
+        agent = PydanticRagAgent(self.llm_provider, agent_config, tools)
    else:
-        return CrewAIAgent(self.llm_provider, agent_config, tools)
+        agent = CrewAIAgent(self.llm_provider, agent_config, tools)
+         
+     self._agent_cache[current_model] = agent
+     return agent
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 0c3b08e9ed0a5f94cbf5c568d1d88f052db0410d and 06f8de853dc97142fd4cf3c5978932422ea69ace.

</details>

<details>
<summary>📒 Files selected for processing (13)</summary>

* `app/modules/intelligence/agents/chat_agents/auto_router_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/crewai_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/langchain_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/pydantic_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/blast_radius_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/code_gen_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/debug_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/integration_test_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/low_level_design_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/qna_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/unit_test_agent.py` (1 hunks)
* `app/modules/intelligence/provider/provider_service.py` (4 hunks)
* `requirements.txt` (2 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

<details>
<summary>app/modules/intelligence/agents/chat_agents/langchain_agent.py</summary>

58-58: Local variable `node_ids` is assigned to but never used

Remove assignment to unused variable `node_ids`

(F841)

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/pydantic_agent.py</summary>

121-121: Local variable `node_ids` is assigned to but never used

Remove assignment to unused variable `node_ids`

(F841)

</details>
<details>
<summary>app/modules/intelligence/provider/provider_service.py</summary>

487-489: Return the condition `provider in ["openai", "anthropic"]` directly

Replace with `return provider in ["openai", "anthropic"]`

(SIM103)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (27)</summary><blockquote>

<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/low_level_design_agent.py (5)</summary>

`1-9`: **Appropriate imports for the implementation**

The imports correctly include all necessary components for both the Pydantic and CrewAI implementations, supporting the flexible agent framework design.

---

`11-19`: **Well-structured class initialization**

The constructor follows good design principles by accepting and storing the provider services needed for agent operation. This approach supports dependency injection and makes testing easier.

---

`53-70`: **Robust context enrichment implementation**

The method effectively enhances the chat context with both code from node IDs and the project's file structure, providing the agent with comprehensive information for generating a low-level design.

---

`74-80`: **Streaming implementation meets PR objectives**

The run_stream method properly implements the streaming functionality mentioned in the PR objectives, allowing for more responsive user experience.

---

`82-95`: **Comprehensive task prompt definition**

The lld_task_prompt is well-structured and provides clear guidance on what should be included in the low-level design plan, covering implementation steps, code changes, and considerations for maintaining code consistency.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/auto_router_agent.py (2)</summary>

`34-66`: **Validate classification fallback logic.**

When an invalid `agent_id` is returned, or if `confidence_score` is below threshold, the fallback to `ctx.curr_agent_id` ensures safe handling. However, confirm that this fallback behavior matches the desired flow, especially handling corner cases where the chosen agent might be overloaded or less suitable.

---

`92-125`: **Looks good overall.**

Your prompt-based classification strategy is comprehensive, and the included guidelines are clear. No immediate issues stand out. This approach should be conducive to accurate agent-routing.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/pydantic_agent.py (2)</summary>

`88-91`: **Validate tool naming convention.**

Replacing spaces with an empty string in tool names prevents issues with certain frameworks. However, verify that this convention is acceptable in all downstream dependencies and that no conflicts arise between tool names that only differ by spacing.

---

`155-176`: **Guard against empty tasks list.**

Accessing `self.tasks[0]` without validating the length of `self.tasks` can lead to an `IndexError` if the list is empty. Validate the tasks list before use or catch any potential errors.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/langchain_agent.py (1)</summary>

`22-25`: **Consider removing or clarifying the purpose of `ActionWrapper`.**

It appears unused in this file. If support for a future feature is intended, add docstrings explaining its role. Otherwise, remove it to reduce code clutter.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/crewai_agent.py (3)</summary>

`8-8`: **Pin the `crewai` version in dependencies.**

Similar to a past review comment, ensure the environment is updated to include a pinned version of `crewai` to avoid version conflicts or missing modules in production.

---

`81-130`: **Ensure proper sanitization of user inputs in the multiline string.**

The `_create_task_description` function interpolates `ctx.query`, `ctx.history`, and `ctx.additional_context` directly into the final string. Consider sanitizing these inputs to avoid any injection or formatting issues.

---

`1-188`: **Overall well-structured implementation with minor suggestions.**

These changes look good for the main functionality. The above suggestions can further improve security, validation, and code clarity. Great job!

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/qna_agent.py (3)</summary>

`1-9`: **Imports look structured and consistent**  
No immediate issues found with imports.

---

`76-78`: **Method implementation looks correct**  
The `run` method orchestrates context enrichment and agent execution in a clear, straightforward manner.

---

`79-85`: **Streaming logic is implemented cleanly**  
The asynchronous generator approach for `run_stream` appears well-structured.

</details>
<details>
<summary>app/modules/intelligence/provider/provider_service.py (4)</summary>

`490-531`: **LGTM: Model factory implementation looks good**

The method creates appropriate model instances based on provider type with proper configuration for API keys, base URLs, and headers. The pattern matching approach provides a clean way to handle different provider types.

---

`20-23`: **LGTM: Well-organized imports for pydantic_ai integration**

The imports are appropriately structured for the new pydantic_ai integration.

---

`28-28`: **LGTM: New provider enum value added**

The addition of the PYDANTICAI provider to the AgentProvider enum integrates well with the existing enum structure.

---

`413-417`: **LGTM: Consolidated header creation**

Header creation has been consolidated into a single location, eliminating redundant code.

</details>
<details>
<summary>requirements.txt (1)</summary>

`25-25`: <details>
<summary>✅ Verification successful</summary>

**Verify compatibility with updated OpenAI version**

The OpenAI library has been updated from 1.60.2 to 1.65.1. Ensure this version is compatible with the rest of the codebase.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check for any OpenAI client initialization patterns that might be affected by version changes

echo "Checking for OpenAI client initialization patterns..."
rg -A 2 "AsyncOpenAI\(" --type py

echo "Checking OpenAI changelog for breaking changes between 1.60.2 and 1.65.1..."
curl -s "https://github.com/openai/openai-python/releases" | grep -A 5 -E "v1\.(6[1-9]|65)"
```

Length of output: 43958

---



</details>

**Compatibility Verified: OpenAI v1.65.1 is Safe to Use**

- The asynchronous client calls using `AsyncOpenAI` in `app/modules/intelligence/provider/provider_service.py` continue to work as expected.
- A review of the GitHub changelog for versions between 1.60.2 and 1.65.1 revealed no breaking changes impacting our usage.
- No additional code modifications are necessary based on the current integration.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/blast_radius_agent.py (3)</summary>

`11-46`: **LGTM: Well-structured agent with dynamic implementation selection**

The implementation elegantly selects between PydanticRagAgent and CrewAIAgent based on model support. The agent configuration is clear and comprehensive.

---

`47-54`: **LGTM: Effective implementation of run methods**

Both the run and run_stream methods efficiently delegate to the dynamically built agent instance, allowing for both standard and streaming responses.

---

`57-94`: **LGTM: Comprehensive task prompt**

The blast_radius_task_prompt provides detailed instructions for analyzing code changes and their impact, with clear steps for formulating queries and structuring the analysis.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/debug_agent.py (3)</summary>

`55-63`: **Incorporate `_enriched_context` into the execution flow.**

The `_enriched_context` function appends code results to the query context but is never invoked by `run` or `run_stream`. Either remove it or call it before passing the context to `self.rag_agent` if enriched context is needed.

---

`11-54`: **LGTM: Well-structured agent with dynamic implementation selection**

The implementation properly initializes either a CrewAIAgent or PydanticRagAgent based on model support. The agent configuration with its comprehensive role, goal, and backstory provides clear guidance for the agent's behavior.

---

`75-156`: **LGTM: Detailed and structured task prompt**

The code_gen_task_prompt provides comprehensive guidance for the agent, with clear steps for analyzing project structure, retrieving context, executing queries, and composing responses.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T05:23:19Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>app/modules/intelligence/provider/provider_service.py (1)</summary><blockquote>

`486-490`: **Simplify boolean return**

The boolean return statement can be simplified for better readability.

```diff
def is_current_model_supported_by_pydanticai(self) -> bool:
    provider = self._get_provider_config("small")
-    if provider in ["openai", "anthropic"]:
-        return True
-    return False
+    return provider in ["openai", "anthropic"]
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

488-490: Return the condition `provider in ["openai", "anthropic"]` directly

Replace with `return provider in ["openai", "anthropic"]`

(SIM103)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 06f8de853dc97142fd4cf3c5978932422ea69ace and 7a7039b9137e0511bdf7cdff6dbc36bb9b949e7a.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `app/modules/intelligence/provider/provider_service.py` (4 hunks)
* `requirements.txt` (2 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* requirements.txt

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

<details>
<summary>app/modules/intelligence/provider/provider_service.py</summary>

488-490: Return the condition `provider in ["openai", "anthropic"]` directly

Replace with `return provider in ["openai", "anthropic"]`

(SIM103)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>app/modules/intelligence/provider/provider_service.py (3)</summary>

`29-29`: **Support for PYDANTICAI provider added**

The addition of PYDANTICAI to the AgentProvider enum allows for integration with Pydantic AI models, enabling the new streaming functionality mentioned in the PR objectives.

---

`414-418`: **Improved header creation**

Header creation has been streamlined and consolidated into a single location in the _initialize_llm method, which reduces code duplication and improves maintainability.

---

`21-24`: **New PydanticAI imports for improved AI model integration**

These imports enable the integration with PydanticAI models, which is critical for the PR's objectives of supporting message streaming and metadata streaming.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T03:02:06Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (7)</summary><blockquote>

<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/debug_agent.py (4)</summary><blockquote>

`20-57`: **Robust agent configuration with flexible implementation selection**

The `_build_agent` method creates a well-defined agent configuration and intelligently selects between `PydanticRagAgent` and `CrewAIAgent` based on model support. This design pattern supports extensibility and graceful fallback.

However, consider extracting the tools list to a class constant or configuration value to improve maintainability:

```diff
- tools = self.tools_provider.get_tools(
-     [
-         "get_code_from_multiple_node_ids",
-         "get_node_neighbours_from_node_id",
-         "get_code_from_probable_node_name",
-         "ask_knowledge_graph_queries",
-         "get_nodes_from_tags",
-         "get_code_file_structure",
-         "webpage_extractor",
-         "github_tool",
-     ]
- )
+ tools = self.tools_provider.get_tools(self.AGENT_TOOLS)
```

With a class constant defined as:
```python
AGENT_TOOLS = [
    "get_code_from_multiple_node_ids",
    "get_node_neighbours_from_node_id",
    "get_code_from_probable_node_name",
    "ask_knowledge_graph_queries",
    "get_nodes_from_tags",
    "get_code_file_structure",
    "webpage_extractor",
    "github_tool",
]
```

---

`80-161`: **Comprehensive task prompt with detailed execution strategy**

The `code_gen_task_prompt` provides a thorough multi-step approach for analyzing project structures and formulating responses. The prompt includes detailed instructions for directory traversal, knowledge graph querying, and response composition.

Consider breaking down this long string into smaller, more manageable components to improve maintainability:

```diff
- code_gen_task_prompt = """
-     1. Analyze project structure:
-     ...
-     161 lines of instructions
- """
+ # Define components of the prompt
+ ANALYSIS_STEPS = """
+     1. Analyze project structure:
+     ...
+ """
+ 
+ DIRECTORY_TRAVERSAL = """
+     Directory traversal strategy:
+     ...
+ """
+ 
+ # Additional components...
+ 
+ code_gen_task_prompt = f"""
+     {ANALYSIS_STEPS}
+ 
+     {DIRECTORY_TRAVERSAL}
+     
+     # Include other components...
+ """
```

This approach would make the prompt easier to manage, update, and test in isolation.

---

`34-38`: **Tasks configuration references before declaration**

The `TaskConfig` references `code_gen_task_prompt` on line 35, but this variable is only defined later in the file (lines 80-161). While Python will find the variable at runtime due to how modules are executed, this creates a readability issue. Consider moving the prompt definition before the class to maintain logical flow.

```diff
- class DebugAgent(ChatAgent):
-     # Class implementation...
- 
- code_gen_task_prompt = """
-     # Long prompt...
- """
+ code_gen_task_prompt = """
+     # Long prompt...
+ """
+ 
+ class DebugAgent(ChatAgent):
+     # Class implementation...
```

Alternatively, make the prompt a class attribute to keep related code together.

---

`1-161`: **Consider adding docstrings for better documentation**

The class and its methods lack docstrings that would provide helpful documentation about their purpose, parameters, and return values. Consider adding docstrings following the PEP 257 guidelines to improve code documentation and maintainability.

For example:

```python
class DebugAgent(ChatAgent):
    """
    An agent specialized in querying knowledge graphs and refining results.
    
    This agent enhances context with code retrieved from node IDs and 
    delegates execution to either PydanticRagAgent or CrewAIAgent based on
    model support.
    """
    
    async def _enriched_context(self, ctx: ChatContext) -> ChatContext:
        """
        Enhance the context with code retrieved from node IDs.
        
        Args:
            ctx: The chat context to enrich
            
        Returns:
            The enriched chat context
        """
```

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/integration_test_agent.py (3)</summary><blockquote>

`50-54`: **Consider caching the agent instance.**

The `_build_agent()` method is called on each `run` and `run_stream` invocation, potentially creating a new agent instance each time. This could be inefficient if the agent creation is resource-intensive.


Consider caching the agent instance:

```diff
def __init__(
    self,
    llm_provider: ProviderService,
    tools_provider: ToolService,
):
    self.llm_provider = llm_provider
    self.tools_provider = tools_provider
+    self._agent = None

def _build_agent(self) -> ChatAgent:
+    if self._agent is not None:
+        return self._agent
+        
    agent_config = AgentConfig(
        # config remains the same
    )
    tools = self.tools_provider.get_tools(
        # tools remain the same
    )

    if self.llm_provider.is_current_model_supported_by_pydanticai():
-        return PydanticRagAgent(self.llm_provider, agent_config, tools)
+        self._agent = PydanticRagAgent(self.llm_provider, agent_config, tools)
    else:
-        return CrewAIAgent(self.llm_provider, agent_config, tools)
+        self._agent = CrewAIAgent(self.llm_provider, agent_config, tools)
+    
+    return self._agent
```

---

`90-96`: **Consider using a more efficient deduplication approach.**

The code uses a manual approach to deduplicate node contexts while preserving order, but this could be simplified using Python's standard library.


```diff
- # Remove duplicates while preserving order
- seen = set()
- unique_node_contexts: List[NodeContext] = []
- for node in all_node_contexts:
-     if node.node_id not in seen:
-         seen.add(node.node_id)
-         unique_node_contexts.append(node)
+ # Remove duplicates while preserving order using dict.fromkeys()
+ unique_node_contexts = list({node.node_id: node for node in all_node_contexts}.values())
```

---

`63-69`: <details>
<summary>❓ Verification inconclusive</summary>

**Consider batch processing for graph retrieval.**

The code currently retrieves graphs for each node ID sequentially, which might be inefficient for many nodes. If the tools_provider supports it, consider implementing batch retrieval.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if the tool service supports batch retrieval
rg -l "get_code_graph_from_multiple_node_ids|batch_get_code_graph" --type py
```

Length of output: 78

---



</details>

**Batch Retrieval Verification: Please Confirm Feasibility**

The search for batch retrieval methods (e.g., methods matching names like `get_code_graph_from_multiple_node_ids` or `batch_get_code_graph`) returned no results. This suggests that the current implementation of the tools provider does not offer a built-in batch processing capability. If you believe performance improvements are critical for graph retrieval when handling many node IDs, please verify manually whether:
- There is an alternative or planned batch retrieval method in the tools provider.
- It makes sense to extend the tools provider with such a method in the future.

Otherwise, the current sequential processing may be the intended design.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 7a7039b9137e0511bdf7cdff6dbc36bb9b949e7a and 2a6cc7f319d83343820af0b572b4dd5695e5d74e.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `app/modules/intelligence/agents/chat_agents/system_agents/debug_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/integration_test_agent.py` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (10)</summary><blockquote>

<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/debug_agent.py (5)</summary>

`1-9`: **Import statements are correctly organized**

The imports are properly structured, with dependencies from the application modules being imported first, followed by relative imports, and then standard library imports. This follows good Python import ordering practices.

---

`11-19`: **Constructor is clean and well-structured**

The class initialization properly stores the provided services as instance variables, creating a clear dependency injection pattern. This approach facilitates testing and allows for flexible service implementations.

---

`58-67`: **Implementation of previously missing context enrichment**

The `_enriched_context` method is now used by both `run` and `run_stream` methods, addressing a previous review comment. The method correctly checks if node IDs exist before attempting to retrieve code, and appends the results to the context.

---

`68-71`: **Proper utilization of enriched context in run method**

The `run` method now correctly calls `_enriched_context` before executing the agent, ensuring that any relevant code from node IDs is included in the context.

---

`72-77`: **Streaming implementation preserves context enrichment**

The `run_stream` method also correctly utilizes the `_enriched_context` method before streaming responses, maintaining consistency with the non-streaming implementation. The async generator is properly implemented to yield chunks as they become available.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/system_agents/integration_test_agent.py (5)</summary>

`18-26`: **Good use of dependency injection for service providers.**

The class definition uses proper dependency injection by accepting provider services through the constructor, making it more testable and maintainable.

---

`27-54`: **Well-structured agent configuration with appropriate role guidance.**

The _build_agent method correctly implements dynamic agent selection based on model capabilities, with clear role definition and comprehensive task description.

---

`55-115`: **Check for potential None or invalid graph data.**

The `_enriched_context` method accesses `graph["graph"]["root_node"]` on line 86 without validating the structure of the graph data. If the graph is malformed or has a different structure, this could lead to KeyError exceptions.


Apply this defensive approach:

```diff
# Extract related nodes from each graph
- node_contexts = extract_unique_node_contexts(graph["graph"]["root_node"])
+ if graph and "graph" in graph and "root_node" in graph["graph"]:
+     node_contexts = extract_unique_node_contexts(graph["graph"]["root_node"])
+ else:
+     # Handle missing structure gracefully
+     node_contexts = []
```

---

`117-125`: **Add exception handling in run methods.**

The run and run_stream methods lack error handling. If either _enriched_context or the agent's run methods fail, there's no graceful error handling or contextual error information.


Implement exception handling as previously suggested:

```diff
async def run(self, ctx: ChatContext) -> ChatAgentResponse:
-    return await self._build_agent().run(self._enriched_context(ctx))
+    try:
+        agent = self._build_agent()
+        enriched_ctx = self._enriched_context(ctx)
+        return await agent.run(enriched_ctx)
+    except Exception as e:
+        # Log the error and provide context
+        raise Exception(f"Failed to run IntegrationTestAgent: {str(e)}") from e

async def run_stream(
    self, ctx: ChatContext
) -> AsyncGenerator[ChatAgentResponse, None]:
-    ctx = self._enriched_context(ctx)
-    async for chunk in self._build_agent().run_stream(ctx):
-        yield chunk
+    try:
+        ctx = self._enriched_context(ctx)
+        agent = self._build_agent()
+        async for chunk in agent.run_stream(ctx):
+            yield chunk
+    except Exception as e:
+        # Log the error and provide context
+        raise Exception(f"Failed to run IntegrationTestAgent stream: {str(e)}") from e
```

---

`128-179`: **Comprehensive and well-structured integration test prompt.**

The integration test task prompt provides excellent guidance for generating tests, covering code analysis, component understanding, test planning, implementation, and refinement. This structured approach should result in high-quality integration tests.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T07:04:45Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (2)</summary><blockquote>

<details>
<summary>app/modules/intelligence/provider/provider_service.py (2)</summary><blockquote>

`555-569`: _🛠️ Refactor suggestion_

**Add error handling for unsupported providers**

The non-Portkey section handles OpenAI and Anthropic providers, but doesn't have a default case to handle unsupported providers. The method signature indicates it can return None, but there's no explicit return statement for this case.


```diff
match provider:
    case "openai":
        return OpenAIModel(
            model_name=model,
            provider=OpenAIProvider(
                api_key=api_key,
            ),
        )
    case "anthropic":
        return AnthropicModel(
            model_name=model,
            anthropic_client=AsyncAnthropic(
                api_key=api_key,
            ),
        )
+    case _:
+        logging.warning(f"Provider {provider} is not supported by PydanticAI")
+        return None
```

---

`495-534`: _🛠️ Refactor suggestion_

**Enhance error handling in Portkey section of get_pydantic_model**

The method correctly creates model instances for OpenAI and Anthropic with Portkey integration, but lacks explicit error handling for unsupported providers. Also, the current model name parsing for "x/y" format does not verify if the slash exists.

```diff
def get_pydantic_model(self) -> Model | None:
    provider = self._get_provider_config("small")
    params = self._build_llm_params(provider, "small")
    routing_provider = params.pop("routing_provider", None)
    api_key = params["api_key"]

    # pick model name => convert "openai/gpt-4o" to "gpt-4o"
    model_name: str = params["model"]
-    model = model_name.split("/")[-1]
+    # Safely extract model name
+    model = model_name.split("/")[-1] if "/" in model_name else model_name

    # if portkey is enabled
    if self.portkey_api_key:
        match provider:
            case "openai":
                return OpenAIModel(
                    model_name=model,
                    provider=OpenAIProvider(
                        api_key=api_key,
                        base_url=PORTKEY_GATEWAY_URL,
                        http_client=httpx.AsyncClient(
                            headers=createHeaders(
                                api_key=self.portkey_api_key,
                                provider=routing_provider,
                                trace_id=str(uuid.uuid4())[:8],
                            ),
                        ),
                    ),
                )
            case "anthropic":
                return AnthropicModel(
                    model_name=model,
                    anthropic_client=AsyncAnthropic(
                        base_url=PORTKEY_GATEWAY_URL,
                        api_key=api_key,
                        default_headers=createHeaders(
                            api_key=self.portkey_api_key,
                            provider=routing_provider,
                            trace_id=str(uuid.uuid4())[:8],
                        ),
                    ),
                )
+            case _:
+                logging.warning(f"Provider {provider} with Portkey is not supported by PydanticAI")
+                return None
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>app/modules/intelligence/provider/provider_service.py (2)</summary><blockquote>

`486-493`: **Simplify boolean return logic**

The current implementation can be simplified by directly returning the boolean condition.

```diff
def is_current_model_supported_by_pydanticai(self) -> bool:
    provider = self._get_provider_config("small")
-    if provider in [
-        "openai",
-        "anthropic",
-    ]:
-        return True
-    return False
+    return provider in ["openai", "anthropic"]
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

488-493: Return the condition `provider in ["openai", "anthropic"]` directly

Replace with `return provider in ["openai", "anthropic"]`

(SIM103)

</details>

</details>

---

`536-553`: **Remove or complete the commented-out code for deepseek provider**

There is a large block of commented-out code for the deepseek provider. Either complete the implementation if it's needed or remove it if it's not required to keep the codebase clean.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 2a6cc7f319d83343820af0b572b4dd5695e5d74e and 8b735ca34ff02c3946ffe5b706bfec70b099cdaf.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `app/modules/intelligence/provider/provider_service.py` (4 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

<details>
<summary>app/modules/intelligence/provider/provider_service.py</summary>

488-493: Return the condition `provider in ["openai", "anthropic"]` directly

Replace with `return provider in ["openai", "anthropic"]`

(SIM103)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>app/modules/intelligence/provider/provider_service.py (3)</summary>

`29-29`: **LGTM: Added PYDANTICAI to AgentProvider enum**

The addition of PYDANTICAI to the AgentProvider enum is appropriate and aligns with the PR objectives of implementing Pydantic AI integration.

---

`414-418`: **Good refactoring of header creation**

The header creation has been streamlined into a single location, improving code maintainability and reducing duplication.

---

`502-503`: **Safeguard Model Name Parsing Logic**

I see you're extracting the model name from the full path using `split("/")[-1]`, which works when model names follow the `provider/model` pattern. However, it's safer to check if the slash exists first.


Since the incoming `params["model"]` could be externally provided, I recommend adding checking for the slash before parsing:

```diff
# pick model name => convert "openai/gpt-4o" to "gpt-4o"
model_name: str = params["model"]
-model = model_name.split("/")[-1]
+model = model_name.split("/")[-1] if "/" in model_name else model_name
```

Let's check if all model names in the codebase follow the expected pattern:

```shell
#!/bin/bash
# Find all model name references in the codebase
rg '"model":\s*"[^"]+"' --type py
```

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T04:26:50Z
user: login: dhirenmathur
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T12:36:53Z
user: login: dhirenmathur
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T13:01:11Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (14)</summary><blockquote>

<details>
<summary>app/modules/intelligence/agents/chat_agent.py (1)</summary><blockquote>

`12-29`: **Well-structured Pydantic model, consider typed details.**
`ToolCallResponse` is clearly defined, with each field documented via `description`. For `tool_call_details`, consider using a more specific typed model or schema if you expect a predictable structure, to further validate the content of the response.

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/auto_router_agent.py (1)</summary><blockquote>

`67-67`: **Caution with broad exception handling.**
Catching `Exception` will also catch unexpected errors, making debugging more difficult. Consider a more targeted approach or separate handling to ensure critical issues are not silently swallowed.

</blockquote></details>
<details>
<summary>app/modules/intelligence/provider/provider_service.py (1)</summary><blockquote>

`486-494`: **Streamline return statement to reduce code.**
You can directly return the condition instead of using if-return pairs:
```diff
-def is_current_model_supported_by_pydanticai(self) -> bool:
-    provider = self._get_provider_config("small")
-    if provider in ["openai", "anthropic"]:
-        return True
-    return False
+def is_current_model_supported_by_pydanticai(self) -> bool:
+    provider = self._get_provider_config("small")
+    return provider in ["openai", "anthropic"]
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

488-493: Return the condition `provider in ["openai", "anthropic"]` directly

Replace with `return provider in ["openai", "anthropic"]`

(SIM103)

</details>

</details>

</blockquote></details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/pydantic_agent.py (11)</summary><blockquote>

`127-127`: **Correct the formatting in node IDs string join.**

There's an unnecessary space in the string join format which results in a comma preceded by a space.

```diff
- Node IDs: {" ,".join(ctx.node_ids)}
+ Node IDs: {", ".join(ctx.node_ids)}
```

---

`129-129`: **Correct the formatting in history string join.**

Similar to the node IDs, there's an unnecessary space in the string join format.

```diff
- Consider the chat history for any specific instructions or context: {" ,".join(ctx.history) if len(ctx.history) > 0 else "no chat history"}
+ Consider the chat history for any specific instructions or context: {", ".join(ctx.history) if len(ctx.history) > 0 else "no chat history"}
```

---

`159-162`: **Remove commented-out code for agentops.**

Commented-out code adds unnecessary noise to the codebase. Either implement the monitoring functionality or remove these comments.

```diff
- # agentops.init(
- #     os.getenv("AGENTOPS_API_KEY"), default_tags=["openai-gpt-notebook"]
- # )
- # session = agentops.start_session()
```

---

`169-169`: **Remove commented-out session end code.**

Similar to the initialization, the session end comment should be removed or implemented.

```diff
- # session and session.end_session("Success")
```

---

`177-179`: **Enhance exception handling with specific error details.**

The current exception handling re-raises the exception without preserving the original error message. Consider adding more context about what operation failed.

```diff
except Exception as e:
    logger.error(f"Error in run method: {str(e)}", exc_info=True)
-    raise Exception from e
+    raise Exception(f"PydanticRagAgent run failed: {str(e)}") from e
```

---

`209-210`: **Remove commented-out response line.**

Similar to other commented code, this should be removed or implemented.

```diff
- # response=f"\n[Tools] The LLM calls tool={event.part.tool_name!r} {event.part.args_as_json_str}\n",
```

---

`226-227`: **Remove commented-out response line.**

Another instance of commented code that should be removed.

```diff
- # response=f"\n[Tools] Tool call {event.result.tool_name!r}\n",
```

---

`247-249`: **Enhance exception handling in run_stream with specific error details.**

Similar to the run method, the exception handling here should provide more context.

```diff
except Exception as e:
    logger.error(f"Error in run method: {str(e)}", exc_info=True)
-    raise Exception from e
+    raise Exception(f"PydanticRagAgent stream failed: {str(e)}") from e
```

---

`94-94`: **Remove commented model specification.**

The commented line specifying a particular model should be removed to avoid confusion.

```diff
- # model="gpt-4o-mini",
```

---

`248-248`: **Correct the error log message.**

The error log message in run_stream method incorrectly references "run method" instead of "run_stream method".

```diff
- logger.error(f"Error in run method: {str(e)}", exc_info=True)
+ logger.error(f"Error in run_stream method: {str(e)}", exc_info=True)
```

---

`76-250`: **Consider adding docstrings to the class and its public methods.**

While some methods have docstrings, they are quite brief. Consider enhancing them with parameter descriptions, return type descriptions, and examples to improve code maintainability.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

121-121: Local variable `node_ids` is assigned to but never used

Remove assignment to unused variable `node_ids`

(F841)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 8b735ca34ff02c3946ffe5b706bfec70b099cdaf and 848a0bd60ea90570e6eea557dc8e94cc0bab0f85.

</details>

<details>
<summary>📒 Files selected for processing (28)</summary>

* `app/modules/conversations/conversation/conversation_schema.py` (2 hunks)
* `app/modules/conversations/conversation/conversation_service.py` (5 hunks)
* `app/modules/intelligence/agents/chat_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/adaptive_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/auto_router_agent.py` (3 hunks)
* `app/modules/intelligence/agents/chat_agents/crewai_agent.py` (5 hunks)
* `app/modules/intelligence/agents/chat_agents/langchain_agent.py` (3 hunks)
* `app/modules/intelligence/agents/chat_agents/pydantic_agent.py` (1 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/blast_radius_agent.py` (2 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/code_gen_agent.py` (3 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/debug_agent.py` (4 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/integration_test_agent.py` (3 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/low_level_design_agent.py` (2 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/qna_agent.py` (3 hunks)
* `app/modules/intelligence/agents/chat_agents/system_agents/unit_test_agent.py` (2 hunks)
* `app/modules/intelligence/provider/provider_service.py` (4 hunks)
* `app/modules/intelligence/tools/change_detection/change_detection_tool.py` (2 hunks)
* `app/modules/intelligence/tools/code_query_tools/get_code_from_node_name_tool.py` (2 hunks)
* `app/modules/intelligence/tools/code_query_tools/get_code_graph_from_node_id_tool.py` (2 hunks)
* `app/modules/intelligence/tools/code_query_tools/get_code_graph_from_node_name_tool.py` (2 hunks)
* `app/modules/intelligence/tools/code_query_tools/get_node_neighbours_from_node_id_tool.py` (2 hunks)
* `app/modules/intelligence/tools/kg_based_tools/get_code_from_multiple_node_ids_tool.py` (2 hunks)
* `app/modules/intelligence/tools/kg_based_tools/get_code_from_node_id_tool.py` (2 hunks)
* `app/modules/intelligence/tools/kg_based_tools/get_code_from_probable_node_name_tool.py` (2 hunks)
* `app/modules/intelligence/tools/tool_service.py` (3 hunks)
* `app/modules/intelligence/tools/web_tools/github_tool.py` (2 hunks)
* `app/modules/intelligence/tools/web_tools/webpage_extractor_tool.py` (2 hunks)
* `requirements.txt` (2 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (24)</summary>

* app/modules/conversations/conversation/conversation_schema.py
* app/modules/intelligence/agents/chat_agents/system_agents/qna_agent.py
* app/modules/intelligence/agents/chat_agents/adaptive_agent.py
* app/modules/intelligence/tools/code_query_tools/get_code_from_node_name_tool.py
* app/modules/intelligence/tools/code_query_tools/get_code_graph_from_node_id_tool.py
* app/modules/intelligence/agents/chat_agents/system_agents/low_level_design_agent.py
* app/modules/intelligence/agents/chat_agents/system_agents/code_gen_agent.py
* app/modules/intelligence/tools/web_tools/webpage_extractor_tool.py
* app/modules/intelligence/agents/chat_agents/system_agents/unit_test_agent.py
* app/modules/intelligence/tools/code_query_tools/get_node_neighbours_from_node_id_tool.py
* app/modules/intelligence/agents/chat_agents/system_agents/debug_agent.py
* app/modules/intelligence/tools/kg_based_tools/get_code_from_node_id_tool.py
* requirements.txt
* app/modules/intelligence/tools/kg_based_tools/get_code_from_multiple_node_ids_tool.py
* app/modules/intelligence/tools/kg_based_tools/get_code_from_probable_node_name_tool.py
* app/modules/intelligence/agents/chat_agents/system_agents/blast_radius_agent.py
* app/modules/intelligence/tools/web_tools/github_tool.py
* app/modules/intelligence/tools/tool_service.py
* app/modules/intelligence/agents/chat_agents/crewai_agent.py
* app/modules/intelligence/agents/chat_agents/langchain_agent.py
* app/modules/intelligence/tools/code_query_tools/get_code_graph_from_node_name_tool.py
* app/modules/intelligence/agents/chat_agents/system_agents/integration_test_agent.py
* app/modules/conversations/conversation/conversation_service.py
* app/modules/intelligence/tools/change_detection/change_detection_tool.py

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

<details>
<summary>app/modules/intelligence/provider/provider_service.py</summary>

488-493: Return the condition `provider in ["openai", "anthropic"]` directly

Replace with `return provider in ["openai", "anthropic"]`

(SIM103)

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/pydantic_agent.py</summary>

121-121: Local variable `node_ids` is assigned to but never used

Remove assignment to unused variable `node_ids`

(F841)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (11)</summary><blockquote>

<details>
<summary>app/modules/intelligence/agents/chat_agent.py (3)</summary>

`2-3`: **No concerns with newly added imports.**
These imports (`Enum`, `Any`, `AsyncGenerator`, etc.) are necessary for your new functionality and look good.

---

`7-9`: **Enum usage is appropriate.**
Defining `ToolCallEventType` as an `Enum` for `CALL` and `RESULT` is a neat way to ensure clarity and type safety for event types.

---

`37-37`: **Default list field is fine with Pydantic.**
`tool_calls: List[ToolCallResponse] = Field([], ...)` is acceptable, though in plain Python mutable defaults can be risky. Here, Pydantic handles it effectively.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/auto_router_agent.py (2)</summary>

`51-56`: **Classification call looks good.**
Using `call_llm_with_structured_output` to retrieve a `ClassificationResponse` object is clear and shows proper usage of your structured output approach.

---

`85-90`: **Corrected class name is consistent.**
Renaming to `ClassificationResponse` matches the usage and clarifies the intended purpose of this Pydantic model.

</details>
<details>
<summary>app/modules/intelligence/provider/provider_service.py (4)</summary>

`6-6`: **Imports and new enum entry are well-structured.**
The additions (`AsyncAnthropic`, `Model`, `OpenAIModel`, `AnthropicModel`, `OpenAIProvider`, and `PYDANTICAI`) look correctly placed. No concerns raised.



Also applies to: 9-9, 12-12, 21-23, 29-29

---

`414-418`: **Header creation is clear.**
Creating headers once here, using `createHeaders`, improves maintainability and consistency for downstream requests.

---

`485-485`: **No functional changes in this line.**  
This line appears to be a minor or whitespace adjustment.

---

`495-570`: **Great extension for Pydantic-based models.**
`get_pydantic_model` correctly configures and returns either an OpenAI or Anthropic model instance. Consider adding explicit handling for additional providers if needed in future expansions.

</details>
<details>
<summary>app/modules/intelligence/agents/chat_agents/pydantic_agent.py (2)</summary>

`76-110`: **LGTM: Well-structured agent initialization with proper configuration.**

The PydanticRagAgent initialization is well-implemented with clear parameter handling, tool name normalization, and appropriate configuration for parallel tool calls and retries.

---

`181-246`: **Well-implemented streaming functionality.**

The run_stream method effectively handles different types of nodes and events, yielding appropriate responses for each case. The implementation enables streaming of both text responses and tool-related events.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T13:02:03Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>app/modules/intelligence/provider/provider_service.py (1)</summary><blockquote>

`555-569`: _🛠️ Refactor suggestion_

**Add fallback handling for unsupported providers**

The current implementation handles only "openai" and "anthropic" providers but doesn't have explicit fallback logic for unsupported providers.


Add a default case to handle unsupported providers and log appropriate warnings:

```diff
    match provider:
        case "openai":
            return OpenAIModel(
                model_name=model,
                provider=OpenAIProvider(
                    api_key=api_key,
                ),
            )
        case "anthropic":
            return AnthropicModel(
                model_name=model,
                anthropic_client=AsyncAnthropic(
                    api_key=api_key,
                ),
            )
+        case _:
+            logging.warning(f"Provider {provider} is not supported by PydanticAI")
+            return None
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>app/modules/intelligence/provider/provider_service.py (5)</summary><blockquote>

`486-493`: **Simplify the conditional return statement**

The current implementation checks if the provider is in a list and then returns True/False accordingly. This can be simplified by directly returning the boolean result of the condition.


```diff
def is_current_model_supported_by_pydanticai(self) -> bool:
    provider = self._get_provider_config("large")
-    if provider in [
-        "openai",
-        "anthropic",
-    ]:
-        return True
-    return False
+    return provider in ["openai", "anthropic"]
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

488-493: Return the condition `provider in ["openai", "anthropic"]` directly

Replace with `return provider in ["openai", "anthropic"]`

(SIM103)

</details>

</details>

---

`495-504`: **Improve model name parsing robustness**

The current implementation extracts the model name by splitting on "/" and taking the last segment. While this works for the defined model formats, it would benefit from additional validation for edge cases.


```diff
def get_pydantic_model(self) -> Model | None:
    provider = self._get_provider_config("large")
    params = self._build_llm_params(provider, "large")
    routing_provider = params.pop("routing_provider", None)
    api_key = params["api_key"]

    # pick model name => convert "openai/gpt-4o" to "gpt-4o"
    model_name: str = params["model"]
+    if not model_name:
+        logging.warning(f"Empty model name for provider {provider}")
+        return None
    model = model_name.split("/")[-1]
```

---

`536-553`: **Remove or complete the commented-out code**

There's a significant block of commented-out code for the "deepseek" provider. This should either be completed and enabled if the feature is ready for use, or removed if it's not planned for the near future.


Either remove the commented code for cleaner maintenance, or add a TODO comment explaining the plan for this provider's implementation with an estimated timeline.

```diff
-                # case "deepseek":
-                #     model_name: str = params["model"]
-                #     model_name = model_name.split("/", 1)[1]
-                #     return OpenAIModel(
-                #         model_name=model_name if model_name else model,
-                #         # model_name="gpt-4o-mini",
-                #         provider=OpenAIProvider(
-                #             api_key=api_key,
-                #             base_url="https://openrouter.ai/api/v1",
-                #             http_client=httpx.AsyncClient(
-                #                 headers=createHeaders(
-                #                     # api_key=self.portkey_api_key,
-                #                     provider=routing_provider,
-                #                     trace_id=str(uuid.uuid4())[:8],
-                #                 ),
-                #             ),
-                #         ),
-                #     )
+                # TODO: Add support for deepseek provider in a future update (ETA: Q2 2025)
```

---

`495-495`: **Add docstring for the new method**

Adding comprehensive documentation for this new method would improve code readability and maintainability.


```diff
def get_pydantic_model(self) -> Model | None:
+    """
+    Creates and returns a PydanticAI model instance based on the current provider configuration.
+    
+    Currently supports OpenAI and Anthropic models, with optional Portkey integration.
+    For unsupported providers, returns None.
+    
+    Returns:
+        Model | None: A PydanticAI model instance or None if the provider is not supported
+    """
    provider = self._get_provider_config("large")
    params = self._build_llm_params(provider, "large")
```

---

`486-486`: **Add docstring for the model support check method**

Adding a docstring for the new support check method would improve code clarity.


```diff
def is_current_model_supported_by_pydanticai(self) -> bool:
+    """
+    Checks if the current model provider is supported by PydanticAI.
+    
+    Currently, only OpenAI and Anthropic providers are supported.
+    
+    Returns:
+        bool: True if the provider is supported, False otherwise
+    """
    provider = self._get_provider_config("large")
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 848a0bd60ea90570e6eea557dc8e94cc0bab0f85 and 1c03706f33cb7eb10f25a99ec2bc759407e3900c.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `app/modules/intelligence/provider/provider_service.py` (4 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

<details>
<summary>app/modules/intelligence/provider/provider_service.py</summary>

488-493: Return the condition `provider in ["openai", "anthropic"]` directly

Replace with `return provider in ["openai", "anthropic"]`

(SIM103)

</details>

</details>

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T14:57:23Z
user: login: dhirenmathur
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-15T15:00:46Z

Review comments:
diff_hunk: @@ -0,0 +1,154 @@
+from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
+from app.modules.intelligence.provider.provider_service import (
+    ProviderService,
+)
+from app.modules.intelligence.tools.tool_service import ToolService
+from ..crewai_agent import AgentConfig, TaskConfig
+from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
+from typing import AsyncGenerator
+
+
+class DebugAgent(ChatAgent):
+    def __init__(
+        self,
+        llm_provider: ProviderService,
+        tools_provider: ToolService,
+    ):
+        self.tools_provider = tools_provider
+        self.rag_agent = PydanticRagAgent(
+            llm_provider,
+            AgentConfig(
+                role="Context curation agent",
+                goal="Handle querying the knowledge graph and refining the results to provide accurate and contextually rich responses.",
+                backstory="""
+                    You are a highly efficient and intelligent RAG agent capable of querying complex knowledge graphs and refining the results to generate precise and comprehensive responses.
+                    Your tasks include:
+                    1. Analyzing the user's query and formulating an effective strategy to extract relevant information from the code knowledge graph.
+                    2. Executing the query with minimal iterations, ensuring accuracy and relevance.
+                    3. Refining and enriching the initial results to provide a detailed and contextually appropriate response.
+                    4. Maintaining traceability by including relevant citations and references in your output.
+                    5. Including relevant citations in the response.
+                """,
+                tasks=[
+                    TaskConfig(
+                        description=code_gen_task_prompt,
+                        expected_output="Markdown formatted chat response to user's query grounded in provided code context and tool results",
+                    )
+                ],
+            ),
+            tools=self.tools_provider.get_tools(
+                [
+                    "get_code_from_multiple_node_ids",
+                    "get_node_neighbours_from_node_id",
+                    "get_code_from_probable_node_name",
+                    "ask_knowledge_graph_queries",
+                    "get_nodes_from_tags",
+                    "get_code_file_structure",
+                    "webpage_extractor",
+                    "github_tool",
+                ]
+            ),
+        )
+
+    async def _enriched_context(self, ctx: ChatContext) -> ChatContext:
+        if ctx.node_ids and len(ctx.node_ids) > 0:
+            code_results = await self.tools_provider.get_code_from_multiple_node_ids_tool.run_multiple(
+                ctx.project_id, ctx.node_ids
+            )
+            ctx.additional_context += (
+                f"Code referred to in the query:\n {code_results}\n"
+            )
+        return ctx
path: app/modules/intelligence/agents/chat_agents/system_agents/debug_agent.py
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Incorporate `_enriched_context` into the execution flow.**
The `_enriched_context` function appends code results to the query context but is never invoked by `run` or `run_stream`. Either remove it or call it before passing the context to `self.rag_agent` if enriched context is needed.

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 2a6cc7f
created_at: 2025-03-10T06:19:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,104 @@
+from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
+from app.modules.intelligence.provider.provider_service import (
+    ProviderService,
+)
+from app.modules.intelligence.tools.tool_service import ToolService
+from ..crewai_agent import AgentConfig, TaskConfig
+from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
+from typing import AsyncGenerator
+
+
+class UnitTestAgent(ChatAgent):
+    def __init__(
+        self,
+        llm_provider: ProviderService,
+        tools_provider: ToolService,
+    ):
+        self.llm_provider = llm_provider
+        self.tools_provider = tools_provider
+
+    def _build_agent(self) -> ChatAgent:
+        return PydanticRagAgent(
+            self.llm_provider,
+            AgentConfig(
+                role="Test Plan and Unit Test Expert",
+                goal="Create test plans and write unit tests based on user requirements",
+                backstory="""You are a seasoned AI test engineer specializing in creating robust test plans and unit tests.
+                        You aim to assist users effectively in generating and refining test plans and unit tests, ensuring they are comprehensive and tailored to the user's project requirements.""",
+                tasks=[
+                    TaskConfig(
+                        description=qna_task_prompt,
+                        expected_output="Outline the test plan and write unit tests for each node based on the test plan.",
+                    )
+                ],
+            ),
+            tools=self.tools_provider.get_tools(
+                [
+                    "get_code_from_node_id",
+                    "get_code_from_probable_node_name",
+                    "webpage_extractor",
+                    "github_tool",
+                ]
+            ),
+        )
+
+    async def _enriched_context(self, ctx: ChatContext) -> ChatContext:
+        if ctx.node_ids and len(ctx.node_ids) > 0:
+            code_results = await self.tools_provider.get_code_from_multiple_node_ids_tool.run_multiple(
+                ctx.project_id, ctx.node_ids
+            )
+            ctx.additional_context += (
+                f"Code Graph context of the node_ids in query:\n {code_results}"
+            )
+        return ctx
+
+    async def run(self, ctx: ChatContext) -> ChatAgentResponse:
+        return await self._build_agent().run(await self._enriched_context(ctx))
+
+    async def run_stream(
+        self, ctx: ChatContext
+    ) -> AsyncGenerator[ChatAgentResponse, None]:
+        ctx = await self._enriched_context(ctx)
+        async for chunk in self._build_agent().run_stream(ctx):
+            yield chunk
+
path: app/modules/intelligence/agents/chat_agents/system_agents/unit_test_agent.py
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add robust exception handling.**
`run` and `run_stream` delegate execution to `_build_agent()`. If `_build_agent()` or `_enriched_context` raises exceptions, the agent call might fail silently. Consider adding try-except blocks or error-handling strategies.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-10T06:19:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,104 @@
+from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
+from app.modules.intelligence.provider.provider_service import (
+    ProviderService,
+)
+from app.modules.intelligence.tools.tool_service import ToolService
+from ..crewai_agent import AgentConfig, TaskConfig
+from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
+from typing import AsyncGenerator
+
+
+class UnitTestAgent(ChatAgent):
+    def __init__(
+        self,
+        llm_provider: ProviderService,
+        tools_provider: ToolService,
+    ):
+        self.llm_provider = llm_provider
+        self.tools_provider = tools_provider
+
+    def _build_agent(self) -> ChatAgent:
+        return PydanticRagAgent(
+            self.llm_provider,
+            AgentConfig(
+                role="Test Plan and Unit Test Expert",
+                goal="Create test plans and write unit tests based on user requirements",
+                backstory="""You are a seasoned AI test engineer specializing in creating robust test plans and unit tests.
+                        You aim to assist users effectively in generating and refining test plans and unit tests, ensuring they are comprehensive and tailored to the user's project requirements.""",
+                tasks=[
+                    TaskConfig(
+                        description=qna_task_prompt,
+                        expected_output="Outline the test plan and write unit tests for each node based on the test plan.",
+                    )
+                ],
+            ),
+            tools=self.tools_provider.get_tools(
+                [
+                    "get_code_from_node_id",
+                    "get_code_from_probable_node_name",
+                    "webpage_extractor",
+                    "github_tool",
+                ]
+            ),
+        )
+
path: app/modules/intelligence/agents/chat_agents/system_agents/unit_test_agent.py
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Validate tool availability before building the agent.**
If any of the tools (`get_code_from_node_id`, `get_code_from_probable_node_name`, etc.) are missing from the `ToolService`, the agent might fail. Consider raising an error or handling it gracefully.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if the required tool names exist in the codebase, verifying that the tool service is configured properly.
rg -A 4 'class ToolService' 
```

Length of output: 460

---



</details>

**Ensure Tool Availability is Verified Before Agent Initialization**

After reviewing the `ToolService` definition in `app/modules/intelligence/tools/tool_service.py`, it appears that only the `webpage_extractor` tool (via `self.webpage_extractor_tool`) is clearly instantiated. There’s no evidence that the remaining tools—`get_code_from_node_id`, `get_code_from_probable_node_name`, and `github_tool`—are registered or available. To avoid potential runtime errors in the unit test agent, please ensure:

- **Tool Existence Check**: Validate that all required tools are present in the `ToolsProvider` (or `ToolService`) before invoking `get_tools` in the `_build_agent` method (located in `app/modules/intelligence/agents/chat_agents/system_agents/unit_test_agent.py`, lines 20-44).
- **Error Handling**: Consider raising an informative error or handling the absence of any tool gracefully, so that if a tool is missing, the failure is both explicit and manageable.

Addressing these points will help prevent failures when the agent is constructed with an incomplete set of tools.

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Fixed in commits 2b855fb to abdfab3
created_at: 2025-03-10T06:19:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,175 @@
+import json
+from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
+from app.modules.intelligence.provider.provider_service import (
+    ProviderService,
+)
+from app.modules.intelligence.tools.tool_service import ToolService
+from ..crewai_agent import AgentConfig, TaskConfig
+from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
+from typing import AsyncGenerator, List
+from pydantic import BaseModel
+
+
+class NodeContext(BaseModel):
+    node_id: str
+    name: str
+
+
+class IntegrationTestAgent(ChatAgent):
+    def __init__(
+        self,
+        llm_provider: ProviderService,
+        tools_provider: ToolService,
+    ):
+        self.llm_provider = llm_provider
+        self.tools_provider = tools_provider
+        self.rag_agent = PydanticRagAgent(
+            llm_provider,
+            AgentConfig(
+                role="Integration Test Writer",
+                goal="Create a comprehensive integration test suite for the provided codebase. Analyze the code, determine the appropriate testing language and framework, and write tests that cover all major integration points.",
+                backstory="""
+                    You are an expert in writing unit tests for code using latest features of the popular testing libraries for the given programming language.
+                """,
+                tasks=[
+                    TaskConfig(
+                        description=integration_test_task_prompt,
+                        expected_output="Write COMPLETE CODE for integration tests for each node based on the test plan.",
+                    )
+                ],
+            ),
+            tools=self.tools_provider.get_tools(
+                [
+                    "get_code_from_multiple_node_ids",
+                    "get_code_from_probable_node_name",
+                    "webpage_extractor",
+                    "github_tool",
+                ]
+            ),
+        )
+
+    def _enriched_context(self, ctx: ChatContext) -> ChatContext:
+        if not ctx.node_ids or len(ctx.node_ids) == 0:
+            return ctx
+
+        # Get graphs for each node to understand component relationships
+        graphs = {}
+        all_node_contexts: List[NodeContext] = []
+
+        for node_id in ctx.node_ids:
+            # Get the code graph for each node
+            graph = self.tools_provider.get_code_graph_from_node_id_tool.run(
+                ctx.project_id, node_id
+            )
+            graphs[node_id] = graph
+
+            def extract_unique_node_contexts(node, visited=None):
+                if visited is None:
+                    visited = set()
+                node_contexts: List[NodeContext] = []
+                if node["id"] not in visited:
+                    visited.add(node["id"])
+                    node_contexts.append(
+                        NodeContext(node_id=node["id"], name=node["name"])
+                    )
+                    for child in node.get("children", []):
+                        node_contexts.extend(
+                            extract_unique_node_contexts(child, visited)
+                        )
+                return node_contexts
+
+            # Extract related nodes from each graph
+            node_contexts = extract_unique_node_contexts(graph["graph"]["root_node"])
+            all_node_contexts.extend(node_contexts)
+
+        # Remove duplicates while preserving order
+        seen = set()
+        unique_node_contexts: List[NodeContext] = []
+        for node in all_node_contexts:
+            if node.node_id not in seen:
+                seen.add(node.node_id)
+                unique_node_contexts.append(node)
+
+        # Format graphs for better readability in the prompt
+        formatted_graphs = {}
+        for node_id, graph in graphs.items():
+            formatted_graphs[node_id] = {
+                "name": next(
+                    (
+                        node.name
+                        for node in unique_node_contexts
+                        if node.node_id == node_id
+                    ),
+                    "Unknown",
+                ),
+                "structure": graph["graph"]["root_node"],
+            }
+
+        ctx.additional_context += f"- Code structure is defined in multiple graphs for each component: \n{json.dumps(formatted_graphs, indent=2)}"
+        ctx.node_ids = [node.node_id for node in unique_node_contexts]
+
+        return ctx
path: app/modules/intelligence/agents/chat_agents/system_agents/integration_test_agent.py
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Check for potential None or invalid graph data.**
`_enriched_context` assumes each node has `graph["graph"]["root_node"]`. Ensure the data structure is consistent for all nodes, or handle potential missing attributes.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-10T06:19:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -724,26 +498,57 @@ async def _generate_and_stream_ai_response(
 
         agent_id = conversation.agent_ids[0]
         project_id = conversation.project_ids[0] if conversation.project_ids else None
-        supervisor = SimplifiedAgentSupervisor(self.sql_db, self.provider_service)
-        await supervisor.initialize(user_id)
+
+        try:
+            history = self.history_manager.get_session_history(user_id, conversation_id)
+            validated_history = [
+                (str(msg.content) if msg.content else msg) for msg in history
+            ]
+
+        except Exception:
+            raise ConversationServiceError("Failed to get chat history")
+
path: app/modules/conversations/conversation/conversation_service.py
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Improve exception chaining for clarity.**  
Raising a new exception without chaining can mask the original cause.  

```diff
-        except Exception:
-            raise ConversationServiceError("Failed to get chat history")
+        except Exception as e:
+            raise ConversationServiceError("Failed to get chat history") from e
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        try:
            history = self.history_manager.get_session_history(user_id, conversation_id)
            validated_history = [
                (str(msg.content) if msg.content else msg) for msg in history
            ]

        except Exception as e:
            raise ConversationServiceError("Failed to get chat history") from e
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

509-509: Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling

(B904)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-10T06:19:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,170 @@
+import json
+from typing import Any, List, AsyncGenerator
+
+from app.modules.intelligence.provider.provider_service import (
+    ProviderService,
+    AgentProvider,
+)
+from crewai import Agent, Crew, Process, Task
+from pydantic import BaseModel
+from app.modules.utils.logger import setup_logger
+from ..chat_agent import ChatAgent, ChatAgentResponse, ChatContext
+
+logger = setup_logger(__name__)
+
+
+class TaskConfig(BaseModel):
+    """Model for task configuration from agent_config.json"""
+
+    description: str
+    expected_output: str
+
+
+class AgentConfig(BaseModel):
+    """Model for agent configuration from agent_config.json"""
+
+    role: str
+    goal: str
+    backstory: str
+    tasks: List[TaskConfig]
+    max_iter: int = 15
+
+
+class CrewAIAgent(ChatAgent):
+    def __init__(
+        self,
+        llm_provider: ProviderService,
+        config: AgentConfig,
+        tools: List[Any],
+    ):
+        """Initialize the agent with configuration and tools"""
+
+        self.tasks = config.tasks
+        self.max_iter = config.max_iter
+
+        self.tools = tools
+        self.tools_description = self._create_tools_description(tools)
+
+        self.agent = Agent(
+            role=config.role,
+            goal=config.goal,
+            backstory=config.backstory,
+            tools=tools,
+            allow_delegation=False,
+            verbose=True,
+            llm=llm_provider.get_large_llm(AgentProvider.CREWAI),
+            max_iter=config.max_iter,
+        )
+
+    def _create_tools_description(self, tools: List[Any]):
+        tools_desc = "Available Tools:\n"
+        for tool in tools:
+            tools_desc += f"{tool.name}: {tool.description}\n"
+            if hasattr(tool, "args_schema"):
+                schema = tool.args_schema.schema()
+                tools_desc += (
+                    f"Input: {json.dumps(schema.get('example', {}), indent=2)}\n\n"
+                )
+        return tools_desc
+
+    def _create_task_description(
+        self,
+        task_config: TaskConfig,
+        ctx: ChatContext,
+    ) -> str:
+        """Create a task description from task configuration"""
+        if ctx.node_ids is None:
+            ctx.node_ids = []
+        if isinstance(ctx.node_ids, str):
+            ctx.node_ids = [ctx.node_ids]
+
+        return f"""
+                CONTEXT:
+                User Query: {ctx.query}
+                Project ID: {ctx.project_id}
+                Node IDs: {" ,".join(ctx.node_ids)}
+
+                Consider the chat history for any specific instructions or context: {" ,".join(ctx.history)}
+
+                Additional Context:
+                {ctx.additional_context}
+
+                TASK:
+                {task_config.description}
+
+                Expected Output:
+                {json.dumps(task_config.expected_output, indent=2)}
+
+                INSTRUCTIONS:
+                1. Use the available tools to gather information
+                2. Process and synthesize the gathered information
+                3. Format your response in markdown
+                4. Include relevant code snippets and file references
+                5. Provide clear explanations
+
+                IMPORTANT:
+                - Respect the max iterations limit of {self.max_iter} when planning and executing tools.
+                - Use tools efficiently and avoid unnecessary API calls
+                - Only use the tools listed below
+                {self.tools_description}
+
+                **Output Requirements:**
+                - Ensure that your final response MUST be a valid JSON object which follows the structure outlined in the Pydantic model: {ChatAgentResponse.model_json_schema()}
+                - Do not wrap the response in ```json, ```python, ```code, or ``` symbols.
+                - For citations, include only the `file_path` of the nodes fetched and used.
+                - Do not include any explanation or additional text outside of this JSON object.
+                - Ensure all of the expected output and code are included within the "response" string.
+
+                With above information answer the user query: {ctx.query}
+            """
path: app/modules/intelligence/agents/chat_agents/crewai_agent.py
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Task description with user input in multiline string.**  
Ensure user-provided data is sanitized and bounded to avoid layout or injection issues.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if there's any mention of input sanitization
rg -A 3 "sanitize" 
rg -A 3 "validate" 
```

Length of output: 11482

---



</details>

**Ensure Proper Sanitization of User Inputs in Multi-Line Task Descriptions**

The `_create_task_description` function directly interpolates user-provided values (like `ctx.query`, `ctx.history`, and `ctx.additional_context`) into the resulting string without any sanitization. This could allow injection or formatting issues if malicious input is supplied. Please consider the following actions:

- **Sanitize Inputs:** Apply necessary escaping/cleaning to user-provided strings to prevent layout or code injection issues.
- **Validate and Bound:** Enforce input length limits and validate the data (e.g., using regex or dedicated validation functions) before it’s included in the multiline string.
- **Consistent Handling:** If sanitization is performed elsewhere in the codebase, ensure that similar measures are applied here to maintain consistency and safeguard against injection risks.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-10T06:19:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,170 @@
+import json
+from typing import Any, List, AsyncGenerator
+
+from app.modules.intelligence.provider.provider_service import (
+    ProviderService,
+    AgentProvider,
+)
+from crewai import Agent, Crew, Process, Task
+from pydantic import BaseModel
+from app.modules.utils.logger import setup_logger
+from ..chat_agent import ChatAgent, ChatAgentResponse, ChatContext
path: app/modules/intelligence/agents/chat_agents/crewai_agent.py
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>❓ Verification inconclusive</summary>

**CrewAI and logger setup.**  
Ensure your environment pins a compatible `crewai` version to prevent version conflicts.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check installed crewai version
pip freeze | grep crewai
```

Length of output: 26

---

Let's gather a bit more information. The previous command didn't return a crewai version, so let's try verifying it by running a command that directly prints the crewai version via Python.

---

🏁 Script executed:

```shell
#!/bin/bash
python -c "import crewai; print('crewai version:', crewai.__version__)"
```

Length of output: 199

---



</details>

**Action Required: Pin and Install the Compatible CrewAI Version**

Our verification indicates that the current environment doesn't include the `crewai` package—attempting to import it results in a `ModuleNotFoundError`. This confirms the concern raised in the comment. Please update your dependency management (e.g., your `requirements.txt` or environment configuration) to include and pin a compatible version of `crewai` to avoid runtime errors and version conflicts.

- **Location:** `app/modules/intelligence/agents/chat_agents/crewai_agent.py` (lines 8-11)
- **Issue:** The environment is missing the `crewai` module, leading to a `ModuleNotFoundError`.
- **Recommendation:** Add and pin the compatible `crewai` version in your dependency file or setup configuration.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-10T06:19:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,293 @@
+import re
+from typing import List, AsyncGenerator
+
+from app.modules.intelligence.provider.provider_service import (
+    ProviderService,
+)
+from .crewai_agent import AgentConfig, TaskConfig
+from app.modules.utils.logger import setup_logger
+
+from ..chat_agent import (
+    ChatAgent,
+    ChatAgentResponse,
+    ChatContext,
+    ToolCallEventType,
+    ToolCallResponse,
+)
+from pydantic import BaseModel
+
+from pydantic_ai import Agent, Tool
+from pydantic_ai.messages import (
+    FunctionToolCallEvent,
+    FunctionToolResultEvent,
+    PartDeltaEvent,
+    TextPartDelta,
+)
+from langchain_core.tools import StructuredTool
+
+logger = setup_logger(__name__)
+
+
+class ActionWrapper(BaseModel):
+    action: str
+    action_input: str
+
path: app/modules/intelligence/agents/chat_agents/pydantic_agent.py
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Consider clarifying class purpose or removing if unused.**

`ActionWrapper` is defined, but there are no references to it throughout the file. If it’s not part of any external API contract, consider removing it or adding comments to clarify its usage.

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Fixed in commits 2b855fb to abdfab3
created_at: 2025-03-12T05:34:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,169 @@
+from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
+from app.modules.intelligence.provider.provider_service import (
+    ProviderService,
+)
+from app.modules.intelligence.tools.tool_service import ToolService
+from ..crewai_agent import AgentConfig, CrewAIAgent, TaskConfig
+from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
+from typing import AsyncGenerator
+
+
+class QnAAgent(ChatAgent):
+    def __init__(
+        self,
+        llm_provider: ProviderService,
+        tools_provider: ToolService,
+    ):
+        self.llm_provider = llm_provider
+        self.tools_provider = tools_provider
+
+    def _build_agent(self) -> ChatAgent:
+        agent_config = AgentConfig(
+            role="QNA Agent",
+            goal="Answer queries of the repo in a detail fashion",
+            backstory="""
+                    You are a highly efficient and intelligent RAG agent capable of querying complex knowledge graphs and refining the results to generate precise and comprehensive responses.
+                    Your tasks include:
+                    1. Analyzing the user's query and formulating an effective strategy to extract relevant information from the code knowledge graph.
+                    2. Executing the query with minimal iterations, ensuring accuracy and relevance.
+                    3. Refining and enriching the initial results to provide a detailed and contextually appropriate response.
+                    4. Maintaining traceability by including relevant citations and references in your output.
+                    5. Including relevant citations in the response.
+                """,
+            tasks=[
+                TaskConfig(
+                    description=qna_task_prompt,
+                    expected_output="Markdown formatted chat response to user's query grounded in provided code context and tool results",
+                )
+            ],
+        )
+        tools = self.tools_provider.get_tools(
+            [
+                "get_code_from_multiple_node_ids",
+                "get_node_neighbours_from_node_id",
+                "get_code_from_probable_node_name",
+                "ask_knowledge_graph_queries",
+                "get_nodes_from_tags",
+                "get_code_file_structure",
+                "webpage_extractor",
+                "github_tool",
+            ]
+        )
+
+        if self.llm_provider.is_current_model_supported_by_pydanticai():
+            return PydanticRagAgent(self.llm_provider, agent_config, tools)
+        else:
+            return CrewAIAgent(self.llm_provider, agent_config, tools)
+
+    async def _enriched_context(self, ctx: ChatContext) -> ChatContext:
+        if ctx.node_ids and len(ctx.node_ids) > 0:
+            code_results = await self.tools_provider.get_code_from_multiple_node_ids_tool.run_multiple(
+                ctx.project_id, ctx.node_ids
+            )
+            ctx.additional_context += (
+                f"Code Graph context of the node_ids in query:\n {code_results}"
+            )
+
+        file_structure = (
+            await self.tools_provider.file_structure_tool.fetch_repo_structure(
+                ctx.project_id
+            )
+        )
+        ctx.additional_context += f"File Structure of the project:\n {file_structure}"
+
+        return ctx
+
path: app/modules/intelligence/agents/chat_agents/system_agents/qna_agent.py
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Enhance error handling within the `_enriched_context` method**  
If the tools fail to fetch code or file structure, the method could raise exceptions or return incomplete context. Consider adding exception handling or fallback logic to avoid unexpected runtime errors.  

```diff
@@ -60,6 +60,13 @@ async def _enriched_context(self, ctx: ChatContext) -> ChatContext:
     if ctx.node_ids and len(ctx.node_ids) > 0:
+        try:
             code_results = await self.tools_provider.get_code_from_multiple_node_ids_tool.run_multiple(
                 ctx.project_id, ctx.node_ids
             )
+        except Exception as e:
+            # fallback or context update indicating code retrieval failure
+            code_results = f"Error retrieving code: {str(e)}"
+            # Alternatively, re-raise or handle appropriately
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    async def _enriched_context(self, ctx: ChatContext) -> ChatContext:
        if ctx.node_ids and len(ctx.node_ids) > 0:
            try:
                code_results = await self.tools_provider.get_code_from_multiple_node_ids_tool.run_multiple(
                    ctx.project_id, ctx.node_ids
                )
            except Exception as e:
                # fallback or context update indicating code retrieval failure
                code_results = f"Error retrieving code: {str(e)}"
                # Alternatively, re-raise or handle appropriately
            ctx.additional_context += (
                f"Code Graph context of the node_ids in query:\n {code_results}"
            )

        file_structure = (
            await self.tools_provider.file_structure_tool.fetch_repo_structure(
                ctx.project_id
            )
        )
        ctx.additional_context += f"File Structure of the project:\n {file_structure}"

        return ctx
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-13T05:23:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,169 @@
+from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
+from app.modules.intelligence.provider.provider_service import (
+    ProviderService,
+)
+from app.modules.intelligence.tools.tool_service import ToolService
+from ..crewai_agent import AgentConfig, CrewAIAgent, TaskConfig
+from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
+from typing import AsyncGenerator
+
+
+class QnAAgent(ChatAgent):
+    def __init__(
+        self,
+        llm_provider: ProviderService,
+        tools_provider: ToolService,
+    ):
+        self.llm_provider = llm_provider
+        self.tools_provider = tools_provider
+
+    def _build_agent(self) -> ChatAgent:
+        agent_config = AgentConfig(
+            role="QNA Agent",
+            goal="Answer queries of the repo in a detail fashion",
+            backstory="""
+                    You are a highly efficient and intelligent RAG agent capable of querying complex knowledge graphs and refining the results to generate precise and comprehensive responses.
+                    Your tasks include:
+                    1. Analyzing the user's query and formulating an effective strategy to extract relevant information from the code knowledge graph.
+                    2. Executing the query with minimal iterations, ensuring accuracy and relevance.
+                    3. Refining and enriching the initial results to provide a detailed and contextually appropriate response.
+                    4. Maintaining traceability by including relevant citations and references in your output.
+                    5. Including relevant citations in the response.
+                """,
+            tasks=[
+                TaskConfig(
+                    description=qna_task_prompt,
+                    expected_output="Markdown formatted chat response to user's query grounded in provided code context and tool results",
+                )
+            ],
+        )
+        tools = self.tools_provider.get_tools(
+            [
+                "get_code_from_multiple_node_ids",
+                "get_node_neighbours_from_node_id",
+                "get_code_from_probable_node_name",
+                "ask_knowledge_graph_queries",
+                "get_nodes_from_tags",
+                "get_code_file_structure",
+                "webpage_extractor",
+                "github_tool",
+            ]
+        )
+
+        if self.llm_provider.is_current_model_supported_by_pydanticai():
+            return PydanticRagAgent(self.llm_provider, agent_config, tools)
+        else:
+            return CrewAIAgent(self.llm_provider, agent_config, tools)
+
path: app/modules/intelligence/agents/chat_agents/system_agents/qna_agent.py
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Validate the availability of the specified engines before returning an agent**  
Currently, if `PydanticRagAgent` or `CrewAIAgent` dependencies are not installed or fail to load, the code will raise an exception. Ensure that these dependencies and associated models are properly handled.

---

🏁 Script executed:

```shell
#!/bin/bash
# Checks for references to PydanticRagAgent and CrewAIAgent classes
rg -A 3 $'PydanticRagAgent'
rg -A 3 $'CrewAIAgent'
```

Length of output: 14612

---



</details>

**Dependency Validation Required for Engine Instantiation**

In file `app/modules/intelligence/agents/chat_agents/system_agents/qna_agent.py` (lines 20–57), the agent is built by directly instantiating either `PydanticRagAgent` or `CrewAIAgent` based on the result of `self.llm_provider.is_current_model_supported_by_pydanticai()`. However, there isn’t any check to verify that the corresponding dependencies are correctly installed or loadable. If either dependency is missing or fails during instantiation, an exception will be raised and potentially crash the system.

**Recommendations:**

- **Add Dependency Checks:** Before returning an agent, validate that the necessary modules for `PydanticRagAgent` and `CrewAIAgent` are available.  
- **Implement Error Handling:** Consider wrapping the instantiation inside a try/except block. Log the error and either fall back to a safe default behavior or raise a custom error (e.g., `AgentInitializationError`) with a clear message.
- **Example Diff Snippet:**

  ```diff
   def _build_agent(self) -> ChatAgent:
       agent_config = AgentConfig(
           role="QNA Agent",
           goal="Answer queries of the repo in a detail fashion",
           backstory=""" ... """
           ,
           tasks=[
               TaskConfig(
                   description=qna_task_prompt,
                   expected_output="Markdown formatted chat response to user's query grounded in provided code context and tool results",
               )
           ],
       )
       tools = self.tools_provider.get_tools(
           [
               "get_code_from_multiple_node_ids",
               "get_node_neighbours_from_node_id",
               "get_code_from_probable_node_name",
               "ask_knowledge_graph_queries",
               "get_nodes_from_tags",
               "get_code_file_structure",
               "webpage_extractor",
               "github_tool",
           ]
       )
-        if self.llm_provider.is_current_model_supported_by_pydanticai():
-            return PydanticRagAgent(self.llm_provider, agent_config, tools)
-        else:
-            return CrewAIAgent(self.llm_provider, agent_config, tools)
+        try:
+            if self.llm_provider.is_current_model_supported_by_pydanticai():
+                return PydanticRagAgent(self.llm_provider, agent_config, tools)
+            else:
+                return CrewAIAgent(self.llm_provider, agent_config, tools)
+        except Exception as e:
+            logger.error("Agent instantiation failed due to missing dependency or initialization error: %s", e)
+            raise AgentInitializationError("Failed to initialize the agent due to dependency issues") from e
  ```

Implementing these changes will improve resilience by ensuring that missing or failing dependencies are handled gracefully, preventing unexpected runtime errors.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    def _build_agent(self) -> ChatAgent:
        agent_config = AgentConfig(
            role="QNA Agent",
            goal="Answer queries of the repo in a detail fashion",
            backstory="""
                    You are a highly efficient and intelligent RAG agent capable of querying complex knowledge graphs and refining the results to generate precise and comprehensive responses.
                    Your tasks include:
                    1. Analyzing the user's query and formulating an effective strategy to extract relevant information from the code knowledge graph.
                    2. Executing the query with minimal iterations, ensuring accuracy and relevance.
                    3. Refining and enriching the initial results to provide a detailed and contextually appropriate response.
                    4. Maintaining traceability by including relevant citations and references in your output.
                    5. Including relevant citations in the response.
                """,
            tasks=[
                TaskConfig(
                    description=qna_task_prompt,
                    expected_output="Markdown formatted chat response to user's query grounded in provided code context and tool results",
                )
            ],
        )
        tools = self.tools_provider.get_tools(
            [
                "get_code_from_multiple_node_ids",
                "get_node_neighbours_from_node_id",
                "get_code_from_probable_node_name",
                "ask_knowledge_graph_queries",
                "get_nodes_from_tags",
                "get_code_file_structure",
                "webpage_extractor",
                "github_tool",
            ]
        )

        try:
            if self.llm_provider.is_current_model_supported_by_pydanticai():
                return PydanticRagAgent(self.llm_provider, agent_config, tools)
            else:
                return CrewAIAgent(self.llm_provider, agent_config, tools)
        except Exception as e:
            logger.error("Agent instantiation failed due to missing dependency or initialization error: %s", e)
            raise AgentInitializationError("Failed to initialize the agent due to dependency issues") from e
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-13T05:23:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -63,3 +63,4 @@ tiktoken==0.7.0
 agentops==0.3.26
 pydantic[email]==2.10.3
 firecrawl-py==1.11.1
+pydantic_ai==0.0.35
path: requirements.txt
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**LGTM: Added pydantic_ai dependency**

The pydantic_ai package has been added with version 0.0.35, which supports the new PYDANTICAI provider implementation.





---

🏁 Script executed:

```shell
#!/bin/bash
# Check if this version of pydantic_ai is the latest available
echo "Checking latest version of pydantic_ai..."
curl -s https://pypi.org/pypi/pydantic_ai/json | jq -r '.info.version'
```

Length of output: 170

---



</details>

**Action Required: Update pydantic_ai Version**

After verifying the latest release on PyPI, the current dependency in `requirements.txt` is pinned to version 0.0.35, but version 0.0.37 is now available. For consistency and to benefit from the latest improvements and fixes, please update the version declaration:

- **requirements.txt (Line 66):**  
  - From: `pydantic_ai==0.0.35`  
  - To:   `pydantic_ai==0.0.37`

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-13T05:23:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,105 @@
+from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
+from app.modules.intelligence.provider.provider_service import (
+    ProviderService,
+)
+from app.modules.intelligence.tools.tool_service import ToolService
+from ..crewai_agent import AgentConfig, CrewAIAgent, TaskConfig
+from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
+from typing import AsyncGenerator
+
+
+class UnitTestAgent(ChatAgent):
+    def __init__(
+        self,
+        llm_provider: ProviderService,
+        tools_provider: ToolService,
+    ):
+        self.llm_provider = llm_provider
+        self.tools_provider = tools_provider
+
+    def _build_agent(self) -> ChatAgent:
+        agent_config = AgentConfig(
+            role="Test Plan and Unit Test Expert",
+            goal="Create test plans and write unit tests based on user requirements",
+            backstory="""You are a seasoned AI test engineer specializing in creating robust test plans and unit tests.
+                        You aim to assist users effectively in generating and refining test plans and unit tests, ensuring they are comprehensive and tailored to the user's project requirements.""",
+            tasks=[
+                TaskConfig(
+                    description=qna_task_prompt,
+                    expected_output="Outline the test plan and write unit tests for each node based on the test plan.",
+                )
+            ],
+        )
+        tools = self.tools_provider.get_tools(
+            [
+                "get_code_from_node_id",
+                "get_code_from_probable_node_name",
+                "webpage_extractor",
+                "github_tool",
+            ]
+        )
+        if self.llm_provider.is_current_model_supported_by_pydanticai():
+            return PydanticRagAgent(self.llm_provider, agent_config, tools)
+        else:
+            return CrewAIAgent(self.llm_provider, agent_config, tools)
path: app/modules/intelligence/agents/chat_agents/system_agents/unit_test_agent.py
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Validate tool availability before building the agent.**

The agent requests specific tools but doesn't validate their existence before attempting to use them. If these tools aren't available, errors might occur later during execution.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if the tools requested by UnitTestAgent are actually registered in ToolService
rg -A 10 "def get_tools\(" app/modules/intelligence/tools/
rg "get_code_from_node_id.*tool" app/modules/intelligence/tools/
rg "get_code_from_probable_node_name.*tool" app/modules/intelligence/tools/
rg "webpage_extractor.*tool" app/modules/intelligence/tools/
rg "github_tool" app/modules/intelligence/tools/
```

Length of output: 3663

---



</details>

**Validate Tool Availability Before Agent Construction**

The current code in `app/modules/intelligence/agents/chat_agents/system_agents/unit_test_agent.py` retrieves tools via `self.tools_provider.get_tools(...)` but does not explicitly confirm that all the required tools are present. While `ToolService.get_tools()` already filters out missing tools, relying on this behavior can lead to subtle runtime errors if any mandatory tool (like `"get_code_from_node_id"`, `"get_code_from_probable_node_name"`, `"webpage_extractor"`, or `"github_tool"`) isn’t registered correctly.

**Recommendation:**
- **Explicitly check** that the returned list contains all the expected tools (e.g., ensure the list’s length equals 4) immediately after retrieval.
- If the check fails, the code should either log an informative error or raise an exception to fail fast and help pinpoint the missing tool(s).

*Example snippet for improvement:*

```python
tools = self.tools_provider.get_tools(
    [
        "get_code_from_node_id",
        "get_code_from_probable_node_name",
        "webpage_extractor",
        "github_tool",
    ]
)

# Validate that all expected tools are present
required_tool_count = 4
if len(tools) != required_tool_count:
    raise RuntimeError(f"Agent initialization failed: Expected {required_tool_count} tools, but only received {len(tools)}. Missing required tool(s).")

if self.llm_provider.is_current_model_supported_by_pydanticai():
    return PydanticRagAgent(self.llm_provider, agent_config, tools)
else:
    return CrewAIAgent(self.llm_provider, agent_config, tools)
```

Implementing this check will help prevent potential errors later during agent execution if one or more tools are inadvertently missing.

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits f1bd555 to 848a0bd
created_at: 2025-03-13T05:23:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,105 @@
+from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
+from app.modules.intelligence.provider.provider_service import (
+    ProviderService,
+)
+from app.modules.intelligence.tools.tool_service import ToolService
+from ..crewai_agent import AgentConfig, CrewAIAgent, TaskConfig
+from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
+from typing import AsyncGenerator
+
+
+class UnitTestAgent(ChatAgent):
+    def __init__(
+        self,
+        llm_provider: ProviderService,
+        tools_provider: ToolService,
+    ):
+        self.llm_provider = llm_provider
+        self.tools_provider = tools_provider
+
+    def _build_agent(self) -> ChatAgent:
+        agent_config = AgentConfig(
+            role="Test Plan and Unit Test Expert",
+            goal="Create test plans and write unit tests based on user requirements",
+            backstory="""You are a seasoned AI test engineer specializing in creating robust test plans and unit tests.
+                        You aim to assist users effectively in generating and refining test plans and unit tests, ensuring they are comprehensive and tailored to the user's project requirements.""",
+            tasks=[
+                TaskConfig(
+                    description=qna_task_prompt,
+                    expected_output="Outline the test plan and write unit tests for each node based on the test plan.",
+                )
+            ],
+        )
+        tools = self.tools_provider.get_tools(
+            [
+                "get_code_from_node_id",
+                "get_code_from_probable_node_name",
+                "webpage_extractor",
+                "github_tool",
+            ]
+        )
+        if self.llm_provider.is_current_model_supported_by_pydanticai():
+            return PydanticRagAgent(self.llm_provider, agent_config, tools)
+        else:
+            return CrewAIAgent(self.llm_provider, agent_config, tools)
+
+    async def _enriched_context(self, ctx: ChatContext) -> ChatContext:
+        if ctx.node_ids and len(ctx.node_ids) > 0:
+            code_results = await self.tools_provider.get_code_from_multiple_node_ids_tool.run_multiple(
+                ctx.project_id, ctx.node_ids
+            )
+            ctx.additional_context += (
+                f"Code Graph context of the node_ids in query:\n {code_results}"
+            )
+        return ctx
path: app/modules/intelligence/agents/chat_agents/system_agents/unit_test_agent.py
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add validation for tool operation result.**

The `_enriched_context` method assumes `get_code_from_multiple_node_ids_tool.run_multiple()` always returns valid results without checking or handling potential errors.


```diff
async def _enriched_context(self, ctx: ChatContext) -> ChatContext:
    if ctx.node_ids and len(ctx.node_ids) > 0:
-        code_results = await self.tools_provider.get_code_from_multiple_node_ids_tool.run_multiple(
-            ctx.project_id, ctx.node_ids
-        )
-        ctx.additional_context += (
-            f"Code Graph context of the node_ids in query:\n {code_results}"
-        )
+        try:
+            code_results = await self.tools_provider.get_code_from_multiple_node_ids_tool.run_multiple(
+                ctx.project_id, ctx.node_ids
+            )
+            if code_results:
+                ctx.additional_context += (
+                    f"Code Graph context of the node_ids in query:\n {code_results}"
+                )
+            else:
+                ctx.additional_context += (
+                    "Note: Attempted to retrieve code for node_ids but no results were found."
+                )
+        except Exception as e:
+            # Log the error but continue with reduced context
+            ctx.additional_context += (
+                f"Note: Failed to retrieve code for node_ids: {str(e)}"
+            )
    return ctx
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    async def _enriched_context(self, ctx: ChatContext) -> ChatContext:
        if ctx.node_ids and len(ctx.node_ids) > 0:
            try:
                code_results = await self.tools_provider.get_code_from_multiple_node_ids_tool.run_multiple(
                    ctx.project_id, ctx.node_ids
                )
                if code_results:
                    ctx.additional_context += (
                        f"Code Graph context of the node_ids in query:\n {code_results}"
                    )
                else:
                    ctx.additional_context += (
                        "Note: Attempted to retrieve code for node_ids but no results were found."
                    )
            except Exception as e:
                # Log the error but continue with reduced context
                ctx.additional_context += (
                    f"Note: Failed to retrieve code for node_ids: {str(e)}"
                )
        return ctx
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits f1bd555 to 848a0bd
created_at: 2025-03-13T05:23:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,177 @@
+import json
+from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
+from app.modules.intelligence.provider.provider_service import (
+    ProviderService,
+)
+from app.modules.intelligence.tools.tool_service import ToolService
+from ..crewai_agent import AgentConfig, CrewAIAgent, TaskConfig
+from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
+from typing import AsyncGenerator, List
+from pydantic import BaseModel
+
+
+class NodeContext(BaseModel):
+    node_id: str
+    name: str
+
+
+class IntegrationTestAgent(ChatAgent):
+    def __init__(
+        self,
+        llm_provider: ProviderService,
+        tools_provider: ToolService,
+    ):
+        self.llm_provider = llm_provider
+        self.tools_provider = tools_provider
+
+        agent_config = AgentConfig(
+            role="Integration Test Writer",
+            goal="Create a comprehensive integration test suite for the provided codebase. Analyze the code, determine the appropriate testing language and framework, and write tests that cover all major integration points.",
+            backstory="""
+                    You are an expert in writing unit tests for code using latest features of the popular testing libraries for the given programming language.
+                """,
+            tasks=[
+                TaskConfig(
+                    description=integration_test_task_prompt,
+                    expected_output="Write COMPLETE CODE for integration tests for each node based on the test plan.",
+                )
+            ],
+        )
+        tools = self.tools_provider.get_tools(
+            [
+                "get_code_from_multiple_node_ids",
+                "get_code_from_probable_node_name",
+                "webpage_extractor",
+                "github_tool",
+            ]
+        )
+
+        self.rag_agent = CrewAIAgent(llm_provider, agent_config, tools)
+        if llm_provider.is_current_model_supported_by_pydanticai():
+            self.rag_agent = PydanticRagAgent(llm_provider, agent_config, tools)
+
+    def _enriched_context(self, ctx: ChatContext) -> ChatContext:
+        if not ctx.node_ids or len(ctx.node_ids) == 0:
+            return ctx
+
+        # Get graphs for each node to understand component relationships
+        graphs = {}
+        all_node_contexts: List[NodeContext] = []
+
+        for node_id in ctx.node_ids:
+            # Get the code graph for each node
+            graph = self.tools_provider.get_code_graph_from_node_id_tool.run(
+                ctx.project_id, node_id
+            )
+            graphs[node_id] = graph
+
+            def extract_unique_node_contexts(node, visited=None):
+                if visited is None:
+                    visited = set()
+                node_contexts: List[NodeContext] = []
+                if node["id"] not in visited:
+                    visited.add(node["id"])
+                    node_contexts.append(
+                        NodeContext(node_id=node["id"], name=node["name"])
+                    )
+                    for child in node.get("children", []):
+                        node_contexts.extend(
+                            extract_unique_node_contexts(child, visited)
+                        )
+                return node_contexts
+
+            # Extract related nodes from each graph
+            node_contexts = extract_unique_node_contexts(graph["graph"]["root_node"])
+            all_node_contexts.extend(node_contexts)
+
+        # Remove duplicates while preserving order
+        seen = set()
+        unique_node_contexts: List[NodeContext] = []
+        for node in all_node_contexts:
+            if node.node_id not in seen:
+                seen.add(node.node_id)
+                unique_node_contexts.append(node)
+
+        # Format graphs for better readability in the prompt
+        formatted_graphs = {}
+        for node_id, graph in graphs.items():
+            formatted_graphs[node_id] = {
+                "name": next(
+                    (
+                        node.name
+                        for node in unique_node_contexts
+                        if node.node_id == node_id
+                    ),
+                    "Unknown",
+                ),
+                "structure": graph["graph"]["root_node"],
+            }
+
+        ctx.additional_context += f"- Code structure is defined in multiple graphs for each component: \n{json.dumps(formatted_graphs, indent=2)}"
+        ctx.node_ids = [node.node_id for node in unique_node_contexts]
+
+        return ctx
+
+    async def run(self, ctx: ChatContext) -> ChatAgentResponse:
+        return await self.rag_agent.run(self._enriched_context(ctx))
+
+    async def run_stream(
+        self, ctx: ChatContext
+    ) -> AsyncGenerator[ChatAgentResponse, None]:
+        ctx = self._enriched_context(ctx)
+        async for chunk in self.rag_agent.run_stream(ctx):
+            yield chunk
path: app/modules/intelligence/agents/chat_agents/system_agents/integration_test_agent.py
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Add exception handling in run methods.**

The `run` and `run_stream` methods lack error handling. If `_enriched_context` or the agent's run methods fail, there's no graceful error handling or contextual error information.


```diff
async def run(self, ctx: ChatContext) -> ChatAgentResponse:
-    return await self.rag_agent.run(self._enriched_context(ctx))
+    try:
+        enriched_ctx = self._enriched_context(ctx)
+        return await self.rag_agent.run(enriched_ctx)
+    except Exception as e:
+        # Log the error and provide context
+        raise Exception(f"Failed to run IntegrationTestAgent: {str(e)}") from e

async def run_stream(
    self, ctx: ChatContext
) -> AsyncGenerator[ChatAgentResponse, None]:
-    ctx = self._enriched_context(ctx)
-    async for chunk in self.rag_agent.run_stream(ctx):
-        yield chunk
+    try:
+        ctx = self._enriched_context(ctx)
+        async for chunk in self.rag_agent.run_stream(ctx):
+            yield chunk
+    except Exception as e:
+        # Log the error and provide context
+        raise Exception(f"Failed to run IntegrationTestAgent stream: {str(e)}") from e
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    async def run(self, ctx: ChatContext) -> ChatAgentResponse:
        try:
            enriched_ctx = self._enriched_context(ctx)
            return await self.rag_agent.run(enriched_ctx)
        except Exception as e:
            # Log the error and provide context
            raise Exception(f"Failed to run IntegrationTestAgent: {str(e)}") from e

    async def run_stream(
        self, ctx: ChatContext
    ) -> AsyncGenerator[ChatAgentResponse, None]:
        try:
            ctx = self._enriched_context(ctx)
            async for chunk in self.rag_agent.run_stream(ctx):
                yield chunk
        except Exception as e:
            # Log the error and provide context
            raise Exception(f"Failed to run IntegrationTestAgent stream: {str(e)}") from e
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits f1bd555 to 848a0bd
created_at: 2025-03-13T05:23:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,231 @@
+from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
+from app.modules.intelligence.provider.provider_service import (
+    ProviderService,
+)
+from app.modules.intelligence.tools.tool_service import ToolService
+from ..crewai_agent import AgentConfig, CrewAIAgent, TaskConfig
+from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
+from typing import AsyncGenerator
+
+
+class CodeGenAgent(ChatAgent):
+    def __init__(
+        self,
+        llm_provider: ProviderService,
+        tools_provider: ToolService,
+    ):
+        self.llm_provider = llm_provider
+        self.tools_provider = tools_provider
+
+    def _build_agent(self) -> ChatAgent:
+        agent_config = AgentConfig(
+            role="Code Generation Agent",
+            goal="Generate precise, copy-paste ready code modifications that maintain project consistency and handle all dependencies",
+            backstory="""
+                    You are an expert code generation agent specialized in creating production-ready,
+                    immediately usable code modifications. Your primary responsibilities include:
+                    1. Analyzing existing codebase context and understanding dependencies
+                    2. Planning code changes that maintain exact project patterns and style
+                    3. Implementing changes with copy-paste ready output
+                    4. Following existing code conventions exactly as shown in the input files
+                    5. Never modifying string literals, escape characters, or formatting unless specifically requested
+
+                    Key principles:
+                    - Provide required new imports in a separate code block
+                    - Output only the specific functions/classes being modified
+                    - Never change existing string formats or escape characters
+                    - Maintain exact indentation and spacing patterns from original code
+                    - Include clear section markers for where code should be inserted/modified
+                """,
+            tasks=[
+                TaskConfig(
+                    description=code_gen_task_prompt,
+                    expected_output="User-friendly, clearly structured code changes with comprehensive dependency analysis, implementation details for ALL impacted files, and complete verification steps",
+                )
+            ],
+        )
+        tools = self.tools_provider.get_tools(
+            [
+                "get_code_from_multiple_node_ids",
+                "get_node_neighbours_from_node_id",
+                "get_code_from_probable_node_name",
+                "ask_knowledge_graph_queries",
+                "get_nodes_from_tags",
+                "get_code_file_structure",
+                "webpage_extractor",
+                "github_tool",
+            ]
+        )
+        if self.llm_provider.is_current_model_supported_by_pydanticai():
+            return PydanticRagAgent(self.llm_provider, agent_config, tools)
+        else:
+            return CrewAIAgent(self.llm_provider, agent_config, tools)
+
+    async def _enriched_context(self, ctx: ChatContext) -> ChatContext:
+        if ctx.node_ids and len(ctx.node_ids) > 0:
+            code_results = await self.tools_provider.get_code_from_multiple_node_ids_tool.run_multiple(
+                ctx.project_id, ctx.node_ids
+            )
+            ctx.additional_context += (
+                f"Code referred to in the query:\n {code_results}\n"
+            )
+        return ctx
+
+    async def run(self, ctx: ChatContext) -> ChatAgentResponse:
+        return await self._build_agent().run(await self._enriched_context(ctx))
+
+    async def run_stream(
+        self, ctx: ChatContext
+    ) -> AsyncGenerator[ChatAgentResponse, None]:
+        ctx = await self._enriched_context(ctx)
+        async for chunk in self._build_agent().run_stream(ctx):
+            yield chunk
path: app/modules/intelligence/agents/chat_agents/system_agents/code_gen_agent.py
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Add exception handling to run methods.**

Both `run` and `run_stream` methods lack error handling for potential failures in `_build_agent()` or `_enriched_context()`, which could lead to unhandled exceptions and poor user experience.


```diff
async def run(self, ctx: ChatContext) -> ChatAgentResponse:
-    return await self._build_agent().run(await self._enriched_context(ctx))
+    try:
+        enriched_ctx = await self._enriched_context(ctx)
+        agent = self._build_agent()
+        return await agent.run(enriched_ctx)
+    except Exception as e:
+        # Log the error and provide context
+        raise Exception(f"Failed to run CodeGenAgent: {str(e)}") from e

async def run_stream(
    self, ctx: ChatContext
) -> AsyncGenerator[ChatAgentResponse, None]:
-    ctx = await self._enriched_context(ctx)
-    async for chunk in self._build_agent().run_stream(ctx):
-        yield chunk
+    try:
+        ctx = await self._enriched_context(ctx)
+        agent = self._build_agent()
+        async for chunk in agent.run_stream(ctx):
+            yield chunk
+    except Exception as e:
+        # Log the error and provide context
+        raise Exception(f"Failed to run CodeGenAgent stream: {str(e)}") from e
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    async def run(self, ctx: ChatContext) -> ChatAgentResponse:
        try:
            enriched_ctx = await self._enriched_context(ctx)
            agent = self._build_agent()
            return await agent.run(enriched_ctx)
        except Exception as e:
            # Log the error and provide context
            raise Exception(f"Failed to run CodeGenAgent: {str(e)}") from e

    async def run_stream(
        self, ctx: ChatContext
    ) -> AsyncGenerator[ChatAgentResponse, None]:
        try:
            ctx = await self._enriched_context(ctx)
            agent = self._build_agent()
            async for chunk in agent.run_stream(ctx):
                yield chunk
        except Exception as e:
            # Log the error and provide context
            raise Exception(f"Failed to run CodeGenAgent stream: {str(e)}") from e
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits f1bd555 to 848a0bd
created_at: 2025-03-13T05:23:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,231 @@
+from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
+from app.modules.intelligence.provider.provider_service import (
+    ProviderService,
+)
+from app.modules.intelligence.tools.tool_service import ToolService
+from ..crewai_agent import AgentConfig, CrewAIAgent, TaskConfig
+from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
+from typing import AsyncGenerator
+
+
+class CodeGenAgent(ChatAgent):
+    def __init__(
+        self,
+        llm_provider: ProviderService,
+        tools_provider: ToolService,
+    ):
+        self.llm_provider = llm_provider
+        self.tools_provider = tools_provider
+
+    def _build_agent(self) -> ChatAgent:
+        agent_config = AgentConfig(
+            role="Code Generation Agent",
+            goal="Generate precise, copy-paste ready code modifications that maintain project consistency and handle all dependencies",
+            backstory="""
+                    You are an expert code generation agent specialized in creating production-ready,
+                    immediately usable code modifications. Your primary responsibilities include:
+                    1. Analyzing existing codebase context and understanding dependencies
+                    2. Planning code changes that maintain exact project patterns and style
+                    3. Implementing changes with copy-paste ready output
+                    4. Following existing code conventions exactly as shown in the input files
+                    5. Never modifying string literals, escape characters, or formatting unless specifically requested
+
+                    Key principles:
+                    - Provide required new imports in a separate code block
+                    - Output only the specific functions/classes being modified
+                    - Never change existing string formats or escape characters
+                    - Maintain exact indentation and spacing patterns from original code
+                    - Include clear section markers for where code should be inserted/modified
+                """,
+            tasks=[
+                TaskConfig(
+                    description=code_gen_task_prompt,
+                    expected_output="User-friendly, clearly structured code changes with comprehensive dependency analysis, implementation details for ALL impacted files, and complete verification steps",
+                )
+            ],
+        )
+        tools = self.tools_provider.get_tools(
+            [
+                "get_code_from_multiple_node_ids",
+                "get_node_neighbours_from_node_id",
+                "get_code_from_probable_node_name",
+                "ask_knowledge_graph_queries",
+                "get_nodes_from_tags",
+                "get_code_file_structure",
+                "webpage_extractor",
+                "github_tool",
+            ]
+        )
+        if self.llm_provider.is_current_model_supported_by_pydanticai():
+            return PydanticRagAgent(self.llm_provider, agent_config, tools)
+        else:
+            return CrewAIAgent(self.llm_provider, agent_config, tools)
+
+    async def _enriched_context(self, ctx: ChatContext) -> ChatContext:
+        if ctx.node_ids and len(ctx.node_ids) > 0:
+            code_results = await self.tools_provider.get_code_from_multiple_node_ids_tool.run_multiple(
+                ctx.project_id, ctx.node_ids
+            )
+            ctx.additional_context += (
+                f"Code referred to in the query:\n {code_results}\n"
+            )
+        return ctx
path: app/modules/intelligence/agents/chat_agents/system_agents/code_gen_agent.py
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Add robust error handling for tool operations.**

The `_enriched_context` method assumes that tool operations will always succeed without proper error handling. This could lead to silent failures or confusing errors during execution.


```diff
async def _enriched_context(self, ctx: ChatContext) -> ChatContext:
    if ctx.node_ids and len(ctx.node_ids) > 0:
-        code_results = await self.tools_provider.get_code_from_multiple_node_ids_tool.run_multiple(
-            ctx.project_id, ctx.node_ids
-        )
-        ctx.additional_context += (
-            f"Code referred to in the query:\n {code_results}\n"
-        )
+        try:
+            code_results = await self.tools_provider.get_code_from_multiple_node_ids_tool.run_multiple(
+                ctx.project_id, ctx.node_ids
+            )
+            if code_results:
+                ctx.additional_context += (
+                    f"Code referred to in the query:\n {code_results}\n"
+                )
+            else:
+                ctx.additional_context += (
+                    "Note: Attempted to retrieve code for referenced node_ids, but no results were found.\n"
+                )
+        except Exception as e:
+            # Log the error but continue with reduced context
+            ctx.additional_context += (
+                f"Note: Failed to retrieve code for node_ids due to: {str(e)}\n"
+            )
    return ctx
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    async def _enriched_context(self, ctx: ChatContext) -> ChatContext:
        if ctx.node_ids and len(ctx.node_ids) > 0:
            try:
                code_results = await self.tools_provider.get_code_from_multiple_node_ids_tool.run_multiple(
                    ctx.project_id, ctx.node_ids
                )
                if code_results:
                    ctx.additional_context += (
                        f"Code referred to in the query:\n {code_results}\n"
                    )
                else:
                    ctx.additional_context += (
                        "Note: Attempted to retrieve code for referenced node_ids, but no results were found.\n"
                    )
            except Exception as e:
                # Log the error but continue with reduced context
                ctx.additional_context += (
                    f"Note: Failed to retrieve code for node_ids due to: {str(e)}\n"
                )
        return ctx
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits f1bd555 to 848a0bd
created_at: 2025-03-13T05:23:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,231 @@
+from app.modules.intelligence.agents.chat_agents.pydantic_agent import PydanticRagAgent
+from app.modules.intelligence.provider.provider_service import (
+    ProviderService,
+)
+from app.modules.intelligence.tools.tool_service import ToolService
+from ..crewai_agent import AgentConfig, CrewAIAgent, TaskConfig
+from ...chat_agent import ChatAgent, ChatAgentResponse, ChatContext
+from typing import AsyncGenerator
+
+
+class CodeGenAgent(ChatAgent):
+    def __init__(
+        self,
+        llm_provider: ProviderService,
+        tools_provider: ToolService,
+    ):
+        self.llm_provider = llm_provider
+        self.tools_provider = tools_provider
+
+    def _build_agent(self) -> ChatAgent:
+        agent_config = AgentConfig(
+            role="Code Generation Agent",
+            goal="Generate precise, copy-paste ready code modifications that maintain project consistency and handle all dependencies",
+            backstory="""
+                    You are an expert code generation agent specialized in creating production-ready,
+                    immediately usable code modifications. Your primary responsibilities include:
+                    1. Analyzing existing codebase context and understanding dependencies
+                    2. Planning code changes that maintain exact project patterns and style
+                    3. Implementing changes with copy-paste ready output
+                    4. Following existing code conventions exactly as shown in the input files
+                    5. Never modifying string literals, escape characters, or formatting unless specifically requested
+
+                    Key principles:
+                    - Provide required new imports in a separate code block
+                    - Output only the specific functions/classes being modified
+                    - Never change existing string formats or escape characters
+                    - Maintain exact indentation and spacing patterns from original code
+                    - Include clear section markers for where code should be inserted/modified
+                """,
+            tasks=[
+                TaskConfig(
+                    description=code_gen_task_prompt,
+                    expected_output="User-friendly, clearly structured code changes with comprehensive dependency analysis, implementation details for ALL impacted files, and complete verification steps",
+                )
+            ],
+        )
+        tools = self.tools_provider.get_tools(
+            [
+                "get_code_from_multiple_node_ids",
+                "get_node_neighbours_from_node_id",
+                "get_code_from_probable_node_name",
+                "ask_knowledge_graph_queries",
+                "get_nodes_from_tags",
+                "get_code_file_structure",
+                "webpage_extractor",
+                "github_tool",
+            ]
+        )
path: app/modules/intelligence/agents/chat_agents/system_agents/code_gen_agent.py
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Validate tool availability before agent creation.**

The agent requests a large number of tools but doesn't verify their existence before attempting to use them. This could lead to runtime errors if any tools are missing.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if all the tools required by CodeGenAgent exist in the codebase
echo "Checking for tool existence in the ToolService class..."
rg -A 3 "def get_tools\(" app/modules/intelligence/tools/

echo "Checking specific tools requested by CodeGenAgent..."
tools=(
  "get_code_from_multiple_node_ids"
  "get_node_neighbours_from_node_id"
  "get_code_from_probable_node_name"
  "ask_knowledge_graph_queries"
  "get_nodes_from_tags"
  "get_code_file_structure"
  "webpage_extractor"
  "github_tool"
)

for tool in "${tools[@]}"; do
  echo "Looking for tool: $tool"
  rg "$tool.*tool" app/modules/intelligence/tools/
done
```

Length of output: 6417

---



</details>

**Action: Add explicit validation of loaded tools in CodeGenAgent initialization**

Our investigation confirms that every tool listed in the agent’s call to `self.tools_provider.get_tools` currently exists in the ToolService. However, the agent still doesn’t perform an explicit check to ensure that all the requested tools were loaded. This could become a risk if future changes cause any of these tools to be missing or misconfigured. Please consider adding a validation step—such as verifying that the number (or set) of returned tools matches the expected list—prior to completing agent initialization.

- **Code location:** `app/modules/intelligence/agents/chat_agents/system_agents/code_gen_agent.py` (Lines 47–58)
- **Recommendation:** After retrieving the tools, add a check (e.g., compare the length of the returned tools to the number of requested tools) to detect any missing tool early.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-13T05:23:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -474,3 +482,53 @@ async def get_global_ai_provider(self, user_id: str) -> GetProviderResponse:
                 high_reasoning_model if high_reasoning_model else default_large_model
             ),
         )
+
+    def is_current_model_supported_by_pydanticai(self) -> bool:
+        provider = self._get_provider_config("small")
+        if provider in ["openai", "anthropic"]:
+            return True
+        return False
+
+    def get_pydantic_model(self) -> Model | None:
+        provider = self._get_provider_config("small")
+        params = self._build_llm_params(provider, "small")
+        routing_provider = params.pop("routing_provider", None)
+
+        base_url = None
+        api_key = params["api_key"]
+        headers = None
+
+        # if portkey is enabled
+        if self.portkey_api_key:
+            base_url = PORTKEY_GATEWAY_URL
+            headers = createHeaders(
+                api_key=self.portkey_api_key,
+                provider=routing_provider,
+                trace_id=str(uuid.uuid4())[:8],
+            )
+
+        # pick model name => convert "openai/gpt-4o" to "gpt-4o"
+        model_name: str = params["model"]
+        [provider_name, model] = model_name.split("/", 1)
+        if provider_name in PLATFORM_PROVIDERS:
+            model_name = model
+
path: app/modules/intelligence/provider/provider_service.py
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Verify model name parsing for edge cases**

The current implementation assumes that all model names contain a provider prefix with a slash. Consider adding a check to handle model names without a slash.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if there are any model names in the codebase that don't follow the provider/model pattern

# Look for model names in MODEL_CONFIGS that don't contain a slash
echo "Checking MODEL_CONFIGS for model names without provider prefix:"
rg -A 2 -B 2 '"model":\s*"[^/"]+[^"]+"' --type py
```

Length of output: 2585

---



</details>

**Action Required: Safeguard Model Name Parsing Logic**

I verified via the script that all entries in `MODEL_CONFIGS` currently follow the `provider/model` pattern. However, since the incoming `params["model"]` is externally provided, the assumption that every model name contains a slash remains risky. I recommend adding an initial check to verify that the model name includes a slash—handling the case gracefully (e.g., raising a clear error or assigning a default value) if it does not.

- **Current findings**: All existing model names in the configuration (e.g., `"openai/gpt-4o-mini"`, `"anthropic/claude-3-5-haiku-20241022"`) include a slash.
- **Recommendation**: Modify the parsing logic to first check for the presence of the slash. For example:

  ```python
  model_name: str = params["model"]
  if "/" in model_name:
      provider_name, model = model_name.split("/", 1)
      if provider_name in PLATFORM_PROVIDERS:
          model_name = model
  else:
      raise ValueError(f"Invalid model name format: {model_name}. Expected 'provider/model'.")
  ```

This additional check will protect against potential runtime errors if an unexpected model name format is provided.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-14T03:02:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -474,3 +482,53 @@ async def get_global_ai_provider(self, user_id: str) -> GetProviderResponse:
                 high_reasoning_model if high_reasoning_model else default_large_model
             ),
         )
+
+    def is_current_model_supported_by_pydanticai(self) -> bool:
+        provider = self._get_provider_config("small")
+        if provider in ["openai", "anthropic"]:
+            return True
+        return False
+
+    def get_pydantic_model(self) -> Model | None:
+        provider = self._get_provider_config("small")
+        params = self._build_llm_params(provider, "small")
+        routing_provider = params.pop("routing_provider", None)
+
+        base_url = None
+        api_key = params["api_key"]
+        headers = None
+
+        # if portkey is enabled
+        if self.portkey_api_key:
+            base_url = PORTKEY_GATEWAY_URL
+            headers = createHeaders(
+                api_key=self.portkey_api_key,
+                provider=routing_provider,
+                trace_id=str(uuid.uuid4())[:8],
+            )
+
+        # pick model name => convert "openai/gpt-4o" to "gpt-4o"
+        model_name: str = params["model"]
+        [provider_name, model] = model_name.split("/", 1)
+        if provider_name in PLATFORM_PROVIDERS:
+            model_name = model
+
+        match provider:
+            case "openai":
+                return OpenAIModel(
+                    model_name=model_name,
+                    provider=OpenAIProvider(
+                        api_key=api_key,
+                        http_client=httpx.AsyncClient(
+                            base_url=base_url or "",
+                            headers=headers,
+                        ),
+                    ),
+                )
+            case "anthropic":
+                return AnthropicModel(
+                    model_name=model_name,
+                    anthropic_client=AsyncAnthropic(
+                        api_key=api_key, base_url=base_url, default_headers=headers
+                    ),
+                )
path: app/modules/intelligence/provider/provider_service.py
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Enhance error handling in get_pydantic_model**

The method correctly creates model instances for OpenAI and Anthropic, but doesn't handle other providers explicitly. The return type indicates it can return None, but there's no explicit return statement for unsupported providers.

```diff
def get_pydantic_model(self) -> Model | None:
    provider = self._get_provider_config("small")
    params = self._build_llm_params(provider, "small")
    routing_provider = params.pop("routing_provider", None)

    base_url = None
    api_key = params["api_key"]
    headers = None

    # if portkey is enabled
    if self.portkey_api_key:
        base_url = PORTKEY_GATEWAY_URL
        headers = createHeaders(
            api_key=self.portkey_api_key,
            provider=routing_provider,
            trace_id=str(uuid.uuid4())[:8],
        )

    # pick model name => convert "openai/gpt-4o" to "gpt-4o"
    model_name: str = params["model"]
+    try:
        [provider_name, model] = model_name.split("/", 1)
+    except ValueError:
+        # If model_name doesn't contain a slash, use it as is
+        model = model_name
+        provider_name = provider
    if provider_name in PLATFORM_PROVIDERS:
        model_name = model

    match provider:
        case "openai":
            return OpenAIModel(
                model_name=model_name,
                provider=OpenAIProvider(
                    api_key=api_key,
                    http_client=httpx.AsyncClient(
                        base_url=base_url or "",
                        headers=headers,
                    ),
                ),
            )
        case "anthropic":
            return AnthropicModel(
                model_name=model_name,
                anthropic_client=AsyncAnthropic(
                    api_key=api_key, base_url=base_url, default_headers=headers
                ),
            )
+        case _:
+            logging.warning(f"Provider {provider} is not supported by PydanticAI")
+            return None
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    def get_pydantic_model(self) -> Model | None:
        provider = self._get_provider_config("small")
        params = self._build_llm_params(provider, "small")
        routing_provider = params.pop("routing_provider", None)

        base_url = None
        api_key = params["api_key"]
        headers = None

        # if portkey is enabled
        if self.portkey_api_key:
            base_url = PORTKEY_GATEWAY_URL
            headers = createHeaders(
                api_key=self.portkey_api_key,
                provider=routing_provider,
                trace_id=str(uuid.uuid4())[:8],
            )

        # pick model name => convert "openai/gpt-4o" to "gpt-4o"
        model_name: str = params["model"]
        try:
            [provider_name, model] = model_name.split("/", 1)
        except ValueError:
            # If model_name doesn't contain a slash, use it as is
            model = model_name
            provider_name = provider
        if provider_name in PLATFORM_PROVIDERS:
            model_name = model

        match provider:
            case "openai":
                return OpenAIModel(
                    model_name=model_name,
                    provider=OpenAIProvider(
                        api_key=api_key,
                        http_client=httpx.AsyncClient(
                            base_url=base_url or "",
                            headers=headers,
                        ),
                    ),
                )
            case "anthropic":
                return AnthropicModel(
                    model_name=model_name,
                    anthropic_client=AsyncAnthropic(
                        api_key=api_key, base_url=base_url, default_headers=headers
                    ),
                )
            case _:
                logging.warning(f"Provider {provider} is not supported by PydanticAI")
                return None
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits f1bd555 to 848a0bd
created_at: 2025-03-14T03:02:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,249 @@
+import re
+from typing import List, AsyncGenerator
+
+from app.modules.intelligence.provider.provider_service import (
+    ProviderService,
+)
+from .crewai_agent import AgentConfig, TaskConfig
+from app.modules.utils.logger import setup_logger
+
+from ..chat_agent import (
+    ChatAgent,
+    ChatAgentResponse,
+    ChatContext,
+    ToolCallEventType,
+    ToolCallResponse,
+)
+
+from pydantic_ai import Agent, Tool
+from pydantic_ai.messages import (
+    FunctionToolCallEvent,
+    FunctionToolResultEvent,
+    PartDeltaEvent,
+    TextPartDelta,
+)
+from langchain_core.tools import StructuredTool
+
+logger = setup_logger(__name__)
+
+
+def get_tool_run_message(tool_name: str):
+    match tool_name:
+        case "GetCodeanddocstringFromProbableNodeName":
+            return "Retrieving code from referenced names"
+        case "Getcodechanges":
+            return "Fetching code changes from your repo"
+        case "GetNodesfromTags":
+            return "Fetching nodes from tags"
+        case "AskKnowledgeGraphQueries":
+            return "Traversing the knowledge graph"
+        case "GetCodeanddocstringFromMultipleNodeIDs":
+            return "Fetching code and docstrings"
+        case "get_code_file_structure":
+            return "Loading the file structure of the repo"
+        case "GetNodeNeighboursFromNodeID":
+            return "Identifying referenced code"
+        case "WebpageContentExtractor":
+            return "Querying information from the web"
+        case "GitHubContentFetcher":
+            return "Fetching content from github"
+        case _:
+            return "Fetching code"
+
+
+def get_tool_response_message(tool_name: str):
+    match tool_name:
+        case "Getcodechanges":
+            return "Code changes fetched successfully"
+        case "GetNodesfromTags":
+            return "Fetched nodes from tags"
+        case "AskKnowledgeGraphQueries":
+            return "Knowledge graph queries successful"
+        case "GetCodeanddocstringFromMultipleNodeIDs":
+            return "Fetched code and docstrings"
+        case "get_code_file_structure":
+            return "File structure of the repo loaded successfully"
+        case "GetNodeNeighboursFromNodeID":
+            return "Fetched referenced code"
+        case "WebpageContentExtractor":
+            return "Information retrieved from web"
+        case "GitHubContentFetcher":
+            return "File contents fetched from github"
+        case _:
+            return "Code fetched"
+
+
+class PydanticRagAgent(ChatAgent):
+    def __init__(
+        self,
+        llm_provider: ProviderService,
+        config: AgentConfig,
+        tools: List[StructuredTool],
+    ):
+        """Initialize the agent with configuration and tools"""
+
+        self.tasks = config.tasks
+        self.max_iter = config.max_iter
+
+        # tool name can't have spaces for langgraph agents
+        for i, tool in enumerate(tools):
+            tools[i].name = re.sub(r" ", "", tool.name)
+
+        self.agent = Agent(
+            model=llm_provider.get_pydantic_model(),
+            # model="gpt-4o-mini",
+            tools=[
+                Tool(
+                    name=tool.name,
+                    description=tool.description,
+                    function=tool.func,  # type: ignore
+                )
+                for tool in tools
+            ],
+            system_prompt=f"Role: {config.role}\nGoal: {config.goal}\nBackstory: {config.backstory}. Respond to the user query",
+            result_type=str,
+            retries=3,
+            result_retries=3,
+            model_settings={
+                "parallel_tool_calls": True,
+            },
+        )
+
+    def _create_task_description(
+        self,
+        task_config: TaskConfig,
+        ctx: ChatContext,
+    ) -> str:
+        """Create a task description from task configuration"""
+        if ctx.node_ids is None:
+            ctx.node_ids = []
+        if isinstance(ctx.node_ids, str):
+            node_ids = [ctx.node_ids]
+
path: app/modules/intelligence/agents/chat_agents/pydantic_agent.py
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix unused variable assignment.**

The local variable `node_ids` is assigned but never used. Consider using this variable in line 127 or removing the assignment.

```diff
if isinstance(ctx.node_ids, str):
-    node_ids = [ctx.node_ids]
+    ctx.node_ids = [ctx.node_ids]
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
            ctx.node_ids = []
        if isinstance(ctx.node_ids, str):
            ctx.node_ids = [ctx.node_ids]
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

121-121: Local variable `node_ids` is assigned to but never used

Remove assignment to unused variable `node_ids`

(F841)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-15T13:02:02Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-10T06:11:54Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
This pull request updates several modules across the codebase to enhance and standardize the handling of tool call data. In the conversations module, an additional `tool_calls` field is added to the response structure. Multiple ChatAgent-related modules are updated to include this field and to adjust agent instantiation using a new Pydantic-based agent when supported. Various tools now return a `StructuredTool` instead of a generic `Tool`. In addition, the provider service is extended to support a new AI model provider, and the requirements file is updated accordingly.

## Changes

| File(s) | Change Summary |
|---------|----------------|
| `app/modules/conversations/conversation/conversation_service.py`<br>`app/modules/conversations/conversation/conversation_schema.py` | Added `tool_calls` field in `ChatMessageResponse` and updated methods to include this field in returned responses. |
| `app/modules/intelligence/agents/chat_agent.py`<br>`app/modules/intelligence/agents/chat_agents/adaptive_agent.py`<br>`app/modules/intelligence/agents/chat_agents/auto_router_agent.py`<br>`app/modules/intelligence/agents/chat_agents/crewai_agent.py`<br>`app/modules/intelligence/agents/chat_agents/langchain_agent.py` | Introduced new enum and Pydantic model for tool calls; updated `ChatAgentResponse` and related methods to include `tool_calls`; fixed naming errors and expanded error handling. |
| `app/modules/intelligence/agents/chat_agents/pydantic_agent.py`<br>`app/modules/intelligence/agents/chat_agents/system_agents/*.py` (blast_radius, code_gen, debug, integration_test, low_level_design, qna, unit_test) | Added new `PydanticRagAgent` and refactored `_build_agent` across system agents to conditionally instantiate either `PydanticRagAgent` or `CrewAIAgent` based on model support. |
| `app/modules/intelligence/tools/**/*.py` (change_detection, code_query_tools, kg_based_tools, tool_service, web_tools) | Updated return types from `Tool` to `StructuredTool` and removed unused `Tool` imports to improve type specificity in tool-related modules. |
| `app/modules/intelligence/provider/provider_service.py` | Added new enum value `PYDANTICAI` and introduced methods to check model support and retrieve a Pydantic model instance. |
| `requirements.txt` | Upgraded `openai` package version and added the new package `pydantic_ai`. |

## Sequence Diagram(s)
```mermaid
sequenceDiagram
    participant U as User
    participant DA as DebugAgent / AgentBuilder
    participant PT as ProviderService
    participant PA as PydanticRagAgent
    participant CA as CrewAIAgent
    participant R as ChatAgentResponse

    U->>DA: Send chat request
    DA->>PT: Check model support (is_current_model_supported_by_pydanticai?)
    PT-->>DA: Returns support status
    alt Model is supported by PydanticAI
        DA->>PA: Instantiate PydanticRagAgent with AgentConfig & tools
        PA->>DA: Process request and yield response (with tool_calls)
    else Model not supported
        DA->>CA: Instantiate CrewAIAgent with AgentConfig & tools
        CA->>DA: Process request and yield response (with tool_calls)
    end
    DA->>U: Return ChatAgentResponse (including tool_calls)
```

## Possibly related PRs
- **potpie-ai/potpie#245:** Involves changes to the `ChatMessageResponse` class and its usage in the conversation service, closely relating to the modifications in this PR.

## Suggested reviewers
- dhirenmathur
- kinshuksinghbist

## Poem
> I'm a little rabbit, hopping through the code,  
> Finding bugs and fields in every node.  
> With tool_calls added, my steps feel light,  
> Pydantic agents guide me through the night.  
> In a meadow of changes, I dance with glee,  
> Celebrating clean code and symmetry!  
> 🐇🌟

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 1c03706f33cb7eb10f25a99ec2bc759407e3900c and beefb8f52dca4f7825f68a58018dfefb234a19b4.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `app/modules/intelligence/agents/chat_agents/pydantic_agent.py` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* app/modules/intelligence/agents/chat_agents/pydantic_agent.py

</details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=potpie-ai/potpie&utm_content=303):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-03-15T14:58:55Z
body: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=getmomentum_momentum-server&pullRequest=303) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [5.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=getmomentum_momentum-server&pullRequest=303&metric=new_duplicated_lines_density&view=list) (required ≤ 3%)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=getmomentum_momentum-server&pullRequest=303)


author_association: NONE