BaseAgentClass
AI ์์ด์ ํธ์ ๋ฒ ์ด์ค ํด๋์ค์ ๋๋ค.๊ธฐ๋ณธ ๊ตฌ์กฐ
๋ณต์ฌ
import { BaseAgentClass, tools } from "sonamu/ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
class CustomerSupportAgentClass extends BaseAgentClass<{
userId: number;
}> {
constructor() {
super('CustomerSupportAgent');
}
@tools({
description: "์ฃผ๋ฌธ ์ ๋ณด๋ฅผ ์กฐํํฉ๋๋ค",
schema: {
input: z.object({
orderId: z.number(),
}),
output: z.object({
orderId: z.number(),
status: z.string(),
items: z.array(z.string()),
}),
}
})
async getOrder(input: { orderId: number }) {
// ์ฃผ๋ฌธ ์กฐํ ๋ก์ง
return {
orderId: input.orderId,
status: 'shipped',
items: ['Product A', 'Product B'],
};
}
}
export const CustomerSupportAgent = new CustomerSupportAgentClass();
@tools ๋ฐ์ฝ๋ ์ดํฐ
๋๊ตฌ๋ฅผ ์ ์ํ๋ ๋ฐ์ฝ๋ ์ดํฐ์ ๋๋ค.๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
๋ณต์ฌ
@tools({
description: "๋๊ตฌ ์ค๋ช
",
schema: {
input: z.object({
// ์
๋ ฅ ์คํค๋ง
}),
output: z.object({
// ์ถ๋ ฅ ์คํค๋ง (์ ํ)
}),
}
})
async toolMethod(input: InputType) {
// ๋๊ตฌ ๋ก์ง
return output;
}
์ต์
- description
- schema
- name
- needsApproval
๋๊ตฌ ์ค๋ช
(ํ์)์ค์: LLM์ด ์ด ์ค๋ช
์ ์ฝ๊ณ ๋๊ตฌ ์ฌ์ฉ ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํฉ๋๋ค.
๋ณต์ฌ
@tools({
description: "์ฌ์ฉ์์ ์ฃผ๋ฌธ ๋ด์ญ์ ์กฐํํฉ๋๋ค. ์ฃผ๋ฌธ ID๋ฅผ ์
๋ ฅ๋ฐ์ ์ฃผ๋ฌธ ์ ๋ณด๋ฅผ ๋ฐํํฉ๋๋ค.",
schema: { /* ... */ }
})
์
์ถ๋ ฅ ์คํค๋ง (ํ์)input: ํ์
output: ์ ํ (ํ์
์ถ๋ก ์ ๋์)
๋ณต์ฌ
@tools({
description: "์ฃผ๋ฌธ ์กฐํ",
schema: {
input: z.object({
orderId: z.number().describe("์ฃผ๋ฌธ ID"),
}),
output: z.object({
status: z.string(),
total: z.number(),
}),
}
})
์ปค์คํ
์ด๋ฆ (์ ํ)
๋ณต์ฌ
@tools({
name: 'order_lookup', // ๊ธฐ๋ณธ๊ฐ: ๋ฉ์๋ ์ด๋ฆ
description: "์ฃผ๋ฌธ ์กฐํ",
schema: { /* ... */ }
})
์น์ธ ํ์ (์ ํ)์ฉ๋: ๋ฐ์ดํฐ ์ญ์ , ๊ฒฐ์ ๋ฑ ์ํํ ์์
๋ณต์ฌ
@tools({
description: "์ฃผ๋ฌธ ์ทจ์ (์ํํ ์์
)",
schema: { /* ... */ },
needsApproval: true, // ์ฌ์ฉ์ ์น์ธ ํ์
})
์ค์ ์์
1. ํธํ ์์ฝ ์์ด์ ํธ
๋ณต์ฌ
import { BaseAgentClass, tools } from "sonamu/ai";
import { z } from "zod";
class HotelBookingAgentClass extends BaseAgentClass<{
userId: number;
locale: string;
}> {
constructor() {
super('HotelBookingAgent');
}
@tools({
description: "๋์์ ๋ ์ง๋ก ์ฌ์ฉ ๊ฐ๋ฅํ ํธํ
์ ๊ฒ์ํฉ๋๋ค",
schema: {
input: z.object({
city: z.string().describe("๋์ ์ด๋ฆ"),
checkIn: z.string().describe("์ฒดํฌ์ธ ๋ ์ง (YYYY-MM-DD)"),
checkOut: z.string().describe("์ฒดํฌ์์ ๋ ์ง (YYYY-MM-DD)"),
guests: z.number().describe("ํฌ์๊ฐ ์"),
}),
output: z.array(z.object({
hotelId: z.number(),
name: z.string(),
price: z.number(),
rating: z.number(),
})),
}
})
async searchHotels(input: {
city: string;
checkIn: string;
checkOut: string;
guests: number;
}) {
// DB์์ ํธํ
๊ฒ์
const hotels = await HotelModel.findMany({
wq: [
['city', input.city],
['available_from', '<=', input.checkIn],
['available_to', '>=', input.checkOut],
['capacity', '>=', input.guests],
],
});
return hotels.data.map(hotel => ({
hotelId: hotel.id,
name: hotel.name,
price: hotel.price_per_night,
rating: hotel.rating,
}));
}
@tools({
description: "ํธํ
์ ์์ฝํฉ๋๋ค",
schema: {
input: z.object({
hotelId: z.number().describe("ํธํ
ID"),
checkIn: z.string(),
checkOut: z.string(),
guests: z.number(),
}),
output: z.object({
bookingId: z.number(),
confirmationNumber: z.string(),
totalPrice: z.number(),
}),
},
needsApproval: true, // ์์ฝ ์ ์ฌ์ฉ์ ์น์ธ
})
async bookHotel(input: {
hotelId: number;
checkIn: string;
checkOut: string;
guests: number;
}) {
const userId = this.store?.userId;
// ์์ฝ ์์ฑ
const booking = await BookingModel.saveOne({
user_id: userId,
hotel_id: input.hotelId,
check_in: input.checkIn,
check_out: input.checkOut,
guests: input.guests,
status: 'confirmed',
});
return {
bookingId: booking.id,
confirmationNumber: booking.confirmation_number,
totalPrice: booking.total_price,
};
}
@tools({
description: "์์ฝ์ ์ทจ์ํฉ๋๋ค",
schema: {
input: z.object({
bookingId: z.number().describe("์์ฝ ID"),
}),
output: z.object({
success: z.boolean(),
refundAmount: z.number(),
}),
},
needsApproval: true, // ์ทจ์ ์ ์ฌ์ฉ์ ์น์ธ
})
async cancelBooking(input: { bookingId: number }) {
const booking = await BookingModel.findById(input.bookingId);
// ์ทจ์ ์ฒ๋ฆฌ
await BookingModel.updateOne(['id', booking.id], {
status: 'cancelled',
});
// ํ๋ถ ์ฒ๋ฆฌ
const refundAmount = booking.total_price * 0.9; // 10% ์์๋ฃ
return {
success: true,
refundAmount,
};
}
}
export const HotelBookingAgent = new HotelBookingAgentClass();
2. ๋ฐ์ดํฐ ๋ถ์ ์์ด์ ํธ
๋ณต์ฌ
import { BaseAgentClass, tools } from "sonamu/ai";
import { z } from "zod";
class DataAnalystAgentClass extends BaseAgentClass<{
sessionId: string;
}> {
constructor() {
super('DataAnalystAgent');
}
@tools({
description: "SQL ์ฟผ๋ฆฌ๋ฅผ ์คํํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์กฐํํฉ๋๋ค",
schema: {
input: z.object({
query: z.string().describe("์คํํ SQL ์ฟผ๋ฆฌ"),
}),
output: z.object({
rows: z.array(z.record(z.any())),
rowCount: z.number(),
}),
}
})
async executeQuery(input: { query: string }) {
// ์์ ์ฑ ๊ฒ์ฆ (SELECT๋ง ํ์ฉ)
if (!/^SELECT/i.test(input.query.trim())) {
throw new Error('SELECT ์ฟผ๋ฆฌ๋ง ํ์ฉ๋ฉ๋๋ค');
}
const result = await DB.query(input.query);
return {
rows: result.rows,
rowCount: result.rowCount,
};
}
@tools({
description: "๋ฐ์ดํฐ๋ฅผ ์๊ฐํํ๊ธฐ ์ํ ์ฐจํธ ์ค์ ์ ์์ฑํฉ๋๋ค",
schema: {
input: z.object({
data: z.array(z.record(z.any())),
xAxis: z.string().describe("X์ถ ์ปฌ๋ผ๋ช
"),
yAxis: z.string().describe("Y์ถ ์ปฌ๋ผ๋ช
"),
chartType: z.enum(['bar', 'line', 'pie']),
}),
output: z.object({
chartId: z.string(),
config: z.any(),
}),
}
})
async createChart(input: {
data: Array<Record<string, any>>;
xAxis: string;
yAxis: string;
chartType: 'bar' | 'line' | 'pie';
}) {
const chartId = `chart_${Date.now()}`;
// ์ฐจํธ ์ค์ ์์ฑ
const config = {
type: input.chartType,
data: {
labels: input.data.map(row => row[input.xAxis]),
datasets: [{
label: input.yAxis,
data: input.data.map(row => row[input.yAxis]),
}],
},
};
return { chartId, config };
}
@tools({
description: "ํต๊ณ ์์ฝ์ ๊ณ์ฐํฉ๋๋ค (ํ๊ท , ์ค์๊ฐ, ํ์คํธ์ฐจ ๋ฑ)",
schema: {
input: z.object({
data: z.array(z.number()),
}),
output: z.object({
mean: z.number(),
median: z.number(),
stdDev: z.number(),
min: z.number(),
max: z.number(),
}),
}
})
async calculateStats(input: { data: number[] }) {
const sorted = [...input.data].sort((a, b) => a - b);
const sum = input.data.reduce((a, b) => a + b, 0);
const mean = sum / input.data.length;
const median = sorted.length % 2 === 0
? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2
: sorted[Math.floor(sorted.length / 2)];
const variance = input.data.reduce((acc, val) =>
acc + Math.pow(val - mean, 2), 0) / input.data.length;
const stdDev = Math.sqrt(variance);
return {
mean,
median,
stdDev,
min: sorted[0],
max: sorted[sorted.length - 1],
};
}
}
export const DataAnalystAgent = new DataAnalystAgentClass();
3. ์ด๋ฉ์ผ ์๋ํ ์์ด์ ํธ
๋ณต์ฌ
import { BaseAgentClass, tools } from "sonamu/ai";
import { z } from "zod";
class EmailAutomationAgentClass extends BaseAgentClass<{
userId: number;
}> {
constructor() {
super('EmailAutomationAgent');
}
@tools({
description: "๋ฐ์ ํธ์งํจ์์ ์ด๋ฉ์ผ์ ๊ฒ์ํฉ๋๋ค",
schema: {
input: z.object({
query: z.string().describe("๊ฒ์ ์ฟผ๋ฆฌ"),
limit: z.number().default(10),
}),
output: z.array(z.object({
id: z.number(),
from: z.string(),
subject: z.string(),
preview: z.string(),
receivedAt: z.string(),
})),
}
})
async searchEmails(input: { query: string; limit: number }) {
const emails = await EmailModel.findMany({
wq: [
['user_id', this.store?.userId],
['subject', 'LIKE', `%${input.query}%`],
],
num: input.limit,
order: [['received_at', 'DESC']],
});
return emails.data.map(email => ({
id: email.id,
from: email.from_address,
subject: email.subject,
preview: email.body.substring(0, 100),
receivedAt: email.received_at.toISOString(),
}));
}
@tools({
description: "์ด๋ฉ์ผ์ ํน์ ํด๋๋ก ์ด๋ํฉ๋๋ค",
schema: {
input: z.object({
emailId: z.number(),
folder: z.enum(['inbox', 'archive', 'trash', 'spam']),
}),
output: z.object({
success: z.boolean(),
}),
}
})
async moveEmail(input: { emailId: number; folder: string }) {
await EmailModel.updateOne(['id', input.emailId], {
folder: input.folder,
});
return { success: true };
}
@tools({
description: "์ด๋ฉ์ผ์ ๋ผ๋ฒจ์ ์ถ๊ฐํฉ๋๋ค",
schema: {
input: z.object({
emailId: z.number(),
labels: z.array(z.string()),
}),
output: z.object({
success: z.boolean(),
}),
}
})
async addLabels(input: { emailId: number; labels: string[] }) {
const email = await EmailModel.findById(input.emailId);
const currentLabels = email.labels || [];
await EmailModel.updateOne(['id', input.emailId], {
labels: [...currentLabels, ...input.labels],
});
return { success: true };
}
@tools({
description: "์ด๋ฉ์ผ์ ๋ณด๋
๋๋ค",
schema: {
input: z.object({
to: z.string().email(),
subject: z.string(),
body: z.string(),
}),
output: z.object({
messageId: z.string(),
sentAt: z.string(),
}),
},
needsApproval: true, // ์ ์ก ์ ์น์ธ
})
async sendEmail(input: {
to: string;
subject: string;
body: string;
}) {
// ์ด๋ฉ์ผ ์ ์ก ๋ก์ง
const result = await sendEmailService(input);
return {
messageId: result.messageId,
sentAt: new Date().toISOString(),
};
}
}
export const EmailAutomationAgent = new EmailAutomationAgentClass();
์์ด์ ํธ ์ฌ์ฉํ๊ธฐ
use() ๋ฉ์๋
์์ด์ ํธ๋ฅผ ์คํํ๋ ๋ฉ์๋์ ๋๋ค.๋ณต์ฌ
import { HotelBookingAgent } from "./agents/hotel-booking";
import { openai } from "@ai-sdk/openai";
const result = await HotelBookingAgent.use(
{
model: openai('gpt-4o'),
instructions: "๋น์ ์ ํธํ
์์ฝ ๋์ฐ๋ฏธ์
๋๋ค.",
toolChoice: 'auto',
},
{ userId: 123, locale: 'ko' }, // store ์ด๊ธฐ๊ฐ
async (agent) => {
// ์์ด์ ํธ ์คํ
const response = await agent.generateText({
prompt: "์์ธ์์ ๋ด์ผ๋ถํฐ 3์ผ๊ฐ ๋ฌต์ ํธํ
์ ์ฐพ์์ค",
});
return response.text;
}
);
API์์ ์ฌ์ฉ
๋ณต์ฌ
import { BaseModel, api } from "sonamu";
import { HotelBookingAgent } from "./agents/hotel-booking";
import { openai } from "@ai-sdk/openai";
class ChatModelClass extends BaseModel {
@api({ httpMethod: 'POST' })
async chat(message: string, ctx: Context) {
const result = await HotelBookingAgent.use(
{
model: openai('gpt-4o'),
instructions: "๋น์ ์ ํธํ
์์ฝ ๋์ฐ๋ฏธ์
๋๋ค.",
toolChoice: 'auto',
},
{ userId: ctx.user.id, locale: ctx.locale || 'ko' },
async (agent) => {
const response = await agent.generateText({
prompt: message,
});
return response;
}
);
return {
text: result.text,
toolCalls: result.toolCalls,
};
}
}
Store (์ํ ๊ด๋ฆฌ)
์์ด์ ํธ์ ์ํ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.์ ์
๋ณต์ฌ
class MyAgentClass extends BaseAgentClass<{
userId: number;
sessionId: string;
preferences: {
language: string;
timezone: string;
};
}> {
// ...
}
์ฌ์ฉ
๋ณต์ฌ
@tools({
description: "์ฌ์ฉ์ ํ๋กํ ์กฐํ",
schema: {
input: z.object({}),
output: z.object({
userId: z.number(),
name: z.string(),
}),
}
})
async getUserProfile() {
// store ์ ๊ทผ
const userId = this.store?.userId;
const language = this.store?.preferences.language;
const user = await UserModel.findById(userId);
return {
userId: user.id,
name: user.name,
};
}
์ฃผ์์ฌํญ
Agent ์์ฑ ์ ์ฃผ์์ฌํญ:
-
๋๊ตฌ ์ค๋ช
๋ช
ํ์ฑ: LLM์ด ์ดํดํ ์ ์๋๋ก ์์ธํ ์์ฑ
๋ณต์ฌ
// โ ๋ถ๋ช ํ description: "์ฃผ๋ฌธ ์กฐํ" // โ ๋ช ํ description: "์ฃผ๋ฌธ ID๋ฅผ ์ ๋ ฅ๋ฐ์ ํด๋น ์ฃผ๋ฌธ์ ์์ธ ์ ๋ณด(์ํ, ์์ดํ , ๊ฐ๊ฒฉ)๋ฅผ ๋ฐํํฉ๋๋ค" -
์
๋ ฅ ์คํค๋ง ์์ธ ์ค๋ช
: describe() ์ฌ์ฉ
๋ณต์ฌ
input: z.object({ orderId: z.number().describe("์กฐํํ ์ฃผ๋ฌธ์ ๊ณ ์ ID"), includeItems: z.boolean().describe("์ฃผ๋ฌธ ์์ดํ ๋ชฉ๋ก ํฌํจ ์ฌ๋ถ"), }) -
์ํํ ์์
์ ์น์ธ ํ์: needsApproval
๋ณต์ฌ
@tools({ description: "์ฃผ๋ฌธ ์ทจ์", schema: { /* ... */ }, needsApproval: true, // ํ์! }) -
์๋ฌ ์ฒ๋ฆฌ: ๋ช
ํํ ์๋ฌ ๋ฉ์์ง
๋ณต์ฌ
async getOrder(input: { orderId: number }) { const order = await OrderModel.findById(input.orderId); if (!order) { throw new Error(`์ฃผ๋ฌธ ID ${input.orderId}๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค`); } return order; } -
store ์ ๊ทผ ์ undefined ์ฒดํฌ
๋ณต์ฌ
const userId = this.store?.userId; if (!userId) { throw new Error('์ฌ์ฉ์ ์ ๋ณด๊ฐ ์์ต๋๋ค'); } -
๋๊ตฌ ์ด๋ฆ ์ถฉ๋ ๋ฐฉ์ง: ๋ช
ํํ ์ด๋ฆ ์ฌ์ฉ
๋ณต์ฌ
@tools({ name: 'hotel_search', // ๋ช ํํ ์ด๋ฆ // ... })