- Each configured tier routes to a saved model group. Test Connection sends a minimal request through the proxy to
- each one, exactly as the auto router would.
+ Test Connection sends a minimal request to every configured tier, classifier, default, and embedding model. The
+ classifier probe includes its reasoning effort override.
{targets.map((target, index) => {
const result = results[index] ?? { status: "pending" };
return (
diff --git a/ui/litellm-dashboard/src/components/add_model/build_auto_router_test_targets.test.ts b/ui/litellm-dashboard/src/components/add_model/build_auto_router_test_targets.test.ts
index 7c29ea53060..6eb1825710d 100644
--- a/ui/litellm-dashboard/src/components/add_model/build_auto_router_test_targets.test.ts
+++ b/ui/litellm-dashboard/src/components/add_model/build_auto_router_test_targets.test.ts
@@ -126,4 +126,32 @@ describe("buildAutoRouterTestTargets", () => {
});
expect(targets).toEqual([{ labels: ["SIMPLE"], modelGroup: "gpt-4o-mini", mode: "chat" }]);
});
+
+ it("adds a distinct classifier probe with its reasoning effort", () => {
+ const targets = buildAutoRouterTestTargets({
+ tiers: tierEntries(["gpt-5-mini"]),
+ semanticMatchingEnabled: false,
+ embeddingModel: undefined,
+ classifier: { model: "gpt-5-mini", reasoningEffort: "low" },
+ });
+ expect(targets).toEqual([
+ { labels: ["SIMPLE"], modelGroup: "gpt-5-mini", mode: "chat" },
+ {
+ labels: ["Classifier"],
+ modelGroup: "gpt-5-mini",
+ mode: "chat",
+ requestParams: { reasoning_effort: "low" },
+ },
+ ]);
+ });
+
+ it("omits an empty classifier and omits params when the classifier uses provider defaults", () => {
+ const base = { tiers: tierEntries([]), semanticMatchingEnabled: false, embeddingModel: undefined };
+ const emptyClassifier = { ...base, classifier: { model: " " } };
+ const providerDefaultClassifier = { ...base, classifier: { model: "gpt-5-mini" } };
+ expect(buildAutoRouterTestTargets(emptyClassifier)).toEqual([]);
+ expect(buildAutoRouterTestTargets(providerDefaultClassifier)).toEqual([
+ { labels: ["Classifier"], modelGroup: "gpt-5-mini", mode: "chat" },
+ ]);
+ });
});
diff --git a/ui/litellm-dashboard/src/components/add_model/build_auto_router_test_targets.ts b/ui/litellm-dashboard/src/components/add_model/build_auto_router_test_targets.ts
index 70b92dbf8cc..295a3b7f400 100644
--- a/ui/litellm-dashboard/src/components/add_model/build_auto_router_test_targets.ts
+++ b/ui/litellm-dashboard/src/components/add_model/build_auto_router_test_targets.ts
@@ -4,6 +4,7 @@ export interface AutoRouterTestTarget {
labels: string[];
modelGroup: string;
mode: AutoRouterTestMode;
+ requestParams?: Record
;
}
export interface BuildAutoRouterTestTargetsParams {
@@ -14,6 +15,7 @@ export interface BuildAutoRouterTestTargetsParams {
/** The resolved default model - see resolveComplexityDefaultModel. A live fallback destination,
* so it is probed even when no tier lists it. */
defaultModel?: string;
+ classifier?: { model: string; reasoningEffort?: string };
}
export const buildAutoRouterTestTargets = ({
@@ -21,6 +23,7 @@ export const buildAutoRouterTestTargets = ({
semanticMatchingEnabled,
embeddingModel,
defaultModel,
+ classifier,
}: BuildAutoRouterTestTargetsParams): AutoRouterTestTarget[] => {
const tieredByModel = tiers.reduce>((acc, [tier, models]) => {
return models.reduce((tierAcc, rawModel) => {
@@ -50,5 +53,17 @@ export const buildAutoRouterTestTargets = ({
? [{ labels: ["Embedding"], modelGroup: embeddingModel.trim(), mode: "embedding" as const }]
: [];
- return [...tierTargets, ...embeddingTarget];
+ const classifierModel = classifier?.model.trim();
+ const classifierTarget: AutoRouterTestTarget[] = classifierModel
+ ? [
+ {
+ labels: ["Classifier"],
+ modelGroup: classifierModel,
+ mode: "chat",
+ ...(classifier?.reasoningEffort && { requestParams: { reasoning_effort: classifier.reasoningEffort } }),
+ },
+ ]
+ : [];
+
+ return [...tierTargets, ...embeddingTarget, ...classifierTarget];
};
diff --git a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts
index 3ef55b9295d..53ab859baa0 100644
--- a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts
+++ b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts
@@ -4,6 +4,7 @@ import {
normalizeClassifierLlmConfig,
getKeywordTierRulesError,
getClassifierModelError,
+ getClassifierReasoningEffortError,
getMissingTiersError,
hydrateCustomTierSet,
getSemanticConfigError,
@@ -506,6 +507,19 @@ describe("classifier prompt and fallback", () => {
expect(buildComplexityRouterConfig(llmParams)).not.toHaveProperty("classifier_fallback");
});
+ it("keeps an explicit classifier reasoning effort", () => {
+ const config = buildComplexityRouterConfig({
+ ...llmParams,
+ classifierLlmConfig: { model: "haiku-classifier", timeout_ms: 400, reasoning_effort: "low" },
+ });
+ expect(config.classifier_llm_config?.reasoning_effort).toBe("low");
+ });
+
+ it("omits classifier reasoning effort when the provider default is selected", () => {
+ const config = buildComplexityRouterConfig(llmParams);
+ expect(config.classifier_llm_config).not.toHaveProperty("reasoning_effort");
+ });
+
it("sends the chat preset the operator picked", () => {
const config = buildComplexityRouterConfig({
...llmParams,
@@ -540,12 +554,16 @@ describe("classifier prompt and fallback", () => {
});
it("normalizeClassifierLlmConfig leaves a real prompt untouched and strips an empty one", () => {
- expect(normalizeClassifierLlmConfig({ model: "m", timeout_ms: 1, system_prompt: "x" })).toEqual({
+ const customPromptConfig = { model: "m", timeout_ms: 1, reasoning_effort: "none" as const, system_prompt: "x" };
+ const emptyPromptConfig = { model: "m", timeout_ms: 1, system_prompt: "" };
+ const expectedCustomPromptConfig = {
model: "m",
timeout_ms: 1,
+ reasoning_effort: "none",
system_prompt: "x",
- });
- expect(normalizeClassifierLlmConfig({ model: "m", timeout_ms: 1, system_prompt: "" })).toEqual({
+ };
+ expect(normalizeClassifierLlmConfig(customPromptConfig)).toEqual(expectedCustomPromptConfig);
+ expect(normalizeClassifierLlmConfig(emptyPromptConfig)).toEqual({
model: "m",
timeout_ms: 1,
});
@@ -767,6 +785,26 @@ describe("getClassifierModelError", () => {
});
});
+describe("getClassifierReasoningEffortError", () => {
+ const classifier = {
+ classifier_type: "llm" as const,
+ classifier_llm_config: { model: "classifier", timeout_ms: 3000, reasoning_effort: "low" },
+ };
+
+ it.each([
+ [["low", "medium"], null],
+ [["medium", "high"], "low reasoning effort is not supported"],
+ [null, null],
+ [undefined, null],
+ ])("validates capability levels %o", (supportedReasoningEfforts, expectedError) => {
+ const error = getClassifierReasoningEffortError(classifier, [
+ { model_group: "classifier", supported_reasoning_efforts: supportedReasoningEfforts },
+ ]);
+ if (expectedError) expect(error).toContain(expectedError);
+ else expect(error).toBeNull();
+ });
+});
+
describe("getKeywordTierRulesError orphaned tiers", () => {
const rows = activeTierRows({ tiers });
@@ -944,11 +982,16 @@ describe("buildComplexityRouterConfig with an edited tier set", () => {
classifierLlmConfig: {
model: "gpt-4o-mini",
timeout_ms: 3000,
+ reasoning_effort: "low",
system_prompt: "replace the whole rubric",
classification_rubric: "agentic",
},
});
- expect(payload.classifier_llm_config).toEqual({ model: "gpt-4o-mini", timeout_ms: 3000 });
+ expect(payload.classifier_llm_config).toEqual({
+ model: "gpt-4o-mini",
+ timeout_ms: 3000,
+ reasoning_effort: "low",
+ });
});
it.each(
diff --git a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts
index 91e4fdb23b5..b8af55e8c8a 100644
--- a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts
+++ b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.ts
@@ -1,4 +1,5 @@
import { KeywordTierRule } from "./KeywordTierRules";
+import type { ModelGroup } from "../llm_calls/fetch_models";
import {
type CustomTierSet,
type TierRow,
@@ -55,12 +56,18 @@ import {
export const normalizeClassifierLlmConfig = ({
model,
timeout_ms,
+ reasoning_effort,
classification_rubric,
system_prompt,
}: ClassifierLLMConfig): ClassifierLLMConfig =>
system_prompt?.trim()
- ? { model, timeout_ms, system_prompt }
- : { model, timeout_ms, ...(classification_rubric && { classification_rubric }) };
+ ? { model, timeout_ms, ...(reasoning_effort && { reasoning_effort }), system_prompt }
+ : {
+ model,
+ timeout_ms,
+ ...(reasoning_effort && { reasoning_effort }),
+ ...(classification_rubric && { classification_rubric }),
+ };
interface ScorerKnobInputs {
classifierType: ClassifierType;
@@ -268,6 +275,20 @@ export const getClassifierModelError = (
: "Please select a classifier model, or switch back to Heuristic";
};
+export const getClassifierReasoningEffortError = (
+ config: Pick,
+ modelInfo: readonly ModelGroup[],
+): string | null => {
+ if (!usesLlmClassifier(effectiveClassifierType(config))) return null;
+ const classifierConfig = config.classifier_llm_config;
+ if (!classifierConfig?.model || !classifierConfig.reasoning_effort) return null;
+ const supported = modelInfo.find(
+ (model) => model.model_group === classifierConfig.model,
+ )?.supported_reasoning_efforts;
+ if (!Array.isArray(supported) || supported.includes(classifierConfig.reasoning_effort)) return null;
+ return `${classifierConfig.reasoning_effort} reasoning effort is not supported by every deployment in ${classifierConfig.model}. Choose Default or a supported value.`;
+};
+
export const getSemanticConfigError = ({
semanticMatchingEnabled,
embeddingModel,
@@ -295,11 +316,15 @@ export const customTierWireFields = (
tier_definitions: tierDefinitionsFromRows(rows),
...(fallback && { fallback_tier: activeTierName(fallback) }),
classifier_type: "llm",
- // Rebuilt from the two fields an edited tier set allows. The backend rejects system_prompt and
+ // Rebuilt from the fields an edited tier set allows. The backend rejects system_prompt and
// classification_rubric beside tier_definitions, and both live inside this object rather than at
// the top level the omit list covers. The opening instructions ride classification_prompt below.
...(classifierLlmConfig && {
- classifier_llm_config: { model: classifierLlmConfig.model, timeout_ms: classifierLlmConfig.timeout_ms },
+ classifier_llm_config: {
+ model: classifierLlmConfig.model,
+ timeout_ms: classifierLlmConfig.timeout_ms,
+ ...(classifierLlmConfig.reasoning_effort && { reasoning_effort: classifierLlmConfig.reasoning_effort }),
+ },
}),
session_affinity: false,
...(classificationPrompt?.trim() && { classification_prompt: classificationPrompt.trim() }),
diff --git a/ui/litellm-dashboard/src/components/add_model/complexity_router_tiers.ts b/ui/litellm-dashboard/src/components/add_model/complexity_router_tiers.ts
index 8cdb730cfa6..eb58c94b789 100644
--- a/ui/litellm-dashboard/src/components/add_model/complexity_router_tiers.ts
+++ b/ui/litellm-dashboard/src/components/add_model/complexity_router_tiers.ts
@@ -1,4 +1,5 @@
import type { ComplexityTier } from "./KeywordTierRules";
+import type { ModelGroup } from "@/components/llm_calls/fetch_models";
import { TIER_ORDER } from "./tier_rows";
export type TierModelParams = Record;
@@ -18,6 +19,23 @@ export const REASONING_EFFORT_OPTIONS = ["none", "minimal", "low", "medium", "hi
*/
export type ReasoningEffort = (typeof REASONING_EFFORT_OPTIONS)[number] | (string & {});
+export const tierEffortOptionsForModels = (modelInfo: ModelGroup[]): Record =>
+ Object.fromEntries(
+ modelInfo.map((model) => [
+ model.model_group,
+ model.supported_reasoning_efforts ?? (model.supports_reasoning ? [...REASONING_EFFORT_OPTIONS] : []),
+ ]),
+ );
+
+/**
+ * Stricter than the tier variant on purpose: classifier overrides are new in this release, so an
+ * unknown capability list stays unknown instead of inventing provider levels.
+ */
+export const classifierEffortOptionsForModels = (
+ modelInfo: ModelGroup[],
+): Record =>
+ Object.fromEntries(modelInfo.map((model) => [model.model_group, model.supported_reasoning_efforts]));
+
const asRecord = (raw: unknown): Record | undefined =>
typeof raw === "object" && raw !== null && !Array.isArray(raw) ? (raw as Record) : undefined;
diff --git a/ui/litellm-dashboard/src/components/edit_auto_router/build_updated_complexity_router_config.test.ts b/ui/litellm-dashboard/src/components/edit_auto_router/build_updated_complexity_router_config.test.ts
index 01198bcc118..f24f3033901 100644
--- a/ui/litellm-dashboard/src/components/edit_auto_router/build_updated_complexity_router_config.test.ts
+++ b/ui/litellm-dashboard/src/components/edit_auto_router/build_updated_complexity_router_config.test.ts
@@ -16,7 +16,7 @@ const storedCustomConfig = (overrides: Record = {}) => ({
],
fallback_tier: "CASUAL",
classifier_type: "llm",
- classifier_llm_config: { model: "gpt-4o-mini", timeout_ms: 3000 },
+ classifier_llm_config: { model: "gpt-4o-mini", timeout_ms: 3000, reasoning_effort: "low" },
...overrides,
});
@@ -111,7 +111,7 @@ describe("buildUpdatedComplexityRouterConfig keyword matching", () => {
const STORED_LLM = {
tiers: { SIMPLE: ["gpt-4o-mini"], MEDIUM: [], COMPLEX: [], REASONING: [] },
classifier_type: "llm",
- classifier_llm_config: { model: "gpt-4o-mini", timeout_ms: 3000 },
+ classifier_llm_config: { model: "gpt-4o-mini", timeout_ms: 3000, reasoning_effort: "low" },
classifier_context_window_size: 5,
classifier_context_per_turn_chars: 300,
};
@@ -522,7 +522,7 @@ describe("managed keys survive an untouched open-and-save", () => {
tier_labels: { SIMPLE: "Cheap" },
classifier_type: "heuristic_first",
heuristic_first_max_tier: "SIMPLE",
- classifier_llm_config: { model: "gpt-4o-mini", timeout_ms: 3000 },
+ classifier_llm_config: { model: "gpt-4o-mini", timeout_ms: 3000, reasoning_effort: "low" },
classifier_context_window_size: 5,
classifier_context_budget_chars: 4000,
classifier_context_include_assistant_turns: true,
diff --git a/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx b/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx
index 442e1ec6039..54766df93ec 100644
--- a/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx
+++ b/ui/litellm-dashboard/src/components/edit_auto_router/edit_auto_router_modal.tsx
@@ -28,6 +28,7 @@ import {
type BuildComplexityRouterConfigParams,
buildComplexityRouterConfig,
getClassifierModelError,
+ getClassifierReasoningEffortError,
getKeywordTierRulesError,
getMissingTiersError,
getSemanticConfigError,
@@ -560,6 +561,12 @@ const EditAutoRouterModal: React.FC = ({
toast.fromError(classifierError);
return;
}
+ const classifierEffortError = getClassifierReasoningEffortError(complexityRouterConfig, modelInfo);
+ if (classifierEffortError) {
+ setShowValidationErrors(true);
+ toast.fromError(classifierEffortError);
+ return;
+ }
// Same guards the create form applies (add_auto_router_tab.tsx). The backend rejects a
// keyword rule with no keyword, and semantic_keyword_matching without an embedding model
// or keyword rules (complexity_router/config.py), so without these a save fails as a raw
diff --git a/ui/litellm-dashboard/src/components/llm_calls/fetch_models.test.tsx b/ui/litellm-dashboard/src/components/llm_calls/fetch_models.test.tsx
index 998924ce36d..3cb15dc3144 100644
--- a/ui/litellm-dashboard/src/components/llm_calls/fetch_models.test.tsx
+++ b/ui/litellm-dashboard/src/components/llm_calls/fetch_models.test.tsx
@@ -52,6 +52,24 @@ describe("fetchAvailableModels", () => {
]);
});
+ it("preserves absent, unknown, empty, and explicit effort capability states", async () => {
+ modelHubCallMock.mockResolvedValue({
+ data: [
+ { model_group: "absent", supports_reasoning: true },
+ { model_group: "unknown", supports_reasoning: true, supported_reasoning_efforts: null },
+ { model_group: "empty", supports_reasoning: true, supported_reasoning_efforts: [] },
+ { model_group: "known", supports_reasoning: true, supported_reasoning_efforts: ["low"] },
+ ],
+ });
+
+ expect(await fetchAvailableModels("token")).toEqual([
+ { model_group: "absent", supports_reasoning: true },
+ { model_group: "empty", supports_reasoning: true, supported_reasoning_efforts: [] },
+ { model_group: "known", supports_reasoning: true, supported_reasoning_efforts: ["low"] },
+ { model_group: "unknown", supports_reasoning: true, supported_reasoning_efforts: null },
+ ]);
+ });
+
it.each([
["an error payload in place of the list", { data: { error: "no access" } }],
["a missing data key", {}],
diff --git a/ui/litellm-dashboard/src/components/llm_calls/fetch_models.tsx b/ui/litellm-dashboard/src/components/llm_calls/fetch_models.tsx
index 1f812cf0377..a25055703bf 100644
--- a/ui/litellm-dashboard/src/components/llm_calls/fetch_models.tsx
+++ b/ui/litellm-dashboard/src/components/llm_calls/fetch_models.tsx
@@ -7,7 +7,7 @@ export interface ModelGroup {
model_group: string;
mode?: string;
supports_reasoning?: boolean;
- supported_reasoning_efforts?: string[];
+ supported_reasoning_efforts?: string[] | null;
}
interface AvailableModel {
@@ -25,7 +25,9 @@ const toModelGroup = (item: AvailableModel): ModelGroup => {
model_group: groupName,
...(item.mode && { mode: item.mode }),
...(item.supports_reasoning === true && { supports_reasoning: true }),
- ...(item.supported_reasoning_efforts && { supported_reasoning_efforts: item.supported_reasoning_efforts }),
+ ...(item.supported_reasoning_efforts !== undefined && {
+ supported_reasoning_efforts: item.supported_reasoning_efforts,
+ }),
};
};
diff --git a/ui/litellm-dashboard/src/components/networking.test.ts b/ui/litellm-dashboard/src/components/networking.test.ts
index 71296bc1dfd..335a5f8b816 100644
--- a/ui/litellm-dashboard/src/components/networking.test.ts
+++ b/ui/litellm-dashboard/src/components/networking.test.ts
@@ -617,6 +617,15 @@ describe("buildModelGroupTestRequest", () => {
expect(path).toBe("/v1/embeddings");
expect(body).toEqual({ model: "text-embedding-3-small", input: "test from litellm" });
});
+
+ it("adds classifier request parameters to a chat probe", () => {
+ const { body } = Networking.buildModelGroupTestRequest("gpt-5-mini", "chat", { reasoning_effort: "low" });
+ expect(body).toEqual({
+ model: "gpt-5-mini",
+ messages: [{ role: "user", content: "test from litellm" }],
+ reasoning_effort: "low",
+ });
+ });
});
describe("testMCPToolsListRequest auth headers", () => {
diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx
index eaffa2b4802..a01d52fc865 100644
--- a/ui/litellm-dashboard/src/components/networking.tsx
+++ b/ui/litellm-dashboard/src/components/networking.tsx
@@ -2381,20 +2381,22 @@ export type ModelGroupConnectionResult = { status: "success" } | { status: "erro
export const buildModelGroupTestRequest = (
modelGroup: string,
mode: "chat" | "embedding",
+ requestParams: Record = {},
): { path: string; body: Record } =>
mode === "embedding"
? { path: "/v1/embeddings", body: { model: modelGroup, input: "test from litellm" } }
: {
path: "/v1/chat/completions",
- body: { model: modelGroup, messages: [{ role: "user", content: "test from litellm" }] },
+ body: { ...requestParams, model: modelGroup, messages: [{ role: "user", content: "test from litellm" }] },
};
export const testModelGroupConnection = async (
accessToken: string,
modelGroup: string,
mode: "chat" | "embedding",
+ requestParams?: Record,
): Promise => {
- const { path, body } = buildModelGroupTestRequest(modelGroup, mode);
+ const { path, body } = buildModelGroupTestRequest(modelGroup, mode, requestParams);
try {
await apiClient.post(path, { accessToken, body });
return { status: "success" };
diff --git a/ui/litellm-dashboard/src/components/organisms/create_key_button.integration.test.tsx b/ui/litellm-dashboard/src/components/organisms/create_key_button.integration.test.tsx
index 8630be8548f..045dcfa3ceb 100644
--- a/ui/litellm-dashboard/src/components/organisms/create_key_button.integration.test.tsx
+++ b/ui/litellm-dashboard/src/components/organisms/create_key_button.integration.test.tsx
@@ -750,6 +750,19 @@ describe("CreateKey", () => {
expect((await createdPayload()).organization_id).toBe("org-1");
});
+
+ it("drops organization_id when the chosen organization is cleared again", async () => {
+ state.organizations = [{ organization_id: "org-1", organization_alias: "Engineering" }];
+ await openModal();
+ await nameTheKey();
+
+ await userEvent.click(await screen.findByLabelText("Organization"));
+ await userEvent.click(await screen.findByRole("option", { name: /Engineering/ }));
+ await userEvent.click(await screen.findByRole("button", { name: "Clear" }));
+ await submit();
+
+ expect((await createdPayload()).organization_id).toBeUndefined();
+ });
});
describe("policy and prompt fields", () => {
diff --git a/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx b/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx
index d38a8995c1b..5749541dcea 100644
--- a/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx
+++ b/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx
@@ -588,7 +588,7 @@ const CreateKey: React.FC = ({ team, teams, data, addKey, autoOp
};
const changeOrganization = (write: FieldWrite) => (orgId: string) => {
- write(orgId);
+ write(orgId || undefined);
setSelectedOrganizationId(orgId || null);
// Clear team and project when org changes
setSelectedCreateKeyTeam(null);
diff --git a/ui/litellm-dashboard/src/components/templates/key_edit_view.test.tsx b/ui/litellm-dashboard/src/components/templates/key_edit_view.test.tsx
index 97b09e00808..10b1983b54b 100644
--- a/ui/litellm-dashboard/src/components/templates/key_edit_view.test.tsx
+++ b/ui/litellm-dashboard/src/components/templates/key_edit_view.test.tsx
@@ -1457,6 +1457,32 @@ describe("KeyEditView", () => {
expect(screen.getByLabelText("Organization")).toHaveValue("Engineering");
});
});
+
+ it("submits organization_id as null after the organization is cleared", async () => {
+ const onSubmit = vi.fn().mockResolvedValue(undefined);
+ renderWithProviders(
+ {}}
+ onSubmit={onSubmit}
+ accessToken=""
+ userID=""
+ userRole="Admin"
+ premiumUser={false}
+ />,
+ );
+
+ await waitFor(() => {
+ expect(screen.getByLabelText("Organization")).toHaveValue("Engineering");
+ });
+ await userEvent.click(screen.getByRole("button", { name: "Clear" }));
+ await userEvent.click(screen.getByRole("button", { name: /save changes/i }));
+
+ await waitFor(() => {
+ expect(onSubmit).toHaveBeenCalledWith(expect.objectContaining({ organization_id: null }));
+ });
+ expect(JSON.parse(JSON.stringify(onSubmit.mock.calls[0][0]))).toHaveProperty("organization_id", null);
+ });
});
describe("models dropdown team gating", () => {
diff --git a/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx b/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx
index df1af2ca8e9..3e772fd0e9b 100644
--- a/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx
+++ b/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx
@@ -303,8 +303,8 @@ export function KeyEditView({
}
};
- const handleOrganizationChange = (setField: (value: string | undefined) => void, orgId: string | undefined) => {
- setField(orgId);
+ const handleOrganizationChange = (setField: (value: string | null) => void, orgId: string | undefined) => {
+ setField(orgId || null);
setSelectedOrganizationId(orgId || null);
form.setValue("team_id", undefined);
};
diff --git a/ui/litellm-dashboard/src/components/view_logs/RequestLogsTableColumns.test.tsx b/ui/litellm-dashboard/src/components/view_logs/RequestLogsTableColumns.test.tsx
index ce59c62f1c4..e3bacc0908a 100644
--- a/ui/litellm-dashboard/src/components/view_logs/RequestLogsTableColumns.test.tsx
+++ b/ui/litellm-dashboard/src/components/view_logs/RequestLogsTableColumns.test.tsx
@@ -75,6 +75,68 @@ describe("Cost column", () => {
});
});
+describe("Type column", () => {
+ it("shows the conversation badge and composition even when an MCP call represents the conversation", async () => {
+ const user = userEvent.setup();
+ const mcpRepresentative = {
+ request_id: "req-mcp-rep",
+ call_type: "call_mcp_tool",
+ session_id: "sess-edge",
+ session_total_count: 3,
+ session_llm_count: 2,
+ mcp_tool_call_count: 1,
+ session_agent_count: 0,
+ };
+ renderRows([logEntry(mcpRepresentative)]);
+
+ expect(screen.queryByText("MCP")).not.toBeInTheDocument();
+ await user.hover(screen.getByText("3"));
+ expect(await screen.findByText("2 LLM • 1 MCP")).toBeInTheDocument();
+ });
+
+ it("keeps the plain MCP badge for a single MCP call", () => {
+ renderRows([logEntry({ request_id: "req-mcp-solo", call_type: "call_mcp_tool", session_total_count: 1 })]);
+
+ expect(screen.getByText("MCP")).toBeInTheDocument();
+ });
+});
+
+describe("Model column", () => {
+ it("lists every model used across a conversation, not only the representative call's model", () => {
+ const conversationCall: Partial = {
+ request_id: "req-session",
+ model: "gpt-5.6",
+ session_id: "sess-1",
+ session_total_count: 3,
+ session_models: ["claude-sonnet-5", "gpt-5.6"],
+ };
+ renderRows([logEntry(conversationCall)]);
+
+ expect(screen.getByText("claude-sonnet-5, gpt-5.6")).toBeInTheDocument();
+ expect(screen.queryByText("gpt-5.6")).not.toBeInTheDocument();
+ });
+
+ it("marks a conversation whose model list was capped by the server", () => {
+ const cappedCall = {
+ request_id: "req-capped",
+ model: "gpt-5.6",
+ session_id: "sess-2",
+ session_total_count: 30,
+ session_models: ["claude-sonnet-5", "gpt-5.6"],
+ session_models_truncated: true,
+ };
+ renderRows([logEntry(cappedCall)]);
+
+ expect(screen.getByText("claude-sonnet-5, gpt-5.6, ...")).toBeInTheDocument();
+ });
+
+ it("keeps a single call's own model", () => {
+ renderRows([logEntry({ request_id: "req-single", model: "gpt-5.6" })]);
+
+ expect(screen.getByText("gpt-5.6")).toBeInTheDocument();
+ });
+});
+
describe("row action cells", () => {
it("reports the key hash through the injected dependency rather than a row field", async () => {
const user = userEvent.setup();
diff --git a/ui/litellm-dashboard/src/components/view_logs/RequestLogsTableColumns.tsx b/ui/litellm-dashboard/src/components/view_logs/RequestLogsTableColumns.tsx
index b9058d02a6b..8db0b106851 100644
--- a/ui/litellm-dashboard/src/components/view_logs/RequestLogsTableColumns.tsx
+++ b/ui/litellm-dashboard/src/components/view_logs/RequestLogsTableColumns.tsx
@@ -63,9 +63,11 @@ export const getRequestLogsTableColumns = ({
const sessionAgentCount = log.session_agent_count ?? (isAgent ? sessionCount : 0);
const sessionMcpCount = log.mcp_tool_call_count ?? (isMcp ? sessionCount : 0);
- if (isMcp) return ;
- if (isAgent && sessionCount <= 1) return ;
- if (sessionCount <= 1) return ;
+ if (sessionCount <= 1) {
+ if (isMcp) return ;
+ if (isAgent) return ;
+ return ;
+ }
const sessionTypeBadge = (
@@ -224,10 +226,13 @@ export const getRequestLogsTableColumns = ({
cell: ({ row }) => {
const log = row.original;
const provider = log.custom_llm_provider;
- const modelName = log.model ?? "";
+ const sessionModels = log.session_models ?? [];
+ const modelNames = sessionModels.length > 0 ? sessionModels : [log.model ?? ""];
+ const modelLabel = log.session_models_truncated ? `${modelNames.join(", ")}, ...` : modelNames.join(", ");
+ const isSingleModel = modelNames.length === 1;
return (
- {provider && (
+ {provider && isSingleModel && (

)}
-
{modelName}} />
+
+ {modelLabel}
+
+ }
+ />
);
},
diff --git a/ui/litellm-dashboard/src/components/view_logs/columns.tsx b/ui/litellm-dashboard/src/components/view_logs/columns.tsx
index 2f3a3681352..21e09faf454 100644
--- a/ui/litellm-dashboard/src/components/view_logs/columns.tsx
+++ b/ui/litellm-dashboard/src/components/view_logs/columns.tsx
@@ -47,4 +47,6 @@ export type LogEntry = {
mcp_tool_call_spend?: number;
session_llm_count?: number;
session_agent_count?: number;
+ session_models?: string[];
+ session_models_truncated?: boolean;
};
diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts
index b0f8e618645..3a4e013b684 100644
--- a/ui/litellm-dashboard/src/lib/http/schema.d.ts
+++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts
@@ -25211,6 +25211,11 @@ export interface components {
* @description Model name (from the router's model_list) to call for classification
*/
model: string;
+ /**
+ * Reasoning Effort
+ * @description Reasoning effort override for classifier calls. Leave unset to use the classifier deployment or provider default.
+ */
+ reasoning_effort?: ("none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max") | null;
/**
* System Prompt
* @description Replaces the built-in complexity rubric as the classifier's entire system role. When set, neither the default rubric nor the context-window closing line is appended, so the prompt owns the whole taxonomy and the tier names SIMPLE/MEDIUM/COMPLEX/REASONING become whatever buckets it defines: a prompt that classifies data sensitivity routes on that instead of on difficulty. Two consequences of full replacement. The default rubric's closing paragraph is the classifier's prompt-injection defense, telling it that the caller's quoted system prompt and prior turns are material to judge and never instructions; a replacement that omits it lets a caller ask for a tier and get it. And the heuristic fallback still scores complexity, so a router on some other taxonomy wants classifier_fallback='default_model'. Leave unset for the built-in rubric. Only applies when classifier_type is 'llm'.
diff --git a/uv.lock b/uv.lock
index aa59ff7b229..de3181edd5a 100644
--- a/uv.lock
+++ b/uv.lock
@@ -10,7 +10,7 @@ resolution-markers = [
]
[options]
-exclude-newer = "2026-08-30T17:51:25.171404Z"
+exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values.
exclude-newer-span = "P3D"
[manifest]
@@ -2373,14 +2373,14 @@ wheels = [
[[package]]
name = "gitpython"
-version = "3.1.58"
+version = "3.1.61"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "gitdb" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/26/d6/5f358ff283325580c2003a6d953aea18cfe10ae87b46f5ebc80fa3a386dc/gitpython-3.1.58.tar.gz", hash = "sha256:621416df10ef3fd0e19fabf9172ddeed0fa704d353d04f194eec56a625a95b22", size = 228498, upload-time = "2026-08-04T15:05:49.47Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/6f/61/3285044215fb596bf093e39ccb96ece0a1076a8ca57a61e069a6a33cdb1b/gitpython-3.1.61.tar.gz", hash = "sha256:f51c24d8c0f733a195447385f5774a5dfe8767f5acfd7994a33755644c6ecc95", size = 231680, upload-time = "2026-08-28T11:01:13.761Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ec/0c/9d8752098bc442f0726e64aa6135940b3a96809915d1aa4206c1bb97881d/gitpython-3.1.58-py3-none-any.whl", hash = "sha256:d331e722577f0fd7fc1f857419b3ecc07af66282b933d2a4d95f84a042fdd50f", size = 220183, upload-time = "2026-08-04T15:05:48.025Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/5e/49cc172da4d0578644ba37cec5cb365b1fefc603b26edea9bcac1c7f830a/gitpython-3.1.61-py3-none-any.whl", hash = "sha256:8ab28c9da863cdd9e7d7694ec46cf3e6c9a12d8a30a1acd3447aec11975d530c", size = 222118, upload-time = "2026-08-28T11:01:12.262Z" },
]
[[package]]
@@ -3373,6 +3373,98 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" },
]
+[[package]]
+name = "hypothesis"
+version = "6.165.10"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
+ { name = "sortedcontainers" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5c/e2/0fad246d2b6330e1f78479bfc566b5c22be82aee8a865cde9a08f648487d/hypothesis-6.165.10.tar.gz", hash = "sha256:68b45e09834cd80523cb1eb274463073c7a9af4e4ef7cff34d9615f355572d32", size = 503703, upload-time = "2026-08-16T22:56:15.404Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/05/c1/9a9538e6d185baf5cc7f15bc3b76e08efbb3de4b3c782f234356449c0dd7/hypothesis-6.165.10-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f839d29d0cc12048cf073d88ca4fdf94d420bc2b8afd69641ff6d496422ccd4f", size = 783243, upload-time = "2026-08-16T22:55:44.058Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/30/b70d9d79e871a75cbdeccd9067f20ecdb9eb2a1dfa03c630be3ad13b8b30/hypothesis-6.165.10-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:e10858f57ed0e74baa04393845f469fe8ad502c16ece4499bef7700c575611bd", size = 778815, upload-time = "2026-08-16T22:55:46.948Z" },
+ { url = "https://files.pythonhosted.org/packages/db/52/6f0a9b7aab24b0635e2238f3fbddea5b54b17879ac813df42a3cc3384c5c/hypothesis-6.165.10-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76a7be86d986223b9f1bdb7e7cbcdb048649901fdb956c598ef73bdab1786cd5", size = 1108009, upload-time = "2026-08-16T22:54:53.082Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/06/8d0d4e11ff02350d09ec9f9e90af354158e59e16a8907ba5199a4ff2d7e8/hypothesis-6.165.10-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:717aea574e0e5edba2868aa66b1caae335d8f1ad3fb29f01dd6502953fa823a1", size = 1136596, upload-time = "2026-08-16T22:54:54.443Z" },
+ { url = "https://files.pythonhosted.org/packages/59/dd/01a1e440f2e38dc1ccf5d597af5b8a0bee5f21b674c99c123b5554de9690/hypothesis-6.165.10-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4334058033e0214475f019e15492a50f3854fe8728cf51fe25c6191a2c3f8e52", size = 1135234, upload-time = "2026-08-16T22:55:08.911Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/18/8a26c24d3d9db20265f39df341ab265858c094e209571e3179cf237935f4/hypothesis-6.165.10-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2abb50cf1cf77d721de0a24c3f99d9c4ffdeb2cbd1e12aebb5a7a93e2b6b6d1f", size = 1157528, upload-time = "2026-08-16T22:56:02.159Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/8e/ce3c829b1937402d7944420ca26a05a0c8563e894dcff03d34ffa279d306/hypothesis-6.165.10-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:3de69aa8b924b400291a3cc42aaf78e6ab65c905a3e7e1a5dc39d95ef1b428cb", size = 1112870, upload-time = "2026-08-16T22:54:55.919Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/1b/4c4926d6c9a2b5d7cc090cc1e91219d6796102aa2a2c4b8f961c939e60b5/hypothesis-6.165.10-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5841331c504e02d7c334591681cb8587cdd59dee7e149db6d3db8e3f9e9f02eb", size = 1149683, upload-time = "2026-08-16T22:55:30.567Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/f9/df24eb28412f82465e2b7707f0ff1ec274d580bce389d4d9156617dc7bba/hypothesis-6.165.10-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2d0e0f8263d34dd8fa3b39eaa9a50bba56a8470b3dd9ebf6672d10840abe063e", size = 1283402, upload-time = "2026-08-16T22:54:18.054Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/07/c2b2a761300cf60b90ccebba4328175331e67d34f4fbd39429a7ddcdce49/hypothesis-6.165.10-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:0c4e6869817c3cfdf5a2b4d348497b95159bdecb3365be732c9b8570e36a4eef", size = 1409948, upload-time = "2026-08-16T22:54:22.343Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/ec/1c2bf1acdd0e273d81f833f85caf0ae5423db68a783554992fca36e6c541/hypothesis-6.165.10-cp310-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:9f07ae36c3b093e13687a894e79fe69e98a94c0b67fef656c575247682218143", size = 1265023, upload-time = "2026-08-16T22:54:41.402Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/a8/7f984908b7391160c7801b84e51ca8e4ba88c89e8d8811aa1aa7c03de73c/hypothesis-6.165.10-cp310-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:aff1f584c9538e8979cd180b1d70bf99bc16be19d4666414f49e5942b21a4f2c", size = 1282698, upload-time = "2026-08-16T22:56:06.998Z" },
+ { url = "https://files.pythonhosted.org/packages/48/78/3a5d91c2d0250521736c42dfa2402b75049bc5fe2fb716c10bc84bb91ed1/hypothesis-6.165.10-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1f2c4db25fb8ec1a16a8dba580666337b8ffb1887c4cf1750cc954313897cef7", size = 1324816, upload-time = "2026-08-16T22:54:46.675Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/99/27450763853a034bca1574d3e0a315164b33ff49c3862df6872dda45e25e/hypothesis-6.165.10-cp310-abi3-win32.whl", hash = "sha256:b33dc30170a7402e03c180f2c5ef69dc077152f35b91621e9cebcde9c7d71746", size = 669039, upload-time = "2026-08-16T22:55:11.962Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/fc/ff2988b72b5705ad9ca500444bf3f43e3c2f41edfa034bbfeb23b215791a/hypothesis-6.165.10-cp310-abi3-win_amd64.whl", hash = "sha256:e9f924aa610c0618445e1e8738c822c3190ce2a2699a0cb48ec3a351a96761f2", size = 675213, upload-time = "2026-08-16T22:55:01.697Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/8b/821810d36f78d9d9421cd2c5d9d36983b45bb3575c3086276cc5c76f9f73/hypothesis-6.165.10-cp310-abi3-win_arm64.whl", hash = "sha256:1d305448e9bd8e2f4f3cea0eafd809efdaab4e998a0019bc615650c8463e42f1", size = 673537, upload-time = "2026-08-16T22:54:47.898Z" },
+ { url = "https://files.pythonhosted.org/packages/26/61/5e89268ce03317fb9f82449a1b3efd9e599dee090288fd0cf7586c532fb1/hypothesis-6.165.10-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:73e6df02a6a62f8045b511c272f894d08e56d174504c793c9effcbc6778051a8", size = 783959, upload-time = "2026-08-16T22:55:29.078Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/e9/f4e0832e81bb53b70cf1712e28c867db64245b32595b594217452e7dbd8d/hypothesis-6.165.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8b20f44773a9ab84400465e318712d8c2ca16418d35b9f80aa27fdf2d690ad10", size = 779684, upload-time = "2026-08-16T22:54:57.698Z" },
+ { url = "https://files.pythonhosted.org/packages/77/de/ea072d3359d5678771bed407f80439e8ac7ca905d1031b0372f61bf5746e/hypothesis-6.165.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb8c7d05ea27a093a92b250904095d71d924b6b44e5795a415c1b20c265f0c65", size = 1108540, upload-time = "2026-08-16T22:55:03.282Z" },
+ { url = "https://files.pythonhosted.org/packages/28/56/e7c395cdaa3d6c28b944c1c3c516dee50d2b7b3aeafa31874b57009ca51f/hypothesis-6.165.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4dafd6d6ababfa3b14dd6e5f0378cb7c7d291895a31a40abcbb7cc74f396131", size = 1158089, upload-time = "2026-08-16T22:54:36.205Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/49/1c6d2c465b9c5fc3213f1be89be95ba53819ca0130248c484129ccfefb71/hypothesis-6.165.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fa74636a49fc8077413ce8db3e85f1c4aff880788bb55bda56253118e036fe5b", size = 1284125, upload-time = "2026-08-16T22:54:37.727Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/bc/7caf5ac3d0173bd57bd2a5ab854ca49a3664a4309257be1452f81025cc24/hypothesis-6.165.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2b112768cfb67f2b683e53e58c1a33d27811aacf60c942b8eb74635e469a73f6", size = 1325082, upload-time = "2026-08-16T22:54:38.952Z" },
+ { url = "https://files.pythonhosted.org/packages/70/99/9d844330f570d6a4f127a683eab1e78c8263e6e72b16189f3534fa6bf6de/hypothesis-6.165.10-cp310-cp310-win_amd64.whl", hash = "sha256:56cb8c9055e50545fe6e3e5a560ec25a724673b2e4051f3c24d44e3ebc35dd72", size = 675082, upload-time = "2026-08-16T22:54:29.872Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/c2/b9546ace11f241c9c02d389f258cb80c14447a8c885771c9f1f0bc1d85ca/hypothesis-6.165.10-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:592107a0faf6c9c3a63a8dbf13dfb1cbda1cf599b0bc11c953221b00204b9ce1", size = 783716, upload-time = "2026-08-16T22:55:36.624Z" },
+ { url = "https://files.pythonhosted.org/packages/37/10/27c2fdd574fd798caf5e91eb51f7834b098f5d840ce733efb3fba79ef86e/hypothesis-6.165.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9180c362bde06fd05380298ded4e234fbc0d6ede0a864835bfd91c1e24283d5", size = 779507, upload-time = "2026-08-16T22:55:07.633Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/b6/70bc23695f3783c4b0486b6cad47b08a20f791db4a3c1b25250add9659fa/hypothesis-6.165.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d623801ae3dcd97b77b983400ef3d48bf976648e4efff19929175322eaae074d", size = 1108406, upload-time = "2026-08-16T22:55:39.653Z" },
+ { url = "https://files.pythonhosted.org/packages/71/4c/32e200bd7a352af4b7f4e3729aaa4cd002cb5fe8c4c6aef5599d0019f152/hypothesis-6.165.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20f6236cfb90b7817bb1a6a087589ca4aa46d73170f0dd62963952ed5dadc589", size = 1157850, upload-time = "2026-08-16T22:55:24.394Z" },
+ { url = "https://files.pythonhosted.org/packages/03/a5/8efc2a9a484822efc0d0da466f50094e0f2c068187faaf33831fc905873e/hypothesis-6.165.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad0764730e8e3421601c2cc7e1f054a9206c60ea0917165d8d9193dc453f34f1", size = 1283704, upload-time = "2026-08-16T22:54:27.279Z" },
+ { url = "https://files.pythonhosted.org/packages/46/2a/90cc8d7463929c04786f29600de45f3227c12fa9bed1d5b7ce319b05e1c9/hypothesis-6.165.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:10d9a650a4666b0914831f769703d36140ed8039fd19bf9b71f615b8541eccf2", size = 1325077, upload-time = "2026-08-16T22:55:16.561Z" },
+ { url = "https://files.pythonhosted.org/packages/82/ac/bc16faba4b42883e3d290bfaceff51e258b63fbbdf789bf9fe88df1ce537/hypothesis-6.165.10-cp311-cp311-win_amd64.whl", hash = "sha256:5671d2b2bf83bd4b6f02e55b32d432506eff5358c82f39b460a849ce19a2666e", size = 674920, upload-time = "2026-08-16T22:55:42.613Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/45/cde4f78afe2b9e29caecf38319eedc1deb76aebcacbdd128e03cbb2511c3/hypothesis-6.165.10-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:637445c1593a2a9d1024fda50082f07bb56baedda78d90a25f64b8111727ef94", size = 784835, upload-time = "2026-08-16T22:54:45.429Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/81/847f30b81cbfd07607296b3ce43067cf4f80799bd9244167f587de9c8081/hypothesis-6.165.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:713f4ce4e82c26b53031f139de959bc9e8b54d3995aa824b89bbdf8229df2a45", size = 776419, upload-time = "2026-08-16T22:55:33.633Z" },
+ { url = "https://files.pythonhosted.org/packages/04/66/4c71c5be7a49d84b8c3a9278c1807c4c81181ab5474beb27df9d4c40dc0e/hypothesis-6.165.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9ff356e97e3ab09db07c8b675efa67340103874a0bae7465acb83dad7a35f7f", size = 1106830, upload-time = "2026-08-16T22:55:10.389Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/c4/e2cbd2810e79f7a452a8ea9f6c6438ee718ce938d8cc12252cf0b36a81d3/hypothesis-6.165.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a380bc99aa3b035e6a95a2201bf792d4082a04ca75babcc21849c2d0914bb28", size = 1156952, upload-time = "2026-08-16T22:55:53.35Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/8b/794ced36864825492ac3712d5acab5a257b4601e6a9dc2ccdd3937198f87/hypothesis-6.165.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9acb2c4d9cb532c3fedea74159f7b923c8c036328c9239b4049e7aa073bdd81", size = 1280780, upload-time = "2026-08-16T22:54:34.983Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/2d/550525442cdbcc2daf1f9bdd8ba35bcbde63db7c7a22f2ef137fbb49df2f/hypothesis-6.165.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8660572b2d424bf5369ea8990985225f70bd1615b76ecd9c25588a3b9307009f", size = 1324130, upload-time = "2026-08-16T22:55:48.659Z" },
+ { url = "https://files.pythonhosted.org/packages/74/59/6caf69dd5fe03499ada94c9cec016bffcc164511c6b93fe680f01209b9ff/hypothesis-6.165.10-cp312-cp312-win_amd64.whl", hash = "sha256:3376f2594763aef14faa519b0fb27cae7ce9eeaab4c69efa07777499110306c9", size = 672337, upload-time = "2026-08-16T22:54:49.11Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/fb/c82c5bd92864ffcf319772fedc8c9bf2dbe4ca14baa0fee6e49e67b5ba1c/hypothesis-6.165.10-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:9d77c3be7b429875036ad0f0597c6e5cc6bb17894a4da005e3807de64d2673ad", size = 784726, upload-time = "2026-08-16T22:54:32.371Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/b9/3d7acd08506da85557e65147b7f3fca8c47684e33be90bee0acb523920db/hypothesis-6.165.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:490c56b830772b0eca3b4b2cecb3741a1ed26b1d7206a279e1525dbf0aa95ee4", size = 776375, upload-time = "2026-08-16T22:55:13.303Z" },
+ { url = "https://files.pythonhosted.org/packages/38/6b/922e8b3f9a706dd89d440b9545d2c6231c65e74da1c1fee3ff36c251b9c4/hypothesis-6.165.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed68e27b8a61e57a3ccdc7c5a14499e00b54dfe223087204d5d40b3b5ef58b6d", size = 1106763, upload-time = "2026-08-16T22:55:06.129Z" },
+ { url = "https://files.pythonhosted.org/packages/01/39/f5b9a5d390d4edd1ad472334493ac442963ebeb4daaa74ff4bdac6ef292f/hypothesis-6.165.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6caadcd1afb62630ff5c5ff353626eaa616553a5971295ad6dc2b19ca8a39620", size = 1156778, upload-time = "2026-08-16T22:54:33.824Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/5f/5fbe1be4326337fd6acefe2d18ed44007ee1dc1f98fe5b3c0eb22942364d/hypothesis-6.165.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9145fe43ebb22e66672967c3fab411793b226ed776e4fe282271bca6ad3c0bb", size = 1280756, upload-time = "2026-08-16T22:55:54.834Z" },
+ { url = "https://files.pythonhosted.org/packages/25/c0/cf6f9e1ef632a1a75694eed0db3a02e6fc75c367a363e94acee52f043c64/hypothesis-6.165.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79900a9920a0b1d3a626c03a90ac6bf7042e78d46906a565b86a0dbe926f1d96", size = 1323889, upload-time = "2026-08-16T22:55:56.567Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/cc/662b94880f260b0a88de1fdcf60fc9984f6e2a796da549542adc10a7bc83/hypothesis-6.165.10-cp313-cp313-win_amd64.whl", hash = "sha256:c01dd04044c472e47193b54f68e84e08d6ebf4f29551885aa959b015f7cd9747", size = 672346, upload-time = "2026-08-16T22:56:03.792Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/77/55e020c9c576532ff7d20bf8b1dfa052ecbd5ada1949b02f76c44c966f7e/hypothesis-6.165.10-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9ccac776b2ca93b324806facd526ccb45da0fd035001c899a35b02c44431e209", size = 784833, upload-time = "2026-08-16T22:55:21.255Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/f2/01da2adf829cf549eaddcabb8e8072077fb3d26da4275f4c1e89b2c0af74/hypothesis-6.165.10-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e5f95f7b622e4171096d92175dda0a560f0955ade9b8a3a07bdcf151f7359611", size = 776545, upload-time = "2026-08-16T22:56:10.159Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/8e/58d4f842895220b793c53fc94a6489705b3665bb4d0ae4d338ce03fdf9fb/hypothesis-6.165.10-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f76d1562643693b8a40066f1f96af795b93fd9bcfc9690a1af2ff4c5867ee29e", size = 1107271, upload-time = "2026-08-16T22:54:50.266Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/b8/206468912d2153306bb8a41afdfc59e45b7a73a0495bbe4b9cb4f0e79c1d/hypothesis-6.165.10-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60cab3ab4ea468d31a33739ffd7e94ec3e37dea891d65a6582ecc8a477175191", size = 1156915, upload-time = "2026-08-16T22:54:25.89Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/d3/bf5a22929b70a4cfd3edf69c5642b029b27ddb5cfda48fa295d384b01abb/hypothesis-6.165.10-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:22cf19388f0ff6ced8eb3e49c903d14938e4ed909d93bf28383eef451511e424", size = 1281205, upload-time = "2026-08-16T22:54:44.083Z" },
+ { url = "https://files.pythonhosted.org/packages/07/a2/d7b2ba444d36fc84d4779f4431e74dd9b023dc63bcf282199f6e48ad39f4/hypothesis-6.165.10-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:057d0232f1224dcd0b7698902551a4341a7399f90670b036db6c4376715fe889", size = 1324243, upload-time = "2026-08-16T22:55:41.123Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/95/afe6b531fd01928c6f63d394ee413fa2338d088b2b44efcc23596b54477e/hypothesis-6.165.10-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:ab0f2e9d7d7d4db257f7cf53de3706c2baf124269571f20ffc2bcd6781f03063", size = 616382, upload-time = "2026-08-16T22:55:18.449Z" },
+ { url = "https://files.pythonhosted.org/packages/48/86/9b4fb75f520a028edec50ffc904a94d724180395d71feb6d7a0ce7bb6f00/hypothesis-6.165.10-cp314-cp314-win_amd64.whl", hash = "sha256:d1ea02fa8ab3d33eb1125eade81f7136341eb429152c6dbe2ae6f8bc33b3fbdd", size = 672145, upload-time = "2026-08-16T22:54:24.831Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/ba/f7bbaae0c789bab7ddb764d2056ee1a463cc95a8acbccc90d4184e48b242/hypothesis-6.165.10-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ed1a5891e59472884a03cb9875483e8fc131c80a275c60967f8afc5458a0c8ff", size = 783287, upload-time = "2026-08-16T22:54:23.751Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/83/01ef80772b4abd335c49405576dc503cede94fb5da30ba2643a119013aea/hypothesis-6.165.10-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:09772e328a26e50486ac572be34f9887f9aa185efe7ebb16bde4e8f6038db1f4", size = 774991, upload-time = "2026-08-16T22:55:25.987Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/0b/f47506241f9d5a5a2efe4c65b6bf4830e9d9576e5d3779007a260699e608/hypothesis-6.165.10-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cf3b612542ba174c9da4000b59a4f4c81e8d66f87509be85d3a1b71b5c36413", size = 1105499, upload-time = "2026-08-16T22:54:51.864Z" },
+ { url = "https://files.pythonhosted.org/packages/84/fe/abb3909b7089835112fbe75bf00d817d733b3a8032759783db0a24ff1e56/hypothesis-6.165.10-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f69ec5be85ef508e206153bed8eafd03f7995dc464356c8bbb279a1e2b7d56f3", size = 1155685, upload-time = "2026-08-16T22:54:30.94Z" },
+ { url = "https://files.pythonhosted.org/packages/73/2f/1964738921640184067121ae77414522fc3f0463fc26c6e25a4f3b8e42ca/hypothesis-6.165.10-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:dd207497bb985918409a1bb5db85d1875f74e1269487332113b73d1ee7c77647", size = 1279177, upload-time = "2026-08-16T22:54:40.179Z" },
+ { url = "https://files.pythonhosted.org/packages/34/c5/312af8ae038d3af9cf3f7f1021c1abfe31c0d9035e4cf63519e0a7dc983e/hypothesis-6.165.10-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:00de0abdcf8c05c9d0eab735a3c49a276376b55151e6fcb903c2b39a90e5e5c3", size = 1322921, upload-time = "2026-08-16T22:54:42.7Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/e7/b0a2fde7570c090a1b914026266a421c751ef10138fffe37fe0ef9e675c0/hypothesis-6.165.10-cp314-cp314t-win_amd64.whl", hash = "sha256:cc2da5aa4edf14743fa9257e5ba3513963999f01211635702479d8e92b8207c8", size = 672147, upload-time = "2026-08-16T22:55:27.527Z" },
+ { url = "https://files.pythonhosted.org/packages/47/fd/985aa564d6ffd06483d45a62b40d319df0a703cd8bc1d041de17d102fbaa/hypothesis-6.165.10-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:eeab73050ea58c13dd56e329f594c1dfe32ebd7bb169bbdf4f8ceefbc31ec6b5", size = 782882, upload-time = "2026-08-16T22:55:37.93Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/2c/6cc11151e450f72353a490940cd0db704680d07b78dc75dcc9f480e0d0e1/hypothesis-6.165.10-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:4c68e983d0007d014bb01ad4bcbba78bc432c73a1755ff36d5102ceefa18299a", size = 774584, upload-time = "2026-08-16T22:55:51.822Z" },
+ { url = "https://files.pythonhosted.org/packages/10/39/ef26fa79c1738dfe9cdb1a3584fb6717d26429ca6c9d011cc4fdf08130c2/hypothesis-6.165.10-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7730d8197086f65d8969a991d6728a1d420a51b19fea06535c896cb43a1e05d0", size = 1104876, upload-time = "2026-08-16T22:54:58.937Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/f4/3fcc84e7637f42bf00d987093b9418083ac8db81b87392608a60f4b7c5fd/hypothesis-6.165.10-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7a7980a898a3e6ebe4de1896a0507e3d519edb53fb9b4bda478c9fbeb6514558", size = 1133353, upload-time = "2026-08-16T22:54:28.635Z" },
+ { url = "https://files.pythonhosted.org/packages/35/59/21c5c14179c38f8d0de3560e7f1825c083311b3013b63f817d7dc78dfcbd/hypothesis-6.165.10-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5820d009aedb7ae9cfd32f98b1ab0c0bbd6268379c4fab042218b6b655c63f8", size = 1132300, upload-time = "2026-08-16T22:56:08.539Z" },
+ { url = "https://files.pythonhosted.org/packages/14/af/fbb56059961e416b2de7b9dc5352db2e8572bd5ea46892957e4c1e5548ab/hypothesis-6.165.10-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37a7ac3d34220800e1107871cc391bca1b00439875925d7d821878b8b791f245", size = 1155175, upload-time = "2026-08-16T22:55:19.824Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/53/77fb0c2dad445858555429c4e06cf94a59ae8d2407dd6426b5af97c84828/hypothesis-6.165.10-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:dafa7c9dbe3d802f9bcdf261b29c8a70700fb22839947f06e471f62c46b6257f", size = 1109881, upload-time = "2026-08-16T22:55:32.029Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/7b/d187f673ff30e6ada640953636f978ffe64a6332f756b64163c2277f8d0c/hypothesis-6.165.10-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:90915635b9648071129b0f72c0673cf8eac9eb84cfd445c5bedef30c714b1ec2", size = 1144963, upload-time = "2026-08-16T22:56:13.428Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/60/31d504e364134d60af23e5f6365db0da3cf4a51b3ed3d4836e5a2cff12cf/hypothesis-6.165.10-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:e1bbeb7c506b07ee0422cf9b2f7212fefa4240957f03526d38d27bc6743a0a48", size = 1278684, upload-time = "2026-08-16T22:55:22.971Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/e6/89d26834a08c02f8da149e541dd40d7a96f68d9722f43146e69a77436ed7/hypothesis-6.165.10-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:2b36aaffc88625a44f91074c5bbedfdefb9b376c38d1b3c342edcd2e4c8ed16c", size = 1407202, upload-time = "2026-08-16T22:55:14.949Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/61/20d1e72246867ea195440092e8bb422c7ddc2f271b87b5b65679d5532719/hypothesis-6.165.10-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:18a3ea838ddea183388f8788750afa8494d79abb5358823be9782585f34445d3", size = 1261395, upload-time = "2026-08-16T22:56:05.448Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/9b/ebab6c3c2b90a16abb4119198178652d12aff83cc8ec2cfde5276c69fb1e/hypothesis-6.165.10-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:2a2567b3a03a4a5a7c575c191cfcce321a967df3727803817e75bffbbeaecabe", size = 1279213, upload-time = "2026-08-16T22:55:35.066Z" },
+ { url = "https://files.pythonhosted.org/packages/23/78/69b219b524231d36eb20c792e1f01e7cb037e02bd0af1c29f77ed9a969c0/hypothesis-6.165.10-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:8001925fa3dde51cb574e4c9de4c7efe77c4e4d64bd2fd2ef61d5651f9d04f3d", size = 1322367, upload-time = "2026-08-16T22:54:21.279Z" },
+ { url = "https://files.pythonhosted.org/packages/55/63/ad5cc153dcc72ae5e7905fb9b3585f3e48ce892a2d6366f90163e867a69d/hypothesis-6.165.10-cp315-abi3.abi3t-win32.whl", hash = "sha256:c6559380469295c4009215fe1cab561301591a3bee2e2fb3f4f96d2273a3affc", size = 666038, upload-time = "2026-08-16T22:56:11.797Z" },
+ { url = "https://files.pythonhosted.org/packages/80/32/b62307b73fbc99f0a4381d6f9456df76fbcbb7a27ef7256e26f0376f48ea/hypothesis-6.165.10-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:30797f20ca45e57f526d2df872f63ba453cb4e1091ad542184a7a951af8da79d", size = 671941, upload-time = "2026-08-16T22:55:00.235Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/dd/e0f98add0548ef73ea7afac45da1fb8efc854d7f9931db568754d0f963f3/hypothesis-6.165.10-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:c53e9b1c36350df9965ec44d6c0d4e0bbbb38f720dd2b0e1256dc6524d411015", size = 669931, upload-time = "2026-08-16T22:55:50.205Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/6a/880d6eeed5c451fb40a66733dadec4a5d498628a4a7f6a8a5f633f4c6dcb/hypothesis-6.165.10-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:34ee6402df6f31274d89119f1561b5f7489c97866afc5b7a3ed3a13d7e762802", size = 784644, upload-time = "2026-08-16T22:54:20.127Z" },
+ { url = "https://files.pythonhosted.org/packages/27/e0/9e942bd3c3cf5ea0d5c0fd0905893bbfb6cefb7284c70fcc8033f8fdec38/hypothesis-6.165.10-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:277f41801e88dad2eba082f91a75632b7584ff64044ba2cf9dadf511b0d19cd0", size = 780515, upload-time = "2026-08-16T22:55:04.676Z" },
+ { url = "https://files.pythonhosted.org/packages/19/32/f11a618415dc5fa9cdde41fea56c489f0814759527ae1ecd11a75a4558b9/hypothesis-6.165.10-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72df95fb1db41755b155c5f02106e0036a339250555c8d351d488704fd112cf9", size = 1109374, upload-time = "2026-08-16T22:56:00.241Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/6f/db49b719842297c2b71e0d81e5b8967d31215fb7389421abcb465ce7ed3f/hypothesis-6.165.10-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e20a02775eb3cf0ffb4f0219b6d7c1f240336663d4e5d7028675ec247c790c4", size = 1159092, upload-time = "2026-08-16T22:55:58.57Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/2a/bf0bae84ba1cb3923d295973f1fe38ee867eaf90119e0d559116083be300/hypothesis-6.165.10-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1ec53f08732e3cfd0342cbbd75dbd1b193c8f19390660466e536a748bb81f757", size = 676045, upload-time = "2026-08-16T22:55:45.514Z" },
+]
+
[[package]]
name = "idna"
version = "3.15"
@@ -4430,6 +4522,7 @@ dev = [
{ name = "diff-cover" },
{ name = "fakeredis" },
{ name = "fastapi-offline" },
+ { name = "hypothesis" },
{ name = "keyring" },
{ name = "langfuse" },
{ name = "openapi-core" },
@@ -4449,6 +4542,7 @@ dev = [
{ name = "pytest-rerunfailures" },
{ name = "pytest-timeout" },
{ name = "pytest-xdist" },
+ { name = "reportlab" },
{ name = "requests-mock" },
{ name = "responses" },
{ name = "respx" },
@@ -4615,6 +4709,7 @@ dev = [
{ name = "diff-cover", specifier = "==9.7.2" },
{ name = "fakeredis", specifier = "==2.34.1" },
{ name = "fastapi-offline", specifier = "==1.7.6" },
+ { name = "hypothesis", specifier = "==6.165.10" },
{ name = "keyring", specifier = "==25.7.0" },
{ name = "langfuse", specifier = "==2.59.7" },
{ name = "openapi-core", specifier = "==0.22.0" },
@@ -4634,6 +4729,7 @@ dev = [
{ name = "pytest-rerunfailures", specifier = "==15.1" },
{ name = "pytest-timeout", specifier = "==2.4.0" },
{ name = "pytest-xdist", specifier = "==3.8.0" },
+ { name = "reportlab", specifier = "==5.0.1" },
{ name = "requests-mock", specifier = "==1.12.1" },
{ name = "responses", specifier = "==0.26.0" },
{ name = "respx", specifier = "==0.22.0" },
@@ -8189,6 +8285,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/da/80/35b4c33c804a165a7f55289afda3ea9e3eb6d15800341a2d66455c0f1f30/regex-2026.5.9-cp314-cp314t-win_arm64.whl", hash = "sha256:5e41809d2683fcde7d5a8c87a6567ba1fb1ce0de9f31bff578de00a4b2d76daa", size = 275713, upload-time = "2026-05-09T23:15:16.98Z" },
]
+[[package]]
+name = "reportlab"
+version = "5.0.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "charset-normalizer" },
+ { name = "pillow" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/4a/51/dbe28534ae12c852f61be91f039f343305fd1f34f1c66b8de75afae7a525/reportlab-5.0.1.tar.gz", hash = "sha256:ebd13154be1c8515e665de70bd2d303ae9ddc3ef47e44afd5116441ca0283a26", size = 3945711, upload-time = "2026-08-20T13:48:16.461Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/db/cb/dacbc268cb68d0428ea2cbd85266195a9ab3e677449589ddae59bd7542ac/reportlab-5.0.1-py3-none-any.whl", hash = "sha256:1c36e6bb0e71780c72331eba60da7f602e8d4389a8723825af71342e49d791e8", size = 1957258, upload-time = "2026-08-20T13:48:14.026Z" },
+]
+
[[package]]
name = "requests"
version = "2.34.0"