naman1102 commited on
Commit
b5e1a68
·
1 Parent(s): 10418ac

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +66 -69
app.py CHANGED
@@ -55,94 +55,91 @@ graph.add_edge("agent", END)
55
  compiled_graph = graph.compile()
56
 
57
  def respond_to_input(user_input: str) -> str:
58
- """
59
- Wrap compiled_graph.invoke(…) (and the “second pass”) in a try/except
60
- so that if the agent ever returns something unparsable (a plain str),
61
- we catch it and avoid the `'str' object has no attribute 'items'` error.
62
- """
63
-
64
- # A) Build a SystemMessage that forces the model to emit bare JSON if it calls a tool:
65
  system_msg = SystemMessage(
66
  content=(
67
  "You are an assistant with access to exactly these tools:\n"
68
  " 1) web_search(query:str)\n"
69
  " 2) parse_excel(path:str,sheet_name:str)\n"
70
  " 3) ocr_image(path:str)\n\n"
71
- "⚠️ **IMPORTANT** ⚠️: If (and only if) you need to call a tool, "
72
- "output exactly one JSON object and nothing else. For example:\n"
73
  "```json\n"
74
  '{"tool":"web_search","query":"Mercedes Sosa albums 2000-2009"}\n'
75
  "```\n"
76
- "That JSON must start at the very first character of your response and end at the very last character—"
77
- "no quotes, no code fences, and no extra explanation.\n\n"
78
  "If you do NOT need any tool, simply reply with your final answer as plain text."
79
  )
80
  )
81
 
82
- # B) Initialize state with just that SystemMessage. We'll pass user_input separately below:
83
- initial_state = { "messages": [system_msg] }
84
 
 
85
  try:
86
- # C) First pass: invoke the graph with the user’s question:
87
- final_state = compiled_graph.invoke(initial_state, user_input)
88
-
89
- # D) Log every AIMessage for debugging (optional—remove in production):
90
- print("===== AGENT MESSAGES (First Pass) =====")
91
- for i, msg in enumerate(final_state["messages"]):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  if isinstance(msg, AIMessage):
93
- print(f"[AIMessage #{i}]: {repr(msg.content)}")
94
  print("=========================================")
95
 
96
- # E) Find the very last AIMessage the agent produced:
97
- last_msg = None
98
- for msg in reversed(final_state["messages"]):
99
  if isinstance(msg, AIMessage):
100
- last_msg = msg.content
101
- break
102
-
103
- # F) Try to parse that last_msg as a tool‐call JSON:
104
- tool_dict = parse_tool_json(last_msg or "")
105
- if tool_dict:
106
- # G) If valid JSON, run the tool:
107
- print(">> Parsed tool call:", tool_dict)
108
- tool_result = tool_node.run(tool_dict)
109
-
110
- # H) Log output of the tool (optional):
111
- print(f">> Tool '{tool_dict['tool']}' returned: {repr(tool_result)}")
112
-
113
- # I) Second pass: feed the tool’s result back into the graph,
114
- # with an empty user_input because we’re continuing the same turn.
115
- continuation_state = {
116
- "messages": [
117
- *final_state["messages"],
118
- AIMessage(content=tool_result)
119
- ]
120
- }
121
- second_pass = compiled_graph.invoke(continuation_state, "")
122
-
123
- # J) Log every AIMessage from the second pass (optional):
124
- print("===== AGENT MESSAGES (Second Pass) =====")
125
- for i, msg in enumerate(second_pass["messages"]):
126
- if isinstance(msg, AIMessage):
127
- print(f"[AIMessage2 #{i}]: {repr(msg.content)}")
128
- print("=========================================")
129
-
130
- # K) Return the last AIMessage from the second pass as the final answer:
131
- for msg in reversed(second_pass["messages"]):
132
- if isinstance(msg, AIMessage):
133
- return msg.content or ""
134
- return ""
135
-
136
- else:
137
- # L) If it wasn’t valid JSON, just treat last_msg as plain‐text final answer:
138
- return last_msg or ""
139
-
140
- except Exception as e:
141
- # M) If *any* part of the above raises (e.g. because the LLM returned a string
142
- # that the routing layer tried to treat as a dict), we catch it here:
143
- print(f"‼️ respond_to_input error: {repr(e)}")
144
- return "" # or return a default fallback string if you prefer
145
-
146
 
147
  class BasicAgent:
148
  def __init__(self):
 
55
  compiled_graph = graph.compile()
56
 
57
  def respond_to_input(user_input: str) -> str:
58
+ # 1) Build a SystemMessage that insists on bare JSON if calling a tool
 
 
 
 
 
 
59
  system_msg = SystemMessage(
60
  content=(
61
  "You are an assistant with access to exactly these tools:\n"
62
  " 1) web_search(query:str)\n"
63
  " 2) parse_excel(path:str,sheet_name:str)\n"
64
  " 3) ocr_image(path:str)\n\n"
65
+ "⚠️ **IMPORTANT** ⚠️: If (and only if) you need to call a tool, output exactly one\n"
66
+ "JSON object (no extra text). For example:\n"
67
  "```json\n"
68
  '{"tool":"web_search","query":"Mercedes Sosa albums 2000-2009"}\n'
69
  "```\n"
70
+ "That JSON must start at the very first character of your response and end at the last\n"
71
+ "character—no quotes, no code fences, and no explanation.\n\n"
72
  "If you do NOT need any tool, simply reply with your final answer as plain text."
73
  )
74
  )
75
 
76
+ # 2) Initialize state with just that SystemMessage
77
+ initial_state = {"messages": [system_msg]}
78
 
79
+ # 3) --- FIRST PASS: just invoke the graph and see what it returns ---
80
  try:
81
+ first_pass = compiled_graph.invoke(initial_state, user_input)
82
+ except Exception as e:
83
+ # If the error happens here, we log it plus any partial messages
84
+ print("‼️ ERROR during first invoke:", repr(e))
85
+ # If there is some state, print it for debugging:
86
+ try:
87
+ partial = first_pass["messages"]
88
+ except Exception:
89
+ partial = None
90
+ print("Partial state messages (if any):", partial)
91
+ # Return empty string so the loop can continue
92
+ return ""
93
+
94
+ # 4) If we reach here, first_pass succeeded. Log every AIMessage so we can see exactly
95
+ print("===== AGENT MESSAGES (First Pass) =====")
96
+ for idx, msg in enumerate(first_pass["messages"]):
97
+ if isinstance(msg, AIMessage):
98
+ # Print out raw content to see if it’s JSON or plain text
99
+ print(f"[AIMessage #{idx}]: {repr(msg.content)}")
100
+ print("=========================================")
101
+
102
+ # 5) Find the last AIMessage content (might be JSON or might be plain text)
103
+ last_msg = None
104
+ for msg in reversed(first_pass["messages"]):
105
+ if isinstance(msg, AIMessage):
106
+ last_msg = msg.content
107
+ break
108
+
109
+ # 6) Attempt to parse last_msg as a JSON dict for a tool call
110
+ tool_dict = parse_tool_json(last_msg or "")
111
+ if tool_dict:
112
+ # 7) If valid JSON, run the tool and do a second pass
113
+ print(">> Parsed tool call:", tool_dict)
114
+ tool_result = tool_node.run(tool_dict)
115
+ print(f">> Tool '{tool_dict['tool']}' returned: {repr(tool_result)}")
116
+
117
+ continuation_state = {
118
+ "messages": [
119
+ *first_pass["messages"],
120
+ AIMessage(content=tool_result)
121
+ ]
122
+ }
123
+ try:
124
+ second_pass = compiled_graph.invoke(continuation_state, "")
125
+ except Exception as e2:
126
+ print("‼️ ERROR during second invoke:", repr(e2))
127
+ return ""
128
+ # 8) Log second‐pass AIMessages
129
+ print("===== AGENT MESSAGES (Second Pass) =====")
130
+ for idx, msg in enumerate(second_pass["messages"]):
131
  if isinstance(msg, AIMessage):
132
+ print(f"[AIMessage2 #{idx}]: {repr(msg.content)}")
133
  print("=========================================")
134
 
135
+ # 9) Return the final AIMessage from second_pass
136
+ for msg in reversed(second_pass["messages"]):
 
137
  if isinstance(msg, AIMessage):
138
+ return msg.content or ""
139
+ return ""
140
+ else:
141
+ # 10) No valid JSON, return last_msg as plain text
142
+ return last_msg or ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
  class BasicAgent:
145
  def __init__(self):