How to make a Cloud Function agentic

Expose a Cloud Function as an agent tool, and design its parameters, output and errors so the agent calls the right tool with the right arguments.

Cloud functions can be exposed as tools to an AI agent. This document explains the requirements, design guidelines, and patterns for writing agent-friendly cloud functions.

Enabling agent access

To expose a cloud function as an agent tool, use @FunctionContext.expose(is_agentic=True):

@FunctionContext.expose(is_agentic=True)
def my_function(context: FunctionContext, ...) -> ...:
    """
    Clear description of what this function does and when to use it.
    """
    ...

Without is_agentic=True, the function is still exposed as a regular cloud function endpoint but will not be registered as an agent tool.

Requirements

All four of the following are required for the agent to call the function correctly and interpret the response.

1. Typed input and output

Use Pydantic models for all input parameters and return types. The expose decorator automatically derives the JSON schema from these types, which the agent uses to understand how to call the function and interpret its response.

Manually providing input_schema or output_schema is possible but discouraged: it bypasses schema validation and makes maintenance harder.

2. A meaningful docstring and verbose function name

The function's name and docstring becomes the tool description visible to the agent. It must clearly explain:

  • What the function does
  • When to use it (and when not to — point to alternative functions if relevant)
  • What each parameter means if it is not self-evident

3. Structured, self-describing output

The agent cannot apply UI-side transformations such as unit conversions, formatting, or factor corrections. Return data exactly as it should be communicated to the user.

Always use ErrorResponse for failure cases so the agent can reliably distinguish between success and failure. For success, return the typed data directly — no wrapper needed.

# Unstructured return: the agent cannot infer the shape and errors carry no context.
def get_value(...) -> dict[str, object]:
    if not valid:
        return []  # caller has no idea what went wrong
    return result  # caller has no idea what structure to expect


# Prefer a typed return annotation so the agent knows the exact response schema.
# Add a descriptive error so the agent can self-correct or surface a meaningful message to the user.
def get_value(...) -> MyResultModel | ErrorResponse:
    if not valid:
        return ErrorResponse(
            message="Tag 'foo' not found. Valid tag IDs are: ['tag1', 'tag2']"
        ).model_dump(mode="json", by_alias=True)
    return result.model_dump(mode="json", by_alias=True)
⚠️

Please note

The IXON SDK does not automatically serialize Pydantic models. Always call .model_dump(mode="json", by_alias=True) on every return value — including ErrorResponse — to produce a JSON-serialisable dict.

4. Informative error responses

Error messages must be actionable. Instead of a generic "Internal error", tell the agent what went wrong and how to recover:

{"success": false, "message": "Invalid tag_id 'foo'. Valid tag IDs are: ['tag1', 'tag2']"}

Design guidelines

Not every existing Cloud Function is a good agent tool: many are written to serve specific UI components with inputs and outputs tailored to that component's behaviour. Consider creating a dedicated agentic method when:

  • The existing function requires too many inputs that the agent cannot reasonably know upfront.
  • The output format depends on UI-side processing (factors, labels, formatting).
  • The failure modes are not communicated clearly enough for the agent to self-correct.

A good agentic function has:

PropertyGuidance
InputsFewer, higher-level parameters with sensible defaults
OutputsVerbose, human-readable, no post-processing required
ErrorsSpecific messages that allow the agent to retry or ask the user for clarification
DocstringExplains intent, typical use cases, and relationships to sibling tools

Examples: Settings Tracking

The following examples are taken from the Settings Tracking cloud function. Each pair shows an existing UI-oriented function alongside its agent-friendly counterpart.

Example 1: latest setting values

UI-oriented function (not agentic):

@FunctionContext.expose
def get(
    context: FunctionContext,
    tags: list[DataTagPublicId],
    range_from: UnixTimestampMillis | None = None,
    range_to: UnixTimestampMillis | None = None,
) -> dict[str, object]:
    """
    Get the Settings Tracking items for given tags in a time range.
    """
    ...

This function requires the caller to know all tag IDs upfront and uses Unix millisecond timestamps, which are not natural for an agent to produce or reason about.

Agent-friendly function (is_agentic=True):

@FunctionContext.expose(is_agentic=True)
def get_latest_changes(
    context: FunctionContext,
    since_days: int = 30,
) -> list[SettingChange] | ErrorResponse:
    """
    Get the latest (most recent) setting value for each tag within the last {since_days} days.

    Returns at most one element per tag_id — the current value and what it changed from.
    Tags with no changes in the window are excluded.

    Use this when you need: "what are the current settings?" or
    "what did each setting change to recently?".
    For "how many times did the setting change?" or full change history,
    use get_settings_history instead.
    """
    ...

Key differences:

  • A single optional since_days parameter replaces three required low-level inputs.
  • Returns a typed list[SettingChange] instead of an untyped dict.
  • The docstring disambiguates this tool from get_settings_history, helping the agent pick the right one.

Example 2: full change history

UI-oriented function (not agentic):

@FunctionContext.expose
def get_details(
    context: FunctionContext,
    tag: DataTagPublicId,
) -> dict[str, TagDetails]:
    """
    Get all Settings Tracking entries for a given tag before a specific point in time.
    """
    ...

The output contains raw time-series entries without context, and only accepts a single tag.

Agent-friendly function (is_agentic=True):

@FunctionContext.expose(is_agentic=True)
def get_settings_history(
    context: FunctionContext,
    range_from: TimestampISOFormat,
    range_to: TimestampISOFormat | None = None,
    tag_ids: list[DataTagPublicId] | None = None,
) -> list[SettingsTrackingEntry] | ErrorResponse:
    """
    Get the full history of setting changes for the given tags within a time range.

    Returns all change events (multiple per tag possible). Use this when you need to know
    how many times a setting changed, or to see the complete timeline of changes.
    If range_to is not provided, it defaults to the current time.
    """

Key differences:

  • Uses ISO 8601 timestamps (TimestampISOFormat) instead of Unix milliseconds, which are natural for an agent to produce.

  • Accepts multiple tags in a single call (tag_ids: list[...] | None).

  • Returns specific error strings per tag when a tag ID is invalid or has no data in the requested window, so the agent can surface precise feedback to the user:

    setting_histories[tag] = (
        f"{tag} is not a valid tag, "
        "ensure to check if that is a tag id, and not a tag name"
    )

expose decorator reference

@FunctionContext.expose(
    is_agentic=True,                    # Register as an agent tool
    requires_human_permission=False,    # Require explicit user approval before execution
    description=None,                   # Override the docstring as the tool description
    examples=None,                      # Example invocation strings shown to the agent
    input_schema=None,                  # Override auto-derived input JSON schema
    output_schema=None,                 # Override auto-derived output JSON schema
    meta=None,                          # Arbitrary metadata attached to the tool definition
    exclude_param_types=None,           # Parameter types to hide from the agent schema
    exclude_param_names=None,           # Parameter names to hide from the agent schema
)

FunctionContext is automatically excluded from the schema regardless of exclude_param_types.


Did this page help you?