functool's non-struct input wrapping ({"Arg0": ...}) breaks todo tools for models that don't guess the hidden envelope key

Cerrado
#1,356 1 comentario 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

@qmuntal ya está trabajando en esto.

Desde el 4/11/2026.

  • #1358 de @qmuntal — abierto

Evaluación

Dificultad
3/5
Tiempo estimado
1-2 días
Aptitud para principiantes
52/100
Tipo de issue
Error
Claridad
Bastante claro
Estado de actividad
Activo
Stack tecnológico
go

Línea de trabajo

Start by reading functool.inputFormatFor and the related change in issue #527 to understand why non-struct inputs are wrapped. Then inspect the todo provider's todos_add, todos_complete, and todos_remove handlers under agent/harness/todo; compare their generated schemas and call behavior with the bare input shapes in this report. Done means the selected fix lets models call these tools using a discoverable input shape, with validation covering the reported cases.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Summary

functool.inputFormatFor wraps any non-struct In type in an undocumented inputWrapper[T]{ Arg0 T } envelope, so the JSON schema exposed to the model for a tool like todos_remove(ids []int) requires {"Arg0": [...]} instead of a bare array. This key is never mentioned in the tool's name, description, or anywhere a model could discover it from context — it only exists because of the Go field name chosen for the wrapper struct.

In practice, several models (confirmed with a local/open model via an OpenAI-compatible endpoint; likely any model that isn't extremely strict about schema adherence) send the natural shape implied by the tool description instead — a bare array — and every call is rejected.

This directly breaks the built-in agent/harness/todo provider's todos_add, todos_complete, and todos_remove tools, which are defined with raw slice handlers:

func(ctx context.Context, input []ItemInput) ([]Item, error)   // todos_add
func(ctx context.Context, items []CompleteInput) (int, error)  // todos_complete
func(ctx context.Context, ids []int) (int, error)              // todos_remove

When a model calls todos_remove with the bare array its own tool description implies, the call fails validation and the model has no way to discover the correct shape short of parsing the raw JSON schema character-by-character. In a real session, this left two todo items permanently stuck as "open" — the model repeatedly tried to mark them complete/remove them, got a rejection every time, and gave up, reporting it as a tool bug (it was right).

Repro

tl := functool.MustNew(
    functool.Config{Name: "todos_remove", Description: "remove"},
    func(ctx context.Context, ids []int) (int, error) {
        return len(ids), nil
    },
)

tl.Call(context.Background(), `[1,2]`)
// → validation error: validating root: type: [1 2] has type "array", want "object"

tl.Call(context.Background(), `{"Arg0":[1,2]}`)
// → 2, nil

Confirmed against both v0.3.0 and current main (2cd194d) — unchanged.

Related prior work

#527 ("Do not wrap pointer-to-struct functool inputs") fixed the same inputWrapper/Arg0 mechanism for the *Struct case, but by design left genuinely non-struct inputs (slices, maps, primitives) wrapped exactly as before. This issue is about that remaining case specifically causing real-world tool-call failures, not about removing the wrapping mechanism itself (which is presumably still needed to keep the top-level schema an object for non-struct types).

Suggested fix

A couple of options that don't require removing the wrapping mechanism:

  1. Give the wrapper field a stable, descriptive JSON name (e.g. value or items) instead of the Go-reflection-derived Arg0, and document it so tool authors know what shape to describe to the model.
  2. For the specific agent/harness/todo provider, change todos_add/todos_complete/todos_remove to accept a small named struct (e.g. type RemoveInput struct { IDs []int }) instead of a raw slice, producing a flat, self-documenting schema ({"ids": [...]}) that doesn't depend on functool's wrapping behavior at all.

Happy to send a PR for option 2 (the todo provider specifically) if that's the preferred direction.

Environment

  • github.com/microsoft/agent-framework-go v0.3.0 and current main
  • Go 1.25
Lenguaje dominante
Go
Estrellas
671
Forks
68
Merge medio
18 h 11 min
PR fusionados (30 d)
82

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de microsoft/agent-framework-go

Todos los issues de microsoft/agent-framework-go

Issues similares

Más issues de Go

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.