Skip to content

Commit

Permalink
Make summing (or otherwise aggregating) costs Excel/user's responsibi…
Browse files Browse the repository at this point in the history
…lity
  • Loading branch information
robatwilliams committed Dec 20, 2023
1 parent 6588cc2 commit 9548e50
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 27 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ For other platforms, [clear the Office cache](https://learn.microsoft.com/en-us/

`OAI.CHAT_COMPLETE()` calls the API to create completions.

`OAI.COST()` calculates the billing cost of completion cell(s).
`OAI.COST()` calculates the billing cost(s) of completion cell(s). Wrap it with `SUM()` to calculate a total.

See the [function metadata](https://github.com/robatwilliams/openai-excel-formulas/blob/main/src/functions/functions.json) for full documentation of functions and parameters. Excel's presentation of custom function documentation varies by platform, but is best in the _Insert Function_ dialog and/or the desktop platform.

Expand Down
45 changes: 24 additions & 21 deletions src/functions/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,32 +91,35 @@ async function chatComplete(messages, params, invocation) {
// Terminology note: our _cost_ is driven by usage and OpenAI's _prices_.
CustomFunctions.associate('COST', cost);
function cost(completionsMatrix, pricesMatrix) {
const completions = completionsMatrix
.flat()
.filter((value) => value !== EMPTY_OR_ZERO);
completions.forEach(validateIsCompletion);
const allPrices = Object.fromEntries(
pricesMatrix.map((row) => [row[0], { input: row[1], output: row[2] }]),
);

return completions.reduce((accumulator, completion) => {
const model = completion.properties.response.properties.model.basicValue;
const usage = completion.properties.response.properties.usage.properties;
const modelPrices = allPrices[model];

if (!modelPrices) {
throw new CustomFunctions.Error(
CustomFunctions.ErrorCode.invalidValue,
`No prices specified for model ${model}`,
return completionsMatrix.map((row) =>
row.map((cell) => {
if (cell === EMPTY_OR_ZERO) {
return 0;
} else {
validateIsCompletion(cell);
}

const model = cell.properties.response.properties.model.basicValue;
const usage = cell.properties.response.properties.usage.properties;
const modelPrices = allPrices[model];

if (!modelPrices) {
throw new CustomFunctions.Error(
CustomFunctions.ErrorCode.invalidValue,
`No prices specified for model ${model}`,
);
}

return (
(usage.prompt_tokens.basicValue / 1000) * modelPrices.input +
(usage.completion_tokens.basicValue / 1000) * modelPrices.output
);
}

return (
accumulator +
(usage.prompt_tokens.basicValue / 1000) * modelPrices.input +
(usage.completion_tokens.basicValue / 1000) * modelPrices.output
);
}, 0);
}),
);
}

function toEntityProperty(value) {
Expand Down
5 changes: 3 additions & 2 deletions src/functions/functions.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
}
},
{
"description": "Calculate the cost of the completions in the given cells",
"description": "Calculate the costs of the completions in the given cells",
"id": "COST",
"name": "COST",
"parameters": [
Expand All @@ -45,7 +45,8 @@
}
],
"result": {
"type": "number"
"type": "number",
"dimensionality": "matrix"
}
}
]
Expand Down
10 changes: 7 additions & 3 deletions src/functions/functions.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,10 @@ describe('COST', () => {
});
const prices = [['gpt-4-0613', 0.03, 0.06]];

assert.strictEqual(cost([[completion]], prices), 0.24);
assert.deepStrictEqual(cost([[completion]], prices), [[0.24]]);
});

it('calculates the cost for a range of completions including empty cells', () => {
it('calculates the costs for a range of completions including empty cells', () => {
const completion = makeCompletionEntity({
modelUsed: 'gpt-4-0613',
tokensPrompt: 1000,
Expand All @@ -197,7 +197,11 @@ describe('COST', () => {
];
const prices = [['gpt-4-0613', 0.03, 0.06]];

assert.strictEqual(cost(completions, prices), 0.27);
assert.deepStrictEqual(cost(completions, prices), [
[0.09, 0.09],
[0.09, 0],
[0, 0],
]);
});

it('throws an error when no prices are specified for the model used', () => {
Expand Down

0 comments on commit 9548e50

Please sign in to comment.