Created
August 19, 2025 12:18
-
-
Save KrishnanSriram/a9d55b50201df1ae6e74cfe3d2663629 to your computer and use it in GitHub Desktop.
A simple implementation of langgraph with tools and agents - Aligned more with agentic approach
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| LangGraph Web Content Agent using ToolNode and Agent approach | |
| Proper implementation with agent decision-making and tool execution | |
| """ | |
| import requests | |
| from datetime import datetime | |
| from typing import TypedDict, List, Annotated | |
| from langgraph.graph import StateGraph, END | |
| from langgraph.prebuilt import ToolNode | |
| from langgraph.graph.message import add_messages | |
| from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, ToolMessage | |
| from langchain_core.tools import tool | |
| from langchain_openai import ChatOpenAI | |
| from bs4 import BeautifulSoup | |
| import os | |
| import uuid | |
| os.environ["OPENAI_API_KEY"] = "XXXXXsdkjbfaksjdgfkDJ-fkjsadbfasdjbfk" | |
| # Define the agent state with messages | |
| class State(TypedDict): | |
| messages: Annotated[List[BaseMessage], add_messages] | |
| # Define tools using @tool decorator | |
| @tool | |
| def fetch_web_content(url: str) -> str: | |
| """Fetch content from a web URL and return cleaned text""" | |
| try: | |
| response = requests.get(url, timeout=10, verify=False) | |
| response.raise_for_status() | |
| # Use BeautifulSoup to parse the HTML and extract text | |
| soup = BeautifulSoup(response.text, 'html.parser') | |
| text = soup.get_text(separator=' ', strip=True) | |
| print("Content fetched and parsed successfully.") | |
| return text | |
| except requests.exceptions.RequestException as e: | |
| return f"Error fetching content: {e}" | |
| except Exception as e: | |
| return f"Error parsing HTML: {e}" | |
| @tool | |
| def summarize_text(content: str) -> str: | |
| """Summarize the given content in simple English using Ollama""" | |
| try: | |
| llm = ChatOpenAI(model="gpt-4", temperature=0.3) | |
| # llm = ChatOllama(model="llama3.2", temperature=0.3) | |
| prompt = f"""Please summarize the following content in simple English. | |
| Use short sentences and common words. Keep it under 200 words. | |
| Content: | |
| {content} | |
| Summary:""" | |
| summary = llm.invoke(prompt) | |
| return summary.content | |
| except Exception as e: | |
| return f"Error summarizing content: {str(e)}" | |
| @tool | |
| def save_content_to_file(content: str, filename: str = None) -> str: | |
| """Save content to a local file with timestamp""" | |
| try: | |
| if not filename: | |
| # timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| filename = f"summary_{str(uuid.uuid4())}.txt" | |
| with open(filename, 'w', encoding='utf-8') as f: | |
| f.write(f"Content Summary - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") | |
| f.write("=" * 50 + "\n\n") | |
| f.write(content) | |
| return f"Content successfully saved to {filename}" | |
| except Exception as e: | |
| return f"Error saving file: {str(e)}" | |
| # Create the tools list | |
| tools = [fetch_web_content, summarize_text, save_content_to_file] | |
| # Create tool node | |
| tool_node = ToolNode(tools) | |
| def create_agent(): | |
| """Create the agent with tool calling capabilities""" | |
| llm = ChatOpenAI(model="gpt-4", temperature=0.5) | |
| # llm = ChatOllama(model="mistral:7b", temperature=0.1) | |
| # Bind tools to the LLM | |
| llm_with_tools = llm.bind_tools(tools) | |
| return llm_with_tools | |
| def agent_node(state: State): | |
| """Agent node that decides what tools to call""" | |
| # Get the last message to understand current context | |
| messages = state["messages"] | |
| last_message = messages[-1] | |
| # Create the agent | |
| agent = create_agent() | |
| # Create system message for the agent | |
| system_prompt = """You are a helpful agent that can fetch web content, summarize it, and save it to files. | |
| Your workflow should be: | |
| 1. First, use fetch_web_content to get content from the provided URL | |
| 2. Then, use summarize_text to create a simple English summary | |
| 3. Finally, use save_content_to_file to save the summary | |
| Always follow this sequence and use the tools in order. Be methodical and complete each step before moving to the next. | |
| Every step is mandatory and should not be skipped""" | |
| # If this is the first message, add system context | |
| if len(messages) == 1 and isinstance(last_message, HumanMessage): | |
| enhanced_messages = [ | |
| HumanMessage(content=system_prompt), | |
| last_message | |
| ] | |
| else: | |
| enhanced_messages = messages | |
| # Get response from agent | |
| response = agent.invoke(enhanced_messages) | |
| return {"messages": [response]} | |
| def should_continue(state: State) -> str: | |
| """Decide whether to continue with tools or end""" | |
| messages = state["messages"] | |
| last_message = messages[-1] | |
| # If the last message has tool calls, go to tools | |
| if hasattr(last_message, 'tool_calls') and last_message.tool_calls: | |
| return "tools" | |
| # Otherwise, we're done | |
| return "end" | |
| def create_workflow(): | |
| """Create the LangGraph workflow with agent and tools""" | |
| # Create the state graph | |
| workflow = StateGraph(State) | |
| # Add nodes | |
| workflow.add_node("agent", agent_node) | |
| workflow.add_node("tools", tool_node) | |
| # Set entry point | |
| workflow.set_entry_point("agent") | |
| # Add conditional edges | |
| workflow.add_conditional_edges( | |
| "agent", | |
| should_continue, | |
| { | |
| "tools": "tools", | |
| "end": END | |
| } | |
| ) | |
| # After tools, always go back to agent | |
| workflow.add_edge("tools", "agent") | |
| # Compile the workflow | |
| return workflow.compile() | |
| def run_agent_workflow(url: str): | |
| """Run the agent workflow for the given URL""" | |
| print("=" * 60) | |
| print("LangGraph Agent with ToolNode") | |
| print("=" * 60) | |
| print(f"Processing URL: {url}") | |
| print("-" * 60) | |
| # Create initial state with human message | |
| initial_message = HumanMessage( | |
| content=f"Please fetch, summarize, and save the content from this URL: {url}" | |
| ) | |
| initial_state = State(messages=[initial_message]) | |
| # Create and run workflow | |
| app = create_workflow() | |
| try: | |
| print("Running workflow...") | |
| # Simply invoke the workflow and get final result | |
| final_result = app.invoke(initial_state) | |
| print("Workflow completed!") | |
| print() | |
| print("=" * 60) | |
| print("RESULTS:") | |
| print("=" * 60) | |
| # Show results from the final state | |
| final_messages = final_result["messages"] | |
| # Count and show tool results | |
| for msg in final_messages: | |
| if isinstance(msg, ToolMessage): | |
| if msg.name == "fetch_web_content": | |
| print(f"✓ Content fetched: {len(msg.content)} characters") | |
| elif msg.name == "summarize_text": | |
| print(f"✓ Content summarized: {len(msg.content)} characters") | |
| print(f" Preview: {msg.content[:150]}...") | |
| elif msg.name == "save_content_to_file": | |
| print(f"✓ {msg.content}") | |
| except Exception as e: | |
| print(f"Error running workflow: {str(e)}") | |
| import traceback | |
| traceback.print_exc() | |
| if __name__ == "__main__": | |
| print("LangGraph Agent with ToolNode") | |
| print("Fetch, Summarize, and Save Web Content") | |
| print() | |
| # Get URL from user | |
| url = "https://www.usbank.com/credit-cards/bank-smartly-visa-signature-credit-card.html" | |
| print() | |
| # Run the agent workflow | |
| run_agent_workflow(url) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment