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 noteThe IXON SDK does not automatically serialize Pydantic models. Always call
.model_dump(mode="json", by_alias=True)on every return value — includingErrorResponse— 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:
| Property | Guidance |
|---|---|
| Inputs | Fewer, higher-level parameters with sensible defaults |
| Outputs | Verbose, human-readable, no post-processing required |
| Errors | Specific messages that allow the agent to retry or ask the user for clarification |
| Docstring | Explains 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_daysparameter replaces three required low-level inputs. - Returns a typed
list[SettingChange]instead of an untypeddict. - 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
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.
Updated about 3 hours ago
