1ㅊㅏ완료
This commit is contained in:
@@ -4,9 +4,47 @@
|
||||
|
||||
const OPENAI_API_URL = 'https://api.openai.com/v1/chat/completions'
|
||||
|
||||
// 모델별 파라미터 설정
|
||||
const MODEL_CONFIG: Record<string, { maxTokensParam: string; defaultMaxTokens: number }> = {
|
||||
// 최신 모델 (max_completion_tokens 사용)
|
||||
'gpt-5.1': { maxTokensParam: 'max_completion_tokens', defaultMaxTokens: 4096 },
|
||||
'gpt-5': { maxTokensParam: 'max_completion_tokens', defaultMaxTokens: 4096 },
|
||||
'gpt-4.1': { maxTokensParam: 'max_completion_tokens', defaultMaxTokens: 4096 },
|
||||
'gpt-4.1-mini': { maxTokensParam: 'max_completion_tokens', defaultMaxTokens: 4096 },
|
||||
'gpt-4.1-nano': { maxTokensParam: 'max_completion_tokens', defaultMaxTokens: 4096 },
|
||||
'o1': { maxTokensParam: 'max_completion_tokens', defaultMaxTokens: 4096 },
|
||||
'o1-mini': { maxTokensParam: 'max_completion_tokens', defaultMaxTokens: 4096 },
|
||||
'o1-pro': { maxTokensParam: 'max_completion_tokens', defaultMaxTokens: 4096 },
|
||||
'o3-mini': { maxTokensParam: 'max_completion_tokens', defaultMaxTokens: 4096 },
|
||||
|
||||
// 이전 모델 (max_tokens 사용)
|
||||
'gpt-4o': { maxTokensParam: 'max_tokens', defaultMaxTokens: 4096 },
|
||||
'gpt-4o-mini': { maxTokensParam: 'max_tokens', defaultMaxTokens: 4096 },
|
||||
'gpt-4-turbo': { maxTokensParam: 'max_tokens', defaultMaxTokens: 4096 },
|
||||
'gpt-4': { maxTokensParam: 'max_tokens', defaultMaxTokens: 4096 },
|
||||
'gpt-3.5-turbo': { maxTokensParam: 'max_tokens', defaultMaxTokens: 4096 },
|
||||
}
|
||||
|
||||
// 기본 모델 설정
|
||||
const DEFAULT_MODEL = 'gpt-5.1'
|
||||
|
||||
function getModelConfig(model: string) {
|
||||
if (MODEL_CONFIG[model]) {
|
||||
return MODEL_CONFIG[model]
|
||||
}
|
||||
|
||||
for (const key of Object.keys(MODEL_CONFIG)) {
|
||||
if (model.startsWith(key)) {
|
||||
return MODEL_CONFIG[key]
|
||||
}
|
||||
}
|
||||
|
||||
return { maxTokensParam: 'max_completion_tokens', defaultMaxTokens: 4096 }
|
||||
}
|
||||
|
||||
interface ChatMessage {
|
||||
role: 'system' | 'user' | 'assistant'
|
||||
content: string
|
||||
content: string | Array<{ type: string; text?: string; image_url?: { url: string } }>
|
||||
}
|
||||
|
||||
interface OpenAIResponse {
|
||||
@@ -17,25 +55,37 @@ interface OpenAIResponse {
|
||||
}[]
|
||||
}
|
||||
|
||||
export async function callOpenAI(messages: ChatMessage[], jsonMode = true): Promise<string> {
|
||||
export async function callOpenAI(
|
||||
messages: ChatMessage[],
|
||||
jsonMode = true,
|
||||
model = DEFAULT_MODEL
|
||||
): Promise<string> {
|
||||
const apiKey = process.env.OPENAI_API_KEY
|
||||
|
||||
if (!apiKey || apiKey === 'your-openai-api-key-here') {
|
||||
throw new Error('OPENAI_API_KEY가 설정되지 않았습니다.')
|
||||
}
|
||||
|
||||
const config = getModelConfig(model)
|
||||
|
||||
const requestBody: any = {
|
||||
model,
|
||||
messages,
|
||||
temperature: 0.1,
|
||||
[config.maxTokensParam]: config.defaultMaxTokens,
|
||||
}
|
||||
|
||||
if (jsonMode) {
|
||||
requestBody.response_format = { type: 'json_object' }
|
||||
}
|
||||
|
||||
const response = await fetch(OPENAI_API_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiKey}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'gpt-4o-mini',
|
||||
messages,
|
||||
temperature: 0.1,
|
||||
...(jsonMode && { response_format: { type: 'json_object' } })
|
||||
})
|
||||
body: JSON.stringify(requestBody)
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -48,54 +98,146 @@ export async function callOpenAI(messages: ChatMessage[], jsonMode = true): Prom
|
||||
}
|
||||
|
||||
/**
|
||||
* 주간보고 텍스트 분석 프롬프트
|
||||
* 이미지 분석용 OpenAI 호출 (Vision)
|
||||
*/
|
||||
export function buildParseReportPrompt(rawText: string): ChatMessage[] {
|
||||
return [
|
||||
{
|
||||
role: 'system',
|
||||
content: `당신은 주간업무보고 텍스트를 분석하여 구조화된 JSON으로 변환하는 전문가입니다.
|
||||
export async function callOpenAIVision(
|
||||
systemPrompt: string,
|
||||
imageBase64List: string[],
|
||||
model = DEFAULT_MODEL
|
||||
): Promise<string> {
|
||||
const apiKey = process.env.OPENAI_API_KEY
|
||||
|
||||
if (!apiKey || apiKey === 'your-openai-api-key-here') {
|
||||
throw new Error('OPENAI_API_KEY가 설정되지 않았습니다.')
|
||||
}
|
||||
|
||||
const config = getModelConfig(model)
|
||||
|
||||
const imageContents = imageBase64List.map(base64 => ({
|
||||
type: 'image_url' as const,
|
||||
image_url: {
|
||||
url: base64.startsWith('data:') ? base64 : `data:image/png;base64,${base64}`
|
||||
}
|
||||
}))
|
||||
|
||||
const requestBody: any = {
|
||||
model,
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text: '이 이미지들에서 주간보고 내용을 추출해주세요.' },
|
||||
...imageContents
|
||||
]
|
||||
}
|
||||
],
|
||||
temperature: 0.1,
|
||||
[config.maxTokensParam]: config.defaultMaxTokens,
|
||||
response_format: { type: 'json_object' }
|
||||
}
|
||||
|
||||
const response = await fetch(OPENAI_API_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiKey}`
|
||||
},
|
||||
body: JSON.stringify(requestBody)
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text()
|
||||
throw new Error(`OpenAI Vision API 오류: ${response.status} - ${error}`)
|
||||
}
|
||||
|
||||
const data = await response.json() as OpenAIResponse
|
||||
return data.choices[0].message.content
|
||||
}
|
||||
|
||||
입력된 텍스트에서 다음 정보를 추출하세요:
|
||||
1. 직원 정보 (이름, 이메일)
|
||||
2. 프로젝트별 실적 (프로젝트명, 금주실적, 차주계획)
|
||||
3. 공통사항 (이슈/리스크, 휴가일정, 기타사항)
|
||||
4. 보고 주차 정보 (텍스트에서 날짜나 주차 정보 추출)
|
||||
/**
|
||||
* 주간보고 분석 시스템 프롬프트 (Task 기반)
|
||||
*/
|
||||
export const REPORT_PARSE_SYSTEM_PROMPT = `당신은 주간업무보고 텍스트를 분석하여 구조화된 JSON으로 변환하는 전문가입니다.
|
||||
|
||||
## 핵심 원칙
|
||||
- **원문의 내용을 그대로 유지하세요!**
|
||||
- **Task는 적당히 묶어서 정리하세요. 너무 세분화하지 마세요!**
|
||||
- 하나의 Task에 여러 줄이 들어갈 수 있습니다.
|
||||
|
||||
## Task 분리 규칙 (중요!)
|
||||
|
||||
❌ 잘못된 예 (너무 세분화):
|
||||
- Task 1: "API 개발"
|
||||
- Task 2: "API 테스트"
|
||||
|
||||
✅ 올바른 예 (적절히 묶기):
|
||||
- Task 1: "API 개발 및 테스트 완료"
|
||||
|
||||
❌ 잘못된 예 (프로젝트명 반복):
|
||||
- "PIMS 고도화 - 사용자 인증 개발"
|
||||
|
||||
✅ 올바른 예 (프로젝트명 제외):
|
||||
- "사용자 인증 개발"
|
||||
|
||||
## 완료여부(isCompleted) 판단 규칙 ★중요★
|
||||
|
||||
금주 실적(workTasks)의 완료여부를 판단합니다:
|
||||
- 기본값: true (완료)
|
||||
- false (진행중): 차주 계획(planTasks)에 비슷한/연관된 작업이 있는 경우
|
||||
|
||||
예시:
|
||||
- 실적: "로그인 API 개발" + 계획: "로그인 API 테스트" → isCompleted: false (연관 작업 있음)
|
||||
- 실적: "DB 백업 완료" + 계획에 관련 없음 → isCompleted: true
|
||||
|
||||
## 수행시간 예측 기준
|
||||
- **0시간**: "없음", "특이사항 없음", "해당없음", "한 게 없다", "작업 없음" 등 실제 작업이 없는 경우
|
||||
- 단순 작업: 2~4시간
|
||||
- 일반 작업: 8시간 (1일)
|
||||
- 복잡한 작업: 16~24시간 (2~3일)
|
||||
|
||||
## JSON 출력 형식
|
||||
|
||||
반드시 아래 JSON 형식으로 응답하세요:
|
||||
{
|
||||
"reportYear": 2025,
|
||||
"reportWeek": 1,
|
||||
"reportWeek": 2,
|
||||
"weekStartDate": "2025-01-06",
|
||||
"weekEndDate": "2025-01-10",
|
||||
"weekEndDate": "2025-01-12",
|
||||
"reports": [
|
||||
{
|
||||
"employeeName": "홍길동",
|
||||
"employeeEmail": "hong@example.com",
|
||||
"projects": [
|
||||
{
|
||||
"projectName": "프로젝트명",
|
||||
"workDescription": "금주 실적 내용",
|
||||
"planDescription": "차주 계획 내용"
|
||||
"projectName": "PIMS 고도화",
|
||||
"workTasks": [
|
||||
{ "description": "사용자 인증 모듈 개발", "hours": 16, "isCompleted": false },
|
||||
{ "description": "DB 백업 스크립트 작성", "hours": 4, "isCompleted": true }
|
||||
],
|
||||
"planTasks": [
|
||||
{ "description": "사용자 인증 테스트 및 배포", "hours": 8 }
|
||||
]
|
||||
}
|
||||
],
|
||||
"issueDescription": "이슈/리스크 내용 또는 null",
|
||||
"vacationDescription": "휴가 일정 또는 null",
|
||||
"remarkDescription": "기타 사항 또는 null"
|
||||
"issueDescription": "개발서버 메모리 부족",
|
||||
"vacationDescription": null,
|
||||
"remarkDescription": null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
주의사항:
|
||||
- 이메일이 없으면 employeeEmail은 null로
|
||||
- 프로젝트가 여러개면 projects 배열에 모두 포함
|
||||
- 날짜 형식은 YYYY-MM-DD
|
||||
- 주차 정보가 없으면 현재 날짜 기준으로 추정
|
||||
- 실적/계획이 명확히 구분 안되면 workDescription에 통합`
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: rawText
|
||||
}
|
||||
## 주의사항
|
||||
- Task description에 프로젝트명을 포함하지 마세요
|
||||
- 비슷한 작업은 하나의 Task로 묶으세요
|
||||
- 한 Task 내 여러 항목은 \\n으로 줄바꿈
|
||||
- 이메일이 없으면 employeeEmail은 null`
|
||||
|
||||
/**
|
||||
* 주간보고 텍스트 분석 프롬프트
|
||||
*/
|
||||
export function buildParseReportPrompt(rawText: string): ChatMessage[] {
|
||||
return [
|
||||
{ role: 'system', content: REPORT_PARSE_SYSTEM_PROMPT },
|
||||
{ role: 'user', content: rawText }
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user