Download nodes/chat/agent_node.py from lablab-ai-amd-developer-hackathon/paperhawk: direct link, hf CLI and curl.
- Browser
- Download file 3.77 kB
-
https://huggingface.co/spaces/lablab-ai-amd-developer-hackathon/paperhawk/resolve/main/nodes/chat/agent_node.py
- Command line
-
hf download hf://spaces/lablab-ai-amd-developer-hackathon/paperhawk/nodes/chat/agent_node.py
-
curl -L -o agent_node.py https://huggingface.co/spaces/lablab-ai-amd-developer-hackathon/paperhawk/resolve/main/nodes/chat/agent_node.py
3.77 kB
| """agent_node — LLM bind_tools, the heart of the ReAct loop. | |
| The node calls the LLM with the full message history + the system prompt. | |
| If the LLM emits a tool_call, the downstream ``tools_condition`` routes to | |
| the ToolNode; otherwise it routes to the synthesizer. | |
| ``build_agent_node(llm_with_tools)`` is a factory returning a closure with | |
| the bound LLM. The graph receives the chat model at compile time. | |
| """ | |
| from __future__ import annotations | |
| from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage | |
| from config import settings | |
| from graph.states.chat_state import ChatState | |
| from nodes.chat._prompts import AGENTIC_SYSTEM_PROMPT | |
| # Friendly-error message prefixes — these are filtered out of the LLM history | |
| # so they don't pollute follow-up reasoning. (Mirrors the parity behavior of | |
| # the original system's ``_filter_history``.) | |
| _ERROR_MESSAGE_PREFIXES: tuple[str, ...] = ( | |
| "Missing", | |
| "Your API balance", | |
| "You exceeded", | |
| "The LLM service", | |
| "Network error", | |
| "Could not load PDF", | |
| "The file is too large", | |
| # Multilingual fallback (HU) | |
| "Hianyzo", | |
| "Az API szamladon", | |
| "Tullepted", | |
| "Az LLM szolgaltatas", | |
| "Halozati hiba", | |
| "Nem sikerult a PDF", | |
| "A fajl tul nagy", | |
| ) | |
| def _filter_history(messages: list[BaseMessage]) -> list[BaseMessage]: | |
| """Drop error-flavored AIMessages from the history. | |
| Friendly-error outputs (e.g. "Your API balance is insufficient") would | |
| confuse follow-up reasoning, so we exclude them when building the LLM input. | |
| """ | |
| cleaned: list[BaseMessage] = [] | |
| for m in messages: | |
| if isinstance(m, AIMessage): | |
| content = m.content | |
| if isinstance(content, str) and any( | |
| content.startswith(prefix) for prefix in _ERROR_MESSAGE_PREFIXES | |
| ): | |
| continue | |
| cleaned.append(m) | |
| return cleaned | |
| def build_agent_node(llm_with_tools, plan_to_prompt: bool = True): | |
| """Factory: capture llm_with_tools in a closure. | |
| Args: | |
| llm_with_tools: a ChatModel Runnable already bound with ``bind_tools(...)`` | |
| plan_to_prompt: if True, append ``state["plan"]`` to the system prompt | |
| """ | |
| async def agent_node(state: ChatState) -> dict: | |
| messages = state.get("messages") or [] | |
| plan = state.get("plan") or [] | |
| intent = state.get("intent", "chat") | |
| # Compose the system prompt | |
| system_text = AGENTIC_SYSTEM_PROMPT | |
| if plan_to_prompt and plan: | |
| system_text += ( | |
| f"\n\n=== CURRENT PLAN (intent: {intent}) ===" | |
| f"\nSuggested tool order (hint, not mandatory): {' → '.join(plan)}" | |
| ) | |
| # Iteration count | |
| iter_count = state.get("iteration_count", 0) | |
| if iter_count >= settings.chat_max_iterations: | |
| # Force-end: synthesize from the existing tool results | |
| return { | |
| "messages": [HumanMessage( | |
| content="Please synthesize an answer from the tool results already collected; do NOT call any more tools." | |
| )], | |
| "iteration_count": iter_count + 1, | |
| "trace": [f"agent: max iter ({iter_count}) → forced synthesis"], | |
| } | |
| # LLM call — error-flavored history is stripped out | |
| cleaned_messages = _filter_history(messages) | |
| full_messages = [SystemMessage(content=system_text)] + cleaned_messages | |
| response = await llm_with_tools.ainvoke(full_messages) | |
| return { | |
| "messages": [response], | |
| "iteration_count": iter_count + 1, | |
| "trace": [f"agent: iter={iter_count + 1}, tool calls={len(getattr(response, 'tool_calls', []) or [])}"], | |
| } | |
| return agent_node | |