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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +72 -52
app.py CHANGED
@@ -54,75 +54,95 @@ graph.add_edge(START, "agent")
54
  graph.add_edge("agent", END)
55
  compiled_graph = graph.compile()
56
 
57
- # 3) The corrected respond_to_input:
58
  def respond_to_input(user_input: str) -> str:
59
-
 
 
 
 
 
 
60
  system_msg = SystemMessage(
61
  content=(
62
  "You are an assistant with access to exactly these tools:\n"
63
  " 1) web_search(query:str)\n"
64
  " 2) parse_excel(path:str,sheet_name:str)\n"
65
  " 3) ocr_image(path:str)\n\n"
66
- "⚠️ If (and only if) you need to call a tool, output exactly one JSON object "
67
- "and nothing else. For example:\n"
68
  "```json\n"
69
  '{"tool":"web_search","query":"Mercedes Sosa albums 2000-2009"}\n'
70
  "```\n"
71
- "That JSON must start at the very first character of your response and end at the last character—"
72
  "no quotes, no code fences, and no extra explanation.\n\n"
73
- "If you do not need any tool, simply reply with your final answer as plain text."
74
  )
75
  )
76
-
 
77
  initial_state = { "messages": [system_msg] }
78
- final_state = compiled_graph.invoke(initial_state, user_input)
79
- print("in respond_to_input : ")
80
-
81
- print("===== AGENT MESSAGES =====")
82
- for i, msg in enumerate(final_state["messages"]):
83
- if isinstance(msg, AIMessage):
84
- print(f" [AIMessage #{i}]: {repr(msg.content)}")
85
- print("==========================")
86
-
87
- # E) Take the very last AIMessage that the agent produced
88
- last_msg = None
89
- for msg in reversed(final_state["messages"]):
90
- if isinstance(msg, AIMessage):
91
- last_msg = msg.content
92
- break
93
-
94
- # F) Try to parse that last message as a JSON‐dict for a tool call
95
- tool_dict = parse_tool_json(last_msg or "")
96
- if tool_dict:
97
- # G) If valid JSON, run the tool
98
- print(">> Parsed tool call:", tool_dict)
99
- result = tool_node.run(tool_dict)
100
-
101
- # H) Feed back the tool’s output as the next AIMessage, then re‐invoke
102
- # (Note: we pass an empty string as user_input because we’re continuing the same turn)
103
- continuation_state = {
104
- "messages": [
105
- *final_state["messages"],
106
- AIMessage(content=result)
107
- ]
108
- }
109
- second_pass = compiled_graph.invoke(continuation_state, "")
110
-
111
- # I) Log that second pass
112
- print("===== AGENT SECOND PASS =====")
113
- for i, msg in enumerate(second_pass["messages"]):
114
  if isinstance(msg, AIMessage):
115
- print(f" [AIMessage2 #{i}]: {repr(msg.content)}")
116
- print("==============================")
117
 
118
- # J) Return the final AIMessage from that second pass
119
- for msg in reversed(second_pass["messages"]):
 
120
  if isinstance(msg, AIMessage):
121
- return msg.content or ""
122
- return ""
123
- else:
124
- # K) If it wasn’t valid JSON, just return the plain last_msg
125
- return last_msg or ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
  class BasicAgent:
128
  def __init__(self):
 
54
  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):