chore: govern chat api logging
This commit is contained in:
parent
f2d4f9e698
commit
cc52f414af
@ -1,3 +1,4 @@
|
||||
import { createLogger } from "@/lib/observability/logger";
|
||||
import { getModelGenerationCreditCost } from "@/lib/ai/image-model-pricing";
|
||||
import {
|
||||
consumeUserPoints,
|
||||
@ -7,6 +8,9 @@ import {
|
||||
getUserById,
|
||||
} from "@/lib/db/queries";
|
||||
|
||||
const chatApiLogger = createLogger("chat.billing");
|
||||
|
||||
|
||||
const MODEL_UUID_REGEX =
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
|
||||
@ -50,7 +54,7 @@ export async function consumeGeneratedVideoPoints(params: {
|
||||
|
||||
const userData = await getUserById({ id: params.userId }).catch(() => undefined);
|
||||
if (userData?.isHighVip) {
|
||||
console.log(`[Points] Skip video points for VIP user=${params.userId}`);
|
||||
chatApiLogger.info(`[Points] Skip video points for VIP user=${params.userId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -89,19 +93,13 @@ export async function consumeGeneratedVideoPoints(params: {
|
||||
});
|
||||
|
||||
if (!consumeResult.ok) {
|
||||
console.warn(
|
||||
`[Points] Failed to consume video points. user=${params.userId} model=${billingModel.id} cost=${unitCost}`,
|
||||
);
|
||||
chatApiLogger.warn(`[Points] Failed to consume video points. user=${params.userId} model=${billingModel.id} cost=${unitCost}`);
|
||||
} else if (consumeResult.skipped) {
|
||||
console.log(
|
||||
`[Points] Skipped ${unitCost} video points for VIP user=${params.userId} model=${billingModel.id}`,
|
||||
);
|
||||
chatApiLogger.info(`[Points] Skipped ${unitCost} video points for VIP user=${params.userId} model=${billingModel.id}`);
|
||||
} else {
|
||||
console.log(
|
||||
`[Points] Consumed ${unitCost} points for video. balance=${consumeResult.balance}`,
|
||||
);
|
||||
chatApiLogger.info(`[Points] Consumed ${unitCost} points for video. balance=${consumeResult.balance}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[Points] Failed to consume points after video generation:", error);
|
||||
chatApiLogger.error("[Points] Failed to consume points after video generation:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { createLogger } from "@/lib/observability/logger";
|
||||
import { generateText } from "ai";
|
||||
import { fetchImageBufferFromUrl } from "@/lib/ai/rmbg/service";
|
||||
import { getLanguageModelById } from "@/lib/ai/dynamic-provider";
|
||||
@ -10,6 +11,9 @@ import { isVideoProviderType } from "./video-models";
|
||||
import type { AgentPresetMessageImagePart } from "./agent-scene-workflow";
|
||||
import type { PostRequestBody } from "../schema";
|
||||
|
||||
const chatApiLogger = createLogger("chat.detailReplacement");
|
||||
|
||||
|
||||
export async function createAgentImageThumbnailBuffer(buffer: Buffer): Promise<Buffer> {
|
||||
const sharp = (await import("sharp")).default;
|
||||
return sharp(buffer, { failOn: "none" })
|
||||
@ -91,7 +95,7 @@ export async function resolveReplacementReferenceGeometry(
|
||||
aspectRatio: inferClosestReplacementAspectRatio(width, height),
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn("[Chat API] Failed to resolve replacement reference geometry:", {
|
||||
chatApiLogger.warn("[Chat API] Failed to resolve replacement reference geometry:", {
|
||||
referenceImageUrl,
|
||||
error: error instanceof Error ? error.message : "unknown",
|
||||
});
|
||||
@ -318,7 +322,7 @@ export function parseReplacementScenePlan(
|
||||
|
||||
return scenePlan;
|
||||
} catch (error) {
|
||||
console.warn("[Chat API] Failed to parse replacement scene plan JSON:", error);
|
||||
chatApiLogger.warn("[Chat API] Failed to parse replacement scene plan JSON:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -583,7 +587,7 @@ export function parseReplacementReferenceDecision(
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("[Chat API] Failed to parse replacement reference decision JSON:", error);
|
||||
chatApiLogger.warn("[Chat API] Failed to parse replacement reference decision JSON:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -694,7 +698,7 @@ export async function analyzeReplacementReferenceImage(params: {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("[Chat API] Configured Qwen-VL reference analysis failed:", error);
|
||||
chatApiLogger.warn("[Chat API] Configured Qwen-VL reference analysis failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -717,7 +721,7 @@ export async function analyzeReplacementReferenceImage(params: {
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("[Chat API] Fallback Qwen3-VL reference analysis failed:", error);
|
||||
chatApiLogger.warn("[Chat API] Fallback Qwen3-VL reference analysis failed:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
@ -747,7 +751,7 @@ export async function resolveReplacementScenePlanningRuntime(params: {
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("[Chat API] Failed to resolve local Moonshot scene planning model:", error);
|
||||
chatApiLogger.warn("[Chat API] Failed to resolve local Moonshot scene planning model:", error);
|
||||
}
|
||||
|
||||
const configuredModels = params.models || (await getEnabledAiModelsWithProvider());
|
||||
@ -777,7 +781,7 @@ export async function resolveReplacementScenePlanningRuntime(params: {
|
||||
configuredVisionModel.model.modelId,
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn("[Chat API] Failed to resolve configured replacement scene planning model:", {
|
||||
chatApiLogger.warn("[Chat API] Failed to resolve configured replacement scene planning model:", {
|
||||
modelId: configuredVisionModel.model.id,
|
||||
error,
|
||||
});
|
||||
@ -811,7 +815,7 @@ export async function analyzeReplacementExecutionPlan(params: {
|
||||
retries: 2,
|
||||
loggerPrefix: "[Chat API] Replacement scene plan",
|
||||
}).catch((error) => {
|
||||
console.warn("[Chat API] Failed to inline replacement scene planning images:", error);
|
||||
chatApiLogger.warn("[Chat API] Failed to inline replacement scene planning images:", error);
|
||||
return { inlineDataUrls: [], skipped: [] };
|
||||
});
|
||||
const effectiveVisionImages =
|
||||
@ -926,7 +930,7 @@ export async function analyzeReplacementExecutionPlan(params: {
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("[Chat API] Replacement scene planning analysis failed:", {
|
||||
chatApiLogger.warn("[Chat API] Replacement scene planning analysis failed:", {
|
||||
referenceLabel: params.referenceLabel,
|
||||
referenceImageUrl: params.referenceImageUrl,
|
||||
error,
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import { createLogger } from "@/lib/observability/logger";
|
||||
import { NextResponse } from "next/server";
|
||||
import { getRequestErrorMessage } from "@/lib/request-error";
|
||||
import { generateUUID } from "@/lib/utils";
|
||||
|
||||
const chatApiLogger = createLogger("chat.errors");
|
||||
|
||||
|
||||
const DEFAULT_CHAT_PRE_RESPONSE_SOFT_TIMEOUT_MS = 55_000;
|
||||
|
||||
export class OperationTimeoutError extends Error {
|
||||
@ -124,7 +128,7 @@ export function createChatApiFailureResponse(
|
||||
const failure = resolveChatApiFailure(error);
|
||||
const traceId = generateUUID();
|
||||
|
||||
console.error("[Chat API] Unhandled request failure:", {
|
||||
chatApiLogger.error("[Chat API] Unhandled request failure:", {
|
||||
traceId,
|
||||
vercelId,
|
||||
category: failure.category,
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import { createLogger } from "@/lib/observability/logger";
|
||||
import { isFalGptImage2EditModelId } from "@/lib/ai/providers/fal-image";
|
||||
import { getLanguageModelById } from "@/lib/ai/dynamic-provider";
|
||||
import type { ChatMessage } from "@/lib/types";
|
||||
import { convertMessagesWithImages } from "./image-media";
|
||||
|
||||
const chatApiLogger = createLogger("chat.imageFailover");
|
||||
|
||||
|
||||
export const IMAGE_FAILOVER_PROVIDER_ALLOWLIST = new Set([
|
||||
"alibaba-image",
|
||||
"bfl-image",
|
||||
@ -130,9 +134,7 @@ export function buildImageFallbackCandidates(
|
||||
}
|
||||
if (isImageFallbackModelCoolingDown(item.model.id)) {
|
||||
const cooldown = imageFailoverModelCooldown.get(item.model.id);
|
||||
console.log(
|
||||
`[Chat API] ⏭ Skip configured image fallback on cooldown: ${item.model.displayName} (${providerType}) - ${cooldown?.reason || "cooldown"}`
|
||||
);
|
||||
chatApiLogger.info(`[Chat API] ⏭ Skip configured image fallback on cooldown: ${item.model.displayName} (${providerType}) - ${cooldown?.reason || "cooldown"}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -243,9 +245,7 @@ export function getImageCrossProviderCandidates(
|
||||
if (options.currentProviderId && m.provider.id === options.currentProviderId) return false;
|
||||
if (isImageFallbackModelCoolingDown(m.model.id)) {
|
||||
const cooldown = imageFailoverModelCooldown.get(m.model.id);
|
||||
console.log(
|
||||
`[Chat API] ⏭ Skip image failover model on cooldown: ${m.model.displayName} (${m.provider.type}) - ${cooldown?.reason || "cooldown"}`
|
||||
);
|
||||
chatApiLogger.info(`[Chat API] ⏭ Skip image failover model on cooldown: ${m.model.displayName} (${m.provider.type}) - ${cooldown?.reason || "cooldown"}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { createLogger } from "@/lib/observability/logger";
|
||||
import { convertToModelMessages } from "ai";
|
||||
import { inlineVisionImages } from "@/lib/ai/vision-inline";
|
||||
import { extractImageUrlsFromText } from "@/lib/image-url";
|
||||
@ -6,6 +7,9 @@ import { uploadFile } from "@/lib/storage/service";
|
||||
import type { ChatMessage } from "@/lib/types";
|
||||
import { ANNOTATION_SYSTEM_PROMPT, hasAnnotatedImage } from "./message-preparation";
|
||||
|
||||
const chatApiLogger = createLogger("chat.imageMedia");
|
||||
|
||||
|
||||
export function guessImageMediaType(url: string): string {
|
||||
const cleanUrl = url.split("?")[0]?.toLowerCase() || "";
|
||||
if (cleanUrl.endsWith(".jpg") || cleanUrl.endsWith(".jpeg")) return "image/jpeg";
|
||||
@ -458,7 +462,7 @@ export async function convertMessagesWithImages(
|
||||
retries: 2,
|
||||
loggerPrefix: target.loggerPrefix,
|
||||
}).catch((error) => {
|
||||
console.warn(`${target.loggerPrefix} failed to inline message images:`, error);
|
||||
chatApiLogger.warn(`${target.loggerPrefix} failed to inline message images:`, error);
|
||||
return { inlineDataUrls: [], skipped: [] };
|
||||
});
|
||||
|
||||
@ -479,7 +483,7 @@ export async function convertMessagesWithImages(
|
||||
return sourceMessages;
|
||||
}
|
||||
|
||||
console.log(`${target.loggerPrefix} inlined chat message images:`, {
|
||||
chatApiLogger.info(`${target.loggerPrefix} inlined chat message images:`, {
|
||||
total: uniqueUrls.length,
|
||||
inlined: inlineMap.size,
|
||||
providerType: target.providerType,
|
||||
@ -575,7 +579,7 @@ export async function convertMessagesWithImages(
|
||||
// 🆕 检测是否有标注图片
|
||||
const hasAnnotation = hasAnnotatedImage(messages);
|
||||
if (hasAnnotation) {
|
||||
console.log("[Message Convert] 🎨 检测到标注图片,将自动添加系统提示");
|
||||
chatApiLogger.info("[Message Convert] 🎨 检测到标注图片,将自动添加系统提示");
|
||||
}
|
||||
|
||||
// 对于 Gemini,需要特殊处理图片
|
||||
@ -596,7 +600,7 @@ export async function convertMessagesWithImages(
|
||||
if (hasAnnotation && message.role === "user" && !hasAddedAnnotationPrompt) {
|
||||
textContent = ANNOTATION_SYSTEM_PROMPT + "\n\n用户请求:" + textContent;
|
||||
hasAddedAnnotationPrompt = true;
|
||||
console.log("[Message Convert] 🎨 已注入标注提示词");
|
||||
chatApiLogger.info("[Message Convert] 🎨 已注入标注提示词");
|
||||
}
|
||||
|
||||
content.push({
|
||||
@ -612,7 +616,7 @@ export async function convertMessagesWithImages(
|
||||
url: part.url,
|
||||
},
|
||||
});
|
||||
console.log("[Message Convert] Image kept as URL format:", part.url.substring(0, 80) + "...");
|
||||
chatApiLogger.info("[Message Convert] Image kept as URL format:", part.url.substring(0, 80) + "...");
|
||||
}
|
||||
}
|
||||
|
||||
@ -622,7 +626,7 @@ export async function convertMessagesWithImages(
|
||||
type: "text",
|
||||
text: ANNOTATION_SYSTEM_PROMPT,
|
||||
});
|
||||
console.log("[Message Convert] 🎨 已在消息开头注入标注提示词");
|
||||
chatApiLogger.info("[Message Convert] 🎨 已在消息开头注入标注提示词");
|
||||
}
|
||||
|
||||
convertedMessages.push({
|
||||
@ -862,7 +866,7 @@ export async function normalizeAssistantImageMarkdown(
|
||||
uploadedUrls.push(uploadedUrl);
|
||||
rebuilt += ``;
|
||||
} catch (error) {
|
||||
console.error("[Chat API] Failed to upload inline data URL image:", error);
|
||||
chatApiLogger.error("[Chat API] Failed to upload inline data URL image:", error);
|
||||
rebuilt += matchText;
|
||||
}
|
||||
|
||||
@ -883,7 +887,7 @@ export async function normalizeAssistantImageMarkdown(
|
||||
uploadedUrls.push(uploadedUrl);
|
||||
normalizedText = normalizedText.replace(rawDataUrl, uploadedUrl);
|
||||
} catch (error) {
|
||||
console.error("[Chat API] Failed to normalize raw data URL image:", error);
|
||||
chatApiLogger.error("[Chat API] Failed to normalize raw data URL image:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -917,14 +921,14 @@ export async function normalizeAssistantImageMarkdown(
|
||||
try {
|
||||
hostedThumbnailUrl = await ensureHostedImageUrl(thumbnailUrl);
|
||||
} catch (error) {
|
||||
console.error("[Chat API] Failed to normalize remote thumbnail URL:", error);
|
||||
chatApiLogger.error("[Chat API] Failed to normalize remote thumbnail URL:", error);
|
||||
}
|
||||
|
||||
if (originalUrl && originalUrl !== thumbnailUrl) {
|
||||
try {
|
||||
hostedOriginalUrl = await ensureHostedImageUrl(originalUrl);
|
||||
} catch (error) {
|
||||
console.error("[Chat API] Failed to normalize remote original URL:", error);
|
||||
chatApiLogger.error("[Chat API] Failed to normalize remote original URL:", error);
|
||||
hostedOriginalUrl = hostedThumbnailUrl || originalUrl;
|
||||
}
|
||||
} else {
|
||||
@ -938,7 +942,7 @@ export async function normalizeAssistantImageMarkdown(
|
||||
uploadedUrls.push(finalOriginalUrl);
|
||||
rebuilt += ``;
|
||||
} catch (error) {
|
||||
console.error("[Chat API] Failed to parse remote image markdown metadata:", error);
|
||||
chatApiLogger.error("[Chat API] Failed to parse remote image markdown metadata:", error);
|
||||
rebuilt += matchText;
|
||||
}
|
||||
|
||||
@ -952,9 +956,7 @@ export async function normalizeAssistantImageMarkdown(
|
||||
const hasMarkdownImage = /!\[[^\]]*\]\((https?:\/\/[^)]+)\)/i.test(normalizedText);
|
||||
if (!hasMarkdownImage && uploadedUrls.length > 0) {
|
||||
const uniqueUrls = [...new Set(uploadedUrls)];
|
||||
console.log(
|
||||
`[Chat API] Converted ${uniqueUrls.length} image link(s) to hosted link(s)`
|
||||
);
|
||||
chatApiLogger.info(`[Chat API] Converted ${uniqueUrls.length} image link(s) to hosted link(s)`);
|
||||
return uniqueUrls
|
||||
.map((url) => ``)
|
||||
.join("\n");
|
||||
|
||||
@ -1,16 +1,20 @@
|
||||
import { createLogger } from "@/lib/observability/logger";
|
||||
import {
|
||||
analyzeOptimizedPromptMerge,
|
||||
buildOptimizedPromptPreview,
|
||||
} from "@/lib/ai/runtime/optimized-prompt";
|
||||
import type { ChatMessage } from "@/lib/types";
|
||||
|
||||
const chatApiLogger = createLogger("chat.messagePreparation");
|
||||
|
||||
|
||||
/**
|
||||
* 将图片 URL 转换为 base64 格式
|
||||
* 用于将 S3 URL 的图片转换为 base64 发送给 Gemini API
|
||||
*/
|
||||
export async function convertImageUrlToBase64(url: string): Promise<string> {
|
||||
try {
|
||||
console.log("[Image Convert] Fetching image from:", url);
|
||||
chatApiLogger.info("[Image Convert] Fetching image from:", url);
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch image: ${response.statusText}`);
|
||||
@ -24,10 +28,10 @@ export async function convertImageUrlToBase64(url: string): Promise<string> {
|
||||
const contentType = response.headers.get("content-type") || "image/jpeg";
|
||||
const dataUrl = `data:${contentType};base64,${base64}`;
|
||||
|
||||
console.log("[Image Convert] Converted to base64, size:", base64.length);
|
||||
chatApiLogger.info("[Image Convert] Converted to base64, size:", base64.length);
|
||||
return dataUrl;
|
||||
} catch (error) {
|
||||
console.error("[Image Convert] Failed:", error);
|
||||
chatApiLogger.error("[Image Convert] Failed:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@ -113,7 +117,7 @@ export function applyOptimizedPromptToLatestUserMessage(
|
||||
.join("\n\n")
|
||||
.trim()
|
||||
: `${hiddenPromptPrefix}${prompt}`.trim();
|
||||
console.log("[Prompt Merge] Final image prompt prepared:", {
|
||||
chatApiLogger.info("[Prompt Merge] Final image prompt prepared:", {
|
||||
original: normalizedOriginal.substring(0, 120),
|
||||
optimized: prompt.substring(0, 120),
|
||||
merged: finalPrompt.substring(0, 180),
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { createLogger } from "@/lib/observability/logger";
|
||||
import { JsonToSseTransformStream } from "ai";
|
||||
import { extractImageUrlsFromText } from "@/lib/image-url";
|
||||
import type {
|
||||
@ -7,6 +8,9 @@ import type {
|
||||
} from "@/lib/ai/context-memory/types";
|
||||
import { generateUUID } from "@/lib/utils";
|
||||
|
||||
const chatApiLogger = createLogger("chat.stream");
|
||||
|
||||
|
||||
export type StreamWriter = {
|
||||
write: (chunk: any) => void;
|
||||
};
|
||||
@ -20,7 +24,7 @@ export function safeWriteStreamChunk(
|
||||
writer.write(chunk);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.warn(`[Chat API] Stream write skipped (${label})`, error);
|
||||
chatApiLogger.warn(`[Chat API] Stream write skipped (${label})`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { createLogger } from "@/lib/observability/logger";
|
||||
import { after } from "next/server";
|
||||
import {
|
||||
getAiModelById,
|
||||
@ -10,6 +11,9 @@ import {
|
||||
} from "@/lib/ai/model-classification";
|
||||
import type { StoryboardModelCandidate } from "@/lib/video-agent/types";
|
||||
|
||||
const chatApiLogger = createLogger("chat.videoModels");
|
||||
|
||||
|
||||
export type VideoModeType =
|
||||
| "text-to-video"
|
||||
| "first-frame"
|
||||
@ -260,12 +264,12 @@ export function scheduleVideoArchive(params: {
|
||||
videoMetadata: nextMetadata as any,
|
||||
});
|
||||
|
||||
console.log("[Chat API] 🕒 已登记延迟归档视频:", {
|
||||
chatApiLogger.info("[Chat API] 🕒 已登记延迟归档视频:", {
|
||||
messageId,
|
||||
archiveAfterAt: new Date(archiveAfterAtMs).toISOString(),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("[Chat API] ❌ 登记视频延迟归档失败(保留临时地址):", {
|
||||
chatApiLogger.error("[Chat API] ❌ 登记视频延迟归档失败(保留临时地址):", {
|
||||
messageId,
|
||||
temporaryVideoUrl,
|
||||
error,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,19 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const files = [
|
||||
"app/(chat)/api/chat/route.ts",
|
||||
"app/(chat)/api/chat/_lib/agent-output.ts",
|
||||
"app/(chat)/api/chat/_lib/agent-scene-workflow.ts",
|
||||
"app/(chat)/api/chat/_lib/billing.ts",
|
||||
"app/(chat)/api/chat/_lib/detail-replacement.ts",
|
||||
"app/(chat)/api/chat/_lib/errors.ts",
|
||||
"app/(chat)/api/chat/_lib/image-failover.ts",
|
||||
"app/(chat)/api/chat/_lib/image-media.ts",
|
||||
"app/(chat)/api/chat/_lib/message-preparation.ts",
|
||||
"app/(chat)/api/chat/_lib/model-resolver.ts",
|
||||
"app/(chat)/api/chat/_lib/persistence.ts",
|
||||
"app/(chat)/api/chat/_lib/stream.ts",
|
||||
"app/(chat)/api/chat/_lib/video-models.ts",
|
||||
"lib/storage/service.ts",
|
||||
"app/(chat)/api/files/upload/route.ts",
|
||||
"app/(auth)/login/page.tsx",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user