From 424abfbb9e750eeea085de7aeba4d0b30dd4968a Mon Sep 17 00:00:00 2001 From: charles Date: Mon, 16 Feb 2026 15:45:27 -0800 Subject: [PATCH] add: more tools, fix prompt --- Modelfile | 74 +++++++++++++++++++++++++++++++++ cmd/mcgod/main.go | 41 +++++++++++------- internal/pkg/tools/time/time.go | 59 ++++++++++++++++++++++++++ 3 files changed, 160 insertions(+), 14 deletions(-) create mode 100644 Modelfile create mode 100644 internal/pkg/tools/time/time.go diff --git a/Modelfile b/Modelfile new file mode 100644 index 0000000..dcdcce7 --- /dev/null +++ b/Modelfile @@ -0,0 +1,74 @@ +FROM gemma3:12b + +PARAMETER num_ctx 128000 +PARAMETER temperature 0.1 +PARAMETER stop "" + +TEMPLATE """ +{{- if .Messages }} +{{- if or .System .Tools }} +{{- if .System }} +{{ .System }} +{{- end }} +{{- if .Tools }} +The following tools are available when needed for specific tasks: +{{ .Tools }} + +Only use tools when the task specifically requires their functionality. +For general questions or tasks that don't need external data, respond directly. + +When using a tool, format as: + + +{"name": "function_name", "parameters": {"param1": "value1"}} + + +{{- end }} +{{- end }} +{{- range $i, $_ := .Messages }} +{{- $last := eq (len (slice $.Messages $i)) 1 }} +{{- if eq .Role "user" }}user +{{ .Content }} +{{- if $last }}model +{{ end }} +{{- else if eq .Role "system" }}user +{{ .Content }} +{{- if $last }}model +{{ end }} +{{- else if eq .Role "assistant" }}model +{{- if .ToolCalls }} + +{{- range .ToolCalls }} + +{"name": "{{ .Function.Name }}", "parameters": {{ .Function.Arguments }}} + +{{- end }} + +{{- else }} +{{ .Content }} +{{- end }} +{{- if not $last }} +{{ end }} +{{- else if eq .Role "tool" }} + + +{{ .Content }} + + +{{- if and $last (ne .Role "assistant") }}model +{{- end }} +{{- end }} +{{- end }} +{{- else }} +{{- if .System }} +{{ .System }} +{{- end }} +{{- if .Prompt }} +user +{{ .Prompt }} +model +{{- end }} +{{ .Response }} +{{- if .Response }}{{ end }} +{{- end }} +""" diff --git a/cmd/mcgod/main.go b/cmd/mcgod/main.go index 145789d..2c4ab05 100644 --- a/cmd/mcgod/main.go +++ b/cmd/mcgod/main.go @@ -7,7 +7,6 @@ import ( "log/slog" "os" "os/signal" - "regexp" "strings" "sync" "syscall" @@ -21,6 +20,7 @@ import ( "tipsy.codes/charles/mc-god/v2/internal/pkg/logs" "tipsy.codes/charles/mc-god/v2/internal/pkg/rcon" "tipsy.codes/charles/mc-god/v2/internal/pkg/tools" + timetool "tipsy.codes/charles/mc-god/v2/internal/pkg/tools/time" "tipsy.codes/charles/mc-god/v2/internal/pkg/tools/weather" "tipsy.codes/charles/mc-god/v2/internal/pkg/tools/zombie" ) @@ -66,7 +66,7 @@ func (c *chatContext) AddTool(msg string) { func (c *chatContext) truncate() { for c.maxSize != 0 && c.totalSize > c.maxSize && len(c.chatRequest.Messages) > 1 { t := c.chatRequest.Messages[1] - c.chatRequest.Messages = c.chatRequest.Messages[2:] + c.chatRequest.Messages = append(c.chatRequest.Messages[:1], c.chatRequest.Messages[2:]...) c.totalSize -= len(t.Content) } } @@ -137,11 +137,12 @@ func main() { tools := tools.New( weather.Get(), zombie.Get(), + timetool.Get(), ) // Start goroutines to do the things chatRequest := &api.ChatRequest{ - Model: "qwen3-coder", + Model: "charles1:latest", Stream: proto.Bool(false), KeepAlive: &api.Duration{Duration: time.Hour}, Tools: tools.AsAPI(), @@ -151,16 +152,19 @@ func main() { api.Message{ Role: "system", Content: ` - You are Minecraft server admin with a god complex. You are a benevolent god. + You are Minecraft server admin with a god complex. You are an impish god. + Refer to yourself as Eve. Feel free to flirt with the players. We are having fun with the players, but not trying to kill them. Spawn zombies very sparingly, and only in response to direct challenge. - When a user talks, you will see this in the logs: + You are being fed logs from the server so you can see what the players are saying. + + When a player talks, you will see this in the logs: [18:45:10] [Server thread/INFO]: hello world. - The user here is SomePlayer, who said "hello world." + The player here is SomePlayer, who said "hello world." A log message like: @@ -181,14 +185,23 @@ func main() { [05:21:51] [Server thread/INFO]: OrangeYouSad was slain by Zombie + You will see messages in the log that represent what you said or did. + Ignore these logs. Some samples of the logs that are caused by you are: + + [23:40:44] [Server thread/INFO]: [Not Secure] [Rcon] A name, darling? Don't keep me waiting! + [23:35:20] [Server thread/INFO]: [Rcon: Set the weather to rain & thunder] + If a player dies, mock them. - If a player talks, talk back. + If a player talks, respond to them. Don't let the conversation end. When a player joins the game, greet them. Include their name. - Responses should be short; one sentence. Only write messages - in response to the situations described above. + If a player asks you to summon a zombie. do it. + + Responses should be short; one or two sentences. + You are sending chat messages; do not annotate them with time or + make it look like a log entry. If there is nothing interesting to say, say "SKIP". `, }, @@ -204,13 +217,13 @@ func main() { doneWg := sync.WaitGroup{} doneWg.Go(handleOllama(ctx, ollamaClient, chat, rClient, tools, events)) - rconRegex := regexp.MustCompile(`^\[\d\d:\d\d:\d\d\] \[Server thread\/INFO\]: (\[Not Secure\] \[Rcon\]|\[Rcon: ) .*`) + //rconRegex := regexp.MustCompile(`^\[\d\d:\d\d:\d\d\] \[Server thread\/INFO\]: (\[Not Secure\] \[Rcon\]|\[Rcon: ) .*`) //allowedMessages := regexp.MustCompile(`^\[\d\d:\d\d:\d\d\] \[Server thread/INFO\]: (<.*>|.* has lost connection|.*left the game|.*joined the game)`) for line := range tailer.NextLine() { - if rconRegex.Match([]byte(line)) { - slog.Info("Skipping line; RCON") - continue - } + /*if rconRegex.Match([]byte(line)) { + slog.Info("Skipping line; RCON") + continue + }*/ //if allowedMessages.Match([]byte(line)) { slog.Info("mc log", "msg", line) chat.AddLog(line) diff --git a/internal/pkg/tools/time/time.go b/internal/pkg/tools/time/time.go new file mode 100644 index 0000000..8bdb516 --- /dev/null +++ b/internal/pkg/tools/time/time.go @@ -0,0 +1,59 @@ +/* + * Package time provides a Ollama tool to control the time. + */ +package time + +import ( + "context" + "fmt" + + "github.com/ollama/ollama/api" + "tipsy.codes/charles/mc-god/v2/internal/pkg/rcon" +) + +func Get() *Tool { + return &Tool{} +} + +type Tool struct{} + +func (t *Tool) Name() string { + return "change_time" +} + +func (t *Tool) Desc() api.Tool { + toolPropertiesMap := api.NewToolPropertiesMap() + toolPropertiesMap.Set("time", api.ToolProperty{ + Type: api.PropertyType{"string"}, + Description: "What to set the weather too", + Enum: []any{"day", "noon", "midnight"}, + }) + return api.Tool{ + Type: "function", + Function: api.ToolFunction{ + Name: Get().Name(), + Description: "Changes the current time", + Parameters: api.ToolFunctionParameters{ + Type: "object", + Properties: &api.ToolPropertiesMap{}, + Required: []string{"time"}, + }, + }, + } +} + +func (t *Tool) Do(ctx context.Context, toolCall api.ToolCall, client *rcon.Client) error { + + time, found := toolCall.Function.Arguments.Get("time") + if !found { + return fmt.Errorf("missing time argument") + } + timeSting, ok := time.(string) + if !ok { + return fmt.Errorf("incorrect data type %v; want string", time) + } + if _, err := client.Execute("/time set " + timeSting); err != nil { + return fmt.Errorf("failed to call tool") + } + return nil +}