Add advisor model to anthropic

This commit is contained in:
Saoud Rizwan 2025-01-17 21:29:27 -08:00
parent 43bf383784
commit 7aeab15ecf
4 changed files with 85 additions and 29 deletions

View file

@ -1,6 +1,14 @@
import { Anthropic } from "@anthropic-ai/sdk"
import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming"
import { anthropicDefaultModelId, AnthropicModelId, anthropicModels, ApiHandlerOptions, ModelInfo } from "../../shared/api"
import {
anthropicDefaultAdvisorModelId,
anthropicDefaultModelId,
AnthropicModelId,
anthropicModels,
ApiHandlerOptions,
ModelInfo,
ModelType,
} from "../../shared/api"
import { ApiHandler } from "../index"
import { ApiStream } from "../transform/stream"
@ -16,9 +24,10 @@ export class AnthropicHandler implements ApiHandler {
})
}
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[], modelType: ModelType): ApiStream {
const model = modelType === "advisor" ? this.getAdvisorModel() : this.getModel()
let stream: AnthropicStream<Anthropic.Beta.PromptCaching.Messages.RawPromptCachingBetaMessageStreamEvent>
const modelId = this.getModel().id
const modelId = model.id
switch (modelId) {
// 'latest' alias does not support cache_control
case "claude-3-5-sonnet-20241022":
@ -37,7 +46,7 @@ export class AnthropicHandler implements ApiHandler {
stream = await this.client.beta.promptCaching.messages.create(
{
model: modelId,
max_tokens: this.getModel().info.maxTokens || 8192,
max_tokens: model.info.maxTokens || 8192,
temperature: 0,
system: [
{
@ -104,7 +113,7 @@ export class AnthropicHandler implements ApiHandler {
default: {
stream = (await this.client.messages.create({
model: modelId,
max_tokens: this.getModel().info.maxTokens || 8192,
max_tokens: model.info.maxTokens || 8192,
temperature: 0,
system: [{ text: systemPrompt, type: "text" }],
messages,
@ -185,4 +194,16 @@ export class AnthropicHandler implements ApiHandler {
info: anthropicModels[anthropicDefaultModelId],
}
}
getAdvisorModel(): { id: string; info: ModelInfo } {
const modelId = this.options.anthropicAdvisorModelId
if (modelId && modelId in anthropicModels) {
const id = modelId as AnthropicModelId
return { id, info: anthropicModels[id] }
}
return {
id: anthropicDefaultAdvisorModelId,
info: anthropicModels[anthropicDefaultAdvisorModelId],
}
}
}

View file

@ -45,6 +45,7 @@ type SecretKey =
type GlobalStateKey =
| "apiProvider"
| "apiModelId"
| "anthropicAdvisorModelId"
| "awsRegion"
| "awsUseCrossRegionInference"
| "vertexProjectId"
@ -382,6 +383,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
const {
apiProvider,
apiModelId,
anthropicAdvisorModelId,
apiKey,
openRouterApiKey,
awsAccessKey,
@ -411,6 +413,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
} = message.apiConfiguration
await this.updateGlobalState("apiProvider", apiProvider)
await this.updateGlobalState("apiModelId", apiModelId)
await this.updateGlobalState("anthropicAdvisorModelId", anthropicAdvisorModelId)
await this.storeSecret("apiKey", apiKey)
await this.storeSecret("openRouterApiKey", openRouterApiKey)
await this.storeSecret("awsAccessKey", awsAccessKey)
@ -1019,6 +1022,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
const [
storedApiProvider,
apiModelId,
anthropicAdvisorModelId,
apiKey,
openRouterApiKey,
awsAccessKey,
@ -1053,6 +1057,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
] = await Promise.all([
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
this.getGlobalState("apiModelId") as Promise<string | undefined>,
this.getGlobalState("anthropicAdvisorModelId") as Promise<string | undefined>,
this.getSecret("apiKey") as Promise<string | undefined>,
this.getSecret("openRouterApiKey") as Promise<string | undefined>,
this.getSecret("awsAccessKey") as Promise<string | undefined>,
@ -1104,6 +1109,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
apiConfiguration: {
apiProvider,
apiModelId,
anthropicAdvisorModelId,
apiKey,
openRouterApiKey,
awsAccessKey,

View file

@ -14,6 +14,7 @@ export type ApiProvider =
export interface ApiHandlerOptions {
apiModelId?: string
apiKey?: string // anthropic
anthropicAdvisorModelId?: string
anthropicBaseUrl?: string
openRouterApiKey?: string
openRouterModelId?: string
@ -60,10 +61,13 @@ export interface ModelInfo {
description?: string
}
export type ModelType = "base" | "advisor"
// Anthropic
// https://docs.anthropic.com/en/docs/about-claude/models // prices updated 2025-01-02
export type AnthropicModelId = keyof typeof anthropicModels
export const anthropicDefaultModelId: AnthropicModelId = "claude-3-5-sonnet-20241022"
export const anthropicDefaultAdvisorModelId: AnthropicModelId = "claude-3-opus-20240229"
export const anthropicModels = {
"claude-3-5-sonnet-20241022": {
maxTokens: 8192,
@ -192,7 +196,6 @@ export const openRouterDefaultAdvisorModelInfo: ModelInfo = {
description:
"The latest and strongest model family from OpenAI, o1 is designed to spend more time thinking before responding.\n\nThe o1 models are optimized for math, science, programming, and other STEM-related tasks. They consistently exhibit PhD-level accuracy on benchmarks in physics, chemistry, and biology. Learn more in the [launch announcement](https://openai.com/o1).\n\nNote: This model is currently experimental and not suitable for production use-cases, and may be heavily rate-limited.",
}
export type ModelType = "base" | "advisor"
// Vertex AI
// https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude

View file

@ -13,6 +13,8 @@ import {
ApiConfiguration,
ApiProvider,
ModelInfo,
ModelType,
anthropicDefaultAdvisorModelId,
anthropicDefaultModelId,
anthropicModels,
azureOpenAiDefaultApiVersion,
@ -39,6 +41,7 @@ import { useExtensionState } from "../../context/ExtensionStateContext"
import { vscode } from "../../utils/vscode"
import VSCodeButtonLink from "../common/VSCodeButtonLink"
import OpenRouterModelPicker, { ModelDescriptionMarkdown, OPENROUTER_MODEL_PICKER_Z_INDEX } from "./OpenRouterModelPicker"
import styled from "styled-components"
interface ApiOptionsProps {
showModelOptions: boolean
@ -52,6 +55,21 @@ const TabPanel = ({ children, isSelected }: { children: React.ReactNode; isSelec
return <div style={{ marginTop: 10 }}>{children}</div>
}
const StyledTabButton = styled.button<{ isSelected: boolean }>`
background: transparent;
border: none;
padding: 8px 16px;
color: ${(props) => (props.isSelected ? "var(--vscode-tab-activeForeground)" : "var(--vscode-tab-inactiveForeground)")};
cursor: pointer;
border-bottom: 2px solid ${(props) => (props.isSelected ? "var(--vscode-foreground)" : "transparent")};
font-size: 12px;
font-weight: 500;
&:hover {
color: var(--vscode-tab-activeForeground);
}
`
const TabButton = ({
isSelected,
onClick,
@ -62,20 +80,9 @@ const TabButton = ({
children: React.ReactNode
}) => {
return (
<button
onClick={onClick}
style={{
background: "var(--vscode-tab-inactiveBackground)",
border: "none",
padding: "8px 16px",
color: isSelected ? "var(--vscode-tab-activeForeground)" : "var(--vscode-tab-inactiveForeground)",
cursor: "pointer",
borderBottom: `2px solid ${isSelected ? "var(--vscode-foreground)" : "transparent"}`,
fontSize: "12px",
fontWeight: 500,
}}>
<StyledTabButton isSelected={isSelected} onClick={onClick}>
{children}
</button>
</StyledTabButton>
)
}
@ -95,7 +102,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, ad
})
}
const { selectedProvider, selectedModelId, selectedModelInfo } = useMemo(() => {
const { selectedProvider, selectedModelId, selectedModelInfo, selectedAdvisorModelId } = useMemo(() => {
return normalizeApiConfiguration(apiConfiguration)
}, [apiConfiguration])
@ -138,12 +145,16 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, ad
As a workaround, we create separate instances of the dropdown for each provider, and then conditionally render the one that matches the current provider.
*/
const createDropdown = (models: Record<string, ModelInfo>) => {
const createDropdown = (models: Record<string, ModelInfo>, modelType?: ModelType) => {
return (
<VSCodeDropdown
id="model-id"
value={selectedModelId}
onChange={handleInputChange("apiModelId")}
// right now anthropic is the only non-openrouter provider that supports advisor models.
// if anthropic then selectedAdvisorId will always have value
value={modelType === "advisor" ? selectedAdvisorModelId || selectedModelId : selectedModelId}
onChange={
modelType === "advisor" ? handleInputChange("anthropicAdvisorModelId") : handleInputChange("apiModelId")
}
style={{ width: "100%" }}>
<VSCodeOption value="">Select a model...</VSCodeOption>
{Object.keys(models).map((modelId) => (
@ -751,6 +762,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, ad
)}
{selectedProvider !== "openrouter" &&
selectedProvider !== "anthropic" &&
selectedProvider !== "openai" &&
selectedProvider !== "ollama" &&
selectedProvider !== "lmstudio" &&
@ -760,7 +772,6 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, ad
<label htmlFor="model-id">
<span style={{ fontWeight: 500 }}>Model</span>
</label>
{selectedProvider === "anthropic" && createDropdown(anthropicModels)}
{selectedProvider === "bedrock" && createDropdown(bedrockModels)}
{selectedProvider === "vertex" && createDropdown(vertexModels)}
{selectedProvider === "gemini" && createDropdown(geminiModels)}
@ -778,7 +789,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, ad
</>
)}
{selectedProvider !== "openrouter" && modelIdErrorMessage && (
{selectedProvider !== "openrouter" && selectedProvider !== "anthropic" && modelIdErrorMessage && (
<p
style={{
margin: "-10px 0 4px 0",
@ -789,7 +800,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, ad
</p>
)}
{selectedProvider === "openrouter" && showModelOptions && (
{(selectedProvider === "openrouter" || selectedProvider === "anthropic") && showModelOptions && (
<div style={{ marginTop: -5 }}>
<div style={{ display: "flex", borderBottom: "1px solid var(--vscode-panel-border)" }}>
<TabButton isSelected={selectedTab === "base"} onClick={() => setSelectedTab("base")}>
@ -810,7 +821,12 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, ad
This is the default driver model for Cline. It will read and edit files, run commands, and more, with
your permission at each step.
</p>
<OpenRouterModelPicker modelType="base" key="base-model-picker" />
{selectedProvider === "anthropic" && (
<div className="dropdown-container" style={{ marginBottom: 15 }}>
{createDropdown(anthropicModels, "base")}
</div>
)}
{selectedProvider === "openrouter" && <OpenRouterModelPicker modelType="base" key="base-model-picker" />}
{modelIdErrorMessage && (
<p
style={{
@ -833,7 +849,14 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, ad
The Cline model can call this smarter, more powerful model to ask for help on planning out a task,
fixing a hard bug, and other complex problems.
</p>
<OpenRouterModelPicker modelType="advisor" key="advisor-model-picker" />
{selectedProvider === "anthropic" && (
<div className="dropdown-container" style={{ marginBottom: 15 }}>
{createDropdown(anthropicModels, "advisor")}
</div>
)}
{selectedProvider === "openrouter" && (
<OpenRouterModelPicker modelType="advisor" key="advisor-model-picker" />
)}
{advisorModelIdErrorMessage && (
<p
style={{
@ -1017,7 +1040,10 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration):
}
switch (provider) {
case "anthropic":
return getProviderData(anthropicModels, anthropicDefaultModelId)
return {
...getProviderData(anthropicModels, anthropicDefaultModelId),
selectedAdvisorModelId: apiConfiguration?.anthropicAdvisorModelId || anthropicDefaultAdvisorModelId,
}
case "bedrock":
return getProviderData(bedrockModels, bedrockDefaultModelId)
case "vertex":