Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/GuaiZai233/FrostAgent/llms.txt

Use this file to discover all available pages before exploring further.

FrostAgent’s tool system gives the LLM a structured way to take actions in the world — sending messages, spawning subagents, fetching weather data, and querying game versions. Every tool is a self-contained Go struct with a JSON Schema definition that the Engine forwards to the LLM so the model knows exactly what arguments each tool accepts. When the LLM requests a tool call, the Engine looks up the tool in the registry, invokes its Execute function with the JSON arguments, and feeds the result back into the conversation.

Tool Interface

Each tool in internal/tools/tools.go is a Tool struct with four methods that satisfy the llm.ToolExecutor interface expected by the Engine:
type Tool struct {
    name        string
    description string
    parameter   any   // JSON Schema passed to the LLM
    execute     func(args string) (string, error)
}

func (t Tool) Name() string               { return t.name }
func (t Tool) Description() string        { return t.description }
func (t Tool) Parameters() map[string]any { /* returns parameter as map */ }
func (t Tool) Execute(args string) (string, error) { return t.execute(args) }
MethodReturnsPurpose
Name()stringUnique identifier used to register the tool and match LLM tool-call requests.
Description()stringHuman-readable description sent to the LLM to help it decide when to call this tool.
Parameters()map[string]anyA JSON Schema object definition. The Engine sends this alongside Name and Description in every ChatRequest.
Execute(args string)(string, error)Called by the Engine with the JSON-encoded arguments the LLM produced. Returns a plain-text or JSON result that is appended as a role: "tool" message.
args is always a raw JSON string produced by the LLM. Your Execute implementation should unmarshal it with json.Unmarshal and validate required fields before acting on them.

Tool Registry

Registry (in internal/tools/registry.go) stores tool metadata and executor functions in two parallel maps, allowing fast lookup by name.
type Registry struct {
    tools     map[string]Tool
    executors map[string]func(args string) (string, error)
}

NewRegistry() *Registry

Creates an empty registry with pre-allocated maps.
reg := tools.NewRegistry()

Register(t Tool)

Adds a tool to both internal maps. Calling Register with a name that already exists silently overwrites the previous entry.
reg.Register(tools.SendMsgTool())
reg.Register(tools.GetWeatherTool())

GetTool(name string) (Tool, bool)

Returns the full Tool struct (including its schema) for a given name, or false if not found.
if t, ok := reg.GetTool("get_weather"); ok {
    fmt.Println(t.Description())
}

GetExecutor(name string) (func(args string) (string, error), bool)

Returns just the executor closure for a given tool name. Useful when you want to call a tool programmatically without going through the full registry.
if exec, ok := reg.GetExecutor("get_weather"); ok {
    result, err := exec(`{"city":"Tokyo"}`)
}

ListTools() []Tool

Returns all registered tool definitions as a slice. Order is non-deterministic (Go map iteration). The Engine uses this to build the Tools array on every ChatRequest.
for _, t := range reg.ListTools() {
    fmt.Println(t.Name(), "-", t.Description())
}

Execute(name, args string) (string, error)

Looks up the executor by name and calls it with args. Returns "tool not found" (not an error) if the name is unknown.
result, err := reg.Execute("get_weather", `{"city":"Paris"}`)

Built-In Tools

FrostAgent ships with four tools registered at startup in cmd/app/main.go.

1. send_message

Delivers a rich message to the user over the active OneBot connection. The Engine fires SendHook when this tool executes, causing the adapter to send the message immediately rather than waiting for the LLM’s final turn.
ParameterTypeRequiredDescription
messagesarraySequence of message payload objects.
messages[].typestringplain, image, record, video, file, mention_user, or quote.
messages[].textstringFor plainThe text string to send.
messages[].pathstringFor media typesLocal file path (prefixed file:// by the adapter).
messages[].urlstringFor media typesRemote URL for the media file.
messages[].mention_user_idstringFor mention_userQQ user ID to @-mention.
messages[].message_idstringFor quoteID of the message to quote/reply to.
sessionstringOverride the target session (platform_id:message_type:session_id). Defaults to the active session.
{
  "messages": [
    { "type": "mention_user", "mention_user_id": "12345" },
    { "type": "plain", "text": "Here is your weather report!" },
    { "type": "image", "url": "https://example.com/weather.png" }
  ]
}

2. use_subagent

Dispatches a task to a named subagent. Currently supports the Coder subagent, which is specialised for code generation tasks.
ParameterTypeRequiredDescription
subagent_namestringName of the subagent to call. Currently "Coder".
contentstringThe task description or code snippet to send to the subagent.
{
  "subagent_name": "Coder",
  "content": "Write a Go function that reverses a string."
}
The use_subagent tool expects send_message to be called first to inform the user that the subagent has been dispatched. This is enforced through the tool’s description prompt.

3. get_weather

Returns current weather information for a named city.
ParameterTypeRequiredDescription
citystringThe name of the city to query.
{ "city": "Tokyo" }
The tool currently returns a simulated response. Replace the execute closure with a call to your preferred weather API to make it live.

4. get_game_version

Returns the latest version string for a named game.
ParameterTypeRequiredDescription
gamestringThe name of the game to query.
{ "game": "Minecraft" }
Like get_weather, this tool returns a mocked response (V1.14.51) by default and is intended to be extended with a real data source.

Registering Tools in main.go

Tools are registered in cmd/app/main.go during the init() function. The current setup uses the Engine’s internal map[string]ToolExecutor directly:
func init() {
    // Build the tool map
    registry := make(map[string]tools.Tool)

    sendMsgTool := tools.SendMsgTool()
    registry[sendMsgTool.Name()] = sendMsgTool

    subAgentTool := tools.SubAgentTool(llmClient)
    registry[subAgentTool.Name()] = subAgentTool

    weatherTool := tools.GetWeatherTool()
    registry[weatherTool.Name()] = weatherTool

    gameVersionTool := tools.GetGameVersionTool()
    registry[gameVersionTool.Name()] = gameVersionTool

    // Convert to ToolExecutor map for the Engine
    executorMap := make(map[string]llm.ToolExecutor)
    for name, tool := range registry {
        executorMap[name] = tool
    }

    GlobalEngine = &llm.Engine{
        MaxIterations: 5,
        ToolRegistry:  executorMap,
        // ...
    }
}
For instructions on building and registering your own tools, see the Custom Tools guide.

Tool Execution Flow

The following describes exactly how the Engine processes a tool call once the LLM returns a response:
1

LLM returns tool_calls

The Provider.Chat response includes one or more ToolCall entries. Each has an ID, a Type (always "function"), and a Function with a Name and JSON-encoded Arguments string.
2

Engine iterates tool calls

runLoop iterates responseMsg.ToolCalls. For each entry it looks up the tool in ToolRegistry by tc.Function.Name.
3

Execute is called

tool.Execute(tc.Function.Arguments) is called with the raw JSON arguments string. The tool’s closure unmarshals the arguments, validates them, and returns a result string or an error.
4

SendHook fires for send_message

If the tool name is "send_message" and engine.SendHook != nil, the Engine calls SendHook(toolResult) immediately. The adapter parses the JSON result and writes a fully-formed OneBot action frame to the WebSocket. The Engine then replaces toolResult with "消息已发送" before continuing.
5

Tool result appended

A new ChatMessage with Role: "tool", Content: toolResult, and ToolCallID: tc.ID is appended to the message slice so the LLM knows what each tool returned.
6

Loop continues

After all tool calls in the current turn are processed, the Engine sends the updated message slice to the LLM again. If the LLM responds with no further tool calls, its content string is returned as the final answer.

Build docs developers (and LLMs) love