Preporato
NCP-AAINVIDIAAgentic AITool CallingFunction Calling

Tool Integration and Function Calling Patterns for Agentic AI

Preporato TeamDecember 10, 202515 min readNCP-AAI

The defining capability of agentic AI systems is their ability to interact with external tools, APIs, and systems to accomplish tasks beyond pure language generation. Tool integration—also called function calling, tool use, or action execution—is what transforms large language models from chatbots into autonomous agents capable of real-world actions. This comprehensive guide covers the tool integration patterns essential for the NVIDIA NCP-AAI certification and production deployment.

What is Tool Integration in Agentic AI?

Tool integration enables LLM-powered agents to:

  • Call external APIs (REST, GraphQL, gRPC)
  • Query databases (SQL, NoSQL, vector stores)
  • Execute code (Python, JavaScript, shell commands)
  • Interact with web interfaces (browser automation)
  • Control physical systems (IoT, robotics)
  • Integrate with enterprise systems (CRM, ERP, ticketing)

Why Tool Integration is Critical for NCP-AAI:

  • Agent Development domain (15% of exam): Implementing tool calling mechanisms
  • Agent Architecture domain (15%): Designing tool orchestration patterns
  • Knowledge Integration domain: Connecting agents to data sources
  • Production Deployment (13%): Securing and scaling tool integrations

The Tool Calling Workflow

User Request
    ↓
Agent (LLM) decides: Language response OR tool call?
    ↓
[Tool Call Branch]
    ↓
1. Agent generates structured tool call
   {
     "tool": "get_weather",
     "arguments": {"location": "San Francisco", "unit": "fahrenheit"}
   }
    ↓
2. Framework executes tool (API call, code execution, etc.)
    ↓
3. Tool returns result
   {"temperature": 62, "condition": "partly cloudy"}
    ↓
4. Result fed back to Agent
    ↓
5. Agent generates natural language response
   "It's currently 62°F and partly cloudy in San Francisco."
    ↓
Response to User

Preparing for NCP-AAI? Practice with 455+ exam questions

Native Function Calling in LLMs

Modern LLMs have native function calling capabilities (OpenAI, Anthropic Claude, Google Gemini):

OpenAI Function Calling

import openai

# Define tools
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get current weather for a location",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "City name, e.g., 'San Francisco'"
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "Temperature unit"
                    }
                },
                "required": ["location"]
            }
        }
    }
]

# Agent decides to call function
response = openai.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "What's the weather in SF?"}],
    tools=tools,
    tool_choice="auto"  # Let model decide when to call
)

# Check if tool was called
if response.choices[0].message.tool_calls:
    tool_call = response.choices[0].message.tool_calls[0]
    function_name = tool_call.function.name
    function_args = json.loads(tool_call.function.arguments)

    # Execute the actual function
    if function_name == "get_weather":
        result = get_weather(**function_args)  # Your implementation

    # Send result back to model
    messages.append(response.choices[0].message)
    messages.append({
        "role": "tool",
        "tool_call_id": tool_call.id,
        "content": json.dumps(result)
    })

    # Get final response
    final_response = openai.chat.completions.create(
        model="gpt-4",
        messages=messages
    )

Key Parameters:

  • tool_choice="auto": Model decides when to call tools
  • tool_choice="required": Force tool call
  • tool_choice={"type": "function", "function": {"name": "get_weather"}}: Force specific tool

Anthropic Claude Tool Use

import anthropic

client = anthropic.Anthropic()

tools = [
    {
        "name": "get_weather",
        "description": "Get current weather for a location",
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {"type": "string", "description": "City name"},
                "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
            },
            "required": ["location"]
        }
    }
]

message = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "Weather in NYC?"}]
)

# Claude returns tool_use content block
if message.stop_reason == "tool_use":
    tool_use = next(block for block in message.content if block.type == "tool_use")

    # Execute tool
    result = get_weather(**tool_use.input)

    # Continue conversation with result
    response = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=1024,
        tools=tools,
        messages=[
            {"role": "user", "content": "Weather in NYC?"},
            {"role": "assistant", "content": message.content},
            {
                "role": "user",
                "content": [
                    {
                        "type": "tool_result",
                        "tool_use_id": tool_use.id,
                        "content": json.dumps(result)
                    }
                ]
            }
        ]
    )

Claude-Specific Features:

  • Multi-tool calls: Can invoke multiple tools in parallel
  • Tool result caching: Caches tool definitions (prompt caching)
  • JSON mode: Stricter structured output validation

Tool Integration Patterns

Pattern 1: Single-Step Tool Use

Use Case: Simple queries requiring one tool call

from langchain.agents import create_react_agent, AgentExecutor
from langchain.tools import Tool

def calculator(expression: str) -> str:
    """Evaluate mathematical expressions"""
    try:
        return str(eval(expression))  # Production: use safe eval
    except Exception as e:
        return f"Error: {e}"

tools = [
    Tool(
        name="Calculator",
        func=calculator,
        description="Useful for mathematical calculations. Input should be a valid Python expression."
    )
]

agent = create_react_agent(llm, tools, prompt_template)
executor = AgentExecutor(agent=agent, tools=tools)

# Single tool call
result = executor.invoke({"input": "What is 25 * 137?"})
# Agent calls Calculator("25 * 137") → Returns "3425"

Characteristics:

  • Latency: 2 LLM calls (planning + response generation)
  • Reliability: High (simple execution path)
  • Use Case: Calculations, fact lookups, single API calls

Pattern 2: Multi-Step Tool Orchestration (ReAct)

Use Case: Complex tasks requiring sequential tool calls

from langchain.agents import create_react_agent

# Multiple tools available
tools = [
    Tool(name="Search", func=search_web, description="Search the web"),
    Tool(name="Calculator", func=calculator, description="Do math"),
    Tool(name="Weather", func=get_weather, description="Get weather")
]

agent = create_react_agent(llm, tools, react_prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

# Multi-step execution
result = executor.invoke({
    "input": "What's the average temperature in the 5 largest US cities?"
})

# Agent execution trace:
# Thought: I need to find the 5 largest US cities
# Action: Search("5 largest US cities by population")
# Observation: New York, Los Angeles, Chicago, Houston, Phoenix
# Thought: Now I need weather for each city
# Action: Weather("New York")
# Observation: 45°F
# Action: Weather("Los Angeles")
# Observation: 68°F
# ... (continues for remaining cities)
# Thought: Now I need to calculate average
# Action: Calculator("(45 + 68 + 52 + 73 + 75) / 5")
# Observation: 62.6
# Thought: I now know the final answer
# Final Answer: The average temperature across the 5 largest US cities is 62.6°F

ReAct Format:

Thought: [Reasoning about what to do]
Action: [Tool name]
Action Input: [Tool arguments]
Observation: [Tool result]
... (repeat Thought/Action/Observation)
Thought: I now know the final answer
Final Answer: [Response to user]

Pattern 3: Parallel Tool Execution

Use Case: Independent tool calls that can run concurrently

import asyncio
from langchain.agents import AgentExecutor

async def parallel_tool_agent(query):
    # Agent identifies multiple independent tool calls
    response = await llm.agenerate(messages=[
        {"role": "user", "content": query}
    ])

    # Extract tool calls
    tool_calls = response.tool_calls  # [{name: "Weather", args: {"city": "NYC"}}, ...]

    # Execute tools in parallel
    tasks = [execute_tool(tc.name, tc.args) for tc in tool_calls]
    results = await asyncio.gather(*tasks)

    # Send results back to agent
    final_response = await llm.agenerate(
        messages=[
            {"role": "user", "content": query},
            {"role": "assistant", "tool_calls": tool_calls},
            {"role": "tool", "tool_results": results}
        ]
    )

    return final_response

# Example: "Compare weather in NYC, LA, and Chicago"
# Executes Weather("NYC"), Weather("LA"), Weather("Chicago") concurrently
result = asyncio.run(parallel_tool_agent("Compare weather in NYC, LA, and Chicago"))

Benefits:

  • Latency reduction: 3 parallel calls vs. 3 sequential (3x faster)
  • Scalability: Handles multiple independent operations efficiently

NCP-AAI Exam Note: Know when parallel execution is safe (independent tools) vs. unsafe (dependent operations)

Pattern 4: Conditional Tool Selection

Use Case: Route to different tools based on query type

from langchain.agents import initialize_agent, AgentType

# Categorized tools
web_tools = [search_tool, scrape_tool]
data_tools = [sql_query_tool, analytics_tool]
code_tools = [python_executor, sandbox_tool]

# Router agent
def route_to_specialist(query):
    # Classify query type
    category = classifier_llm.predict(query)

    if category == "web_search":
        agent = initialize_agent(web_tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION)
    elif category == "data_analysis":
        agent = initialize_agent(data_tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION)
    elif category == "code_execution":
        agent = initialize_agent(code_tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION)

    return agent.run(query)

# Routes to appropriate specialist agent with relevant tools
result = route_to_specialist("Analyze sales data for Q4 2024")  # → data_tools

Advantages:

  • Reduced confusion: Agent chooses from smaller, relevant tool set
  • Specialized behavior: Different prompts/strategies per category
  • Security: Limit tool access per query type

Pattern 5: Hierarchical Tool Composition

Use Case: Complex tools built from simpler primitives

from langchain.tools import StructuredTool

# Low-level tools
def read_file(filepath: str) -> str:
    with open(filepath) as f:
        return f.read()

def write_file(filepath: str, content: str) -> str:
    with open(filepath, 'w') as f:
        f.write(content)
    return "File written successfully"

# High-level composed tool
def refactor_code(filepath: str, instructions: str) -> str:
    """Refactor code file based on instructions"""
    # Uses multiple low-level tools
    code = read_file(filepath)  # Tool 1
    refactored = llm.predict(f"Refactor this code:\n{code}\n\nInstructions: {instructions}")  # Tool 2
    write_file(filepath, refactored)  # Tool 3
    return f"Refactored {filepath}"

# Agent uses high-level tool (internally uses 3 tools)
tools = [
    StructuredTool.from_function(refactor_code)
]

agent = initialize_agent(tools, llm)
result = agent.run("Refactor main.py to use async/await")

Benefits:

  • Abstraction: Hide complexity from agent
  • Reusability: Compose tools into higher-level operations
  • Reliability: Tested compositions reduce agent errors

Tool Definition Best Practices

1. Descriptive Naming and Documentation

Bad:

def func(x, y):
    return x + y

Tool(name="f", func=func, description="Does stuff")

Good:

def calculate_sum(number1: float, number2: float) -> float:
    """
    Calculate the sum of two numbers.

    Args:
        number1: First number to add
        number2: Second number to add

    Returns:
        The sum of number1 and number2

    Example:
        calculate_sum(5, 3) → 8
    """
    return number1 + number2

Tool(
    name="CalculateSum",
    func=calculate_sum,
    description="Adds two numbers together. Use when you need to perform addition. Input: two numbers separated by comma."
)

NCP-AAI Best Practice: Tool descriptions are embedded in LLM prompts—clear descriptions improve tool selection accuracy.

2. Type Hints and Validation

from pydantic import BaseModel, Field
from langchain.tools import StructuredTool

class WeatherInput(BaseModel):
    """Input schema for weather tool"""
    location: str = Field(description="City name, e.g., 'San Francisco'")
    unit: str = Field(
        description="Temperature unit",
        pattern="^(celsius|fahrenheit)$"
    )

def get_weather(location: str, unit: str = "fahrenheit") -> dict:
    """Get current weather"""
    # Implementation
    pass

weather_tool = StructuredTool.from_function(
    func=get_weather,
    name="GetWeather",
    description="Get current weather for a location",
    args_schema=WeatherInput  # Validated by Pydantic
)

Benefits:

  • Validation: Catches invalid arguments before execution
  • LLM guidance: Schema guides model to generate correct arguments
  • Documentation: Self-documenting tool interfaces

3. Error Handling in Tools

def robust_api_call(endpoint: str, params: dict) -> str:
    """Call external API with proper error handling"""
    try:
        response = requests.get(endpoint, params=params, timeout=10)
        response.raise_for_status()
        return json.dumps(response.json())

    except requests.exceptions.Timeout:
        return "Error: API request timed out after 10 seconds. Try again later."

    except requests.exceptions.HTTPError as e:
        if e.response.status_code == 404:
            return "Error: Resource not found. Check your parameters."
        elif e.response.status_code == 401:
            return "Error: Authentication failed. Check API credentials."
        else:
            return f"Error: API returned status {e.response.status_code}"

    except Exception as e:
        return f"Error: Unexpected error occurred: {str(e)}"

Why This Matters:

  • Agent can understand and respond to errors
  • Prevents cryptic Python tracebacks in agent output
  • Enables retry logic with corrected arguments

4. Tool Result Formatting

Bad: Return raw data structures

def get_user(user_id: int):
    return {"id": 123, "name": "John", "email": "john@example.com", "created_at": "2024-01-15T10:30:00Z", ...}
    # Agent receives overwhelming JSON blob

Good: Return formatted, relevant information

def get_user(user_id: int) -> str:
    user = db.query(User).get(user_id)
    if not user:
        return f"User {user_id} not found"

    return f"""User Information:
    - Name: {user.name}
    - Email: {user.email}
    - Account Created: {user.created_at.strftime('%B %d, %Y')}
    - Status: {user.status}
    """
    # Agent receives human-readable summary

NCP-AAI Principle: Tool results are added to LLM context—format for readability and token efficiency.

Advanced Tool Patterns

Dynamic Tool Loading

Load tools based on context or user permissions:

class DynamicToolAgent:
    def __init__(self, user_role: str):
        self.user_role = user_role
        self.tools = self._load_tools_for_role()

    def _load_tools_for_role(self):
        # Base tools for all users
        tools = [search_tool, calculator_tool]

        # Role-specific tools
        if self.user_role == "admin":
            tools.extend([delete_tool, modify_users_tool])
        elif self.user_role == "analyst":
            tools.extend([query_database_tool, export_report_tool])

        return tools

    def run(self, query):
        agent = initialize_agent(self.tools, llm)
        return agent.run(query)

# Different users see different tools
admin_agent = DynamicToolAgent("admin")
analyst_agent = DynamicToolAgent("analyst")

Tool Call Auditing

Track all tool executions for compliance and debugging:

import logging
from datetime import datetime

class AuditedTool:
    def __init__(self, tool, audit_log):
        self.tool = tool
        self.audit_log = audit_log

    def __call__(self, *args, **kwargs):
        # Log before execution
        self.audit_log.info(f"[{datetime.now()}] Calling {self.tool.name} with args={args}, kwargs={kwargs}")

        try:
            result = self.tool.func(*args, **kwargs)

            # Log success
            self.audit_log.info(f"[{datetime.now()}] {self.tool.name} succeeded: {result[:100]}")
            return result

        except Exception as e:
            # Log failure
            self.audit_log.error(f"[{datetime.now()}] {self.tool.name} failed: {str(e)}")
            raise

# Wrap all tools
audit_log = logging.getLogger("tool_audit")
audited_tools = [AuditedTool(tool, audit_log) for tool in tools]

Audit Log Output:

[2025-01-15 10:30:15] Calling QueryDatabase with args=('SELECT * FROM users WHERE id=?', 123)
[2025-01-15 10:30:16] QueryDatabase succeeded: User(id=123, name='John')
[2025-01-15 10:30:20] Calling DeleteUser with args=(123,)
[2025-01-15 10:30:21] DeleteUser succeeded: User 123 deleted

Tool Result Caching

Cache expensive or redundant tool calls:

from functools import lru_cache
import hashlib

class CachedTool:
    def __init__(self, tool, ttl_seconds=300):
        self.tool = tool
        self.cache = {}
        self.ttl = ttl_seconds

    def __call__(self, *args, **kwargs):
        # Create cache key
        cache_key = hashlib.md5(
            f"{self.tool.name}:{args}:{kwargs}".encode()
        ).hexdigest()

        # Check cache
        if cache_key in self.cache:
            cached_result, timestamp = self.cache[cache_key]
            if time.time() - timestamp < self.ttl:
                return cached_result

        # Execute tool
        result = self.tool.func(*args, **kwargs)

        # Store in cache
        self.cache[cache_key] = (result, time.time())

        return result

# Wrap expensive tools
weather_tool = CachedTool(weather_tool, ttl_seconds=600)  # Cache 10 min

Benefits:

  • Cost reduction: Avoid redundant API calls
  • Latency: Instant results for repeated queries
  • Rate limiting: Prevent API quota exhaustion

Master These Concepts with Practice

Our NCP-AAI practice bundle includes:

  • 7 full practice exams (455+ questions)
  • Detailed explanations for every answer
  • Domain-by-domain performance tracking

30-day money-back guarantee

Tool Integration with NVIDIA Platform

NVIDIA NIM (Inference Microservices)

from langchain_nvidia_ai_endpoints import ChatNVIDIA

# NVIDIA NIM with function calling
llm = ChatNVIDIA(
    model="meta/llama3-70b-instruct",
    nvidia_api_key="nvapi-...",
    temperature=0.2
)

# Define tools
tools = [weather_tool, calculator_tool, search_tool]

# Create agent with NIM backend
from langchain.agents import create_tool_calling_agent
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools)

# Execute
result = executor.invoke({"input": "What's 25% of the temperature in NYC?"})
# NIM processes: Weather("NYC") → Calculator("72 * 0.25")

NVIDIA Triton Integration

Deploy custom tool execution as Triton inference services:

import tritonclient.http as httpclient

# Tool as Triton model
class TritonTool:
    def __init__(self, model_name, triton_url="localhost:8000"):
        self.client = httpclient.InferenceServerClient(url=triton_url)
        self.model_name = model_name

    def __call__(self, input_data):
        # Prepare input tensor
        inputs = httpclient.InferInput("INPUT", input_data.shape, "FP32")
        inputs.set_data_from_numpy(input_data)

        # Execute on Triton
        result = self.client.infer(self.model_name, inputs=[inputs])

        # Extract output
        return result.as_numpy("OUTPUT")

# Use in agent
image_analysis_tool = TritonTool("image_classifier")
tools = [image_analysis_tool, ...]

Multi-Agent Tool Coordination

Shared Tool Pool

class SharedToolPool:
    def __init__(self):
        self.tools = {
            "search": search_tool,
            "weather": weather_tool,
            "database": db_tool
        }
        self.lock = threading.Lock()

    def execute(self, tool_name, *args, **kwargs):
        with self.lock:  # Prevent concurrent access issues
            tool = self.tools[tool_name]
            return tool(*args, **kwargs)

# Multiple agents share tool pool
tool_pool = SharedToolPool()

agent1 = Agent(tools=[tool_pool])
agent2 = Agent(tools=[tool_pool])

# Both agents can use same tools safely

Tool Delegation

class OrchestratorAgent:
    def __init__(self):
        # Specialist agents with different tools
        self.web_agent = Agent(tools=[search_tool, scrape_tool])
        self.data_agent = Agent(tools=[sql_tool, analytics_tool])
        self.code_agent = Agent(tools=[python_tool, sandbox_tool])

    def handle_query(self, query):
        # Orchestrator decides which specialist to delegate to
        if "search" in query.lower():
            return self.web_agent.run(query)
        elif "analyze data" in query.lower():
            return self.data_agent.run(query)
        elif "write code" in query.lower():
            return self.code_agent.run(query)
        else:
            # Orchestrator handles directly
            return self.default_response(query)

Security and Governance

Tool Access Control

class SecureToolExecutor:
    def __init__(self, tools, permissions):
        self.tools = tools
        self.permissions = permissions  # {user_id: [allowed_tool_names]}

    def execute(self, user_id, tool_name, *args, **kwargs):
        # Check permissions
        if tool_name not in self.permissions.get(user_id, []):
            raise PermissionError(f"User {user_id} not authorized to use {tool_name}")

        # Rate limiting
        if self._exceeds_rate_limit(user_id, tool_name):
            raise RateLimitError(f"Rate limit exceeded for {tool_name}")

        # Execute with audit trail
        with self._audit_context(user_id, tool_name):
            tool = self.tools[tool_name]
            return tool(*args, **kwargs)

# Configuration
executor = SecureToolExecutor(
    tools={"search": search_tool, "delete": delete_tool},
    permissions={
        "user123": ["search"],  # Regular user
        "admin456": ["search", "delete"]  # Admin
    }
)

Input Sanitization

def safe_sql_query_tool(query: str) -> str:
    """Execute SQL with injection protection"""
    # Whitelist allowed operations
    if not query.strip().upper().startswith("SELECT"):
        return "Error: Only SELECT queries allowed"

    # Block dangerous keywords
    dangerous_keywords = ["DROP", "DELETE", "UPDATE", "INSERT", "EXEC"]
    if any(kw in query.upper() for kw in dangerous_keywords):
        return "Error: Query contains forbidden operations"

    # Use parameterized queries
    # ... (implementation with parameterization)

    return execute_query(query)

NCP-AAI Exam Preparation

Key Tool Integration Concepts

Agent Development (15%):

  1. Implementing function calling with OpenAI/Claude APIs
  2. LangChain tool creation and registration
  3. ReAct pattern for multi-step tool orchestration
  4. Error handling and tool result formatting

Agent Architecture (15%):

  1. Single-step vs. multi-step tool patterns
  2. Parallel vs. sequential tool execution
  3. Hierarchical tool composition
  4. Tool routing and delegation strategies

Production Deployment (13%):

  1. Tool access control and permissions
  2. Rate limiting and quota management
  3. Auditing and monitoring tool usage
  4. Caching strategies for expensive tools

Practice Questions

Q1: An agent needs to fetch data from 5 independent APIs before generating a response. Which pattern minimizes latency?

A) Sequential tool calls in ReAct loop B) Parallel tool execution with asyncio C) Hierarchical tool composition D) Single tool that internally calls all 5 APIs

Answer: B - Parallel execution (5x faster than sequential for independent calls)

Q2: A tool frequently returns malformed JSON that breaks agent execution. Best solution?

A) Improve tool description to guide model B) Add try-except in tool to return formatted error messages C) Switch to a different LLM D) Remove the tool

Answer: B - Robust error handling in tools prevents agent failures

Q3: For a customer-facing agent, which security measure is most critical?

A) Caching all tool results B) Using async tool execution C) Implementing role-based tool access control D) Parallel tool calling

Answer: C - Prevents unauthorized access to sensitive tools (delete, modify, etc.)

Master Tool Integration for NCP-AAI

Build production-ready agentic AI systems with tool integration expertise. Preporato's NCP-AAI Practice Bundle includes:

100+ tool integration questions covering all patterns and frameworks ✅ Hands-on labs to implement ReAct agents, function calling, and tool orchestration ✅ Code examples for LangChain, OpenAI, and NVIDIA platform integrations ✅ Security scenarios testing tool access control and governance ✅ Performance optimization questions on caching, parallelization, and latency

Limited Offer: Use code TOOLS25 for 20% off—expires December 31, 2025.


Summary

Tool integration transforms LLMs into action-capable agents. For NCP-AAI success, master:

  • Native function calling: OpenAI, Anthropic, and NVIDIA NIM implementations
  • Integration patterns: Single-step, ReAct, parallel, conditional, hierarchical
  • Tool design: Clear descriptions, type validation, error handling, result formatting
  • Advanced techniques: Dynamic loading, auditing, caching, delegation
  • Security: Access control, input sanitization, rate limiting

Key Takeaway: Well-designed tools with clear interfaces, robust error handling, and appropriate security measures are essential for reliable production agentic AI systems.

Ready to master tool integration? Start practicing with Preporato today! 🚀

Ready to Pass the NCP-AAI Exam?

Join thousands who passed with Preporato practice tests

Instant access30-day guaranteeUpdated monthly