mirror of
https://github.com/FJurmanovic/sugarr.git
synced 2026-11-11 07:41:08 +00:00
Support OpenRouter's real per-request cost reporting
Send usage: { include: true } on every chat completion request (an
OpenRouter-specific extension - requires Usage Accounting enabled on
the OpenRouter account; other OpenAI-compatible providers generally
ignore unknown top-level request fields). When the response includes
OpenRouter's own usage.cost, forward it to Langfuse as
costDetails.total, so cost is the real per-request figure OpenRouter
billed rather than a guess from Langfuse's model price list (which
has no entry for proxied/aggregated OpenRouter model names anyway).
Verified: full suite (84/84) passing, tsc/eslint clean, docker
compose build succeeds. Per Langfuse's OpenRouter integration guide:
https://langfuse.com/integrations/gateways/openrouter
This commit is contained in:
@@ -73,11 +73,26 @@ describe('LangfuseTracer', () => {
|
||||
expect.objectContaining({
|
||||
output: 'done',
|
||||
usageDetails: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
|
||||
costDetails: undefined,
|
||||
})
|
||||
);
|
||||
expect(generationEnd).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('forwards an explicit cost (e.g. from OpenRouter) as costDetails.total instead of relying on price-list lookup', () => {
|
||||
const tracer = new LangfuseTracer({ publicKey: 'pk', secretKey: 'sk', baseUrl: 'http://lf' });
|
||||
const handle = tracer.startTrace('my-pipeline');
|
||||
const gen = handle.startGeneration({ name: 'step-1', model: 'openrouter/auto', input: ['hi'] });
|
||||
gen.end({
|
||||
output: 'done',
|
||||
usage: { promptTokens: 10, completionTokens: 5, totalTokens: 15, cost: 0.00042 },
|
||||
});
|
||||
|
||||
expect(generationUpdate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ costDetails: { total: 0.00042 } })
|
||||
);
|
||||
});
|
||||
|
||||
it('maps cachedTokens to the OpenAI-native nested prompt_tokens_details.cached_tokens, not a mixed flat key', () => {
|
||||
const tracer = new LangfuseTracer({ publicKey: 'pk', secretKey: 'sk', baseUrl: 'http://lf' });
|
||||
const handle = tracer.startTrace('my-pipeline');
|
||||
|
||||
@@ -29,6 +29,24 @@ describe('LlmClient', () => {
|
||||
temperature: 0.7,
|
||||
max_tokens: undefined,
|
||||
messages: [{ role: 'user', content: 'hi' }],
|
||||
usage: { include: true },
|
||||
});
|
||||
});
|
||||
|
||||
it('extracts OpenRouter cost from the response usage when present', async () => {
|
||||
createMock.mockResolvedValue({
|
||||
choices: [{ message: { content: 'hello' } }],
|
||||
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15, cost: 0.00042 },
|
||||
});
|
||||
|
||||
const client = new LlmClient({ baseUrl: 'https://openrouter.ai/api/v1', apiKey: 'key' });
|
||||
const result = await client.chat([{ role: 'user', content: 'hi' }], { model: 'openrouter/auto' });
|
||||
|
||||
expect(result.usage).toEqual({
|
||||
promptTokens: 10,
|
||||
completionTokens: 5,
|
||||
totalTokens: 15,
|
||||
cost: 0.00042,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user