mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-07-28 17:06:05 +00:00
* feat(agents): per-agent model and generation settings Let each custom agent choose its own model and sampling settings (temperature, max_tokens) plus thinking / reasoning_effort defaults, so agents sharing a model profile are no longer stuck with one shared temperature and output length (#4336). AgentConfig gains optional model_settings / thinking_enabled / reasoning_effort (None = inherit). create_chat_model applies per-caller model_overrides on top of the profile before the thinking/Codex transforms; the lead agent resolves each knob with precedence request > agent config > profile/default. The /api/agents create/update routes persist the fields and reject an unknown model. The default lead agent path is unchanged (no agent config -> overrides None). The agent chat composer also stops force-overriding an agent's configured default model with models[0]. * fix(agents): tri-state thinking control and default-model capability gating The model-settings dialog seeded the thinking switch to false, so opening it to tweak temperature and saving silently disabled thinking (the runtime default is on) with no way back to inherit. It also hid the thinking / reasoning controls whenever the agent inherited the global default model, since `__default__` never resolved through `models.find`. Give thinking an explicit Inherit / On / Off tri-state so an untouched save is a no-op, and resolve `__default__` to the effective default (models[0]) for the capability check. Logic lives in the tested helpers module.
106 lines
3.6 KiB
TypeScript
106 lines
3.6 KiB
TypeScript
import { describe, expect, it } from "@rstest/core";
|
|
|
|
import {
|
|
DEFAULT_MODEL_VALUE,
|
|
INHERIT_VALUE,
|
|
MAX_AGENT_OUTPUT_TOKENS,
|
|
parseAgentModelSettingsDraft,
|
|
resolveEffectiveModel,
|
|
selectionToThinkingEnabled,
|
|
thinkingEnabledToSelection,
|
|
} from "@/components/workspace/agents/agent-settings-dialog-helpers";
|
|
import type { Model } from "@/core/models/types";
|
|
|
|
describe("parseAgentModelSettingsDraft", () => {
|
|
it("rejects invalid temperature values before save", () => {
|
|
expect(
|
|
parseAgentModelSettingsDraft({ temperature: "-0.1", maxTokens: "" }),
|
|
).toEqual({ ok: false, error: "temperature" });
|
|
expect(
|
|
parseAgentModelSettingsDraft({ temperature: "2.1", maxTokens: "" }),
|
|
).toEqual({ ok: false, error: "temperature" });
|
|
expect(
|
|
parseAgentModelSettingsDraft({ temperature: "warm", maxTokens: "" }),
|
|
).toEqual({ ok: false, error: "temperature" });
|
|
});
|
|
|
|
it("rejects invalid max token values before save", () => {
|
|
expect(
|
|
parseAgentModelSettingsDraft({ temperature: "", maxTokens: "0" }),
|
|
).toEqual({ ok: false, error: "max_tokens" });
|
|
expect(
|
|
parseAgentModelSettingsDraft({ temperature: "", maxTokens: "1.5" }),
|
|
).toEqual({ ok: false, error: "max_tokens" });
|
|
expect(
|
|
parseAgentModelSettingsDraft({
|
|
temperature: "",
|
|
maxTokens: String(MAX_AGENT_OUTPUT_TOKENS + 1),
|
|
}),
|
|
).toEqual({ ok: false, error: "max_tokens" });
|
|
});
|
|
|
|
it("returns null settings when both fields inherit", () => {
|
|
expect(
|
|
parseAgentModelSettingsDraft({ temperature: " ", maxTokens: "" }),
|
|
).toEqual({ ok: true, modelSettings: null });
|
|
});
|
|
|
|
it("keeps explicit nulls for cleared sub-fields when another setting remains", () => {
|
|
expect(
|
|
parseAgentModelSettingsDraft({ temperature: "0.2", maxTokens: "" }),
|
|
).toEqual({
|
|
ok: true,
|
|
modelSettings: { temperature: 0.2, max_tokens: null },
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("thinkingEnabledToSelection", () => {
|
|
it("maps null/undefined to inherit so an untouched save stays a no-op", () => {
|
|
// Runtime default is thinking-on; a plain on/off switch seeded to false
|
|
// would silently disable it. Inherit keeps the persisted null intact.
|
|
expect(thinkingEnabledToSelection(null)).toBe(INHERIT_VALUE);
|
|
expect(thinkingEnabledToSelection(undefined)).toBe(INHERIT_VALUE);
|
|
});
|
|
|
|
it("maps explicit booleans to on/off", () => {
|
|
expect(thinkingEnabledToSelection(true)).toBe("on");
|
|
expect(thinkingEnabledToSelection(false)).toBe("off");
|
|
});
|
|
});
|
|
|
|
describe("selectionToThinkingEnabled", () => {
|
|
it("round-trips the tri-state back to the persisted value", () => {
|
|
expect(selectionToThinkingEnabled(INHERIT_VALUE)).toBeNull();
|
|
expect(selectionToThinkingEnabled("on")).toBe(true);
|
|
expect(selectionToThinkingEnabled("off")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("resolveEffectiveModel", () => {
|
|
const models: Model[] = [
|
|
{
|
|
id: "a",
|
|
name: "a",
|
|
model: "a",
|
|
display_name: "A",
|
|
supports_thinking: true,
|
|
},
|
|
{ id: "b", name: "b", model: "b", display_name: "B" },
|
|
];
|
|
|
|
it("resolves the default sentinel to the effective default (models[0])", () => {
|
|
// An agent inheriting the global default must still surface the default
|
|
// model's capabilities instead of hiding thinking/reasoning controls.
|
|
expect(resolveEffectiveModel(models, DEFAULT_MODEL_VALUE)).toBe(models[0]);
|
|
});
|
|
|
|
it("resolves an explicit model by name", () => {
|
|
expect(resolveEffectiveModel(models, "b")).toBe(models[1]);
|
|
});
|
|
|
|
it("returns undefined for an unknown model", () => {
|
|
expect(resolveEffectiveModel(models, "missing")).toBeUndefined();
|
|
});
|
|
});
|