khaledsayed1 commited on
Commit
2bc0211
Β·
verified Β·
1 Parent(s): 689fb76

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +416 -416
app.py CHANGED
@@ -1,417 +1,417 @@
1
- import os
2
- import gradio as gr
3
- from langchain_community.document_loaders import WebBaseLoader
4
- from langchain_text_splitters import RecursiveCharacterTextSplitter
5
- from langchain_google_genai import GoogleGenerativeAIEmbeddings
6
- from langchain_core.vectorstores import InMemoryVectorStore
7
- from langchain.tools.retriever import create_retriever_tool
8
- from langgraph.graph import MessagesState, StateGraph, START, END
9
- from langchain.chat_models import init_chat_model
10
- from langgraph.prebuilt import ToolNode, tools_condition
11
- from pydantic import BaseModel, Field
12
- from typing import Literal
13
- from langchain_core.messages import HumanMessage, AIMessage
14
- import logging
15
-
16
- # Set up logging
17
- logging.basicConfig(level=logging.INFO)
18
- logger = logging.getLogger(__name__)
19
-
20
- # Get API key from Hugging Face Spaces secrets
21
- GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
22
- if GOOGLE_API_KEY:
23
- os.environ["GOOGLE_API_KEY"] = GOOGLE_API_KEY
24
- else:
25
- logger.warning("GOOGLE_API_KEY not found in environment variables")
26
-
27
- class LegalConsultingBot:
28
- def __init__(self):
29
- self.graph = None
30
- self.retriever_tool = None
31
- self.response_model = None
32
- self.grader_model = None
33
- self.initialize_bot()
34
-
35
- def initialize_bot(self):
36
- """Initialize the bot with error handling."""
37
- try:
38
- if not GOOGLE_API_KEY:
39
- logger.error("Google API key not available")
40
- return
41
-
42
- self.setup_models()
43
- self.setup_retriever()
44
- self.setup_workflow()
45
- logger.info("Bot initialized successfully")
46
- except Exception as e:
47
- logger.error(f"Failed to initialize bot: {e}")
48
-
49
- def setup_models(self):
50
- """Initialize the language models."""
51
- try:
52
- self.response_model = init_chat_model(
53
- "gemini-2.0-flash",
54
- model_provider="google_genai",
55
- temperature=0
56
- )
57
- self.grader_model = init_chat_model(
58
- "gemini-2.0-flash",
59
- model_provider="google_genai",
60
- temperature=0
61
- )
62
- logger.info("Models initialized successfully")
63
- except Exception as e:
64
- logger.error(f"Failed to initialize models: {e}")
65
- raise
66
-
67
- def setup_retriever(self):
68
- """Initialize the document retriever with legal consulting URLs."""
69
- urls = [
70
- "https://firststepslegal.co.uk/",
71
- "https://sprintlaw.co.uk/",
72
- "https://www.smbs.solutions/legal-and-compliance-resources-legal-assistance-for-businesses",
73
- "https://ignition.law/lawyers-for-smes/",
74
- "https://www.cocredo.co.uk/news/free-legal-advice-small-business-owners",
75
- "https://www.gannons.co.uk/sectors/smes/",
76
- "https://kkbservices.com/who-we-work-with/small-businesses/",
77
- "https://farringfordlegal.co.uk/",
78
- "https://stanislawlegal.com/en/legal-solutions/for-sme-companies/",
79
- "https://www.lawhive.co.uk/small-business/",
80
- "https://www.catalystlaw.co.uk/business-legal-advice.html",
81
- "https://medium.com/@kmitsme123/legal-consulting-tips-for-your-small-business-9075005eb574",
82
- "https://smecomply.co.uk/",
83
- "https://dojobusiness.com/blogs/news/legal-consultant-complete-guide",
84
- "https://englishlegaladvice.com/",
85
- ]
86
-
87
- try:
88
- # Load documents with error handling
89
- docs = []
90
- successful_loads = 0
91
-
92
- for url in urls:
93
- try:
94
- loader = WebBaseLoader(url)
95
- docs.extend(loader.load())
96
- successful_loads += 1
97
- logger.info(f"Successfully loaded: {url}")
98
- except Exception as e:
99
- logger.warning(f"Failed to load {url}: {e}")
100
- continue
101
-
102
- logger.info(f"Successfully loaded {successful_loads}/{len(urls)} URLs")
103
-
104
- if not docs:
105
- logger.warning("No documents could be loaded, using fallback mode")
106
- return
107
-
108
- # Split documents
109
- text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
110
- chunk_size=300, chunk_overlap=50
111
- )
112
- doc_splits = text_splitter.split_documents(docs)
113
- logger.info(f"Created {len(doc_splits)} document chunks")
114
-
115
- # Create embeddings and vectorstore
116
- embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001")
117
- vectorstore = InMemoryVectorStore.from_documents(
118
- documents=doc_splits,
119
- embedding=embeddings
120
- )
121
-
122
- # Create retriever tool
123
- retriever = vectorstore.as_retriever()
124
- self.retriever_tool = create_retriever_tool(
125
- retriever,
126
- name="legal_consulting_retriever",
127
- description="Search and return relevant legal information and consulting resources for small and medium-sized businesses."
128
- )
129
- logger.info("Retriever tool created successfully")
130
-
131
- except Exception as e:
132
- logger.error(f"Error setting up retriever: {e}")
133
- self.retriever_tool = None
134
-
135
- def setup_workflow(self):
136
- """Set up the LangGraph workflow."""
137
- try:
138
- if not self.response_model:
139
- logger.error("Response model not available")
140
- return
141
-
142
- # Create workflow
143
- workflow = StateGraph(MessagesState)
144
-
145
- # Add nodes
146
- workflow.add_node("generate_query_or_respond", self.generate_query_or_respond)
147
- if self.retriever_tool:
148
- workflow.add_node("retrieve", ToolNode([self.retriever_tool]))
149
- workflow.add_node("grade_documents", self.grade_documents_node)
150
- workflow.add_node("rewrite_question", self.rewrite_question)
151
- workflow.add_node("generate_answer", self.generate_answer)
152
-
153
- # Add edges
154
- workflow.add_edge(START, "generate_query_or_respond")
155
-
156
- if self.retriever_tool:
157
- workflow.add_conditional_edges(
158
- "generate_query_or_respond",
159
- tools_condition,
160
- {
161
- "tools": "retrieve",
162
- END: END,
163
- },
164
- )
165
- workflow.add_edge("retrieve", "grade_documents")
166
- workflow.add_conditional_edges(
167
- "grade_documents",
168
- lambda x: x.get("grade_result", "generate_answer"),
169
- {
170
- "generate_answer": "generate_answer",
171
- "rewrite_question": "rewrite_question"
172
- }
173
- )
174
- else:
175
- workflow.add_edge("generate_query_or_respond", END)
176
-
177
- workflow.add_edge("generate_answer", END)
178
- workflow.add_edge("rewrite_question", "generate_query_or_respond")
179
-
180
- self.graph = workflow.compile()
181
- logger.info("Workflow compiled successfully")
182
-
183
- except Exception as e:
184
- logger.error(f"Error setting up workflow: {e}")
185
- self.graph = None
186
-
187
- def generate_query_or_respond(self, state: MessagesState):
188
- """Generate query or respond directly."""
189
- try:
190
- if not self.retriever_tool:
191
- # Fallback response when retriever is not available
192
- fallback_response = """I'm a legal consulting assistant for small and medium enterprises. While my document retriever is currently unavailable, I can still help answer general questions about:
193
-
194
- - Business formation and structure
195
- - Contract basics and employment law
196
- - Intellectual property fundamentals
197
- - Compliance and regulatory matters
198
- - General legal considerations for SMEs
199
-
200
- Please note: This is general information only, not legal advice. Always consult qualified legal professionals for specific matters."""
201
- return {"messages": [AIMessage(content=fallback_response)]}
202
-
203
- response = (
204
- self.response_model
205
- .bind_tools([self.retriever_tool])
206
- .invoke(state["messages"])
207
- )
208
- return {"messages": [response]}
209
- except Exception as e:
210
- logger.error(f"Error in generate_query_or_respond: {e}")
211
- return {"messages": [AIMessage(content="I'm sorry, I encountered an error. Please try again.")]}
212
-
213
- class GradeDocuments(BaseModel):
214
- """Grade documents using a binary score for relevance check."""
215
- binary_score: str = Field(
216
- description="Relevance score: 'yes' if relevant, or 'no' if not relevant"
217
- )
218
-
219
- def grade_documents_node(self, state: MessagesState):
220
- """Node wrapper for document grading."""
221
- try:
222
- question = state["messages"][0].content
223
- context = state["messages"][-1].content if state["messages"] else ""
224
-
225
- GRADE_PROMPT = (
226
- "You are a grader assessing relevance of a retrieved document to a user question. \n "
227
- "Here is the retrieved document: \n\n {context} \n\n"
228
- "Here is the user question: {question} \n"
229
- "If the document contains keyword(s) or semantic meaning related to the user question, grade it as relevant. \n"
230
- "Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question."
231
- )
232
-
233
- prompt = GRADE_PROMPT.format(question=question, context=context)
234
- response = (
235
- self.grader_model
236
- .with_structured_output(self.GradeDocuments)
237
- .invoke([{"role": "user", "content": prompt}])
238
- )
239
-
240
- grade_result = "generate_answer" if response.binary_score == "yes" else "rewrite_question"
241
- return {"grade_result": grade_result}
242
- except Exception as e:
243
- logger.error(f"Error in grade_documents_node: {e}")
244
- return {"grade_result": "generate_answer"} # Default to generating answer
245
-
246
- def rewrite_question(self, state: MessagesState):
247
- """Rewrite the original user question."""
248
- try:
249
- messages = state["messages"]
250
- question = messages[0].content if messages else ""
251
-
252
- REWRITE_PROMPT = (
253
- "Look at the input and try to reason about the underlying semantic intent / meaning.\n"
254
- "Here is the initial question:"
255
- "\n ------- \n"
256
- "{question}"
257
- "\n ------- \n"
258
- "Formulate an improved question that would be better for searching legal consulting information:"
259
- )
260
-
261
- prompt = REWRITE_PROMPT.format(question=question)
262
- response = self.response_model.invoke([{"role": "user", "content": prompt}])
263
- return {"messages": [HumanMessage(content=response.content)]}
264
- except Exception as e:
265
- logger.error(f"Error in rewrite_question: {e}")
266
- # Return original message if rewriting fails
267
- return {"messages": state["messages"][:1] if state["messages"] else []}
268
-
269
- def generate_answer(self, state: MessagesState):
270
- """Generate an answer based on retrieved context."""
271
- try:
272
- question = state["messages"][0].content if state["messages"] else ""
273
- context = state["messages"][-1].content if len(state["messages"]) > 1 else ""
274
-
275
- GENERATE_PROMPT = (
276
- "You are an assistant for question-answering tasks about legal consulting for small and medium businesses. "
277
- "Use the following pieces of retrieved context to answer the question. "
278
- "If you don't know the answer, just say that you don't know. "
279
- "Keep the answer concise but informative. Always remind users that this is general information and not legal advice.\n"
280
- "Question: {question} \n"
281
- "Context: {context}"
282
- )
283
-
284
- prompt = GENERATE_PROMPT.format(question=question, context=context)
285
- response = self.response_model.invoke([{"role": "user", "content": prompt}])
286
- return {"messages": [response]}
287
- except Exception as e:
288
- logger.error(f"Error in generate_answer: {e}")
289
- return {"messages": [AIMessage(content="I'm sorry, I encountered an error while generating the answer. Please try again.")]}
290
-
291
- def chat(self, message: str, history: list) -> str:
292
- """Main chat function for Gradio interface."""
293
- try:
294
- if not message or not message.strip():
295
- return "Please enter a question."
296
-
297
- if not self.response_model:
298
- return "Sorry, the system is not properly initialized. Please check if the API key is configured correctly."
299
-
300
- # For simple cases without retriever, provide direct response
301
- if not self.graph:
302
- prompt = f"""You are a helpful assistant specializing in legal consulting for small and medium enterprises.
303
- Answer this question: {message}
304
-
305
- Always remind users that this is general information only and not professional legal advice."""
306
-
307
- response = self.response_model.invoke([{"role": "user", "content": prompt}])
308
- return response.content if hasattr(response, 'content') else str(response)
309
-
310
- # Create initial state
311
- initial_state = {"messages": [HumanMessage(content=message)]}
312
-
313
- # Run the graph
314
- result = self.graph.invoke(initial_state)
315
-
316
- # Extract the final response
317
- if result and "messages" in result and result["messages"]:
318
- final_message = result["messages"][-1]
319
- if hasattr(final_message, 'content'):
320
- return final_message.content
321
- else:
322
- return str(final_message)
323
- else:
324
- return "I'm sorry, I couldn't generate a response. Please try again."
325
-
326
- except Exception as e:
327
- logger.error(f"Error in chat function: {e}")
328
- return f"An error occurred while processing your request. Please try again."
329
-
330
- # Initialize the bot
331
- logger.info("Initializing Legal Consulting Bot...")
332
- bot = LegalConsultingBot()
333
-
334
- # Create Gradio interface
335
- def create_interface():
336
- with gr.Blocks(
337
- title="Legal Consulting Assistant for SMEs",
338
- theme=gr.themes.Soft(),
339
- css="""
340
- .container {
341
- max-width: 800px;
342
- margin: auto;
343
- }
344
- """
345
- ) as demo:
346
- gr.Markdown("""
347
- # 🏒 Legal Consulting Assistant for Small & Medium Enterprises
348
-
349
- Get informed answers about legal consulting, compliance, and business law for SMEs.
350
- This assistant uses information from various legal consulting websites to provide relevant guidance.
351
-
352
- **⚠️ Important**: This provides general information only and does not constitute professional legal advice.
353
- """)
354
-
355
- with gr.Row():
356
- with gr.Column(scale=4):
357
- chatbot = gr.Chatbot(
358
- height=500,
359
- placeholder="πŸ’¬ Ask me about legal consulting for your business...",
360
- avatar_images=("πŸ‘€", "πŸ€–"),
361
- bubble_full_width=False
362
- )
363
-
364
- with gr.Row():
365
- msg = gr.Textbox(
366
- placeholder="e.g., What legal structure should I choose for my startup?",
367
- label="Your Question",
368
- scale=4
369
- )
370
- submit_btn = gr.Button("Send", variant="primary", scale=1)
371
-
372
- clear = gr.Button("πŸ—‘οΈ Clear Chat", variant="secondary")
373
-
374
- def respond(message, chat_history):
375
- if not message.strip():
376
- return "", chat_history
377
-
378
- # Show typing indicator
379
- chat_history.append((message, "πŸ€” Thinking..."))
380
- yield "", chat_history
381
-
382
- # Get bot response
383
- bot_message = bot.chat(message, chat_history)
384
- chat_history[-1] = (message, bot_message)
385
- yield "", chat_history
386
-
387
- def clear_chat():
388
- return []
389
-
390
- # Event handlers
391
- msg.submit(respond, [msg, chatbot], [msg, chatbot])
392
- submit_btn.click(respond, [msg, chatbot], [msg, chatbot])
393
- clear.click(clear_chat, outputs=[chatbot])
394
-
395
- gr.Markdown("""
396
- ### πŸ“‹ Example Questions
397
- - *What legal structure should I choose for my small business?*
398
- - *What compliance requirements do SMEs need to consider?*
399
- - *How can I protect my business's intellectual property?*
400
- - *What should be included in employment contracts?*
401
-
402
- ### βš–οΈ Disclaimer
403
- This chatbot provides general information about legal consulting for SMEs based on publicly available resources.
404
- **This information should not be considered as professional legal advice.**
405
- Always consult with qualified legal professionals for specific legal matters and before making important business decisions.
406
- """)
407
-
408
- return demo
409
-
410
- # Create and launch the interface
411
- if __name__ == "__main__":
412
- demo = create_interface()
413
- demo.launch(
414
- server_name="0.0.0.0",
415
- server_port=7860,
416
- share=False
417
  )
 
1
+ import os
2
+ import gradio as gr
3
+ from langchain_community.document_loaders import WebBaseLoader
4
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
5
+ from langchain_google_genai import GoogleGenerativeAIEmbeddings
6
+ from langchain_core.vectorstores import InMemoryVectorStore
7
+ from langchain.tools.retriever import create_retriever_tool
8
+ from langgraph.graph import MessagesState, StateGraph, START, END
9
+ from langchain.chat_models import init_chat_model
10
+ from langgraph.prebuilt import ToolNode, tools_condition
11
+ from pydantic import BaseModel, Field
12
+ from typing import Literal
13
+ from langchain_core.messages import HumanMessage, AIMessage
14
+ import logging
15
+
16
+ # Set up logging
17
+ logging.basicConfig(level=logging.INFO)
18
+ logger = logging.getLogger(__name__)
19
+
20
+ # Get API key from Hugging Face Spaces secrets
21
+ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
22
+ if GOOGLE_API_KEY:
23
+ os.environ["GOOGLE_API_KEY"] = GOOGLE_API_KEY
24
+ else:
25
+ logger.warning("GOOGLE_API_KEY not found in environment variables")
26
+
27
+ class LegalConsultingBot:
28
+ def __init__(self):
29
+ self.graph = None
30
+ self.retriever_tool = None
31
+ self.response_model = None
32
+ self.grader_model = None
33
+ self.initialize_bot()
34
+
35
+ def initialize_bot(self):
36
+ """Initialize the bot with error handling."""
37
+ try:
38
+ if not GOOGLE_API_KEY:
39
+ logger.error("Google API key not available")
40
+ return
41
+
42
+ self.setup_models()
43
+ self.setup_retriever()
44
+ self.setup_workflow()
45
+ logger.info("Bot initialized successfully")
46
+ except Exception as e:
47
+ logger.error(f"Failed to initialize bot: {e}")
48
+
49
+ def setup_models(self):
50
+ """Initialize the language models."""
51
+ try:
52
+ self.response_model = init_chat_model(
53
+ "gemini-2.0-flash",
54
+ model_provider="google_genai",
55
+ temperature=0
56
+ )
57
+ self.grader_model = init_chat_model(
58
+ "gemini-2.0-flash",
59
+ model_provider="google_genai",
60
+ temperature=0
61
+ )
62
+ logger.info("Models initialized successfully")
63
+ except Exception as e:
64
+ logger.error(f"Failed to initialize models: {e}")
65
+ raise
66
+
67
+ def setup_retriever(self):
68
+ """Initialize the document retriever with legal consulting URLs."""
69
+ urls = [
70
+ "https://firststepslegal.co.uk/",
71
+ "https://sprintlaw.co.uk/",
72
+ "https://www.smbs.solutions/legal-and-compliance-resources-legal-assistance-for-businesses",
73
+ "https://ignition.law/lawyers-for-smes/",
74
+ "https://www.cocredo.co.uk/news/free-legal-advice-small-business-owners",
75
+ "https://www.gannons.co.uk/sectors/smes/",
76
+ "https://kkbservices.com/who-we-work-with/small-businesses/",
77
+ "https://farringfordlegal.co.uk/",
78
+ "https://stanislawlegal.com/en/legal-solutions/for-sme-companies/",
79
+ "https://www.lawhive.co.uk/small-business/",
80
+ "https://www.catalystlaw.co.uk/business-legal-advice.html",
81
+ "https://medium.com/@kmitsme123/legal-consulting-tips-for-your-small-business-9075005eb574",
82
+ "https://smecomply.co.uk/",
83
+ "https://dojobusiness.com/blogs/news/legal-consultant-complete-guide",
84
+ "https://englishlegaladvice.com/",
85
+ ]
86
+
87
+ try:
88
+ # Load documents with error handling
89
+ docs = []
90
+ successful_loads = 0
91
+
92
+ for url in urls:
93
+ try:
94
+ loader = WebBaseLoader(url)
95
+ docs.extend(loader.load())
96
+ successful_loads += 1
97
+ logger.info(f"Successfully loaded: {url}")
98
+ except Exception as e:
99
+ logger.warning(f"Failed to load {url}: {e}")
100
+ continue
101
+
102
+ logger.info(f"Successfully loaded {successful_loads}/{len(urls)} URLs")
103
+
104
+ if not docs:
105
+ logger.warning("No documents could be loaded, using fallback mode")
106
+ return
107
+
108
+ # Split documents
109
+ text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
110
+ chunk_size=300, chunk_overlap=50
111
+ )
112
+ doc_splits = text_splitter.split_documents(docs)
113
+ logger.info(f"Created {len(doc_splits)} document chunks")
114
+
115
+ # Create embeddings and vectorstore
116
+ embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001")
117
+ vectorstore = InMemoryVectorStore.from_documents(
118
+ documents=doc_splits,
119
+ embedding=embeddings
120
+ )
121
+
122
+ # Create retriever tool
123
+ retriever = vectorstore.as_retriever()
124
+ self.retriever_tool = create_retriever_tool(
125
+ retriever,
126
+ name="legal_consulting_retriever",
127
+ description="Search and return relevant legal information and consulting resources for small and medium-sized businesses."
128
+ )
129
+ logger.info("Retriever tool created successfully")
130
+
131
+ except Exception as e:
132
+ logger.error(f"Error setting up retriever: {e}")
133
+ self.retriever_tool = None
134
+
135
+ def setup_workflow(self):
136
+ """Set up the LangGraph workflow."""
137
+ try:
138
+ if not self.response_model:
139
+ logger.error("Response model not available")
140
+ return
141
+
142
+ # Create workflow
143
+ workflow = StateGraph(MessagesState)
144
+
145
+ # Add nodes
146
+ workflow.add_node("generate_query_or_respond", self.generate_query_or_respond)
147
+ if self.retriever_tool:
148
+ workflow.add_node("retrieve", ToolNode([self.retriever_tool]))
149
+ workflow.add_node("grade_documents", self.grade_documents_node)
150
+ workflow.add_node("rewrite_question", self.rewrite_question)
151
+ workflow.add_node("generate_answer", self.generate_answer)
152
+
153
+ # Add edges
154
+ workflow.add_edge(START, "generate_query_or_respond")
155
+
156
+ if self.retriever_tool:
157
+ workflow.add_conditional_edges(
158
+ "generate_query_or_respond",
159
+ tools_condition,
160
+ {
161
+ "tools": "retrieve",
162
+ END: END,
163
+ },
164
+ )
165
+ workflow.add_edge("retrieve", "grade_documents")
166
+ workflow.add_conditional_edges(
167
+ "grade_documents",
168
+ lambda x: x.get("grade_result", "generate_answer"),
169
+ {
170
+ "generate_answer": "generate_answer",
171
+ "rewrite_question": "rewrite_question"
172
+ }
173
+ )
174
+ else:
175
+ workflow.add_edge("generate_query_or_respond", END)
176
+
177
+ workflow.add_edge("generate_answer", END)
178
+ workflow.add_edge("rewrite_question", "generate_query_or_respond")
179
+
180
+ self.graph = workflow.compile()
181
+ logger.info("Workflow compiled successfully")
182
+
183
+ except Exception as e:
184
+ logger.error(f"Error setting up workflow: {e}")
185
+ self.graph = None
186
+
187
+ def generate_query_or_respond(self, state: MessagesState):
188
+ """Generate query or respond directly."""
189
+ try:
190
+ if not self.retriever_tool:
191
+ # Fallback response when retriever is not available
192
+ fallback_response = """I'm a legal consulting assistant for small and medium enterprises. While my document retriever is currently unavailable, I can still help answer general questions about:
193
+
194
+ - Business formation and structure
195
+ - Contract basics and employment law
196
+ - Intellectual property fundamentals
197
+ - Compliance and regulatory matters
198
+ - General legal considerations for SMEs
199
+
200
+ Please note: This is general information only, not legal advice. Always consult qualified legal professionals for specific matters."""
201
+ return {"messages": [AIMessage(content=fallback_response)]}
202
+
203
+ response = (
204
+ self.response_model
205
+ .bind_tools([self.retriever_tool])
206
+ .invoke(state["messages"])
207
+ )
208
+ return {"messages": [response]}
209
+ except Exception as e:
210
+ logger.error(f"Error in generate_query_or_respond: {e}")
211
+ return {"messages": [AIMessage(content="I'm sorry, I encountered an error. Please try again.")]}
212
+
213
+ class GradeDocuments(BaseModel):
214
+ """Grade documents using a binary score for relevance check."""
215
+ binary_score: str = Field(
216
+ description="Relevance score: 'yes' if relevant, or 'no' if not relevant"
217
+ )
218
+
219
+ def grade_documents_node(self, state: MessagesState):
220
+ """Node wrapper for document grading."""
221
+ try:
222
+ question = state["messages"][0].content
223
+ context = state["messages"][-1].content if state["messages"] else ""
224
+
225
+ GRADE_PROMPT = (
226
+ "You are a grader assessing relevance of a retrieved document to a user question. \n "
227
+ "Here is the retrieved document: \n\n {context} \n\n"
228
+ "Here is the user question: {question} \n"
229
+ "If the document contains keyword(s) or semantic meaning related to the user question, grade it as relevant. \n"
230
+ "Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question."
231
+ )
232
+
233
+ prompt = GRADE_PROMPT.format(question=question, context=context)
234
+ response = (
235
+ self.grader_model
236
+ .with_structured_output(self.GradeDocuments)
237
+ .invoke([{"role": "user", "content": prompt}])
238
+ )
239
+
240
+ grade_result = "generate_answer" if response.binary_score == "yes" else "rewrite_question"
241
+ return {"grade_result": grade_result}
242
+ except Exception as e:
243
+ logger.error(f"Error in grade_documents_node: {e}")
244
+ return {"grade_result": "generate_answer"} # Default to generating answer
245
+
246
+ def rewrite_question(self, state: MessagesState):
247
+ """Rewrite the original user question."""
248
+ try:
249
+ messages = state["messages"]
250
+ question = messages[0].content if messages else ""
251
+
252
+ REWRITE_PROMPT = (
253
+ "Look at the input and try to reason about the underlying semantic intent / meaning.\n"
254
+ "Here is the initial question:"
255
+ "\n ------- \n"
256
+ "{question}"
257
+ "\n ------- \n"
258
+ "Formulate an improved question that would be better for searching legal consulting information:"
259
+ )
260
+
261
+ prompt = REWRITE_PROMPT.format(question=question)
262
+ response = self.response_model.invoke([{"role": "user", "content": prompt}])
263
+ return {"messages": [HumanMessage(content=response.content)]}
264
+ except Exception as e:
265
+ logger.error(f"Error in rewrite_question: {e}")
266
+ # Return original message if rewriting fails
267
+ return {"messages": state["messages"][:1] if state["messages"] else []}
268
+
269
+ def generate_answer(self, state: MessagesState):
270
+ """Generate an answer based on retrieved context."""
271
+ try:
272
+ question = state["messages"][0].content if state["messages"] else ""
273
+ context = state["messages"][-1].content if len(state["messages"]) > 1 else ""
274
+
275
+ GENERATE_PROMPT = (
276
+ "You are an assistant for question-answering tasks about legal consulting for small and medium businesses. "
277
+ "Use the following pieces of retrieved context to answer the question. "
278
+ "If you don't know the answer, just say that you don't know. "
279
+ "Keep the answer concise but informative. Always remind users that this is general information and not legal advice.\n"
280
+ "Question: {question} \n"
281
+ "Context: {context}"
282
+ )
283
+
284
+ prompt = GENERATE_PROMPT.format(question=question, context=context)
285
+ response = self.response_model.invoke([{"role": "user", "content": prompt}])
286
+ return {"messages": [response]}
287
+ except Exception as e:
288
+ logger.error(f"Error in generate_answer: {e}")
289
+ return {"messages": [AIMessage(content="I'm sorry, I encountered an error while generating the answer. Please try again.")]}
290
+
291
+ def chat(self, message: str, history: list) -> str:
292
+ """Main chat function for Gradio interface."""
293
+ try:
294
+ if not message or not message.strip():
295
+ return "Please enter a question."
296
+
297
+ if not self.response_model:
298
+ return "Sorry, the system is not properly initialized. Please check if the API key is configured correctly."
299
+
300
+ # For simple cases without retriever, provide direct response
301
+ if not self.graph:
302
+ prompt = f"""You are a helpful assistant specializing in legal consulting for small and medium enterprises.
303
+ Answer this question: {message}
304
+
305
+ Always remind users that this is general information only and not professional legal advice."""
306
+
307
+ response = self.response_model.invoke([{"role": "user", "content": prompt}])
308
+ return response.content if hasattr(response, 'content') else str(response)
309
+
310
+ # Create initial state
311
+ initial_state = {"messages": [HumanMessage(content=message)]}
312
+
313
+ # Run the graph
314
+ result = self.graph.invoke(initial_state)
315
+
316
+ # Extract the final response
317
+ if result and "messages" in result and result["messages"]:
318
+ final_message = result["messages"][-1]
319
+ if hasattr(final_message, 'content'):
320
+ return final_message.content
321
+ else:
322
+ return str(final_message)
323
+ else:
324
+ return "I'm sorry, I couldn't generate a response. Please try again."
325
+
326
+ except Exception as e:
327
+ logger.error(f"Error in chat function: {e}")
328
+ return f"An error occurred while processing your request. Please try again."
329
+
330
+ # Initialize the bot
331
+ logger.info("Initializing Legal Consulting Bot...")
332
+ bot = LegalConsultingBot()
333
+
334
+ # Create Gradio interface
335
+ def create_interface():
336
+ with gr.Blocks(
337
+ title="Legal Consulting Assistant for SMEs",
338
+ theme=gr.themes.Soft(),
339
+ css="""
340
+ .container {
341
+ max-width: 800px;
342
+ margin: auto;
343
+ }
344
+ """
345
+ ) as demo:
346
+ gr.Markdown("""
347
+ # 🏒 Legal Consulting Assistant for Small & Medium Enterprises
348
+
349
+ Get informed answers about legal consulting, compliance, and business law for SMEs.
350
+ This assistant uses information from various legal consulting websites to provide relevant guidance.
351
+
352
+ **⚠️ Important**: This provides general information only and does not constitute professional legal advice.
353
+ """)
354
+
355
+ with gr.Row():
356
+ with gr.Column(scale=4):
357
+ chatbot = gr.Chatbot(
358
+ height=500,
359
+ placeholder="πŸ’¬ Ask me about legal consulting for your business...",
360
+ avatar_images=("πŸ‘€", "πŸ€–"),
361
+ bubble_full_width=False
362
+ )
363
+
364
+ with gr.Row():
365
+ msg = gr.Textbox(
366
+ placeholder="e.g., What legal structure should I choose for my startup?",
367
+ label="Your Question",
368
+ scale=4
369
+ )
370
+ submit_btn = gr.Button("Send", variant="primary", scale=1)
371
+
372
+ clear = gr.Button("πŸ—‘οΈ Clear Chat", variant="secondary")
373
+
374
+ def respond(message, chat_history):
375
+ if not message.strip():
376
+ return "", chat_history
377
+
378
+ # Show typing indicator
379
+ chat_history.append((message, "πŸ€” Thinking..."))
380
+ yield "", chat_history
381
+
382
+ # Get bot response
383
+ bot_message = bot.chat(message, chat_history)
384
+ chat_history[-1] = (message, bot_message)
385
+ yield "", chat_history
386
+
387
+ def clear_chat():
388
+ return []
389
+
390
+ # Event handlers
391
+ msg.submit(respond, [msg, chatbot], [msg, chatbot])
392
+ submit_btn.click(respond, [msg, chatbot], [msg, chatbot])
393
+ clear.click(clear_chat, outputs=[chatbot])
394
+
395
+ gr.Markdown("""
396
+ ### πŸ“‹ Example Questions
397
+ - *What legal structure should I choose for my small business?*
398
+ - *What compliance requirements do SMEs need to consider?*
399
+ - *How can I protect my business's intellectual property?*
400
+ - *What should be included in employment contracts?*
401
+
402
+ ### βš–οΈ Disclaimer
403
+ This chatbot provides general information about legal consulting for SMEs based on publicly available resources.
404
+ **This information should not be considered as professional legal advice.**
405
+ Always consult with qualified legal professionals for specific legal matters and before making important business decisions.
406
+ """)
407
+
408
+ return demo
409
+
410
+ # Create and launch the interface
411
+ if __name__ == "__main__":
412
+ demo = create_interface()
413
+ demo.launch(
414
+ server_name="0.0.0.0",
415
+ server_port=7860,
416
+ share=False
417
  )