AI functions
会話中に呼び出せるカスタム backend logic で AI agent を拡張します。
AI Functions を使用する理由
AI agent には質問への回答以上のことが必要です。注文状況の確認、database の更新、domain-specific calculation の実行、外部 API の呼び出しなどを行う必要があります。model 単体では、そのようなことを実行する方法を知りません。
AI Functions がない場合、agent は model がすでに知っている情報に制限されます。AI Functions を使用すると、会話で必要になったときに agent が backend code を呼び出せます。
- TypeScript
- Python
// 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(', ');
}
# Backend: define the function
@ai_function(
'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 def list_pirates_on_ship(self, params: dict) -> str:
# Your custom logic: query a database, call an API, run a calculation, etc.
ship_name = params['shipName']
crew = await self.lookup_crew(ship_name)
return ', '.join(crew)
// 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 は、リクエストごとに渡す代わりに、Agent Studio または setAgentOptionInPath を通じて agent に永続的に追加することもできます。
agent は function の description を読み取り、いつ呼び出すかを判断し、その結果を response に組み込みます。
概要
AI Functions は、@aiFunction で decorate された Squid Service 内の method です。AI agent にこれらをアタッチすると、agent は function の description と user prompt に基づき、会話中に呼び出せます。
AI Functions を使用する場合
| ユースケース | 推奨 |
|---|---|
| Agent がカスタム server-side logic を呼び出す必要がある | AI function |
| Agent が接続済み database に query または write する必要がある | database connector、またはカスタム query logic 用の AI function を使用 |
| Agent が接続済み service(例: CRM、calendar、API)にアクセスする必要がある | connected integration を使用 |
| Agent が MCP 経由で外部 tool に接続する必要がある | MCP を使用 |
| Agent がアップロード済み document を検索する必要がある | Knowledge Bases を使用 |
仕組み
SquidServiceを拡張する class の method を@aiFunctionで decorate します- SDK または console の Agent Studio を使用して、名前で function を agent にアタッチします
- user が message を送信すると、agent は function の description を prompt と照らし合わせて評価します
- agent が function に関連性があると判断した場合、AI-generated parameter value とともに function を呼び出します
- function が backend で実行され、string の result を返します
- agent が result を response に組み込みます
クイックスタート
前提条件
- TypeScript
- Python
ステップ 1: AI function を作成する
Squid Service に @aiFunction decorator を付けた method を追加します。
- TypeScript
- Python
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.`;
}
}
from squidcloud_backend import SquidService, ai_function
class AiService(SquidService):
@ai_function(
'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 def get_weather(self, params: dict) -> str:
city = params['city']
# Replace with your actual weather API call
return f'The weather in {city} is 72°F and sunny.'
ステップ 2: backend を deploy する
ローカル開発では、次を実行します。
squid start
cloud に deploy するには、backend の deploy を参照してください。
deploy 後、function は console の Agent Studio の Abilities に表示されます。
ステップ 3: function を使用して agent を呼び出す
const response = await squid
.ai()
.agent('my-agent')
.ask("What's the weather in Tokyo?", {
functions: ['getWeather'],
});
console.log(response);
agent は user が weather について質問したことを認識し、{ city: "Tokyo" } を指定して getWeather を呼び出し、その結果を response に含めます。
setAgentOptionInPath を使用して function を agent に永続的に追加することもできるため、リクエストごとに渡す必要はありません。
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?");
基本概念
@aiFunction decorator
decorator は、必須の 2 つの引数を受け取ります。
description(string): agent にこの function をいつ呼び出すか伝えます。曖昧な label ではなく、明確な instruction として記述してください。params(array): function の呼び出し時に agent が指定すべき parameter を定義します。
- TypeScript
- Python
@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> {
// ...
}
@ai_function(
'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 def update_stock(self, params: dict) -> str:
...
あるいは、decorator に単一の options object を渡すこともできます。
- TypeScript
- Python
@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> {
// ...
}
@ai_function({
'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 def update_stock(self, params: dict) -> str:
...
Parameter 定義
params array 内の各 parameter は、次の field をサポートします。
| Field | Type | 必須 | 説明 |
|---|---|---|---|
name | string | はい | function の params object 内の key に対応する parameter 名 |
description | string | はい | agent に指定すべき value を伝えます |
type | string | はい | data type: 'string'、'number'、'boolean' など |
required | boolean | はい | agent がこの value を必ず指定するかどうか |
enum | any[] | いいえ | value を許可された option の set に制限します |
affinity | string | いいえ | 'pod' または 'worker'。この parameter を function の affinity key としてマークし、routing の sticky 性を指定します。この parameter の value が一致する call は、同じ pod または同じ worker に送られます。affinity による Sticky routing を参照してください。 |
有効な option が特定のものに限られる場合は、enum を使用して value を制約します。
- TypeScript
- Python
@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;
// Persist the section using your own data layer.
await this.writeSection('my-doc', sectionId, content);
return 'Section saved: ' + sectionId;
}
@ai_function('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 def save_section(self, params: dict) -> str:
section_id = params['sectionId']
content = params['content']
# Persist the section using your own data layer.
await self.write_section('my-doc', section_id, content)
return f'Section saved: {section_id}'
affinity による Sticky routing
ほとんどの AI Functions は stateless のため、任意の worker が任意の call を処理できます。function が warm cache、live session、起動した child process など、request の外部に state を保持する場合は、その state を識別する parameter を affinity でマークしてください。同じ value を持つ call は、その value が存続している限り常に同じ場所に routing されます。
'pod': たとえば disk 上の file や child process のように、同じ pod の任意の worker が state にアクセスできます。'worker': state は 1 つの worker の memory 内に存在するため、worker 自体が再利用されます。
affinity と以下の live-state hook は TypeScript backend SDK 向けに document 化されているため、この section の例には Python tab がありません。
import { SquidService, aiFunction } from '@squidcloud/backend';
/** A long-lived interpreter, for example a child process running Python. */
interface Interpreter {
run(code: string): Promise<string>;
dispose(): Promise<void>;
}
export class InterpreterService extends SquidService {
private readonly sessions = new Map<string, Interpreter>();
@aiFunction<{ sessionName: string; code: string }>('Runs a Python snippet in a named, reusable interpreter session', [
{
name: 'sessionName',
type: 'string',
description: 'The interpreter session to run in. Reuse a name to keep variables between calls.',
required: true,
affinity: 'worker',
},
{
name: 'code',
type: 'string',
description: 'The Python code to execute',
required: true,
},
])
async runInSession(params: { sessionName: string; code: string }): Promise<string> {
const { sessionName, code } = params;
// affinity routes every call with this sessionName back to this worker, so the
// interpreter held in `sessions` is the same one each time.
let interpreter = this.sessions.get(sessionName);
if (!interpreter) {
// The worker may have been replaced since the last call, so rebuild rather than assume.
interpreter = await this.startInterpreter();
this.sessions.set(sessionName, interpreter);
}
return await interpreter.run(code);
}
// Replace with your actual interpreter setup.
private async startInterpreter(): Promise<Interpreter> {
throw new Error('Not implemented');
}
// Keeps the worker alive while a session is open. Called often by the runtime,
// so keep it cheap and synchronous. See "Keeping a worker alive" below.
override hasLiveState(): boolean {
return this.sessions.size > 0;
}
// Called when the worker is being terminated. Child processes outlive the thread that spawned them.
override async releaseLiveState(): Promise<void> {
await Promise.all([...this.sessions.values()].map((session) => session.dispose()));
this.sessions.clear();
}
}
function では 1 つの string parameter だけが affinity を宣言できます。他の type の parameter でこれを宣言すると bundle build が失敗します。undefined、null、または '' の value は affinity がないことを意味し、call は通常どおり routing されます。空白は意図的に key ではありません。そのため、parameter を空のままにした caller は、同じく空のままにした他のすべての caller と instance を共有せず、自身専用の instance を取得します。
value は application、function の service、および呼び出し元 job 内の namespace であり、job を持たない caller では conversation に fallback します。同じ value を選択した 2 つの agent request でも独立して routing されるため、value は 1 つの request 自身の call 内でのみ一意であれば十分です。2 つの caller が value を合わせることで意図的に 1 つの instance を共有することはできません。真に共有された state が必要な function では、代わりに shared storage が必要です。namespace は単一の function ではなく service 単位であるため、1 つの service の companion function は互いの state にアクセスできます。
affinity が保証するのは routing であり、durability ではありません。pod または worker が終了すると、そこに保持されていたものも失われ、次の call は新しいものに送られます。state が存続していると想定せず、function 内でこれを検出して state を再構築してください。
worker を存続させる
idle worker は通常数分後に teardown されます。これは stateless service では問題になりませんが、live session や child process を保持する service では破壊的です。このような state が存在する間は service で hasLiveState() を override して報告し、runtime が worker を terminate するときに解放するため releaseLiveState() を override します。これにより上記の InterpreterService の session は存続します。map が interpreter を保持している限り hasLiveState() が報告し、worker が最終的に recycle されるときに releaseLiveState() がそれらを dispose します。
true を無期限に返すと pod の存続期間中 worker が固定されるため、代わりに state 自体の lifetime を追跡してください。
Return value
AI Functions は Promise<string>(TypeScript)または str(Python)を返す必要があります。agent はこの string を受け取り、response を作成するために使用します。明確で簡潔な result を返してください。
- TypeScript
- Python
// 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);
# 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.dumps(order_object)
Attributes
Attributes を使用すると、AI function を特定の connector type に bind し、その connector の built-in capability をカスタム logic で拡張できます。その type の connector を agent に追加すると、connector の default behavior とともに function が自動的に含まれます。
たとえば、database connector はすでに agent が AI で data を query できるようにします。しかし、agent に常に特定の方法で query させたい場合は、その connector type に attribute を付与した AI function を作成できます。
- TypeScript
- Python
@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.
}
@ai_function({
'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 def get_customer_orders(self, params: dict, ctx: dict) -> str:
customer_email = params['customerEmail']
integration_id = ctx['integrationId']
# 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,
# write an AI function that you can instruct it to call instead.
...
PostgreSQL connector を agent に追加すると、この function が自動的に含まれます。
connector type に attribute を付与した function は、Agent Studio の AI Functions list には表示されません。agent への追加は connector によって処理されます。
Categories
Categories を使用すると、Agent Studio 内で AI Functions を group 化し、整理しやすくできます。
- TypeScript
- Python
@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.
}
@ai_function({
'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 def get_product_reviews(self, params: dict, ctx: dict) -> str:
product_id = params['productId']
cutoff_date = params.get('cutoffDate')
integration_id = ctx['integrationId']
# Your logic to gather the user reviews.
...
これにより、getProductReviews は AI Functions list 内の「Data Gathering」category に表示されます。複数の category を指定でき、function はそれぞれの category に表示されます。
Parameter value の override
一部の agent では、AI に判断させるのではなく、特定の parameter value を固定したい場合があります。predefinedParameters を使用して parameter を override できます。AI は override された parameter の存在を認識せず、その value を設定することもできません。
たとえば、先ほどの saveSection function を使用すると、introduction section のみを更新する agent を作成できます。
chat widget 経由:
<squid-chat-widget
...
squid-ai-agent-chat-options={{
functions: [{
name: 'saveSection',
predefinedParameters: { sectionId: 'introduction' }
}]
}}
...
>
</squid-chat-widget>
SDK 経由:
const result = await squid
.ai()
.agent('my-agent')
.ask('Save that content.', {
functions: [
{
name: 'saveSection',
predefinedParameters: { sectionId: 'introduction' },
},
],
});
agent が指定できるのは content parameter のみです。sectionId は常に 'introduction' になります。
AI Functions に context を渡す
AI-generated params に加えて、context object を使用して独自の value を AI Functions に渡せます。これは、document ID、user preference、security 関連の value など、AI が control すべきではない data に役立ちます。
context には 2 つの scope があります。
agentContext: agent が行うすべての AI function call に渡されます。ctx.agentContextを介してアクセスします。functionContext: 特定の function にのみ渡されます。ctx.functionContextを介してアクセスします。
例
saveSection function を基に、document ID を agent level で設定し、保存前に content から内部 codename を redact したいとします。
- TypeScript
- Python
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> {
let censoredContent = content;
for (const censorWord of ctx.functionContext.codenameList) {
censoredContent = censoredContent.replaceAll(censorWord, 'REDACTED');
}
// Persist the censored section using your own data layer.
await this.writeSection(ctx.agentContext.documentId, sectionId, censoredContent);
return 'Section saved: ' + sectionId;
}
}
from squidcloud_backend import SquidService, ai_function
SECTION_IDS = ['introduction', 'background', 'methodology', 'results', 'conclusion']
class DocumentService(SquidService):
@ai_function('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 def save_section(self, params: dict, ctx: dict) -> str:
section_id = params['sectionId']
content = params['content']
document_id = ctx['agentContext']['documentId']
codename_list = ctx['functionContext']['codenameList']
censored_content = content
for censor_word in codename_list:
censored_content = censored_content.replace(censor_word, 'REDACTED')
# Persist the censored section using your own data layer.
await self.write_section(document_id, section_id, censored_content)
return f'Section saved: {section_id}'
次のように、chat widget を介して client からこれらの context value を渡します。
<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>
または SDK 経由:
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'] },
},
],
});
この場合、"LITANIA" が codename list に含まれるため、保存される content は "REDACTED has determined the answer to be 42" になります。
Error Handling
AI function が error を throw すると、agent は error message を受け取り、user に伝達するか、どのように進めるかを判断できます。明確で説明的な error を throw してください。
- TypeScript
- Python
@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}`;
}
@ai_function(
'Looks up the status of an order.',
[{'name': 'orderId', 'description': 'The order ID', 'type': 'string', 'required': True}],
)
async def get_order_status(self, params: dict) -> str:
order_id = params['orderId']
order = await self.lookup_order(order_id)
if order is None:
raise RuntimeError(f'Order {order_id} not found')
return f"Order {order_id}: {order['status']}, shipped {order['shippedDate']}"
よくある問題
| 問題 | 原因 | 解決方法 |
|---|---|---|
| Function が呼び出されない | Description が user prompt と一致しない | function をいつ呼び出すかが明確になるよう description を書き換える |
| Parameter value が正しくない | Parameter description が曖昧 | 具体的な description を追加し、enum で value を制約する |
| Agent が function を見つけられない | functions option に function 名が渡されていない | ask call に function 名を渡すか、Agent Studio で追加する |
| Function が Agent Studio に表示されない | Function が cloud に deploy されていない | squid deploy で backend を deploy する |
ベストプラクティス
明確な description を書く
description は、agent が function を正しく呼び出すかどうかを左右する最も重要な要素です。いつ使用すべきかを agent に正確に伝える instruction として記述してください。
Good: 'Returns the shipping status and tracking number for an order. Call when the user asks about order status, delivery, or tracking.'
Bad: 'Gets order info'
Parameter を慎重に設計する
- function が動作するために不可欠な value にのみ
required: trueを使用します - 有効な value が既知の set である場合は
enumを設定します - agent が使用すべき format(例: 「ISO8601 date」、「email address」)を伝える parameter description を記述します
Return value を有益なものにする
agent は return string を使用して response を作成します。raw data structure ではなく、人間が読める情報を返してください。
Input を validate する
AI が parameter value を生成したとしても、function 内で validate してください。AI は予期しない value を生成する場合があります。
- TypeScript
- Python
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');
}
// ...
return 'Quantity updated';
}
async def update_quantity(self, params: dict) -> str:
quantity = params['quantity']
if not isinstance(quantity, (int, float)) or quantity < 0:
raise ValueError('Quantity must be a non-negative number')
# ...
return 'Quantity updated'
次のステップ
- agent の設定と function のアタッチについては AI agent documentation
- agent を外部 tool server に接続するには MCP
- AI Functions を使用する完全な例については AI home maintenance tutorial