167AliRaza commited on
Commit
df6b3ab
·
verified ·
1 Parent(s): 013746f

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +392 -0
app.py ADDED
@@ -0,0 +1,392 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from fastapi import FastAPI, HTTPException
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from pydantic import BaseModel
5
+ from langchain_openai import ChatOpenAI
6
+ from crewai import Agent, Task, Crew, LLM
7
+ from crewai_tools import SerperDevTool
8
+ from typing import List, Optional
9
+ import uvicorn
10
+
11
+ # Environment variables
12
+ OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
13
+ SERPER_API_KEY = os.getenv("SERPER_API_KEY")
14
+
15
+ if not OPENROUTER_API_KEY:
16
+ raise ValueError("Missing OPENROUTER_API_KEY environment variable")
17
+
18
+ # Initialize FastAPI
19
+ app = FastAPI(
20
+ title="Construction AI Assistant",
21
+ description="Expert construction chatbot powered by DeepSeek R1",
22
+ version="1.0.0"
23
+ )
24
+
25
+ # CORS middleware
26
+ app.add_middleware(
27
+ CORSMiddleware,
28
+ allow_origins=["*"],
29
+ allow_credentials=True,
30
+ allow_methods=["*"],
31
+ allow_headers=["*"],
32
+ )
33
+
34
+ # LLM configurations
35
+ crew_llm = LLM(
36
+ model="openrouter/deepseek/deepseek-r1",
37
+ base_url="https://openrouter.ai/api/v1",
38
+ api_key=OPENROUTER_API_KEY,
39
+ temperature=0.7
40
+ )
41
+
42
+ direct_llm = ChatOpenAI(
43
+ model="deepseek/deepseek-r1",
44
+ openai_api_key=OPENROUTER_API_KEY,
45
+ openai_api_base="https://openrouter.ai/api/v1",
46
+ temperature=0.7,
47
+ max_tokens=2000
48
+ )
49
+
50
+ # Pydantic models
51
+ class ChatMessage(BaseModel):
52
+ role: str
53
+ content: str
54
+
55
+ class ChatRequest(BaseModel):
56
+ message: str
57
+ session_id: Optional[str] = "default"
58
+
59
+ class ChatResponse(BaseModel):
60
+ response: str
61
+ session_id: str
62
+ memory_count: int
63
+ search_enabled: bool
64
+
65
+ class ClearRequest(BaseModel):
66
+ session_id: Optional[str] = "default"
67
+
68
+ class ConstructionChatbot:
69
+ def __init__(self):
70
+ self.sessions = {} # Store memory per session
71
+ self.setup_tools()
72
+ self.setup_crew()
73
+
74
+ def get_session_memory(self, session_id: str) -> List[tuple]:
75
+ """Get or create session memory"""
76
+ if session_id not in self.sessions:
77
+ self.sessions[session_id] = []
78
+ return self.sessions[session_id]
79
+
80
+ def setup_tools(self):
81
+ """Set up web search tools"""
82
+ try:
83
+ if SERPER_API_KEY:
84
+ self.search_tool = SerperDevTool()
85
+ print("✅ Web search tool initialized successfully")
86
+ else:
87
+ self.search_tool = None
88
+ print("⚠️ Warning: SERPER_API_KEY not set - web search disabled")
89
+ except Exception as e:
90
+ self.search_tool = None
91
+ print(f"⚠️ Warning: Could not initialize web search tool: {e}")
92
+
93
+ def setup_crew(self):
94
+ """Set up CrewAI agents and tasks"""
95
+ tools = [self.search_tool] if self.search_tool else []
96
+
97
+ self.construction_agent = Agent(
98
+ role='Construction Expert Assistant',
99
+ goal='Provide accurate construction-related information ONLY. Reject all non-construction queries.',
100
+ backstory="""You are a specialized construction industry expert with deep knowledge in:
101
+ - Building safety and regulations
102
+ - Fire safety codes and compliance
103
+ - Construction materials and costs
104
+ - Project management methodologies
105
+ - Heavy machinery and equipment
106
+ - Civil engineering principles
107
+ - Structural design and analysis
108
+ - Site management and safety protocols
109
+
110
+ IMPORTANT: You MUST ONLY respond to construction-related questions.
111
+ If a user asks about anything not related to construction, building,
112
+ engineering, safety, materials, or project management, you must respond
113
+ with EXACTLY: "I can only assist with construction-related queries. Please ask about building, safety, materials, project management, or engineering topics."
114
+
115
+ When you need current information about construction topics, use the search tool.""",
116
+ llm=crew_llm,
117
+ tools=tools,
118
+ verbose=True,
119
+ allow_delegation=False,
120
+ max_iter=3,
121
+ max_execution_time=45
122
+ )
123
+
124
+ if self.search_tool:
125
+ self.research_agent = Agent(
126
+ role='Construction Research Specialist',
127
+ goal='Search and gather current construction-related information from the internet ONLY',
128
+ backstory="""You are a specialized researcher focused exclusively on construction industry topics.
129
+ You search for the most current information about:
130
+ - Construction practices and regulations
131
+ - Building costs and material prices
132
+ - Safety standards and compliance requirements
133
+ - Industry trends and new technologies
134
+ - Engineering standards and best practices
135
+
136
+ You ONLY research construction-related topics. If asked to research non-construction
137
+ topics, decline politely and redirect to construction subjects.""",
138
+ llm=crew_llm,
139
+ tools=[self.search_tool],
140
+ verbose=True,
141
+ allow_delegation=False,
142
+ max_iter=2,
143
+ max_execution_time=30
144
+ )
145
+ else:
146
+ self.research_agent = None
147
+
148
+ def add_to_memory(self, session_id: str, user_query: str, response: str):
149
+ """Add interaction to rolling memory window"""
150
+ memory = self.get_session_memory(session_id)
151
+ memory.append((user_query, response))
152
+ if len(memory) > 5:
153
+ memory.pop(0)
154
+
155
+ def get_chat_history(self, session_id: str) -> str:
156
+ """Format chat history for prompt"""
157
+ memory = self.get_session_memory(session_id)
158
+ if not memory:
159
+ return "No previous conversation."
160
+
161
+ history = ""
162
+ for i, (user_msg, bot_msg) in enumerate(memory, 1):
163
+ history += f"Message {i}:\nUser: {user_msg}\nAssistant: {bot_msg}\n\n"
164
+ return history.strip()
165
+
166
+ def is_construction_related(self, query: str) -> bool:
167
+ """Simple check if query is construction-related"""
168
+ construction_keywords = [
169
+ 'construction', 'building', 'concrete', 'steel', 'foundation', 'safety',
170
+ 'project management', 'engineering', 'structure', 'material', 'cost',
171
+ 'regulation', 'fire safety', 'osha', 'machinery', 'equipment', 'site',
172
+ 'contractor', 'cement', 'rebar', 'excavation', 'blueprint', 'architect',
173
+ 'electrical', 'plumbing', 'hvac', 'roofing', 'insulation', 'drywall'
174
+ ]
175
+
176
+ query_lower = query.lower()
177
+ return any(keyword in query_lower for keyword in construction_keywords)
178
+
179
+ def generate_response_with_crew(self, user_query: str, session_id: str) -> str:
180
+ """Generate response using CrewAI with web search capabilities"""
181
+ if not self.is_construction_related(user_query):
182
+ response = "I can only assist with construction-related queries. Please ask about building, safety, materials, project management, or engineering topics."
183
+ self.add_to_memory(session_id, user_query, response)
184
+ return response
185
+
186
+ chat_history = self.get_chat_history(session_id)
187
+
188
+ try:
189
+ search_keywords = ['current', 'latest', 'recent', 'today', '2024', '2025', 'price', 'cost', 'regulation', 'new', 'trend']
190
+ needs_search = any(keyword in user_query.lower() for keyword in search_keywords)
191
+
192
+ if needs_search and self.research_agent:
193
+ research_task = Task(
194
+ description=f"""Search for current construction-related information about: {user_query}
195
+
196
+ Focus on finding:
197
+ - Latest construction industry data
198
+ - Current material prices and costs
199
+ - Recent regulations and safety updates
200
+ - New construction technologies and methods
201
+ - Industry trends and market information
202
+
203
+ Search query should be concise and focused on construction industry information.
204
+ """,
205
+ expected_output="Current, accurate construction industry information and data",
206
+ agent=self.research_agent
207
+ )
208
+
209
+ response_task = Task(
210
+ description=f"""Based on research findings and chat history, provide a comprehensive response to: {user_query}
211
+
212
+ Chat history: {chat_history}
213
+
214
+ Guidelines:
215
+ - Use the research data to provide accurate, current information
216
+ - Focus on construction industry expertise
217
+ - Provide practical, actionable advice
218
+ - Include specific details like prices, regulations, or technical specifications when available
219
+ - Structure the response clearly and professionally
220
+ """,
221
+ expected_output="Detailed, informative construction industry response with current data",
222
+ agent=self.construction_agent,
223
+ context=[research_task]
224
+ )
225
+
226
+ crew = Crew(
227
+ agents=[self.research_agent, self.construction_agent],
228
+ tasks=[research_task, response_task],
229
+ verbose=False
230
+ )
231
+ else:
232
+ response_task = Task(
233
+ description=f"""Provide expert construction advice for: {user_query}
234
+
235
+ Chat history: {chat_history}
236
+
237
+ Guidelines:
238
+ - Draw from your construction industry expertise
239
+ - Provide detailed, accurate information
240
+ - Include relevant safety considerations
241
+ - Suggest best practices and standards
242
+ - Structure the response professionally
243
+ """,
244
+ expected_output="Expert construction industry advice and information",
245
+ agent=self.construction_agent
246
+ )
247
+
248
+ crew = Crew(
249
+ agents=[self.construction_agent],
250
+ tasks=[response_task],
251
+ verbose=False
252
+ )
253
+
254
+ result = crew.kickoff()
255
+ response = str(result).strip()
256
+
257
+ if not response or len(response) < 10:
258
+ response = "I apologize, but I'm having trouble generating a proper response. Could you please rephrase your construction-related question?"
259
+
260
+ self.add_to_memory(session_id, user_query, response)
261
+ return response
262
+
263
+ except Exception as e:
264
+ print(f"CrewAI Error: {e}")
265
+ return self.generate_response_direct(user_query, session_id)
266
+
267
+ def generate_response_direct(self, user_query: str, session_id: str) -> str:
268
+ """Fallback method using direct LLM with construction filtering"""
269
+ if not self.is_construction_related(user_query):
270
+ response = "I can only assist with construction-related queries. Please ask about building, safety, materials, project management, or engineering topics."
271
+ self.add_to_memory(session_id, user_query, response)
272
+ return response
273
+
274
+ chat_history = self.get_chat_history(session_id)
275
+
276
+ prompt = f"""You are a specialized construction industry AI assistant with expertise in building, safety, materials, project management, and engineering.
277
+
278
+ Chat history: {chat_history}
279
+
280
+ User question: {user_query}
281
+
282
+ Provide a detailed, professional response focusing on construction industry knowledge. Include specific information about safety standards, building codes, material specifications, cost estimates, or project management advice as relevant to the question.
283
+
284
+ Response:"""
285
+
286
+ try:
287
+ response = direct_llm.invoke(prompt)
288
+ if hasattr(response, 'content'):
289
+ response_text = response.content
290
+ else:
291
+ response_text = str(response)
292
+
293
+ self.add_to_memory(session_id, user_query, response_text)
294
+ return response_text
295
+
296
+ except Exception as e:
297
+ fallback_response = f"""I apologize, but I'm experiencing technical difficulties. However, I can still help with construction-related questions about safety, materials, project management, and engineering. Please try rephrasing your question.
298
+
299
+ Technical error: {str(e)[:100]}..."""
300
+
301
+ self.add_to_memory(session_id, user_query, fallback_response)
302
+ return fallback_response
303
+
304
+ def generate_response(self, user_query: str, session_id: str = "default") -> str:
305
+ """Main response generation method"""
306
+ try:
307
+ return self.generate_response_with_crew(user_query, session_id)
308
+ except Exception as e:
309
+ print(f"Crew method failed, using direct method: {e}")
310
+ return self.generate_response_direct(user_query, session_id)
311
+
312
+ def clear_session(self, session_id: str):
313
+ """Clear a specific session's memory"""
314
+ if session_id in self.sessions:
315
+ self.sessions[session_id].clear()
316
+
317
+ # Initialize chatbot
318
+ chatbot = ConstructionChatbot()
319
+
320
+ # API Endpoints
321
+ @app.get("/")
322
+ async def root():
323
+ """Root endpoint with API information"""
324
+ return {
325
+ "message": "Construction AI Assistant API",
326
+ "version": "1.0.0",
327
+ "endpoints": {
328
+ "/chat": "POST - Send a message to the chatbot",
329
+ "/clear": "POST - Clear conversation history",
330
+ "/status": "GET - Check system status",
331
+ "/docs": "GET - Interactive API documentation"
332
+ }
333
+ }
334
+
335
+ @app.get("/status")
336
+ async def status():
337
+ """Get system status"""
338
+ return {
339
+ "status": "online",
340
+ "model": "DeepSeek R1",
341
+ "web_search_enabled": chatbot.search_tool is not None,
342
+ "active_sessions": len(chatbot.sessions)
343
+ }
344
+
345
+ @app.post("/chat", response_model=ChatResponse)
346
+ async def chat(request: ChatRequest):
347
+ """
348
+ Send a message to the construction chatbot
349
+
350
+ - **message**: Your construction-related question
351
+ - **session_id**: Optional session identifier for maintaining conversation context
352
+ """
353
+ try:
354
+ if not request.message or not request.message.strip():
355
+ raise HTTPException(status_code=400, detail="Message cannot be empty")
356
+
357
+ response = chatbot.generate_response(request.message, request.session_id)
358
+ memory_count = len(chatbot.get_session_memory(request.session_id))
359
+
360
+ return ChatResponse(
361
+ response=response,
362
+ session_id=request.session_id,
363
+ memory_count=memory_count,
364
+ search_enabled=chatbot.search_tool is not None
365
+ )
366
+ except Exception as e:
367
+ raise HTTPException(status_code=500, detail=f"Error processing request: {str(e)}")
368
+
369
+ @app.post("/clear")
370
+ async def clear_chat(request: ClearRequest):
371
+ """
372
+ Clear conversation history for a session
373
+
374
+ - **session_id**: Optional session identifier to clear (default: "default")
375
+ """
376
+ try:
377
+ chatbot.clear_session(request.session_id)
378
+ return {
379
+ "message": f"Conversation history cleared for session: {request.session_id}",
380
+ "session_id": request.session_id
381
+ }
382
+ except Exception as e:
383
+ raise HTTPException(status_code=500, detail=f"Error clearing session: {str(e)}")
384
+
385
+ if __name__ == "__main__":
386
+ print("🏗️ Starting Construction AI Assistant API...")
387
+ print(f"🧠 Model: DeepSeek R1")
388
+ print(f"🌐 Web Search: {'ENABLED' if chatbot.search_tool else 'DISABLED'}")
389
+ print(f"📡 Server starting at: http://localhost:8000")
390
+ print(f"📚 API Docs available at: http://localhost:8000/docs")
391
+
392
+ uvicorn.run(app, host="0.0.0.0", port=8000)