Service policy function reference

Important

This feature is in Beta. Workspace admins can control access to this feature from the Previews page by opting into the Unity Gateway Service Policies preview. See Manage workspace-level previews.

A custom service policy function is a user-defined function (UDF) registered in Unity Catalog that Azure Databricks evaluates on each interaction with the service it is attached to. This page is the field and syntax reference for those functions. For the end-to-end procedure, see Create a SQL policy.

For Python policy functions, see Python policy functions and Write a custom Python service policy.

Function signature

A SQL service policy function takes a single event VARIANT parameter and returns a VARIANT.

CREATE OR REPLACE FUNCTION <catalog>.<schema>.<function_name>(
  event VARIANT
)
RETURNS VARIANT
LANGUAGE SQL
RETURN <expression>;

The function runs at both evaluation points. Branch on event:type::string to tell them apart:

  • 'request': the input phase (ON CALL), before the service is invoked.
  • 'response': the output phase (ON RESULT), after the service responds.

The event argument

event carries the interaction data and context. The available fields depend on the service type:

Field Applies to Description
event:type All services The phase: 'request' (input, ON CALL) or 'response' (output, ON RESULT).
event:target All services The Unity Catalog full name of the service the policy is attached to (optional).
event:context.request_tags Model Services, Model Provider Services, MCP Services (tools/call) A JSON object of caller-supplied string keys and string values. See Request tags.
event:context.actor.run_as All services The run-as identity the request authorizes against.
event:context.actor.context.is_on_behalf_of All services true when an agent or app is acting on behalf of a user (on-behalf-of, or OBO). Use it to write agent-aware policies that apply only when an agent acts for a user.
event:context.actor.context.client_id All services The OAuth client ID of the acting identity, when present (OBO calls).
event:context.actor.context.actor_resource All services The resource of the acting identity, such as an agent, when present.
event:context.actor.context.is_actor_authenticated All services true when the acting identity authenticated as a confidential client.
event:context.tool.name, event:context.tool.arguments MCP Services The tool being called and its arguments (for example, event:context.tool.arguments.repo).
event:context.message Model Services, Model Provider Services The extracted last user or assistant message (API-agnostic). Use this for content checks.
event:context.conversation.conversation Model Services, Model Provider Services An array of provider-neutral message objects in the configured conversation window. Available to custom SQL and Python functions during both input and output evaluation, including when the window is set to one turn. During output evaluation, the array contains the originating request's conversation window.
event:context.conversation.latest_input Model Services, Model Provider Services The text from the most recent user message in the full conversation, even when the configured window excludes that message.
event:data Model Services, Model Provider Services The full request or response payload.
event:request_data Model Services, Model Provider Services The original request, available during the output phase (ON RESULT).

Note

Path access (event:...) returns a VARIANT. Cast it to a scalar type before comparing it to a literal (for example, event:type::string = 'request'); otherwise the comparison fails with a DATATYPE_MISMATCH error.

Conversation context

For a custom SQL or Python function attached to a model service or model provider service, you can configure a conversation window. For any configured value, including one turn, event:context.conversation has this provider-neutral shape during both input and output evaluation. During output evaluation, the object contains the originating request's conversation.

{
  "conversation": [
    { "role": "user", "content": "First question" },
    { "role": "assistant", "content": "First answer" },
    { "role": "user", "content": "Follow-up question" }
  ],
  "latest_input": "Follow-up question"
}

The conversation array contains the configured window. latest_input contains the most recent user message from the full conversation, even when that message is outside the window. If multi-turn conversation context is disabled for the workspace, every configured window is treated as one turn.

The gateway preserves event:context.message and event:data, so existing functions continue to receive the single extracted message and full provider payload. The conversation object is not added when the window is unset or for MCP Services.

Request tags

Custom policies can evaluate the tags sent in the Databricks-Ai-Gateway-Request-Tags header:

Databricks-Ai-Gateway-Request-Tags: {"project":"p-1042"}

This support applies to Unity Gateway requests on paths where service policies run, including MCP tools/call.

Tags come from request headers and appear in event:context alongside other policy metadata. event:data and event:request_data contain payload bodies.

event:context.request_tags is an object inside the event VARIANT, with string keys and string values, logically MAP<STRING, STRING>. Read a tag with event:context.request_tags.project::string. Keys and values preserve case, punctuation, and empty strings. A request without the header has an empty object ({}). Both evaluation phases receive the same request tags, including through request transformations, retries, and model fallback.

By default, the combined size of the decoded keys and values is limited to 10 KiB of UTF-8 data. HTTP header size limits also apply. For SDK and REST examples, see Request tagging.

Model services reject malformed or oversized tag headers. For MCP tools/call requests with applicable service policies, the gateway also rejects these headers before evaluating the policies, including in Log mode.

Tags are supplied by the caller and are separate from service tags and authenticated actor context. A tag alone doesn't establish identity or permission to use a project. Use trusted identity and authorization data when checking those permissions. See Require an eligible project for an example of checking a tag value.

The following limitations apply:

  • Request tags aren't automatically added to LLM-as-a-judge prompts. External service policies receive them, as described in the external service policy API reference.
  • MCP ASK approvals don't distinguish request tags, and approval prompts don't display them. An approval can be reused for an otherwise matching call with different tags. Use ALLOW and DENY decisions for tag-based enforcement. Those decisions are evaluated on each request.

Return value

A custom SQL policy is a Decision Policy: it returns a VARIANT with a result field of ALLOW, DENY, or ASK (case-insensitive) and an optional reason. The result value determines what happens:

  • ALLOW: the interaction proceeds.
  • DENY: Azure Databricks blocks the interaction. Instead of an error, the caller receives a successful (HTTP 200) response. On a Model Service or Model Provider Service, the assistant turn reports the block, with the reason in a top-level databricks_service_policy object. On an MCP Service, the response is a JSON-RPC error with the reason in error.message.
  • ASK: on an MCP Service, the request pauses for user approval before the tool runs. If a custom policy function returns ASK for a Model Service or Model Provider Service, Azure Databricks blocks the request or response because these services cannot ask the user for approval.

Build the result with named_struct and wrap it in to_variant_object so the function returns a VARIANT, keeping result and reason as top-level fields. A bare named_struct returns a STRUCT, and CAST(... AS VARIANT) is not supported.

to_variant_object(named_struct('result', 'DENY', 'reason', 'GitHub push operations are not permitted by policy.'))

The gateway also accepts a forward-looking envelope form:

to_variant_object(named_struct('decision', named_struct('result', 'DENY', 'reason', '...')))

Note

A field that does not exist in the VARIANT parameter reads as NULL, as in standard SQL VARIANT access. A comparison that reads a missing field, such as =, <, or IN, raises an error and results in DENY. This prevents a policy from allowing an interaction when expected fields are missing. To handle a field that might be missing, use a null-handling function such as ISNULL or COALESCE. See Null-handling functions.

Python policy functions

A Python policy function takes two VARIANT parameters, event and config, and returns a VARIANT:

CREATE OR REPLACE FUNCTION <catalog>.<schema>.<function_name>(
  event VARIANT,
  config VARIANT
)
RETURNS VARIANT
LANGUAGE PYTHON
ENVIRONMENT (environment_version = '6')
AS $$
  <function body>
$$;
  • event has the fields listed in The event argument. In Python, convert it with toPython() and read the same fields as dictionary keys. For example, event:context.tool.name in SQL is event_value.get("context", {}).get("tool", {}).get("name") in Python.
  • config holds the options stored on the policy's attachment, or an empty object when it has none. The function must declare it, even if it doesn't use it.
  • The function returns a VARIANT that holds a decision object, with a result of ALLOW, DENY, or ASK (case-insensitive) and an optional reason:
import json

def policy_decision(result, reason=""):
    return type(event).parseJson(json.dumps({"decision": {"result": result, "reason": reason}}))

event_value = event.toPython() if hasattr(event, "toPython") else event
if event_value.get("type") == "request" and "confidential" in str(event_value.get("context", {}).get("message", "")).lower():
    return policy_decision("DENY", "Requests about confidential projects are not allowed.")
return policy_decision("ALLOW")

The supported SQL restrictions don't apply to Python functions, which run as Unity Catalog Python UDFs and can use the Python standard library and declared dependencies. An unhandled exception, such as a KeyError from reading a missing field with [], denies the interaction. Read optional fields with get(). For requirements, dependencies, service credentials, and limitations, see Write a custom Python service policy.

Supported SQL

Azure Databricks transpiles the policy body to CEL and evaluates it at runtime, so the function body supports only a restricted subset of SQL. Azure Databricks rejects an unsupported function or construct when you attach the policy, with the error policy function '<name>' SQL cannot be transpiled to CEL, and the policy fails closed (DENY) at evaluation. Creating the function and running it in Test function check the body as SQL only. Azure Databricks checks CEL compatibility when you attach the policy, so attach the policy to confirm that the body is supported.

Category Supported in the policy body
Operators Comparison, logical, and arithmetic operators; \|\|, IN, LIKE, and IS [NOT] NULL
Control flow CASE and IF
Casts CAST to INT/BIGINT, DOUBLE/FLOAT, STRING, or BOOLEAN; the :: operator
Data access VARIANT / JSON path access
String functions CONCAT, LENGTH, CHAR_LENGTH, UPPER, LOWER, SUBSTRING, TRIM, LTRIM, RTRIM, REPLACE, STARTSWITH, ENDSWITH, CONTAINS
Other functions COALESCE, NULLIF, IFNULL, NVL, ABS, MOD, ISNULL, ISNOTNULL, NAMED_STRUCT, TO_VARIANT_OBJECT

Not supported: ai_query, subqueries, BETWEEN, aggregate functions, lambdas / EXISTS, and variadic CONCAT or COALESCE.

Null-handling functions

COALESCE, IFNULL, NVL, ISNULL, and ISNOTNULL fail CEL validation when their first argument is cast to STRING inside the call, for example event:context.message::STRING. Pass the event field as a VARIANT value instead. For COALESCE, IFNULL, and NVL, cast the result:

Fails CEL validation Passes CEL validation
COALESCE(event:context.message::STRING, '') CAST(COALESCE(event:context.message, '') AS STRING)
IFNULL(event:context.message::STRING, '') CAST(IFNULL(event:context.message, '') AS STRING)
NVL(event:context.message::STRING, '') CAST(NVL(event:context.message, '') AS STRING)
ISNULL(event:context.message::STRING) ISNULL(event:context.message)
ISNOTNULL(event:context.message::STRING) ISNOTNULL(event:context.message)

The fallback can be a literal, such as '', or another event field. COALESCE accepts exactly two arguments.

NULLIF also fails CEL validation when its first argument is cast to STRING, for example NULLIF(event:context.message::STRING, 'n/a').

Because a missing field reads as NULL, these functions can handle optional fields. For example, a request without a project tag has no event:context.request_tags.project field, so the following condition is true:

CAST(COALESCE(event:context.request_tags.project, '') AS STRING) = ''