diff --git a/.github/workflows/studio-inference-smoke.yml b/.github/workflows/studio-inference-smoke.yml index 000974f715..93c119872c 100644 --- a/.github/workflows/studio-inference-smoke.yml +++ b/.github/workflows/studio-inference-smoke.yml @@ -478,10 +478,7 @@ jobs: "stream": False, "temperature": 0.0, "seed": SEED, - # tool_choice='required' constrains the grammar so the - # model emits the JSON tool_call envelope directly; 96 is - # plenty for `{"city":"Paris"}` plus the wrapping fields. - "max_tokens": 96, + "max_tokens": 120, }) assert status == 200, f"tool call status {status}: {data}" choice = data["choices"][0] @@ -496,8 +493,6 @@ jobs: # 123 * 456 = 56088. The agentic loop streams SSE; we # accumulate the assistant text and look for the answer. We # accept "56088" or "56,088" since the model may format it. - # 320 tokens covers the tool_call + tool result + brief - # natural-language answer; 600 was 2x what the model needs. content = post_sse("/v1/chat/completions", { "messages": [{"role": "user", "content": "What is 123 * 456? Use the python tool to compute it and tell me the number."}], "enable_tools": True, @@ -505,20 +500,29 @@ jobs: "session_id": "ci-tool-calling-py", "temperature": 0.0, "seed": SEED, - "max_tokens": 320, + "max_tokens": 600, }) assert "56088" in content or "56,088" in content, ( f"expected 56088 in python-tool answer, got: {content!r}" ) print(f"[tools] PASS python tool ({len(content)} chars)") - # NOTE: the dedicated "Server-side bash (terminal) tool" axis - # was dropped in favour of the python axis above. Both share - # the same server-side agentic-loop wiring (only the registry - # entry differs); the python axis is the canonical proof. - # Saves one SSE round (~30 s on macos, ~12 s on linux/windows). + # ── 3. Server-side bash (terminal) tool ────────────────────── + content = post_sse("/v1/chat/completions", { + "messages": [{"role": "user", "content": "Use the terminal tool to run `echo hello-bash-tool` and tell me the exact output."}], + "enable_tools": True, + "enabled_tools": ["terminal"], + "session_id": "ci-tool-calling-bash", + "temperature": 0.0, + "seed": SEED, + "max_tokens": 600, + }) + assert "hello-bash-tool" in content, ( + f"expected 'hello-bash-tool' in terminal-tool answer, got: {content!r}" + ) + print(f"[tools] PASS bash/terminal tool ({len(content)} chars)") - # ── 3. Server-side web_search tool ─────────────────────────── + # ── 4. Server-side web_search tool ─────────────────────────── # DuckDuckGo is flaky from CI runners and small Qwen3.5-2B # may not actually search. Only assert that the SSE stream # opens and yields any data; HTTP / parser failures already @@ -531,13 +535,13 @@ jobs: "session_id": "ci-tool-calling-web", "temperature": 0.0, "seed": SEED, - "max_tokens": 192, + "max_tokens": 400, }) print(f"[tools] PASS web_search stream ({len(content)} chars)") except Exception as exc: print(f"[tools] WARN web_search probe failed (non-blocking): {exc}") - # ── 4. Thinking on / off ───────────────────────────────────── + # ── 5. Thinking on / off ───────────────────────────────────── # Studio strips think blocks from message.content for tools-mode # responses, so we toggle plain chat (no enable_tools) and look # at the surfaced reasoning_content / message.thinking field. @@ -548,10 +552,7 @@ jobs: "enable_thinking": enable, "temperature": 0.0, "seed": SEED, - # 17 is small; 160 tokens is plenty of room for either - # "Yes, 17 is prime" + brief reasoning or a short - # ...+answer. 300 was overkill. - "max_tokens": 160, + "max_tokens": 300, }) assert status == 200 msg = data["choices"][0]["message"] diff --git a/.github/workflows/studio-windows-inference-smoke.yml b/.github/workflows/studio-windows-inference-smoke.yml index 98ccccc1c9..b5eabd6d26 100644 --- a/.github/workflows/studio-windows-inference-smoke.yml +++ b/.github/workflows/studio-windows-inference-smoke.yml @@ -609,10 +609,7 @@ jobs: "stream": False, "temperature": TEMP, "seed": SEED, - # tool_choice='required' constrains the grammar so the - # model emits the JSON tool_call envelope directly; 128 - # is plenty for `{"city":"Paris"}` plus the wrapping. - "max_tokens": 128, + "max_tokens": 600, }) assert status == 200, f"tool call status {status}: {data}" choice = data["choices"][0] @@ -632,8 +629,6 @@ jobs: ) # ── 2. Server-side python tool ─────────────────────────────── - # 320 tokens covers tool_call + result + brief answer; 600 - # was 2x what the model needs. content = post_sse("/v1/chat/completions", { "messages": [{"role": "user", "content": "What is 123 * 456? Use the python tool to compute it and tell me the number."}], "enable_tools": True, @@ -641,7 +636,7 @@ jobs: "session_id": "ci-tool-calling-py", "temperature": TEMP, "seed": SEED, - "max_tokens": 320, + "max_tokens": 600, }) if "56088" in content or "56,088" in content: print(f"[tools] PASS python tool ({len(content)} chars, found 56088)") @@ -652,13 +647,30 @@ jobs: f"model didn't return 56088 -- model output drift" ) - # NOTE: the dedicated "Server-side bash (terminal) tool" axis - # was dropped in favour of the python axis above. Both share - # the same server-side agentic-loop wiring (only the registry - # entry differs); the python axis is the canonical proof. - # Saves one SSE round (~12 s on windows-latest). + # ── 3. Server-side bash (terminal) tool ────────────────────── + # On Windows the terminal tool resolves to the system shell + # (cmd.exe wrapper) and `echo hello-bash-tool` works the same + # way it does on POSIX. The model still has to choose to + # invoke the tool; assert non-empty SSE if it doesn't. + content = post_sse("/v1/chat/completions", { + "messages": [{"role": "user", "content": "Use the terminal tool to run `echo hello-bash-tool` and tell me the exact output."}], + "enable_tools": True, + "enabled_tools": ["terminal"], + "session_id": "ci-tool-calling-bash", + "temperature": TEMP, + "seed": SEED, + "max_tokens": 600, + }) + if "hello-bash-tool" in content: + print(f"[tools] PASS terminal tool ({len(content)} chars)") + else: + assert content, "terminal tool: SSE stream empty" + print( + f"[tools] WARN terminal tool: SSE OK ({len(content)} chars) but " + f"model didn't echo 'hello-bash-tool' -- model output drift" + ) - # ── 3. Server-side web_search tool ─────────────────────────── + # ── 4. Server-side web_search tool ─────────────────────────── # DuckDuckGo can be flaky from CI runners; only assert that # the SSE stream opens and yields any data. try: @@ -669,13 +681,13 @@ jobs: "session_id": "ci-tool-calling-web", "temperature": TEMP, "seed": SEED, - "max_tokens": 192, + "max_tokens": 400, }) print(f"[tools] PASS web_search stream ({len(content)} chars)") except Exception as exc: print(f"[tools] WARN web_search probe failed (non-blocking): {exc}") - # ── 4. Thinking on / off ───────────────────────────────────── + # ── 5. Thinking on / off ───────────────────────────────────── def thinking_call(enable): status, data = post("/v1/chat/completions", { "messages": [{"role": "user", "content": "Briefly: is 17 prime?"}], @@ -683,10 +695,7 @@ jobs: "enable_thinking": enable, "temperature": TEMP, "seed": SEED, - # 17 is small; 160 tokens is plenty for either "Yes" - # + brief reasoning or a short ... + - # answer. 300 was overkill. - "max_tokens": 160, + "max_tokens": 300, }) assert status == 200 msg = data["choices"][0]["message"]