naman1102 commited on
Commit
7dbc634
·
1 Parent(s): 5a43f6c
Files changed (2) hide show
  1. app.py +17 -17
  2. tools.py +60 -17
app.py CHANGED
@@ -39,7 +39,7 @@ def plan_node(state: AgentState) -> AgentState:
39
  system_msg = SystemMessage(
40
  content=(
41
  "You are an agent that decides whether to call a tool or answer directly.\n"
42
- "Users question: \"" + user_input + "\"\n\n"
43
  "• If you can answer directly, return exactly {\"final_answer\":\"<your answer>\"}.\n"
44
  "• Otherwise, respond with exactly one of:\n"
45
  " {\"web_search_query\":\"<search terms>\"}\n"
@@ -56,9 +56,9 @@ def plan_node(state: AgentState) -> AgentState:
56
  llm_out = llm_response.content.strip()
57
 
58
  # ── DEBUG: print raw LLM output ──
59
- print("\n>>> plan_node got raw LLM output:")
60
- print(llm_out)
61
- print("<<< end raw output\n")
62
 
63
  # (3) Append the LLM output to the message history
64
  ai_msg = AIMessage(content=llm_out)
@@ -67,7 +67,7 @@ def plan_node(state: AgentState) -> AgentState:
67
  # (4) Try parsing as JSON
68
  try:
69
  parsed = json.loads(llm_out)
70
- print(">>> plan_node parsed JSON:", parsed)
71
  if isinstance(parsed, dict):
72
  partial: AgentState = {"messages": new_msgs}
73
  allowed = {
@@ -81,13 +81,13 @@ def plan_node(state: AgentState) -> AgentState:
81
  for k, v in parsed.items():
82
  if k in allowed:
83
  partial[k] = v
84
- print(f">>> plan_node is setting {k!r} → {v!r}")
85
  return partial
86
  except json.JSONDecodeError as e:
87
- print(">>> plan_node JSON parse error:", e)
88
-
89
  # (5) Fallback
90
- print(">>> plan_node falling back to final_answer alone\n")
91
  return {"messages": new_msgs, "final_answer": "Sorry, I could not parse your intent."}
92
 
93
 
@@ -136,28 +136,28 @@ def tool_node(state: AgentState) -> AgentState:
136
  # "web_search_query", "ocr_path", "excel_path"/"excel_sheet_name", "audio_path"
137
  # Whichever is present, call the corresponding tool and return its result.
138
  if state.get("web_search_query"):
139
- print(f">>> tools_node dispatching web_search_tool with query: {state['web_search_query']!r}")
140
  out = web_search_tool(state)
141
  return out
142
 
143
  if state.get("ocr_path"):
144
- print(f">>> tools_node dispatching ocr_image_tool with path: {state['ocr_path']!r}")
145
  out = ocr_image_tool(state)
146
  return out
147
 
148
  if state.get("excel_path"):
149
  # We assume plan_node always sets both excel_path and excel_sheet_name together
150
- print(f">>> tools_node dispatching parse_excel_tool with path: {state['excel_path']!r}, sheet: {state.get('excel_sheet_name')!r}")
151
  out = parse_excel_tool(state)
152
  return out
153
 
154
  if state.get("audio_path"):
155
- print(f">>> tools_node dispatching audio_transcriber_tool with path: {state['audio_path']!r}")
156
  out = audio_transcriber_tool(state)
157
  return out
158
 
159
  # If we somehow reach here, no recognized tool key was set:
160
- print(">>> tools_node: no valid tool key found in state!")
161
  return {}
162
 
163
 
@@ -188,7 +188,7 @@ graph.add_edge(START, "plan")
188
  # 5.c) plan → conditional: if any tool key was set, go to "tools"; otherwise "finalize"
189
  def route_plan(plan_out: AgentState) -> str:
190
  # print what keys are present in plan_out
191
- print(f">> route_plan sees plan_out keys: {list(plan_out.keys())}")
192
 
193
  if (
194
  plan_out.get("web_search_query")
@@ -196,9 +196,9 @@ def route_plan(plan_out: AgentState) -> str:
196
  or plan_out.get("excel_path")
197
  or plan_out.get("audio_path")
198
  ):
199
- print(">> route_plan ➡️ tools")
200
  return "tools"
201
- print(">> route_plan ➡️ finalize")
202
  return "finalize"
203
 
204
 
 
39
  system_msg = SystemMessage(
40
  content=(
41
  "You are an agent that decides whether to call a tool or answer directly.\n"
42
+ "User's question: \"" + user_input + "\"\n\n"
43
  "• If you can answer directly, return exactly {\"final_answer\":\"<your answer>\"}.\n"
44
  "• Otherwise, respond with exactly one of:\n"
45
  " {\"web_search_query\":\"<search terms>\"}\n"
 
56
  llm_out = llm_response.content.strip()
57
 
58
  # ── DEBUG: print raw LLM output ──
59
+ # print("\n>>> plan_node got raw LLM output:")
60
+ # print(llm_out)
61
+ # print("<<< end raw output\n")
62
 
63
  # (3) Append the LLM output to the message history
64
  ai_msg = AIMessage(content=llm_out)
 
67
  # (4) Try parsing as JSON
68
  try:
69
  parsed = json.loads(llm_out)
70
+ # print(">>> plan_node parsed JSON:", parsed)
71
  if isinstance(parsed, dict):
72
  partial: AgentState = {"messages": new_msgs}
73
  allowed = {
 
81
  for k, v in parsed.items():
82
  if k in allowed:
83
  partial[k] = v
84
+ # print(f">>> plan_node is setting {k!r} → {v!r}")
85
  return partial
86
  except json.JSONDecodeError as e:
87
+ # print(">>> plan_node JSON parse error:", e)
88
+ pass
89
  # (5) Fallback
90
+ # print(">>> plan_node falling back to final_answer alone\n")
91
  return {"messages": new_msgs, "final_answer": "Sorry, I could not parse your intent."}
92
 
93
 
 
136
  # "web_search_query", "ocr_path", "excel_path"/"excel_sheet_name", "audio_path"
137
  # Whichever is present, call the corresponding tool and return its result.
138
  if state.get("web_search_query"):
139
+ # print(f">>> tools_node dispatching web_search_tool with query: {state['web_search_query']!r}")
140
  out = web_search_tool(state)
141
  return out
142
 
143
  if state.get("ocr_path"):
144
+ # print(f">>> tools_node dispatching ocr_image_tool with path: {state['ocr_path']!r}")
145
  out = ocr_image_tool(state)
146
  return out
147
 
148
  if state.get("excel_path"):
149
  # We assume plan_node always sets both excel_path and excel_sheet_name together
150
+ # print(f">>> tools_node dispatching parse_excel_tool with path: {state['excel_path']!r}, sheet: {state.get('excel_sheet_name')!r}")
151
  out = parse_excel_tool(state)
152
  return out
153
 
154
  if state.get("audio_path"):
155
+ # print(f">>> tools_node dispatching audio_transcriber_tool with path: {state['audio_path']!r}")
156
  out = audio_transcriber_tool(state)
157
  return out
158
 
159
  # If we somehow reach here, no recognized tool key was set:
160
+ # print(">>> tools_node: no valid tool key found in state!")
161
  return {}
162
 
163
 
 
188
  # 5.c) plan → conditional: if any tool key was set, go to "tools"; otherwise "finalize"
189
  def route_plan(plan_out: AgentState) -> str:
190
  # print what keys are present in plan_out
191
+ # print(f">> route_plan sees plan_out keys: {list(plan_out.keys())}")
192
 
193
  if (
194
  plan_out.get("web_search_query")
 
196
  or plan_out.get("excel_path")
197
  or plan_out.get("audio_path")
198
  ):
199
+ # print(">> route_plan ➡️ tools")
200
  return "tools"
201
+ # print(">> route_plan ➡️ finalize")
202
  return "finalize"
203
 
204
 
tools.py CHANGED
@@ -49,34 +49,77 @@ def ocr_image_tool(state: AgentState) -> AgentState:
49
 
50
  def parse_excel_tool(state: AgentState) -> AgentState:
51
  """
52
- Expects: state["excel_path"] is a path to an .xlsx file,
53
- and state["excel_sheet_name"] optionally names a sheet.
54
- Returns: {"excel_path": None, "excel_sheet_name": None, "excel_result": <string>}.
 
 
 
 
 
 
55
  """
56
- print("reached parse excel tool")
57
  path = state.get("excel_path", "")
58
  sheet = state.get("excel_sheet_name", "")
59
  if not path:
60
  return {}
61
 
62
- try:
63
- xls = pd.ExcelFile(path)
64
- if sheet and sheet in xls.sheet_names:
65
- df = pd.read_excel(xls, sheet_name=sheet)
66
- else:
67
- df = pd.read_excel(xls, sheet_name=xls.sheet_names[0])
68
- records = df.to_dict(orient="records")
69
- text = str(records)
70
- except Exception as e:
71
- text = f"Error reading Excel: {e}"
72
- print(f"excel_result: {text}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  return {
74
  "excel_path": None,
75
  "excel_sheet_name": None,
76
- "excel_result": text
77
  }
78
 
79
-
80
  def run_tools(state: AgentState, tool_out: AgentState) -> AgentState:
81
  """
82
  Merges whatever partial state the tool wrapper returned (tool_out)
 
49
 
50
  def parse_excel_tool(state: AgentState) -> AgentState:
51
  """
52
+ Attempts to read an actual .xlsx file at state["excel_path"]. If the file isn’t found,
53
+ scans the conversation history for a Markdown‐style table and returns that instead.
54
+ Returns:
55
+ {
56
+ "excel_path": None,
57
+ "excel_sheet_name": None,
58
+ "excel_result": "<either CSV‐like text or extracted Markdown table>"
59
+ }
60
+ If neither a real file nor a table block is found, returns an error message.
61
  """
 
62
  path = state.get("excel_path", "")
63
  sheet = state.get("excel_sheet_name", "")
64
  if not path:
65
  return {}
66
 
67
+ # 1) Try reading the real file first
68
+ if os.path.exists(path):
69
+ try:
70
+ xls = pd.ExcelFile(path)
71
+ if sheet and sheet in xls.sheet_names:
72
+ df = pd.read_excel(xls, sheet_name=sheet)
73
+ else:
74
+ df = pd.read_excel(xls, sheet_name=xls.sheet_names[0])
75
+ records = df.to_dict(orient="records")
76
+ text = str(records)
77
+ return {
78
+ "excel_path": None,
79
+ "excel_sheet_name": None,
80
+ "excel_result": text
81
+ }
82
+ except Exception as e:
83
+ # If there's an I/O or parsing error, fall through to table‐extraction
84
+ print(f">>> parse_excel_tool: Error reading Excel file {path}: {e}")
85
+
86
+ # 2) Fallback: extract a Markdown table from any HumanMessage in state["messages"]
87
+ messages = state.get("messages", [])
88
+ table_lines = []
89
+ collecting = False
90
+
91
+ for msg in messages:
92
+ if isinstance(msg, HumanMessage):
93
+ for line in msg.content.splitlines():
94
+ # Start collecting when we see the first table header row
95
+ if re.match(r"^\s*\|\s*[-A-Za-z0-9]", line):
96
+ collecting = True
97
+ if collecting:
98
+ if not re.match(r"^\s*\|", line):
99
+ # stop when the block ends (blank line or non‐table line)
100
+ collecting = False
101
+ break
102
+ table_lines.append(line)
103
+ if table_lines:
104
+ break
105
+
106
+ if not table_lines:
107
+ return {
108
+ "excel_path": None,
109
+ "excel_sheet_name": None,
110
+ "excel_result": "Error: No Excel file found and no Markdown table detected in prompt."
111
+ }
112
+
113
+ # Remove any separator rows like "| ---- | ---- |"
114
+ clean_rows = [row for row in table_lines if not re.match(r"^\s*\|\s*-+", row)]
115
+ table_block = "\n".join(clean_rows).strip()
116
+
117
  return {
118
  "excel_path": None,
119
  "excel_sheet_name": None,
120
+ "excel_result": table_block
121
  }
122
 
 
123
  def run_tools(state: AgentState, tool_out: AgentState) -> AgentState:
124
  """
125
  Merges whatever partial state the tool wrapper returned (tool_out)