Back to Examples
Intermediate

Inspecting and Understanding Agent Tools

Learn how to programmatically inspect tool schemas, parameters, and formats for debugging and integration

ToolsIntrospectionDebugging

#Overview

Learn how to programmatically inspect and understand the tools available to your agents. This example covers tool introspection, schema exploration, parameter validation, and format conversion - essential skills for debugging, testing, and building advanced agent systems.

#What You'll Learn

  • Inspecting available tools and their metadata
  • Understanding tool parameters and schemas
  • Converting tool formats (OpenAI, Anthropic, etc.)
  • Using agent health checks
  • Debugging tool integration issues
  • Programmatically generating tool documentation

#Prerequisites

#Why Tool Introspection Matters

Tool introspection enables:

  • Debugging: Understand why tools aren't being called correctly
  • Testing: Validate tool configurations programmatically
  • Documentation: Auto-generate tool docs from schemas
  • Integration: Convert tool formats for different LLM providers
  • Monitoring: Track which tools are available and working

#Step 1: List Available Tools

See what tools an agent has access to:

python
from daita.agents.agent import Agent
from daita.plugins.postgresql import postgresql
import asyncio
 
async def main():
    # Create plugin and agent
    db = postgresql(
        host="localhost",
        database="test_db",
        user="test_user",
        password="test_pass"
    )
 
    agent = Agent(
        name="introspection_agent",
        tools=[db]
    )
 
    # List all tool names
    print(f"Agent has {len(agent.tool_names)} tools:")
    for tool_name in agent.tool_names:
        print(f"  - {tool_name}")
 
    await agent.stop()
 
if __name__ == "__main__":
    asyncio.run(main())

Output:

python
Agent has 4 tools:
  - list_tables
  - get_table_schema
  - query_database
  - execute_sql

#Step 2: Inspect Tool Metadata

Access detailed information about each tool:

python
async def main():
    db = postgresql(
        host="localhost",
        database="test_db",
        user="test_user",
        password="test_pass"
    )
 
    agent = Agent(name="introspection_agent", tools=[db])
 
    # Get tool from registry
    tool = agent.tool_registry.get("query_database")
 
    print(f"Tool: {tool.name}")
    print(f"Description: {tool.description}")
    print(f"Category: {tool.category}")
    print(f"Source: {tool.source}")
    print(f"Plugin: {tool.plugin_name}")
    print(f"Timeout: {tool.timeout_seconds}s")
 
    await agent.stop()
 
if __name__ == "__main__":
    asyncio.run(main())

Output:

python
Tool: query_database
Description: Execute SELECT queries on the PostgreSQL database
Category: database
Source: plugin
Plugin: postgresql
Timeout: 30s

Tool metadata includes:

  • name: Tool identifier
  • description: What the tool does
  • category: Logical grouping (database, storage, compute, etc.)
  • source: Where it came from (plugin, custom, framework)
  • plugin_name: Parent plugin name
  • timeout_seconds: Maximum execution time

#Step 3: Explore Tool Parameters

Understand what parameters a tool accepts:

python
async def main():
    db = postgresql(
        host="localhost",
        database="test_db",
        user="test_user",
        password="test_pass"
    )
 
    agent = Agent(name="introspection_agent", tools=[db])
 
    # Inspect parameters for a specific tool
    tool = agent.tool_registry.get("query_database")
 
    print(f"Tool: {tool.name}\n")
    print("Parameters:")
 
    for param_name, param_info in tool.parameters.items():
        required = "required" if param_info.get("required") else "optional"
        param_type = param_info.get("type", "unknown")
        description = param_info.get("description", "No description")
 
        print(f"\n  {param_name} ({param_type}, {required})")
        print(f"    {description}")
 
        # Show default value if present
        if "default" in param_info:
            print(f"    Default: {param_info['default']}")
 
    await agent.stop()
 
if __name__ == "__main__":
    asyncio.run(main())

Output:

python
Tool: query_database
 
Parameters:
 
  sql (string, required)
    The SQL SELECT query to execute
 
  params (array, optional)
    Query parameters for parameterized queries (e.g., [$1, $2])
    Default: None

#Step 4: Convert to OpenAI Function Format

Convert tool schemas to OpenAI's function calling format:

python
async def main():
    db = postgresql(
        host="localhost",
        database="test_db",
        user="test_user",
        password="test_pass"
    )
 
    agent = Agent(name="introspection_agent", tools=[db])
 
    # Convert tool to OpenAI format
    tool = agent.tool_registry.get("query_database")
    openai_format = tool.to_openai_function()
 
    print("OpenAI Function Format:")
    print(f"Name: {openai_format['name']}")
    print(f"Description: {openai_format['description']}")
    print(f"\nParameters:")
    print(f"  Type: {openai_format['parameters']['type']}")
    print(f"  Properties: {list(openai_format['parameters']['properties'].keys())}")
    print(f"  Required: {openai_format['parameters'].get('required', [])}")
 
    # Full format (for API calls)
    import json
    print(f"\nFull OpenAI Format:")
    print(json.dumps(openai_format, indent=2))
 
    await agent.stop()
 
if __name__ == "__main__":
    asyncio.run(main())

Output:

python
OpenAI Function Format:
Name: query_database
Description: Execute SELECT queries on the PostgreSQL database
 
Parameters:
  Type: object
  Properties: ['sql', 'params']
  Required: ['sql']
 
Full OpenAI Format:
{
  "name": "query_database",
  "description": "Execute SELECT queries on the PostgreSQL database",
  "parameters": {
    "type": "object",
    "properties": {
      "sql": {
        "type": "string",
        "description": "The SQL SELECT query to execute"
      },
      "params": {
        "type": "array",
        "description": "Query parameters for parameterized queries",
        "default": null
      }
    },
    "required": ["sql"]
  }
}

#Step 5: Convert to Anthropic Tool Format

Convert tool schemas to Anthropic's tool format:

python
async def main():
    db = postgresql(
        host="localhost",
        database="test_db",
        user="test_user",
        password="test_pass"
    )
 
    agent = Agent(name="introspection_agent", tools=[db])
 
    # Convert tool to Anthropic format
    tool = agent.tool_registry.get("query_database")
    anthropic_format = tool.to_anthropic_tool()
 
    print("Anthropic Tool Format:")
    print(f"Name: {anthropic_format['name']}")
    print(f"Description: {anthropic_format['description']}")
    print(f"\nInput Schema:")
    print(f"  Type: {anthropic_format['input_schema']['type']}")
    print(f"  Properties: {list(anthropic_format['input_schema']['properties'].keys())}")
    print(f"  Required: {anthropic_format['input_schema'].get('required', [])}")
 
    # Full format
    import json
    print(f"\nFull Anthropic Format:")
    print(json.dumps(anthropic_format, indent=2))
 
    await agent.stop()
 
if __name__ == "__main__":
    asyncio.run(main())

Output:

python
Anthropic Tool Format:
Name: query_database
Description: Execute SELECT queries on the PostgreSQL database
 
Input Schema:
  Type: object
  Properties: ['sql', 'params']
  Required: ['sql']
 
Full Anthropic Format:
{
  "name": "query_database",
  "description": "Execute SELECT queries on the PostgreSQL database",
  "input_schema": {
    "type": "object",
    "properties": {
      "sql": {
        "type": "string",
        "description": "The SQL SELECT query to execute"
      },
      "params": {
        "type": "array",
        "description": "Query parameters for parameterized queries",
        "default": null
      }
    },
    "required": ["sql"]
  }
}

#Step 6: Use Agent Health Checks

Monitor agent status including tool availability:

python
async def main():
    db = postgresql(
        host="localhost",
        database="test_db",
        user="test_user",
        password="test_pass"
    )
 
    agent = Agent(name="introspection_agent", tools=[db])
 
    # Get agent health status
    health = agent.health
 
    print("Agent Health Status:")
    print(f"  Status: {health['status']}")
    print(f"  Agent Name: {health['name']}")
    print(f"\nTools:")
    print(f"  Count: {health['tools']['count']}")
    print(f"  Setup: {health['tools']['setup']}")
    print(f"  Available: {', '.join(health['tools']['names'])}")
 
    if 'llm' in health:
        print(f"\nLLM:")
        print(f"  Provider: {health['llm']['provider']}")
        print(f"  Model: {health['llm']['model']}")
        print(f"  Connected: {health['llm']['connected']}")
 
    await agent.stop()
 
if __name__ == "__main__":
    asyncio.run(main())

Output:

python
Agent Health Status:
  Status: healthy
  Agent Name: introspection_agent
 
Tools:
  Count: 4
  Setup: True
  Available: list_tables, get_table_schema, query_database, execute_sql

#Step 7: Comprehensive Tool Inspector

Build a complete tool inspection utility:

python
async def inspect_all_tools(agent):
    """Comprehensive tool inspection utility"""
    print("="*70)
    print(f"TOOL INSPECTION REPORT - Agent: {agent.name}")
    print("="*70)
 
    for tool_name in agent.tool_names:
        tool = agent.tool_registry.get(tool_name)
 
        print(f"\n{'='*70}")
        print(f"Tool: {tool.name}")
        print(f"{'='*70}")
 
        # Basic info
        print(f"\nBasic Information:")
        print(f"  Description: {tool.description}")
        print(f"  Category: {tool.category}")
        print(f"  Source: {tool.source}")
        print(f"  Plugin: {tool.plugin_name}")
        print(f"  Timeout: {tool.timeout_seconds}s")
 
        # Parameters
        print(f"\nParameters:")
        if tool.parameters:
            for param_name, param_info in tool.parameters.items():
                required = "✓ required" if param_info.get("required") else "○ optional"
                print(f"  • {param_name} ({param_info['type']}) - {required}")
                print(f"    {param_info.get('description', 'No description')}")
                if "default" in param_info:
                    print(f"    Default: {param_info['default']}")
        else:
            print("  No parameters")
 
        # Format examples
        print(f"\nOpenAI Function Format:")
        openai_format = tool.to_openai_function()
        print(f"  Name: {openai_format['name']}")
        print(f"  Has parameters: {bool(openai_format['parameters']['properties'])}")
 
        print(f"\nAnthropic Tool Format:")
        anthropic_format = tool.to_anthropic_tool()
        print(f"  Name: {anthropic_format['name']}")
        print(f"  Has input schema: {bool(anthropic_format['input_schema']['properties'])}")
 
    print(f"\n{'='*70}")
    print(f"Total Tools: {len(agent.tool_names)}")
    print(f"{'='*70}\n")
 
 
async def main():
    db = postgresql(
        host="localhost",
        database="test_db",
        user="test_user",
        password="test_pass"
    )
 
    agent = Agent(name="introspection_agent", tools=[db])
 
    # Run comprehensive inspection
    await inspect_all_tools(agent)
 
    await agent.stop()
 
if __name__ == "__main__":
    asyncio.run(main())

#Step 8: Generate Tool Documentation

Automatically create markdown documentation from tool schemas:

python
async def generate_tool_docs(agent, output_file="tools_documentation.md"):
    """Generate markdown documentation for all tools"""
 
    with open(output_file, "w") as f:
        f.write(f"# Tool Documentation - {agent.name}\n\n")
        f.write(f"Total tools: {len(agent.tool_names)}\n\n")
 
        for tool_name in agent.tool_names:
            tool = agent.tool_registry.get(tool_name)
 
            # Tool header
            f.write(f"## {tool.name}\n\n")
            f.write(f"**Description:** {tool.description}\n\n")
            f.write(f"**Category:** {tool.category}\n\n")
            f.write(f"**Source:** {tool.source} ({tool.plugin_name})\n\n")
            f.write(f"**Timeout:** {tool.timeout_seconds}s\n\n")
 
            # Parameters table
            if tool.parameters:
                f.write("### Parameters\n\n")
                f.write("| Parameter | Type | Required | Description |\n")
                f.write("|-----------|------|----------|-------------|\n")
 
                for param_name, param_info in tool.parameters.items():
                    required = "✓" if param_info.get("required") else "○"
                    param_type = param_info.get("type", "unknown")
                    description = param_info.get("description", "No description")
                    f.write(f"| {param_name} | {param_type} | {required} | {description} |\n")
 
                f.write("\n")
 
            # Usage example
            f.write("### Usage Example\n\n")
            f.write("```python\n")
            f.write(f'result = await agent.call_tool("{tool_name}", {{\n')
 
            if tool.parameters:
                for param_name, param_info in tool.parameters.items():
                    if param_info.get("required"):
                        example = '"example_value"' if param_info["type"] == "string" else "[]"
                        f.write(f'    "{param_name}": {example},\n')
 
            f.write("})\n")
            f.write("```\n\n")
            f.write("---\n\n")
 
    print(f"Documentation generated: {output_file}")
 
 
async def main():
    db = postgresql(
        host="localhost",
        database="test_db",
        user="test_user",
        password="test_pass"
    )
 
    agent = Agent(name="introspection_agent", tools=[db])
 
    # Generate documentation
    await generate_tool_docs(agent)
 
    await agent.stop()
 
if __name__ == "__main__":
    asyncio.run(main())

#Complete Example

Full tool introspection example:

python
from daita.agents.agent import Agent
from daita.plugins.postgresql import postgresql
import asyncio
import json
 
 
async def main():
    # Create plugin and agent
    db = postgresql(
        host="localhost",
        database="test_db",
        user="test_user",
        password="test_pass"
    )
 
    agent = Agent(name="introspection_agent", tools=[db])
 
    print("\n" + "="*70)
    print("TOOL INTROSPECTION EXAMPLE")
    print("="*70)
 
    # 1. List all tools
    print(f"\n1. Available Tools ({len(agent.tool_names)}):")
    for tool_name in agent.tool_names:
        print(f"   - {tool_name}")
 
    # 2. Inspect each tool
    print("\n2. Detailed Tool Information:\n")
 
    for tool_name in agent.tool_names:
        tool = agent.tool_registry.get(tool_name)
 
        print(f"\nTool: {tool.name}")
        print(f"  Description: {tool.description}")
        print(f"  Category: {tool.category}")
        print(f"  Source: {tool.source}")
        print(f"  Plugin: {tool.plugin_name}")
        print(f"  Timeout: {tool.timeout_seconds}s")
        print(f"  Parameters:")
 
        for param_name, param_info in tool.parameters.items():
            required = "required" if param_info.get("required") else "optional"
            print(f"    - {param_name} ({param_info['type']}, {required})")
            print(f"      {param_info.get('description', 'No description')}")
 
        # Format conversion examples
        print(f"\n  OpenAI Function Format:")
        openai_format = tool.to_openai_function()
        print(f"    {json.dumps(openai_format, indent=6)}")
 
        print(f"\n  Anthropic Tool Format:")
        anthropic_format = tool.to_anthropic_tool()
        print(f"    Name: {anthropic_format['name']}")
        print(f"    Input Schema: {anthropic_format['input_schema']['type']}")
 
        print("\n" + "-"*70)
 
    # 3. Agent health check
    print("\n3. Agent Health Status:")
    health = agent.health
    print(f"   Tools: {health['tools']['count']} tools")
    print(f"   Setup: {health['tools']['setup']}")
    print(f"   Available: {', '.join(health['tools']['names'])}")
 
    print("\n" + "="*70)
 
    await agent.stop()
 
 
if __name__ == "__main__":
    asyncio.run(main())

#Framework Internals

Tool Registry Architecture:

  1. Tool Registry: Central store for all tools (agent.tool_registry)
  2. Tool Metadata: Each tool has rich metadata (name, description, params, etc.)
  3. Format Converters: Built-in conversion to LLM-specific formats
  4. Health Monitoring: Runtime status of tool availability
  5. Dynamic Registration: Tools can be added/removed at runtime

Tool Schema Structure:

python
{
    "name": "tool_name",
    "description": "What it does",
    "category": "database",
    "source": "plugin",
    "plugin_name": "postgresql",
    "timeout_seconds": 30,
    "parameters": {
        "param1": {
            "type": "string",
            "description": "Param description",
            "required": True
        }
    }
}

#Use Cases for Tool Introspection

  1. Debugging: Verify tools are registered correctly
  2. Testing: Validate tool schemas in CI/CD
  3. Documentation: Auto-generate tool docs
  4. Monitoring: Track tool availability in production
  5. LLM Integration: Convert to provider-specific formats
  6. Tool Discovery: Let agents inspect their own capabilities

#Key Takeaways

  1. Tool introspection provides complete visibility into agent capabilities
  2. Tool metadata is rich including descriptions, parameters, and constraints
  3. Format conversion is built-in for OpenAI and Anthropic
  4. Agent health checks monitor tool availability
  5. Programmatic access enables automation and testing
  6. Tool schemas are self-documenting - use them for docs generation

#Next Steps