@kood/claude-code 0.1.1 → 0.1.3
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 +81 -38
- 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/deployment/cloudflare.md +537 -190
- package/templates/hono/docs/deployment/docker.md +517 -0
- package/templates/hono/docs/deployment/index.md +181 -213
- package/templates/hono/docs/deployment/railway.md +416 -0
- package/templates/hono/docs/deployment/vercel.md +572 -0
- package/templates/hono/docs/git/git.md +285 -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 -0
- 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/git/git.md +307 -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/deployment/cloudflare.md +234 -51
- package/templates/tanstack-start/docs/deployment/index.md +322 -32
- package/templates/tanstack-start/docs/deployment/nitro.md +201 -20
- package/templates/tanstack-start/docs/deployment/railway.md +305 -153
- package/templates/tanstack-start/docs/deployment/vercel.md +353 -78
- package/templates/tanstack-start/docs/git/{index.md → git.md} +81 -7
- 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 -1
- package/templates/tanstack-start/docs/library/prisma/schema.md +123 -25
- 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
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# Git 워크플로우
|
|
2
|
+
|
|
3
|
+
> Hono 서버 프레임워크 프로젝트 Git 컨벤션
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## ⛔ NEVER (절대 금지)
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
❌ 커밋에 "Generated with Claude Code" 포함
|
|
11
|
+
❌ 커밋에 "🤖" 또는 AI 관련 이모지 포함
|
|
12
|
+
❌ 커밋에 "Co-Authored-By:" 헤더 포함
|
|
13
|
+
❌ 커밋에 AI/봇이 작성했다는 어떤 표시도 포함
|
|
14
|
+
❌ 커밋 메시지 여러 줄 작성
|
|
15
|
+
❌ 커밋 메시지에 이모지 사용
|
|
16
|
+
❌ 커밋 메시지에 마침표(.) 사용
|
|
17
|
+
❌ 여러 작업을 하나의 커밋으로 퉁치기
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## ✅ ALWAYS (필수)
|
|
23
|
+
|
|
24
|
+
### 작업 완료 후 반드시 실행
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
git add <관련 파일들>
|
|
28
|
+
git commit -m "<prefix>: <설명>"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### ⭐ 커밋 분리 원칙
|
|
32
|
+
|
|
33
|
+
**하나의 커밋 = 하나의 논리적 변경 단위**
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# ❌ 잘못된 방식: 모든 작업을 하나로 퉁침
|
|
37
|
+
git add .
|
|
38
|
+
git commit -m "feat: 여러 기능 추가"
|
|
39
|
+
|
|
40
|
+
# ✅ 올바른 방식: 논리적 단위로 분리
|
|
41
|
+
git add src/routes/auth.ts src/middleware/auth.ts
|
|
42
|
+
git commit -m "feat: 사용자 인증 API 추가"
|
|
43
|
+
|
|
44
|
+
git add src/routes/users.ts src/services/user.ts
|
|
45
|
+
git commit -m "feat: 사용자 CRUD API 추가"
|
|
46
|
+
|
|
47
|
+
git add docs/
|
|
48
|
+
git commit -m "docs: API 문서 업데이트"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 📝 커밋 형식
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
<prefix>: <설명>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**한 줄로 간결하게** 작성합니다. 본문이나 푸터는 작성하지 않습니다.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 🏷 Prefix 목록
|
|
64
|
+
|
|
65
|
+
| Prefix | 용도 | 예시 |
|
|
66
|
+
|--------|------|------|
|
|
67
|
+
| `feat` | 새로운 기능 | `feat: 사용자 인증 API 추가` |
|
|
68
|
+
| `fix` | 버그 수정 | `fix: JWT 토큰 검증 오류 수정` |
|
|
69
|
+
| `refactor` | 리팩토링 | `refactor: 인증 미들웨어 분리` |
|
|
70
|
+
| `style` | 코드 스타일 | `style: prettier 적용` |
|
|
71
|
+
| `docs` | 문서 수정 | `docs: API 문서 업데이트` |
|
|
72
|
+
| `test` | 테스트 | `test: 인증 미들웨어 테스트 추가` |
|
|
73
|
+
| `chore` | 빌드/설정 | `chore: wrangler 설정 업데이트` |
|
|
74
|
+
| `perf` | 성능 개선 | `perf: 쿼리 최적화` |
|
|
75
|
+
| `ci` | CI/CD | `ci: GitHub Actions 추가` |
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## ✅ 올바른 예시
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
feat: 사용자 로그인 API 추가
|
|
83
|
+
fix: 세션 만료 오류 수정
|
|
84
|
+
refactor: 인증 미들웨어 분리
|
|
85
|
+
docs: README 설치 가이드 추가
|
|
86
|
+
chore: Prisma 7.x 버전 업그레이드
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## ❌ 잘못된 예시
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# prefix 없음
|
|
95
|
+
사용자 인증 기능 추가함
|
|
96
|
+
|
|
97
|
+
# 마침표 불필요
|
|
98
|
+
feat: 사용자 인증 추가.
|
|
99
|
+
|
|
100
|
+
# 대문자 사용
|
|
101
|
+
FEAT: 사용자 인증 추가
|
|
102
|
+
|
|
103
|
+
# scope 불필요
|
|
104
|
+
feat(auth): 인증 추가
|
|
105
|
+
|
|
106
|
+
# AI 작성 표시 (절대 금지!)
|
|
107
|
+
feat: 로그인 기능 추가
|
|
108
|
+
|
|
109
|
+
🤖 Generated with Claude Code
|
|
110
|
+
|
|
111
|
+
# Co-Author 표시 (절대 금지!)
|
|
112
|
+
feat: 로그인 기능 추가
|
|
113
|
+
|
|
114
|
+
Co-Authored-By: Claude <noreply@anthropic.com>
|
|
115
|
+
|
|
116
|
+
# 여러 줄 본문 (금지)
|
|
117
|
+
feat: 로그인 기능 추가
|
|
118
|
+
|
|
119
|
+
- JWT 인증 구현
|
|
120
|
+
- 세션 관리 추가
|
|
121
|
+
|
|
122
|
+
# 여러 작업을 하나로 퉁침 (금지)
|
|
123
|
+
feat: 로그인, 회원가입, 사용자 API 추가
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 🔄 작업 흐름
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# 1. 작업 전 최신 코드 동기화
|
|
132
|
+
git pull origin main
|
|
133
|
+
|
|
134
|
+
# 2. 라우트 작업 완료 → 커밋
|
|
135
|
+
git add src/routes/users.ts src/validators/user.ts
|
|
136
|
+
git commit -m "feat: 사용자 CRUD API 구현"
|
|
137
|
+
|
|
138
|
+
# 3. 미들웨어 작업 완료 → 커밋
|
|
139
|
+
git add src/middleware/auth.ts src/middleware/logger.ts
|
|
140
|
+
git commit -m "feat: 인증 및 로깅 미들웨어 추가"
|
|
141
|
+
|
|
142
|
+
# 4. 문서 작업 완료 → 커밋
|
|
143
|
+
git add docs/
|
|
144
|
+
git commit -m "docs: API 엔드포인트 문서 추가"
|
|
145
|
+
|
|
146
|
+
# 5. 푸시
|
|
147
|
+
git push origin main
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 📦 커밋 분리 가이드
|
|
153
|
+
|
|
154
|
+
### 언제 커밋을 분리해야 하나요?
|
|
155
|
+
|
|
156
|
+
| 상황 | 커밋 분리 |
|
|
157
|
+
|------|----------|
|
|
158
|
+
| 서로 다른 API 엔드포인트 | ✅ 분리 |
|
|
159
|
+
| 버그 수정 + 새 기능 | ✅ 분리 |
|
|
160
|
+
| 코드 변경 + 문서 변경 | ✅ 분리 |
|
|
161
|
+
| 리팩토링 + 기능 추가 | ✅ 분리 |
|
|
162
|
+
| 동일 기능의 라우트 + 밸리데이터 | 🔄 묶어도 됨 |
|
|
163
|
+
| 동일 기능의 서비스 + 타입 | 🔄 묶어도 됨 |
|
|
164
|
+
|
|
165
|
+
### 예시: 사용자 관리 API 개발
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# 1. 인증 API 커밋
|
|
169
|
+
git add src/routes/auth.ts src/middleware/auth.ts src/validators/auth.ts
|
|
170
|
+
git commit -m "feat: 사용자 인증 API 추가"
|
|
171
|
+
|
|
172
|
+
# 2. 사용자 API 커밋
|
|
173
|
+
git add src/routes/users.ts src/services/user.ts src/validators/user.ts
|
|
174
|
+
git commit -m "feat: 사용자 CRUD API 추가"
|
|
175
|
+
|
|
176
|
+
# 3. 에러 핸들링 커밋
|
|
177
|
+
git add src/lib/errors.ts src/index.ts
|
|
178
|
+
git commit -m "feat: 글로벌 에러 핸들러 추가"
|
|
179
|
+
|
|
180
|
+
# 4. 테스트 커밋
|
|
181
|
+
git add tests/
|
|
182
|
+
git commit -m "test: 사용자 API 테스트 추가"
|
|
183
|
+
|
|
184
|
+
# 5. 문서 커밋
|
|
185
|
+
git add docs/
|
|
186
|
+
git commit -m "docs: 사용자 API 문서 추가"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## 🌿 브랜치 전략
|
|
192
|
+
|
|
193
|
+
### 간단한 프로젝트
|
|
194
|
+
```
|
|
195
|
+
main ← 모든 작업 직접 커밋
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 팀 프로젝트
|
|
199
|
+
```
|
|
200
|
+
main
|
|
201
|
+
└── feature/기능명
|
|
202
|
+
└── fix/버그명
|
|
203
|
+
└── hotfix/긴급수정
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### 브랜치 명명
|
|
207
|
+
```bash
|
|
208
|
+
feature/user-auth-api
|
|
209
|
+
feature/payment-webhook
|
|
210
|
+
fix/jwt-validation-error
|
|
211
|
+
hotfix/security-patch
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## 📋 자주 사용하는 명령어
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
# 상태 확인
|
|
220
|
+
git status
|
|
221
|
+
|
|
222
|
+
# 변경 내용 확인
|
|
223
|
+
git diff
|
|
224
|
+
|
|
225
|
+
# 최근 커밋 로그
|
|
226
|
+
git log --oneline -10
|
|
227
|
+
|
|
228
|
+
# 커밋 취소 (작업 내용 유지)
|
|
229
|
+
git reset --soft HEAD~1
|
|
230
|
+
|
|
231
|
+
# 스테이징 취소
|
|
232
|
+
git restore --staged .
|
|
233
|
+
|
|
234
|
+
# 변경사항 임시 저장
|
|
235
|
+
git stash
|
|
236
|
+
git stash pop
|
|
237
|
+
|
|
238
|
+
# 특정 파일만 스테이징
|
|
239
|
+
git add <파일경로>
|
|
240
|
+
|
|
241
|
+
# 대화형 스테이징 (부분 커밋용)
|
|
242
|
+
git add -p
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## 📚 .gitignore
|
|
248
|
+
|
|
249
|
+
```gitignore
|
|
250
|
+
# Dependencies
|
|
251
|
+
node_modules/
|
|
252
|
+
|
|
253
|
+
# Build
|
|
254
|
+
dist/
|
|
255
|
+
.output/
|
|
256
|
+
|
|
257
|
+
# Environment
|
|
258
|
+
.env
|
|
259
|
+
.env.local
|
|
260
|
+
.dev.vars
|
|
261
|
+
|
|
262
|
+
# IDE
|
|
263
|
+
.idea/
|
|
264
|
+
.vscode/
|
|
265
|
+
*.swp
|
|
266
|
+
|
|
267
|
+
# OS
|
|
268
|
+
.DS_Store
|
|
269
|
+
|
|
270
|
+
# Prisma
|
|
271
|
+
prisma/generated/
|
|
272
|
+
|
|
273
|
+
# Wrangler
|
|
274
|
+
.wrangler/
|
|
275
|
+
|
|
276
|
+
# Cache
|
|
277
|
+
.cache/
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## 🔗 관련 문서
|
|
283
|
+
|
|
284
|
+
- [CLAUDE.md](../../CLAUDE.md)
|
|
285
|
+
- [아키텍처](../architecture/architecture.md)
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
# AI SDK - Hono 통합 가이드
|
|
2
|
+
|
|
3
|
+
> Vercel AI SDK를 Hono와 함께 사용하기
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 개요
|
|
8
|
+
|
|
9
|
+
AI SDK는 TypeScript 기반의 AI 애플리케이션 개발 라이브러리입니다. Hono의 경량화된 API와 결합하여 고성능 AI 엔드포인트를 구축할 수 있습니다.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 설치
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install ai @ai-sdk/openai
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### 프로바이더별 설치
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @ai-sdk/anthropic # Claude
|
|
23
|
+
npm install @ai-sdk/google # Gemini
|
|
24
|
+
npm install @ai-sdk/mistral # Mistral
|
|
25
|
+
npm install @ai-sdk/groq # Groq
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 빠른 시작
|
|
31
|
+
|
|
32
|
+
### 기본 채팅 API
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { Hono } from 'hono'
|
|
36
|
+
import { streamText, convertToModelMessages } from 'ai'
|
|
37
|
+
import { openai } from '@ai-sdk/openai'
|
|
38
|
+
|
|
39
|
+
const app = new Hono()
|
|
40
|
+
|
|
41
|
+
app.post('/api/chat', async (c) => {
|
|
42
|
+
const { messages } = await c.req.json()
|
|
43
|
+
|
|
44
|
+
const result = streamText({
|
|
45
|
+
model: openai('gpt-4o'),
|
|
46
|
+
messages: convertToModelMessages(messages),
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
return result.toUIMessageStreamResponse()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
export default app
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 텍스트 생성 API
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { Hono } from 'hono'
|
|
59
|
+
import { generateText } from 'ai'
|
|
60
|
+
import { openai } from '@ai-sdk/openai'
|
|
61
|
+
|
|
62
|
+
const app = new Hono()
|
|
63
|
+
|
|
64
|
+
app.post('/api/generate', async (c) => {
|
|
65
|
+
const { prompt } = await c.req.json()
|
|
66
|
+
|
|
67
|
+
const { text } = await generateText({
|
|
68
|
+
model: openai('gpt-4o'),
|
|
69
|
+
prompt,
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
return c.json({ text })
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
export default app
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 핵심 함수
|
|
81
|
+
|
|
82
|
+
| 함수 | 용도 | 응답 타입 |
|
|
83
|
+
|------|------|----------|
|
|
84
|
+
| `generateText` | 텍스트 생성 (비스트리밍) | `Promise<{ text }>` |
|
|
85
|
+
| `streamText` | 텍스트 스트리밍 | `StreamTextResult` |
|
|
86
|
+
| `generateObject` | 구조화된 객체 생성 | `Promise<{ object }>` |
|
|
87
|
+
| `streamObject` | 객체 스트리밍 | `StreamObjectResult` |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Hono 미들웨어 패턴
|
|
92
|
+
|
|
93
|
+
### AI 컨텍스트 미들웨어
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { Hono } from 'hono'
|
|
97
|
+
import { createMiddleware } from 'hono/factory'
|
|
98
|
+
import { openai } from '@ai-sdk/openai'
|
|
99
|
+
|
|
100
|
+
type AIVariables = {
|
|
101
|
+
aiModel: ReturnType<typeof openai>
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const aiMiddleware = createMiddleware<{ Variables: AIVariables }>(
|
|
105
|
+
async (c, next) => {
|
|
106
|
+
c.set('aiModel', openai('gpt-4o'))
|
|
107
|
+
await next()
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
const app = new Hono<{ Variables: AIVariables }>()
|
|
112
|
+
|
|
113
|
+
app.use('/api/ai/*', aiMiddleware)
|
|
114
|
+
|
|
115
|
+
app.post('/api/ai/chat', async (c) => {
|
|
116
|
+
const model = c.get('aiModel')
|
|
117
|
+
const { messages } = await c.req.json()
|
|
118
|
+
|
|
119
|
+
const result = streamText({
|
|
120
|
+
model,
|
|
121
|
+
messages,
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
return result.toUIMessageStreamResponse()
|
|
125
|
+
})
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Rate Limiting 미들웨어
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { Hono } from 'hono'
|
|
132
|
+
import { rateLimiter } from 'hono-rate-limiter'
|
|
133
|
+
import { streamText } from 'ai'
|
|
134
|
+
import { openai } from '@ai-sdk/openai'
|
|
135
|
+
|
|
136
|
+
const app = new Hono()
|
|
137
|
+
|
|
138
|
+
// AI 엔드포인트에 Rate Limiting 적용
|
|
139
|
+
app.use(
|
|
140
|
+
'/api/ai/*',
|
|
141
|
+
rateLimiter({
|
|
142
|
+
windowMs: 60 * 1000, // 1분
|
|
143
|
+
limit: 10, // 최대 10 요청
|
|
144
|
+
standardHeaders: 'draft-6',
|
|
145
|
+
keyGenerator: (c) => c.req.header('x-forwarded-for') ?? 'anonymous',
|
|
146
|
+
})
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
app.post('/api/ai/chat', async (c) => {
|
|
150
|
+
const { messages } = await c.req.json()
|
|
151
|
+
|
|
152
|
+
const result = streamText({
|
|
153
|
+
model: openai('gpt-4o'),
|
|
154
|
+
messages,
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
return result.toUIMessageStreamResponse()
|
|
158
|
+
})
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 스트리밍 응답 처리
|
|
164
|
+
|
|
165
|
+
### 기본 스트리밍
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { Hono } from 'hono'
|
|
169
|
+
import { streamText } from 'ai'
|
|
170
|
+
import { openai } from '@ai-sdk/openai'
|
|
171
|
+
|
|
172
|
+
const app = new Hono()
|
|
173
|
+
|
|
174
|
+
app.post('/api/stream', async (c) => {
|
|
175
|
+
const { prompt } = await c.req.json()
|
|
176
|
+
|
|
177
|
+
const result = streamText({
|
|
178
|
+
model: openai('gpt-4o'),
|
|
179
|
+
prompt,
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
// UI 메시지 스트림 (프론트엔드용)
|
|
183
|
+
return result.toUIMessageStreamResponse()
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
app.post('/api/stream-text', async (c) => {
|
|
187
|
+
const { prompt } = await c.req.json()
|
|
188
|
+
|
|
189
|
+
const result = streamText({
|
|
190
|
+
model: openai('gpt-4o'),
|
|
191
|
+
prompt,
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
// 텍스트 스트림 (SSE)
|
|
195
|
+
return result.toTextStreamResponse()
|
|
196
|
+
})
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### 커스텀 스트림 처리
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import { Hono } from 'hono'
|
|
203
|
+
import { streamText } from 'ai'
|
|
204
|
+
import { openai } from '@ai-sdk/openai'
|
|
205
|
+
import { stream } from 'hono/streaming'
|
|
206
|
+
|
|
207
|
+
const app = new Hono()
|
|
208
|
+
|
|
209
|
+
app.post('/api/custom-stream', async (c) => {
|
|
210
|
+
const { prompt } = await c.req.json()
|
|
211
|
+
|
|
212
|
+
const result = streamText({
|
|
213
|
+
model: openai('gpt-4o'),
|
|
214
|
+
prompt,
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
return stream(c, async (stream) => {
|
|
218
|
+
for await (const chunk of result.textStream) {
|
|
219
|
+
await stream.write(chunk)
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
})
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## 도구 (Tool) 통합
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
import { Hono } from 'hono'
|
|
231
|
+
import { streamText, tool, convertToModelMessages } from 'ai'
|
|
232
|
+
import { openai } from '@ai-sdk/openai'
|
|
233
|
+
import { z } from 'zod'
|
|
234
|
+
|
|
235
|
+
const app = new Hono()
|
|
236
|
+
|
|
237
|
+
app.post('/api/chat-with-tools', async (c) => {
|
|
238
|
+
const { messages } = await c.req.json()
|
|
239
|
+
|
|
240
|
+
const result = streamText({
|
|
241
|
+
model: openai('gpt-4o'),
|
|
242
|
+
messages: convertToModelMessages(messages),
|
|
243
|
+
tools: {
|
|
244
|
+
getWeather: tool({
|
|
245
|
+
description: 'Get weather for a location',
|
|
246
|
+
inputSchema: z.object({
|
|
247
|
+
location: z.string().describe('City name'),
|
|
248
|
+
}),
|
|
249
|
+
execute: async ({ location }) => {
|
|
250
|
+
// 실제 날씨 API 호출
|
|
251
|
+
return { location, temperature: 22, condition: 'Sunny' }
|
|
252
|
+
},
|
|
253
|
+
}),
|
|
254
|
+
searchDatabase: tool({
|
|
255
|
+
description: 'Search the database',
|
|
256
|
+
inputSchema: z.object({
|
|
257
|
+
query: z.string(),
|
|
258
|
+
}),
|
|
259
|
+
execute: async ({ query }) => {
|
|
260
|
+
// 데이터베이스 검색
|
|
261
|
+
return { results: [] }
|
|
262
|
+
},
|
|
263
|
+
}),
|
|
264
|
+
},
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
return result.toUIMessageStreamResponse()
|
|
268
|
+
})
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## 구조화된 출력
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
import { Hono } from 'hono'
|
|
277
|
+
import { generateObject, streamObject } from 'ai'
|
|
278
|
+
import { openai } from '@ai-sdk/openai'
|
|
279
|
+
import { z } from 'zod'
|
|
280
|
+
|
|
281
|
+
const app = new Hono()
|
|
282
|
+
|
|
283
|
+
const userSchema = z.object({
|
|
284
|
+
name: z.string(),
|
|
285
|
+
age: z.number(),
|
|
286
|
+
email: z.string().email(),
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
// 비스트리밍 객체 생성
|
|
290
|
+
app.post('/api/generate-user', async (c) => {
|
|
291
|
+
const { prompt } = await c.req.json()
|
|
292
|
+
|
|
293
|
+
const { object } = await generateObject({
|
|
294
|
+
model: openai('gpt-4o'),
|
|
295
|
+
schema: userSchema,
|
|
296
|
+
prompt,
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
return c.json(object)
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
// 스트리밍 객체 생성
|
|
303
|
+
app.post('/api/stream-user', async (c) => {
|
|
304
|
+
const { prompt } = await c.req.json()
|
|
305
|
+
|
|
306
|
+
const result = streamObject({
|
|
307
|
+
model: openai('gpt-4o'),
|
|
308
|
+
schema: userSchema,
|
|
309
|
+
prompt,
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
return result.toTextStreamResponse()
|
|
313
|
+
})
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## 에러 처리
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
import { Hono } from 'hono'
|
|
322
|
+
import { HTTPException } from 'hono/http-exception'
|
|
323
|
+
import { streamText } from 'ai'
|
|
324
|
+
import { openai } from '@ai-sdk/openai'
|
|
325
|
+
|
|
326
|
+
const app = new Hono()
|
|
327
|
+
|
|
328
|
+
app.post('/api/chat', async (c) => {
|
|
329
|
+
try {
|
|
330
|
+
const { messages } = await c.req.json()
|
|
331
|
+
|
|
332
|
+
if (!messages || !Array.isArray(messages)) {
|
|
333
|
+
throw new HTTPException(400, { message: 'Invalid messages format' })
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const result = streamText({
|
|
337
|
+
model: openai('gpt-4o'),
|
|
338
|
+
messages,
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
return result.toUIMessageStreamResponse()
|
|
342
|
+
} catch (error) {
|
|
343
|
+
if (error instanceof HTTPException) {
|
|
344
|
+
throw error
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
console.error('AI Error:', error)
|
|
348
|
+
throw new HTTPException(500, { message: 'AI processing failed' })
|
|
349
|
+
}
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
// 글로벌 에러 핸들러
|
|
353
|
+
app.onError((err, c) => {
|
|
354
|
+
if (err instanceof HTTPException) {
|
|
355
|
+
return c.json({ error: err.message }, err.status)
|
|
356
|
+
}
|
|
357
|
+
return c.json({ error: 'Internal server error' }, 500)
|
|
358
|
+
})
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Cloudflare Workers 배포
|
|
364
|
+
|
|
365
|
+
### 기본 설정
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
// src/index.ts
|
|
369
|
+
import { Hono } from 'hono'
|
|
370
|
+
import { streamText, convertToModelMessages } from 'ai'
|
|
371
|
+
import { createOpenAI } from '@ai-sdk/openai'
|
|
372
|
+
|
|
373
|
+
type Bindings = {
|
|
374
|
+
OPENAI_API_KEY: string
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const app = new Hono<{ Bindings: Bindings }>()
|
|
378
|
+
|
|
379
|
+
app.post('/api/chat', async (c) => {
|
|
380
|
+
const openai = createOpenAI({
|
|
381
|
+
apiKey: c.env.OPENAI_API_KEY,
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
const { messages } = await c.req.json()
|
|
385
|
+
|
|
386
|
+
const result = streamText({
|
|
387
|
+
model: openai('gpt-4o'),
|
|
388
|
+
messages: convertToModelMessages(messages),
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
return result.toUIMessageStreamResponse()
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
export default app
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### wrangler.toml
|
|
398
|
+
|
|
399
|
+
```toml
|
|
400
|
+
name = "ai-api"
|
|
401
|
+
main = "src/index.ts"
|
|
402
|
+
compatibility_date = "2024-01-01"
|
|
403
|
+
|
|
404
|
+
[vars]
|
|
405
|
+
# 환경 변수는 Cloudflare 대시보드에서 설정
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## 환경 변수
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
# .env
|
|
414
|
+
OPENAI_API_KEY=sk-...
|
|
415
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
416
|
+
GOOGLE_GENERATIVE_AI_API_KEY=...
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## 관련 문서
|
|
422
|
+
|
|
423
|
+
- [프로바이더](./providers.md) - AI 프로바이더 설정
|
|
424
|
+
- [OpenRouter](./openrouter.md) - 통합 AI 게이트웨이 (수백 개 모델)
|
|
425
|
+
- [스트리밍](./streaming.md) - 텍스트 생성과 스트리밍
|
|
426
|
+
- [도구](./tools.md) - Tool Calling 구현
|
|
427
|
+
- [구조화된 출력](./structured-output.md) - 타입 안전한 객체 생성
|