@kood/claude-code 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +129 -5
- package/package.json +2 -2
- package/templates/hono/CLAUDE.md +20 -2
- package/templates/hono/docs/architecture/architecture.md +909 -0
- package/templates/hono/docs/commands/git.md +275 -0
- package/templates/hono/docs/deployment/cloudflare.md +527 -190
- package/templates/hono/docs/deployment/docker.md +514 -0
- package/templates/hono/docs/deployment/index.md +179 -214
- package/templates/hono/docs/deployment/railway.md +416 -0
- package/templates/hono/docs/deployment/vercel.md +567 -0
- package/templates/hono/docs/library/ai-sdk/index.md +427 -0
- package/templates/hono/docs/library/ai-sdk/openrouter.md +479 -0
- package/templates/hono/docs/library/ai-sdk/providers.md +468 -0
- package/templates/hono/docs/library/ai-sdk/streaming.md +447 -0
- package/templates/hono/docs/library/ai-sdk/structured-output.md +493 -0
- package/templates/hono/docs/library/ai-sdk/tools.md +513 -0
- package/templates/hono/docs/library/hono/env-setup.md +458 -0
- package/templates/hono/docs/library/hono/index.md +1 -3
- package/templates/hono/docs/library/pino/index.md +437 -0
- package/templates/hono/docs/library/prisma/cloudflare-d1.md +503 -0
- package/templates/hono/docs/library/prisma/config.md +362 -0
- package/templates/hono/docs/library/prisma/index.md +86 -13
- package/templates/hono/docs/skills/gemini-review/SKILL.md +116 -116
- package/templates/hono/docs/skills/gemini-review/references/checklists.md +125 -125
- package/templates/hono/docs/skills/gemini-review/references/prompt-templates.md +191 -191
- package/templates/npx/CLAUDE.md +309 -0
- package/templates/npx/docs/commands/git.md +275 -0
- package/templates/npx/docs/library/commander/index.md +164 -0
- package/templates/npx/docs/library/fs-extra/index.md +171 -0
- package/templates/npx/docs/library/prompts/index.md +253 -0
- package/templates/npx/docs/mcp/index.md +60 -0
- package/templates/npx/docs/skills/gemini-review/SKILL.md +220 -0
- package/templates/npx/docs/skills/gemini-review/references/checklists.md +134 -0
- package/templates/npx/docs/skills/gemini-review/references/prompt-templates.md +301 -0
- package/templates/tanstack-start/CLAUDE.md +43 -5
- package/templates/tanstack-start/docs/architecture/architecture.md +134 -4
- package/templates/tanstack-start/docs/commands/git.md +275 -0
- package/templates/tanstack-start/docs/deployment/cloudflare.md +223 -50
- package/templates/tanstack-start/docs/deployment/index.md +320 -30
- package/templates/tanstack-start/docs/deployment/nitro.md +195 -14
- package/templates/tanstack-start/docs/deployment/railway.md +302 -150
- package/templates/tanstack-start/docs/deployment/vercel.md +345 -75
- package/templates/tanstack-start/docs/guides/best-practices.md +203 -1
- package/templates/tanstack-start/docs/guides/env-setup.md +450 -0
- package/templates/tanstack-start/docs/library/ai-sdk/hooks.md +472 -0
- package/templates/tanstack-start/docs/library/ai-sdk/index.md +264 -0
- package/templates/tanstack-start/docs/library/ai-sdk/openrouter.md +371 -0
- package/templates/tanstack-start/docs/library/ai-sdk/providers.md +403 -0
- package/templates/tanstack-start/docs/library/ai-sdk/streaming.md +320 -0
- package/templates/tanstack-start/docs/library/ai-sdk/structured-output.md +454 -0
- package/templates/tanstack-start/docs/library/ai-sdk/tools.md +473 -0
- package/templates/tanstack-start/docs/library/pino/index.md +320 -0
- package/templates/tanstack-start/docs/library/prisma/cloudflare-d1.md +404 -0
- package/templates/tanstack-start/docs/library/prisma/config.md +377 -0
- package/templates/tanstack-start/docs/library/prisma/index.md +3 -5
- package/templates/tanstack-start/docs/library/prisma/schema.md +123 -25
- package/templates/tanstack-start/docs/library/prisma/setup.md +0 -7
- package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +80 -2
- package/templates/tanstack-start/docs/skills/gemini-review/SKILL.md +116 -116
- package/templates/tanstack-start/docs/skills/gemini-review/references/checklists.md +138 -144
- package/templates/tanstack-start/docs/skills/gemini-review/references/prompt-templates.md +186 -187
- package/templates/hono/docs/git/index.md +0 -180
- package/templates/tanstack-start/docs/git/index.md +0 -203
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
# AI SDK - Tool Calling
|
|
2
|
+
|
|
3
|
+
> **상위 문서**: [AI SDK](./index.md)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 개요
|
|
8
|
+
|
|
9
|
+
AI SDK의 도구(Tool)를 사용하면 AI 모델이 외부 함수를 호출할 수 있습니다. 날씨 조회, 데이터베이스 검색, API 호출 등 다양한 작업을 수행할 수 있습니다.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 기본 도구 정의
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { generateText, tool } from 'ai'
|
|
17
|
+
import { openai } from '@ai-sdk/openai'
|
|
18
|
+
import { z } from 'zod'
|
|
19
|
+
|
|
20
|
+
const { text, toolCalls, toolResults } = await generateText({
|
|
21
|
+
model: openai('gpt-4o'),
|
|
22
|
+
prompt: 'What is the weather in Seoul?',
|
|
23
|
+
tools: {
|
|
24
|
+
getWeather: tool({
|
|
25
|
+
description: 'Get the weather for a location',
|
|
26
|
+
inputSchema: z.object({
|
|
27
|
+
location: z.string().describe('The city name'),
|
|
28
|
+
}),
|
|
29
|
+
execute: async ({ location }) => {
|
|
30
|
+
// 실제 날씨 API 호출
|
|
31
|
+
return {
|
|
32
|
+
location,
|
|
33
|
+
temperature: 22,
|
|
34
|
+
condition: 'Sunny',
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
}),
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## tool() 함수 구조
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { tool } from 'ai'
|
|
48
|
+
import { z } from 'zod'
|
|
49
|
+
|
|
50
|
+
const myTool = tool({
|
|
51
|
+
// 도구 설명 (모델이 언제 사용할지 결정하는데 사용)
|
|
52
|
+
description: 'Tool description for the AI model',
|
|
53
|
+
|
|
54
|
+
// 입력 스키마 (Zod로 정의)
|
|
55
|
+
inputSchema: z.object({
|
|
56
|
+
param1: z.string().describe('Parameter description'),
|
|
57
|
+
param2: z.number().optional(),
|
|
58
|
+
}),
|
|
59
|
+
|
|
60
|
+
// 실행 함수
|
|
61
|
+
execute: async (args, context) => {
|
|
62
|
+
// args: 스키마에 맞는 입력 값
|
|
63
|
+
// context: { toolCallId, messages } 등 컨텍스트 정보
|
|
64
|
+
return { result: 'success' }
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 여러 도구 정의
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { generateText, tool } from 'ai'
|
|
75
|
+
import { openai } from '@ai-sdk/openai'
|
|
76
|
+
import { z } from 'zod'
|
|
77
|
+
|
|
78
|
+
const result = await generateText({
|
|
79
|
+
model: openai('gpt-4o'),
|
|
80
|
+
prompt: 'What is the weather in Seoul and what attractions should I visit?',
|
|
81
|
+
tools: {
|
|
82
|
+
getWeather: tool({
|
|
83
|
+
description: 'Get the weather for a location',
|
|
84
|
+
inputSchema: z.object({
|
|
85
|
+
location: z.string(),
|
|
86
|
+
}),
|
|
87
|
+
execute: async ({ location }) => ({
|
|
88
|
+
temperature: 22,
|
|
89
|
+
condition: 'Sunny',
|
|
90
|
+
}),
|
|
91
|
+
}),
|
|
92
|
+
|
|
93
|
+
getAttractions: tool({
|
|
94
|
+
description: 'Get tourist attractions for a city',
|
|
95
|
+
inputSchema: z.object({
|
|
96
|
+
city: z.string(),
|
|
97
|
+
}),
|
|
98
|
+
execute: async ({ city }) => ({
|
|
99
|
+
attractions: ['Gyeongbokgung Palace', 'N Seoul Tower', 'Bukchon Hanok Village'],
|
|
100
|
+
}),
|
|
101
|
+
}),
|
|
102
|
+
},
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
console.log(result.text)
|
|
106
|
+
console.log(result.toolResults)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 도구 호출 결과 접근
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
const result = await generateText({
|
|
115
|
+
model: openai('gpt-4o'),
|
|
116
|
+
prompt: 'Get the weather in Tokyo',
|
|
117
|
+
tools: { /* ... */ },
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// 도구 호출 목록
|
|
121
|
+
for (const toolCall of result.toolCalls) {
|
|
122
|
+
console.log('Tool:', toolCall.toolName)
|
|
123
|
+
console.log('Input:', toolCall.input)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 도구 실행 결과
|
|
127
|
+
for (const toolResult of result.toolResults) {
|
|
128
|
+
console.log('Tool:', toolResult.toolName)
|
|
129
|
+
console.log('Output:', toolResult.output)
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 스트리밍에서 도구 사용
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { streamText, tool } from 'ai'
|
|
139
|
+
import { openai } from '@ai-sdk/openai'
|
|
140
|
+
import { z } from 'zod'
|
|
141
|
+
|
|
142
|
+
const result = streamText({
|
|
143
|
+
model: openai('gpt-4o'),
|
|
144
|
+
prompt: 'What is the weather?',
|
|
145
|
+
tools: {
|
|
146
|
+
getWeather: tool({
|
|
147
|
+
description: 'Get weather',
|
|
148
|
+
inputSchema: z.object({ location: z.string() }),
|
|
149
|
+
execute: async ({ location }) => ({ temp: 22 }),
|
|
150
|
+
}),
|
|
151
|
+
},
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// fullStream으로 도구 이벤트 처리
|
|
155
|
+
for await (const event of result.fullStream) {
|
|
156
|
+
switch (event.type) {
|
|
157
|
+
case 'text-delta':
|
|
158
|
+
process.stdout.write(event.textDelta)
|
|
159
|
+
break
|
|
160
|
+
case 'tool-call':
|
|
161
|
+
console.log('Tool called:', event.toolName)
|
|
162
|
+
break
|
|
163
|
+
case 'tool-result':
|
|
164
|
+
console.log('Tool result:', event.output)
|
|
165
|
+
break
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## API Route에서 도구 사용
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// app/api/chat/route.ts
|
|
176
|
+
import { streamText, tool, convertToModelMessages } from 'ai'
|
|
177
|
+
import { openai } from '@ai-sdk/openai'
|
|
178
|
+
import { z } from 'zod'
|
|
179
|
+
|
|
180
|
+
export async function POST(req: Request) {
|
|
181
|
+
const { messages } = await req.json()
|
|
182
|
+
|
|
183
|
+
const result = streamText({
|
|
184
|
+
model: openai('gpt-4o'),
|
|
185
|
+
messages: convertToModelMessages(messages),
|
|
186
|
+
tools: {
|
|
187
|
+
getWeather: tool({
|
|
188
|
+
description: 'Get weather for a location',
|
|
189
|
+
inputSchema: z.object({
|
|
190
|
+
location: z.string(),
|
|
191
|
+
}),
|
|
192
|
+
execute: async ({ location }) => {
|
|
193
|
+
// 날씨 API 호출
|
|
194
|
+
const weather = await fetchWeather(location)
|
|
195
|
+
return weather
|
|
196
|
+
},
|
|
197
|
+
}),
|
|
198
|
+
searchDatabase: tool({
|
|
199
|
+
description: 'Search the database',
|
|
200
|
+
inputSchema: z.object({
|
|
201
|
+
query: z.string(),
|
|
202
|
+
}),
|
|
203
|
+
execute: async ({ query }) => {
|
|
204
|
+
// 데이터베이스 검색
|
|
205
|
+
const results = await db.search(query)
|
|
206
|
+
return results
|
|
207
|
+
},
|
|
208
|
+
}),
|
|
209
|
+
},
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
return result.toUIMessageStreamResponse()
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## 도구 컨텍스트 접근
|
|
219
|
+
|
|
220
|
+
도구 실행 시 추가 컨텍스트 정보에 접근할 수 있습니다:
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
const result = await generateText({
|
|
224
|
+
model: openai('gpt-4o'),
|
|
225
|
+
prompt: 'Hello',
|
|
226
|
+
tools: {
|
|
227
|
+
myTool: tool({
|
|
228
|
+
description: 'My tool',
|
|
229
|
+
inputSchema: z.object({ input: z.string() }),
|
|
230
|
+
execute: async (args, context) => {
|
|
231
|
+
// 도구 호출 ID
|
|
232
|
+
console.log('Tool call ID:', context.toolCallId)
|
|
233
|
+
|
|
234
|
+
// 전체 메시지 히스토리
|
|
235
|
+
console.log('Messages:', context.messages)
|
|
236
|
+
|
|
237
|
+
return { success: true }
|
|
238
|
+
},
|
|
239
|
+
}),
|
|
240
|
+
},
|
|
241
|
+
})
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 다중 단계 실행
|
|
247
|
+
|
|
248
|
+
AI가 여러 도구를 순차적으로 호출하도록 허용:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import { generateText, stepCountIs } from 'ai'
|
|
252
|
+
|
|
253
|
+
const result = await generateText({
|
|
254
|
+
model: openai('gpt-4o'),
|
|
255
|
+
prompt: 'Plan a trip to Seoul',
|
|
256
|
+
tools: { /* ... */ },
|
|
257
|
+
stopWhen: stepCountIs(5), // 최대 5단계
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
// 각 단계 정보 접근
|
|
261
|
+
for (const step of result.steps) {
|
|
262
|
+
console.log('Step:', step.toolCalls)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 모든 도구 호출 추출
|
|
266
|
+
const allToolCalls = result.steps.flatMap(step => step.toolCalls)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## 도구 호출 복구
|
|
272
|
+
|
|
273
|
+
잘못된 도구 호출을 자동으로 복구:
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
import { generateText, generateObject, NoSuchToolError, tool } from 'ai'
|
|
277
|
+
import { openai } from '@ai-sdk/openai'
|
|
278
|
+
|
|
279
|
+
const result = await generateText({
|
|
280
|
+
model: openai('gpt-4o'),
|
|
281
|
+
prompt: 'Get weather',
|
|
282
|
+
tools: { /* ... */ },
|
|
283
|
+
|
|
284
|
+
experimental_repairToolCall: async ({ toolCall, tools, inputSchema, error }) => {
|
|
285
|
+
// 존재하지 않는 도구면 복구 시도 안함
|
|
286
|
+
if (NoSuchToolError.isInstance(error)) {
|
|
287
|
+
return null
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// 더 강력한 모델로 입력 재생성
|
|
291
|
+
const tool = tools[toolCall.toolName as keyof typeof tools]
|
|
292
|
+
|
|
293
|
+
const { object: repairedArgs } = await generateObject({
|
|
294
|
+
model: openai('gpt-4o'),
|
|
295
|
+
schema: tool.inputSchema,
|
|
296
|
+
prompt: [
|
|
297
|
+
`The tool "${toolCall.toolName}" was called with invalid inputs:`,
|
|
298
|
+
JSON.stringify(toolCall.input),
|
|
299
|
+
'Please fix the inputs according to the schema.',
|
|
300
|
+
].join('\n'),
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
return { ...toolCall, input: JSON.stringify(repairedArgs) }
|
|
304
|
+
},
|
|
305
|
+
})
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## 도구 상태 스트리밍
|
|
311
|
+
|
|
312
|
+
도구 실행 중 상태를 UI로 전송:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
import { streamText, tool, createUIMessageStream, createUIMessageStreamResponse } from 'ai'
|
|
316
|
+
|
|
317
|
+
export async function POST(req: Request) {
|
|
318
|
+
const { messages } = await req.json()
|
|
319
|
+
|
|
320
|
+
const stream = createUIMessageStream({
|
|
321
|
+
execute: ({ writer }) => {
|
|
322
|
+
const result = streamText({
|
|
323
|
+
model: openai('gpt-4o'),
|
|
324
|
+
messages,
|
|
325
|
+
tools: {
|
|
326
|
+
longRunningTask: tool({
|
|
327
|
+
description: 'A long running task',
|
|
328
|
+
inputSchema: z.object({ input: z.string() }),
|
|
329
|
+
execute: async (args, { toolCallId }) => {
|
|
330
|
+
// 진행 상태 전송
|
|
331
|
+
writer.write({
|
|
332
|
+
type: 'data-tool-status',
|
|
333
|
+
id: toolCallId,
|
|
334
|
+
data: { status: 'started' },
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
// 작업 수행...
|
|
338
|
+
await doSomething()
|
|
339
|
+
|
|
340
|
+
writer.write({
|
|
341
|
+
type: 'data-tool-status',
|
|
342
|
+
id: toolCallId,
|
|
343
|
+
data: { status: 'completed' },
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
return { result: 'done' }
|
|
347
|
+
},
|
|
348
|
+
}),
|
|
349
|
+
},
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
writer.merge(result.toUIMessageStream())
|
|
353
|
+
},
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
return createUIMessageStreamResponse({ stream })
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## 타입 안전한 도구
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
import { tool, TypedToolCall, TypedToolResult } from 'ai'
|
|
366
|
+
import { z } from 'zod'
|
|
367
|
+
|
|
368
|
+
const myToolSet = {
|
|
369
|
+
greet: tool({
|
|
370
|
+
description: 'Greet a user',
|
|
371
|
+
inputSchema: z.object({ name: z.string() }),
|
|
372
|
+
execute: async ({ name }) => `Hello, ${name}!`,
|
|
373
|
+
}),
|
|
374
|
+
calculate: tool({
|
|
375
|
+
description: 'Calculate',
|
|
376
|
+
inputSchema: z.object({
|
|
377
|
+
a: z.number(),
|
|
378
|
+
b: z.number(),
|
|
379
|
+
}),
|
|
380
|
+
execute: async ({ a, b }) => a + b,
|
|
381
|
+
}),
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// 타입 추출
|
|
385
|
+
type MyToolCall = TypedToolCall<typeof myToolSet>
|
|
386
|
+
type MyToolResult = TypedToolResult<typeof myToolSet>
|
|
387
|
+
|
|
388
|
+
// 타입 안전한 함수
|
|
389
|
+
async function generate(prompt: string): Promise<{
|
|
390
|
+
text: string
|
|
391
|
+
toolCalls: MyToolCall[]
|
|
392
|
+
toolResults: MyToolResult[]
|
|
393
|
+
}> {
|
|
394
|
+
return generateText({
|
|
395
|
+
model: openai('gpt-4o'),
|
|
396
|
+
tools: myToolSet,
|
|
397
|
+
prompt,
|
|
398
|
+
})
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## 클라이언트에서 도구 결과 표시
|
|
405
|
+
|
|
406
|
+
```tsx
|
|
407
|
+
'use client'
|
|
408
|
+
|
|
409
|
+
import { useChat } from '@ai-sdk/react'
|
|
410
|
+
|
|
411
|
+
export default function Chat() {
|
|
412
|
+
const { messages } = useChat({ api: '/api/chat' })
|
|
413
|
+
|
|
414
|
+
return (
|
|
415
|
+
<div>
|
|
416
|
+
{messages.map((m) => (
|
|
417
|
+
<div key={m.id}>
|
|
418
|
+
{/* 텍스트 */}
|
|
419
|
+
{m.content && <p>{m.content}</p>}
|
|
420
|
+
|
|
421
|
+
{/* 도구 호출 */}
|
|
422
|
+
{m.toolInvocations?.map((tool, i) => (
|
|
423
|
+
<div key={i} className="tool-invocation">
|
|
424
|
+
<h4>Tool: {tool.toolName}</h4>
|
|
425
|
+
<pre>Args: {JSON.stringify(tool.args, null, 2)}</pre>
|
|
426
|
+
|
|
427
|
+
{tool.state === 'result' && (
|
|
428
|
+
<pre>Result: {JSON.stringify(tool.result, null, 2)}</pre>
|
|
429
|
+
)}
|
|
430
|
+
|
|
431
|
+
{tool.state === 'partial-call' && (
|
|
432
|
+
<span>Calling...</span>
|
|
433
|
+
)}
|
|
434
|
+
</div>
|
|
435
|
+
))}
|
|
436
|
+
</div>
|
|
437
|
+
))}
|
|
438
|
+
</div>
|
|
439
|
+
)
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## 도구 정의 팁
|
|
446
|
+
|
|
447
|
+
1. **명확한 설명**: 모델이 언제 도구를 사용할지 이해하도록 상세히 작성
|
|
448
|
+
2. **스키마 설명**: 각 파라미터에 `.describe()` 추가
|
|
449
|
+
3. **에러 처리**: execute 함수에서 적절한 에러 처리
|
|
450
|
+
4. **반환값**: 모델이 이해할 수 있는 구조화된 데이터 반환
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
const goodTool = tool({
|
|
454
|
+
description: `
|
|
455
|
+
Search for products in the database.
|
|
456
|
+
Use this when the user asks about available products,
|
|
457
|
+
product prices, or product availability.
|
|
458
|
+
`,
|
|
459
|
+
inputSchema: z.object({
|
|
460
|
+
query: z.string().describe('Search query for product name or category'),
|
|
461
|
+
maxResults: z.number().default(10).describe('Maximum number of results'),
|
|
462
|
+
category: z.string().optional().describe('Filter by category'),
|
|
463
|
+
}),
|
|
464
|
+
execute: async ({ query, maxResults, category }) => {
|
|
465
|
+
try {
|
|
466
|
+
const results = await db.products.search({ query, maxResults, category })
|
|
467
|
+
return { success: true, products: results }
|
|
468
|
+
} catch (error) {
|
|
469
|
+
return { success: false, error: 'Failed to search products' }
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
})
|
|
473
|
+
```
|