Sahil Garg commited on
Commit
dea72cd
·
1 Parent(s): 24ccd4e
Files changed (4) hide show
  1. .gitignore +1 -2
  2. agents/langgraph_routes.py +60 -0
  3. app.py +56 -3
  4. 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
- agents/financial_agents.py
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
- # Import agent system
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
- langsmith
 
13
  langchain-openai
14
  langchain-community
15
  langchain-core
16
+ langsmithz