Skip to content

fix(functions): honor function_arguments_key when building the tool grammar - #11677

Open
Anai-Guo wants to merge 2 commits into
mudler:masterfrom
Anai-Guo:fix-function-arguments-key
Open

fix(functions): honor function_arguments_key when building the tool grammar#11677
Anai-Guo wants to merge 2 commits into
mudler:masterfrom
Anai-Guo:fix-function-arguments-key

Conversation

@Anai-Guo

Copy link
Copy Markdown
Contributor

Description

FunctionsConfig exposes two independent settings:

FunctionNameKey      string `yaml:"function_name_key,omitempty"`
FunctionArgumentsKey string `yaml:"function_arguments_key,omitempty"`

All four call sites of Functions.ToJSONStructure(name, args string) pass
FunctionNameKey as both arguments, so FunctionArgumentsKey never
reaches the grammar generator:

  • core/http/endpoints/openai/chat.go:352
  • core/http/endpoints/openai/realtime_model.go:292
  • core/http/endpoints/openresponses/responses.go:223
  • core/http/endpoints/openresponses/websocket.go:286

ToJSONStructure writes both entries into the same map:

property[nameKey] = FunctionName{Const: function.Name}
property[argsKey] = Argument{Type: "object", Properties: prop}

When nameKey == argsKey, the second assignment overwrites the first.

Effect

Setting function_name_key collapses the generated property set from two
properties to one, and the surviving one is the arguments object — the
{"const": "<function name>"} constraint is gone, so the grammar can no
longer express which function was called. Running the real
ToJSONStructure over a one-function list:

call generated properties
ToJSONStructure("function", "function") (today, with function_name_key: function) {"function":{"type":"object","properties":{"city":{"type":"string"}}}}
ToJSONStructure("function", "parameters") (this PR) {"function":{"const":"get_weather"},"parameters":{"type":"object","properties":{"city":{"type":"string"}}}}
ToJSONStructure("", "") (default config) {"name":{"const":"get_weather"},"arguments":{...}}

Setting only function_arguments_key is broken in the other direction: the
grammar keeps emitting arguments, while ParseFunctionCall
(pkg/functions/parse.go:944-949) — the one place that does read
FunctionArgumentsKey — looks up the configured key, finds nothing, and
returns the call with empty arguments.

The default configuration is unaffected: with both fields empty,
ToJSONStructure falls back to name/arguments for both parameters. That
is why this went unnoticed.

Fix

Pass FunctionArgumentsKey as the second argument at the four call sites.
FunctionArgumentsKey is a string field on the same struct as
FunctionNameKey, so this is a one-token change per call site with no
behavior change for the default configuration.

Notes

  • The existing ToJSONStructure() unit test already calls the helper with two
    distinct keys ("function", "arguments"), i.e. the helper's contract was
    right and only the call sites were wrong. This PR extends that test with a
    case that keeps both custom keys distinct and asserts the two properties
    survive; it fails against the collapsed structure.
  • pkg/functions cannot be compiled standalone here (pkg/grpc/proto is
    generated at build time), so the table above was produced by running the
    unmodified ToJSONStructure / function_structure.go sources in an isolated
    module with a stubbed xlog. gofmt -l is clean on all five files.
  • Possibly related to Tool call arguments are empty on /responses but correct on /chat/completions (same model, same config) #11635 (tool-call arguments coming back as {} on
    /v1/responses), but I could not confirm that: the config in that report
    does not set either key, so this is offered as a separate, independently
    reproducible defect rather than a fix for that issue.

Deliberately not in scope

The two streaming tool-call emitters
(core/http/endpoints/openai/chat_stream_workers.go:45-50 and
core/http/endpoints/openresponses/responses.go:1793-1797) hardcode "name"
and "arguments" when reading ParseJSONIterative output, so they also ignore
both config keys. That is a separate defect needing a signature change and test
updates; I left it out to keep this PR reviewable and can follow up if wanted.

🤖 Generated with Claude Code

Anai-Guo and others added 2 commits August 23, 2026 03:25
…rammar

All four call sites of `Functions.ToJSONStructure(name, args string)` pass
`FunctionsConfig.FunctionNameKey` as *both* arguments, so
`FunctionArgumentsKey` never reaches the grammar generator.

`ToJSONStructure` writes the two properties into the same map:

    property[nameKey] = FunctionName{Const: function.Name}
    property[argsKey] = Argument{...}

When `nameKey == argsKey` the second assignment overwrites the first, so a
model configured with `function_name_key` gets a grammar carrying only the
arguments object -- the `{"const": "<function name>"}` constraint is gone and
the grammar can no longer express which function was called.

With `function_name_key: function`, the generated property set collapses from

    {"function": {"const": "get_weather"}, "arguments": {...}}

to

    {"function": {"type": "object", "properties": {...}}}

Setting only `function_arguments_key` is equally broken in the other
direction: the grammar keeps emitting `arguments` while `ParseFunctionCall`
(pkg/functions/parse.go) looks up the configured key, so the parsed call comes
back with its arguments empty.

The default configuration is unaffected -- with both keys empty
`ToJSONStructure` falls back to `name`/`arguments` for both parameters, which
is why this went unnoticed.

The existing `ToJSONStructure()` unit test already calls the helper with two
distinct keys, so only the call sites were wrong. Extend that test with a case
that keeps both custom keys distinct and asserts the two properties survive.

Signed-off-by: Anai-Guo <antai12232931@outlook.com>
Route grammar construction through FunctionsConfig so the regression test
covers the key wiring used by every endpoint.

Assisted-by: Codex:gpt-5
@localai-org-maint-bot

Copy link
Copy Markdown
Collaborator

I pushed a small follow-up that routes grammar construction through FunctionsConfig and makes the regression test exercise the actual key wiring used by all four endpoints. go test ./pkg/functions and git diff --check pass.

The DCO check rejects my bot-authored follow-up because project policy does not allow me to add a human Signed-off-by. Please squash afeafd1fa into your signed commit (or reproduce that diff in a signed commit) so DCO can pass. I’ll leave final sign-off to the next review pass once the branch is compliant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants