Spaces:
Running
Running
| import contextvars | |
| from typing import Literal, List | |
| from langchain_core.tools import tool | |
| from src.retriever import graph | |
| from src.retriever.state import RetrievedNode | |
| # ContextVar to collect retrieved nodes dynamically across tool executions for CLI/serve metadata | |
| retrieved_nodes_var = contextvars.ContextVar("retrieved_nodes", default=None) | |
| def _format_nodes(nodes: List[RetrievedNode]) -> str: | |
| """Helper to format retrieved nodes for LLM observation.""" | |
| if not nodes: | |
| return "No matching legal sections or procedures found." | |
| # Store in ContextVar if active | |
| collected = retrieved_nodes_var.get() | |
| if collected is not None: | |
| for node in nodes: | |
| if not any(x["node_id"] == node["node_id"] for x in collected): | |
| collected.append(node) | |
| formatted = [] | |
| for node in nodes: | |
| fmt = ( | |
| f"ID: {node['node_id']}\n" | |
| f"Act/Corpus: {node['act_code']}\n" | |
| f"Title: {node['title']}\n" | |
| f"Summary: {node.get('summary', '')}\n" | |
| f"Content:\n{node.get('content', '')}\n" | |
| f"Retrieval Method: {node.get('retrieval_method', 'unknown')}\n" | |
| f"Score: {node.get('score', 1.0):.2f}" | |
| ) | |
| formatted.append(fmt) | |
| return "\n---\n" + "\n---\n".join(formatted) | |
| async def search_statutes( | |
| statute_code: Literal["BNS", "BNSS", "BSA"], | |
| query: str, | |
| method: Literal["tree", "bm25", "hybrid"] = "hybrid" | |
| ) -> str: | |
| """ | |
| Searches a specific Indian statutory act (BNS, BNSS, or BSA) for sections relevant to a query. | |
| Use this when you need legal definitions, criminal offenses, punishments, or trial procedures. | |
| Parameters: | |
| - statute_code: | |
| - 'BNS' (Bharatiya Nyaya Sanhita - Criminal offences, penalties, punishments, murder, robbery, etc.) | |
| - 'BNSS' (Bharatiya Nagarik Suraksha Sanhita - Criminal trial procedures, arrests, bail, FIRs, investigations) | |
| - 'BSA' (Bharatiya Sakshya Adhiniyam - Evidence act, witnesses, confessions, burden of proof) | |
| - query: Specific keywords, section numbers, or legal scenario to search. | |
| - method: Search methodology ('tree' for hierarchical navigation, 'bm25' for keyword search, 'hybrid' for both). | |
| """ | |
| if not graph._corpus_index: | |
| return "Error: Corpus index not initialized. Ensure retriever.load() has been called." | |
| nodes = [] | |
| seen_ids = set() | |
| # 1. Tree Navigation (guided search) | |
| if method in ["tree", "hybrid"] and graph._tree_navigator: | |
| try: | |
| tree_nodes = await graph._tree_navigator.navigate(query, statute_code) | |
| for n in tree_nodes: | |
| if n["node_id"] not in seen_ids: | |
| nodes.append(n) | |
| seen_ids.add(n["node_id"]) | |
| except Exception as e: | |
| print(f"[Tool: search_statutes] Tree Nav failed: {e}") | |
| # 2. BM25 (keyword search) | |
| if method in ["bm25", "hybrid"] and graph._bm25_index: | |
| try: | |
| bm25_nodes = graph._bm25_index.search( | |
| query, | |
| graph._corpus_index, | |
| top_k=5, | |
| act_filter=[statute_code] | |
| ) | |
| for n in bm25_nodes: | |
| if n["node_id"] not in seen_ids: | |
| nodes.append(n) | |
| seen_ids.add(n["node_id"]) | |
| except Exception as e: | |
| print(f"[Tool: search_statutes] BM25 failed: {e}") | |
| return _format_nodes(nodes) | |
| async def search_police_sop(query: str) -> str: | |
| """ | |
| Searches the Police Standard Operating Procedures (SOP) manual for operational guidelines, | |
| patrol duties, checklists, timelines, and practical steps taken by police officers. | |
| Use this when the query relates to how a police officer should register an FIR, conduct an arrest, | |
| handle electronic evidence, or maintain a police station diary. | |
| """ | |
| if not graph._sop_retriever or not graph._corpus_index: | |
| return "Error: SOP retriever not initialized." | |
| try: | |
| nodes = await graph._sop_retriever.retrieve(query, top_k=5) | |
| return _format_nodes(nodes) | |
| except Exception as e: | |
| return f"Error searching Police SOP: {e}" | |
| async def enrich_with_cross_references(section_id: str) -> str: | |
| """ | |
| Fetches other legal sections that are cross-referenced or linked to a specific section ID (e.g., 'BNSS_S35'). | |
| Use this when you have retrieved a section and want to follow its legal citations to other acts or sections | |
| (for example, connecting a police procedure in the SOP to a section in the BNSS). | |
| """ | |
| if not graph._cross_ref_linker or not graph._corpus_index: | |
| return "Error: Cross-reference linker not initialized." | |
| node = graph._corpus_index.get_node(section_id) | |
| if not node: | |
| return f"Error: Legal section '{section_id}' not found in the index." | |
| # Construct a skeleton RetrievedNode representation for the linker to consume | |
| act_code = section_id.split("_")[0] | |
| p_node: RetrievedNode = { | |
| "node_id": section_id, | |
| "act_code": act_code, | |
| "title": node.get("title", ""), | |
| "summary": node.get("summary", ""), | |
| "content": node.get("content", ""), | |
| "score": 1.0, | |
| "node_type": node.get("node_type", "section"), | |
| "page_range": node.get("metadata", {}).get("page_range", []), | |
| "cross_act_refs": node.get("metadata", {}).get("cross_act_refs", []), | |
| "internal_refs": node.get("metadata", {}).get("internal_refs", []), | |
| "retrieval_method": "direct_lookup" | |
| } | |
| try: | |
| enriched_nodes = graph._cross_ref_linker.enrich([p_node], max_links_per_node=5) | |
| return _format_nodes(enriched_nodes) | |
| except Exception as e: | |
| return f"Error resolving cross references: {e}" | |