# Core API Components

## Table of Contents
- [LLMBackend](#llmbackend)
  - [Implementation Notes](#llmbackend-implementation-notes)
- [MCPToolProvider](#mcptoolprovider)
  - [Key Features](#mcptoolprovider-key-features)
  - [Implementation Notes](#mcptoolprovider-implementation-notes)
  - [Lifecycle Management](#lifecycle-management)
- [ChatClient](#chatclient)
  - [Key Features](#chatclient-key-features)
  - [Usage Examples](#chatclient-usage-examples)

This document covers the core components of the Agentical API.

## LLMBackend

The abstract base class that defines the interface for all LLM implementations.

```python
from abc import ABC, abstractmethod
from typing import Generic, TypeVar
from mcp.types import Tool as MCPTool

Context = TypeVar("Context")

class LLMBackend(ABC, Generic[Context]):
    @abstractmethod
    async def process_query(
        self,
        query: str,
        tools: list[MCPTool],
        execute_tool: callable,
        context: Context | None = None,
    ) -> str:
        """Process a user query using the LLM and execute tool calls if needed.

        Args:
            query: The user's input query
            tools: List of available MCP tools
            execute_tool: Callback function to execute a tool
            context: Optional conversation context/history

        Returns:
            The response generated by the LLM model
        """
        pass

    @abstractmethod
    def convert_tools(self, tools: list[MCPTool]) -> list[MCPTool]:
        """Convert MCP tools to the format expected by this LLM.

        Args:
            tools: List of MCP tools to convert

        Returns:
            Tools in the format expected by this LLM implementation
        """
        pass
```

### LLMBackend Implementation Notes

1. **Context Management**
   - Use the `Context` type parameter for your specific context type
   - Context can be None if not needed
   - Implement proper context handling in your backend

2. **Tool Execution**
   - The `execute_tool` callback handles actual tool execution
   - Tool results should be properly integrated into responses
   - Handle tool execution errors gracefully

3. **Tool Conversion**
   - Convert MCP tools to your LLM's expected format
   - Preserve all necessary tool information
   - Handle validation and error cases

## MCPToolProvider

The main facade for integrating LLMs with MCP tools. For detailed information about component lifecycles, state transitions, and interactions, see [System Lifecycles](../discovery/system-lifecycles.md).

```python
from agentical.api import LLMBackend
from agentical.mcp.config import MCPConfigProvider
from agentical.mcp.schemas import ServerConfig

class MCPToolProvider:
    def __init__(
        self,
        llm_backend: LLMBackend,
        config_provider: Optional[MCPConfigProvider] = None,
        server_configs: Optional[Dict[str, ServerConfig]] = None
    ):
        """Initialize the MCP Tool Provider."""
        pass
```

### Key Features
- Server connection management
- Tool discovery and registration
- Query processing with LLM integration
- Resource cleanup and management

### Implementation Notes
- Uses connection manager for robust server connections
- Implements health monitoring with automatic recovery
- Maintains tool registry for efficient dispatch
- Provides comprehensive error handling
- Ensures proper resource cleanup

### Lifecycle Management
The MCPToolProvider implements several key lifecycles:

1. [Provider Lifecycle](../discovery/system-lifecycles.md#2-provider-lifecycle)
   - Initialization and configuration
   - Connection management
   - Operation handling
   - Resource cleanup

2. [Connection Management](../discovery/system-lifecycles.md#3-connection-lifecycle)
   - Connection establishment
   - Health monitoring
   - Automatic recovery
   - Resource cleanup

3. [Tool Management](../discovery/system-lifecycles.md#4-tool-lifecycle)
   - Tool discovery
   - Registration
   - Execution
   - Cleanup

4. [Error Handling](../discovery/system-lifecycles.md#7-error-handling-and-recovery)
   - Error detection
   - Recovery mechanisms
   - Resource protection

## ChatClient

Interactive chat client for the MCP Tool Provider.

```python
from agentical.api import LLMBackend
from agentical.mcp import MCPToolProvider
from agentical.mcp.config import FileBasedMCPConfigProvider, MCPConfigProvider
import logging
import time

logger = logging.getLogger(__name__)

async def run_demo(llm_backend: LLMBackend, config_provider: MCPConfigProvider | None = None):
    """Run an interactive demo session with comprehensive logging and error handling.

    Args:
        llm_backend: The LLM backend to use
        config_provider: Optional configuration provider. If not provided,
            will use command line arguments to create a FileBasedMCPConfigProvider.

    Example:
        ```python
        from agentical.api import LLMBackend
        from agentical.mcp.config import FileBasedMCPConfigProvider

        # Initialize LLM backend
        llm_backend = YourLLMBackend()

        # Option 1: Use default config file
        await run_demo(llm_backend)

        # Option 2: Use custom config provider
        config_provider = FileBasedMCPConfigProvider("custom_config.json")
        await run_demo(llm_backend, config_provider)
        ```
    """
    # Initialize provider
    provider = MCPToolProvider(llm_backend=llm_backend, config_provider=config_provider)
    await provider.initialize()

    # Select and connect to server(s)
    selected_server = await interactive_server_selection(provider)
    if selected_server:
        await provider.mcp_connect(selected_server)
    else:
        await provider.mcp_connect_all()

    # Start chat session
    await chat_loop(provider)

async def chat_loop(provider: MCPToolProvider):
    """Run an interactive chat session with comprehensive logging and statistics.

    Args:
        provider: The configured MCP Tool Provider

    Example:
        ```python
        # Basic usage
        await chat_loop(provider)

        # With custom logging
        logger = logging.getLogger(__name__)
        logger.setLevel(logging.DEBUG)
        await chat_loop(provider)
        ```

    The chat loop provides:
    - Query timing information
    - Error tracking and reporting
    - Session statistics
    - Clean exit handling
    """
    start_time = time.time()
    query_count = 0
    error_count = 0

    try:
        while True:
            query = input("\nQuery: ").strip()
            if query.lower() == "quit":
                break

            query_count += 1
            query_start = time.time()
            try:
                response = await provider.process_query(query)
                query_duration = time.time() - query_start
                logger.debug(
                    "Query processed",
                    extra={
                        "query_number": query_count,
                        "duration_ms": int(query_duration * 1000),
                    },
                )
                print("\n" + response)
            except Exception as e:
                error_count += 1
                query_duration = time.time() - query_start
                logger.error(
                    "Query processing error",
                    extra={
                        "query_number": query_count,
                        "error": str(e),
                        "duration_ms": int(query_duration * 1000),
                    },
                    exc_info=True,
                )
                print(f"\nError processing query: {e!s}")
    finally:
        session_duration = time.time() - start_time
        logger.info(
            "Chat session ended",
            extra={
                "total_queries": query_count,
                "successful_queries": query_count - error_count,
                "failed_queries": error_count,
                "duration_ms": int(session_duration * 1000),
            },
        )

async def interactive_server_selection(provider: MCPToolProvider) -> str | None:
    """Interactively prompt the user to select an MCP server with validation and logging.

    Args:
        provider: The MCP Tool Provider

    Returns:
        Selected server name or None for all servers

    Example:
        ```python
        # Basic usage
        server = await interactive_server_selection(provider)

        # With custom logging
        logger = logging.getLogger(__name__)
        logger.setLevel(logging.DEBUG)
        server = await interactive_server_selection(provider)
        ```

    The selection process includes:
    - Server availability validation
    - Input validation and error handling
    - Comprehensive logging
    - Option to select all servers
    """
    servers = provider.list_available_servers()
    if not servers:
        raise ValueError("No MCP servers available in configuration")

    print("\nAvailable MCP servers:")
    for idx, server in enumerate(servers, 1):
        print(f"{idx}. {server}")
    print(f"{len(servers) + 1}. All above servers")

    while True:
        try:
            choice = input("\nSelect a server (enter number): ").strip()
            idx = int(choice) - 1

            if idx == len(servers):
                return None
            if 0 <= idx < len(servers):
                return servers[idx]

            print("Invalid selection. Please try again.")
        except ValueError:
            print("Please enter a valid number.")
```

### ChatClient Key Features

1. **Interactive Mode**
   - User-friendly command-line interface
   - Server selection with validation
   - Query/response loop with statistics
   - Clean exit handling

2. **Error Handling**
   - Comprehensive error tracking
   - Query timing and performance metrics
   - Session statistics
   - Detailed logging

3. **Logging and Monitoring**
   - Query processing timing
   - Error tracking and reporting
   - Session statistics
   - Performance metrics

4. **Configuration**
   - Flexible configuration options
   - Command-line argument support
   - Custom config provider support
   - Environment variable integration

### ChatClient Usage Examples

1. **Basic Usage**
```python
from agentical.api import LLMBackend
from agentical.mcp.config import FileBasedMCPConfigProvider

# Initialize components
llm_backend = YourLLMBackend()
config_provider = FileBasedMCPConfigProvider("config.json")

# Run demo
await run_demo(llm_backend, config_provider)
```

2. **Custom Configuration**
```python
from agentical.api import LLMBackend
from agentical.mcp.config import MCPConfigProvider

class CustomConfigProvider(MCPConfigProvider):
    def get_server_configs(self) -> dict[str, ServerConfig]:
        # Custom configuration logic
        return {...}

# Use custom config provider
config_provider = CustomConfigProvider()
await run_demo(llm_backend, config_provider)
```

3. **Advanced Usage with Logging**
```python
import logging

# Configure logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

# Run with detailed logging
await run_demo(llm_backend, config_provider)
```