Skip to content

Commit

Permalink
feat(tool): add calculator tool (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-desmond authored Sep 6, 2024
1 parent 925f7d8 commit 2af8af7
Show file tree
Hide file tree
Showing 4 changed files with 236 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
"fast-xml-parser": "^4.4.1",
"header-generator": "^2.1.54",
"joplin-turndown-plugin-gfm": "^1.0.12",
"mathjs": "^13.1.1",
"mustache": "^4.2.0",
"object-hash": "^3.0.0",
"p-queue": "^8.0.1",
Expand Down
46 changes: 46 additions & 0 deletions src/tools/calculator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Copyright 2024 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { CalculatorTool } from "@/tools/calculator.js";
import { beforeEach, expect } from "vitest";

describe("Calculator", () => {
let instance: CalculatorTool;

beforeEach(() => {
instance = new CalculatorTool();
});

it("Runs", async () => {
const x1 = 1;
const y1 = 1;
const x2 = 4;
const y2 = 5;

const response = await instance.run({
expression: `sqrt( (${x2}-${x1})^2 + (${y2}-${y1})^2 )`,
});
expect(response.result).toBe(5);
});

it("Throws", async () => {
await expect(
instance.run({
expression: "import",
}),
).rejects.toThrowError();
});
});
97 changes: 97 additions & 0 deletions src/tools/calculator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* Copyright 2024 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { StringToolOutput, Tool, ToolInput } from "@/tools/base.js";
import { z } from "zod";
import { create, all, evaluate, ImportOptions, ImportObject, ConfigOptions } from "mathjs";

export interface CalculatorToolInput {
config?: ConfigOptions;
imports?: {
entries: ImportObject | ImportObject[];
options?: ImportOptions;
};
}

/**
* Waring: The CalculatorTool enbales the agent (and by proxy the user) to execute arbirtary
* expressions via mathjs.
*
* Please consider the security and stability risks documented at
* https://mathjs.org/docs/expressions/security.html before using this tool.
*/
export class CalculatorTool extends Tool<StringToolOutput> {
name = "Calculator";
description = `A calculator tool that performs basic arithmetic operations like addition, subtraction, multiplication, and division.
Only use the calculator tool if you need to perform a calculation.`;

inputSchema() {
return z.object({
expression: z
.string()
.min(1)
.describe(
`The mathematical expression to evaluate (e.g., "2 + 3 * 4"). Use Mathjs basic expression syntax. Constants only.`,
),
});
}

protected limitedEvaluate: typeof evaluate;

constructor({ config, imports, ...options }: CalculatorToolInput = {}) {
super(options);
const math = create(all, config);
this.limitedEvaluate = math.evaluate;
// Disable use of potentially vulnerable functions
math.import(
{
// most important (hardly any functional impact)
import: function () {
throw new Error("Function import is disabled");
},
createUnit: function () {
throw new Error("Function createUnit is disabled");
},
reviver: function () {
throw new Error("Function reviver is disabled");
},

// extra (has functional impact)
evaluate: function () {
throw new Error("Function evaluate is disabled");
},
parse: function () {
throw new Error("Function parse is disabled");
},
simplify: function () {
throw new Error("Function simplify is disabled");
},
derivative: function () {
throw new Error("Function derivative is disabled");
},
resolve: function () {
throw new Error("Function resolve is disabled");
},
},
{ override: true, ...imports?.options },
);
}

protected async _run({ expression }: ToolInput<this>) {
const result = this.limitedEvaluate(expression);
return new StringToolOutput(result);
}
}
92 changes: 92 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ __metadata:
languageName: node
linkType: hard

"@babel/runtime@npm:^7.25.4":
version: 7.25.6
resolution: "@babel/runtime@npm:7.25.6"
dependencies:
regenerator-runtime: "npm:^0.14.0"
checksum: 10c0/d6143adf5aa1ce79ed374e33fdfd74fa975055a80bc6e479672ab1eadc4e4bfd7484444e17dd063a1d180e051f3ec62b357c7a2b817e7657687b47313158c3d2
languageName: node
linkType: hard

"@bufbuild/protobuf@npm:^1.10.0":
version: 1.10.0
resolution: "@bufbuild/protobuf@npm:1.10.0"
Expand Down Expand Up @@ -2539,6 +2548,7 @@ __metadata:
joplin-turndown-plugin-gfm: "npm:^1.0.12"
langchain: "npm:~0.2.16"
lint-staged: "npm:^15.2.9"
mathjs: "npm:^13.1.1"
mustache: "npm:^4.2.0"
object-hash: "npm:^3.0.0"
ollama: "npm:^0.5.8"
Expand Down Expand Up @@ -3096,6 +3106,13 @@ __metadata:
languageName: node
linkType: hard

"complex.js@npm:^2.1.1":
version: 2.1.1
resolution: "complex.js@npm:2.1.1"
checksum: 10c0/c5dbb83954b472cf7fb1aebf8d140a7eb85a00b73eeb2b4ee4d192f98f038f3800f54550747b85be1114fedcbefebb2ade709b17b3cd40ab2bd546cb8fe9121c
languageName: node
linkType: hard

"concat-map@npm:0.0.1":
version: 0.0.1
resolution: "concat-map@npm:0.0.1"
Expand Down Expand Up @@ -3417,6 +3434,13 @@ __metadata:
languageName: node
linkType: hard

"decimal.js@npm:^10.4.3":
version: 10.4.3
resolution: "decimal.js@npm:10.4.3"
checksum: 10c0/6d60206689ff0911f0ce968d40f163304a6c1bc739927758e6efc7921cfa630130388966f16bf6ef6b838cb33679fbe8e7a78a2f3c478afce841fd55ac8fb8ee
languageName: node
linkType: hard

"decode-named-character-reference@npm:^1.0.0":
version: 1.0.2
resolution: "decode-named-character-reference@npm:1.0.2"
Expand Down Expand Up @@ -3852,6 +3876,13 @@ __metadata:
languageName: node
linkType: hard

"escape-latex@npm:^1.2.0":
version: 1.2.0
resolution: "escape-latex@npm:1.2.0"
checksum: 10c0/b77ea1594a38625295793a61105222c283c1792d1b2511bbfd6338cf02cc427dcabce7e7c1e22ec2f5c40baf3eaf2eeaf229a62dbbb74c6e69bb4a4209f2544f
languageName: node
linkType: hard

"escape-string-regexp@npm:^1.0.5":
version: 1.0.5
resolution: "escape-string-regexp@npm:1.0.5"
Expand Down Expand Up @@ -4382,6 +4413,13 @@ __metadata:
languageName: node
linkType: hard

"fraction.js@npm:^4.3.7":
version: 4.3.7
resolution: "fraction.js@npm:4.3.7"
checksum: 10c0/df291391beea9ab4c263487ffd9d17fed162dbb736982dee1379b2a8cc94e4e24e46ed508c6d278aded9080ba51872f1bc5f3a5fd8d7c74e5f105b508ac28711
languageName: node
linkType: hard

"fs-extra@npm:^11.2.0":
version: 11.2.0
resolution: "fs-extra@npm:11.2.0"
Expand Down Expand Up @@ -5316,6 +5354,13 @@ __metadata:
languageName: node
linkType: hard

"javascript-natural-sort@npm:^0.7.1":
version: 0.7.1
resolution: "javascript-natural-sort@npm:0.7.1"
checksum: 10c0/340f8ffc5d30fb516e06dc540e8fa9e0b93c865cf49d791fed3eac3bdc5fc71f0066fc81d44ec1433edc87caecaf9f13eec4a1fce8c5beafc709a71eaedae6fe
languageName: node
linkType: hard

"jiti@npm:^1.19.1":
version: 1.21.6
resolution: "jiti@npm:1.21.6"
Expand Down Expand Up @@ -6042,6 +6087,25 @@ __metadata:
languageName: node
linkType: hard

"mathjs@npm:^13.1.1":
version: 13.1.1
resolution: "mathjs@npm:13.1.1"
dependencies:
"@babel/runtime": "npm:^7.25.4"
complex.js: "npm:^2.1.1"
decimal.js: "npm:^10.4.3"
escape-latex: "npm:^1.2.0"
fraction.js: "npm:^4.3.7"
javascript-natural-sort: "npm:^0.7.1"
seedrandom: "npm:^3.0.5"
tiny-emitter: "npm:^2.1.0"
typed-function: "npm:^4.2.1"
bin:
mathjs: bin/cli.js
checksum: 10c0/db0b9d822fed889fc5f2cf4a9875e3fa627a26c9f6b2efbe7e000e42bfe9c0d71c125124329d2c8fd27a9d22d08b8794681367744787ef1eb8465e8f95955fd3
languageName: node
linkType: hard

"mdast-util-find-and-replace@npm:^3.0.0":
version: 3.0.1
resolution: "mdast-util-find-and-replace@npm:3.0.1"
Expand Down Expand Up @@ -7920,6 +7984,13 @@ __metadata:
languageName: node
linkType: hard

"regenerator-runtime@npm:^0.14.0":
version: 0.14.1
resolution: "regenerator-runtime@npm:0.14.1"
checksum: 10c0/1b16eb2c4bceb1665c89de70dcb64126a22bc8eb958feef3cd68fe11ac6d2a4899b5cd1b80b0774c7c03591dc57d16631a7f69d2daa2ec98100e2f29f7ec4cc4
languageName: node
linkType: hard

"registry-auth-token@npm:^5.0.2":
version: 5.0.2
resolution: "registry-auth-token@npm:5.0.2"
Expand Down Expand Up @@ -8260,6 +8331,13 @@ __metadata:
languageName: node
linkType: hard

"seedrandom@npm:^3.0.5":
version: 3.0.5
resolution: "seedrandom@npm:3.0.5"
checksum: 10c0/929752ac098ff4990b3f8e0ac39136534916e72879d6eb625230141d20db26e2f44c4d03d153d457682e8cbaab0fb7d58a1e7267a157cf23fd8cf34e25044e88
languageName: node
linkType: hard

"semver-diff@npm:^4.0.0":
version: 4.0.0
resolution: "semver-diff@npm:4.0.0"
Expand Down Expand Up @@ -8792,6 +8870,13 @@ __metadata:
languageName: node
linkType: hard

"tiny-emitter@npm:^2.1.0":
version: 2.1.0
resolution: "tiny-emitter@npm:2.1.0"
checksum: 10c0/459c0bd6e636e80909898220eb390e1cba2b15c430b7b06cec6ac29d87acd29ef618b9b32532283af749f5d37af3534d0e3bde29fdf6bcefbf122784333c953d
languageName: node
linkType: hard

"tiny-invariant@npm:^1.3.3":
version: 1.3.3
resolution: "tiny-invariant@npm:1.3.3"
Expand Down Expand Up @@ -9028,6 +9113,13 @@ __metadata:
languageName: node
linkType: hard

"typed-function@npm:^4.2.1":
version: 4.2.1
resolution: "typed-function@npm:4.2.1"
checksum: 10c0/0b4e9a359e456f7df50f3d7cc53e2408d23a516f27b50c2c0654f388110ecf407c0595b1bf2296d3d8667fae6aae311ec2af90c602385777d12fe724bae99156
languageName: node
linkType: hard

"typedarray-to-buffer@npm:^3.1.5":
version: 3.1.5
resolution: "typedarray-to-buffer@npm:3.1.5"
Expand Down

0 comments on commit 2af8af7

Please sign in to comment.