# nodes.py
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph.message import add_messages
from langchain.tools import tool
from langgraph.prebuilt import create_react_agent, ToolNode
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.runnables import RunnableConfig
# from chatbot.llm_engine import llm_overall_agent, generate_expense_info_feedback
from llm_engine import (
llm_overall_agent,
generate_expense_info_feedback,
generate_expense_description_feedback,
map_input_to_project_deliverable
)
from form_management.acceptance_criteria import acceptance_criteria
from form_management.project_task_descriptions import project_tasks
from prompts.agent_prompts import speaker_system_message
from form_management.form_management import Form
from kie import receipt_kie
import ast
import json
import sqlite3
import os
from collections import defaultdict
# conn = sqlite3.connect(r'C:\Users\Gavin\Desktop\hello-earth\db\project_data.db')
db_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'database', 'hello_earth_data_2.db')
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# placeholder, init empty form info
session_form_data = defaultdict(lambda: {
"Seller Name": 'None',
"Seller Address": 'None',
"Seller Phone Number": 'None',
"Buyer Name": 'None',
"Buyer Address": 'None',
"Transaction Date": 'None',
"Total Payment Amount": 'None',
"Associated Deliverable": 'None',
"Expense Description": 'None',
})
# define the project_deliverables from the db
# cursor.execute("SELECT title FROM deliverables")
cursor.execute("SELECT title FROM Deliverable")
rows = cursor.fetchall()
deliverable_titles = [row[0] for row in rows]
print(f"deliverable_titles: {deliverable_titles}")
def get_form_info(session_id):
return session_form_data[session_id]
def set_form_info(session_id, key, value):
print("set form info")
session_form_data[session_id][key] = value
print(session_id)
print(session_form_data[session_id])
class State(TypedDict):
messages: Annotated[list, add_messages]
session_id: str
# form_info: dict
# @tool
# def placeholder_tool(input: str) -> str:
# """
# Placeholder tool that does nothing.
# """
# return "This is a placeholder tool."
# session_id: str might need to be added later when there are concurrent users
@tool
def apply_ocr(image_path: str, config: RunnableConfig) -> str:
"""
Use this tool when the user provides an image path.
Apply optical-character-recognition (OCR) to the image and initalize the form information.
Feedback will then be provided on whether the form is complete or not along with the updated form info.
"""
print("using apply_ocr tool")
session_id = config["configurable"].get("thread_id")
temp_form_info = receipt_kie(image_path)
form_info = session_form_data[session_id]
for key in temp_form_info.keys():
if key in form_info:
form_info[key] = temp_form_info[key]
print(type(form_info))
print(form_info)
feedback = auditor_feedback(form_info)
print("feedback: {} end of feedback".format(feedback))
return f"""
{form_info}
{feedback}
"""
@tool
def edit_form(key: str, value: str, config: RunnableConfig) -> str:
"""
This is used to edit the form data.
The key must be one of the keys in the form_info dictionary.
The target key will be assigned with the provided value.
Feedback will then be provided on whether the form is complete or not along with the updated form info.
Use this when the user wants to edit the form data.
"""
print("using edit_form tool")
session_id = config["configurable"].get("thread_id")
form_info = session_form_data[session_id]
if key in form_info:
form_info[key] = value
print(form_info)
print(f"Updated {key} to {value}")
if key == "Expense Description":
feedback = expense_description_feedback(form_info, value)
elif key == "Associated Deliverable":
res = map_associated_deliverable(value, deliverable_titles)
if res == "No Matching Deliverable":
form_info[key] = "None"
feedback = "A valid associated deliverable is required."
else:
form_info[key] = res
feedback = auditor_feedback(form_info)
else:
feedback = auditor_feedback(form_info)
print("feedback: {} end of feedback".format(feedback))
return f"""
{form_info}
{feedback}
"""
else:
print(form_info)
print(f"Invalid key: {key}. No changes made.")
return f"Invalid key: {key}. No changes made."
@tool
def inspect_form(config: RunnableConfig) -> str:
"""
This is used to inspect the form data.
Use this when you need to address any inquiries the user might have about the current state of the form.
"""
print("using inspect_form tool")
session_id = config["configurable"].get("thread_id")
form_info = session_form_data[session_id]
print("session_id:",session_id)
print("form_info:",form_info)
return f"""
{form_info}
"""
tools = [apply_ocr, edit_form, inspect_form]
speaker_memory = MemorySaver()
speaker_agent = create_react_agent(
llm_overall_agent,
tools,
state_modifier=speaker_system_message,
checkpointer=speaker_memory
)
tool_node = ToolNode(tools=tools)
def speaker_chatbot_node(state: State):
config = {"configurable": {"thread_id": state["session_id"]}}
response = speaker_agent.invoke(
{
"messages": [
(
"user", "{}".format(state["messages"][-1].content)
)
]
},
config,
)["messages"][-1]
return {
"messages": response.content,
"session_id": state["session_id"]
}
# provides feedback based on form info
def auditor_feedback(form_info):
print("in auditor_feedback...")
response = generate_expense_info_feedback(acceptance_criteria, form_info)
return response
def expense_description_feedback(form_info, expense_description):
response = generate_expense_description_feedback(form_info["Associated Deliverable"], expense_description)
return response
# assume static project deliverables, would need project id to get the deliverables in actual case
def map_associated_deliverable(user_input, project_deliverables):
response = map_input_to_project_deliverable(user_input, str(project_deliverables))
return response
# # example usage
# if __name__ == "__main__":
# # example_form_info = {
# # "Receipt Title": "Office Supplies",
# # "Project Name": "Project Alpha",
# # "Date": "2023-10-01",
# # "Receipt Number": "12345",
# # "Payer's Name": "John Doe",
# # "Payment Details Table": {
# # "Column Headers": ["Item", "Quantity", "Price"],
# # "Row Entries": [["Pen", "10", "$5"], ["Notebook", "5", "$20"]],
# # },
# # "Total Payment Amount": "$125",
# # "Incompleteness Description": "None",
# # "Expense Justification": "Office supplies for team use",
# # }
# # example_form_info = {
# # "Receipt Title": "Office Supplies",
# # "Project Name": "Reforestation of the Amazon Forest",
# # "Date": "2023-10-01",
# # "Receipt Number": "12345",
# # "Payer's Name": "John Doe",
# # "Payment Details Table": {
# # "Column Headers": ["Item", "Quantity", "Price"],
# # "Row Entries": [["Pen", "10", "$5"], ["Notebook", "5", "$20"]],
# # },
# # "Total Payment Amount": "$125",
# # "Incompleteness Description": "None",
# # "Expense Justification": "Office supplies for team use",
# # }
# # example_form_info = {
# # "Receipt Title": "Office Supplies",
# # "Project Name": "Reforestation of the Amazon Forest",
# # "Date": "2023-10-01",
# # "Receipt Number": "12345",
# # "Payer's Name": "John Doe",
# # "Payment Details Table": {
# # "Column Headers": ["Item", "Quantity", "Price"],
# # "Row Entries": [["Pen", "10", "$5"], ["Notebook", "5", "$20"]],
# # },
# # "Total Payment Amount": "$125",
# # "Incompleteness Description": "None",
# # "Expense Justification": "The office supplies are used for paperwork regarding the project.",
# # }
# # feedback = auditor_feedback(example_form_info)
# # print("Generated Feedback:", feedback)