|
Download ARCHITECTURE.md from PrernaKu/LegalDoc: direct link, hf CLI and curl.
- Browser
- Download file 21.9 kB
-
https://huggingface.co/spaces/PrernaKu/LegalDoc/resolve/main/ARCHITECTURE.md
- Command line
-
hf download hf://spaces/PrernaKu/LegalDoc/ARCHITECTURE.md
-
curl -L -o ARCHITECTURE.md https://huggingface.co/spaces/PrernaKu/LegalDoc/resolve/main/ARCHITECTURE.md
21.9 kB
| # Multi-Agent Procurement System - Architecture Document | |
| ## Executive Summary | |
| This document provides a comprehensive architectural overview of the Multi-Agent Autonomous Procurement System. It is designed for architects, engineers, and technical leads evaluating this system for production deployment. | |
| **Key Design Principles:** | |
| - **Modularity**: Each agent has a single, well-defined responsibility | |
| - **Observability**: Full tracing of all LLM calls and agent decisions | |
| - **Human-in-the-Loop**: Critical decisions subject to human approval | |
| - **State Persistence**: Complete workflow state maintained across interruptions | |
| - **Production-Ready**: Follows enterprise patterns for error handling and monitoring | |
| --- | |
| ## System Overview | |
| ### High-Level Architecture | |
| ``` | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β MULTI-AGENT PROCUREMENT SYSTEM β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| External Inputs Agents & Tools State Management | |
| β β β | |
| ββ Procurement Request ββ ResearchAgent ββββ ββ ProcurementState | |
| ββ Budget Limit β ββ VendorSearch β β ββ vendor_options | |
| ββ (Human Approval) β β β ββ selected_vendor | |
| ββ AnalysisAgent ββ β β ββ analysis_approved | |
| β ββ LLM Eval β β β ββ human_approved | |
| β ββ BudgetCheck β β β ββ contract_draft | |
| β β β β | |
| ββ ApprovalGate ββββΌββΌβββ MemorySaver | |
| β ββ (Interrupts) β β β (Checkpointer) | |
| β β β β | |
| ββ LegalAgent βββ β β β | |
| β ββ ContractGen β β β | |
| β βΌ βΌ β | |
| ββββββββββββββββββββββββ β | |
| Command Routing | |
| ``` | |
| ### Data Flow | |
| ``` | |
| Initial Request | |
| β | |
| βΌ | |
| research_node: "Find vendors" | |
| β Tool: mock_vendor_search | |
| βΌ Returns: vendor_options[] | |
| analysis_node: "Evaluate & validate budget" | |
| ββ LLM Call: Recommend vendor | |
| ββ Tool: validate_budget | |
| β Returns: within_budget decision | |
| β | |
| ββ If within budget: | |
| β βΌ | |
| β approval_gate: "Request human approval" | |
| β β State Checkpoint Created | |
| β β *** GRAPH PAUSES *** | |
| β β (External: Human updates state) | |
| β βΌ | |
| β approval_decision: "Route based on approval" | |
| β β | |
| β ββ If approved: | |
| β β βΌ | |
| β β legal_node: "Generate contract" | |
| β β β LLM Call: Create purchase agreement | |
| β β βΌ | |
| β β contract_draft β END | |
| β β | |
| β ββ If rejected: | |
| β βΌ | |
| β END | |
| β | |
| ββ If exceeds budget: | |
| βΌ | |
| END (Rejection) | |
| ``` | |
| --- | |
| ## Component Architecture | |
| ### 1. State Schema (TypedDict) | |
| ```python | |
| class ProcurementState(TypedDict): | |
| """Complete graph state with type safety.""" | |
| # Input Fields | |
| procurement_request: str # What: Raw requirement | |
| budget_limit: float # Budget: Maximum spend | |
| # Agent Output Fields | |
| vendor_options: list[dict] # Research output: candidates | |
| selected_vendor: dict # Analysis output: chosen vendor | |
| # Decision Fields | |
| analysis_approved: bool # Analysis: Budget check result | |
| human_approved: bool # Approval gate: Human decision | |
| # Final Output | |
| contract_draft: str # Legal output: Purchase agreement | |
| # Audit | |
| logs: list[str] # Complete decision history | |
| ``` | |
| **Design Rationale:** | |
| - **Strict Types**: TypedDict enables IDE autocomplete and type checking | |
| - **Annotated[list, add]**: Custom operator for list accumulation in logs | |
| - **Immutable Pattern**: State never modified directly; new values via Command updates | |
| - **Audit Trail**: Every decision logged with timestamp | |
| ### 2. Agents | |
| #### ResearchAgent | |
| **Responsibility**: Vendor Discovery | |
| ``` | |
| Input: procurement_request: str | |
| Process: | |
| 1. Parse requirement | |
| 2. Call vendor search tool | |
| 3. Return structured vendor list | |
| Output: vendor_options: list[dict] | |
| Error Handling: Return empty list if search fails | |
| ``` | |
| **Tool Integration:** | |
| ```python | |
| @tool | |
| def mock_vendor_search(query: str) -> list[dict]: | |
| """Mock vendor database search.""" | |
| # Returns: [{"vendor_id", "name", "price_per_month", "capabilities", ...}] | |
| # Production: Replace with real APIs | |
| def real_vendor_search(query: str) -> list[dict]: | |
| return requests.get("https://api.vendors.com/search").json() | |
| ``` | |
| #### AnalysisAgent | |
| **Responsibility**: Vendor Evaluation & Budget Validation | |
| ``` | |
| Input: | |
| - vendor_options: list[dict] | |
| - budget_limit: float | |
| - procurement_request: str | |
| Process: | |
| 1. Use LLM to analyze vendors | |
| ββ Prompt: "Recommend best vendor considering price, reputation, capabilities" | |
| ββ Temperature: 0.3 (low randomness for consistency) | |
| ββ Max tokens: 2048 | |
| 2. Parse LLM response for recommendation | |
| 3. Extract selected vendor from options | |
| 4. Call budget_validator tool | |
| ββ Returns: {within_budget: bool, variance: float, status: "APPROVED|REJECTED"} | |
| 5. Route based on budget result | |
| ββ Within budget β approval_gate | |
| ββ Exceeded β END | |
| Output: | |
| - selected_vendor: dict | |
| - analysis_approved: bool | |
| Routing: | |
| - goto="approval_gate" (if approved) | |
| - goto=END (if rejected) | |
| ``` | |
| **LLM Considerations:** | |
| - **Model**: claude-3-5-sonnet-20241022 (balanced performance/cost) | |
| - **Temperature**: 0.3 (consistent financial analysis) | |
| - **System Prompt**: Embedded in PromptTemplate (financial context) | |
| - **Token Limit**: 2048 (sufficient for vendor analysis + response) | |
| #### LegalAgent | |
| **Responsibility**: Contract Generation | |
| ``` | |
| Input: | |
| - selected_vendor: dict | |
| - procurement_request: str | |
| - budget_limit: float | |
| Process: | |
| 1. Format vendor details for legal document | |
| 2. Call LLM with contract template | |
| ββ Prompt: "Generate professional purchase contract in markdown" | |
| ββ Includes: Parties, services, pricing, terms, warranties | |
| ββ Output: Markdown document | |
| 3. Format and return contract | |
| Output: | |
| - contract_draft: str (markdown) | |
| Routing: | |
| - goto=END (always terminates) | |
| ``` | |
| **Contract Template Includes:** | |
| - Header and document information | |
| - Parties (Company & Vendor) | |
| - Services/Goods description | |
| - Pricing and payment terms | |
| - Term and termination clauses | |
| - Liabilities and warranties | |
| - Confidentiality | |
| - Signature block | |
| ### 3. Tools | |
| #### Vendor Search Tool | |
| ```python | |
| @tool | |
| def mock_vendor_search(query: str) -> list[dict]: | |
| """ | |
| Discover vendors matching procurement request. | |
| Mock Database Structure: | |
| - Categories: cloud_infrastructure, software_licensing | |
| - Per vendor: id, name, price, capabilities, reputation, terms | |
| Production Integration: | |
| - Query vendor API | |
| - Filter by region, compliance, integration support | |
| - Return ranked by relevance/price | |
| """ | |
| ``` | |
| #### Budget Validator Tool | |
| ```python | |
| @tool | |
| def validate_budget(vendor_price: float, budget_limit: float) -> dict: | |
| """ | |
| Validate vendor pricing against budget. | |
| Returns: | |
| { | |
| "within_budget": True/False, | |
| "vendor_price": float, | |
| "budget_limit": float, | |
| "variance": float, # budget - price | |
| "variance_percentage": float, | |
| "status": "APPROVED" | "REJECTED" | |
| } | |
| Production Integration: | |
| - Query ERP system for available budget | |
| - Check cost center allocation | |
| - Validate against PO limits | |
| - Return approval status | |
| """ | |
| ``` | |
| ### 4. Graph Structure | |
| #### Nodes | |
| ``` | |
| START | |
| β | |
| research_node (ResearchAgent) | |
| β Command.goto="analysis_node" | |
| analysis_node (AnalysisAgent) | |
| ββ Command.goto="approval_gate" (within budget) | |
| ββ Command.goto=END (exceeds budget) | |
| β | |
| approval_gate (Interruption) | |
| β Command.goto="approval_decision" | |
| approval_decision (Router) | |
| ββ Route to "legal_node" (if human_approved) | |
| ββ Route to END (if not approved) | |
| β | |
| legal_node (LegalAgent) | |
| β Command.goto=END | |
| END | |
| ``` | |
| #### Edges | |
| ```python | |
| # Linear flow | |
| graph.add_edge(START, "research_node") | |
| # Command-based routing (agents return Command with explicit goto) | |
| # research_node β analysis_node (via Command) | |
| # analysis_node β {approval_gate OR END} (via Command) | |
| # Explicit edge for approval flow | |
| graph.add_edge("approval_gate", "approval_decision") | |
| # Conditional routing | |
| graph.add_conditional_edges( | |
| "approval_decision", | |
| lambda state: "legal_node" if state.get("human_approved") else END | |
| ) | |
| # Terminal edge | |
| graph.add_edge("legal_node", END) | |
| ``` | |
| #### Checkpointing Configuration | |
| ```python | |
| checkpointer = MemorySaver() # Development | |
| # or | |
| checkpointer = PostgresSaver() # Production | |
| compiled_graph = graph.compile( | |
| checkpointer=checkpointer, | |
| interrupt_before=["legal_node"] | |
| ) | |
| # Interruption Semantics: | |
| # - Graph executes all nodes until reaching legal_node | |
| # - Before executing legal_node, graph PAUSES | |
| # - Control returns to application | |
| # - State persisted to checkpointer | |
| # - External system updates state.human_approved | |
| # - graph.update_state(config, {"human_approved": True}) | |
| # - graph.stream(None, config) resumes execution | |
| # - Continues with approval_decision and legal_node | |
| ``` | |
| --- | |
| ## Execution Model | |
| ### Single Execution Cycle | |
| ``` | |
| Timeline: T=0ms to T=60ms | |
| T+0ms Input: procurement_request, budget_limit | |
| Initial State created | |
| T+5ms research_node executes | |
| β Input: procurement_request | |
| ββ Call: mock_vendor_search(request) | |
| β LangSmith: Log tool invocation | |
| ββ Output: vendor_options populated, goto="analysis_node" | |
| T+10ms analysis_node executes | |
| β Input: vendor_options, budget_limit, request | |
| ββ Call: LLM with vendor analysis prompt | |
| β LangSmith: Log LLM call (tokens, latency, cost) | |
| ββ Parse: Extract recommendation | |
| ββ Call: validate_budget(vendor_price, budget) | |
| β LangSmith: Log tool invocation | |
| ββ Decide: | |
| within_budget β goto="approval_gate" | |
| exceeded β goto=END | |
| T+15ms (if within budget) approval_gate executes | |
| β Input: selected_vendor, logs | |
| ββ Create log entry | |
| ββ Return Command(goto="approval_decision") | |
| T+16ms *** GRAPH PAUSES *** | |
| State checkpointed to MemorySaver | |
| Control returns to application | |
| External: Human reviews state | |
| External: Calls executor.approve_vendor(approval=True) | |
| External: graph.update_state(config, {"human_approved": True}) | |
| T+60s+ External: graph.stream(None, config) called to resume | |
| T+61ms approval_decision executes | |
| β Input: human_approved=True (updated state) | |
| ββ Route: goto="legal_node" | |
| T+70ms legal_node executes | |
| β Input: selected_vendor, request | |
| ββ Call: LLM with contract generation prompt | |
| β LangSmith: Log LLM call | |
| ββ Format: Markdown contract | |
| ββ Output: contract_draft populated, goto=END | |
| T+75ms Graph reaches END | |
| Execution complete | |
| Final state returned to application | |
| ``` | |
| ### State Checkpoint Mechanics | |
| **Checkpoint Creation (T+16ms):** | |
| ```python | |
| # MemorySaver internal representation | |
| checkpointer.storage = { | |
| "procurement_20250628_145230": { | |
| "values": { | |
| # Full state at interruption point | |
| "procurement_request": "...", | |
| "vendor_options": [...], | |
| "selected_vendor": {...}, | |
| "budget_limit": 5500.0, | |
| "analysis_approved": True, | |
| "human_approved": False, # Checkpoint created here | |
| "contract_draft": "", | |
| "logs": [...] | |
| }, | |
| "next": "approval_decision", # Next node to execute | |
| "task_id": "...", | |
| "channel": "input" | |
| } | |
| } | |
| ``` | |
| **State Retrieval & Update:** | |
| ```python | |
| # Retrieve saved state | |
| config = {"configurable": {"thread_id": "procurement_20250628_145230"}} | |
| saved_state = graph.get_state(config) # Full state recovered | |
| # Update with human decision | |
| graph.update_state(config, {"human_approved": True}) | |
| # State in checkpoint is now: | |
| # {..., "human_approved": True, ...} | |
| # Resume from checkpoint | |
| graph.stream(None, config) # Continues from next=approval_decision | |
| ``` | |
| --- | |
| ## Observability & Monitoring | |
| ### LangSmith Integration | |
| **Automatic Tracing:** | |
| ``` | |
| Every LLM call, tool invocation, and state transition is logged to LangSmith. | |
| Trace Structure: | |
| ββ Procurement Workflow Run | |
| β ββ research_node | |
| β β ββ Tool: mock_vendor_search | |
| β β ββ Input: {"query": "cloud infrastructure"} | |
| β β ββ Output: [3 vendors] | |
| β β ββ Latency: 2ms | |
| β β | |
| β ββ analysis_node | |
| β β ββ LLM: ChatAnthropic | |
| β β β ββ Prompt: "Analyze vendors..." | |
| β β β ββ Response: "Based on analysis..." | |
| β β β ββ Tokens: 342 in, 198 out | |
| β β β ββ Cost: $0.0032 | |
| β β β ββ Latency: 1.23s | |
| β β β | |
| β β ββ Tool: validate_budget | |
| β β ββ Input: {"vendor_price": 5000, "budget": 5500} | |
| β β ββ Output: {"within_budget": true, ...} | |
| β β ββ Latency: 1ms | |
| β β | |
| β ββ approval_gate | |
| β β ββ Status: INTERRUPTED (awaiting human approval) | |
| β β | |
| β ββ legal_node | |
| β ββ LLM: ChatAnthropic | |
| β ββ Prompt: "Generate contract..." | |
| β ββ Response: "# PURCHASE AGREEMENT..." | |
| β ββ Tokens: 287 in, 856 out | |
| β ββ Cost: $0.0056 | |
| β ββ Latency: 1.89s | |
| ``` | |
| **Analytics Available:** | |
| - **Traces**: View every run with full execution tree | |
| - **Metrics**: Token usage, latency by agent, cost per run | |
| - **Datasets**: Log examples for continuous improvement | |
| - **Feedback**: Mark runs as good/bad for analysis | |
| - **Comparison**: Compare LLM models or prompts | |
| ### Audit Trail | |
| State logs maintain complete decision history: | |
| ```python | |
| state["logs"] = [ | |
| "Workflow initiated", | |
| "[2025-06-28T14:52:30] ResearchNode: Analyzing request: We need enterprise cloud... | Found 3 vendors.", | |
| "[2025-06-28T14:52:31] AnalysisNode: Validating 3 vendors against budget $5500.00 | Selected: CloudTech Solutions | Status: APPROVED", | |
| "[2025-06-28T14:52:31] ApprovalGate: Awaiting human approval for CloudTech Solutions", | |
| "[2025-06-28T14:52:35] LegalNode: Drafting contract for CloudTech Solutions | Contract generated successfully" | |
| ] | |
| ``` | |
| **Compliance Features:** | |
| - Timestamped decision points | |
| - Vendor selection rationale | |
| - Budget validation evidence | |
| - Human approval tracking | |
| - Contract generation audit | |
| --- | |
| ## Failure Modes & Recovery | |
| ### Failure Mode: Research Tool Fails | |
| ``` | |
| research_node β Exception in mock_vendor_search | |
| β | |
| Error: "No vendors found for query" | |
| β | |
| Handling: | |
| - Log error: {"error": "vendor_search failed", "request": "..."} | |
| - Return empty vendor_options | |
| - Next node (analysis_node) handles gracefully or routes to END | |
| - User notified with audit trail | |
| ``` | |
| ### Failure Mode: LLM Call Fails | |
| ``` | |
| analysis_node β Timeout in ChatAnthropic.invoke() | |
| β | |
| Error: "Model request timed out" | |
| β | |
| Handling: | |
| - LangChain builtin: Automatic retry with exponential backoff | |
| - Max retries: 3 | |
| - If all retry: Exception bubbles up | |
| - Can be caught in node: return Command(goto=END, update={"error": "..."}) | |
| ``` | |
| ### Failure Mode: Graph Paused, Process Crashes | |
| ``` | |
| T+16ms: Graph paused, state checkpointed | |
| T+30s: Process crashes | |
| T+50s: New process starts with same thread_id | |
| β | |
| Recovery: | |
| 1. Create new graph instance | |
| 2. Call: saved_state = graph.get_state(config) | |
| β Retrieves full state from checkpoint | |
| 3. Update: graph.update_state(config, {"human_approved": True}) | |
| β Modifies checkpointed state | |
| 4. Resume: graph.stream(None, config) | |
| β Continues from approval_decision node | |
| 5. Final state: Returned with contract_draft populated | |
| ``` | |
| **Zero-Loss Recovery**: No lost state due to checkpointing. | |
| ### Failure Mode: Budget Exceeds | |
| ``` | |
| analysis_node β validate_budget(6000, 5000) β {"within_budget": False} | |
| β | |
| Handling: | |
| - Set analysis_approved = False | |
| - Route to END (not approval_gate) | |
| - Contract never generated | |
| - Audit log shows rejection reason | |
| - User notified: "Budget exceeded by $1000" | |
| ``` | |
| --- | |
| ## Scalability Considerations | |
| ### Current Architecture (MemorySaver) | |
| - **Single Process**: State held in memory | |
| - **Single Machine**: No inter-process state sharing | |
| - **Suitable For**: POC, development, low-volume deployments | |
| ### Production Architecture (PostgresSaver) | |
| **Recommended Setup:** | |
| ``` | |
| Multiple Worker Processes β PostgreSQL Database | |
| (Shared State) | |
| β Checkpointing | |
| β Shared thread storage | |
| ``` | |
| **Benefits:** | |
| - **Horizontal Scaling**: Add workers for concurrent workflows | |
| - **Persistence**: State survives process restarts | |
| - **Distributed**: Workers on different machines | |
| - **Concurrent Workflows**: 1000s of independent procurement requests | |
| **Estimated Capacity:** | |
| - 1 PostgreSQL instance: 10,000+ concurrent workflow states | |
| - 1 worker process: 10-50 concurrent workflows (I/O limited) | |
| - Scale out: Add more workers as needed | |
| ### Performance Optimization | |
| **Latency Breakdown (Current):** | |
| ``` | |
| research_node: ~5ms (mock tool, instant) | |
| analysis_node: ~1,200ms (1 LLM call) | |
| ββ LLM latency: ~1,200ms | |
| ββ Tool latency: ~1ms | |
| legal_node: ~1,900ms (1 LLM call) | |
| ββ LLM latency: ~1,900ms (longer response) | |
| ββ Tool latency: ~0ms | |
| Total: ~3.1 seconds execution | |
| Human approval: 60+ seconds (external) | |
| ``` | |
| **Optimization Opportunities:** | |
| 1. **Parallel Tool Calls**: Use asyncio for mock_vendor_search + validate_budget | |
| 2. **Model Selection**: Use claude-3-haiku for analysis (cheaper, faster) | |
| 3. **Prompt Optimization**: Reduce tokens in analysis prompt | |
| 4. **Caching**: Cache vendor list if query repeats | |
| 5. **Batch Processing**: Run multiple workflows with thread pools | |
| ### Cost Optimization | |
| **Current Pricing** (Claude 3.5 Sonnet): | |
| - Input tokens: $0.003 per 1K | |
| - Output tokens: $0.015 per 1K | |
| **Per-Workflow Cost:** | |
| - Analysis node: ~$0.0032 (342 in + 198 out) | |
| - Legal node: ~0.0056 (287 in + 856 out) | |
| - **Total: ~$0.0088 per complete workflow** | |
| **Cost Reduction:** | |
| - Switch to Haiku: ~90% cost reduction | |
| - Optimize prompts: Reduce token usage | |
| - Cache responses: Avoid repeated calls | |
| - Batch similar requests: Amortize overhead | |
| --- | |
| ## Security Architecture | |
| ### Data Protection | |
| - **State Checkpoint**: Stored in checkpointer (MemorySaver or PostgreSQL) | |
| - **Production**: Encrypt at rest (PostgreSQL TLS) | |
| - **Secrets**: Never logged or traced | |
| - **Audit**: All decisions logged with timestamps | |
| ### Access Control | |
| - **Graph Access**: Gated by thread_id (implicit isolation) | |
| - **State Updates**: Only via authenticated graph.update_state() calls | |
| - **Token Storage**: Never persisted (LLM calls ephemeral) | |
| - **API Keys**: Environment variables, not in code | |
| ### Compliance | |
| - **Audit Trail**: Complete decision history in state | |
| - **Timestamping**: All actions timestamped | |
| - **Approval Tracking**: human_approved flag with external update tracking | |
| - **Data Retention**: Checkpoint retention policy configurable | |
| --- | |
| ## Extension Points | |
| ### Adding New Agents | |
| ```python | |
| def new_agent_node(state: ProcurementState) -> Command: | |
| """New specialized agent.""" | |
| # Process state | |
| new_state = {"field": value} | |
| return Command(update=new_state, goto="next_node") | |
| # Add to graph | |
| graph.add_node("new_agent", new_agent_node) | |
| graph.add_edge("previous_node", "new_agent") | |
| ``` | |
| ### Adding New Tools | |
| ```python | |
| @tool | |
| def new_tool(param: str) -> dict: | |
| """Specialized tool for domain.""" | |
| # Implement business logic | |
| return {"result": "..."} | |
| # Use in agent nodes | |
| result = self.new_tool(state["field"]) | |
| ``` | |
| ### Custom Routing | |
| ```python | |
| def custom_router(state: ProcurementState) -> str: | |
| """Complex routing logic.""" | |
| if state["field"] == "value": | |
| return "node_a" | |
| else: | |
| return "node_b" | |
| graph.add_conditional_edges("source", custom_router) | |
| ``` | |
| ### Integration with External Systems | |
| ```python | |
| # Send approval request | |
| await send_slack_notification(state) | |
| # Sync to document management | |
| await upload_contract_to_docusign(state["contract_draft"]) | |
| # Log to compliance system | |
| await log_to_audit_trail(state["logs"]) | |
| ``` | |
| --- | |
| ## Conclusion | |
| This architecture provides a **production-grade foundation** for multi-agent procurement systems. Key strengths: | |
| 1. **Modularity**: Independent agents, tools, and routing | |
| 2. **Observability**: Full tracing via LangSmith | |
| 3. **Reliability**: State persistence and recovery | |
| 4. **Extensibility**: Easy to add agents, tools, and routing | |
| 5. **Compliance**: Complete audit trail and approval tracking | |
| The system is suitable for immediate POC deployment and scales to production with PostgreSQL checkpointing and real tool integrations. | |