Ayanshu's picture
Upload folder using huggingface_hub
f97126e verified
Raw
History Blame Contribute Delete
6.02 kB
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)
@tool
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)
@tool
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}"
@tool
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}"