Detailed Documentation: agent_features.py (Developer 2 Domain)
This file manages the Specialized AI Agents: Multi-Agent Negotiation and the Legal Parser.
1. Environment & API Key Guard (Best Practice)
At the top of the file, we handle environment loading and an API key guard.
import os
from dotenv import load_dotenv
# 0. Load the .env file immediately
load_dotenv()
# Guard: Check if the key exists BEFORE initializing models
if not os.getenv("GROQ_API_KEY"):
raise ValueError("GROQ_API_KEY is missing. Check your .env file.")
- Viva Point: This check ensures the LLM's constructor (which will look for the key) doesn't fail with a confusing error message later in the code.
2. Multi-Agent System Roles & Temperatures
We use the state-of-the-art OpenAI GPT-OSS 120B model for these tasks due to the high reasoning requirements of negotiation and legal analysis.
# Using openai/gpt-oss-120b for all tasks - high reasoning for negotiation and legal
proposer_llm = ChatGroq(model="openai/gpt-oss-120b", temperature=0.5)
responder_llm = ChatGroq(model="openai/gpt-oss-120b", temperature=0.6)
evaluator_llm = ChatGroq(model="openai/gpt-oss-120b", temperature=0.0)
legal_llm = ChatGroq(model="openai/gpt-oss-120b", temperature=0.0)
- OpenAI GPT-OSS 120B: This model provides exceptional reasoning capabilities essential for multi-agent negotiation and legal analysis.
- Proposer & Responder: Higher temperatures (
0.5-0.6) allow for dynamic negotiation and creative counter-offers. - Evaluator & Legal: Set to
0.0for maximum precision. These agents MUST be objective and never "hallucinate" or vary their logic.
3. The Cyclic Multi-Agent Graph
This is the Cyclic Logic that makes the agents talk to each other.
_nw = StateGraph(NegotiationState)
_nw.add_node("proposer", _node_proposer)
_nw.add_node("responder", _node_responder)
_nw.add_node("evaluator", _node_evaluator)
_nw.add_edge(START, "proposer")
_nw.add_edge("proposer", "responder")
_nw.add_edge("responder", "evaluator")
_nw.add_conditional_edges(
"evaluator",
_route_negotiation,
{
"continue": "proposer",
"end": END,
},
)
- Step 1: The
proposermakes an offer on our behalf. - Step 2: The
respondersimulates the other party, countering or accepting. - Step 3: The
evaluator(the "referee") looks at the thread and decides if terms are agreed upon. - Step 4:
_route_negotiation(the loop brain) repeats the process unless agreement is reached or themax_roundslimit is hit.
4. Legal Document Parser
This feature uses a highly detailed Pydantic model to extract structured data from complex legal text.
class LegalParseOutput(BaseModel):
obligations: list[str] = Field(description="Explicit duties/commitments found")
deadlines: list[str] = Field(description="Dates, deadlines, or time constraints")
clauses: list[LegalClause] = Field(
description="Identified legal clauses with risk levels"
)
risk_flags: list[str] = Field(description="High-risk words or phrases")
overall_risk: Literal["low", "medium", "high"] = Field(
description="Overall legal risk rating"
)
plain_summary: str = Field(description="Plain-English explanation of commitments")
- Viva Point: This model forces the LLM to output structured risk data. Notice how
overall_riskis constrained to aLiteralof only three values, ensuring consistent data for our frontend UI.
5. API Endpoint Implementation
The bridge between HTTP and the AI graphs.
@router.post("/negotiate_email")
async def negotiate_email(request: NegotiationRequest):
result = await neg_graph.ainvoke({
"topic": request.topic,
"our_position": request.our_position,
"their_position": request.their_position,
"category": request.category,
"rounds": 0,
"max_rounds": request.max_rounds,
"history": [],
"evaluator_decision": None,
"agreement_reached": False,
})
return {
"topic": request.topic,
"rounds_completed": result["rounds"],
"agreement_reached": result["agreement_reached"],
"summary": result["evaluator_decision"].summary,
"email_thread": [
{"role": e.role, "subject": e.subject, "body": e.body}
for e in result["history"]
],
}
- Asynchronous Execution: Like the formalizer, this uses
ainvokeso that long negotiation loops don't block the entire server. - Summary Retrieval: Notice the check:
result["evaluator_decision"].summaryextracts the final verdict from the referee agent.
6. Available Tasks & Functional Logic
A. Multi-Agent Negotiation Simulation
Purpose: Helps users practice or simulate a difficult email negotiation before sending actual mail.
- Proposer Agent: Acts as the user's advocate. It tries to achieve the user's "Our Position" using persuasive but professional language.
- Responder Agent: Simulates the other party. It acts realistically—rejecting unfair offers and suggesting compromises.
- Evaluator Agent: Acts as the "Referee." It reads the back-and-forth history and identifies when an agreement has been reached, providing a final summary of the agreed terms.
B. Legal & Contract Parser
Purpose: Simplifies "legalese" and identifies hidden risks in contracts.
- Risk Scorecard: Identifies "Risk Flags" like indemnification, irrevocability, or sole discretion that might be dangerous for a user.
- Commitment Extraction: Clearly lists all Obligations (what you MUST do) and Deadlines (WHEN you must do it).
- Plain Summary: Translates complex legal clauses into 2-3 simple English sentences so anyone can understand what they are signing.