Skip to main content

AI functions

AI functions enhance and extend AI agents with custom functionality

When configuring your AI agents, you might find that certain questions require specific responses that are difficult to configure through instructions. For example, you might have industry-specific formulas that the model isn't familiar with. Or you may want the agent to take an action in response to a specific prompt, such as updating the database.

In these cases (and many others), you can use Squid's AI functions. By extending a function in a Squid Service with the @aiFunction decorator, you can connect your agent to these functions, allowing you to customize each agent with the functionality it requires.

Because these functions are defined in a Squid Service in Typescript, they can access the full power of the Squid platform. This means customization of these functions is limitless.

How to create AI functions

AI functions take a description parameter, which is a string describing the function. This description is how the AI agent determines when to use the function, so it is important that it is clear and informative.

The following example uses the @aiFunction decorator to define a function that an AI agent can use:

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

class AiService extends SquidService {
@aiFunction<{ shipName: string }>(
'This function returns the list of pirates in a ship. ' +
'Call this function whenever the user asks about the crew of a ship.',
[
{
name: 'shipName',
description: 'The name of the ship',
type: 'string',
required: true,
}
]
)
async listPiratesInAShip(params: { shipName: string }): Promise<string> {
const { shipName } = params;
if (shipName === 'Black Pearl') {
return 'Jack Sparrow';
}
else if (shipName === "Queen Anne's Revenge") {
return 'Black Beard';
}
return 'No ship found';
}
}

To add the function to an AI agent, pass the function name with a call to ask as part of the options parameter:

const response = await squid
.ai()
.agent('AGENT_ID')
.ask('Which pirate was captain of the Black Pearl?', {
functions: ['listPiratesInAShip'],
});
Note

After deploying the backend, added AI Functions will appear under the Abilities section in the console and can be used directly by agents within the Agent Studio. See the Backend deployment guide for more info.

Using AI Function to update a database

You can also provide AI agents with functions to update a database. For example, the following function updates a specific section in a large document in the built-in no-sql Squid database:

Backend code
// Assume we have the following section id definitions
export const SECTION_IDS = ['introduction', 'background', 'methodology', 'results', 'conclusion'] as const;
export type SectionId = typeof SECTION_IDS[number];

@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;
},
): Promise<string> {

const docRef = this.squid.collection("documents").doc("my-doc");
await docRef.update({ [sectionId]: content });
return 'section saved: ' + sectionId;
}

Note here we also used enum to restrict the possible values for sectionId. This helps the AI agent understand what values are valid.

Additional @aiFunction parameters

In addition to the description and input parameters, @aiFunction also take attributes and categories.

Attributes

Attributes let you define the specific integration type(s) that you want to pair with this function. If you want to write an AI Function that works with your PostgreSQL database, you can define your decorator like this:

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: ['postgresql'],
},
})
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.
}

This has the effect that when you add your PostgreSQL connector to an agent, this function will also automatically be added to that agent.

Note

Functions that are attributed to an integration type will not appear on the AI Functions list in Studio for the agent because its inclusion with the agent is handled by the inclusion of the connector.

Categories

Categories let you define a sense of grouping of your AI Functions for the purpose of viewing them in the AI Agent Studio. For example:

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 will cause getProductReviews to show under a "Data Gathering" category in the AI Functions list. You may provide more than one category, and the function will appear multiple times, once under each category.

Overriding parameter values

For some agents, it can make sense to let the AI decide the values for all of the params. But for other agents, you might want to override some parameters to have predefined values. In this case, the AI will not know of the existence of the overridden parameters and will not be able to set their values.

Overriding is done via the functions field in the options. For example, let's revisit this previous sample @aiFunction decorator:

Backend code
@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,
},
])

Let's say you want to make a separate agent that is in charge of updating only the 'introduction' section ID. You can then override the sectionId field using predefinedParameters via either the Squid chat widget:

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

Or via the SDK:

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

Then all the AI can do is provide content in the content parameter and your function's sectionId will always receive the value introduction.

Passing parameters to the AI Function via the context

In addition to the params you define within the @aiFunction decorator, you can also pass parameter values to the AI function using the second parameter context object (ctx in the example code), which is an AiFunctionCallContext. Or, if associated with an integration type via the attributes field, it will be an AiFunctionCallContextWithIntegration.

The difference between this and the previous params field is that the values passed into the params are provided by the AI agent, but you can use this context parameter to programmatically pass in other values you may need, independent of the AI.

There are two main context fields, agentContext and functionContext. Their difference is in their scope:

  • agentContext contains context you define for the given AI agent, and it is passed into every AI function call made by that agent. This object maps to ctx.agentContext.
  • functionContext contains context you define to be passed into that specific function. This object maps to ctx.functionContext.

Use these to provide additional information to the function when it is called.

For example, building on the previous sample, note how it was saving to a hard-coded "my-doc" document ID. We can instead make this document ID defined at the agent-level using agentContext. Say you also want to make sure the database's copy of the document has your internal codenames redacted. For these two goals, see how we define and use documentId and codenameList:

Backend code
// Assume we have the following section id definitions
export const SECTION_IDS = ['introduction', 'background', 'methodology', 'results', 'conclusion'] as const;
export type SectionId = typeof SECTION_IDS[number];

// Assume we defined agent context as the following
interface DocumentIdAgentContext {
/** The specific document in the database that the agent will modify. */
documentId: string;
}

// Assume we defined a function context as the following
interface DocumentFunctionContext {
/**
* Avoid leaking our internal codenames.
*
* If any of these strings are found in the input content, we will replace them with "REDACTED".
*/
codenameList: string[];
}

@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<DocumentFunctionContext, 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;
}

You can pass these context parameters to the agent via either the Squid 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:

Backend 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'] }
}]
});

Next Steps

Want to see a more complex example of an AI function in action? Take a look at our AI home maintenance tutorial.