Skip to main content

AI functions

Extend AI agents with custom backend logic they can call during conversations.

Why Use AI Functions

Your AI agent needs to do more than answer questions. It needs to look up order status, update a database, run a domain-specific calculation, or call an external API. The model doesn't know how to do any of that on its own.

Without AI functions, the agent is limited to what the model already knows. With AI functions, the agent can call your backend code whenever the conversation requires it:

Backend code
// Backend: define the function
@aiFunction<{ shipName: string }>(
'Returns the list of pirates on a given ship. Call when the user asks about a ship crew.',
[{ name: 'shipName', description: 'The name of the ship', type: 'string', required: true }],
)
async listPiratesOnShip(params: { shipName: string }): Promise<string> {
// Your custom logic: query a database, call an API, run a calculation, etc.
const { shipName } = params;
const crew = await this.lookupCrew(shipName);
return crew.join(', ');
}
Client code
// Frontend: pass the function to the agent and ask a question
const response = await squid
.ai()
.agent('pirate-agent')
.ask('Who is on the Black Pearl?', {
functions: ['listPiratesOnShip'],
});

Functions can also be added permanently to an agent via the Agent Studio or setAgentOptionInPath, instead of passing them per request.

The agent reads the function's description, decides when to call it, and incorporates the result into its response.

Overview

AI functions are methods in a Squid Service decorated with @aiFunction. When you attach them to an AI agent, the agent can invoke them during a conversation based on the function's description and the user's prompt.

When to use AI functions

Use CaseRecommendation
Agent needs to call custom server-side logicAI function
Agent needs to query or write to a connected databaseUse a database connector, or an AI function for custom query logic
Agent needs to access a connected service (e.g. CRM, calendar, API)Use a connected integration
Agent needs to connect to external tools via MCPUse MCP
Agent needs to search uploaded documentsUse Knowledge Bases

How it works

  1. You decorate a method with @aiFunction in a class that extends SquidService
  2. You attach the function to an agent by name (via the SDK or the Agent Studio in the console)
  3. When a user sends a message, the agent evaluates the function's description against the prompt
  4. If the agent determines the function is relevant, it calls the function with AI-generated parameter values
  5. Your function runs on the backend and returns a string result
  6. The agent incorporates the result into its response

Quick Start

Prerequisites

  • A Squid backend project initialized with squid init-backend
  • The @squidcloud/backend package installed
  • An AI agent created

Step 1: Create an AI function

Add a method with the @aiFunction decorator in a Squid Service:

Backend code
import { SquidService, aiFunction } from '@squidcloud/backend';

export class AiService extends SquidService {
@aiFunction<{ city: string }>('Returns the current weather for a given city. Call when the user asks about weather.', [{ name: 'city', description: 'The city name', type: 'string', required: true }])
async getWeather(params: { city: string }): Promise<string> {
const { city } = params;
// Replace with your actual weather API call
return `The weather in ${city} is 72°F and sunny.`;
}
}

Step 2: Deploy the backend

For local development, run:

squid start

To deploy to the cloud, see deploying your backend.

After deployment, the function appears under Abilities in the Agent Studio in the console.

Step 3: Call the agent with the function

Client code
const response = await squid
.ai()
.agent('my-agent')
.ask("What's the weather in Tokyo?", {
functions: ['getWeather'],
});
console.log(response);

The agent sees the user asked about weather, calls getWeather with { city: "Tokyo" }, and includes the result in its response.

You can also add functions permanently to the agent using setAgentOptionInPath, so you don't need to pass them on every request:

Client code
await squid.ai().agent('my-agent').setAgentOptionInPath('functions', ['getWeather']);

// Now the agent always has access to getWeather
const response = await squid.ai().agent('my-agent').ask("What's the weather in Tokyo?");

Core Concepts

The @aiFunction decorator

The decorator accepts two required arguments:

  1. description (string): Tells the agent when to call this function. Write it as a clear instruction, not a vague label.
  2. params (array): Defines the parameters the agent should provide when calling the function.
Backend code
@aiFunction<{ productId: string; quantity: number }>(
'Updates the stock quantity for a product. Call when the user wants to adjust inventory.',
[
{ name: 'productId', description: 'The product ID', type: 'string', required: true },
{ name: 'quantity', description: 'Amount to add (negative to subtract)', type: 'number', required: true },
],
)
async updateStock(params: { productId: string; quantity: number }): Promise<string> {
// ...
}

Alternatively, you can pass the decorator a single options object:

Backend code
@aiFunction({
description: 'Updates the stock quantity for a product.',
params: [
{ name: 'productId', description: 'The product ID', type: 'string', required: true },
{ name: 'quantity', description: 'Amount to add (negative to subtract)', type: 'number', required: true },
],
})
async updateStock(params: { productId: string; quantity: number }): Promise<string> {
// ...
}

Parameter definitions

Each parameter in the params array supports these fields:

FieldTypeRequiredDescription
namestringYesParameter name, matching the key in your function's params object
descriptionstringYesTells the agent what value to provide
typestringYesData type: 'string', 'number', 'boolean', etc.
requiredbooleanYesWhether the agent must provide this value
enumany[]NoRestricts the value to a set of allowed options

Use enum to constrain values when only specific options are valid:

Backend code
@aiFunction('Saves a section in a document', [
{
name: 'sectionId',
type: 'string',
description: 'Which section to update in the document',
required: true,
enum: ['introduction', 'background', 'methodology', 'results', 'conclusion'],
},
{
name: 'content',
type: 'string',
description: 'The content of the section',
required: true,
},
])
async saveSection(params: { sectionId: string; content: string }): Promise<string> {
const { sectionId, content } = params;
const docRef = this.squid.collection('documents').doc('my-doc');
await docRef.update({ [sectionId]: content });
return 'Section saved: ' + sectionId;
}

Return values

AI functions must return a Promise<string>. The agent receives this string and uses it to formulate its response. Return a clear, concise result:

Backend code
// Good: returns useful information the agent can relay
return 'Order #1234 shipped on March 5. Tracking number: ABC123';

// Bad: returns raw JSON the agent has to interpret
return JSON.stringify(orderObject);

Attributes

Attributes let you bind an AI function to a specific connector type, extending that connector's built-in capabilities with your own custom logic. When you add a connector of that type to an agent, the function is automatically included alongside the connector's default behavior.

For example, a database connector already lets the agent query data with AI. But if you want the agent to consistently query in a specific way, you can write an AI function attributed to that connector type:

Backend code
@aiFunction({
description: 'Retrieves recent orders for a customer from the PostgreSQL database',
params: [
{
name: 'customerEmail',
description: 'The email address of the customer',
type: 'string',
required: true,
},
],
attributes: {
integrationType: ['postgres'],
},
})
async getCustomerOrders(
{ customerEmail }: { customerEmail: string },
{ integrationId }: AiFunctionCallContextWithIntegration,
): Promise<string> {
// Custom query logic.
//
// Squid provides "Query with AI" where the agent can write the query and execute it to
// accomplish a task, but if you want it to consistently query in a certain way, you can write
// an AI Function that you can instruct it to call instead.
}

When you add your PostgreSQL connector to an agent, this function is automatically included.

note

Functions attributed to a connector type do not appear in the AI Functions list in the Agent Studio. Their inclusion with the agent is handled by the connector.

Categories

Categories let you group AI functions in the Agent Studio for easier organization:

Backend code
@aiFunction({
description: 'Get all new user reviews from the product listing on Amazon.',
params: [
{
name: 'productId',
description: 'The ID of the listing on Amazon, e.g. "B094D3JGLT" for the URL "https://www.amazon.com/dp/B094D3JGLT"',
type: 'string',
required: true,
},
{
name: 'cutoffDate',
description: 'The cutoff date. Only reviews newer than this date should be returned. Use ISO8601 format. Defaults to returning all reviews.',
type: 'string',
required: false,
},
],
categories: ['Data Gathering'],
})
async getProductReviews(
{ productId, cutoffDate }: { productId: string; cutoffDate?: string },
{ integrationId }: AiFunctionCallContextWithIntegration,
): Promise<string> {
// Your logic to gather the user reviews.
}

This causes getProductReviews to appear under a "Data Gathering" category in the AI Functions list. You can provide more than one category, and the function appears under each.

Overriding parameter values

For some agents, you may want to fix certain parameter values rather than letting the AI decide. You can override parameters using predefinedParameters. The AI will not know that overridden parameters exist and cannot set their values.

For example, using the saveSection function from earlier, you can create an agent that only updates the introduction section:

Via the chat widget:

Client code
<squid-chat-widget
...
squid-ai-agent-chat-options={{
functions: [{
name: 'saveSection',
predefinedParameters: { sectionId: 'introduction' }
}]
}}
...
>
</squid-chat-widget>

Via the SDK:

Client code
const result = await squid
.ai()
.agent('my-agent')
.ask('Save that content.', {
functions: [
{
name: 'saveSection',
predefinedParameters: { sectionId: 'introduction' },
},
],
});

The agent can only provide the content parameter. The sectionId is always 'introduction'.

Passing context to AI functions

In addition to the AI-generated params, you can pass your own values into AI functions using the context object. This is useful for data the AI should not control, such as document IDs, user preferences, or security-related values.

The context has two scopes:

  • agentContext: Passed to every AI function call made by the agent. Access it via ctx.agentContext.
  • functionContext: Passed to a specific function only. Access it via ctx.functionContext.

Example

Building on the saveSection function, suppose you want the document ID to be set at the agent level and you want to redact internal codenames from content before saving:

Backend code
import { SquidService, aiFunction, AiFunctionCallContext } from '@squidcloud/backend';

const SECTION_IDS = ['introduction', 'background', 'methodology', 'results', 'conclusion'] as const;
type SectionId = (typeof SECTION_IDS)[number];

interface DocumentIdAgentContext {
documentId: string;
}

interface RedactionFunctionContext {
codenameList: string[];
}

class DocumentService extends SquidService {
@aiFunction('Saves a section in a document', [
{
name: 'sectionId',
type: 'string',
description: 'Which section to update in the document',
required: true,
enum: [...SECTION_IDS],
},
{
name: 'content',
type: 'string',
description: 'The content of the section',
required: true,
},
])
async saveSection({ sectionId, content }: { sectionId: SectionId; content: string }, ctx: AiFunctionCallContext<RedactionFunctionContext, DocumentIdAgentContext>): Promise<string> {
const docRef = this.squid.collection('documents').doc(ctx.agentContext.documentId);
let censoredContent = content;
for (const censorWord of ctx.functionContext.codenameList) {
censoredContent = censoredContent.replaceAll(censorWord, 'REDACTED');
}
await docRef.update({ [sectionId]: censoredContent });
return 'Section saved: ' + sectionId;
}
}

Pass these context values from the client via the chat widget:

Client code
<squid-chat-widget
...
squid-ai-agent-chat-options={{
agentContext: { documentId: "document_controlled_by_this_agent" },
functions: [{
name: 'saveSection',
context: { codenameList: ['LITANIA', 'LEOPARD'] }
}]
}}
...
>
</squid-chat-widget>

Or via the SDK:

Client code
const result = await squid
.ai()
.agent('my-agent')
.ask('I want the "results" section to be "LITANIA has determined the answer to be 42".', {
agentContext: { documentId: 'document_controlled_by_this_agent' },
functions: [
{
name: 'saveSection',
context: { codenameList: ['LITANIA', 'LEOPARD'] },
},
],
});

In this case, the saved content would contain "REDACTED has determined the answer to be 42" because "LITANIA" is in the codename list.

Error Handling

When an AI function throws an error, the agent receives the error message and can relay it to the user or decide how to proceed. Throw clear, descriptive errors:

Backend code
@aiFunction<{ orderId: string }>(
'Looks up the status of an order.',
[{ name: 'orderId', description: 'The order ID', type: 'string', required: true }],
)
async getOrderStatus(params: { orderId: string }): Promise<string> {
const { orderId } = params;
const order = await this.squid.collection('orders').doc(orderId).snapshot();
if (!order) {
throw new Error(`Order ${orderId} not found`);
}
return `Order ${orderId}: ${order.status}, shipped ${order.shippedDate}`;
}

Common issues

IssueCauseSolution
Function never gets calledDescription does not match user promptsRewrite the description to clearly state when to call the function
Wrong parameter valuesParameter descriptions are vagueAdd specific descriptions and use enum to constrain values
Function not found by agentFunction name not passed in functions optionPass the function name in the ask call or add it in Agent Studio
Function not appearing in Agent StudioFunction not deployed to the cloudDeploy the backend with squid deploy

Best Practices

Write clear descriptions

The description is the single most important factor in whether the agent calls your function correctly. Write it as an instruction that tells the agent exactly when to use it:

Backend code
// Good: specific and instructive
'Returns the shipping status and tracking number for an order. Call when the user asks about order status, delivery, or tracking.';

// Bad: vague
'Gets order info';

Design parameters carefully

  • Use required: true only for values the function cannot work without
  • Set enum when the valid values are a known set
  • Write parameter descriptions that tell the agent what format to use (e.g., "ISO8601 date", "email address")

Keep return values informative

The agent uses your return string to formulate its response. Return human-readable information, not raw data structures.

Validate inputs

Even though the AI generates parameter values, validate them in your function. The AI may produce unexpected values:

Backend code
async updateQuantity(params: { quantity: number }): Promise<string> {
const { quantity } = params;
if (!Number.isFinite(quantity) || quantity < 0) {
throw new Error('Quantity must be a non-negative number');
}
// ...
}

Next Steps