feat(tools): add calculator tool for math expressions
- Adds a new tool in `pkg/tools/calculator.go` using `github.com/Knetic/govaluate` to securely evaluate mathematical expressions. - Updates `pkg/config/tools.go` to include the tool configuration under `PICOCLAW_TOOLS_CALCULATOR_`. - Registers the new tool conditionally in `pkg/agent/instance.go`. - Adds unit tests in `pkg/tools/calculator_test.go` to verify correct expression evaluation, error handling, and float resolution. Co-authored-by: hobbyistlabs-coder <267281733+hobbyistlabs-coder@users.noreply.github.com>
This commit is contained in:
parent
a2c9e1ebcb
commit
e31cece76e
5 changed files with 136 additions and 0 deletions
1
go.mod
1
go.mod
|
|
@ -3,6 +3,7 @@ module jane
|
||||||
go 1.25.7
|
go 1.25.7
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/Knetic/govaluate v3.0.0+incompatible
|
||||||
github.com/adhocore/gronx v1.19.6
|
github.com/adhocore/gronx v1.19.6
|
||||||
github.com/alpacahq/alpaca-trade-api-go/v3 v3.9.1
|
github.com/alpacahq/alpaca-trade-api-go/v3 v3.9.1
|
||||||
github.com/anthropics/anthropic-sdk-go v1.22.1
|
github.com/anthropics/anthropic-sdk-go v1.22.1
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,10 @@ func NewAgentInstance(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.Tools.IsToolEnabled("calculator") {
|
||||||
|
toolsRegistry.Register(tools.NewCalculatorTool())
|
||||||
|
}
|
||||||
|
|
||||||
sessionsDir := filepath.Join(workspace, "sessions")
|
sessionsDir := filepath.Join(workspace, "sessions")
|
||||||
sessions := initSessionStore(sessionsDir)
|
sessions := initSessionStore(sessionsDir)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,7 @@ type ToolsConfig struct {
|
||||||
WriteFile ToolConfig `json:"write_file" envPrefix:"PICOCLAW_TOOLS_WRITE_FILE_"`
|
WriteFile ToolConfig `json:"write_file" envPrefix:"PICOCLAW_TOOLS_WRITE_FILE_"`
|
||||||
BrowserAction ToolConfig `json:"browser_action" envPrefix:"PICOCLAW_TOOLS_BROWSER_ACTION_"`
|
BrowserAction ToolConfig `json:"browser_action" envPrefix:"PICOCLAW_TOOLS_BROWSER_ACTION_"`
|
||||||
GoEval ToolConfig `json:"go_eval" envPrefix:"PICOCLAW_TOOLS_GO_EVAL_"`
|
GoEval ToolConfig `json:"go_eval" envPrefix:"PICOCLAW_TOOLS_GO_EVAL_"`
|
||||||
|
Calculator ToolConfig `json:"calculator" envPrefix:"PICOCLAW_TOOLS_CALCULATOR_"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SearchCacheConfig struct {
|
type SearchCacheConfig struct {
|
||||||
|
|
@ -235,6 +236,8 @@ func (t *ToolsConfig) IsToolEnabled(name string) bool {
|
||||||
return t.MCP.Enabled
|
return t.MCP.Enabled
|
||||||
case "mcp2cli":
|
case "mcp2cli":
|
||||||
return t.MCP2Cli.Enabled
|
return t.MCP2Cli.Enabled
|
||||||
|
case "calculator":
|
||||||
|
return t.Calculator.Enabled
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
58
pkg/tools/calculator.go
Normal file
58
pkg/tools/calculator.go
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Knetic/govaluate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CalculatorTool evaluates mathematical expressions.
|
||||||
|
type CalculatorTool struct{}
|
||||||
|
|
||||||
|
// NewCalculatorTool creates a new CalculatorTool.
|
||||||
|
func NewCalculatorTool() *CalculatorTool {
|
||||||
|
return &CalculatorTool{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CalculatorTool) Name() string {
|
||||||
|
return "calculator"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CalculatorTool) Description() string {
|
||||||
|
return "Evaluates mathematical expressions. Input should be a mathematical expression as a string."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CalculatorTool) Parameters() map[string]any {
|
||||||
|
return map[string]any{
|
||||||
|
"type": "object",
|
||||||
|
"properties": map[string]any{
|
||||||
|
"expression": map[string]any{
|
||||||
|
"type": "string",
|
||||||
|
"description": "The mathematical expression to evaluate (e.g., '2 + 2', '10 * (5 - 3)').",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": []string{"expression"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *CalculatorTool) Execute(ctx context.Context, args map[string]any) *ToolResult {
|
||||||
|
exprStr, ok := args["expression"].(string)
|
||||||
|
if !ok || exprStr == "" {
|
||||||
|
return ErrorResult("expression argument is required and must be a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
expression, err := govaluate.NewEvaluableExpression(exprStr)
|
||||||
|
if err != nil {
|
||||||
|
return ErrorResult(fmt.Sprintf("invalid expression: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := expression.Evaluate(nil)
|
||||||
|
if err != nil {
|
||||||
|
return ErrorResult(fmt.Sprintf("evaluation failed: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format result to handle floats properly if needed
|
||||||
|
resStr := fmt.Sprintf("%v", result)
|
||||||
|
return UserResult(resStr)
|
||||||
|
}
|
||||||
70
pkg/tools/calculator_test.go
Normal file
70
pkg/tools/calculator_test.go
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCalculatorTool_Execute(t *testing.T) {
|
||||||
|
calc := NewCalculatorTool()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
expression string
|
||||||
|
want string
|
||||||
|
wantError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple addition",
|
||||||
|
expression: "2 + 2",
|
||||||
|
want: "4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "complex math",
|
||||||
|
expression: "10 * (5 - 3)",
|
||||||
|
want: "20",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "floating point result",
|
||||||
|
expression: "10 / 4",
|
||||||
|
want: "2.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing expression",
|
||||||
|
expression: "",
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid expression",
|
||||||
|
expression: "2 + * 2",
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
args := map[string]any{}
|
||||||
|
if tt.expression != "" {
|
||||||
|
args["expression"] = tt.expression
|
||||||
|
}
|
||||||
|
|
||||||
|
result := calc.Execute(context.Background(), args)
|
||||||
|
|
||||||
|
if tt.wantError {
|
||||||
|
assert.True(t, result.IsError)
|
||||||
|
} else {
|
||||||
|
assert.False(t, result.IsError)
|
||||||
|
assert.Equal(t, tt.want, result.ForLLM)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCalculatorTool_Info(t *testing.T) {
|
||||||
|
calc := NewCalculatorTool()
|
||||||
|
assert.Equal(t, "calculator", calc.Name())
|
||||||
|
assert.NotEmpty(t, calc.Description())
|
||||||
|
assert.NotNil(t, calc.Parameters())
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue