mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-18 06:26:16 +00:00
Merge pull request #14740 from BerriAI/litellm_dev_09_19_2025_p2
UI - Add team-level sampling setting for tracing Langsmith
This commit is contained in:
@@ -46,7 +46,7 @@ const asset_logos_folder = '/ui/assets/logos/';
|
||||
interface CallbackInfo {
|
||||
logo: string;
|
||||
supports_key_team_logging: boolean;
|
||||
dynamic_params: Record<string, "text" | "password" | "select" | "upload">;
|
||||
dynamic_params: Record<string, "text" | "password" | "select" | "upload" | "number">;
|
||||
description: string | null;
|
||||
}
|
||||
|
||||
@@ -86,7 +86,8 @@ export const callbackInfo: Record<string, CallbackInfo> = {
|
||||
dynamic_params: {
|
||||
"langsmith_api_key": "password",
|
||||
"langsmith_project": "text",
|
||||
"langsmith_base_url": "text"
|
||||
"langsmith_base_url": "text",
|
||||
"langsmith_sampling_rate": "number"
|
||||
},
|
||||
description: "Langsmith Logging Integration"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
import React from 'react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { renderWithProviders, screen, fireEvent } from "../../../tests/test-utils";
|
||||
import LoggingSettings from './LoggingSettings';
|
||||
|
||||
describe('LoggingSettings', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('passes a number to updateCallbackVar when user inputs a number in NumericalInput', async () => {
|
||||
const user = userEvent.setup();
|
||||
const mockOnChange = vi.fn();
|
||||
|
||||
// Create initial config with a callback that has number parameters (LangSmith has langsmith_sampling_rate)
|
||||
const initialValue = [
|
||||
{
|
||||
callback_name: 'langsmith',
|
||||
callback_type: 'success',
|
||||
callback_vars: {}
|
||||
}
|
||||
];
|
||||
|
||||
renderWithProviders(
|
||||
<LoggingSettings
|
||||
value={initialValue}
|
||||
onChange={mockOnChange}
|
||||
/>
|
||||
);
|
||||
|
||||
// Find the numerical input for langsmith_sampling_rate
|
||||
const numericalInput = screen.getByPlaceholderText('os.environ/LANGSMITH_SAMPLING_RATE');
|
||||
expect(numericalInput).toBeInTheDocument();
|
||||
|
||||
// Use fireEvent.change to directly set the value (more reliable for number inputs)
|
||||
fireEvent.change(numericalInput, { target: { value: '0.75' } });
|
||||
|
||||
// Verify that onChange was called
|
||||
expect(mockOnChange).toHaveBeenCalled();
|
||||
|
||||
// Get the last call to onChange
|
||||
const lastCall = mockOnChange.mock.calls[mockOnChange.mock.calls.length - 1];
|
||||
const updatedConfig = lastCall[0];
|
||||
|
||||
// Verify the structure and that the value is stored as a string (as expected by the component)
|
||||
expect(updatedConfig).toHaveLength(1);
|
||||
expect(updatedConfig[0].callback_vars.langsmith_sampling_rate).toBe('0.75');
|
||||
});
|
||||
|
||||
it('displays number type indicator and validation hint for number parameters', () => {
|
||||
const initialValue = [
|
||||
{
|
||||
callback_name: 'langsmith',
|
||||
callback_type: 'success',
|
||||
callback_vars: {}
|
||||
}
|
||||
];
|
||||
|
||||
renderWithProviders(
|
||||
<LoggingSettings
|
||||
value={initialValue}
|
||||
onChange={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
// Check for the "Number" badge
|
||||
expect(screen.getByText('Number')).toBeInTheDocument();
|
||||
|
||||
// Check for the validation hint
|
||||
expect(screen.getByText('Value must be between 0 and 1')).toBeInTheDocument();
|
||||
|
||||
// Check that the input has the correct step attribute
|
||||
const numericalInput = screen.getByPlaceholderText('os.environ/LANGSMITH_SAMPLING_RATE');
|
||||
expect(numericalInput).toHaveAttribute('step', '0.01');
|
||||
});
|
||||
|
||||
it('handles number input and text input independently', async () => {
|
||||
const mockOnChange = vi.fn();
|
||||
|
||||
// Start with some existing values to simulate a more realistic scenario
|
||||
const initialValue = [
|
||||
{
|
||||
callback_name: 'langsmith',
|
||||
callback_type: 'success',
|
||||
callback_vars: {
|
||||
langsmith_sampling_rate: '0.3',
|
||||
langsmith_api_key: 'initial-key'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
renderWithProviders(
|
||||
<LoggingSettings
|
||||
value={initialValue}
|
||||
onChange={mockOnChange}
|
||||
/>
|
||||
);
|
||||
|
||||
// Find both number and text inputs
|
||||
const numericalInput = screen.getByPlaceholderText('os.environ/LANGSMITH_SAMPLING_RATE');
|
||||
const textInput = screen.getByPlaceholderText('os.environ/LANGSMITH_API_KEY');
|
||||
|
||||
// Verify initial values are displayed
|
||||
expect(numericalInput).toHaveValue(0.3); // NumberInput shows numeric value
|
||||
expect(textInput).toHaveValue('initial-key');
|
||||
|
||||
// Change the numerical input
|
||||
fireEvent.change(numericalInput, { target: { value: '0.5' } });
|
||||
|
||||
// Verify numerical input change was recorded and preserves other values
|
||||
expect(mockOnChange).toHaveBeenCalled();
|
||||
let lastCall = mockOnChange.mock.calls[mockOnChange.mock.calls.length - 1];
|
||||
let updatedConfig = lastCall[0];
|
||||
expect(updatedConfig[0].callback_vars.langsmith_sampling_rate).toBe('0.5');
|
||||
expect(updatedConfig[0].callback_vars.langsmith_api_key).toBe('initial-key'); // Should preserve existing value
|
||||
|
||||
// Change the text input (this tests that text inputs work independently)
|
||||
fireEvent.change(textInput, { target: { value: 'test-api-key' } });
|
||||
|
||||
// Verify text input change was also recorded
|
||||
lastCall = mockOnChange.mock.calls[mockOnChange.mock.calls.length - 1];
|
||||
updatedConfig = lastCall[0];
|
||||
expect(updatedConfig[0].callback_vars.langsmith_api_key).toBe('test-api-key');
|
||||
// The component preserves the original initial value since we're starting from initial state each time
|
||||
expect(updatedConfig[0].callback_vars.langsmith_sampling_rate).toBe('0.3'); // Preserves initial value
|
||||
});
|
||||
|
||||
it('correctly handles numerical input with decimal values', () => {
|
||||
const mockOnChange = vi.fn();
|
||||
|
||||
const initialValue = [
|
||||
{
|
||||
callback_name: 'langsmith',
|
||||
callback_type: 'success',
|
||||
callback_vars: {}
|
||||
}
|
||||
];
|
||||
|
||||
renderWithProviders(
|
||||
<LoggingSettings
|
||||
value={initialValue}
|
||||
onChange={mockOnChange}
|
||||
/>
|
||||
);
|
||||
|
||||
const numericalInput = screen.getByPlaceholderText('os.environ/LANGSMITH_SAMPLING_RATE');
|
||||
|
||||
// Test various decimal values
|
||||
const testValues = ['0.1', '0.25', '0.5', '0.75', '1.0'];
|
||||
|
||||
testValues.forEach(value => {
|
||||
fireEvent.change(numericalInput, { target: { value } });
|
||||
|
||||
const lastCall = mockOnChange.mock.calls[mockOnChange.mock.calls.length - 1];
|
||||
const updatedConfig = lastCall[0];
|
||||
expect(updatedConfig[0].callback_vars.langsmith_sampling_rate).toBe(value);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,7 @@ import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { Button, Card, TextInput } from '@tremor/react';
|
||||
import { PlusIcon, TrashIcon, CogIcon, BanIcon } from '@heroicons/react/outline';
|
||||
import { callbackInfo, Callbacks, callback_map, mapDisplayToInternalNames } from '../callback_info_helpers';
|
||||
import NumericalInput from '../shared/numerical_input';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@@ -126,13 +127,33 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({
|
||||
Sensitive
|
||||
</span>
|
||||
)}
|
||||
{paramType === 'number' && (
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-yellow-100 text-yellow-800">
|
||||
Number
|
||||
</span>
|
||||
)}
|
||||
</label>
|
||||
<TextInput
|
||||
type={paramType === 'password' ? 'password' : 'text'}
|
||||
placeholder={`os.environ/${paramName.toUpperCase()}`}
|
||||
value={config.callback_vars[paramName] || ''}
|
||||
onChange={(e) => updateCallbackVar(configIndex, paramName, e.target.value)}
|
||||
/>
|
||||
{paramType === 'number' && (
|
||||
<span className="text-xs text-gray-500">
|
||||
Value must be between 0 and 1
|
||||
</span>
|
||||
)}
|
||||
{paramType === 'number' ? (
|
||||
<NumericalInput
|
||||
step={0.01}
|
||||
width={400}
|
||||
placeholder={`os.environ/${paramName.toUpperCase()}`}
|
||||
value={config.callback_vars[paramName] || ''}
|
||||
onChange={(e: any) => updateCallbackVar(configIndex, paramName, e.target.value)}
|
||||
/>
|
||||
) : (
|
||||
<TextInput
|
||||
type={paramType === 'password' ? 'password' : 'text'}
|
||||
placeholder={`os.environ/${paramName.toUpperCase()}`}
|
||||
value={config.callback_vars[paramName] || ''}
|
||||
onChange={(e) => updateCallbackVar(configIndex, paramName, e.target.value)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user