Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

gpencil/toolargs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

2 Commits

Repository files navigation

toolargs

中文文档

Schema-first argument binding and repair for LLM tool calls.

toolargs is the argument-contract layer between an LLM and your MCP/tool implementation.

It solves a common production problem: the model sees a schema, but the arguments it sends back are often not the exact Go/API shape your tool expects. Without a shared layer, every tool ends up writing its own field-name fixes, type coercion, schema rendering, validation, logging, and retry feedback.

With toolargs, a tool author defines one Go struct, registers it once at startup, exposes schema from the compiled contract, and binds runtime arguments through the same contract.

The Problem

LLM tool calls fail in ways that are easy to underestimate:

  • The model sends the wrong field name, such as phone instead of phoneNumber.
  • The model changes casing or separators, such as ticket_id instead of ticketId.
  • The model sends the wrong JSON type, such as "8" instead of 8.
  • The model omits required fields or sends extra meaningless fields.
  • The model sends an ambiguous field, such as phone when both phoneNumber and phoneNum exist.
  • Tool authors duplicate repair logic across tools, and each copy behaves slightly differently.
  • Schema generation and argument binding drift apart when they are implemented separately.
  • Bad tool definitions are discovered only after a model call reaches production.

These are generic LLM argument-contract problems, not business-domain problems. toolargs keeps them out of each individual MCP tool.

Common Scenarios

1. The model uses a shorter field name

Your schema says:

{"phoneNumber": "string"}

The model sends:

{"phone": "13120057004"}

Without toolargs, each tool has to decide whether phone means phoneNumber, reject it, or silently ignore it. With toolargs, deterministic matching can bind phone to phoneNumber when it is the only safe candidate, and records the repair in BindReport.

2. The model sends a usable value with the wrong JSON type

Your tool expects:

{"priority": 3}

The model sends:

{"priority": "3"}

toolargs can coerce safe scalar values and report the conversion. Tool logic receives the typed Go value instead of reimplementing conversion in every handler.

3. The model sends an ambiguous field

Your tool has both:

{"phoneNumber": "string", "phoneNum": "string"}

The model sends:

{"phone": "13120057004"}

toolargs does not guess. It returns a retryable argument error that tells the model to use phoneNumber or phoneNum.

4. The tool needs both bound values and diagnostics

The model sends mixed-quality arguments:

{"phone": "13120057004", "priority": "3", "extra": "ignored?"}

toolargs analyzes the full argument object, matches fields, coerces safe values, and writes the final typed Go struct when the input is acceptable. If the input is not acceptable, it returns a structured error that can be sent back to the model, including the problematic path, expected field or type, received value, examples, and a suggested fix. At the same time, BindReport gives the application enough detail to log what was repaired or rejected, making later debugging and prompt/tool-schema tuning much easier.

5. A high-risk tool must not auto-repair field names

For payment, deletion, permission, or other high-risk tools, silent field-name repair may be unacceptable. Register the tool with MatchingModeExact, and toolargs will only accept fields that exactly match the schema.

6. A bad tool definition should fail before production traffic

Invalid examples, unsupported field types, required groups that reference missing fields, schema conflicts, and contradictory matching options are caught during Register. The service fails fast instead of discovering the issue after an LLM call reaches the tool.

What It Does

  • Uses one registered Go struct as the single source of truth for model-facing schema and runtime binding.
  • Validates predictable setup problems at startup: tags, examples, required groups, schema conflicts, field-name collision warnings, unknown-field policy, and matching-mode conflicts.
  • Repairs deterministic model mistakes by default: exact, normalized, and derived-key field names, plus safe type coercion.
  • Analyzes arguments and assigns typed Go values when the input is acceptable.
  • Lets high-risk tools choose strict exact field matching instead of automatic field-name repair.
  • Returns clear model-readable feedback when the input is not acceptable.
  • Produces BindReport for logs so teams can see exactly which fields were repaired, converted, ignored, rejected, or left ambiguous.

What It Does Not Do

toolargs does not implement MCP transport, choose tools, reroute between tools, call an LLM to repair arguments, or own downstream business validation. It only handles the generic LLM tool-argument contract layer.

Install

go get github.com/gpencil/toolargs

Quick Start

package main
import (
	"errors"
	"log"
	"github.com/gpencil/toolargs"
)
type TicketArgs struct {
	PhoneNumber string `json:"phoneNumber" toolargs:"required,desc=user phone number,example=13120057004"`
	Priority int `json:"priority" toolargs:"required,desc=ticket priority,example=3"`
}
func main() {
	registry := toolargs.NewRegistry()
	err := toolargs.Register[TicketArgs](registry, toolargs.Tool{
		Name: "query_tickets",
		Description: "Query support tickets by user phone number.",
	})
	if err != nil {
		log.Fatal(err)
	}
	schema, err := registry.Schema("query_tickets")
	if err != nil {
		log.Fatal(err)
	}
	_ = schema // expose this schema to the upstream model
	rawArgs := []byte(`{"phone":"13120057004","priority":"3"}`)
	var args TicketArgs
	report, err := toolargs.BindWithReport[TicketArgs](registry, "query_tickets", rawArgs, &args)
	if err != nil {
		var uncertain *toolargs.UncertainMatchError
		if errors.As(err, &uncertain) {
			log.Printf("arguments bound with uncertain repairs: %+v", uncertain.Repairs)
			// args has already been populated; decide whether to continue based on tool risk.
		} else {
			log.Printf("retryable model feedback: %s", registry.Explain(err))
			return
		}
	}
	if report.HasRepairs() {
		log.Printf("argument repairs: %+v", report.Repairs)
	}
}

Matching Policy

By default, toolargs uses MatchingModeToolArgs, which enables deterministic field-name matching:

  • exact: phoneNumber -> phoneNumber
  • normalized: phone_number -> phoneNumber
  • derived_key: phone -> phoneNumber when it is the only candidate

High-risk tools can require exact schema field names only:

err := toolargs.Register[TicketArgs](registry, toolargs.Tool{
	Name: "query_tickets",
}, toolargs.WithMatchingMode(toolargs.MatchingModeExact))

Optional fuzzy, semantic, and contextual matching are not enabled by default. Enable them explicitly only when the tool owner can handle UncertainMatchError and log BindReport.

Required Groups

Use WithRequiredAny for generic constraints such as "projectName or projectId must be provided".

err := toolargs.Register[QueryProjectArgs](registry, toolargs.Tool{
	Name: "query_projects",
	Description: "Query projects.",
}, toolargs.WithRequiredAny("projectName", "projectId"))

The default schema includes both:

  • a natural-language top-level description, for example must provide one of projectName or projectId
  • a program-readable x-required-any extension

Unknown Field Policy

Unknown fields are rejected by default to avoid silently accepting meaningless model output.

Low-risk tools can opt into ignoring unknown fields:

err := toolargs.Register[QueryProjectArgs](registry, toolargs.Tool{
	Name: "query_projects",
}, toolargs.WithUnknownFieldPolicy(toolargs.UnknownFieldIgnore))

Ignored fields are still recorded in BindReport with the ignored_field layer.

Extension Points

toolargs keeps the core flow stable and exposes extension interfaces:

  • FieldMatcher: add custom field-name matching
  • Coercer: add custom type conversion
  • FormatNormalizer: normalize custom field formats
  • SchemaRenderer: replace schema rendering
  • IssueReporter: replace model-readable error output

Hidden Defaults

Hidden defaults such as includeArchived=false should be injected by your MCP adapter or business assembly layer, not by toolargs.

License

MIT

About

这是一个基础 SDK,目标是让更多 MCP / LLM tool 项目直接集成,专门处理 LLM tool arguments 的 schema、bind、repair、explain。

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

Contributors

Languages

AltStyle によって変換されたページ (->オリジナル) /