Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -28,12 +28,6 @@ llm = ChatOpenAI(model_name="gpt-4.1-mini", temperature=0.0)
|
|
28 |
|
29 |
# ─── 2) Revised plan_node ───
|
30 |
def plan_node(state: AgentState) -> AgentState:
|
31 |
-
"""
|
32 |
-
Look at the last HumanMessage in state['messages'] to get user_input.
|
33 |
-
Then call llm with exactly [SystemMessage, HumanMessage(user_input)] so
|
34 |
-
we never feed in a list lacking an AIMessage internally.
|
35 |
-
"""
|
36 |
-
# 1) Find the last HumanMessage from prior history
|
37 |
prior_msgs = state.get("messages", [])
|
38 |
user_input = ""
|
39 |
for msg in reversed(prior_msgs):
|
@@ -41,30 +35,39 @@ def plan_node(state: AgentState) -> AgentState:
|
|
41 |
user_input = msg.content
|
42 |
break
|
43 |
|
44 |
-
#
|
45 |
system_msg = SystemMessage(
|
46 |
content=(
|
47 |
-
"You are an agent that decides whether to call a tool or answer
|
48 |
-
"
|
49 |
-
"If you
|
50 |
-
"
|
51 |
-
"
|
52 |
-
"
|
53 |
-
"
|
54 |
-
"
|
|
|
55 |
)
|
56 |
)
|
57 |
human_msg = HumanMessage(content=user_input)
|
58 |
|
59 |
-
#
|
60 |
llm_response = llm([system_msg, human_msg])
|
61 |
llm_out = llm_response.content.strip()
|
62 |
|
63 |
-
#
|
|
|
|
|
|
|
|
|
|
|
64 |
ai_msg = AIMessage(content=llm_out)
|
65 |
new_msgs = prior_msgs.copy() + [ai_msg]
|
|
|
|
|
66 |
try:
|
67 |
-
parsed =
|
|
|
68 |
if isinstance(parsed, dict):
|
69 |
partial: AgentState = {"messages": new_msgs}
|
70 |
allowed = {
|
@@ -78,31 +81,29 @@ def plan_node(state: AgentState) -> AgentState:
|
|
78 |
for k, v in parsed.items():
|
79 |
if k in allowed:
|
80 |
partial[k] = v
|
|
|
81 |
return partial
|
82 |
-
except
|
83 |
-
|
|
|
|
|
|
|
|
|
84 |
|
85 |
-
# 5) Fallback
|
86 |
-
return {
|
87 |
-
"messages": new_msgs,
|
88 |
-
"final_answer": "Sorry, I could not parse your intent."
|
89 |
-
}
|
90 |
|
91 |
|
92 |
# ─── 3) Revised finalize_node ───
|
93 |
def finalize_node(state: AgentState) -> AgentState:
|
94 |
-
# If plan_node already provided a final answer, skip LLM
|
95 |
if state.get("final_answer") is not None:
|
|
|
96 |
return {"final_answer": state["final_answer"]}
|
97 |
|
98 |
-
# Re-extract the last user question from messages
|
99 |
question = ""
|
100 |
for msg in reversed(state.get("messages", [])):
|
101 |
if isinstance(msg, HumanMessage):
|
102 |
question = msg.content
|
103 |
break
|
104 |
|
105 |
-
# Build a combined context
|
106 |
combined = f"USER_QUESTION: {question}\n"
|
107 |
if sr := state.get("web_search_result"):
|
108 |
combined += f"WEB_SEARCH_RESULT: {sr}\n"
|
@@ -110,11 +111,17 @@ def finalize_node(state: AgentState) -> AgentState:
|
|
110 |
combined += f"OCR_RESULT: {orc}\n"
|
111 |
if exr := state.get("excel_result"):
|
112 |
combined += f"EXCEL_RESULT: {exr}\n"
|
113 |
-
# Check for both possible transcript keys
|
114 |
audio_transcript = state.get("audio_transcript") or state.get("transcript")
|
115 |
if audio_transcript:
|
116 |
combined += f"AUDIO_TRANSCRIPT: {audio_transcript}\n"
|
117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
118 |
|
119 |
llm_response = llm([SystemMessage(content=combined)])
|
120 |
return {"final_answer": llm_response.content.strip()}
|
@@ -153,17 +160,21 @@ graph.add_edge(START, "plan")
|
|
153 |
|
154 |
# 5.c) plan → conditional: if any tool key was set, go to "tools"; otherwise "finalize"
|
155 |
def route_plan(plan_out: AgentState) -> str:
|
|
|
|
|
|
|
156 |
if (
|
157 |
plan_out.get("web_search_query")
|
158 |
or plan_out.get("ocr_path")
|
159 |
or plan_out.get("excel_path")
|
160 |
-
or plan_out.get("audio_path")
|
161 |
):
|
162 |
-
print("
|
163 |
return "tools"
|
164 |
-
print("
|
165 |
return "finalize"
|
166 |
|
|
|
167 |
graph.add_conditional_edges(
|
168 |
"plan",
|
169 |
route_plan,
|
|
|
28 |
|
29 |
# ─── 2) Revised plan_node ───
|
30 |
def plan_node(state: AgentState) -> AgentState:
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
prior_msgs = state.get("messages", [])
|
32 |
user_input = ""
|
33 |
for msg in reversed(prior_msgs):
|
|
|
35 |
user_input = msg.content
|
36 |
break
|
37 |
|
38 |
+
# (1) Build your system/human messages exactly as before
|
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"
|
46 |
+
" {\"ocr_path\":\"<path to image>\"}\n"
|
47 |
+
" {\"excel_path\":\"<path to xlsx>\", \"excel_sheet_name\":\"<sheet>\"}\n"
|
48 |
+
" {\"audio_path\":\"<path to audio file>\"}\n"
|
49 |
+
"Do not include any extra characters or markdown—only the JSON literal."
|
50 |
)
|
51 |
)
|
52 |
human_msg = HumanMessage(content=user_input)
|
53 |
|
54 |
+
# (2) Call the LLM
|
55 |
llm_response = llm([system_msg, human_msg])
|
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)
|
65 |
new_msgs = prior_msgs.copy() + [ai_msg]
|
66 |
+
|
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 |
+
|
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 |
|
94 |
|
95 |
# ─── 3) Revised finalize_node ───
|
96 |
def finalize_node(state: AgentState) -> AgentState:
|
|
|
97 |
if state.get("final_answer") is not None:
|
98 |
+
print(">>> finalize_node: returning existing final_answer:", state["final_answer"])
|
99 |
return {"final_answer": state["final_answer"]}
|
100 |
|
|
|
101 |
question = ""
|
102 |
for msg in reversed(state.get("messages", [])):
|
103 |
if isinstance(msg, HumanMessage):
|
104 |
question = msg.content
|
105 |
break
|
106 |
|
|
|
107 |
combined = f"USER_QUESTION: {question}\n"
|
108 |
if sr := state.get("web_search_result"):
|
109 |
combined += f"WEB_SEARCH_RESULT: {sr}\n"
|
|
|
111 |
combined += f"OCR_RESULT: {orc}\n"
|
112 |
if exr := state.get("excel_result"):
|
113 |
combined += f"EXCEL_RESULT: {exr}\n"
|
|
|
114 |
audio_transcript = state.get("audio_transcript") or state.get("transcript")
|
115 |
if audio_transcript:
|
116 |
combined += f"AUDIO_TRANSCRIPT: {audio_transcript}\n"
|
117 |
+
|
118 |
+
combined += (
|
119 |
+
"Based on the above, provide ONLY the final answer. "
|
120 |
+
"…(rest of your instructions)…"
|
121 |
+
)
|
122 |
+
|
123 |
+
# DEBUG: show exactly what we're sending to GPT-4 for final answer
|
124 |
+
print("\n>>> finalize_node prompt to LLM:\n" + combined + "\n<<< end prompt >>>\n")
|
125 |
|
126 |
llm_response = llm([SystemMessage(content=combined)])
|
127 |
return {"final_answer": llm_response.content.strip()}
|
|
|
160 |
|
161 |
# 5.c) plan → conditional: if any tool key was set, go to "tools"; otherwise "finalize"
|
162 |
def route_plan(plan_out: AgentState) -> str:
|
163 |
+
# print what keys are present in plan_out
|
164 |
+
print(f">> route_plan sees plan_out keys: {list(plan_out.keys())}")
|
165 |
+
|
166 |
if (
|
167 |
plan_out.get("web_search_query")
|
168 |
or plan_out.get("ocr_path")
|
169 |
or plan_out.get("excel_path")
|
170 |
+
or plan_out.get("audio_path")
|
171 |
):
|
172 |
+
print(">> route_plan ➡️ tools")
|
173 |
return "tools"
|
174 |
+
print(">> route_plan ➡️ finalize")
|
175 |
return "finalize"
|
176 |
|
177 |
+
|
178 |
graph.add_conditional_edges(
|
179 |
"plan",
|
180 |
route_plan,
|