メインコンテンツまでスキップ

AI functions(AI 関数)

会話中に呼び出せるカスタム backend logic で AI agents を拡張します。

AI Functions を使う理由

AI agent は、質問に答えるだけでは不十分です。注文ステータスの検索、database の更新、domain-specific calculation の実行、external API の呼び出しが必要になることがあります。モデルは、それらを単独で行う方法を知りません。

AI functions がない場合、agent はモデルがすでに知っていることに制限されます。AI functions があれば、会話で必要になったときに agent が backend code を呼び出せます。

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 は、リクエストごとに渡す代わりに、Agent Studio または setAgentOptionInPath を使って agent に永続的に追加することもできます。

Agent は function の description を読み、いつ呼び出すかを判断し、その結果を response に組み込みます。

概要

AI functions は、@aiFunction で decorate された Squid Service 内の methods です。AI agent に attach すると、agent は function の description と user prompt に基づいて、会話中にそれらを呼び出せます。

AI functions を使うタイミング

Use CaseRecommendation
Agent が custom server-side logic を呼び出す必要があるAI function
Agent が connected database に query または write する必要があるdatabase connector を使うか、custom query logic 用に AI function を使う
Agent が connected service(例: CRM、calendar、API)にアクセスする必要があるconnected integration を使う
Agent が MCP 経由で external tools に接続する必要があるMCP を使う
Agent が uploaded documents を検索する必要があるKnowledge Bases を使う

仕組み

  1. SquidService を extends する class 内の method を @aiFunction で decorate します
  2. SDK または console の Agent Studio を通じて、function を名前で agent に attach します
  3. User が message を送信すると、agent は function の description を prompt と照合して評価します
  4. Agent が function を relevant と判断した場合、AI-generated parameter values で function を呼び出します
  5. Function が backend で実行され、string result を返します
  6. Agent がその result を response に組み込みます

Quick Start

前提条件

  • squid init で初期化された Squid backend project
  • NPM から installed された @squidcloud/backend package
  • 作成済みの AI agent

Step 1: AI function を作成する

Squid Service 内に @aiFunction decorator を付けた method を追加します。

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: Backend を deploy する

Local development では、次を実行します。

squid start

Cloud に deploy するには、deploying your backend を参照してください。

Deployment 後、function は console の Agent Studio の Abilities に表示されます。

Step 3: Function で agent を呼び出す

Client code
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 を呼び出し、その result を response に含めます。

setAgentOptionInPath を使って functions を agent に永続的に追加することもできるため、毎回 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

@aiFunction decorator

Decorator は 2 つの required arguments を受け取ります。

  1. description(string): この function をいつ呼び出すべきかを agent に伝えます。曖昧な label ではなく、明確な instruction として書いてください。
  2. params(array): Function を呼び出すときに agent が提供すべき parameters を定義します。
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> {
// ...
}

または、decorator に単一の 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

params array 内の各 parameter は、次の fields をサポートします。

FieldTypeRequiredDescription
namestringYesParameter name。function の params object 内の key と一致します
descriptionstringYesAgent に提供すべき value を伝えます
typestringYesData type: 'string''number''boolean' など
requiredbooleanYesAgent がこの value を必ず提供する必要があるかどうか
enumany[]NoValue を allowed options の set に制限します
affinitystringNo'pod' または 'worker'。この parameter を function の affinity key としてマークし、routing の sticky さを指定します。この parameter の value が一致する calls は、同じ pod または同じ worker に route されます。affinity による Sticky routing を参照してください

有効な選択肢が特定の options のみに限られる場合は、enum を使って values を制約します。

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;
// Persist the section using your own data layer.
await this.writeSection('my-doc', sectionId, content);
return 'Section saved: ' + sectionId;
}

affinity による Sticky routing

ほとんどの AI functions は stateless なので、どの worker でもどの call でも処理できます。function が warm cache、live session、または spawn した child process のように request の外側で state を保持する場合、その state を識別する parameter に affinity を指定します。同じ value を持つすべての call は、その state が存続している間、同じ場所に route されます。

  • 'pod': 同じ pod の任意の worker が state に到達できます。例として、disk 上の file や child process があります。
  • 'worker': state は 1 つの worker の memory に存在するため、その worker 自体が再利用されます。

affinity と後述の live-state hooks は TypeScript backend SDK 向けに記載されています。そのため、このセクションの examples には Python tab がありません。

Backend code
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();
}
}

1 つの function で affinity を宣言できるのは 1 つの string parameter のみであり、それ以外の type の parameter に宣言すると bundle build は失敗します。undefinednull、または '' の value は affinity がまったくないことを意味し、call は通常どおり route されます。blank は意図的に key ではないため、parameter を空のままにした caller は、同じく空のままにした他のすべての caller と共有するのではなく、自分用の instance を取得します。

value は、application、function の service、calling job 内の namespace であり、calling job がない caller では conversation に fallback します。同じ value を選んだ 2 つの agent requests であっても独立して route されるため、value は 1 つの request 自身の calls 内で一意であれば十分です。2 つの caller が value を合わせることで意図的に 1 つの instance を共有することはできません。本当に shared state が必要な function には、代わりに shared storage が必要です。namespace は単一の function ではなく service であるため、同じ service の companion functions は互いの state に到達します。

affinity が保証するのは routing であり、durability ではありません。pod または worker がなくなると、そこに保持されていたものも失われ、次の call は新しい場所に到達します。それが存続すると仮定するのではなく、function 内で検出して state を rebuild してください。

worker を alive に保つ

idle worker は通常、数分後に tear down されます。これは stateless service では見えませんが、live session や child process を保持する service には破壊的です。そのような state が存在する間は service の hasLiveState() を override して報告し、runtime が worker を terminate するときにそれを解放するために releaseLiveState() を override します。上記の InterpreterService の sessions が alive に保たれるのはこの仕組みによるものです。hasLiveState() は map が interpreter を保持している間は報告し続け、releaseLiveState() は worker が最終的に recycle されるときにそれらを dispose します。

true を無期限に返すと、pod が生きている間 worker が pin されるため、state 自体の lifetime を tracking してください。

Return values

AI functions は Promise<string>(TypeScript)または str(Python)を返す必要があります。Agent はこの string を受け取り、それを使って response を組み立てます。明確で簡潔な 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 を使うと、AI function を特定の connector type に bind し、その connector の built-in capabilities を独自の custom logic で拡張できます。その type の connector を agent に追加すると、function は connector の default behavior と一緒に自動的に含まれます。

たとえば、database connector はすでに agent が AI で data を query できるようにします。しかし、agent に特定の方法で一貫して query させたい場合は、その connector type に attributed された AI function を書けます。

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.
}

PostgreSQL connector を agent に追加すると、この function は自動的に含まれます。

注記

Connector type に attributed された functions は、Agent Studio の AI Functions list には表示されません。Agent への inclusion は connector によって処理されます。

Categories

Categories を使うと、Agent Studio 内で AI functions を group 化し、整理しやすくできます。

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.
}

これにより、getProductReviewsAI Functions list 内の "Data Gathering" category の下に表示されます。複数の category を指定でき、function はそれぞれの下に表示されます。

Parameter values の override

Agent によっては、AI に判断させるのではなく、特定の parameter values を固定したい場合があります。predefinedParameters を使って parameters を override できます。AI は overridden parameters の存在を認識せず、その values を設定できません。

たとえば、先ほどの saveSection function を使って、introduction section だけを更新する agent を作成できます。

chat widget 経由:

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

SDK 経由:

Client code
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 の passing

AI-generated params に加えて、context object を使って独自の values を AI functions に渡すことができます。これは、document IDs、user preferences、security-related values など、AI が control すべきでない data に便利です。

Context には 2 つの scopes があります。

  • agentContext: Agent によって行われるすべての AI function call に渡されます。ctx.agentContext 経由で access します。
  • functionContext: 特定の function のみに渡されます。ctx.functionContext 経由で access します。

saveSection function を基に、document ID を agent level で設定し、保存前に content から internal codenames を redact したいとします。

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> {
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;
}
}

これらの context values を client から 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>

または 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'] },
},
],
});

この場合、"LITANIA" が codename list に含まれているため、保存される content には "REDACTED has determined the answer to be 42" が含まれます。

Error Handling

AI function が error を throw すると、agent は error message を受け取り、それを user に伝えるか、次にどう進めるかを判断できます。明確で説明的な errors を throw してください。

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}`;
}

よくある問題

IssueCauseSolution
Function がまったく呼び出されないDescription が user prompts と一致していないFunction をいつ呼び出すかが明確になるように description を書き直す
Parameter values が誤っているParameter descriptions が曖昧具体的な descriptions を追加し、enum で values を制約する
Agent が function を見つけられないFunction name が functions option に渡されていないask call で function name を渡すか、Agent Studio で追加する
Function が Agent Studio に表示されないFunction が cloud に deployed されていないsquid deploy で backend を deploy する

Best Practices

明確な descriptions を書く

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'

Parameters を慎重に設計する

  • Function が動作するために不可欠な values にのみ required: true を使う
  • Valid values が既知の set である場合は enum を設定する
  • Agent に使用すべき format を伝える parameter descriptions を書く(例: "ISO8601 date"、"email address")

Return values を有益に保つ

Agent は return string を使って response を組み立てます。Raw data structures ではなく、人間が読める information を返してください。

Inputs を validate する

AI が parameter values を生成する場合でも、function 内で validate してください。AI は予期しない 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');
}
// ...
return 'Quantity updated';
}

Next Steps