Spaces:
Sleeping
Sleeping
Sahil Garg
commited on
Commit
·
dea72cd
1
Parent(s):
24ccd4e
langgraph
Browse files- .gitignore +1 -2
- agents/langgraph_routes.py +60 -0
- app.py +56 -3
- requirements.txt +1 -1
.gitignore
CHANGED
|
@@ -20,5 +20,4 @@ app/__pycache__/
|
|
| 20 |
pnlbs/__pycache__/
|
| 21 |
AGENT_GUIDE.md
|
| 22 |
docker-compose.dev.yml
|
| 23 |
-
|
| 24 |
-
agents/financial_tools.py
|
|
|
|
| 20 |
pnlbs/__pycache__/
|
| 21 |
AGENT_GUIDE.md
|
| 22 |
docker-compose.dev.yml
|
| 23 |
+
file_cleanup.py
|
|
|
agents/langgraph_routes.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import TypedDict, Dict, Any, List, Annotated
|
| 2 |
+
import time, uuid, os
|
| 3 |
+
from langgraph.graph import StateGraph, END
|
| 4 |
+
from langchain_core.messages import HumanMessage, AIMessage, BaseMessage
|
| 5 |
+
from agents.simple_tools import (
|
| 6 |
+
generate_notes_full_pipeline_from_path,
|
| 7 |
+
generate_balance_sheet,
|
| 8 |
+
generate_pnl_statement,
|
| 9 |
+
generate_cash_flow_statement,
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
class FinancialAgentState(TypedDict):
|
| 13 |
+
messages: Annotated[List[BaseMessage], "History"]
|
| 14 |
+
file_path: str
|
| 15 |
+
result: Dict[str, Any]
|
| 16 |
+
status: str
|
| 17 |
+
start_time: float
|
| 18 |
+
end_time: float
|
| 19 |
+
error: str
|
| 20 |
+
|
| 21 |
+
def make_workflow(tool_func):
|
| 22 |
+
def node(state: FinancialAgentState) -> FinancialAgentState:
|
| 23 |
+
state["start_time"] = time.time()
|
| 24 |
+
try:
|
| 25 |
+
# Use .invoke() to avoid deprecation warning
|
| 26 |
+
result = tool_func.invoke({"file_path": state["file_path"]})
|
| 27 |
+
state["result"] = result
|
| 28 |
+
state["status"] = "success" if result.get("status") == "success" else "error"
|
| 29 |
+
state["error"] = result.get("error", "")
|
| 30 |
+
except Exception as e:
|
| 31 |
+
state["status"] = "error"
|
| 32 |
+
state["error"] = str(e)
|
| 33 |
+
state["end_time"] = time.time()
|
| 34 |
+
return state
|
| 35 |
+
|
| 36 |
+
wf = StateGraph(FinancialAgentState)
|
| 37 |
+
wf.add_node("run", node)
|
| 38 |
+
wf.set_entry_point("run")
|
| 39 |
+
wf.add_edge("run", END)
|
| 40 |
+
return wf.compile()
|
| 41 |
+
|
| 42 |
+
workflows = {
|
| 43 |
+
"notes": make_workflow(generate_notes_full_pipeline_from_path),
|
| 44 |
+
"pnl": make_workflow(generate_pnl_statement),
|
| 45 |
+
"bs": make_workflow(generate_balance_sheet),
|
| 46 |
+
"cf": make_workflow(generate_cash_flow_statement),
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
def run_workflow(file_path: str, kind: str) -> Dict[str, Any]:
|
| 50 |
+
state = FinancialAgentState(
|
| 51 |
+
messages=[HumanMessage(content=f"Run {kind} for {file_path}")],
|
| 52 |
+
file_path=file_path,
|
| 53 |
+
result={},
|
| 54 |
+
status="",
|
| 55 |
+
start_time=0,
|
| 56 |
+
end_time=0,
|
| 57 |
+
error="",
|
| 58 |
+
)
|
| 59 |
+
final = workflows[kind].invoke(state)
|
| 60 |
+
return final
|
app.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
|
| 2 |
from fastapi import FastAPI, APIRouter, UploadFile, File, Form, HTTPException
|
| 3 |
from fastapi.responses import JSONResponse, PlainTextResponse, FileResponse
|
| 4 |
from typing import Optional, Dict, Any
|
|
@@ -16,13 +15,13 @@ from notes.llm_notes_generator import FlexibleFinancialNoteGenerator
|
|
| 16 |
from notes.notes_generator import process_json
|
| 17 |
from notes.json_to_excel import json_to_xlsx
|
| 18 |
from utils.utils_normalize import normalize_llm_note_json, normalize_llm_notes_json
|
|
|
|
| 19 |
|
| 20 |
# Configure logging for the application
|
| 21 |
logging.basicConfig(level=logging.INFO)
|
| 22 |
logger = logging.getLogger("financial_notes_api")
|
| 23 |
|
| 24 |
-
|
| 25 |
-
from agents.simple_agent import FinancialStatementAgent
|
| 26 |
|
| 27 |
app = FastAPI(
|
| 28 |
title="Financial Notes Generator API",
|
|
@@ -400,6 +399,60 @@ async def agent_generate_statements(
|
|
| 400 |
logger.error(f"Error in agent statement generation: {e}")
|
| 401 |
raise HTTPException(status_code=500, detail=f"Agent generation failed: {str(e)}")
|
| 402 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 403 |
app.include_router(router)
|
| 404 |
|
| 405 |
if __name__ == "__main__":
|
|
|
|
|
|
|
| 1 |
from fastapi import FastAPI, APIRouter, UploadFile, File, Form, HTTPException
|
| 2 |
from fastapi.responses import JSONResponse, PlainTextResponse, FileResponse
|
| 3 |
from typing import Optional, Dict, Any
|
|
|
|
| 15 |
from notes.notes_generator import process_json
|
| 16 |
from notes.json_to_excel import json_to_xlsx
|
| 17 |
from utils.utils_normalize import normalize_llm_note_json, normalize_llm_notes_json
|
| 18 |
+
from agents.langgraph_routes import run_workflow
|
| 19 |
|
| 20 |
# Configure logging for the application
|
| 21 |
logging.basicConfig(level=logging.INFO)
|
| 22 |
logger = logging.getLogger("financial_notes_api")
|
| 23 |
|
| 24 |
+
|
|
|
|
| 25 |
|
| 26 |
app = FastAPI(
|
| 27 |
title="Financial Notes Generator API",
|
|
|
|
| 399 |
logger.error(f"Error in agent statement generation: {e}")
|
| 400 |
raise HTTPException(status_code=500, detail=f"Agent generation failed: {str(e)}")
|
| 401 |
|
| 402 |
+
@router.post("/notes")
|
| 403 |
+
async def notes_route(file: UploadFile = File(...)):
|
| 404 |
+
file_path = f"data/input/{file.filename}"
|
| 405 |
+
os.makedirs("data/input", exist_ok=True)
|
| 406 |
+
with open(file_path, "wb") as buffer:
|
| 407 |
+
shutil.copyfileobj(file.file, buffer)
|
| 408 |
+
result = run_workflow(file_path, "notes")
|
| 409 |
+
if result["status"] == "success":
|
| 410 |
+
return FileResponse(result["result"]["output_xlsx_path"], filename=os.path.basename(result["result"]["output_xlsx_path"]))
|
| 411 |
+
raise HTTPException(status_code=500, detail=result["error"])
|
| 412 |
+
|
| 413 |
+
@router.post("/pnl")
|
| 414 |
+
async def pnl_route(file: UploadFile = File(...)):
|
| 415 |
+
file_path = f"data/input/{file.filename}"
|
| 416 |
+
os.makedirs("data/input", exist_ok=True)
|
| 417 |
+
with open(file_path, "wb") as buffer:
|
| 418 |
+
shutil.copyfileobj(file.file, buffer)
|
| 419 |
+
result = run_workflow(file_path, "pnl")
|
| 420 |
+
if result["status"] == "success":
|
| 421 |
+
return FileResponse(result["result"].get("output_path", "data/pnl_statement.xlsx"), filename=os.path.basename(result["result"].get("output_path", "data/pnl_statement.xlsx")))
|
| 422 |
+
raise HTTPException(status_code=500, detail=result["error"])
|
| 423 |
+
|
| 424 |
+
@router.post("/bs")
|
| 425 |
+
async def bs_route(file: UploadFile = File(...)):
|
| 426 |
+
file_path = f"data/input/{file.filename}"
|
| 427 |
+
os.makedirs("data/input", exist_ok=True)
|
| 428 |
+
with open(file_path, "wb") as buffer:
|
| 429 |
+
shutil.copyfileobj(file.file, buffer)
|
| 430 |
+
result = run_workflow(file_path, "bs")
|
| 431 |
+
if result["status"] == "success":
|
| 432 |
+
# Use first xlsx file in output dir if present
|
| 433 |
+
output_file = result["result"].get("output_path")
|
| 434 |
+
if not output_file or not os.path.isfile(output_file):
|
| 435 |
+
# Try to find the first .xlsx file in data/output/ (ensure it's a file, not a directory)
|
| 436 |
+
output_dir = "data/output/"
|
| 437 |
+
xlsx_files = [f for f in os.listdir(output_dir) if f.endswith('.xlsx') and os.path.isfile(os.path.join(output_dir, f))]
|
| 438 |
+
if xlsx_files:
|
| 439 |
+
output_file = os.path.join(output_dir, xlsx_files[0])
|
| 440 |
+
else:
|
| 441 |
+
raise HTTPException(status_code=500, detail="No balance sheet Excel file produced")
|
| 442 |
+
return FileResponse(output_file, filename=os.path.basename(output_file))
|
| 443 |
+
else:
|
| 444 |
+
raise HTTPException(status_code=500, detail=result["error"])
|
| 445 |
+
|
| 446 |
+
@router.post("/cf")
|
| 447 |
+
async def cf_route(file: UploadFile = File(...)):
|
| 448 |
+
file_path = f"data/input/{file.filename}"
|
| 449 |
+
os.makedirs("data/input", exist_ok=True)
|
| 450 |
+
with open(file_path, "wb") as buffer:
|
| 451 |
+
shutil.copyfileobj(file.file, buffer)
|
| 452 |
+
result = run_workflow(file_path, "cf")
|
| 453 |
+
if result["status"] == "success":
|
| 454 |
+
return FileResponse(result["result"].get("output_path", "data/cash_flow_statements.xlsx"), filename=os.path.basename(result["result"].get("output_path", "data/cash_flow_statements.xlsx")))
|
| 455 |
+
raise HTTPException(status_code=500, detail=result["error"])
|
| 456 |
app.include_router(router)
|
| 457 |
|
| 458 |
if __name__ == "__main__":
|
requirements.txt
CHANGED
|
@@ -13,4 +13,4 @@ langchain
|
|
| 13 |
langchain-openai
|
| 14 |
langchain-community
|
| 15 |
langchain-core
|
| 16 |
-
|
|
|
|
| 13 |
langchain-openai
|
| 14 |
langchain-community
|
| 15 |
langchain-core
|
| 16 |
+
langsmithz
|