Merge remote-tracking branch 'unslothai/feature/tool-choice-kwarg-openai-format' into pr-5061-head
# Conflicts: # studio/backend/models/inference.py # studio/backend/routes/inference.py
This commit is contained in:
commit
8a265a2143
5 changed files with 50 additions and 22 deletions
|
|
@ -374,7 +374,9 @@ class ChatMessage(BaseModel):
|
|||
if self.content is None:
|
||||
raise ValueError("tool messages require content")
|
||||
if not self.tool_call_id:
|
||||
raise ValueError("tool messages require tool_call_id")
|
||||
raise ValueError(
|
||||
'role="tool" messages require "tool_call_id" per the OpenAI spec.'
|
||||
)
|
||||
else:
|
||||
if self.content is None:
|
||||
raise ValueError(f"{self.role} messages require content")
|
||||
|
|
|
|||
|
|
@ -111,9 +111,31 @@ class TestChatMessageToolRoles:
|
|||
ChatMessage(role = "function", content = "x")
|
||||
|
||||
def test_content_absent_defaults_to_none(self):
|
||||
msg = ChatMessage(role = "assistant")
|
||||
msg = ChatMessage(
|
||||
role = "assistant",
|
||||
tool_calls = [
|
||||
{"id": "c1", "type": "function", "function": {"name": "f", "arguments": "{}"}}
|
||||
],
|
||||
)
|
||||
assert msg.content is None
|
||||
|
||||
def test_tool_role_missing_tool_call_id_rejected(self):
|
||||
# Per OpenAI spec, role="tool" messages must carry tool_call_id so
|
||||
# upstream backends can associate the result with its prior call.
|
||||
# Pin the boundary-level rejection so a malformed tool-result
|
||||
# message never reaches the passthrough path.
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
ChatMessage(role = "tool", content = '{"temperature": 72}')
|
||||
assert "tool_call_id" in str(exc_info.value)
|
||||
|
||||
def test_tool_role_empty_tool_call_id_rejected(self):
|
||||
with pytest.raises(ValidationError):
|
||||
ChatMessage(
|
||||
role = "tool",
|
||||
tool_call_id = "",
|
||||
content = '{"temperature": 72}',
|
||||
)
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# ChatCompletionRequest — standard OpenAI tool fields
|
||||
|
|
|
|||
|
|
@ -330,7 +330,7 @@ export function AppSidebar() {
|
|||
<SidebarMenu>
|
||||
<NavItem
|
||||
icon={ZapIcon}
|
||||
label="Studio"
|
||||
label="Train"
|
||||
active={pathname === "/studio" || pathname.startsWith("/studio/")}
|
||||
disabled={chatOnly}
|
||||
onClick={() => {
|
||||
|
|
@ -511,7 +511,7 @@ export function AppSidebar() {
|
|||
/>
|
||||
<div className="flex flex-col gap-0.5 leading-none group-data-[collapsible=icon]:hidden">
|
||||
<span className="truncate text-sm font-semibold">Unsloth</span>
|
||||
<span className="truncate text-[11px] text-muted-foreground">Studio</span>
|
||||
<span className="truncate text-[11px] text-muted-foreground">Train</span>
|
||||
</div>
|
||||
<ChevronsUpDown strokeWidth={1.25} className="ml-auto size-4 text-muted-foreground group-data-[collapsible=icon]:hidden" />
|
||||
</SidebarMenuButton>
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ export function WizardFooter({
|
|||
if (currentStep === 1 && sessionStorage.getItem("unsloth_chat_only") === "1") {
|
||||
sessionStorage.removeItem("unsloth_chat_only");
|
||||
markOnboardingDone();
|
||||
window.location.assign(returnTo);
|
||||
window.location.assign("/chat");
|
||||
} else {
|
||||
nextStep();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { usePlatformStore } from "@/config/env";
|
||||
import { resetOnboardingDone } from "@/features/auth";
|
||||
import { useChatRuntimeStore } from "@/features/chat/stores/chat-runtime-store";
|
||||
import { useSettingsDialogStore } from "@/features/settings";
|
||||
|
|
@ -95,6 +96,7 @@ export function GeneralTab() {
|
|||
const setHfToken = useChatRuntimeStore((s) => s.setHfToken);
|
||||
const autoTitle = useChatRuntimeStore((s) => s.autoTitle);
|
||||
const setAutoTitle = useChatRuntimeStore((s) => s.setAutoTitle);
|
||||
const chatOnly = usePlatformStore((s) => s.chatOnly);
|
||||
const redirectTo = `${pathname}${search}`;
|
||||
|
||||
const [draftToken, setDraftToken] = useState(hfToken ?? "");
|
||||
|
|
@ -170,24 +172,26 @@ export function GeneralTab() {
|
|||
</SettingsRow>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection title="Getting started">
|
||||
<SettingsRow
|
||||
label="Start onboarding"
|
||||
description="Open the setup wizard again without changing your account."
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
resetOnboardingDone();
|
||||
closeDialog();
|
||||
navigate({ to: "/onboarding", search: { redirectTo } });
|
||||
}}
|
||||
{!chatOnly && (
|
||||
<SettingsSection title="Getting started">
|
||||
<SettingsRow
|
||||
label="Start onboarding"
|
||||
description="Open the setup wizard again without changing your account."
|
||||
>
|
||||
Start onboarding
|
||||
</Button>
|
||||
</SettingsRow>
|
||||
</SettingsSection>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
resetOnboardingDone();
|
||||
closeDialog();
|
||||
navigate({ to: "/onboarding", search: { redirectTo } });
|
||||
}}
|
||||
>
|
||||
Start onboarding
|
||||
</Button>
|
||||
</SettingsRow>
|
||||
</SettingsSection>
|
||||
)}
|
||||
|
||||
<SettingsSection title="Danger zone">
|
||||
<SettingsRow
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue