@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,454 @@
|
|
|
1
|
+
# AI SDK - 구조화된 출력
|
|
2
|
+
|
|
3
|
+
> **상위 문서**: [AI SDK](./index.md)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 개요
|
|
8
|
+
|
|
9
|
+
AI SDK의 `generateObject`와 `streamObject`를 사용하면 타입 안전한 구조화된 데이터를 생성할 수 있습니다. Zod 스키마를 사용하여 출력 형식을 정의합니다.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## generateObject
|
|
14
|
+
|
|
15
|
+
### 기본 사용
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { generateObject } from 'ai'
|
|
19
|
+
import { openai } from '@ai-sdk/openai'
|
|
20
|
+
import { z } from 'zod'
|
|
21
|
+
|
|
22
|
+
const { object } = await generateObject({
|
|
23
|
+
model: openai('gpt-4o'),
|
|
24
|
+
schema: z.object({
|
|
25
|
+
name: z.string(),
|
|
26
|
+
age: z.number(),
|
|
27
|
+
email: z.string().email(),
|
|
28
|
+
}),
|
|
29
|
+
prompt: 'Generate a random user profile.',
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
console.log(object.name) // 타입 안전: string
|
|
33
|
+
console.log(object.age) // 타입 안전: number
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 복잡한 스키마
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { generateObject } from 'ai'
|
|
40
|
+
import { openai } from '@ai-sdk/openai'
|
|
41
|
+
import { z } from 'zod'
|
|
42
|
+
|
|
43
|
+
const recipeSchema = z.object({
|
|
44
|
+
name: z.string().describe('Recipe name'),
|
|
45
|
+
ingredients: z.array(
|
|
46
|
+
z.object({
|
|
47
|
+
name: z.string(),
|
|
48
|
+
amount: z.string(),
|
|
49
|
+
unit: z.string().optional(),
|
|
50
|
+
})
|
|
51
|
+
),
|
|
52
|
+
steps: z.array(z.string()),
|
|
53
|
+
prepTime: z.number().describe('Preparation time in minutes'),
|
|
54
|
+
cookTime: z.number().describe('Cooking time in minutes'),
|
|
55
|
+
servings: z.number(),
|
|
56
|
+
difficulty: z.enum(['easy', 'medium', 'hard']),
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const { object: recipe } = await generateObject({
|
|
60
|
+
model: openai('gpt-4o'),
|
|
61
|
+
schema: recipeSchema,
|
|
62
|
+
prompt: 'Generate a lasagna recipe.',
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
console.log(recipe.name)
|
|
66
|
+
console.log(recipe.ingredients)
|
|
67
|
+
console.log(recipe.steps)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 출력 모드
|
|
73
|
+
|
|
74
|
+
### Object (기본)
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
const { object } = await generateObject({
|
|
78
|
+
model: openai('gpt-4o'),
|
|
79
|
+
schema: z.object({
|
|
80
|
+
name: z.string(),
|
|
81
|
+
age: z.number(),
|
|
82
|
+
}),
|
|
83
|
+
prompt: 'Generate a user.',
|
|
84
|
+
})
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Array
|
|
88
|
+
|
|
89
|
+
배열 형태의 출력:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
const { object } = await generateObject({
|
|
93
|
+
model: openai('gpt-4o'),
|
|
94
|
+
output: 'array',
|
|
95
|
+
schema: z.object({
|
|
96
|
+
name: z.string(),
|
|
97
|
+
class: z.string().describe('Character class'),
|
|
98
|
+
description: z.string(),
|
|
99
|
+
}),
|
|
100
|
+
prompt: 'Generate 3 RPG characters.',
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
// object는 배열
|
|
104
|
+
for (const character of object) {
|
|
105
|
+
console.log(character.name)
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Enum
|
|
110
|
+
|
|
111
|
+
분류 작업에 유용:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
const { object } = await generateObject({
|
|
115
|
+
model: openai('gpt-4o'),
|
|
116
|
+
output: 'enum',
|
|
117
|
+
enum: ['action', 'comedy', 'drama', 'horror', 'sci-fi'],
|
|
118
|
+
prompt: `
|
|
119
|
+
Classify this movie:
|
|
120
|
+
"A group of astronauts travel through a wormhole
|
|
121
|
+
in search of a new habitable planet."
|
|
122
|
+
`,
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
console.log(object) // 'sci-fi'
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### No Schema
|
|
129
|
+
|
|
130
|
+
스키마 없이 자유 형식 JSON 생성:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
const { object } = await generateObject({
|
|
134
|
+
model: openai('gpt-4o'),
|
|
135
|
+
output: 'no-schema',
|
|
136
|
+
prompt: 'Generate a recipe for pasta.',
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
// object는 any 타입
|
|
140
|
+
console.log(object)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## streamObject
|
|
146
|
+
|
|
147
|
+
스트리밍으로 객체를 점진적으로 생성:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { streamObject } from 'ai'
|
|
151
|
+
import { openai } from '@ai-sdk/openai'
|
|
152
|
+
import { z } from 'zod'
|
|
153
|
+
|
|
154
|
+
const schema = z.object({
|
|
155
|
+
name: z.string(),
|
|
156
|
+
bio: z.string(),
|
|
157
|
+
skills: z.array(z.string()),
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
const result = streamObject({
|
|
161
|
+
model: openai('gpt-4o'),
|
|
162
|
+
schema,
|
|
163
|
+
prompt: 'Generate a developer profile.',
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
// 부분 객체 스트리밍
|
|
167
|
+
for await (const partialObject of result.partialObjectStream) {
|
|
168
|
+
console.log(partialObject)
|
|
169
|
+
// { name: 'J' }
|
|
170
|
+
// { name: 'John' }
|
|
171
|
+
// { name: 'John', bio: 'A' }
|
|
172
|
+
// ...
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 최종 객체
|
|
176
|
+
const finalObject = await result.object
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## generateText에서 구조화된 출력
|
|
182
|
+
|
|
183
|
+
`generateText`에서도 구조화된 출력을 사용할 수 있습니다:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { generateText, Output } from 'ai'
|
|
187
|
+
import { openai } from '@ai-sdk/openai'
|
|
188
|
+
import { z } from 'zod'
|
|
189
|
+
|
|
190
|
+
const { output } = await generateText({
|
|
191
|
+
model: openai('gpt-4o'),
|
|
192
|
+
prompt: 'Generate a random user.',
|
|
193
|
+
output: Output.object({
|
|
194
|
+
schema: z.object({
|
|
195
|
+
name: z.string(),
|
|
196
|
+
age: z.number(),
|
|
197
|
+
labels: z.array(z.string()),
|
|
198
|
+
}),
|
|
199
|
+
}),
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
console.log(output.name)
|
|
203
|
+
console.log(output.age)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## 도구와 구조화된 출력 결합
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import { generateText, tool, Output, stepCountIs } from 'ai'
|
|
212
|
+
import { openai } from '@ai-sdk/openai'
|
|
213
|
+
import { z } from 'zod'
|
|
214
|
+
|
|
215
|
+
const result = await generateText({
|
|
216
|
+
model: openai('gpt-4o'),
|
|
217
|
+
prompt: 'Analyze this data and provide a summary.',
|
|
218
|
+
output: Output.object({
|
|
219
|
+
schema: z.object({
|
|
220
|
+
summary: z.string(),
|
|
221
|
+
sentiment: z.enum(['positive', 'neutral', 'negative']),
|
|
222
|
+
}),
|
|
223
|
+
}),
|
|
224
|
+
tools: {
|
|
225
|
+
analyzeData: tool({
|
|
226
|
+
description: 'Analyze data',
|
|
227
|
+
inputSchema: z.object({ data: z.string() }),
|
|
228
|
+
execute: async ({ data }) => ({ result: 'analyzed' }),
|
|
229
|
+
}),
|
|
230
|
+
},
|
|
231
|
+
// 구조화된 출력을 위해 추가 단계 필요
|
|
232
|
+
stopWhen: stepCountIs(3),
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
console.log(result.output)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## API Route에서 사용
|
|
241
|
+
|
|
242
|
+
### generateObject
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
// app/api/generate-profile/route.ts
|
|
246
|
+
import { generateObject } from 'ai'
|
|
247
|
+
import { openai } from '@ai-sdk/openai'
|
|
248
|
+
import { z } from 'zod'
|
|
249
|
+
|
|
250
|
+
const profileSchema = z.object({
|
|
251
|
+
name: z.string(),
|
|
252
|
+
bio: z.string(),
|
|
253
|
+
skills: z.array(z.string()),
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
export async function POST(req: Request) {
|
|
257
|
+
const { prompt } = await req.json()
|
|
258
|
+
|
|
259
|
+
const { object } = await generateObject({
|
|
260
|
+
model: openai('gpt-4o'),
|
|
261
|
+
schema: profileSchema,
|
|
262
|
+
prompt,
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
return Response.json(object)
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### streamObject
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
// app/api/stream-profile/route.ts
|
|
273
|
+
import { streamObject } from 'ai'
|
|
274
|
+
import { openai } from '@ai-sdk/openai'
|
|
275
|
+
import { z } from 'zod'
|
|
276
|
+
|
|
277
|
+
const profileSchema = z.object({
|
|
278
|
+
name: z.string(),
|
|
279
|
+
bio: z.string(),
|
|
280
|
+
skills: z.array(z.string()),
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
export async function POST(req: Request) {
|
|
284
|
+
const { prompt } = await req.json()
|
|
285
|
+
|
|
286
|
+
const result = streamObject({
|
|
287
|
+
model: openai('gpt-4o'),
|
|
288
|
+
schema: profileSchema,
|
|
289
|
+
prompt,
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
return result.toTextStreamResponse()
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## 클라이언트에서 사용 (useObject)
|
|
299
|
+
|
|
300
|
+
```tsx
|
|
301
|
+
'use client'
|
|
302
|
+
|
|
303
|
+
import { experimental_useObject as useObject } from '@ai-sdk/react'
|
|
304
|
+
import { z } from 'zod'
|
|
305
|
+
|
|
306
|
+
const profileSchema = z.object({
|
|
307
|
+
name: z.string(),
|
|
308
|
+
bio: z.string(),
|
|
309
|
+
skills: z.array(z.string()),
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
export default function ProfileGenerator() {
|
|
313
|
+
const { object, submit, isLoading, error } = useObject({
|
|
314
|
+
api: '/api/stream-profile',
|
|
315
|
+
schema: profileSchema,
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
return (
|
|
319
|
+
<div>
|
|
320
|
+
<button
|
|
321
|
+
onClick={() => submit('Generate a developer profile')}
|
|
322
|
+
disabled={isLoading}
|
|
323
|
+
>
|
|
324
|
+
{isLoading ? 'Generating...' : 'Generate Profile'}
|
|
325
|
+
</button>
|
|
326
|
+
|
|
327
|
+
{error && <p className="error">{error.message}</p>}
|
|
328
|
+
|
|
329
|
+
{object && (
|
|
330
|
+
<div className="profile">
|
|
331
|
+
<h2>{object.name}</h2>
|
|
332
|
+
<p>{object.bio}</p>
|
|
333
|
+
<ul>
|
|
334
|
+
{object.skills?.map((skill, i) => (
|
|
335
|
+
<li key={i}>{skill}</li>
|
|
336
|
+
))}
|
|
337
|
+
</ul>
|
|
338
|
+
</div>
|
|
339
|
+
)}
|
|
340
|
+
</div>
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## 스키마 설계 팁
|
|
348
|
+
|
|
349
|
+
### 필드 설명 추가
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
const schema = z.object({
|
|
353
|
+
title: z.string().describe('A catchy title for the article'),
|
|
354
|
+
summary: z.string().describe('A 2-3 sentence summary'),
|
|
355
|
+
tags: z.array(z.string()).describe('Relevant topic tags'),
|
|
356
|
+
})
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Optional 필드
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
const schema = z.object({
|
|
363
|
+
name: z.string(),
|
|
364
|
+
nickname: z.string().optional(),
|
|
365
|
+
age: z.number().nullable(),
|
|
366
|
+
})
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### 기본값
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
const schema = z.object({
|
|
373
|
+
name: z.string(),
|
|
374
|
+
role: z.string().default('user'),
|
|
375
|
+
isActive: z.boolean().default(true),
|
|
376
|
+
})
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Enum 사용
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
const schema = z.object({
|
|
383
|
+
status: z.enum(['pending', 'approved', 'rejected']),
|
|
384
|
+
priority: z.enum(['low', 'medium', 'high', 'critical']),
|
|
385
|
+
})
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### 중첩 객체
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
const schema = z.object({
|
|
392
|
+
user: z.object({
|
|
393
|
+
name: z.string(),
|
|
394
|
+
email: z.string().email(),
|
|
395
|
+
}),
|
|
396
|
+
address: z.object({
|
|
397
|
+
street: z.string(),
|
|
398
|
+
city: z.string(),
|
|
399
|
+
country: z.string(),
|
|
400
|
+
}),
|
|
401
|
+
})
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## 반환값
|
|
407
|
+
|
|
408
|
+
### generateObject
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
const result = await generateObject({
|
|
412
|
+
model: openai('gpt-4o'),
|
|
413
|
+
schema,
|
|
414
|
+
prompt,
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
result.object // 생성된 객체 (타입 안전)
|
|
418
|
+
result.usage // 토큰 사용량
|
|
419
|
+
result.finishReason // 완료 이유
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### streamObject
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
const result = streamObject({
|
|
426
|
+
model: openai('gpt-4o'),
|
|
427
|
+
schema,
|
|
428
|
+
prompt,
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
result.partialObjectStream // AsyncIterable<PartialObject>
|
|
432
|
+
result.object // Promise<FinalObject>
|
|
433
|
+
result.usage // Promise<Usage>
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## 에러 처리
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
try {
|
|
442
|
+
const { object } = await generateObject({
|
|
443
|
+
model: openai('gpt-4o'),
|
|
444
|
+
schema,
|
|
445
|
+
prompt,
|
|
446
|
+
})
|
|
447
|
+
} catch (error) {
|
|
448
|
+
if (error instanceof z.ZodError) {
|
|
449
|
+
console.error('Validation error:', error.errors)
|
|
450
|
+
} else {
|
|
451
|
+
console.error('Generation error:', error)
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
```
|