> ## Documentation Index
> Fetch the complete documentation index at: https://docs.redpill.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Function Calling

> Enable AI models to call external functions and APIs

## Overview

Function calling allows models to intelligently call functions you define, enabling:

* API integrations
* Database queries
* External tool use
* Structured data extraction

## Define Functions

```python theme={null}
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"]
                }
            },
            "required": ["location"]
        }
    }
}]
```

## Complete Example

<CodeGroup>
  ```python Python theme={null}
  from openai import OpenAI
  import json

  client = OpenAI(
      api_key="YOUR_API_KEY",
      base_url="https://api.redpill.ai/v1"
  )

  # Define available functions
  tools = [{
      "type": "function",
      "function": {
          "name": "get_weather",
          "description": "Get current weather",
          "parameters": {
              "type": "object",
              "properties": {
                  "location": {"type": "string"},
                  "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
              },
              "required": ["location"]
          }
      }
  }]

  # Make request
  response = client.chat.completions.create(
      model="openai/gpt-5",
      messages=[{"role": "user", "content": "What's the weather in SF?"}],
      tools=tools
  )

  # Check if model wants to call function
  tool_call = response.choices[0].message.tool_calls[0]
  if tool_call.function.name == "get_weather":
      args = json.loads(tool_call.function.arguments)
      # Call your actual function
      weather = get_weather(args["location"], args.get("unit", "celsius"))

      # Send result back to model
      response = client.chat.completions.create(
          model="openai/gpt-5",
          messages=[
              {"role": "user", "content": "What's the weather in SF?"},
              response.choices[0].message,
              {
                  "role": "tool",
                  "tool_call_id": tool_call.id,
                  "content": json.dumps(weather)
              }
          ],
          tools=tools
      )

      print(response.choices[0].message.content)
  ```

  ```javascript JavaScript theme={null}
  import OpenAI from 'openai';

  const tools = [{
    type: 'function',
    function: {
      name: 'get_weather',
      description: 'Get current weather',
      parameters: {
        type: 'object',
        properties: {
          location: { type: 'string' },
          unit: { type: 'string', enum: ['celsius', 'fahrenheit'] }
        },
        required: ['location']
      }
    }
  }];

  const response = await client.chat.completions.create({
    model: 'openai/gpt-5',
    messages: [{ role: 'user', content: "What's the weather in SF?" }],
    tools: tools
  });

  const toolCall = response.choices[0].message.tool_calls[0];
  if (toolCall.function.name === 'get_weather') {
    const args = JSON.parse(toolCall.function.arguments);
    const weather = await getWeather(args.location, args.unit);

    const finalResponse = await client.chat.completions.create({
      model: 'openai/gpt-5',
      messages: [
        { role: 'user', content: "What's the weather in SF?" },
        response.choices[0].message,
        {
          role: 'tool',
          tool_call_id: toolCall.id,
          content: JSON.stringify(weather)
        }
      ],
      tools: tools
    });

    console.log(finalResponse.choices[0].message.content);
  }
  ```
</CodeGroup>

## Supported models

Function calling works on any model whose `supported_parameters` include `tools`. Check a model's
`supported_parameters` in [`GET /v1/models`](/api-reference/models) before relying on it.

## Structured Outputs

**Structured outputs** guarantee that model responses match your exact JSON schema - perfect for data extraction, form filling, and API responses.

### Using Pydantic Models

```python theme={null}
from openai import OpenAI
from pydantic import BaseModel

client = OpenAI(
    api_key="YOUR_API_KEY",
    base_url="https://api.redpill.ai/v1"
)

# Define your structure with Pydantic
class CalendarEvent(BaseModel):
    name: str
    date: str
    participants: list[str]

# Request structured output
response = client.beta.chat.completions.parse(
    model="openai/gpt-5",
    messages=[
        {"role": "system", "content": "Extract calendar events from text"},
        {"role": "user", "content": "Team meeting tomorrow at 2pm with Alice and Bob"}
    ],
    response_format=CalendarEvent
)

# Guaranteed to match schema
event = response.choices[0].message.parsed
print(event.name)  # "Team meeting"
print(event.participants)  # ["Alice", "Bob"]
```

### JSON Schema Approach

```python theme={null}
# Define schema directly
schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "email": {"type": "string", "format": "email"},
        "age": {"type": "integer", "minimum": 0},
        "interests": {
            "type": "array",
            "items": {"type": "string"}
        }
    },
    "required": ["name", "email"],
    "additionalProperties": False
}

response = client.chat.completions.create(
    model="openai/gpt-5",
    messages=[
        {"role": "user", "content": "Extract user info: John Doe, john@example.com, 30 years old, likes hiking and coding"}
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "user_profile",
            "strict": True,
            "schema": schema
        }
    }
)

import json
user = json.loads(response.choices[0].message.content)
# Guaranteed to match schema exactly
```

### Complex Nested Structures

```python theme={null}
from pydantic import BaseModel, Field
from typing import List, Optional

class Address(BaseModel):
    street: str
    city: str
    country: str
    zipcode: str

class Company(BaseModel):
    name: str
    industry: str
    employees: int = Field(ge=1)

class Person(BaseModel):
    full_name: str = Field(description="Full name of the person")
    email: str = Field(pattern=r"^[\w\.-]+@[\w\.-]+\.\w+$")
    age: Optional[int] = Field(None, ge=0, le=150)
    address: Address
    employer: Optional[Company] = None
    skills: List[str] = Field(default_factory=list)

# Extract complex structured data
response = client.beta.chat.completions.parse(
    model="openai/gpt-5",
    messages=[{
        "role": "user",
        "content": """
        Extract: Alice Johnson, alice@example.com, 28 years old.
        Lives at 123 Main St, San Francisco, CA 94105.
        Works at TechCorp (software industry, 500 employees).
        Skills: Python, Machine Learning, Data Science.
        """
    }],
    response_format=Person
)

person = response.choices[0].message.parsed
print(f"{person.full_name} works at {person.employer.name}")
print(f"Skills: {', '.join(person.skills)}")
```

### Use Cases

<AccordionGroup>
  <Accordion title="Data Extraction from Text">
    Extract structured data from unstructured text:

    ```python theme={null}
    class Invoice(BaseModel):
        invoice_number: str
        date: str
        total_amount: float
        currency: str
        line_items: List[dict]

    # Extract from invoice text
    response = client.beta.chat.completions.parse(
        model="openai/gpt-5",
        messages=[{
            "role": "user",
            "content": "Extract invoice: Invoice #12345, dated 2025-01-15, total $1,234.56 USD..."
        }],
        response_format=Invoice
    )
    ```
  </Accordion>

  <Accordion title="Form Auto-Fill">
    Auto-fill forms from natural language:

    ```python theme={null}
    class JobApplication(BaseModel):
        first_name: str
        last_name: str
        email: str
        phone: str
        years_experience: int
        resume_summary: str

    response = client.beta.chat.completions.parse(
        model="openai/gpt-5",
        messages=[{
            "role": "user",
            "content": "Fill job application for John Doe, john@example.com, 555-1234, 5 years experience in software..."
        }],
        response_format=JobApplication
    )
    ```
  </Accordion>

  <Accordion title="API Response Formatting">
    Ensure API responses match your spec:

    ```python theme={null}
    class APIResponse(BaseModel):
        status: str = Field(pattern="^(success|error)$")
        data: Optional[dict] = None
        error_message: Optional[str] = None
        timestamp: str

    # Model will always return valid API response
    response = client.beta.chat.completions.parse(
        model="openai/gpt-5",
        messages=[{"role": "user", "content": "Process this request..."}],
        response_format=APIResponse
    )
    ```
  </Accordion>

  <Accordion title="Database Record Creation">
    Generate database-ready records:

    ```python theme={null}
    class Product(BaseModel):
        sku: str = Field(pattern="^[A-Z]{3}-\d{4}$")
        name: str
        category: str
        price: float = Field(gt=0)
        in_stock: bool
        tags: List[str]

    response = client.beta.chat.completions.parse(
        model="openai/gpt-5",
        messages=[{
            "role": "user",
            "content": "Create product: Wireless Mouse, Electronics, $29.99, in stock, tags: mouse, wireless, computer"
        }],
        response_format=Product
    )

    # Insert directly into database
    db.products.insert(response.choices[0].message.parsed.dict())
    ```
  </Accordion>
</AccordionGroup>

### Structured Outputs + TEE Privacy

**Privacy benefits with the platform:**

```python theme={null}
# Medical record extraction - TEE protected
class MedicalRecord(BaseModel):
    patient_id: str
    diagnosis: str
    medications: List[str]
    visit_date: str

response = client.beta.chat.completions.parse(
    model="qwen/qwen-2.5-7b-instruct",  # Runs on a verified provider
    messages=[{
        "role": "user",
        "content": "Extract from: Patient #12345 diagnosed with hypertension, prescribed lisinopril..."
    }],
    response_format=MedicalRecord
)

# Confidential (is_tee) models run on a verified TEE provider.
# The gateway does not retain request bodies.
```

### Validation & Error Handling

```python theme={null}
from pydantic import ValidationError

try:
    response = client.beta.chat.completions.parse(
        model="openai/gpt-5",
        messages=[{"role": "user", "content": "..."}],
        response_format=MySchema
    )

    # If model returns invalid JSON, OpenAI automatically refines
    # until it matches schema (up to 3 retries)

    data = response.choices[0].message.parsed
    print("Valid structured output:", data)

except ValidationError as e:
    # Pydantic validation failed
    print(f"Schema validation error: {e}")

except Exception as e:
    # Other errors
    print(f"Error: {e}")
```

### Which models support structured outputs

Structured outputs work on models whose `supported_parameters` include `response_format` or
`structured_outputs`. Check [`GET /v1/models`](/api-reference/models).

<Tip>
  For models without native structured output support, use function calling with a single function whose parameters match your desired schema.
</Tip>

### Performance Tips

1. **Use `strict: true`** for guaranteed schema compliance
2. **Simpler schemas** = faster responses
3. **Provide examples** in system message for complex structures
4. **Cache Pydantic models** for repeated use

```python theme={null}
# Good: Simple, clear schema
class User(BaseModel):
    name: str
    age: int

# Avoid: Overly complex nested schemas
class OverlyComplex(BaseModel):
    nested: dict[str, list[dict[str, Union[int, str, list[dict]]]]]
```

## Model Context Protocol (MCP)

[MCP](https://modelcontextprotocol.io/) clients work with this API out of the box. Convert each MCP
tool definition to an OpenAI `tools` entry (the MCP `name`, `description`, and `inputSchema` map
directly to `function.name`, `function.description`, and `function.parameters`), send the request, and
execute the returned `tool_calls` against your MCP server. No special endpoint is required: MCP runs on
the standard tool-calling shown above.

## Best Practices

1. **Clear descriptions**: Help model understand when to call functions
2. **Type safety**: Use strict JSON Schema types or Pydantic models
3. **Error handling**: Validate function arguments with try/except
4. **Security**: Never execute untrusted code from function calls
5. **Use structured outputs**: For guaranteed JSON schema compliance
6. **TEE for sensitive data**: Use confidential (`is_tee`) models for PII extraction

<Warning>
  Always validate and sanitize function arguments before execution. Even with structured outputs, implement defensive programming.
</Warning>
