Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -78,36 +78,32 @@ google_genai_client: Optional[google_genai_sdk.Client] = None
|
|
| 78 |
try:
|
| 79 |
from langgraph.graph import StateGraph, END
|
| 80 |
try:
|
| 81 |
-
from langgraph.prebuilt import ToolNode
|
| 82 |
LG_ToolExecutor_Class = ToolNode
|
| 83 |
print("Using langgraph.prebuilt.ToolNode for LangGraph tool execution.")
|
| 84 |
except ImportError:
|
| 85 |
try:
|
| 86 |
-
from langgraph.prebuilt import ToolExecutor
|
| 87 |
LG_ToolExecutor_Class = ToolExecutor
|
| 88 |
print("Using langgraph.prebuilt.ToolExecutor (fallback) for LangGraph tool execution.")
|
| 89 |
except ImportError as e_lg_exec_inner:
|
| 90 |
print(f"Failed to import ToolNode and ToolExecutor from langgraph.prebuilt: {e_lg_exec_inner}")
|
| 91 |
LG_ToolExecutor_Class = None
|
| 92 |
|
| 93 |
-
if LG_ToolExecutor_Class is not None:
|
| 94 |
try:
|
| 95 |
from langgraph.prebuilt import ToolInvocation as LGToolInvocationActual
|
| 96 |
except ImportError:
|
| 97 |
try:
|
| 98 |
-
from langgraph.tools import ToolInvocation as LGToolInvocationActual
|
| 99 |
-
print("Imported ToolInvocation from langgraph.tools
|
| 100 |
except ImportError as e_ti:
|
| 101 |
-
print(f"WARNING: Could not import ToolInvocation from langgraph.prebuilt or langgraph.tools: {e_ti}
|
| 102 |
-
LGToolInvocationActual = None
|
| 103 |
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
# Set LANGGRAPH_FLAVOR_AVAILABLE only if all critical components for your setup are loaded.
|
| 109 |
-
# If LGToolInvocationActual is critical for your chosen LG_ToolExecutor_Class, check it here.
|
| 110 |
-
if LGToolInvocationActual is not None or type(LG_ToolExecutor_Class).__name__ == 'ToolNode': # ToolNode might not need separate ToolInvocation
|
| 111 |
LANGGRAPH_FLAVOR_AVAILABLE = True
|
| 112 |
LG_StateGraph, LG_END, LG_ToolInvocation, add_messages, MemorySaver_Class = \
|
| 113 |
StateGraph, END, LGToolInvocationActual, lg_add_messages, LGMemorySaver # type: ignore
|
|
@@ -115,14 +111,14 @@ try:
|
|
| 115 |
else:
|
| 116 |
LANGGRAPH_FLAVOR_AVAILABLE = False
|
| 117 |
LG_StateGraph, LG_END, add_messages, MemorySaver_Class = (None,) * 4 # type: ignore
|
| 118 |
-
print(f"WARNING: LangGraph ToolInvocation not found and may be required. LangGraph agent functionality might be limited or disabled.")
|
| 119 |
|
| 120 |
-
else:
|
| 121 |
LANGGRAPH_FLAVOR_AVAILABLE = False
|
| 122 |
LG_StateGraph, LG_END, LG_ToolInvocation, add_messages, MemorySaver_Class = (None,) * 5 # type: ignore
|
| 123 |
print(f"WARNING: No suitable LangGraph tool executor (ToolNode/ToolExecutor) found. LangGraph agent will be disabled.")
|
| 124 |
|
| 125 |
-
except ImportError as e:
|
| 126 |
LANGGRAPH_FLAVOR_AVAILABLE = False
|
| 127 |
LG_StateGraph, LG_ToolExecutor_Class, LG_END, LG_ToolInvocation, add_messages, MemorySaver_Class = (None,) * 6
|
| 128 |
print(f"WARNING: Core LangGraph components (like StateGraph, END) not found or import error: {e}. LangGraph agent will be disabled.")
|
|
@@ -259,7 +255,7 @@ def _download_file(file_identifier: str, task_id_for_file: Optional[str] = None)
|
|
| 259 |
name_without_ext, current_ext = os.path.splitext(effective_save_path)
|
| 260 |
if not current_ext:
|
| 261 |
content_type_header = r.headers.get('content-type', '')
|
| 262 |
-
content_type_val = content_type_header.split(';').strip() if content_type_header else ''
|
| 263 |
if content_type_val:
|
| 264 |
guessed_ext = mimetypes.guess_extension(content_type_val)
|
| 265 |
if guessed_ext: effective_save_path += guessed_ext; logger.info(f"Added guessed ext: {guessed_ext}")
|
|
@@ -400,9 +396,9 @@ def initialize_agent_and_tools(force_reinit=False):
|
|
| 400 |
model=GEMINI_MODEL_NAME,
|
| 401 |
google_api_key=GOOGLE_API_KEY,
|
| 402 |
temperature=0.0,
|
| 403 |
-
# safety_settings is removed to use model defaults
|
| 404 |
timeout=120,
|
| 405 |
-
convert_system_message_to_human=False # Explicitly False
|
| 406 |
)
|
| 407 |
logger.info(f"LangChain LLM (Planner) initialized: {GEMINI_MODEL_NAME} (Using default safety settings, convert_system_message_to_human=False)")
|
| 408 |
except Exception as e:
|
|
@@ -421,61 +417,68 @@ def initialize_agent_and_tools(force_reinit=False):
|
|
| 421 |
except Exception as e: logger.warning(f"PythonREPLTool init failed: {e}")
|
| 422 |
logger.info(f"Final tools list for agent: {[t.name for t in TOOLS]}")
|
| 423 |
|
| 424 |
-
if LANGGRAPH_FLAVOR_AVAILABLE and all([LG_StateGraph, LG_ToolExecutor_Class, LG_END, LLM_INSTANCE, add_messages]): # LG_ToolInvocation removed from check
|
| 425 |
if not LANGGRAPH_MEMORY_SAVER and MemorySaver_Class: LANGGRAPH_MEMORY_SAVER = MemorySaver_Class(); logger.info("LangGraph MemorySaver initialized.")
|
| 426 |
try:
|
| 427 |
logger.info(f"Attempting LangGraph init (Tool Executor type: {LG_ToolExecutor_Class.__name__ if LG_ToolExecutor_Class else 'None'})")
|
| 428 |
_TypedDict = getattr(__import__('typing_extensions'), 'TypedDict', dict)
|
| 429 |
class AgentState(_TypedDict): input: str; messages: Annotated[List[Any], add_messages]
|
| 430 |
|
| 431 |
-
|
| 432 |
tools="\n".join([f"- {t.name}: {t.description}" for t in TOOLS]),
|
| 433 |
-
input="{current_task_input_placeholder}"
|
| 434 |
)
|
| 435 |
|
| 436 |
def agent_node(state: AgentState):
|
| 437 |
current_task_actual_input = state.get('input', '')
|
| 438 |
-
system_message_content =
|
| 439 |
|
| 440 |
messages_for_llm = [SystemMessage(content=system_message_content)]
|
|
|
|
|
|
|
|
|
|
| 441 |
messages_for_llm.extend(state.get('messages', []))
|
| 442 |
|
| 443 |
logger.debug(f"LangGraph agent_node - messages_for_llm: {messages_for_llm}")
|
| 444 |
-
# Check the
|
| 445 |
-
if not messages_for_llm or not (isinstance(messages_for_llm, SystemMessage) and messages_for_llm.content and messages_for_llm.content.strip()):
|
| 446 |
-
logger.error("LLM call would fail in agent_node: First message (SystemMessage) is
|
| 447 |
-
return {"messages": [AIMessage(content="[ERROR] Agent node: Initial SystemMessage content is
|
| 448 |
|
| 449 |
-
bound_llm = LLM_INSTANCE.bind_tools(TOOLS)
|
| 450 |
response = bound_llm.invoke(messages_for_llm)
|
| 451 |
return {"messages": [response]}
|
| 452 |
|
| 453 |
-
if not LG_ToolExecutor_Class: raise ValueError("LG_ToolExecutor_Class is None for LangGraph.")
|
| 454 |
tool_executor_instance_lg = LG_ToolExecutor_Class(tools=TOOLS)
|
| 455 |
|
| 456 |
-
|
|
|
|
|
|
|
| 457 |
last_msg = state['messages'][-1] if state.get('messages') and isinstance(state['messages'][-1], AIMessage) else None
|
| 458 |
if not last_msg or not last_msg.tool_calls: return {"messages": []}
|
| 459 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 460 |
tool_results = []
|
| 461 |
-
for tc in last_msg.tool_calls:
|
| 462 |
name, args, tc_id = tc.get('name'), tc.get('args'), tc.get('id')
|
| 463 |
if not all([name, isinstance(args, dict), tc_id]):
|
| 464 |
err_msg=f"Invalid tool_call: {tc}"; logger.error(err_msg)
|
| 465 |
tool_results.append(ToolMessage(f"Error: {err_msg}", tool_call_id=tc_id or "error_id", name=name or "error_tool"))
|
| 466 |
continue
|
| 467 |
try:
|
| 468 |
-
logger.info(f"LG Tool Invoking: '{name}' with {args} (ID: {tc_id})")
|
| 469 |
-
#
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
#
|
| 474 |
-
|
| 475 |
-
tool_invocation_obj_lg = LG_ToolInvocation(tool=name, tool_input=args)
|
| 476 |
-
output_lg = tool_executor_instance_lg.invoke(tool_invocation_obj_lg) # type: ignore
|
| 477 |
-
else: # Assume tool_executor_instance_lg (e.g. ToolNode) can handle the raw tool_call dict
|
| 478 |
-
output_lg = tool_executor_instance_lg.invoke(tc) # type: ignore
|
| 479 |
|
| 480 |
tool_results.append(ToolMessage(content=str(output_lg), tool_call_id=tc_id, name=name))
|
| 481 |
except Exception as e_tool_node_lg:
|
|
@@ -483,12 +486,15 @@ def initialize_agent_and_tools(force_reinit=False):
|
|
| 483 |
tool_results.append(ToolMessage(content=f"Error for tool {name}: {str(e_tool_node_lg)}", tool_call_id=tc_id, name=name))
|
| 484 |
return {"messages": tool_results}
|
| 485 |
|
| 486 |
-
|
| 487 |
workflow_lg = LG_StateGraph(AgentState) # type: ignore
|
| 488 |
workflow_lg.add_node("agent", agent_node)
|
| 489 |
-
# If LG_ToolExecutor_Class is ToolNode,
|
| 490 |
-
|
| 491 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 492 |
|
| 493 |
workflow_lg.set_entry_point("agent")
|
| 494 |
def should_continue_lg(state: AgentState): return "tools" if state['messages'][-1].tool_calls else LG_END
|
|
@@ -681,7 +687,7 @@ with gr.Blocks(css=".gradio-container {max-width:1280px !important;margin:auto !
|
|
| 681 |
demo.load(update_ui_on_load_fn_within_context, [], [agent_status_display, missing_secrets_display])
|
| 682 |
|
| 683 |
if __name__ == "__main__":
|
| 684 |
-
logger.info(f"Application starting up (v7 -
|
| 685 |
if not PYPDF2_AVAILABLE: logger.warning("PyPDF2 (PDF tool) NOT AVAILABLE.")
|
| 686 |
if not PIL_TESSERACT_AVAILABLE: logger.warning("Pillow/Pytesseract (OCR tool) NOT AVAILABLE.")
|
| 687 |
if not WHISPER_AVAILABLE: logger.warning("Whisper (Audio tool) NOT AVAILABLE.")
|
|
|
|
| 78 |
try:
|
| 79 |
from langgraph.graph import StateGraph, END
|
| 80 |
try:
|
| 81 |
+
from langgraph.prebuilt import ToolNode
|
| 82 |
LG_ToolExecutor_Class = ToolNode
|
| 83 |
print("Using langgraph.prebuilt.ToolNode for LangGraph tool execution.")
|
| 84 |
except ImportError:
|
| 85 |
try:
|
| 86 |
+
from langgraph.prebuilt import ToolExecutor
|
| 87 |
LG_ToolExecutor_Class = ToolExecutor
|
| 88 |
print("Using langgraph.prebuilt.ToolExecutor (fallback) for LangGraph tool execution.")
|
| 89 |
except ImportError as e_lg_exec_inner:
|
| 90 |
print(f"Failed to import ToolNode and ToolExecutor from langgraph.prebuilt: {e_lg_exec_inner}")
|
| 91 |
LG_ToolExecutor_Class = None
|
| 92 |
|
| 93 |
+
if LG_ToolExecutor_Class is not None:
|
| 94 |
try:
|
| 95 |
from langgraph.prebuilt import ToolInvocation as LGToolInvocationActual
|
| 96 |
except ImportError:
|
| 97 |
try:
|
| 98 |
+
from langgraph.tools import ToolInvocation as LGToolInvocationActual
|
| 99 |
+
print("Imported ToolInvocation from langgraph.tools")
|
| 100 |
except ImportError as e_ti:
|
| 101 |
+
print(f"WARNING: Could not import ToolInvocation from langgraph.prebuilt or langgraph.tools: {e_ti}")
|
| 102 |
+
LGToolInvocationActual = None
|
| 103 |
|
| 104 |
+
if LGToolInvocationActual is not None or type(LG_ToolExecutor_Class).__name__ == 'ToolNode':
|
| 105 |
+
from langgraph.graph.message import add_messages as lg_add_messages
|
| 106 |
+
from langgraph.checkpoint.memory import MemorySaver as LGMemorySaver
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
LANGGRAPH_FLAVOR_AVAILABLE = True
|
| 108 |
LG_StateGraph, LG_END, LG_ToolInvocation, add_messages, MemorySaver_Class = \
|
| 109 |
StateGraph, END, LGToolInvocationActual, lg_add_messages, LGMemorySaver # type: ignore
|
|
|
|
| 111 |
else:
|
| 112 |
LANGGRAPH_FLAVOR_AVAILABLE = False
|
| 113 |
LG_StateGraph, LG_END, add_messages, MemorySaver_Class = (None,) * 4 # type: ignore
|
| 114 |
+
print(f"WARNING: LangGraph ToolInvocation not found and may be required by older ToolExecutor. LangGraph agent functionality might be limited or disabled.")
|
| 115 |
|
| 116 |
+
else:
|
| 117 |
LANGGRAPH_FLAVOR_AVAILABLE = False
|
| 118 |
LG_StateGraph, LG_END, LG_ToolInvocation, add_messages, MemorySaver_Class = (None,) * 5 # type: ignore
|
| 119 |
print(f"WARNING: No suitable LangGraph tool executor (ToolNode/ToolExecutor) found. LangGraph agent will be disabled.")
|
| 120 |
|
| 121 |
+
except ImportError as e:
|
| 122 |
LANGGRAPH_FLAVOR_AVAILABLE = False
|
| 123 |
LG_StateGraph, LG_ToolExecutor_Class, LG_END, LG_ToolInvocation, add_messages, MemorySaver_Class = (None,) * 6
|
| 124 |
print(f"WARNING: Core LangGraph components (like StateGraph, END) not found or import error: {e}. LangGraph agent will be disabled.")
|
|
|
|
| 255 |
name_without_ext, current_ext = os.path.splitext(effective_save_path)
|
| 256 |
if not current_ext:
|
| 257 |
content_type_header = r.headers.get('content-type', '')
|
| 258 |
+
content_type_val = content_type_header.split(';')[0].strip() if content_type_header else ''
|
| 259 |
if content_type_val:
|
| 260 |
guessed_ext = mimetypes.guess_extension(content_type_val)
|
| 261 |
if guessed_ext: effective_save_path += guessed_ext; logger.info(f"Added guessed ext: {guessed_ext}")
|
|
|
|
| 396 |
model=GEMINI_MODEL_NAME,
|
| 397 |
google_api_key=GOOGLE_API_KEY,
|
| 398 |
temperature=0.0,
|
| 399 |
+
# safety_settings is removed to use model defaults to isolate "contents not specified" error
|
| 400 |
timeout=120,
|
| 401 |
+
convert_system_message_to_human=False # Explicitly set to False
|
| 402 |
)
|
| 403 |
logger.info(f"LangChain LLM (Planner) initialized: {GEMINI_MODEL_NAME} (Using default safety settings, convert_system_message_to_human=False)")
|
| 404 |
except Exception as e:
|
|
|
|
| 417 |
except Exception as e: logger.warning(f"PythonREPLTool init failed: {e}")
|
| 418 |
logger.info(f"Final tools list for agent: {[t.name for t in TOOLS]}")
|
| 419 |
|
| 420 |
+
if LANGGRAPH_FLAVOR_AVAILABLE and all([LG_StateGraph, LG_ToolExecutor_Class, LG_END, LLM_INSTANCE, add_messages]): # LG_ToolInvocation removed from check
|
| 421 |
if not LANGGRAPH_MEMORY_SAVER and MemorySaver_Class: LANGGRAPH_MEMORY_SAVER = MemorySaver_Class(); logger.info("LangGraph MemorySaver initialized.")
|
| 422 |
try:
|
| 423 |
logger.info(f"Attempting LangGraph init (Tool Executor type: {LG_ToolExecutor_Class.__name__ if LG_ToolExecutor_Class else 'None'})")
|
| 424 |
_TypedDict = getattr(__import__('typing_extensions'), 'TypedDict', dict)
|
| 425 |
class AgentState(_TypedDict): input: str; messages: Annotated[List[Any], add_messages]
|
| 426 |
|
| 427 |
+
base_system_prompt_content_template_lg = LANGGRAPH_PROMPT_TEMPLATE_STR.format(
|
| 428 |
tools="\n".join([f"- {t.name}: {t.description}" for t in TOOLS]),
|
| 429 |
+
input="{current_task_input_placeholder}"
|
| 430 |
)
|
| 431 |
|
| 432 |
def agent_node(state: AgentState):
|
| 433 |
current_task_actual_input = state.get('input', '')
|
| 434 |
+
system_message_content = base_system_prompt_content_template_lg.replace("{current_task_input_placeholder}", current_task_actual_input)
|
| 435 |
|
| 436 |
messages_for_llm = [SystemMessage(content=system_message_content)]
|
| 437 |
+
# If there's history, append it after the SystemMessage.
|
| 438 |
+
# The current_task_actual_input is now part of the SystemMessage.
|
| 439 |
+
# The 'messages' in state are previous AIMessages/ToolMessages.
|
| 440 |
messages_for_llm.extend(state.get('messages', []))
|
| 441 |
|
| 442 |
logger.debug(f"LangGraph agent_node - messages_for_llm: {messages_for_llm}")
|
| 443 |
+
# Check the first message (SystemMessage)
|
| 444 |
+
if not messages_for_llm or not (isinstance(messages_for_llm[0], SystemMessage) and messages_for_llm[0].content and str(messages_for_llm[0].content).strip()):
|
| 445 |
+
logger.error("LLM call would fail in agent_node: First message (SystemMessage) is missing, has no content, is not a string, or content is empty/whitespace.")
|
| 446 |
+
return {"messages": [AIMessage(content="[ERROR] Agent node: Initial SystemMessage content is invalid or empty.")]}
|
| 447 |
|
| 448 |
+
bound_llm = LLM_INSTANCE.bind_tools(TOOLS)
|
| 449 |
response = bound_llm.invoke(messages_for_llm)
|
| 450 |
return {"messages": [response]}
|
| 451 |
|
| 452 |
+
if not LG_ToolExecutor_Class: raise ValueError("LG_ToolExecutor_Class (ToolNode or ToolExecutor) is None for LangGraph.")
|
| 453 |
tool_executor_instance_lg = LG_ToolExecutor_Class(tools=TOOLS)
|
| 454 |
|
| 455 |
+
# If LG_ToolExecutor_Class is ToolNode, it's often added directly to the graph.
|
| 456 |
+
# If we keep a custom tool_node, it needs to correctly use the tool_executor_instance_lg.
|
| 457 |
+
def tool_node(state: AgentState):
|
| 458 |
last_msg = state['messages'][-1] if state.get('messages') and isinstance(state['messages'][-1], AIMessage) else None
|
| 459 |
if not last_msg or not last_msg.tool_calls: return {"messages": []}
|
| 460 |
|
| 461 |
+
# ToolNode can often take the AIMessage directly, or a list of tool_calls
|
| 462 |
+
# The `invoke` method of ToolNode typically expects the full previous message or just tool_calls.
|
| 463 |
+
# Depending on the exact version and how ToolNode is implemented.
|
| 464 |
+
# The most straightforward is to let ToolNode handle the AIMessage's tool_calls.
|
| 465 |
+
# This implies tool_executor_instance_lg should be the node itself.
|
| 466 |
+
# However, if we must use a custom function:
|
| 467 |
tool_results = []
|
| 468 |
+
for tc in last_msg.tool_calls: # tc is a dict from AIMessage.tool_calls
|
| 469 |
name, args, tc_id = tc.get('name'), tc.get('args'), tc.get('id')
|
| 470 |
if not all([name, isinstance(args, dict), tc_id]):
|
| 471 |
err_msg=f"Invalid tool_call: {tc}"; logger.error(err_msg)
|
| 472 |
tool_results.append(ToolMessage(f"Error: {err_msg}", tool_call_id=tc_id or "error_id", name=name or "error_tool"))
|
| 473 |
continue
|
| 474 |
try:
|
| 475 |
+
logger.info(f"LG Tool Invoking via custom tool_node: '{name}' with {args} (ID: {tc_id})")
|
| 476 |
+
# Construct ToolInvocation if LG_ToolInvocation is available and needed by the executor_instance
|
| 477 |
+
if LG_ToolInvocation and not isinstance(tool_executor_instance_lg, ToolNode): # ToolNode might not need this
|
| 478 |
+
invocation = LG_ToolInvocation(tool=name, tool_input=args)
|
| 479 |
+
output_lg = tool_executor_instance_lg.invoke(invocation) # type: ignore
|
| 480 |
+
else: # Assume ToolNode or compatible executor can take the dict directly
|
| 481 |
+
output_lg = tool_executor_instance_lg.invoke(tc) # Pass the tool_call dict
|
|
|
|
|
|
|
|
|
|
|
|
|
| 482 |
|
| 483 |
tool_results.append(ToolMessage(content=str(output_lg), tool_call_id=tc_id, name=name))
|
| 484 |
except Exception as e_tool_node_lg:
|
|
|
|
| 486 |
tool_results.append(ToolMessage(content=f"Error for tool {name}: {str(e_tool_node_lg)}", tool_call_id=tc_id, name=name))
|
| 487 |
return {"messages": tool_results}
|
| 488 |
|
|
|
|
| 489 |
workflow_lg = LG_StateGraph(AgentState) # type: ignore
|
| 490 |
workflow_lg.add_node("agent", agent_node)
|
| 491 |
+
# If LG_ToolExecutor_Class is ToolNode, use the instance directly
|
| 492 |
+
if type(LG_ToolExecutor_Class).__name__ == 'ToolNode':
|
| 493 |
+
workflow_lg.add_node("tools", tool_executor_instance_lg)
|
| 494 |
+
logger.info("Added ToolNode instance directly to LangGraph.")
|
| 495 |
+
else: # Fallback to custom tool_node (might be needed for older ToolExecutor)
|
| 496 |
+
workflow_lg.add_node("tools", tool_node)
|
| 497 |
+
logger.info("Added custom tool_node function to LangGraph.")
|
| 498 |
|
| 499 |
workflow_lg.set_entry_point("agent")
|
| 500 |
def should_continue_lg(state: AgentState): return "tools" if state['messages'][-1].tool_calls else LG_END
|
|
|
|
| 687 |
demo.load(update_ui_on_load_fn_within_context, [], [agent_status_display, missing_secrets_display])
|
| 688 |
|
| 689 |
if __name__ == "__main__":
|
| 690 |
+
logger.info(f"Application starting up (v7.1 - Agent Node Message Structure Change)...")
|
| 691 |
if not PYPDF2_AVAILABLE: logger.warning("PyPDF2 (PDF tool) NOT AVAILABLE.")
|
| 692 |
if not PIL_TESSERACT_AVAILABLE: logger.warning("Pillow/Pytesseract (OCR tool) NOT AVAILABLE.")
|
| 693 |
if not WHISPER_AVAILABLE: logger.warning("Whisper (Audio tool) NOT AVAILABLE.")
|