Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion experiments/endpoint_configs/config_template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,21 @@ model_config_4o_openai: &client_4o_openai
model: gpt-4o-2024-08-06
max_retries: 5

# Anthropic Claude configuration example
model_config_claude_anthropic: &client_claude_anthropic
provider: autogen_ext.models.anthropic.AnthropicChatCompletionClient
config:
model: claude-4-sonnet-20251114
# api_key: <YOUR ANTHROPIC API KEY> # Uncomment and add your API key
max_retries: 5

orchestrator_client: *client_4o_openai
coder_client: *client_4o_openai
web_surfer_client: *client_4o_openai
file_surfer_client: *client_4o_openai
action_guard_client: *client_4o_openai
user_proxy_client: *client_4o_openai
model_client: *client_4o_openai
model_client: *client_4o_openai

# To use Anthropic, uncomment and replace one or more of the above with:
# orchestrator_client: *client_claude_anthropic
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import {
OpenAIModelConfigForm,
AzureModelConfigForm,
OllamaModelConfigForm,
AnthropicModelConfigForm,
} from "./modelConfigForms";
import { ModelConfig, ModelConfigFormProps } from "./modelConfigForms/types";

// Import the default configs from each form
import { DEFAULT_OPENAI } from "./modelConfigForms/OpenAIModelConfigForm";
import { DEFAULT_AZURE } from "./modelConfigForms/AzureModelConfigForm";
import { DEFAULT_OLLAMA } from "./modelConfigForms/OllamaModelConfigForm";
import { DEFAULT_ANTHROPIC } from "./modelConfigForms/AnthropicModelConfigForm";

interface ModelSelectorProps {
onChange: (m: ModelConfig) => void;
Expand All @@ -20,7 +22,8 @@ interface ModelSelectorProps {
export const PROVIDERS = {
openai: DEFAULT_OPENAI.provider,
azure: DEFAULT_AZURE.provider,
ollama: DEFAULT_OLLAMA.provider
ollama: DEFAULT_OLLAMA.provider,
anthropic: DEFAULT_ANTHROPIC.provider
}

// Map each model value to its config form, label, and initial config value
Expand Down Expand Up @@ -107,6 +110,41 @@ export const PROVIDER_FORM_MAP: Record<string, { label: string, defaultValue: Mo
form: OllamaModelConfigForm,
presets: { [DEFAULT_OLLAMA.config.model]: { ...DEFAULT_OLLAMA } }
},
[DEFAULT_ANTHROPIC.provider]: {
label: "Anthropic",
defaultValue: { ...DEFAULT_ANTHROPIC },
form: AnthropicModelConfigForm,
presets: {
"claude-4-sonnet-20251114": {
...DEFAULT_ANTHROPIC,
config: {
...DEFAULT_ANTHROPIC.config,
model: "claude-4-sonnet-20251114"
}
},
"claude-3-5-sonnet-20241022": {
...DEFAULT_ANTHROPIC,
config: {
...DEFAULT_ANTHROPIC.config,
model: "claude-3-5-sonnet-20241022"
}
},
"claude-3-5-haiku-20241022": {
...DEFAULT_ANTHROPIC,
config: {
...DEFAULT_ANTHROPIC.config,
model: "claude-3-5-haiku-20241022"
}
},
"claude-3-opus-20240229": {
...DEFAULT_ANTHROPIC,
config: {
...DEFAULT_ANTHROPIC.config,
model: "claude-3-opus-20240229"
}
}
}
},
};

const ModelSelector: React.FC<ModelSelectorProps> = ({ onChange, value }) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React, { useEffect } from "react";
import { Input, Form, Button, Switch, Flex, Collapse } from "antd";
import { ModelConfigFormProps, AnthropicModelConfig } from "./types";

export const DEFAULT_ANTHROPIC: AnthropicModelConfig = {
provider: "autogen_ext.models.anthropic.AnthropicChatCompletionClient",
config: {
model: "claude-4-sonnet-20251114",
api_key: null,
base_url: null,
max_retries: 5,
}
};

const ADVANCED_DEFAULTS = {
vision: true,
function_calling: true,
json_output: false,
family: "claude-4-sonnet" as const,
structured_output: false,
multiple_system_messages: false,
};

function normalizeConfig(config: any, hideAdvancedToggles?: boolean) {
const newConfig = { ...DEFAULT_ANTHROPIC, ...config };
if (hideAdvancedToggles) {
if (newConfig.config.model_info) delete newConfig.config.model_info;
} else {
newConfig.config.model_info = {
...ADVANCED_DEFAULTS,
...(newConfig.config.model_info || {})
};
}
return newConfig;
}


export const AnthropicModelConfigForm: React.FC<ModelConfigFormProps> = ({ onChange, onSubmit, value, hideAdvancedToggles }) => {
const [form] = Form.useForm();

const handleValuesChange = (_: any, allValues: any) => {
const mergedConfig = { ...DEFAULT_ANTHROPIC.config, ...allValues.config };
const normalizedConfig = normalizeConfig(mergedConfig, hideAdvancedToggles);
const newValue = { ...DEFAULT_ANTHROPIC, config: normalizedConfig };
if (onChange) onChange(newValue);
};
const handleSubmit = () => {
const mergedConfig = { ...DEFAULT_ANTHROPIC.config, ...form.getFieldsValue().config };
const normalizedConfig = normalizeConfig(mergedConfig, hideAdvancedToggles);
const newValue = { ...DEFAULT_ANTHROPIC, config: normalizedConfig };
if (onSubmit) onSubmit(newValue);
};


useEffect(() => {
if (value) {
form.setFieldsValue(normalizeConfig(value, hideAdvancedToggles))
}
}, [value, form]);

return (
<Form
form={form}
initialValues={normalizeConfig(value, hideAdvancedToggles)}
onFinish={handleSubmit}
onValuesChange={handleValuesChange}
layout="vertical"
>
<Flex vertical gap="small">
<Form.Item label="Model" name={["config", "model"]} rules={[{ required: true, message: "Please enter the model name" }]}>
<Input placeholder="claude-4-sonnet-20251114" />
</Form.Item>
<Collapse style={{ width: "100%" }}>
<Collapse.Panel key="1" header="Optional Properties">
<Form.Item label="API Key" name={["config", "api_key"]} rules={[{ required: false, message: "Please enter your Anthropic API key" }]}>
<Input.Password placeholder="Your Anthropic API key" />
</Form.Item>
<Form.Item label="Base URL" name={["config", "base_url"]} rules={[{ required: false, message: "Please enter your Base URL" }]}>
<Input placeholder="https://api.anthropic.com" />
</Form.Item>
<Form.Item label="Max Retries" name={["config", "max_retries"]} rules={[{ type: "number", min: 1, max: 20, message: "Enter a value between 1 and 20" }]}>
<Input type="number" />
</Form.Item>
{!hideAdvancedToggles && (
<Flex gap="small" wrap justify="space-between">
<Form.Item label="Vision" name={["config", "model_info", "vision"]} valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item label="Function Calling" name={["config", "model_info", "function_calling"]} valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item label="JSON Output" name={["config", "model_info", "json_output"]} valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item label="Structured Output" name={["config", "model_info", "structured_output"]} valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item label="Multiple System Messages" name={["config", "model_info", "multiple_system_messages"]} valuePropName="checked">
<Switch />
</Form.Item>
</Flex>
)}
</Collapse.Panel>
</Collapse>
{onSubmit && <Button onClick={handleSubmit}>Save</Button>}
</Flex>
</Form>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/**
* @jest-environment jsdom
*/

import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import { AnthropicModelConfigForm, DEFAULT_ANTHROPIC } from '../AnthropicModelConfigForm';
import { AnthropicModelConfig } from '../types';

// Mock antd components
jest.mock('antd', () => ({
Input: ({ placeholder, onChange, value, ...props }: any) => (
<input
placeholder={placeholder}
onChange={(e) => onChange?.(e.target.value)}
value={value}
{...props}
/>
),
Form: {
useForm: () => [{ setFieldsValue: jest.fn(), getFieldsValue: () => ({}) }],
Item: ({ children, label }: any) => (
<div>
<label>{label}</label>
{children}
</div>
),
},
Button: ({ children, onClick }: any) => (
<button onClick={onClick}>{children}</button>
),
Switch: ({ checked, onChange }: any) => (
<input
type="checkbox"
checked={checked}
onChange={(e) => onChange?.(e.target.checked)}
/>
),
Flex: ({ children }: any) => <div>{children}</div>,
Collapse: {
Panel: ({ children }: any) => <div>{children}</div>,
},
}));

describe('AnthropicModelConfigForm', () => {
const mockOnChange = jest.fn();
const mockOnSubmit = jest.fn();

beforeEach(() => {
jest.clearAllMocks();
});

it('renders with default Anthropic configuration', () => {
render(<AnthropicModelConfigForm onChange={mockOnChange} />);

expect(screen.getByDisplayValue('claude-4-sonnet-20251114')).toBeInTheDocument();
expect(screen.getByLabelText('Model')).toBeInTheDocument();
expect(screen.getByLabelText('API Key')).toBeInTheDocument();
expect(screen.getByLabelText('Base URL')).toBeInTheDocument();
expect(screen.getByLabelText('Max Retries')).toBeInTheDocument();
});

it('displays correct default values', () => {
expect(DEFAULT_ANTHROPIC.provider).toBe('autogen_ext.models.anthropic.AnthropicChatCompletionClient');
expect(DEFAULT_ANTHROPIC.config.model).toBe('claude-4-sonnet-20251114');
expect(DEFAULT_ANTHROPIC.config.max_retries).toBe(5);
});

it('shows advanced toggles when hideAdvancedToggles is false', () => {
render(<AnthropicModelConfigForm onChange={mockOnChange} hideAdvancedToggles={false} />);

expect(screen.getByLabelText('Vision')).toBeInTheDocument();
expect(screen.getByLabelText('Function Calling')).toBeInTheDocument();
expect(screen.getByLabelText('JSON Output')).toBeInTheDocument();
expect(screen.getByLabelText('Structured Output')).toBeInTheDocument();
expect(screen.getByLabelText('Multiple System Messages')).toBeInTheDocument();
});

it('hides advanced toggles when hideAdvancedToggles is true', () => {
render(<AnthropicModelConfigForm onChange={mockOnChange} hideAdvancedToggles={true} />);

expect(screen.queryByLabelText('Vision')).not.toBeInTheDocument();
expect(screen.queryByLabelText('Function Calling')).not.toBeInTheDocument();
expect(screen.queryByLabelText('JSON Output')).not.toBeInTheDocument();
});

it('calls onChange when model value changes', async () => {
render(<AnthropicModelConfigForm onChange={mockOnChange} />);

const modelInput = screen.getByLabelText('Model');
fireEvent.change(modelInput, { target: { value: 'claude-3-5-sonnet-20241022' } });

await waitFor(() => {
expect(mockOnChange).toHaveBeenCalled();
});
});

it('calls onChange when API key changes', async () => {
render(<AnthropicModelConfigForm onChange={mockOnChange} />);

const apiKeyInput = screen.getByLabelText('API Key');
fireEvent.change(apiKeyInput, { target: { value: 'sk-ant-test-key' } });

await waitFor(() => {
expect(mockOnChange).toHaveBeenCalled();
});
});

it('shows Save button when onSubmit prop is provided', () => {
render(<AnthropicModelConfigForm onChange={mockOnChange} onSubmit={mockOnSubmit} />);

expect(screen.getByText('Save')).toBeInTheDocument();
});

it('does not show Save button when onSubmit prop is not provided', () => {
render(<AnthropicModelConfigForm onChange={mockOnChange} />);

expect(screen.queryByText('Save')).not.toBeInTheDocument();
});

it('calls onSubmit when Save button is clicked', () => {
render(<AnthropicModelConfigForm onChange={mockOnChange} onSubmit={mockOnSubmit} />);

const saveButton = screen.getByText('Save');
fireEvent.click(saveButton);

expect(mockOnSubmit).toHaveBeenCalled();
});

it('renders with provided value prop', () => {
const testValue: AnthropicModelConfig = {
provider: 'autogen_ext.models.anthropic.AnthropicChatCompletionClient',
config: {
model: 'claude-3-opus-20240229',
api_key: 'test-key',
base_url: 'https://custom-endpoint.com',
max_retries: 3,
}
};

render(<AnthropicModelConfigForm onChange={mockOnChange} value={testValue} />);

expect(screen.getByDisplayValue('claude-3-opus-20240229')).toBeInTheDocument();
expect(screen.getByDisplayValue('test-key')).toBeInTheDocument();
expect(screen.getByDisplayValue('https://custom-endpoint.com')).toBeInTheDocument();
expect(screen.getByDisplayValue('3')).toBeInTheDocument();
});

it('has correct placeholder text', () => {
render(<AnthropicModelConfigForm onChange={mockOnChange} />);

expect(screen.getByPlaceholderText('claude-4-sonnet-20251114')).toBeInTheDocument();
expect(screen.getByPlaceholderText('Your Anthropic API key')).toBeInTheDocument();
expect(screen.getByPlaceholderText('https://api.anthropic.com')).toBeInTheDocument();
});

it('sets correct model family in advanced defaults', () => {
const { container } = render(
<AnthropicModelConfigForm onChange={mockOnChange} hideAdvancedToggles={false} />
);

// The model family should be set to claude-4-sonnet by default
// This is tested indirectly through the form structure
expect(container).toBeInTheDocument();
});
});
Loading
Loading