Files
Cyrene/backend/pkg/plugins/calculator/plugin.go
T
AskaEth 71f0a1abdb feat: Go模块路径迁移 + Docker生产部署适配 + ethend Docker兼容
- 所有Go模块路径从 github.com/yourname/cyrene-ai 迁移到 git.yeij.top/AskaEth/Cyrene
- 5个Go Dockerfile添加 GOPROXY=https://goproxy.cn,direct 解决国内构建问题
- ai-core go.mod 添加 pkg/plugins replace 指令
- Caddyfile 简化为 http:// 通配 + handle 保留 /api 前缀
- ethend Dockerfile 适配 (npm install + 仅 COPY package.json)
- ethend 新增 RUNNING_IN_DOCKER 环境变量,健康检查改用Docker服务名
- ethend 数据库状态检查支持Docker hostname (postgres/redis/qdrant/minio)
- process-manager 新增 CONTAINER_SVC_MAP + Docker模式自动检测
- 统一 docker-compose.dev.db.yml 卷名 (pg_data/redis_data/qdrant_data/minio_data)
- docker-compose.yml ethend服务挂载docker.sock + 端口变量化
- 清理 .env 统一后的残留文件与提示信息

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 13:43:22 +08:00

280 lines
5.9 KiB
Go

package calculator
import (
"context"
"fmt"
"math"
"strconv"
"strings"
"unicode"
"git.yeij.top/AskaEth/Cyrene/pkg/plugins/sdk"
)
type CalculatorPlugin struct {
sdk.BasePlugin
}
func (p *CalculatorPlugin) Metadata() sdk.PluginMetadata {
return sdk.PluginMetadata{
Name: "calculator", DisplayName: "Calculator", Version: "1.0.0",
Description: "Safe mathematical expression evaluation with custom parser",
Category: "utility", Author: sdk.PluginAuthor{Name: "Cyrene Team"},
}
}
func (p *CalculatorPlugin) Tools() []sdk.Tool {
return []sdk.Tool{&CalculatorTool{}}
}
type CalculatorTool struct {
sdk.BaseTool
}
func (t *CalculatorTool) Definition() sdk.ToolDefinition {
return sdk.ToolDefinition{
ID: "calculator", Name: "calculator", DisplayName: "Calculator",
Description: "Execute mathematical calculations. Supports arithmetic, trig, logs, powers.",
Category: "utility", Complexity: sdk.ComplexitySimple,
Parameters: map[string]interface{}{
"type": "object", "properties": map[string]interface{}{"expression": map[string]interface{}{"type": "string"}},
"required": []string{"expression"},
},
}
}
func (t *CalculatorTool) Validate(args map[string]interface{}) error {
if _, ok := args["expression"]; !ok {
return fmt.Errorf("missing required parameter: expression")
}
return nil
}
func (t *CalculatorTool) Execute(_ context.Context, args map[string]interface{}) (*sdk.ToolResult, error) {
expr, _ := args["expression"].(string)
result, err := evalExpression(expr)
if err != nil {
return &sdk.ToolResult{ToolName: "calculator", Success: false, Error: err.Error()}, nil
}
return &sdk.ToolResult{ToolName: "calculator", Success: true, Output: fmt.Sprintf("%v", result)}, nil
}
// Expression parser supporting +, -, *, /, %, ^, functions, constants.
type exprParser struct {
s string
pos int
}
func evalExpression(s string) (float64, error) {
p := &exprParser{s: strings.TrimSpace(s)}
result, err := p.parseAddSub()
if err != nil {
return 0, err
}
if p.pos < len(p.s) {
return 0, fmt.Errorf("unexpected character at position %d: %c", p.pos, p.s[p.pos])
}
return result, nil
}
func (p *exprParser) peek() byte {
if p.pos < len(p.s) {
return p.s[p.pos]
}
return 0
}
func (p *exprParser) skipSpaces() {
for p.pos < len(p.s) && p.s[p.pos] == ' ' {
p.pos++
}
}
func (p *exprParser) parseAddSub() (float64, error) {
left, err := p.parseMulDiv()
if err != nil {
return 0, err
}
for {
p.skipSpaces()
op := p.peek()
if op != '+' && op != '-' {
break
}
p.pos++
right, err := p.parseMulDiv()
if err != nil {
return 0, err
}
if op == '+' {
left += right
} else {
left -= right
}
}
return left, nil
}
func (p *exprParser) parseMulDiv() (float64, error) {
left, err := p.parsePower()
if err != nil {
return 0, err
}
for {
p.skipSpaces()
op := p.peek()
if op != '*' && op != '/' && op != '%' {
break
}
p.pos++
right, err := p.parsePower()
if err != nil {
return 0, err
}
switch op {
case '*':
left *= right
case '/':
if right == 0 {
return 0, fmt.Errorf("division by zero")
}
left /= right
case '%':
left = math.Mod(left, right)
}
}
return left, nil
}
func (p *exprParser) parsePower() (float64, error) {
left, err := p.parseUnary()
if err != nil {
return 0, err
}
p.skipSpaces()
if p.peek() == '^' {
p.pos++
right, err := p.parseUnary()
if err != nil {
return 0, err
}
return math.Pow(left, right), nil
}
return left, nil
}
func (p *exprParser) parseUnary() (float64, error) {
p.skipSpaces()
if p.peek() == '-' {
p.pos++
val, err := p.parseAtom()
if err != nil {
return 0, err
}
return -val, nil
}
if p.peek() == '+' {
p.pos++
}
return p.parseAtom()
}
func (p *exprParser) parseAtom() (float64, error) {
p.skipSpaces()
if p.peek() == '(' {
p.pos++
result, err := p.parseAddSub()
if err != nil {
return 0, err
}
p.skipSpaces()
if p.peek() != ')' {
return 0, fmt.Errorf("missing closing parenthesis")
}
p.pos++
return result, nil
}
if p.peek() == 0 {
return 0, fmt.Errorf("unexpected end of expression")
}
if unicode.IsDigit(rune(p.peek())) || p.peek() == '.' {
return p.parseNumber()
}
return p.parseFuncOrConst()
}
func (p *exprParser) parseNumber() (float64, error) {
start := p.pos
for p.pos < len(p.s) && (unicode.IsDigit(rune(p.s[p.pos])) || p.s[p.pos] == '.') {
p.pos++
}
return strconv.ParseFloat(p.s[start:p.pos], 64)
}
func (p *exprParser) parseFuncOrConst() (float64, error) {
start := p.pos
for p.pos < len(p.s) && (unicode.IsLetter(rune(p.s[p.pos])) || p.s[p.pos] == '_') {
p.pos++
}
name := p.s[start:p.pos]
p.skipSpaces()
switch name {
case "pi":
return math.Pi, nil
case "e":
return math.E, nil
case "sqrt", "sin", "cos", "tan", "abs", "floor", "ceil", "round", "log", "ln":
if p.peek() != '(' {
return 0, fmt.Errorf("expected '(' after function %s", name)
}
p.pos++
arg, err := p.parseAddSub()
if err != nil {
return 0, err
}
if p.peek() != ')' {
return 0, fmt.Errorf("missing ')' after function argument")
}
p.pos++
return applyFunc(name, arg)
default:
return 0, fmt.Errorf("unknown function or constant: %s", name)
}
}
func applyFunc(name string, x float64) (float64, error) {
switch name {
case "sqrt":
if x < 0 {
return 0, fmt.Errorf("square root of negative number")
}
return math.Sqrt(x), nil
case "sin":
return math.Sin(x), nil
case "cos":
return math.Cos(x), nil
case "tan":
return math.Tan(x), nil
case "abs":
return math.Abs(x), nil
case "floor":
return math.Floor(x), nil
case "ceil":
return math.Ceil(x), nil
case "round":
return math.Round(x), nil
case "log":
if x <= 0 {
return 0, fmt.Errorf("log of non-positive number")
}
return math.Log10(x), nil
case "ln":
if x <= 0 {
return 0, fmt.Errorf("ln of non-positive number")
}
return math.Log(x), nil
}
return 0, fmt.Errorf("unknown function: %s", name)
}