Spaces:
Sleeping
Sleeping
Sahil Garg
commited on
Commit
Β·
2ac8811
1
Parent(s):
7e453aa
level-1 of generator and validator for notes
Browse files- SYSTEM_ARCHITECTURE_BOXES.md +139 -0
- agents/generator_validator.py +386 -0
- app.py +51 -19
SYSTEM_ARCHITECTURE_BOXES.md
CHANGED
|
@@ -2421,3 +2421,142 @@ FILE DOWNLOAD
|
|
| 2421 |
βxlsx β
|
| 2422 |
βββββββββββββββββββββββ
|
| 2423 |
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2421 |
βxlsx β
|
| 2422 |
βββββββββββββββββββββββ
|
| 2423 |
```
|
| 2424 |
+
|
| 2425 |
+
|
| 2426 |
+
|
| 2427 |
+
|
| 2428 |
+
|
| 2429 |
+
|
| 2430 |
+
**generator-validator**
|
| 2431 |
+
βββββββββββββββββββ
|
| 2432 |
+
β API Request β
|
| 2433 |
+
β POST /notes-llmβ
|
| 2434 |
+
β with file β
|
| 2435 |
+
βββββββββββ¬ββββββββ
|
| 2436 |
+
β
|
| 2437 |
+
βΌ
|
| 2438 |
+
βββββββββββββββββββ
|
| 2439 |
+
β create_notes_ β
|
| 2440 |
+
β pipeline() β
|
| 2441 |
+
β β
|
| 2442 |
+
β β LLMNotesGen β
|
| 2443 |
+
β β NotesValidatorβ
|
| 2444 |
+
βββββββββββ¬ββββββββ
|
| 2445 |
+
β
|
| 2446 |
+
βΌ
|
| 2447 |
+
βββββββββββββββββββ βββββββββββββββββββ
|
| 2448 |
+
βGenerator-Validatorββββββ Max 3 Attempts β
|
| 2449 |
+
β Pipeline β β β
|
| 2450 |
+
β β β β
|
| 2451 |
+
β βββββββββββββββ β βββββββββββββββββββ
|
| 2452 |
+
β β Attempt β β β No
|
| 2453 |
+
β β Counter=0 β β βΌ
|
| 2454 |
+
β βββββββββββββββ β βββββββββββββββββββ
|
| 2455 |
+
βββββββββββ¬ββββββββ β Return Best β
|
| 2456 |
+
β β Result β
|
| 2457 |
+
βΌ βββββββββββββββββββ
|
| 2458 |
+
βββββββββββββββββββ
|
| 2459 |
+
β Generate β
|
| 2460 |
+
β (LLM) β
|
| 2461 |
+
β β
|
| 2462 |
+
β - Call langgraphβ
|
| 2463 |
+
β - Use RLHF if β
|
| 2464 |
+
β requested β
|
| 2465 |
+
β - Track attempt β
|
| 2466 |
+
βββββββββββ¬ββββββββ
|
| 2467 |
+
β
|
| 2468 |
+
βΌ
|
| 2469 |
+
βββββββββββββββββββ
|
| 2470 |
+
β Validate β
|
| 2471 |
+
β Quality β
|
| 2472 |
+
β β
|
| 2473 |
+
β - File exists β
|
| 2474 |
+
β - Size >1KB β
|
| 2475 |
+
β - Metadata OK β
|
| 2476 |
+
β - RLHF quality β
|
| 2477 |
+
β - Score 0.0-1.0 β
|
| 2478 |
+
βββββββββββ¬ββββββββ
|
| 2479 |
+
β
|
| 2480 |
+
βββββββ΄ββββββ
|
| 2481 |
+
β β
|
| 2482 |
+
βΌ βΌ
|
| 2483 |
+
βββββββββββ βββββββββββ
|
| 2484 |
+
β Valid? β β Invalid β
|
| 2485 |
+
β Score β β Score β
|
| 2486 |
+
β β₯0.6 β β <0.6 β
|
| 2487 |
+
βββββββ¬ββββ βββββββ¬ββββ
|
| 2488 |
+
β β
|
| 2489 |
+
βΌ βΌ
|
| 2490 |
+
βββββββββββ βββββββββββ
|
| 2491 |
+
β Return β β Refine β
|
| 2492 |
+
β Success β β & Retry β
|
| 2493 |
+
β with β β β
|
| 2494 |
+
β Metadata β β - Use β
|
| 2495 |
+
β Headers β β feedbackβ
|
| 2496 |
+
βββββββββββ βββββββ¬ββββ
|
| 2497 |
+
β
|
| 2498 |
+
βΌ
|
| 2499 |
+
βββββββββββ
|
| 2500 |
+
βIncrement β
|
| 2501 |
+
β Attempt β
|
| 2502 |
+
β Counter β
|
| 2503 |
+
βββββββ¬ββββ
|
| 2504 |
+
β
|
| 2505 |
+
βββββββββββββββ
|
| 2506 |
+
βΌ
|
| 2507 |
+
βββββββββββββββββββ
|
| 2508 |
+
β Continue to β
|
| 2509 |
+
β Next Attempt β
|
| 2510 |
+
βββββββββββββββββββ
|
| 2511 |
+
|
| 2512 |
+
|
| 2513 |
+
**refine and retry**
|
| 2514 |
+
|
| 2515 |
+
βββββββββββββββββββ
|
| 2516 |
+
β Attempt 1 β
|
| 2517 |
+
β Generate β
|
| 2518 |
+
β β Validate β
|
| 2519 |
+
β Score: 0.4 β β FAIL (< 0.6)
|
| 2520 |
+
βββββββββββ¬ββββββββ
|
| 2521 |
+
β
|
| 2522 |
+
βΌ
|
| 2523 |
+
βββββββββββββββββββ
|
| 2524 |
+
β Refinement β
|
| 2525 |
+
β Analysis β
|
| 2526 |
+
β β
|
| 2527 |
+
β Feedback: β
|
| 2528 |
+
β - "Low quality" β
|
| 2529 |
+
β - "Small file" β
|
| 2530 |
+
βββββββββββ¬ββββββββ
|
| 2531 |
+
β
|
| 2532 |
+
βββββββ΄ββββββ
|
| 2533 |
+
β β
|
| 2534 |
+
βΌ βΌ
|
| 2535 |
+
βββββββββββ βββββββββββ
|
| 2536 |
+
βQuality β βOther β
|
| 2537 |
+
βIssue? β βIssue? β
|
| 2538 |
+
β β β β
|
| 2539 |
+
β"quality" β βFile size β
|
| 2540 |
+
βin feedbackβ βMetadata β
|
| 2541 |
+
βββββββ¬ββββ βββββββ¬ββββ
|
| 2542 |
+
β β
|
| 2543 |
+
βΌ βΌ
|
| 2544 |
+
βββββββββββ βββββββββββ
|
| 2545 |
+
βSwitch to β βSimple β
|
| 2546 |
+
βRLHF β βRetry β
|
| 2547 |
+
βMode β β β
|
| 2548 |
+
β β βUse same β
|
| 2549 |
+
βuse_rlhf= β βconfig β
|
| 2550 |
+
βtrue β β β
|
| 2551 |
+
βββββββ¬ββββ βββββββ¬ββββ
|
| 2552 |
+
β β
|
| 2553 |
+
βββββββ¬ββββββ
|
| 2554 |
+
β
|
| 2555 |
+
βΌ
|
| 2556 |
+
βββββββββββββββββββ
|
| 2557 |
+
β Attempt 2 β
|
| 2558 |
+
β Generate β
|
| 2559 |
+
β (Improved) β
|
| 2560 |
+
β β Validate β
|
| 2561 |
+
β Score: 0.8 β β
SUCCESS (β₯ 0.6)
|
| 2562 |
+
βββββββββββββββββββ
|
agents/generator_validator.py
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Generator-Validator Pattern Implementation for Financial Notes
|
| 3 |
+
Implements formal Generator-Validator pattern with iterative refinement
|
| 4 |
+
"""
|
| 5 |
+
import os
|
| 6 |
+
import json
|
| 7 |
+
import logging
|
| 8 |
+
from abc import ABC, abstractmethod
|
| 9 |
+
from typing import Dict, Any, List, Optional, Tuple
|
| 10 |
+
from dataclasses import dataclass
|
| 11 |
+
from datetime import datetime
|
| 12 |
+
import subprocess
|
| 13 |
+
import shutil
|
| 14 |
+
|
| 15 |
+
logger = logging.getLogger(__name__)
|
| 16 |
+
|
| 17 |
+
@dataclass
|
| 18 |
+
class ValidationResult:
|
| 19 |
+
"""Result of validation process"""
|
| 20 |
+
is_valid: bool
|
| 21 |
+
score: float
|
| 22 |
+
feedback: List[str]
|
| 23 |
+
suggestions: List[str]
|
| 24 |
+
metadata: Dict[str, Any]
|
| 25 |
+
|
| 26 |
+
@dataclass
|
| 27 |
+
class GenerationResult:
|
| 28 |
+
"""Result of generation process"""
|
| 29 |
+
success: bool
|
| 30 |
+
output_path: Optional[str]
|
| 31 |
+
data: Optional[Dict[str, Any]]
|
| 32 |
+
error: Optional[str]
|
| 33 |
+
metadata: Dict[str, Any]
|
| 34 |
+
|
| 35 |
+
class BaseGenerator(ABC):
|
| 36 |
+
"""Abstract base class for financial statement generators"""
|
| 37 |
+
|
| 38 |
+
def __init__(self, max_attempts: int = 3):
|
| 39 |
+
self.max_attempts = max_attempts
|
| 40 |
+
self.attempts_made = 0
|
| 41 |
+
|
| 42 |
+
@abstractmethod
|
| 43 |
+
def generate(self, file_path: str, **kwargs) -> GenerationResult:
|
| 44 |
+
"""Generate financial statement from input file"""
|
| 45 |
+
pass
|
| 46 |
+
|
| 47 |
+
@abstractmethod
|
| 48 |
+
def refine(self, previous_result: GenerationResult, feedback: List[str]) -> GenerationResult:
|
| 49 |
+
"""Refine generation based on validation feedback"""
|
| 50 |
+
pass
|
| 51 |
+
|
| 52 |
+
class BaseValidator(ABC):
|
| 53 |
+
"""Abstract base class for financial statement validators"""
|
| 54 |
+
|
| 55 |
+
@abstractmethod
|
| 56 |
+
def validate(self, generation_result: GenerationResult) -> ValidationResult:
|
| 57 |
+
"""Validate the generated financial statement"""
|
| 58 |
+
pass
|
| 59 |
+
|
| 60 |
+
@abstractmethod
|
| 61 |
+
def get_validation_criteria(self) -> List[str]:
|
| 62 |
+
"""Return list of validation criteria"""
|
| 63 |
+
pass
|
| 64 |
+
|
| 65 |
+
class LLMNotesGenerator(BaseGenerator):
|
| 66 |
+
"""Generator for AI-powered financial notes"""
|
| 67 |
+
|
| 68 |
+
def __init__(self, max_attempts: int = 3, use_rlhf: bool = False):
|
| 69 |
+
super().__init__(max_attempts)
|
| 70 |
+
self.use_rlhf = use_rlhf
|
| 71 |
+
|
| 72 |
+
def generate(self, file_path: str, **kwargs) -> GenerationResult:
|
| 73 |
+
"""Generate notes using AI/LLM approach"""
|
| 74 |
+
try:
|
| 75 |
+
self.attempts_made += 1
|
| 76 |
+
execution_id = f"notes_llm_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{self.attempts_made}"
|
| 77 |
+
|
| 78 |
+
# Choose workflow based on RLHF preference
|
| 79 |
+
if self.use_rlhf:
|
| 80 |
+
from agents.rlhf_workflows import run_rlhf_workflow
|
| 81 |
+
result = run_rlhf_workflow(file_path, "notes-llm")
|
| 82 |
+
else:
|
| 83 |
+
from agents.langgraph import run_workflow
|
| 84 |
+
result = run_workflow(file_path, "notes-llm")
|
| 85 |
+
|
| 86 |
+
if result["status"] == "success":
|
| 87 |
+
return GenerationResult(
|
| 88 |
+
success=True,
|
| 89 |
+
output_path=result["result"]["output_xlsx_path"],
|
| 90 |
+
data=result["result"],
|
| 91 |
+
error=None,
|
| 92 |
+
metadata={
|
| 93 |
+
"execution_id": execution_id,
|
| 94 |
+
"generation_method": "llm",
|
| 95 |
+
"use_rlhf": self.use_rlhf,
|
| 96 |
+
"attempt": self.attempts_made,
|
| 97 |
+
"rlhf_metadata": result["result"].get("rlhf_metadata", {})
|
| 98 |
+
}
|
| 99 |
+
)
|
| 100 |
+
else:
|
| 101 |
+
return GenerationResult(
|
| 102 |
+
success=False,
|
| 103 |
+
output_path=None,
|
| 104 |
+
data=None,
|
| 105 |
+
error=result.get("error", "Unknown error"),
|
| 106 |
+
metadata={
|
| 107 |
+
"execution_id": execution_id,
|
| 108 |
+
"generation_method": "llm",
|
| 109 |
+
"use_rlhf": self.use_rlhf,
|
| 110 |
+
"attempt": self.attempts_made
|
| 111 |
+
}
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
except Exception as e:
|
| 115 |
+
logger.error(f"LLM Notes generation failed: {e}")
|
| 116 |
+
return GenerationResult(
|
| 117 |
+
success=False,
|
| 118 |
+
output_path=None,
|
| 119 |
+
data=None,
|
| 120 |
+
error=str(e),
|
| 121 |
+
metadata={
|
| 122 |
+
"execution_id": f"error_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
|
| 123 |
+
"generation_method": "llm",
|
| 124 |
+
"use_rlhf": self.use_rlhf,
|
| 125 |
+
"attempt": self.attempts_made
|
| 126 |
+
}
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
def refine(self, previous_result: GenerationResult, feedback: List[str]) -> GenerationResult:
|
| 130 |
+
"""Refine LLM notes generation based on feedback"""
|
| 131 |
+
logger.info(f"Refining LLM notes generation with feedback: {feedback}")
|
| 132 |
+
|
| 133 |
+
# For LLM generation, we can try different approaches:
|
| 134 |
+
# 1. Switch to RLHF if not already using it
|
| 135 |
+
# 2. Retry with different parameters
|
| 136 |
+
# 3. Use fallback models
|
| 137 |
+
|
| 138 |
+
if not self.use_rlhf and "quality" in str(feedback).lower():
|
| 139 |
+
# If quality issues and not using RLHF, try RLHF
|
| 140 |
+
logger.info("Switching to RLHF for better quality")
|
| 141 |
+
original_rlhf = self.use_rlhf
|
| 142 |
+
self.use_rlhf = True
|
| 143 |
+
result = self.generate(previous_result.data.get("file_path") if previous_result.data else None)
|
| 144 |
+
self.use_rlhf = original_rlhf # Reset for future calls
|
| 145 |
+
return result
|
| 146 |
+
else:
|
| 147 |
+
# Otherwise, just retry
|
| 148 |
+
return self.generate(previous_result.data.get("file_path") if previous_result.data else None)
|
| 149 |
+
"""Generator for AI-powered financial notes"""
|
| 150 |
+
|
| 151 |
+
def __init__(self, max_attempts: int = 3, use_rlhf: bool = False):
|
| 152 |
+
super().__init__(max_attempts)
|
| 153 |
+
self.use_rlhf = use_rlhf
|
| 154 |
+
|
| 155 |
+
def generate(self, file_path: str, **kwargs) -> GenerationResult:
|
| 156 |
+
"""Generate notes using AI/LLM approach"""
|
| 157 |
+
try:
|
| 158 |
+
self.attempts_made += 1
|
| 159 |
+
execution_id = f"notes_llm_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{self.attempts_made}"
|
| 160 |
+
|
| 161 |
+
# Choose workflow based on RLHF preference
|
| 162 |
+
if self.use_rlhf:
|
| 163 |
+
from agents.rlhf_workflows import run_rlhf_workflow
|
| 164 |
+
result = run_rlhf_workflow(file_path, "notes-llm")
|
| 165 |
+
else:
|
| 166 |
+
from agents.langgraph import run_workflow
|
| 167 |
+
result = run_workflow(file_path, "notes-llm")
|
| 168 |
+
|
| 169 |
+
if result["status"] == "success":
|
| 170 |
+
return GenerationResult(
|
| 171 |
+
success=True,
|
| 172 |
+
output_path=result["result"]["output_xlsx_path"],
|
| 173 |
+
data=result["result"],
|
| 174 |
+
error=None,
|
| 175 |
+
metadata={
|
| 176 |
+
"execution_id": execution_id,
|
| 177 |
+
"generation_method": "llm",
|
| 178 |
+
"use_rlhf": self.use_rlhf,
|
| 179 |
+
"attempt": self.attempts_made,
|
| 180 |
+
"rlhf_metadata": result["result"].get("rlhf_metadata", {})
|
| 181 |
+
}
|
| 182 |
+
)
|
| 183 |
+
else:
|
| 184 |
+
return GenerationResult(
|
| 185 |
+
success=False,
|
| 186 |
+
output_path=None,
|
| 187 |
+
data=None,
|
| 188 |
+
error=result.get("error", "Unknown error"),
|
| 189 |
+
metadata={
|
| 190 |
+
"execution_id": execution_id,
|
| 191 |
+
"generation_method": "llm",
|
| 192 |
+
"use_rlhf": self.use_rlhf,
|
| 193 |
+
"attempt": self.attempts_made
|
| 194 |
+
}
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
except Exception as e:
|
| 198 |
+
logger.error(f"LLM Notes generation failed: {e}")
|
| 199 |
+
return GenerationResult(
|
| 200 |
+
success=False,
|
| 201 |
+
output_path=None,
|
| 202 |
+
data=None,
|
| 203 |
+
error=str(e),
|
| 204 |
+
metadata={
|
| 205 |
+
"execution_id": f"error_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
|
| 206 |
+
"generation_method": "llm",
|
| 207 |
+
"use_rlhf": self.use_rlhf,
|
| 208 |
+
"attempt": self.attempts_made
|
| 209 |
+
}
|
| 210 |
+
)
|
| 211 |
+
|
| 212 |
+
def refine(self, previous_result: GenerationResult, feedback: List[str]) -> GenerationResult:
|
| 213 |
+
"""Refine LLM notes generation based on feedback"""
|
| 214 |
+
logger.info(f"Refining LLM notes generation with feedback: {feedback}")
|
| 215 |
+
|
| 216 |
+
# For LLM generation, we can try different approaches:
|
| 217 |
+
# 1. Switch to RLHF if not already using it
|
| 218 |
+
# 2. Retry with different parameters
|
| 219 |
+
# 3. Use fallback models
|
| 220 |
+
|
| 221 |
+
if not self.use_rlhf and "quality" in str(feedback).lower():
|
| 222 |
+
# If quality issues and not using RLHF, try RLHF
|
| 223 |
+
logger.info("Switching to RLHF for better quality")
|
| 224 |
+
original_rlhf = self.use_rlhf
|
| 225 |
+
self.use_rlhf = True
|
| 226 |
+
result = self.generate(previous_result.data.get("file_path") if previous_result.data else None)
|
| 227 |
+
self.use_rlhf = original_rlhf # Reset for future calls
|
| 228 |
+
return result
|
| 229 |
+
else:
|
| 230 |
+
# Otherwise, just retry
|
| 231 |
+
return self.generate(previous_result.data.get("file_path") if previous_result.data else None)
|
| 232 |
+
|
| 233 |
+
class NotesValidator(BaseValidator):
|
| 234 |
+
"""Validator for financial notes quality"""
|
| 235 |
+
|
| 236 |
+
def validate(self, generation_result: GenerationResult) -> ValidationResult:
|
| 237 |
+
"""Validate generated financial notes"""
|
| 238 |
+
if not generation_result.success or not generation_result.output_path:
|
| 239 |
+
return ValidationResult(
|
| 240 |
+
is_valid=False,
|
| 241 |
+
score=0.0,
|
| 242 |
+
feedback=["Generation failed - no output produced"],
|
| 243 |
+
suggestions=["Retry generation process"],
|
| 244 |
+
metadata={"validation_type": "basic"}
|
| 245 |
+
)
|
| 246 |
+
|
| 247 |
+
feedback = []
|
| 248 |
+
suggestions = []
|
| 249 |
+
score = 1.0 # Start with perfect score
|
| 250 |
+
|
| 251 |
+
try:
|
| 252 |
+
# Check if output file exists
|
| 253 |
+
if not os.path.exists(generation_result.output_path):
|
| 254 |
+
feedback.append("Output file does not exist")
|
| 255 |
+
suggestions.append("Check file generation process")
|
| 256 |
+
score -= 0.5
|
| 257 |
+
|
| 258 |
+
# Check file size (reasonable minimum)
|
| 259 |
+
if os.path.exists(generation_result.output_path):
|
| 260 |
+
file_size = os.path.getsize(generation_result.output_path)
|
| 261 |
+
if file_size < 1000: # Less than 1KB
|
| 262 |
+
feedback.append("Output file is unusually small")
|
| 263 |
+
suggestions.append("Verify data processing and Excel generation")
|
| 264 |
+
score -= 0.3
|
| 265 |
+
|
| 266 |
+
# Check for common financial notes elements
|
| 267 |
+
# This would require reading the Excel file, but for now we'll do basic checks
|
| 268 |
+
|
| 269 |
+
# Check metadata completeness
|
| 270 |
+
metadata = generation_result.metadata
|
| 271 |
+
if not metadata.get("execution_id"):
|
| 272 |
+
feedback.append("Missing execution ID in metadata")
|
| 273 |
+
score -= 0.1
|
| 274 |
+
|
| 275 |
+
if generation_result.metadata.get("generation_method") == "llm":
|
| 276 |
+
# Additional checks for LLM-generated content
|
| 277 |
+
rlhf_metadata = metadata.get("rlhf_metadata", {})
|
| 278 |
+
if rlhf_metadata and rlhf_metadata.get("predicted_quality", 0) < 0.5:
|
| 279 |
+
feedback.append("Low quality score from RLHF validation")
|
| 280 |
+
suggestions.append("Consider regenerating with RLHF enabled")
|
| 281 |
+
score -= 0.4
|
| 282 |
+
|
| 283 |
+
# Ensure score doesn't go below 0
|
| 284 |
+
score = max(0.0, score)
|
| 285 |
+
|
| 286 |
+
return ValidationResult(
|
| 287 |
+
is_valid=score >= 0.6, # 60% threshold for validity
|
| 288 |
+
score=score,
|
| 289 |
+
feedback=feedback,
|
| 290 |
+
suggestions=suggestions,
|
| 291 |
+
metadata={
|
| 292 |
+
"validation_type": "comprehensive",
|
| 293 |
+
"file_size": os.path.getsize(generation_result.output_path) if os.path.exists(generation_result.output_path) else 0,
|
| 294 |
+
"has_rlhf": bool(generation_result.metadata.get("rlhf_metadata"))
|
| 295 |
+
}
|
| 296 |
+
)
|
| 297 |
+
|
| 298 |
+
except Exception as e:
|
| 299 |
+
logger.error(f"Validation failed: {e}")
|
| 300 |
+
return ValidationResult(
|
| 301 |
+
is_valid=False,
|
| 302 |
+
score=0.0,
|
| 303 |
+
feedback=[f"Validation error: {str(e)}"],
|
| 304 |
+
suggestions=["Check system logs for detailed error information"],
|
| 305 |
+
metadata={"validation_type": "error"}
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
def get_validation_criteria(self) -> List[str]:
|
| 309 |
+
"""Return list of validation criteria"""
|
| 310 |
+
return [
|
| 311 |
+
"Output file exists and is accessible",
|
| 312 |
+
"File size is reasonable (>1KB)",
|
| 313 |
+
"Metadata contains required fields",
|
| 314 |
+
"For LLM generation: quality score meets threshold",
|
| 315 |
+
"RLHF metadata present when RLHF is enabled",
|
| 316 |
+
"No critical errors in generation process"
|
| 317 |
+
]
|
| 318 |
+
|
| 319 |
+
class GeneratorValidatorPipeline:
|
| 320 |
+
"""Main pipeline that orchestrates Generator-Validator pattern"""
|
| 321 |
+
|
| 322 |
+
def __init__(self, generator: BaseGenerator, validator: BaseValidator):
|
| 323 |
+
self.generator = generator
|
| 324 |
+
self.validator = validator
|
| 325 |
+
self.generation_history = []
|
| 326 |
+
self.validation_history = []
|
| 327 |
+
|
| 328 |
+
def process(self, file_path: str, **kwargs) -> Tuple[GenerationResult, ValidationResult]:
|
| 329 |
+
"""Process file through generator-validator pipeline"""
|
| 330 |
+
logger.info("Starting Generator-Validator pipeline")
|
| 331 |
+
|
| 332 |
+
best_result = None
|
| 333 |
+
best_validation = None
|
| 334 |
+
|
| 335 |
+
for attempt in range(self.generator.max_attempts):
|
| 336 |
+
logger.info(f"Attempt {attempt + 1}/{self.generator.max_attempts}")
|
| 337 |
+
|
| 338 |
+
# Generate
|
| 339 |
+
generation_result = self.generator.generate(file_path, **kwargs)
|
| 340 |
+
self.generation_history.append(generation_result)
|
| 341 |
+
|
| 342 |
+
# Validate
|
| 343 |
+
validation_result = self.validator.validate(generation_result)
|
| 344 |
+
self.validation_history.append(validation_result)
|
| 345 |
+
|
| 346 |
+
logger.info(f"Generation success: {generation_result.success}, Validation score: {validation_result.score}")
|
| 347 |
+
|
| 348 |
+
# Keep track of best result
|
| 349 |
+
if best_result is None or (generation_result.success and validation_result.score > (best_validation.score if best_validation else 0)):
|
| 350 |
+
best_result = generation_result
|
| 351 |
+
best_validation = validation_result
|
| 352 |
+
|
| 353 |
+
# If validation passes, return immediately
|
| 354 |
+
if validation_result.is_valid:
|
| 355 |
+
logger.info("Validation passed - returning result")
|
| 356 |
+
return generation_result, validation_result
|
| 357 |
+
|
| 358 |
+
# If not the last attempt, try to refine
|
| 359 |
+
if attempt < self.generator.max_attempts - 1:
|
| 360 |
+
logger.info(f"Validation failed - refining with feedback: {validation_result.feedback}")
|
| 361 |
+
# Reset attempts counter for refinement
|
| 362 |
+
original_attempts = self.generator.attempts_made
|
| 363 |
+
self.generator.attempts_made = 0 # Reset for refinement
|
| 364 |
+
generation_result = self.generator.refine(generation_result, validation_result.feedback)
|
| 365 |
+
self.generator.attempts_made = original_attempts
|
| 366 |
+
|
| 367 |
+
logger.info("All attempts completed - returning best result")
|
| 368 |
+
return best_result, best_validation
|
| 369 |
+
|
| 370 |
+
def get_processing_summary(self) -> Dict[str, Any]:
|
| 371 |
+
"""Get summary of the processing pipeline"""
|
| 372 |
+
return {
|
| 373 |
+
"total_attempts": len(self.generation_history),
|
| 374 |
+
"successful_generations": sum(1 for g in self.generation_history if g.success),
|
| 375 |
+
"validation_scores": [v.score for v in self.validation_history],
|
| 376 |
+
"best_score": max([v.score for v in self.validation_history]) if self.validation_history else 0,
|
| 377 |
+
"generation_methods": list(set([g.metadata.get("generation_method") for g in self.generation_history if g.metadata])),
|
| 378 |
+
"validation_criteria": self.validator.get_validation_criteria()
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
def create_notes_pipeline(use_rlhf: bool = False) -> GeneratorValidatorPipeline:
|
| 382 |
+
"""Factory function to create LLM-based pipeline for notes generation"""
|
| 383 |
+
generator = LLMNotesGenerator(use_rlhf=use_rlhf)
|
| 384 |
+
validator = NotesValidator()
|
| 385 |
+
|
| 386 |
+
return GeneratorValidatorPipeline(generator, validator)
|
app.py
CHANGED
|
@@ -5,6 +5,7 @@ import os
|
|
| 5 |
import shutil
|
| 6 |
import logging
|
| 7 |
import json
|
|
|
|
| 8 |
from agents.langgraph import run_workflow
|
| 9 |
from agents.rlhf_workflows import run_rlhf_workflow
|
| 10 |
from agents.rlhf_routes import rlhf_router
|
|
@@ -37,29 +38,60 @@ router = APIRouter()
|
|
| 37 |
|
| 38 |
@router.post("/notes-llm")
|
| 39 |
async def notes_llm_route(file: UploadFile = File(...), use_rlhf: bool = Query(False)):
|
|
|
|
| 40 |
file_path = f"data/input/{file.filename}"
|
| 41 |
os.makedirs("data/input", exist_ok=True)
|
| 42 |
with open(file_path, "wb") as buffer:
|
| 43 |
shutil.copyfileobj(file.file, buffer)
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
response
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
|
| 65 |
@router.post("/notes")
|
|
|
|
| 5 |
import shutil
|
| 6 |
import logging
|
| 7 |
import json
|
| 8 |
+
from agents.generator_validator import create_notes_pipeline
|
| 9 |
from agents.langgraph import run_workflow
|
| 10 |
from agents.rlhf_workflows import run_rlhf_workflow
|
| 11 |
from agents.rlhf_routes import rlhf_router
|
|
|
|
| 38 |
|
| 39 |
@router.post("/notes-llm")
|
| 40 |
async def notes_llm_route(file: UploadFile = File(...), use_rlhf: bool = Query(False)):
|
| 41 |
+
"""Generate AI-powered financial notes using Generator-Validator pattern"""
|
| 42 |
file_path = f"data/input/{file.filename}"
|
| 43 |
os.makedirs("data/input", exist_ok=True)
|
| 44 |
with open(file_path, "wb") as buffer:
|
| 45 |
shutil.copyfileobj(file.file, buffer)
|
| 46 |
+
|
| 47 |
+
try:
|
| 48 |
+
# Create Generator-Validator pipeline for LLM notes
|
| 49 |
+
pipeline = create_notes_pipeline(use_rlhf=use_rlhf)
|
| 50 |
+
|
| 51 |
+
# Process through pipeline
|
| 52 |
+
generation_result, validation_result = pipeline.process(file_path)
|
| 53 |
+
|
| 54 |
+
# Log processing summary
|
| 55 |
+
summary = pipeline.get_processing_summary()
|
| 56 |
+
logger.info(f"LLM Notes Pipeline Summary: {summary}")
|
| 57 |
+
|
| 58 |
+
if generation_result.success and validation_result.is_valid:
|
| 59 |
+
response = FileResponse(
|
| 60 |
+
generation_result.output_path,
|
| 61 |
+
filename=os.path.basename(generation_result.output_path)
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
# Add comprehensive metadata to headers
|
| 65 |
+
response.headers["X-Generation-Method"] = "llm"
|
| 66 |
+
response.headers["X-Validation-Score"] = str(validation_result.score)
|
| 67 |
+
response.headers["X-Attempts-Made"] = str(generation_result.metadata.get("attempt", 1))
|
| 68 |
+
response.headers["X-Execution-ID"] = generation_result.metadata.get("execution_id", "")
|
| 69 |
+
|
| 70 |
+
# Add RLHF metadata if available
|
| 71 |
+
if use_rlhf and "rlhf_metadata" in generation_result.metadata:
|
| 72 |
+
rlhf_data = generation_result.metadata["rlhf_metadata"]
|
| 73 |
+
response.headers["X-RLHF-Statement-ID"] = str(rlhf_data.get("statement_id", ""))
|
| 74 |
+
response.headers["X-RLHF-Quality-Score"] = str(rlhf_data.get("predicted_quality", ""))
|
| 75 |
+
response.headers["X-RLHF-Confidence"] = str(rlhf_data.get("confidence_score", ""))
|
| 76 |
+
|
| 77 |
+
# Add validation feedback
|
| 78 |
+
if validation_result.feedback:
|
| 79 |
+
response.headers["X-Validation-Feedback"] = json.dumps(validation_result.feedback)
|
| 80 |
+
|
| 81 |
+
return response
|
| 82 |
+
else:
|
| 83 |
+
error_detail = {
|
| 84 |
+
"generation_error": generation_result.error,
|
| 85 |
+
"validation_feedback": validation_result.feedback,
|
| 86 |
+
"validation_score": validation_result.score,
|
| 87 |
+
"attempts_made": generation_result.metadata.get("attempt", 1),
|
| 88 |
+
"processing_summary": summary
|
| 89 |
+
}
|
| 90 |
+
raise HTTPException(status_code=500, detail=json.dumps(error_detail))
|
| 91 |
+
|
| 92 |
+
except Exception as e:
|
| 93 |
+
logger.error(f"LLM Notes pipeline failed: {e}")
|
| 94 |
+
raise HTTPException(status_code=500, detail=f"Pipeline processing failed: {str(e)}")
|
| 95 |
|
| 96 |
|
| 97 |
@router.post("/notes")
|