feat(billing): record prompt cache usage
This commit is contained in:
@@ -29,6 +29,7 @@ import {
|
||||
shouldLoadGeneralDailyPanchanga,
|
||||
} from "@/lib/consultation-entrypoint";
|
||||
import { CreditRpcError } from "@/lib/consultation-billing";
|
||||
import { cachedSystemMessage, mergePromptCacheUsage, promptCacheUsage } from "@/lib/agent-generation-settings";
|
||||
import { resolveFeaturePricing } from "@/lib/feature-pricing";
|
||||
import { reserveConsultationModel } from "@/lib/consultation-model-selection";
|
||||
import { resolveSessionLanguageModel } from "@/lib/model-catalog";
|
||||
@@ -203,13 +204,22 @@ function chinaCalendarDate(now: Date) {
|
||||
return new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
type Usage = { inputTokens?: number; outputTokens?: number };
|
||||
type Usage = {
|
||||
inputTokens?: number;
|
||||
outputTokens?: number;
|
||||
cache?: ReturnType<typeof promptCacheUsage>;
|
||||
};
|
||||
|
||||
function mergeUsage(usages: Promise<Usage>[]): Promise<Usage> {
|
||||
return Promise.all(usages).then((items) => items.reduce((total, item) => ({
|
||||
inputTokens: (total.inputTokens ?? 0) + (item.inputTokens ?? 0),
|
||||
outputTokens: (total.outputTokens ?? 0) + (item.outputTokens ?? 0),
|
||||
}), {} as Usage));
|
||||
return Promise.all(usages).then((items) => items.reduce<Usage>((total, item) => {
|
||||
const usage = item && typeof item === "object" ? item as Record<string, unknown> : {};
|
||||
const cache = promptCacheUsage(usage);
|
||||
return {
|
||||
inputTokens: (total.inputTokens ?? 0) + (typeof usage.inputTokens === "number" ? usage.inputTokens : 0),
|
||||
outputTokens: (total.outputTokens ?? 0) + (typeof usage.outputTokens === "number" ? usage.outputTokens : 0),
|
||||
cache: mergePromptCacheUsage([total.cache, cache]),
|
||||
};
|
||||
}, {} as Usage));
|
||||
}
|
||||
|
||||
function shouldUseAgenticRuntime(user: { id: string; app_metadata?: Record<string, unknown> }) {
|
||||
@@ -484,8 +494,10 @@ export async function POST(request: Request) {
|
||||
const usageStartedAt = Date.now();
|
||||
async function usagePayload(usage: Promise<{ inputTokens?: number; outputTokens?: number }>) {
|
||||
const resolved = await usage;
|
||||
const inputTokens = Math.max(0, Math.trunc(resolved.inputTokens ?? 0));
|
||||
const outputTokens = Math.max(0, Math.trunc(resolved.outputTokens ?? 0));
|
||||
const usageRecord = resolved as Record<string, unknown>;
|
||||
const cache = promptCacheUsage(usageRecord);
|
||||
const inputTokens = Math.max(0, Math.trunc(typeof usageRecord.inputTokens === "number" ? usageRecord.inputTokens : 0));
|
||||
const outputTokens = Math.max(0, Math.trunc(typeof usageRecord.outputTokens === "number" ? usageRecord.outputTokens : 0));
|
||||
return {
|
||||
eventKey: requestId,
|
||||
actualModelId: selectedModel.id,
|
||||
@@ -497,6 +509,7 @@ export async function POST(request: Request) {
|
||||
+ outputTokens * (selectedModel.outputCostMicrousdPerMillion ?? 0)
|
||||
) / 1_000_000),
|
||||
durationMs: Date.now() - usageStartedAt,
|
||||
...(cache ? { metadata: { cache: { ...cache, hit: cache.readTokens > 0 } } } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -674,7 +687,9 @@ export async function POST(request: Request) {
|
||||
// failure response must not depend on it succeeding.
|
||||
}
|
||||
};
|
||||
const cacheBoundary = cachedSystemMessage("【上下文缓存边界】后续内容为本轮请求输入。", selectedModel.model);
|
||||
const baseMessages = [
|
||||
...(cacheBoundary ? [cacheBoundary] : []),
|
||||
...history.map((message) => message.role === "user"
|
||||
? { role: "user" as const, content: message.text }
|
||||
: { role: "assistant" as const, content: message.text }),
|
||||
@@ -1033,7 +1048,9 @@ export async function POST(request: Request) {
|
||||
return await runAgenticConsultation(consultationMode, history, name, generalDailyContext);
|
||||
}
|
||||
if (!shouldRunBirthChartWorkflow(consultationMode)) {
|
||||
const cacheBoundary = cachedSystemMessage("【上下文缓存边界】后续内容为本轮请求输入。", selectedModel.model);
|
||||
const result = await getGeneralJyotishAgent(selectedModel).stream([
|
||||
...(cacheBoundary ? [cacheBoundary] : []),
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
@@ -1107,7 +1124,9 @@ export async function POST(request: Request) {
|
||||
);
|
||||
const workflowReceipt = consultationWorkflowReceipt(workflowContext);
|
||||
|
||||
const cacheBoundary = cachedSystemMessage("【上下文缓存边界】后续内容为本轮请求输入。", selectedModel.model);
|
||||
const result = await getLegacyJyotishAgent(selectedModel, workflowContext).stream([
|
||||
...(cacheBoundary ? [cacheBoundary] : []),
|
||||
...history.map((message) => message.role === "user"
|
||||
? { role: "user" as const, content: message.text }
|
||||
: { role: "assistant" as const, content: message.text }),
|
||||
|
||||
@@ -594,6 +594,7 @@ export async function POST(request: Request) {
|
||||
modelConfigVersion: selectedModel.configVersion,
|
||||
inputTokens: usage.inputTokens,
|
||||
outputTokens: usage.outputTokens,
|
||||
...(usage.cache ? { metadata: { cache: { ...usage.cache, hit: usage.cache.readTokens > 0 } } } : {}),
|
||||
costMicrousd: Math.round((
|
||||
usage.inputTokens * (selectedModel.inputCostMicrousdPerMillion ?? 0)
|
||||
+ usage.outputTokens * (selectedModel.outputCostMicrousdPerMillion ?? 0)
|
||||
|
||||
@@ -215,6 +215,7 @@ export async function POST(request: Request) {
|
||||
eventKey: "report.full", actualModelId: usage.actualModelId,
|
||||
modelConfigVersion: usage.modelConfigVersion, inputTokens: usage.inputTokens,
|
||||
outputTokens: usage.outputTokens, costMicrousd, durationMs: usage.durationMs,
|
||||
...(usage.cache ? { metadata: { cache: { ...usage.cache, hit: usage.cache.readTokens > 0 } } } : {}),
|
||||
});
|
||||
return settled.success;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user