Spaces:
Build error
Build error
Icamtu
Refactor SDLC graph builder to remove memory checkpointer; add prompt library for user stories; update Streamlit UI for project details input; enhance project details handling in session state.
98f1063 | import logging | |
| from langgraph.graph import StateGraph, START, END | |
| from langgraph.constants import Send | |
| import json | |
| from datetime import datetime | |
| from langgraph.checkpoint.memory import MemorySaver | |
| from typing import Annotated, List, TypedDict, Optional, Dict, Any, Literal | |
| from pydantic import BaseModel, Field | |
| from enum import Enum | |
| from langchain_core.messages import SystemMessage, HumanMessage | |
| import os | |
| from dotenv import load_dotenv | |
| import functools | |
| import time | |
| # Configure logging | |
| logging.basicConfig( | |
| level=logging.DEBUG, | |
| format="\n**********\n%(levelname)s | %(asctime)s | %(message)s\n**********\n", | |
| datefmt="%Y-%m-%d %H:%M:%S" | |
| ) | |
| logger = logging.getLogger(__name__) | |
| # Load environment variables | |
| load_dotenv() | |
| os.environ["LANGSMITH_API_KEY"] = os.getenv("LANGCHAIN_API_KEY") | |
| # Define the model | |
| from langchain_google_genai import ChatGoogleGenerativeAI | |
| model = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0) | |
| ######################### | |
| #########STATE########### | |
| ######################## | |
| class SDLCStages(Enum): | |
| """Software Development Life Cycle (SDLC) stages.""" | |
| PLANNING = "planning" | |
| DESIGN = "design" | |
| DEVELOPMENT = "development" | |
| TESTING = "testing" | |
| DEPLOYMENT = "deployment" | |
| COMPLETE = "complete" | |
| class SDLCState(BaseModel): | |
| """Software Development Life Cycle (SDLC) state.""" | |
| # core state attributes | |
| session_id: str = Field(..., description="Unique identifier for the session.") | |
| current_stage: SDLCStages = Field(default=SDLCStages.PLANNING, description="Current stage of the software development life cycle.") | |
| # Inputs gathered during various stages, especially Planning | |
| project_name: Optional[str] = Field(None, description="Name of the project.") | |
| project_description: Optional[str] = Field(None, description="Description of the project.") | |
| project_goals: Optional[str] = Field(None, description="Goals of the project.") | |
| project_scope: Optional[str] = Field(None, description="Scope of the project.") | |
| project_objectives: Optional[str] = Field(None, description="Objectives of the project.") | |
| requirements: Optional[str] = Field(None, description="Detailed project requirements.") | |
| user_stories: Optional[str] = Field(None, description="User stories generated based on requirements.") | |
| # Artifacts generated during different SDLC stages | |
| generated_requirements: Optional[str] = Field(None, description="Generated requirements based on user input.") | |
| generated_user_stories: Optional[str] = Field(None, description="Generated user stories based on requirements.") | |
| planning_artifact: Optional[str] = Field(None, description="Artifact generated during the planning stage.") | |
| design_artifact: Optional[str] = Field(None, description="Artifact generated during the design stage.") | |
| development_artifact: Optional[str] = Field(None, description="Artifact generated during the development stage.") | |
| testing_artifact: Optional[str] = Field(None, description="Artifact generated during the testing stage.") | |
| deployment_artifact: Optional[str] = Field(None, description="Artifact generated during the deployment stage.") | |
| # Feedback | |
| feedback: Dict[str, List[str]] = Field(default_factory=dict, description="User feedback by stage") | |
| feedback_decision: Optional[str] = Field(None, description="Decision after processing feedback (accept/reject)") | |
| # Metadata | |
| created_at: str = Field(default_factory=lambda: datetime.now().isoformat(), description="Creation timestamp") | |
| last_updated: str = Field(default_factory=lambda: datetime.now().isoformat(), description="Last update timestamp") | |
| history: List[Dict[str, Any]] = Field(default_factory=list, description="State history for monitoring") | |
| def to_dict(self) -> Dict[str, Any]: | |
| """Convert state to dictionary.""" | |
| return self.model_dump() | |
| def update_stage(self, new_stage: SDLCStages): | |
| """Update current stage and record in history.""" | |
| self.history.append({ | |
| "stage": self.current_stage.value if isinstance(self.current_stage, SDLCStages) else self.current_stage, | |
| "timestamp": datetime.now().isoformat() | |
| }) | |
| self.current_stage = new_stage | |
| self.last_updated = datetime.now().isoformat() | |
| def add_feedback(self, stage: SDLCStages, feedback_text: str): | |
| """Add feedback for a specific stage.""" | |
| stage_value = stage.value | |
| if stage_value not in self.feedback: | |
| self.feedback[stage_value] = [] | |
| self.feedback[stage_value].append(feedback_text) | |
| self.last_updated = datetime.now().isoformat() | |
| def get_last_feedback_for_stage(self, stage: SDLCStages) -> Optional[str]: | |
| """Get the last feedback entry for a specific SDLC stage.""" | |
| stage_value = stage.value | |
| if stage_value in self.feedback and self.feedback[stage_value]: | |
| return self.feedback[stage_value][-1] | |
| return None | |
| def get_next_stage(self) -> Optional[SDLCStages]: | |
| """Get the next stage in the SDLC process.""" | |
| stages_list = list(SDLCStages) | |
| try: | |
| current_index = stages_list.index(self.current_stage) | |
| if current_index < len(stages_list) - 1: | |
| return stages_list[current_index + 1] | |
| return SDLCStages.COMPLETE | |
| except ValueError: | |
| return SDLCStages.PLANNING | |
| def get_all_artifacts(self) -> Dict[str, Optional[str]]: | |
| """Get all artifacts in the state.""" | |
| return { | |
| "requirements": self.requirements, | |
| "planning_artifact": self.planning_artifact, | |
| "design_artifact": self.design_artifact, | |
| "development_artifact": self.development_artifact, | |
| "testing_artifact": self.testing_artifact, | |
| "deployment_artifact": self.deployment_artifact, | |
| } | |
| ######################################################################################################## | |
| ##############################sdlcNode######################################################## | |
| ######################################################################################################## | |
| class SdlcNode: | |
| def __init__(self, model): | |
| """Initialize the SDLC Node with an LLM.""" | |
| self.llm = model | |
| def user_input(self, state: SDLCState) -> dict: | |
| """Handle user input, distinguishing between initial requirements and feedback.""" | |
| logger.debug(f"Executing user_input with state: {state}") | |
| if state.get_last_feedback_for_stage(SDLCStages.PLANNING): | |
| state.user_stories = None | |
| # For testing, use hard-coded values instead of Streamlit | |
| # In production, uncomment and use the streamlit imports | |
| state.project_name = "Test Project" # st.session_state.get("project_name") | |
| state.project_description = "Test Description" # st.session_state.get("project_description") | |
| state.project_goals = "Test Goals" # st.session_state.get("project_goals") | |
| state.project_scope = "Test Scope" # st.session_state.get("project_scope") | |
| state.project_objectives = "Test Objectives" # st.session_state.get("project_objectives") | |
| return {"user_input": "captured"} | |
| def generate_requirements(self, state: SDLCState) -> dict: | |
| """Dummy requirements generator for testing feedback loop.""" | |
| logger.debug("Dummy generate_requirements called") | |
| state.generated_requirements = "DUMMY REQUIREMENTS" | |
| return {"generated_requirements": state.generated_requirements} | |
| def generate_user_stories(self, state: SDLCState) -> dict: | |
| """Dummy user stories generator for testing feedback loop.""" | |
| logger.debug("Dummy generate_user_stories called") | |
| state.user_stories = "DUMMY USER STORIES" | |
| return {"user_stories": state.user_stories} | |
| def process_feedback(self, state: SDLCState) -> dict: | |
| """Process user feedback and update state with decision.""" | |
| current_stage_value = state.current_stage.value if isinstance(state.current_stage, SDLCStages) else state.current_stage | |
| logger.debug(f"Processing feedback for stage: {current_stage_value}") | |
| # Simplified feedback check - just look at the feedback value in state | |
| feedback = state.feedback.get(current_stage_value, []) | |
| latest_feedback = feedback[-1] if feedback else None | |
| if latest_feedback and latest_feedback.strip().lower() == "accept": | |
| state.feedback_decision = "accept" | |
| else: | |
| state.feedback_decision = "reject" | |
| logger.debug(f"Feedback decision: {state.feedback_decision}") | |
| return { | |
| "feedback_decision": state.feedback_decision, | |
| "feedback": state.feedback | |
| } | |
| def feedback_route(self, state: SDLCState) -> str: | |
| """Simplified routing based on feedback decision.""" | |
| # Simple routing based on feedback_decision attribute | |
| if hasattr(state, "feedback_decision"): | |
| route = "accept" if state.feedback_decision == "accept" else "reject" | |
| logger.debug(f"Routing based on feedback_decision: {route}") | |
| return route | |
| # Fallback to reject if no decision found | |
| logger.debug("No feedback_decision found, defaulting to 'reject'") | |
| return "reject" | |
| ################################################################################################## | |
| ######################## graph_builder ########################################## | |
| ################################################################################################## | |
| class SdlcGraphBuilder: | |
| def __init__(self, llm, memory: MemorySaver=None): | |
| self.llm = llm | |
| # self.memory = memory or MemorySaver() | |
| def build_graph(self): | |
| try: | |
| if not self.llm: | |
| raise ValueError("LLM model not initialized") | |
| graph_builder = StateGraph(state_schema=SDLCState) | |
| sldc_node = SdlcNode(self.llm) | |
| # Add nodes | |
| graph_builder.add_node("Requirement", sldc_node.user_input) | |
| graph_builder.add_node("GenerateRequirements", sldc_node.generate_requirements) | |
| graph_builder.add_node("GenerateUserStories", sldc_node.generate_user_stories) | |
| graph_builder.add_node("ProcessFeedback", sldc_node.process_feedback) | |
| # Define Edges | |
| graph_builder.add_edge(START, "Requirement") | |
| graph_builder.add_edge("Requirement", "GenerateRequirements") | |
| graph_builder.add_edge("GenerateRequirements", "GenerateUserStories") | |
| graph_builder.add_edge("GenerateUserStories", "ProcessFeedback") | |
| # Conditional edge after feedback processing | |
| graph_builder.add_conditional_edges( | |
| "ProcessFeedback", | |
| sldc_node.feedback_route, | |
| { | |
| "accept": END, | |
| "reject": "GenerateUserStories" | |
| } | |
| ) | |
| # Compile with interrupt | |
| graph = graph_builder.compile( | |
| interrupt_before=["ProcessFeedback"] | |
| # checkpointer=self.memory | |
| ) | |
| # logger.debug("Graph compiled with checkpointer: %s", self.memory) | |
| return graph | |
| except Exception as e: | |
| logger.error(f"Error building graph: {e}") | |
| raise | |
| # Initialize the graph builder and agent | |
| sdlc_builder_instance = SdlcGraphBuilder(model) | |
| agent = sdlc_builder_instance.build_graph() |