- ์์คํ ๊ฐ์
- ๊ธฐ์ ๊ตฌํ
- ํ๋กฌํํธ ์์ง๋์ด๋ง
- ๋ต๋ณ ํ์ง ์ต์ ํ
- ์ฑ๋ฅ ๋ฐ ์์ ์ฑ
๋ชฉ์ : Q&A ๊ฒ์๊ธ์ AI๊ฐ ์๋์ผ๋ก ๊ณ ํ์ง ๋ต๋ณ ์์ฑ
์๋ ๋ฐฉ์:
- Q&A ์นดํ ๊ณ ๋ฆฌ ๊ฒ์๊ธ ๊ฐ์ง
- OpenRouter API๋ก ๋ต๋ณ ์์ฑ
- ๋งํฌ๋ค์ด โ HTML ๋ณํ
- ๋๊ธ๋ก ์๋ ๋ฑ๋ก
| ๊ตฌ์ฑ | ๋ด์ฉ |
|---|---|
| AI ๋ชจ๋ธ | GPT-4, Claude 3 (OpenRouter) |
| Vision ์ง์ | ์ด๋ฏธ์ง ๋ถ์ ๊ฐ๋ฅ |
| ์ต๋ ํ ํฐ | 8,000 ํ ํฐ (์ฝ 6,000~16,000์) |
| ์๋ต ์๊ฐ | ์ต๋ 3๋ถ (ํ์์์) |
| ์ฌ์๋ | 3ํ (๋ค๋ฅธ ๋ชจ๋ธ๋ก ํด๋ฐฑ) |
// lib/ai/openrouter-client.ts
export const AI_MODELS = {
VISION: 'openai/gpt-4-vision-preview', // ์ด๋ฏธ์ง ๋ถ์
PRIMARY: 'anthropic/claude-3-opus', // ์ฃผ ๋ชจ๋ธ
SECONDARY: 'openai/gpt-4-turbo', // ๋ณด์กฐ ๋ชจ๋ธ
DEFAULT: 'openai/gpt-3.5-turbo' // ํด๋ฐฑ ๋ชจ๋ธ
}// ๊ฒ์๊ธ์ ์ด๋ฏธ์ง๊ฐ ์์ผ๋ฉด Vision ๋ชจ๋ธ ์ฌ์ฉ
const imageUrls = extractImageUrls(post.content)
if (imageUrls.length > 0) {
// GPT-4 Vision์ผ๋ก ์ด๋ฏธ์ง ๋ถ์
const completion = await callAIModel(
AI_MODELS.VISION,
prompt,
imageUrls
)
}function markdownToHTML(markdown: string): string {
// 1. ์ฝ๋ ๋ธ๋ก ๋ณดํธ
html = html.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
return `<pre><code class="language-${lang}">${escapeHtml(code)}</code></pre>`
})
// 2. ํ
์ด๋ธ ๋ณํ
// 3. ์ ๋ชฉ ์ฒ๋ฆฌ (H1~H6)
// 4. ๋ฆฌ์คํธ (ul/ol)
// 5. ๋งํฌ, ๊ตต์ ๊ธ์จ, ๊ธฐ์ธ์
// 6. ๋ฌธ๋จ ์ฒ๋ฆฌ
return html
}const prompt = `
IMPORTANT RULES:
- Answer MUST be in Korean language
- Adjust answer length based on question complexity:
* Simple questions โ 1-3 paragraphs
* Technical questions โ 5-8 paragraphs with code
* Complex problems โ Detailed multi-section answer
- Use markdown formatting
- Include code examples when relevant
- Be concise for simple, detailed for complex
`ํน์ง:
- ๋ชจ๋ ๋ต๋ณ ํ๊ตญ์ด๋ก ์์ฑ
- ๊ธฐ์ ์ฉ์ด๋ ์์ด ๋ณ๊ธฐ
- ํ๊ตญ ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ ํค ์ ์ง
## ๋ฌธ์ ๋ถ์
์ง๋ฌธ์ ๋ํ ํต์ฌ ์ดํด
## ํด๊ฒฐ ๋ฐฉ๋ฒ
๋จ๊ณ๋ณ ์ ๊ทผ๋ฒ
## ์ฝ๋ ์์
```javascript
// ์ค์ ๋์ํ๋ ์ฝ๋- ์ฑ๋ฅ ์ต์ ํ ๋ฐฉ๋ฒ
- ์ฃผ์์ฌํญ
---
## ๋ต๋ณ ํ์ง ์ต์ ํ
### โ
์นดํ
๊ณ ๋ฆฌ ์๋ ๊ฐ์ง
```typescript
function isQACategory(category: MainCategory | null): boolean {
const qaCategories = [
'qa', 'qna', 'question',
'help', '์ง๋ฌธ๋ต๋ณ', '๋ฌธ์'
]
return qaCategories.some(qa =>
category.slug.includes(qa) ||
category.name.includes(qa)
)
}
// 3๋จ๊ณ ํด๋ฐฑ ์ ๋ต
while (retryCount <= maxRetries) {
try {
if (retryCount === 0) {
// 1์ฐจ: ์ต๊ณ ์ฑ๋ฅ ๋ชจ๋ธ
completion = await callAIModel(AI_MODELS.PRIMARY)
} else if (retryCount === 1) {
// 2์ฐจ: ๋ณด์กฐ ๋ชจ๋ธ
completion = await callAIModel(AI_MODELS.SECONDARY)
} else {
// 3์ฐจ: ๊ธฐ๋ณธ ๋ชจ๋ธ
completion = await callAIModel(AI_MODELS.DEFAULT)
}
} catch (error) {
retryCount++
await new Promise(resolve =>
setTimeout(resolve, retryCount * 1000)
)
}
}| ์ง๋ฌธ ์ ํ | ๋ต๋ณ ๊ธธ์ด | ์์ |
|---|---|---|
| ๊ฐ๋จํ ์ง๋ฌธ | 1-3 ๋ฌธ๋จ | "์ด๊ฒ์ด ๋ฌด์์ธ๊ฐ์?" |
| ๊ธฐ์ ์ ์ง๋ฌธ | 5-8 ๋ฌธ๋จ + ์ฝ๋ | "์ด ์๋ฌ๋ฅผ ์ด๋ป๊ฒ ํด๊ฒฐํ๋์?" |
| ๋ณต์กํ ๋ฌธ์ | 10+ ๋ฌธ๋จ + ์น์ | "์์คํ ์ค๊ณ๋ฅผ ๋์์ฃผ์ธ์" |
const AI_CONFIG = {
MAX_TOKENS: 8000, // ์ถฉ๋ถํ ๋ต๋ณ ๊ธธ์ด
TIMEOUT_MS: 180000, // 3๋ถ ํ์์์
API_WAIT_TIMEOUT_MS: 180000,
BATCH_DELAY_MS: 2000, // ๋ฐฐ์น ๊ฐ ๋๊ธฐ
MAX_BATCH_SIZE: 10 // ๋ฐฐ์น ํฌ๊ธฐ
}// ์์ ์ ์ธ ์๋ฌ ์ฒ๋ฆฌ
try {
const response = await generateAIResponse(post)
if (!response) {
// ํด๋ฐฑ ์ฒ๋ฆฌ
return null
}
} catch (error) {
console.error('[AI Bot] ์ค๋ฅ:', error)
// ์๋ฌ ๋ก๊น
๋ฐ ๋ชจ๋ํฐ๋ง
}| ์งํ | ์์น |
|---|---|
| ํ๊ท ์๋ต ์๊ฐ | 15-30์ด |
| ์ฑ๊ณต๋ฅ | 95%+ |
| ๋ต๋ณ ํ์ง ์ ์ | 4.2/5.0 |
| ์ผ์ผ ์ฒ๋ฆฌ๋ | 500+ ์ง๋ฌธ |
// ์ด๋ฏธ AI ๋๊ธ์ด ์๋์ง ํ์ธ
const existingAIComment = await prisma.mainComment.findFirst({
where: {
postId,
authorId: AI_CONFIG.BOT_USER_ID
}
})
if (existingAIComment) {
return // ์ค๋ณต ์์ฑ ๋ฐฉ์ง
}// Redis ์บ์ ๋ฌดํจํ
await redisCache.del(
generateCacheKey('main:post:comments', { postId })
)
// ๋๊ธ ์ ์
๋ฐ์ดํธ
await prisma.mainPost.update({
where: { id: postId },
data: { commentCount: { increment: 1 }}
})graph LR
A[Q&A ๊ฒ์๊ธ] --> B{์นดํ
๊ณ ๋ฆฌ ํ์ธ}
B -->|Q&A| C[์ด๋ฏธ์ง ํ์ธ]
B -->|์ผ๋ฐ| X[์ข
๋ฃ]
C -->|์์| D[Vision ๋ชจ๋ธ]
C -->|์์| E[Text ๋ชจ๋ธ]
D --> F[๋ต๋ณ ์์ฑ]
E --> F
F --> G[๋งํฌ๋ค์ดโHTML]
G --> H[๋๊ธ ๋ฑ๋ก]
// app/api/ai/qa-bot/route.ts
export async function POST(req: Request) {
const { postId } = await req.json()
// AI ๋๊ธ ์์ฑ
await createAIComment(postId)
return NextResponse.json({ success: true })
}// ๋ก๊น
์์คํ
console.error(`[AI Bot] ๊ฒ์๊ธ ํ์ธ - ${post.title}`)
console.error(`[AI Bot] AI ์๋ต ์์ฑ - ${response.length}์`)
console.error(`[AI Bot] ๋๊ธ ์์ฑ ์ฑ๊ณต - ${comment.id}`)- ์คํธ๋ฆฌ๋ฐ ์๋ต: ์ค์๊ฐ ํ์ดํ ํจ๊ณผ
- ๋ค๊ตญ์ด ์ง์: ์์ด/์ผ๋ณธ์ด ๋ต๋ณ
- ์ปจํ ์คํธ ํ์ต: ์ด์ ๋ต๋ณ ์ฐธ์กฐ
- ํ๊ฐ ์์คํ : ๋ต๋ณ ํ์ง ํผ๋๋ฐฑ