> ## Documentation Index
> Fetch the complete documentation index at: https://genai.codewithsiva.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Tool Agent

> Build an agent using custom tools that loops until a specific action is completed

In this section, we will see how to build an agent that uses custom tools to edit and save a document.

## Objectives

1. Create custom tools using the `@tool` decorator.
2. Maintain global state to update content across loop executions.
3. Build a loop of execution (agent -> tools -> agent) that terminates only when a specific tool is called.

## Agent V: Custom Tool Agent

#### Goal

Build a document drafting agent (Drafter) that loops using custom tools (`update` and `save`) to modify and save content to a local file.

#### Sample Input

Sequential instructions:

1. `"Write a basic email draft saying I am sick."`
2. `"Save the document to email.txt."`

#### Sample Output

1. `"Document updated successfully. Current content is: Hi, I am unable to attend today due to sickness."`
2. `"Document has been saved successfully to 'email.txt'."`

#### Plan

1. Define the `AgentState` schema using `add_messages` to append messages to history.
2. Create custom tools `@tool` for `update` (updates content in global variable) and `save` (saves content to a file).
3. Write the `our_agent` node function and `should_continue` conditional router function.
4. Compile and run the agent.

### Code Implementation

#### 1. Define the State Schema

We define a state schema using `add_messages` annotation:

```python theme={null}
from typing import Annotated, Sequence, TypedDict
from langchain_core.messages import BaseMessage
from langgraph.graph.message import add_messages

class AgentState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], add_messages]
```

#### 2. Create Custom Tools

We define two custom tools that operate on a global document content variable:

```python theme={null}
from langchain_core.tools import tool

document_content = ""

@tool
def update(content: str) -> str:
    \"\"\"Updates the document with the provided content.\"\"\"
    global document_content
    document_content = content
    return f"Document updated successfully. Current content is:\\n{document_content}"

@tool
def save(filename: str) -> str:
    \"\"\"Save the current document to a text file and finish.\n    \n    Args:\n        filename: Name for the text file.\n    \"\"\"
    global document_content
    if not filename.endswith('.txt'):
        filename = f"{filename}.txt"
    try:
        with open(filename, 'w') as file:
            file.write(document_content)
        return f"Document has been saved successfully to '{filename}'."
    except Exception as e:
        return f"Error saving document: {str(e)}"
```

#### 3. Define Nodes and Routing Logic

We write the agent node (using a system prompt with current document content) and the router function that terminates execution when the save tool returns successfully:

```python theme={null}
from langchain_core.messages import SystemMessage, ToolMessage
from langchain_openai import ChatOpenAI

tools = [update, save]
model = ChatOpenAI(model="gpt-4o").bind_tools(tools)

def our_agent(state: AgentState) -> dict:
    sys = SystemMessage(content=f\"\"\"
    You are Drafter, a helpful writing assistant. Use 'update' to modify content, and 'save' to finish.
    The current document content is: {document_content}
    \"\"\")
    response = model.invoke([sys] + list(state["messages"]))
    return {"messages": [response]}

def should_continue(state: AgentState) -> str:
    messages = state["messages"]
    for msg in reversed(messages):
        if isinstance(msg, ToolMessage) and "saved successfully" in msg.content:
            return "end"
    return "continue"
```

#### 4. Build and Compile the Graph

We hook up the loops:

```python theme={null}
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode

graph = StateGraph(AgentState)
graph.add_node("agent", our_agent)
graph.add_node("tools", ToolNode(tools))

graph.set_entry_point("agent")
graph.add_edge("agent", "tools")
graph.add_conditional_edges(
    "tools",
    should_continue,
    {
        "continue": "agent",
        "end": END
    }
)
app = graph.compile()
```

## Exercise: Todo List Manager Agent 🗃

#### Goal

Build a Todo List Manager Agent with custom tools to add items, mark them as complete, and save the todo list to a text file.

#### Sample Input

Sequential inputs:

1. `"Add buy milk to my todo list."`
2. `"Save my todo list to todos.txt."`

#### Sample Output

1. `"Todo list updated successfully. Current todos: ['buy milk']"`
2. `"Todo list has been saved successfully to 'todos.txt'."`

#### Plan

1. Create an `AgentState` schema for messages and a global list `todo_list = []` to store items.
2. Create `@tool` for `add_todo(todo: str)` (appends to list) and `save_todos(filename: str)` (writes list to file).
3. Initialize `StateGraph`, register agent and tool nodes, add loops, compile, and run.

<Accordion title="Solution">
  ```python theme={null}
  from typing import Annotated, Sequence, TypedDict
  from langchain_core.messages import BaseMessage, SystemMessage, ToolMessage
  from langgraph.graph.message import add_messages
  from langchain_openai import ChatOpenAI
  from langchain_core.tools import tool
  from langgraph.graph import StateGraph, START, END
  from langgraph.prebuilt import ToolNode

  todo_list = []

  # 1. State
  class AgentState(TypedDict):
      messages: Annotated[Sequence[BaseMessage], add_messages]

  # 2. Tools
  @tool
  def add_todo(todo: str) -> str:
      \"\"\"Adds a new todo item to the list.\"\"\"
      global todo_list
      todo_list.append(todo)
      return f"Todo list updated successfully. Current todos: {todo_list}"

  @tool
  def save_todos(filename: str) -> str:
      \"\"\"Saves the todo list to a file and finishes.\"\"\"
      global todo_list
      if not filename.endswith('.txt'):
          filename = f"{filename}.txt"
      try:
          with open(filename, 'w') as f:
              f.write('\\n'.join(todo_list))
          return f"Todo list has been saved successfully to '{filename}'."
      except Exception as e:
          return f"Error saving todos: {str(e)}"

  tools = [add_todo, save_todos]
  model = ChatOpenAI(model="gpt-4o").bind_tools(tools)

  # 3. Nodes and Routing
  def todo_agent(state: AgentState) -> dict:
      sys = SystemMessage(content=f\"\"\"
      You are a Todo List Manager. Use 'add_todo' to add items, and 'save_todos' to save and finish.
      Current todos: {todo_list}
      \"\"\")
      response = model.invoke([sys] + list(state["messages"]))
      return {"messages": [response]}

  def check_continue(state: AgentState) -> str:
      messages = state["messages"]
      for msg in reversed(messages):
          if isinstance(msg, ToolMessage) and "saved successfully" in msg.content:
              return "end"
      return "continue"

  # 4. Build Graph
  graph = StateGraph(AgentState)
  graph.add_node("agent", todo_agent)
  graph.add_node("tools", ToolNode(tools))
  graph.set_entry_point("agent")
  graph.add_edge("agent", "tools")
  graph.add_conditional_edges(
      "tools",
      check_continue,
      {
          "continue": "agent",
          "end": END
      }
  )
  app = graph.compile()

  # Test
  state = {"messages": [HumanMessage(content="Add buy milk to my todo list.")]}
  state = app.invoke(state)
  state["messages"].append(HumanMessage(content="Save my todo list to todos.txt."))
  res = app.invoke(state)
  print(res["messages"][-1].content)
  ```
</Accordion>

## Practice & Exercises

To reinforce what you've learned in this section, practice with the interactive notebook:

<CardGroup cols={1}>
  <Card title="Practice & Exercises" icon="laptop-code">
    Practice managing global variable states inside custom tools, executing loops until a termination response is triggered, and compiling custom tool workflows.

    [💻 VS Code](vscode://file/Users/sivaprasad/Downloads/GenAI%20With%20Python/public/notebooks/langgraph/Custom_Tool_Agent.ipynb) | [🚀 Colab](https://colab.research.google.com/github/prasad230776/genai-course/blob/master/public/notebooks/langgraph/Custom_Tool_Agent.ipynb) | <a href="/public/notebooks/langgraph/Custom_Tool_Agent.ipynb" download>📥 Download</a>
  </Card>
</CardGroup>
