LegalDoc / setup_and_deployment.md
PrernaKu's picture
Upload 13 files
e36160c verified
|
Raw History Blame Contribute Delete
14 kB

Multi-Agent Procurement System: Setup & Deployment Guide

Quick Start

1. Prerequisites

  • Python 3.10+
  • pip or poetry for dependency management

2. Installation

# Create virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install dependencies
pip install -r requirements.txt

3. Configure Environment Variables

Create a .env file in the project root:

# LangChain / Anthropic Configuration
ANTHROPIC_API_KEY="sk-ant-xxx"

# LangSmith Configuration (Optional but recommended)
LANGSMITH_API_KEY="your_langsmith_key_here"
LANGSMITH_PROJECT="procurement_system"
LANGSMITH_ENDPOINT="https://api.smith.langchain.com"

# Optional: Debugging
DEBUG=False
LOG_LEVEL="INFO"

4. Run the System

python procurement_system.py

Architecture Deep Dive

Graph Flow Diagram

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    PROCUREMENT WORKFLOW                          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

                              START
                                β”‚
                                β–Ό
                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                        β”‚ ResearchNode β”‚ ◄─── Vendor Search Tool
                        β”‚ (Agent 1)    β”‚
                        β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚ Returns: vendor_options[]
                               β–Ό
                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                        β”‚ AnalysisNode β”‚ ◄─── Budget Validator Tool
                        β”‚ (Agent 2)    β”‚     LLM Recommendation
                        β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚
                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                 β”‚                            β”‚
            Within                        Exceeds
            Budget                        Budget
                 β”‚                            β”‚
                 β–Ό                            β–Ό
         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚ ApprovalGate β”‚              β”‚   END   β”‚
         β”‚ (Interrupts) β”‚              β”‚ REJECT  β”‚
         β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                β”‚
    *** GRAPH PAUSES HERE ***
    External system updates:
    state.human_approved = True/False
                β”‚
                β–Ό
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚ ApprovalDecRouter  β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚
         β”Œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚                β”‚
    Approved         Rejected
         β”‚                β”‚
         β–Ό                β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚LegalNodeβ”‚      β”‚  END   β”‚
    β”‚(Agent 3)β”‚      β”‚REJECT  β”‚
    β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β–Ό
       β”Œβ”€β”€β”€β”
       β”‚ENDβ”‚ (Contract generated)
       β””β”€β”€β”€β”˜

State Evolution

Initial State:

{
    "procurement_request": "...",
    "vendor_options": [],
    "selected_vendor": {},
    "budget_limit": 5500.0,
    "analysis_approved": False,
    "human_approved": False,
    "contract_draft": "",
    "logs": ["Workflow initiated"]
}

After ResearchNode:

{
    # ... previous fields
    "vendor_options": [
        {
            "vendor_id": "VENDOR_001",
            "name": "CloudTech Solutions",
            "price_per_month": 5000,
            "capabilities": [...],
            ...
        },
        # ... more vendors
    ]
}

After AnalysisNode (Budget OK):

{
    # ... previous fields
    "selected_vendor": {
        "vendor_id": "VENDOR_001",
        "name": "CloudTech Solutions",
        "price_per_month": 5000,
        ...
    },
    "analysis_approved": True
}

After ApprovalGate (INTERRUPTED):

Graph PAUSES at interrupt_before=["legal_node"]
Human reviews state and updates via executor.approve_vendor(approval=True)

After LegalNode:

{
    # ... all previous fields
    "contract_draft": "# PURCHASE AGREEMENT\n\n## Parties\n...",
    "human_approved": True
}

LangSmith Integration Setup

Why LangSmith?

For a multi-agent system, observability is critical:

  • Trace each LLM call: See what each agent is asking the LLM
  • Track tool invocations: Log vendor search and budget validation calls
  • Monitor latency: Identify bottlenecks in agent reasoning
  • Debug state transitions: Understand why the graph routed to a specific node
  • Audit trail: Full compliance logging for procurement decisions

Step-by-Step LangSmith Setup

1. Create LangSmith Account

2. Generate API Key

  • Navigate to Settings (bottom-left profile)
  • Click "API keys"
  • Create new API key
  • Copy the key (format: lsk_xyz...)

3. Configure Environment

# Set environment variables
export LANGSMITH_API_KEY="lsk_your_api_key_here"
export LANGSMITH_PROJECT="procurement_system"

# Verify configuration
python -c "from langsmith import Client; c = Client(); print('βœ“ LangSmith connected')"

4. Run System with Tracing

python procurement_system.py

5. View Traces in LangSmith Dashboard

  • Go to: https://smith.langchain.com/projects
  • Select project: "procurement_system"
  • Each workflow run appears as a trace tree showing:
    • ResearchNode execution: mock_vendor_search tool call
    • AnalysisNode: LLM invocation with vendor analysis prompt
    • AnalysisNode: validate_budget tool call
    • Approval interruption: State checkpoint
    • LegalNode: Contract generation LLM call

LangSmith Dashboard Features

Traces Tab

Run #1: Procurement for "Cloud Infrastructure"
β”œβ”€β”€ ResearchNode
β”‚   └── Tool: mock_vendor_search
β”‚       └── Input: "cloud infrastructure"
β”‚       └── Output: [3 vendors found]
β”‚
β”œβ”€β”€ AnalysisNode
β”‚   β”œβ”€β”€ LLM Call: ChatAnthropic
β”‚   β”‚   └── Tokens: 342 prompt, 198 completion
β”‚   β”‚   └── Cost: $0.00X
β”‚   β”‚   └── Latency: 1.23s
β”‚   └── Tool: validate_budget
β”‚       └── Status: APPROVED (within budget)
β”‚
└── ApprovalGate: INTERRUPTED

Analytics

  • Agent performance: Which agent takes longest?
  • LLM costs: Total spend on this project
  • Tool success rate: How often do tools succeed?
  • Latency breakdown: Time spent in each node

Dataset Features (for continuous improvement)

  • Log LLM calls as training examples
  • Create feedback loops for agent improvements
  • Track prompt performance over time

Production Deployment Considerations

1. Checkpointer Upgrade

Development (current):

checkpointer = MemorySaver()  # In-memory only, lost on restart

Production:

from langgraph.checkpoint.postgres import PostgresSaver

checkpointer = PostgresSaver(
    conn_string="postgresql://user:password@localhost:5432/langgraph"
)

This persists all state to a database, allowing:

  • Recovery from crashes
  • Long-running workflows
  • Concurrent workflow management

2. LLM Configuration

Development:

llm = ChatAnthropic(model="claude-3-5-sonnet-20241022", temperature=0.3)

Production - Consider:

llm = ChatAnthropic(
    model="claude-3-5-sonnet-20241022",
    temperature=0.1,  # Lower for consistency
    max_tokens=4096,
    timeout=30,
    max_retries=3  # Retry failed calls
)

3. Tool Enhancements

Mock tools (current):

@tool
def mock_vendor_search(query: str) -> list[dict]:
    # Returns hardcoded data

Production - Real implementations:

@tool
def real_vendor_search(query: str) -> list[dict]:
    """Search live vendor database or API."""
    response = requests.get(
        "https://vendor-api.example.com/search",
        params={"q": query}
    )
    return response.json()

@tool
def real_budget_validator(vendor_id: str, budget: float) -> dict:
    """Query actual ERP system for budget allocation."""
    erp = ERPClient(api_key=os.getenv("ERP_API_KEY"))
    allocation = erp.get_budget_allocation()
    return {"within_budget": allocation >= budget}

4. Error Handling & Resilience

def research_node(state: ProcurementState) -> Command:
    """Enhanced with error handling."""
    try:
        vendors = self.vendor_search_tool(state["procurement_request"])
        if not vendors:
            raise ValueError("No vendors found for request")
    except Exception as e:
        new_log = f"ERROR in ResearchNode: {str(e)}"
        return Command(
            update={"logs": [new_log]},
            goto=END  # Graceful failure
        )
    
    return Command(
        update={"vendor_options": vendors, "logs": [...]},
        goto="analysis_node"
    )

5. Approval Gate Enhancement

Current (manual):

state_update = {"human_approved": True}
self.graph.update_state(config, state_update)

Production (with workflow engine):

# Integrate with Slack bot, email approval link, web dashboard
async def wait_for_human_approval(thread_id: str, timeout_seconds=86400):
    """Poll approval status from database."""
    while True:
        approval = db.get_approval_status(thread_id)
        if approval is not None:
            return approval
        await asyncio.sleep(60)

6. Logging & Monitoring

import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

logger = logging.getLogger(__name__)

# In each node:
logger.info(f"ResearchNode: Processing request: {request}")
logger.info(f"AnalysisNode: Budget validation completed: {validation}")

7. Async Execution

# For long-running workflows, use async execution
from langgraph.types import StreamMode

async def run_async():
    config = {"configurable": {"thread_id": thread_id}}
    
    async for event in graph.astream(initial_state, config):
        logger.info(f"Event: {event}")

Troubleshooting

Issue: Graph doesn't pause at approval_gate

Cause: interrupt_before=["legal_node"] pauses before legal_node, not at approval_gate.

Solution: Current design is correct. The graph pauses when it reaches the logical point before executing legal_node (which only runs if approved).

Issue: State not persisting across restarts

Cause: Using MemorySaver (in-memory only).

Solution: Upgrade to PostgresSaver for production:

pip install "langgraph[postgres]"

Issue: LLM calls not appearing in LangSmith

Cause: LANGSMITH_API_KEY not set.

Solution:

export LANGSMITH_API_KEY="your_key"
echo $LANGSMITH_API_KEY  # Verify it's set
python procurement_system.py

Issue: Vendor search returns empty list

Cause: Query doesn't match mock data keywords.

Solution: Use "cloud" or "software" in procurement request:

executor.start_workflow(
    procurement_request="We need enterprise cloud infrastructure",  # βœ“ Works
    budget_limit=5000
)

Testing the System

Unit Test Example

import pytest
from procurement_system import ProcurementAgents, ProcurementState

@pytest.fixture
def llm():
    from langchain_anthropic import ChatAnthropic
    return ChatAnthropic(model="claude-3-5-sonnet-20241022")

def test_research_node(llm):
    agents = ProcurementAgents(llm)
    state = ProcurementState(
        procurement_request="cloud infrastructure",
        vendor_options=[],
        selected_vendor={},
        budget_limit=5000,
        analysis_approved=False,
        human_approved=False,
        contract_draft="",
        logs=[]
    )
    
    result = agents.research_node(state)
    assert len(result.update["vendor_options"]) > 0
    assert result.goto == "analysis_node"

def test_analysis_node_budget_exceeded(llm):
    agents = ProcurementAgents(llm)
    state = ProcurementState(
        procurement_request="",
        vendor_options=[{"name": "Expensive", "price_per_month": 10000}],
        selected_vendor={},
        budget_limit=1000,
        analysis_approved=False,
        human_approved=False,
        contract_draft="",
        logs=[]
    )
    
    result = agents.analysis_node(state)
    assert result.goto == END
    assert result.update["analysis_approved"] == False

# Run tests
pytest test_procurement.py -v

Next Steps

  1. Test locally with mock tools
  2. Integrate real tools: Connect to actual vendor APIs
  3. Deploy checkpointer: Use PostgreSQL for state persistence
  4. Add web UI: Build frontend for approval gate (Streamlit, FastAPI + React)
  5. Monitor in LangSmith: Track performance and costs
  6. Add compliance logging: Audit trail for procurement regulations

References