feat(日志): 重构日志系统并添加系统提示管理功能

- 将logPrompt重命名为logConversation以支持输入输出日志
- 添加manageSystemPrompt函数来管理系统提示文本文件
- 在流式和非流式请求中记录完整响应文本
- 改进提示文本提取逻辑以获取最新用户输入
This commit is contained in:
hex2077 2025-07-21 14:28:53 +08:00
parent 1f24706f3f
commit ea0e49f568
3 changed files with 87 additions and 36 deletions

View file

@ -94,10 +94,11 @@ import {
GeminiApiService,
API_ACTIONS,
formatExpiryTime,
logPrompt,
logConversation, // Changed from logPrompt
extractPromptText,
extractResponseText,
getRequestBody
getRequestBody,
manageSystemPrompt,
} from './gemini-core.js';
// --- Configuration Parsing ---
@ -209,9 +210,13 @@ async function handleStreamRequest(res, service, model, requestBody) {
const stream = service.generateContentStream(model, requestBody);
console.log('[Server Response Stream]');
process.stdout.write('> ');
let fullResponseText = '';
for await (const chunk of stream) {
const chunkText = extractResponseText(chunk);
if (chunkText) process.stdout.write(chunkText);
if (chunkText) {
process.stdout.write(chunkText);
fullResponseText += chunkText;
}
const chunkString = JSON.stringify(chunk);
res.write(`data: ${chunkString}\n\n`);
}
@ -219,6 +224,8 @@ async function handleStreamRequest(res, service, model, requestBody) {
res.end();
const expiryDate = service.authClient.credentials.expiry_date;
console.log(`[Auth Token] Time until expiry: ${formatExpiryTime(expiryDate)}`);
await logConversation('output', fullResponseText, PROMPT_LOG_MODE, PROMPT_LOG_FILENAME);
}
async function handleUnaryRequest(res, service, model, requestBody) {
const response = await service.generateContent(model, requestBody);
@ -232,6 +239,8 @@ async function handleUnaryRequest(res, service, model, requestBody) {
res.end(responseString);
const expiryDate = service.authClient.credentials.expiry_date;
console.log(`[Auth Token] Time until expiry: ${formatExpiryTime(expiryDate)}`);
await logConversation('output', responseText, PROMPT_LOG_MODE, PROMPT_LOG_FILENAME);
}
function handleError(res, error) {
console.error('\n[Server] Request failed:', error.stack);
@ -271,16 +280,16 @@ async function requestHandler(req, res) {
const [, model, action] = urlMatch;
const requestBody = await getRequestBody(req);
if (PROMPT_LOG_MODE !== 'none') {
const promptText = extractPromptText(requestBody);
await logPrompt(promptText, PROMPT_LOG_MODE, PROMPT_LOG_FILENAME);
}
await manageSystemPrompt(requestBody); // Call the new function here
const promptText = extractPromptText(requestBody);
await logConversation('input', promptText, PROMPT_LOG_MODE, PROMPT_LOG_FILENAME);
if (action === API_ACTIONS.STREAM_GENERATE_CONTENT) {
await handleStreamRequest(res, service, model, requestBody);
} else {
await handleUnaryRequest(res, service, model, requestBody);
}
return;
}

View file

@ -17,7 +17,7 @@ export const API_ACTIONS = {
GENERATE_CONTENT: 'generateContent',
STREAM_GENERATE_CONTENT: 'streamGenerateContent',
};
const SYSTEM_PROMPT_FILE = path.join(process.cwd(), 'system_prompt.txt');
// --- Utility Functions ---
export function ensureRolesInContents(requestBody) {
@ -54,41 +54,77 @@ export function formatExpiryTime(expiryTimestamp) {
return `${pad(hours)}h ${pad(minutes)}m ${pad(seconds)}s`;
}
export async function logPrompt(promptText, logMode, logFilename) {
export async function logConversation(type, content, logMode, logFilename) {
if (logMode === 'none') return;
const logContent = `--- Prompt Log @ ${new Date().toISOString()} ---\n${promptText}\n--------------------------------------\n\n`;
if (logMode === 'console') {
console.log(logContent);
const timestamp = new Date().toLocaleString();
const logEntry = `${timestamp} [${type.toUpperCase()}]:\n${content}\n\n\n--------------------------------------\n\n\n`;
if (logMode === 'console' && type === 'input') {
console.log(logEntry);
} else if (logMode === 'file') {
try {
await fs.appendFile(logFilename, logContent);
// Append to the file
await fs.appendFile(logFilename, logEntry);
} catch (err) {
console.error(`[Error] Failed to write prompt to ${logFilename}:`, err);
console.error(`[Error] Failed to write conversation log to ${logFilename}:`, err);
}
}
}
export function extractPromptText(requestBody) {
if (!requestBody) return "[No request body found]";
const logParts = [];
// ** FIX: Check for both snake_case and camelCase for system instruction **
const systemInstruction = requestBody.system_instruction || requestBody.systemInstruction;
if (!requestBody || !Array.isArray(requestBody.contents)) return "[No request body found]";
if (systemInstruction && Array.isArray(systemInstruction.parts)) {
const systemText = systemInstruction.parts.filter(p => p && typeof p.text === 'string').map(p => p.text).join('\n');
if (systemText) logParts.push(`[SYSTEM INSTRUCTION]\n${systemText}`);
}
let latestPrompt = "[No text prompt found]";
if (Array.isArray(requestBody.contents)) {
const userText = requestBody.contents.flatMap(c => (c && Array.isArray(c.parts) ? c.parts : [])).filter(p => p && typeof p.text === 'string').map(p => p.text).join('\n');
if (userText) {
if (logParts.length > 0) logParts.push('----------------------');
logParts.push(`[USER PROMPT]\n${userText}`);
// Iterate through contents in reverse to find the latest user prompt
for (let i = requestBody.contents.length - 1; i >= 0; i--) {
const content = requestBody.contents[i];
if (content && content.role === 'user' && Array.isArray(content.parts)) {
const userParts = content.parts.filter(part => part && typeof part.text === 'string');
if (userParts.length > 0) {
latestPrompt = userParts.map(part => part.text).join('\n');
break; // Found the latest user prompt, exit loop
}
}
}
if (logParts.length === 0) return "[No text prompt or system instruction found]";
return logParts.join('\n');
return latestPrompt;
}
export async function manageSystemPrompt(requestBody) {
const incomingSystemInstruction = requestBody.system_instruction || requestBody.systemInstruction;
let incomingSystemText = '';
if (incomingSystemInstruction && Array.isArray(incomingSystemInstruction.parts)) {
incomingSystemText = incomingSystemInstruction.parts
.filter(p => p && typeof p.text === 'string')
.map(p => p.text)
.join('\n');
}
try {
let currentSystemText = '';
try {
currentSystemText = await fs.readFile(SYSTEM_PROMPT_FILE, 'utf8');
} catch (error) {
if (error.code !== 'ENOENT') {
console.error(`[System Prompt Manager] Error reading system prompt file: ${error.message}`);
}
// If file doesn't exist, currentSystemText remains empty, which is fine.
}
if (incomingSystemText && incomingSystemText !== currentSystemText) {
await fs.writeFile(SYSTEM_PROMPT_FILE, incomingSystemText);
console.log('[System Prompt Manager] System prompt updated in file.');
} else if (!incomingSystemText && currentSystemText) {
// If incoming request has no system prompt but file has one, clear the file
await fs.writeFile(SYSTEM_PROMPT_FILE, '');
console.log('[System Prompt Manager] System prompt cleared from file.');
}
} catch (error) {
console.error(`[System Prompt Manager] Failed to manage system prompt file: ${error.message}`);
}
}
export function extractResponseText(responseObject) {

View file

@ -117,10 +117,11 @@ import {
GeminiApiService,
API_ACTIONS,
formatExpiryTime,
logPrompt,
logConversation, // Changed from logPrompt
extractPromptText,
getRequestBody,
extractResponseText
extractResponseText,
manageSystemPrompt, // New import
} from './gemini-core.js';
// --- Configuration Parsing ---
@ -424,12 +425,14 @@ async function handleStreamRequest(res, service, model, requestBody) {
const stream = service.generateContentStream(model, requestBody);
console.log('[Server Response Stream]');
process.stdout.write('> ');
let fullResponseText = ''; // Declare fullResponseText here
try {
for await (const chunk of stream) {
const openAIChunk = toOpenAIStreamChunk(chunk, model);
const chunkText = openAIChunk.choices[0].delta.content || "";
if (chunkText) {
process.stdout.write(chunkText);
fullResponseText += chunkText; // Accumulate text here
}
res.write(`data: ${JSON.stringify(openAIChunk)}\n\n`);
}
@ -447,6 +450,8 @@ async function handleStreamRequest(res, service, model, requestBody) {
if (!res.writableEnded) {
res.end();
}
// Log the full conversation here
await logConversation('output', fullResponseText, PROMPT_LOG_MODE, PROMPT_LOG_FILENAME);
}
const expiryDate = service.authClient.credentials.expiry_date;
console.log(`[Auth Token] Time until expiry: ${formatExpiryTime(expiryDate)}`);
@ -465,6 +470,8 @@ async function handleUnaryRequest(res, service, model, requestBody) {
res.end(JSON.stringify(openAIResponse));
const expiryDate = service.authClient.credentials.expiry_date;
console.log(`[Auth Token] Time until expiry: ${formatExpiryTime(expiryDate)}`);
await logConversation('output', responseText, PROMPT_LOG_MODE, PROMPT_LOG_FILENAME);
}
function handleError(res, error) {
@ -504,10 +511,9 @@ async function requestHandler(req, res) {
const model = openaiRequest.model;
const geminiRequest = toGeminiRequest(openaiRequest);
if (PROMPT_LOG_MODE !== 'none') {
const promptText = extractPromptText(geminiRequest); // Use geminiRequest for logging
await logPrompt(promptText, PROMPT_LOG_MODE, PROMPT_LOG_FILENAME);
}
await manageSystemPrompt(geminiRequest); // Call the new function here
const promptText = extractPromptText(geminiRequest); // Use geminiRequest for logging
await logConversation('input', promptText, PROMPT_LOG_MODE, PROMPT_LOG_FILENAME);
if (openaiRequest.stream) {
await handleStreamRequest(res, service, model, geminiRequest);