You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
412 lines
15 KiB
412 lines
15 KiB
from functools import lru_cache
|
|
from io import BytesIO
|
|
import os
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
from typing import List, Optional, Union
|
|
|
|
import PyPDF2
|
|
|
|
from swarms.structs.agent import Agent
|
|
from swarms.structs.conversation import Conversation
|
|
from swarms.structs.ma_utils import set_random_models_for_agents
|
|
from swarms.utils.history_output_formatter import (
|
|
history_output_formatter,
|
|
)
|
|
|
|
from swarms.utils.generate_keys import generate_api_key
|
|
|
|
# System prompts for each agent
|
|
CLARA_SYS_PROMPT = """
|
|
You are Clara, a meticulous and client-focused Criteria Agent specialized in understanding and validating contract requirements for a legal automation system. Your purpose is to ensure all contract criteria are clear, complete, and actionable.
|
|
|
|
KEY RESPONSIBILITIES:
|
|
- Extract and interpret contract requirements from client documents, text, or instructions.
|
|
- Validate criteria for completeness, consistency, and legal feasibility.
|
|
- Identify ambiguities, missing details, or potential risks in the requirements.
|
|
- Produce a clear, structured summary of the criteria for downstream use.
|
|
|
|
APPROACH:
|
|
- Begin with a professional introduction explaining your role in ensuring contract clarity.
|
|
- Ask targeted questions to resolve ambiguities or fill gaps in the criteria.
|
|
- Summarize and confirm requirements to ensure accuracy.
|
|
- Flag any criteria that may lead to legal or practical issues.
|
|
- Maintain strict confidentiality and data security.
|
|
|
|
OUTPUT FORMAT:
|
|
Provide a plain text summary with:
|
|
1. Validated contract criteria (e.g., parties, purpose, terms, jurisdiction).
|
|
2. Notes on any ambiguities or missing information.
|
|
3. Recommendations for clarifying or refining criteria.
|
|
"""
|
|
|
|
MASON_SYS_PROMPT = """
|
|
You are Mason, a precise and creative Contract Drafting Agent specialized in crafting exceptional, extensive legal contracts for a legal automation system. Your expertise lies in producing long, comprehensive, enforceable, and tailored contract documents that cover all possible contingencies and details.
|
|
|
|
KEY RESPONSIBILITIES:
|
|
- Draft detailed, lengthy contracts based on validated criteria provided.
|
|
- Ensure contracts are legally sound, exhaustive, and client-specific, addressing all relevant aspects thoroughly.
|
|
- Use precise language while maintaining accessibility for non-legal readers.
|
|
- Incorporate feedback from evaluations to refine and enhance drafts.
|
|
- Include extensive clauses to cover all potential scenarios, risks, and obligations.
|
|
|
|
APPROACH:
|
|
- Structure contracts with clear, detailed sections and consistent formatting.
|
|
- Include all essential elements (parties, purpose, terms, signatures, etc.) with comprehensive elaboration.
|
|
- Tailor clauses to address specific client needs, jurisdictional requirements, and potential future disputes.
|
|
- Provide in-depth explanations of terms, conditions, and contingencies.
|
|
- Highlight areas requiring further review or customization.
|
|
- Output the contract as a plain text string, avoiding markdown.
|
|
|
|
OUTPUT FORMAT:
|
|
Provide a plain text contract with:
|
|
1. Identification of parties and effective date.
|
|
2. Detailed statement of purpose and scope.
|
|
3. Exhaustive terms and conditions covering all possible scenarios.
|
|
4. Comprehensive rights and obligations of each party.
|
|
5. Detailed termination and amendment procedures.
|
|
6. Signature blocks.
|
|
7. Annotations for areas needing review (in comments).
|
|
"""
|
|
|
|
SOPHIA_SYS_PROMPT = """
|
|
You are Sophia, a rigorous and insightful Contract Evaluation Agent specialized in reviewing and improving legal contracts for a legal automation system. Your role is to evaluate contracts for quality, compliance, and clarity, providing actionable feedback to enhance the final document.
|
|
|
|
KEY RESPONSIBILITIES:
|
|
- Review draft contracts for legal risks, clarity, and enforceability.
|
|
- Identify compliance issues with relevant laws and regulations.
|
|
- Assess whether the contract meets the provided criteria and client needs.
|
|
- Provide specific, actionable feedback for revisions.
|
|
- Recommend areas requiring human attorney review.
|
|
|
|
APPROACH:
|
|
- Begin with a disclaimer that your evaluation is automated and not a substitute for human legal advice.
|
|
- Analyze the contract section by section, focusing on legal soundness and clarity.
|
|
- Highlight strengths and weaknesses, with emphasis on areas for improvement.
|
|
- Provide precise suggestions for revised language or additional clauses.
|
|
- Maintain a professional, constructive tone to support iterative improvement.
|
|
|
|
OUTPUT FORMAT:
|
|
Provide a plain text evaluation with:
|
|
1. Summary of the contract's strengths.
|
|
2. Identified issues (legal risks, ambiguities, missing elements).
|
|
3. Specific feedback for revisions (e.g., suggested clause changes).
|
|
4. Compliance notes (e.g., relevant laws or regulations).
|
|
5. Recommendations for human attorney review.
|
|
"""
|
|
|
|
|
|
class LegalSwarm:
|
|
def __init__(
|
|
self,
|
|
name: str = "Legal Swarm",
|
|
description: str = "A swarm of agents that can handle legal tasks",
|
|
max_loops: int = 1,
|
|
user_name: str = "John Doe",
|
|
documents: Optional[List[str]] = None,
|
|
output_type: str = "list",
|
|
):
|
|
"""
|
|
Initialize the LegalSwarm with a base LLM and configure agents.
|
|
|
|
Args:
|
|
llm (BaseLLM): The underlying LLM model for all agents.
|
|
max_loops (int): Maximum iterations for each agent's task.
|
|
"""
|
|
self.max_loops = max_loops
|
|
self.name = name
|
|
self.description = description
|
|
self.user_name = user_name
|
|
self.documents = documents
|
|
self.output_type = output_type
|
|
|
|
self.agents = self._initialize_agents()
|
|
self.agents = set_random_models_for_agents(self.agents)
|
|
self.conversation = Conversation()
|
|
self.handle_initial_processing()
|
|
|
|
def handle_initial_processing(self):
|
|
if self.documents:
|
|
documents_text = self.handle_documents(self.documents)
|
|
else:
|
|
documents_text = None
|
|
|
|
self.conversation.add(
|
|
role=self.user_name,
|
|
content=f"Firm Name: {self.name}\nFirm Description: {self.description}\nUser Name: {self.user_name}\nDocuments: {documents_text}",
|
|
)
|
|
|
|
def _initialize_agents(self) -> List[Agent]:
|
|
"""
|
|
Initialize all agents with their respective prompts and configurations.
|
|
|
|
Returns:
|
|
List[Agent]: List of Agent instances.
|
|
"""
|
|
return [
|
|
Agent(
|
|
agent_name="Clara-Intake-Agent",
|
|
agent_description="Handles client data intake and validation",
|
|
system_prompt=CLARA_SYS_PROMPT,
|
|
max_loops=self.max_loops,
|
|
dynamic_temperature_enabled=True,
|
|
output_type="final",
|
|
),
|
|
# Agent(
|
|
# agent_name="Riley-Report-Agent",
|
|
# agent_description="Generates client reports from intake data",
|
|
# system_prompt=RILEY_SYS_PROMPT,
|
|
# max_loops=self.max_loops,
|
|
# dynamic_temperature_enabled=True,
|
|
# output_type = "final"
|
|
# ),
|
|
Agent(
|
|
agent_name="Mason-Contract-Agent",
|
|
agent_description="Creates and updates legal contracts",
|
|
system_prompt=MASON_SYS_PROMPT,
|
|
max_loops=self.max_loops,
|
|
dynamic_temperature_enabled=True,
|
|
output_type="final",
|
|
),
|
|
Agent(
|
|
agent_name="Sophia-Counsel-Agent",
|
|
agent_description="Provides legal advice and compliance checks",
|
|
system_prompt=SOPHIA_SYS_PROMPT,
|
|
max_loops=self.max_loops,
|
|
dynamic_temperature_enabled=True,
|
|
output_type="final",
|
|
),
|
|
# Agent(
|
|
# agent_name="Ethan-Coordinator-Agent",
|
|
# agent_description="Manages workflow and communication",
|
|
# system_prompt=ETHAN_SYS_PROMPT,
|
|
# max_loops=self.max_loops,
|
|
# dynamic_temperature_enabled=True,
|
|
# output_type = "final"
|
|
# ),
|
|
]
|
|
|
|
@lru_cache(maxsize=1)
|
|
def handle_documents(self, documents: List[str]) -> str:
|
|
"""
|
|
Handle a list of documents concurrently, extracting text from PDFs and other documents.
|
|
|
|
Args:
|
|
documents (List[str]): List of document file paths to process.
|
|
|
|
Returns:
|
|
str: Combined text content from all documents.
|
|
"""
|
|
|
|
def process_document(file_path: str) -> str:
|
|
"""Process a single document and return its text content."""
|
|
if not os.path.exists(file_path):
|
|
return f"Error: File not found - {file_path}"
|
|
|
|
try:
|
|
if file_path.lower().endswith(".pdf"):
|
|
with open(file_path, "rb") as file:
|
|
pdf_reader = PyPDF2.PdfReader(file)
|
|
text = ""
|
|
for page in pdf_reader.pages:
|
|
text += page.extract_text() + "\n"
|
|
return text
|
|
else:
|
|
# Handle other document types (txt, docx, etc.)
|
|
with open(
|
|
file_path, "r", encoding="utf-8"
|
|
) as file:
|
|
return file.read()
|
|
except Exception as e:
|
|
return f"Error processing {file_path}: {str(e)}"
|
|
|
|
# Process documents concurrently
|
|
combined_text = ""
|
|
with ThreadPoolExecutor(
|
|
max_workers=min(len(documents), 4)
|
|
) as executor:
|
|
results = list(executor.map(process_document, documents))
|
|
combined_text = "\n\n".join(results)
|
|
|
|
return combined_text
|
|
|
|
def find_agent_by_name(self, name: str) -> Agent:
|
|
"""
|
|
Find an agent by their name.
|
|
"""
|
|
for agent in self.agents:
|
|
if agent.agent_name == name:
|
|
return agent
|
|
|
|
def initial_processing(self):
|
|
clara_agent = self.find_agent_by_name("Clara-Intake-Agent")
|
|
|
|
# Run Clara's agent
|
|
clara_output = clara_agent.run(
|
|
f"History: {self.conversation.get_str()}\n Create a structured summary document of the customer's case."
|
|
)
|
|
|
|
self.conversation.add(
|
|
role="Clara-Intake-Agent", content=clara_output
|
|
)
|
|
|
|
def create_contract(self, task: str):
|
|
mason_agent = self.find_agent_by_name("Mason-Contract-Agent")
|
|
|
|
mason_output = mason_agent.run(
|
|
f"History: {self.conversation.get_str()}\n Your purpose is to create a contract based on the following details: {task}"
|
|
)
|
|
|
|
self.conversation.add(
|
|
role="Mason-Contract-Agent", content=mason_output
|
|
)
|
|
|
|
artifact_id = generate_api_key(
|
|
"legal-swarm-artifact-", length=10
|
|
)
|
|
|
|
# Run Sophia's agent
|
|
sophia_agent = self.find_agent_by_name("Sophia-Counsel-Agent")
|
|
sophia_output = sophia_agent.run(
|
|
f"History: {self.conversation.get_str()}\n Your purpose is to review the contract Mason created and provide feedback."
|
|
)
|
|
|
|
self.conversation.add(
|
|
role="Sophia-Counsel-Agent", content=sophia_output
|
|
)
|
|
|
|
# Run Mason's agent
|
|
mason_agent = self.find_agent_by_name("Mason-Contract-Agent")
|
|
mason_output = mason_agent.run(
|
|
f"History: {self.conversation.get_str()}\n Your purpose is to update the contract based on the feedback Sophia provided."
|
|
)
|
|
|
|
self.conversation.add(
|
|
role="Mason-Contract-Agent", content=mason_output
|
|
)
|
|
|
|
self.create_pdf_from_string(
|
|
mason_output, f"{artifact_id}-contract.pdf"
|
|
)
|
|
|
|
def create_pdf_from_string(
|
|
self, string: str, output_path: Optional[str] = None
|
|
) -> Union[BytesIO, str]:
|
|
"""
|
|
Create a PDF from a string with proper formatting and styling.
|
|
|
|
Args:
|
|
string (str): The text content to convert to PDF
|
|
output_path (Optional[str]): If provided, save the PDF to this path. Otherwise return BytesIO object
|
|
|
|
Returns:
|
|
Union[BytesIO, str]: Either a BytesIO object containing the PDF or the path where it was saved
|
|
"""
|
|
try:
|
|
from reportlab.lib.pagesizes import letter
|
|
from reportlab.pdfgen import canvas
|
|
from reportlab.lib.styles import (
|
|
getSampleStyleSheet,
|
|
ParagraphStyle,
|
|
)
|
|
from reportlab.platypus import (
|
|
Paragraph,
|
|
SimpleDocTemplate,
|
|
)
|
|
from reportlab.lib.units import inch
|
|
|
|
# Create a buffer or file
|
|
if output_path:
|
|
doc = SimpleDocTemplate(output_path, pagesize=letter)
|
|
else:
|
|
buffer = BytesIO()
|
|
doc = SimpleDocTemplate(buffer, pagesize=letter)
|
|
|
|
# Create styles
|
|
styles = getSampleStyleSheet()
|
|
custom_style = ParagraphStyle(
|
|
"CustomStyle",
|
|
parent=styles["Normal"],
|
|
fontSize=12,
|
|
leading=14,
|
|
spaceAfter=12,
|
|
firstLineIndent=0.5 * inch,
|
|
)
|
|
|
|
# Prepare content
|
|
story = []
|
|
paragraphs = string.split("\n\n")
|
|
|
|
for para in paragraphs:
|
|
if para.strip():
|
|
story.append(
|
|
Paragraph(para.strip(), custom_style)
|
|
)
|
|
|
|
# Build PDF
|
|
doc.build(story)
|
|
|
|
if output_path:
|
|
return output_path
|
|
else:
|
|
buffer.seek(0)
|
|
return buffer
|
|
|
|
except ImportError:
|
|
raise ImportError(
|
|
"Please install reportlab: pip install reportlab"
|
|
)
|
|
except Exception as e:
|
|
raise Exception(f"Error creating PDF: {str(e)}")
|
|
|
|
def run(self, task: str):
|
|
"""
|
|
Process an input document through the swarm, coordinating tasks among agents.
|
|
|
|
Args:
|
|
task (str): The input task or text to process.
|
|
|
|
Returns:
|
|
Dict[str, Any]: Final output including client data, report, contract, counsel, and workflow status.
|
|
"""
|
|
self.conversation.add(role=self.user_name, content=task)
|
|
|
|
self.initial_processing()
|
|
|
|
self.create_contract(task)
|
|
|
|
return history_output_formatter(
|
|
self.conversation, type=self.output_type
|
|
)
|
|
|
|
|
|
# Example usage
|
|
if __name__ == "__main__":
|
|
# Initialize the swarm
|
|
swarm = LegalSwarm(
|
|
max_loops=1,
|
|
name="TGSC's Legal Swarm",
|
|
description="A swarm of agents that can handle legal tasks",
|
|
user_name="Kye Gomez",
|
|
output_type="json",
|
|
)
|
|
|
|
# Sample document for COO employment contract
|
|
sample_document = """
|
|
Company: Swarms TGSC
|
|
Entity Type: Delaware C Corporation
|
|
Position: Chief Operating Officer (COO)
|
|
Details: Creating an employment contract for a COO position with standard executive-level terms including:
|
|
- Base salary and equity compensation $5,000,000
|
|
- Performance bonuses and incentives
|
|
- Benefits package
|
|
- Non-compete and confidentiality clauses
|
|
- Termination provisions
|
|
- Stock options and vesting schedule
|
|
- Reporting structure and responsibilities
|
|
Contact: hr@swarms.tgsc
|
|
"""
|
|
|
|
# Run the swarm
|
|
result = swarm.run(task=sample_document)
|
|
print("Swarm Output:", result)
|