fix: Server Expert AI JSON parsing and numeric type conversion
- Add response_format: { type: 'json_object' } for review mode to force JSON response
- Convert expectedDau and expectedConcurrent from string to number before API call
- Add enhanced KV session debugging with key names in logs
Fixes:
- AI returning plain text instead of JSON in review mode
- 400 error from recommend API due to string values in expected_users
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,17 +25,18 @@ export async function getServerSession(
|
|||||||
): Promise<ServerSession | null> {
|
): Promise<ServerSession | null> {
|
||||||
try {
|
try {
|
||||||
const key = `${SESSION_KEY_PREFIX}${userId}`;
|
const key = `${SESSION_KEY_PREFIX}${userId}`;
|
||||||
|
logger.info('세션 조회 시도', { userId, key });
|
||||||
const data = await kv.get(key, 'json');
|
const data = await kv.get(key, 'json');
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
logger.info('세션 없음', { userId });
|
logger.info('세션 없음', { userId, key });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('세션 조회 성공', { userId, status: (data as ServerSession).status });
|
logger.info('세션 조회 성공', { userId, key, status: (data as ServerSession).status });
|
||||||
return data as ServerSession;
|
return data as ServerSession;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('세션 조회 실패', error as Error, { userId });
|
logger.error('세션 조회 실패', error as Error, { userId, key: `${SESSION_KEY_PREFIX}${userId}` });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,13 +50,16 @@ export async function saveServerSession(
|
|||||||
const key = `${SESSION_KEY_PREFIX}${userId}`;
|
const key = `${SESSION_KEY_PREFIX}${userId}`;
|
||||||
session.updatedAt = Date.now();
|
session.updatedAt = Date.now();
|
||||||
|
|
||||||
await kv.put(key, JSON.stringify(session), {
|
const sessionData = JSON.stringify(session);
|
||||||
|
logger.info('세션 저장 시도', { userId, key, status: session.status, dataLength: sessionData.length });
|
||||||
|
|
||||||
|
await kv.put(key, sessionData, {
|
||||||
expirationTtl: SESSION_TTL,
|
expirationTtl: SESSION_TTL,
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info('세션 저장 성공', { userId, status: session.status });
|
logger.info('세션 저장 성공', { userId, key, status: session.status });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('세션 저장 실패', error as Error, { userId });
|
logger.error('세션 저장 실패', error as Error, { userId, key: `${SESSION_KEY_PREFIX}${userId}` });
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -370,20 +374,31 @@ ${JSON.stringify(session.collectedInfo, null, 2)}
|
|||||||
|
|
||||||
// Loop to handle tool calls
|
// Loop to handle tool calls
|
||||||
while (toolCallCount < MAX_TOOL_CALLS) {
|
while (toolCallCount < MAX_TOOL_CALLS) {
|
||||||
|
// 검토 모드에서는 도구 없이 JSON 응답만 요청
|
||||||
|
const requestBody = isReviewMode
|
||||||
|
? {
|
||||||
|
model: 'gpt-4o-mini',
|
||||||
|
messages,
|
||||||
|
response_format: { type: 'json_object' },
|
||||||
|
max_tokens: 500,
|
||||||
|
temperature: 0.5,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
model: 'gpt-4o-mini',
|
||||||
|
messages,
|
||||||
|
tools: serverExpertTools,
|
||||||
|
tool_choice: 'auto',
|
||||||
|
max_tokens: 800,
|
||||||
|
temperature: 0.7,
|
||||||
|
};
|
||||||
|
|
||||||
const response = await fetch(getOpenAIUrl(env), {
|
const response = await fetch(getOpenAIUrl(env), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': `Bearer ${env.OPENAI_API_KEY}`,
|
'Authorization': `Bearer ${env.OPENAI_API_KEY}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(requestBody),
|
||||||
model: 'gpt-4o-mini',
|
|
||||||
messages,
|
|
||||||
tools: serverExpertTools,
|
|
||||||
tool_choice: 'auto',
|
|
||||||
max_tokens: 800,
|
|
||||||
temperature: 0.7,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -570,11 +585,14 @@ export async function processServerConsultation(
|
|||||||
|
|
||||||
// 동시접속자 우선 사용, 없으면 scale 기반 추론
|
// 동시접속자 우선 사용, 없으면 scale 기반 추론
|
||||||
let expectedUsers = 10; // Default
|
let expectedUsers = 10; // Default
|
||||||
if (session.collectedInfo.expectedConcurrent) {
|
const concurrent = Number(session.collectedInfo.expectedConcurrent) || 0;
|
||||||
expectedUsers = session.collectedInfo.expectedConcurrent;
|
const dau = Number(session.collectedInfo.expectedDau) || 0;
|
||||||
} else if (session.collectedInfo.expectedDau) {
|
|
||||||
|
if (concurrent > 0) {
|
||||||
|
expectedUsers = concurrent;
|
||||||
|
} else if (dau > 0) {
|
||||||
// DAU가 있으면 10% 비율로 동시접속자 계산
|
// DAU가 있으면 10% 비율로 동시접속자 계산
|
||||||
expectedUsers = Math.ceil(session.collectedInfo.expectedDau * 0.1);
|
expectedUsers = Math.ceil(dau * 0.1);
|
||||||
} else if (session.collectedInfo.scale) {
|
} else if (session.collectedInfo.scale) {
|
||||||
expectedUsers = inferExpectedUsers(session.collectedInfo.scale);
|
expectedUsers = inferExpectedUsers(session.collectedInfo.scale);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user