guv3-add-tool

SKILL.md

Add Tool to Existing Agent in guv3

Overview

Tools in guv3 are decorated Python functions that agents invoke via LLM tool-calling. Adding a tool requires changes in 2-3 files depending on scope.

File Locations

Scope Location When to use
Agent-specific tool gu/agents/{agent_name}/nodes/tools.py Tool only used by one agent
Global/shared tool gu/tools/global_tools.py Tool reused across multiple agents
Arg schemas gu/tools/schemas/schemas.py When tool has structured args
Tool helpers gu/tools/tool_helpers.py Pure helper functions (no @tool decorator)

Step 1: Define the Arg Schema (if needed)

File: gu/tools/schemas/schemas.py

from typing import List, Optional
from pydantic import BaseModel, Field
from enum import Enum

# For tools with no args
class DefaultEmptyArgs(BaseModel):
    "Default empty args"

# For tools with structured args
class YourToolArgs(BaseModel):
    param1: Optional[str] = Field(
        default=None,
        description="Description the LLM sees to understand this param"
    )
    param2: Optional[int] = Field(
        default=0,
        description="Another parameter"
    )
    param3: Optional[List[str]] = Field(
        default=None,
        description="List parameter"
    )

# For restricted values, use Enum
class OperationType(str, Enum):
    VENTA = "Venta"
    RENTA = "Renta"

Rules:

  • Every Field MUST have a description — this is what the LLM reads to understand the parameter
  • Use Optional[type] = Field(default=...) for all parameters
  • Descriptions can be multi-line with explicit allowed values

Step 2: Create the Tool Function

File: gu/agents/{agent_name}/nodes/tools.py

from langchain_core.tools import tool
from langchain_core.runnables import RunnableConfig
from loguru import logger
from gu.helpers.helpers import get_user, get_bot, update_user
from gu.db.connections.mongo import find_one, find, update_one, insert_one
from gu.core.notification_functions import (
    send_whatsapp_message,
    send_notification_to_owner,
    save_chat,
)
from gu.tools.schemas.schemas import YourToolArgs


@tool(args_schema=YourToolArgs)
def your_tool_name(
    param1: str = None,
    param2: int = 0,
    *,
    config: RunnableConfig,
):
    """Clear description of what this tool does.

    Use this when [specific scenario].

    Args:
        param1: Description
        param2: Description
    """
    try:
        # 1. Get user and bot context
        user = get_user(config)
        if not user:
            logger.error("[your_tool_name] No user found")
            return ""
        bot = get_bot(config)

        lead_id = user.get("lead_id")
        phone_number = user.get("phone_number")
        owner_firebase_id = user.get("owner_firebase_id")

        # 2. Your business logic here

        # 3. Database operations (if needed)
        result = find_one("collection_name", {"field": value})
        update_one(
            "collection_name",
            {"field": value},
            {"$set": {"updated_field": new_value}}
        )

        # 4. Send notifications (if needed)
        save_chat(lead_id, "Message text", "Gu")

        # 5. Return user-facing message
        return "Response the LLM will use to reply to the user"

    except Exception as e:
        logger.error(f"Error in your_tool_name: {e}")
        return "User-friendly error message"

Critical Rules

Config parameter

# CORRECT — keyword-only after *
def my_tool(param1: str, *, config: RunnableConfig):

# WRONG — positional parameter
def my_tool(param1: str, config: RunnableConfig):

Return values

# CORRECT — always return strings
return "Tu cita ha sido actualizada"
return ""  # empty string on error (LLM handles gracefully)

# WRONG — raising exceptions
raise ValueError("Bad input")  # crashes the tool node

Accessing user data

user = get_user(config)
# Common fields:
user.get("lead_id")              # User identifier
user.get("phone_number")         # WhatsApp number
user.get("owner_firebase_id")    # Owner ID
user.get("last_property_info")   # Current property dict
user.get("country_iso")          # "MEX", "PER", "DOM", etc.
user.get("is_agent")             # True/False
user.get("name")                 # User name
user.get("email")                # User email

bot = get_bot(config)
bot.get("bot_number")            # Bot WhatsApp number
bot.get("bot_phone_number")      # Bot phone number

Database operations

from gu.db.connections.mongo import find_one, find, update_one, insert_one
from gu.config.config import MONGO_DB_NAME_V1

# Query single document
doc = find_one("appointments", {"appointment_id": appt_id})

# Query multiple documents
docs = find("appointments", {"lead_id": lead_id, "status": {"$ne": "cancelled"}})

# Update document
update_one("appointments", {"appointment_id": appt_id}, {"$set": {"status": "confirmed"}})

# Query different database
doc = find_one("property_data", {"firebase_id": prop_id}, MONGO_DB_NAME_V1)

WhatsApp notifications

from gu.core.notification_functions import (
    send_whatsapp_message,          # Send text message
    send_whatsapp_message_audio,    # Send audio message
    send_contact_of_owner,          # Send owner contact card
    send_notification_to_owner,     # Notify the property owner
    save_chat,                      # Save to chat history
)

# Send text
send_whatsapp_message(bot, user, "Your message")

# Send audio
from gu.helpers.helpers import text_to_audio
audio = text_to_audio("Message text", config)
send_whatsapp_message_audio(bot, user, audio)

# Notify owner
send_notification_to_owner({
    "user": user,
    "question": question,
    "template": TEMPLATE_NAME,
    "bot_phone_number": bot.get("bot_number"),
    "property_id": property_id,
})

# Save to chat history
save_chat(lead_id, "Message", "Gu")  # "Gu" or "User"

Step 3: Add to the Tools List

At the bottom of the same tools.py file:

tools = [
    existing_tool_1,
    existing_tool_2,
    your_tool_name,  # ADD HERE
]

That's it for agent-specific tools. No other files need changes.

Step 4: Global Tools (if sharing across agents)

If the tool needs to be used by multiple agents, define it in gu/tools/global_tools.py instead, then import it in each agent's tools.py:

# In gu/agents/{agent_name}/nodes/tools.py
from gu.tools.global_tools import your_shared_tool

tools = [
    your_shared_tool,
    # ... other tools
]

Tool Helpers (non-decorated functions)

For reusable logic that isn't a tool itself, add to gu/tools/tool_helpers.py:

# In tool_helpers.py — no @tool decorator
def calculate_something(user, property_info):
    """Helper function used by multiple tools."""
    # Logic here
    return result

# In tools.py — import and use inside a tool
from gu.tools.tool_helpers import calculate_something

@tool(args_schema=SomeArgs)
def my_tool(*, config: RunnableConfig):
    user = get_user(config)
    result = calculate_something(user, user.get("last_property_info"))
    return str(result)

Checklist

  • Schema defined in gu/tools/schemas/schemas.py (if needed)
  • Tool function created with @tool(args_schema=...) decorator
  • config: RunnableConfig is keyword-only (after *)
  • Tool returns strings only
  • User validation with get_user(config)
  • Error handling with try/except returning user-friendly message
  • Tool added to tools = [...] list
  • Tool docstring describes when the LLM should use it
Weekly Installs
3
First Seen
13 days ago
Installed on
opencode3
gemini-cli3
claude-code3
github-copilot3
codex3
kimi-cli3