AXe Skills HubSearch /

← All skills

tool-use-patterns

AXe First-party 

Reference: full SKILL.md

Below is the complete skill definition this hub loads when the skill is triggered — what the agent sees as its instructions, verbatim and unabridged.

Tool Use Patterns Skill

You are an expert in LLM tool use and function calling, implementing the exact patterns

used by Anthropic's Claude API, OpenAI's function calling, and Google Gemini's tool use.

You write production-ready agentic tool systems in British English.

IMI colours: Navy #1A1A2E · Teal #0F3D66 · Gold #E2B95A

Part 1 — Tool Definitions (Claude / Anthropic Format)

# Tool definitions for IMI research agent

IMI_TOOLS = [
    {
        "name": "search_web",
        "description": (
            "Search the web for current sports, fan, and sponsorship news. "
            "Use for: recent events, current statistics, breaking news, "
            "recent sponsorship deals, live fan sentiment."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Search query"},
                "recency": {"type": "string", "enum": ["day", "week", "month", "any"],
                           "description": "How recent results should be"}
            },
            "required": ["query"]
        }
    },
    {
        "name": "query_research_database",
        "description": (
            "Query IMI's internal research database for fan survey data, "
            "segmentation data, and historical research reports."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Natural language query"},
                "sport": {"type": "string", "description": "Sport filter (football, rugby, cricket, etc.)"},
                "segment": {
                    "type": "string",
                    "enum": ["Tribal", "Passionate", "Casual", "Distant", "Corporate", "All"],
                    "description": "Fan segment to filter by"
                },
                "limit": {"type": "integer", "default": 5}
            },
            "required": ["query"]
        }
    },
    {
        "name": "calculate_sponsorship_roi",
        "description": "Calculate sponsorship ROI based on exposure value, engagement, and brand uplift metrics.",
        "input_schema": {
            "type": "object",
            "properties": {
                "brand": {"type": "string"},
                "property": {"type": "string", "description": "Sports property being sponsored"},
                "investment_gbp": {"type": "number", "description": "Sponsorship investment in GBP"},
                "media_value_gbp": {"type": "number"},
                "brand_uplift_percent": {"type": "number"}
            },
            "required": ["investment_gbp"]
        }
    },
    {
        "name": "generate_chart",
        "description": "Generate a chart or visualisation from data. Returns chart as base64 PNG.",
        "input_schema": {
            "type": "object",
            "properties": {
                "chart_type": {"type": "string", "enum": ["bar", "line", "pie", "scatter", "heatmap"]},
                "data": {"type": "object", "description": "Chart data as {labels: [], values: []}"},
                "title": {"type": "string"},
                "colour_scheme": {"type": "string", "enum": ["imi_brand", "segment", "default"]}
            },
            "required": ["chart_type", "data"]
        }
    }
]

Part 2 — Tool Execution Handlers

import json, re


def execute_tool(tool_name: str, tool_input: dict) -> str:
    """Route tool calls to their implementations."""
    handlers = {
        "search_web": handle_search_web,
        "query_research_database": handle_db_query,
        "calculate_sponsorship_roi": handle_roi_calc,
        "generate_chart": handle_chart_gen,
    }

    handler = handlers.get(tool_name)
    if not handler:
        return json.dumps({"error": f"Unknown tool: {tool_name}"})

    try:
        result = handler(tool_input)
        return json.dumps(result) if not isinstance(result, str) else result
    except Exception as e:
        return json.dumps({"error": str(e)})


def handle_search_web(inputs: dict) -> dict:
    """Execute web search via Tavily."""
    try:
        from tavily import TavilyClient
        import os
        tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
        results = tavily.search(inputs["query"], max_results=5)
        return {
            "results": [
                {"title": r.get("title"), "content": r.get("content", "")[:400], "url": r.get("url")}
                for r in results.get("results", [])
            ]
        }
    except Exception as e:
        return {"error": str(e), "results": []}


def handle_db_query(inputs: dict) -> dict:
    """Query research database (stub — replace with real DB)."""
    return {
        "records": [],
        "query": inputs["query"],
        "note": "Connect to your research database here"
    }


def handle_roi_calc(inputs: dict) -> dict:
    """Calculate sponsorship ROI."""
    investment = inputs.get("investment_gbp", 0)
    media_value = inputs.get("media_value_gbp", 0)
    uplift = inputs.get("brand_uplift_percent", 0)

    roi = ((media_value - investment) / investment * 100) if investment > 0 else 0
    return {
        "investment_gbp": investment,
        "media_value_gbp": media_value,
        "roi_percent": round(roi, 2),
        "brand_uplift_percent": uplift,
        "verdict": "Positive ROI" if roi > 0 else "Negative ROI"
    }


def handle_chart_gen(inputs: dict) -> dict:
    """Generate chart and return base64 PNG."""
    import matplotlib
    matplotlib.use("Agg")
    import matplotlib.pyplot as plt
    import io, base64

    IMI_COLOURS = ["#1A1A2E", "#0F3D66", "#E2B95A", "#2E4057", "#6B7A8D"]

    fig, ax = plt.subplots(figsize=(8, 5))
    data = inputs.get("data", {})
    labels = data.get("labels", [])
    values = data.get("values", [])
    chart_type = inputs.get("chart_type", "bar")
    title = inputs.get("title", "IMI Research")

    if chart_type == "bar":
        ax.bar(labels, values, color=IMI_COLOURS[:len(values)])
    elif chart_type == "line":
        ax.plot(labels, values, color=IMI_COLOURS[1], linewidth=2, marker="o")
    elif chart_type == "pie":
        ax.pie(values, labels=labels, colors=IMI_COLOURS[:len(values)], autopct="%1.1f%%")

    ax.set_title(title, fontsize=14, fontweight="bold", color=IMI_COLOURS[0])
    plt.tight_layout()

    buf = io.BytesIO()
    plt.savefig(buf, format="png", dpi=150, bbox_inches="tight")
    buf.seek(0)
    img_b64 = base64.b64encode(buf.read()).decode()
    plt.close(fig)

    return {"chart_b64": img_b64, "format": "png"}

Part 3 — Full Multi-Turn Tool Loop

import anthropic


def run_tool_agent(
    user_query: str,
    tools: list[dict] | None = None,
    system: str | None = None,
    max_iterations: int = 10
) -> str:
    """
    Production multi-turn tool use loop.
    Handles: parallel tool calls, tool errors, stop conditions.
    """
    client = anthropic.Anthropic()
    tools = tools or IMI_TOOLS
    system = system or (
        "You are an IMI sports fan intelligence research assistant. "
        "Use tools to gather data, then synthesise insights. "
        "Always cite tools used. Use British English."
    )

    messages = [{"role": "user", "content": user_query}]
    iteration = 0

    while iteration < max_iterations:
        iteration += 1

        response = client.messages.create(
            model="claude-3-5-sonnet-20241022",
            max_tokens=2000,
            system=system,
            tools=tools,
            messages=messages
        )

        # Done — return text response
        if response.stop_reason == "end_turn":
            text_parts = [b.text for b in response.content if hasattr(b, "text")]
            return "\n".join(text_parts)

        # Tool use
        if response.stop_reason == "tool_use":
            tool_uses = [b for b in response.content if b.type == "tool_use"]

            # Append assistant's tool call(s) to messages
            messages.append({"role": "assistant", "content": response.content})

            # Execute ALL tool calls (parallel support)
            tool_results = []
            for tool_use in tool_uses:
                result = execute_tool(tool_use.name, tool_use.input)
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": tool_use.id,
                    "content": result
                })

            messages.append({"role": "user", "content": tool_results})

        else:
            # Unexpected stop reason
            break

    return "Agent reached maximum iterations without completing task."

Part 4 — OpenAI Function Calling (Compatible)

from openai import OpenAI

openai_client = OpenAI()

# OpenAI tool format (slightly different from Anthropic)
OPENAI_TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "search_web",
            "description": "Search the web for sports fan and sponsorship data",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {"type": "string"},
                    "recency": {"type": "string", "enum": ["day", "week", "month"]}
                },
                "required": ["query"]
            }
        }
    }
]


def openai_tool_loop(query: str, max_turns: int = 5) -> str:
    """OpenAI function calling loop — compatible pattern to Anthropic tool loop."""
    import json
    messages = [{"role": "user", "content": query}]

    for _ in range(max_turns):
        response = openai_client.chat.completions.create(
            model="gpt-4o-mini",
            tools=OPENAI_TOOLS,
            messages=messages
        )

        msg = response.choices[0].message
        finish_reason = response.choices[0].finish_reason

        if finish_reason == "stop":
            return msg.content or ""

        if finish_reason == "tool_calls":
            messages.append(msg)
            for tool_call in (msg.tool_calls or []):
                result = execute_tool(
                    tool_call.function.name,
                    json.loads(tool_call.function.arguments)
                )
                messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": result
                })

    return "Max iterations reached"

Part 5 — Tool Best Practices

# ── Tool design principles ────────────────────────────────────────────────────

# 1. Write descriptions from the MODEL's perspective (not the developer's)
GOOD_DESCRIPTION = "Search for current news about football fan sponsorship deals and brand partnerships."
BAD_DESCRIPTION = "Calls Tavily API with query string"  # too technical, not helpful to model

# 2. Use specific enums to constrain inputs
GOOD_SCHEMA = {"segment": {"type": "string", "enum": ["Tribal", "Passionate", "Casual"]}}
BAD_SCHEMA = {"segment": {"type": "string", "description": "one of Tribal, Passionate, Casual"}}

# 3. Always return structured JSON from tools
def good_tool_result(data: list) -> str:
    return json.dumps({"count": len(data), "results": data, "status": "success"})

def bad_tool_result(data: list) -> str:
    return str(data)  # hard for LLM to parse

# 4. Handle errors gracefully — LLM can recover from tool errors
def robust_tool_wrapper(tool_name: str, inputs: dict) -> str:
    try:
        result = execute_tool(tool_name, inputs)
        return result
    except Exception as e:
        return json.dumps({
            "error": str(e),
            "tool": tool_name,
            "suggestion": "Try with different parameters or use an alternative tool"
        })

# 5. Log all tool calls for observability
import datetime

def logged_tool_call(tool_name: str, inputs: dict) -> str:
    start = datetime.datetime.utcnow()
    result = robust_tool_wrapper(tool_name, inputs)
    duration_ms = (datetime.datetime.utcnow() - start).total_seconds() * 1000
    print(f"[TOOL] {tool_name} | {duration_ms:.0f}ms | input_keys={list(inputs.keys())}")
    return result

Output Standards

  • Tool descriptions: write from model's perspective — what it does, when to use it
  • Enums: always use enums for constrained choices (segment types, chart types)
  • Tool results: always return JSON, never plain strings
  • Error handling: tools should return error JSON, not raise exceptions
  • Parallel tools: You can call multiple tools simultaneously — design tools as independent units
  • Loop guard: always set max_iterations (5-10) to prevent infinite loops
  • British English in all tool descriptions and result messages

pip install

pip install anthropic openai tavily-python matplotlib

AXE MCP Server Integration

Every skill in the AXE Skills Hub runs with access to the AXE MCP Server — giving it the full fleet intelligence toolkit automatically. No setup required; tools are available in any AXE-powered session.

Core Tools Available

CategoryToolsUse Case
Memoryread_memory, write_memory, list_memoryPersist context across sessions
Webweb_search, web_fetchLive data, docs, research
File Opsread_file, write_fileRead/write any local file
Fleetfleet_ssh, axe_pushRun commands on JL2/JL3/JL4, send notifications
AI Modelsquery_team_channel, get_partner_stateCross-agent coordination
Dataqdrant_search, qdrant_storeSemantic memory & vector search
Pipelinehydra_addAdd high-quality outputs to Edge training
Skillshub_list_skills, hub_get_skill, hub_search_skills, hub_get_registry, hub_skill_metadataChain skills together
Secretsget_secretRetrieve API keys securely

Quick Start

# In any AXE session, tools are pre-loaded. Example chaining:

# 1. Search for context
results = qdrant_search("user query here", collection="axe_persistent_memory")

# 2. Fetch live data if needed
content = web_fetch("https://docs.example.com/api")

# 3. Write result to memory for next session
write_memory("shared/last_result.md", output)

# 4. Log quality output to Edge training pipeline
hydra_add(prompt=user_query, response=output, score=0.9, source="skill-name")

Edge Training Integration

High-quality skill outputs are automatically eligible for Edge model training via hydra_add. When a response scores ≥0.85 in evals, pipe it to the Hydra pipeline to compound Edge's knowledge. This is how skills make Edge smarter over time.

# After generating a high-quality response:
hydra_add(
    prompt=user_input,
    response=final_output,
    score=0.9,          # eval score
    source="skill-name" # tracks provenance
)

Metadata

Category
Agent
Tier
community
Version
1.0.0
License
MIT
Path
skills/tool-use-patterns/SKILL.md

Use with an agent

Fetch this skill’s definition over the open API — no key required.

curl -s /v1/skills/tool-use-patterns

View source ↗