Skip to content

[Engine] accept Codex spellings of a namespace tool - #3972

Closed
Righteous-AI wants to merge 2 commits into
Niko1221:mainfrom
Righteous-AI:codex-mcp-tool-spellings
Closed

Righteous-AI wants to merge 2 commits into
Niko1221:mainfrom
Righteous-AI:codex-mcp-tool-spellings

Conversation

@Righteous-AI

Copy link
Copy Markdown
Contributor

Problem and result: Codex CLI, talking to Strata over the Responses API, stops the turn when Qwen writes a namespace tool under a spelling Strata does not accept. The block comes back as assistant text, so Codex never runs it. Stock main already accepts namespace__member (PR #3556). This also accepts namespace=member, namespace_member, and the bare member when exactly one declared tool has that member.

Observed on Strata v0.2.2 (011c9e60), Codex 0.163, model Qwen3.8-Flash-Next-FP4. The model wrote <function=mcp__filesystem=write_file>, <function=mcp__filesystem_edit_file>, and later a bare <function=write_file> and <function=list_allowed_directories>. The declared tool was mcp__filesystem.write_file (and the same namespace for the others). After this change those calls come back as function_call items with namespace mcp__filesystem and the member name.

Related discussion: none found for these spellings. #3075 stays as it is: a name that matches nothing is still text, and two namespaces that share a member are not guessed. A Codex turn does end when that text path happens. This PR does not change that rule.

Approach: the alias table in OutputParser already rewrites namespace__member to the declared namespace.member name, which request_tools maps back to a namespace and a member. The same table now includes the equals form (the <function= tag's equals sign), the one-underscore form, and the bare member. An alias is kept only when one declared tool produces it, and a name that is itself declared wins.

Validation:
Passed: python3 -m unittest serve.test_tool_name_alias serve.test_tool_call_envelope -v on 6f5d5ab (parent 7dd5cf18, Strata main). Python 3.14, jinja2 3.x, no GPU. 20 tests, OK. The new file covers the equals write, the underscore edit with a string argument, bare write_file (path /tmp/patch-tencut.py), bare list_allowed_directories, the existing double-underscore form, an equals call inside thinking, an unknown name staying text, two namespaces sharing write_file staying text, and a flat tool named write_file winning over the namespace member.
Failed: none in those tests.
Not run: CUDA, HIP, and SYCL builds. This diff is the Python parser and its doc line. serve.test_responses.ToolRoundTrip.test_namespace_call_written_with_double_underscore was not run: Service imports strata_tokenizer, which this checkout does not put on the path, and no engine was started. A live Codex session against this branch was not run. The fixtures are the call text saved from the session above.

Limits: an unknown name, and a bare member shared by two namespaces, still come back as text (#3075). Codex ends the turn in that case. Say if that should be a follow-up where the block is a function_call with no namespace, so the client can reject it and continue.

Qwen writes mcp__server=tool, mcp__server_tool, and a bare member such as
write_file. Each maps to namespace.member when that spelling belongs to
exactly one declared tool. An unknown name still stays text.
@Righteous-AI

Copy link
Copy Markdown
Contributor Author

Extra validation for 6f5d5ab:

  • Before/after: serve.test_tool_name_alias fails 5 of 9 on main (7dd5cf18). mcp__filesystem=write_file, mcp__filesystem_edit_file, and bare write_file / list_allowed_directories come back as assistant text. It passes 9 of 9 on this branch. The guard cases (unknown name stays text, shared member not guessed, exact name wins) pass on both.
  • Rest of the suite: serve.test_responses.ToolRoundTrip.test_namespace_call_written_with_double_underscore passes (CPU, strata_tokenizer is pure Python under tools/). The full serve suite gives 1043 tests with 1 failure here and 1034 with 2 failures on main. Both failures are the existing test_lifeline portable-watcher test and an intermittent NoLeakedSampler thread count, and neither is related to this change.
  • Live: six fresh headless Codex 0.164.0 turns against this build on an RTX 5090 (Qwen3.8-Flash-Next-FP4, Responses API) all completed. Every filesystem call came back as a function_call in namespace mcp__filesystem, and none was returned as text. The model wrote the declared dotted names in these runs, so this shows no regression on the live path. The drifted spellings are sampling-dependent and are covered by the before/after tests above.

Niko1221 pushed a commit that referenced this pull request Oct 27, 2026
A declared namespace.member tool (Codex MCP: mcp__filesystem.write_file) was accepted only as namespace__member (#3556).
Qwen3.8-Flash-Next also writes namespace=member (the <function= tag's equals sign ends up in the name), namespace_member
(one underscore dropped) and the bare member. Each is now an alias of the declared tool when exactly one declared tool
produces it and no declared name is the same; a name that matches nothing stays text (#3075), two namespaces that share
a member are not guessed, and a declared flat tool always wins. Requests without namespaced tools are unchanged.

serve/test_tool_name_alias.py (10 tests, 5 of the first 9 fail without the change; the tenth pins that a flat tool named
like the underscore alias wins), docs/DETAILS.md names the spellings.

Ported from PR #3972.
@Niko1221

Copy link
Copy Markdown
Owner

Thank you. Fixed in 0.2.7: Accept Codex spellings of a namespace tool. Your name is on the commit.

Release: https://github.com/Niko1221/Strata/releases/tag/v0.2.7

@Niko1221 Niko1221 closed this Oct 27, 2026
MistVVK added a commit to MistVVK/XeStrata that referenced this pull request Oct 30, 2026
宣言した namespace.member のツール(Codex の MCP: mcp__filesystem.write_file)は namespace__member だけを
受けていた(Niko1221#3556)。Qwen3.8-Flash-Next は namespace=member(<function= の等号が名前に入る)、
namespace_member(下線が 1 つ落ちる)、メンバーだけも書くので、宣言したツールの 1 つだけがその綴りを生み、
同じ名前の宣言が無いときに限り、その別名として受ける。どれにも合わない名前は文のまま(Niko1221#3075)、
同じメンバーを持つ 2 つの名前空間は推し量らず、宣言した平らなツールが常に勝つ。upstream Niko1221#3972。
ruc1j added a commit to ruc1j/XeStrata-890M that referenced this pull request Oct 31, 2026
…の 1)

serve の変更
- 宣言していない名前の tool call を文のまま返すとき、その名前を strata_rejected_tool_calls で答えに添え、/metrics/prometheus で数える(upstream 805ebe1、Niko1221#3796)
- 名前空間つきの tool は namespace=member、namespace_member、member だけの綴りでも受け、`__` を `_` に潰した綴りも宣言した tool として読む(upstream fd8d37e、805ebe17b、Niko1221#3972)
- Chat Completions の tool の結果を、答えた call の順に並べ直す(upstream 11237f8、Niko1221#3395)
- message のない chat の要求は 400 にする(upstream 8a3ca07)
- --host :: で IPv6 で待ち受ける(upstream eba3106、Niko1221#3212)
- /v1/responses の max_output_tokens は 0 と -1 を上限なしとして受け、答えに timings を付ける(upstream 9c2d35d、a0fc88abd、Niko1221#4036、Niko1221#3291)
- response_format の repair_latex_escapes で、LaTeX のエスケープで壊れた JSON を直してから検査する(upstream 2cc6ea1、Niko1221#2893)
- 1 トークンの答えには decode の速さを出さない(upstream 5d5c26d)
- elastic な画像エンコーダーが起動できなければ 503 を返す(upstream 092c7d1、Niko1221#2435)
- lifeline の識別は子プロセスの開始時刻にし、sampler は壊れた source に耐える(upstream b3db5a1、Niko1221#3590、Niko1221#3407)
- web ページ: 答えを追うのは読み手が離れていないあいだだけ(upstream 89a68d8)、広い窓を使い狭い画面に収める(upstream ad1a310、Niko1221#3176)

setup の変更
- cmake と ninja は venv のものを先に取り、CMakeLists.txt が求める 3.24 より古い cmake は使わない(upstream 5bfce4b、Niko1221#3822)
- 別の generator で configure された build フォルダーは作り直す(upstream bf994ca、Niko1221#4038)
- ダウンロードの長さが取れなければ公開されている大きさを使う(upstream a1d2fd7、Niko1221#2287)
- 2 分データが来ないダウンロードは止まっていると言う(upstream ed0c893、Niko1221#3567)
- --rope-scale は有限で 1 以上の値だけ受ける(upstream a332cc9、Niko1221#3393)
- pip の記録はどの OS が書いたかを覚え、別の OS の記録は信じない(upstream fb3bae2、Niko1221#3634)

tools の変更
- gguf_reader.py はヘッダーの読みに上限を置き、壊れた metadata を ValueError にする(upstream 409f246、Niko1221#2288、Niko1221#1919)
- strata_bench.py は amd-smi しかない ROCm 7 以降でも AMD のカードの名前を書く(upstream c471588、Niko1221#3630)
- calibrate.py は計測の途中でエンジンが落ちても、その候補だけを落として続ける(upstream 41188ec、Niko1221#3317)
- chat.py は端末で fenced code block に色を付ける(upstream 3e81173、PR Niko1221#1895)
- 試験: llama.cpp の gguf-py がなければ test_iq_pack は skip、ほか試験の穴 3 つ(upstream 5b14fe2、Niko1221#3236)

fork に合わせて変えたこと: upstream の docs/DETAILS.md の差分は取らず、文書は別に書く。logprobs(upstream 9ea0449)は fork にすでにあり、allowed_token_ids は見送り。Control(strata_control)に依る setup の変更と、fork にない機能(presets の警告、Delete a model、queue timeout の badge)は取らない。
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