CoolFace
Apppublic

Hamza4100/ai-workflow-agent

sourceHugging Faceupdated 7mo agoView on Hugging Face
0likes
chat_handler.py426 linesDownload Raw Back to root
1# AI Workflow Agent - Chat API
2"""
3Conversational interface for the AI Workflow Agent.
4Supports multi-turn conversations with session management.
5"""
6
7import uuid
8import json
9import logging
10from datetime import datetime
11from typing import Dict, Any, Optional, List
12from dataclasses import dataclass, field, asdict
13from enum import Enum
14
15logger = logging.getLogger(__name__)
16
17
18class MessageRole(Enum):
19    """Message roles in conversation."""
20    USER = "user"
21    ASSISTANT = "assistant"
22    SYSTEM = "system"
23
24
25class ConversationState(Enum):
26    """Current state of conversation."""
27    INITIAL = "initial"
28    ANALYZING = "analyzing"
29    CLARIFYING = "clarifying"
30    PLANNING = "planning"
31    BUILDING = "building"
32    COMPLETE = "complete"
33    ERROR = "error"
34
35
36@dataclass
37class Message:
38    """Single message in conversation."""
39    role: str
40    content: str
41    timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
42    metadata: Dict[str, Any] = field(default_factory=dict)
43
44
45@dataclass
46class Session:
47    """Conversation session."""
48    session_id: str
49    created_at: str
50    state: str = ConversationState.INITIAL.value
51    messages: List[Dict[str, Any]] = field(default_factory=list)
52    context: Dict[str, Any] = field(default_factory=dict)
53    project_type: Optional[str] = None
54    workflow: Optional[Dict[str, Any]] = None
55    pending_questions: List[str] = field(default_factory=list)
56    
57    def add_message(self, role: str, content: str, metadata: Dict = None):
58        """Add a message to the conversation."""
59        self.messages.append({
60            "role": role,
61            "content": content,
62            "timestamp": datetime.now().isoformat(),
63            "metadata": metadata or {}
64        })
65    
66    def get_history_text(self, limit: int = 10) -> str:
67        """Get conversation history as text for LLM context."""
68        recent = self.messages[-limit:]
69        lines = []
70        for msg in recent:
71            role = msg["role"].upper()
72            content = msg["content"]
73            lines.append(f"{role}: {content}")
74        return "\n".join(lines)
75    
76    def to_dict(self) -> Dict[str, Any]:
77        """Convert to dictionary."""
78        return asdict(self)
79
80
81class SessionManager:
82    """Manages conversation sessions."""
83    
84    def __init__(self, max_sessions: int = 100):
85        self.sessions: Dict[str, Session] = {}
86        self.max_sessions = max_sessions
87    
88    def create_session(self) -> Session:
89        """Create a new conversation session."""
90        # Cleanup old sessions if limit reached
91        if len(self.sessions) >= self.max_sessions:
92            self._cleanup_old_sessions()
93        
94        session_id = str(uuid.uuid4())[:8]
95        session = Session(
96            session_id=session_id,
97            created_at=datetime.now().isoformat()
98        )
99        
100        # Add system message
101        session.add_message(
102            MessageRole.SYSTEM.value,
103            "AI Workflow Agent initialized. Ready to help build n8n, ComfyUI, "
104            "or hybrid workflows. Describe what you want to create."
105        )
106        
107        self.sessions[session_id] = session
108        logger.info(f"Created session: {session_id}")
109        return session
110    
111    def get_session(self, session_id: str) -> Optional[Session]:
112        """Get existing session by ID."""
113        return self.sessions.get(session_id)
114    
115    def get_or_create(self, session_id: Optional[str] = None) -> Session:
116        """Get existing session or create new one."""
117        if session_id and session_id in self.sessions:
118            return self.sessions[session_id]
119        return self.create_session()
120    
121    def update_state(self, session_id: str, state: ConversationState):
122        """Update session state."""
123        if session_id in self.sessions:
124            self.sessions[session_id].state = state.value
125    
126    def delete_session(self, session_id: str) -> bool:
127        """Delete a session."""
128        if session_id in self.sessions:
129            del self.sessions[session_id]
130            logger.info(f"Deleted session: {session_id}")
131            return True
132        return False
133    
134    def list_sessions(self) -> List[Dict[str, Any]]:
135        """List all active sessions."""
136        return [
137            {
138                "session_id": s.session_id,
139                "created_at": s.created_at,
140                "state": s.state,
141                "message_count": len(s.messages),
142                "project_type": s.project_type
143            }
144            for s in self.sessions.values()
145        ]
146    
147    def _cleanup_old_sessions(self):
148        """Remove oldest sessions to make room."""
149        if not self.sessions:
150            return
151        
152        # Sort by creation time and remove oldest 20%
153        sorted_sessions = sorted(
154            self.sessions.items(),
155            key=lambda x: x[1].created_at
156        )
157        
158        to_remove = len(sorted_sessions) // 5
159        for session_id, _ in sorted_sessions[:to_remove]:
160            del self.sessions[session_id]
161        
162        logger.info(f"Cleaned up {to_remove} old sessions")
163
164
165class ChatHandler:
166    """Handles chat interactions with the agent system."""
167    
168    def __init__(self):
169        self.session_manager = SessionManager()
170        self._agent_system = None  # Lazy load
171    
172    @property
173    def agent_system(self):
174        """Lazy load agent system to avoid circular imports."""
175        if self._agent_system is None:
176            from crew_agents import crew_agent_system
177            self._agent_system = crew_agent_system
178        return self._agent_system
179    
180    async def chat(
181        self,
182        message: str,
183        session_id: Optional[str] = None
184    ) -> Dict[str, Any]:
185        """
186        Process a chat message and return response.
187        
188        Args:
189            message: User message
190            session_id: Optional existing session ID
191            
192        Returns:
193            Dict with response, session_id, state, and optionally questions/workflow
194        """
195        # Get or create session
196        session = self.session_manager.get_or_create(session_id)
197        
198        # Add user message
199        session.add_message(MessageRole.USER.value, message)
200        
201        try:
202            # Handle based on current state
203            if session.state == ConversationState.CLARIFYING.value:
204                # User is answering clarifying questions
205                return await self._handle_clarification(session, message)
206            else:
207                # New request or continuation
208                return await self._handle_request(session, message)
209                
210        except Exception as e:
211            logger.error(f"Chat error: {e}")
212            session.state = ConversationState.ERROR.value
213            session.add_message(
214                MessageRole.ASSISTANT.value,
215                f"Sorry, I encountered an error: {str(e)}. Please try again."
216            )
217            return {
218                "success": False,
219                "session_id": session.session_id,
220                "response": f"Error: {str(e)}",
221                "state": session.state
222            }
223    
224    async def _handle_request(self, session: Session, message: str) -> Dict[str, Any]:
225        """Handle a new or continuing request."""
226        session.state = ConversationState.ANALYZING.value
227        
228        # Analyze the request
229        analysis = await self.agent_system.analyze_request(
230            query=message,
231            session_id=session.session_id,
232            context={"history": session.get_history_text()}
233        )
234        
235        if not analysis.get("success"):
236            error_msg = analysis.get("error", "Analysis failed")
237            session.add_message(MessageRole.ASSISTANT.value, f"Error: {error_msg}")
238            return {
239                "success": False,
240                "session_id": session.session_id,
241                "response": error_msg,
242                "state": session.state
243            }
244        
245        # Check if clarification needed
246        if analysis.get("needs_clarification") and analysis.get("confidence", 0) < 0.7:
247            session.state = ConversationState.CLARIFYING.value
248            questions = analysis.get("questions", [])
249            session.pending_questions = questions
250            
251            # Build response with questions
252            response_parts = [analysis.get("analysis", "I need some clarification:")]
253            for i, q in enumerate(questions, 1):
254                response_parts.append(f"\n{i}. {q}")
255            
256            response = "\n".join(response_parts)
257            session.add_message(MessageRole.ASSISTANT.value, response)
258            
259            return {
260                "success": True,
261                "session_id": session.session_id,
262                "response": response,
263                "state": session.state,
264                "needs_clarification": True,
265                "questions": questions,
266                "project_type": analysis.get("project_type")
267            }
268        
269        # Proceed to build
270        return await self._build_workflow(session, analysis)
271    
272    async def _handle_clarification(self, session: Session, answer: str) -> Dict[str, Any]:
273        """Handle user's answer to clarifying questions."""
274        # Store the clarification
275        if session.pending_questions:
276            question = session.pending_questions[0]
277            self.agent_system.add_clarification(
278                session.session_id,
279                question,
280                answer
281            )
282            session.pending_questions = session.pending_questions[1:]
283        
284        # If more questions pending, ask next one
285        if session.pending_questions:
286            next_question = session.pending_questions[0]
287            response = f"Thanks! Next question: {next_question}"
288            session.add_message(MessageRole.ASSISTANT.value, response)
289            
290            return {
291                "success": True,
292                "session_id": session.session_id,
293                "response": response,
294                "state": session.state,
295                "needs_clarification": True,
296                "questions": session.pending_questions
297            }
298        
299        # All questions answered, proceed to build
300        session.add_message(
301            MessageRole.ASSISTANT.value,
302            "Great, I have all the information I need. Building your workflow..."
303        )
304        
305        # Re-analyze with new information
306        conv_context = self.agent_system.get_session(session.session_id)
307        if conv_context:
308            analysis = {
309                "project_type": conv_context.project_type,
310                "confidence": 0.9,
311                "requirements": conv_context.requirements
312            }
313            return await self._build_workflow(session, analysis)
314        
315        return await self._handle_request(session, session.messages[-2]["content"])
316    
317    async def _build_workflow(self, session: Session, analysis: Dict[str, Any]) -> Dict[str, Any]:
318        """Build the workflow based on analysis."""
319        session.state = ConversationState.PLANNING.value
320        session.project_type = analysis.get("project_type")
321        
322        # Use the simple builders for reliability (CrewAI for complex cases)
323        from tools.n8n_builder import N8NWorkflowBuilder
324        from tools.comfyui_builder import ComfyUIWorkflowBuilder
325        from tools.github_search import GitHubSearchTool
326        
327        project_type = analysis.get("project_type", "unknown")
328        original_query = session.messages[1]["content"] if len(session.messages) > 1 else ""
329        
330        session.state = ConversationState.BUILDING.value
331        
332        try:
333            if project_type == "n8n":
334                builder = N8NWorkflowBuilder()
335                workflow = await builder.generate_workflow(original_query)
336                response = "I've generated an n8n workflow for you. Here's the configuration:"
337                
338            elif project_type == "comfyui":
339                builder = ComfyUIWorkflowBuilder()
340                workflow = await builder.generate_workflow(original_query)
341                response = "I've generated a ComfyUI workflow. Here's the configuration:"
342                
343            elif project_type == "hybrid":
344                n8n_builder = N8NWorkflowBuilder()
345                comfyui_builder = ComfyUIWorkflowBuilder()
346                
347                n8n_wf = await n8n_builder.generate_workflow(original_query)
348                comfyui_wf = await comfyui_builder.generate_workflow(original_query)
349                
350                workflow = {
351                    "type": "hybrid",
352                    "n8n_workflow": n8n_wf,
353                    "comfyui_workflow": comfyui_wf,
354                    "integration_note": "n8n can call ComfyUI via HTTP Request node to /prompt endpoint"
355                }
356                response = "I've generated a hybrid workflow combining n8n automation with ComfyUI for AI generation."
357                
358            elif project_type == "external_repo":
359                github = GitHubSearchTool()
360                repos = await github.search(original_query, max_results=3)
361                recommendation = await github.generate_recommendation(repos)
362                
363                workflow = {
364                    "type": "external_repo",
365                    "repositories": repos,
366                    "recommendation": recommendation
367                }
368                response = f"I found some relevant repositories:\n\n{recommendation}"
369                
370            else:
371                workflow = None
372                response = "I couldn't determine the project type. Could you provide more details?"
373            
374            session.workflow = workflow
375            session.state = ConversationState.COMPLETE.value
376            
377            if workflow and project_type not in ["external_repo"]:
378                response += f"\n\n```json\n{json.dumps(workflow, indent=2)[:2000]}\n```"
379            
380            session.add_message(MessageRole.ASSISTANT.value, response[:500] + "..." if len(response) > 500 else response)
381            
382            return {
383                "success": True,
384                "session_id": session.session_id,
385                "response": response,
386                "state": session.state,
387                "project_type": project_type,
388                "workflow": workflow
389            }
390            
391        except Exception as e:
392            logger.error(f"Build error: {e}")
393            session.state = ConversationState.ERROR.value
394            response = f"Error building workflow: {str(e)}"
395            session.add_message(MessageRole.ASSISTANT.value, response)
396            
397            return {
398                "success": False,
399                "session_id": session.session_id,
400                "response": response,
401                "state": session.state,
402                "error": str(e)
403            }
404    
405    def get_session_info(self, session_id: str) -> Optional[Dict[str, Any]]:
406        """Get session information."""
407        session = self.session_manager.get_session(session_id)
408        if session:
409            return session.to_dict()
410        return None
411    
412    def list_sessions(self) -> List[Dict[str, Any]]:
413        """List all sessions."""
414        return self.session_manager.list_sessions()
415    
416    def clear_session(self, session_id: str) -> bool:
417        """Clear a session."""
418        # Also clear from agent system
419        if hasattr(self, '_agent_system') and self._agent_system:
420            self._agent_system.clear_session(session_id)
421        return self.session_manager.delete_session(session_id)
422
423
424# Singleton instance
425chat_handler = ChatHandler()
426