Charles Grandjean commited on
Commit
6d35100
·
1 Parent(s): 2f4c4da

Update the retrieve doc

Browse files
agent_api.py CHANGED
@@ -129,7 +129,7 @@ class CyberLegalAPI:
129
  # Initialize Resend
130
  resend.api_key = os.getenv("RESEND_API_KEY")
131
  logger.info("✅ Resend client initialized")
132
-
133
  self.agent_client = CyberLegalAgent(llm=llm, tools=tools.tools_for_client,tools_facade=tools.tools_for_client_facade)
134
  self.agent_lawyer = CyberLegalAgent(llm=llm, tools=tools.tools_for_lawyer,tools_facade=tools.tools_for_lawyer_facade)
135
  self.pdf_analyzer = PDFAnalyzerAgent(llm=llm, mistral_client=mistral_client)
@@ -283,7 +283,7 @@ class CyberLegalAPI:
283
  logger.info(f"🤖 Calling agent.process_query with jurisdiction: {request.jurisdiction}")
284
  result = await agent.process_query(
285
  user_query=request.message,
286
- client_id=request.clientId,
287
  conversation_history=conversation_history,
288
  jurisdiction=request.jurisdiction,
289
  system_prompt=system_prompt
@@ -415,7 +415,7 @@ class CyberLegalAPI:
415
  logger.info("=" * 80)
416
  logger.info("📥 DOC_CREATOR REQUEST RECEIVED")
417
  logger.info("=" * 80)
418
- logger.info(f"👤 Client ID: {request.clientId}")
419
  logger.info(f"📋 Instruction: {request.instruction}")
420
  logger.info(f"📏 Document size: {len(request.documentContent)} bytes")
421
 
@@ -564,7 +564,7 @@ async def doc_creator_endpoint(request: DocCreatorRequest):
564
  - contentFormat: Always "html"
565
  - documentSummaries: Optional context from analyzed documents
566
  - conversationHistory: Optional previous conversation messages
567
- - clientId: Unique client identifier
568
 
569
  Returns:
570
  DocCreatorResponse with assistant's response and modified document
 
129
  # Initialize Resend
130
  resend.api_key = os.getenv("RESEND_API_KEY")
131
  logger.info("✅ Resend client initialized")
132
+
133
  self.agent_client = CyberLegalAgent(llm=llm, tools=tools.tools_for_client,tools_facade=tools.tools_for_client_facade)
134
  self.agent_lawyer = CyberLegalAgent(llm=llm, tools=tools.tools_for_lawyer,tools_facade=tools.tools_for_lawyer_facade)
135
  self.pdf_analyzer = PDFAnalyzerAgent(llm=llm, mistral_client=mistral_client)
 
283
  logger.info(f"🤖 Calling agent.process_query with jurisdiction: {request.jurisdiction}")
284
  result = await agent.process_query(
285
  user_query=request.message,
286
+ user_id=request.userId,
287
  conversation_history=conversation_history,
288
  jurisdiction=request.jurisdiction,
289
  system_prompt=system_prompt
 
415
  logger.info("=" * 80)
416
  logger.info("📥 DOC_CREATOR REQUEST RECEIVED")
417
  logger.info("=" * 80)
418
+ logger.info(f"👤 User ID: {request.userId}")
419
  logger.info(f"📋 Instruction: {request.instruction}")
420
  logger.info(f"📏 Document size: {len(request.documentContent)} bytes")
421
 
 
564
  - contentFormat: Always "html"
565
  - documentSummaries: Optional context from analyzed documents
566
  - conversationHistory: Optional previous conversation messages
567
+ - userId: Unique user identifier (UUID)
568
 
569
  Returns:
570
  DocCreatorResponse with assistant's response and modified document
agent_states/agent_state.py CHANGED
@@ -13,7 +13,7 @@ class AgentState(TypedDict):
13
  """
14
  # User interaction
15
  user_query: str
16
- client_id: Optional[str]
17
  conversation_history: List[Dict[str, str]]
18
  intermediate_steps: List[Dict[str, Any]]
19
  system_prompt: Optional[str]
 
13
  """
14
  # User interaction
15
  user_query: str
16
+ user_id: Optional[str]
17
  conversation_history: List[Dict[str, str]]
18
  intermediate_steps: List[Dict[str, Any]]
19
  system_prompt: Optional[str]
agent_states/lawyer_messenger_state.py CHANGED
@@ -12,7 +12,7 @@ class LawyerMessengerState(TypedDict):
12
 
13
  # Input
14
  conversation_history: List[dict]
15
- client_id: str
16
 
17
  # Intermediate
18
  lawyers: Optional[List[dict]] # Fetched from frontend API
 
12
 
13
  # Input
14
  conversation_history: List[dict]
15
+ user_id: str
16
 
17
  # Intermediate
18
  lawyers: Optional[List[dict]] # Fetched from frontend API
langgraph_agent.py CHANGED
@@ -113,10 +113,16 @@ class CyberLegalAgent:
113
  args["jurisdiction"] = state.get("jurisdiction")
114
  logger.info(f"🌍 Injecting jurisdiction: {args['jurisdiction']}")
115
 
116
- # Inject client_id for message_lawyer tool
117
  if tool_call['name'] == "message_lawyer":
118
- args["client_id"] = state.get("client_id")
119
- logger.info(f"👤 Injecting client_id: {args['client_id']}")
 
 
 
 
 
 
120
  tool_call['name']="_" + tool_call['name']
121
 
122
  result = await tool_func.ainvoke(args)
@@ -126,10 +132,10 @@ class CyberLegalAgent:
126
  state["intermediate_steps"] = intermediate_steps
127
  return state
128
 
129
- async def process_query(self, user_query: str, client_id: Optional[str] = None, jurisdiction: str = "Romania", conversation_history: Optional[List[Dict[str, str]]] = None, system_prompt: Optional[str] = None) -> Dict[str, Any]:
130
  initial_state = {
131
  "user_query": user_query,
132
- "client_id": client_id,
133
  "conversation_history": conversation_history or [],
134
  "intermediate_steps": [],
135
  "relevant_documents": [],
 
113
  args["jurisdiction"] = state.get("jurisdiction")
114
  logger.info(f"🌍 Injecting jurisdiction: {args['jurisdiction']}")
115
 
116
+ # Inject user_id for message_lawyer tool
117
  if tool_call['name'] == "message_lawyer":
118
+ args["user_id"] = state.get("user_id")
119
+ logger.info(f"👤 Injecting user_id: {args['user_id']}")
120
+
121
+ # Inject user_id for retrieve_lawyer_document tool
122
+ if tool_call['name'] == "retrieve_lawyer_document":
123
+ args["user_id"] = state.get("user_id")
124
+ logger.info(f"📄 Injecting user_id for retrieve_lawyer_document: {args['user_id']}")
125
+
126
  tool_call['name']="_" + tool_call['name']
127
 
128
  result = await tool_func.ainvoke(args)
 
132
  state["intermediate_steps"] = intermediate_steps
133
  return state
134
 
135
+ async def process_query(self, user_query: str, user_id: Optional[str] = None, jurisdiction: str = "Romania", conversation_history: Optional[List[Dict[str, str]]] = None, system_prompt: Optional[str] = None) -> Dict[str, Any]:
136
  initial_state = {
137
  "user_query": user_query,
138
+ "user_id": user_id,
139
  "conversation_history": conversation_history or [],
140
  "intermediate_steps": [],
141
  "relevant_documents": [],
prompts/main.py CHANGED
@@ -71,6 +71,7 @@ Lawyer Jurisdiction: {jurisdiction}
71
  ### Available Tools
72
  1. **query_knowledge_graph**: Search legal documents to answer questions about law, regulations and directives.
73
  2. **search_web**: Search the web for current information, court decisions, or news that may not be in the knowledge graph.
 
74
 
75
  ### Tool-Calling Process
76
  You operate in an iterative loop:
@@ -84,6 +85,7 @@ You operate in an iterative loop:
84
  2. Reference specific articles, paragraphs, recitals, and provisions from regulations and directives. Include precise legal citations.
85
  3. Analyze legal implications, potential interpretations, and relevant jurisprudence where applicable.
86
  4. Create a section at the end of your response called "References" that lists the source documents used to answer the user's question with full legal citations.
 
87
 
88
  ### Tone
89
  - Professional and authoritative
@@ -94,13 +96,3 @@ You operate in an iterative loop:
94
 
95
  # Default system prompt (client-friendly for backward compatibility)
96
  SYSTEM_PROMPT = SYSTEM_PROMPT_CLIENT
97
-
98
- RESPONSE_FORMATTING_PROMPT = """### Task
99
- We aim at answering the user's question based on your common knowledge as well as the knowledge graph context.
100
-
101
- ### Inputs
102
- **Knowledge Graph Context:**{context}
103
- **User Query:** {user_query}
104
-
105
- ### Response format
106
- Answer in clear language and provide a direct answer to the user's question."""
 
71
  ### Available Tools
72
  1. **query_knowledge_graph**: Search legal documents to answer questions about law, regulations and directives.
73
  2. **search_web**: Search the web for current information, court decisions, or news that may not be in the knowledge graph.
74
+ 3. **retrieve_lawyer_document**: Retrieve the full content of a specific document from your document database. Use this tool when you need to inspect the detailed text, clauses, or specific provisions of a document mentioned in your documents_tree. But avoid it if the summary key details and actors are sufficient as it can introduce long document in the chat.
75
 
76
  ### Tool-Calling Process
77
  You operate in an iterative loop:
 
85
  2. Reference specific articles, paragraphs, recitals, and provisions from regulations and directives. Include precise legal citations.
86
  3. Analyze legal implications, potential interpretations, and relevant jurisprudence where applicable.
87
  4. Create a section at the end of your response called "References" that lists the source documents used to answer the user's question with full legal citations.
88
+ 5. If the user asks a specific detail about a document from your documents_tree, use the retrieve_lawyer_document tool, but avoid it if the summary key details and actors are sufficient as it can introduce long document in the chat.
89
 
90
  ### Tone
91
  - Professional and authoritative
 
96
 
97
  # Default system prompt (client-friendly for backward compatibility)
98
  SYSTEM_PROMPT = SYSTEM_PROMPT_CLIENT
 
 
 
 
 
 
 
 
 
 
structured_outputs/api_models.py CHANGED
@@ -52,7 +52,7 @@ class DocumentsTree(BaseModel):
52
 
53
  class ChatRequest(BaseModel):
54
  """Chat request model"""
55
- clientId: str = Field(..., description="Unique client identifier")
56
  message: str = Field(..., description="User's question")
57
  conversationHistory: Optional[List[Message]] = Field(default=[], description="Previous conversation messages")
58
  userType: Optional[str] = Field(default="client", description="User type: 'client' for general users or 'lawyer' for legal professionals")
@@ -102,7 +102,7 @@ class DocCreatorRequest(BaseModel):
102
  contentFormat: str = Field(default="html", description="Format of document content (always 'html')")
103
  documents_tree: Optional[DocumentsTree] = Field(default=None, description="Hierarchical tree of documents for context")
104
  conversationHistory: Optional[List[Message]] = Field(default=[], description="Previous conversation messages")
105
- clientId: str = Field(..., description="Unique client identifier")
106
 
107
 
108
  class DocCreatorResponse(BaseModel):
 
52
 
53
  class ChatRequest(BaseModel):
54
  """Chat request model"""
55
+ userId: str = Field(..., description="Unique user identifier (UUID)")
56
  message: str = Field(..., description="User's question")
57
  conversationHistory: Optional[List[Message]] = Field(default=[], description="Previous conversation messages")
58
  userType: Optional[str] = Field(default="client", description="User type: 'client' for general users or 'lawyer' for legal professionals")
 
102
  contentFormat: str = Field(default="html", description="Format of document content (always 'html')")
103
  documents_tree: Optional[DocumentsTree] = Field(default=None, description="Hierarchical tree of documents for context")
104
  conversationHistory: Optional[List[Message]] = Field(default=[], description="Previous conversation messages")
105
+ userId: str = Field(..., description="Unique user identifier (UUID)")
106
 
107
 
108
  class DocCreatorResponse(BaseModel):
subagents/lawyer_messenger.py CHANGED
@@ -171,7 +171,7 @@ class LawyerMessengerAgent:
171
  API_KEY = os.getenv("CYBERLGL_API_KEY")
172
 
173
  payload = {
174
- "clientId": state["client_id"],
175
  "lawyerId": lawyer_selection["lawyer_id"],
176
  "subject": lawyer_selection["subject"],
177
  "message": lawyer_selection["message"]
@@ -221,11 +221,11 @@ class LawyerMessengerAgent:
221
  state["message_sent"] = False
222
  return state
223
 
224
- async def send_lawyer_message(self, conversation_history: List[dict], client_id: str) -> str:
225
  """Main entry point: identify lawyer and send message"""
226
  result = await self.workflow.ainvoke({
227
  "conversation_history": conversation_history,
228
- "client_id": client_id,
229
  "lawyers": None,
230
  "lawyer_selection": None,
231
  "message_sent": False,
 
171
  API_KEY = os.getenv("CYBERLGL_API_KEY")
172
 
173
  payload = {
174
+ "userId": state["user_id"],
175
  "lawyerId": lawyer_selection["lawyer_id"],
176
  "subject": lawyer_selection["subject"],
177
  "message": lawyer_selection["message"]
 
221
  state["message_sent"] = False
222
  return state
223
 
224
+ async def send_lawyer_message(self, conversation_history: List[dict], user_id: str) -> str:
225
  """Main entry point: identify lawyer and send message"""
226
  result = await self.workflow.ainvoke({
227
  "conversation_history": conversation_history,
228
+ "user_id": user_id,
229
  "lawyers": None,
230
  "lawyer_selection": None,
231
  "message_sent": False,
utils/tools.py CHANGED
@@ -219,14 +219,14 @@ async def message_lawyer() -> str:
219
  if lawyer_messenger_agent is None:
220
  raise ValueError("LawyerMessengerAgent not initialized. Please initialize it in agent_api.py")
221
 
222
- # conversation_history and client_id will be injected by the agent from state
223
- raise ValueError("conversation_history and client_id not provided - these should be injected by the agent")
224
 
225
  except Exception as e:
226
  return f"Error sending message to lawyer: {str(e)}"
227
 
228
  @tool
229
- async def _message_lawyer(conversation_history, client_id) -> str:
230
  """
231
  Send a message to a lawyer identified from the conversation.
232
 
@@ -245,7 +245,7 @@ async def _message_lawyer(conversation_history, client_id) -> str:
245
  raise ValueError("LawyerMessengerAgent not initialized. Please initialize it in agent_api.py")
246
 
247
 
248
- return await lawyer_messenger_agent.send_lawyer_message(conversation_history, client_id)
249
  except Exception as e:
250
  return f"Error sending message to lawyer: {str(e)}"
251
 
@@ -339,9 +339,118 @@ async def _edit_document(doc_text: str, instruction: str, doc_summaries: List[st
339
  return f"Error editing document: {str(e)}"
340
 
341
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  # Export tool sets for different user types
343
  tools_for_client_facade = [query_knowledge_graph, find_lawyers, message_lawyer, search_web, edit_document]
344
  tools_for_client = [_query_knowledge_graph, _find_lawyers, _message_lawyer, search_web, _edit_document]
345
- tools_for_lawyer_facade = [query_knowledge_graph, search_web, edit_document]
346
- tools_for_lawyer = [_query_knowledge_graph, search_web, _edit_document]
347
- tools = tools_for_client
 
219
  if lawyer_messenger_agent is None:
220
  raise ValueError("LawyerMessengerAgent not initialized. Please initialize it in agent_api.py")
221
 
222
+ # conversation_history and user_id will be injected by the agent from state
223
+ raise ValueError("conversation_history and user_id not provided - these should be injected by the agent")
224
 
225
  except Exception as e:
226
  return f"Error sending message to lawyer: {str(e)}"
227
 
228
  @tool
229
+ async def _message_lawyer(conversation_history, user_id) -> str:
230
  """
231
  Send a message to a lawyer identified from the conversation.
232
 
 
245
  raise ValueError("LawyerMessengerAgent not initialized. Please initialize it in agent_api.py")
246
 
247
 
248
+ return await lawyer_messenger_agent.send_lawyer_message(conversation_history, user_id)
249
  except Exception as e:
250
  return f"Error sending message to lawyer: {str(e)}"
251
 
 
339
  return f"Error editing document: {str(e)}"
340
 
341
 
342
+ @tool
343
+ async def retrieve_lawyer_document(file_path: str) -> str:
344
+ """
345
+ Retrieve a specific document from the lawyer's document database.
346
+
347
+ This tool fetches a document by its file path from the lawyer's personal
348
+ document storage. It returns the extracted text, summary, actors, and key
349
+ details for the document.
350
+
351
+ Use this tool when you need to inspect the full content of a document
352
+ mentioned in the documents_tree, such as when:
353
+ - User asks about specific clauses or details in a contract
354
+ - You need to verify information in a legal document
355
+ - User requests analysis of a specific document's content
356
+
357
+ Args:
358
+ file_path: Path to the document from the documents_tree
359
+ (e.g., "Contracts/bail-commercial.pdf" or "note-juridique.pdf")
360
+
361
+ Returns:
362
+ Document content including extracted_text, summary, actors, and key_details
363
+ """
364
+ try:
365
+ raise ValueError("user_id not provided - this should be injected by the agent")
366
+ except Exception as e:
367
+ return f"Error retrieving document: {str(e)}"
368
+
369
+
370
+ @tool
371
+ async def _retrieve_lawyer_document(
372
+ user_id: str,
373
+ file_path: str
374
+ ) -> str:
375
+ """
376
+ Retrieve a specific document from the lawyer's document database.
377
+
378
+ Args:
379
+ user_id: UUID of the lawyer (injected from userId)
380
+ file_path: Path to the document (e.g., "Contracts/bail-commercial.pdf")
381
+ format: "text" (default) returns JSON with extracted_text,
382
+ "raw" returns raw file bytes
383
+
384
+ Returns:
385
+ Document content including extracted_text, summary, actors, and key_details
386
+ """
387
+ try:
388
+ import httpx
389
+
390
+ # Check configuration from environment
391
+ lawyer_db_url = os.getenv("LAWYER_DB_URL")
392
+ cyberlgl_api_key = os.getenv("CYBERLGL_API_KEY")
393
+
394
+ if not lawyer_db_url:
395
+ return "Error: LAWYER_DB_URL not configured in environment"
396
+
397
+ if not cyberlgl_api_key:
398
+ return "Error: CYBERLGL_API_KEY not configured in environment"
399
+
400
+ # Build URL - remove any trailing slash
401
+ base_url = lawyer_db_url.rstrip('/')
402
+ url = f"{base_url}/retrieve-lawyer-document"
403
+
404
+ # Make request
405
+ async with httpx.AsyncClient() as client:
406
+ response = await client.get(
407
+ url,
408
+ params={
409
+ "lawyer_id": user_id,
410
+ "file_path": file_path,
411
+ "format": 'text'
412
+ },
413
+ headers={
414
+ "x-api-key": cyberlgl_api_key
415
+ },
416
+ timeout=30.0
417
+ )
418
+
419
+ # Handle different status codes
420
+ if response.status_code == 404:
421
+ return f"Document not found: {file_path}"
422
+ elif response.status_code == 403:
423
+ return "Access denied: You do not have permission to access this document"
424
+ elif response.status_code != 200:
425
+ return f"Error retrieving document: HTTP {response.status_code}"
426
+
427
+ # Parse JSON response
428
+ data = response.json()
429
+
430
+ # Format the response for the LLM
431
+ output = [
432
+ f"📄 Document: {data.get('file_name', file_path)}",
433
+ "-" * 30,
434
+ "\nExtracted Text:",
435
+ data.get('extracted_text', 'No text available'),
436
+ "-" * 80
437
+ ]
438
+
439
+ return "\n".join(output)
440
+
441
+ except httpx.TimeoutError:
442
+ return "Error: Timeout while retrieving document"
443
+ except httpx.RequestError as e:
444
+ return f"Error: Failed to connect to document server: {str(e)}"
445
+ except json.JSONDecodeError:
446
+ return "Error: Invalid response from document server"
447
+ except Exception as e:
448
+ return f"Error retrieving document: {str(e)}"
449
+
450
+
451
  # Export tool sets for different user types
452
  tools_for_client_facade = [query_knowledge_graph, find_lawyers, message_lawyer, search_web, edit_document]
453
  tools_for_client = [_query_knowledge_graph, _find_lawyers, _message_lawyer, search_web, _edit_document]
454
+ tools_for_lawyer_facade = [query_knowledge_graph, search_web, edit_document, retrieve_lawyer_document]
455
+ tools_for_lawyer = [_query_knowledge_graph, search_web, _edit_document, _retrieve_lawyer_document]
456
+ tools = tools_for_client