@kood/claude-code 0.1.7 → 0.1.9
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 +118 -3
- package/package.json +8 -2
- package/templates/hono/CLAUDE.md +53 -326
- package/templates/hono/docs/architecture/architecture.md +93 -747
- package/templates/hono/docs/deployment/cloudflare.md +59 -513
- package/templates/hono/docs/deployment/docker.md +41 -356
- package/templates/hono/docs/deployment/index.md +49 -190
- package/templates/hono/docs/deployment/railway.md +36 -306
- package/templates/hono/docs/deployment/vercel.md +49 -434
- package/templates/hono/docs/library/ai-sdk/index.md +53 -290
- package/templates/hono/docs/library/ai-sdk/openrouter.md +19 -387
- package/templates/hono/docs/library/ai-sdk/providers.md +28 -394
- package/templates/hono/docs/library/ai-sdk/streaming.md +52 -353
- package/templates/hono/docs/library/ai-sdk/structured-output.md +63 -395
- package/templates/hono/docs/library/ai-sdk/tools.md +62 -431
- package/templates/hono/docs/library/hono/env-setup.md +24 -313
- package/templates/hono/docs/library/hono/error-handling.md +34 -295
- package/templates/hono/docs/library/hono/index.md +24 -122
- package/templates/hono/docs/library/hono/middleware.md +21 -188
- package/templates/hono/docs/library/hono/rpc.md +40 -341
- package/templates/hono/docs/library/hono/validation.md +35 -195
- package/templates/hono/docs/library/pino/index.md +42 -333
- package/templates/hono/docs/library/prisma/cloudflare-d1.md +64 -367
- package/templates/hono/docs/library/prisma/config.md +19 -260
- package/templates/hono/docs/library/prisma/index.md +64 -320
- package/templates/hono/docs/library/zod/index.md +53 -257
- package/templates/npx/CLAUDE.md +58 -276
- package/templates/npx/docs/references/patterns.md +160 -0
- package/templates/tanstack-start/CLAUDE.md +0 -4
- package/templates/tanstack-start/docs/architecture/architecture.md +44 -589
- package/templates/tanstack-start/docs/design/index.md +119 -12
- package/templates/tanstack-start/docs/guides/conventions.md +103 -0
- package/templates/tanstack-start/docs/guides/env-setup.md +34 -340
- package/templates/tanstack-start/docs/guides/getting-started.md +22 -209
- package/templates/tanstack-start/docs/guides/hooks.md +166 -0
- package/templates/tanstack-start/docs/guides/routes.md +166 -0
- package/templates/tanstack-start/docs/guides/services.md +143 -0
- package/templates/tanstack-start/docs/library/tanstack-query/index.md +18 -2
- package/templates/tanstack-start/docs/library/zod/index.md +16 -1
- package/templates/tanstack-start/docs/design/accessibility.md +0 -163
- package/templates/tanstack-start/docs/design/color.md +0 -93
- package/templates/tanstack-start/docs/design/spacing.md +0 -122
- package/templates/tanstack-start/docs/design/typography.md +0 -80
- package/templates/tanstack-start/docs/guides/best-practices.md +0 -950
- package/templates/tanstack-start/docs/guides/husky-lint-staged.md +0 -303
- package/templates/tanstack-start/docs/guides/prettier.md +0 -189
- package/templates/tanstack-start/docs/guides/project-templates.md +0 -710
- package/templates/tanstack-start/docs/library/tanstack-query/setup.md +0 -48
- package/templates/tanstack-start/docs/library/zod/basic-types.md +0 -74
|
@@ -1,93 +1,49 @@
|
|
|
1
|
-
# AI SDK - 텍스트 생성 & 스트리밍
|
|
1
|
+
# AI SDK - 텍스트 생성 & 스트리밍
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 개요
|
|
8
|
-
|
|
9
|
-
AI SDK의 `generateText`와 `streamText`를 Hono에서 사용하는 방법입니다.
|
|
3
|
+
> generateText, streamText 사용법
|
|
10
4
|
|
|
11
5
|
---
|
|
12
6
|
|
|
13
7
|
## generateText
|
|
14
8
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
### 기본 사용
|
|
9
|
+
한 번에 텍스트 생성 (비스트리밍).
|
|
18
10
|
|
|
19
11
|
```typescript
|
|
20
|
-
import { Hono } from 'hono'
|
|
21
12
|
import { generateText } from 'ai'
|
|
22
13
|
import { openai } from '@ai-sdk/openai'
|
|
23
14
|
|
|
24
|
-
const app = new Hono()
|
|
25
|
-
|
|
26
15
|
app.post('/api/generate', async (c) => {
|
|
27
16
|
const { prompt } = await c.req.json()
|
|
28
17
|
|
|
29
|
-
const { text } = await generateText({
|
|
18
|
+
const { text, usage, finishReason } = await generateText({
|
|
30
19
|
model: openai('gpt-4o'),
|
|
31
20
|
prompt,
|
|
32
21
|
})
|
|
33
22
|
|
|
34
|
-
return c.json({ text })
|
|
35
|
-
})
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### 메시지 형식
|
|
39
|
-
|
|
40
|
-
```typescript
|
|
41
|
-
import { Hono } from 'hono'
|
|
42
|
-
import { generateText } from 'ai'
|
|
43
|
-
import { openai } from '@ai-sdk/openai'
|
|
44
|
-
|
|
45
|
-
const app = new Hono()
|
|
46
|
-
|
|
47
|
-
app.post('/api/chat', async (c) => {
|
|
48
|
-
const { messages } = await c.req.json()
|
|
49
|
-
|
|
50
|
-
const { text } = await generateText({
|
|
51
|
-
model: openai('gpt-4o'),
|
|
52
|
-
messages,
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
return c.json({ response: text })
|
|
23
|
+
return c.json({ text, usage, finishReason })
|
|
56
24
|
})
|
|
57
25
|
```
|
|
58
26
|
|
|
59
27
|
### 시스템 프롬프트
|
|
60
28
|
|
|
61
29
|
```typescript
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
model: openai('gpt-4o'),
|
|
67
|
-
system: 'You are a helpful coding assistant.',
|
|
68
|
-
prompt,
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
return c.json({ text })
|
|
30
|
+
const { text } = await generateText({
|
|
31
|
+
model: openai('gpt-4o'),
|
|
32
|
+
system: 'You are a helpful coding assistant.',
|
|
33
|
+
prompt,
|
|
72
34
|
})
|
|
73
35
|
```
|
|
74
36
|
|
|
75
|
-
###
|
|
37
|
+
### 메시지 형식
|
|
76
38
|
|
|
77
39
|
```typescript
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return c.json({
|
|
87
|
-
text: result.text,
|
|
88
|
-
usage: result.usage, // 토큰 사용량
|
|
89
|
-
finishReason: result.finishReason, // 완료 이유
|
|
90
|
-
})
|
|
40
|
+
const { text } = await generateText({
|
|
41
|
+
model: openai('gpt-4o'),
|
|
42
|
+
messages: [
|
|
43
|
+
{ role: 'user', content: 'Hello!' },
|
|
44
|
+
{ role: 'assistant', content: 'Hi!' },
|
|
45
|
+
{ role: 'user', content: 'How are you?' },
|
|
46
|
+
],
|
|
91
47
|
})
|
|
92
48
|
```
|
|
93
49
|
|
|
@@ -95,16 +51,10 @@ app.post('/api/analyze', async (c) => {
|
|
|
95
51
|
|
|
96
52
|
## streamText
|
|
97
53
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
### 기본 스트리밍
|
|
54
|
+
실시간 텍스트 스트리밍.
|
|
101
55
|
|
|
102
56
|
```typescript
|
|
103
|
-
import { Hono } from 'hono'
|
|
104
57
|
import { streamText } from 'ai'
|
|
105
|
-
import { openai } from '@ai-sdk/openai'
|
|
106
|
-
|
|
107
|
-
const app = new Hono()
|
|
108
58
|
|
|
109
59
|
app.post('/api/stream', async (c) => {
|
|
110
60
|
const { prompt } = await c.req.json()
|
|
@@ -114,42 +64,28 @@ app.post('/api/stream', async (c) => {
|
|
|
114
64
|
prompt,
|
|
115
65
|
})
|
|
116
66
|
|
|
117
|
-
return result.
|
|
67
|
+
return result.toUIMessageStreamResponse()
|
|
118
68
|
})
|
|
119
69
|
```
|
|
120
70
|
|
|
121
|
-
###
|
|
71
|
+
### 스트림 응답 타입
|
|
122
72
|
|
|
123
73
|
```typescript
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
import { openai } from '@ai-sdk/openai'
|
|
74
|
+
// UI 메시지 스트림 (프론트엔드용)
|
|
75
|
+
return result.toUIMessageStreamResponse()
|
|
127
76
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
app.post('/api/chat', async (c) => {
|
|
131
|
-
const { messages } = await c.req.json()
|
|
132
|
-
|
|
133
|
-
const result = streamText({
|
|
134
|
-
model: openai('gpt-4o'),
|
|
135
|
-
messages: convertToModelMessages(messages),
|
|
136
|
-
})
|
|
77
|
+
// 텍스트 스트림 (SSE)
|
|
78
|
+
return result.toTextStreamResponse()
|
|
137
79
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
})
|
|
80
|
+
// Data 스트림
|
|
81
|
+
return result.toDataStreamResponse()
|
|
141
82
|
```
|
|
142
83
|
|
|
143
|
-
###
|
|
84
|
+
### 커스텀 스트림 처리
|
|
144
85
|
|
|
145
86
|
```typescript
|
|
146
|
-
import { Hono } from 'hono'
|
|
147
|
-
import { streamText } from 'ai'
|
|
148
|
-
import { openai } from '@ai-sdk/openai'
|
|
149
87
|
import { stream } from 'hono/streaming'
|
|
150
88
|
|
|
151
|
-
const app = new Hono()
|
|
152
|
-
|
|
153
89
|
app.post('/api/custom-stream', async (c) => {
|
|
154
90
|
const { prompt } = await c.req.json()
|
|
155
91
|
|
|
@@ -166,282 +102,45 @@ app.post('/api/custom-stream', async (c) => {
|
|
|
166
102
|
})
|
|
167
103
|
```
|
|
168
104
|
|
|
169
|
-
### SSE (Server-Sent Events)
|
|
170
|
-
|
|
171
|
-
```typescript
|
|
172
|
-
import { Hono } from 'hono'
|
|
173
|
-
import { streamText } from 'ai'
|
|
174
|
-
import { openai } from '@ai-sdk/openai'
|
|
175
|
-
import { streamSSE } from 'hono/streaming'
|
|
176
|
-
|
|
177
|
-
const app = new Hono()
|
|
178
|
-
|
|
179
|
-
app.post('/api/sse', async (c) => {
|
|
180
|
-
const { prompt } = await c.req.json()
|
|
181
|
-
|
|
182
|
-
const result = streamText({
|
|
183
|
-
model: openai('gpt-4o'),
|
|
184
|
-
prompt,
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
return streamSSE(c, async (stream) => {
|
|
188
|
-
for await (const chunk of result.textStream) {
|
|
189
|
-
await stream.writeSSE({
|
|
190
|
-
data: chunk,
|
|
191
|
-
event: 'message',
|
|
192
|
-
})
|
|
193
|
-
}
|
|
194
|
-
await stream.writeSSE({
|
|
195
|
-
data: '[DONE]',
|
|
196
|
-
event: 'done',
|
|
197
|
-
})
|
|
198
|
-
})
|
|
199
|
-
})
|
|
200
|
-
```
|
|
201
|
-
|
|
202
105
|
---
|
|
203
106
|
|
|
204
|
-
##
|
|
205
|
-
|
|
206
|
-
### fullStream
|
|
207
|
-
|
|
208
|
-
모든 이벤트에 접근:
|
|
107
|
+
## 옵션
|
|
209
108
|
|
|
210
109
|
```typescript
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
for await (const event of result.fullStream) {
|
|
221
|
-
switch (event.type) {
|
|
222
|
-
case 'text-delta':
|
|
223
|
-
await stream.write(
|
|
224
|
-
JSON.stringify({ type: 'text', content: event.textDelta }) + '\n'
|
|
225
|
-
)
|
|
226
|
-
break
|
|
227
|
-
case 'tool-call':
|
|
228
|
-
await stream.write(
|
|
229
|
-
JSON.stringify({ type: 'tool', name: event.toolName }) + '\n'
|
|
230
|
-
)
|
|
231
|
-
break
|
|
232
|
-
case 'finish':
|
|
233
|
-
await stream.write(
|
|
234
|
-
JSON.stringify({
|
|
235
|
-
type: 'finish',
|
|
236
|
-
usage: event.usage,
|
|
237
|
-
}) + '\n'
|
|
238
|
-
)
|
|
239
|
-
break
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
})
|
|
110
|
+
const result = streamText({
|
|
111
|
+
model: openai('gpt-4o'),
|
|
112
|
+
prompt,
|
|
113
|
+
temperature: 0.7, // 창의성 (0-2)
|
|
114
|
+
maxTokens: 1000, // 최대 토큰
|
|
115
|
+
topP: 0.9, // 누적 확률
|
|
116
|
+
frequencyPenalty: 0, // 반복 페널티
|
|
117
|
+
presencePenalty: 0, // 존재 페널티
|
|
118
|
+
stopSequences: ['---'], // 중단 시퀀스
|
|
243
119
|
})
|
|
244
120
|
```
|
|
245
121
|
|
|
246
122
|
---
|
|
247
123
|
|
|
248
|
-
##
|
|
249
|
-
|
|
250
|
-
### 역할 기반 시스템 프롬프트
|
|
124
|
+
## 스트림 이벤트
|
|
251
125
|
|
|
252
126
|
```typescript
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const result = streamText({
|
|
264
|
-
model: openai('gpt-4o'),
|
|
265
|
-
system: systemPrompts[role] ?? systemPrompts.coder,
|
|
266
|
-
messages,
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
return result.toUIMessageStreamResponse()
|
|
270
|
-
})
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
### 컨텍스트 주입
|
|
274
|
-
|
|
275
|
-
```typescript
|
|
276
|
-
app.post('/api/chat-with-context', async (c) => {
|
|
277
|
-
const { messages, context } = await c.req.json()
|
|
278
|
-
|
|
279
|
-
const result = streamText({
|
|
280
|
-
model: openai('gpt-4o'),
|
|
281
|
-
system: `You are a helpful assistant.
|
|
282
|
-
|
|
283
|
-
Use the following context to answer questions:
|
|
284
|
-
${context}
|
|
285
|
-
|
|
286
|
-
If the context doesn't contain relevant information, say so.`,
|
|
287
|
-
messages,
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
return result.toUIMessageStreamResponse()
|
|
291
|
-
})
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
---
|
|
295
|
-
|
|
296
|
-
## 에러 처리
|
|
297
|
-
|
|
298
|
-
```typescript
|
|
299
|
-
import { Hono } from 'hono'
|
|
300
|
-
import { HTTPException } from 'hono/http-exception'
|
|
301
|
-
import { streamText } from 'ai'
|
|
302
|
-
import { openai } from '@ai-sdk/openai'
|
|
303
|
-
|
|
304
|
-
const app = new Hono()
|
|
305
|
-
|
|
306
|
-
app.post('/api/chat', async (c) => {
|
|
307
|
-
try {
|
|
308
|
-
const body = await c.req.json()
|
|
309
|
-
|
|
310
|
-
if (!body.messages) {
|
|
311
|
-
throw new HTTPException(400, { message: 'Messages required' })
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
const result = streamText({
|
|
315
|
-
model: openai('gpt-4o'),
|
|
316
|
-
messages: body.messages,
|
|
317
|
-
})
|
|
318
|
-
|
|
319
|
-
return result.toUIMessageStreamResponse()
|
|
320
|
-
} catch (error) {
|
|
321
|
-
if (error instanceof HTTPException) {
|
|
322
|
-
throw error
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
console.error('Stream error:', error)
|
|
326
|
-
throw new HTTPException(500, { message: 'AI processing failed' })
|
|
327
|
-
}
|
|
127
|
+
const result = streamText({
|
|
128
|
+
model: openai('gpt-4o'),
|
|
129
|
+
prompt,
|
|
130
|
+
onChunk: ({ chunk }) => {
|
|
131
|
+
console.log('Chunk:', chunk)
|
|
132
|
+
},
|
|
133
|
+
onFinish: ({ text, usage, finishReason }) => {
|
|
134
|
+
console.log('Finished:', text)
|
|
135
|
+
console.log('Usage:', usage)
|
|
136
|
+
},
|
|
328
137
|
})
|
|
329
138
|
```
|
|
330
139
|
|
|
331
140
|
---
|
|
332
141
|
|
|
333
|
-
##
|
|
334
|
-
|
|
335
|
-
```typescript
|
|
336
|
-
app.post('/api/stream', async (c) => {
|
|
337
|
-
const { prompt } = await c.req.json()
|
|
338
|
-
|
|
339
|
-
const abortController = new AbortController()
|
|
340
|
-
|
|
341
|
-
// 클라이언트 연결 종료 시 abort
|
|
342
|
-
c.req.raw.signal.addEventListener('abort', () => {
|
|
343
|
-
abortController.abort()
|
|
344
|
-
})
|
|
142
|
+
## 관련 문서
|
|
345
143
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
abortSignal: abortController.signal,
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
return result.toTextStreamResponse()
|
|
353
|
-
})
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
---
|
|
357
|
-
|
|
358
|
-
## 캐싱 패턴
|
|
359
|
-
|
|
360
|
-
```typescript
|
|
361
|
-
import { Hono } from 'hono'
|
|
362
|
-
import { generateText } from 'ai'
|
|
363
|
-
import { openai } from '@ai-sdk/openai'
|
|
364
|
-
|
|
365
|
-
type Bindings = {
|
|
366
|
-
AI_CACHE: KVNamespace // Cloudflare KV
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const app = new Hono<{ Bindings: Bindings }>()
|
|
370
|
-
|
|
371
|
-
app.post('/api/generate', async (c) => {
|
|
372
|
-
const { prompt } = await c.req.json()
|
|
373
|
-
|
|
374
|
-
// 캐시 키 생성
|
|
375
|
-
const cacheKey = `ai:${btoa(prompt)}`
|
|
376
|
-
|
|
377
|
-
// 캐시 확인
|
|
378
|
-
const cached = await c.env.AI_CACHE.get(cacheKey)
|
|
379
|
-
if (cached) {
|
|
380
|
-
return c.json({ text: cached, cached: true })
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// 새로 생성
|
|
384
|
-
const { text } = await generateText({
|
|
385
|
-
model: openai('gpt-4o'),
|
|
386
|
-
prompt,
|
|
387
|
-
})
|
|
388
|
-
|
|
389
|
-
// 캐시 저장 (1시간)
|
|
390
|
-
await c.env.AI_CACHE.put(cacheKey, text, { expirationTtl: 3600 })
|
|
391
|
-
|
|
392
|
-
return c.json({ text, cached: false })
|
|
393
|
-
})
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
---
|
|
397
|
-
|
|
398
|
-
## 타임아웃 설정
|
|
399
|
-
|
|
400
|
-
```typescript
|
|
401
|
-
app.post('/api/generate', async (c) => {
|
|
402
|
-
const { prompt } = await c.req.json()
|
|
403
|
-
|
|
404
|
-
const controller = new AbortController()
|
|
405
|
-
const timeout = setTimeout(() => controller.abort(), 30000) // 30초
|
|
406
|
-
|
|
407
|
-
try {
|
|
408
|
-
const { text } = await generateText({
|
|
409
|
-
model: openai('gpt-4o'),
|
|
410
|
-
prompt,
|
|
411
|
-
abortSignal: controller.signal,
|
|
412
|
-
})
|
|
413
|
-
|
|
414
|
-
return c.json({ text })
|
|
415
|
-
} catch (error) {
|
|
416
|
-
if (error instanceof Error && error.name === 'AbortError') {
|
|
417
|
-
return c.json({ error: 'Request timed out' }, 408)
|
|
418
|
-
}
|
|
419
|
-
throw error
|
|
420
|
-
} finally {
|
|
421
|
-
clearTimeout(timeout)
|
|
422
|
-
}
|
|
423
|
-
})
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
---
|
|
427
|
-
|
|
428
|
-
## 병렬 생성
|
|
429
|
-
|
|
430
|
-
```typescript
|
|
431
|
-
app.post('/api/multi-generate', async (c) => {
|
|
432
|
-
const { prompts } = await c.req.json()
|
|
433
|
-
|
|
434
|
-
const results = await Promise.all(
|
|
435
|
-
prompts.map((prompt: string) =>
|
|
436
|
-
generateText({
|
|
437
|
-
model: openai('gpt-4o'),
|
|
438
|
-
prompt,
|
|
439
|
-
})
|
|
440
|
-
)
|
|
441
|
-
)
|
|
442
|
-
|
|
443
|
-
return c.json({
|
|
444
|
-
texts: results.map((r) => r.text),
|
|
445
|
-
})
|
|
446
|
-
})
|
|
447
|
-
```
|
|
144
|
+
- [AI SDK 개요](./index.md)
|
|
145
|
+
- [도구](./tools.md)
|
|
146
|
+
- [구조화된 출력](./structured-output.md)
|